@agent-compose/sdk 0.2.1 → 0.2.2
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/package.json +10 -2
- package/src/agent/agent-loop.ts +131 -0
- package/src/agent/protocol-suffix.md +57 -0
- package/src/agent/protocol.ts +22 -0
- package/src/agent/run-agent.ts +141 -0
- package/src/client.ts +446 -0
- package/src/env.d.ts +5 -0
- package/src/errors.ts +10 -0
- package/src/index.ts +111 -0
- package/src/runtimes/claude.ts +305 -0
- package/src/runtimes/openai-desktop.ts +151 -0
- package/src/sandbox.ts +458 -0
- package/src/sse.ts +56 -0
- package/src/types/events.ts +51 -0
- package/src/types/protocol.ts +74 -0
- package/src/types/runtime.ts +59 -0
- package/src/types/sandbox-environment.ts +64 -0
- package/src/types/sandbox.ts +51 -0
- package/src/types/workflow.ts +128 -0
- package/src/utils/bundler.ts +81 -0
- package/src/utils/discovery.ts +4 -0
- package/src/utils/errors.ts +4 -0
- package/src/utils/schemas.ts +10 -0
- package/src/utils/source-loader.ts +16 -0
- package/src/workflows/engine.ts +110 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-compose/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Client library for agent-compose — define agents, runtimes, and workflows, and invoke them against an agent-compose server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,15 @@
|
|
|
24
24
|
"default": "./dist/runtimes/openai-desktop.js"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
-
"files": [
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src/**/*.ts",
|
|
30
|
+
"src/**/*.md",
|
|
31
|
+
"!src/**/__tests__/**",
|
|
32
|
+
"!src/**/*.test.ts",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
28
36
|
"engines": {
|
|
29
37
|
"node": ">=20"
|
|
30
38
|
},
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent loop — runner-agnostic iteration driver operating through ModelExecutionContract.
|
|
3
|
+
* Each iteration: build prompt → sendMessage() → parse <status> → done / continue / circuit-break.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RuntimeOptions, ModelExecutionContract } from "../index.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { AgentStatusSchema, parseAgentResponse } from "./protocol.js";
|
|
9
|
+
import type { AgentStatus, AgentMessage } from "./protocol.js";
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_CLAUDE_MODEL = "claude-opus-4-7";
|
|
12
|
+
|
|
13
|
+
const SAME_BLOCKER_ITERATIONS = 3;
|
|
14
|
+
const STALL_ITERATIONS = 3;
|
|
15
|
+
|
|
16
|
+
export function parseAgentStatus(text: string): AgentStatus | null {
|
|
17
|
+
const match = text.match(/<status>([\s\S]*?)<\/status>/);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
try {
|
|
20
|
+
const result = AgentStatusSchema.safeParse(JSON.parse(match[1].trim()));
|
|
21
|
+
return result.success ? result.data : null;
|
|
22
|
+
} catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DEFAULT_ALLOWED_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebFetch"];
|
|
26
|
+
|
|
27
|
+
export interface AgentLoopResult {
|
|
28
|
+
sessionId: string;
|
|
29
|
+
lastStatus: AgentStatus | null;
|
|
30
|
+
iterations: number;
|
|
31
|
+
response?: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function agentLoop(opts: {
|
|
35
|
+
label?: string;
|
|
36
|
+
onIteration?: (iteration: number, status: AgentStatus | null) => void;
|
|
37
|
+
turnsPerIteration?: number;
|
|
38
|
+
maxIterations?: number;
|
|
39
|
+
buildPrompt: (lastStatus: AgentStatus | null, iteration: number) => string;
|
|
40
|
+
onAgentEvent?: (iteration: number, msg: AgentMessage) => void;
|
|
41
|
+
allowedTools?: string[];
|
|
42
|
+
responseSchema?: z.ZodType<unknown>;
|
|
43
|
+
runtime?: (opts: RuntimeOptions) => ModelExecutionContract;
|
|
44
|
+
cwd?: string;
|
|
45
|
+
}): Promise<AgentLoopResult> {
|
|
46
|
+
const label = opts.label ?? "[Agent Loop]";
|
|
47
|
+
const turnsPerIteration = opts.turnsPerIteration ?? 40;
|
|
48
|
+
const maxIterations = opts.maxIterations ?? 8;
|
|
49
|
+
|
|
50
|
+
if (!opts.runtime) throw new Error("agentLoop: opts.runtime is required");
|
|
51
|
+
const client = opts.runtime({
|
|
52
|
+
maxTurns: turnsPerIteration,
|
|
53
|
+
allowedTools: opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
|
|
54
|
+
label,
|
|
55
|
+
cwd: opts.cwd,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let lastSessionId = "";
|
|
59
|
+
let lastStatus: AgentStatus | null = null;
|
|
60
|
+
let iterationsWithoutStatus = 0;
|
|
61
|
+
let blockerStreak: { key: string; count: number } | null = null;
|
|
62
|
+
|
|
63
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
64
|
+
const prompt = opts.buildPrompt(lastStatus, iteration);
|
|
65
|
+
process.stderr.write(`${label} iteration ${iteration + 1}/${maxIterations} · ${turnsPerIteration} turns\n`);
|
|
66
|
+
|
|
67
|
+
let responseText = "";
|
|
68
|
+
for await (const msg of client.sendMessage({ prompt, sessionId: iteration > 0 ? lastSessionId : undefined })) {
|
|
69
|
+
opts.onAgentEvent?.(iteration, msg);
|
|
70
|
+
if (msg.type === "init") lastSessionId = msg.sessionId;
|
|
71
|
+
if (msg.type === "text") responseText += msg.text;
|
|
72
|
+
if (msg.type === "error") throw new Error(`Agent error: ${msg.text}`);
|
|
73
|
+
}
|
|
74
|
+
process.stdout.write("\n");
|
|
75
|
+
|
|
76
|
+
let status = parseAgentStatus(responseText);
|
|
77
|
+
process.stderr.write(`\n${label} iteration ${iteration + 1} status: exit_signal=${status?.exit_signal ?? "(no status)"} blockers=${JSON.stringify(status?.blockers ?? [])}\n`);
|
|
78
|
+
lastStatus = status ?? lastStatus;
|
|
79
|
+
opts.onIteration?.(iteration + 1, status);
|
|
80
|
+
|
|
81
|
+
if (status?.exit_signal && (status.blockers?.length ?? 0) === 0) {
|
|
82
|
+
let response: unknown = status;
|
|
83
|
+
if (opts.responseSchema) {
|
|
84
|
+
const raw = parseAgentResponse(responseText);
|
|
85
|
+
if (raw === null) {
|
|
86
|
+
process.stderr.write(`\n${label} NO <response> BLOCK — response tail: ${responseText.slice(-400)}\n`);
|
|
87
|
+
status = { ...status!, exit_signal: false, blockers: ["No <response> block found — emit a <response> block with the required JSON fields before setting exit_signal: true"] };
|
|
88
|
+
opts.onIteration?.(iteration + 1, status);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const parsed = opts.responseSchema.safeParse({ ...status, ...(raw as object) });
|
|
92
|
+
if (!parsed.success) {
|
|
93
|
+
process.stderr.write(`\n${label} <response> SCHEMA FAILED: ${parsed.error.message}\nraw: ${JSON.stringify(raw).slice(0, 400)}\n`);
|
|
94
|
+
status = { ...status!, exit_signal: false, blockers: [`<response> schema validation failed: ${parsed.error.message}`] };
|
|
95
|
+
opts.onIteration?.(iteration + 1, status);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
response = parsed.data;
|
|
99
|
+
}
|
|
100
|
+
process.stderr.write(`${label} done after ${iteration + 1}/${maxIterations} iterations\n`);
|
|
101
|
+
return { sessionId: lastSessionId, lastStatus: status, iterations: iteration + 1, response };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!status) {
|
|
105
|
+
if (++iterationsWithoutStatus >= STALL_ITERATIONS)
|
|
106
|
+
throw new Error(`${label} stalled: no <status> block after ${iterationsWithoutStatus} iterations`);
|
|
107
|
+
} else {
|
|
108
|
+
iterationsWithoutStatus = 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (status?.blockers?.length) {
|
|
112
|
+
const key = status.blockers.join("|");
|
|
113
|
+
if (blockerStreak !== null && blockerStreak.key === key) {
|
|
114
|
+
if (++blockerStreak.count >= SAME_BLOCKER_ITERATIONS)
|
|
115
|
+
throw new Error(`${label} circuit break: same blocker repeated ${blockerStreak.count}x — "${status.blockers[0]}"`);
|
|
116
|
+
} else {
|
|
117
|
+
blockerStreak = { key, count: 1 };
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
blockerStreak = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (iteration + 1 < maxIterations)
|
|
124
|
+
process.stderr.write(`${label} continuing to iteration ${iteration + 2}/${maxIterations}\n`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (opts.responseSchema)
|
|
128
|
+
throw new Error(`${label} did not produce a valid <response> after ${maxIterations} iterations`);
|
|
129
|
+
process.stderr.write(`${label} exhausted ${maxIterations} iterations, proceeding with available work\n`);
|
|
130
|
+
return { sessionId: lastSessionId, lastStatus, iterations: maxIterations };
|
|
131
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
## Status Signal
|
|
2
|
+
|
|
3
|
+
When you have finished your work or are blocked, emit a `<status>` block at the end of your response:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
<status>
|
|
7
|
+
{
|
|
8
|
+
"summary": "one sentence describing what was done or what is blocking",
|
|
9
|
+
"completed": ["each acceptance criterion that is now fully met"],
|
|
10
|
+
"blockers": [],
|
|
11
|
+
"changed_files": ["relative/path/to/file"],
|
|
12
|
+
"tests_run": true,
|
|
13
|
+
"exit_signal": true
|
|
14
|
+
}
|
|
15
|
+
</status>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Field semantics:**
|
|
19
|
+
- `summary`: one sentence — what was accomplished or what is blocking
|
|
20
|
+
- `completed`: acceptance criteria items that are fully done — be specific
|
|
21
|
+
- `blockers`: non-empty when `exit_signal: false` — describe the exact obstacle
|
|
22
|
+
- `changed_files`: relative paths of files you created or modified
|
|
23
|
+
- `tests_run`: `true` if you ran any test suite (pass or fail); `false` if no tests exist or you skipped them
|
|
24
|
+
- `exit_signal: true` — set when ALL acceptance criteria are met and no blockers remain
|
|
25
|
+
- `exit_signal: false` — set when blocked or unfinished; `blockers` must be non-empty
|
|
26
|
+
|
|
27
|
+
**If you are still actively working** and have not reached a natural stopping point, do NOT emit a `<status>` block — just keep working.
|
|
28
|
+
|
|
29
|
+
**Example (done):**
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
<status>
|
|
33
|
+
{
|
|
34
|
+
"summary": "Added input validation middleware to /api/tasks with tests",
|
|
35
|
+
"completed": ["POST /api/tasks validates required fields", "Returns 400 with details on invalid input", "Unit tests passing"],
|
|
36
|
+
"blockers": [],
|
|
37
|
+
"changed_files": ["src/middleware/validate.ts", "src/routes/tasks.ts", "tests/validate.test.ts"],
|
|
38
|
+
"tests_run": true,
|
|
39
|
+
"exit_signal": true
|
|
40
|
+
}
|
|
41
|
+
</status>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Example (blocked):**
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
<status>
|
|
48
|
+
{
|
|
49
|
+
"summary": "Implemented middleware but tests are failing due to module resolution",
|
|
50
|
+
"completed": ["Middleware created and wired into route"],
|
|
51
|
+
"blockers": ["Tests fail: cannot resolve import './validate' — module resolution config unclear"],
|
|
52
|
+
"changed_files": ["src/middleware/validate.ts"],
|
|
53
|
+
"tests_run": true,
|
|
54
|
+
"exit_signal": false
|
|
55
|
+
}
|
|
56
|
+
</status>
|
|
57
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AgentStatusSchema } from "../utils/schemas.js";
|
|
3
|
+
import type { AgentMessage } from "../types/protocol.js";
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
AgentMessage, AgentMessageInit, AgentMessageText, AgentMessageThinking,
|
|
7
|
+
AgentMessageToolUse, AgentMessageToolResult, AgentMessageDone,
|
|
8
|
+
AgentMessageError, AgentMessageUsage, AgentStatus,
|
|
9
|
+
} from "../types/protocol.js";
|
|
10
|
+
|
|
11
|
+
export { AgentStatusSchema };
|
|
12
|
+
|
|
13
|
+
export const AgentMessageSchema = z.object({
|
|
14
|
+
type: z.enum(["init", "text", "thinking", "tool_use", "tool_result", "done", "error", "usage"]),
|
|
15
|
+
timestamp: z.string(),
|
|
16
|
+
}).passthrough() as unknown as z.ZodType<AgentMessage>;
|
|
17
|
+
|
|
18
|
+
export function parseAgentResponse(text: string): unknown {
|
|
19
|
+
const match = text.match(/<response>([\s\S]*?)<\/response>/);
|
|
20
|
+
if (!match) return null;
|
|
21
|
+
try { return JSON.parse(match[1].trim()); } catch { return null; }
|
|
22
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runAgent — canonical entry point for embedding an LLM agent inside a
|
|
3
|
+
* workflow. The workflow's `run()` body calls it; the loop executes
|
|
4
|
+
* against the runner's own VM.
|
|
5
|
+
*
|
|
6
|
+
* Glue packaged so workflows don't duplicate it:
|
|
7
|
+
* - Inject `{{VAR}}` placeholders into the prompt template.
|
|
8
|
+
* - Strip the `--- frontmatter ---` header authors use for IDE hints.
|
|
9
|
+
* - Append PROTOCOL_SUFFIX (status/response format instructions).
|
|
10
|
+
* - Append a response-format appendix when `responseSchema` is set.
|
|
11
|
+
* - Delegate to `agentLoop`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { agentLoop } from "./agent-loop.js";
|
|
16
|
+
import type { AgentLoopResult } from "./agent-loop.js";
|
|
17
|
+
import type { AgentMessage, AgentStatus } from "../types/protocol.js";
|
|
18
|
+
import type { AgentRuntime, RuntimeOptions } from "../types/runtime.js";
|
|
19
|
+
import type { SandboxProvider } from "../types/sandbox.js";
|
|
20
|
+
import type { AgentBudget } from "../types/workflow.js";
|
|
21
|
+
import PROTOCOL_SUFFIX_RAW from "./protocol-suffix.md" with { type: "text" };
|
|
22
|
+
|
|
23
|
+
const PROTOCOL_SUFFIX = stripFrontmatter(PROTOCOL_SUFFIX_RAW);
|
|
24
|
+
|
|
25
|
+
function stripFrontmatter(content: string): string {
|
|
26
|
+
if (!content.startsWith("---")) return content;
|
|
27
|
+
const end = content.indexOf("\n---", 3);
|
|
28
|
+
return end === -1 ? content : content.slice(end + 4).trimStart();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function inject(template: string, vars: Record<string, string>): string {
|
|
32
|
+
return Object.entries(vars).reduce((t, [k, v]) => t.replaceAll(`{{${k}}}`, v), template);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
function zodTypeName(schema: any): string {
|
|
37
|
+
if (schema instanceof z.ZodString) return "string";
|
|
38
|
+
if (schema instanceof z.ZodNumber) return "number";
|
|
39
|
+
if (schema instanceof z.ZodBoolean) return "boolean";
|
|
40
|
+
if (schema instanceof z.ZodArray) return `${zodTypeName(schema.element)}[]`;
|
|
41
|
+
if (schema instanceof z.ZodEnum) return (schema.options as string[]).map((o: string) => `"${o}"`).join(" | ");
|
|
42
|
+
if (schema instanceof z.ZodOptional) return `${zodTypeName(schema.unwrap())} (optional)`;
|
|
43
|
+
if (schema instanceof z.ZodNullable) return `${zodTypeName(schema.unwrap())} | null`;
|
|
44
|
+
if (schema instanceof z.ZodLiteral) return `"${String(schema.value)}"`;
|
|
45
|
+
if (schema instanceof z.ZodObject) return "object";
|
|
46
|
+
return "any";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
function buildResponseFormatAppendix(schema: z.ZodType<any>): string {
|
|
51
|
+
const header = ["## Response Format", "", "When you emit `exit_signal: true` in your `<status>` block, also include a `<response>` block containing JSON immediately after `<status>`:", ""];
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
const shape: Record<string, any> | null =
|
|
54
|
+
schema instanceof z.ZodObject ? (schema as z.ZodObject<z.ZodRawShape>).shape : null;
|
|
55
|
+
const body = shape
|
|
56
|
+
? (() => {
|
|
57
|
+
const entries = Object.entries(shape);
|
|
58
|
+
const example = Object.fromEntries(entries.map(([k, v]) => [k, `<${zodTypeName(v)}>`]));
|
|
59
|
+
return [
|
|
60
|
+
"Fields:",
|
|
61
|
+
...entries.map(([k, v]) => `- \`${k}\` (${zodTypeName(v)})`),
|
|
62
|
+
"", "Example:", "<response>",
|
|
63
|
+
JSON.stringify(example, null, 2),
|
|
64
|
+
"</response>",
|
|
65
|
+
];
|
|
66
|
+
})()
|
|
67
|
+
: [
|
|
68
|
+
"The response must match this schema:",
|
|
69
|
+
`Expected type: ${zodTypeName(schema)}`,
|
|
70
|
+
"", "Example:", "<response>",
|
|
71
|
+
`<${zodTypeName(schema)}>`,
|
|
72
|
+
"</response>",
|
|
73
|
+
];
|
|
74
|
+
return [...header, ...body].join("\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface RunAgentOpts<T = unknown> {
|
|
78
|
+
/** Sandbox the runtime executes commands against. Inside a workflow,
|
|
79
|
+
* always pass `ctx.sandbox` — a pre-constructed local provider for
|
|
80
|
+
* the runner's own VM. Exposed as a parameter so tests and non-workflow
|
|
81
|
+
* callers can substitute their own. */
|
|
82
|
+
sandbox: SandboxProvider;
|
|
83
|
+
/** Runtime definition from `createClaudeRuntime({...})` (or custom). */
|
|
84
|
+
runtime: AgentRuntime;
|
|
85
|
+
/** Prompt template. Authors can include YAML-style `--- frontmatter ---`
|
|
86
|
+
* at the top for IDE hints; it's stripped before the model sees it. */
|
|
87
|
+
prompt: string;
|
|
88
|
+
/** Substitution map for `{{VAR}}` placeholders in the prompt. `WORKING_DIR`
|
|
89
|
+
* and `DIFF_BASE` auto-populate from `opts.workingDir` unless overridden. */
|
|
90
|
+
promptVars?: Record<string, string>;
|
|
91
|
+
/** `cwd` forwarded to the runtime — every shell command runs here. */
|
|
92
|
+
workingDir?: string;
|
|
93
|
+
/** Tools the model may use. Defaults to a safe kitchen-sink set inside
|
|
94
|
+
* `agentLoop`. Pass [] to disable tool use entirely. */
|
|
95
|
+
tools?: string[];
|
|
96
|
+
/** Turn/iteration caps. Defaults: 40 turns/iteration, 8 iterations. */
|
|
97
|
+
budget?: AgentBudget;
|
|
98
|
+
/** If set, the loop demands a `<response>` block when `exit_signal=true`
|
|
99
|
+
* and validates it against this schema. The response format appendix is
|
|
100
|
+
* appended to the prompt on first iteration. */
|
|
101
|
+
responseSchema?: z.ZodType<T>;
|
|
102
|
+
/** Label prefix for runtime stderr ("[sbid][agent]" by default). */
|
|
103
|
+
label?: string;
|
|
104
|
+
/** Per-message event callback — wire this to your workflow's event
|
|
105
|
+
* telemetry if you want per-tool-call observability. */
|
|
106
|
+
onAgentEvent?: (iteration: number, msg: AgentMessage) => void;
|
|
107
|
+
/** Per-iteration status callback — fires after each model turn with the
|
|
108
|
+
* parsed `<status>` block (or null if the model didn't emit one). */
|
|
109
|
+
onIteration?: (iteration: number, status: AgentStatus | null) => void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Run an agent loop inside a workflow. Returns the loop's final
|
|
114
|
+
* `AgentLoopResult`, including `response` when a `responseSchema` was
|
|
115
|
+
* supplied and the model validated against it.
|
|
116
|
+
*/
|
|
117
|
+
export async function runAgent<T = unknown>(opts: RunAgentOpts<T>): Promise<AgentLoopResult> {
|
|
118
|
+
const workingDir = opts.workingDir ?? "";
|
|
119
|
+
const promptVars = { WORKING_DIR: workingDir, DIFF_BASE: "HEAD~1", ...opts.promptVars };
|
|
120
|
+
|
|
121
|
+
return agentLoop({
|
|
122
|
+
runtime: (runtimeOpts: RuntimeOptions) => opts.runtime.create(opts.sandbox, runtimeOpts),
|
|
123
|
+
...(opts.label !== undefined ? { label: opts.label } : {}),
|
|
124
|
+
...(opts.budget?.turnsPerIteration !== undefined ? { turnsPerIteration: opts.budget.turnsPerIteration } : {}),
|
|
125
|
+
...(opts.budget?.maxIterations !== undefined ? { maxIterations: opts.budget.maxIterations } : {}),
|
|
126
|
+
...(opts.tools !== undefined ? { allowedTools: opts.tools } : {}),
|
|
127
|
+
...(opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {}),
|
|
128
|
+
...(workingDir ? { cwd: workingDir } : {}),
|
|
129
|
+
buildPrompt: (_lastStatus, iteration) => {
|
|
130
|
+
// Mid-loop: session resumes via --resume, model has history; only the
|
|
131
|
+
// protocol suffix is needed so tool-use + status block grammar stays fresh.
|
|
132
|
+
if (iteration > 0) return PROTOCOL_SUFFIX;
|
|
133
|
+
const base = inject(stripFrontmatter(opts.prompt), promptVars);
|
|
134
|
+
return opts.responseSchema
|
|
135
|
+
? `${base}\n\n${PROTOCOL_SUFFIX}\n\n${buildResponseFormatAppendix(opts.responseSchema)}`
|
|
136
|
+
: `${base}\n\n${PROTOCOL_SUFFIX}`;
|
|
137
|
+
},
|
|
138
|
+
...(opts.onAgentEvent ? { onAgentEvent: opts.onAgentEvent } : {}),
|
|
139
|
+
...(opts.onIteration ? { onIteration: opts.onIteration } : {}),
|
|
140
|
+
});
|
|
141
|
+
}
|