v0.5.0: Binary-Free Mode - No OpenCode binary required
✨ Major Features: - Native session management without OpenCode binary - Provider routing: OpenCode Zen (free), Qwen OAuth, Z.AI - Streaming chat with tool execution loop - Mode detection API (/api/meta/mode) - MCP integration fix (resolved infinite loading) - NomadArch Native option in UI with comparison info 🆓 Free Models (No API Key): - GPT-5 Nano (400K context) - Grok Code Fast 1 (256K context) - GLM-4.7 (205K context) - Doubao Seed Code (256K context) - Big Pickle (200K context) 📦 New Files: - session-store.ts: Native session persistence - native-sessions.ts: REST API for sessions - lite-mode.ts: UI mode detection client - native-sessions.ts (UI): SolidJS store 🔧 Updated: - All installers: Optional binary download - All launchers: Mode detection display - Binary selector: Added NomadArch Native option - README: Binary-Free Mode documentation
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
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/paas/v4"),
|
||||
endpoint: z.string().default("https://api.z.ai/api/coding/paas/v4"),
|
||||
enabled: z.boolean().default(false),
|
||||
timeout: z.number().default(300000)
|
||||
})
|
||||
@@ -10,18 +11,55 @@ export const ZAIConfigSchema = z.object({
|
||||
export type ZAIConfig = z.infer<typeof ZAIConfigSchema>
|
||||
|
||||
export const ZAIMessageSchema = z.object({
|
||||
role: z.enum(["user", "assistant", "system"]),
|
||||
content: z.string()
|
||||
role: z.enum(["user", "assistant", "system", "tool"]),
|
||||
content: z.string().optional(),
|
||||
tool_calls: z.array(z.object({
|
||||
id: z.string(),
|
||||
type: z.literal("function"),
|
||||
function: z.object({
|
||||
name: z.string(),
|
||||
arguments: z.string()
|
||||
})
|
||||
})).optional(),
|
||||
tool_call_id: z.string().optional()
|
||||
})
|
||||
|
||||
export type ZAIMessage = z.infer<typeof ZAIMessageSchema>
|
||||
|
||||
// Tool Definition Schema (OpenAI-compatible)
|
||||
export const ZAIToolSchema = z.object({
|
||||
type: z.literal("function"),
|
||||
function: z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
parameters: z.object({
|
||||
type: z.literal("object"),
|
||||
properties: z.record(z.object({
|
||||
type: z.string(),
|
||||
description: z.string().optional()
|
||||
})),
|
||||
required: z.array(z.string()).optional()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export type ZAITool = z.infer<typeof ZAIToolSchema>
|
||||
|
||||
export const ZAIChatRequestSchema = z.object({
|
||||
model: z.string().default("glm-4.7"),
|
||||
messages: z.array(ZAIMessageSchema),
|
||||
max_tokens: z.number().default(8192),
|
||||
stream: z.boolean().default(true),
|
||||
temperature: z.number().optional(),
|
||||
tools: z.array(ZAIToolSchema).optional(),
|
||||
tool_choice: z.union([
|
||||
z.literal("auto"),
|
||||
z.literal("none"),
|
||||
z.object({
|
||||
type: z.literal("function"),
|
||||
function: z.object({ name: z.string() })
|
||||
})
|
||||
]).optional(),
|
||||
thinking: z.object({
|
||||
type: z.enum(["enabled", "disabled"]).optional()
|
||||
}).optional()
|
||||
@@ -38,8 +76,16 @@ export const ZAIChatResponseSchema = z.object({
|
||||
index: z.number(),
|
||||
message: z.object({
|
||||
role: z.string(),
|
||||
content: z.string().optional(),
|
||||
reasoning_content: z.string().optional()
|
||||
content: z.string().optional().nullable(),
|
||||
reasoning_content: z.string().optional(),
|
||||
tool_calls: z.array(z.object({
|
||||
id: z.string(),
|
||||
type: z.literal("function"),
|
||||
function: z.object({
|
||||
name: z.string(),
|
||||
arguments: z.string()
|
||||
})
|
||||
})).optional()
|
||||
}),
|
||||
finish_reason: z.string()
|
||||
})),
|
||||
@@ -61,8 +107,17 @@ export const ZAIStreamChunkSchema = z.object({
|
||||
index: z.number(),
|
||||
delta: z.object({
|
||||
role: z.string().optional(),
|
||||
content: z.string().optional(),
|
||||
reasoning_content: z.string().optional()
|
||||
content: z.string().optional().nullable(),
|
||||
reasoning_content: z.string().optional(),
|
||||
tool_calls: z.array(z.object({
|
||||
index: z.number().optional(),
|
||||
id: z.string().optional(),
|
||||
type: z.literal("function").optional(),
|
||||
function: z.object({
|
||||
name: z.string().optional(),
|
||||
arguments: z.string().optional()
|
||||
}).optional()
|
||||
})).optional()
|
||||
}),
|
||||
finish_reason: z.string().nullable().optional()
|
||||
}))
|
||||
@@ -106,7 +161,12 @@ export class ZAIClient {
|
||||
})
|
||||
})
|
||||
|
||||
return response.status !== 401 && response.status !== 403
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
console.error(`Z.AI connection failed (${response.status}): ${text}`)
|
||||
}
|
||||
|
||||
return response.ok
|
||||
} catch (error) {
|
||||
console.error("Z.AI connection test failed:", error)
|
||||
return false
|
||||
@@ -194,9 +254,52 @@ export class ZAIClient {
|
||||
}
|
||||
|
||||
private getHeaders(): Record<string, string> {
|
||||
const token = this.generateToken(this.config.apiKey!)
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${this.config.apiKey}`
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user