fix: add robust Z.AI config persistence with error handling and logging
Some checks failed
Release Binaries / release (push) Has been cancelled
Some checks failed
Release Binaries / release (push) Has been cancelled
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import { z } from "zod"
|
||||
import { createHmac } from "crypto"
|
||||
|
||||
export const ZAIConfigSchema = z.object({
|
||||
apiKey: z.string().optional(),
|
||||
endpoint: z.string().default("https://api.z.ai/api/coding/paas/v4"),
|
||||
endpoint: z.string().default("https://api.z.ai/api"),
|
||||
enabled: z.boolean().default(false),
|
||||
timeout: z.number().default(300000)
|
||||
})
|
||||
@@ -142,7 +140,8 @@ export class ZAIClient {
|
||||
|
||||
constructor(config: ZAIConfig) {
|
||||
this.config = config
|
||||
this.baseUrl = config.endpoint.replace(/\/$/, "")
|
||||
const trimmed = config.endpoint.replace(/\/$/, "")
|
||||
this.baseUrl = trimmed.replace(/\/(?:api\/coding\/)?paas\/v4$/, "")
|
||||
}
|
||||
|
||||
async testConnection(): Promise<boolean> {
|
||||
@@ -151,7 +150,7 @@ export class ZAIClient {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
const response = await fetch(`${this.baseUrl}/paas/v4/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -182,7 +181,7 @@ export class ZAIClient {
|
||||
throw new Error("Z.AI API key is required")
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
const response = await fetch(`${this.baseUrl}/paas/v4/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -236,7 +235,7 @@ export class ZAIClient {
|
||||
throw new Error("Z.AI API key is required")
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
const response = await fetch(`${this.baseUrl}/paas/v4/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -254,52 +253,9 @@ export class ZAIClient {
|
||||
}
|
||||
|
||||
private getHeaders(): Record<string, string> {
|
||||
const token = this.generateToken(this.config.apiKey!)
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
private generateToken(apiKey: string, expiresIn: number = 3600): string {
|
||||
try {
|
||||
const [id, secret] = apiKey.split(".")
|
||||
if (!id || !secret) return apiKey // Fallback or handle error
|
||||
|
||||
const now = Date.now()
|
||||
const payload = {
|
||||
api_key: id,
|
||||
exp: now + expiresIn * 1000,
|
||||
timestamp: now
|
||||
}
|
||||
|
||||
const header = {
|
||||
alg: "HS256",
|
||||
sign_type: "SIGN"
|
||||
}
|
||||
|
||||
const base64UrlEncode = (obj: any) => {
|
||||
return Buffer.from(JSON.stringify(obj))
|
||||
.toString('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '')
|
||||
}
|
||||
|
||||
const encodedHeader = base64UrlEncode(header)
|
||||
const encodedPayload = base64UrlEncode(payload)
|
||||
|
||||
const signature = createHmac("sha256", secret)
|
||||
.update(`${encodedHeader}.${encodedPayload}`)
|
||||
.digest("base64")
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '')
|
||||
|
||||
return `${encodedHeader}.${encodedPayload}.${signature}`
|
||||
} catch (e) {
|
||||
console.warn("Failed to generate JWT, using raw key", e)
|
||||
return apiKey
|
||||
"Authorization": `Bearer ${this.config.apiKey!}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -661,22 +661,23 @@ async function streamWithZAI(
|
||||
let content = ""
|
||||
const toolCalls: ToolCall[] = []
|
||||
|
||||
const baseUrl = "https://api.z.ai"
|
||||
const baseUrl = "https://api.z.ai/api"
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers["Authorization"] = `Bearer ${accessToken}`
|
||||
if (!accessToken) {
|
||||
throw new Error("Z.AI API key required. Please authenticate with Z.AI first.")
|
||||
}
|
||||
headers["Authorization"] = `Bearer ${accessToken}`
|
||||
|
||||
const response = await fetch(`${baseUrl}/v1/chat/completions`, {
|
||||
const response = await fetch(`${baseUrl}/paas/v4/chat/completions`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: model ?? "z1-mini",
|
||||
model: model ?? "glm-4.7",
|
||||
messages,
|
||||
stream: true,
|
||||
tools: tools.length > 0 ? tools : undefined,
|
||||
|
||||
@@ -350,22 +350,42 @@ async function chatWithToolLoop(
|
||||
|
||||
function getZAIConfig(): ZAIConfig {
|
||||
try {
|
||||
console.log(`[Z.AI] Looking for config at: ${CONFIG_FILE}`)
|
||||
if (existsSync(CONFIG_FILE)) {
|
||||
const data = readFileSync(CONFIG_FILE, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
const parsed = JSON.parse(data)
|
||||
console.log(`[Z.AI] Config loaded from file, enabled: ${parsed.enabled}`)
|
||||
return parsed
|
||||
}
|
||||
console.log(`[Z.AI] Config file not found, using defaults`)
|
||||
return { enabled: false, endpoint: "https://api.z.ai/api/coding/paas/v4", timeout: 300000 }
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error(`[Z.AI] Error reading config:`, error)
|
||||
return { enabled: false, endpoint: "https://api.z.ai/api/coding/paas/v4", timeout: 300000 }
|
||||
}
|
||||
}
|
||||
|
||||
function updateZAIConfig(config: Partial<ZAIConfig>): void {
|
||||
// Ensure directory exists
|
||||
// Ensure directory exists with proper error handling
|
||||
try {
|
||||
if (!existsSync(CONFIG_DIR)) {
|
||||
console.log(`[Z.AI] Creating config directory: ${CONFIG_DIR}`)
|
||||
mkdirSync(CONFIG_DIR, { recursive: true })
|
||||
}
|
||||
} catch (mkdirError) {
|
||||
console.error(`[Z.AI] Failed to create config directory:`, mkdirError)
|
||||
throw new Error(`Failed to create config directory: ${mkdirError}`)
|
||||
}
|
||||
|
||||
const current = getZAIConfig()
|
||||
const updated = { ...current, ...config }
|
||||
writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2))
|
||||
|
||||
try {
|
||||
console.log(`[Z.AI] Writing config to: ${CONFIG_FILE}`)
|
||||
writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), 'utf-8')
|
||||
console.log(`[Z.AI] Config saved successfully`)
|
||||
} catch (writeError) {
|
||||
console.error(`[Z.AI] Failed to write config file:`, writeError)
|
||||
throw new Error(`Failed to write config file: ${writeError}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ const ZAISettings: Component = () => {
|
||||
<label class="block font-medium mb-2">Endpoint</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://api.z.ai/api/coding/paas/v4"
|
||||
placeholder="https://api.z.ai/api"
|
||||
value={config().endpoint || ''}
|
||||
onChange={(e) => handleConfigChange('endpoint', e.target.value)}
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800"
|
||||
|
||||
Reference in New Issue
Block a user