From 229f86c229c2e2a0d6f029c822d463de8c3ad235 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Mon, 29 Dec 2025 00:13:55 +0400 Subject: [PATCH] fix: add robust Z.AI config persistence with error handling and logging --- packages/server/src/integrations/zai-api.ts | 60 +++---------------- .../src/server/routes/native-sessions.ts | 11 ++-- packages/server/src/server/routes/zai.ts | 32 ++++++++-- .../src/components/settings/ZAISettings.tsx | 2 +- 4 files changed, 41 insertions(+), 64 deletions(-) diff --git a/packages/server/src/integrations/zai-api.ts b/packages/server/src/integrations/zai-api.ts index b29ac5c..a2724f2 100644 --- a/packages/server/src/integrations/zai-api.ts +++ b/packages/server/src/integrations/zai-api.ts @@ -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 { @@ -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,56 +253,13 @@ export class ZAIClient { } private getHeaders(): Record { - 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!}` } } static validateApiKey(apiKey: string): boolean { return typeof apiKey === "string" && apiKey.length > 0 } -} \ No newline at end of file +} diff --git a/packages/server/src/server/routes/native-sessions.ts b/packages/server/src/server/routes/native-sessions.ts index 1dc1109..38f9046 100644 --- a/packages/server/src/server/routes/native-sessions.ts +++ b/packages/server/src/server/routes/native-sessions.ts @@ -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 = { "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, diff --git a/packages/server/src/server/routes/zai.ts b/packages/server/src/server/routes/zai.ts index 4632db2..94402c7 100644 --- a/packages/server/src/server/routes/zai.ts +++ b/packages/server/src/server/routes/zai.ts @@ -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): void { - // Ensure directory exists - if (!existsSync(CONFIG_DIR)) { - mkdirSync(CONFIG_DIR, { recursive: true }) + // 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}`) + } } diff --git a/packages/ui/src/components/settings/ZAISettings.tsx b/packages/ui/src/components/settings/ZAISettings.tsx index ba42942..00b4795 100644 --- a/packages/ui/src/components/settings/ZAISettings.tsx +++ b/packages/ui/src/components/settings/ZAISettings.tsx @@ -186,7 +186,7 @@ const ZAISettings: Component = () => { 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"