@nylorun/harness 0.11.1-beta → 0.13.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +124 -5
- package/dist/definition/agent-definition.d.ts +11 -0
- package/dist/definition/agent-definition.js +12 -0
- package/dist/definition/assemble.d.ts +22 -0
- package/dist/{build → definition}/assemble.js +6 -7
- package/dist/definition/bind-agent.d.ts +9 -0
- package/dist/definition/bind-agent.js +21 -0
- package/dist/{build → definition}/bind-tool.d.ts +2 -1
- package/dist/{build → definition}/bind-tool.js +1 -0
- package/dist/definition/bound.d.ts +16 -0
- package/dist/definition/builder.d.ts +30 -0
- package/dist/definition/builder.js +94 -0
- package/dist/definition/declaration.d.ts +3 -0
- package/dist/definition/declaration.js +53 -0
- package/dist/{build → definition}/helpers.d.ts +1 -1
- package/dist/{build → definition}/manifest.d.ts +3 -1
- package/dist/definition/manifest.js +45 -0
- package/dist/{session → definition}/output-contract.d.ts +2 -2
- package/dist/{session → definition}/output-contract.js +1 -1
- package/dist/definition/registry.d.ts +10 -0
- package/dist/definition/registry.js +48 -0
- package/dist/{build → definition}/schema.d.ts +4 -4
- package/dist/errors.d.ts +1 -1
- package/dist/execution/dispatch.d.ts +2 -0
- package/dist/execution/dispatch.js +164 -0
- package/dist/execution/initial-state.d.ts +3 -0
- package/dist/execution/initial-state.js +14 -0
- package/dist/execution/invocation.d.ts +21 -0
- package/dist/execution/invocation.js +32 -0
- package/dist/{model → execution/model}/adapters.d.ts +2 -2
- package/dist/{model → execution/model}/adapters.js +2 -2
- package/dist/{model → execution/model}/normalize.d.ts +2 -2
- package/dist/{model → execution/model}/normalize.js +2 -2
- package/dist/{model → execution/model}/prepared.d.ts +3 -3
- package/dist/{step → execution/model}/project.d.ts +1 -1
- package/dist/{step → execution/model}/project.js +2 -10
- package/dist/execution/observe.d.ts +17 -0
- package/dist/execution/observe.js +53 -0
- package/dist/execution/options.d.ts +10 -0
- package/dist/execution/options.js +49 -0
- package/dist/execution/resume.d.ts +4 -0
- package/dist/execution/resume.js +52 -0
- package/dist/execution/run.d.ts +5 -0
- package/dist/execution/run.js +256 -0
- package/dist/execution/state.d.ts +8 -0
- package/dist/execution/state.js +208 -0
- package/dist/{step → execution/step}/canonicalize.d.ts +2 -2
- package/dist/{step → execution/step}/canonicalize.js +2 -2
- package/dist/{step → execution/step}/compose.d.ts +3 -2
- package/dist/{step → execution/step}/compose.js +5 -5
- package/dist/{step → execution/step}/context-draft.d.ts +3 -5
- package/dist/{step → execution/step}/context-draft.js +5 -22
- package/dist/execution/step/describe.d.ts +9 -0
- package/dist/execution/step/describe.js +22 -0
- package/dist/{step → execution/step}/model-configuration.d.ts +4 -3
- package/dist/{step → execution/step}/model-configuration.js +3 -3
- package/dist/execution/step/resolve.d.ts +11 -0
- package/dist/{step → execution/step}/resolve.js +5 -3
- package/dist/execution/step/run.d.ts +29 -0
- package/dist/{step → execution/step}/run.js +19 -53
- package/dist/execution/step/runtime.d.ts +32 -0
- package/dist/execution/step/runtime.js +1 -0
- package/dist/{step → execution/step}/seal.d.ts +13 -8
- package/dist/{step → execution/step}/seal.js +8 -6
- package/dist/{step → execution/step}/slot-assembly.d.ts +4 -4
- package/dist/{step → execution/step}/slot-assembly.js +4 -4
- package/dist/{step → execution/step}/step-context.d.ts +11 -12
- package/dist/{step → execution/step}/step-context.js +5 -27
- package/dist/execution/tool-result.d.ts +4 -0
- package/dist/execution/tool-result.js +30 -0
- package/dist/execution/transcript.d.ts +2 -0
- package/dist/{session/seed.js → execution/transcript.js} +9 -39
- package/dist/index.d.ts +15 -12
- package/dist/index.js +5 -5
- package/dist/types/agent.d.ts +9 -0
- package/dist/types/agent.js +1 -0
- package/dist/types/execution.d.ts +97 -0
- package/dist/types/execution.js +1 -0
- package/dist/types/manifest.d.ts +16 -13
- package/dist/types/middleware.d.ts +11 -40
- package/dist/types/model.d.ts +10 -10
- package/dist/types/observe.d.ts +165 -0
- package/dist/types/observe.js +1 -0
- package/dist/types/shared.d.ts +1 -225
- package/dist/types/tool.d.ts +21 -19
- package/dist/types/transcript.d.ts +65 -0
- package/dist/types/transcript.js +1 -0
- package/dist/utils/canonical.d.ts +1 -0
- package/dist/utils/canonical.js +10 -0
- package/dist/utils/immutable.d.ts +1 -0
- package/dist/utils/immutable.js +8 -0
- package/package.json +5 -5
- package/dist/build/agent.d.ts +0 -18
- package/dist/build/agent.js +0 -37
- package/dist/build/assemble.d.ts +0 -8
- package/dist/build/builder.d.ts +0 -46
- package/dist/build/builder.js +0 -151
- package/dist/build/manifest.js +0 -20
- package/dist/session/capability-state.d.ts +0 -14
- package/dist/session/capability-state.js +0 -67
- package/dist/session/event-log.d.ts +0 -12
- package/dist/session/event-log.js +0 -63
- package/dist/session/input-queue.d.ts +0 -41
- package/dist/session/input-queue.js +0 -94
- package/dist/session/record.d.ts +0 -11
- package/dist/session/record.js +0 -26
- package/dist/session/scheduler.d.ts +0 -62
- package/dist/session/scheduler.js +0 -485
- package/dist/session/seed.d.ts +0 -10
- package/dist/session/session.d.ts +0 -18
- package/dist/session/session.js +0 -103
- package/dist/session/state.d.ts +0 -15
- package/dist/session/state.js +0 -48
- package/dist/session/submission-stream.d.ts +0 -16
- package/dist/session/submission-stream.js +0 -46
- package/dist/step/resolve.d.ts +0 -10
- package/dist/step/run.d.ts +0 -33
- package/dist/turn/plan-runner.d.ts +0 -70
- package/dist/turn/plan-runner.js +0 -388
- package/dist/turn/runner.d.ts +0 -69
- package/dist/turn/runner.js +0 -158
- package/dist/types/session.d.ts +0 -227
- package/dist/utils/maps.d.ts +0 -1
- package/dist/utils/maps.js +0 -37
- package/dist/utils/observe.d.ts +0 -8
- package/dist/utils/observe.js +0 -29
- /package/dist/{types/session.js → definition/bound.js} +0 -0
- /package/dist/{build → definition}/helpers.js +0 -0
- /package/dist/{build → definition}/schema.js +0 -0
- /package/dist/{model → execution/model}/prepared.js +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { HarnessError, isHarnessError } from "../errors.js";
|
|
2
|
+
import { runStep } from "./step/run.js";
|
|
3
|
+
import { createId } from "../utils/ids.js";
|
|
4
|
+
import { dispatchPlan } from "./dispatch.js";
|
|
5
|
+
import { createInvocation } from "./invocation.js";
|
|
6
|
+
import { openExecution, validateRunOptions } from "./options.js";
|
|
7
|
+
import { applyResume } from "./resume.js";
|
|
8
|
+
/** All mutable progress belongs to this invocation and is discarded when it settles. */
|
|
9
|
+
export async function execute(agent, options) {
|
|
10
|
+
validateRunOptions(options);
|
|
11
|
+
const opened = openExecution(agent, options);
|
|
12
|
+
let { state } = opened;
|
|
13
|
+
const { input, definitions } = opened;
|
|
14
|
+
if (state.status === "paused" && !(input.kind === "continue" && options.signal?.aborted)) {
|
|
15
|
+
state = applyResume(state, input, definitions);
|
|
16
|
+
if (input.kind === "approve" || input.kind === "respond")
|
|
17
|
+
state = {
|
|
18
|
+
...state,
|
|
19
|
+
transcript: [
|
|
20
|
+
...state.transcript,
|
|
21
|
+
{ kind: "input", turnId: state.plan.turnId, event: input },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
else if (input.kind === "approve" || input.kind === "respond" || input.kind === "settle") {
|
|
26
|
+
throw new HarnessError("execution.invalid-input", "There is no matching paused operation for this input");
|
|
27
|
+
}
|
|
28
|
+
const invocation = createInvocation({ agent, options, state, definitions });
|
|
29
|
+
const { signal, observe, record } = invocation;
|
|
30
|
+
const finish = (result) => finishInvocation(invocation, result);
|
|
31
|
+
try {
|
|
32
|
+
signal.throwIfAborted();
|
|
33
|
+
let turnId;
|
|
34
|
+
let stepNumber;
|
|
35
|
+
let arrivals = [];
|
|
36
|
+
let toolResults = [];
|
|
37
|
+
if (invocation.state.plan) {
|
|
38
|
+
turnId = invocation.state.plan.turnId;
|
|
39
|
+
stepNumber = invocation.state.plan.stepNumber + 1;
|
|
40
|
+
invocation.state = { ...invocation.state, status: "active" };
|
|
41
|
+
await record();
|
|
42
|
+
if ((await dispatchPlan(invocation)) === "paused")
|
|
43
|
+
return finish({ status: "paused" });
|
|
44
|
+
toolResults = lastResults(invocation.state);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
turnId = createId("turn");
|
|
48
|
+
stepNumber = 1;
|
|
49
|
+
if (input.kind !== "continue" && input.kind !== "settle")
|
|
50
|
+
arrivals = [input];
|
|
51
|
+
invocation.state = {
|
|
52
|
+
...invocation.state,
|
|
53
|
+
status: "active",
|
|
54
|
+
turnCount: invocation.state.turnCount + 1,
|
|
55
|
+
transcript: [
|
|
56
|
+
...invocation.state.transcript,
|
|
57
|
+
...arrivals.map((event) => ({ kind: "input", turnId, event })),
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
await record();
|
|
61
|
+
}
|
|
62
|
+
for (;; stepNumber++) {
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
const stepId = createId("step");
|
|
65
|
+
const snapshot = {
|
|
66
|
+
id: invocation.state.executionId,
|
|
67
|
+
status: "running",
|
|
68
|
+
revision: invocation.state.revision,
|
|
69
|
+
turnCount: invocation.state.turnCount,
|
|
70
|
+
transcript: invocation.state.transcript,
|
|
71
|
+
};
|
|
72
|
+
const result = await runStep({
|
|
73
|
+
agent: {
|
|
74
|
+
middleware: agent.middleware,
|
|
75
|
+
invoke: options.onModelCall,
|
|
76
|
+
registry: agent.registry,
|
|
77
|
+
},
|
|
78
|
+
state: snapshot,
|
|
79
|
+
executionId: invocation.state.executionId,
|
|
80
|
+
turnId,
|
|
81
|
+
stepId,
|
|
82
|
+
turnNumber: invocation.state.turnCount,
|
|
83
|
+
stepNumber,
|
|
84
|
+
arrivals,
|
|
85
|
+
toolResults,
|
|
86
|
+
signal,
|
|
87
|
+
info: options.info,
|
|
88
|
+
observe,
|
|
89
|
+
output: agent.output,
|
|
90
|
+
});
|
|
91
|
+
signal.throwIfAborted();
|
|
92
|
+
if (result.candidate) {
|
|
93
|
+
invocation.state = {
|
|
94
|
+
...invocation.state,
|
|
95
|
+
transcript: [
|
|
96
|
+
...invocation.state.transcript,
|
|
97
|
+
{ kind: "candidate", turnId, stepId, candidate: result.candidate },
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
observe({
|
|
101
|
+
type: "model.completed",
|
|
102
|
+
turnId,
|
|
103
|
+
stepId,
|
|
104
|
+
...(result.modelInvocationId ? { invocationId: result.modelInvocationId } : {}),
|
|
105
|
+
...(result.requestedModelId === undefined
|
|
106
|
+
? {}
|
|
107
|
+
: { requestedModelId: result.requestedModelId }),
|
|
108
|
+
attributes: result.candidate,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (result.output.kind === "tripwire") {
|
|
112
|
+
observe({
|
|
113
|
+
type: "tripwire",
|
|
114
|
+
turnId,
|
|
115
|
+
stepId,
|
|
116
|
+
code: result.output.tripwire.code,
|
|
117
|
+
scope: result.output.tripwire.scope ?? "step",
|
|
118
|
+
attributes: { message: result.output.tripwire.message },
|
|
119
|
+
});
|
|
120
|
+
return finish({ status: "failed", error: result.output.tripwire });
|
|
121
|
+
}
|
|
122
|
+
if (result.output.kind === "final") {
|
|
123
|
+
invocation.state = {
|
|
124
|
+
...invocation.state,
|
|
125
|
+
transcript: [
|
|
126
|
+
...invocation.state.transcript,
|
|
127
|
+
{ kind: "final", turnId, stepId, output: result.output.output },
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
observe({
|
|
131
|
+
type: "turn.completed",
|
|
132
|
+
turnId,
|
|
133
|
+
stepId,
|
|
134
|
+
attributes: { output: result.output.output },
|
|
135
|
+
});
|
|
136
|
+
return finish({ status: "completed", output: result.output.output });
|
|
137
|
+
}
|
|
138
|
+
const plan = result.output.plan;
|
|
139
|
+
const calls = plan.executable.map((entry) => {
|
|
140
|
+
const reference = agent.registry.reference(entry.definition);
|
|
141
|
+
definitions.set(entry.invocationId, agent.registry.restore(reference));
|
|
142
|
+
return {
|
|
143
|
+
callId: entry.call.callId,
|
|
144
|
+
toolName: entry.call.toolName,
|
|
145
|
+
args: entry.call.args,
|
|
146
|
+
rawArgs: entry.rawArgs,
|
|
147
|
+
invocationId: entry.invocationId,
|
|
148
|
+
reference,
|
|
149
|
+
status: entry.interaction ? "interaction" : "pending",
|
|
150
|
+
...(entry.interaction
|
|
151
|
+
? { interaction: entry.interaction, interactionPhase: "before" }
|
|
152
|
+
: {}),
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
invocation.state = {
|
|
156
|
+
...invocation.state,
|
|
157
|
+
plan: {
|
|
158
|
+
turnId,
|
|
159
|
+
stepId,
|
|
160
|
+
stepNumber,
|
|
161
|
+
order: plan.order,
|
|
162
|
+
immediate: plan.immediateResults,
|
|
163
|
+
calls,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
observe({
|
|
167
|
+
type: "tool.sealed",
|
|
168
|
+
turnId,
|
|
169
|
+
stepId,
|
|
170
|
+
attributes: {
|
|
171
|
+
executable: plan.executable.map((entry) => ({
|
|
172
|
+
callId: entry.call.callId,
|
|
173
|
+
toolName: entry.call.toolName,
|
|
174
|
+
args: entry.call.args,
|
|
175
|
+
rawArgs: entry.rawArgs,
|
|
176
|
+
invocationId: entry.invocationId,
|
|
177
|
+
owner: entry.owner,
|
|
178
|
+
...(entry.interaction ? { interaction: entry.interaction } : {}),
|
|
179
|
+
})),
|
|
180
|
+
immediate: plan.immediateResults,
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
await record();
|
|
184
|
+
if ((await dispatchPlan(invocation)) === "paused")
|
|
185
|
+
return finish({ status: "paused" });
|
|
186
|
+
toolResults = lastResults(invocation.state);
|
|
187
|
+
arrivals = [];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (isHarnessError(error) && error.code === "execution.record-failed")
|
|
192
|
+
throw error;
|
|
193
|
+
if (signal.aborted)
|
|
194
|
+
return finish({ status: "cancelled" });
|
|
195
|
+
return finish({
|
|
196
|
+
status: "failed",
|
|
197
|
+
error: {
|
|
198
|
+
code: isHarnessError(error) ? error.code : "execution.failed",
|
|
199
|
+
message: error instanceof Error ? error.message : String(error),
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function finishInvocation(invocation, result) {
|
|
205
|
+
if (result.status === "cancelled" && invocation.state.plan) {
|
|
206
|
+
const plan = invocation.state.plan;
|
|
207
|
+
const results = [
|
|
208
|
+
...plan.immediate,
|
|
209
|
+
...plan.calls.map((call) => call.result ?? {
|
|
210
|
+
kind: "failed",
|
|
211
|
+
callId: call.callId,
|
|
212
|
+
toolName: call.toolName,
|
|
213
|
+
code: "tool.cancelled",
|
|
214
|
+
message: "Invocation cancelled; external effects are not automatically retried",
|
|
215
|
+
}),
|
|
216
|
+
];
|
|
217
|
+
const { plan: _, ...rest } = invocation.state;
|
|
218
|
+
invocation.state = {
|
|
219
|
+
...rest,
|
|
220
|
+
cancelledCalls: [
|
|
221
|
+
...(rest.cancelledCalls ?? []),
|
|
222
|
+
...plan.calls.filter((call) => !call.result),
|
|
223
|
+
],
|
|
224
|
+
transcript: [
|
|
225
|
+
...invocation.state.transcript,
|
|
226
|
+
{
|
|
227
|
+
kind: "tool-results",
|
|
228
|
+
turnId: plan.turnId,
|
|
229
|
+
stepId: plan.stepId,
|
|
230
|
+
results: plan.order.map((item) => results.find((result) => result.callId === item.callId && result.toolName === item.toolName)),
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
invocation.state = { ...invocation.state, status: result.status };
|
|
236
|
+
await invocation.record();
|
|
237
|
+
invocation.emit({
|
|
238
|
+
type: `execution.${result.status}`,
|
|
239
|
+
...(result.status === "completed"
|
|
240
|
+
? { attributes: { output: result.output } }
|
|
241
|
+
: result.status === "failed"
|
|
242
|
+
? { attributes: { error: result.error } }
|
|
243
|
+
: {}),
|
|
244
|
+
});
|
|
245
|
+
if (result.status === "paused")
|
|
246
|
+
return {
|
|
247
|
+
status: "paused",
|
|
248
|
+
state: invocation.state,
|
|
249
|
+
pending: invocation.state.plan.calls.filter((call) => call.status === "interaction" || call.status === "deferred"),
|
|
250
|
+
};
|
|
251
|
+
return { ...result, state: invocation.state };
|
|
252
|
+
}
|
|
253
|
+
function lastResults(state) {
|
|
254
|
+
const entry = state.transcript[state.transcript.length - 1];
|
|
255
|
+
return entry?.kind === "tool-results" ? entry.results : [];
|
|
256
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BuiltAgent } from "../types/agent.js";
|
|
2
|
+
import type { ExecutionInput, ExecutionState } from "../types/execution.js";
|
|
3
|
+
import type { InputEvent } from "../types/transcript.js";
|
|
4
|
+
export declare function createExecutionState<Info, Output>(agent: BuiltAgent<Info, Output>): ExecutionState;
|
|
5
|
+
export declare function validateExecutionState(value: unknown): ExecutionState;
|
|
6
|
+
export declare function normalizeInput(value: ExecutionInput): InputEvent | Extract<ExecutionInput, {
|
|
7
|
+
kind: "continue" | "settle";
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { HarnessError } from "../errors.js";
|
|
3
|
+
import { assertJson, copyJson } from "../utils/immutable.js";
|
|
4
|
+
import { definitionFor } from "../definition/agent-definition.js";
|
|
5
|
+
import { initializeExecutionState } from "./initial-state.js";
|
|
6
|
+
import { validateTranscript } from "../execution/transcript.js";
|
|
7
|
+
const id = z.string().min(1);
|
|
8
|
+
const json = z.unknown().refine((value) => {
|
|
9
|
+
try {
|
|
10
|
+
assertJson(value);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}, "Expected JSON data");
|
|
17
|
+
const interaction = z
|
|
18
|
+
.object({
|
|
19
|
+
id,
|
|
20
|
+
kind: z.enum(["approval", "response"]),
|
|
21
|
+
prompt: z.string(),
|
|
22
|
+
metadata: json.optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
const outcome = z.discriminatedUnion("kind", [
|
|
26
|
+
z.object({ kind: z.literal("completed"), output: json }).strict(),
|
|
27
|
+
z.object({ kind: z.literal("denied"), reason: z.string() }).strict(),
|
|
28
|
+
z.object({ kind: z.literal("failed"), code: id, message: z.string() }).strict(),
|
|
29
|
+
]);
|
|
30
|
+
const reference = z
|
|
31
|
+
.object({
|
|
32
|
+
capabilityId: id,
|
|
33
|
+
toolName: id,
|
|
34
|
+
descriptor: z
|
|
35
|
+
.object({
|
|
36
|
+
name: id,
|
|
37
|
+
description: z.string().optional(),
|
|
38
|
+
inputSchema: z.record(z.string(), json),
|
|
39
|
+
outputSchema: z.record(z.string(), json).optional(),
|
|
40
|
+
})
|
|
41
|
+
.strict(),
|
|
42
|
+
})
|
|
43
|
+
.strict();
|
|
44
|
+
const call = z
|
|
45
|
+
.object({
|
|
46
|
+
callId: id,
|
|
47
|
+
invocationId: id,
|
|
48
|
+
toolName: id,
|
|
49
|
+
args: json,
|
|
50
|
+
rawArgs: json,
|
|
51
|
+
reference,
|
|
52
|
+
status: z.enum(["pending", "active", "settled", "interaction", "deferred"]),
|
|
53
|
+
result: json.optional(),
|
|
54
|
+
interaction: interaction.optional(),
|
|
55
|
+
interactionPhase: z.enum(["before", "execute"]).optional(),
|
|
56
|
+
resume: z
|
|
57
|
+
.discriminatedUnion("kind", [
|
|
58
|
+
z
|
|
59
|
+
.object({
|
|
60
|
+
kind: z.literal("approval"),
|
|
61
|
+
interactionId: id,
|
|
62
|
+
approved: z.boolean(),
|
|
63
|
+
token: json.optional(),
|
|
64
|
+
})
|
|
65
|
+
.strict(),
|
|
66
|
+
z
|
|
67
|
+
.object({
|
|
68
|
+
kind: z.literal("response"),
|
|
69
|
+
interactionId: id,
|
|
70
|
+
value: json,
|
|
71
|
+
token: json.optional(),
|
|
72
|
+
})
|
|
73
|
+
.strict(),
|
|
74
|
+
])
|
|
75
|
+
.optional(),
|
|
76
|
+
token: json.optional(),
|
|
77
|
+
})
|
|
78
|
+
.strict();
|
|
79
|
+
const schema = z
|
|
80
|
+
.object({
|
|
81
|
+
version: z.literal(1),
|
|
82
|
+
agentId: id,
|
|
83
|
+
executionId: id,
|
|
84
|
+
outputContract: z.record(z.string(), json).optional(),
|
|
85
|
+
revision: z.number().int().nonnegative(),
|
|
86
|
+
turnCount: z.number().int().nonnegative(),
|
|
87
|
+
transcript: z.array(json),
|
|
88
|
+
status: z.enum(["ready", "active", "completed", "paused", "cancelled", "failed"]),
|
|
89
|
+
cancelledCalls: z.array(call).optional(),
|
|
90
|
+
plan: z
|
|
91
|
+
.object({
|
|
92
|
+
turnId: id,
|
|
93
|
+
stepId: id,
|
|
94
|
+
stepNumber: z.number().int().positive(),
|
|
95
|
+
order: z.array(z.object({ callId: id, toolName: id }).strict()),
|
|
96
|
+
immediate: z.array(json),
|
|
97
|
+
calls: z.array(call),
|
|
98
|
+
})
|
|
99
|
+
.strict()
|
|
100
|
+
.optional(),
|
|
101
|
+
})
|
|
102
|
+
.strict();
|
|
103
|
+
export function createExecutionState(agent) {
|
|
104
|
+
return initializeExecutionState(definitionFor(agent));
|
|
105
|
+
}
|
|
106
|
+
function omitLegacyExecutionVersion(value) {
|
|
107
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
108
|
+
return value;
|
|
109
|
+
if (!("executionVersion" in value))
|
|
110
|
+
return value;
|
|
111
|
+
const { executionVersion: _ignored, ...rest } = value;
|
|
112
|
+
return rest;
|
|
113
|
+
}
|
|
114
|
+
export function validateExecutionState(value) {
|
|
115
|
+
try {
|
|
116
|
+
assertJson(value);
|
|
117
|
+
const parsed = schema.parse(omitLegacyExecutionVersion(value));
|
|
118
|
+
validateTranscript(parsed.transcript);
|
|
119
|
+
const plan = parsed.plan;
|
|
120
|
+
if (parsed.status === "paused" && !plan)
|
|
121
|
+
throw new HarnessError("execution.invalid-state", "Paused execution requires a pending plan");
|
|
122
|
+
if (plan) {
|
|
123
|
+
const ids = new Set();
|
|
124
|
+
const invocations = new Set();
|
|
125
|
+
const interactions = new Set();
|
|
126
|
+
const ordered = new Set(plan.order.map((item) => item.callId));
|
|
127
|
+
if (ordered.size !== plan.order.length ||
|
|
128
|
+
plan.order.length !== plan.calls.length + plan.immediate.length)
|
|
129
|
+
throw new HarnessError("execution.invalid-state", "Plan order must identify every result exactly once");
|
|
130
|
+
for (const result of plan.immediate) {
|
|
131
|
+
if (ids.has(result.callId) ||
|
|
132
|
+
!plan.order.some((item) => item.callId === result.callId && item.toolName === result.toolName))
|
|
133
|
+
throw new HarnessError("execution.invalid-state", "Invalid immediate result identity");
|
|
134
|
+
ids.add(result.callId);
|
|
135
|
+
}
|
|
136
|
+
for (const item of plan.calls) {
|
|
137
|
+
if (ids.has(item.callId) || invocations.has(item.invocationId))
|
|
138
|
+
throw new HarnessError("execution.invalid-state", "Duplicate pending call identity");
|
|
139
|
+
ids.add(item.callId);
|
|
140
|
+
invocations.add(item.invocationId);
|
|
141
|
+
if (!plan.order.some((order) => order.callId === item.callId && order.toolName === item.toolName))
|
|
142
|
+
throw new HarnessError("execution.invalid-state", "Pending call missing from plan order");
|
|
143
|
+
if (item.toolName !== item.reference.descriptor.name)
|
|
144
|
+
throw new HarnessError("execution.invalid-state", "Pending tool name differs from its contract");
|
|
145
|
+
if (item.toolName !== item.reference.toolName)
|
|
146
|
+
throw new HarnessError("execution.invalid-state", "Pending tool name differs from its reference");
|
|
147
|
+
if (item.interaction) {
|
|
148
|
+
if (interactions.has(item.interaction.id))
|
|
149
|
+
throw new HarnessError("execution.invalid-state", "Duplicate interaction identity");
|
|
150
|
+
interactions.add(item.interaction.id);
|
|
151
|
+
}
|
|
152
|
+
if (item.status !== "settled" && item.result)
|
|
153
|
+
throw new HarnessError("execution.invalid-state", "Unsettled call cannot contain a result");
|
|
154
|
+
if (item.status === "settled" && !item.result)
|
|
155
|
+
throw new HarnessError("execution.invalid-state", "Settled call requires a result");
|
|
156
|
+
if (item.status === "interaction" && (!item.interaction || !item.interactionPhase))
|
|
157
|
+
throw new HarnessError("execution.invalid-state", "Missing interaction state");
|
|
158
|
+
if (item.result &&
|
|
159
|
+
(item.result.callId !== item.callId || item.result.toolName !== item.toolName))
|
|
160
|
+
throw new HarnessError("execution.invalid-state", "Result identity mismatch");
|
|
161
|
+
}
|
|
162
|
+
validateTranscript([
|
|
163
|
+
{
|
|
164
|
+
kind: "tool-results",
|
|
165
|
+
turnId: plan.turnId,
|
|
166
|
+
stepId: plan.stepId,
|
|
167
|
+
results: [
|
|
168
|
+
...plan.immediate,
|
|
169
|
+
...plan.calls.flatMap((item) => (item.result ? [item.result] : [])),
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
]);
|
|
173
|
+
if (parsed.status === "paused" &&
|
|
174
|
+
(plan.calls.some((item) => item.status === "active") ||
|
|
175
|
+
!plan.calls.some((item) => item.status === "interaction" || item.status === "deferred")))
|
|
176
|
+
throw new HarnessError("execution.invalid-state", "Invalid paused plan");
|
|
177
|
+
}
|
|
178
|
+
return copyJson(parsed);
|
|
179
|
+
}
|
|
180
|
+
catch (cause) {
|
|
181
|
+
throw new HarnessError("execution.invalid-state", `Invalid execution state: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export function normalizeInput(value) {
|
|
185
|
+
try {
|
|
186
|
+
if (typeof value === "string")
|
|
187
|
+
return { kind: "user-message", text: value };
|
|
188
|
+
assertJson(value);
|
|
189
|
+
if ("kind" in value && value.kind === "continue")
|
|
190
|
+
return z
|
|
191
|
+
.object({ kind: z.literal("continue") })
|
|
192
|
+
.strict()
|
|
193
|
+
.parse(value);
|
|
194
|
+
if ("kind" in value && value.kind === "settle")
|
|
195
|
+
return z
|
|
196
|
+
.object({ kind: z.literal("settle"), invocationId: id, outcome })
|
|
197
|
+
.strict()
|
|
198
|
+
.parse(value);
|
|
199
|
+
const event = "kind" in value ? value : { kind: "user-message", ...value };
|
|
200
|
+
const validated = validateTranscript([
|
|
201
|
+
{ kind: "input", turnId: "validation", event: event },
|
|
202
|
+
]);
|
|
203
|
+
return validated[0].event;
|
|
204
|
+
}
|
|
205
|
+
catch (cause) {
|
|
206
|
+
throw new HarnessError("execution.invalid-input", `Invalid input: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ModelCandidate, ModelOutputBlock, ModelToolCall } from "
|
|
2
|
-
import type { JsonObject } from "
|
|
1
|
+
import type { ModelCandidate, ModelOutputBlock, ModelToolCall } from "../../types/model.js";
|
|
2
|
+
import type { JsonObject } from "../../types/shared.js";
|
|
3
3
|
export interface CanonicalCall {
|
|
4
4
|
readonly id: string;
|
|
5
5
|
readonly name: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createId } from "
|
|
2
|
-
import { copyJson } from "
|
|
1
|
+
import { createId } from "../../utils/ids.js";
|
|
2
|
+
import { copyJson } from "../../utils/immutable.js";
|
|
3
3
|
export function canonicalizeCalls(calls) {
|
|
4
4
|
const idCounts = new Map();
|
|
5
5
|
for (const call of calls) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { BoundMiddleware
|
|
2
|
-
import type {
|
|
1
|
+
import type { BoundMiddleware } from "../../definition/bound.js";
|
|
2
|
+
import type { StepResponse } from "../../types/middleware.js";
|
|
3
|
+
import type { ObserveEmit } from "../observe.js";
|
|
3
4
|
import { StepContext } from "./step-context.js";
|
|
4
5
|
export declare function runMiddleware(middleware: readonly BoundMiddleware[], context: StepContext, terminal: () => Promise<StepResponse>, observe: ObserveEmit): Promise<StepResponse>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HarnessError, isHarnessError } from "
|
|
1
|
+
import { HarnessError, isHarnessError } from "../../errors.js";
|
|
2
2
|
import { isBrandedResponse, StepContext } from "./step-context.js";
|
|
3
3
|
export async function runMiddleware(middleware, context, terminal, observe) {
|
|
4
4
|
const dispatch = async (index) => {
|
|
@@ -55,7 +55,7 @@ export async function runMiddleware(middleware, context, terminal, observe) {
|
|
|
55
55
|
return context.tripwire({
|
|
56
56
|
code: "middleware.next-called-twice",
|
|
57
57
|
message: message(handlerError),
|
|
58
|
-
scope: "
|
|
58
|
+
scope: "execution",
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
return context.tripwire({
|
|
@@ -70,8 +70,8 @@ export async function runMiddleware(middleware, context, terminal, observe) {
|
|
|
70
70
|
message: `Middleware '${item.id}' must return the StepResponse from next()`,
|
|
71
71
|
// Calling next() completed the step successfully; forgetting to
|
|
72
72
|
// return that response is isolated to this step. A non-response
|
|
73
|
-
// replacement remains a
|
|
74
|
-
scope: result === undefined ? "step" : "
|
|
73
|
+
// replacement remains a execution-level middleware-contract breach.
|
|
74
|
+
scope: result === undefined ? "step" : "execution",
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
return inner;
|
|
@@ -80,7 +80,7 @@ export async function runMiddleware(middleware, context, terminal, observe) {
|
|
|
80
80
|
return context.tripwire({
|
|
81
81
|
code: "middleware.invalid-response",
|
|
82
82
|
message: `Middleware '${item.id}' must return a branded StepResponse`,
|
|
83
|
-
scope: "
|
|
83
|
+
scope: "execution",
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
return result;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { ContextMutationOptions, ContextSnapshot } from "
|
|
2
|
-
import type { ContextItem
|
|
3
|
-
/** Per-step
|
|
1
|
+
import type { ContextMutationOptions, ContextSnapshot } from "../../types/model.js";
|
|
2
|
+
import type { ContextItem } from "../../types/shared.js";
|
|
3
|
+
/** Per-step model-visible context supplied explicitly by middleware. */
|
|
4
4
|
export declare class ContextDraft {
|
|
5
5
|
#private;
|
|
6
|
-
private readonly hostContext?;
|
|
7
|
-
constructor(hostContext?: JsonObject | undefined);
|
|
8
6
|
set(middlewareId: string, middlewareOrder: number, slot: string, items: readonly ContextItem[], options?: ContextMutationOptions): void;
|
|
9
7
|
snapshot(): ContextSnapshot;
|
|
10
8
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import { copyJson } from "
|
|
1
|
+
import { HarnessError } from "../../errors.js";
|
|
2
|
+
import { copyJson } from "../../utils/immutable.js";
|
|
3
3
|
import { SlotDraft } from "./slot-assembly.js";
|
|
4
4
|
const CONTEXT_TYPE = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
5
|
-
/** Per-step
|
|
5
|
+
/** Per-step model-visible context supplied explicitly by middleware. */
|
|
6
6
|
export class ContextDraft {
|
|
7
|
-
hostContext;
|
|
8
7
|
#slots = new SlotDraft();
|
|
9
|
-
constructor(hostContext) {
|
|
10
|
-
this.hostContext = hostContext;
|
|
11
|
-
}
|
|
12
8
|
set(middlewareId, middlewareOrder, slot, items, options) {
|
|
13
9
|
if (!Array.isArray(items))
|
|
14
10
|
throw new HarnessError("context.invalid-item", "Runtime context items must be an array");
|
|
@@ -27,14 +23,8 @@ export class ContextDraft {
|
|
|
27
23
|
}
|
|
28
24
|
snapshot() {
|
|
29
25
|
const slots = this.#slots.values();
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
: [Object.freeze({ type: "session", value: copyJson(this.hostContext) })];
|
|
33
|
-
const items = Object.freeze([...host, ...slots.flatMap((slot) => slot.value)]);
|
|
34
|
-
const contributors = Object.freeze([
|
|
35
|
-
...(this.hostContext === undefined ? [] : [hostContributor()]),
|
|
36
|
-
...slots.map((slot) => contributor(slot.owner, slot.reason)),
|
|
37
|
-
]);
|
|
26
|
+
const items = Object.freeze(slots.flatMap((slot) => slot.value));
|
|
27
|
+
const contributors = Object.freeze(slots.map((slot) => contributor(slot.owner, slot.reason)));
|
|
38
28
|
return Object.freeze({ items, contributors });
|
|
39
29
|
}
|
|
40
30
|
}
|
|
@@ -48,13 +38,6 @@ function normalizeItem(item) {
|
|
|
48
38
|
value: copyJson(item.value),
|
|
49
39
|
});
|
|
50
40
|
}
|
|
51
|
-
function hostContributor() {
|
|
52
|
-
return Object.freeze({
|
|
53
|
-
middlewareId: "host",
|
|
54
|
-
slot: "session",
|
|
55
|
-
order: 0,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
41
|
function contributor(owner, reason) {
|
|
59
42
|
return Object.freeze({
|
|
60
43
|
middlewareId: owner.middlewareId,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModelConfigurationSnapshot } from "../../types/model.js";
|
|
2
|
+
import type { ToolDescriptor } from "../../types/tool.js";
|
|
3
|
+
import type { BoundToolDefinition } from "../../definition/bound.js";
|
|
4
|
+
/** Executable configuration stays inside the invocation. */
|
|
5
|
+
export interface InternalModelConfigurationSnapshot extends Omit<ModelConfigurationSnapshot, "tools"> {
|
|
6
|
+
readonly tools: readonly BoundToolDefinition[];
|
|
7
|
+
}
|
|
8
|
+
export declare function describeTool(tool: BoundToolDefinition): ToolDescriptor;
|
|
9
|
+
export declare function describeConfiguration(configuration: InternalModelConfigurationSnapshot): ModelConfigurationSnapshot;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { copyJson } from "../../utils/immutable.js";
|
|
2
|
+
export function describeTool(tool) {
|
|
3
|
+
return copyJson({
|
|
4
|
+
name: tool.name,
|
|
5
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
6
|
+
owner: tool.owner,
|
|
7
|
+
inputSchema: { jsonSchema: tool.inputSchema.jsonSchema },
|
|
8
|
+
...(tool.outputSchema === undefined
|
|
9
|
+
? {}
|
|
10
|
+
: { outputSchema: { jsonSchema: tool.outputSchema.jsonSchema } }),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function describeConfiguration(configuration) {
|
|
14
|
+
return copyJson({
|
|
15
|
+
version: configuration.version,
|
|
16
|
+
...(configuration.model === undefined ? {} : { model: configuration.model }),
|
|
17
|
+
instructions: configuration.instructions,
|
|
18
|
+
tools: configuration.tools.map(describeTool),
|
|
19
|
+
toolContracts: configuration.toolContracts,
|
|
20
|
+
contributors: configuration.contributors,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { InternalModelConfigurationSnapshot } from "./describe.js";
|
|
2
|
+
import type { ModelConfigurationMutationOptions, ModelDirective } from "../../types/model.js";
|
|
3
|
+
import type { ToolDefinition } from "../../types/tool.js";
|
|
3
4
|
/** Per-step, middleware-owned model configuration draft. It never persists past the call. */
|
|
4
5
|
export declare class ModelConfigurationDraft {
|
|
5
6
|
#private;
|
|
@@ -9,5 +10,5 @@ export declare class ModelConfigurationDraft {
|
|
|
9
10
|
select(middlewareId: string, middlewareOrder: number, directive: ModelDirective, options?: Omit<ModelConfigurationMutationOptions, "order">): void;
|
|
10
11
|
replace(middlewareId: string, middlewareOrder: number, directive: ModelDirective, options?: Omit<ModelConfigurationMutationOptions, "order">): void;
|
|
11
12
|
clear(options?: Omit<ModelConfigurationMutationOptions, "order">): void;
|
|
12
|
-
snapshot():
|
|
13
|
+
snapshot(): InternalModelConfigurationSnapshot;
|
|
13
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { bindTool } from "
|
|
2
|
-
import { HarnessError, isHarnessError } from "
|
|
1
|
+
import { bindTool } from "../../definition/bind-tool.js";
|
|
2
|
+
import { HarnessError, isHarnessError } from "../../errors.js";
|
|
3
3
|
import { normalizeDirective, sameDirective } from "../model/normalize.js";
|
|
4
|
-
import { copyJson } from "
|
|
4
|
+
import { copyJson } from "../../utils/immutable.js";
|
|
5
5
|
import { checkedReason, slotOwner, SlotDraft } from "./slot-assembly.js";
|
|
6
6
|
/** Per-step, middleware-owned model configuration draft. It never persists past the call. */
|
|
7
7
|
export class ModelConfigurationDraft {
|