Vercel AI SDK provider that talks to Z.AI / GLM. It reuses ai-sdk-provider-claude-code, rewrites the Anthropic env vars for Z.AI, and wires the published MCP servers.
- Claude Code CLI (
zaiClaudeCode/createZaiClaudeCode): same CLI tools plus Z.AI web-search, reader/WebFetch, and vision servers. Optional guardrails strip Bash/Task when needed. - HTTP (
zaiAnthropic/createZaiAnthropic): Anthropic-compatible endpoint that remaps GLM ids and keeps custom tools deterministic. - Custom tool helper (
forceCustomTools): run your tool manually, inject thetool-call/tool-result, and hand context back to the CLI without letting it call Bash. - Typed configuration: env merges, MCP settings, model remaps, timeouts, and permission defaults are exposed as options.
npm install ai-sdk-zai-provider ai
# ensure ZAI_API_KEY is set in your shell or .envimport { generateText } from 'ai';
import { zaiClaudeCode } from 'ai-sdk-zai-provider';
const result = await generateText({
model: zaiClaudeCode('glm-4.6'),
prompt: 'List three threat-modeling checks we should automate.',
});import { streamText } from 'ai';
import { zaiAnthropic } from 'ai-sdk-zai-provider';
const result = streamText({
model: zaiAnthropic('glm-4.6'),
tools: { /* Anthropic-style tool map */ },
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Summarize the latest GLM release.' },
],
});Pass GLM SKUs (
glm-4.6,glm-4.5-air, etc.). The provider rewrites them to the Claude-compatible fallback id until Z.AI exposes the spec v2 HTTP surface. Override viaglmFallbackModel,ZAI_HTTP_FALLBACK_MODEL, or set it tofalseonce Z.AI supports GLM IDs natively.
import { createZaiClaudeCode } from 'ai-sdk-zai-provider';
const provider = createZaiClaudeCode({
anthropicBaseUrl: 'https://api.z.ai/api/anthropic',
includeDefaultAllowedTools: true,
enableWebSearchMcp: true,
enableWebReaderMcp: true,
enableVisionMcp: true,
customToolsOnly: {
allowedTools: ['echo_tool'],
appendSystemPrompt: 'Do not call Bash/Task.',
},
});
const model = provider('glm-4.6');Every model inherits:
- Z.AI env vars (
ANTHROPIC_*,API_TIMEOUT_MS, per-alias mappings). - Claude Code default tools + optional MCP tool ids (
mcp__web-search-prime__webSearchPrime,mcp__web-reader__webReader,WebFetch,mcp__zai-vision__*). - MCP definitions pointing at the Z.AI endpoints or the stdio
@z_ai/mcp-server. OverridevisionCommandto use a different runner. permissionMode: 'bypassPermissions'unless overridden.
- Emits the same stream structure (
start,text-delta,tool-input-*,tool-call,tool-result,finish). result.usageandresult.providerMetadata['claude-code']stay intact for billing/analytics.- HTTP provider mirrors CLI blocks so swapping surfaces doesn’t require schema changes.
npm test # unit + mocked integration suites (requires ZAI_API_KEY)
npm run test:integration # focus on the integration specCoverage includes env merging, MCP wiring, deterministic custom tools via HTTP, and stream-shape validation. Set ZAI_API_KEY (and optionally ZAI_HTTP_FALLBACK_MODEL if you need a different Claude-compatible fallback). All integration specs run against local fixtures—no flaky external dependencies.
HTTP (recommended)
const tools = {
repo_search: {
description: 'Search GitHub repositories for a keyword.',
parameters: z.object({ query: z.string() }),
execute: async ({ query }) => {
const res = await fetch(`https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&per_page=3`);
const data = await res.json();
return data.items.map((repo) => ({ name: repo.full_name, stars: repo.stargazers_count }));
},
},
};
const result = streamText({
model: zaiAnthropic('glm-4.6'),
tools,
messages: [{ role: 'system', content: 'Use repo_search exactly once before answering.' }],
});Claude Code guardrail mode
import { createZaiClaudeCode, forceCustomTools } from 'ai-sdk-zai-provider';
const provider = createZaiClaudeCode({
customToolsOnly: { allowedTools: ['echo_tool'] },
defaultSettings: { allowedTools: ['echo_tool'] },
});
const tools = {
echo_tool: {
description: 'Echo text back for verification.',
parameters: z.object({ text: z.string() }),
execute: async ({ text }, options) => ({ echoed: text, callId: options.toolCallId }),
},
};
await forceCustomTools({
model: provider('glm-4.6'),
tools,
toolName: 'echo_tool',
toolInput: { text: 'custom_check' },
messages: [{ role: 'user', content: 'Confirm the custom tool output.' }],
});forceCustomTools runs your tool once, injects the tool-call/tool-result, and hands control back to the CLI model with Bash/Task disabled.
MIT © 2025