melchizedek-agents 1.6.0 → 1.7.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/CHANGELOG.md +83 -0
- package/DOCUMENTATION.md +5 -1
- package/config/agents/syndicate.schema.json +63 -0
- package/config/agents/syndicateSchema.yaml +50 -0
- package/dist/lib/a2a/executor.js +21 -0
- package/dist/lib/compile.d.ts +4 -3
- package/dist/lib/compile.js +8 -3
- package/dist/lib/index.d.ts +8 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/loadSyndicate.d.ts +13 -0
- package/dist/lib/loadSyndicate.js +11 -1
- package/dist/lib/observability/supabaseSpanExporter.js +2 -1
- package/dist/lib/observability/tracer.d.ts +2 -2
- package/dist/lib/observability/tracer.js +2 -2
- package/dist/lib/runtime/native/checkpoint.d.ts +2 -1
- package/dist/lib/runtime/native/checkpoint.js +6 -2
- package/dist/lib/runtime/native/interrupts.js +9 -2
- package/dist/lib/runtime/nativeTurn.d.ts +2 -1
- package/dist/lib/runtime/nativeTurn.js +5 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +11 -1
- package/dist/lib/runtime/syndicateTurn.js +383 -5
- package/dist/lib/sandbox/sandbox.d.ts +121 -0
- package/dist/lib/sandbox/sandbox.js +276 -0
- package/dist/lib/sandbox/worker.d.ts +29 -0
- package/dist/lib/sandbox/worker.js +280 -0
- package/dist/lib/script/config.d.ts +35 -0
- package/dist/lib/script/config.js +17 -0
- package/dist/lib/script/journal.d.ts +137 -0
- package/dist/lib/script/journal.js +193 -0
- package/dist/lib/script/run.d.ts +138 -0
- package/dist/lib/script/run.js +364 -0
- package/dist/lib/syndicateSchema.d.ts +20 -0
- package/dist/lib/syndicateSchema.js +98 -6
- package/dist/lib/toolRegistry.js +4 -0
- package/dist/lib/tools/runCodeTool.d.ts +50 -0
- package/dist/lib/tools/runCodeTool.js +59 -0
- package/package.json +5 -1
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/script/run.ts — one run of a syndicate's `script:` (ADR 0137 Part B):
|
|
3
|
+
* the script in the sandbox, its calls journaled, a paused or interrupted
|
|
4
|
+
* run resumed by replaying them.
|
|
5
|
+
*
|
|
6
|
+
* WHAT THE SCRIPT IS PASSED (item 6): `message`, `transcript` and
|
|
7
|
+
* `variables`, and the functions `agent`, `tool`, `askUser`, `now`,
|
|
8
|
+
* `random`, `log`, plus the sandbox's `parallel`. Nothing else reaches it.
|
|
9
|
+
* The host (lib/runtime/syndicateTurn.ts) supplies what `agent` and `tool`
|
|
10
|
+
* do; this module owns the journal, the replay, the pauses and the spans.
|
|
11
|
+
*
|
|
12
|
+
* REPLAY (item 9). A paused run's next message runs the script again from
|
|
13
|
+
* the top, on the source, message and transcript the run pinned. Each call
|
|
14
|
+
* is answered from the journal while one is recorded for it: a call is
|
|
15
|
+
* matched on its kind, its name and the hash of its arguments, the first
|
|
16
|
+
* unconsumed entry that matches, so concurrency under `parallel` (whose
|
|
17
|
+
* calls start in an order that depends on what finished first) replays the
|
|
18
|
+
* same. The open pause takes the person's answer; the calls after it run
|
|
19
|
+
* live. A run that ends with recorded calls it never made again diverged:
|
|
20
|
+
* it fails SCRIPT_DIVERGED, naming the first. Replayed calls are never
|
|
21
|
+
* charged again, open no span, and log() lines are not repeated while the
|
|
22
|
+
* run replays.
|
|
23
|
+
*
|
|
24
|
+
* THE PAUSES (items 8 and 10). A live askUser, and a tool() call to a tool
|
|
25
|
+
* in `script.require_approval`, let the calls already in flight settle,
|
|
26
|
+
* store the run's journal and the pause, then halt the sandbox; a call
|
|
27
|
+
* started after it waits forever and dies with the run, so a second pause
|
|
28
|
+
* is reached only once the first is answered. A question's answer is the
|
|
29
|
+
* next message (a JSON reply to a question with a schema is parsed, plain
|
|
30
|
+
* text passes as is, as an ask_user node's). An approval's answer is the
|
|
31
|
+
* decision: approved, the pinned arguments run; refused, the script gets
|
|
32
|
+
* `{ error }`, as a refused tool node's output is.
|
|
33
|
+
*
|
|
34
|
+
* WHEN THE JOURNAL IS STORED. Only a paused run reads it, so by default it
|
|
35
|
+
* is stored with the pause and a run that finishes stores none (on the
|
|
36
|
+
* Supabase store every append rewrites the session's events). A durable
|
|
37
|
+
* run's store (JOURNAL_EVERY_CALL, lib/runtime/native/checkpoint.ts) takes
|
|
38
|
+
* each event as its call ends, the first carrying the pin, and treats each
|
|
39
|
+
* as a checkpoint boundary, so a worker killed mid-script resumes from its
|
|
40
|
+
* last call. A resumed run that ends, and a durable run that finishes,
|
|
41
|
+
* store an `end` marker.
|
|
42
|
+
*
|
|
43
|
+
* THE SPANS (item 16). `script.invoke <name>` around the run and
|
|
44
|
+
* `script.call <kind>:<name>` around each live call, in their own trace,
|
|
45
|
+
* with no argument and no result on them; each agent() call's own turn
|
|
46
|
+
* span carries `script.run` and `script.call.seq` (the host adds them), so
|
|
47
|
+
* a trace view pairs them.
|
|
48
|
+
*/
|
|
49
|
+
import { randomUUID } from 'node:crypto';
|
|
50
|
+
import { ROOT_CONTEXT, SpanStatusCode, trace } from '@opentelemetry/api';
|
|
51
|
+
import { getFunctionCalls } from '../runtime/events.js';
|
|
52
|
+
import { approvalRequestOf } from '../runtime/approvals.js';
|
|
53
|
+
import { RUNTIME_SPAN_SCOPE } from '../observability/lineage.js';
|
|
54
|
+
import { startEngineSpan } from '../observability/tracer.js';
|
|
55
|
+
import { SandboxHalt, runInSandbox } from '../sandbox/sandbox.js';
|
|
56
|
+
import { APPROVAL_TEXTS } from '../tools/tool.js';
|
|
57
|
+
import { inputRequestFrom } from '../workflowConfig.js';
|
|
58
|
+
import { approvalEvent, callEvent, callHash, endEvent, pauseEvent, sourceHash } from './journal.js';
|
|
59
|
+
// A paused run's journal, for a client that shows it (melch-app's journal view): the reader and its shapes.
|
|
60
|
+
export { openRun, scriptPauseIn, SCRIPT_CALL, SCRIPT_PAUSE, SCRIPT_RUN } from './journal.js';
|
|
61
|
+
/** The most calls one run may journal (item 13), as a walk has NODE_RUN_LIMIT. */
|
|
62
|
+
export const SCRIPT_CALL_LIMIT = 1_000;
|
|
63
|
+
/** Fatal: thrown by a host function to fail the turn with a code. */
|
|
64
|
+
export class ScriptFatal extends SandboxHalt {
|
|
65
|
+
code;
|
|
66
|
+
constructor(code, message) {
|
|
67
|
+
super(message, { code });
|
|
68
|
+
this.code = code;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const never = () => new Promise(() => { });
|
|
72
|
+
function replayed(outcome) {
|
|
73
|
+
if ('error' in outcome) {
|
|
74
|
+
const err = new Error(outcome.error.message);
|
|
75
|
+
err.name = outcome.error.name;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
return outcome.value;
|
|
79
|
+
}
|
|
80
|
+
function describe(entry) {
|
|
81
|
+
return `${entry.kind}(${JSON.stringify(entry.name)}) recorded as call ${entry.seq}`;
|
|
82
|
+
}
|
|
83
|
+
/** A question's reply: parsed when the question declared a schema and the reply is JSON, else the text. */
|
|
84
|
+
function replyOf(answer, schema) {
|
|
85
|
+
if (!schema)
|
|
86
|
+
return answer;
|
|
87
|
+
const text = answer.trim();
|
|
88
|
+
if (!/^[[{"]|^-?\d|^(true|false|null)$/.test(text))
|
|
89
|
+
return answer;
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(text);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return answer;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export async function runScript(input, host) {
|
|
98
|
+
const run = input.resume?.run ?? `r-${randomUUID()}`;
|
|
99
|
+
const pending = input.resume ? input.resume.entries.filter((e) => e.outcome || e.pause) : [];
|
|
100
|
+
let nextSeq = (input.resume?.entries ?? []).reduce((max, e) => Math.max(max, e.seq), 0) + 1;
|
|
101
|
+
const limit = input.callLimit ?? SCRIPT_CALL_LIMIT;
|
|
102
|
+
const inflight = new Set();
|
|
103
|
+
const askedIds = new Set();
|
|
104
|
+
const pin = { run, source: input.source, source_hash: sourceHash(input.source), message: input.message, transcript: input.transcript };
|
|
105
|
+
// A run stored call by call pins itself on its first event; a resumed one already did.
|
|
106
|
+
let pinStored = !!input.resume;
|
|
107
|
+
let calls = 0;
|
|
108
|
+
let replayedCalls = 0;
|
|
109
|
+
let pausing = false;
|
|
110
|
+
let paused;
|
|
111
|
+
let approval;
|
|
112
|
+
let answeredPause = false;
|
|
113
|
+
/** Journal events not yet stored: all of them until a pause, unless the host takes each as its call ends. */
|
|
114
|
+
const journal = [];
|
|
115
|
+
const flush = async () => {
|
|
116
|
+
for (const event of journal.splice(0))
|
|
117
|
+
await host.append(event);
|
|
118
|
+
};
|
|
119
|
+
const record = async (event) => {
|
|
120
|
+
journal.push(event);
|
|
121
|
+
if (host.journalEveryCall)
|
|
122
|
+
await flush();
|
|
123
|
+
};
|
|
124
|
+
const outcomeEvent = (call, outcome) => {
|
|
125
|
+
const withPin = host.journalEveryCall && !pinStored;
|
|
126
|
+
if (withPin)
|
|
127
|
+
pinStored = true;
|
|
128
|
+
return callEvent(host.script, host.invocationId, call, outcome, withPin ? pin : undefined);
|
|
129
|
+
};
|
|
130
|
+
// ── Spans ──────────────────────────────────────────────────────────────────
|
|
131
|
+
const invokeSpan = startEngineSpan(RUNTIME_SPAN_SCOPE, `script.invoke ${host.script}`, {
|
|
132
|
+
root: true,
|
|
133
|
+
attributes: {
|
|
134
|
+
'gen_ai.operation.name': 'invoke_script',
|
|
135
|
+
'syndicate.name': host.script,
|
|
136
|
+
'script.name': host.script,
|
|
137
|
+
'script.run': run,
|
|
138
|
+
'script.source_hash': pin.source_hash,
|
|
139
|
+
'script.resumed': !!input.resume,
|
|
140
|
+
'adk.invocation_id': host.invocationId,
|
|
141
|
+
...(host.sessionId ? { 'session.id': host.sessionId, 'gen_ai.conversation.id': host.sessionId } : {}),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
const parent = trace.setSpan(ROOT_CONTEXT, invokeSpan);
|
|
145
|
+
const callSpan = (call) => startEngineSpan(RUNTIME_SPAN_SCOPE, `script.call ${call.kind}:${call.name}`, {
|
|
146
|
+
attributes: {
|
|
147
|
+
'gen_ai.operation.name': 'execute_script_call',
|
|
148
|
+
'syndicate.name': host.script,
|
|
149
|
+
'script.name': host.script,
|
|
150
|
+
'script.run': run,
|
|
151
|
+
'script.call.kind': call.kind,
|
|
152
|
+
'script.call.name': call.name,
|
|
153
|
+
'script.call.seq': call.seq,
|
|
154
|
+
...(host.sessionId ? { 'session.id': host.sessionId } : {}),
|
|
155
|
+
},
|
|
156
|
+
}, parent);
|
|
157
|
+
const endCall = (span, status) => {
|
|
158
|
+
span.setAttribute('script.call.status', status);
|
|
159
|
+
if (status === 'error')
|
|
160
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
161
|
+
span.end();
|
|
162
|
+
};
|
|
163
|
+
/** The first recorded call with this kind, name and hash, removed from the journal still to replay. */
|
|
164
|
+
const take = (kind, name, hash) => {
|
|
165
|
+
const at = pending.findIndex((e) => e.kind === kind && e.name === name && e.hash === hash);
|
|
166
|
+
return at === -1 ? undefined : pending.splice(at, 1)[0];
|
|
167
|
+
};
|
|
168
|
+
const count = () => {
|
|
169
|
+
if (++calls > limit)
|
|
170
|
+
throw new ScriptFatal('SCRIPT_CALL_LIMIT', `The script made more than ${limit} calls in one run.`);
|
|
171
|
+
};
|
|
172
|
+
const track = (running) => {
|
|
173
|
+
inflight.add(running);
|
|
174
|
+
running.then(() => inflight.delete(running), () => inflight.delete(running));
|
|
175
|
+
return running;
|
|
176
|
+
};
|
|
177
|
+
/** Runs `exec` as a live call: its span, its journal event, its outcome thrown or returned into the script. */
|
|
178
|
+
const live = (record_, exec) => track((async () => {
|
|
179
|
+
const span = callSpan(record_);
|
|
180
|
+
let outcome;
|
|
181
|
+
try {
|
|
182
|
+
outcome = { value: (await exec()) ?? null };
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
if (err instanceof SandboxHalt || host.signal.aborted) {
|
|
186
|
+
endCall(span, 'error');
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
190
|
+
outcome = { error: { name: e.name || 'Error', message: e.message } };
|
|
191
|
+
}
|
|
192
|
+
endCall(span, 'error' in outcome ? 'error' : 'completed');
|
|
193
|
+
await record(outcomeEvent(record_, outcome));
|
|
194
|
+
return replayed(outcome);
|
|
195
|
+
})());
|
|
196
|
+
/** A call: replayed from the journal when recorded, else run and journaled. */
|
|
197
|
+
const call = (kind, name, args, exec) => {
|
|
198
|
+
if (pausing)
|
|
199
|
+
return never();
|
|
200
|
+
count();
|
|
201
|
+
const hash = callHash(kind, name, args);
|
|
202
|
+
const recorded = take(kind, name, hash);
|
|
203
|
+
if (recorded?.outcome) {
|
|
204
|
+
replayedCalls++;
|
|
205
|
+
return Promise.resolve().then(() => replayed(recorded.outcome));
|
|
206
|
+
}
|
|
207
|
+
const rec = { run, seq: nextSeq++, kind, name, hash };
|
|
208
|
+
return live(rec, () => exec({ run, seq: rec.seq }));
|
|
209
|
+
};
|
|
210
|
+
/** The run pauses here: the calls in flight settle and are stored, then the journal and the pause, then the sandbox halts. */
|
|
211
|
+
const pause = async (rec, event) => {
|
|
212
|
+
pausing = true;
|
|
213
|
+
await Promise.allSettled([...inflight]);
|
|
214
|
+
const interruptId = (host.newInterruptId ?? randomUUID)();
|
|
215
|
+
await host.beforePause?.();
|
|
216
|
+
await flush();
|
|
217
|
+
const stored = event(interruptId);
|
|
218
|
+
await host.append(stored);
|
|
219
|
+
endCall(callSpan(rec), 'waiting');
|
|
220
|
+
throw new SandboxHalt('waiting for the person');
|
|
221
|
+
};
|
|
222
|
+
const askUser = async (id, question, opts) => {
|
|
223
|
+
if (typeof id !== 'string' || !id)
|
|
224
|
+
throw new TypeError('askUser(id, question, { payload?, schema? }): id must be a non-empty string, unique within the run');
|
|
225
|
+
if (typeof question !== 'string' || !question)
|
|
226
|
+
throw new TypeError('askUser(id, question, …): question must be a non-empty string');
|
|
227
|
+
const o = opts && typeof opts === 'object' ? opts : {};
|
|
228
|
+
const schema = o.schema && typeof o.schema === 'object' ? o.schema : undefined;
|
|
229
|
+
if (askedIds.has(id))
|
|
230
|
+
throw new Error(`askUser: the id '${id}' was already asked in this run; each question needs its own id`);
|
|
231
|
+
askedIds.add(id);
|
|
232
|
+
const args = { question, payload: o.payload ?? null, schema: schema ?? null };
|
|
233
|
+
const hash = callHash('askUser', id, args);
|
|
234
|
+
const recorded = take('askUser', id, hash);
|
|
235
|
+
if (recorded?.outcome) {
|
|
236
|
+
replayedCalls++;
|
|
237
|
+
return replayed(recorded.outcome);
|
|
238
|
+
}
|
|
239
|
+
if (recorded?.pause && input.resume?.answer !== undefined && recorded.pause.kind !== 'approval') {
|
|
240
|
+
// The question the run waits on: this turn's message is the answer.
|
|
241
|
+
const value = { reply: replyOf(input.resume.answer, schema), input: args.payload };
|
|
242
|
+
await record(callEvent(host.script, host.invocationId, { run, seq: recorded.seq, kind: 'askUser', name: id, hash }, { value }));
|
|
243
|
+
answeredPause = true;
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
if (pausing)
|
|
247
|
+
return never();
|
|
248
|
+
count();
|
|
249
|
+
const rec = { run, seq: nextSeq++, kind: 'askUser', name: id, hash };
|
|
250
|
+
return pause(rec, (interruptId) => {
|
|
251
|
+
const event = pauseEvent(host.script, host.invocationId, rec, { ...pin, interrupt_id: interruptId, kind: 'question' }, {
|
|
252
|
+
message: question,
|
|
253
|
+
...(o.payload !== undefined ? { payload: o.payload } : {}),
|
|
254
|
+
...(schema ? { schema } : {}),
|
|
255
|
+
});
|
|
256
|
+
const [request] = getFunctionCalls(event);
|
|
257
|
+
paused = inputRequestFrom(host.script, request);
|
|
258
|
+
return event;
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const tool = async (name, args) => {
|
|
262
|
+
if (typeof name !== 'string' || !name)
|
|
263
|
+
throw new TypeError('tool(name, args): name must be a tool in script.tools');
|
|
264
|
+
const a = args ?? {};
|
|
265
|
+
if (!host.gated?.(name))
|
|
266
|
+
return call('tool', name, a, (site) => host.tool(name, a, site));
|
|
267
|
+
// A gated tool (`script.require_approval`): a person approves the exact call first.
|
|
268
|
+
if (pausing)
|
|
269
|
+
return never();
|
|
270
|
+
count();
|
|
271
|
+
const hash = callHash('tool', name, a);
|
|
272
|
+
const recorded = take('tool', name, hash);
|
|
273
|
+
if (recorded?.outcome) {
|
|
274
|
+
replayedCalls++;
|
|
275
|
+
return replayed(recorded.outcome);
|
|
276
|
+
}
|
|
277
|
+
if (recorded?.pause && input.resume?.decision && recorded.pause.kind === 'approval') {
|
|
278
|
+
answeredPause = true;
|
|
279
|
+
const rec = { run, seq: recorded.seq, kind: 'tool', name, hash };
|
|
280
|
+
if (input.resume.decision.approved)
|
|
281
|
+
return live(rec, () => host.tool(name, a, { run, seq: rec.seq }));
|
|
282
|
+
const refused = { error: APPROVAL_TEXTS.rejected };
|
|
283
|
+
endCall(callSpan(rec), 'refused');
|
|
284
|
+
await record(callEvent(host.script, host.invocationId, rec, { value: refused }));
|
|
285
|
+
return refused;
|
|
286
|
+
}
|
|
287
|
+
const rec = { run, seq: nextSeq++, kind: 'tool', name, hash };
|
|
288
|
+
return pause(rec, (interruptId) => {
|
|
289
|
+
const event = approvalEvent(host.script, host.invocationId, rec, { ...pin, interrupt_id: interruptId, kind: 'approval' }, { tool: name, args: a, hint: APPROVAL_TEXTS.hint(name) });
|
|
290
|
+
const [request] = getFunctionCalls(event);
|
|
291
|
+
approval = approvalRequestOf(event, request);
|
|
292
|
+
return event;
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const sandboxed = await runInSandbox({
|
|
296
|
+
source: input.source,
|
|
297
|
+
mode: 'api',
|
|
298
|
+
value: { message: input.message, transcript: input.transcript, variables: input.variables },
|
|
299
|
+
signal: host.signal,
|
|
300
|
+
limits: { ...input.limits },
|
|
301
|
+
functions: {
|
|
302
|
+
agent: ([name, agentInput, opts]) => {
|
|
303
|
+
if (typeof name !== 'string' || !name)
|
|
304
|
+
throw new TypeError('agent(name, input, opts?): name must be a declared agent\'s name');
|
|
305
|
+
const o = opts === undefined || opts === null ? {} : opts;
|
|
306
|
+
if (typeof o !== 'object' || Array.isArray(o))
|
|
307
|
+
throw new TypeError('agent(name, input, opts?): opts must be an object');
|
|
308
|
+
return call('agent', name, { input: agentInput ?? null, opts: o }, (site) => host.agent(name, agentInput ?? null, o, site));
|
|
309
|
+
},
|
|
310
|
+
tool: ([name, args]) => tool(name, args),
|
|
311
|
+
askUser: ([id, question, opts]) => askUser(id, question, opts),
|
|
312
|
+
now: () => call('now', 'now', null, async () => Date.now()),
|
|
313
|
+
random: () => call('random', 'random', null, async () => Math.random()),
|
|
314
|
+
log: ([text]) => {
|
|
315
|
+
// A replayed run already said these.
|
|
316
|
+
if (!pending.length)
|
|
317
|
+
host.log(typeof text === 'string' ? text : JSON.stringify(text));
|
|
318
|
+
return null;
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
const logs = sandboxed.logs;
|
|
323
|
+
const waiting = !!(paused || approval);
|
|
324
|
+
// A resumed run, or one stored call by call, that ended (not one the turn's signal stopped) is closed, so the next message starts a new one.
|
|
325
|
+
if (!waiting && !host.signal.aborted && (input.resume || (host.journalEveryCall && pinStored))) {
|
|
326
|
+
await flush();
|
|
327
|
+
await host.append(endEvent(host.script, host.invocationId, run, nextSeq++));
|
|
328
|
+
}
|
|
329
|
+
const unreplayed = () => {
|
|
330
|
+
const left = pending[0];
|
|
331
|
+
return left ? { code: 'SCRIPT_DIVERGED', message: `The resumed run did not make the call its journal holds: ${describe(left)}. A script must make the same calls, with the same arguments, on every run (ADR 0137 item 9).` } : undefined;
|
|
332
|
+
};
|
|
333
|
+
const finish = (outcome) => {
|
|
334
|
+
invokeSpan.setAttributes({ 'script.status': outcome.status, 'script.calls': calls, 'script.replayed_calls': replayedCalls });
|
|
335
|
+
if (outcome.status === 'failed') {
|
|
336
|
+
invokeSpan.setAttribute('script.error_code', outcome.error.code);
|
|
337
|
+
invokeSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
338
|
+
}
|
|
339
|
+
invokeSpan.end();
|
|
340
|
+
return outcome;
|
|
341
|
+
};
|
|
342
|
+
if (waiting) {
|
|
343
|
+
const diverged = unreplayed();
|
|
344
|
+
if (diverged)
|
|
345
|
+
return finish({ status: 'failed', run, error: diverged, logs });
|
|
346
|
+
return finish(approval ? { status: 'approval', run, approval, logs } : { status: 'paused', run, input: paused, logs });
|
|
347
|
+
}
|
|
348
|
+
if (sandboxed.halt) {
|
|
349
|
+
const code = sandboxed.halt instanceof ScriptFatal ? sandboxed.halt.code : 'SCRIPT_HALTED';
|
|
350
|
+
return finish({ status: 'failed', run, error: { code, message: sandboxed.halt.message }, logs });
|
|
351
|
+
}
|
|
352
|
+
if (!sandboxed.ok) {
|
|
353
|
+
const error = sandboxed.error;
|
|
354
|
+
return finish({ status: 'failed', run, error: { code: error.code === 'CODE_ERROR' ? 'SCRIPT_ERROR' : error.code, message: error.message }, logs });
|
|
355
|
+
}
|
|
356
|
+
const awaited = input.resume?.answer !== undefined || input.resume?.decision !== undefined;
|
|
357
|
+
if (awaited && !answeredPause) {
|
|
358
|
+
return finish({ status: 'failed', run, error: { code: 'SCRIPT_DIVERGED', message: 'The resumed run never reached the pause it was waiting on.' }, logs });
|
|
359
|
+
}
|
|
360
|
+
const diverged = unreplayed();
|
|
361
|
+
if (diverged)
|
|
362
|
+
return finish({ status: 'failed', run, error: diverged, logs });
|
|
363
|
+
return finish({ status: 'completed', run, value: sandboxed.result, logs });
|
|
364
|
+
}
|
|
@@ -225,6 +225,9 @@ export declare const agentSchema: z.ZodObject<{
|
|
|
225
225
|
}, z.core.$strict>>;
|
|
226
226
|
}, z.core.$strict>>>;
|
|
227
227
|
code_execution: z.ZodOptional<z.ZodLiteral<"gemini">>;
|
|
228
|
+
run_code: z.ZodOptional<z.ZodObject<{
|
|
229
|
+
attach: z.ZodOptional<z.ZodBoolean>;
|
|
230
|
+
}, z.core.$strict>>;
|
|
228
231
|
context: z.ZodOptional<z.ZodObject<{
|
|
229
232
|
compact_after_tokens: z.ZodNumber;
|
|
230
233
|
keep_recent_events: z.ZodOptional<z.ZodNumber>;
|
|
@@ -439,6 +442,9 @@ export declare const subagentSchema: z.ZodObject<{
|
|
|
439
442
|
}, z.core.$strict>>;
|
|
440
443
|
}, z.core.$strict>>>;
|
|
441
444
|
code_execution: z.ZodOptional<z.ZodLiteral<"gemini">>;
|
|
445
|
+
run_code: z.ZodOptional<z.ZodObject<{
|
|
446
|
+
attach: z.ZodOptional<z.ZodBoolean>;
|
|
447
|
+
}, z.core.$strict>>;
|
|
442
448
|
context: z.ZodOptional<z.ZodObject<{
|
|
443
449
|
compact_after_tokens: z.ZodNumber;
|
|
444
450
|
keep_recent_events: z.ZodOptional<z.ZodNumber>;
|
|
@@ -462,6 +468,8 @@ export declare const subagentSchema: z.ZodObject<{
|
|
|
462
468
|
yaml_reference: z.ZodOptional<z.ZodString>;
|
|
463
469
|
a2a_agent_url: z.ZodOptional<z.ZodString>;
|
|
464
470
|
}, z.core.$strict>;
|
|
471
|
+
/** The largest `script.source` a syndicate may carry. */
|
|
472
|
+
export declare const MAX_SCRIPT_SOURCE_CHARS: number;
|
|
465
473
|
export declare const syndicateSchema: z.ZodObject<{
|
|
466
474
|
syndicate_name: z.ZodString;
|
|
467
475
|
orchestrator: z.ZodObject<{
|
|
@@ -657,6 +665,9 @@ export declare const syndicateSchema: z.ZodObject<{
|
|
|
657
665
|
}, z.core.$strict>>;
|
|
658
666
|
}, z.core.$strict>>>;
|
|
659
667
|
code_execution: z.ZodOptional<z.ZodLiteral<"gemini">>;
|
|
668
|
+
run_code: z.ZodOptional<z.ZodObject<{
|
|
669
|
+
attach: z.ZodOptional<z.ZodBoolean>;
|
|
670
|
+
}, z.core.$strict>>;
|
|
660
671
|
context: z.ZodOptional<z.ZodObject<{
|
|
661
672
|
compact_after_tokens: z.ZodNumber;
|
|
662
673
|
keep_recent_events: z.ZodOptional<z.ZodNumber>;
|
|
@@ -865,6 +876,9 @@ export declare const syndicateSchema: z.ZodObject<{
|
|
|
865
876
|
}, z.core.$strict>>;
|
|
866
877
|
}, z.core.$strict>>>;
|
|
867
878
|
code_execution: z.ZodOptional<z.ZodLiteral<"gemini">>;
|
|
879
|
+
run_code: z.ZodOptional<z.ZodObject<{
|
|
880
|
+
attach: z.ZodOptional<z.ZodBoolean>;
|
|
881
|
+
}, z.core.$strict>>;
|
|
868
882
|
context: z.ZodOptional<z.ZodObject<{
|
|
869
883
|
compact_after_tokens: z.ZodNumber;
|
|
870
884
|
keep_recent_events: z.ZodOptional<z.ZodNumber>;
|
|
@@ -921,6 +935,12 @@ export declare const syndicateSchema: z.ZodObject<{
|
|
|
921
935
|
}, z.core.$strict>>>;
|
|
922
936
|
max_concurrency: z.ZodOptional<z.ZodNumber>;
|
|
923
937
|
}, z.core.$strict>>;
|
|
938
|
+
script: z.ZodOptional<z.ZodObject<{
|
|
939
|
+
source: z.ZodString;
|
|
940
|
+
models: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
941
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
942
|
+
require_approval: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
943
|
+
}, z.core.$strict>>;
|
|
924
944
|
retries: z.ZodOptional<z.ZodObject<{
|
|
925
945
|
model_errors: z.ZodOptional<z.ZodNumber>;
|
|
926
946
|
tool_errors: z.ZodOptional<z.ZodNumber>;
|
|
@@ -410,6 +410,12 @@ const agentFields = {
|
|
|
410
410
|
.literal('gemini')
|
|
411
411
|
.optional()
|
|
412
412
|
.describe('"gemini": the model writes and runs Python in Gemini\'s server-side sandbox; nothing runs on this host. Gemini models only.'),
|
|
413
|
+
run_code: z
|
|
414
|
+
.strictObject({
|
|
415
|
+
attach: z.boolean().optional().describe('Put each run\'s code and result on the reply as a code_run data part. Default false.'),
|
|
416
|
+
})
|
|
417
|
+
.optional()
|
|
418
|
+
.describe('How this agent\'s run_code tool behaves (ADR 0137). Needs run_code in `tools`.'),
|
|
413
419
|
context: z
|
|
414
420
|
.strictObject({
|
|
415
421
|
compact_after_tokens: z.number().int().min(1000).describe('Compact when the last request\'s prompt passed this many tokens.'),
|
|
@@ -533,6 +539,31 @@ const workflowSchema = z
|
|
|
533
539
|
max_concurrency: z.number().int().positive().optional().describe('Nodes that may run at once. Default: unbounded.'),
|
|
534
540
|
})
|
|
535
541
|
.describe('Opts into a WORKFLOW: the syndicate as a graph (lib/workflow.ts). Cannot be combined with dispatch.');
|
|
542
|
+
// ── Script (ADR 0137) ────────────────────────────────────────────────────────
|
|
543
|
+
/** The largest `script.source` a syndicate may carry. */
|
|
544
|
+
export const MAX_SCRIPT_SOURCE_CHARS = 64 * 1024;
|
|
545
|
+
const scriptSchema = z
|
|
546
|
+
.strictObject({
|
|
547
|
+
source: z
|
|
548
|
+
.string()
|
|
549
|
+
.min(1)
|
|
550
|
+
.max(MAX_SCRIPT_SOURCE_CHARS)
|
|
551
|
+
.describe('One JavaScript module whose default export is an async function of { message, variables, agent, tool, askUser, parallel, now, random, log }: the orchestration. Its return value is the answer. {{token}} interpolation does not touch it; it reads `variables`.'),
|
|
552
|
+
models: z
|
|
553
|
+
.array(z.string().min(1))
|
|
554
|
+
.max(16)
|
|
555
|
+
.optional()
|
|
556
|
+
.describe('The models agent(name, input, { model }) may move a call to; each agent is compiled and checked on every one at load.'),
|
|
557
|
+
tools: z
|
|
558
|
+
.array(z.string().min(1))
|
|
559
|
+
.optional()
|
|
560
|
+
.describe('The registry tools tool(name, args) may run; the only path from the script to the network or the host.'),
|
|
561
|
+
require_approval: z
|
|
562
|
+
.array(z.string().min(1))
|
|
563
|
+
.optional()
|
|
564
|
+
.describe('Tools from script.tools whose every call waits for a person to approve the exact call (ADR 0028): the turn ends input-required; approved, the pinned arguments run, refused, the script gets { error }.'),
|
|
565
|
+
})
|
|
566
|
+
.describe('Opts into SCRIPT orchestration (lib/script/): the syndicate\'s logic as code run in the engine\'s sandbox. Needs MELCHIZEDEK_CODE_SANDBOX=on. Cannot be combined with dispatch or workflow.');
|
|
536
567
|
// ── Syndicate ────────────────────────────────────────────────────────────────
|
|
537
568
|
export const syndicateSchema = z
|
|
538
569
|
.strictObject({
|
|
@@ -544,6 +575,7 @@ export const syndicateSchema = z
|
|
|
544
575
|
.describe('The orchestrator\'s team. Omit it (or write `subagents: []`) for a single-agent syndicate.'),
|
|
545
576
|
dispatch: dispatchSchema.optional(),
|
|
546
577
|
workflow: workflowSchema.optional(),
|
|
578
|
+
script: scriptSchema.optional(),
|
|
547
579
|
retries: z
|
|
548
580
|
.strictObject({
|
|
549
581
|
model_errors: z.number().int().min(0).max(5).optional().describe('Retries of a malformed model reply. Default 2; 0 off.'),
|
|
@@ -667,6 +699,8 @@ function knownKeysAt(p) {
|
|
|
667
699
|
return Object.keys(dispatchSchema.shape);
|
|
668
700
|
case 'dispatch/route_overrides/#':
|
|
669
701
|
return Object.keys(routeOverride.shape);
|
|
702
|
+
case 'script':
|
|
703
|
+
return Object.keys(scriptSchema.shape);
|
|
670
704
|
case 'orchestrator/orchestration':
|
|
671
705
|
case 'subagents/#/orchestration':
|
|
672
706
|
return ['role', 'delegates'];
|
|
@@ -901,14 +935,23 @@ function crossFieldProblems(raw) {
|
|
|
901
935
|
if (agent.code_execution !== undefined && model && !/^gemini-/.test(model)) {
|
|
902
936
|
out.push({ path: [...path, 'code_execution'], message: `code_execution: gemini needs a gemini-* model (this agent's is ${model})` });
|
|
903
937
|
}
|
|
904
|
-
if (agent.context !== undefined && (!isOrchestrator || isObj(raw.dispatch) || isObj(raw.workflow))) {
|
|
938
|
+
if (agent.context !== undefined && (!isOrchestrator || isObj(raw.dispatch) || isObj(raw.workflow) || isObj(raw.script))) {
|
|
905
939
|
out.push({
|
|
906
940
|
path: [...path, 'context'],
|
|
907
|
-
message: 'context compaction applies to the orchestrator of a delegate syndicate: a dispatch route reads a bounded projection, a workflow node sees only its input, and a subagent starts fresh on every call',
|
|
941
|
+
message: 'context compaction applies to the orchestrator of a delegate syndicate: a dispatch route reads a bounded projection, a workflow node sees only its input, a script\'s agent() call starts fresh, and a subagent starts fresh on every call',
|
|
908
942
|
});
|
|
909
943
|
}
|
|
910
|
-
if (agent.mode !== undefined && !isObj(raw.workflow)) {
|
|
911
|
-
out.push({ path: [...path, 'mode'], message: 'mode: task applies to workflow nodes, whose output is the finish_task arguments' });
|
|
944
|
+
if (agent.mode !== undefined && !isObj(raw.workflow) && !isObj(raw.script)) {
|
|
945
|
+
out.push({ path: [...path, 'mode'], message: 'mode: task applies to workflow nodes and script agents, whose output is the finish_task arguments' });
|
|
946
|
+
}
|
|
947
|
+
// run_code (ADR 0137): the key configures the tool, so the tool must be listed; a nested or remote agent sets its own.
|
|
948
|
+
if (agent.run_code !== undefined) {
|
|
949
|
+
if (typeof agent.yaml_reference === 'string' || typeof agent.a2a_agent_url === 'string') {
|
|
950
|
+
out.push({ path: [...path, 'run_code'], message: 'applies to an inline agent; a nested syndicate (yaml_reference) or a remote agent (a2a_agent_url) sets its own' });
|
|
951
|
+
}
|
|
952
|
+
else if (!(Array.isArray(agent.tools) && agent.tools.includes('run_code'))) {
|
|
953
|
+
out.push({ path: [...path, 'run_code'], message: 'configures the run_code tool, which this agent\'s tools do not list; add run_code to tools' });
|
|
954
|
+
}
|
|
912
955
|
}
|
|
913
956
|
if (agent.mcp_tools !== undefined && typeof agent.mcp_server_url !== 'string') {
|
|
914
957
|
out.push({ path: [...path, 'mcp_tools'], message: 'mcp_tools chooses among an MCP server\'s tools; it needs mcp_server_url' });
|
|
@@ -927,12 +970,14 @@ function crossFieldProblems(raw) {
|
|
|
927
970
|
if (isObj(raw.orchestrator))
|
|
928
971
|
executionProblems(raw.orchestrator, ['orchestrator'], true);
|
|
929
972
|
// max_concurrency bounds an orchestrator's delegated calls (ADR 0116): a workflow bounds its nodes under workflow:, and a dispatch classifier delegates nothing.
|
|
930
|
-
if (raw.max_concurrency !== undefined && (isObj(raw.workflow) || isObj(raw.dispatch))) {
|
|
973
|
+
if (raw.max_concurrency !== undefined && (isObj(raw.workflow) || isObj(raw.dispatch) || isObj(raw.script))) {
|
|
931
974
|
out.push({
|
|
932
975
|
path: ['max_concurrency'],
|
|
933
976
|
message: isObj(raw.workflow)
|
|
934
977
|
? 'max_concurrency at the root bounds a delegate orchestrator\'s subagent calls; a workflow bounds its nodes with workflow.max_concurrency'
|
|
935
|
-
:
|
|
978
|
+
: isObj(raw.script)
|
|
979
|
+
? 'max_concurrency at the root bounds a delegate orchestrator\'s subagent calls; a script bounds its own with parallel(thunks, { max })'
|
|
980
|
+
: 'max_concurrency bounds a delegate orchestrator\'s subagent calls; a dispatch syndicate\'s classifier delegates nothing (set it in a route\'s own syndicate file)',
|
|
936
981
|
});
|
|
937
982
|
}
|
|
938
983
|
for (const [i, sub] of (Array.isArray(raw.subagents) ? raw.subagents : []).entries())
|
|
@@ -1024,6 +1069,8 @@ function crossFieldProblems(raw) {
|
|
|
1024
1069
|
});
|
|
1025
1070
|
if (isObj(raw.workflow))
|
|
1026
1071
|
out.push(...workflowProblems(raw, subs));
|
|
1072
|
+
if (isObj(raw.script))
|
|
1073
|
+
out.push(...scriptProblems(raw, subs));
|
|
1027
1074
|
// Names must be unique across the tree this file declares: two AgentTools
|
|
1028
1075
|
// with one name collide, and dispatch routes by name.
|
|
1029
1076
|
const seen = new Map();
|
|
@@ -1210,6 +1257,51 @@ function workflowProblems(raw, subs) {
|
|
|
1210
1257
|
return out;
|
|
1211
1258
|
}
|
|
1212
1259
|
/** Where an agent declares an authorization_code grant (mcp_auth, mcp_servers[].auth, openapi[].auth), by its path below the agent. */
|
|
1260
|
+
/**
|
|
1261
|
+
* The rules a `script:` syndicate keeps (ADR 0137), which a JSON Schema
|
|
1262
|
+
* cannot say: it is one orchestration method, not three; a pause inside a
|
|
1263
|
+
* called agent cannot be replayed in this version (item 12), so the gates,
|
|
1264
|
+
* the ask_user tool, skill scripts and per-user OAuth consent are refused on
|
|
1265
|
+
* its agents by name, and the script's own askUser is its pause; and an
|
|
1266
|
+
* agent that runs Gemini's code execution can be moved only to Gemini models.
|
|
1267
|
+
*/
|
|
1268
|
+
function scriptProblems(raw, subs) {
|
|
1269
|
+
const out = [];
|
|
1270
|
+
const script = raw.script;
|
|
1271
|
+
if (isObj(raw.dispatch))
|
|
1272
|
+
out.push({ path: ['script'], message: 'a syndicate orchestrates with a script or a plan-dispatch router, not both; remove `dispatch` (the script can route)' });
|
|
1273
|
+
if (isObj(raw.workflow))
|
|
1274
|
+
out.push({ path: ['script'], message: 'a syndicate orchestrates with a script or a workflow graph, not both; remove `workflow`' });
|
|
1275
|
+
const allowed = Array.isArray(script.tools) ? script.tools : [];
|
|
1276
|
+
(Array.isArray(script.require_approval) ? script.require_approval : []).forEach((name, j) => {
|
|
1277
|
+
if (typeof name === 'string' && !allowed.includes(name))
|
|
1278
|
+
out.push({ path: ['script', 'require_approval', j], message: `'${name}' is not in script.tools` });
|
|
1279
|
+
});
|
|
1280
|
+
const models = Array.isArray(script.models) ? script.models.filter((m) => typeof m === 'string') : [];
|
|
1281
|
+
const agents = [];
|
|
1282
|
+
if (isObj(raw.orchestrator))
|
|
1283
|
+
agents.push([['orchestrator'], raw.orchestrator]);
|
|
1284
|
+
subs.forEach((sub, i) => isObj(sub) && agents.push([['subagents', i], sub]));
|
|
1285
|
+
const pausesInside = 'a pause inside an agent a script calls cannot be resumed in this version (ADR 0137 item 12); pause in the script with askUser';
|
|
1286
|
+
for (const [path, agent] of agents) {
|
|
1287
|
+
if (agent.require_approval !== undefined)
|
|
1288
|
+
out.push({ path: [...path, 'require_approval'], message: pausesInside });
|
|
1289
|
+
const tools = Array.isArray(agent.tools) ? agent.tools : [];
|
|
1290
|
+
const asks = tools.indexOf('ask_user');
|
|
1291
|
+
if (asks !== -1)
|
|
1292
|
+
out.push({ path: [...path, 'tools', asks], message: pausesInside });
|
|
1293
|
+
if (isObj(agent.skills) && agent.skills.scripts === 'local')
|
|
1294
|
+
out.push({ path: [...path, 'skills', 'scripts'], message: `skill scripts pause for approval; ${pausesInside}` });
|
|
1295
|
+
for (const grant of authorizationCodeGrants(agent))
|
|
1296
|
+
out.push({ path: [...path, ...grant], message: `an authorization_code grant pauses for the person's consent; ${pausesInside}` });
|
|
1297
|
+
if (agent.code_execution !== undefined) {
|
|
1298
|
+
const other = models.find((m) => !/^gemini-/.test(m));
|
|
1299
|
+
if (other)
|
|
1300
|
+
out.push({ path: [...path, 'code_execution'], message: `code_execution: gemini needs a gemini-* model, and script.models lets agent() move this agent to ${other}` });
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return out;
|
|
1304
|
+
}
|
|
1213
1305
|
function authorizationCodeGrants(agent) {
|
|
1214
1306
|
const out = [];
|
|
1215
1307
|
const isCode = (auth) => isObj(auth) && isObj(auth.oauth2) && auth.oauth2.grant === 'authorization_code';
|
package/dist/lib/toolRegistry.js
CHANGED
|
@@ -24,6 +24,7 @@ import { SCIENCE_TOOL_CONTRACTS } from './tools/scienceTools.js';
|
|
|
24
24
|
import { TASK_TOOL_CONTRACTS } from './tools/taskTools.js';
|
|
25
25
|
import { askUserTool } from './runtime/questions.js';
|
|
26
26
|
import { xApiSearchTool } from './tools/xApiSearchTool.js';
|
|
27
|
+
import { runCodeTool } from './tools/runCodeTool.js';
|
|
27
28
|
// Knowledge-bundle tools, derived from their contracts so the YAML names
|
|
28
29
|
// can never drift from the definitions. The agentic composites
|
|
29
30
|
// (wiki_query/wiki_garden) are deliberately absent: a syndicate reaches
|
|
@@ -79,6 +80,9 @@ const BUILTIN_TOOLS = {
|
|
|
79
80
|
// Both reach the run's memory service, pinned to the root namespace.
|
|
80
81
|
load_memory: asTool(loadMemoryTool),
|
|
81
82
|
preload_memory: preloadMemoryTool,
|
|
83
|
+
// JavaScript in the engine's QuickJS sandbox (lib/tools/runCodeTool.ts,
|
|
84
|
+
// ADR 0137): any provider; runs only under MELCHIZEDEK_CODE_SANDBOX=on.
|
|
85
|
+
run_code: runCodeTool,
|
|
82
86
|
};
|
|
83
87
|
// Null-prototype copy (as GUARD_MAP is): resolution and registration go
|
|
84
88
|
// through this map, never the literal above.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/tools/runCodeTool.ts — `run_code`: an agent runs JavaScript in the
|
|
3
|
+
* engine's sandbox and rests its answer on what the code returned
|
|
4
|
+
* (ADR 0137 items 2 to 4).
|
|
5
|
+
*
|
|
6
|
+
* WHY a registry tool: any agent on any provider can list it
|
|
7
|
+
* (`tools: [run_code]`), since the model only writes text, and it gets what
|
|
8
|
+
* every registry tool gets: `require_approval`, MCP exposure (ADR 0125), the
|
|
9
|
+
* result bound and `retries.tool_errors`. `code_execution: gemini` stays,
|
|
10
|
+
* for Python in Gemini's own sandbox on Gemini models (ADR 0033).
|
|
11
|
+
*
|
|
12
|
+
* THE CONTRACT: `{ code, input? }`. The code is a JavaScript module whose
|
|
13
|
+
* default export is called with `input`; the tool returns
|
|
14
|
+
* `{ result, logs, error }`: the export's JSON-serializable return, the
|
|
15
|
+
* `console.log` output, and what stopped it, if anything. Each is bounded at
|
|
16
|
+
* MAX_RESULT_CHARS. The code reaches nothing but `input` and `console`: no
|
|
17
|
+
* network, no host, no keys, no clock, no randomness (lib/sandbox/).
|
|
18
|
+
*
|
|
19
|
+
* THE SWITCH: nothing runs unless the operator set MELCHIZEDEK_CODE_SANDBOX=on;
|
|
20
|
+
* a syndicate listing run_code without it is refused at load (refuseOnNative),
|
|
21
|
+
* and a call that reaches here anyway answers with the reason.
|
|
22
|
+
*
|
|
23
|
+
* ATTACH: an agent's `run_code: { attach: true }` puts each run's code and
|
|
24
|
+
* result on the reply as a `code_run` data part (lib/runtime/syndicateTurn.ts
|
|
25
|
+
* collects them, the A2A executor publishes them). Off by default.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
/** The registry name. */
|
|
29
|
+
export declare const RUN_CODE = "run_code";
|
|
30
|
+
/** The largest program a call may send. */
|
|
31
|
+
export declare const MAX_CODE_CHARS = 64000;
|
|
32
|
+
/** What a call returns. */
|
|
33
|
+
export interface RunCodeOutput {
|
|
34
|
+
result: unknown;
|
|
35
|
+
logs: string;
|
|
36
|
+
error: string | null;
|
|
37
|
+
}
|
|
38
|
+
export declare const runCodeTool: import("./toolContract.ts").DefinedTool<z.ZodObject<{
|
|
39
|
+
code: z.ZodString;
|
|
40
|
+
input: z.ZodOptional<z.ZodUnknown>;
|
|
41
|
+
}, z.core.$strip>, RunCodeOutput>;
|
|
42
|
+
/** One run an agent made, as a `code_run` data part carries it. */
|
|
43
|
+
export interface CodeRun {
|
|
44
|
+
type: 'code_run';
|
|
45
|
+
/** The agent that ran it. */
|
|
46
|
+
agent: string;
|
|
47
|
+
code: string;
|
|
48
|
+
result: unknown;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|