fix: add robust Z.AI config persistence with error handling and logging
Some checks failed
Release Binaries / release (push) Has been cancelled

This commit is contained in:
Gemini AI
2025-12-29 00:13:55 +04:00
Unverified
parent 2ac7eb12ce
commit 229f86c229
4 changed files with 41 additions and 64 deletions

View File

@@ -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!}`
}
}

View File

@@ -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,

View File

@@ -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}`)
}
}

View File

@@ -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"