@pumped-fn/agent-sdk 1.0.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/LICENSE +21 -0
- package/PATTERNS.md +458 -0
- package/README.md +515 -0
- package/dist/index.cjs +1336 -0
- package/dist/index.d.cts +478 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +478 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1281 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _pumped_fn_lite = require("@pumped-fn/lite");
|
|
3
|
+
let _pumped_fn_lite_extension_suspense = require("@pumped-fn/lite-extension-suspense");
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
const step = (0, _pumped_fn_lite.tag)({
|
|
6
|
+
label: "agent.step",
|
|
7
|
+
default: {}
|
|
8
|
+
});
|
|
9
|
+
const materialKind = (0, _pumped_fn_lite.tag)({ label: "agent.materialKind" });
|
|
10
|
+
const workers = (0, _pumped_fn_lite.tag)({ label: "agent.workerRegistry" });
|
|
11
|
+
var WorkerRegistry = class {
|
|
12
|
+
flows = /* @__PURE__ */ new Map();
|
|
13
|
+
register(flow, name = flow.name) {
|
|
14
|
+
if (!name) throw new Error("Worker flow must have a name");
|
|
15
|
+
this.flows.set(name, flow);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
get(name) {
|
|
19
|
+
const found = this.flows.get(name);
|
|
20
|
+
if (!found) throw new Error(`Worker "${name}" not registered`);
|
|
21
|
+
return found;
|
|
22
|
+
}
|
|
23
|
+
list() {
|
|
24
|
+
return [...this.flows.keys()];
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function workerRegistry(flows = []) {
|
|
28
|
+
const registry = new WorkerRegistry();
|
|
29
|
+
for (const workerFlow of flows) registry.register(workerFlow);
|
|
30
|
+
return registry;
|
|
31
|
+
}
|
|
32
|
+
function formatStepKey(key) {
|
|
33
|
+
return (0, _pumped_fn_lite_extension_suspense.formatSuspenseStepKey)(key);
|
|
34
|
+
}
|
|
35
|
+
const workflowRun = (0, _pumped_fn_lite.tag)({ label: "workflow.run" });
|
|
36
|
+
const workflow = (0, _pumped_fn_lite.tag)({ label: "workflow.runtime" });
|
|
37
|
+
const runtime = (0, _pumped_fn_lite.tag)({ label: "agent.runtime" });
|
|
38
|
+
const abortSignal = (0, _pumped_fn_lite.tag)({ label: "workflow.abortSignal" });
|
|
39
|
+
const activeWorkflowEvent = (0, _pumped_fn_lite.tag)({ label: "workflow.event" });
|
|
40
|
+
async function delegateWorker(ctx, name, input) {
|
|
41
|
+
const registry = registryOf(ctx);
|
|
42
|
+
if (!registry) throw new Error("Worker registry not found");
|
|
43
|
+
const target = registry.get(name);
|
|
44
|
+
return ctx.exec({
|
|
45
|
+
flow: target,
|
|
46
|
+
input
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function workflowExtension(options) {
|
|
50
|
+
const base = createWorkflowExtension({
|
|
51
|
+
name: "workflow",
|
|
52
|
+
options,
|
|
53
|
+
shouldHandle: shouldHandleWorkflowTarget,
|
|
54
|
+
run: (event, next) => runTimer(event.target, event.ctx, next)
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
...base,
|
|
58
|
+
async wrapExec(next, target, ctx) {
|
|
59
|
+
const wrapExec = base.wrapExec;
|
|
60
|
+
if (!wrapExec) return withRuntimeTag(ctx, workflow, workflowRuntimeOf(ctx, options), next);
|
|
61
|
+
return withRuntimeTag(ctx, workflow, workflowRuntimeOf(ctx, options), () => wrapExec(next, target, ctx));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function extension(options = {}) {
|
|
66
|
+
return {
|
|
67
|
+
name: "agent-sdk",
|
|
68
|
+
async wrapExec(next, target, ctx) {
|
|
69
|
+
return withRuntimeTag(ctx, runtime, runtimeOf(ctx), async () => {
|
|
70
|
+
if (stepOf(target, ctx).remote !== true) return next();
|
|
71
|
+
if (!options.remoteRunner) throw new Error("Remote step requires remoteRunner");
|
|
72
|
+
return options.remoteRunner.run(execEvent(target, ctx), next);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createWorkflowExtension(options) {
|
|
78
|
+
return (0, _pumped_fn_lite_extension_suspense.extension)({
|
|
79
|
+
name: options.name,
|
|
80
|
+
log: options.options.log,
|
|
81
|
+
defaultTaskId: options.options.defaultTaskId,
|
|
82
|
+
defaultRunId: options.options.defaultRunId,
|
|
83
|
+
getKey: (ctx) => nextWorkflowKey(ctx, options.options),
|
|
84
|
+
shouldHandle: options.shouldHandle,
|
|
85
|
+
shouldSuspend: (event) => {
|
|
86
|
+
const config = stepOf(event.target, event.ctx);
|
|
87
|
+
return config.durable === true && config.remote !== true;
|
|
88
|
+
},
|
|
89
|
+
createPendingEntry: (event) => ({
|
|
90
|
+
status: "pending",
|
|
91
|
+
key: event.key,
|
|
92
|
+
targetName: event.targetName,
|
|
93
|
+
input: event.input,
|
|
94
|
+
kind: "durable"
|
|
95
|
+
}),
|
|
96
|
+
run: (event, next) => {
|
|
97
|
+
const previous = event.ctx.data.getTag(activeWorkflowEvent);
|
|
98
|
+
event.ctx.data.setTag(activeWorkflowEvent, event);
|
|
99
|
+
return options.run(event, next).finally(() => {
|
|
100
|
+
if (previous) event.ctx.data.setTag(activeWorkflowEvent, previous);
|
|
101
|
+
else event.ctx.data.deleteTag(activeWorkflowEvent);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function registryOf(ctx) {
|
|
107
|
+
return ctx.data.seekTag(workers);
|
|
108
|
+
}
|
|
109
|
+
function execEvent(target, ctx) {
|
|
110
|
+
return ctx.data.seekTag(activeWorkflowEvent) ?? {
|
|
111
|
+
target,
|
|
112
|
+
ctx,
|
|
113
|
+
targetName: targetNameOf(target, ctx),
|
|
114
|
+
input: ctx.input
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function nextWorkflowKey(ctx, options) {
|
|
118
|
+
const config = ctx.data.seekTag(workflowRun);
|
|
119
|
+
const foundTaskId = config?.taskId ?? options.defaultTaskId ?? "default-task";
|
|
120
|
+
const foundRunId = config?.runId ?? options.defaultRunId ?? "default-run";
|
|
121
|
+
let counter = ctx.data.seekTag(_pumped_fn_lite_extension_suspense.stepCounter);
|
|
122
|
+
if (!counter) {
|
|
123
|
+
counter = { next: 0 };
|
|
124
|
+
rootContext(ctx).data.setTag(_pumped_fn_lite_extension_suspense.stepCounter, counter);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
taskId: foundTaskId,
|
|
128
|
+
runId: foundRunId,
|
|
129
|
+
step: counter.next++
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function workflowRuntimeOf(ctx, options) {
|
|
133
|
+
const config = ctx.data.seekTag(workflowRun);
|
|
134
|
+
return {
|
|
135
|
+
taskId: config?.taskId ?? options.defaultTaskId ?? "default-task",
|
|
136
|
+
runId: config?.runId ?? options.defaultRunId ?? "default-run"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function runtimeOf(ctx) {
|
|
140
|
+
const config = ctx.data.seekTag(workflow);
|
|
141
|
+
if (!config) throw new Error("agent extension requires workflow extension");
|
|
142
|
+
return {
|
|
143
|
+
taskId: config.taskId,
|
|
144
|
+
runId: config.runId,
|
|
145
|
+
delegate: (name, input) => delegateWorker(ctx, name, input)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function withRuntimeTag(ctx, runtimeTag, value, next) {
|
|
149
|
+
const hadPrevious = ctx.data.hasTag(runtimeTag);
|
|
150
|
+
const previous = ctx.data.getTag(runtimeTag);
|
|
151
|
+
ctx.data.setTag(runtimeTag, value);
|
|
152
|
+
return next().finally(() => {
|
|
153
|
+
if (hadPrevious) ctx.data.setTag(runtimeTag, previous);
|
|
154
|
+
else ctx.data.deleteTag(runtimeTag);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function shouldHandleWorkflowTarget(target, ctx) {
|
|
158
|
+
const config = stepOf(target, ctx);
|
|
159
|
+
return config.workflow === true || config.durable === true || config.timeoutMs !== void 0;
|
|
160
|
+
}
|
|
161
|
+
function rootContext(ctx) {
|
|
162
|
+
let current = ctx;
|
|
163
|
+
while (current.parent) current = current.parent;
|
|
164
|
+
return current;
|
|
165
|
+
}
|
|
166
|
+
function targetNameOf(target, ctx) {
|
|
167
|
+
const name = ctx.name || target.name;
|
|
168
|
+
if (!name) throw new Error("Agent target must have a name");
|
|
169
|
+
return name;
|
|
170
|
+
}
|
|
171
|
+
function runTimer(target, ctx, next) {
|
|
172
|
+
const timeoutMs = stepOf(target, ctx).timeoutMs;
|
|
173
|
+
if (timeoutMs === void 0) return next();
|
|
174
|
+
const controller = new AbortController();
|
|
175
|
+
let timer;
|
|
176
|
+
return withRuntimeTag(ctx, abortSignal, controller.signal, () => Promise.race([next(), new Promise((_, reject) => {
|
|
177
|
+
timer = setTimeout(() => {
|
|
178
|
+
const error = /* @__PURE__ */ new Error(`Workflow step timed out after ${timeoutMs}ms`);
|
|
179
|
+
controller.abort(error);
|
|
180
|
+
reject(error);
|
|
181
|
+
}, timeoutMs);
|
|
182
|
+
})])).finally(() => {
|
|
183
|
+
if (timer) clearTimeout(timer);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function stepOf(target, ctx) {
|
|
187
|
+
return {
|
|
188
|
+
...typeof target === "function" ? {} : step.find(target),
|
|
189
|
+
...ctx.data.seekTag(step) ?? {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const materialPatches = /* @__PURE__ */ new WeakMap();
|
|
193
|
+
var MaterialConflictError = class extends Error {
|
|
194
|
+
expectedRevision;
|
|
195
|
+
currentRevision;
|
|
196
|
+
name = "MaterialConflictError";
|
|
197
|
+
constructor(expectedRevision, currentRevision) {
|
|
198
|
+
super(`Material revision conflict: expected ${expectedRevision}, current ${currentRevision}`);
|
|
199
|
+
this.expectedRevision = expectedRevision;
|
|
200
|
+
this.currentRevision = currentRevision;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
function material(name, options) {
|
|
204
|
+
return (0, _pumped_fn_lite.atom)({
|
|
205
|
+
keepAlive: options.keepAlive ?? true,
|
|
206
|
+
tags: [materialKind(options.kind), ...options.tags ?? []],
|
|
207
|
+
factory: () => ({
|
|
208
|
+
name,
|
|
209
|
+
kind: options.kind,
|
|
210
|
+
revision: 0,
|
|
211
|
+
state: clone(options.initialState)
|
|
212
|
+
})
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async function patchMaterial(ctx, target, ops, options = {}) {
|
|
216
|
+
return queueMaterialPatch(ctx, target, async () => {
|
|
217
|
+
const ctrl = ctx.scope.controller(target);
|
|
218
|
+
if (ctrl.state === "idle") await ctrl.resolve();
|
|
219
|
+
const current = ctrl.get();
|
|
220
|
+
if (current.kind !== "json") throw new Error(`Material "${current.name}" does not accept JSON Patch`);
|
|
221
|
+
if (options.expectedRevision !== void 0 && options.expectedRevision !== current.revision) throw new MaterialConflictError(options.expectedRevision, current.revision);
|
|
222
|
+
ctrl.set({
|
|
223
|
+
...current,
|
|
224
|
+
revision: current.revision + 1,
|
|
225
|
+
state: applyJsonPatch(current.state, ops)
|
|
226
|
+
});
|
|
227
|
+
return ctrl.get();
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function derivedMaterial(name, source, derive, options) {
|
|
231
|
+
return (0, _pumped_fn_lite.atom)({
|
|
232
|
+
keepAlive: options.keepAlive ?? true,
|
|
233
|
+
deps: { source },
|
|
234
|
+
tags: [materialKind(options.kind), ...options.tags ?? []],
|
|
235
|
+
factory: (_ctx, deps) => ({
|
|
236
|
+
name,
|
|
237
|
+
kind: options.kind,
|
|
238
|
+
revision: deps.source.revision,
|
|
239
|
+
state: derive(deps.source.state)
|
|
240
|
+
})
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function queueMaterialPatch(ctx, target, run) {
|
|
244
|
+
let scopePatches = materialPatches.get(ctx.scope);
|
|
245
|
+
if (!scopePatches) {
|
|
246
|
+
scopePatches = /* @__PURE__ */ new WeakMap();
|
|
247
|
+
materialPatches.set(ctx.scope, scopePatches);
|
|
248
|
+
}
|
|
249
|
+
const current = (scopePatches.get(target) ?? Promise.resolve()).catch(() => void 0).then(run);
|
|
250
|
+
const lock = current.then(() => void 0, () => void 0);
|
|
251
|
+
scopePatches.set(target, lock);
|
|
252
|
+
lock.then(() => {
|
|
253
|
+
if (scopePatches.get(target) === lock) scopePatches.delete(target);
|
|
254
|
+
});
|
|
255
|
+
return current;
|
|
256
|
+
}
|
|
257
|
+
function applyJsonPatch(source, ops) {
|
|
258
|
+
let document = clone(source);
|
|
259
|
+
for (const op of ops) {
|
|
260
|
+
if (op.path === "") {
|
|
261
|
+
if (op.op === "remove") document = null;
|
|
262
|
+
else document = clone(op.value);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const parts = splitPointer(op.path);
|
|
266
|
+
const key = parts.at(-1);
|
|
267
|
+
if (key === void 0) throw new Error("JSON Patch path cannot be empty");
|
|
268
|
+
const parent = findPatchParent(document, parts.slice(0, -1));
|
|
269
|
+
if (op.op === "remove") removeValue(parent, key);
|
|
270
|
+
else setValue(parent, key, clone(op.value), op.op);
|
|
271
|
+
}
|
|
272
|
+
return document;
|
|
273
|
+
}
|
|
274
|
+
function splitPointer(path) {
|
|
275
|
+
if (!path.startsWith("/")) throw new Error(`Invalid JSON Pointer "${path}"`);
|
|
276
|
+
return path.slice(1).split("/").map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
277
|
+
}
|
|
278
|
+
function findPatchParent(document, parts) {
|
|
279
|
+
let current = document;
|
|
280
|
+
for (const part of parts) {
|
|
281
|
+
if (Array.isArray(current)) {
|
|
282
|
+
current = current[Number(part)];
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (isRecord(current)) {
|
|
286
|
+
current = current[part];
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
throw new Error(`Cannot traverse JSON Patch path at "${part}"`);
|
|
290
|
+
}
|
|
291
|
+
return current;
|
|
292
|
+
}
|
|
293
|
+
function setValue(parent, key, value, op) {
|
|
294
|
+
if (Array.isArray(parent)) {
|
|
295
|
+
if (key === "-") {
|
|
296
|
+
parent.push(value);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const index = Number(key);
|
|
300
|
+
if (op === "add") parent.splice(index, 0, value);
|
|
301
|
+
else parent[index] = value;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!isRecord(parent)) throw new Error(`Cannot set JSON Patch path "${key}"`);
|
|
305
|
+
parent[key] = value;
|
|
306
|
+
}
|
|
307
|
+
function removeValue(parent, key) {
|
|
308
|
+
if (Array.isArray(parent)) {
|
|
309
|
+
parent.splice(Number(key), 1);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (!isRecord(parent)) throw new Error(`Cannot remove JSON Patch path "${key}"`);
|
|
313
|
+
delete parent[key];
|
|
314
|
+
}
|
|
315
|
+
function isRecord(value) {
|
|
316
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
317
|
+
}
|
|
318
|
+
function clone(value) {
|
|
319
|
+
return structuredClone(value);
|
|
320
|
+
}
|
|
321
|
+
var CliWorkerError = class extends Error {
|
|
322
|
+
result;
|
|
323
|
+
name = "CliWorkerError";
|
|
324
|
+
constructor(message, result) {
|
|
325
|
+
super(message);
|
|
326
|
+
this.result = result;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
function cliWorker(options) {
|
|
330
|
+
const config = { kind: options.kind ?? "cli" };
|
|
331
|
+
if (options.timeoutMs !== void 0) config.timeoutMs = options.timeoutMs;
|
|
332
|
+
const tags = [step(config), ...options.tags ?? []];
|
|
333
|
+
const factory = async (ctx) => {
|
|
334
|
+
const input = ctx.input;
|
|
335
|
+
const result = await runCli({
|
|
336
|
+
command: resolveRequiredValue(options.command, input, ctx),
|
|
337
|
+
args: resolveValue(options.args ?? [], input, ctx),
|
|
338
|
+
stdin: resolveValue(options.stdin, input, ctx),
|
|
339
|
+
cwd: resolveValue(options.cwd, input, ctx),
|
|
340
|
+
env: resolveValue(options.env, input, ctx),
|
|
341
|
+
isolate: resolveValue(options.isolate, input, ctx),
|
|
342
|
+
timeoutMs: options.timeoutMs,
|
|
343
|
+
signal: ctx.data.seekTag(abortSignal)
|
|
344
|
+
});
|
|
345
|
+
return options.parseOutput ? options.parseOutput(result, input) : result.stdout.trim();
|
|
346
|
+
};
|
|
347
|
+
const flowOptions = {
|
|
348
|
+
name: options.name,
|
|
349
|
+
tags,
|
|
350
|
+
factory
|
|
351
|
+
};
|
|
352
|
+
return typeof options.parse === "function" ? (0, _pumped_fn_lite.flow)({
|
|
353
|
+
...flowOptions,
|
|
354
|
+
parse: options.parse
|
|
355
|
+
}) : (0, _pumped_fn_lite.flow)({
|
|
356
|
+
...flowOptions,
|
|
357
|
+
parse: options.parse ?? (0, _pumped_fn_lite.typed)()
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async function runCli(options) {
|
|
361
|
+
const { execFile } = await import("node:child_process");
|
|
362
|
+
const prepared = await prepareCli(options);
|
|
363
|
+
return new Promise((resolve, reject) => {
|
|
364
|
+
execFile(prepared.command, [...prepared.args], {
|
|
365
|
+
cwd: prepared.cwd,
|
|
366
|
+
env: prepared.env,
|
|
367
|
+
timeout: options.timeoutMs,
|
|
368
|
+
signal: options.signal
|
|
369
|
+
}, async (error, stdout, stderr) => {
|
|
370
|
+
await prepared.cleanup();
|
|
371
|
+
const execError = error;
|
|
372
|
+
const exitCode = typeof execError?.code === "number" ? execError.code : error ? null : 0;
|
|
373
|
+
const signal = execError?.signal ?? null;
|
|
374
|
+
const result = {
|
|
375
|
+
stdout: String(stdout),
|
|
376
|
+
stderr: String(stderr),
|
|
377
|
+
exitCode,
|
|
378
|
+
signal
|
|
379
|
+
};
|
|
380
|
+
if (execError?.killed && options.timeoutMs !== void 0) {
|
|
381
|
+
reject(new CliWorkerError(`CLI command timed out after ${options.timeoutMs}ms`, result));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (execError?.name === "AbortError") {
|
|
385
|
+
reject(new CliWorkerError("CLI command aborted", result));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (error) {
|
|
389
|
+
reject(new CliWorkerError(exitCode === null ? error.message : `CLI command failed with exit code ${exitCode}`, result));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
resolve(result);
|
|
393
|
+
}).stdin?.end(options.stdin);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
async function prepareCli(options) {
|
|
397
|
+
if (!options.isolate) return {
|
|
398
|
+
command: options.command,
|
|
399
|
+
args: options.args ?? [],
|
|
400
|
+
cwd: options.cwd,
|
|
401
|
+
env: {
|
|
402
|
+
...process.env,
|
|
403
|
+
...options.env
|
|
404
|
+
},
|
|
405
|
+
cleanup: async () => void 0
|
|
406
|
+
};
|
|
407
|
+
return prepareIsolatedCli(options, typeof options.isolate === "boolean" ? {} : options.isolate);
|
|
408
|
+
}
|
|
409
|
+
async function prepareIsolatedCli(options, isolate) {
|
|
410
|
+
const { mkdtemp, rm, realpath } = await import("node:fs/promises");
|
|
411
|
+
const { existsSync } = await import("node:fs");
|
|
412
|
+
const { dirname, join, resolve } = await import("node:path");
|
|
413
|
+
const { tmpdir } = await import("node:os");
|
|
414
|
+
const hostCwd = resolve(options.cwd ?? process.cwd());
|
|
415
|
+
const workdir = isolate.workdir ?? "/workspace";
|
|
416
|
+
const tempDirs = [];
|
|
417
|
+
const home = isolate.home ?? await mkdtemp(join(tmpdir(), "pumped-fn-home-"));
|
|
418
|
+
if (!isolate.home) tempDirs.push(home);
|
|
419
|
+
const binds = /* @__PURE__ */ new Map();
|
|
420
|
+
addBind(binds, hostCwd, workdir, isolate.writable === true ? "rw" : "ro");
|
|
421
|
+
addBind(binds, home, "/home/agent", "rw");
|
|
422
|
+
if (isolate.codexHome) addBind(binds, isolate.codexHome, "/codex-home", "rw");
|
|
423
|
+
const commandPath = await commandPathOf(options.command, process.env["PATH"] ?? "", existsSync, realpath);
|
|
424
|
+
const nodePath = await realpath(process.execPath);
|
|
425
|
+
for (const dir of defaultCliDirs(existsSync)) addBind(binds, dir, dir, "ro");
|
|
426
|
+
for (const dir of defaultCliCertDirs(existsSync)) addBind(binds, dir, dir, "ro");
|
|
427
|
+
for (const file of defaultCliFiles(existsSync)) addBind(binds, file, file, "ro");
|
|
428
|
+
if (commandPath) {
|
|
429
|
+
addBind(binds, dirname(commandPath), dirname(commandPath), "ro");
|
|
430
|
+
addBind(binds, dirname(dirname(commandPath)), dirname(dirname(commandPath)), "ro");
|
|
431
|
+
}
|
|
432
|
+
addBind(binds, dirname(nodePath), dirname(nodePath), "ro");
|
|
433
|
+
addBind(binds, dirname(dirname(nodePath)), dirname(dirname(nodePath)), "ro");
|
|
434
|
+
for (const bind of isolate.bind ?? []) addBind(binds, bind.source, bind.target ?? bind.source, bind.mode ?? "ro");
|
|
435
|
+
const env = {
|
|
436
|
+
PATH: isolatedPathEnv(commandPath, nodePath, dirname),
|
|
437
|
+
HOME: "/home/agent",
|
|
438
|
+
TMPDIR: "/tmp",
|
|
439
|
+
...isolate.codexHome ? { CODEX_HOME: "/codex-home" } : {},
|
|
440
|
+
...options.env,
|
|
441
|
+
...isolate.env
|
|
442
|
+
};
|
|
443
|
+
return {
|
|
444
|
+
command: isolate.bwrap ?? "bwrap",
|
|
445
|
+
args: [
|
|
446
|
+
"--die-with-parent",
|
|
447
|
+
"--unshare-all",
|
|
448
|
+
...isolate.network === true ? ["--share-net"] : [],
|
|
449
|
+
"--proc",
|
|
450
|
+
"/proc",
|
|
451
|
+
"--dev",
|
|
452
|
+
"/dev",
|
|
453
|
+
"--tmpfs",
|
|
454
|
+
"/tmp",
|
|
455
|
+
"--dir",
|
|
456
|
+
"/etc",
|
|
457
|
+
"--dir",
|
|
458
|
+
"/home",
|
|
459
|
+
...Object.entries(env).flatMap(([key, value]) => value === void 0 ? [] : [
|
|
460
|
+
"--setenv",
|
|
461
|
+
key,
|
|
462
|
+
value
|
|
463
|
+
]),
|
|
464
|
+
...[...binds.values()].flatMap((bind) => [
|
|
465
|
+
bind.mode === "rw" ? "--bind" : "--ro-bind",
|
|
466
|
+
bind.source,
|
|
467
|
+
bind.target
|
|
468
|
+
]),
|
|
469
|
+
"--chdir",
|
|
470
|
+
workdir,
|
|
471
|
+
"--",
|
|
472
|
+
commandPath ?? options.command,
|
|
473
|
+
...options.args ?? []
|
|
474
|
+
],
|
|
475
|
+
env: { PATH: process.env["PATH"] },
|
|
476
|
+
cleanup: async () => {
|
|
477
|
+
await Promise.all(tempDirs.map((dir) => rm(dir, {
|
|
478
|
+
recursive: true,
|
|
479
|
+
force: true
|
|
480
|
+
})));
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function addBind(binds, source, target, mode) {
|
|
485
|
+
binds.set(`${source}\u0000${target}`, {
|
|
486
|
+
source,
|
|
487
|
+
target,
|
|
488
|
+
mode
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
function defaultCliDirs(exists) {
|
|
492
|
+
return [
|
|
493
|
+
"/usr/bin",
|
|
494
|
+
"/bin",
|
|
495
|
+
"/usr/lib",
|
|
496
|
+
"/usr/lib64",
|
|
497
|
+
"/lib",
|
|
498
|
+
"/lib64"
|
|
499
|
+
].filter((path, index, items) => items.indexOf(path) === index && exists(path));
|
|
500
|
+
}
|
|
501
|
+
function defaultCliCertDirs(exists) {
|
|
502
|
+
return [
|
|
503
|
+
"/etc/ssl",
|
|
504
|
+
"/etc/pki",
|
|
505
|
+
"/etc/ca-certificates",
|
|
506
|
+
"/usr/share/ca-certificates",
|
|
507
|
+
"/usr/local/share/ca-certificates"
|
|
508
|
+
].filter((path, index, items) => items.indexOf(path) === index && exists(path));
|
|
509
|
+
}
|
|
510
|
+
function defaultCliFiles(exists) {
|
|
511
|
+
return ["/etc/hosts", "/etc/resolv.conf"].filter((path, index, items) => items.indexOf(path) === index && exists(path));
|
|
512
|
+
}
|
|
513
|
+
function isolatedPathEnv(commandPath, nodePath, dirname) {
|
|
514
|
+
return [
|
|
515
|
+
commandPath ? dirname(commandPath) : void 0,
|
|
516
|
+
dirname(nodePath),
|
|
517
|
+
"/usr/bin",
|
|
518
|
+
"/bin"
|
|
519
|
+
].filter((path, index, items) => path !== void 0 && items.indexOf(path) === index).join(":");
|
|
520
|
+
}
|
|
521
|
+
async function commandPathOf(command, pathEnv, exists, realpath) {
|
|
522
|
+
if (command.includes("/")) return realpath(command);
|
|
523
|
+
const found = pathEnv.split(":").filter(Boolean).map((dir) => `${dir}/${command}`).find((path) => exists(path));
|
|
524
|
+
return found ? realpath(found) : void 0;
|
|
525
|
+
}
|
|
526
|
+
function resolveValue(value, input, ctx) {
|
|
527
|
+
if (typeof value === "function") return value(input, ctx);
|
|
528
|
+
return value;
|
|
529
|
+
}
|
|
530
|
+
function resolveRequiredValue(value, input, ctx) {
|
|
531
|
+
if (typeof value === "function") return value(input, ctx);
|
|
532
|
+
return value;
|
|
533
|
+
}
|
|
534
|
+
function guard(name, text = "") {
|
|
535
|
+
return material(name, {
|
|
536
|
+
kind: "json",
|
|
537
|
+
initialState: { text }
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
function claudeCliWorker(options = {}) {
|
|
541
|
+
assertNoClaudeBare(options.extraArgs ?? []);
|
|
542
|
+
return cliWorker({
|
|
543
|
+
name: options.name ?? "claude",
|
|
544
|
+
command: options.command ?? "claude",
|
|
545
|
+
args: (input) => cliPromptArgs(["-p", ...options.extraArgs ?? []], input.prompt),
|
|
546
|
+
isolate: options.isolate,
|
|
547
|
+
timeoutMs: options.timeoutMs,
|
|
548
|
+
kind: "llm",
|
|
549
|
+
tags: options.tags
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function codexCliWorker(options = {}) {
|
|
553
|
+
return cliWorker({
|
|
554
|
+
name: options.name ?? "codex",
|
|
555
|
+
command: options.command ?? "codex",
|
|
556
|
+
args: (input) => cliPromptArgs([
|
|
557
|
+
"exec",
|
|
558
|
+
"-s",
|
|
559
|
+
options.sandbox ?? "read-only",
|
|
560
|
+
...options.extraArgs ?? []
|
|
561
|
+
], input.prompt),
|
|
562
|
+
isolate: options.isolate,
|
|
563
|
+
timeoutMs: options.timeoutMs,
|
|
564
|
+
kind: "llm",
|
|
565
|
+
tags: options.tags
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function claudeHarness(options = {}) {
|
|
569
|
+
return cliHarnessModel(claudeCliWorker({
|
|
570
|
+
name: options.name ?? "claude-harness",
|
|
571
|
+
command: options.command,
|
|
572
|
+
extraArgs: ["--no-session-persistence", ...options.extraArgs ?? []],
|
|
573
|
+
isolate: options.isolate ?? { network: true },
|
|
574
|
+
timeoutMs: options.timeoutMs,
|
|
575
|
+
tags: options.tags
|
|
576
|
+
}), {
|
|
577
|
+
guard: options.guard === void 0 ? guard(`${options.name ?? "claude-harness"}-guard`) : options.guard,
|
|
578
|
+
prompt: options.prompt,
|
|
579
|
+
parse: options.parse
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function codexHarness(options = {}) {
|
|
583
|
+
return cliHarnessModel(codexCliWorker({
|
|
584
|
+
name: options.name ?? "codex-harness",
|
|
585
|
+
command: options.command,
|
|
586
|
+
sandbox: options.sandbox,
|
|
587
|
+
extraArgs: [
|
|
588
|
+
"--ephemeral",
|
|
589
|
+
"--ignore-user-config",
|
|
590
|
+
...options.extraArgs ?? []
|
|
591
|
+
],
|
|
592
|
+
isolate: options.isolate ?? { network: true },
|
|
593
|
+
timeoutMs: options.timeoutMs,
|
|
594
|
+
tags: options.tags
|
|
595
|
+
}), {
|
|
596
|
+
guard: options.guard === void 0 ? guard(`${options.name ?? "codex-harness"}-guard`) : options.guard,
|
|
597
|
+
prompt: options.prompt,
|
|
598
|
+
parse: options.parse
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
function cliHarnessModel(worker, config) {
|
|
602
|
+
return { complete: async (ctx, request) => {
|
|
603
|
+
const current = config.guard ? await ctx.resolve(config.guard) : void 0;
|
|
604
|
+
const state = current?.state ?? { text: "" };
|
|
605
|
+
const output = await ctx.exec({
|
|
606
|
+
flow: worker,
|
|
607
|
+
input: { prompt: config.prompt ? config.prompt(request, state) : modelPrompt(request, state) }
|
|
608
|
+
});
|
|
609
|
+
const response = config.parse ? config.parse(output) : parseModelOutput(output);
|
|
610
|
+
if (config.guard && current) await collectGuard(ctx, config.guard, current, response);
|
|
611
|
+
return response;
|
|
612
|
+
} };
|
|
613
|
+
}
|
|
614
|
+
async function collectGuard(ctx, store, current, response) {
|
|
615
|
+
const text = guardTextOf(response.guard);
|
|
616
|
+
if (!text || current.state.text) return;
|
|
617
|
+
await patchMaterial(ctx, store, [{
|
|
618
|
+
op: "replace",
|
|
619
|
+
path: "/text",
|
|
620
|
+
value: text
|
|
621
|
+
}], { expectedRevision: current.revision });
|
|
622
|
+
}
|
|
623
|
+
function modelPrompt(request, guard) {
|
|
624
|
+
return [
|
|
625
|
+
"Return JSON only.",
|
|
626
|
+
"Schema: {\"content\":string,\"stop\"?:boolean,\"guard\"?:string,\"skillCalls\"?:array,\"toolCalls\"?:array,\"subagentCalls\"?:array}.",
|
|
627
|
+
guard.text ? `Guard:\n${guard.text}` : "First run only: set guard to the anti-goal that should prevent this agent from drifting.",
|
|
628
|
+
`Agent: ${request.agentName}`,
|
|
629
|
+
request.instructions ? `Instructions:\n${request.instructions}` : void 0,
|
|
630
|
+
request.skills.length ? `Available skills:\n${request.skills.map(formatCapability).join("\n")}` : void 0,
|
|
631
|
+
request.loadedSkills.length ? `Loaded skills:\n${request.loadedSkills.map(formatLoadedSkill).join("\n\n")}` : void 0,
|
|
632
|
+
request.tools.length ? `Available tools:\n${request.tools.map(formatCapability).join("\n")}` : void 0,
|
|
633
|
+
request.subagents.length ? `Available subagents:\n${request.subagents.map(formatCapability).join("\n")}` : void 0,
|
|
634
|
+
`Round: ${request.round}`,
|
|
635
|
+
`Messages:\n${request.messages.map(formatMessage).join("\n")}`
|
|
636
|
+
].filter((item) => item !== void 0).join("\n\n");
|
|
637
|
+
}
|
|
638
|
+
function parseModelOutput(output) {
|
|
639
|
+
const value = readJson(output);
|
|
640
|
+
if (!isRecord(value)) return {
|
|
641
|
+
content: output,
|
|
642
|
+
stop: true
|
|
643
|
+
};
|
|
644
|
+
const response = {
|
|
645
|
+
content: typeof value["content"] === "string" ? value["content"] : output,
|
|
646
|
+
stop: typeof value["stop"] === "boolean" ? value["stop"] : true
|
|
647
|
+
};
|
|
648
|
+
const guard = guardTextOf(value["guard"] ?? value["antiGoal"]);
|
|
649
|
+
const skillCalls = skillCallsOf(value["skillCalls"]);
|
|
650
|
+
const toolCalls = toolCallsOf(value["toolCalls"]);
|
|
651
|
+
const subagentCalls = subagentCallsOf(value["subagentCalls"]);
|
|
652
|
+
if (guard) response.guard = guard;
|
|
653
|
+
if (skillCalls) response.skillCalls = skillCalls;
|
|
654
|
+
if (toolCalls) response.toolCalls = toolCalls;
|
|
655
|
+
if (subagentCalls) response.subagentCalls = subagentCalls;
|
|
656
|
+
return response;
|
|
657
|
+
}
|
|
658
|
+
function readJson(output) {
|
|
659
|
+
const trimmed = output.trim();
|
|
660
|
+
if (!trimmed) return void 0;
|
|
661
|
+
try {
|
|
662
|
+
return JSON.parse(trimmed);
|
|
663
|
+
} catch {
|
|
664
|
+
const start = trimmed.indexOf("{");
|
|
665
|
+
const end = trimmed.lastIndexOf("}");
|
|
666
|
+
if (start === -1 || end <= start) return void 0;
|
|
667
|
+
try {
|
|
668
|
+
return JSON.parse(trimmed.slice(start, end + 1));
|
|
669
|
+
} catch {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function skillCallsOf(value) {
|
|
675
|
+
if (!Array.isArray(value)) return void 0;
|
|
676
|
+
const calls = value.flatMap((item) => {
|
|
677
|
+
if (!isRecord(item) || typeof item["name"] !== "string") return [];
|
|
678
|
+
return [{
|
|
679
|
+
name: item["name"],
|
|
680
|
+
...typeof item["id"] === "string" ? { id: item["id"] } : {}
|
|
681
|
+
}];
|
|
682
|
+
});
|
|
683
|
+
return calls.length ? calls : void 0;
|
|
684
|
+
}
|
|
685
|
+
function toolCallsOf(value) {
|
|
686
|
+
if (!Array.isArray(value)) return void 0;
|
|
687
|
+
const calls = value.flatMap((item) => {
|
|
688
|
+
if (!isRecord(item) || typeof item["name"] !== "string") return [];
|
|
689
|
+
return [{
|
|
690
|
+
name: item["name"],
|
|
691
|
+
input: item["input"],
|
|
692
|
+
...typeof item["id"] === "string" ? { id: item["id"] } : {}
|
|
693
|
+
}];
|
|
694
|
+
});
|
|
695
|
+
return calls.length ? calls : void 0;
|
|
696
|
+
}
|
|
697
|
+
function subagentCallsOf(value) {
|
|
698
|
+
if (!Array.isArray(value)) return void 0;
|
|
699
|
+
const calls = value.flatMap((item) => {
|
|
700
|
+
if (!isRecord(item) || typeof item["name"] !== "string") return [];
|
|
701
|
+
return [{
|
|
702
|
+
name: item["name"],
|
|
703
|
+
input: turnInputOf(item["input"]),
|
|
704
|
+
...typeof item["id"] === "string" ? { id: item["id"] } : {}
|
|
705
|
+
}];
|
|
706
|
+
});
|
|
707
|
+
return calls.length ? calls : void 0;
|
|
708
|
+
}
|
|
709
|
+
function turnInputOf(value) {
|
|
710
|
+
if (isRecord(value)) return value;
|
|
711
|
+
return { prompt: stringifyAgentValue(value) };
|
|
712
|
+
}
|
|
713
|
+
function guardTextOf(value) {
|
|
714
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
715
|
+
}
|
|
716
|
+
function formatCapability(capability) {
|
|
717
|
+
return `- ${capability.name}: ${capability.description}`;
|
|
718
|
+
}
|
|
719
|
+
function formatLoadedSkill(skill) {
|
|
720
|
+
return `## ${skill.name}\n${skill.content}`;
|
|
721
|
+
}
|
|
722
|
+
function formatMessage(message) {
|
|
723
|
+
return message.name ? `${message.role}(${message.name}): ${message.content}` : `${message.role}: ${message.content}`;
|
|
724
|
+
}
|
|
725
|
+
function assertNoClaudeBare(args) {
|
|
726
|
+
if (args.some((arg) => arg === "--bare" || arg.startsWith("--bare="))) throw new Error("Claude harness must not use --bare");
|
|
727
|
+
}
|
|
728
|
+
function cliPromptArgs(args, prompt) {
|
|
729
|
+
if (args.includes("--")) throw new Error("CLI helper extraArgs cannot include --");
|
|
730
|
+
return [
|
|
731
|
+
...args,
|
|
732
|
+
"--",
|
|
733
|
+
prompt
|
|
734
|
+
];
|
|
735
|
+
}
|
|
736
|
+
const events = (0, _pumped_fn_lite.resource)({
|
|
737
|
+
name: "agent.events",
|
|
738
|
+
ownership: "boundary",
|
|
739
|
+
factory: () => {
|
|
740
|
+
let next = 0;
|
|
741
|
+
const events = [];
|
|
742
|
+
return {
|
|
743
|
+
get events() {
|
|
744
|
+
return events;
|
|
745
|
+
},
|
|
746
|
+
record(event) {
|
|
747
|
+
const stored = {
|
|
748
|
+
...event,
|
|
749
|
+
index: next++
|
|
750
|
+
};
|
|
751
|
+
events.push(stored);
|
|
752
|
+
return stored;
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
const model = (0, _pumped_fn_lite.tag)({ label: "agent.model" });
|
|
758
|
+
const sandbox = (0, _pumped_fn_lite.tag)({ label: "agent.sandbox" });
|
|
759
|
+
function session(name, options = {}) {
|
|
760
|
+
return material(name, {
|
|
761
|
+
kind: "json",
|
|
762
|
+
initialState: { messages: options.messages ?? [] },
|
|
763
|
+
tags: options.tags,
|
|
764
|
+
keepAlive: options.keepAlive
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
function tool(options) {
|
|
768
|
+
const name = options.name ?? options.flow.name;
|
|
769
|
+
if (!name) throw new Error("Agent tool requires a name");
|
|
770
|
+
return {
|
|
771
|
+
name,
|
|
772
|
+
description: options.description,
|
|
773
|
+
flow: options.flow
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function skill(options) {
|
|
777
|
+
if (options.load) return {
|
|
778
|
+
name: options.name,
|
|
779
|
+
description: options.description,
|
|
780
|
+
load: options.load
|
|
781
|
+
};
|
|
782
|
+
const content = options.content ?? "";
|
|
783
|
+
return {
|
|
784
|
+
name: options.name,
|
|
785
|
+
description: options.description,
|
|
786
|
+
load: () => content
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function sub(options) {
|
|
790
|
+
return {
|
|
791
|
+
name: options.name ?? options.agent.name,
|
|
792
|
+
description: options.description,
|
|
793
|
+
agent: options.agent
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function agent(options) {
|
|
797
|
+
const tools = options.tools ?? [];
|
|
798
|
+
const skills = options.skills ?? [];
|
|
799
|
+
const subagents = options.subagents ?? [];
|
|
800
|
+
const maxRounds = options.maxRounds ?? 4;
|
|
801
|
+
let agent;
|
|
802
|
+
const turn = (0, _pumped_fn_lite.flow)({
|
|
803
|
+
name: options.name,
|
|
804
|
+
parse: (0, _pumped_fn_lite.typed)(),
|
|
805
|
+
deps: { model: _pumped_fn_lite.tags.required(model) },
|
|
806
|
+
tags: agentStepTags({
|
|
807
|
+
workflow: true,
|
|
808
|
+
kind: "agent"
|
|
809
|
+
}, options.tags),
|
|
810
|
+
factory: (ctx, deps) => executeAgentTurn(ctx, agent, deps.model)
|
|
811
|
+
});
|
|
812
|
+
agent = {
|
|
813
|
+
name: options.name,
|
|
814
|
+
description: options.description,
|
|
815
|
+
instructions: options.instructions ?? "",
|
|
816
|
+
tools,
|
|
817
|
+
skills,
|
|
818
|
+
subagents,
|
|
819
|
+
turn,
|
|
820
|
+
maxRounds
|
|
821
|
+
};
|
|
822
|
+
return agent;
|
|
823
|
+
}
|
|
824
|
+
function execTurn(ctx, agent, input, tags = []) {
|
|
825
|
+
return ctx.exec({
|
|
826
|
+
flow: agent.turn,
|
|
827
|
+
input,
|
|
828
|
+
name: agent.name,
|
|
829
|
+
tags: [...tags]
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
async function send(ctx, session, agent, input) {
|
|
833
|
+
const current = await ctx.resolve(session);
|
|
834
|
+
const result = await execTurn(ctx, agent, {
|
|
835
|
+
...input,
|
|
836
|
+
messages: [...current.state.messages, ...input.messages ?? []]
|
|
837
|
+
});
|
|
838
|
+
await patchMaterial(ctx, session, [{
|
|
839
|
+
op: "replace",
|
|
840
|
+
path: "/messages",
|
|
841
|
+
value: serializeMessages(result.messages)
|
|
842
|
+
}], { expectedRevision: current.revision });
|
|
843
|
+
return result;
|
|
844
|
+
}
|
|
845
|
+
function channel(options) {
|
|
846
|
+
const flowOptions = {
|
|
847
|
+
name: options.name,
|
|
848
|
+
tags: agentStepTags({
|
|
849
|
+
workflow: true,
|
|
850
|
+
kind: "channel"
|
|
851
|
+
}, options.tags),
|
|
852
|
+
factory: async (ctx) => execTurn(ctx, options.agent, await options.input(ctx))
|
|
853
|
+
};
|
|
854
|
+
return typeof options.parse === "function" ? (0, _pumped_fn_lite.flow)({
|
|
855
|
+
...flowOptions,
|
|
856
|
+
parse: options.parse
|
|
857
|
+
}) : (0, _pumped_fn_lite.flow)({
|
|
858
|
+
...flowOptions,
|
|
859
|
+
parse: options.parse
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
function schedule(options) {
|
|
863
|
+
return (0, _pumped_fn_lite.flow)({
|
|
864
|
+
name: options.name,
|
|
865
|
+
tags: agentStepTags({
|
|
866
|
+
workflow: true,
|
|
867
|
+
kind: "schedule"
|
|
868
|
+
}, options.tags),
|
|
869
|
+
factory: async (ctx) => execTurn(ctx, options.agent, await options.input(ctx))
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
function judge(options) {
|
|
873
|
+
return options;
|
|
874
|
+
}
|
|
875
|
+
function suite(options) {
|
|
876
|
+
const judges = options.judges ?? [];
|
|
877
|
+
assertJudgeQuorum(judges);
|
|
878
|
+
return {
|
|
879
|
+
name: options.name,
|
|
880
|
+
agent: options.agent,
|
|
881
|
+
cases: options.cases,
|
|
882
|
+
judges
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function includes(text) {
|
|
886
|
+
return (result) => ({
|
|
887
|
+
name: `output includes "${text}"`,
|
|
888
|
+
passed: result.content.includes(text)
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
function used(name) {
|
|
892
|
+
return (result) => ({
|
|
893
|
+
name: `tool used "${name}"`,
|
|
894
|
+
passed: result.toolResults.some((call) => call.name === name)
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
function loaded(name) {
|
|
898
|
+
return (result) => ({
|
|
899
|
+
name: `skill loaded "${name}"`,
|
|
900
|
+
passed: result.skillResults.some((call) => call.name === name)
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
function delegated(name) {
|
|
904
|
+
return (result) => ({
|
|
905
|
+
name: `subagent used "${name}"`,
|
|
906
|
+
passed: result.subagentResults.some((call) => call.name === name)
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
async function runEval(ctx, target) {
|
|
910
|
+
assertJudgeQuorum(target.judges);
|
|
911
|
+
const cases = [];
|
|
912
|
+
for (const item of target.cases) {
|
|
913
|
+
const result = await execTurn(ctx, target.agent, item.input);
|
|
914
|
+
const checks = await runEvalChecks(result, item.checks ?? []);
|
|
915
|
+
const judges = await runEvalJudges(ctx, result, target.judges);
|
|
916
|
+
cases.push({
|
|
917
|
+
name: item.name,
|
|
918
|
+
result,
|
|
919
|
+
checks,
|
|
920
|
+
judges,
|
|
921
|
+
passed: checks.every((check) => check.passed) && judges.every((judge) => judge.passed)
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
name: target.name,
|
|
926
|
+
cases,
|
|
927
|
+
passed: cases.every((item) => item.passed)
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
async function inspect(log, query) {
|
|
931
|
+
const entries = (await log.entries(query)).filter((entry) => entry.key.taskId === query.taskId && entry.key.runId === query.runId);
|
|
932
|
+
if (!entries[0]) throw new Error("Run not found");
|
|
933
|
+
const steps = entries.map(runStep);
|
|
934
|
+
return {
|
|
935
|
+
taskId: query.taskId,
|
|
936
|
+
runId: query.runId,
|
|
937
|
+
status: steps.some((item) => item.status === "pending") ? "pending" : "completed",
|
|
938
|
+
steps
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function summary(report) {
|
|
942
|
+
return jsonValue({
|
|
943
|
+
name: report.name,
|
|
944
|
+
passed: report.passed,
|
|
945
|
+
cases: report.cases.map((item) => ({
|
|
946
|
+
name: item.name,
|
|
947
|
+
passed: item.passed,
|
|
948
|
+
output: item.result.content,
|
|
949
|
+
checks: item.checks,
|
|
950
|
+
judges: item.judges,
|
|
951
|
+
tools: item.result.toolResults.map((call) => ({
|
|
952
|
+
name: call.name,
|
|
953
|
+
output: call.output
|
|
954
|
+
})),
|
|
955
|
+
skills: item.result.skillResults.map((call) => ({ name: call.name })),
|
|
956
|
+
subagents: item.result.subagentResults.map((call) => ({
|
|
957
|
+
name: call.name,
|
|
958
|
+
output: call.output.content
|
|
959
|
+
})),
|
|
960
|
+
events: item.result.events.map((event) => ({
|
|
961
|
+
type: event.type,
|
|
962
|
+
agentName: event.agentName,
|
|
963
|
+
targetName: event.targetName,
|
|
964
|
+
round: event.round
|
|
965
|
+
}))
|
|
966
|
+
}))
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
function http(options) {
|
|
970
|
+
return (0, _pumped_fn_lite.flow)({
|
|
971
|
+
name: options.name ?? `${options.agent.name}-http`,
|
|
972
|
+
parse: (0, _pumped_fn_lite.typed)(),
|
|
973
|
+
tags: agentStepTags({
|
|
974
|
+
workflow: true,
|
|
975
|
+
kind: "channel"
|
|
976
|
+
}),
|
|
977
|
+
factory: async (ctx) => {
|
|
978
|
+
const request = ctx.input;
|
|
979
|
+
const input = options.input ? await options.input(request) : await request.json();
|
|
980
|
+
const tags = options.tags ? await options.tags(request) : [];
|
|
981
|
+
return Response.json(jsonValue(await execTurn(ctx, options.agent, input, tags)));
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
async function executeAgentTurn(ctx, agent, model) {
|
|
986
|
+
const messages = initialMessages(ctx.input);
|
|
987
|
+
const loadedSkills = [];
|
|
988
|
+
const skillResults = [];
|
|
989
|
+
const toolResults = [];
|
|
990
|
+
const subagentResults = [];
|
|
991
|
+
const maxRounds = ctx.input.maxRounds ?? agent.maxRounds;
|
|
992
|
+
let content = "";
|
|
993
|
+
let rounds = 0;
|
|
994
|
+
const startIndex = (await ctx.resolve(events)).events.length;
|
|
995
|
+
await recordAgentEvent(ctx, {
|
|
996
|
+
type: "agent_start",
|
|
997
|
+
agentName: agent.name,
|
|
998
|
+
input: ctx.input
|
|
999
|
+
});
|
|
1000
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
1001
|
+
rounds = round + 1;
|
|
1002
|
+
await recordAgentEvent(ctx, {
|
|
1003
|
+
type: "agent_model_start",
|
|
1004
|
+
agentName: agent.name,
|
|
1005
|
+
round,
|
|
1006
|
+
input: messages
|
|
1007
|
+
});
|
|
1008
|
+
const response = await model.complete(ctx, {
|
|
1009
|
+
agentName: agent.name,
|
|
1010
|
+
instructions: agent.instructions,
|
|
1011
|
+
messages,
|
|
1012
|
+
tools: agent.tools.map(agentToolCapability),
|
|
1013
|
+
skills: agent.skills.map(agentSkillCapability),
|
|
1014
|
+
loadedSkills,
|
|
1015
|
+
subagents: agent.subagents.map(agentSubagentCapability),
|
|
1016
|
+
round
|
|
1017
|
+
});
|
|
1018
|
+
content = response.content;
|
|
1019
|
+
await recordAgentEvent(ctx, {
|
|
1020
|
+
type: "agent_model_end",
|
|
1021
|
+
agentName: agent.name,
|
|
1022
|
+
round,
|
|
1023
|
+
output: response
|
|
1024
|
+
});
|
|
1025
|
+
const skillCalls = response.skillCalls ?? [];
|
|
1026
|
+
const toolCalls = response.toolCalls ?? [];
|
|
1027
|
+
const subagentCalls = response.subagentCalls ?? [];
|
|
1028
|
+
if (response.content) messages.push({
|
|
1029
|
+
role: "assistant",
|
|
1030
|
+
content: response.content
|
|
1031
|
+
});
|
|
1032
|
+
if (response.stop === true || skillCalls.length === 0 && toolCalls.length === 0 && subagentCalls.length === 0) break;
|
|
1033
|
+
for (const call of skillCalls) {
|
|
1034
|
+
const result = await executeAgentSkill(ctx, agent, call);
|
|
1035
|
+
loadedSkills.push({
|
|
1036
|
+
name: result.name,
|
|
1037
|
+
content: result.content,
|
|
1038
|
+
description: findByName(agent.skills, result.name, "skill").description
|
|
1039
|
+
});
|
|
1040
|
+
skillResults.push(result);
|
|
1041
|
+
messages.push({
|
|
1042
|
+
role: "skill",
|
|
1043
|
+
name: result.name,
|
|
1044
|
+
content: result.content
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
for (const call of toolCalls) {
|
|
1048
|
+
const result = await executeAgentTool(ctx, agent, call);
|
|
1049
|
+
toolResults.push(result);
|
|
1050
|
+
messages.push({
|
|
1051
|
+
role: "tool",
|
|
1052
|
+
name: result.name,
|
|
1053
|
+
content: stringifyAgentValue(result.output)
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
for (const call of subagentCalls) {
|
|
1057
|
+
const result = await executeAgentSubagent(ctx, agent, call);
|
|
1058
|
+
subagentResults.push(result);
|
|
1059
|
+
messages.push({
|
|
1060
|
+
role: "subagent",
|
|
1061
|
+
name: result.name,
|
|
1062
|
+
content: result.output.content
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
await recordAgentEvent(ctx, {
|
|
1067
|
+
type: "agent_end",
|
|
1068
|
+
agentName: agent.name,
|
|
1069
|
+
output: content
|
|
1070
|
+
});
|
|
1071
|
+
const buffer = await ctx.resolve(events);
|
|
1072
|
+
return {
|
|
1073
|
+
agentName: agent.name,
|
|
1074
|
+
content,
|
|
1075
|
+
messages,
|
|
1076
|
+
skillResults,
|
|
1077
|
+
toolResults,
|
|
1078
|
+
subagentResults,
|
|
1079
|
+
rounds,
|
|
1080
|
+
events: buffer.events.slice(startIndex)
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
async function executeAgentSkill(ctx, agent, call) {
|
|
1084
|
+
const target = findByName(agent.skills, call.name, "skill");
|
|
1085
|
+
await recordAgentEvent(ctx, {
|
|
1086
|
+
type: "agent_skill_start",
|
|
1087
|
+
agentName: agent.name,
|
|
1088
|
+
targetName: target.name
|
|
1089
|
+
});
|
|
1090
|
+
const content = await ctx.exec({
|
|
1091
|
+
fn: (skillCtx) => target.load(skillCtx),
|
|
1092
|
+
params: [],
|
|
1093
|
+
name: target.name,
|
|
1094
|
+
tags: [agentStepTag({
|
|
1095
|
+
workflow: true,
|
|
1096
|
+
kind: "skill"
|
|
1097
|
+
})]
|
|
1098
|
+
});
|
|
1099
|
+
await recordAgentEvent(ctx, {
|
|
1100
|
+
type: "agent_skill_end",
|
|
1101
|
+
agentName: agent.name,
|
|
1102
|
+
targetName: target.name,
|
|
1103
|
+
output: content
|
|
1104
|
+
});
|
|
1105
|
+
return {
|
|
1106
|
+
name: target.name,
|
|
1107
|
+
id: call.id,
|
|
1108
|
+
content
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
async function executeAgentTool(ctx, agent, call) {
|
|
1112
|
+
const target = findByName(agent.tools, call.name, "tool");
|
|
1113
|
+
await recordAgentEvent(ctx, {
|
|
1114
|
+
type: "agent_tool_start",
|
|
1115
|
+
agentName: agent.name,
|
|
1116
|
+
targetName: target.name,
|
|
1117
|
+
input: call.input
|
|
1118
|
+
});
|
|
1119
|
+
const output = await ctx.exec({
|
|
1120
|
+
flow: target.flow,
|
|
1121
|
+
rawInput: call.input,
|
|
1122
|
+
name: target.name,
|
|
1123
|
+
tags: [agentStepTag({
|
|
1124
|
+
workflow: true,
|
|
1125
|
+
kind: "tool"
|
|
1126
|
+
}, target.flow.tags)]
|
|
1127
|
+
});
|
|
1128
|
+
await recordAgentEvent(ctx, {
|
|
1129
|
+
type: "agent_tool_end",
|
|
1130
|
+
agentName: agent.name,
|
|
1131
|
+
targetName: target.name,
|
|
1132
|
+
output
|
|
1133
|
+
});
|
|
1134
|
+
return {
|
|
1135
|
+
name: target.name,
|
|
1136
|
+
id: call.id,
|
|
1137
|
+
input: call.input,
|
|
1138
|
+
output
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
async function executeAgentSubagent(ctx, agent, call) {
|
|
1142
|
+
const target = findByName(agent.subagents, call.name, "subagent");
|
|
1143
|
+
await recordAgentEvent(ctx, {
|
|
1144
|
+
type: "agent_subagent_start",
|
|
1145
|
+
agentName: agent.name,
|
|
1146
|
+
targetName: target.name,
|
|
1147
|
+
input: call.input
|
|
1148
|
+
});
|
|
1149
|
+
const output = await ctx.exec({
|
|
1150
|
+
flow: target.agent.turn,
|
|
1151
|
+
input: call.input,
|
|
1152
|
+
name: target.name,
|
|
1153
|
+
tags: [agentStepTag({
|
|
1154
|
+
workflow: true,
|
|
1155
|
+
kind: "subagent"
|
|
1156
|
+
}, target.agent.turn.tags)]
|
|
1157
|
+
});
|
|
1158
|
+
await recordAgentEvent(ctx, {
|
|
1159
|
+
type: "agent_subagent_end",
|
|
1160
|
+
agentName: agent.name,
|
|
1161
|
+
targetName: target.name,
|
|
1162
|
+
output
|
|
1163
|
+
});
|
|
1164
|
+
return {
|
|
1165
|
+
name: target.name,
|
|
1166
|
+
id: call.id,
|
|
1167
|
+
input: call.input,
|
|
1168
|
+
output
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
async function recordAgentEvent(ctx, event) {
|
|
1172
|
+
return (await ctx.resolve(events)).record(event);
|
|
1173
|
+
}
|
|
1174
|
+
function initialMessages(input) {
|
|
1175
|
+
return [...input.messages ?? [], ...input.prompt ? [{
|
|
1176
|
+
role: "user",
|
|
1177
|
+
content: input.prompt
|
|
1178
|
+
}] : []];
|
|
1179
|
+
}
|
|
1180
|
+
function agentStepTags(defaults, source = []) {
|
|
1181
|
+
return [agentStepTag(defaults, source), ...source.filter((tagged) => tagged.key !== step.key)];
|
|
1182
|
+
}
|
|
1183
|
+
function agentStepTag(defaults, source = []) {
|
|
1184
|
+
return step(Object.assign({}, defaults, ...step.collect(source)));
|
|
1185
|
+
}
|
|
1186
|
+
function agentToolCapability(tool) {
|
|
1187
|
+
return {
|
|
1188
|
+
name: tool.name,
|
|
1189
|
+
description: tool.description
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function agentSkillCapability(skill) {
|
|
1193
|
+
return {
|
|
1194
|
+
name: skill.name,
|
|
1195
|
+
description: skill.description ?? ""
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function agentSubagentCapability(subagent) {
|
|
1199
|
+
return {
|
|
1200
|
+
name: subagent.name,
|
|
1201
|
+
description: subagent.description
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
function findByName(items, name, kind) {
|
|
1205
|
+
const found = items.find((item) => item.name === name);
|
|
1206
|
+
if (!found) throw new Error(`Agent ${kind} "${name}" not found`);
|
|
1207
|
+
return found;
|
|
1208
|
+
}
|
|
1209
|
+
function runStep(entry) {
|
|
1210
|
+
if (entry.status === "pending") return {
|
|
1211
|
+
key: entry.key,
|
|
1212
|
+
status: entry.status,
|
|
1213
|
+
targetName: entry.targetName,
|
|
1214
|
+
input: entry.input,
|
|
1215
|
+
kind: entry.kind
|
|
1216
|
+
};
|
|
1217
|
+
if (entry.status === "resolved") return {
|
|
1218
|
+
key: entry.key,
|
|
1219
|
+
status: entry.status,
|
|
1220
|
+
targetName: entry.targetName,
|
|
1221
|
+
output: entry.value
|
|
1222
|
+
};
|
|
1223
|
+
return {
|
|
1224
|
+
key: entry.key,
|
|
1225
|
+
status: entry.status,
|
|
1226
|
+
targetName: entry.targetName,
|
|
1227
|
+
output: entry.result
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
function stringifyAgentValue(value) {
|
|
1231
|
+
if (typeof value === "string") return value;
|
|
1232
|
+
if (value === void 0) return String(value);
|
|
1233
|
+
if (typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") return String(value);
|
|
1234
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1235
|
+
return JSON.stringify(value, (_key, item) => {
|
|
1236
|
+
if (typeof item === "bigint" || typeof item === "symbol" || typeof item === "function") return String(item);
|
|
1237
|
+
if (typeof item === "object" && item !== null) {
|
|
1238
|
+
if (seen.has(item)) return "[Circular]";
|
|
1239
|
+
seen.add(item);
|
|
1240
|
+
}
|
|
1241
|
+
return item;
|
|
1242
|
+
}) ?? String(value);
|
|
1243
|
+
}
|
|
1244
|
+
function jsonValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1245
|
+
if (value === null) return null;
|
|
1246
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
1247
|
+
if (value === void 0 || typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") return String(value);
|
|
1248
|
+
if (Array.isArray(value)) return value.map((item) => jsonValue(item, seen));
|
|
1249
|
+
if (typeof value === "object") {
|
|
1250
|
+
if (seen.has(value)) return "[Circular]";
|
|
1251
|
+
seen.add(value);
|
|
1252
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonValue(item, seen)]));
|
|
1253
|
+
}
|
|
1254
|
+
return String(value);
|
|
1255
|
+
}
|
|
1256
|
+
function serializeMessages(messages) {
|
|
1257
|
+
return messages.map((message) => {
|
|
1258
|
+
const serialized = {
|
|
1259
|
+
role: message.role,
|
|
1260
|
+
content: message.content
|
|
1261
|
+
};
|
|
1262
|
+
if (message.name !== void 0) serialized["name"] = message.name;
|
|
1263
|
+
return serialized;
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
async function runEvalChecks(result, checks) {
|
|
1267
|
+
const results = [];
|
|
1268
|
+
for (const check of checks) results.push(await check(result));
|
|
1269
|
+
return results;
|
|
1270
|
+
}
|
|
1271
|
+
function assertJudgeQuorum(judges) {
|
|
1272
|
+
if (judges.length === 1) throw new Error("Agent evals require zero judges or at least two judges");
|
|
1273
|
+
}
|
|
1274
|
+
async function runEvalJudges(ctx, result, judges) {
|
|
1275
|
+
const results = [];
|
|
1276
|
+
for (const judge of judges) results.push(await judge.evaluate(ctx, result));
|
|
1277
|
+
return results;
|
|
1278
|
+
}
|
|
1279
|
+
//#endregion
|
|
1280
|
+
exports.CliWorkerError = CliWorkerError;
|
|
1281
|
+
exports.MaterialConflictError = MaterialConflictError;
|
|
1282
|
+
Object.defineProperty(exports, "SuspendSignal", {
|
|
1283
|
+
enumerable: true,
|
|
1284
|
+
get: function() {
|
|
1285
|
+
return _pumped_fn_lite_extension_suspense.SuspendSignal;
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
Object.defineProperty(exports, "SuspenseSignal", {
|
|
1289
|
+
enumerable: true,
|
|
1290
|
+
get: function() {
|
|
1291
|
+
return _pumped_fn_lite_extension_suspense.SuspenseSignal;
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
exports.WorkerRegistry = WorkerRegistry;
|
|
1295
|
+
exports.abortSignal = abortSignal;
|
|
1296
|
+
exports.agent = agent;
|
|
1297
|
+
exports.channel = channel;
|
|
1298
|
+
exports.claudeCliWorker = claudeCliWorker;
|
|
1299
|
+
exports.claudeHarness = claudeHarness;
|
|
1300
|
+
exports.cliWorker = cliWorker;
|
|
1301
|
+
exports.codexCliWorker = codexCliWorker;
|
|
1302
|
+
exports.codexHarness = codexHarness;
|
|
1303
|
+
exports.delegated = delegated;
|
|
1304
|
+
exports.derivedMaterial = derivedMaterial;
|
|
1305
|
+
exports.events = events;
|
|
1306
|
+
exports.extension = extension;
|
|
1307
|
+
exports.formatStepKey = formatStepKey;
|
|
1308
|
+
exports.guard = guard;
|
|
1309
|
+
exports.http = http;
|
|
1310
|
+
exports.includes = includes;
|
|
1311
|
+
exports.inspect = inspect;
|
|
1312
|
+
exports.judge = judge;
|
|
1313
|
+
exports.loaded = loaded;
|
|
1314
|
+
exports.material = material;
|
|
1315
|
+
exports.materialKind = materialKind;
|
|
1316
|
+
exports.model = model;
|
|
1317
|
+
exports.patchMaterial = patchMaterial;
|
|
1318
|
+
exports.runCli = runCli;
|
|
1319
|
+
exports.runEval = runEval;
|
|
1320
|
+
exports.runtime = runtime;
|
|
1321
|
+
exports.sandbox = sandbox;
|
|
1322
|
+
exports.schedule = schedule;
|
|
1323
|
+
exports.send = send;
|
|
1324
|
+
exports.session = session;
|
|
1325
|
+
exports.skill = skill;
|
|
1326
|
+
exports.step = step;
|
|
1327
|
+
exports.sub = sub;
|
|
1328
|
+
exports.suite = suite;
|
|
1329
|
+
exports.summary = summary;
|
|
1330
|
+
exports.tool = tool;
|
|
1331
|
+
exports.used = used;
|
|
1332
|
+
exports.workerRegistry = workerRegistry;
|
|
1333
|
+
exports.workers = workers;
|
|
1334
|
+
exports.workflow = workflow;
|
|
1335
|
+
exports.workflowExtension = workflowExtension;
|
|
1336
|
+
exports.workflowRun = workflowRun;
|