glm-acp-agent 1.5.0 → 1.6.1
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.
- package/README.md +3 -2
- package/dist/cli-args.d.ts +8 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +34 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/protocol/agent.d.ts +29 -3
- package/dist/protocol/agent.d.ts.map +1 -1
- package/dist/protocol/agent.js +192 -59
- package/dist/protocol/agent.js.map +1 -1
- package/dist/protocol/connection.d.ts +2 -1
- package/dist/protocol/connection.d.ts.map +1 -1
- package/dist/protocol/connection.js +2 -2
- package/dist/protocol/connection.js.map +1 -1
- package/dist/tests/agent.test.js +228 -1
- package/dist/tests/agent.test.js.map +1 -1
- package/dist/tests/cli-args.test.d.ts +2 -0
- package/dist/tests/cli-args.test.d.ts.map +1 -0
- package/dist/tests/cli-args.test.js +56 -0
- package/dist/tests/cli-args.test.js.map +1 -0
- package/dist/tests/session-mcp-client.test.d.ts +2 -0
- package/dist/tests/session-mcp-client.test.d.ts.map +1 -0
- package/dist/tests/session-mcp-client.test.js +349 -0
- package/dist/tests/session-mcp-client.test.js.map +1 -0
- package/dist/tests/vision-mcp-client.test.js +159 -2
- package/dist/tests/vision-mcp-client.test.js.map +1 -1
- package/dist/tools/session-mcp-client.d.ts +64 -1
- package/dist/tools/session-mcp-client.d.ts.map +1 -1
- package/dist/tools/session-mcp-client.js +287 -56
- package/dist/tools/session-mcp-client.js.map +1 -1
- package/dist/tools/vision-mcp-client.d.ts +19 -0
- package/dist/tools/vision-mcp-client.d.ts.map +1 -1
- package/dist/tools/vision-mcp-client.js +213 -50
- package/dist/tools/vision-mcp-client.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,13 +26,13 @@ Built-in web tools use Coding Plan-compatible MCP endpoints, not the general `/a
|
|
|
26
26
|
|
|
27
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
28
|
- **Streaming** – assistant text and reasoning tokens are forwarded as incremental ACP chunks
|
|
29
|
-
- **Tool calling** – agentic loop with
|
|
29
|
+
- **Tool calling** – agentic loop with a configurable cap of GLM function-calling turns (default 20; see `ACP_GLM_MAX_TURNS` / `--max-turns`)
|
|
30
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
31
|
- **Session permission modes** – supports `default`, `accept_edits`, and `bypass_permissions` via `session/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.
|
|
32
32
|
- **Per-session model switching** – `session/set_model` lets clients change the active GLM model mid-conversation; `session/new` returns the curated `availableModels` list
|
|
33
33
|
- **Image input via Coding Plan-native vision or Vision MCP** – `promptCapabilities.image` is advertised; the advertised coding models route pasted ACP image blocks through Z.AI Vision MCP (`@z_ai/mcp-server`), while `glm-5v-turbo` sessions — opt-in via `ACP_GLM_AVAILABLE_MODELS`, since it is no longer on the Coding Plan allowlist — send supported image parts directly to the model. Direct chat-image-only models (e.g. `glm-4v-plus`) are intentionally not used.
|
|
34
34
|
- **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`
|
|
35
|
-
- **
|
|
35
|
+
- **Seven built-in tools** (see below)
|
|
36
36
|
- **Self-sufficient local tools** – file reads/writes, directory listings, and shell commands run in the agent process, so they do not depend on ACP client `fs` or `terminal` capabilities
|
|
37
37
|
- **Configurable permissions** – `write_file` and `run_command` behavior depends on the active session mode (prompts by default)
|
|
38
38
|
- **Protocol-correct stop reasons** – maps model and runtime conditions to ACP `end_turn`, `max_tokens`, `max_turn_requests`, `refusal`, and `cancelled`
|
|
@@ -143,6 +143,7 @@ The agent reads its configuration from environment variables, plus an optional c
|
|
|
143
143
|
| `ACP_GLM_AVAILABLE_MODELS` | No | built-in list | Comma-separated list of model ids advertised in `session/set_model` |
|
|
144
144
|
| `ACP_GLM_BASE_URL` | No | `https://api.z.ai/api/coding/paas/v4` | Override the API base URL |
|
|
145
145
|
| `ACP_GLM_MAX_TOKENS` | No | `8192` | Cap on `max_tokens` for each completion |
|
|
146
|
+
| `ACP_GLM_MAX_TURNS` | No | `20` | Max model/tool turns per prompt (also settable via `--max-turns`) |
|
|
146
147
|
| `ACP_GLM_THINKING` | No | auto-detected | Force thinking mode `true` / `false` |
|
|
147
148
|
| `ACP_GLM_SESSION_DIR` | No | `$XDG_STATE_HOME/glm-acp-agent/sessions` | Where session JSON files are persisted |
|
|
148
149
|
| `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) |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `--max-turns <n>` (or `--max-turns=<n>`) from CLI args.
|
|
3
|
+
* Returns `undefined` only when the flag is absent; invalid input selects the
|
|
4
|
+
* default (20), never `$ACP_GLM_MAX_TURNS` — explicit-but-bad CLI input must
|
|
5
|
+
* not silently fall through to the env var.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseMaxTurnsFlag(argv: string[]): number | undefined;
|
|
8
|
+
//# sourceMappingURL=cli-args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CA2BpE"}
|
package/dist/cli-args.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DEFAULT_MAX_TURNS } from "./protocol/agent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse `--max-turns <n>` (or `--max-turns=<n>`) from CLI args.
|
|
4
|
+
* Returns `undefined` only when the flag is absent; invalid input selects the
|
|
5
|
+
* default (20), never `$ACP_GLM_MAX_TURNS` — explicit-but-bad CLI input must
|
|
6
|
+
* not silently fall through to the env var.
|
|
7
|
+
*/
|
|
8
|
+
export function parseMaxTurnsFlag(argv) {
|
|
9
|
+
const idx = argv.indexOf("--max-turns");
|
|
10
|
+
if (idx !== -1) {
|
|
11
|
+
if (idx + 1 >= argv.length) {
|
|
12
|
+
process.stderr.write("glm-acp-agent: --max-turns requires a value\n");
|
|
13
|
+
return DEFAULT_MAX_TURNS;
|
|
14
|
+
}
|
|
15
|
+
const parsed = Number(argv[idx + 1]);
|
|
16
|
+
if (Number.isFinite(parsed) && Math.floor(parsed) >= 1) {
|
|
17
|
+
return Math.floor(parsed);
|
|
18
|
+
}
|
|
19
|
+
process.stderr.write(`glm-acp-agent: ignoring invalid --max-turns "${argv[idx + 1]}"\n`);
|
|
20
|
+
return DEFAULT_MAX_TURNS;
|
|
21
|
+
}
|
|
22
|
+
const eq = argv.find((a) => a.startsWith("--max-turns="));
|
|
23
|
+
if (eq !== undefined) {
|
|
24
|
+
const raw = eq.slice("--max-turns=".length);
|
|
25
|
+
const parsed = Number(raw);
|
|
26
|
+
if (Number.isFinite(parsed) && Math.floor(parsed) >= 1) {
|
|
27
|
+
return Math.floor(parsed);
|
|
28
|
+
}
|
|
29
|
+
process.stderr.write(`glm-acp-agent: ignoring invalid --max-turns "${raw}"\n`);
|
|
30
|
+
return DEFAULT_MAX_TURNS;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=cli-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAc;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACf,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;YACtE,OAAO,iBAAiB,CAAC;QAC3B,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gDAAgD,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CACnE,CAAC;QACF,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;IAC1D,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gDAAgD,GAAG,KAAK,CAAC,CAAC;QAC/E,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* Z_AI_API_KEY - API key for the Z.AI / Zhipu AI service. If unset,
|
|
11
11
|
* falls back to the credentials file written by --setup.
|
|
12
12
|
* ACP_GLM_MODEL - (optional) Override the default model (default: glm-5.3)
|
|
13
|
+
* ACP_GLM_MAX_TURNS - (optional) Max model/tool turns per prompt (default: 20)
|
|
13
14
|
*/
|
|
14
15
|
import { startConnection } from "./protocol/connection.js";
|
|
16
|
+
import { parseMaxTurnsFlag } from "./cli-args.js";
|
|
15
17
|
import { runSetup } from "./setup.js";
|
|
16
18
|
const args = process.argv.slice(2);
|
|
17
19
|
if (args.includes("--setup")) {
|
|
@@ -30,11 +32,13 @@ else if (args.includes("--help") || args.includes("-h")) {
|
|
|
30
32
|
"Usage:",
|
|
31
33
|
" glm-acp-agent Start the ACP stdio loop (run by an ACP client)",
|
|
32
34
|
" glm-acp-agent --setup Interactively store your Z.AI API key on disk",
|
|
35
|
+
" glm-acp-agent --max-turns <n> Max model/tool turns per prompt",
|
|
33
36
|
" glm-acp-agent --help Show this message",
|
|
34
37
|
"",
|
|
35
38
|
"Environment variables:",
|
|
36
39
|
" Z_AI_API_KEY API key (overrides the stored credentials)",
|
|
37
40
|
" ACP_GLM_MODEL Default model id (e.g. glm-5.3)",
|
|
41
|
+
" ACP_GLM_MAX_TURNS Max model/tool turns per prompt (default 20)",
|
|
38
42
|
" ACP_GLM_AVAILABLE_MODELS Comma-separated list of advertised models",
|
|
39
43
|
" ACP_GLM_BASE_URL Override the Z.AI API base URL",
|
|
40
44
|
" ACP_GLM_MAX_TOKENS Per-call max output tokens (default 8192)",
|
|
@@ -47,7 +51,7 @@ else if (args.includes("--help") || args.includes("-h")) {
|
|
|
47
51
|
process.exit(0);
|
|
48
52
|
}
|
|
49
53
|
else {
|
|
50
|
-
const connection = startConnection();
|
|
54
|
+
const connection = startConnection({ maxTurns: parseMaxTurnsFlag(args) });
|
|
51
55
|
// Keep the process alive until the connection closes
|
|
52
56
|
connection.closed
|
|
53
57
|
.then(() => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,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,kEAAkE;QAClE,6CAA6C;QAC7C,EAAE;QACF,wBAAwB;QACxB,6EAA6E;QAC7E,kEAAkE;QAClE,+EAA+E;QAC/E,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,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE1E,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"}
|
package/dist/protocol/agent.d.ts
CHANGED
|
@@ -15,7 +15,10 @@ export interface GlmAcpAgentOptions {
|
|
|
15
15
|
glm?: {
|
|
16
16
|
streamChat: (messages: GlmMessage[], signal?: AbortSignal, options?: StreamChatOptions) => AsyncIterable<GlmStreamChunk>;
|
|
17
17
|
};
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of model/tool turns per single prompt. Default 20,
|
|
20
|
+
* overridable via `$ACP_GLM_MAX_TURNS`.
|
|
21
|
+
*/
|
|
19
22
|
maxTurns?: number;
|
|
20
23
|
/**
|
|
21
24
|
* Override the session store (used in tests). When undefined the agent
|
|
@@ -39,6 +42,7 @@ export interface GlmAcpAgentOptions {
|
|
|
39
42
|
* GLM series models (via `GlmClient`), providing a full prompt loop with
|
|
40
43
|
* tool-calling and streaming support.
|
|
41
44
|
*/
|
|
45
|
+
export declare const DEFAULT_MAX_TURNS = 20;
|
|
42
46
|
export declare class GlmAcpAgent implements Agent {
|
|
43
47
|
private connection;
|
|
44
48
|
private sessions;
|
|
@@ -56,8 +60,30 @@ export declare class GlmAcpAgent implements Agent {
|
|
|
56
60
|
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
|
57
61
|
unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse>;
|
|
58
62
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
63
|
+
* Switch a session to a new model id — shared by `session/set_model` and the
|
|
64
|
+
* `model` config option. Uncatalogued ids are allowed on purpose (Z.AI may
|
|
65
|
+
* offer models we haven't catalogued), but log a stderr hint. Persists,
|
|
66
|
+
* notifies clients via `session_info_update`, and pushes a
|
|
67
|
+
* `config_option_update` because the valid thought levels may differ between
|
|
68
|
+
* models (e.g. switching from 5.3 to 4.7 drops the effort ladder).
|
|
69
|
+
*/
|
|
70
|
+
private applySessionModel;
|
|
71
|
+
/**
|
|
72
|
+
* Build the SessionConfigOptions we advertise: `thought_level` (levels depend
|
|
73
|
+
* on the model, see {@link getThoughtLevels}), `mode`, and `model`.
|
|
74
|
+
*
|
|
75
|
+
* The `mode` option mirrors {@link modesState} as a `category: "mode"`
|
|
76
|
+
* selector. Clients that render config options (Zed) suppress the legacy mode
|
|
77
|
+
* selector once any config option is advertised, so the permission mode would
|
|
78
|
+
* otherwise be unreachable from their UI. `currentMode` is always read from
|
|
79
|
+
* the live session state, so a mode set through `session/set_mode` shows up
|
|
80
|
+
* here too.
|
|
81
|
+
*
|
|
82
|
+
* The `model` option mirrors {@link modelsState} as a `category: "model"`
|
|
83
|
+
* selector for the same reason: clients that render config options suppress
|
|
84
|
+
* the legacy model selector too, so the active model would otherwise be
|
|
85
|
+
* unreachable. `currentModel` is read from the live session state, so a model
|
|
86
|
+
* set through `session/set_model` shows up here as well.
|
|
61
87
|
*/
|
|
62
88
|
private configOptionsState;
|
|
63
89
|
setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/protocol/agent.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,KAAK,EACL,mBAAmB,EAEnB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,8BAA8B,EAK/B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAEvB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAGzE,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAa3F;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,cAAc,GAAG,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/protocol/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/protocol/agent.js
CHANGED
|
@@ -20,6 +20,35 @@ import { debug, error, isDebugEnabled } from "../llm/logger.js";
|
|
|
20
20
|
* files in their AGENTS.md.
|
|
21
21
|
*/
|
|
22
22
|
const PROJECT_CONTEXT_CAP_CHARS = 8 * 1024;
|
|
23
|
+
/**
|
|
24
|
+
* The session modes we advertise, in the order clients should display them.
|
|
25
|
+
*
|
|
26
|
+
* Single source of truth for both the ACP `modes` state (`session/set_mode`)
|
|
27
|
+
* and the `mode`-category SessionConfigOption. Clients like Zed suppress the
|
|
28
|
+
* legacy mode selector as soon as an agent advertises any config option, so
|
|
29
|
+
* the same list has to reach the UI through both channels.
|
|
30
|
+
*/
|
|
31
|
+
const SESSION_MODES = [
|
|
32
|
+
{
|
|
33
|
+
id: "default",
|
|
34
|
+
name: "Ask for permission",
|
|
35
|
+
description: "Prompt before edits and commands.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "accept_edits",
|
|
39
|
+
name: "Auto-approve edits",
|
|
40
|
+
description: "Edits run without prompting. Commands still prompt.",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "bypass_permissions",
|
|
44
|
+
name: "Bypass all permissions",
|
|
45
|
+
description: "Edits and commands run without prompting.",
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const SESSION_MODE_IDS = SESSION_MODES.map((mode) => mode.id);
|
|
49
|
+
function isSessionModeId(value) {
|
|
50
|
+
return typeof value === "string" && SESSION_MODE_IDS.includes(value);
|
|
51
|
+
}
|
|
23
52
|
/**
|
|
24
53
|
* Display names for thought levels shown in client UIs. Kept as an exhaustive
|
|
25
54
|
* map (not capitalize-first-letter) so `xhigh` renders as "X-High" and adding
|
|
@@ -42,6 +71,25 @@ const THOUGHT_LEVEL_NAMES = {
|
|
|
42
71
|
* GLM series models (via `GlmClient`), providing a full prompt loop with
|
|
43
72
|
* tool-calling and streaming support.
|
|
44
73
|
*/
|
|
74
|
+
export const DEFAULT_MAX_TURNS = 20;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the fallback maxTurns from `$ACP_GLM_MAX_TURNS` when the caller did
|
|
77
|
+
* not pass an explicit value. Returns `undefined` when unset or invalid (the
|
|
78
|
+
* constructor then applies the default, and logs a warning on invalid input).
|
|
79
|
+
* Values that floor below 1 (e.g. 0.5) count as invalid — flooring them to 0
|
|
80
|
+
* would just be silently replaced by the default in the constructor.
|
|
81
|
+
*/
|
|
82
|
+
function envMaxTurns() {
|
|
83
|
+
const raw = process.env["ACP_GLM_MAX_TURNS"];
|
|
84
|
+
if (raw === undefined || raw === "")
|
|
85
|
+
return undefined;
|
|
86
|
+
const parsed = Number(raw);
|
|
87
|
+
if (!Number.isFinite(parsed) || Math.floor(parsed) < 1) {
|
|
88
|
+
process.stderr.write(`glm-acp-agent: ignoring invalid ACP_GLM_MAX_TURNS="${raw}"\n`);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
return Math.floor(parsed);
|
|
92
|
+
}
|
|
45
93
|
export class GlmAcpAgent {
|
|
46
94
|
connection;
|
|
47
95
|
sessions = new Map();
|
|
@@ -54,11 +102,10 @@ export class GlmAcpAgent {
|
|
|
54
102
|
constructor(connection, options = {}) {
|
|
55
103
|
this.connection = connection;
|
|
56
104
|
this._glm = options.glm ?? null;
|
|
57
|
-
const candidateMaxTurns = options.maxTurns ??
|
|
105
|
+
const candidateMaxTurns = options.maxTurns ?? envMaxTurns() ?? DEFAULT_MAX_TURNS;
|
|
106
|
+
const floored = Math.floor(candidateMaxTurns);
|
|
58
107
|
this.maxTurns =
|
|
59
|
-
Number.isFinite(
|
|
60
|
-
? Math.floor(candidateMaxTurns)
|
|
61
|
-
: 20;
|
|
108
|
+
Number.isFinite(floored) && floored >= 1 ? floored : DEFAULT_MAX_TURNS;
|
|
62
109
|
this.sessionStore =
|
|
63
110
|
options.sessionStore === null
|
|
64
111
|
? null
|
|
@@ -193,7 +240,7 @@ export class GlmAcpAgent {
|
|
|
193
240
|
sessionId,
|
|
194
241
|
models: this.modelsState(model),
|
|
195
242
|
modes: this.modesState("default"),
|
|
196
|
-
configOptions: this.configOptionsState(model, thoughtLevel),
|
|
243
|
+
configOptions: this.configOptionsState(model, thoughtLevel, "default"),
|
|
197
244
|
};
|
|
198
245
|
}
|
|
199
246
|
async unstable_setSessionModel(params) {
|
|
@@ -201,49 +248,68 @@ export class GlmAcpAgent {
|
|
|
201
248
|
if (!session) {
|
|
202
249
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
203
250
|
}
|
|
251
|
+
await this.applySessionModel(params.sessionId, session, params.modelId);
|
|
252
|
+
return {};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Switch a session to a new model id — shared by `session/set_model` and the
|
|
256
|
+
* `model` config option. Uncatalogued ids are allowed on purpose (Z.AI may
|
|
257
|
+
* offer models we haven't catalogued), but log a stderr hint. Persists,
|
|
258
|
+
* notifies clients via `session_info_update`, and pushes a
|
|
259
|
+
* `config_option_update` because the valid thought levels may differ between
|
|
260
|
+
* models (e.g. switching from 5.3 to 4.7 drops the effort ladder).
|
|
261
|
+
*/
|
|
262
|
+
async applySessionModel(sessionId, session, modelId) {
|
|
204
263
|
const available = getAvailableModels();
|
|
205
|
-
const known = available.find((m) => m.modelId ===
|
|
264
|
+
const known = available.find((m) => m.modelId === modelId);
|
|
206
265
|
if (!known) {
|
|
207
|
-
|
|
208
|
-
// (Z.AI may offer models we haven't catalogued), but log a stderr hint.
|
|
209
|
-
process.stderr.write(`[glm-acp-agent] warning: model "${params.modelId}" is not in the advertised list; using as-is.\n`);
|
|
266
|
+
process.stderr.write(`[glm-acp-agent] warning: model "${modelId}" is not in the advertised list; using as-is.\n`);
|
|
210
267
|
}
|
|
211
|
-
session.model =
|
|
212
|
-
// The valid thought levels differ per model
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
session.thoughtLevel = resolveThoughtLevel(params.modelId, session.thoughtLevel);
|
|
268
|
+
session.model = modelId;
|
|
269
|
+
// The valid thought levels differ per model, so clamp the current level to
|
|
270
|
+
// what the new model supports.
|
|
271
|
+
session.thoughtLevel = resolveThoughtLevel(modelId, session.thoughtLevel);
|
|
216
272
|
session.updatedAt = new Date().toISOString();
|
|
217
273
|
// Persist immediately so a fork/reload before the next prompt doesn't
|
|
218
|
-
// resurrect the previous model or thought level
|
|
219
|
-
this.persistSession(
|
|
274
|
+
// resurrect the previous model or thought level.
|
|
275
|
+
this.persistSession(sessionId, session);
|
|
220
276
|
// Notify clients so any UI that displays the active model refreshes
|
|
221
277
|
// immediately, instead of waiting for the next prompt to complete.
|
|
222
278
|
await safeSessionUpdate(this.connection, {
|
|
223
|
-
sessionId
|
|
279
|
+
sessionId,
|
|
224
280
|
update: {
|
|
225
281
|
sessionUpdate: "session_info_update",
|
|
226
282
|
updatedAt: session.updatedAt,
|
|
227
283
|
},
|
|
228
284
|
});
|
|
229
|
-
// Push updated thought_level options — the set of valid levels may
|
|
230
|
-
// differ between models (e.g. switching from 5.3 to 4.7 drops the
|
|
231
|
-
// effort ladder).
|
|
232
285
|
await safeSessionUpdate(this.connection, {
|
|
233
|
-
sessionId
|
|
286
|
+
sessionId,
|
|
234
287
|
update: {
|
|
235
288
|
sessionUpdate: "config_option_update",
|
|
236
|
-
configOptions: this.configOptionsState(session.model, session.thoughtLevel),
|
|
289
|
+
configOptions: this.configOptionsState(session.model, session.thoughtLevel, session.mode),
|
|
237
290
|
},
|
|
238
291
|
});
|
|
239
|
-
return {};
|
|
240
292
|
}
|
|
241
293
|
/**
|
|
242
|
-
* Build the `thought_level`
|
|
243
|
-
*
|
|
294
|
+
* Build the SessionConfigOptions we advertise: `thought_level` (levels depend
|
|
295
|
+
* on the model, see {@link getThoughtLevels}), `mode`, and `model`.
|
|
296
|
+
*
|
|
297
|
+
* The `mode` option mirrors {@link modesState} as a `category: "mode"`
|
|
298
|
+
* selector. Clients that render config options (Zed) suppress the legacy mode
|
|
299
|
+
* selector once any config option is advertised, so the permission mode would
|
|
300
|
+
* otherwise be unreachable from their UI. `currentMode` is always read from
|
|
301
|
+
* the live session state, so a mode set through `session/set_mode` shows up
|
|
302
|
+
* here too.
|
|
303
|
+
*
|
|
304
|
+
* The `model` option mirrors {@link modelsState} as a `category: "model"`
|
|
305
|
+
* selector for the same reason: clients that render config options suppress
|
|
306
|
+
* the legacy model selector too, so the active model would otherwise be
|
|
307
|
+
* unreachable. `currentModel` is read from the live session state, so a model
|
|
308
|
+
* set through `session/set_model` shows up here as well.
|
|
244
309
|
*/
|
|
245
|
-
configOptionsState(model, thoughtLevel) {
|
|
310
|
+
configOptionsState(model, thoughtLevel, currentMode) {
|
|
246
311
|
const levels = getThoughtLevels(model);
|
|
312
|
+
const modelOptions = availableModelsWith(model);
|
|
247
313
|
return [
|
|
248
314
|
{
|
|
249
315
|
id: "thought_level",
|
|
@@ -257,6 +323,30 @@ export class GlmAcpAgent {
|
|
|
257
323
|
name: THOUGHT_LEVEL_NAMES[level],
|
|
258
324
|
})),
|
|
259
325
|
},
|
|
326
|
+
{
|
|
327
|
+
id: "mode",
|
|
328
|
+
name: "Mode",
|
|
329
|
+
description: "Tool permission mode",
|
|
330
|
+
category: "mode",
|
|
331
|
+
type: "select",
|
|
332
|
+
currentValue: currentMode,
|
|
333
|
+
options: SESSION_MODES.map((mode) => ({
|
|
334
|
+
value: mode.id,
|
|
335
|
+
name: mode.name,
|
|
336
|
+
})),
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "model",
|
|
340
|
+
name: "Model",
|
|
341
|
+
description: "GLM model for this session",
|
|
342
|
+
category: "model",
|
|
343
|
+
type: "select",
|
|
344
|
+
currentValue: model,
|
|
345
|
+
options: modelOptions.map((m) => ({
|
|
346
|
+
value: m.modelId,
|
|
347
|
+
name: m.name,
|
|
348
|
+
})),
|
|
349
|
+
},
|
|
260
350
|
];
|
|
261
351
|
}
|
|
262
352
|
async setSessionConfigOption(params) {
|
|
@@ -264,6 +354,39 @@ export class GlmAcpAgent {
|
|
|
264
354
|
if (!session) {
|
|
265
355
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
266
356
|
}
|
|
357
|
+
if (params.configId === "mode") {
|
|
358
|
+
// Same reject-don't-coerce policy as thought_level below.
|
|
359
|
+
if (!isSessionModeId(params.value)) {
|
|
360
|
+
throw new Error(`Invalid mode value: ${String(params.value)}`);
|
|
361
|
+
}
|
|
362
|
+
session.mode = params.value;
|
|
363
|
+
session.updatedAt = new Date().toISOString();
|
|
364
|
+
this.persistSession(params.sessionId, session);
|
|
365
|
+
// Mirror setSessionMode so clients tracking the ACP mode state (rather
|
|
366
|
+
// than the config option) stay in sync with the dropdown.
|
|
367
|
+
await safeSessionUpdate(this.connection, {
|
|
368
|
+
sessionId: params.sessionId,
|
|
369
|
+
update: {
|
|
370
|
+
sessionUpdate: "current_mode_update",
|
|
371
|
+
currentModeId: session.mode,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
configOptions: this.configOptionsState(session.model, session.thoughtLevel, session.mode),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (params.configId === "model") {
|
|
379
|
+
// Same reject-don't-coerce policy as thought_level below, but only for
|
|
380
|
+
// values that aren't model ids at all. Uncatalogued ids are allowed on
|
|
381
|
+
// purpose, mirroring unstable_setSessionModel / ACP_GLM_MODEL.
|
|
382
|
+
if (typeof params.value !== "string" || params.value.length === 0) {
|
|
383
|
+
throw new Error(`Invalid model value: ${String(params.value)}`);
|
|
384
|
+
}
|
|
385
|
+
await this.applySessionModel(params.sessionId, session, params.value);
|
|
386
|
+
return {
|
|
387
|
+
configOptions: this.configOptionsState(session.model, session.thoughtLevel, session.mode),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
267
390
|
if (params.configId !== "thought_level") {
|
|
268
391
|
throw new Error(`Unknown config option: ${params.configId}`);
|
|
269
392
|
}
|
|
@@ -280,7 +403,7 @@ export class GlmAcpAgent {
|
|
|
280
403
|
session.updatedAt = new Date().toISOString();
|
|
281
404
|
this.persistSession(params.sessionId, session);
|
|
282
405
|
return {
|
|
283
|
-
configOptions: this.configOptionsState(session.model, session.thoughtLevel),
|
|
406
|
+
configOptions: this.configOptionsState(session.model, session.thoughtLevel, session.mode),
|
|
284
407
|
};
|
|
285
408
|
}
|
|
286
409
|
/**
|
|
@@ -294,35 +417,12 @@ export class GlmAcpAgent {
|
|
|
294
417
|
* represent the selection the agent is actually using.
|
|
295
418
|
*/
|
|
296
419
|
modelsState(currentModelId) {
|
|
297
|
-
|
|
298
|
-
if (available.some((m) => m.modelId === currentModelId)) {
|
|
299
|
-
return { availableModels: available, currentModelId };
|
|
300
|
-
}
|
|
301
|
-
return {
|
|
302
|
-
availableModels: [...available, { modelId: currentModelId, name: currentModelId }],
|
|
303
|
-
currentModelId,
|
|
304
|
-
};
|
|
420
|
+
return { availableModels: availableModelsWith(currentModelId), currentModelId };
|
|
305
421
|
}
|
|
306
422
|
/** Build the SessionModeState we advertise on session create/load/resume/fork. */
|
|
307
423
|
modesState(currentModeId) {
|
|
308
424
|
return {
|
|
309
|
-
availableModes:
|
|
310
|
-
{
|
|
311
|
-
id: "default",
|
|
312
|
-
name: "Ask for permission",
|
|
313
|
-
description: "Prompt before edits and commands.",
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
id: "accept_edits",
|
|
317
|
-
name: "Auto-approve edits",
|
|
318
|
-
description: "Edits run without prompting. Commands still prompt.",
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
id: "bypass_permissions",
|
|
322
|
-
name: "Bypass all permissions",
|
|
323
|
-
description: "Edits and commands run without prompting.",
|
|
324
|
-
},
|
|
325
|
-
],
|
|
425
|
+
availableModes: SESSION_MODES.map((mode) => ({ ...mode })),
|
|
326
426
|
currentModeId,
|
|
327
427
|
};
|
|
328
428
|
}
|
|
@@ -331,9 +431,8 @@ export class GlmAcpAgent {
|
|
|
331
431
|
if (!session) {
|
|
332
432
|
throw new Error(`Session not found: ${params.sessionId}`);
|
|
333
433
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
throw new Error(`Invalid modeId: ${params.modeId}. Valid modes are: ${validModeIds.join(", ")}`);
|
|
434
|
+
if (!isSessionModeId(params.modeId)) {
|
|
435
|
+
throw new Error(`Invalid modeId: ${params.modeId}. Valid modes are: ${SESSION_MODE_IDS.join(", ")}`);
|
|
337
436
|
}
|
|
338
437
|
const newMode = params.modeId;
|
|
339
438
|
session.mode = newMode;
|
|
@@ -346,6 +445,16 @@ export class GlmAcpAgent {
|
|
|
346
445
|
currentModeId: newMode,
|
|
347
446
|
},
|
|
348
447
|
});
|
|
448
|
+
// Config options are a separate state channel: re-publish them so a mode
|
|
449
|
+
// dropdown (category "mode") doesn't keep a stale currentValue when the
|
|
450
|
+
// mode changes through the classic ACP path.
|
|
451
|
+
await safeSessionUpdate(this.connection, {
|
|
452
|
+
sessionId: params.sessionId,
|
|
453
|
+
update: {
|
|
454
|
+
sessionUpdate: "config_option_update",
|
|
455
|
+
configOptions: this.configOptionsState(session.model, session.thoughtLevel, session.mode),
|
|
456
|
+
},
|
|
457
|
+
});
|
|
349
458
|
return {};
|
|
350
459
|
}
|
|
351
460
|
// ---------------------------------------------------------------------------
|
|
@@ -559,7 +668,7 @@ export class GlmAcpAgent {
|
|
|
559
668
|
return {
|
|
560
669
|
models: this.modelsState(persisted.model),
|
|
561
670
|
modes: this.modesState(persisted.mode),
|
|
562
|
-
configOptions: this.configOptionsState(restored.model, restored.thoughtLevel),
|
|
671
|
+
configOptions: this.configOptionsState(restored.model, restored.thoughtLevel, restored.mode),
|
|
563
672
|
};
|
|
564
673
|
}
|
|
565
674
|
async unstable_forkSession(params) {
|
|
@@ -591,7 +700,7 @@ export class GlmAcpAgent {
|
|
|
591
700
|
sessionId: newSessionId,
|
|
592
701
|
models: this.modelsState(forked.model),
|
|
593
702
|
modes: this.modesState(forked.mode),
|
|
594
|
-
configOptions: this.configOptionsState(forked.model, forked.thoughtLevel),
|
|
703
|
+
configOptions: this.configOptionsState(forked.model, forked.thoughtLevel, forked.mode),
|
|
595
704
|
};
|
|
596
705
|
}
|
|
597
706
|
async resumeSession(params) {
|
|
@@ -618,7 +727,7 @@ export class GlmAcpAgent {
|
|
|
618
727
|
return {
|
|
619
728
|
models: this.modelsState(persisted.model),
|
|
620
729
|
modes: this.modesState(persisted.mode),
|
|
621
|
-
configOptions: this.configOptionsState(restored.model, restored.thoughtLevel),
|
|
730
|
+
configOptions: this.configOptionsState(restored.model, restored.thoughtLevel, restored.mode),
|
|
622
731
|
};
|
|
623
732
|
}
|
|
624
733
|
// ---------------------------------------------------------------------------
|
|
@@ -825,7 +934,18 @@ export class GlmAcpAgent {
|
|
|
825
934
|
// Loop and continue – GLM expects a follow-up completion now that it has
|
|
826
935
|
// tool results.
|
|
827
936
|
}
|
|
828
|
-
// Reached MAX_TURNS without resolution.
|
|
937
|
+
// Reached MAX_TURNS without resolution. Tell the user why we stopped —
|
|
938
|
+
// without this, hitting the cap is indistinguishable from a normal end.
|
|
939
|
+
await this.connection.sessionUpdate({
|
|
940
|
+
sessionId,
|
|
941
|
+
update: {
|
|
942
|
+
sessionUpdate: "agent_message_chunk",
|
|
943
|
+
content: {
|
|
944
|
+
type: "text",
|
|
945
|
+
text: `\n[stopped: reached the ${this.maxTurns}-turn limit — send a message to continue]`,
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
});
|
|
829
949
|
return { stopReason: "max_turn_requests", usage: lastUsage };
|
|
830
950
|
}
|
|
831
951
|
mapStopReason(stopReason) {
|
|
@@ -1009,6 +1129,19 @@ function stringifyUserMessage(content) {
|
|
|
1009
1129
|
.filter((text) => text.length > 0)
|
|
1010
1130
|
.join("\n");
|
|
1011
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* The advertised model list, always including `currentModelId` — a session
|
|
1134
|
+
* restored from disk can be pinned to a de-listed id (`glm-5.2` was the
|
|
1135
|
+
* previous default), and `ACP_GLM_MODEL` / `session/set_model` / the `model`
|
|
1136
|
+
* config option all accept uncatalogued ids on purpose. Dropping the active id
|
|
1137
|
+
* from the list would leave pickers unable to represent the selection in use.
|
|
1138
|
+
*/
|
|
1139
|
+
function availableModelsWith(currentModelId) {
|
|
1140
|
+
const available = getAvailableModels();
|
|
1141
|
+
return available.some((m) => m.modelId === currentModelId)
|
|
1142
|
+
? available
|
|
1143
|
+
: [...available, { modelId: currentModelId, name: currentModelId }];
|
|
1144
|
+
}
|
|
1012
1145
|
/**
|
|
1013
1146
|
* Read an `AGENTS.md` (preferred) or `CLAUDE.md` from the session's cwd, returning
|
|
1014
1147
|
* its contents capped to {@link PROJECT_CONTEXT_CAP_CHARS} characters. Read errors
|