An Agent Client Protocol (ACP) agent written in TypeScript that uses the Z.AI / Zhipu AI GLM model family (GLM-5.3, GLM-5.3 Flash, GLM-5 Turbo, GLM-4.7) as its reasoning core.
The agent connects to any ACP-compatible IDE or client over stdio, streams responses back in real time, and can call a rich set of tools to interact with the user's file system, terminal, and the web.
Streaming responses must include a supported terminal finish reason. An early connection close is reported as an interrupted response, with received text retained for session replay. Tool calls run only after a complete tool_calls response; calls in output-limit (length) or filtered (content_filter) responses are discarded and the corresponding stop reason is preserved.
Completed provider reasoning is retained unchanged in conversation history and saved sessions for subsequent model calls, even when thought display is disabled. ACP_GLM_STREAM_THINKING=false controls client display only. Reasoning from cancelled, incomplete, or output-limited responses is not replayed as a completed reasoning chain.
glm-acp-agent is intentionally built for the Z.AI GLM Coding Plan. It is not a general-purpose Z.AI Open Platform API client.
By default, model calls use the Coding Plan endpoint:
https://api.z.ai/api/coding/paas/v4
The same Coding Plan API key is used for the agent's GLM model calls and supported Coding Plan tools. General Z.AI API/resource-package billing surfaces, such as direct /api/paas/v4 Tool API calls, are intentionally out of scope for this ACP agent.
Built-in web tools use Coding Plan-compatible MCP endpoints, not the general /api/paas/v4 Tool API. If you need general Z.AI API billing, separate resource packages, or non-Coding Plan endpoints, use a different provider configuration or fork this agent for that purpose.
- Full ACP compliance – implements
initialize,authenticate,session/new,session/set_mode,session/prompt,session/cancel,session/close,session/list,session/load,session/fork,session/resume, andsession/set_model, and pushessession_info_update,config_option_update,current_mode_update, andavailable_commands_updatenotifications - Streaming – assistant text and reasoning tokens are forwarded as incremental ACP chunks
- Tool calling – agentic loop with a configurable cap of GLM function-calling turns (default 100; see
ACP_GLM_MAX_TURNS/--max-turns) - Thinking mode – GLM's
reasoning_contenttokens are surfaced asagent_thought_chunkblocks so the client can show the model's chain of thought - Session permission modes – supports
default,accept_edits, andbypass_permissionsviasession/set_mode. Clients like DevFlow can use this to toggle between prompting for every edit, auto-approving edits while prompting for commands, or bypassing permissions entirely. - Per-session model switching –
session/set_modellets clients change the active GLM model mid-conversation;session/newreturns the curatedavailableModelslist - Slash commands – commands and skills found under the session's
.claude/directory are advertised to the client withavailable_commands_update, so/autocomplete is populated (see Slash commands) - Image input via Coding Plan-native vision or Vision MCP –
promptCapabilities.imageis advertised;glm-5.3-flashsends supported pasted ACP image blocks directly as nativeimage_urlcontent parts, while the other advertised coding models (including defaultglm-5.3) route them through Z.AI Vision MCP (@z_ai/mcp-server).glm-5v-turbokeeps the same native-vision path when re-added viaACP_GLM_AVAILABLE_MODELS— it is no longer on the Coding Plan allowlist. Direct chat-image-only models (e.g.glm-4v-plus) are intentionally not used. - Session persistence – conversations are written to
~/.local/state/glm-acp-agent/sessions/and can be reloaded viasession/load, branched viasession/fork, or resumed without replay viasession/resume - Nine built-in tools (see below)
- Self-sufficient local tools – directory listings and shell commands always run in the agent process; file tools fall back to local filesystem access when ACP client
fscapabilities are unavailable - Configurable permissions –
write_fileandrun_commandbehavior depends on the active session mode (prompts by default) - Protocol-correct stop reasons – maps model and runtime conditions to ACP
end_turn,max_tokens,max_turn_requests,refusal, andcancelled - Protocol-correct tool statuses –
pending→in_progress→completed/failed - Token usage reporting – aggregated usage is returned on the
session/promptresponse
ACP Client (IDE plugin, CLI, …)
│ stdio (ndjson)
▼
GlmAcpAgent ← ACP protocol layer (src/protocol/)
│
├─ GlmClient ← Z.AI / Zhipu AI Coding Plan Chat Completions (src/llm/)
│
├─ ToolExecutor ← executes tool calls (src/tools/)
│ ├─ read_file / list_files → ACP client fs for read_file when read/write are advertised, else Agent process (Node fs); read_file paginated (offset/limit)
│ ├─ write_file / edit_file → ACP client fs when advertised (editor-buffer diffs), else Agent process (Node fs)
│ ├─ list_files / run_command → Agent process (Node fs / child_process)
│ ├─ web_search / web_reader → Z.AI Coding Plan Web MCP (HTTP)
│ ├─ image_analysis → Z.AI Coding Plan Vision MCP (stdio)
│ └─ todowrite → Agent process (per-session task list; replaces chat narration)
│
└─ VisionMcpClient ← spawns `npx @z_ai/mcp-server` on demand
The agent process needs network access to api.z.ai for chat completions and Web MCP, plus npx available on PATH so it can launch @z_ai/mcp-server for vision. Filesystem and shell operations use paths resolved against the ACP session working directory. When the client advertises fs.writeTextFile, writes and edits are routed through the ACP client so they render as native diffs. When it advertises both fs.readTextFile and fs.writeTextFile, read_file and edit-file reads also use the editor buffer; otherwise those reads fall back to the agent process filesystem. Writes and arbitrary shell commands still go through ACP session/request_permission, and the permission payload is always the full tool arguments — the user approves exactly what will run.
Client-facing tool cards stay compact: long strings in rawInput/rawOutput are elided to a short head plus a character count, and every completed or failed card's visible text — including the read_file content preview — is bounded to an aggregate 16 KiB UTF-8 budget; oversized previews end with [… N bytes elided in preview; full result sent to model] and card titles are elided to 240 characters. The model still receives the full tool result, capped at 256 KiB by ACP_GLM_TOOL_RESULT_LIMIT_BYTES; write payloads and permission requests remain complete. Progress narration lives in the todowrite task list rather than prose, and reasoning tokens are only forwarded as agent_thought_chunk when ACP_GLM_STREAM_THINKING is not false (the default preserves streaming).
All MCP transports share an 8 MiB response cap (MCP_RESPONSE_LIMIT_BYTES): HTTP response bodies are read incrementally and a larger body cancels the read and fails the tool call, while an oversized or newline-less stdio JSON-RPC frame fails the connection, rejecting pending requests and terminating the child process. The cap applies to client-provided servers and to the built-in Web MCP and Vision MCP clients.
ACP clients may attach MCP server definitions when creating or loading a session. The agent supports client-configured stdio and HTTP MCP servers. SSE is unsupported and rejected when the agent tries to connect. In the ACP SDK capability schema, mcpCapabilities.http advertises HTTP support and mcpCapabilities.sse advertises SSE support. This agent advertises only http: true and does not advertise SSE; the capability object has no stdio flag.
A stdio MCP child starts with a copy of the agent process environment, then the server's configured environment entries override matching inherited values. This can pass credentials such as Z_AI_API_KEY to that child. Review client-supplied MCP commands and environment values as trusted code/configuration.
Before provider requests, the agent estimates context use with a heuristic rather than the provider's tokenizer. When history exceeds the model budget, it can remove older complete exchanges and shorten tool results; the system context and latest user request are retained. If those, the tool schemas, and reserved output/safety space still exceed the model's context window, the request fails with guidance to narrow it or choose a larger-context model. Earlier conversation details may therefore not remain verbatim in model context.
The agent does not run tools inside an OS or container sandbox. Shell commands and local filesystem fallbacks run with the agent process's OS-user permissions; shell children inherit its environment, and commands can access the network available to that user. ACP permission prompts and session modes govern consent behavior but do not provide process isolation. Treat workspaces, prompts, and MCP configurations as trusted, and use external OS/container isolation when you need a stronger boundary.
| Tool | Runs on | Permission behavior | Description |
|---|---|---|---|
read_file |
ACP client when both fs read/write capabilities are advertised, otherwise agent process | Always silent | Read a text file or editor buffer, paginated by offset/limit (default 2000 lines, capped at 5000). Local scans are byte-bounded; totals can be unknown and an incomplete line is never given a next offset. |
write_file |
Agent process (ACP client fs when advertised) |
Mode-dependent | Write or overwrite a text file. Silent in accept_edits and bypass_permissions. |
edit_file |
Agent process (ACP client fs when advertised) |
Mode-dependent | Replace one exact, unique snippet in an existing file. It refuses an input or editor buffer over the read/edit budget, and re-validates after permission and progress notification delivery to preserve changes present at the final read. Silent in accept_edits and bypass_permissions. |
todowrite |
Agent process | Always silent | Create or replace the session's structured task list so multi-step progress is tracked instead of narrated in chat. Each call replaces the list; the tool result renders it back to the model. |
list_files |
Agent process | Always silent | List a directory through a bounded iterator; a truncated result is a disclosed subset. |
run_command |
Agent process | Mode-dependent | Run an arbitrary shell command; cancelling a turn terminates its shell process group, while intentionally backgrounded processes survive a normal shell exit. Silent only in bypass_permissions. |
web_search |
Agent (Z.AI Coding Plan MCP) | Always silent | Search the web — returns titles, URLs, and summaries |
web_reader |
Agent (Z.AI Coding Plan MCP) | Always silent | Fetch and parse a web page (markdown or plain text) |
image_analysis |
Agent (Z.AI Vision MCP, stdio) | Always silent | Analyze a local image path or remote URL using @z_ai/mcp-server |
Editor edits use an optimistic read followed by a full-buffer write. ACP's fs/write_text_file request has no version precondition, so edits made during that write RPC can still race with the replacement. The final read preserves unrelated changes already present and rejects a target snippet that has disappeared or become ambiguous.
Clients can use session/set_mode to drive the permission policy:
| Mode ID | Name | write_file |
run_command |
|---|---|---|---|
default |
Ask for permission | Prompt | Prompt |
accept_edits |
Auto-approve edits | Silent | Prompt |
bypass_permissions |
Bypass all permissions | Silent | Silent |
Reads, listings, and MCP tool calls are always silent across all modes.
Permission prompts always carry the full, unelided tool arguments, so the user approves exactly what will run. The prompt offers exactly allow and reject, and a tool runs only when the response selects allow with outcome selected; anything else — a missing or unrecognised option id, an unknown outcome string, or a null/absent response — is treated as reject. An explicit cancelled outcome remains distinct from rejection.
After session/new, session/load, session/fork, and session/resume, the agent
sends an ACP available_commands_update
notification. Clients such as Zed and Paseo use it to populate their / autocomplete.
Each notification is a full snapshot that replaces the previous list.
The snapshot is built from the same on-disk layout Claude Code uses, read from the
session's working directory and from ~/.claude for user-level definitions:
| Path | Command name |
|---|---|
<cwd>/.claude/commands/commit.md |
/commit |
<cwd>/.claude/commands/review/pr.md |
/review:pr |
<cwd>/.claude/skills/audit/SKILL.md |
/audit |
Project definitions shadow user-level ones with the same name. The description
and argument-hint keys of a file's YAML frontmatter become the menu entry's
description and input hint; without a description, the first heading (then the
first line) of the body is used instead.
Advertised names are sent without a leading slash — the client prepends it for
display and invokes the command by sending /name … as ordinary prompt text. When
that text names an advertised command, the agent injects the definition's body into
the user message, substituting $ARGUMENTS where the file asks for it and otherwise
appending the typed arguments. Unknown /foo is left untouched and reaches the model
as prose, so typing a slash by accident never fails the turn.
- Node.js 20.19.0+, 22.13.0+, or 24+ (native
fetchand Web Streams required) - npm 9 or later
- A Z.AI API key — obtain one at https://z.ai/manage-apikey/apikey-list
Install the published package globally to get the glm-acp-agent command on your PATH:
npm install -g glm-acp-agent@latestThen either export your Z.AI API key and run it directly:
export Z_AI_API_KEY=your_key_here
glm-acp-agent…or run the interactive setup once to persist the key to disk (see One-time setup) and point any ACP-compatible client at the glm-acp-agent command.
Clone the repository if you want to hack on the agent or pin a specific commit:
git clone https://github.com/stefandevo/glm-acp-agent.git
cd glm-acp-agent
npm install
npm run buildThe build output lands in dist/; the rest of this README uses node dist/index.js whenever it refers to the source-build entry point.
The agent reads its configuration from environment variables, plus an optional credentials file written by glm-acp-agent --setup.
| Variable | Required | Default | Description |
|---|---|---|---|
Z_AI_API_KEY |
One of env / --setup |
— | API key for the Z.AI / Zhipu AI service. If unset, the credentials file is consulted. |
ACP_GLM_MODEL |
No | glm-5.3 |
Default GLM model for new sessions |
ACP_GLM_AVAILABLE_MODELS |
No | built-in list | Comma-separated list of model ids advertised in session/set_model |
ACP_GLM_BASE_URL |
No | https://api.z.ai/api/coding/paas/v4 |
Override the API base URL |
ACP_GLM_MAX_TOKENS |
No | 32768 |
Cap on max_tokens for each completion. Invalid values fall back to the default with a stderr warning. |
ACP_GLM_MAX_TURNS |
No | 100 |
Max model/tool turns per prompt (also settable via --max-turns) |
ACP_GLM_COMMAND_TIMEOUT_MS |
No | 120000 |
Deadline for each run_command, in milliseconds. Invalid values fall back to the default with a stderr warning. |
ACP_GLM_COMMAND_OUTPUT_LIMIT_BYTES |
No | 65536 |
Maximum combined bytes captured from each run_command stdout and stderr. Further output is drained and reported as truncated. Invalid values fall back to the default with a stderr warning. |
ACP_GLM_TOOL_RESULT_LIMIT_BYTES |
No | 262144 |
Inclusive UTF-8 byte limit for every model-facing tool result. It preserves a prefix and suffix with an omitted-bytes marker; values below 128 fall back with a stderr warning. |
ACP_GLM_READ_FILE_LIMIT_BYTES |
No | 8388608 |
Maximum local bytes consumed while reading a page or whole file for edit_file. A bounded scan may not know the total line count. |
ACP_GLM_LIST_FILES_MAX_ENTRIES |
No | 2000 |
Maximum entries collected by list_files; larger directories return a disclosed subset. |
ACP_GLM_LIST_FILES_LIMIT_BYTES |
No | 262144 |
Maximum bytes assembled for a list_files result before its truncation marker. |
ACP_GLM_THINKING |
No | auto-detected | Force thinking mode true / false |
ACP_GLM_STREAM_THINKING |
No | true |
Forward reasoning tokens to the client as agent_thought_chunk; set false to keep reasoning off the wire (the model still thinks — only the client-side stream is silenced) |
ACP_GLM_STREAM_IDLE_TIMEOUT_MS |
No | 300000 |
Idle deadline in milliseconds for a stalled model-provider stream read; invalid values fall back to the default with a stderr warning. |
ACP_GLM_SESSION_DIR |
No | $XDG_STATE_HOME/glm-acp-agent/sessions |
Where session JSON files are persisted |
ACP_GLM_DEBUG |
No | — | Set to true or 1 to enable verbose debug logging to stderr (shows model selection, API key resolution, tool calls, and usage stats) |
XDG_CONFIG_HOME |
No | ~/.config |
Where the credentials file is read/written |
If you'd rather not pass Z_AI_API_KEY through your ACP client's environment block, run the interactive setup once and the agent will read the key from disk on subsequent launches:
glm-acp-agent --setupIf you're working from a source clone instead of the published package, use the build output directly:
node dist/index.js --setupThe key is written to $XDG_CONFIG_HOME/glm-acp-agent/credentials.json (default: ~/.config/glm-acp-agent/credentials.json) with 0600 permissions. The Z_AI_API_KEY environment variable, when set, always wins over the file.
The agent advertises only the models on the current Z.AI Coding Plan allowlist:
| Model | Notes |
|---|---|
glm-5.3 |
Default. Newest 1M-context coding model; thinking mode always on; text-only (images go through Vision MCP) |
glm-5.3-flash |
Native-vision 1M-context model; cheaper Coding Plan quota |
glm-5-turbo |
Faster Coding Plan reasoning model; 128K context |
glm-4.7 |
200K-context reasoning model |
Only models the Coding Plan endpoint serves under their own name are advertised. Several ids that used to be listed are now aliases: a request for glm-5.2 or glm-5.1 comes back with "model": "glm-5.3", and glm-4.5-air comes back as glm-4.7. They still work, but advertising them would report a model you are not actually talking to. Z.AI's Coding Plan overview currently also claims glm-5-turbo and glm-4.7 requests are routed to GLM-5.3-Flash; that has not been confirmed by a live probe here, so both ids stay in the picker. glm-5v-turbo is no longer on the Coding Plan allowlist — selecting it fails with business code 1311 ("subscription plan does not yet include access").
ACP_GLM_AVAILABLE_MODELS lets you advertise any id you like, including the ones above. Custom ids sit outside the supported Coding Plan list — the endpoint rejects any model code Z.AI hasn't whitelisted (business code 1211). The native-vision path is unchanged, so if your plan does include glm-5v-turbo, add it back with ACP_GLM_AVAILABLE_MODELS and image blocks still reach it as native image_url content parts.
Vision-only chat models (glm-4v-plus etc.) are not advertised. glm-5.3-flash is the built-in native-vision Coding Plan model (image_url content parts). Default glm-5.3, glm-5-turbo, and glm-4.7 still use the Vision MCP path. glm-5v-turbo, when re-added via the override above, keeps native image_url handling as an opt-in exception.
When the model name matches glm-4.5, glm-4.6, glm-4.7, or the glm-5 family, the agent enables Z.AI's thinking: { type: "enabled" } extension and forwards reasoning tokens to the client as agent_thought_chunk blocks. This includes glm-5.3-flash and glm-5v-turbo. ACP_GLM_THINKING=false asks for plain completions on models that still accept thinking.type: "disabled" (GLM-4.7, GLM-5 Turbo). On GLM-5.3 and GLM-5.3-Flash the flag is a no-op: those models reject disabled, so the agent leaves thinking enabled rather than sending it (see Thought level below).
The provider stream itself is guarded against stalls. The Z.AI SDK's HTTP timeout only covers waiting for response headers — a response whose body stops mid-stream would otherwise hang the turn forever. The agent therefore aborts a provider stream read that produces no data for ACP_GLM_STREAM_IDLE_TIMEOUT_MS milliseconds (default 300000; invalid values for the variable fall back to the default with a stderr warning). On timeout, whatever partial output already reached the user is kept, an [error] message is appended as an agent message, the turn ends, and the last valid on-disk session checkpoint is preserved. A slow ACP client back-pressuring chunks it already received does not trip the watchdog — only silence from the provider between reads does.
The agent advertises a thought_level SessionConfigOption so clients that support config options (e.g. a "Thinking" selector) can control reasoning effort per session. The available levels depend on the active model:
| Model | Levels | Mapping to the Z.AI request |
|---|---|---|
glm-5.3 (and glm-5.2 / glm-5.1, which route to 5.3) |
Minimal / Low / Medium / High / X-High / Max |
thinking: { type: "enabled" } + reasoning_effort set to the matching value |
glm-5.3-flash |
Low / High / Max |
Same mapping; Z.AI only documents these three levels for Flash |
| other thinking-capable models | Off / On |
Off → thinking: { type: "disabled" }; On → thinking: { type: "enabled" } |
GLM-5.3 and GLM-5.3-Flash have no Off level, because thinking cannot be turned off on them. Sending thinking: { type: "disabled" } now errors (it is no longer a silent no-op), so ACP_GLM_THINKING=false leaves thinking enabled rather than sending disabled. Use glm-4.7 if you need non-reasoning completions — with the caveat that if Coding Plan really routes turbo/4.7 to Flash, a session that picks glm-4.7 + thought level Off may 4xx.
reasoning_effort only takes effect on the GLM-5.3 family; glm-5-turbo and glm-4.7 accept the field without validating it and answer identically either way, so the agent omits it for them. New sessions default to Max, which is also Z.AI's default when the field is omitted, so out-of-the-box behaviour is unchanged. Switching models re-clamps the level (an effort-ladder selection reverts to On when you move to GLM-4.7; an Off selection becomes Max on GLM-5.3 / Flash; switching from GLM-5.3 onto Flash maps minimal→low, medium→high, xhigh→max) and pushes a config_option_update. Clients that don't support config options simply ignore the advertised option and get the default behaviour.
The endpoint validates reasoning_effort against none | minimal | low | medium | high | xhigh | max on GLM-5.3. GLM-5.3-Flash's documented values are only low | high | max, so the agent advertises that shorter ladder for Flash. none is never offered on either model.
ACP_GLM_PROMPT_IMAGES=false still hides the image-attachment capability at session startup. With that flag set, clients should not offer image attachments at all.
Switching to a text-only model is rejected while retained conversation history contains native images. Keep an image-capable model or start a text-only session with a textual description; images are never silently discarded or analyzed as part of a model switch. During an active prompt on a native-image model, wait for the turn to finish before switching to text-only capability. Other compatible selections affect subsequent model calls, while an in-flight call keeps its captured model and reasoning level. Restored histories receive the same compatibility check before a provider request.
For glm-5.3-flash (built-in) and glm-5v-turbo (opt-in via ACP_GLM_AVAILABLE_MODELS), pasted ACP image blocks with image/jpeg, image/jpg, or image/png are sent directly to chat completions as image_url content parts. HTTPS image URLs are forwarded as URLs; inline base64 data is sent as a data:<mime>;base64,... URI. Unsupported image MIME types are rejected client-side with an inline <image_unsupported_format> annotation so the prompt can continue without a provider 4xx.
For non-native models (including default glm-5.3), pasted ACP image blocks are not sent to the chat-completions endpoint. Instead, the agent boots @z_ai/mcp-server over stdio (via npx -y @z_ai/mcp-server@latest) and calls its image_analysis tool. The text result is spliced into the user message as <image_analysis index="N">…</image_analysis> so the regular Coding Plan model can reason about it.
Prerequisites:
npxonPATH(Node 18+ / npm 9+).- The same
Z_AI_API_KEYused for chat completions; the agent forwards it to the MCP server asZ_AI_API_KEYplusZ_AI_MODE=ZAI.
The model can also call image_analysis explicitly with { image_source: "/path/or/url", prompt?: "…" }. Vision failures (missing npx, MCP startup, quota) are surfaced as actionable errors but never abort the prompt; an inline <image_analysis_error> annotation is used so the conversation can continue.
If you installed the published package globally, just run the CLI:
export Z_AI_API_KEY=your_key_here
glm-acp-agentIf you built from source, invoke the entry point directly:
export Z_AI_API_KEY=your_key_here
node dist/index.jsThe agent speaks the ACP newline-delimited JSON protocol over stdin/stdout. You can connect any ACP-compatible client to it.
When working from a source clone, run tsc in watch mode so dist/ rebuilds on every save:
export Z_AI_API_KEY=your_key_here
npm run dev # tsc --watchZed is currently the most polished editor for trying out ACP agents locally — it spawns the agent process over stdio and surfaces it in the agent panel. This is the fastest way to iterate on glm-acp-agent before publishing it to the ACP registry or to npm.
- A recent build of Zed that supports the
agent_serverssetting - Node.js 20.19.0+, 22.13.0+, or 24+ on your
PATH(node --version) - A Z.AI API key — create one at https://z.ai/manage-apikey/apikey-list
Follow either path from the Installation section above:
-
Quick Start —
npm install -g glm-acp-agent@latest. Zed can then spawn the agent with"command": "glm-acp-agent". -
From source — clone,
npm install,npm run build. Zed needs the absolute path to the built entry point (no~, no$HOMEshortcuts):echo "$(pwd)/dist/index.js"
Open (or create) ~/.config/zed/settings.json and add an agent_servers entry. Pick one of the two API-key strategies below.
Option A — inline env block (simplest):
{
"agent_servers": {
"glm": {
"command": "glm-acp-agent",
"env": { "Z_AI_API_KEY": "sk-…" }
}
}
}If you installed from source instead of the published package, replace command / args with the absolute path to the build output:
{
"agent_servers": {
"glm": {
"command": "node",
"args": ["/absolute/path/to/glm-acp-agent/dist/index.js"],
"env": { "Z_AI_API_KEY": "sk-…" }
}
}
}Option B — credentials file (no key in your editor settings):
Run the interactive setup once:
glm-acp-agent --setupIf you're working from a source clone, use the build output directly: node dist/index.js --setup.
The key is written to ~/.config/glm-acp-agent/credentials.json with 0600 permissions. Then drop the env block from the Zed entry — the agent will read the file on launch:
{
"agent_servers": {
"glm": {
"command": "glm-acp-agent"
}
}
}If Z_AI_API_KEY is set in the environment and a credentials file exists, the environment variable wins.
- Save
settings.json. Zed reloads settings automatically. - Open the agent panel (use the command palette:
agent panel: toggle focus). - In the agent picker, select glm — Zed labels external agents by their
agent_serverskey. - Start a new thread and send a small prompt that exercises a tool, e.g.
Read package.json and tell me the project name. - You should see streaming text, a
read_filetool call that runs without a permission prompt, and (with a thinking-capable model likeglm-5.3) reasoning surfaced as a separate thought block. Writes and shell commands require approval in the default mode.
After editing the source, rebuild:
npm run buildZed spawns a fresh agent process per thread, so the easiest way to pick up changes is to start a new thread with the glm agent. If you see stale behavior, fully quit and reopen Zed — that guarantees no in-flight process is reused.
For a tighter inner loop, run npm run dev in a terminal so dist/ rebuilds on every save; you only need to start a new Zed thread to test the latest code.
- The "glm" agent doesn't appear in the picker. —
settings.jsonlikely has a JSON parse error, or your Zed build is older thanagent_serverssupport. Open the Zed log via the command palette (zed: open log) and look for settings errors. Error: Cannot find module '/.../dist/index.js'— you skippednpm run build, or the path inargsis wrong. It must be absolute and point at a file that exists.No API key found.— neitherZ_AI_API_KEYnor~/.config/glm-acp-agent/credentials.jsonis set. Use Option A or Option B in step 3.HTTP 401: Invalid API key— your key is wrong, expired, or for the wrong region. Rotate it on https://z.ai/manage-apikey/apikey-list.- A write or command did not run. — approve the ACP permission prompt for that tool call. Rejected or cancelled prompts are reported back to the model as skipped operations.
Any client that supports configuring an ACP agent via a command + args invocation works the same way:
- If you installed via
npm install -g glm-acp-agent@latest, setcommandtoglm-acp-agent(no args required). - If you built from source, set
commandtonodeandargsto["/absolute/path/to/glm-acp-agent/dist/index.js"].
Supply Z_AI_API_KEY in the environment, or run glm-acp-agent --setup once so the agent reads the key from disk on launch.
The agent advertises two authentication methods at initialize time:
- An
agent-default method — the agent will read the API key itself, either from theZ_AI_API_KEYenvironment variable or from the credentials file written byglm-acp-agent --setup. - An
env_varmethod (experimental SDK extension) describing theZ_AI_API_KEYvariable so capable clients can prompt the user and inject it.
ACP clients that support the auth-methods proposal will use whichever method they recognise; clients that don't handle auth methods should set Z_AI_API_KEY themselves before launching the agent (or run glm-acp-agent --setup once and let the agent read it from disk).
src/
├── index.ts # Entry point – starts stdio connection or --setup flow
├── setup.ts # Interactive credential setup (`--setup`)
├── llm/
│ ├── glm-client.ts # OpenAI-compatible client for Z.AI / Zhipu AI
│ └── credentials.ts # API-key resolution (env var > credentials.json)
├── protocol/
│ ├── connection.ts # Sets up the ACP stdio connection
│ ├── agent.ts # GlmAcpAgent – ACP protocol implementation
│ ├── slash-commands.ts # Discovers .claude commands/skills; expands `/name`
│ └── session-store.ts # On-disk persistence for load/fork/resume
├── tools/
│ ├── definitions.ts # Tool JSON schemas (function-calling format)
│ └── executor.ts # ToolExecutor – dispatches tool calls
└── tests/
├── agent.test.ts # Protocol-level tests for GlmAcpAgent
├── credentials.test.ts # Credential resolution and --setup persistence
├── executor.test.ts # Tests for ToolExecutor
├── glm-client.test.ts # Tests for streaming / tool-call assembly
├── slash-commands.test.ts # Command discovery and `/name` expansion
└── integration.test.ts # End-to-end tests over the real ACP ndjson transport
npm run build # one-shot TypeScript compilation → dist/
npm run dev # watch mode
npm test # build + run unit tests with the node:test runnerThe test suite covers:
- ACP protocol-version negotiation, capability advertising, and auth method shape
- Session lifecycle (
new/list/close, filter bycwd) - Prompt loop streaming, tool-call assembly, max-turn cap, cancellation, stop-reason mapping (
stop→end_turn,length→max_tokens,content_filter→refusal,tool_callsexhausted →max_turn_requests) - Content-block conversion (
text,resource_link, embeddedresource) - Token usage reporting on the
PromptResponse - Tool call lifecycle (
pending→in_progress→completed/failed) - Agent-owned local file and shell tools that work without ACP
fs/terminalclient capabilities - Permission flows for
write_fileandrun_command(allow / reject / cancel) - Shell-quoted argument handling for
list_filesandrun_command - GLM streaming: text deltas,
reasoning_contentdeltas, multi-chunk tool-call assembly, and trailing usage - Image preprocessing through a mocked Vision MCP client and graceful degradation on Vision MCP failures
No API key found.— either setZ_AI_API_KEYin the environment, or runglm-acp-agent --setup(ornode dist/index.js --setupfrom a source clone) once to store the key on disk.HTTP 401: Invalid API key— your key is wrong or expired; rotate it on https://z.ai/manage-apikey/apikey-list.- Writes or commands never get to run. — make sure your ACP client supports
session/request_permissionand that you approve the prompt for the specific tool call.
Apache 2.0 – see LICENSE.