docs/zai/implementation.md
This document describes the z.ai integration that is implemented on the feat/zai-passthrough-mcp branch: what was added, how it works internally, and how to validate it.
Related deep dives:
docs/zai/provider.mddocs/zai/mcp.mddocs/zai/vision-mcp.mddocs/proxy/auth.mddocs/proxy/accounts.md/v1/messages, /v1/messages/count_tokens).All settings are persisted in the existing data directory (same place as Google accounts and gui_config.json).
proxy.auth_mode (off | strict | all_except_health | auto)
off: no auth requiredstrict: auth required for all routesall_except_health: auth required for all routes except GET /healthzauto: if allow_lan_access=true -> all_except_health, else offproxy.api_key: required when auth is enabledImplementation:
src-tauri/src/proxy/config.rs (ProxyAuthMode)src-tauri/src/proxy/security.rssrc-tauri/src/proxy/middleware/auth.rsConfig lives under proxy.zai (src-tauri/src/proxy/config.rs):
enabled: boolbase_url: string (default https://api.z.ai/api/anthropic)api_key: stringdispatch_mode: off | exclusive | pooled | fallback
off: never use z.aiexclusive: all Claude protocol requests go to z.aipooled: z.ai is treated as one additional slot in the shared pool (no priority, no strict guarantee)fallback: z.ai is used only when the Google pool has 0 accountsmodels: defaults used when the incoming Anthropic request uses claude-* model ids
opus default glm-4.7sonnet default glm-4.7haiku default glm-4.5-airmodel_mapping: optional exact-match overrides ({ "<incoming_model>": "<glm-model-id>" })
model string, it is replaced with the mapped z.ai model id before forwarding upstream.mcp toggles:
enabledweb_search_enabledweb_reader_enabledvision_enabledRuntime hot update:
save_config hot-updates auth, upstream_proxy, model mappings, and z.ai without restart.
src-tauri/src/commands/mod.rs calls axum_server.update_security(...) and axum_server.update_zai(...)./v1/messages (Anthropic messages)Handler: src-tauri/src/proxy/handlers/claude.rs (handle_messages)
Flow:
HeaderMap + raw JSON Value.dispatch_mode=exclusive -> use z.ai.dispatch_mode=fallback -> use z.ai only if Google pool size is 0.dispatch_mode=pooled -> use round-robin across (google_accounts + 1) slots; slot 0 is z.ai, others are Google.model may be rewritten:
proxy.zai.model_mapping contains an exact match, that mapping winsglm-* stays unchangedclaude-* becomes one of proxy.zai.models.{opus,sonnet,haiku} based on name match/v1/messages/count_tokensHandler: src-tauri/src/proxy/handlers/claude.rs (handle_count_tokens)
{input_tokens: 0, output_tokens: 0}.Provider: src-tauri/src/proxy/providers/zai_anthropic.rs
Security / header handling:
content-type, accept, anthropic-version, user-agent).x-api-key, it is replaced with z.ai key.Authorization, it is replaced with Bearer <zai_key>.x-api-key: <zai_key> is used.Networking:
proxy.upstream_proxy) for outbound HTTP calls.Handlers: src-tauri/src/proxy/handlers/mcp.rs
Routes: src-tauri/src/proxy/server.rs
Local endpoints:
/mcp/web_search_prime/mcp → https://api.z.ai/api/mcp/web_search_prime/mcp/mcp/web_reader/mcp → https://api.z.ai/api/mcp/web_reader/mcpBehavior:
proxy.zai.mcp.* flags:
mcp.enabled=false -> endpoints return 404.Authorization: Bearer <zai_key>.Note:
proxy.auth_mode.Handlers:
src-tauri/src/proxy/handlers/mcp.rs (handle_zai_mcp_server)src-tauri/src/proxy/zai_vision_tools.rs (tool registry + z.ai vision API client)Local endpoint:
/mcp/zai-mcp-server/mcpBehavior:
proxy.zai.mcp.enabled and proxy.zai.mcp.vision_enabled.
mcp.enabled=false -> returns 404.vision_enabled=false -> returns 404.proxy.zai.api_key when calling the z.ai vision API.POST /mcp supports initialize, tools/list, tools/callGET /mcp returns an SSE stream with keep-alive events for an initialized sessionDELETE /mcp terminates a sessionUpstream calls:
https://api.z.ai/api/paas/v4/chat/completionsAuthorization: Bearer <zai_key>glm-4.6v (hardcoded for now)Tool input and limits:
.png, .jpg, .jpeg up to 5 MB (local files are encoded as data:<mime>;base64,...)..mp4, .mov, .m4v up to 8 MB.ui_to_artifactextract_text_from_screenshotdiagnose_error_screenshotunderstand_technical_diagramanalyze_data_visualizationui_diff_checkanalyze_imageanalyze_videoPage: src/pages/ApiProxy.tsx
Added controls:
off/strict/all_except_health/auto)GET <base_url>/v1/models)opus/sonnet/haiku mappingTranslations:
src/locales/en.jsonsrc/locales/zh.jsonBuild:
npm run buildcd src-tauri && cargo buildManual (example):
proxy.api_key.dispatch_mode=exclusiveapi_key=<your_z.ai.key>GET http://127.0.0.1:<port>/healthz (should work without auth in all-except-health; always works in off)POST http://127.0.0.1:<port>/v1/messages with Authorization: Bearer <proxy.api_key> and a normal Anthropic request body./mcp/web_search_prime/mcp via an MCP client (the proxy injects z.ai auth upstream).POST http://127.0.0.1:<port>/mcp/zai-mcp-server/mcp with a JSON-RPC initializePOST ... with tools/list using the returned Mcp-Session-Id header./v1/models/claude) and is not yet provider-aware.