@myagentroam/agent 0.9.142 → 0.9.144
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/agent-core/model/anthropic-messages.js +24 -13
- package/dist/agent-core/model/contracts.d.ts +4 -0
- package/dist/agent-core/model/openai-responses.js +16 -9
- package/dist/agent-core/runtime/async-queue.js +7 -5
- package/dist/agent-core/runtime/context/gc-tokens.js +4 -2
- package/dist/agent-core/runtime/context/gc.js +147 -14
- package/dist/agent-core/runtime/context/projection.js +32 -1
- package/dist/agent-core/runtime/events.d.ts +1 -0
- package/dist/agent-core/runtime/execution.js +4 -0
- package/dist/agent-core/sdk/session.js +1 -1
- package/dist/agent-core/session/context-gc-checkpoint.d.ts +4 -1
- package/dist/agent-core/session/context-gc-checkpoint.js +123 -26
- package/dist/agent-core/session/context-gc-observations.d.ts +38 -0
- package/dist/agent-core/session/context-gc-observations.js +148 -0
- package/dist/agent-core/session/context-gc-replacements.d.ts +1 -1
- package/dist/agent-core/session/context-gc-replacements.js +6 -4
- package/dist/agent-core/tools/callers.js +1 -1
- package/dist/agent-core/tools/code-mode/cells.d.ts +3 -0
- package/dist/agent-core/tools/code-mode/cells.js +62 -6
- package/dist/agent-core/tools/code-mode/dispatch.js +7 -4
- package/dist/agent-core/tools/code-mode/engine.d.ts +12 -0
- package/dist/agent-core/tools/code-mode/index.js +183 -14
- package/dist/agent-core/tools/code-mode/observations.d.ts +103 -0
- package/dist/agent-core/tools/code-mode/observations.js +195 -0
- package/dist/agent-core/tools/code-mode/quickjs-engine.js +121 -20
- package/dist/agent-core/tools/code-mode/worker.js +8 -1
- package/dist/agent-core/tools/contracts.d.ts +4 -0
- package/dist/agent-core/tools/execution.d.ts +2 -0
- package/dist/agent-core/tools/execution.js +10 -1
- package/dist/agent-core/tools/output-projection.js +35 -1
- package/package.json +1 -1
|
@@ -3,12 +3,15 @@ import { MarAgentError, describeMarAgentError } from '../../error.js';
|
|
|
3
3
|
import { truncateModelText } from '../../runtime/token-budget.js';
|
|
4
4
|
import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
|
|
5
5
|
import { BoundedTextOutput, renderCapturedText } from './text-output.js';
|
|
6
|
+
import { boundObservations, selectObservationImages, newObservationId, visibleObservation } from './observations.js';
|
|
6
7
|
/** Keeps a failed tool call observable without discarding earlier explicit output. */
|
|
7
8
|
export class CodeCellExecutionError extends MarAgentError {
|
|
8
9
|
result;
|
|
9
10
|
constructor(error, result) {
|
|
10
11
|
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_CODE_FAILED');
|
|
11
|
-
super(diagnostic.code, diagnostic.message.
|
|
12
|
+
super(diagnostic.code, diagnostic.message.startsWith(`${diagnostic.code}: `)
|
|
13
|
+
? diagnostic.message.slice(diagnostic.code.length + 2)
|
|
14
|
+
: diagnostic.message, {
|
|
12
15
|
cause: error,
|
|
13
16
|
...(error instanceof MarAgentError ? { retryable: error.retryable } : {})
|
|
14
17
|
});
|
|
@@ -88,6 +91,9 @@ export class CodeCells {
|
|
|
88
91
|
status: 'running',
|
|
89
92
|
text: new BoundedTextOutput(limits.codeMaxOutputBytes),
|
|
90
93
|
images: [],
|
|
94
|
+
observations: [],
|
|
95
|
+
calls: new Map(),
|
|
96
|
+
imageOffset: 0,
|
|
91
97
|
yielded: false,
|
|
92
98
|
truncated: false,
|
|
93
99
|
listeners: new Set(),
|
|
@@ -104,6 +110,8 @@ export class CodeCells {
|
|
|
104
110
|
.then(async () => {
|
|
105
111
|
releaseSession = await context.codeMode?.retainSession?.();
|
|
106
112
|
return run(source, { ...context, signal: controller.signal }, {
|
|
113
|
+
call: (value) => cell.calls.set(value.callId, value),
|
|
114
|
+
observe: (value) => cell.observations.push(value),
|
|
107
115
|
text: (text, originalBytes) => cell.text.append(text, originalBytes),
|
|
108
116
|
image: (image) => cell.images.push(image),
|
|
109
117
|
yield: () => {
|
|
@@ -199,6 +207,31 @@ export class CodeCells {
|
|
|
199
207
|
if (!supportsImages && (cell.images.length || cell.terminal?.images?.length))
|
|
200
208
|
throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'The current model cannot receive this cell image output.');
|
|
201
209
|
if (cell.terminal) {
|
|
210
|
+
if (cell.terminal.observations) {
|
|
211
|
+
const bounded = boundObservations(cell.terminal.observations, input.maxOutputTokens);
|
|
212
|
+
const visible = selectObservationImages(bounded.observations, cell.terminal.images ?? []);
|
|
213
|
+
const original = cell.terminal.data;
|
|
214
|
+
const data = {
|
|
215
|
+
...original,
|
|
216
|
+
output: truncateModelText(original.output ?? '', input.maxOutputTokens).content,
|
|
217
|
+
observations: visible.observations,
|
|
218
|
+
cached: true,
|
|
219
|
+
truncated: cell.terminal.truncated === true || bounded.truncated
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
...cell.terminal,
|
|
223
|
+
observations: visible.observations,
|
|
224
|
+
images: visible.images,
|
|
225
|
+
content: JSON.stringify({
|
|
226
|
+
...data,
|
|
227
|
+
output: undefined,
|
|
228
|
+
calls: undefined,
|
|
229
|
+
observations: visible.observations.map(visibleObservation)
|
|
230
|
+
}),
|
|
231
|
+
data,
|
|
232
|
+
truncated: data.truncated
|
|
233
|
+
};
|
|
234
|
+
}
|
|
202
235
|
const snapshot = cell.terminal.data;
|
|
203
236
|
const bounded = truncateModelText(snapshot.output, input.maxOutputTokens);
|
|
204
237
|
const data = {
|
|
@@ -219,7 +252,21 @@ export class CodeCells {
|
|
|
219
252
|
const text = renderCapturedText(captured, images.length);
|
|
220
253
|
cell.yielded = false;
|
|
221
254
|
const bounded = truncateModelText(text, input.maxOutputTokens);
|
|
222
|
-
|
|
255
|
+
const pending = cell.observations.splice(0);
|
|
256
|
+
if (!pending.length && captured.text)
|
|
257
|
+
pending.push({
|
|
258
|
+
type: 'value',
|
|
259
|
+
id: newObservationId(),
|
|
260
|
+
format: 'text',
|
|
261
|
+
value: captured.text,
|
|
262
|
+
truncated: captured.droppedBytes > 0
|
|
263
|
+
});
|
|
264
|
+
const structured = boundObservations(pending, input.maxOutputTokens);
|
|
265
|
+
const visible = selectObservationImages(structured.observations, images, cell.imageOffset);
|
|
266
|
+
const remapped = visible.observations;
|
|
267
|
+
const visibleImages = visible.images;
|
|
268
|
+
cell.imageOffset += images.length;
|
|
269
|
+
cell.truncated ||= captured.droppedBytes > 0 || bounded.truncated || structured.truncated;
|
|
223
270
|
const error = cell.error === undefined
|
|
224
271
|
? undefined
|
|
225
272
|
: describeMarAgentError(cell.error, 'MAR_AGENT_CODE_FAILED');
|
|
@@ -227,6 +274,8 @@ export class CodeCells {
|
|
|
227
274
|
cellId: cell.cellId,
|
|
228
275
|
status: cell.status,
|
|
229
276
|
output: bounded.content,
|
|
277
|
+
observations: remapped,
|
|
278
|
+
calls: [...cell.calls.values()],
|
|
230
279
|
...cell.result?.data,
|
|
231
280
|
outputBytes: captured.originalBytes,
|
|
232
281
|
outputDroppedBytes: captured.droppedBytes,
|
|
@@ -235,10 +284,17 @@ export class CodeCells {
|
|
|
235
284
|
cached: false
|
|
236
285
|
};
|
|
237
286
|
const result = {
|
|
238
|
-
content: JSON.stringify(
|
|
287
|
+
content: JSON.stringify({
|
|
288
|
+
...data,
|
|
289
|
+
output: undefined,
|
|
290
|
+
calls: undefined,
|
|
291
|
+
observations: remapped.map(visibleObservation)
|
|
292
|
+
}),
|
|
293
|
+
calls: [...cell.calls.values()],
|
|
294
|
+
observations: remapped,
|
|
239
295
|
data,
|
|
240
|
-
truncated: cell.truncated,
|
|
241
|
-
...(
|
|
296
|
+
truncated: cell.truncated || structured.truncated,
|
|
297
|
+
...(visibleImages.length ? { images: visibleImages } : {})
|
|
242
298
|
};
|
|
243
299
|
if (cell.status !== 'running')
|
|
244
300
|
cell.terminal = result;
|
|
@@ -246,7 +302,7 @@ export class CodeCells {
|
|
|
246
302
|
if (initial && cell.status !== 'running') {
|
|
247
303
|
if (cell.error !== undefined)
|
|
248
304
|
throw new CodeCellExecutionError(cell.error, result);
|
|
249
|
-
return
|
|
305
|
+
return result;
|
|
250
306
|
}
|
|
251
307
|
return result;
|
|
252
308
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { MarAgentError, describeMarAgentError } from '../../error.js';
|
|
3
3
|
import { AGENT_EXECUTION_POLICY } from '../../runtime/execution-policy.js';
|
|
4
|
-
import { truncateModelText } from '../../runtime/token-budget.js';
|
|
5
4
|
import { allowsToolCaller } from '../callers.js';
|
|
6
|
-
import { structuredToolEventData } from '../output-projection.js';
|
|
5
|
+
import { boundedModelToolOutput, structuredToolEventData } from '../output-projection.js';
|
|
7
6
|
import { executeRegisteredTool } from '../runtime.js';
|
|
8
7
|
/** Capture only the cell's immutable dispatch identity, never the model loop or its AbortSignal. */
|
|
9
8
|
export function createCodeToolDispatch(input) {
|
|
@@ -24,7 +23,7 @@ export function createCodeToolDispatch(input) {
|
|
|
24
23
|
signal,
|
|
25
24
|
...(expected ? { expectedMcpTool: expected } : {})
|
|
26
25
|
});
|
|
27
|
-
const bounded =
|
|
26
|
+
const bounded = boundedModelToolOutput(call.name, result.content, result.data);
|
|
28
27
|
const data = structuredToolEventData(call.name, result.data);
|
|
29
28
|
const summary = result.content.slice(0, AGENT_EXECUTION_POLICY.toolEventSummaryCharacters);
|
|
30
29
|
await input.emit({
|
|
@@ -37,7 +36,11 @@ export function createCodeToolDispatch(input) {
|
|
|
37
36
|
summary.length < result.content.length,
|
|
38
37
|
...(result.artifacts ? { artifacts: result.artifacts } : {})
|
|
39
38
|
});
|
|
40
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
...result,
|
|
41
|
+
content: bounded.content,
|
|
42
|
+
truncated: result.truncated === true || bounded.truncated
|
|
43
|
+
};
|
|
41
44
|
}
|
|
42
45
|
catch (error) {
|
|
43
46
|
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
|
|
@@ -32,6 +32,18 @@ export interface CodeModeEngine {
|
|
|
32
32
|
onToolCall(call: ToolCall): Promise<ProgrammaticToolResult>;
|
|
33
33
|
onText(value: string, originalBytes?: number): void;
|
|
34
34
|
onImage(value: unknown): void;
|
|
35
|
+
onObserve(value: {
|
|
36
|
+
type: 'tool_result';
|
|
37
|
+
operationId: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'value';
|
|
40
|
+
format: 'text' | 'json';
|
|
41
|
+
value: string;
|
|
42
|
+
originalBytes: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'image';
|
|
45
|
+
value: unknown;
|
|
46
|
+
}): void;
|
|
35
47
|
onYield(): void;
|
|
36
48
|
}): Promise<{
|
|
37
49
|
readonly stateMutations: readonly CodeModeStateMutation[];
|
|
@@ -8,6 +8,7 @@ import { MarAgentError, describeMarAgentError } from '../../error.js';
|
|
|
8
8
|
import { directLocalProcessExecutor, signalProcessTree } from '../../host/local-process-executor.js';
|
|
9
9
|
import { allowsDirectTool, allowsToolCaller } from '../callers.js';
|
|
10
10
|
import { CodeCells } from './cells.js';
|
|
11
|
+
import { newObservationId, renderObservations } from './observations.js';
|
|
11
12
|
import { parseCodeModeStateMutations } from './state.js';
|
|
12
13
|
import { BoundedTextOutput, renderCapturedText } from './text-output.js';
|
|
13
14
|
import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
|
|
@@ -20,7 +21,7 @@ export const codeModeToolDefinition = {
|
|
|
20
21
|
- Use bounded combinations, parallel independent calls, and filtering of large intermediate results to reduce model round trips.
|
|
21
22
|
- Evaluates source in a fresh restricted ECMAScript runtime with top-level await and Promise.all. It is not Node.js or a browser. JavaScript globals do not survive between cells; bounded JSON state is available through state.
|
|
22
23
|
- Accepts raw JavaScript source text, not JSON, quoted strings, or markdown code fences. Responses uses the source directly; Anthropic uses the code field of its tool input object. In either case, do not quote or fence the source again.
|
|
23
|
-
- Use only standard ECMAScript built-ins and the explicitly documented tools,
|
|
24
|
+
- Use only standard ECMAScript built-ins and the explicitly documented tools, observe, state, catalog and cell namespaces. Do not use Node.js, browser or Web APIs. Module imports, filesystem and network APIs are unavailable. Obtain external capabilities only through available tools. Use tools.exec to start managed processes when permitted; use tools.exec_wait afterward to wait, write stdin or cancel.
|
|
24
25
|
|
|
25
26
|
Tool calls:
|
|
26
27
|
- tools.NAME(input) returns a Promise. Use the existing tool input schema; freeform tools such as apply_patch accept a raw string, not a JSON wrapper.
|
|
@@ -29,25 +30,26 @@ Tool calls:
|
|
|
29
30
|
- skill, tool_search, question, code_exec, code_exec_wait and direct-only tools cannot be called here. Load Skills directly and read their instructions before the next tool call. Use direct tool_search before a later code cell for deferred tools; the current cell's tools do not change.
|
|
30
31
|
|
|
31
32
|
Built-in namespaces:
|
|
32
|
-
-
|
|
33
|
-
-
|
|
33
|
+
- observe.result(result): void registers an unmodified complete tools.NAME result or caught tool error as a trusted tool_result, using the original text and all images without repeating program data. Non-original, copied or modified values are rejected. This selects evidence for the next model response, not a log and not a new model call. Prefer observe.result for inspecting tool results.
|
|
34
|
+
- observe.text(value): void registers bounded text/JSON values. observe.image(value): void registers one image value using the existing capability, MIME, byte and count limits. Both accept original tool results for compatibility and use the same complete tool_result observation path; they do not select part of an original result. Extracted single images, copies and computed summaries are value observations, never trusted tool results. Observations are returned in registration order. Only explicitly observed evidence reaches the model; registration does not prove inspection. Sources and types are runtime-assigned. No out or callable observe(value) alias exists.
|
|
34
35
|
- state.get(key), state.has(key), state.set(key, value), state.delete(key) access bounded session JSON state. Mutations commit atomically only when the cell succeeds; failed or cancelled cells discard them.
|
|
35
36
|
- catalog.tools() returns a read-only snapshot of the tools callable in this cell, including name, description, inputSchema and inputMode. It does not discover or authorize additional tools.
|
|
36
37
|
- await cell.sleep(ms) performs a cancellable Host-side delay up to 30000 ms and remains subject to the cell deadline.
|
|
37
38
|
- await cell.yield_control() returns accumulated output to the model immediately while this cell continues running.
|
|
38
39
|
- Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}, "maxOutputTokens": 10000}. yieldMs defaults to ${limits.codeDefaultYieldMs} ms and is a wait window (0..${limits.codeMaxYieldMs}), not a cell timeout. Explicit values are preserved and do not change nested tools' wait windows. A running result includes cellId; use direct code_exec_wait for new output, completion or termination.
|
|
39
40
|
- Registered cells are Session warm resources: ending or cancelling the model Execution cancels its wait, not the cell. Reuse retained cellId; do not start duplicate work. Host cleanup or explicit termination stops the cell.
|
|
40
|
-
- tools,
|
|
41
|
+
- tools, observe, state, catalog and cell are frozen bindings. Do not shadow their names.
|
|
41
42
|
- Input max 1 MiB; cumulative text capture max 1 MiB with truncation; 128 calls, 120 seconds. Tool-specific limits apply before explicit text capture. Protocol, image, memory and time limits remain hard failures. Failures do not roll back completed tool work; do not replay successful operations blindly.
|
|
42
43
|
|
|
43
44
|
Examples (copy the JavaScript source, not the fences; use only tools available in the current directory):
|
|
44
|
-
Independent reads,
|
|
45
|
+
Independent reads, observing the original tool results:
|
|
45
46
|
\`\`\`js
|
|
46
47
|
const [a, b] = await Promise.all([
|
|
47
48
|
tools.read({ reads: [{ path: "a.txt" }] }),
|
|
48
49
|
tools.read({ reads: [{ path: "b.txt" }] })
|
|
49
50
|
]);
|
|
50
|
-
|
|
51
|
+
observe.result(a);
|
|
52
|
+
observe.result(b);
|
|
51
53
|
\`\`\`
|
|
52
54
|
Inspect before a dependent edit; successful await needs no invented success flag:
|
|
53
55
|
\`\`\`js
|
|
@@ -55,7 +57,7 @@ const inspected = await tools.read({ reads: [{ path: "source.txt" }] });
|
|
|
55
57
|
const sourceText = inspected.data.results[0].text;
|
|
56
58
|
if (!sourceText?.includes("original=1")) throw new Error("Unexpected source");
|
|
57
59
|
const patched = await tools.apply_patch("*** Begin Patch\\n*** Update File: source.txt\\n@@\\n original=1\\n+updated=2\\n*** End Patch");
|
|
58
|
-
|
|
60
|
+
observe.result(patched);
|
|
59
61
|
\`\`\``,
|
|
60
62
|
inputMode: 'freeform',
|
|
61
63
|
inputSchema: {
|
|
@@ -267,6 +269,34 @@ export class CodeModeTool {
|
|
|
267
269
|
});
|
|
268
270
|
const text = new BoundedTextOutput(limits.codeMaxOutputBytes);
|
|
269
271
|
const images = [];
|
|
272
|
+
const observations = [];
|
|
273
|
+
let observationBytes = 0;
|
|
274
|
+
const appendObservation = (value, originalBytes) => {
|
|
275
|
+
const capture = new BoundedTextOutput(Math.max(0, limits.codeMaxOutputBytes - observationBytes), '');
|
|
276
|
+
const body = value.type === 'tool_result' ? value.result.content : value.value;
|
|
277
|
+
capture.append(body);
|
|
278
|
+
const bounded = capture.snapshot();
|
|
279
|
+
observationBytes += bounded.retainedBytes;
|
|
280
|
+
text.append(body, originalBytes ?? Buffer.byteLength(body));
|
|
281
|
+
output?.text(body, originalBytes ?? Buffer.byteLength(body));
|
|
282
|
+
const observation = value.type === 'tool_result'
|
|
283
|
+
? {
|
|
284
|
+
...value,
|
|
285
|
+
result: {
|
|
286
|
+
...value.result,
|
|
287
|
+
content: bounded.text,
|
|
288
|
+
truncated: value.result.truncated === true || bounded.droppedBytes > 0
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
: {
|
|
292
|
+
...value,
|
|
293
|
+
value: bounded.text,
|
|
294
|
+
truncated: value.truncated === true || bounded.droppedBytes > 0
|
|
295
|
+
};
|
|
296
|
+
observations.push(observation);
|
|
297
|
+
output?.observe(observation);
|
|
298
|
+
};
|
|
299
|
+
const toolResults = new Map();
|
|
270
300
|
let transportBytes = 0;
|
|
271
301
|
let calls = 0;
|
|
272
302
|
let complete = false;
|
|
@@ -316,13 +346,94 @@ export class CodeModeTool {
|
|
|
316
346
|
const receive = (frame) => {
|
|
317
347
|
if (complete)
|
|
318
348
|
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent data after completion.');
|
|
319
|
-
if (frame.kind === '
|
|
349
|
+
if (frame.kind === 'observe' && frame.observation && typeof frame.observation === 'object') {
|
|
350
|
+
const value = frame.observation;
|
|
351
|
+
if (observations.length >= 4096)
|
|
352
|
+
throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Too many Code Mode observations.');
|
|
353
|
+
const addImage = (raw) => {
|
|
354
|
+
if (!context.codeMode?.supportsImages)
|
|
355
|
+
throw new MarAgentError('MAR_AGENT_INPUT_CAPABILITY_UNSUPPORTED', 'The current model cannot receive image observations.');
|
|
356
|
+
if (images.length >= 5)
|
|
357
|
+
throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode emitted too many images.');
|
|
358
|
+
const image = parseImage(raw);
|
|
359
|
+
const index = images.length;
|
|
360
|
+
images.push(image);
|
|
361
|
+
output?.image(image);
|
|
362
|
+
return index;
|
|
363
|
+
};
|
|
364
|
+
let observation;
|
|
365
|
+
if (value.type === 'tool_result' && typeof value.operationId === 'string') {
|
|
366
|
+
const original = toolResults.get(value.operationId);
|
|
367
|
+
if (!original)
|
|
368
|
+
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Unknown observation source.');
|
|
369
|
+
const bounded = {
|
|
370
|
+
content: original.result.content,
|
|
371
|
+
truncated: original.result.truncated === true
|
|
372
|
+
};
|
|
373
|
+
const indices = original.result.images?.map(addImage);
|
|
374
|
+
const data = [
|
|
375
|
+
'apply_patch',
|
|
376
|
+
'todo_read',
|
|
377
|
+
'todo_write',
|
|
378
|
+
'agent_start',
|
|
379
|
+
'agent_wait',
|
|
380
|
+
'agent_message',
|
|
381
|
+
'agent_cancel'
|
|
382
|
+
].includes(original.source.toolName)
|
|
383
|
+
? original.result.data
|
|
384
|
+
: undefined;
|
|
385
|
+
observation = {
|
|
386
|
+
type: 'tool_result',
|
|
387
|
+
id: newObservationId(),
|
|
388
|
+
source: original.source,
|
|
389
|
+
outcome: original.outcome,
|
|
390
|
+
result: {
|
|
391
|
+
content: bounded.content,
|
|
392
|
+
truncated: original.result.truncated === true || bounded.truncated,
|
|
393
|
+
...(data === undefined ? {} : { data }),
|
|
394
|
+
...(indices?.length ? { imageIndices: indices } : {})
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
else if (value.type === 'image') {
|
|
399
|
+
observation = {
|
|
400
|
+
type: 'value',
|
|
401
|
+
id: newObservationId(),
|
|
402
|
+
format: 'image',
|
|
403
|
+
value: '',
|
|
404
|
+
imageIndices: [addImage(value.value)]
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
else if (value.type === 'value' &&
|
|
408
|
+
typeof value.value === 'string' &&
|
|
409
|
+
['text', 'json'].includes(String(value.format))) {
|
|
410
|
+
observation = {
|
|
411
|
+
type: 'value',
|
|
412
|
+
id: newObservationId(),
|
|
413
|
+
format: value.format,
|
|
414
|
+
value: value.value,
|
|
415
|
+
truncated: typeof value.originalBytes === 'number' &&
|
|
416
|
+
value.originalBytes > Buffer.byteLength(value.value)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
else
|
|
420
|
+
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Invalid observation frame.');
|
|
421
|
+
appendObservation(observation, value.type === 'value' && typeof value.originalBytes === 'number'
|
|
422
|
+
? value.originalBytes
|
|
423
|
+
: undefined);
|
|
424
|
+
}
|
|
425
|
+
else if (frame.kind === 'text' && typeof frame.value === 'string') {
|
|
320
426
|
const originalBytes = frame.originalBytes ?? Buffer.byteLength(frame.value);
|
|
321
427
|
if (!Number.isSafeInteger(originalBytes) ||
|
|
322
428
|
originalBytes < Buffer.byteLength(frame.value))
|
|
323
429
|
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent invalid text byte counts.');
|
|
324
|
-
|
|
325
|
-
|
|
430
|
+
appendObservation({
|
|
431
|
+
type: 'value',
|
|
432
|
+
id: newObservationId(),
|
|
433
|
+
format: 'text',
|
|
434
|
+
value: frame.value,
|
|
435
|
+
truncated: originalBytes > Buffer.byteLength(frame.value)
|
|
436
|
+
}, originalBytes);
|
|
326
437
|
}
|
|
327
438
|
else if (frame.kind === 'image') {
|
|
328
439
|
if (!context.codeMode?.supportsImages)
|
|
@@ -332,6 +443,13 @@ export class CodeModeTool {
|
|
|
332
443
|
throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode emitted too many images.');
|
|
333
444
|
images.push(image);
|
|
334
445
|
output?.image(image);
|
|
446
|
+
appendObservation({
|
|
447
|
+
type: 'value',
|
|
448
|
+
id: newObservationId(),
|
|
449
|
+
format: 'image',
|
|
450
|
+
value: '',
|
|
451
|
+
imageIndices: [images.length - 1]
|
|
452
|
+
});
|
|
335
453
|
}
|
|
336
454
|
else if (frame.kind === 'yield') {
|
|
337
455
|
output?.yield();
|
|
@@ -343,6 +461,14 @@ export class CodeModeTool {
|
|
|
343
461
|
if (seen.has(id) || ++calls > limits.codeMaxCalls)
|
|
344
462
|
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode call ID or call count is invalid.');
|
|
345
463
|
seen.add(id);
|
|
464
|
+
const source = {
|
|
465
|
+
callId: randomUUID(),
|
|
466
|
+
toolName: frame.name,
|
|
467
|
+
input: frame.arguments
|
|
468
|
+
};
|
|
469
|
+
const operationId = typeof frame.operationId === 'string' ? frame.operationId : String(id);
|
|
470
|
+
const recordCall = (value) => output?.call(value);
|
|
471
|
+
recordCall({ ...source, status: 'running' });
|
|
346
472
|
const job = (async () => {
|
|
347
473
|
try {
|
|
348
474
|
if (frame.name === 'code_exec' || !available.has(frame.name))
|
|
@@ -350,7 +476,27 @@ export class CodeModeTool {
|
|
|
350
476
|
const output = await invoke({
|
|
351
477
|
name: frame.name,
|
|
352
478
|
arguments: frame.arguments,
|
|
353
|
-
callId:
|
|
479
|
+
callId: source.callId
|
|
480
|
+
});
|
|
481
|
+
toolResults.set(operationId, { source, outcome: 'completed', result: output });
|
|
482
|
+
recordCall({
|
|
483
|
+
...source,
|
|
484
|
+
status: 'completed',
|
|
485
|
+
result: {
|
|
486
|
+
content: '',
|
|
487
|
+
truncated: output.truncated === true,
|
|
488
|
+
data: [
|
|
489
|
+
'apply_patch',
|
|
490
|
+
'todo_read',
|
|
491
|
+
'todo_write',
|
|
492
|
+
'agent_start',
|
|
493
|
+
'agent_wait',
|
|
494
|
+
'agent_message',
|
|
495
|
+
'agent_cancel'
|
|
496
|
+
].includes(source.toolName)
|
|
497
|
+
? output.data
|
|
498
|
+
: undefined
|
|
499
|
+
}
|
|
354
500
|
});
|
|
355
501
|
const { content, ...structured } = output;
|
|
356
502
|
const projected = output.data !== undefined && content === JSON.stringify(output.data)
|
|
@@ -359,13 +505,34 @@ export class CodeModeTool {
|
|
|
359
505
|
await write({ id, ok: true, output: projected });
|
|
360
506
|
}
|
|
361
507
|
catch (error) {
|
|
508
|
+
// Serialization/transport failures cannot undo an already completed tool call.
|
|
509
|
+
if (toolResults.get(operationId)?.outcome === 'completed')
|
|
510
|
+
throw error;
|
|
362
511
|
if (context.signal.aborted)
|
|
363
512
|
return;
|
|
364
513
|
const diagnostic = describeMarAgentError(error, 'MAR_AGENT_HOST_OPERATION_FAILED');
|
|
514
|
+
recordCall({
|
|
515
|
+
...source,
|
|
516
|
+
status: 'failed',
|
|
517
|
+
error: { code: diagnostic.code, message: diagnostic.message }
|
|
518
|
+
});
|
|
519
|
+
toolResults.set(operationId, {
|
|
520
|
+
source,
|
|
521
|
+
outcome: 'failed',
|
|
522
|
+
result: {
|
|
523
|
+
content: JSON.stringify({ error: diagnostic.code, message: diagnostic.message })
|
|
524
|
+
}
|
|
525
|
+
});
|
|
365
526
|
await write({
|
|
366
527
|
id,
|
|
367
528
|
ok: false,
|
|
368
|
-
error: {
|
|
529
|
+
error: {
|
|
530
|
+
code: diagnostic.code,
|
|
531
|
+
// The guest carries the code separately; the outer error adds its own prefix.
|
|
532
|
+
message: diagnostic.message.startsWith(`${diagnostic.code}: `)
|
|
533
|
+
? diagnostic.message.slice(diagnostic.code.length + 2)
|
|
534
|
+
: diagnostic.message
|
|
535
|
+
}
|
|
369
536
|
});
|
|
370
537
|
}
|
|
371
538
|
})().catch(fail);
|
|
@@ -470,8 +637,10 @@ export class CodeModeTool {
|
|
|
470
637
|
await context.codeMode.commitState?.(stateMutations);
|
|
471
638
|
const captured = text.snapshot();
|
|
472
639
|
return {
|
|
473
|
-
content:
|
|
474
|
-
|
|
640
|
+
content: observations.length
|
|
641
|
+
? renderObservations(observations)
|
|
642
|
+
: renderCapturedText(captured, images.length) || '[]',
|
|
643
|
+
observations,
|
|
475
644
|
data: {
|
|
476
645
|
toolCalls: calls,
|
|
477
646
|
outputBytes: captured.originalBytes,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Execution metadata is independent of explicit model observations. */
|
|
2
|
+
export interface CodeToolCallRecord {
|
|
3
|
+
callId: string;
|
|
4
|
+
toolName: string;
|
|
5
|
+
input: unknown;
|
|
6
|
+
status: 'running' | 'completed' | 'failed';
|
|
7
|
+
result?: {
|
|
8
|
+
content: string;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
truncated?: boolean;
|
|
11
|
+
};
|
|
12
|
+
error?: {
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare function parseCodeCalls(value: unknown): CodeToolCallRecord[] | undefined;
|
|
18
|
+
export type CodeObservation = {
|
|
19
|
+
type: 'tool_result';
|
|
20
|
+
id: string;
|
|
21
|
+
source: {
|
|
22
|
+
callId: string;
|
|
23
|
+
toolName: string;
|
|
24
|
+
input: unknown;
|
|
25
|
+
};
|
|
26
|
+
outcome: 'completed' | 'failed';
|
|
27
|
+
result: {
|
|
28
|
+
content: string;
|
|
29
|
+
data?: unknown;
|
|
30
|
+
truncated?: boolean;
|
|
31
|
+
imageIndices?: number[];
|
|
32
|
+
};
|
|
33
|
+
} | {
|
|
34
|
+
type: 'value';
|
|
35
|
+
id: string;
|
|
36
|
+
format: 'text' | 'json' | 'image';
|
|
37
|
+
value: string;
|
|
38
|
+
truncated?: boolean;
|
|
39
|
+
imageIndices?: number[];
|
|
40
|
+
};
|
|
41
|
+
export declare function observationContent(value: CodeObservation): string;
|
|
42
|
+
export declare function observationImages(value: CodeObservation): readonly number[];
|
|
43
|
+
export declare function selectObservationImages(values: readonly CodeObservation[], images: readonly {
|
|
44
|
+
mimeType: string;
|
|
45
|
+
dataBase64: string;
|
|
46
|
+
}[], offset?: number): {
|
|
47
|
+
observations: ({
|
|
48
|
+
result: {
|
|
49
|
+
imageIndices: number[];
|
|
50
|
+
content: string;
|
|
51
|
+
data?: unknown;
|
|
52
|
+
truncated?: boolean;
|
|
53
|
+
};
|
|
54
|
+
type: "tool_result";
|
|
55
|
+
id: string;
|
|
56
|
+
source: {
|
|
57
|
+
callId: string;
|
|
58
|
+
toolName: string;
|
|
59
|
+
input: unknown;
|
|
60
|
+
};
|
|
61
|
+
outcome: "completed" | "failed";
|
|
62
|
+
} | {
|
|
63
|
+
imageIndices: number[];
|
|
64
|
+
type: "value";
|
|
65
|
+
id: string;
|
|
66
|
+
format: "text" | "json" | "image";
|
|
67
|
+
value: string;
|
|
68
|
+
truncated?: boolean;
|
|
69
|
+
})[];
|
|
70
|
+
images: {
|
|
71
|
+
mimeType: string;
|
|
72
|
+
dataBase64: string;
|
|
73
|
+
}[];
|
|
74
|
+
};
|
|
75
|
+
/** Only the visible projection crosses into model text; program data and inputs stay private. */
|
|
76
|
+
export declare function visibleObservation(value: CodeObservation): Record<string, unknown>;
|
|
77
|
+
export declare function renderObservations(values: readonly CodeObservation[]): string;
|
|
78
|
+
export declare function renderObservationResult(values: readonly CodeObservation[], original: string): string;
|
|
79
|
+
/** Truncate each body before encoding, never a JSON array or its trusted identity. */
|
|
80
|
+
export declare function boundObservations(values: readonly CodeObservation[], maximumTokens: number): {
|
|
81
|
+
observations: CodeObservation[];
|
|
82
|
+
content: string;
|
|
83
|
+
truncated: boolean;
|
|
84
|
+
};
|
|
85
|
+
export declare function newObservationId(): string;
|
|
86
|
+
export declare function observationModelParts(message: {
|
|
87
|
+
content?: string;
|
|
88
|
+
observations?: readonly CodeObservation[];
|
|
89
|
+
images?: readonly {
|
|
90
|
+
mimeType: string;
|
|
91
|
+
dataBase64: string;
|
|
92
|
+
}[];
|
|
93
|
+
}): Array<{
|
|
94
|
+
type: 'text';
|
|
95
|
+
text: string;
|
|
96
|
+
} | {
|
|
97
|
+
type: 'image';
|
|
98
|
+
image: {
|
|
99
|
+
mimeType: string;
|
|
100
|
+
dataBase64: string;
|
|
101
|
+
};
|
|
102
|
+
}> | undefined;
|
|
103
|
+
export declare function parseObservations(value: unknown): CodeObservation[] | undefined;
|