@agent-compose/sdk 0.2.3 → 0.2.5
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/README.md +145 -33
- package/dist/agent/agent-loop.d.ts +83 -5
- package/dist/agent/run-agent.d.ts +34 -9
- package/dist/client.d.ts +247 -99
- package/dist/index.d.ts +26 -11
- package/dist/index.js +1967 -745
- package/dist/processors/builtins.d.ts +35 -0
- package/dist/processors/index.d.ts +4 -0
- package/dist/processors/processor.d.ts +91 -0
- package/dist/processors/processor.test.d.ts +1 -0
- package/dist/processors/runner.d.ts +19 -0
- package/dist/request-context/index.d.ts +2 -0
- package/dist/request-context/request-context.d.ts +159 -0
- package/dist/request-context/request-context.test.d.ts +1 -0
- package/dist/runtimes/claude.d.ts +27 -50
- package/dist/runtimes/openai-desktop.js +1918 -741
- package/dist/runtimes/vercel.d.ts +34 -0
- package/dist/runtimes/vercel.js +474 -0
- package/dist/sandbox.d.ts +29 -25
- package/dist/step-invocation/__tests__/invoker.test.d.ts +1 -0
- package/dist/step-invocation/__tests__/protocol.test.d.ts +1 -0
- package/dist/step-invocation/__tests__/server.test.d.ts +1 -0
- package/dist/step-invocation/index.d.ts +25 -0
- package/dist/step-invocation/invoker.d.ts +65 -0
- package/dist/step-invocation/protocol.d.ts +44 -0
- package/dist/step-invocation/server.d.ts +63 -0
- package/dist/step-invocation/types.d.ts +72 -0
- package/dist/tools/coding.d.ts +49 -0
- package/dist/tools/coding.test.d.ts +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/types/events.d.ts +36 -0
- package/dist/types/execution-context.d.ts +22 -0
- package/dist/types/runtime.d.ts +32 -0
- package/dist/types/sandbox-environment.d.ts +5 -2
- package/dist/types/sandbox.d.ts +14 -12
- package/dist/types/workflow-metadata.d.ts +51 -0
- package/dist/types/workflow-plan.d.ts +19 -0
- package/dist/types/workflow.d.ts +57 -17
- package/dist/utils/bundler.d.ts +62 -3
- package/dist/workflow-steps/__tests__/observability.test.d.ts +1 -0
- package/dist/workflow-steps/index.d.ts +10 -0
- package/dist/workflow-steps/observability.d.ts +58 -0
- package/dist/workflow-steps/runner.d.ts +96 -0
- package/dist/workflow-steps/step.d.ts +25 -0
- package/dist/workflow-steps/types.d.ts +135 -0
- package/dist/workflow-steps/workflow-steps.test.d.ts +1 -0
- package/dist/workflow-steps/workflow.d.ts +50 -0
- package/dist/workflows/engine.d.ts +27 -13
- package/dist/workflows/invoke-child.d.ts +10 -0
- package/package.json +25 -15
- package/src/agent/agent-loop.ts +197 -26
- package/src/agent/run-agent.ts +40 -15
- package/src/client.ts +326 -76
- package/src/index.ts +124 -10
- package/src/processors/builtins.ts +72 -0
- package/src/processors/index.ts +15 -0
- package/src/processors/processor.ts +103 -0
- package/src/processors/runner.ts +42 -0
- package/src/request-context/index.ts +17 -0
- package/src/request-context/request-context.ts +302 -0
- package/src/runtimes/claude.ts +123 -254
- package/src/runtimes/vercel.ts +180 -0
- package/src/sandbox.ts +53 -21
- package/src/step-invocation/index.ts +33 -0
- package/src/step-invocation/invoker.ts +204 -0
- package/src/step-invocation/protocol.ts +57 -0
- package/src/step-invocation/server.ts +184 -0
- package/src/step-invocation/types.ts +70 -0
- package/src/tools/coding.ts +126 -0
- package/src/tools/index.ts +8 -0
- package/src/types/events.ts +40 -0
- package/src/types/execution-context.ts +30 -0
- package/src/types/runtime.ts +24 -0
- package/src/types/sandbox-environment.ts +7 -5
- package/src/types/sandbox.ts +16 -12
- package/src/types/workflow-metadata.ts +84 -0
- package/src/types/workflow-plan.ts +24 -0
- package/src/types/workflow.ts +139 -25
- package/src/utils/bundler.ts +206 -18
- package/src/utils/source-loader.ts +2 -2
- package/src/workflow-steps/index.ts +30 -0
- package/src/workflow-steps/observability.ts +103 -0
- package/src/workflow-steps/runner.ts +244 -0
- package/src/workflow-steps/step.ts +38 -0
- package/src/workflow-steps/types.ts +134 -0
- package/src/workflow-steps/workflow.ts +95 -0
- package/src/workflows/engine.ts +69 -40
- package/src/workflows/invoke-child.ts +29 -0
package/dist/index.js
CHANGED
|
@@ -17,24 +17,1423 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
+
// src/runtimes/vercel.ts
|
|
21
|
+
import { streamText, stepCountIs, tool } from "ai";
|
|
22
|
+
|
|
20
23
|
// src/types/runtime.ts
|
|
21
24
|
function defineRuntime(pkg) {
|
|
22
25
|
if (typeof pkg.create !== "function")
|
|
23
26
|
throw new Error("defineRuntime: 'create' is required");
|
|
24
27
|
return pkg;
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
|
|
30
|
+
// src/tools/coding.ts
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { isAbsolute, join } from "node:path/posix";
|
|
33
|
+
function q(value) {
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
}
|
|
36
|
+
function resolvePath(path, cwd) {
|
|
37
|
+
return cwd && !isAbsolute(path) ? join(cwd, path) : path;
|
|
38
|
+
}
|
|
39
|
+
async function readFile(sandbox, path, opts) {
|
|
40
|
+
const script = `
|
|
41
|
+
const fs = require("fs");
|
|
42
|
+
const path = process.argv[1];
|
|
43
|
+
const offset = process.argv[2] ? Number(process.argv[2]) : 1;
|
|
44
|
+
const limit = process.argv[3] ? Number(process.argv[3]) : 2000;
|
|
45
|
+
const data = fs.readFileSync(path, "utf8");
|
|
46
|
+
const lines = data.split(/\\r?\\n/);
|
|
47
|
+
const start = Math.max(1, offset);
|
|
48
|
+
const end = Math.min(lines.length, start + Math.max(0, limit) - 1);
|
|
49
|
+
for (let i = start; i <= end; i++) console.log(String(i) + ": " + lines[i - 1]);
|
|
50
|
+
`;
|
|
51
|
+
const args = [q(path), opts?.offset !== undefined ? String(opts.offset) : "", opts?.limit !== undefined ? String(opts.limit) : ""].filter(Boolean).map(q).join(" ");
|
|
52
|
+
const { stdout } = await sandbox.commands.run(`node -e ${q(script)} ${args}`, { cwd: opts?.cwd, timeoutMs: 30000 });
|
|
53
|
+
return stdout;
|
|
54
|
+
}
|
|
55
|
+
async function readRawFile(sandbox, path, cwd) {
|
|
56
|
+
const script = `const fs = require("fs"); process.stdout.write(fs.readFileSync(process.argv[1], "utf8"));`;
|
|
57
|
+
const { stdout } = await sandbox.commands.run(`node -e ${q(script)} ${q(path)}`, { cwd, timeoutMs: 30000 });
|
|
58
|
+
return stdout;
|
|
59
|
+
}
|
|
60
|
+
var readTool = {
|
|
61
|
+
name: "Read",
|
|
62
|
+
description: "Read a UTF-8 text file from the sandbox. Returns numbered lines. Use offset/limit for large files.",
|
|
63
|
+
inputSchema: z.object({
|
|
64
|
+
path: z.string().describe("File path to read. Relative paths resolve from the agent working directory."),
|
|
65
|
+
offset: z.number().int().positive().optional().describe("1-based line number to start at."),
|
|
66
|
+
limit: z.number().int().positive().max(5000).optional().describe("Maximum number of lines to return.")
|
|
67
|
+
}),
|
|
68
|
+
execute: ({ path, offset, limit }, ctx) => readFile(ctx.sandbox, path, { offset, limit, cwd: ctx.cwd })
|
|
69
|
+
};
|
|
70
|
+
var writeTool = {
|
|
71
|
+
name: "Write",
|
|
72
|
+
description: "Write UTF-8 content to a file in the sandbox, creating or replacing the file.",
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
path: z.string().describe("File path to write. Relative paths resolve from the agent working directory."),
|
|
75
|
+
content: z.string().describe("Complete file contents to write.")
|
|
76
|
+
}),
|
|
77
|
+
execute: async ({ path, content }, ctx) => {
|
|
78
|
+
const target = resolvePath(path, ctx.cwd);
|
|
79
|
+
await ctx.sandbox.files.write(target, content);
|
|
80
|
+
return `Wrote ${content.length} bytes to ${target}`;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var editTool = {
|
|
84
|
+
name: "Edit",
|
|
85
|
+
description: "Edit a text file by replacing an exact string. Fails if the old string is not found or is ambiguous without replaceAll.",
|
|
86
|
+
inputSchema: z.object({
|
|
87
|
+
path: z.string().describe("File path to edit. Relative paths resolve from the agent working directory."),
|
|
88
|
+
oldString: z.string().min(1).describe("Exact text to replace."),
|
|
89
|
+
newString: z.string().describe("Replacement text."),
|
|
90
|
+
replaceAll: z.boolean().optional().describe("Replace every occurrence instead of requiring exactly one match.")
|
|
91
|
+
}),
|
|
92
|
+
execute: async ({ path, oldString, newString, replaceAll }, ctx) => {
|
|
93
|
+
const target = resolvePath(path, ctx.cwd);
|
|
94
|
+
const before = await readRawFile(ctx.sandbox, target, ctx.cwd);
|
|
95
|
+
const count = before.split(oldString).length - 1;
|
|
96
|
+
if (count === 0)
|
|
97
|
+
throw new Error(`Edit failed: oldString not found in ${target}`);
|
|
98
|
+
if (!replaceAll && count !== 1)
|
|
99
|
+
throw new Error(`Edit failed: oldString occurs ${count} times in ${target}; set replaceAll=true or make oldString more specific`);
|
|
100
|
+
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
101
|
+
await ctx.sandbox.files.write(target, after);
|
|
102
|
+
return `Edited ${target}: replaced ${replaceAll ? count : 1} occurrence${(replaceAll ? count : 1) === 1 ? "" : "s"}`;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var bashTool = {
|
|
106
|
+
name: "Bash",
|
|
107
|
+
description: "Run a shell command in the sandbox. Use for file discovery/search (e.g. globbing/grep via shell commands), tests, builds, and package commands.",
|
|
108
|
+
inputSchema: z.object({
|
|
109
|
+
command: z.string().min(1).describe("Shell command to execute."),
|
|
110
|
+
timeoutMs: z.number().int().positive().max(3600000).optional().describe("Timeout in milliseconds."),
|
|
111
|
+
cwd: z.string().optional().describe("Optional command working directory. Defaults to the agent working directory.")
|
|
112
|
+
}),
|
|
113
|
+
execute: async ({ command, timeoutMs, cwd }, ctx) => {
|
|
114
|
+
const result = await ctx.sandbox.commands.run(command, {
|
|
115
|
+
cwd: cwd ?? ctx.cwd,
|
|
116
|
+
timeoutMs: timeoutMs ?? 120000
|
|
117
|
+
});
|
|
118
|
+
if (result.exitCode !== 0)
|
|
119
|
+
throw new Error(`Command failed with exit code ${result.exitCode}${result.stdout ? `
|
|
120
|
+
${result.stdout}` : ""}`);
|
|
121
|
+
return result.stdout;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var codingTools = [readTool, writeTool, editTool, bashTool];
|
|
125
|
+
// src/processors/runner.ts
|
|
126
|
+
async function runProcessorChain(processors, selectHook, initial, ctx) {
|
|
127
|
+
let current = initial;
|
|
128
|
+
for (const p of processors) {
|
|
129
|
+
const fn = selectHook(p);
|
|
130
|
+
if (!fn)
|
|
131
|
+
continue;
|
|
132
|
+
const verdict = await fn.call(p, current, ctx);
|
|
133
|
+
if (verdict.kind !== "continue")
|
|
134
|
+
return verdict;
|
|
135
|
+
current = verdict.value;
|
|
136
|
+
}
|
|
137
|
+
return { kind: "continue", value: current };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/request-context/request-context.ts
|
|
141
|
+
var AC_RESERVED_PREFIX = "ac__";
|
|
142
|
+
var AC_TEAM_ID = "ac__teamId";
|
|
143
|
+
var AC_RUN_ID = "ac__runId";
|
|
144
|
+
var AC_WORKFLOW_ID = "ac__workflowId";
|
|
145
|
+
var AC_FACTORY_ID = "ac__factoryId";
|
|
146
|
+
var AC_API_KEY_SCOPES = "ac__apiKeyScopes";
|
|
147
|
+
var AC_PARENT_RUN_ID = "ac__parentRunId";
|
|
148
|
+
var AC_ABORT_SIGNAL = "ac__abortSignal";
|
|
149
|
+
var RESERVED_KEYS = new Set([
|
|
150
|
+
AC_TEAM_ID,
|
|
151
|
+
AC_RUN_ID,
|
|
152
|
+
AC_WORKFLOW_ID,
|
|
153
|
+
AC_FACTORY_ID,
|
|
154
|
+
AC_API_KEY_SCOPES,
|
|
155
|
+
AC_PARENT_RUN_ID,
|
|
156
|
+
AC_ABORT_SIGNAL
|
|
157
|
+
]);
|
|
158
|
+
var SERIALISABLE_RESERVED_KEYS = new Set([
|
|
159
|
+
AC_TEAM_ID,
|
|
160
|
+
AC_RUN_ID,
|
|
161
|
+
AC_WORKFLOW_ID,
|
|
162
|
+
AC_FACTORY_ID,
|
|
163
|
+
AC_API_KEY_SCOPES,
|
|
164
|
+
AC_PARENT_RUN_ID
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
class ReservedKeyError extends Error {
|
|
168
|
+
key;
|
|
169
|
+
constructor(key) {
|
|
170
|
+
super(`request-context: "${key}" is reserved and cannot be mutated downstream`);
|
|
171
|
+
this.name = "ReservedKeyError";
|
|
172
|
+
this.key = key;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
class NonSerialisableValueError extends Error {
|
|
177
|
+
constructor(key, cause) {
|
|
178
|
+
super(`request-context: value for "${key}" is not JSON-serialisable`, { cause });
|
|
179
|
+
this.name = "NonSerialisableValueError";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function assertJsonSerialisable(key, value, seen = new WeakSet) {
|
|
183
|
+
if (value === null)
|
|
184
|
+
return;
|
|
185
|
+
const type = typeof value;
|
|
186
|
+
if (type === "string" || type === "boolean")
|
|
187
|
+
return;
|
|
188
|
+
if (type === "number") {
|
|
189
|
+
if (Number.isFinite(value))
|
|
190
|
+
return;
|
|
191
|
+
throw new NonSerialisableValueError(key, new TypeError("non-finite numbers are not JSON-serialisable"));
|
|
192
|
+
}
|
|
193
|
+
if (type !== "object") {
|
|
194
|
+
throw new NonSerialisableValueError(key, new TypeError(`${type} is not JSON-serialisable`));
|
|
195
|
+
}
|
|
196
|
+
const objectValue = value;
|
|
197
|
+
if (seen.has(objectValue)) {
|
|
198
|
+
throw new NonSerialisableValueError(key, new TypeError("cyclic values are not JSON-serialisable"));
|
|
199
|
+
}
|
|
200
|
+
seen.add(objectValue);
|
|
201
|
+
if (Array.isArray(value)) {
|
|
202
|
+
for (const item of value)
|
|
203
|
+
assertJsonSerialisable(key, item, seen);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (Object.getPrototypeOf(objectValue) !== Object.prototype) {
|
|
207
|
+
throw new NonSerialisableValueError(key, new TypeError("only plain objects are JSON-serialisable"));
|
|
208
|
+
}
|
|
209
|
+
for (const item of Object.values(objectValue))
|
|
210
|
+
assertJsonSerialisable(key, item, seen);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
class RequestContext {
|
|
214
|
+
reserved;
|
|
215
|
+
user;
|
|
216
|
+
constructor(reserved, user) {
|
|
217
|
+
this.reserved = Object.freeze({
|
|
218
|
+
...reserved,
|
|
219
|
+
apiKeyScopes: Object.freeze([...reserved.apiKeyScopes])
|
|
220
|
+
});
|
|
221
|
+
this.user = user;
|
|
222
|
+
}
|
|
223
|
+
static fromReserved(reserved) {
|
|
224
|
+
return new RequestContext(reserved, new Map);
|
|
225
|
+
}
|
|
226
|
+
static deserialise(wire) {
|
|
227
|
+
const ctx = new RequestContext({ ...wire.reserved, abortSignal: undefined }, new Map(Object.entries(wire.user)));
|
|
228
|
+
return ctx;
|
|
229
|
+
}
|
|
230
|
+
withAbortSignal(signal) {
|
|
231
|
+
return new RequestContext({ ...this.reserved, abortSignal: signal }, new Map(this.user));
|
|
232
|
+
}
|
|
233
|
+
get teamId() {
|
|
234
|
+
return this.reserved.teamId;
|
|
235
|
+
}
|
|
236
|
+
get runId() {
|
|
237
|
+
return this.reserved.runId;
|
|
238
|
+
}
|
|
239
|
+
get workflowId() {
|
|
240
|
+
return this.reserved.workflowId;
|
|
241
|
+
}
|
|
242
|
+
get factoryId() {
|
|
243
|
+
return this.reserved.factoryId;
|
|
244
|
+
}
|
|
245
|
+
get apiKeyScopes() {
|
|
246
|
+
return this.reserved.apiKeyScopes;
|
|
247
|
+
}
|
|
248
|
+
get parentRunId() {
|
|
249
|
+
return this.reserved.parentRunId;
|
|
250
|
+
}
|
|
251
|
+
get abortSignal() {
|
|
252
|
+
return this.reserved.abortSignal;
|
|
253
|
+
}
|
|
254
|
+
hasScope(scope) {
|
|
255
|
+
return this.reserved.apiKeyScopes.includes(scope);
|
|
256
|
+
}
|
|
257
|
+
set(key, value) {
|
|
258
|
+
if (RESERVED_KEYS.has(key) || key.startsWith(AC_RESERVED_PREFIX)) {
|
|
259
|
+
throw new ReservedKeyError(key);
|
|
260
|
+
}
|
|
261
|
+
assertJsonSerialisable(key, value);
|
|
262
|
+
this.user.set(key, value);
|
|
263
|
+
}
|
|
264
|
+
get(key) {
|
|
265
|
+
return this.user.get(key);
|
|
266
|
+
}
|
|
267
|
+
has(key) {
|
|
268
|
+
return this.user.has(key);
|
|
269
|
+
}
|
|
270
|
+
delete(key) {
|
|
271
|
+
if (RESERVED_KEYS.has(key) || key.startsWith(AC_RESERVED_PREFIX)) {
|
|
272
|
+
throw new ReservedKeyError(key);
|
|
273
|
+
}
|
|
274
|
+
return this.user.delete(key);
|
|
275
|
+
}
|
|
276
|
+
entries() {
|
|
277
|
+
return this.user.entries();
|
|
278
|
+
}
|
|
279
|
+
serialise() {
|
|
280
|
+
return {
|
|
281
|
+
reserved: {
|
|
282
|
+
teamId: this.reserved.teamId,
|
|
283
|
+
runId: this.reserved.runId,
|
|
284
|
+
workflowId: this.reserved.workflowId,
|
|
285
|
+
factoryId: this.reserved.factoryId,
|
|
286
|
+
apiKeyScopes: [...this.reserved.apiKeyScopes],
|
|
287
|
+
parentRunId: this.reserved.parentRunId
|
|
288
|
+
},
|
|
289
|
+
user: Object.fromEntries(this.user)
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
forChild(opts) {
|
|
293
|
+
return RequestContext.fromReserved({
|
|
294
|
+
teamId: this.reserved.teamId,
|
|
295
|
+
runId: opts.runId,
|
|
296
|
+
workflowId: opts.workflowId,
|
|
297
|
+
factoryId: this.reserved.factoryId,
|
|
298
|
+
apiKeyScopes: this.reserved.apiKeyScopes,
|
|
299
|
+
parentRunId: this.reserved.runId
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/utils/errors.ts
|
|
305
|
+
function formatError(err) {
|
|
306
|
+
return err instanceof Error ? err.message : String(err);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/runtimes/vercel.ts
|
|
310
|
+
function now() {
|
|
311
|
+
return new Date().toISOString();
|
|
312
|
+
}
|
|
313
|
+
function serialiseOutput(value) {
|
|
314
|
+
return typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
315
|
+
}
|
|
316
|
+
function toAgentMessages(part) {
|
|
317
|
+
const ts = now();
|
|
318
|
+
switch (part.type) {
|
|
319
|
+
case "text-delta":
|
|
320
|
+
case "text":
|
|
321
|
+
return [{ type: "text", text: String(part.text ?? ""), timestamp: ts }];
|
|
322
|
+
case "reasoning-delta":
|
|
323
|
+
case "reasoning":
|
|
324
|
+
return [{ type: "thinking", text: String(part.text ?? ""), timestamp: ts }];
|
|
325
|
+
case "tool-call":
|
|
326
|
+
return [{
|
|
327
|
+
type: "tool_use",
|
|
328
|
+
toolName: String(part.toolName ?? ""),
|
|
329
|
+
toolInput: part.input ?? {},
|
|
330
|
+
toolUseId: String(part.toolCallId ?? ""),
|
|
331
|
+
timestamp: ts
|
|
332
|
+
}];
|
|
333
|
+
case "tool-result":
|
|
334
|
+
return [{
|
|
335
|
+
type: "tool_result",
|
|
336
|
+
toolUseId: String(part.toolCallId ?? ""),
|
|
337
|
+
output: serialiseOutput(part.output),
|
|
338
|
+
isError: false,
|
|
339
|
+
timestamp: ts
|
|
340
|
+
}];
|
|
341
|
+
case "tool-error":
|
|
342
|
+
return [{
|
|
343
|
+
type: "tool_result",
|
|
344
|
+
toolUseId: String(part.toolCallId ?? ""),
|
|
345
|
+
output: formatError(part.error),
|
|
346
|
+
isError: true,
|
|
347
|
+
timestamp: ts
|
|
348
|
+
}];
|
|
349
|
+
case "error":
|
|
350
|
+
return [{ type: "error", text: formatError(part.error), timestamp: ts }];
|
|
351
|
+
default:
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
class VercelRunner {
|
|
357
|
+
sandbox;
|
|
358
|
+
options;
|
|
359
|
+
config;
|
|
360
|
+
supportsToolCallProcessor = true;
|
|
361
|
+
tools;
|
|
362
|
+
messages = [];
|
|
363
|
+
constructor(sandbox, options, config) {
|
|
364
|
+
this.sandbox = sandbox;
|
|
365
|
+
this.options = options;
|
|
366
|
+
this.config = config;
|
|
367
|
+
this.tools = config.tools ?? codingTools;
|
|
368
|
+
}
|
|
369
|
+
async gateToolCall(call, ctx) {
|
|
370
|
+
const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
|
|
371
|
+
if (verdict.kind === "continue")
|
|
372
|
+
return { kind: "allow", call: verdict.value };
|
|
373
|
+
return verdict;
|
|
374
|
+
}
|
|
375
|
+
buildTools(iteration, signal) {
|
|
376
|
+
const set = {};
|
|
377
|
+
for (const t of this.tools) {
|
|
378
|
+
if (this.options.allowedTools && !this.options.allowedTools.includes(t.name))
|
|
379
|
+
continue;
|
|
380
|
+
set[t.name] = tool({
|
|
381
|
+
description: t.description,
|
|
382
|
+
inputSchema: t.inputSchema,
|
|
383
|
+
execute: async (input, execOpts) => {
|
|
384
|
+
const call = {
|
|
385
|
+
toolName: t.name,
|
|
386
|
+
toolInput: input,
|
|
387
|
+
toolUseId: execOpts.toolCallId ?? `${t.name}-${Date.now()}`
|
|
388
|
+
};
|
|
389
|
+
const requestContext = this.options.requestContext ?? RequestContext.fromReserved({
|
|
390
|
+
teamId: "",
|
|
391
|
+
runId: "",
|
|
392
|
+
workflowId: "",
|
|
393
|
+
factoryId: null,
|
|
394
|
+
apiKeyScopes: [],
|
|
395
|
+
parentRunId: null
|
|
396
|
+
});
|
|
397
|
+
const gate = await this.gateToolCall(call, {
|
|
398
|
+
requestContext,
|
|
399
|
+
abortSignal: execOpts.abortSignal ?? signal ?? new AbortController().signal,
|
|
400
|
+
retryCount: 0,
|
|
401
|
+
agentId: this.options.agentId ?? "agent",
|
|
402
|
+
iteration
|
|
403
|
+
});
|
|
404
|
+
if (gate.kind === "deny")
|
|
405
|
+
return gate.reason;
|
|
406
|
+
if (gate.kind === "abort")
|
|
407
|
+
throw new Error(gate.reason);
|
|
408
|
+
return t.execute(gate.call.toolInput, {
|
|
409
|
+
sandbox: this.sandbox,
|
|
410
|
+
cwd: this.options.cwd,
|
|
411
|
+
abortSignal: execOpts.abortSignal ?? signal
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return set;
|
|
417
|
+
}
|
|
418
|
+
async* sendMessage(opts) {
|
|
419
|
+
const sessionId = opts.sessionId ?? `vercel-${Date.now()}`;
|
|
420
|
+
yield { type: "init", sessionId, timestamp: now() };
|
|
421
|
+
const inputMessages = [...this.messages, { role: "user", content: opts.prompt }];
|
|
422
|
+
const result = streamText({
|
|
423
|
+
model: this.config.model,
|
|
424
|
+
...this.config.system ? { system: this.config.system } : {},
|
|
425
|
+
messages: inputMessages,
|
|
426
|
+
tools: this.buildTools(opts.iteration ?? 1, opts.signal),
|
|
427
|
+
stopWhen: stepCountIs(this.options.maxTurns ?? 40),
|
|
428
|
+
abortSignal: opts.signal
|
|
429
|
+
});
|
|
430
|
+
try {
|
|
431
|
+
for await (const part of result.fullStream) {
|
|
432
|
+
for (const msg of toAgentMessages(part))
|
|
433
|
+
yield msg;
|
|
434
|
+
}
|
|
435
|
+
const response = await result.response;
|
|
436
|
+
const responseMessages = response.messages;
|
|
437
|
+
if (responseMessages) {
|
|
438
|
+
this.messages.length = 0;
|
|
439
|
+
this.messages.push(...responseMessages);
|
|
440
|
+
}
|
|
441
|
+
let usage;
|
|
442
|
+
try {
|
|
443
|
+
usage = await result.usage;
|
|
444
|
+
} catch {
|
|
445
|
+
usage = undefined;
|
|
446
|
+
}
|
|
447
|
+
if (usage) {
|
|
448
|
+
yield {
|
|
449
|
+
type: "usage",
|
|
450
|
+
inputTokens: usage.inputTokens ?? 0,
|
|
451
|
+
outputTokens: usage.outputTokens ?? 0,
|
|
452
|
+
cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
|
|
453
|
+
cacheCreationTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
|
|
454
|
+
durationMs: 0,
|
|
455
|
+
numTurns: opts.iteration ?? 1,
|
|
456
|
+
timestamp: now()
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
} catch (err) {
|
|
460
|
+
yield { type: "error", text: formatError(err), timestamp: now() };
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
yield { type: "done", sessionId, timestamp: now() };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function createVercelRuntime(config) {
|
|
467
|
+
return defineRuntime({
|
|
468
|
+
create: (sandbox, opts) => new VercelRunner(sandbox, opts, config)
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
// src/types/workflow.ts
|
|
472
|
+
import { z as z2 } from "zod";
|
|
473
|
+
|
|
474
|
+
// src/workflow-steps/types.ts
|
|
475
|
+
var WORKFLOW_BRAND = Symbol.for("@agent-compose/sdk.Workflow");
|
|
476
|
+
|
|
477
|
+
// src/types/workflow-metadata.ts
|
|
478
|
+
function extractMetadata(source) {
|
|
479
|
+
const out = {};
|
|
480
|
+
if (source.networkPolicy !== undefined)
|
|
481
|
+
out.networkPolicy = freezeMetadataValue(source.networkPolicy);
|
|
482
|
+
if (source.placeholders !== undefined)
|
|
483
|
+
out.placeholders = Object.freeze({ ...source.placeholders });
|
|
484
|
+
if (source.snapshot !== undefined)
|
|
485
|
+
out.snapshot = source.snapshot;
|
|
486
|
+
if (source.saveSnapshot !== undefined)
|
|
487
|
+
out.saveSnapshot = source.saveSnapshot;
|
|
488
|
+
if (source.processors !== undefined)
|
|
489
|
+
out.processors = Object.freeze([...source.processors]);
|
|
490
|
+
if (source.memory !== undefined)
|
|
491
|
+
out.memory = typeof source.memory === "object" && source.memory !== null ? Object.freeze({ ...source.memory }) : source.memory;
|
|
492
|
+
return Object.freeze(out);
|
|
493
|
+
}
|
|
494
|
+
function freezeMetadataValue(value) {
|
|
495
|
+
if (value === null || typeof value !== "object")
|
|
496
|
+
return value;
|
|
497
|
+
if (Array.isArray(value)) {
|
|
498
|
+
for (const v of value)
|
|
499
|
+
freezeMetadataValue(v);
|
|
500
|
+
return Object.freeze(value);
|
|
501
|
+
}
|
|
502
|
+
for (const v of Object.values(value)) {
|
|
503
|
+
freezeMetadataValue(v);
|
|
504
|
+
}
|
|
505
|
+
return Object.freeze(value);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/workflow-steps/workflow.ts
|
|
509
|
+
function createStepWorkflow(opts) {
|
|
510
|
+
if (!opts.id)
|
|
511
|
+
throw new Error("defineWorkflow: 'id' is required for step-based workflows");
|
|
512
|
+
const build = (steps) => ({
|
|
513
|
+
step(step) {
|
|
514
|
+
return build([...steps, step]);
|
|
515
|
+
},
|
|
516
|
+
build() {
|
|
517
|
+
if (steps.length === 0) {
|
|
518
|
+
throw new Error(`defineWorkflow("${opts.id}").build: no steps registered — add at least one with .step(step)`);
|
|
519
|
+
}
|
|
520
|
+
const finalStep = steps[steps.length - 1];
|
|
521
|
+
if (finalStep.output !== opts.output) {
|
|
522
|
+
throw new Error(`defineWorkflow("${opts.id}").build: final step "${finalStep.name}" output schema is not the same Zod schema instance as the workflow's declared output schema. ` + `Pass the same schema reference to both the final step and defineWorkflow.`);
|
|
523
|
+
}
|
|
524
|
+
const workflow = {
|
|
525
|
+
id: opts.id,
|
|
526
|
+
input: opts.input,
|
|
527
|
+
output: opts.output,
|
|
528
|
+
steps: Object.freeze([...steps]),
|
|
529
|
+
metadata: extractMetadata(opts)
|
|
530
|
+
};
|
|
531
|
+
Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
|
|
532
|
+
return Object.freeze(workflow);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
return build([]);
|
|
536
|
+
}
|
|
537
|
+
function isWorkflow(value) {
|
|
538
|
+
return typeof value === "object" && value !== null && value[WORKFLOW_BRAND] === true;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/types/workflow.ts
|
|
542
|
+
function compileRunForm(def, metadata) {
|
|
543
|
+
const unknownSchema = z2.unknown();
|
|
544
|
+
const step = {
|
|
545
|
+
name: "run",
|
|
546
|
+
input: unknownSchema,
|
|
547
|
+
output: unknownSchema,
|
|
548
|
+
run: async (stepCtx) => {
|
|
549
|
+
const workflowCtx = {
|
|
550
|
+
input: stepCtx.input,
|
|
551
|
+
run: stepCtx.run,
|
|
552
|
+
requestContext: stepCtx.requestContext,
|
|
553
|
+
invokeChild: stepCtx.invokeChild,
|
|
554
|
+
setMetadata: stepCtx.setMetadata,
|
|
555
|
+
step: stepCtx.step,
|
|
556
|
+
agentEvents: stepCtx.agentEvents,
|
|
557
|
+
processors: metadata.processors ?? []
|
|
558
|
+
};
|
|
559
|
+
const sandbox = stepCtx.sandbox;
|
|
560
|
+
if (!sandbox)
|
|
561
|
+
throw new Error("legacy run-form workflow requires a sandbox in StepContext");
|
|
562
|
+
return def.run(workflowCtx, sandbox);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const workflow = {
|
|
566
|
+
id: "@run-form",
|
|
567
|
+
input: unknownSchema,
|
|
568
|
+
output: unknownSchema,
|
|
569
|
+
steps: Object.freeze([step]),
|
|
570
|
+
metadata
|
|
571
|
+
};
|
|
572
|
+
Object.defineProperty(workflow, WORKFLOW_BRAND, { value: true, enumerable: false });
|
|
573
|
+
return Object.freeze(workflow);
|
|
574
|
+
}
|
|
575
|
+
function defineWorkflow(def) {
|
|
576
|
+
if ("run" in def)
|
|
577
|
+
return compileRunForm(def, extractMetadata(def));
|
|
578
|
+
return createStepWorkflow(def);
|
|
579
|
+
}
|
|
580
|
+
// src/types/sandbox-environment.ts
|
|
581
|
+
function defineSandboxEnvironment(env) {
|
|
582
|
+
if (typeof env.setup !== "function") {
|
|
583
|
+
throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
|
|
584
|
+
}
|
|
585
|
+
return defineWorkflow({
|
|
586
|
+
saveSnapshot: env.saveSnapshot ?? true,
|
|
587
|
+
run: async (_ctx, sandbox) => env.setup(sandbox)
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
// src/processors/processor.ts
|
|
591
|
+
var Verdict = {
|
|
592
|
+
continue: (value) => ({ kind: "continue", value }),
|
|
593
|
+
deny: (reason) => ({ kind: "deny", reason }),
|
|
594
|
+
abort: (reason) => ({ kind: "abort", reason })
|
|
595
|
+
};
|
|
596
|
+
// src/processors/builtins.ts
|
|
597
|
+
function denyTools(names) {
|
|
598
|
+
const banned = new Set(names);
|
|
599
|
+
return {
|
|
600
|
+
name: "denyTools",
|
|
601
|
+
processToolCall: (call) => banned.has(call.toolName) ? Verdict.deny(`tool "${call.toolName}" is denied by policy`) : Verdict.continue(call)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function requireScope(required) {
|
|
605
|
+
return {
|
|
606
|
+
name: "requireScope",
|
|
607
|
+
processInput: (prompt, ctx) => {
|
|
608
|
+
const missing = required.filter((s) => !ctx.requestContext.hasScope(s));
|
|
609
|
+
return missing.length === 0 ? Verdict.continue(prompt) : Verdict.abort(`API key is missing required scopes: ${missing.join(", ")}`);
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function redactPattern(pattern, replacement = "[REDACTED]") {
|
|
614
|
+
return {
|
|
615
|
+
name: "redactPattern",
|
|
616
|
+
processOutput: (message) => {
|
|
617
|
+
if (message.type === "text" || message.type === "thinking") {
|
|
618
|
+
const redacted = message.text.replace(pattern, replacement);
|
|
619
|
+
if (redacted !== message.text) {
|
|
620
|
+
return Verdict.continue({ ...message, text: redacted });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return Verdict.continue(message);
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
// src/client.ts
|
|
628
|
+
import { ofetch } from "ofetch";
|
|
629
|
+
|
|
630
|
+
// src/errors.ts
|
|
631
|
+
class AgentComposeError extends Error {
|
|
632
|
+
status;
|
|
633
|
+
constructor(status, message) {
|
|
634
|
+
super(message);
|
|
635
|
+
this.status = status;
|
|
636
|
+
this.name = "AgentComposeError";
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/sse.ts
|
|
641
|
+
async function* parseSseStream(body) {
|
|
642
|
+
const reader = body.getReader();
|
|
643
|
+
const decoder = new TextDecoder;
|
|
644
|
+
let buffer = "";
|
|
645
|
+
let event = "";
|
|
646
|
+
let seq = 0;
|
|
647
|
+
let dataLines = [];
|
|
648
|
+
try {
|
|
649
|
+
while (true) {
|
|
650
|
+
const { done, value } = await reader.read();
|
|
651
|
+
if (done)
|
|
652
|
+
break;
|
|
653
|
+
buffer += decoder.decode(value, { stream: true });
|
|
654
|
+
const lines = buffer.split(`
|
|
655
|
+
`);
|
|
656
|
+
buffer = lines.pop() ?? "";
|
|
657
|
+
for (const line of lines) {
|
|
658
|
+
if (line.startsWith("id:")) {
|
|
659
|
+
seq = Number(line.slice(3).trim());
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (line.startsWith("event:")) {
|
|
663
|
+
event = line.slice(6).trim();
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (line.startsWith("data:")) {
|
|
667
|
+
dataLines.push(line.slice(5).trim());
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (line === "" && dataLines.length > 0) {
|
|
671
|
+
try {
|
|
672
|
+
const data = JSON.parse(dataLines.join(`
|
|
673
|
+
`));
|
|
674
|
+
yield { id: seq, event, data };
|
|
675
|
+
} catch {}
|
|
676
|
+
event = "";
|
|
677
|
+
seq = 0;
|
|
678
|
+
dataLines = [];
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
} finally {
|
|
683
|
+
reader.releaseLock();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/client.ts
|
|
688
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
689
|
+
var DEFAULT_FACTORY = "default";
|
|
690
|
+
function detectAmbientParentRunId() {
|
|
691
|
+
const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
|
|
692
|
+
return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
|
|
693
|
+
}
|
|
694
|
+
function templatePath(factorySlug, ...rest) {
|
|
695
|
+
const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
|
|
696
|
+
return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
class AgentComposeClient {
|
|
700
|
+
fetch;
|
|
701
|
+
baseUrl;
|
|
702
|
+
apiKey;
|
|
703
|
+
constructor(baseUrl, apiKey) {
|
|
704
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
705
|
+
this.apiKey = apiKey;
|
|
706
|
+
this.fetch = ofetch.create({
|
|
707
|
+
baseURL: this.baseUrl,
|
|
708
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
709
|
+
async onResponseError({ response }) {
|
|
710
|
+
const body = response._data;
|
|
711
|
+
throw new AgentComposeError(response.status, body?.error ?? response.statusText);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
register(payload) {
|
|
716
|
+
const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
|
|
717
|
+
return this.fetch(templatePath(factorySlug), { method: "POST", body });
|
|
718
|
+
}
|
|
719
|
+
invoke(name, input, opts) {
|
|
720
|
+
const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
|
|
721
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
722
|
+
return this.fetch(templatePath(factorySlug, name, "invoke"), {
|
|
723
|
+
method: "POST",
|
|
724
|
+
body: {
|
|
725
|
+
input,
|
|
726
|
+
...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
|
|
727
|
+
...opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {},
|
|
728
|
+
...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
|
|
729
|
+
...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {},
|
|
730
|
+
...parentRunId ? { parentRunId } : {},
|
|
731
|
+
...opts?.agentId ? { agentId: opts.agentId } : {}
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
async invokeAndWait(name, input, opts) {
|
|
736
|
+
const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
|
|
737
|
+
const pollMs = opts?.pollIntervalMs ?? 1000;
|
|
738
|
+
const { id: runId } = await this.invoke(name, input, opts);
|
|
739
|
+
const deadline = Date.now() + timeoutMs;
|
|
740
|
+
while (Date.now() < deadline) {
|
|
741
|
+
const status = await this.getStatus(runId);
|
|
742
|
+
if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
|
|
743
|
+
return status;
|
|
744
|
+
}
|
|
745
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
746
|
+
}
|
|
747
|
+
throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
|
|
748
|
+
}
|
|
749
|
+
async listSnapshots(opts) {
|
|
750
|
+
const q2 = new URLSearchParams;
|
|
751
|
+
if (opts?.workflow)
|
|
752
|
+
q2.set("workflow", opts.workflow);
|
|
753
|
+
if (opts?.limit != null)
|
|
754
|
+
q2.set("limit", String(opts.limit));
|
|
755
|
+
const body = await this.fetch(`/api/v1/snapshots${q2.toString() ? `?${q2}` : ""}`);
|
|
756
|
+
return body.data;
|
|
757
|
+
}
|
|
758
|
+
deleteSnapshot(runId) {
|
|
759
|
+
return this.fetch(`/api/v1/workflows/${runId}/snapshot`, { method: "DELETE" });
|
|
760
|
+
}
|
|
761
|
+
getStatus(runId) {
|
|
762
|
+
return this.fetch(`/api/v1/workflows/${runId}/status`);
|
|
763
|
+
}
|
|
764
|
+
getRun(runId) {
|
|
765
|
+
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}`);
|
|
766
|
+
}
|
|
767
|
+
async getRunTimeline(runId, opts) {
|
|
768
|
+
const q2 = new URLSearchParams;
|
|
769
|
+
if (opts?.limit !== undefined)
|
|
770
|
+
q2.set("limit", String(opts.limit));
|
|
771
|
+
if (opts?.offset !== undefined)
|
|
772
|
+
q2.set("offset", String(opts.offset));
|
|
773
|
+
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/timeline${q2.toString() ? `?${q2}` : ""}`);
|
|
774
|
+
return body.events;
|
|
775
|
+
}
|
|
776
|
+
async reportEvent(runId, input) {
|
|
777
|
+
const body = {
|
|
778
|
+
...input,
|
|
779
|
+
...input.timestamp instanceof Date ? { timestamp: input.timestamp.toISOString() } : {}
|
|
780
|
+
};
|
|
781
|
+
const response = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`, {
|
|
782
|
+
method: "POST",
|
|
783
|
+
body
|
|
784
|
+
});
|
|
785
|
+
return response.event;
|
|
786
|
+
}
|
|
787
|
+
async listRunEvents(runId) {
|
|
788
|
+
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/events`);
|
|
789
|
+
return body.events;
|
|
790
|
+
}
|
|
791
|
+
async listFactoryEvents(opts) {
|
|
792
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
793
|
+
const q2 = new URLSearchParams;
|
|
794
|
+
if (opts?.limit != null)
|
|
795
|
+
q2.set("limit", String(opts.limit));
|
|
796
|
+
if (opts?.name)
|
|
797
|
+
q2.set("name", opts.name);
|
|
798
|
+
if (opts?.from)
|
|
799
|
+
q2.set("from", opts.from);
|
|
800
|
+
if (opts?.before)
|
|
801
|
+
q2.set("before", opts.before);
|
|
802
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/events${q2.toString() ? `?${q2}` : ""}`);
|
|
803
|
+
}
|
|
804
|
+
async listRunLogs(runId, opts) {
|
|
805
|
+
const q2 = new URLSearchParams;
|
|
806
|
+
if (opts?.afterId !== undefined)
|
|
807
|
+
q2.set("afterId", String(opts.afterId));
|
|
808
|
+
if (opts?.limit !== undefined)
|
|
809
|
+
q2.set("limit", String(opts.limit));
|
|
810
|
+
if (opts?.direction !== undefined)
|
|
811
|
+
q2.set("direction", opts.direction);
|
|
812
|
+
const body = await this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/logs${q2.toString() ? `?${q2}` : ""}`);
|
|
813
|
+
return body.logs;
|
|
814
|
+
}
|
|
815
|
+
async listTemplates(opts) {
|
|
816
|
+
const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
|
|
817
|
+
const body = await this.fetch(path);
|
|
818
|
+
return body.templates;
|
|
819
|
+
}
|
|
820
|
+
async listFactories() {
|
|
821
|
+
const body = await this.fetch("/api/v1/factories");
|
|
822
|
+
return body.factories;
|
|
823
|
+
}
|
|
824
|
+
createFactory(payload) {
|
|
825
|
+
return this.fetch("/api/v1/factories", { method: "POST", body: payload });
|
|
826
|
+
}
|
|
827
|
+
getFactory(slug) {
|
|
828
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
|
|
829
|
+
}
|
|
830
|
+
updateFactory(slug, updates) {
|
|
831
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
|
|
832
|
+
}
|
|
833
|
+
deleteFactory(slug) {
|
|
834
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
|
|
835
|
+
}
|
|
836
|
+
setSecret(workflowName, key, value, opts) {
|
|
837
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
838
|
+
return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
|
|
839
|
+
method: "POST",
|
|
840
|
+
body: { key, value }
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
async listSecrets(workflowName, opts) {
|
|
844
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
845
|
+
const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
|
|
846
|
+
return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
|
|
847
|
+
}
|
|
848
|
+
deleteSecret(workflowName, key, opts) {
|
|
849
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
850
|
+
return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
|
|
851
|
+
}
|
|
852
|
+
createApiKey(input) {
|
|
853
|
+
return this.fetch("/api-keys", { method: "POST", body: input });
|
|
854
|
+
}
|
|
855
|
+
async listApiKeys() {
|
|
856
|
+
const body = await this.fetch("/api-keys");
|
|
857
|
+
return body.data;
|
|
858
|
+
}
|
|
859
|
+
getUsage(from, to) {
|
|
860
|
+
const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
|
|
861
|
+
return this.fetch(`/api/v1/usage${qs}`);
|
|
862
|
+
}
|
|
863
|
+
cancelRun(runId) {
|
|
864
|
+
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
|
|
865
|
+
}
|
|
866
|
+
async* streamRunLogs(runId, opts) {
|
|
867
|
+
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
868
|
+
if (opts?.lastEventId && opts.lastEventId > 0) {
|
|
869
|
+
headers["Last-Event-ID"] = String(opts.lastEventId);
|
|
870
|
+
}
|
|
871
|
+
const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
|
|
872
|
+
headers,
|
|
873
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
874
|
+
});
|
|
875
|
+
if (!res.ok || !res.body) {
|
|
876
|
+
let message = res.statusText;
|
|
877
|
+
try {
|
|
878
|
+
const body = await res.json();
|
|
879
|
+
if (body.error)
|
|
880
|
+
message = body.error;
|
|
881
|
+
} catch {}
|
|
882
|
+
throw new AgentComposeError(res.status, message);
|
|
883
|
+
}
|
|
884
|
+
for await (const ev of parseSseStream(res.body)) {
|
|
885
|
+
yield ev.data;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// src/utils/discovery.ts
|
|
890
|
+
function discoverRuntimeName(source) {
|
|
891
|
+
return source.match(/runtime:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
892
|
+
}
|
|
893
|
+
// src/utils/bundler.ts
|
|
894
|
+
import { createHash } from "node:crypto";
|
|
895
|
+
import { parse as babelParse } from "@babel/parser";
|
|
896
|
+
|
|
897
|
+
// src/utils/source-loader.ts
|
|
898
|
+
import { dirname } from "path";
|
|
899
|
+
import { writeFile, rm, mkdir } from "node:fs/promises";
|
|
900
|
+
var TMP_DIR = process.env.TMP_DIR ?? "/tmp/agent-compose";
|
|
901
|
+
var LATEST_VERSION = "$LATEST";
|
|
902
|
+
async function importSourceModule(source, tmpPath) {
|
|
903
|
+
await mkdir(dirname(tmpPath), { recursive: true });
|
|
904
|
+
await writeFile(tmpPath, source, "utf8");
|
|
905
|
+
const mod = await import(tmpPath);
|
|
906
|
+
const cleanup = () => rm(tmpPath, { force: true });
|
|
907
|
+
return { mod, cleanup };
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/types/workflow-plan.ts
|
|
911
|
+
function workflowPlan(steps) {
|
|
912
|
+
return { steps };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/utils/bundler.ts
|
|
916
|
+
var BUNDLER_VERSION = 1;
|
|
917
|
+
|
|
918
|
+
class WorkflowSourceValidationError extends Error {
|
|
919
|
+
constructor(message) {
|
|
920
|
+
super(message);
|
|
921
|
+
this.name = "WorkflowSourceValidationError";
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async function bundle(path, label) {
|
|
925
|
+
const bun = globalThis;
|
|
926
|
+
if (!bun.Bun?.build)
|
|
927
|
+
throw new Error("bundleWorkflow requires the Bun runtime (Bun.build)");
|
|
928
|
+
const result = await bun.Bun.build({ entrypoints: [path], format: "esm", target: "node" });
|
|
929
|
+
if (!result.success) {
|
|
930
|
+
throw new Error(`Failed to bundle ${label}:
|
|
931
|
+
${result.logs.map((l) => l.message).join(`
|
|
932
|
+
`)}`);
|
|
933
|
+
}
|
|
934
|
+
return result.outputs[0].text();
|
|
935
|
+
}
|
|
936
|
+
async function extractFromBundle(source, label, pick) {
|
|
937
|
+
const tmpPath = `/tmp/agentc-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`;
|
|
938
|
+
try {
|
|
939
|
+
const loaded = await importSourceModule(source, tmpPath);
|
|
940
|
+
try {
|
|
941
|
+
return pick(loaded.mod.default);
|
|
942
|
+
} finally {
|
|
943
|
+
await loaded.cleanup();
|
|
944
|
+
}
|
|
945
|
+
} catch {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
var DEFINE_WORKFLOW_CALLEE = /^(?:defineWorkflow|defineSandboxEnvironment)\d*$/;
|
|
950
|
+
function isDefineWorkflowCall(node) {
|
|
951
|
+
if (node.callee.type === "Identifier") {
|
|
952
|
+
return DEFINE_WORKFLOW_CALLEE.test(node.callee.name);
|
|
953
|
+
}
|
|
954
|
+
if (node.callee.type === "MemberExpression" && node.callee.object.type === "CallExpression") {
|
|
955
|
+
return isDefineWorkflowCall(node.callee.object);
|
|
956
|
+
}
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
function assertDefaultExportIsDefineWorkflow(source, label) {
|
|
960
|
+
let ast;
|
|
961
|
+
try {
|
|
962
|
+
ast = babelParse(source, {
|
|
963
|
+
sourceType: "module",
|
|
964
|
+
plugins: [],
|
|
965
|
+
errorRecovery: false
|
|
966
|
+
});
|
|
967
|
+
} catch (err) {
|
|
968
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
969
|
+
throw new WorkflowSourceValidationError(`Bundled workflow ${label} failed to parse as JavaScript: ${msg}. ` + `If you registered raw .ts source, switch to \`agentc register <file.ts>\` so the source is bundled first.`);
|
|
970
|
+
}
|
|
971
|
+
const defaultExpr = findDefaultExportExpression(ast.program.body);
|
|
972
|
+
if (!defaultExpr) {
|
|
973
|
+
throw new WorkflowSourceValidationError(`Bundled workflow ${label} has no default export. ` + `Use \`export default defineWorkflow({ run: ... })\`.`);
|
|
974
|
+
}
|
|
975
|
+
if (defaultExpr.type !== "CallExpression" || !isDefineWorkflowCall(defaultExpr)) {
|
|
976
|
+
throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export must be a \`defineWorkflow({...})\` call ` + `(or a \`defineWorkflow(...).step(...).build()\` chain). ` + `Got: ${defaultExpr.type}. Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk".`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function findDefaultExportExpression(body) {
|
|
980
|
+
const defaultDecl = body.find((node) => node.type === "ExportDefaultDeclaration");
|
|
981
|
+
if (defaultDecl) {
|
|
982
|
+
return defaultDecl.declaration;
|
|
983
|
+
}
|
|
984
|
+
const exportNamed = body.find((node) => node.type === "ExportNamedDeclaration" && node.specifiers.some((spec2) => spec2.type === "ExportSpecifier" && spec2.exported.type === "Identifier" && spec2.exported.name === "default"));
|
|
985
|
+
if (!exportNamed || exportNamed.type !== "ExportNamedDeclaration")
|
|
986
|
+
return null;
|
|
987
|
+
const spec = exportNamed.specifiers.find((s) => s.type === "ExportSpecifier" && s.exported.type === "Identifier" && s.exported.name === "default");
|
|
988
|
+
if (!spec || spec.type !== "ExportSpecifier" || spec.local.type !== "Identifier")
|
|
989
|
+
return null;
|
|
990
|
+
const localName = spec.local.name;
|
|
991
|
+
for (const stmt of body) {
|
|
992
|
+
if (stmt.type !== "VariableDeclaration")
|
|
993
|
+
continue;
|
|
994
|
+
for (const decl of stmt.declarations) {
|
|
995
|
+
if (decl.id.type === "Identifier" && decl.id.name === localName && decl.init && decl.init.type !== "TSSatisfiesExpression") {
|
|
996
|
+
return decl.init;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
function sha256(text) {
|
|
1003
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
1004
|
+
}
|
|
1005
|
+
async function bundleWorkflow(workflowPath, overrides) {
|
|
1006
|
+
const label = `"${workflowPath}"`;
|
|
1007
|
+
const source = await bundle(workflowPath, `workflow ${label}`);
|
|
1008
|
+
assertDefaultExportIsDefineWorkflow(source, label);
|
|
1009
|
+
const workflow = await extractFromBundle(source, "workflow", (wf) => isWorkflow(wf) ? wf : undefined);
|
|
1010
|
+
if (!workflow) {
|
|
1011
|
+
throw new WorkflowSourceValidationError(`Bundled workflow ${label} default export is not a Workflow. ` + `Wrap your workflow with \`defineWorkflow\` from "@agent-compose/sdk" — ` + `raw \`export default async function\` is no longer supported.`);
|
|
1012
|
+
}
|
|
1013
|
+
const { metadata } = workflow;
|
|
1014
|
+
const plan = workflowPlan(workflow.steps.map((step, index) => ({ index, name: step.name })));
|
|
1015
|
+
const manifest = {
|
|
1016
|
+
definitionBrand: true,
|
|
1017
|
+
sourceHash: sha256(source),
|
|
1018
|
+
bundlerVersion: BUNDLER_VERSION
|
|
1019
|
+
};
|
|
1020
|
+
return {
|
|
1021
|
+
source,
|
|
1022
|
+
manifest,
|
|
1023
|
+
workflowPlan: plan,
|
|
1024
|
+
networkPolicy: overrides?.networkPolicy ?? metadata.networkPolicy,
|
|
1025
|
+
placeholders: overrides?.placeholders ?? metadata.placeholders,
|
|
1026
|
+
...metadata.snapshot !== undefined ? { snapshot: metadata.snapshot } : {},
|
|
1027
|
+
...metadata.saveSnapshot !== undefined ? { saveSnapshot: metadata.saveSnapshot } : {},
|
|
1028
|
+
...metadata.memory !== undefined ? { memory: metadata.memory } : {}
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
// src/utils/schemas.ts
|
|
1032
|
+
import { z as z3 } from "zod";
|
|
1033
|
+
var AgentStatusSchema = z3.object({
|
|
1034
|
+
summary: z3.string(),
|
|
1035
|
+
completed: z3.array(z3.string()),
|
|
1036
|
+
blockers: z3.array(z3.string()),
|
|
1037
|
+
exit_signal: z3.boolean()
|
|
1038
|
+
});
|
|
1039
|
+
// src/runtimes/claude.ts
|
|
1040
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1041
|
+
|
|
1042
|
+
// src/agent/agent-loop.ts
|
|
1043
|
+
import { z as z5 } from "zod";
|
|
1044
|
+
|
|
1045
|
+
// src/agent/protocol.ts
|
|
1046
|
+
import { z as z4 } from "zod";
|
|
1047
|
+
var AgentMessageSchema = z4.object({
|
|
1048
|
+
type: z4.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
|
|
1049
|
+
timestamp: z4.string()
|
|
1050
|
+
}).passthrough();
|
|
1051
|
+
function parseAgentResponse(text) {
|
|
1052
|
+
const match = text.match(/<response>([\s\S]*?)<\/response>/);
|
|
1053
|
+
if (!match)
|
|
1054
|
+
return null;
|
|
1055
|
+
try {
|
|
1056
|
+
return JSON.parse(match[1].trim());
|
|
1057
|
+
} catch {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/agent/agent-loop.ts
|
|
1063
|
+
import { randomUUID } from "node:crypto";
|
|
1064
|
+
var DEFAULT_CLAUDE_MODEL = "claude-opus-4-7";
|
|
1065
|
+
var SAME_BLOCKER_ITERATIONS = 3;
|
|
1066
|
+
var STALL_ITERATIONS = 3;
|
|
1067
|
+
var MESSAGE_PREVIEW_CHARS = 400;
|
|
1068
|
+
function parseAgentStatus(text) {
|
|
1069
|
+
const match = text.match(/<status>([\s\S]*?)<\/status>/);
|
|
1070
|
+
if (!match)
|
|
1071
|
+
return null;
|
|
1072
|
+
try {
|
|
1073
|
+
const result = AgentStatusSchema.safeParse(JSON.parse(match[1].trim()));
|
|
1074
|
+
return result.success ? result.data : null;
|
|
1075
|
+
} catch {
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
var DEFAULT_ALLOWED_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch"];
|
|
1080
|
+
function truncate(value) {
|
|
1081
|
+
return value.length > MESSAGE_PREVIEW_CHARS ? `${value.slice(0, MESSAGE_PREVIEW_CHARS)}…` : value;
|
|
1082
|
+
}
|
|
1083
|
+
function preview(value) {
|
|
1084
|
+
try {
|
|
1085
|
+
return truncate(JSON.stringify(value));
|
|
1086
|
+
} catch {
|
|
1087
|
+
return truncate(String(value));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function summarizeAgentMessage(msg) {
|
|
1091
|
+
switch (msg.type) {
|
|
1092
|
+
case "init":
|
|
1093
|
+
return { type: "init", sessionId: msg.sessionId };
|
|
1094
|
+
case "text":
|
|
1095
|
+
return { type: "text", text: msg.text };
|
|
1096
|
+
case "thinking":
|
|
1097
|
+
return { type: "thinking", text: msg.text };
|
|
1098
|
+
case "tool_use":
|
|
1099
|
+
return { type: "tool_use", toolName: msg.toolName, toolUseId: msg.toolUseId, toolInputPreview: preview(msg.toolInput) };
|
|
1100
|
+
case "tool_result":
|
|
1101
|
+
return { type: "tool_result", toolUseId: msg.toolUseId, output: truncate(msg.output), isError: msg.isError };
|
|
1102
|
+
case "usage":
|
|
1103
|
+
return {
|
|
1104
|
+
type: "usage",
|
|
1105
|
+
inputTokens: msg.inputTokens,
|
|
1106
|
+
outputTokens: msg.outputTokens,
|
|
1107
|
+
cacheReadTokens: msg.cacheReadTokens,
|
|
1108
|
+
cacheCreationTokens: msg.cacheCreationTokens,
|
|
1109
|
+
durationMs: msg.durationMs,
|
|
1110
|
+
numTurns: msg.numTurns
|
|
1111
|
+
};
|
|
1112
|
+
case "done":
|
|
1113
|
+
return { type: "done", sessionId: msg.sessionId };
|
|
1114
|
+
case "error":
|
|
1115
|
+
return { type: "error", text: truncate(msg.text) };
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
async function agentLoop(opts) {
|
|
1119
|
+
const agentId = opts.agentId ?? randomUUID();
|
|
1120
|
+
const label = opts.label ?? "agent";
|
|
1121
|
+
const logLabel = opts.label ?? "[Agent Loop]";
|
|
1122
|
+
const startedAt = Date.now();
|
|
1123
|
+
const turnsPerIteration = opts.turnsPerIteration ?? 40;
|
|
1124
|
+
const maxIterations = opts.maxIterations ?? 8;
|
|
1125
|
+
const processors = opts.processors ?? [];
|
|
1126
|
+
const requestContext = opts.requestContext ?? RequestContext.fromReserved({
|
|
1127
|
+
teamId: "",
|
|
1128
|
+
runId: "",
|
|
1129
|
+
workflowId: "",
|
|
1130
|
+
factoryId: null,
|
|
1131
|
+
apiKeyScopes: [],
|
|
1132
|
+
parentRunId: null
|
|
1133
|
+
});
|
|
1134
|
+
const loopAbort = new AbortController;
|
|
1135
|
+
const buildProcCtx = (iteration) => ({
|
|
1136
|
+
requestContext,
|
|
1137
|
+
abortSignal: loopAbort.signal,
|
|
1138
|
+
retryCount: 0,
|
|
1139
|
+
agentId,
|
|
1140
|
+
iteration
|
|
1141
|
+
});
|
|
1142
|
+
if (!opts.runtime)
|
|
1143
|
+
throw new Error("agentLoop: opts.runtime is required");
|
|
1144
|
+
const client = opts.runtime({
|
|
1145
|
+
maxTurns: turnsPerIteration,
|
|
1146
|
+
allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
|
|
1147
|
+
label: logLabel,
|
|
1148
|
+
cwd: opts.cwd,
|
|
1149
|
+
processors,
|
|
1150
|
+
requestContext,
|
|
1151
|
+
agentId,
|
|
1152
|
+
...opts.responseSchema ? { outputFormat: { type: "json_schema", schema: z5.toJSONSchema(opts.responseSchema) } } : {}
|
|
1153
|
+
});
|
|
1154
|
+
if (processors.some((p) => p.processToolCall) && !client.supportsToolCallProcessor) {
|
|
1155
|
+
process.stderr.write(`${logLabel} processToolCall registered but runtime does not support pre-tool-use gating — hook is dormant
|
|
1156
|
+
`);
|
|
1157
|
+
}
|
|
1158
|
+
let lastSessionId = "";
|
|
1159
|
+
let lastStatus = null;
|
|
1160
|
+
let iterationsWithoutStatus = 0;
|
|
1161
|
+
let blockerStreak = null;
|
|
1162
|
+
let completedIterations = 0;
|
|
1163
|
+
let lastResponseText = "";
|
|
1164
|
+
let lastResponseValidationError = "";
|
|
1165
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.spawned", at: startedAt, agentId, label, allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS });
|
|
1166
|
+
try {
|
|
1167
|
+
for (let iteration = 0;iteration < maxIterations; iteration++) {
|
|
1168
|
+
const procCtx = buildProcCtx(iteration + 1);
|
|
1169
|
+
const initialPrompt = opts.buildPrompt(lastStatus, iteration);
|
|
1170
|
+
const inputVerdict = await runProcessorChain(processors, (p) => p.processInput, initialPrompt, procCtx);
|
|
1171
|
+
if (inputVerdict.kind !== "continue") {
|
|
1172
|
+
loopAbort.abort();
|
|
1173
|
+
throw new Error(`${logLabel} input ${inputVerdict.kind === "deny" ? "rejected" : "aborted"}: ${inputVerdict.reason}`);
|
|
1174
|
+
}
|
|
1175
|
+
const prompt = inputVerdict.value;
|
|
1176
|
+
process.stdout.write(`${logLabel} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns
|
|
1177
|
+
`);
|
|
1178
|
+
let responseText = "";
|
|
1179
|
+
for await (const rawMsg of client.sendMessage({ prompt, sessionId: iteration > 0 ? lastSessionId : undefined, iteration: iteration + 1 })) {
|
|
1180
|
+
const outputVerdict = await runProcessorChain(processors, (p) => p.processOutput, rawMsg, procCtx);
|
|
1181
|
+
if (outputVerdict.kind === "abort") {
|
|
1182
|
+
loopAbort.abort();
|
|
1183
|
+
throw new Error(`${logLabel} output aborted: ${outputVerdict.reason}`);
|
|
1184
|
+
}
|
|
1185
|
+
if (outputVerdict.kind === "deny") {
|
|
1186
|
+
process.stderr.write(`${logLabel} message dropped by processor: ${outputVerdict.reason}
|
|
1187
|
+
`);
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
const msg = outputVerdict.value;
|
|
1191
|
+
opts.onAgentEvent?.(iteration, msg);
|
|
1192
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.message", at: Date.now(), agentId, label, iteration: iteration + 1, message: summarizeAgentMessage(msg) });
|
|
1193
|
+
if (msg.type === "init")
|
|
1194
|
+
lastSessionId = msg.sessionId;
|
|
1195
|
+
if (msg.type === "text")
|
|
1196
|
+
responseText += msg.text;
|
|
1197
|
+
if (msg.type === "error")
|
|
1198
|
+
throw new Error(`Agent error: ${msg.text}`);
|
|
1199
|
+
}
|
|
1200
|
+
lastResponseText = responseText;
|
|
1201
|
+
let status = parseAgentStatus(responseText);
|
|
1202
|
+
const rawResponse = opts.responseSchema ? parseAgentResponse(responseText) ?? parseRawJsonResponse(responseText) : null;
|
|
1203
|
+
if (opts.responseSchema && rawResponse !== null) {
|
|
1204
|
+
const parsed = opts.responseSchema.safeParse(rawResponse);
|
|
1205
|
+
if (parsed.success) {
|
|
1206
|
+
const successStatus = status ?? { summary: "structured response completed", completed: [], blockers: [], exit_signal: true };
|
|
1207
|
+
process.stdout.write(`${logLabel} structured response validated after ${iteration + 1}/${maxIterations} iterations
|
|
1208
|
+
`);
|
|
1209
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: iteration + 1, durationMs: Date.now() - startedAt });
|
|
1210
|
+
return { agentId, label, sessionId: lastSessionId, lastStatus: successStatus, iterations: iteration + 1, response: parsed.data };
|
|
1211
|
+
}
|
|
1212
|
+
lastResponseValidationError = parsed.error.message;
|
|
1213
|
+
}
|
|
1214
|
+
completedIterations = iteration + 1;
|
|
1215
|
+
process.stdout.write(`${logLabel} iteration ${iteration + 1} status: exit_signal=${status?.exit_signal ?? "(no status)"} blockers=${JSON.stringify(status?.blockers ?? [])}
|
|
1216
|
+
`);
|
|
1217
|
+
lastStatus = status ?? lastStatus;
|
|
1218
|
+
opts.onIteration?.(iteration + 1, status);
|
|
1219
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.iteration", at: Date.now(), agentId, label, iteration: iteration + 1, status });
|
|
1220
|
+
if (status?.exit_signal && (status.blockers?.length ?? 0) === 0) {
|
|
1221
|
+
let response = status;
|
|
1222
|
+
if (opts.responseSchema) {
|
|
1223
|
+
if (rawResponse === null) {
|
|
1224
|
+
process.stderr.write(`${logLabel} NO <response> BLOCK — response tail: ${responseText.slice(-400)}
|
|
1225
|
+
`);
|
|
1226
|
+
status = { ...status, exit_signal: false, blockers: ["No <response> block found — emit a <response> block with the required JSON fields before setting exit_signal: true"] };
|
|
1227
|
+
opts.onIteration?.(iteration + 1, status);
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
const parsed = opts.responseSchema.safeParse({ ...status, ...rawResponse });
|
|
1231
|
+
if (!parsed.success) {
|
|
1232
|
+
lastResponseValidationError = parsed.error.message;
|
|
1233
|
+
process.stderr.write(`${logLabel} <response> SCHEMA FAILED: ${parsed.error.message}
|
|
1234
|
+
raw: ${JSON.stringify(rawResponse).slice(0, 400)}
|
|
1235
|
+
`);
|
|
1236
|
+
status = { ...status, exit_signal: false, blockers: [`<response> schema validation failed: ${parsed.error.message}`] };
|
|
1237
|
+
opts.onIteration?.(iteration + 1, status);
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
response = parsed.data;
|
|
1241
|
+
}
|
|
1242
|
+
process.stdout.write(`${logLabel} done after ${iteration + 1}/${maxIterations} iterations
|
|
1243
|
+
`);
|
|
1244
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: iteration + 1, durationMs: Date.now() - startedAt });
|
|
1245
|
+
return { agentId, label, sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
|
|
1246
|
+
}
|
|
1247
|
+
if (!status) {
|
|
1248
|
+
if (++iterationsWithoutStatus >= STALL_ITERATIONS)
|
|
1249
|
+
throw new Error(`${logLabel} stalled: no <status> block after ${iterationsWithoutStatus} iterations`);
|
|
1250
|
+
} else {
|
|
1251
|
+
iterationsWithoutStatus = 0;
|
|
1252
|
+
}
|
|
1253
|
+
if (status?.blockers?.length) {
|
|
1254
|
+
const key = status.blockers.join("|");
|
|
1255
|
+
if (blockerStreak !== null && blockerStreak.key === key) {
|
|
1256
|
+
if (++blockerStreak.count >= SAME_BLOCKER_ITERATIONS)
|
|
1257
|
+
throw new Error(`${logLabel} circuit break: same blocker repeated ${blockerStreak.count}x — "${status.blockers[0]}"`);
|
|
1258
|
+
} else {
|
|
1259
|
+
blockerStreak = { key, count: 1 };
|
|
1260
|
+
}
|
|
1261
|
+
} else {
|
|
1262
|
+
blockerStreak = null;
|
|
1263
|
+
}
|
|
1264
|
+
if (iteration + 1 < maxIterations)
|
|
1265
|
+
process.stdout.write(`${logLabel} continuing to iteration ${iteration + 2}/${maxIterations}
|
|
1266
|
+
`);
|
|
1267
|
+
}
|
|
1268
|
+
if (opts.responseSchema)
|
|
1269
|
+
throw new Error(`${logLabel} did not produce a valid <response> after ${maxIterations} iterations${lastResponseValidationError ? `: ${lastResponseValidationError}` : ""}. Response tail: ${lastResponseText.slice(-600)}`);
|
|
1270
|
+
process.stderr.write(`${logLabel} exhausted ${maxIterations} iterations, proceeding with available work
|
|
1271
|
+
`);
|
|
1272
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "success", iterations: maxIterations, durationMs: Date.now() - startedAt });
|
|
1273
|
+
return { agentId, label, sessionId: lastSessionId, lastStatus, iterations: maxIterations };
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
opts.onAgentLifecycleEvent?.({ event: "agent.settled", at: Date.now(), agentId, label, outcome: "failed", iterations: completedIterations, durationMs: Date.now() - startedAt, failureReason: err instanceof Error ? err.message : String(err) });
|
|
1276
|
+
throw err;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function parseRawJsonResponse(text) {
|
|
1280
|
+
const trimmed = text.trim();
|
|
1281
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
|
|
1282
|
+
return null;
|
|
1283
|
+
try {
|
|
1284
|
+
return JSON.parse(trimmed);
|
|
1285
|
+
} catch {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// src/runtimes/claude.ts
|
|
1291
|
+
function now2() {
|
|
1292
|
+
return new Date().toISOString();
|
|
1293
|
+
}
|
|
1294
|
+
function translateMessage(message) {
|
|
1295
|
+
const ts = now2();
|
|
1296
|
+
const msgs = [];
|
|
1297
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
1298
|
+
msgs.push({ type: "init", sessionId: String(message.session_id ?? ""), timestamp: ts });
|
|
1299
|
+
return msgs;
|
|
1300
|
+
}
|
|
1301
|
+
if (message.type === "assistant") {
|
|
1302
|
+
const raw = message.message;
|
|
1303
|
+
for (const block of raw?.content ?? []) {
|
|
1304
|
+
const b = block;
|
|
1305
|
+
if (b.type === "text")
|
|
1306
|
+
msgs.push({ type: "text", text: String(b.text ?? ""), timestamp: ts });
|
|
1307
|
+
if (b.type === "thinking")
|
|
1308
|
+
msgs.push({ type: "thinking", text: String(b.thinking ?? ""), timestamp: ts });
|
|
1309
|
+
if (b.type === "tool_use")
|
|
1310
|
+
msgs.push({ type: "tool_use", toolName: String(b.name ?? ""), toolInput: b.input ?? {}, toolUseId: String(b.id ?? ""), timestamp: ts });
|
|
1311
|
+
}
|
|
1312
|
+
return msgs;
|
|
1313
|
+
}
|
|
1314
|
+
if (message.type === "user") {
|
|
1315
|
+
const raw = message.message;
|
|
1316
|
+
for (const block of raw?.content ?? []) {
|
|
1317
|
+
const b = block;
|
|
1318
|
+
if (b.type === "tool_result")
|
|
1319
|
+
msgs.push({ type: "tool_result", toolUseId: String(b.tool_use_id ?? ""), output: typeof b.content === "string" ? b.content : JSON.stringify(b.content ?? ""), isError: Boolean(b.is_error), timestamp: ts });
|
|
1320
|
+
}
|
|
1321
|
+
return msgs;
|
|
1322
|
+
}
|
|
1323
|
+
if (message.type === "result") {
|
|
1324
|
+
if (typeof message.result === "string" && message.result.trim()) {
|
|
1325
|
+
msgs.push({ type: "text", text: message.result, timestamp: ts });
|
|
1326
|
+
}
|
|
1327
|
+
const usage = message.usage;
|
|
1328
|
+
if (usage)
|
|
1329
|
+
msgs.push({
|
|
1330
|
+
type: "usage",
|
|
1331
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
1332
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
1333
|
+
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
1334
|
+
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
|
|
1335
|
+
durationMs: Number(message.duration_ms ?? 0),
|
|
1336
|
+
numTurns: Number(message.num_turns ?? 0),
|
|
1337
|
+
timestamp: ts
|
|
1338
|
+
});
|
|
1339
|
+
if (message.is_error || message.subtype === "error_during_execution") {
|
|
1340
|
+
msgs.push({ type: "error", text: formatError(message.error ?? message.result ?? message.message), timestamp: ts });
|
|
1341
|
+
} else {
|
|
1342
|
+
msgs.push({ type: "done", sessionId: String(message.session_id ?? ""), timestamp: ts });
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return msgs;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
class ClaudeRunner {
|
|
1349
|
+
options;
|
|
1350
|
+
config;
|
|
1351
|
+
supportsToolCallProcessor = true;
|
|
1352
|
+
constructor(_sandbox, options = {}, config = {}) {
|
|
1353
|
+
this.options = options;
|
|
1354
|
+
this.config = config;
|
|
1355
|
+
}
|
|
1356
|
+
async gateToolCall(call, ctx) {
|
|
1357
|
+
const verdict = await runProcessorChain(this.options.processors ?? [], (p) => p.processToolCall, call, ctx);
|
|
1358
|
+
if (verdict.kind === "continue")
|
|
1359
|
+
return { kind: "allow", call: verdict.value };
|
|
1360
|
+
return verdict;
|
|
1361
|
+
}
|
|
1362
|
+
async* sendMessage(opts) {
|
|
1363
|
+
const requestContext = this.options.requestContext ?? RequestContext.fromReserved({
|
|
1364
|
+
teamId: "",
|
|
1365
|
+
runId: "",
|
|
1366
|
+
workflowId: "",
|
|
1367
|
+
factoryId: null,
|
|
1368
|
+
apiKeyScopes: [],
|
|
1369
|
+
parentRunId: null
|
|
1370
|
+
});
|
|
1371
|
+
const preToolHook = async (input, toolUseId, { signal }) => {
|
|
1372
|
+
const pre = input;
|
|
1373
|
+
const call = {
|
|
1374
|
+
toolName: pre.tool_name,
|
|
1375
|
+
toolInput: pre.tool_input ?? {},
|
|
1376
|
+
toolUseId: toolUseId ?? `${pre.tool_name}-${Date.now()}`
|
|
1377
|
+
};
|
|
1378
|
+
const gate = await this.gateToolCall(call, {
|
|
1379
|
+
requestContext,
|
|
1380
|
+
abortSignal: signal ?? opts.signal ?? new AbortController().signal,
|
|
1381
|
+
retryCount: 0,
|
|
1382
|
+
agentId: this.options.agentId ?? "agent",
|
|
1383
|
+
iteration: opts.iteration ?? 1
|
|
1384
|
+
});
|
|
1385
|
+
if (gate.kind === "allow")
|
|
1386
|
+
return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "allow", updatedInput: gate.call.toolInput } };
|
|
1387
|
+
if (gate.kind === "deny")
|
|
1388
|
+
return { hookSpecificOutput: { hookEventName: pre.hook_event_name, permissionDecision: "deny", permissionDecisionReason: gate.reason } };
|
|
1389
|
+
return { continue: false, systemMessage: gate.reason };
|
|
1390
|
+
};
|
|
1391
|
+
try {
|
|
1392
|
+
let emittedAssistantText = false;
|
|
1393
|
+
for await (const message of query({
|
|
1394
|
+
prompt: opts.prompt,
|
|
1395
|
+
options: {
|
|
1396
|
+
tools: this.options.allowedTools,
|
|
1397
|
+
allowedTools: this.options.allowedTools,
|
|
1398
|
+
maxTurns: this.options.maxTurns,
|
|
1399
|
+
model: this.config.model ?? this.options.model ?? DEFAULT_CLAUDE_MODEL,
|
|
1400
|
+
outputFormat: this.options.outputFormat,
|
|
1401
|
+
thinking: this.config.thinking,
|
|
1402
|
+
effort: this.config.effort,
|
|
1403
|
+
cwd: this.options.cwd,
|
|
1404
|
+
env: { ...process.env, ...this.config.env ?? {} },
|
|
1405
|
+
pathToClaudeCodeExecutable: this.config.pathToClaudeCodeExecutable ?? process.env.CLAUDE_CODE_EXECUTABLE ?? "claude",
|
|
1406
|
+
...this.config.claudeMdContent ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.config.claudeMdContent } } : {},
|
|
1407
|
+
resume: opts.sessionId,
|
|
1408
|
+
mcpServers: this.config.mcpServers,
|
|
1409
|
+
permissionMode: "acceptEdits",
|
|
1410
|
+
hooks: { PreToolUse: [{ hooks: [preToolHook] }] }
|
|
1411
|
+
}
|
|
1412
|
+
})) {
|
|
1413
|
+
const raw = message;
|
|
1414
|
+
const translated = translateMessage(raw);
|
|
1415
|
+
for (const msg of translated) {
|
|
1416
|
+
if (raw.type === "result" && msg.type === "text" && emittedAssistantText)
|
|
1417
|
+
continue;
|
|
1418
|
+
if (msg.type === "text" && raw.type === "assistant")
|
|
1419
|
+
emittedAssistantText = true;
|
|
1420
|
+
yield msg;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
yield { type: "error", text: formatError(err), timestamp: now2() };
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function createClaudeRuntime(config = {}) {
|
|
1429
|
+
return defineRuntime({
|
|
1430
|
+
create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config)
|
|
33
1431
|
});
|
|
34
1432
|
}
|
|
1433
|
+
var claude_default = createClaudeRuntime();
|
|
35
1434
|
// src/sandbox.ts
|
|
36
1435
|
import { promises as fs } from "node:fs";
|
|
37
|
-
import { dirname } from "node:path";
|
|
1436
|
+
import { dirname as dirname2 } from "node:path";
|
|
38
1437
|
import { spawn } from "node:child_process";
|
|
39
1438
|
import { Sandbox } from "e2b";
|
|
40
1439
|
import { Sandbox as Desktop } from "@e2b/desktop";
|
|
@@ -42,7 +1441,16 @@ import pRetry from "p-retry";
|
|
|
42
1441
|
var AGENT_COMPOSE_TAG = process.env.AGENT_COMPOSE_TAG ?? `agent-compose-${process.env.AGENT_COMPOSE_ENV ?? "dev"}`;
|
|
43
1442
|
var VERCEL_VM_LIFETIME_WINDOW_MS = 6 * 60 * 60 * 1000;
|
|
44
1443
|
function makeSandboxProvider(sb) {
|
|
45
|
-
return {
|
|
1444
|
+
return {
|
|
1445
|
+
sandboxId: sb.sandboxId,
|
|
1446
|
+
commands: sb.commands,
|
|
1447
|
+
files: {
|
|
1448
|
+
async write(path, content) {
|
|
1449
|
+
await sb.files.write(path, content);
|
|
1450
|
+
}
|
|
1451
|
+
},
|
|
1452
|
+
kill: () => sb.kill()
|
|
1453
|
+
};
|
|
46
1454
|
}
|
|
47
1455
|
function makeDesktopSandboxProvider(sb) {
|
|
48
1456
|
return {
|
|
@@ -105,7 +1513,7 @@ function makeVercelSandboxProvider(sb, globalEnvs) {
|
|
|
105
1513
|
commands: {
|
|
106
1514
|
async run(cmd, opts) {
|
|
107
1515
|
if (opts?.background) {
|
|
108
|
-
sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true }).catch(() => {});
|
|
1516
|
+
sb.runCommand({ cmd: "sh", args: ["-c", cmd], cwd: opts.cwd, env: mergeEnvs(opts.envs), detached: true }).catch((err) => console.error(`[sandbox] background command failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
109
1517
|
return { exitCode: 0, stdout: "" };
|
|
110
1518
|
}
|
|
111
1519
|
const signal = opts?.timeoutMs ? AbortSignal.timeout(opts.timeoutMs) : undefined;
|
|
@@ -168,7 +1576,7 @@ function makeLocalSandboxProvider() {
|
|
|
168
1576
|
},
|
|
169
1577
|
files: {
|
|
170
1578
|
async write(path, content) {
|
|
171
|
-
await fs.mkdir(
|
|
1579
|
+
await fs.mkdir(dirname2(path), { recursive: true });
|
|
172
1580
|
await fs.writeFile(path, content);
|
|
173
1581
|
}
|
|
174
1582
|
},
|
|
@@ -239,7 +1647,7 @@ var SANDBOX_PROVIDERS = {
|
|
|
239
1647
|
sandboxes.push(...await paginator.nextItems());
|
|
240
1648
|
if (sandboxes.length === 0)
|
|
241
1649
|
return;
|
|
242
|
-
await Promise.all(sandboxes.map((s) => Sandbox.kill(s.sandboxId)
|
|
1650
|
+
await Promise.all(sandboxes.map((s) => Sandbox.kill(s.sandboxId)));
|
|
243
1651
|
console.info(`[sandbox] killed ${sandboxes.length} stale E2B sandboxes (tag: ${AGENT_COMPOSE_TAG})`);
|
|
244
1652
|
},
|
|
245
1653
|
getActiveCount: async () => (await SANDBOX_PROVIDERS.e2b.listOwned({})).length,
|
|
@@ -325,723 +1733,236 @@ async function listOwnedSandboxes() {
|
|
|
325
1733
|
} catch (err) {
|
|
326
1734
|
out[name] = err instanceof Error ? err : new Error(String(err));
|
|
327
1735
|
}
|
|
328
|
-
}));
|
|
329
|
-
return out;
|
|
330
|
-
}
|
|
331
|
-
async function killSandboxById(provider, sandboxId) {
|
|
332
|
-
const def = SANDBOX_PROVIDERS[provider];
|
|
333
|
-
if (!def?.reconnect)
|
|
334
|
-
throw new Error(`Provider "${provider}" does not support reconnect (required for kill-by-id)`);
|
|
335
|
-
const sb = await def.reconnect(sandboxId);
|
|
336
|
-
await sb.kill();
|
|
337
|
-
}
|
|
338
|
-
async function killAllSandboxes(onError) {
|
|
339
|
-
await Promise.allSettled(Object.entries(SANDBOX_PROVIDERS).filter(([, def]) => def.killAll).map(([name, def]) => {
|
|
340
|
-
const env = Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k] ?? ""]));
|
|
341
|
-
if (Object.keys(def.requiredEnv).some((k) => !process.env[k]))
|
|
342
|
-
return Promise.resolve();
|
|
343
|
-
return def.killAll(env).catch((err) => onError?.(name, err));
|
|
344
|
-
}));
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// src/types/sandbox-environment.ts
|
|
348
|
-
function defineSandboxEnvironment(env) {
|
|
349
|
-
if (typeof env.setup !== "function") {
|
|
350
|
-
throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
|
|
351
|
-
}
|
|
352
|
-
return defineWorkflow({
|
|
353
|
-
saveSnapshot: env.saveSnapshot ?? true,
|
|
354
|
-
run: () => env.setup(makeLocalSandboxProvider())
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
// src/client.ts
|
|
358
|
-
import { ofetch } from "ofetch";
|
|
359
|
-
|
|
360
|
-
// src/errors.ts
|
|
361
|
-
class AgentComposeError extends Error {
|
|
362
|
-
status;
|
|
363
|
-
constructor(status, message) {
|
|
364
|
-
super(message);
|
|
365
|
-
this.status = status;
|
|
366
|
-
this.name = "AgentComposeError";
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// src/sse.ts
|
|
371
|
-
async function* parseSseStream(body) {
|
|
372
|
-
const reader = body.getReader();
|
|
373
|
-
const decoder = new TextDecoder;
|
|
374
|
-
let buffer = "";
|
|
375
|
-
let event = "";
|
|
376
|
-
let seq = 0;
|
|
377
|
-
let dataLines = [];
|
|
378
|
-
try {
|
|
379
|
-
while (true) {
|
|
380
|
-
const { done, value } = await reader.read();
|
|
381
|
-
if (done)
|
|
382
|
-
break;
|
|
383
|
-
buffer += decoder.decode(value, { stream: true });
|
|
384
|
-
const lines = buffer.split(`
|
|
385
|
-
`);
|
|
386
|
-
buffer = lines.pop() ?? "";
|
|
387
|
-
for (const line of lines) {
|
|
388
|
-
if (line.startsWith("id:")) {
|
|
389
|
-
seq = Number(line.slice(3).trim());
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
if (line.startsWith("event:")) {
|
|
393
|
-
event = line.slice(6).trim();
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
if (line.startsWith("data:")) {
|
|
397
|
-
dataLines.push(line.slice(5).trim());
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
if (line === "" && dataLines.length > 0) {
|
|
401
|
-
try {
|
|
402
|
-
const data = JSON.parse(dataLines.join(`
|
|
403
|
-
`));
|
|
404
|
-
yield { id: seq, event, data };
|
|
405
|
-
} catch {}
|
|
406
|
-
event = "";
|
|
407
|
-
seq = 0;
|
|
408
|
-
dataLines = [];
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
} finally {
|
|
413
|
-
reader.releaseLock();
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// src/client.ts
|
|
418
|
-
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
419
|
-
var DEFAULT_FACTORY = "default";
|
|
420
|
-
function detectAmbientParentRunId() {
|
|
421
|
-
const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
|
|
422
|
-
return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
|
|
423
|
-
}
|
|
424
|
-
function templatePath(factorySlug, ...rest) {
|
|
425
|
-
const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
|
|
426
|
-
return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
class AgentComposeClient {
|
|
430
|
-
fetch;
|
|
431
|
-
baseUrl;
|
|
432
|
-
apiKey;
|
|
433
|
-
constructor(baseUrl, apiKey) {
|
|
434
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
435
|
-
this.apiKey = apiKey;
|
|
436
|
-
this.fetch = ofetch.create({
|
|
437
|
-
baseURL: this.baseUrl,
|
|
438
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
439
|
-
async onResponseError({ response }) {
|
|
440
|
-
const body = response._data;
|
|
441
|
-
throw new AgentComposeError(response.status, body?.error ?? response.statusText);
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
register(payload) {
|
|
446
|
-
const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
|
|
447
|
-
return this.fetch(templatePath(factorySlug), { method: "POST", body });
|
|
448
|
-
}
|
|
449
|
-
invoke(name, input, opts) {
|
|
450
|
-
const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
|
|
451
|
-
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
452
|
-
return this.fetch(templatePath(factorySlug, name, "invoke"), {
|
|
453
|
-
method: "POST",
|
|
454
|
-
body: {
|
|
455
|
-
input,
|
|
456
|
-
...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
|
|
457
|
-
...opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {},
|
|
458
|
-
...parentRunId ? { parentRunId } : {},
|
|
459
|
-
...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
|
|
460
|
-
...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {}
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
async invokeAndWait(name, input, opts) {
|
|
465
|
-
const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
|
|
466
|
-
const pollMs = opts?.pollIntervalMs ?? 1000;
|
|
467
|
-
const { id: runId } = await this.invoke(name, input, {
|
|
468
|
-
...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
|
|
469
|
-
...opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {},
|
|
470
|
-
...opts?.parentRunId !== undefined ? { parentRunId: opts.parentRunId } : {},
|
|
471
|
-
...opts?.factorySlug !== undefined ? { factorySlug: opts.factorySlug } : {},
|
|
472
|
-
...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
|
|
473
|
-
...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {}
|
|
474
|
-
});
|
|
475
|
-
const deadline = Date.now() + timeoutMs;
|
|
476
|
-
while (Date.now() < deadline) {
|
|
477
|
-
const status = await this.getStatus(runId);
|
|
478
|
-
if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
|
|
479
|
-
return status;
|
|
480
|
-
}
|
|
481
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
482
|
-
}
|
|
483
|
-
throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
|
|
484
|
-
}
|
|
485
|
-
async listSnapshots(opts) {
|
|
486
|
-
const q = new URLSearchParams;
|
|
487
|
-
if (opts?.workflow)
|
|
488
|
-
q.set("workflow", opts.workflow);
|
|
489
|
-
if (opts?.limit != null)
|
|
490
|
-
q.set("limit", String(opts.limit));
|
|
491
|
-
const body = await this.fetch(`/api/v1/snapshots${q.toString() ? `?${q}` : ""}`);
|
|
492
|
-
return body.data;
|
|
493
|
-
}
|
|
494
|
-
deleteSnapshot(runId) {
|
|
495
|
-
return this.fetch(`/api/v1/workflows/${runId}/snapshot`, { method: "DELETE" });
|
|
496
|
-
}
|
|
497
|
-
getStatus(runId) {
|
|
498
|
-
return this.fetch(`/api/v1/workflows/${runId}/status`);
|
|
499
|
-
}
|
|
500
|
-
async listTemplates(opts) {
|
|
501
|
-
const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
|
|
502
|
-
const body = await this.fetch(path);
|
|
503
|
-
return body.templates;
|
|
504
|
-
}
|
|
505
|
-
async listFactories() {
|
|
506
|
-
const body = await this.fetch("/api/v1/factories");
|
|
507
|
-
return body.factories;
|
|
508
|
-
}
|
|
509
|
-
createFactory(payload) {
|
|
510
|
-
return this.fetch("/api/v1/factories", { method: "POST", body: payload });
|
|
511
|
-
}
|
|
512
|
-
getFactory(slug) {
|
|
513
|
-
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
|
|
514
|
-
}
|
|
515
|
-
updateFactory(slug, updates) {
|
|
516
|
-
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
|
|
517
|
-
}
|
|
518
|
-
deleteFactory(slug) {
|
|
519
|
-
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
|
|
520
|
-
}
|
|
521
|
-
setSecret(workflowName, key, value, opts) {
|
|
522
|
-
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
523
|
-
return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
|
|
524
|
-
method: "POST",
|
|
525
|
-
body: { key, value }
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
async listSecrets(workflowName, opts) {
|
|
529
|
-
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
530
|
-
const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
|
|
531
|
-
return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
|
|
532
|
-
}
|
|
533
|
-
deleteSecret(workflowName, key, opts) {
|
|
534
|
-
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
535
|
-
return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
|
|
536
|
-
}
|
|
537
|
-
createApiKey(input) {
|
|
538
|
-
return this.fetch("/api-keys", { method: "POST", body: input });
|
|
539
|
-
}
|
|
540
|
-
async listApiKeys() {
|
|
541
|
-
const body = await this.fetch("/api-keys");
|
|
542
|
-
return body.data;
|
|
543
|
-
}
|
|
544
|
-
getUsage(from, to) {
|
|
545
|
-
const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
|
|
546
|
-
return this.fetch(`/api/v1/usage${qs}`);
|
|
547
|
-
}
|
|
548
|
-
cancelRun(runId) {
|
|
549
|
-
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
|
|
550
|
-
}
|
|
551
|
-
async* streamRunLogs(runId, opts) {
|
|
552
|
-
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
553
|
-
if (opts?.lastEventId && opts.lastEventId > 0) {
|
|
554
|
-
headers["Last-Event-ID"] = String(opts.lastEventId);
|
|
555
|
-
}
|
|
556
|
-
const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
|
|
557
|
-
headers,
|
|
558
|
-
...opts?.signal ? { signal: opts.signal } : {}
|
|
559
|
-
});
|
|
560
|
-
if (!res.ok || !res.body) {
|
|
561
|
-
let message = res.statusText;
|
|
562
|
-
try {
|
|
563
|
-
const body = await res.json();
|
|
564
|
-
if (body.error)
|
|
565
|
-
message = body.error;
|
|
566
|
-
} catch {}
|
|
567
|
-
throw new AgentComposeError(res.status, message);
|
|
568
|
-
}
|
|
569
|
-
for await (const ev of parseSseStream(res.body)) {
|
|
570
|
-
yield ev.data;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
// src/utils/errors.ts
|
|
575
|
-
function formatError(err) {
|
|
576
|
-
return err instanceof Error ? err.message : String(err);
|
|
577
|
-
}
|
|
578
|
-
// src/utils/discovery.ts
|
|
579
|
-
function discoverRuntimeName(source) {
|
|
580
|
-
return source.match(/runtime:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
1736
|
+
}));
|
|
1737
|
+
return out;
|
|
581
1738
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
await mkdir(dirname2(tmpPath), { recursive: true });
|
|
589
|
-
await writeFile(tmpPath, source, "utf8");
|
|
590
|
-
const mod = await import(tmpPath);
|
|
591
|
-
const cleanup = () => unlink(tmpPath).catch(() => {});
|
|
592
|
-
return { mod, cleanup };
|
|
1739
|
+
async function killSandboxById(provider, sandboxId) {
|
|
1740
|
+
const def = SANDBOX_PROVIDERS[provider];
|
|
1741
|
+
if (!def?.reconnect)
|
|
1742
|
+
throw new Error(`Provider "${provider}" does not support reconnect (required for kill-by-id)`);
|
|
1743
|
+
const sb = await def.reconnect(sandboxId);
|
|
1744
|
+
await sb.kill();
|
|
593
1745
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (!result.success) {
|
|
602
|
-
throw new Error(`Failed to bundle ${label}:
|
|
603
|
-
${result.logs.map((l) => l.message).join(`
|
|
604
|
-
`)}`);
|
|
605
|
-
}
|
|
606
|
-
return result.outputs[0].text();
|
|
1746
|
+
async function killAllSandboxes(onError) {
|
|
1747
|
+
await Promise.allSettled(Object.entries(SANDBOX_PROVIDERS).filter(([, def]) => def.killAll).map(([name, def]) => {
|
|
1748
|
+
const env = Object.fromEntries(Object.keys(def.requiredEnv).map((k) => [k, process.env[k] ?? ""]));
|
|
1749
|
+
if (Object.keys(def.requiredEnv).some((k) => !process.env[k]))
|
|
1750
|
+
return Promise.resolve();
|
|
1751
|
+
return def.killAll(env).catch((err) => onError?.(name, err));
|
|
1752
|
+
}));
|
|
607
1753
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1754
|
+
// src/workflow-steps/observability.ts
|
|
1755
|
+
class StepObservabilityCollector {
|
|
1756
|
+
metadata = {};
|
|
1757
|
+
events = [];
|
|
1758
|
+
subSteps = [];
|
|
1759
|
+
setMetadata = async (data) => {
|
|
1760
|
+
Object.assign(this.metadata, data);
|
|
1761
|
+
};
|
|
1762
|
+
step = async (name, fn) => {
|
|
1763
|
+
const startedAt = Date.now();
|
|
612
1764
|
try {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1765
|
+
const result = await fn();
|
|
1766
|
+
this.subSteps.push({
|
|
1767
|
+
name,
|
|
1768
|
+
startedAt,
|
|
1769
|
+
durationMs: Date.now() - startedAt,
|
|
1770
|
+
status: "completed"
|
|
1771
|
+
});
|
|
1772
|
+
return result;
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
this.subSteps.push({
|
|
1775
|
+
name,
|
|
1776
|
+
startedAt,
|
|
1777
|
+
durationMs: Date.now() - startedAt,
|
|
1778
|
+
status: "failed",
|
|
1779
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1780
|
+
});
|
|
1781
|
+
throw err;
|
|
616
1782
|
}
|
|
617
|
-
} catch {
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
async function bundleWorkflow(workflowPath, overrides) {
|
|
622
|
-
const source = await bundle(workflowPath, `workflow "${workflowPath}"`);
|
|
623
|
-
const wfBits = await extractFromBundle(source, "workflow", (wf) => wf ? {
|
|
624
|
-
networkPolicy: wf.networkPolicy,
|
|
625
|
-
placeholders: wf.placeholders,
|
|
626
|
-
snapshot: wf.snapshot,
|
|
627
|
-
saveSnapshot: wf.saveSnapshot
|
|
628
|
-
} : undefined);
|
|
629
|
-
const { networkPolicy, placeholders, snapshot, saveSnapshot } = wfBits ?? {};
|
|
630
|
-
return {
|
|
631
|
-
source,
|
|
632
|
-
networkPolicy: overrides?.networkPolicy ?? networkPolicy,
|
|
633
|
-
placeholders: overrides?.placeholders ?? placeholders,
|
|
634
|
-
...snapshot !== undefined ? { snapshot } : {},
|
|
635
|
-
...saveSnapshot !== undefined ? { saveSnapshot } : {}
|
|
636
1783
|
};
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
return
|
|
656
|
-
try {
|
|
657
|
-
return JSON.parse(match[1].trim());
|
|
658
|
-
} catch {
|
|
659
|
-
return null;
|
|
1784
|
+
agentEvents = {
|
|
1785
|
+
emit: (event) => {
|
|
1786
|
+
this.events.push(event);
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
snapshot() {
|
|
1790
|
+
const hasMetadata = Object.keys(this.metadata).length > 0;
|
|
1791
|
+
const hasEvents = this.events.length > 0;
|
|
1792
|
+
const hasSubSteps = this.subSteps.length > 0;
|
|
1793
|
+
if (!hasMetadata && !hasEvents && !hasSubSteps)
|
|
1794
|
+
return;
|
|
1795
|
+
const result = {};
|
|
1796
|
+
if (hasMetadata)
|
|
1797
|
+
result.metadata = { ...this.metadata };
|
|
1798
|
+
if (hasEvents)
|
|
1799
|
+
result.events = [...this.events];
|
|
1800
|
+
if (hasSubSteps)
|
|
1801
|
+
result.subSteps = [...this.subSteps];
|
|
1802
|
+
return result;
|
|
660
1803
|
}
|
|
661
1804
|
}
|
|
662
1805
|
|
|
663
|
-
// src/
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
} catch {
|
|
675
|
-
return null;
|
|
1806
|
+
// src/workflow-steps/runner.ts
|
|
1807
|
+
class StepValidationError extends Error {
|
|
1808
|
+
stepName;
|
|
1809
|
+
side;
|
|
1810
|
+
kind = "step-validation";
|
|
1811
|
+
constructor(stepName, side, cause) {
|
|
1812
|
+
super(`Step "${stepName}" ${side} failed schema validation: ${cause.message}`);
|
|
1813
|
+
this.stepName = stepName;
|
|
1814
|
+
this.side = side;
|
|
1815
|
+
this.name = "StepValidationError";
|
|
1816
|
+
this.cause = cause;
|
|
676
1817
|
}
|
|
677
1818
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
|
|
688
|
-
label,
|
|
689
|
-
cwd: opts.cwd
|
|
690
|
-
});
|
|
691
|
-
let lastSessionId = "";
|
|
692
|
-
let lastStatus = null;
|
|
693
|
-
let iterationsWithoutStatus = 0;
|
|
694
|
-
let blockerStreak = null;
|
|
695
|
-
for (let iteration = 0;iteration < maxIterations; iteration++) {
|
|
696
|
-
const prompt = opts.buildPrompt(lastStatus, iteration);
|
|
697
|
-
process.stderr.write(`${label} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns
|
|
698
|
-
`);
|
|
699
|
-
let responseText = "";
|
|
700
|
-
for await (const msg of client.sendMessage({ prompt, sessionId: iteration > 0 ? lastSessionId : undefined })) {
|
|
701
|
-
opts.onAgentEvent?.(iteration, msg);
|
|
702
|
-
if (msg.type === "init")
|
|
703
|
-
lastSessionId = msg.sessionId;
|
|
704
|
-
if (msg.type === "text")
|
|
705
|
-
responseText += msg.text;
|
|
706
|
-
if (msg.type === "error")
|
|
707
|
-
throw new Error(`Agent error: ${msg.text}`);
|
|
708
|
-
}
|
|
709
|
-
process.stdout.write(`
|
|
710
|
-
`);
|
|
711
|
-
let status = parseAgentStatus(responseText);
|
|
712
|
-
process.stderr.write(`
|
|
713
|
-
${label} iteration ${iteration + 1} status: exit_signal=${status?.exit_signal ?? "(no status)"} blockers=${JSON.stringify(status?.blockers ?? [])}
|
|
714
|
-
`);
|
|
715
|
-
lastStatus = status ?? lastStatus;
|
|
716
|
-
opts.onIteration?.(iteration + 1, status);
|
|
717
|
-
if (status?.exit_signal && (status.blockers?.length ?? 0) === 0) {
|
|
718
|
-
let response = status;
|
|
719
|
-
if (opts.responseSchema) {
|
|
720
|
-
const raw = parseAgentResponse(responseText);
|
|
721
|
-
if (raw === null) {
|
|
722
|
-
process.stderr.write(`
|
|
723
|
-
${label} NO <response> BLOCK — response tail: ${responseText.slice(-400)}
|
|
724
|
-
`);
|
|
725
|
-
status = { ...status, exit_signal: false, blockers: ["No <response> block found — emit a <response> block with the required JSON fields before setting exit_signal: true"] };
|
|
726
|
-
opts.onIteration?.(iteration + 1, status);
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
const parsed = opts.responseSchema.safeParse({ ...status, ...raw });
|
|
730
|
-
if (!parsed.success) {
|
|
731
|
-
process.stderr.write(`
|
|
732
|
-
${label} <response> SCHEMA FAILED: ${parsed.error.message}
|
|
733
|
-
raw: ${JSON.stringify(raw).slice(0, 400)}
|
|
734
|
-
`);
|
|
735
|
-
status = { ...status, exit_signal: false, blockers: [`<response> schema validation failed: ${parsed.error.message}`] };
|
|
736
|
-
opts.onIteration?.(iteration + 1, status);
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
response = parsed.data;
|
|
740
|
-
}
|
|
741
|
-
process.stderr.write(`${label} done after ${iteration + 1}/${maxIterations} iterations
|
|
742
|
-
`);
|
|
743
|
-
return { sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
|
|
744
|
-
}
|
|
745
|
-
if (!status) {
|
|
746
|
-
if (++iterationsWithoutStatus >= STALL_ITERATIONS)
|
|
747
|
-
throw new Error(`${label} stalled: no <status> block after ${iterationsWithoutStatus} iterations`);
|
|
748
|
-
} else {
|
|
749
|
-
iterationsWithoutStatus = 0;
|
|
750
|
-
}
|
|
751
|
-
if (status?.blockers?.length) {
|
|
752
|
-
const key = status.blockers.join("|");
|
|
753
|
-
if (blockerStreak !== null && blockerStreak.key === key) {
|
|
754
|
-
if (++blockerStreak.count >= SAME_BLOCKER_ITERATIONS)
|
|
755
|
-
throw new Error(`${label} circuit break: same blocker repeated ${blockerStreak.count}x — "${status.blockers[0]}"`);
|
|
756
|
-
} else {
|
|
757
|
-
blockerStreak = { key, count: 1 };
|
|
758
|
-
}
|
|
759
|
-
} else {
|
|
760
|
-
blockerStreak = null;
|
|
761
|
-
}
|
|
762
|
-
if (iteration + 1 < maxIterations)
|
|
763
|
-
process.stderr.write(`${label} continuing to iteration ${iteration + 2}/${maxIterations}
|
|
764
|
-
`);
|
|
1819
|
+
|
|
1820
|
+
class WorkflowInputValidationError extends Error {
|
|
1821
|
+
workflowId;
|
|
1822
|
+
kind = "workflow-input-validation";
|
|
1823
|
+
constructor(workflowId, cause) {
|
|
1824
|
+
super(`Workflow "${workflowId}" input failed schema validation: ${cause.message}`);
|
|
1825
|
+
this.workflowId = workflowId;
|
|
1826
|
+
this.name = "WorkflowInputValidationError";
|
|
1827
|
+
this.cause = cause;
|
|
765
1828
|
}
|
|
766
|
-
if (opts.responseSchema)
|
|
767
|
-
throw new Error(`${label} did not produce a valid <response> after ${maxIterations} iterations`);
|
|
768
|
-
process.stderr.write(`${label} exhausted ${maxIterations} iterations, proceeding with available work
|
|
769
|
-
`);
|
|
770
|
-
return { sessionId: lastSessionId, lastStatus, iterations: maxIterations };
|
|
771
1829
|
}
|
|
772
1830
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
if (event.type === "assistant") {
|
|
783
|
-
const message = event.message;
|
|
784
|
-
for (const block of message?.content ?? []) {
|
|
785
|
-
const b = block;
|
|
786
|
-
if (b.type === "text")
|
|
787
|
-
msgs.push({ type: "text", text: String(b.text ?? ""), timestamp: ts });
|
|
788
|
-
if (b.type === "thinking")
|
|
789
|
-
msgs.push({ type: "thinking", text: String(b.thinking ?? ""), timestamp: ts });
|
|
790
|
-
if (b.type === "tool_use")
|
|
791
|
-
msgs.push({ type: "tool_use", toolName: String(b.name ?? ""), toolInput: b.input ?? {}, toolUseId: String(b.id ?? ""), timestamp: ts });
|
|
792
|
-
}
|
|
793
|
-
return msgs;
|
|
794
|
-
}
|
|
795
|
-
if (event.type === "user") {
|
|
796
|
-
const message = event.message;
|
|
797
|
-
for (const block of message?.content ?? []) {
|
|
798
|
-
const b = block;
|
|
799
|
-
if (b.type === "tool_result") {
|
|
800
|
-
const raw = b.content;
|
|
801
|
-
msgs.push({ type: "tool_result", toolUseId: String(b.tool_use_id ?? ""), output: typeof raw === "string" ? raw : JSON.stringify(raw ?? ""), isError: Boolean(b.is_error), timestamp: ts });
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return msgs;
|
|
805
|
-
}
|
|
806
|
-
if (event.type === "result") {
|
|
807
|
-
const usage = event.usage;
|
|
808
|
-
if (usage) {
|
|
809
|
-
msgs.push({ type: "usage", inputTokens: usage.input_tokens ?? 0, outputTokens: usage.output_tokens ?? 0, cacheReadTokens: usage.cache_read_input_tokens ?? 0, cacheCreationTokens: usage.cache_creation_input_tokens ?? 0, durationMs: Number(event.duration_ms ?? 0), numTurns: Number(event.num_turns ?? 0), timestamp: ts });
|
|
810
|
-
}
|
|
811
|
-
if (event.is_error || event.subtype === "error_during_execution") {
|
|
812
|
-
const raw = event.error ?? event.result ?? event.message;
|
|
813
|
-
msgs.push({ type: "error", text: raw == null ? "Unknown error" : typeof raw === "object" ? JSON.stringify(raw) : String(raw), timestamp: ts });
|
|
814
|
-
} else {
|
|
815
|
-
msgs.push({ type: "done", sessionId: String(event.session_id ?? ""), timestamp: ts });
|
|
816
|
-
}
|
|
817
|
-
return msgs;
|
|
1831
|
+
class WorkflowOutputValidationError extends Error {
|
|
1832
|
+
workflowId;
|
|
1833
|
+
kind = "workflow-output-validation";
|
|
1834
|
+
constructor(workflowId, cause) {
|
|
1835
|
+
super(`Workflow "${workflowId}" output failed schema validation: ${cause.message}`);
|
|
1836
|
+
this.workflowId = workflowId;
|
|
1837
|
+
this.name = "WorkflowOutputValidationError";
|
|
1838
|
+
this.cause = cause;
|
|
818
1839
|
}
|
|
819
|
-
return msgs;
|
|
820
1840
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1841
|
+
async function runWorkflowSingleStep(opts) {
|
|
1842
|
+
const step = opts.workflow.steps[opts.stepIndex];
|
|
1843
|
+
if (!step)
|
|
1844
|
+
throw new Error(`Step index ${opts.stepIndex} not found in workflow "${opts.workflow.id}"`);
|
|
1845
|
+
const parsedInput = step.input.safeParse(opts.input);
|
|
1846
|
+
if (!parsedInput.success)
|
|
1847
|
+
throw new StepValidationError(step.name, "input", parsedInput.error);
|
|
1848
|
+
const collector = new StepObservabilityCollector;
|
|
1849
|
+
const stepCtx = {
|
|
1850
|
+
input: parsedInput.data,
|
|
1851
|
+
requestContext: opts.requestContext,
|
|
1852
|
+
run: opts.run,
|
|
1853
|
+
...opts.sandbox ? { sandbox: opts.sandbox } : {},
|
|
1854
|
+
stepName: step.name,
|
|
1855
|
+
abortSignal: opts.abortSignal ?? new AbortController().signal,
|
|
1856
|
+
invokeChild: opts.invokeChild ?? (() => {
|
|
1857
|
+
throw new Error("StepContext.invokeChild is not configured for this workflow engine");
|
|
1858
|
+
}),
|
|
1859
|
+
setMetadata: collector.setMetadata,
|
|
1860
|
+
step: collector.step,
|
|
1861
|
+
agentEvents: collector.agentEvents
|
|
1862
|
+
};
|
|
1863
|
+
const output = await step.run(stepCtx);
|
|
1864
|
+
const parsedOutput = step.output.safeParse(output);
|
|
1865
|
+
if (!parsedOutput.success)
|
|
1866
|
+
throw new StepValidationError(step.name, "output", parsedOutput.error);
|
|
1867
|
+
const observability = collector.snapshot();
|
|
1868
|
+
return observability === undefined ? { output: parsedOutput.data } : { output: parsedOutput.data, observability };
|
|
1869
|
+
}
|
|
1870
|
+
async function runWorkflowSteps(opts) {
|
|
1871
|
+
const { workflow, run, requestContext } = opts;
|
|
1872
|
+
const abortSignal = opts.abortSignal ?? new AbortController().signal;
|
|
1873
|
+
const invokeChild = opts.invokeChild ?? (() => {
|
|
1874
|
+
throw new Error("StepContext.invokeChild is not configured for this workflow engine");
|
|
1875
|
+
});
|
|
1876
|
+
const parsedInput = workflow.input.safeParse(opts.input);
|
|
1877
|
+
if (!parsedInput.success) {
|
|
1878
|
+
throw new WorkflowInputValidationError(workflow.id, parsedInput.error);
|
|
847
1879
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1880
|
+
let current = parsedInput.data;
|
|
1881
|
+
const stepResults = [];
|
|
1882
|
+
for (let i = 0;i < workflow.steps.length; i++) {
|
|
1883
|
+
const step = workflow.steps[i];
|
|
1884
|
+
const cached = opts.getCachedOutput ? await opts.getCachedOutput(i, step.name) : undefined;
|
|
1885
|
+
if (cached !== undefined) {
|
|
1886
|
+
const parsedCached = step.output.safeParse(cached);
|
|
1887
|
+
if (!parsedCached.success) {
|
|
1888
|
+
throw new StepValidationError(step.name, "output", parsedCached.error);
|
|
1889
|
+
}
|
|
1890
|
+
current = parsedCached.data;
|
|
1891
|
+
stepResults.push({ name: step.name, status: "completed", output: parsedCached.data, durationMs: 0 });
|
|
1892
|
+
continue;
|
|
858
1893
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1894
|
+
await opts.onStepStarted?.(i, step.name);
|
|
1895
|
+
const parsedStepInput = step.input.safeParse(current);
|
|
1896
|
+
if (!parsedStepInput.success) {
|
|
1897
|
+
const err = new StepValidationError(step.name, "input", parsedStepInput.error);
|
|
1898
|
+
await opts.onStepFailed?.(i, step.name, err, 0);
|
|
1899
|
+
throw err;
|
|
865
1900
|
}
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1901
|
+
const collector = new StepObservabilityCollector;
|
|
1902
|
+
const stepCtx = {
|
|
1903
|
+
input: parsedStepInput.data,
|
|
1904
|
+
requestContext,
|
|
1905
|
+
run,
|
|
1906
|
+
...opts.sandbox ? { sandbox: opts.sandbox } : {},
|
|
1907
|
+
stepName: step.name,
|
|
1908
|
+
abortSignal,
|
|
1909
|
+
invokeChild,
|
|
1910
|
+
setMetadata: collector.setMetadata,
|
|
1911
|
+
step: collector.step,
|
|
1912
|
+
agentEvents: collector.agentEvents
|
|
1913
|
+
};
|
|
1914
|
+
const startedAt = Date.now();
|
|
1915
|
+
let output;
|
|
873
1916
|
try {
|
|
874
|
-
await
|
|
875
|
-
} catch {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
let lineBuffer = "";
|
|
889
|
-
let stderrBuffer = "";
|
|
890
|
-
const maxTurns = this.options.maxTurns ?? 40;
|
|
891
|
-
function handleStdout(data) {
|
|
892
|
-
lineBuffer += data;
|
|
893
|
-
const lines = lineBuffer.split(`
|
|
894
|
-
`);
|
|
895
|
-
lineBuffer = lines.pop() ?? "";
|
|
896
|
-
for (const line of lines) {
|
|
897
|
-
const trimmed = line.trim();
|
|
898
|
-
if (!trimmed)
|
|
899
|
-
continue;
|
|
900
|
-
try {
|
|
901
|
-
const event = JSON.parse(trimmed);
|
|
902
|
-
if (event.type === "assistant") {
|
|
903
|
-
const content = event.message?.content ?? [];
|
|
904
|
-
for (const block of content) {
|
|
905
|
-
const b = block;
|
|
906
|
-
if (b.type === "text")
|
|
907
|
-
process.stdout.write(".");
|
|
908
|
-
if (b.type === "tool_use")
|
|
909
|
-
process.stderr.write(`
|
|
910
|
-
${label} ${b.name}(${JSON.stringify(b.input).slice(0, 80)})`);
|
|
911
|
-
}
|
|
912
|
-
} else if (event.type === "result") {
|
|
913
|
-
process.stderr.write(`
|
|
914
|
-
${label}[result:${event.num_turns}/${maxTurns}/${event.subtype}]
|
|
915
|
-
`);
|
|
916
|
-
} else if (event.type === "system") {
|
|
917
|
-
const ev = event;
|
|
918
|
-
const mcpSrvs = ev.mcp_servers ?? [];
|
|
919
|
-
const mcpStr = mcpSrvs.length ? ` mcp=[${mcpSrvs.map((s) => `${s.name}:${s.status}`).join(",")}]` : " mcp=[]";
|
|
920
|
-
process.stderr.write(`
|
|
921
|
-
${label}[init:${ev.session_id}]${mcpStr}
|
|
922
|
-
`);
|
|
923
|
-
}
|
|
924
|
-
enqueue(translateEvent(event));
|
|
925
|
-
} catch {}
|
|
926
|
-
}
|
|
1917
|
+
output = await step.run(stepCtx);
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
1920
|
+
const durationMs2 = Date.now() - startedAt;
|
|
1921
|
+
const observability2 = collector.snapshot();
|
|
1922
|
+
stepResults.push({
|
|
1923
|
+
name: step.name,
|
|
1924
|
+
status: "failed",
|
|
1925
|
+
error: wrapped.message,
|
|
1926
|
+
durationMs: durationMs2,
|
|
1927
|
+
...observability2 ? { observability: observability2 } : {}
|
|
1928
|
+
});
|
|
1929
|
+
await opts.onStepFailed?.(i, step.name, wrapped, durationMs2);
|
|
1930
|
+
throw wrapped;
|
|
927
1931
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
});
|
|
940
|
-
runPromise.catch((err) => {
|
|
941
|
-
const e = err;
|
|
942
|
-
if (e && typeof e === "object" && "exitCode" in e)
|
|
943
|
-
process.stderr.write(`${label}[transport] command failed — exitCode=${e.exitCode} error=${JSON.stringify(e.error)} stdout_tail=${String(e.stdout ?? "").slice(-200)} stderr_tail=${String(e.stderr ?? "").slice(-200)}
|
|
944
|
-
`);
|
|
945
|
-
queue.push({ sentinel: "error", text: stderrBuffer.trim() ? `Agent process failed: ${formatError(err)}
|
|
946
|
-
|
|
947
|
-
${stderrBuffer.trim()}` : `Agent process failed: ${formatError(err)}` });
|
|
948
|
-
}).finally(() => {
|
|
949
|
-
if (lineBuffer.trim()) {
|
|
950
|
-
try {
|
|
951
|
-
enqueue(translateEvent(JSON.parse(lineBuffer.trim())));
|
|
952
|
-
} catch {}
|
|
953
|
-
}
|
|
954
|
-
queue.push(null);
|
|
955
|
-
notify?.();
|
|
956
|
-
notify = null;
|
|
957
|
-
});
|
|
958
|
-
while (true) {
|
|
959
|
-
while (queue.length > 0) {
|
|
960
|
-
const item = queue.shift();
|
|
961
|
-
if (item === null)
|
|
962
|
-
return;
|
|
963
|
-
if ("sentinel" in item) {
|
|
964
|
-
yield { type: "error", text: item.text, timestamp: new Date().toISOString() };
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
if (signal?.aborted)
|
|
968
|
-
return;
|
|
969
|
-
const msg = item;
|
|
970
|
-
yield msg.type === "error" && stderrBuffer.trim() && !msg.text.includes(stderrBuffer.trim()) ? { ...msg, text: `${msg.text}
|
|
971
|
-
|
|
972
|
-
${stderrBuffer.trim()}` } : msg;
|
|
973
|
-
}
|
|
974
|
-
await new Promise((r) => {
|
|
975
|
-
notify = r;
|
|
1932
|
+
const parsedOutput = step.output.safeParse(output);
|
|
1933
|
+
if (!parsedOutput.success) {
|
|
1934
|
+
const err = new StepValidationError(step.name, "output", parsedOutput.error);
|
|
1935
|
+
const durationMs2 = Date.now() - startedAt;
|
|
1936
|
+
const observability2 = collector.snapshot();
|
|
1937
|
+
stepResults.push({
|
|
1938
|
+
name: step.name,
|
|
1939
|
+
status: "failed",
|
|
1940
|
+
error: err.message,
|
|
1941
|
+
durationMs: durationMs2,
|
|
1942
|
+
...observability2 ? { observability: observability2 } : {}
|
|
976
1943
|
});
|
|
1944
|
+
await opts.onStepFailed?.(i, step.name, err, durationMs2);
|
|
1945
|
+
throw err;
|
|
977
1946
|
}
|
|
1947
|
+
const durationMs = Date.now() - startedAt;
|
|
1948
|
+
current = parsedOutput.data;
|
|
1949
|
+
const observability = collector.snapshot();
|
|
1950
|
+
stepResults.push({
|
|
1951
|
+
name: step.name,
|
|
1952
|
+
status: "completed",
|
|
1953
|
+
output: parsedOutput.data,
|
|
1954
|
+
durationMs,
|
|
1955
|
+
...observability ? { observability } : {}
|
|
1956
|
+
});
|
|
1957
|
+
await opts.onStepCompleted?.(i, step.name, parsedOutput.data, durationMs);
|
|
978
1958
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
await this.sandbox.commands.run(`pkill -x claude 2>/dev/null || true`, { timeoutMs: 1e4 });
|
|
983
|
-
}
|
|
984
|
-
const maxTurns = this.options.maxTurns ?? 40;
|
|
985
|
-
const model = this.configModel ?? this.options.model ?? DEFAULT_CLAUDE_MODEL;
|
|
986
|
-
const label = this.label;
|
|
987
|
-
const promptFile = `/tmp/agent-prompt-${Date.now()}.txt`;
|
|
988
|
-
await this.sandbox.files.write(promptFile, opts.prompt);
|
|
989
|
-
const hasMcp = this.configMcpServers && Object.keys(this.configMcpServers).length > 0;
|
|
990
|
-
const allowedToolsFlag = !hasMcp && this.options.allowedTools?.length ? ["--allowedTools", this.options.allowedTools.join(",")] : [];
|
|
991
|
-
const buildCmd = (sessionId) => [
|
|
992
|
-
"claude",
|
|
993
|
-
"--dangerously-skip-permissions",
|
|
994
|
-
"--verbose",
|
|
995
|
-
"--output-format",
|
|
996
|
-
"stream-json",
|
|
997
|
-
"--max-turns",
|
|
998
|
-
String(maxTurns),
|
|
999
|
-
"--model",
|
|
1000
|
-
model,
|
|
1001
|
-
...allowedToolsFlag,
|
|
1002
|
-
...sessionId ? ["--resume", sessionId] : [],
|
|
1003
|
-
"-p",
|
|
1004
|
-
`"$(cat ${promptFile})"`
|
|
1005
|
-
].join(" ");
|
|
1006
|
-
const MAX_529_RETRIES = 5;
|
|
1007
|
-
const BACKOFF_MS = [60000, 120000, 240000, 480000, 600000];
|
|
1008
|
-
let resumeSessionId = opts.sessionId;
|
|
1009
|
-
for (let attempt = 0;attempt <= MAX_529_RETRIES; attempt++) {
|
|
1010
|
-
let got529 = false;
|
|
1011
|
-
let newSession = "";
|
|
1012
|
-
for await (const msg of this._runCli(buildCmd(resumeSessionId), label, opts.signal)) {
|
|
1013
|
-
if (msg.type === "init")
|
|
1014
|
-
newSession = msg.sessionId;
|
|
1015
|
-
if (msg.type === "error" && msg.text.includes("529")) {
|
|
1016
|
-
got529 = true;
|
|
1017
|
-
break;
|
|
1018
|
-
}
|
|
1019
|
-
yield msg;
|
|
1020
|
-
if (msg.type === "done" || msg.type === "error")
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
if (!got529)
|
|
1024
|
-
return;
|
|
1025
|
-
if (attempt === MAX_529_RETRIES) {
|
|
1026
|
-
yield { type: "error", text: "Agent process failed: Repeated 529 Overloaded errors — all retries exhausted", timestamp: new Date().toISOString() };
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
if (newSession)
|
|
1030
|
-
resumeSessionId = newSession;
|
|
1031
|
-
const wait = BACKOFF_MS[attempt];
|
|
1032
|
-
process.stderr.write(`
|
|
1033
|
-
${label}[529] Overloaded — resuming in ${wait / 1000}s (attempt ${attempt + 1}/${MAX_529_RETRIES})${resumeSessionId ? " with session resume" : ""}
|
|
1034
|
-
`);
|
|
1035
|
-
await new Promise((r) => setTimeout(r, wait));
|
|
1036
|
-
}
|
|
1959
|
+
const parsedFinal = workflow.output.safeParse(current);
|
|
1960
|
+
if (!parsedFinal.success) {
|
|
1961
|
+
throw new WorkflowOutputValidationError(workflow.id, parsedFinal.error);
|
|
1037
1962
|
}
|
|
1963
|
+
return { output: parsedFinal.data, steps: stepResults };
|
|
1038
1964
|
}
|
|
1039
|
-
|
|
1040
|
-
return defineRuntime({
|
|
1041
|
-
create: (sandbox, opts) => new ClaudeRunner(sandbox, opts, config.claudeMdContent ?? "", config.env ?? {}, config.model, config.mcpServers)
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
var claude_default = createClaudeRuntime();
|
|
1965
|
+
|
|
1045
1966
|
// src/workflows/engine.ts
|
|
1046
1967
|
class WorkflowError extends Error {
|
|
1047
1968
|
kind = "workflow";
|
|
@@ -1071,41 +1992,288 @@ function parseNameVersion(ref) {
|
|
|
1071
1992
|
const atIdx = ref.lastIndexOf("@");
|
|
1072
1993
|
return atIdx > 0 ? { name: ref.slice(0, atIdx), version: ref.slice(atIdx + 1) } : { name: ref, version: undefined };
|
|
1073
1994
|
}
|
|
1995
|
+
function defaultRequestContext(runId, workflowId = "") {
|
|
1996
|
+
return RequestContext.fromReserved({
|
|
1997
|
+
teamId: "",
|
|
1998
|
+
runId,
|
|
1999
|
+
workflowId,
|
|
2000
|
+
factoryId: null,
|
|
2001
|
+
apiKeyScopes: [],
|
|
2002
|
+
parentRunId: null
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
1074
2005
|
async function runWorkflow(wf, ctx, opts) {
|
|
1075
2006
|
const hooks = opts?.hooks ?? {};
|
|
1076
|
-
const
|
|
2007
|
+
const requestContext = opts?.requestContext ?? defaultRequestContext(ctx.run.id, wf.id);
|
|
1077
2008
|
const sandbox = makeLocalSandboxProvider();
|
|
1078
|
-
|
|
1079
|
-
|
|
2009
|
+
const invokeChild = opts?.invokeChild ?? (() => {
|
|
2010
|
+
throw new Error("StepContext.invokeChild is not configured for this workflow engine");
|
|
2011
|
+
});
|
|
1080
2012
|
try {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
2013
|
+
const result = await runWorkflowSteps({
|
|
2014
|
+
workflow: wf,
|
|
2015
|
+
input: ctx.input,
|
|
2016
|
+
run: ctx.run,
|
|
2017
|
+
requestContext,
|
|
2018
|
+
sandbox,
|
|
2019
|
+
invokeChild,
|
|
2020
|
+
...opts?.getCachedOutput ? { getCachedOutput: opts.getCachedOutput } : {},
|
|
2021
|
+
onStepStarted: async (idx, name) => {
|
|
1086
2022
|
hooks.onStepStart?.(name);
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
2023
|
+
await opts?.onStepStarted?.(idx, name);
|
|
2024
|
+
},
|
|
2025
|
+
onStepCompleted: async (idx, name, output, durationMs) => {
|
|
2026
|
+
hooks.onStepComplete?.(name, durationMs);
|
|
2027
|
+
await opts?.onStepCompleted?.(idx, name, output, durationMs);
|
|
2028
|
+
},
|
|
2029
|
+
onStepFailed: async (idx, name, err, durationMs) => {
|
|
2030
|
+
hooks.onStepFail?.(name, durationMs, formatError(err));
|
|
2031
|
+
await opts?.onStepFailed?.(idx, name, err, durationMs);
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
2034
|
+
return { response: result.output ?? null };
|
|
2035
|
+
} catch (err) {
|
|
2036
|
+
if (err instanceof WorkflowError)
|
|
2037
|
+
throw err;
|
|
2038
|
+
throw new WorkflowError(formatError(err), { cause: err });
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
// src/workflows/invoke-child.ts
|
|
2042
|
+
function buildInvokeChild(runId, opts = {}) {
|
|
2043
|
+
let childClient = null;
|
|
2044
|
+
const getChildClient = () => {
|
|
2045
|
+
if (childClient)
|
|
2046
|
+
return childClient;
|
|
2047
|
+
const baseUrl = process.env.AGENT_COMPOSE_URL ?? opts.fallbackBaseUrl;
|
|
2048
|
+
const apiKey = process.env.AGENT_COMPOSE_API_KEY;
|
|
2049
|
+
if (!baseUrl || !apiKey) {
|
|
2050
|
+
throw new Error("ctx.invokeChild requires AGENT_COMPOSE_URL and AGENT_COMPOSE_API_KEY");
|
|
2051
|
+
}
|
|
2052
|
+
childClient = new AgentComposeClient(baseUrl, apiKey);
|
|
2053
|
+
return childClient;
|
|
2054
|
+
};
|
|
2055
|
+
return (name, input, childOpts) => getChildClient().invokeAndWait(name, input, {
|
|
2056
|
+
...childOpts,
|
|
2057
|
+
parentRunId: runId,
|
|
2058
|
+
factorySlug: childOpts?.factorySlug ?? opts.defaultFactorySlug ?? process.env.AGENT_COMPOSE_FACTORY_SLUG ?? "default"
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
// src/workflow-steps/step.ts
|
|
2062
|
+
function defineStep(opts) {
|
|
2063
|
+
if (!opts.name)
|
|
2064
|
+
throw new Error("defineStep: 'name' is required");
|
|
2065
|
+
return {
|
|
2066
|
+
name: opts.name,
|
|
2067
|
+
input: opts.input,
|
|
2068
|
+
output: opts.output,
|
|
2069
|
+
run: opts.run
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
// src/step-invocation/protocol.ts
|
|
2073
|
+
var STEP_RESULT_PREFIX = "__AC_STEP_RESULT__";
|
|
2074
|
+
function stepResultLinePrefix(token) {
|
|
2075
|
+
return `${STEP_RESULT_PREFIX}${token}:`;
|
|
2076
|
+
}
|
|
2077
|
+
var RUNNER_BUNDLE_PATH = "/tmp/runner.bundle.js";
|
|
2078
|
+
var RUNNER_COMMAND = `node ${RUNNER_BUNDLE_PATH}`;
|
|
2079
|
+
var STEP_ENV = {
|
|
2080
|
+
RUN_ID: "RUN_ID",
|
|
2081
|
+
STEP_MODE: "AC_STEP_MODE",
|
|
2082
|
+
STEP_INDEX: "AC_STEP_INDEX",
|
|
2083
|
+
STEP_INPUT_PATH: "AC_STEP_INPUT_PATH",
|
|
2084
|
+
REQUEST_CONTEXT_PATH: "AC_REQUEST_CONTEXT_PATH",
|
|
2085
|
+
STEP_RESULT_TOKEN: "AC_STEP_RESULT_TOKEN"
|
|
2086
|
+
};
|
|
2087
|
+
function stepInputPath(stepIndex) {
|
|
2088
|
+
return `/tmp/wf/step-input-${stepIndex}.json`;
|
|
2089
|
+
}
|
|
2090
|
+
function requestContextPath(stepIndex) {
|
|
2091
|
+
return `/tmp/wf/request-context-${stepIndex}.json`;
|
|
2092
|
+
}
|
|
2093
|
+
// src/step-invocation/invoker.ts
|
|
2094
|
+
import { randomBytes } from "node:crypto";
|
|
2095
|
+
function buildStepEnvs(args) {
|
|
2096
|
+
return {
|
|
2097
|
+
[STEP_ENV.RUN_ID]: args.runId,
|
|
2098
|
+
[STEP_ENV.STEP_MODE]: "1",
|
|
2099
|
+
[STEP_ENV.STEP_INDEX]: String(args.stepIndex),
|
|
2100
|
+
[STEP_ENV.STEP_INPUT_PATH]: stepInputPath(args.stepIndex),
|
|
2101
|
+
[STEP_ENV.REQUEST_CONTEXT_PATH]: requestContextPath(args.stepIndex),
|
|
2102
|
+
[STEP_ENV.STEP_RESULT_TOKEN]: args.resultToken
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
function parseStepResult(stdout, resultToken) {
|
|
2106
|
+
const prefix = stepResultLinePrefix(resultToken);
|
|
2107
|
+
const line = stdout.split(/\r?\n/).find((l) => l.startsWith(prefix));
|
|
2108
|
+
if (!line)
|
|
2109
|
+
return null;
|
|
2110
|
+
let parsed;
|
|
2111
|
+
try {
|
|
2112
|
+
parsed = JSON.parse(line.slice(prefix.length));
|
|
2113
|
+
} catch {
|
|
2114
|
+
return { ok: false, error: { kind: "protocol", message: "step result JSON parse failed" } };
|
|
2115
|
+
}
|
|
2116
|
+
if (parsed.ok) {
|
|
2117
|
+
return parsed.observability ? { ok: true, output: parsed.output, observability: parsed.observability } : { ok: true, output: parsed.output };
|
|
2118
|
+
}
|
|
2119
|
+
const kind = parsed.kind === "user-step" ? "user-step" : "protocol";
|
|
2120
|
+
const baseMessage = parsed.error ?? "step body failed without a message";
|
|
2121
|
+
const message = parsed.kind && parsed.kind !== "user-step" && parsed.kind !== "protocol" ? `(unknown kind "${parsed.kind}") ${baseMessage}` : baseMessage;
|
|
2122
|
+
return { ok: false, error: { kind, message } };
|
|
2123
|
+
}
|
|
2124
|
+
async function invokeStep(sandbox, request, opts) {
|
|
2125
|
+
const resultToken = randomBytes(16).toString("hex");
|
|
2126
|
+
await Promise.all([
|
|
2127
|
+
sandbox.files.write(stepInputPath(request.stepIndex), JSON.stringify(request.input)),
|
|
2128
|
+
sandbox.files.write(requestContextPath(request.stepIndex), JSON.stringify(request.requestContext))
|
|
2129
|
+
]);
|
|
2130
|
+
const envs = {
|
|
2131
|
+
...opts?.envs ?? {},
|
|
2132
|
+
...buildStepEnvs({
|
|
2133
|
+
runId: request.runId,
|
|
2134
|
+
stepIndex: request.stepIndex,
|
|
2135
|
+
resultToken
|
|
2136
|
+
})
|
|
2137
|
+
};
|
|
2138
|
+
const sentinelPrefix = stepResultLinePrefix(resultToken);
|
|
2139
|
+
const makeLineSplitter = (sink, filterSentinel) => {
|
|
2140
|
+
if (!sink)
|
|
2141
|
+
return { onChunk: undefined, flush: () => {} };
|
|
2142
|
+
let buf = "";
|
|
2143
|
+
return {
|
|
2144
|
+
onChunk: (chunk) => {
|
|
2145
|
+
buf += chunk;
|
|
2146
|
+
let nl;
|
|
2147
|
+
while ((nl = buf.indexOf(`
|
|
2148
|
+
`)) !== -1) {
|
|
2149
|
+
const line = buf.slice(0, nl);
|
|
2150
|
+
buf = buf.slice(nl + 1);
|
|
2151
|
+
if (filterSentinel && line.startsWith(sentinelPrefix))
|
|
2152
|
+
continue;
|
|
2153
|
+
sink(line);
|
|
1094
2154
|
}
|
|
2155
|
+
},
|
|
2156
|
+
flush: () => {
|
|
2157
|
+
if (buf.length === 0)
|
|
2158
|
+
return;
|
|
2159
|
+
const line = buf;
|
|
2160
|
+
buf = "";
|
|
2161
|
+
if (filterSentinel && line.startsWith(sentinelPrefix))
|
|
2162
|
+
return;
|
|
2163
|
+
sink(line);
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
};
|
|
2167
|
+
const stdoutSplitter = makeLineSplitter(opts?.onStdout, true);
|
|
2168
|
+
const stderrSplitter = makeLineSplitter(opts?.onStderr, false);
|
|
2169
|
+
const result = await sandbox.commands.run(RUNNER_COMMAND, {
|
|
2170
|
+
envs,
|
|
2171
|
+
timeoutMs: 0,
|
|
2172
|
+
...stdoutSplitter.onChunk ? { onStdout: stdoutSplitter.onChunk } : {},
|
|
2173
|
+
...stderrSplitter.onChunk ? { onStderr: stderrSplitter.onChunk } : {}
|
|
2174
|
+
});
|
|
2175
|
+
stdoutSplitter.flush();
|
|
2176
|
+
stderrSplitter.flush();
|
|
2177
|
+
const parsed = parseStepResult(result.stdout, resultToken);
|
|
2178
|
+
if (parsed)
|
|
2179
|
+
return parsed;
|
|
2180
|
+
if (result.exitCode !== 0) {
|
|
2181
|
+
return {
|
|
2182
|
+
ok: false,
|
|
2183
|
+
error: {
|
|
2184
|
+
kind: "runner-exit",
|
|
2185
|
+
message: `runner subprocess exited ${result.exitCode} before emitting a step result`,
|
|
2186
|
+
exitCode: result.exitCode
|
|
1095
2187
|
}
|
|
1096
|
-
}
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
ok: false,
|
|
2192
|
+
error: { kind: "protocol", message: "no tokenised step result found on stdout" }
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
// src/step-invocation/server.ts
|
|
2196
|
+
import { readFileSync } from "node:fs";
|
|
2197
|
+
function emitResult(token, payload) {
|
|
2198
|
+
return new Promise((resolve, reject) => {
|
|
2199
|
+
const line = `${STEP_RESULT_PREFIX}${token}:${JSON.stringify(payload)}
|
|
2200
|
+
`;
|
|
2201
|
+
process.stdout.write(line, (err) => {
|
|
2202
|
+
if (err)
|
|
2203
|
+
reject(err);
|
|
2204
|
+
else
|
|
2205
|
+
resolve();
|
|
2206
|
+
});
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
function scrubProtocolEnvs() {
|
|
2210
|
+
for (const key of [
|
|
2211
|
+
STEP_ENV.STEP_MODE,
|
|
2212
|
+
STEP_ENV.STEP_INDEX,
|
|
2213
|
+
STEP_ENV.STEP_INPUT_PATH,
|
|
2214
|
+
STEP_ENV.REQUEST_CONTEXT_PATH,
|
|
2215
|
+
STEP_ENV.STEP_RESULT_TOKEN
|
|
2216
|
+
]) {
|
|
2217
|
+
delete process.env[key];
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
async function serveStep(handler) {
|
|
2221
|
+
const token = process.env[STEP_ENV.STEP_RESULT_TOKEN];
|
|
2222
|
+
if (!token) {
|
|
2223
|
+
process.stderr.write(`[step-server] missing ${STEP_ENV.STEP_RESULT_TOKEN}
|
|
2224
|
+
`);
|
|
2225
|
+
process.exit(1);
|
|
2226
|
+
}
|
|
2227
|
+
let exitCode = 0;
|
|
2228
|
+
let payload;
|
|
2229
|
+
let setupResult;
|
|
2230
|
+
try {
|
|
2231
|
+
const runId = process.env[STEP_ENV.RUN_ID];
|
|
2232
|
+
const stepIndexEnv = process.env[STEP_ENV.STEP_INDEX];
|
|
2233
|
+
const inputPath = process.env[STEP_ENV.STEP_INPUT_PATH];
|
|
2234
|
+
const ctxPath = process.env[STEP_ENV.REQUEST_CONTEXT_PATH];
|
|
2235
|
+
const stepIndex = stepIndexEnv === undefined ? Number.NaN : Number(stepIndexEnv);
|
|
2236
|
+
if (!runId || !inputPath || !ctxPath || !Number.isFinite(stepIndex)) {
|
|
2237
|
+
throw new Error(`step-server missing required env: ${[
|
|
2238
|
+
!runId && STEP_ENV.RUN_ID,
|
|
2239
|
+
!Number.isFinite(stepIndex) && STEP_ENV.STEP_INDEX,
|
|
2240
|
+
!inputPath && STEP_ENV.STEP_INPUT_PATH,
|
|
2241
|
+
!ctxPath && STEP_ENV.REQUEST_CONTEXT_PATH
|
|
2242
|
+
].filter(Boolean).join(", ")}`);
|
|
2243
|
+
}
|
|
2244
|
+
const input = JSON.parse(readFileSync(inputPath, "utf8"));
|
|
2245
|
+
const requestContext = JSON.parse(readFileSync(ctxPath, "utf8"));
|
|
2246
|
+
setupResult = { runId, stepIndex, input, requestContext };
|
|
1097
2247
|
} catch (err) {
|
|
1098
|
-
|
|
2248
|
+
exitCode = 1;
|
|
2249
|
+
payload = { ok: false, kind: "protocol", error: err instanceof Error ? err.message : String(err) };
|
|
2250
|
+
}
|
|
2251
|
+
if (setupResult) {
|
|
2252
|
+
scrubProtocolEnvs();
|
|
2253
|
+
try {
|
|
2254
|
+
const { output, observability } = await handler(setupResult);
|
|
2255
|
+
payload = observability ? { ok: true, output, observability } : { ok: true, output };
|
|
2256
|
+
} catch (err) {
|
|
2257
|
+
exitCode = 1;
|
|
2258
|
+
payload = { ok: false, kind: "user-step", error: err instanceof Error ? err.message : String(err) };
|
|
2259
|
+
}
|
|
1099
2260
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
2261
|
+
await emitResult(token, payload);
|
|
2262
|
+
process.exit(exitCode);
|
|
2263
|
+
}
|
|
2264
|
+
// src/step-invocation/types.ts
|
|
2265
|
+
class StepExecutionError extends Error {
|
|
2266
|
+
kind;
|
|
2267
|
+
exitCode;
|
|
2268
|
+
constructor(error, stepLabel) {
|
|
2269
|
+
super(`[${error.kind}] ${stepLabel}: ${error.message}`);
|
|
2270
|
+
this.name = "StepExecutionError";
|
|
2271
|
+
this.kind = error.kind;
|
|
2272
|
+
this.exitCode = error.kind === "runner-exit" ? error.exitCode : undefined;
|
|
1104
2273
|
}
|
|
1105
|
-
return { response: wfOutput ?? null };
|
|
1106
2274
|
}
|
|
1107
2275
|
// src/agent/run-agent.ts
|
|
1108
|
-
import { z as
|
|
2276
|
+
import { z as z6 } from "zod";
|
|
1109
2277
|
|
|
1110
2278
|
// src/agent/protocol-suffix.md
|
|
1111
2279
|
var protocol_suffix_default = '## Status Signal\n\nWhen you have finished your work or are blocked, emit a `<status>` block at the end of your response:\n\n```json\n<status>\n{\n "summary": "one sentence describing what was done or what is blocking",\n "completed": ["each acceptance criterion that is now fully met"],\n "blockers": [],\n "changed_files": ["relative/path/to/file"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Field semantics:**\n- `summary`: one sentence — what was accomplished or what is blocking\n- `completed`: acceptance criteria items that are fully done — be specific\n- `blockers`: non-empty when `exit_signal: false` — describe the exact obstacle\n- `changed_files`: relative paths of files you created or modified\n- `tests_run`: `true` if you ran any test suite (pass or fail); `false` if no tests exist or you skipped them\n- `exit_signal: true` — set when ALL acceptance criteria are met and no blockers remain\n- `exit_signal: false` — set when blocked or unfinished; `blockers` must be non-empty\n\n**If you are still actively working** and have not reached a natural stopping point, do NOT emit a `<status>` block — just keep working.\n\n**Example (done):**\n\n```json\n<status>\n{\n "summary": "Added input validation middleware to /api/tasks with tests",\n "completed": ["POST /api/tasks validates required fields", "Returns 400 with details on invalid input", "Unit tests passing"],\n "blockers": [],\n "changed_files": ["src/middleware/validate.ts", "src/routes/tasks.ts", "tests/validate.test.ts"],\n "tests_run": true,\n "exit_signal": true\n}\n</status>\n```\n\n**Example (blocked):**\n\n```json\n<status>\n{\n "summary": "Implemented middleware but tests are failing due to module resolution",\n "completed": ["Middleware created and wired into route"],\n "blockers": ["Tests fail: cannot resolve import \'./validate\' — module resolution config unclear"],\n "changed_files": ["src/middleware/validate.ts"],\n "tests_run": true,\n "exit_signal": false\n}\n</status>\n```\n';
|
|
@@ -1119,33 +2287,30 @@ function stripFrontmatter(content) {
|
|
|
1119
2287
|
---`, 3);
|
|
1120
2288
|
return end === -1 ? content : content.slice(end + 4).trimStart();
|
|
1121
2289
|
}
|
|
1122
|
-
function inject(template, vars) {
|
|
1123
|
-
return Object.entries(vars).reduce((t, [k, v]) => t.replaceAll(`{{${k}}}`, v), template);
|
|
1124
|
-
}
|
|
1125
2290
|
function zodTypeName(schema) {
|
|
1126
|
-
if (schema instanceof
|
|
2291
|
+
if (schema instanceof z6.ZodString)
|
|
1127
2292
|
return "string";
|
|
1128
|
-
if (schema instanceof
|
|
2293
|
+
if (schema instanceof z6.ZodNumber)
|
|
1129
2294
|
return "number";
|
|
1130
|
-
if (schema instanceof
|
|
2295
|
+
if (schema instanceof z6.ZodBoolean)
|
|
1131
2296
|
return "boolean";
|
|
1132
|
-
if (schema instanceof
|
|
2297
|
+
if (schema instanceof z6.ZodArray)
|
|
1133
2298
|
return `${zodTypeName(schema.element)}[]`;
|
|
1134
|
-
if (schema instanceof
|
|
2299
|
+
if (schema instanceof z6.ZodEnum)
|
|
1135
2300
|
return schema.options.map((o) => `"${o}"`).join(" | ");
|
|
1136
|
-
if (schema instanceof
|
|
2301
|
+
if (schema instanceof z6.ZodOptional)
|
|
1137
2302
|
return `${zodTypeName(schema.unwrap())} (optional)`;
|
|
1138
|
-
if (schema instanceof
|
|
2303
|
+
if (schema instanceof z6.ZodNullable)
|
|
1139
2304
|
return `${zodTypeName(schema.unwrap())} | null`;
|
|
1140
|
-
if (schema instanceof
|
|
2305
|
+
if (schema instanceof z6.ZodLiteral)
|
|
1141
2306
|
return `"${String(schema.value)}"`;
|
|
1142
|
-
if (schema instanceof
|
|
2307
|
+
if (schema instanceof z6.ZodObject)
|
|
1143
2308
|
return "object";
|
|
1144
2309
|
return "any";
|
|
1145
2310
|
}
|
|
1146
2311
|
function buildResponseFormatAppendix(schema) {
|
|
1147
2312
|
const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
|
|
1148
|
-
const shape = schema instanceof
|
|
2313
|
+
const shape = schema instanceof z6.ZodObject ? schema.shape : null;
|
|
1149
2314
|
const body = shape ? (() => {
|
|
1150
2315
|
const entries = Object.entries(shape);
|
|
1151
2316
|
const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
|
|
@@ -1170,11 +2335,11 @@ function buildResponseFormatAppendix(schema) {
|
|
|
1170
2335
|
return [...header, ...body].join(`
|
|
1171
2336
|
`);
|
|
1172
2337
|
}
|
|
1173
|
-
async function
|
|
2338
|
+
async function agent(opts) {
|
|
1174
2339
|
const workingDir = opts.workingDir ?? "";
|
|
1175
|
-
const promptVars = { WORKING_DIR: workingDir, DIFF_BASE: "HEAD~1", ...opts.promptVars };
|
|
1176
2340
|
return agentLoop({
|
|
1177
2341
|
runtime: (runtimeOpts) => opts.runtime.create(opts.sandbox, runtimeOpts),
|
|
2342
|
+
...opts.agentId !== undefined ? { agentId: opts.agentId } : {},
|
|
1178
2343
|
...opts.label !== undefined ? { label: opts.label } : {},
|
|
1179
2344
|
...opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {},
|
|
1180
2345
|
...opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {},
|
|
@@ -1184,7 +2349,7 @@ async function runAgent(opts) {
|
|
|
1184
2349
|
buildPrompt: (_lastStatus, iteration) => {
|
|
1185
2350
|
if (iteration > 0)
|
|
1186
2351
|
return PROTOCOL_SUFFIX;
|
|
1187
|
-
const base =
|
|
2352
|
+
const base = stripFrontmatter(opts.prompt);
|
|
1188
2353
|
return opts.responseSchema ? `${base}
|
|
1189
2354
|
|
|
1190
2355
|
${PROTOCOL_SUFFIX}
|
|
@@ -1193,14 +2358,36 @@ ${buildResponseFormatAppendix(opts.responseSchema)}` : `${base}
|
|
|
1193
2358
|
|
|
1194
2359
|
${PROTOCOL_SUFFIX}`;
|
|
1195
2360
|
},
|
|
2361
|
+
...opts.events ? { onAgentLifecycleEvent: (event) => {
|
|
2362
|
+
opts.events?.emit(event);
|
|
2363
|
+
} } : {},
|
|
1196
2364
|
...opts.onAgentEvent ? { onAgentEvent: opts.onAgentEvent } : {},
|
|
1197
|
-
...opts.onIteration ? { onIteration: opts.onIteration } : {}
|
|
2365
|
+
...opts.onIteration ? { onIteration: opts.onIteration } : {},
|
|
2366
|
+
...opts.processors?.length ? { processors: opts.processors } : {},
|
|
2367
|
+
requestContext: opts.requestContext ?? RequestContext.fromReserved({
|
|
2368
|
+
teamId: "",
|
|
2369
|
+
runId: "",
|
|
2370
|
+
workflowId: "",
|
|
2371
|
+
factoryId: null,
|
|
2372
|
+
apiKeyScopes: [],
|
|
2373
|
+
parentRunId: null
|
|
2374
|
+
})
|
|
1198
2375
|
});
|
|
1199
2376
|
}
|
|
1200
2377
|
export {
|
|
2378
|
+
writeTool,
|
|
2379
|
+
stepInputPath,
|
|
2380
|
+
serveStep,
|
|
2381
|
+
runWorkflowSteps,
|
|
2382
|
+
runWorkflowSingleStep,
|
|
1201
2383
|
runWorkflow,
|
|
1202
|
-
|
|
2384
|
+
runProcessorChain,
|
|
2385
|
+
requireScope,
|
|
2386
|
+
requestContextPath,
|
|
2387
|
+
redactPattern,
|
|
1203
2388
|
reconnectSandbox,
|
|
2389
|
+
readTool,
|
|
2390
|
+
parseStepResult,
|
|
1204
2391
|
parseSseStream,
|
|
1205
2392
|
parseSseExecStream,
|
|
1206
2393
|
parseNameVersion,
|
|
@@ -1211,29 +2398,64 @@ export {
|
|
|
1211
2398
|
listOwnedSandboxes,
|
|
1212
2399
|
killSandboxById,
|
|
1213
2400
|
killAllSandboxes,
|
|
2401
|
+
isWorkflow,
|
|
2402
|
+
invokeStep,
|
|
1214
2403
|
importSourceModule,
|
|
1215
2404
|
getSandboxQuotas,
|
|
1216
2405
|
formatError,
|
|
2406
|
+
editTool,
|
|
1217
2407
|
discoverRuntimeName,
|
|
2408
|
+
denyTools,
|
|
1218
2409
|
deleteSandboxSnapshot,
|
|
1219
2410
|
defineWorkflow,
|
|
2411
|
+
defineStep,
|
|
1220
2412
|
defineSandboxEnvironment,
|
|
1221
2413
|
defineRuntime,
|
|
2414
|
+
createVercelRuntime,
|
|
1222
2415
|
createSandbox,
|
|
1223
2416
|
createClaudeRuntime,
|
|
2417
|
+
codingTools,
|
|
1224
2418
|
claude_default as claudeRuntime,
|
|
1225
2419
|
classifyError,
|
|
1226
2420
|
bundleWorkflow,
|
|
2421
|
+
buildStepEnvs,
|
|
2422
|
+
buildInvokeChild,
|
|
2423
|
+
bashTool,
|
|
2424
|
+
assertDefaultExportIsDefineWorkflow,
|
|
1227
2425
|
agentLoop,
|
|
2426
|
+
agent,
|
|
2427
|
+
WorkflowSourceValidationError,
|
|
2428
|
+
WorkflowOutputValidationError,
|
|
2429
|
+
WorkflowInputValidationError,
|
|
1228
2430
|
WorkflowError,
|
|
2431
|
+
Verdict,
|
|
2432
|
+
VercelRunner,
|
|
1229
2433
|
TMP_DIR,
|
|
2434
|
+
StepValidationError,
|
|
2435
|
+
StepExecutionError,
|
|
2436
|
+
STEP_RESULT_PREFIX,
|
|
2437
|
+
STEP_ENV,
|
|
2438
|
+
ReservedKeyError,
|
|
2439
|
+
RequestContext,
|
|
2440
|
+
RUNNER_COMMAND,
|
|
2441
|
+
RUNNER_BUNDLE_PATH,
|
|
2442
|
+
NonSerialisableValueError,
|
|
1230
2443
|
LATEST_VERSION,
|
|
1231
2444
|
EngineError,
|
|
1232
2445
|
DEFAULT_CLAUDE_MODEL,
|
|
1233
2446
|
ClaudeRunner,
|
|
2447
|
+
BUNDLER_VERSION,
|
|
1234
2448
|
AgentStatusSchema,
|
|
1235
2449
|
AgentMessageSchema,
|
|
1236
2450
|
AgentComposeError,
|
|
1237
2451
|
AgentComposeClient,
|
|
1238
|
-
AGENT_COMPOSE_TAG
|
|
2452
|
+
AGENT_COMPOSE_TAG,
|
|
2453
|
+
AC_WORKFLOW_ID,
|
|
2454
|
+
AC_TEAM_ID,
|
|
2455
|
+
AC_RUN_ID,
|
|
2456
|
+
AC_RESERVED_PREFIX,
|
|
2457
|
+
AC_PARENT_RUN_ID,
|
|
2458
|
+
AC_FACTORY_ID,
|
|
2459
|
+
AC_API_KEY_SCOPES,
|
|
2460
|
+
AC_ABORT_SIGNAL
|
|
1239
2461
|
};
|