@llm4ts/core 2.40.0 → 2.41.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.
- package/dist/providers/ClaudeCliConnector.d.ts.map +1 -1
- package/dist/providers/ClaudeCliConnector.js +26 -13
- package/dist/providers/ClaudeCliConnector.js.map +1 -1
- package/dist/providers/CliSupport.d.ts +17 -1
- package/dist/providers/CliSupport.d.ts.map +1 -1
- package/dist/providers/CliSupport.js +40 -2
- package/dist/providers/CliSupport.js.map +1 -1
- package/dist/providers/CodexConnector.d.ts.map +1 -1
- package/dist/providers/CodexConnector.js +24 -5
- package/dist/providers/CodexConnector.js.map +1 -1
- package/dist/providers/GeminiCliProvider.d.ts.map +1 -1
- package/dist/providers/GeminiCliProvider.js +2 -16
- package/dist/providers/GeminiCliProvider.js.map +1 -1
- package/dist/providers/PiConnector.d.ts +10 -0
- package/dist/providers/PiConnector.d.ts.map +1 -1
- package/dist/providers/PiConnector.js +83 -12
- package/dist/providers/PiConnector.js.map +1 -1
- package/package.json +1 -1
- package/src/providers/ClaudeCliConnector.ts +37 -14
- package/src/providers/CliSupport.ts +63 -2
- package/src/providers/CodexConnector.ts +26 -3
- package/src/providers/GeminiCliProvider.ts +3 -17
- package/src/providers/PiConnector.ts +115 -27
|
@@ -2,6 +2,16 @@ import { type CliConnectorShape } from "../Connector.ts";
|
|
|
2
2
|
import type { CliConnectorConfig } from "../ConnectorConfig.ts";
|
|
3
3
|
import { LlmChunk } from "../Models.ts";
|
|
4
4
|
import type { ProcessExecutorShape } from "../ProcessExecutor.ts";
|
|
5
|
+
/**
|
|
6
|
+
* The first pi whose `--tools` allowlist can be kept free of MCP tools: from
|
|
7
|
+
* 1.0.4 `--tools read` keeps every MCP tool the user configured in
|
|
8
|
+
* `~/.pi/agent/mcp.json` unless `--no-mcp` (new in that release) drops them,
|
|
9
|
+
* and MCP tools are built in since 0.99.0. Below this a read-only seat can
|
|
10
|
+
* reach any MCP write tool, so llm4ts refuses it (ADR 0010, ADR 0035).
|
|
11
|
+
*/
|
|
12
|
+
export declare const piReadOnlyFloor = "1.0.4";
|
|
13
|
+
/** Why this pi cannot hold a read-only seat, or nothing when it can. */
|
|
14
|
+
export declare const piReadOnlyProblem: (versionText: string) => string | undefined;
|
|
5
15
|
export declare const piExtraArgs: (config: CliConnectorConfig) => ReadonlyArray<string>;
|
|
6
16
|
export declare const parsePiStreamLine: (line: string) => ReadonlyArray<LlmChunk>;
|
|
7
17
|
export declare const makePiConnector: (config: CliConnectorConfig, executor: ProcessExecutorShape) => CliConnectorShape;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PiConnector.d.ts","sourceRoot":"","sources":["../../src/providers/PiConnector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAE/D,OAAO,EAAuC,QAAQ,EAAc,MAAM,cAAc,CAAA;AACxF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"PiConnector.d.ts","sourceRoot":"","sources":["../../src/providers/PiConnector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAE/D,OAAO,EAAuC,QAAQ,EAAc,MAAM,cAAc,CAAA;AACxF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAqBjE;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,UAAU,CAAA;AAEtC,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,GAAI,aAAa,MAAM,KAAG,MAAM,GAAG,SAchE,CAAA;AAMD,eAAO,MAAM,WAAW,GAAI,QAAQ,kBAAkB,KAAG,aAAa,CAAC,MAAM,CAwB5E,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,aAAa,CAAC,QAAQ,CAgHtE,CAAA;AAED,eAAO,MAAM,eAAe,GAC1B,QAAQ,kBAAkB,EAC1B,UAAU,oBAAoB,KAC7B,iBAwFF,CAAA"}
|
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Stream from "effect/Stream";
|
|
3
3
|
import { makeCliConnector } from "../Connector.js";
|
|
4
|
-
import { ProviderError } from "../Errors.js";
|
|
4
|
+
import { InvalidRequestError, ProviderError } from "../Errors.js";
|
|
5
5
|
import { ConnectorCapabilities, ConnectorIds, LlmChunk, TokenUsage } from "../Models.js";
|
|
6
|
-
import { effortWord, cumulativeUsage, failClassifiedCliError, jsonBooleanField, jsonField, jsonIntField, jsonStringField, optionalModelArgs, parseJsonLine, sortedFlagArgs, toolEventChunk, toolResultChunk, toolResultText, usageEventChunk } from "./CliSupport.js";
|
|
6
|
+
import { atLeastVersion, effortWord, cumulativeUsage, failClassifiedCliError, jsonBooleanField, jsonField, jsonIntField, jsonStringField, optionalModelArgs, parseJsonLine, sortedFlagArgs, toolEventChunk, toolResultChunk, toolResultText, usageEventChunk, versionTriple, statusChunk } from "./CliSupport.js";
|
|
7
|
+
/**
|
|
8
|
+
* The first pi whose `--tools` allowlist can be kept free of MCP tools: from
|
|
9
|
+
* 1.0.4 `--tools read` keeps every MCP tool the user configured in
|
|
10
|
+
* `~/.pi/agent/mcp.json` unless `--no-mcp` (new in that release) drops them,
|
|
11
|
+
* and MCP tools are built in since 0.99.0. Below this a read-only seat can
|
|
12
|
+
* reach any MCP write tool, so llm4ts refuses it (ADR 0010, ADR 0035).
|
|
13
|
+
*/
|
|
14
|
+
export const piReadOnlyFloor = "1.0.4";
|
|
15
|
+
/** Why this pi cannot hold a read-only seat, or nothing when it can. */
|
|
16
|
+
export const piReadOnlyProblem = (versionText) => {
|
|
17
|
+
const version = versionTriple(versionText);
|
|
18
|
+
const floor = versionTriple(piReadOnlyFloor) ?? [];
|
|
19
|
+
if (version === undefined) {
|
|
20
|
+
return (`could not read the pi version from '${versionText.trim()}'; a read-only seat needs ` +
|
|
21
|
+
`pi >= ${piReadOnlyFloor}, whose --no-mcp keeps MCP tools out of the read allowlist`);
|
|
22
|
+
}
|
|
23
|
+
return atLeastVersion(version, floor)
|
|
24
|
+
? undefined
|
|
25
|
+
: `pi ${version.join(".")} cannot enforce a read-only seat: --tools read keeps MCP tools ` +
|
|
26
|
+
`until --no-mcp arrived in ${piReadOnlyFloor}; upgrade pi, or give the judge and ` +
|
|
27
|
+
"reviewer seats to another harness";
|
|
28
|
+
};
|
|
29
|
+
/** The provider half of pi's `provider/model[:thinking]` model id. */
|
|
30
|
+
const providerOf = (model) => model === undefined ? undefined : model.split("/", 1)[0];
|
|
7
31
|
export const piExtraArgs = (config) => [
|
|
8
32
|
...optionalModelArgs(config.model),
|
|
9
33
|
// `noTools` takes precedence over `readOnly`: `--no-tools` disables tool
|
|
@@ -11,12 +35,22 @@ export const piExtraArgs = (config) => [
|
|
|
11
35
|
// offers a read tool the model can invoke — fine for an interactive
|
|
12
36
|
// session, unsafe for a one-shot complete() call with no tool-loop
|
|
13
37
|
// continuation (see the field's doc comment on CliConnectorConfig).
|
|
14
|
-
|
|
38
|
+
// `--no-tools` also drops MCP tools; `--tools read` keeps them (pi >= 1.0.4),
|
|
39
|
+
// so a read-only seat adds `--no-mcp` (ADR 0035).
|
|
40
|
+
...(config.noTools ? ["--no-tools"] : config.readOnly ? ["--tools", "read", "--no-mcp"] : []),
|
|
15
41
|
// `--thinking` is pi's effort flag (off, minimal, low, medium, high, xhigh).
|
|
16
42
|
...(config.effort === undefined ? [] : ["--thinking", effortWord(config.effort, "xhigh")]),
|
|
17
43
|
// Isolation is partial (ADR 0029): project extensions and skills stay
|
|
18
44
|
// out; pi still reads AGENTS.md from the working directory and its parents.
|
|
19
|
-
|
|
45
|
+
// `--no-extensions` also drops the built-in llama.cpp provider (pi >=
|
|
46
|
+
// 0.99.0), so a local model on it is re-enabled by name.
|
|
47
|
+
...(config.isolated
|
|
48
|
+
? [
|
|
49
|
+
"--no-extensions",
|
|
50
|
+
"--no-skills",
|
|
51
|
+
...(providerOf(config.model) === "llama.cpp" ? ["-e", "builtin:llama.cpp"] : [])
|
|
52
|
+
]
|
|
53
|
+
: []),
|
|
20
54
|
...sortedFlagArgs(config.flags)
|
|
21
55
|
];
|
|
22
56
|
export const parsePiStreamLine = (line) => {
|
|
@@ -32,23 +66,35 @@ export const parsePiStreamLine = (line) => {
|
|
|
32
66
|
: undefined;
|
|
33
67
|
return delta === undefined || delta.length === 0 ? [] : [LlmChunk.make({ delta })];
|
|
34
68
|
}
|
|
35
|
-
|
|
69
|
+
// A call nested in another (codemode) names its parent (ADR 0033).
|
|
70
|
+
case "tool_execution_start": {
|
|
71
|
+
const parent = jsonStringField(json, "parentToolCallId");
|
|
36
72
|
return [
|
|
37
|
-
toolEventChunk(jsonStringField(json, "toolName") ?? "", jsonField(json, "args"), jsonStringField(json, "toolCallId"))
|
|
73
|
+
toolEventChunk(jsonStringField(json, "toolName") ?? "", jsonField(json, "args"), jsonStringField(json, "toolCallId"), parent === undefined ? {} : { parent })
|
|
38
74
|
];
|
|
39
|
-
|
|
75
|
+
}
|
|
76
|
+
case "tool_execution_end": {
|
|
77
|
+
const parent = jsonStringField(json, "parentToolCallId");
|
|
78
|
+
const durationMs = jsonIntField(json, "durationMs");
|
|
40
79
|
return [
|
|
41
80
|
toolResultChunk(jsonStringField(json, "toolCallId"), {
|
|
42
81
|
failed: jsonField(json, "isError") === true,
|
|
82
|
+
...(parent === undefined ? {} : { parent }),
|
|
83
|
+
...(durationMs === undefined ? {} : { durationMs }),
|
|
43
84
|
...(() => {
|
|
44
85
|
const output = toolResultText(jsonField(jsonField(json, "result"), "content"));
|
|
45
86
|
return output === undefined ? {} : { output };
|
|
46
87
|
})()
|
|
47
88
|
})
|
|
48
89
|
];
|
|
90
|
+
}
|
|
49
91
|
case "message_end":
|
|
50
92
|
case "agent_end": {
|
|
51
93
|
const message = jsonField(json, "message");
|
|
94
|
+
// An aborted turn (a signal, a steer) is not a reply either.
|
|
95
|
+
if (jsonStringField(message, "stopReason") === "aborted") {
|
|
96
|
+
return [LlmChunk.make({ delta: "", metadata: { piError: "pi aborted the turn" } })];
|
|
97
|
+
}
|
|
52
98
|
// pi reports a provider refusal (a usage limit, an auth failure) as an
|
|
53
99
|
// assistant message that stopped with an error and exits 0; without
|
|
54
100
|
// this it reads as an empty, successful reply.
|
|
@@ -78,6 +124,10 @@ export const parsePiStreamLine = (line) => {
|
|
|
78
124
|
}))
|
|
79
125
|
];
|
|
80
126
|
}
|
|
127
|
+
// A provider retry and a context compaction pause the turn: the lane
|
|
128
|
+
// says so instead of looking stuck (ADR 0033).
|
|
129
|
+
case "auto_retry_start":
|
|
130
|
+
return [statusChunk("retrying", "start", jsonStringField(json, "errorMessage"))];
|
|
81
131
|
case "auto_retry_end":
|
|
82
132
|
return jsonBooleanField(json, "success") === false
|
|
83
133
|
? [
|
|
@@ -88,7 +138,11 @@ export const parsePiStreamLine = (line) => {
|
|
|
88
138
|
}
|
|
89
139
|
})
|
|
90
140
|
]
|
|
91
|
-
: [];
|
|
141
|
+
: [statusChunk("retrying", "end")];
|
|
142
|
+
case "compaction_start":
|
|
143
|
+
return [statusChunk("compacting", "start")];
|
|
144
|
+
case "compaction_end":
|
|
145
|
+
return [statusChunk("compacting", "end")];
|
|
92
146
|
case "extension_error":
|
|
93
147
|
return [
|
|
94
148
|
LlmChunk.make({
|
|
@@ -105,7 +159,22 @@ export const parsePiStreamLine = (line) => {
|
|
|
105
159
|
export const makePiConnector = (config, executor) => {
|
|
106
160
|
const cwd = config.workingDir ?? ".";
|
|
107
161
|
const extraArgs = piExtraArgs(config);
|
|
162
|
+
// A read-only seat is only taken on a pi whose read allowlist can exclude
|
|
163
|
+
// MCP tools (`piReadOnlyFloor`): the version answer decides before the
|
|
164
|
+
// first turn, once per connector; a failed probe is retried on the next.
|
|
165
|
+
let floorChecked = !config.readOnly;
|
|
166
|
+
const checkFloorOnce = Effect.suspend(() => floorChecked
|
|
167
|
+
? Effect.void
|
|
168
|
+
: Effect.flatMap(executor.run(["pi", "--version"], cwd, config.envVars), (result) => {
|
|
169
|
+
const problem = piReadOnlyProblem([...result.stdout, ...result.stderr].join("\n"));
|
|
170
|
+
return problem === undefined
|
|
171
|
+
? Effect.sync(() => {
|
|
172
|
+
floorChecked = true;
|
|
173
|
+
})
|
|
174
|
+
: Effect.fail(InvalidRequestError.make({ message: problem }));
|
|
175
|
+
}));
|
|
108
176
|
const complete = Effect.fn("@llm4ts/core/providers/PiConnector.complete")(function* (prompt) {
|
|
177
|
+
yield* checkFloorOnce;
|
|
109
178
|
const result = yield* executor.runWithStdin(["pi", "-p", ...extraArgs], cwd, config.envVars, prompt);
|
|
110
179
|
if (result.exitCode !== 0) {
|
|
111
180
|
// pi explains a refusal on stderr (a usage error, a missing model, an
|
|
@@ -119,9 +188,7 @@ export const makePiConnector = (config, executor) => {
|
|
|
119
188
|
}
|
|
120
189
|
return result.stdout.join("\n");
|
|
121
190
|
});
|
|
122
|
-
const completeStream = (prompt) => executor
|
|
123
|
-
.runStreamingWithStdin(["pi", "-p", "--mode", "json", ...extraArgs], cwd, config.envVars, prompt)
|
|
124
|
-
.pipe(Stream.flatMap((line) => Stream.fromIterable(parsePiStreamLine(line))),
|
|
191
|
+
const completeStream = (prompt) => Stream.fromEffect(checkFloorOnce).pipe(Stream.drain, Stream.concat(executor.runStreamingWithStdin(["pi", "-p", "--mode", "json", ...extraArgs], cwd, config.envVars, prompt)), Stream.flatMap((line) => Stream.fromIterable(parsePiStreamLine(line))),
|
|
125
192
|
// pi reports usage per assistant message; a turn is all of them.
|
|
126
193
|
cumulativeUsage, Stream.mapEffect((chunk) => {
|
|
127
194
|
const message = chunk.metadata.piError;
|
|
@@ -135,13 +202,17 @@ export const makePiConnector = (config, executor) => {
|
|
|
135
202
|
// `--tools read` is pi's documented comma-separated ALLOWLIST of tool
|
|
136
203
|
// names: only `read` is enabled, so bash/edit/write are absent — a real
|
|
137
204
|
// capability removal, the same mechanism class as claude's `--tools`.
|
|
205
|
+
// `--no-mcp` keeps MCP tools out of it, and `checkFloorOnce` refuses a
|
|
206
|
+
// read-only seat on a pi without that flag, so the grade holds.
|
|
138
207
|
capabilities: ConnectorCapabilities.make({
|
|
139
208
|
interactiveSessions: true,
|
|
140
209
|
readOnlyEnforcement: "enforced",
|
|
141
210
|
effort: "mapped",
|
|
142
211
|
isolatedHeadless: "partial"
|
|
143
212
|
}),
|
|
144
|
-
|
|
213
|
+
// `--` ends the options (pi >= 0.84.3): a prompt starting with a dash
|
|
214
|
+
// is a prompt, not a flag. Stdin paths need no separator.
|
|
215
|
+
buildArgv: (prompt, _context) => ["pi", "-p", ...extraArgs, "--", prompt],
|
|
145
216
|
buildInteractiveArgv: (_context) => ["pi", ...extraArgs],
|
|
146
217
|
complete,
|
|
147
218
|
completeStream,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PiConnector.js","sourceRoot":"","sources":["../../src/providers/PiConnector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAA0B,MAAM,iBAAiB,CAAA;AAE1E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"PiConnector.js","sourceRoot":"","sources":["../../src/providers/PiConnector.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -31,7 +31,8 @@ import {
|
|
|
31
31
|
toolResultText,
|
|
32
32
|
usageEventChunk,
|
|
33
33
|
type JsonValue,
|
|
34
|
-
effortWord
|
|
34
|
+
effortWord,
|
|
35
|
+
jsonObjectEntries
|
|
35
36
|
} from "./CliSupport.ts"
|
|
36
37
|
|
|
37
38
|
/**
|
|
@@ -93,6 +94,9 @@ export const parseClaudeCliStreamLine = (line: string): ReadonlyArray<LlmChunk>
|
|
|
93
94
|
return []
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
// A sub-agent's lines carry the id of the Agent call that spawned it (ADR 0033).
|
|
98
|
+
const parentId = jsonStringField(json, "parent_tool_use_id")
|
|
99
|
+
const parent = parentId === undefined || parentId.length === 0 ? {} : { parent: parentId }
|
|
96
100
|
switch (jsonStringField(json, "type")) {
|
|
97
101
|
case "assistant":
|
|
98
102
|
return jsonArray(jsonField(jsonField(json, "message"), "content")).flatMap(
|
|
@@ -107,7 +111,8 @@ export const parseClaudeCliStreamLine = (line: string): ReadonlyArray<LlmChunk>
|
|
|
107
111
|
toolEventChunk(
|
|
108
112
|
jsonStringField(block, "name") ?? "",
|
|
109
113
|
jsonField(block, "input"),
|
|
110
|
-
jsonStringField(block, "id")
|
|
114
|
+
jsonStringField(block, "id"),
|
|
115
|
+
parent
|
|
111
116
|
)
|
|
112
117
|
]
|
|
113
118
|
default:
|
|
@@ -123,6 +128,7 @@ export const parseClaudeCliStreamLine = (line: string): ReadonlyArray<LlmChunk>
|
|
|
123
128
|
? [
|
|
124
129
|
toolResultChunk(jsonStringField(block, "tool_use_id"), {
|
|
125
130
|
failed: jsonField(block, "is_error") === true,
|
|
131
|
+
...parent,
|
|
126
132
|
...outputOf(jsonField(block, "content"))
|
|
127
133
|
})
|
|
128
134
|
]
|
|
@@ -133,21 +139,38 @@ export const parseClaudeCliStreamLine = (line: string): ReadonlyArray<LlmChunk>
|
|
|
133
139
|
if (usage === undefined) {
|
|
134
140
|
return []
|
|
135
141
|
}
|
|
136
|
-
const prompt = jsonIntField(usage, "input_tokens") ?? 0
|
|
137
|
-
const completion = jsonIntField(usage, "output_tokens") ?? 0
|
|
138
|
-
const cached = jsonIntField(usage, "cache_read_input_tokens")
|
|
139
142
|
// The CLI reports its own exact cost on the result event; carry it so
|
|
140
143
|
// trackers prefer it over pricing-table estimates. modelUsage's key is
|
|
141
|
-
// a model-name fallback for when no init line was observed
|
|
144
|
+
// a model-name fallback for when no init line was observed, and its
|
|
145
|
+
// per-model counts include the sub-agents' turns, which `usage` leaves
|
|
146
|
+
// out (ADR 0033): sum them when they are there.
|
|
142
147
|
const costUsd = jsonNumberField(json, "total_cost_usd")
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const modelNames = jsonObjectEntries(jsonField(json, "modelUsage")).map(([name]) => name)
|
|
149
|
+
// Only entries that count tokens can replace `usage`; a key-only
|
|
150
|
+
// modelUsage (older CLIs, fixtures) still names the model.
|
|
151
|
+
const perModel = jsonObjectEntries(jsonField(json, "modelUsage")).filter(
|
|
152
|
+
([, entry]) =>
|
|
153
|
+
jsonIntField(entry, "inputTokens") !== undefined ||
|
|
154
|
+
jsonIntField(entry, "outputTokens") !== undefined
|
|
155
|
+
)
|
|
156
|
+
const summed = perModel.reduce(
|
|
157
|
+
(sum, [, entry]) => ({
|
|
158
|
+
prompt: sum.prompt + (jsonIntField(entry, "inputTokens") ?? 0),
|
|
159
|
+
completion: sum.completion + (jsonIntField(entry, "outputTokens") ?? 0),
|
|
160
|
+
cached: sum.cached + (jsonIntField(entry, "cacheReadInputTokens") ?? 0)
|
|
161
|
+
}),
|
|
162
|
+
{ prompt: 0, completion: 0, cached: 0 }
|
|
163
|
+
)
|
|
164
|
+
const prompt =
|
|
165
|
+
perModel.length === 0 ? (jsonIntField(usage, "input_tokens") ?? 0) : summed.prompt
|
|
166
|
+
const completion =
|
|
167
|
+
perModel.length === 0 ? (jsonIntField(usage, "output_tokens") ?? 0) : summed.completion
|
|
168
|
+
const cached =
|
|
169
|
+
perModel.length === 0
|
|
170
|
+
? jsonIntField(usage, "cache_read_input_tokens")
|
|
171
|
+
: summed.cached === 0
|
|
172
|
+
? undefined
|
|
173
|
+
: summed.cached
|
|
151
174
|
return [
|
|
152
175
|
usageEventChunk(
|
|
153
176
|
modelNames.length === 1 ? modelNames[0] : undefined,
|
|
@@ -75,14 +75,25 @@ export const optionalModelArgs = (model: string | undefined): ReadonlyArray<stri
|
|
|
75
75
|
export const effortWord = (effort: Effort, top: "max" | "xhigh" = "max"): string =>
|
|
76
76
|
effort === "max" ? top : effort
|
|
77
77
|
|
|
78
|
-
export const toolEventChunk = (
|
|
78
|
+
export const toolEventChunk = (
|
|
79
|
+
name: string,
|
|
80
|
+
input: JsonValue | undefined,
|
|
81
|
+
id?: string,
|
|
82
|
+
options: {
|
|
83
|
+
/** The tool call this one runs inside, when a harness delegated it to a sub-agent (ADR 0033). */
|
|
84
|
+
readonly parent?: string
|
|
85
|
+
} = {}
|
|
86
|
+
): LlmChunk =>
|
|
79
87
|
LlmChunk.make({
|
|
80
88
|
delta: "",
|
|
81
89
|
metadata: {
|
|
82
90
|
event: "tool_use",
|
|
83
91
|
tool_name: name,
|
|
84
92
|
tool_input: input === undefined ? "{}" : jsonText(input),
|
|
85
|
-
...(id === undefined || id.length === 0 ? {} : { tool_id: id })
|
|
93
|
+
...(id === undefined || id.length === 0 ? {} : { tool_id: id }),
|
|
94
|
+
...(options.parent === undefined || options.parent.length === 0
|
|
95
|
+
? {}
|
|
96
|
+
: { parent: options.parent })
|
|
86
97
|
}
|
|
87
98
|
})
|
|
88
99
|
|
|
@@ -117,6 +128,10 @@ export const toolResultChunk = (
|
|
|
117
128
|
readonly input?: JsonValue
|
|
118
129
|
/** What the tool returned, for an opt-in transcript; never in the trace. */
|
|
119
130
|
readonly output?: string
|
|
131
|
+
/** The tool call this one ran inside (ADR 0033). */
|
|
132
|
+
readonly parent?: string
|
|
133
|
+
/** How long the harness says the tool ran, when it reports that itself. */
|
|
134
|
+
readonly durationMs?: number
|
|
120
135
|
} = {}
|
|
121
136
|
): LlmChunk =>
|
|
122
137
|
LlmChunk.make({
|
|
@@ -125,6 +140,12 @@ export const toolResultChunk = (
|
|
|
125
140
|
event: "tool_result",
|
|
126
141
|
...(id === undefined || id.length === 0 ? {} : { tool_id: id }),
|
|
127
142
|
...(options.failed === true ? { tool_failed: "true" } : {}),
|
|
143
|
+
...(options.parent === undefined || options.parent.length === 0
|
|
144
|
+
? {}
|
|
145
|
+
: { parent: options.parent }),
|
|
146
|
+
...(options.durationMs === undefined || !Number.isFinite(options.durationMs)
|
|
147
|
+
? {}
|
|
148
|
+
: { tool_duration_ms: String(Math.max(0, Math.round(options.durationMs))) }),
|
|
128
149
|
...(options.tool === undefined ? {} : { tool_name: options.tool }),
|
|
129
150
|
...(options.input === undefined ? {} : { tool_input: jsonText(options.input) }),
|
|
130
151
|
...(options.output === undefined ? {} : { tool_content: options.output })
|
|
@@ -221,3 +242,43 @@ export const failClassifiedCliError = Effect.fn(
|
|
|
221
242
|
message: `${fallbackPrefix}: ${raw}`
|
|
222
243
|
})
|
|
223
244
|
})
|
|
245
|
+
|
|
246
|
+
/** The first `x.y.z` in a CLI's `--version` answer, if it holds one. */
|
|
247
|
+
export const versionTriple = (text: string): ReadonlyArray<number> | undefined => {
|
|
248
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(text)
|
|
249
|
+
return match === null ? undefined : match.slice(1, 4).map(Number)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Whether `version` is `floor` or newer, component by component. */
|
|
253
|
+
export const atLeastVersion = (
|
|
254
|
+
version: ReadonlyArray<number>,
|
|
255
|
+
floor: ReadonlyArray<number>
|
|
256
|
+
): boolean => {
|
|
257
|
+
for (let index = 0; index < floor.length; index += 1) {
|
|
258
|
+
const have = version[index] ?? 0
|
|
259
|
+
const want = floor[index] ?? 0
|
|
260
|
+
if (have !== want) {
|
|
261
|
+
return have > want
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* A harness pausing a turn on its own account — a provider retry, a context
|
|
269
|
+
* compaction — so the lane can say so instead of looking stuck (ADR 0033).
|
|
270
|
+
*/
|
|
271
|
+
export const statusChunk = (
|
|
272
|
+
status: "retrying" | "compacting",
|
|
273
|
+
phase: "start" | "end",
|
|
274
|
+
detail?: string
|
|
275
|
+
): LlmChunk =>
|
|
276
|
+
LlmChunk.make({
|
|
277
|
+
delta: "",
|
|
278
|
+
metadata: {
|
|
279
|
+
event: "status",
|
|
280
|
+
status,
|
|
281
|
+
phase,
|
|
282
|
+
...(detail === undefined || detail.length === 0 ? {} : { status_detail: detail })
|
|
283
|
+
}
|
|
284
|
+
})
|
|
@@ -97,15 +97,31 @@ export const parseCodexStreamLine = (line: string): ReadonlyArray<LlmChunk> => {
|
|
|
97
97
|
switch (jsonStringField(json, "type")) {
|
|
98
98
|
case "item.started": {
|
|
99
99
|
const item = jsonField(json, "item")
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
switch (jsonStringField(item, "type")) {
|
|
101
|
+
case "command_execution":
|
|
102
|
+
return [
|
|
102
103
|
toolEventChunk(
|
|
103
104
|
"Bash",
|
|
104
105
|
{ command: jsonStringField(item, "command") ?? "" },
|
|
105
106
|
jsonStringField(item, "id")
|
|
106
107
|
)
|
|
107
108
|
]
|
|
108
|
-
:
|
|
109
|
+
// A sub-agent call (spawn_agent, wait, send_input, close_agent): the
|
|
110
|
+
// delegation shows as a tool, its threads as the arguments (ADR 0033).
|
|
111
|
+
case "collab_tool_call":
|
|
112
|
+
return [
|
|
113
|
+
toolEventChunk(
|
|
114
|
+
jsonStringField(item, "tool") ?? "collab",
|
|
115
|
+
{
|
|
116
|
+
prompt: jsonStringField(item, "prompt") ?? "",
|
|
117
|
+
receivers: jsonField(item, "receiver_thread_ids") ?? []
|
|
118
|
+
},
|
|
119
|
+
jsonStringField(item, "id")
|
|
120
|
+
)
|
|
121
|
+
]
|
|
122
|
+
default:
|
|
123
|
+
return []
|
|
124
|
+
}
|
|
109
125
|
}
|
|
110
126
|
case "item.completed": {
|
|
111
127
|
const item = jsonField(json, "item")
|
|
@@ -129,6 +145,13 @@ export const parseCodexStreamLine = (line: string): ReadonlyArray<LlmChunk> => {
|
|
|
129
145
|
})
|
|
130
146
|
]
|
|
131
147
|
}
|
|
148
|
+
case "collab_tool_call":
|
|
149
|
+
return [
|
|
150
|
+
toolResultChunk(jsonStringField(item, "id"), {
|
|
151
|
+
failed: jsonStringField(item, "status") === "failed",
|
|
152
|
+
tool: jsonStringField(item, "tool") ?? "collab"
|
|
153
|
+
})
|
|
154
|
+
]
|
|
132
155
|
default:
|
|
133
156
|
return []
|
|
134
157
|
}
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { collect } from "../Streaming.ts"
|
|
28
28
|
import { jsonCandidates, parseFromText, withSchemaHint } from "../StructuredOutput.ts"
|
|
29
29
|
import {
|
|
30
|
+
atLeastVersion,
|
|
30
31
|
failClassifiedCliError,
|
|
31
32
|
isLoopDetectedMessage,
|
|
32
33
|
jsonField,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
jsonStringField,
|
|
37
38
|
jsonText,
|
|
38
39
|
parseJsonLine,
|
|
40
|
+
versionTriple,
|
|
39
41
|
type JsonValue
|
|
40
42
|
} from "./CliSupport.ts"
|
|
41
43
|
|
|
@@ -271,25 +273,9 @@ export const geminiProcessEnv = (
|
|
|
271
273
|
*/
|
|
272
274
|
export const geminiReadOnlyFloor = "0.37.0"
|
|
273
275
|
|
|
274
|
-
const versionTriple = (text: string): ReadonlyArray<number> | undefined => {
|
|
275
|
-
const match = /(\d+)\.(\d+)\.(\d+)/.exec(text)
|
|
276
|
-
return match === null ? undefined : match.slice(1, 4).map(Number)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
276
|
/** The `x.y.z` in a `gemini --version` answer, if it holds one. */
|
|
280
277
|
export const geminiVersionOf = (text: string): string | undefined => versionTriple(text)?.join(".")
|
|
281
278
|
|
|
282
|
-
const atLeast = (version: ReadonlyArray<number>, floor: ReadonlyArray<number>): boolean => {
|
|
283
|
-
for (let index = 0; index < floor.length; index += 1) {
|
|
284
|
-
const have = version[index] ?? 0
|
|
285
|
-
const want = floor[index] ?? 0
|
|
286
|
-
if (have !== want) {
|
|
287
|
-
return have > want
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return true
|
|
291
|
-
}
|
|
292
|
-
|
|
293
279
|
/** Why this CLI cannot hold a read-only seat, or nothing when it can. */
|
|
294
280
|
export const geminiReadOnlyProblem = (versionText: string): string | undefined => {
|
|
295
281
|
const version = versionTriple(versionText)
|
|
@@ -300,7 +286,7 @@ export const geminiReadOnlyProblem = (versionText: string): string | undefined =
|
|
|
300
286
|
`needs gemini >= ${geminiReadOnlyFloor}, whose plan mode is enforced by its policy engine`
|
|
301
287
|
)
|
|
302
288
|
}
|
|
303
|
-
return
|
|
289
|
+
return atLeastVersion(version, floor)
|
|
304
290
|
? undefined
|
|
305
291
|
: `Gemini CLI ${version.join(".")} cannot enforce a read-only seat: plan mode is a policy ` +
|
|
306
292
|
`only from ${geminiReadOnlyFloor}; upgrade gemini, or give the judge and reviewer ` +
|