glm-acp-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +365 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +62 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/llm/credentials.d.ts +15 -0
  7. package/dist/llm/credentials.d.ts.map +1 -0
  8. package/dist/llm/credentials.js +54 -0
  9. package/dist/llm/credentials.js.map +1 -0
  10. package/dist/llm/glm-client.d.ts +63 -0
  11. package/dist/llm/glm-client.d.ts.map +1 -0
  12. package/dist/llm/glm-client.js +217 -0
  13. package/dist/llm/glm-client.js.map +1 -0
  14. package/dist/llm/logger.d.ts +19 -0
  15. package/dist/llm/logger.d.ts.map +1 -0
  16. package/dist/llm/logger.js +44 -0
  17. package/dist/llm/logger.js.map +1 -0
  18. package/dist/protocol/agent.d.ts +77 -0
  19. package/dist/protocol/agent.d.ts.map +1 -0
  20. package/dist/protocol/agent.js +732 -0
  21. package/dist/protocol/agent.js.map +1 -0
  22. package/dist/protocol/connection.d.ts +9 -0
  23. package/dist/protocol/connection.d.ts.map +1 -0
  24. package/dist/protocol/connection.js +18 -0
  25. package/dist/protocol/connection.js.map +1 -0
  26. package/dist/protocol/image-preprocessor.d.ts +32 -0
  27. package/dist/protocol/image-preprocessor.d.ts.map +1 -0
  28. package/dist/protocol/image-preprocessor.js +149 -0
  29. package/dist/protocol/image-preprocessor.js.map +1 -0
  30. package/dist/protocol/session-store.d.ts +54 -0
  31. package/dist/protocol/session-store.d.ts.map +1 -0
  32. package/dist/protocol/session-store.js +109 -0
  33. package/dist/protocol/session-store.js.map +1 -0
  34. package/dist/protocol/system-prompt.d.ts +29 -0
  35. package/dist/protocol/system-prompt.d.ts.map +1 -0
  36. package/dist/protocol/system-prompt.js +103 -0
  37. package/dist/protocol/system-prompt.js.map +1 -0
  38. package/dist/setup.d.ts +8 -0
  39. package/dist/setup.d.ts.map +1 -0
  40. package/dist/setup.js +95 -0
  41. package/dist/setup.js.map +1 -0
  42. package/dist/tests/agent.test.d.ts +2 -0
  43. package/dist/tests/agent.test.d.ts.map +1 -0
  44. package/dist/tests/agent.test.js +1118 -0
  45. package/dist/tests/agent.test.js.map +1 -0
  46. package/dist/tests/credentials.test.d.ts +2 -0
  47. package/dist/tests/credentials.test.d.ts.map +1 -0
  48. package/dist/tests/credentials.test.js +112 -0
  49. package/dist/tests/credentials.test.js.map +1 -0
  50. package/dist/tests/executor.test.d.ts +2 -0
  51. package/dist/tests/executor.test.d.ts.map +1 -0
  52. package/dist/tests/executor.test.js +497 -0
  53. package/dist/tests/executor.test.js.map +1 -0
  54. package/dist/tests/glm-client.test.d.ts +2 -0
  55. package/dist/tests/glm-client.test.d.ts.map +1 -0
  56. package/dist/tests/glm-client.test.js +205 -0
  57. package/dist/tests/glm-client.test.js.map +1 -0
  58. package/dist/tests/image-preprocessor.test.d.ts +2 -0
  59. package/dist/tests/image-preprocessor.test.d.ts.map +1 -0
  60. package/dist/tests/image-preprocessor.test.js +133 -0
  61. package/dist/tests/image-preprocessor.test.js.map +1 -0
  62. package/dist/tests/integration.test.d.ts +2 -0
  63. package/dist/tests/integration.test.d.ts.map +1 -0
  64. package/dist/tests/integration.test.js +192 -0
  65. package/dist/tests/integration.test.js.map +1 -0
  66. package/dist/tests/logger.test.d.ts +2 -0
  67. package/dist/tests/logger.test.d.ts.map +1 -0
  68. package/dist/tests/logger.test.js +14 -0
  69. package/dist/tests/logger.test.js.map +1 -0
  70. package/dist/tests/mcp-arg-remap.test.d.ts +2 -0
  71. package/dist/tests/mcp-arg-remap.test.d.ts.map +1 -0
  72. package/dist/tests/mcp-arg-remap.test.js +65 -0
  73. package/dist/tests/mcp-arg-remap.test.js.map +1 -0
  74. package/dist/tests/vision-mcp-client.test.d.ts +2 -0
  75. package/dist/tests/vision-mcp-client.test.d.ts.map +1 -0
  76. package/dist/tests/vision-mcp-client.test.js +129 -0
  77. package/dist/tests/vision-mcp-client.test.js.map +1 -0
  78. package/dist/tests/zai-mcp-client.test.d.ts +2 -0
  79. package/dist/tests/zai-mcp-client.test.d.ts.map +1 -0
  80. package/dist/tests/zai-mcp-client.test.js +386 -0
  81. package/dist/tests/zai-mcp-client.test.js.map +1 -0
  82. package/dist/tools/definitions.d.ts +18 -0
  83. package/dist/tools/definitions.d.ts.map +1 -0
  84. package/dist/tools/definitions.js +149 -0
  85. package/dist/tools/definitions.js.map +1 -0
  86. package/dist/tools/executor.d.ts +48 -0
  87. package/dist/tools/executor.d.ts.map +1 -0
  88. package/dist/tools/executor.js +670 -0
  89. package/dist/tools/executor.js.map +1 -0
  90. package/dist/tools/mcp-arg-remap.d.ts +21 -0
  91. package/dist/tools/mcp-arg-remap.d.ts.map +1 -0
  92. package/dist/tools/mcp-arg-remap.js +70 -0
  93. package/dist/tools/mcp-arg-remap.js.map +1 -0
  94. package/dist/tools/vision-mcp-client.d.ts +38 -0
  95. package/dist/tools/vision-mcp-client.d.ts.map +1 -0
  96. package/dist/tools/vision-mcp-client.js +191 -0
  97. package/dist/tools/vision-mcp-client.js.map +1 -0
  98. package/dist/tools/zai-mcp-client.d.ts +24 -0
  99. package/dist/tools/zai-mcp-client.d.ts.map +1 -0
  100. package/dist/tools/zai-mcp-client.js +189 -0
  101. package/dist/tools/zai-mcp-client.js.map +1 -0
  102. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,365 @@
