@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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { truncateModelText, countModelTokens } from '../../runtime/token-budget.js';
|
|
3
|
+
export function parseCodeCalls(value) {
|
|
4
|
+
if (!Array.isArray(value) || value.length > 128)
|
|
5
|
+
return undefined;
|
|
6
|
+
const ids = new Set();
|
|
7
|
+
for (const item of value) {
|
|
8
|
+
if (!item ||
|
|
9
|
+
typeof item !== 'object' ||
|
|
10
|
+
typeof item.callId !== 'string' ||
|
|
11
|
+
!item.callId ||
|
|
12
|
+
ids.has(item.callId) ||
|
|
13
|
+
typeof item.toolName !== 'string' ||
|
|
14
|
+
!['running', 'completed', 'failed'].includes(item.status))
|
|
15
|
+
return undefined;
|
|
16
|
+
if (item.result !== undefined && (!item.result || typeof item.result.content !== 'string'))
|
|
17
|
+
return undefined;
|
|
18
|
+
ids.add(item.callId);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
export function observationContent(value) {
|
|
23
|
+
return value.type === 'tool_result' ? value.result.content : value.value;
|
|
24
|
+
}
|
|
25
|
+
export function observationImages(value) {
|
|
26
|
+
return value.type === 'tool_result'
|
|
27
|
+
? (value.result.imageIndices ?? [])
|
|
28
|
+
: (value.imageIndices ?? []);
|
|
29
|
+
}
|
|
30
|
+
export function selectObservationImages(values, images, offset = 0) {
|
|
31
|
+
const selected = [];
|
|
32
|
+
const indices = new Map();
|
|
33
|
+
const observations = values.map((value) => {
|
|
34
|
+
const remap = observationImages(value).flatMap((index) => {
|
|
35
|
+
const image = images[index - offset];
|
|
36
|
+
if (!image)
|
|
37
|
+
return [];
|
|
38
|
+
let next = indices.get(index);
|
|
39
|
+
if (next === undefined) {
|
|
40
|
+
next = selected.length;
|
|
41
|
+
indices.set(index, next);
|
|
42
|
+
selected.push(image);
|
|
43
|
+
}
|
|
44
|
+
return [next];
|
|
45
|
+
});
|
|
46
|
+
return value.type === 'tool_result'
|
|
47
|
+
? { ...value, result: { ...value.result, imageIndices: remap } }
|
|
48
|
+
: { ...value, imageIndices: remap };
|
|
49
|
+
});
|
|
50
|
+
return { observations, images: selected };
|
|
51
|
+
}
|
|
52
|
+
/** Only the visible projection crosses into model text; program data and inputs stay private. */
|
|
53
|
+
export function visibleObservation(value) {
|
|
54
|
+
return value.type === 'tool_result'
|
|
55
|
+
? {
|
|
56
|
+
type: value.type,
|
|
57
|
+
toolName: value.source.toolName,
|
|
58
|
+
outcome: value.outcome,
|
|
59
|
+
content: value.result.content,
|
|
60
|
+
...(value.result.truncated ? { truncated: true } : {})
|
|
61
|
+
}
|
|
62
|
+
: {
|
|
63
|
+
type: value.type,
|
|
64
|
+
format: value.format,
|
|
65
|
+
value: value.value,
|
|
66
|
+
...(value.truncated ? { truncated: true } : {})
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function renderObservations(values) {
|
|
70
|
+
return JSON.stringify(values.map(visibleObservation));
|
|
71
|
+
}
|
|
72
|
+
export function renderObservationResult(values, original) {
|
|
73
|
+
try {
|
|
74
|
+
const envelope = JSON.parse(original);
|
|
75
|
+
if (envelope && !Array.isArray(envelope) && typeof envelope === 'object')
|
|
76
|
+
return JSON.stringify({ ...envelope, observations: values.map(visibleObservation) });
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
return renderObservations(values);
|
|
80
|
+
}
|
|
81
|
+
/** Truncate each body before encoding, never a JSON array or its trusted identity. */
|
|
82
|
+
export function boundObservations(values, maximumTokens) {
|
|
83
|
+
let remaining = Math.max(0, maximumTokens - 2);
|
|
84
|
+
let truncated = false;
|
|
85
|
+
const observations = [];
|
|
86
|
+
const selected = [];
|
|
87
|
+
let overheadBudget = 0;
|
|
88
|
+
for (const original of values) {
|
|
89
|
+
const empty = original.type === 'tool_result'
|
|
90
|
+
? { ...original, result: { ...original.result, content: '', truncated: true } }
|
|
91
|
+
: { ...original, value: '', truncated: true };
|
|
92
|
+
const overhead = countModelTokens(JSON.stringify(visibleObservation(empty))) + 8;
|
|
93
|
+
if (overheadBudget + overhead > remaining) {
|
|
94
|
+
truncated = true;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
selected.push({ original, overhead });
|
|
98
|
+
overheadBudget += overhead;
|
|
99
|
+
}
|
|
100
|
+
for (const { original, overhead } of selected) {
|
|
101
|
+
let bodyBudget = Math.max(0, remaining - overheadBudget - 12);
|
|
102
|
+
let value;
|
|
103
|
+
let bodyTruncated = false;
|
|
104
|
+
for (;;) {
|
|
105
|
+
const bounded = truncateModelText(observationContent(original), bodyBudget);
|
|
106
|
+
bodyTruncated ||= bounded.truncated;
|
|
107
|
+
value =
|
|
108
|
+
original.type === 'tool_result'
|
|
109
|
+
? {
|
|
110
|
+
...original,
|
|
111
|
+
result: {
|
|
112
|
+
...original.result,
|
|
113
|
+
content: bounded.content,
|
|
114
|
+
truncated: original.result.truncated === true || bodyTruncated
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
: {
|
|
118
|
+
...original,
|
|
119
|
+
value: bounded.content,
|
|
120
|
+
truncated: original.truncated === true || bodyTruncated
|
|
121
|
+
};
|
|
122
|
+
if (countModelTokens(JSON.stringify(visibleObservation(value))) + 1 <=
|
|
123
|
+
remaining - (overheadBudget - overhead) ||
|
|
124
|
+
bodyBudget === 0)
|
|
125
|
+
break;
|
|
126
|
+
bodyBudget = Math.floor(bodyBudget / 2);
|
|
127
|
+
}
|
|
128
|
+
overheadBudget -= overhead;
|
|
129
|
+
observations.push(value);
|
|
130
|
+
remaining -= countModelTokens(JSON.stringify(visibleObservation(value))) + 1;
|
|
131
|
+
truncated ||=
|
|
132
|
+
bodyTruncated ||
|
|
133
|
+
(original.type === 'tool_result'
|
|
134
|
+
? original.result.truncated === true
|
|
135
|
+
: original.truncated === true);
|
|
136
|
+
}
|
|
137
|
+
return { observations, content: renderObservations(observations), truncated };
|
|
138
|
+
}
|
|
139
|
+
export function newObservationId() {
|
|
140
|
+
return randomUUID();
|
|
141
|
+
}
|
|
142
|
+
export function observationModelParts(message) {
|
|
143
|
+
if (!message.observations || !message.images?.length)
|
|
144
|
+
return undefined;
|
|
145
|
+
const prefix = [];
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(message.content ?? '');
|
|
148
|
+
if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
|
|
149
|
+
const { observations: _observations, ...metadata } = parsed;
|
|
150
|
+
prefix.push({ type: 'text', text: JSON.stringify(metadata) });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch { }
|
|
154
|
+
return [
|
|
155
|
+
...prefix,
|
|
156
|
+
...message.observations.flatMap((value) => {
|
|
157
|
+
const parts = [{ type: 'text', text: JSON.stringify(visibleObservation(value)) }];
|
|
158
|
+
for (const index of observationImages(value)) {
|
|
159
|
+
const image = message.images?.[index];
|
|
160
|
+
if (image)
|
|
161
|
+
parts.push({ type: 'image', image });
|
|
162
|
+
}
|
|
163
|
+
return parts;
|
|
164
|
+
})
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
export function parseObservations(value) {
|
|
168
|
+
if (!Array.isArray(value) || value.length > 4096)
|
|
169
|
+
return undefined;
|
|
170
|
+
const ids = new Set();
|
|
171
|
+
for (const item of value) {
|
|
172
|
+
if (!item || typeof item !== 'object' || typeof item.id !== 'string' || ids.has(item.id))
|
|
173
|
+
return undefined;
|
|
174
|
+
ids.add(item.id);
|
|
175
|
+
if (item.type === 'tool_result') {
|
|
176
|
+
if (!item.source ||
|
|
177
|
+
typeof item.source.callId !== 'string' ||
|
|
178
|
+
typeof item.source.toolName !== 'string' ||
|
|
179
|
+
!['completed', 'failed'].includes(item.outcome) ||
|
|
180
|
+
!item.result ||
|
|
181
|
+
typeof item.result.content !== 'string')
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
else if (item.type !== 'value' ||
|
|
185
|
+
!['text', 'json', 'image'].includes(item.format) ||
|
|
186
|
+
typeof item.value !== 'string')
|
|
187
|
+
return undefined;
|
|
188
|
+
const indices = item.type === 'tool_result' ? item.result.imageIndices : item.imageIndices;
|
|
189
|
+
if (indices !== undefined &&
|
|
190
|
+
(!Array.isArray(indices) ||
|
|
191
|
+
indices.some((i) => !Number.isSafeInteger(i) || Number(i) < 0 || Number(i) >= 5)))
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
@@ -29,18 +29,22 @@ export async function createCodeModeEngine(options) {
|
|
|
29
29
|
const stateMutations = [];
|
|
30
30
|
let outputBytes = 0;
|
|
31
31
|
let textCalls = 0;
|
|
32
|
+
const origins = [];
|
|
32
33
|
const notify = () => {
|
|
33
34
|
wake?.();
|
|
34
35
|
wake = undefined;
|
|
35
36
|
};
|
|
36
|
-
const fail = (handle) => {
|
|
37
|
+
const fail = (handle, locateSource = false) => {
|
|
37
38
|
const error = vm.dump(handle);
|
|
38
39
|
handle.dispose();
|
|
39
40
|
if (input.signal.aborted)
|
|
40
41
|
throw input.signal.reason;
|
|
41
42
|
if (Date.now() >= deadline)
|
|
42
43
|
throw diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its deadline.');
|
|
43
|
-
|
|
44
|
+
let message = String(error?.message ?? error).slice(0, 4096);
|
|
45
|
+
const location = locateSource ? compilerSourceLocation(error, input.code) : '';
|
|
46
|
+
if (location)
|
|
47
|
+
message = message.slice(0, 4096 - location.length) + location;
|
|
44
48
|
const code = message.includes('out of memory')
|
|
45
49
|
? 'MAR_AGENT_CODE_MEMORY_LIMIT'
|
|
46
50
|
: typeof error?.code === 'string'
|
|
@@ -72,6 +76,28 @@ export async function createCodeModeEngine(options) {
|
|
|
72
76
|
});
|
|
73
77
|
};
|
|
74
78
|
globalThis.__text = ${streamText.toString()};
|
|
79
|
+
globalThis.__snapshot = (() => {
|
|
80
|
+
const keys = Reflect.ownKeys, descriptor = Object.getOwnPropertyDescriptor;
|
|
81
|
+
const create = Object.create, stringify = JSON.stringify;
|
|
82
|
+
return (value, errorSnapshot) => {
|
|
83
|
+
const seen = create(null); let depth = 0;
|
|
84
|
+
function walk(item) {
|
|
85
|
+
if (item === null || typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') return stringify(item);
|
|
86
|
+
if (typeof item !== 'object') throw new Error('Not an original JSON object.');
|
|
87
|
+
for (let i = 0; i < depth; i++) if (seen[i] === item) throw new Error('Cyclic object.');
|
|
88
|
+
seen[depth++] = item;
|
|
89
|
+
const names = keys(item); let text = '{';
|
|
90
|
+
for (let i = 0; i < names.length; i++) {
|
|
91
|
+
const name = names[i], field = descriptor(item, name);
|
|
92
|
+
if (errorSnapshot && depth === 1 && name !== 'code' && name !== 'message' && name !== 'name') continue;
|
|
93
|
+
if (typeof name !== 'string' || !field || !('value' in field)) throw new Error('Accessor object.');
|
|
94
|
+
text += stringify(name) + ':' + Number(field.enumerable) + Number(field.writable) + Number(field.configurable) + walk(field.value) + ';';
|
|
95
|
+
}
|
|
96
|
+
depth--; return text + '}';
|
|
97
|
+
}
|
|
98
|
+
try { return walk(value); } catch { return ''; }
|
|
99
|
+
};
|
|
100
|
+
})();
|
|
75
101
|
globalThis.__deepFreeze = function deepFreeze(value) {
|
|
76
102
|
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
77
103
|
for (const item of Object.values(value)) deepFreeze(item);
|
|
@@ -86,6 +112,20 @@ export async function createCodeModeEngine(options) {
|
|
|
86
112
|
const parseJson = vm.getProp(vm.global, '__parseJson');
|
|
87
113
|
const serialize = vm.getProp(vm.global, '__serialize');
|
|
88
114
|
const formatText = vm.getProp(vm.global, '__text');
|
|
115
|
+
const snapshotFunction = vm.getProp(vm.global, '__snapshot');
|
|
116
|
+
const snapshot = (handle, errorSnapshot = false) => {
|
|
117
|
+
const value = vm.callFunction(snapshotFunction, vm.undefined, handle, errorSnapshot ? vm.true : vm.false);
|
|
118
|
+
if (value.error) {
|
|
119
|
+
value.error.dispose();
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
return vm.getString(value.value);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
value.value.dispose();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
89
129
|
const deepFreeze = vm.getProp(vm.global, '__deepFreeze');
|
|
90
130
|
const parse = (value) => {
|
|
91
131
|
const handle = vm.newString(JSON.stringify(value));
|
|
@@ -142,11 +182,17 @@ export async function createCodeModeEngine(options) {
|
|
|
142
182
|
const deferred = vm.newPromise();
|
|
143
183
|
deferreds.add(deferred);
|
|
144
184
|
const job = Promise.resolve()
|
|
145
|
-
.then(() => input.onToolCall({ name, arguments: parsed.value }))
|
|
185
|
+
.then(() => input.onToolCall({ name, arguments: parsed.value, callId: String(operationId) }))
|
|
146
186
|
.then((output) => {
|
|
147
187
|
if (disposed || input.signal.aborted)
|
|
148
188
|
return;
|
|
149
189
|
const handle = parse(output);
|
|
190
|
+
origins.push({
|
|
191
|
+
handle: handle.dup(),
|
|
192
|
+
snapshot: snapshot(handle),
|
|
193
|
+
errorSnapshot: false,
|
|
194
|
+
operationId: String(operationId)
|
|
195
|
+
});
|
|
150
196
|
try {
|
|
151
197
|
deferred.resolve(handle);
|
|
152
198
|
}
|
|
@@ -163,6 +209,12 @@ export async function createCodeModeEngine(options) {
|
|
|
163
209
|
const code = vm.newString(String(error?.code ?? 'MAR_AGENT_HOST_OPERATION_FAILED'));
|
|
164
210
|
vm.setProp(handle, 'code', code);
|
|
165
211
|
code.dispose();
|
|
212
|
+
origins.push({
|
|
213
|
+
handle: handle.dup(),
|
|
214
|
+
snapshot: snapshot(handle, true),
|
|
215
|
+
errorSnapshot: true,
|
|
216
|
+
operationId: String(operationId)
|
|
217
|
+
});
|
|
166
218
|
try {
|
|
167
219
|
deferred.reject(handle);
|
|
168
220
|
}
|
|
@@ -198,8 +250,28 @@ export async function createCodeModeEngine(options) {
|
|
|
198
250
|
});
|
|
199
251
|
vm.setProp(vm.global, '__consumeToolCall', consumeToolCall);
|
|
200
252
|
consumeToolCall.dispose();
|
|
201
|
-
const
|
|
202
|
-
|
|
253
|
+
const observeValue = (handle, kind) => {
|
|
254
|
+
for (const origin of origins) {
|
|
255
|
+
if (handle &&
|
|
256
|
+
vm.eq(handle, origin.handle) &&
|
|
257
|
+
origin.snapshot &&
|
|
258
|
+
snapshot(handle, origin.errorSnapshot) === origin.snapshot) {
|
|
259
|
+
input.onObserve({ type: 'tool_result', operationId: origin.operationId });
|
|
260
|
+
return vm.undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (kind === 'result')
|
|
264
|
+
return {
|
|
265
|
+
error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'observe.result requires an unmodified original tool result or error.')
|
|
266
|
+
};
|
|
267
|
+
// A standalone image is a value, never an original tool-result observation.
|
|
268
|
+
if (kind === 'image') {
|
|
269
|
+
const parsed = jsonValue(handle);
|
|
270
|
+
if (parsed.error)
|
|
271
|
+
return parsed;
|
|
272
|
+
input.onObserve({ type: 'image', value: parsed.value });
|
|
273
|
+
return vm.undefined;
|
|
274
|
+
}
|
|
203
275
|
const separatorBytes = textCalls ? 1 : 0;
|
|
204
276
|
const capture = new BoundedTextOutput(Math.max(0, input.limits.outputBytes - outputBytes - separatorBytes), '');
|
|
205
277
|
const emit = vm.newFunction('textChunk', (chunk) => {
|
|
@@ -225,22 +297,23 @@ export async function createCodeModeEngine(options) {
|
|
|
225
297
|
const captured = capture.snapshot();
|
|
226
298
|
outputBytes += captured.retainedBytes + separatorBytes;
|
|
227
299
|
textCalls++;
|
|
228
|
-
input.
|
|
300
|
+
input.onObserve({
|
|
301
|
+
type: 'value',
|
|
302
|
+
format: handle && vm.typeof(handle) === 'object' ? 'json' : 'text',
|
|
303
|
+
value: captured.text,
|
|
304
|
+
originalBytes: captured.originalBytes
|
|
305
|
+
});
|
|
229
306
|
}
|
|
230
307
|
finally {
|
|
231
308
|
result.value.dispose();
|
|
232
309
|
}
|
|
233
310
|
return vm.undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return vm.undefined;
|
|
241
|
-
});
|
|
242
|
-
vm.setProp(vm.global, 'out', out);
|
|
243
|
-
out.dispose();
|
|
311
|
+
};
|
|
312
|
+
const observe = vm.newObject();
|
|
313
|
+
for (const kind of ['result', 'text', 'image'])
|
|
314
|
+
method(observe, kind, (handle) => observeValue(handle, kind));
|
|
315
|
+
vm.setProp(vm.global, 'observe', observe);
|
|
316
|
+
observe.dispose();
|
|
244
317
|
const stateObject = vm.newObject();
|
|
245
318
|
method(stateObject, 'get', (keyHandle) => {
|
|
246
319
|
const parsed = key(keyHandle);
|
|
@@ -384,7 +457,7 @@ export async function createCodeModeEngine(options) {
|
|
|
384
457
|
{ code: 'MAR_AGENT_TOOL_NOT_AVAILABLE' }); };
|
|
385
458
|
return target[name];
|
|
386
459
|
}});
|
|
387
|
-
for (const name of ['tools', '
|
|
460
|
+
for (const name of ['tools', 'observe', 'state', 'catalog', 'cell']) {
|
|
388
461
|
const value = globalThis[name];
|
|
389
462
|
Object.freeze(value);
|
|
390
463
|
Object.defineProperty(globalThis, name, {
|
|
@@ -394,6 +467,7 @@ export async function createCodeModeEngine(options) {
|
|
|
394
467
|
delete globalThis.__parseJson;
|
|
395
468
|
delete globalThis.__serialize;
|
|
396
469
|
delete globalThis.__text;
|
|
470
|
+
delete globalThis.__snapshot;
|
|
397
471
|
delete globalThis.__deepFreeze;
|
|
398
472
|
delete globalThis.__consumeToolCall;
|
|
399
473
|
`);
|
|
@@ -405,7 +479,7 @@ export async function createCodeModeEngine(options) {
|
|
|
405
479
|
try {
|
|
406
480
|
const evaluated = vm.evalCode(`(async () => {\n${input.code}\n})()`, 'code-mode.js');
|
|
407
481
|
if (evaluated.error)
|
|
408
|
-
fail(evaluated.error);
|
|
482
|
+
fail(evaluated.error, true);
|
|
409
483
|
main = evaluated.value;
|
|
410
484
|
for (;;) {
|
|
411
485
|
input.signal.throwIfAborted();
|
|
@@ -449,6 +523,9 @@ export async function createCodeModeEngine(options) {
|
|
|
449
523
|
parseJson.dispose();
|
|
450
524
|
serialize.dispose();
|
|
451
525
|
formatText.dispose();
|
|
526
|
+
snapshotFunction.dispose();
|
|
527
|
+
for (const origin of origins)
|
|
528
|
+
origin.handle.dispose();
|
|
452
529
|
deepFreeze.dispose();
|
|
453
530
|
}
|
|
454
531
|
},
|
|
@@ -528,7 +605,7 @@ function streamText(value, emit) {
|
|
|
528
605
|
emit('{');
|
|
529
606
|
let first = true;
|
|
530
607
|
const names = Object.keys(item);
|
|
531
|
-
// Special-case view_image and native MCP image objects: text omits Base64;
|
|
608
|
+
// Special-case view_image and native MCP image objects: text omits Base64; observe.image keeps pixels.
|
|
532
609
|
const imagePayload = names.includes('mimeType') &&
|
|
533
610
|
typeof item.mimeType === 'string' &&
|
|
534
611
|
item.mimeType.startsWith('image/');
|
|
@@ -542,7 +619,7 @@ function streamText(value, emit) {
|
|
|
542
619
|
write(imagePayload &&
|
|
543
620
|
(name === 'dataBase64' || (name === 'data' && item.type === 'image')) &&
|
|
544
621
|
typeof field === 'string'
|
|
545
|
-
? '[image payload omitted; use
|
|
622
|
+
? '[image payload omitted; use observe.image]'
|
|
546
623
|
: field, name);
|
|
547
624
|
}
|
|
548
625
|
emit('}');
|
|
@@ -551,6 +628,30 @@ function streamText(value, emit) {
|
|
|
551
628
|
};
|
|
552
629
|
write(value, '');
|
|
553
630
|
}
|
|
631
|
+
function compilerSourceLocation(error, source) {
|
|
632
|
+
if (error?.name !== 'SyntaxError' ||
|
|
633
|
+
error.fileName !== 'code-mode.js' ||
|
|
634
|
+
!Number.isSafeInteger(error.lineNumber) ||
|
|
635
|
+
error.lineNumber <= 1)
|
|
636
|
+
return '';
|
|
637
|
+
// QuickJS counts LF lines and Unicode code-point columns; the wrapper adds one LF line.
|
|
638
|
+
const lines = source.split('\n');
|
|
639
|
+
const line = error.lineNumber - 1;
|
|
640
|
+
if (line > lines.length)
|
|
641
|
+
return ` (code-mode.js:${source.split(/\r\n|[\r\n\u2028\u2029]/u).length}, end of input)`;
|
|
642
|
+
const column = Number(typeof error.stack === 'string'
|
|
643
|
+
? error.stack.match(/^\s*at code-mode\.js:\d+:(\d+)(?:\n|$)/u)?.[1]
|
|
644
|
+
: undefined);
|
|
645
|
+
if (!Number.isSafeInteger(column) || column < 1)
|
|
646
|
+
return '';
|
|
647
|
+
const prefix = [
|
|
648
|
+
...lines.slice(0, line - 1),
|
|
649
|
+
Array.from(lines[line - 1])
|
|
650
|
+
.slice(0, column - 1)
|
|
651
|
+
.join('')
|
|
652
|
+
].join('\n');
|
|
653
|
+
return ` (code-mode.js:${prefix.split(/\r\n|[\r\n\u2028\u2029]/u).length})`;
|
|
654
|
+
}
|
|
554
655
|
function diagnostic(code, message) {
|
|
555
656
|
return Object.assign(new Error(message), { code });
|
|
556
657
|
}
|
|
@@ -34,10 +34,17 @@ lines.on('line', async (line) => {
|
|
|
34
34
|
onText: (value, originalBytes) => send({ kind: 'text', value, originalBytes }),
|
|
35
35
|
onImage: (value) => send({ kind: 'image', value }),
|
|
36
36
|
onYield: () => send({ kind: 'yield' }),
|
|
37
|
+
onObserve: (value) => send({ kind: 'observe', observation: value }),
|
|
37
38
|
onToolCall: (call) => new Promise((resolve, reject) => {
|
|
38
39
|
const id = ++sequence;
|
|
39
40
|
pending.set(id, { resolve, reject });
|
|
40
|
-
send({
|
|
41
|
+
send({
|
|
42
|
+
kind: 'call',
|
|
43
|
+
id,
|
|
44
|
+
operationId: call.callId,
|
|
45
|
+
name: call.name,
|
|
46
|
+
arguments: call.arguments
|
|
47
|
+
});
|
|
41
48
|
})
|
|
42
49
|
});
|
|
43
50
|
if (pending.size)
|
|
@@ -81,7 +81,9 @@ export interface MarAgentToolImageArtifact {
|
|
|
81
81
|
}
|
|
82
82
|
export type MarAgentToolArtifact = MarAgentToolImageArtifact;
|
|
83
83
|
export interface ToolExecutionResult {
|
|
84
|
+
calls?: readonly import('./code-mode/observations.js').CodeToolCallRecord[];
|
|
84
85
|
content: string;
|
|
86
|
+
observations?: readonly import('./code-mode/observations.js').CodeObservation[];
|
|
85
87
|
data?: unknown;
|
|
86
88
|
/** Runtime-only definitions used to encode a Tool Search output. */
|
|
87
89
|
toolSearchTools?: readonly ClientToolDefinition[];
|
|
@@ -93,9 +95,11 @@ export interface ToolExecutionResult {
|
|
|
93
95
|
artifacts?: readonly MarAgentToolArtifact[];
|
|
94
96
|
}
|
|
95
97
|
export interface CodeCellEventData {
|
|
98
|
+
calls?: readonly import('./code-mode/observations.js').CodeToolCallRecord[];
|
|
96
99
|
cellId: string;
|
|
97
100
|
status: 'running' | 'completed' | 'failed' | 'cancelled';
|
|
98
101
|
cached?: boolean;
|
|
102
|
+
observations?: readonly import('./code-mode/observations.js').CodeObservation[];
|
|
99
103
|
}
|
|
100
104
|
export interface ExecResult {
|
|
101
105
|
status: 'running' | 'completed' | 'cancelled';
|
|
@@ -10,6 +10,8 @@ export type ExecutedToolCall = {
|
|
|
10
10
|
call: ClientToolCall;
|
|
11
11
|
content: string;
|
|
12
12
|
images?: ToolExecutionResult['images'];
|
|
13
|
+
observations?: ToolExecutionResult['observations'];
|
|
14
|
+
calls?: ToolExecutionResult['calls'];
|
|
13
15
|
} & ({
|
|
14
16
|
failed: false;
|
|
15
17
|
output: ToolExecutionResult;
|
|
@@ -68,6 +68,8 @@ export async function executeRoundTools(calls, scope) {
|
|
|
68
68
|
call,
|
|
69
69
|
output,
|
|
70
70
|
content: modelOutput.content,
|
|
71
|
+
...(output.calls ? { calls: output.calls } : {}),
|
|
72
|
+
...(output.observations ? { observations: output.observations } : {}),
|
|
71
73
|
...(output.images?.length ? { images: output.images } : {}),
|
|
72
74
|
failed: false
|
|
73
75
|
};
|
|
@@ -81,7 +83,12 @@ export async function executeRoundTools(calls, scope) {
|
|
|
81
83
|
callId: call.callId,
|
|
82
84
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
83
85
|
code: diagnostic.code,
|
|
84
|
-
message: diagnostic.message
|
|
86
|
+
message: diagnostic.message,
|
|
87
|
+
...(partial?.data === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: {
|
|
90
|
+
data: structuredToolEventData(call.name, partial.data)
|
|
91
|
+
})
|
|
85
92
|
});
|
|
86
93
|
return {
|
|
87
94
|
call,
|
|
@@ -94,6 +101,8 @@ export async function executeRoundTools(calls, scope) {
|
|
|
94
101
|
: boundedModelToolOutput(call.name, partial.content, partial.data).content,
|
|
95
102
|
...(partial?.images?.length ? { images: partial.images } : {}),
|
|
96
103
|
failed: true,
|
|
104
|
+
...(partial?.calls ? { calls: partial.calls } : {}),
|
|
105
|
+
...(partial?.observations ? { observations: partial.observations } : {}),
|
|
97
106
|
error
|
|
98
107
|
};
|
|
99
108
|
}
|
|
@@ -14,6 +14,10 @@ export function currentExecutionImages(modelInputImages, toolImages) {
|
|
|
14
14
|
.map((image) => ({ mimeType: image.mimeType, data: Buffer.from(image.data) }));
|
|
15
15
|
}
|
|
16
16
|
export function boundedModelToolOutput(toolName, value, data) {
|
|
17
|
+
if ((toolName === 'code_exec' || toolName === 'code_exec_wait') &&
|
|
18
|
+
isRecord(data) &&
|
|
19
|
+
Array.isArray(data.observations))
|
|
20
|
+
return { content: value, truncated: data.truncated === true };
|
|
17
21
|
const execDiagnostics = (toolName === 'exec' || toolName === 'exec_wait') &&
|
|
18
22
|
isRecord(data) &&
|
|
19
23
|
typeof data.stdout === 'string' &&
|
|
@@ -36,7 +40,37 @@ export function structuredToolEventData(toolName, value) {
|
|
|
36
40
|
return {
|
|
37
41
|
cellId: value.cellId,
|
|
38
42
|
status: value.status,
|
|
39
|
-
cached: value.cached === true
|
|
43
|
+
cached: value.cached === true,
|
|
44
|
+
...(Array.isArray(value.calls)
|
|
45
|
+
? {
|
|
46
|
+
calls: value.calls.filter((entry) => isRecord(entry) &&
|
|
47
|
+
[
|
|
48
|
+
'apply_patch',
|
|
49
|
+
'todo_read',
|
|
50
|
+
'todo_write',
|
|
51
|
+
'agent_start',
|
|
52
|
+
'agent_wait',
|
|
53
|
+
'agent_message',
|
|
54
|
+
'agent_cancel'
|
|
55
|
+
].includes(String(entry.toolName)))
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
...(Array.isArray(value.observations)
|
|
59
|
+
? {
|
|
60
|
+
observations: value.observations.filter((entry) => isRecord(entry) &&
|
|
61
|
+
entry.type === 'tool_result' &&
|
|
62
|
+
isRecord(entry.source) &&
|
|
63
|
+
[
|
|
64
|
+
'apply_patch',
|
|
65
|
+
'todo_read',
|
|
66
|
+
'todo_write',
|
|
67
|
+
'agent_start',
|
|
68
|
+
'agent_wait',
|
|
69
|
+
'agent_message',
|
|
70
|
+
'agent_cancel'
|
|
71
|
+
].includes(String(entry.source.toolName)))
|
|
72
|
+
}
|
|
73
|
+
: {})
|
|
40
74
|
};
|
|
41
75
|
if ((toolName === 'agent_start' ||
|
|
42
76
|
toolName === 'agent_wait' ||
|