1
+ # glm-acp-agent
2
+
3
+ An [Agent Client Protocol (ACP)](https://agentclientprotocol.com) agent written in TypeScript that uses the **Z.AI / Zhipu AI GLM** model family (GLM-5.1, GLM-4.7, GLM-4.6, …) as its reasoning core.
4
+
5
+ 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.
6
+
7
+ ---
8
+
9
+ ## Coding Plan Only
10
+
11
+ `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.
12
+
13
+ By default, model calls use the Coding Plan endpoint:
14
+
15
+ ```text
16
+ https://api.z.ai/api/coding/paas/v4
17
+ ```
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - **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`, and `session/set_model`
28
+ - **Streaming** – assistant text and reasoning tokens are forwarded as incremental ACP chunks
29
+ - **Tool calling** – agentic loop with up to 20 turns of GLM function calling
30
+ - **Thinking mode** – GLM's `reasoning_content` tokens are surfaced as `agent_thought_chunk` blocks so the client can show the model's chain of thought
31
+ - **Per-session model switching** – `session/set_model` lets clients change the active GLM model mid-conversation; `session/new` returns the curated `availableModels` list
32
+ - **Image input via Coding Plan Vision MCP** – `promptCapabilities.image` is advertised; pasted ACP image blocks are routed through Z.AI Vision MCP (`@z_ai/mcp-server`) and the resulting analysis is fed into the main coding model. Direct chat-image (e.g. `glm-4v-plus`) calls are intentionally not used.
33
+ - **Session persistence** – conversations are written to `~/.local/state/glm-acp-agent/sessions/` and can be reloaded via `session/load`, branched via `session/fork`, or resumed without replay via `session/resume`
34
+ - **Six built-in tools** (see below)
35
+ - **Capability-aware** – every tool is gated on the client capabilities advertised at `initialize` time; tools the client can't run return a clear error to the model instead of crashing
36
+ - **Permissioned writes / commands** – every `write_file` and `run_command` call asks the user via `session/request_permission` before doing anything
37
+ - **Protocol-correct stop reasons** – maps model and runtime conditions to ACP `end_turn`, `max_tokens`, `max_turn_requests`, `refusal`, and `cancelled`
38
+ - **Protocol-correct tool statuses** – `pending` → `in_progress` → `completed` / `failed`
39
+ - **Token usage reporting** – aggregated usage is returned on the `session/prompt` response
40
+
41
+ ---
42
+
43
+ ## Architecture
44
+
45
+ ```text
46
+ ACP Client (IDE plugin, CLI, …)
47
+ │ stdio (ndjson)
48
+ ▼
49
+ GlmAcpAgent ← ACP protocol layer (src/protocol/)
50
+ │
51
+ ├─ GlmClient ← Z.AI / Zhipu AI Coding Plan Chat Completions (src/llm/)
52
+ │
53
+ ├─ ToolExecutor ← executes tool calls (src/tools/)
54
+ │ ├─ read_file / write_file → ACP client (fs.*)
55
+ │ ├─ list_files / run_command → ACP client (terminal)
56
+ │ ├─ web_search / web_reader → Z.AI Coding Plan Web MCP (HTTP)
57
+ │ └─ image_analysis → Z.AI Coding Plan Vision MCP (stdio)
58
+ │
59
+ └─ VisionMcpClient ← spawns `npx @z_ai/mcp-server` on demand
60
+ ```
61
+
62
+ 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 remain delegated to the ACP client; the agent itself only writes to the OS temp directory when it needs to materialize a pasted image for Vision MCP, and those temp files are removed once the prompt completes.
63
+
64
+ ---
65
+
66
+ ## Available Tools
67
+
68
+ | Tool | Runs on | Requires client capability | Description |
69
+ |------|---------|----------------------------|-------------|
70
+ | `read_file` | ACP client | `fs.readTextFile` | Read the text content of a file |
71
+ | `write_file` | ACP client | `fs.writeTextFile` | Write or overwrite a text file (asks for permission) |
72
+ | `list_files` | ACP client | `terminal` | List a directory via `ls -la` (POSIX shell required) |
73
+ | `run_command` | ACP client | `terminal` | Run an arbitrary shell command via `sh -c` (asks for permission) |
74
+ | `web_search` | Agent (Z.AI Coding Plan MCP) | – | Search the web — returns titles, URLs, and summaries |
75
+ | `web_reader` | Agent (Z.AI Coding Plan MCP) | – | Fetch and parse a web page (markdown or plain text) |
76
+ | `image_analysis` | Agent (Z.AI Vision MCP, stdio) | – | Analyze a local image path or remote URL using `@z_ai/mcp-server` |
77
+
78
+ ---
79
+
80
+ ## Prerequisites
81
+
82
+ - **Node.js** 20 or later (native `fetch` and Web Streams required)
83
+ - **npm** 9 or later
84
+ - A **Z.AI API key** — obtain one at <https://z.ai/manage-apikey/apikey-list>
85
+
86
+ ---
87
+
88
+ ## Installation
89
+
90
+ ```bash
91
+ git clone https://github.com/stefandevo/glm-acp-agent.git
92
+ cd glm-acp-agent
93
+ npm install
94
+ npm run build
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Configuration
100
+
101
+ The agent reads its configuration from environment variables, plus an optional credentials file written by `glm-acp-agent --setup`.
102
+
103
+ | Variable | Required | Default | Description |
104
+ |----------|----------|---------|-------------|
105
+ | `Z_AI_API_KEY` | One of env / `--setup` | — | API key for the Z.AI / Zhipu AI service. If unset, the credentials file is consulted. |
106
+ | `ACP_GLM_MODEL` | No | `glm-5.1` | Default GLM model for new sessions |
107
+ | `ACP_GLM_AVAILABLE_MODELS` | No | built-in list | Comma-separated list of model ids advertised in `session/set_model` |
108
+ | `ACP_GLM_BASE_URL` | No | `https://api.z.ai/api/coding/paas/v4` | Override the API base URL |
109
+ | `ACP_GLM_MAX_TOKENS` | No | `8192` | Cap on `max_tokens` for each completion |
110
+ | `ACP_GLM_THINKING` | No | auto-detected | Force thinking mode `true` / `false` |
111
+ | `ACP_GLM_SESSION_DIR` | No | `$XDG_STATE_HOME/glm-acp-agent/sessions` | Where session JSON files are persisted |
112
+ | `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) |
113
+ | `XDG_CONFIG_HOME` | No | `~/.config` | Where the credentials file is read/written |
114
+
115
+ ### One-time setup
116
+
117
+ 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:
118
+
119
+ ```bash
120
+ # From the project directory (after npm run build)
121
+ node dist/index.js --setup
122
+
123
+ # Or, if you installed globally
124
+ glm-acp-agent --setup
125
+ ```
126
+
127
+ The 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.
128
+
129
+ ### Supported models
130
+
131
+ The agent advertises only the models on the current Z.AI Coding Plan allowlist:
132
+
133
+ | Model | Notes |
134
+ |-------|-------|
135
+ | `glm-5.1` | **Default.** Long-horizon coding model; thinking mode auto-enabled |
136
+ | `glm-5-turbo` | Faster Coding Plan reasoning model |
137
+ | `glm-4.7` | 200K-context reasoning model |
138
+ | `glm-4.5-air` | Lightweight, lower-latency model |
139
+
140
+ `ACP_GLM_AVAILABLE_MODELS` still lets you advertise custom IDs, but custom IDs sit outside the supported Coding Plan list — the Coding Plan endpoint will reject any model code Z.AI hasn't whitelisted (business code `1211`).
141
+
142
+ Vision (`glm-4v-plus` etc.) is **not** advertised as a chat model: vision on the Coding Plan flows through the [Vision MCP](#vision-mcp) section below instead.
143
+
144
+ When the model name matches `glm-4.5`, `glm-4.6`, `glm-4.7`, or `glm-5.x`, the agent enables Z.AI's `thinking: { type: "enabled" }` extension and forwards reasoning tokens to the client as `agent_thought_chunk` blocks. Override with `ACP_GLM_THINKING=false` if you want plain completions only.
145
+
146
+ ### Vision MCP
147
+
148
+ 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.
149
+
150
+ Prerequisites:
151
+
152
+ - `npx` on `PATH` (Node 18+ / npm 9+).
153
+ - The same `Z_AI_API_KEY` used for chat completions; the agent forwards it to the MCP server as `Z_AI_API_KEY` plus `Z_AI_MODE=ZAI`.
154
+
155
+ 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.
156
+
157
+ ---
158
+
159
+ ## Running
160
+
161
+ ### Standalone (stdio)
162
+
163
+ ```bash
164
+ export Z_AI_API_KEY=your_key_here
165
+ node dist/index.js
166
+ ```
167
+
168
+ The agent speaks the ACP newline-delimited JSON protocol over stdin/stdout. You can connect any ACP-compatible client to it.
169
+
170
+ ### As a global CLI
171
+
172
+ ```bash
173
+ npm install -g .
174
+ export Z_AI_API_KEY=your_key_here
175
+ glm-acp-agent
176
+ ```
177
+
178
+ ### Development mode (watch)
179
+
180
+ ```bash
181
+ export Z_AI_API_KEY=your_key_here
182
+ npm run dev # tsc --watch
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Connecting to an ACP Client
188
+
189
+ ### Zed (recommended for local testing)
190
+
191
+ [Zed](https://zed.dev) 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.
192
+
193
+ #### 1. Prerequisites
194
+
195
+ - A recent build of [Zed](https://zed.dev/download) that supports the `agent_servers` setting
196
+ - Node.js 20 or later on your `PATH` (`node --version`)
197
+ - A Z.AI API key — create one at <https://z.ai/manage-apikey/apikey-list>
198
+
199
+ #### 2. Build the agent
200
+
201
+ ```bash
202
+ git clone https://github.com/stefandevo/glm-acp-agent.git
203
+ cd glm-acp-agent
204
+ npm install
205
+ npm run build
206
+ ```
207
+
208
+ Take note of the absolute path to the freshly built entry point — Zed needs it (no `~`, no `$HOME` shortcuts):
209
+
210
+ ```bash
211
+ echo "$(pwd)/dist/index.js"
212
+ ```
213
+
214
+ #### 3. Wire it into Zed
215
+
216
+ Open (or create) `~/.config/zed/settings.json` and add an `agent_servers` entry. Pick **one** of the two API-key strategies below.
217
+
218
+ **Option A — inline `env` block (simplest):**
219
+
220
+ ```json
221
+ {
222
+ "agent_servers": {
223
+ "glm": {
224
+ "command": "node",
225
+ "args": ["/absolute/path/to/glm-acp-agent/dist/index.js"],
226
+ "env": { "Z_AI_API_KEY": "sk-…" }
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ **Option B — credentials file (no key in your editor settings):**
233
+
234
+ Run the interactive setup once. From the project directory:
235
+
236
+ ```bash
237
+ node dist/index.js --setup
238
+ ```
239
+
240
+ …or, if you `npm install -g .`'d the package:
241
+
242
+ ```bash
243
+ glm-acp-agent --setup
244
+ ```
245
+
246
+ 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:
247
+
248
+ ```json
249
+ {
250
+ "agent_servers": {
251
+ "glm": {
252
+ "command": "node",
253
+ "args": ["/absolute/path/to/glm-acp-agent/dist/index.js"]
254
+ }
255
+ }
256
+ }
257
+ ```
258
+
259
+ If `Z_AI_API_KEY` is set in the environment **and** a credentials file exists, the environment variable wins.
260
+
261
+ #### 4. Verify it works
262
+
263
+ 1. Save `settings.json`. Zed reloads settings automatically.
264
+ 2. Open the **agent panel** (use the command palette: `agent panel: toggle focus`).
265
+ 3. In the agent picker, select **glm** — Zed labels external agents by their `agent_servers` key.
266
+ 4. Start a new thread and send a small prompt that exercises a tool, e.g. `Read package.json and tell me the project name.`
267
+ 5. You should see streaming text, a `read_file` tool call awaiting permission, and (with a thinking-capable model like `glm-5.1`) reasoning surfaced as a separate thought block.
268
+
269
+ #### 5. Iterating on the agent
270
+
271
+ After editing the source, rebuild:
272
+
273
+ ```bash
274
+ npm run build
275
+ ```
276
+
277
+ Zed 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.
278
+
279
+ 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.
280
+
281
+ #### 6. Troubleshooting
282
+
283
+ - **The "glm" agent doesn't appear in the picker.** — `settings.json` likely has a JSON parse error, or your Zed build is older than `agent_servers` support. Open the Zed log via the command palette (`zed: open log`) and look for settings errors.
284
+ - **`Error: Cannot find module '/.../dist/index.js'`** — you skipped `npm run build`, or the path in `args` is wrong. It must be absolute and point at a file that exists.
285
+ - **`No API key found.`** — neither `Z_AI_API_KEY` nor `~/.config/glm-acp-agent/credentials.json` is set. Use Option A or Option B in step 3.
286
+ - **`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>.
287
+ - **`client does not advertise the … capability`** — Zed didn't expose that capability to the agent (e.g. `terminal` for `run_command`/`list_files`). Ask the model to use a different tool, or upgrade Zed.
288
+
289
+ ### Neovim / VS Code / JetBrains / any ACP client
290
+
291
+ Any client that supports configuring an ACP agent via a `command` + `args` invocation works the same way: point it at `node /absolute/path/to/glm-acp-agent/dist/index.js` and supply `Z_AI_API_KEY` in the environment.
292
+
293
+ ### Authentication
294
+
295
+ The agent advertises two authentication methods at `initialize` time:
296
+
297
+ 1. An **`agent`-default** method — the agent will read the API key itself, either from the `Z_AI_API_KEY` environment variable or from the credentials file written by `glm-acp-agent --setup`.
298
+ 2. An **`env_var`** method (experimental SDK extension) describing the `Z_AI_API_KEY` variable so capable clients can prompt the user and inject it.
299
+
300
+ 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).
301
+
302
+ ---
303
+
304
+ ## Project Structure
305
+
306
+ ```text
307
+ src/
308
+ ├── index.ts # Entry point – starts stdio connection or --setup flow
309
+ ├── setup.ts # Interactive credential setup (`--setup`)
310
+ ├── llm/
311
+ │ ├── glm-client.ts # OpenAI-compatible client for Z.AI / Zhipu AI
312
+ │ └── credentials.ts # API-key resolution (env var > credentials.json)
313
+ ├── protocol/
314
+ │ ├── connection.ts # Sets up the ACP stdio connection
315
+ │ ├── agent.ts # GlmAcpAgent – ACP protocol implementation
316
+ │ └── session-store.ts # On-disk persistence for load/fork/resume
317
+ ├── tools/
318
+ │ ├── definitions.ts # Tool JSON schemas (function-calling format)
319
+ │ └── executor.ts # ToolExecutor – dispatches tool calls
320
+ └── tests/
321
+ ├── agent.test.ts # Protocol-level tests for GlmAcpAgent
322
+ ├── credentials.test.ts # Credential resolution and --setup persistence
323
+ ├── executor.test.ts # Tests for ToolExecutor
324
+ ├── glm-client.test.ts # Tests for streaming / tool-call assembly
325
+ └── integration.test.ts # End-to-end tests over the real ACP ndjson transport
326
+ ```
327
+
328
+ ---
329
+
330
+ ## Building & Testing
331
+
332
+ ```bash
333
+ npm run build # one-shot TypeScript compilation → dist/
334
+ npm run dev # watch mode
335
+ npm test # build + run unit tests with the node:test runner
336
+ ```
337
+
338
+ The test suite covers:
339
+
340
+ - ACP protocol-version negotiation, capability advertising, and auth method shape
341
+ - Session lifecycle (`new` / `list` / `close`, filter by `cwd`)
342
+ - Prompt loop streaming, tool-call assembly, max-turn cap, cancellation, stop-reason mapping (`stop` → `end_turn`, `length` → `max_tokens`, `content_filter` → `refusal`, `tool_calls` exhausted → `max_turn_requests`)
343
+ - Content-block conversion (`text`, `resource_link`, embedded `resource`)
344
+ - Token usage reporting on the `PromptResponse`
345
+ - Tool call lifecycle (`pending` → `in_progress` → `completed` / `failed`)
346
+ - Capability gating (tools refuse cleanly when the client did not advertise the required capability)
347
+ - Permission flows for `write_file` and `run_command` (allow / reject / cancel)
348
+ - Shell-quoted argument handling for `list_files` and `run_command`
349
+ - GLM streaming: text deltas, `reasoning_content` deltas, multi-chunk tool-call assembly, and trailing usage
350
+ - Image preprocessing through a mocked Vision MCP client and graceful degradation on Vision MCP failures
351
+
352
+ ---
353
+
354
+ ## Troubleshooting
355
+
356
+ - **`No API key found.`** — either set `Z_AI_API_KEY` in the environment, or run `node dist/index.js --setup` (or `glm-acp-agent --setup` if installed globally) once to store the key on disk.
357
+ - **`HTTP 401: Invalid API key`** — your key is wrong or expired; rotate it on <https://z.ai/manage-apikey/apikey-list>.
358
+ - **The agent says "client does not advertise the … capability".** — your ACP client doesn't expose that capability (e.g. terminal). Ask the model to use a different tool, or upgrade the client.
359
+ - **Tools never get to run.** — make sure the client is sending the `clientCapabilities` field in `initialize`; the agent uses it to decide which tools to expose to the model.
360
+
361
+ ---
362
+
363
+ ## License
364
+
365
+ Apache 2.0 – see [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GLM ACP Agent entry point.
4
+ *
5
+ * Starts an ACP agent over stdio that uses the Zhipu AI GLM model family
6
+ * as its reasoning core. Pass `--setup` instead of starting the protocol
7
+ * loop to interactively store a Z.AI API key on disk.
8
+ *
9
+ * Environment variables:
10
+ * Z_AI_API_KEY - API key for the Z.AI / Zhipu AI service. If unset,
11
+ * falls back to the credentials file written by --setup.
12
+ * ACP_GLM_MODEL - (optional) Override the default model (default: glm-5.1)
13
+ */
14
+ import { startConnection } from "./protocol/connection.js";
15
+ import { runSetup } from "./setup.js";
16
+ const args = process.argv.slice(2);
17
+ if (args.includes("--setup")) {
18
+ runSetup()
19
+ .then(() => process.exit(0))
20
+ .catch((err) => {
21
+ const message = err instanceof Error ? err.message : String(err);
22
+ process.stderr.write(`Setup failed: ${message}\n`);
23
+ process.exit(1);
24
+ });
25
+ }
26
+ else if (args.includes("--help") || args.includes("-h")) {
27
+ process.stdout.write([
28
+ "glm-acp-agent — ACP agent using Zhipu AI's GLM models",
29
+ "",
30
+ "Usage:",
31
+ " glm-acp-agent Start the ACP stdio loop (run by an ACP client)",
32
+ " glm-acp-agent --setup Interactively store your Z.AI API key on disk",
33
+ " glm-acp-agent --help Show this message",
34
+ "",
35
+ "Environment variables:",
36
+ " Z_AI_API_KEY API key (overrides the stored credentials)",
37
+ " ACP_GLM_MODEL Default model id (e.g. glm-5.1)",
38
+ " ACP_GLM_AVAILABLE_MODELS Comma-separated list of advertised models",
39
+ " ACP_GLM_BASE_URL Override the Z.AI API base URL",
40
+ " ACP_GLM_MAX_TOKENS Per-call max output tokens (default 8192)",
41
+ " ACP_GLM_THINKING Force thinking mode (true / false)",
42
+ " ACP_GLM_SESSION_DIR Where to persist sessions (default: ~/.local/state/glm-acp-agent/sessions)",
43
+ " ACP_GLM_DEBUG Enable verbose stderr logging (true or 1)",
44
+ " XDG_CONFIG_HOME Where to read/write credentials.json (default: ~/.config)",
45
+ "",
46
+ ].join("\n"));
47
+ process.exit(0);
48
+ }
49
+ else {
50
+ const connection = startConnection();
51
+ // Keep the process alive until the connection closes
52
+ connection.closed
53
+ .then(() => {
54
+ process.exit(0);
55
+ })
56
+ .catch((err) => {
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ process.stderr.write(`Fatal error: ${message}\n`);
59
+ process.exit(1);
60
+ });
61
+ }
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,EAAE;SACP,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAC3B,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,IAAI,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC;KAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,uDAAuD;QACvD,EAAE;QACF,QAAQ;QACR,2EAA2E;QAC3E,yEAAyE;QACzE,6CAA6C;QAC7C,EAAE;QACF,wBAAwB;QACxB,6EAA6E;QAC7E,kEAAkE;QAClE,4EAA4E;QAC5E,iEAAiE;QACjE,4EAA4E;QAC5E,qEAAqE;QACrE,6GAA6G;QAC7G,4EAA4E;QAC5E,4FAA4F;QAC5F,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;KAAM,CAAC;IACN,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC;IAErC,qDAAqD;IACrD,UAAU,CAAC,MAAM;SACd,IAAI,CAAC,GAAG,EAAE;QACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,OAAO,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,15 @@
1
+ /** Path to the credentials JSON file. Honours `$XDG_CONFIG_HOME`. */
2
+ export declare function credentialsPath(): string;
3
+ /** Read the API key from the credentials file, returning undefined if absent. */
4
+ export declare function readCredentialsKey(path?: string): string | undefined;
5
+ /**
6
+ * Resolve the API key: env var > credentials file. Returns undefined when
7
+ * neither source has one set.
8
+ */
9
+ export declare function resolveApiKey(): string | undefined;
10
+ /**
11
+ * Write the API key to the credentials file. Restricts the file to mode 0600
12
+ * so it isn't world-readable on shared machines.
13
+ */
14
+ export declare function writeCredentials(apiKey: string, path?: string): void;
15
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/llm/credentials.ts"],"names":[],"mappings":"AAuBA,qEAAqE;AACrE,wBAAgB,eAAe,IAAI,MAAM,CAIxC;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAA0B,GAAG,MAAM,GAAG,SAAS,CASvF;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAalD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAA0B,GAAG,IAAI,CAOvF"}
@@ -0,0 +1,54 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, dirname } from "node:path";
4
+ import { debug, maskSecret } from "./logger.js";
5
+ /** Path to the credentials JSON file. Honours `$XDG_CONFIG_HOME`. */
6
+ export function credentialsPath() {
7
+ const xdg = process.env["XDG_CONFIG_HOME"];
8
+ const base = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
9
+ return join(base, "glm-acp-agent", "credentials.json");
10
+ }
11
+ /** Read the API key from the credentials file, returning undefined if absent. */
12
+ export function readCredentialsKey(path = credentialsPath()) {
13
+ try {
14
+ const raw = readFileSync(path, "utf8");
15
+ const parsed = JSON.parse(raw);
16
+ const key = parsed.z_ai_api_key;
17
+ return typeof key === "string" && key.length > 0 ? key : undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ /**
24
+ * Resolve the API key: env var > credentials file. Returns undefined when
25
+ * neither source has one set.
26
+ */
27
+ export function resolveApiKey() {
28
+ const fromEnv = process.env["Z_AI_API_KEY"];
29
+ if (typeof fromEnv === "string" && fromEnv.length > 0) {
30
+ debug(`resolveApiKey: source=env key=${maskSecret(fromEnv)}`);
31
+ return fromEnv;
32
+ }
33
+ const fromFile = readCredentialsKey();
34
+ if (fromFile) {
35
+ debug(`resolveApiKey: source=file key=${maskSecret(fromFile)}`);
36
+ }
37
+ else {
38
+ debug("resolveApiKey: no key found");
39
+ }
40
+ return fromFile;
41
+ }
42
+ /**
43
+ * Write the API key to the credentials file. Restricts the file to mode 0600
44
+ * so it isn't world-readable on shared machines.
45
+ */
46
+ export function writeCredentials(apiKey, path = credentialsPath()) {
47
+ if (!apiKey || apiKey.length === 0) {
48
+ throw new Error("Refusing to write empty API key");
49
+ }
50
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
51
+ const body = { z_ai_api_key: apiKey };
52
+ writeFileSync(path, JSON.stringify(body, null, 2) + "\n", { mode: 0o600 });
53
+ }
54
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/llm/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAoBhD,qEAAqE;AACrE,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAC;AACzD,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB,CAAC,OAAe,eAAe,EAAE;IACjE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC;QAChC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,iCAAiC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9D,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,kCAAkC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAAe,eAAe,EAAE;IAC/E,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAoB,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,63 @@
1
+ import type { ChatCompletionMessageParam } from "openai/resources/index.js";
2
+ import type { ModelInfo, Usage } from "@agentclientprotocol/sdk";
3
+ /**
4
+ * A single message in the GLM conversation history.
5
+ */
6
+ export type GlmMessage = ChatCompletionMessageParam;
7
+ /**
8
+ * A streamed chunk from the GLM API.
9
+ */
10
+ export interface GlmStreamChunk {
11
+ /** Incremental assistant text */
12
+ text?: string;
13
+ /** Incremental reasoning/thinking text */
14
+ thinking?: string;
15
+ /** A complete tool call (assembled from streaming deltas) */
16
+ toolCall?: {
17
+ id: string;
18
+ name: string;
19
+ arguments: string;
20
+ };
21
+ /** Token usage reported when the stream finishes */
22
+ usage?: Usage;
23
+ /** Set when the stream is done */
24
+ done?: boolean;
25
+ /** Stop reason when done */
26
+ stopReason?: string;
27
+ }
28
+ /** Options applied to a single `streamChat` call. */
29
+ export interface StreamChatOptions {
30
+ /** GLM model identifier to use for this call. */
31
+ model: string;
32
+ }
33
+ /** Default GLM model when neither client nor user has chosen one. */
34
+ export declare const DEFAULT_MODEL = "glm-5.1";
35
+ /**
36
+ * Resolve the list of advertised models, allowing the user to override the
37
+ * built-in list via `ACP_GLM_AVAILABLE_MODELS`.
38
+ */
39
+ export declare function getAvailableModels(): ModelInfo[];
40
+ /** Default model for new sessions: env override → built-in default. */
41
+ export declare function getDefaultModel(): string;
42
+ /**
43
+ * Wrapper around the OpenAI-compatible Zhipu AI (Z.AI) API.
44
+ *
45
+ * Uses the standard `openai` npm package pointed at `https://api.z.ai/api/paas/v4`
46
+ * so no Zhipu-specific SDK is required. The Z.AI service speaks the OpenAI
47
+ * Chat Completions wire format, plus a few GLM-specific extras (like the
48
+ * `thinking` field and `delta.reasoning_content` for reasoning tokens).
49
+ */
50
+ export declare class GlmClient {
51
+ private client;
52
+ private maxTokens;
53
+ constructor();
54
+ /**
55
+ * Stream a chat completion from the GLM model, yielding chunks as they arrive.
56
+ *
57
+ * Reasoning/thinking tokens (from GLM "thinking" mode) are mapped to
58
+ * `thinking` chunks so the ACP agent can forward them as `agent_thought_chunk`
59
+ * blocks.
60
+ */
61
+ streamChat(messages: GlmMessage[], signal?: AbortSignal, options?: StreamChatOptions): AsyncGenerator<GlmStreamChunk>;
62
+ }
63
+ //# sourceMappingURL=glm-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glm-client.d.ts","sourceRoot":"","sources":["../../src/llm/glm-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAsB,MAAM,2BAA2B,CAAC;AAChG,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAKjE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,oDAAoD;IACpD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,kCAAkC;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAKD,qEAAqE;AACrE,eAAO,MAAM,aAAa,YAAY,CAAC;AAiCvC;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,SAAS,EAAE,CAYhD;AAED,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;;GAOG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;;IAgB1B;;;;;;OAMG;IACI,UAAU,CACf,QAAQ,EAAE,UAAU,EAAE,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,cAAc,CAAC,cAAc,CAAC;CA6IlC"}