@nylorun/harness 0.13.0-beta → 0.15.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 +48 -0
- package/HOST_CONTRACT.md +27 -0
- package/README.md +62 -100
- package/dist/compatibility.d.ts +3 -0
- package/dist/compatibility.js +3 -0
- package/dist/definition/agent-definition.d.ts +11 -3
- package/dist/definition/agent-definition.js +18 -10
- package/dist/definition/compatibility.d.ts +11 -0
- package/dist/definition/compatibility.js +30 -0
- package/dist/definition/registry.d.ts +2 -2
- package/dist/definition/registry.js +7 -6
- package/dist/index.d.ts +4 -18
- package/dist/index.js +1 -6
- package/dist/loop/dispatch.js +327 -0
- package/dist/loop/host-suspension.d.ts +6 -0
- package/dist/loop/host-suspension.js +10 -0
- package/dist/loop/initial-state.d.ts +13 -0
- package/dist/loop/initial-state.js +30 -0
- package/dist/{execution → loop}/invocation.d.ts +5 -2
- package/dist/{execution → loop}/invocation.js +8 -3
- package/dist/{execution → loop}/model/adapters.d.ts +4 -2
- package/dist/{execution → loop}/model/adapters.js +3 -2
- package/dist/{execution → loop}/model/normalize.d.ts +2 -2
- package/dist/{execution → loop}/model/normalize.js +2 -2
- package/dist/{execution → loop}/model/prepared.d.ts +2 -2
- package/dist/{execution → loop}/model/project.d.ts +1 -1
- package/dist/{execution → loop}/model/project.js +1 -1
- package/dist/{execution → loop}/observe.d.ts +2 -2
- package/dist/{execution → loop}/observe.js +1 -1
- package/dist/{execution → loop}/options.d.ts +2 -1
- package/dist/{execution → loop}/options.js +28 -8
- package/dist/{execution → loop}/resume.d.ts +1 -1
- package/dist/{execution → loop}/resume.js +2 -2
- package/dist/{execution → loop}/run.d.ts +1 -1
- package/dist/{execution → loop}/run.js +9 -1
- package/dist/{execution → loop}/state.d.ts +2 -2
- package/dist/{execution → loop}/state.js +13 -3
- package/dist/{execution → loop}/step/canonicalize.d.ts +2 -2
- package/dist/{execution → loop}/step/canonicalize.js +1 -1
- package/dist/{execution → loop}/step/compose.d.ts +2 -2
- package/dist/{execution → loop}/step/compose.js +4 -1
- package/dist/{execution → loop}/step/context-draft.d.ts +2 -2
- package/dist/{execution → loop}/step/context-draft.js +2 -2
- package/dist/{execution → loop}/step/describe.d.ts +3 -3
- package/dist/{execution → loop}/step/describe.js +1 -1
- package/dist/loop/step/dynamics.d.ts +29 -0
- package/dist/loop/step/dynamics.js +147 -0
- package/dist/{execution → loop}/step/model-configuration.d.ts +2 -2
- package/dist/{execution → loop}/step/model-configuration.js +3 -3
- package/dist/{execution → loop}/step/resolve.d.ts +4 -4
- package/dist/{execution → loop}/step/run.d.ts +5 -4
- package/dist/{execution → loop}/step/run.js +87 -2
- package/dist/{execution → loop}/step/runtime.d.ts +12 -4
- package/dist/{execution → loop}/step/seal.d.ts +5 -5
- package/dist/{execution → loop}/step/seal.js +2 -2
- package/dist/{execution → loop}/step/slot-assembly.d.ts +1 -1
- package/dist/{execution → loop}/step/slot-assembly.js +1 -1
- package/dist/{execution → loop}/step/step-context.d.ts +5 -5
- package/dist/{execution → loop}/step/step-context.js +2 -2
- package/dist/{execution → loop}/tool-result.d.ts +2 -2
- package/dist/{execution → loop}/tool-result.js +2 -2
- package/dist/{execution → loop}/transcript.d.ts +1 -1
- package/dist/{execution → loop}/transcript.js +2 -2
- package/dist/loop/waits.d.ts +50 -0
- package/dist/loop/waits.js +11 -0
- package/dist/run/durable.d.ts +71 -0
- package/dist/run/durable.js +164 -0
- package/dist/run/index.d.ts +33 -0
- package/dist/run/index.js +38 -0
- package/dist/types/client.d.ts +88 -0
- package/dist/types/execution.d.ts +22 -5
- package/dist/utils/ids.d.ts +1 -0
- package/dist/utils/ids.js +11 -1
- package/package.json +18 -6
- package/dist/definition/assemble.d.ts +0 -22
- package/dist/definition/assemble.js +0 -43
- package/dist/definition/bind-agent.d.ts +0 -9
- package/dist/definition/bind-agent.js +0 -21
- package/dist/definition/bind-tool.d.ts +0 -4
- package/dist/definition/bind-tool.js +0 -19
- package/dist/definition/bound.d.ts +0 -16
- package/dist/definition/builder.d.ts +0 -30
- package/dist/definition/builder.js +0 -94
- package/dist/definition/declaration.d.ts +0 -3
- package/dist/definition/declaration.js +0 -53
- package/dist/definition/helpers.d.ts +0 -6
- package/dist/definition/helpers.js +0 -4
- package/dist/definition/manifest.d.ts +0 -9
- package/dist/definition/manifest.js +0 -45
- package/dist/definition/output-contract.d.ts +0 -6
- package/dist/definition/output-contract.js +0 -12
- package/dist/definition/schema.d.ts +0 -14
- package/dist/definition/schema.js +0 -205
- package/dist/errors.d.ts +0 -17
- package/dist/errors.js +0 -17
- package/dist/execution/dispatch.js +0 -164
- package/dist/execution/initial-state.d.ts +0 -3
- package/dist/execution/initial-state.js +0 -14
- package/dist/types/agent.d.ts +0 -9
- package/dist/types/agent.js +0 -1
- package/dist/types/manifest.d.ts +0 -22
- package/dist/types/manifest.js +0 -1
- package/dist/types/middleware.d.ts +0 -66
- package/dist/types/middleware.js +0 -1
- package/dist/types/model.d.ts +0 -184
- package/dist/types/model.js +0 -1
- package/dist/types/observe.d.ts +0 -165
- package/dist/types/observe.js +0 -1
- package/dist/types/shared.d.ts +0 -25
- package/dist/types/shared.js +0 -1
- package/dist/types/tool.d.ts +0 -138
- package/dist/types/tool.js +0 -1
- package/dist/types/transcript.d.ts +0 -65
- package/dist/types/transcript.js +0 -1
- package/dist/utils/canonical.d.ts +0 -1
- package/dist/utils/canonical.js +0 -10
- package/dist/utils/immutable.d.ts +0 -6
- package/dist/utils/immutable.js +0 -76
- /package/dist/{execution → loop}/dispatch.d.ts +0 -0
- /package/dist/{execution → loop}/model/prepared.js +0 -0
- /package/dist/{execution → loop}/step/resolve.js +0 -0
- /package/dist/{execution → loop}/step/runtime.js +0 -0
- /package/dist/{definition/bound.js → types/client.js} +0 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { HostSuspension } from "./host-suspension.js";
|
|
2
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
3
|
+
import { isToolError } from "@nylorun/core/define";
|
|
4
|
+
import { createId } from "../utils/ids.js";
|
|
5
|
+
import { copyJson } from "@nylorun/core/define";
|
|
6
|
+
import { createSessionStateBag } from "./initial-state.js";
|
|
7
|
+
import { toolResult } from "./tool-result.js";
|
|
8
|
+
import { isWaitSignal, WaitSignal } from "./waits.js";
|
|
9
|
+
export async function dispatchPlan(invocation) {
|
|
10
|
+
const { signal, definitions, options, observe, record } = invocation;
|
|
11
|
+
let plan = invocation.state.plan;
|
|
12
|
+
if (plan.calls.some((call) => call.status === "interaction" && call.interactionPhase === "before")) {
|
|
13
|
+
for (const call of plan.calls)
|
|
14
|
+
if (call.status === "interaction")
|
|
15
|
+
observe({
|
|
16
|
+
type: "interaction.required",
|
|
17
|
+
turnId: plan.turnId,
|
|
18
|
+
stepId: plan.stepId,
|
|
19
|
+
interactionId: call.interaction.id,
|
|
20
|
+
kind: call.interaction.kind,
|
|
21
|
+
callId: call.callId,
|
|
22
|
+
toolName: call.toolName,
|
|
23
|
+
phase: "interaction",
|
|
24
|
+
attributes: {
|
|
25
|
+
prompt: call.interaction.prompt,
|
|
26
|
+
...(call.interaction.metadata ? { metadata: call.interaction.metadata } : {}),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
return "paused";
|
|
30
|
+
}
|
|
31
|
+
signal.throwIfAborted();
|
|
32
|
+
const pending = plan.calls.filter((call) => call.status === "pending" || call.status === "active");
|
|
33
|
+
const redelivering = plan.calls.some((call) => call.status === "active");
|
|
34
|
+
if (pending.length) {
|
|
35
|
+
invocation.state = {
|
|
36
|
+
...invocation.state,
|
|
37
|
+
plan: {
|
|
38
|
+
...plan,
|
|
39
|
+
calls: plan.calls.map((call) => call.status === "pending" || call.status === "active"
|
|
40
|
+
? { ...call, status: "active" }
|
|
41
|
+
: call),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
await record();
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
const sessionBag = invocation.sessionBag;
|
|
47
|
+
const stateApi = createSessionStateBag(sessionBag);
|
|
48
|
+
const settled = await settleDispatch(pending.map(async (call) => {
|
|
49
|
+
const definition = definitions.get(call.invocationId);
|
|
50
|
+
const eventIds = {
|
|
51
|
+
executionId: invocation.state.executionId,
|
|
52
|
+
turnId: plan.turnId,
|
|
53
|
+
stepId: plan.stepId,
|
|
54
|
+
callId: call.callId,
|
|
55
|
+
invocationId: call.invocationId,
|
|
56
|
+
toolName: call.toolName,
|
|
57
|
+
middlewareId: call.reference.capabilityId,
|
|
58
|
+
slot: definition.owner.slot,
|
|
59
|
+
};
|
|
60
|
+
observe({ type: "tool.started", ...eventIds, attributes: { args: call.args } });
|
|
61
|
+
let outcome;
|
|
62
|
+
try {
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
if (definition.approval && !call.resume) {
|
|
65
|
+
const decision = await definition.approval(call.args);
|
|
66
|
+
const prompt = typeof decision === "string"
|
|
67
|
+
? decision
|
|
68
|
+
: decision === true
|
|
69
|
+
? `Approve ${call.toolName}?`
|
|
70
|
+
: undefined;
|
|
71
|
+
if (prompt) {
|
|
72
|
+
return copyJson({
|
|
73
|
+
...call,
|
|
74
|
+
status: "interaction",
|
|
75
|
+
interaction: {
|
|
76
|
+
kind: "approval",
|
|
77
|
+
prompt,
|
|
78
|
+
id: createId("interaction"),
|
|
79
|
+
},
|
|
80
|
+
interactionPhase: "before",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const stepMemos = new Map();
|
|
85
|
+
if (call.token && typeof call.token === "object" && !Array.isArray(call.token)) {
|
|
86
|
+
const memos = call.token.steps;
|
|
87
|
+
if (memos)
|
|
88
|
+
for (const [key, value] of Object.entries(memos))
|
|
89
|
+
stepMemos.set(key, value);
|
|
90
|
+
}
|
|
91
|
+
const raw = await definition.execute(call.args, {
|
|
92
|
+
executionId: invocation.state.executionId,
|
|
93
|
+
turnId: plan.turnId,
|
|
94
|
+
stepId: plan.stepId,
|
|
95
|
+
callId: call.callId,
|
|
96
|
+
invocationId: call.invocationId,
|
|
97
|
+
signal,
|
|
98
|
+
info: options.info,
|
|
99
|
+
onModelCall: options.onModelCall,
|
|
100
|
+
idempotencyKey: call.callId,
|
|
101
|
+
...(redelivering || call.status === "active" ? { redelivery: true } : {}),
|
|
102
|
+
state: stateApi,
|
|
103
|
+
session: { id: invocation.state.executionId },
|
|
104
|
+
progress(message, data) {
|
|
105
|
+
observe({
|
|
106
|
+
type: "tool.progress",
|
|
107
|
+
...eventIds,
|
|
108
|
+
attributes: {
|
|
109
|
+
message,
|
|
110
|
+
...(data === undefined ? {} : { data }),
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
async ask(prompt, opts) {
|
|
115
|
+
if (call.resume?.kind === "response")
|
|
116
|
+
return call.resume.value;
|
|
117
|
+
throw new WaitSignal({
|
|
118
|
+
kind: "interaction-required",
|
|
119
|
+
interaction: {
|
|
120
|
+
kind: "response",
|
|
121
|
+
prompt,
|
|
122
|
+
...(opts ? { metadata: opts } : {}),
|
|
123
|
+
},
|
|
124
|
+
wait: { kind: "ask", waitId: createId("wait") },
|
|
125
|
+
token: { steps: Object.fromEntries(stepMemos) },
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
async approve(prompt) {
|
|
129
|
+
if (call.resume?.kind === "approval")
|
|
130
|
+
return Boolean(call.resume.approved);
|
|
131
|
+
throw new WaitSignal({
|
|
132
|
+
kind: "interaction-required",
|
|
133
|
+
interaction: { kind: "approval", prompt },
|
|
134
|
+
wait: { kind: "approve", waitId: createId("wait") },
|
|
135
|
+
token: { steps: Object.fromEntries(stepMemos) },
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
async sleep(duration) {
|
|
139
|
+
throw new WaitSignal({
|
|
140
|
+
kind: "deferred",
|
|
141
|
+
wait: { kind: "sleep", waitId: createId("wait") },
|
|
142
|
+
token: { steps: Object.fromEntries(stepMemos), duration },
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
async waitFor(event, opts) {
|
|
146
|
+
throw new WaitSignal({
|
|
147
|
+
kind: "deferred",
|
|
148
|
+
wait: { kind: "waitFor", waitId: createId("wait"), name: event },
|
|
149
|
+
token: { steps: Object.fromEntries(stepMemos), event, ...(opts ?? {}) },
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
async step(name, fn) {
|
|
153
|
+
if (stepMemos.has(name))
|
|
154
|
+
return stepMemos.get(name);
|
|
155
|
+
const value = await fn();
|
|
156
|
+
stepMemos.set(name, value);
|
|
157
|
+
return value;
|
|
158
|
+
},
|
|
159
|
+
...(call.resume ? { resume: call.resume } : {}),
|
|
160
|
+
});
|
|
161
|
+
outcome = normalizeOutcome(raw);
|
|
162
|
+
if (!outcome || typeof outcome !== "object")
|
|
163
|
+
throw new HarnessError("tool.invalid-tool-result", "Tool returned an invalid outcome");
|
|
164
|
+
if (outcome.kind === "interaction-required") {
|
|
165
|
+
if (!["approval", "response"].includes(outcome.interaction.kind) ||
|
|
166
|
+
typeof outcome.interaction.prompt !== "string")
|
|
167
|
+
throw new HarnessError("tool.invalid-tool-result", "Tool returned an invalid interaction");
|
|
168
|
+
return copyJson({
|
|
169
|
+
...call,
|
|
170
|
+
status: "interaction",
|
|
171
|
+
interaction: {
|
|
172
|
+
...outcome.interaction,
|
|
173
|
+
id: outcome.interaction.id ?? createId("interaction"),
|
|
174
|
+
},
|
|
175
|
+
interactionPhase: "execute",
|
|
176
|
+
...(outcome.token === undefined ? {} : { token: outcome.token }),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (outcome.kind === "deferred") {
|
|
180
|
+
observe({
|
|
181
|
+
type: "tool.deferred",
|
|
182
|
+
...eventIds,
|
|
183
|
+
attributes: outcome.token === undefined ? {} : { token: outcome.token },
|
|
184
|
+
});
|
|
185
|
+
return copyJson({
|
|
186
|
+
...call,
|
|
187
|
+
status: "deferred",
|
|
188
|
+
...(outcome.token === undefined ? {} : { token: outcome.token }),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const result = toolResult(call, definition, outcome);
|
|
192
|
+
observe({
|
|
193
|
+
type: "tool.completed",
|
|
194
|
+
...eventIds,
|
|
195
|
+
outcome: result.kind,
|
|
196
|
+
attributes: result,
|
|
197
|
+
});
|
|
198
|
+
return copyJson({ ...call, status: "settled", result });
|
|
199
|
+
}
|
|
200
|
+
catch (cause) {
|
|
201
|
+
if (cause instanceof HostSuspension)
|
|
202
|
+
throw cause;
|
|
203
|
+
if (isWaitSignal(cause)) {
|
|
204
|
+
if (cause.outcome.kind === "interaction-required") {
|
|
205
|
+
return copyJson({
|
|
206
|
+
...call,
|
|
207
|
+
status: "interaction",
|
|
208
|
+
interaction: {
|
|
209
|
+
...cause.outcome.interaction,
|
|
210
|
+
id: createId("interaction"),
|
|
211
|
+
},
|
|
212
|
+
interactionPhase: "execute",
|
|
213
|
+
wait: cause.outcome.wait,
|
|
214
|
+
...(cause.outcome.token === undefined ? {} : { token: cause.outcome.token }),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
observe({
|
|
218
|
+
type: "tool.deferred",
|
|
219
|
+
...eventIds,
|
|
220
|
+
attributes: cause.outcome.token === undefined ? {} : { token: cause.outcome.token },
|
|
221
|
+
});
|
|
222
|
+
return copyJson({
|
|
223
|
+
...call,
|
|
224
|
+
status: "deferred",
|
|
225
|
+
wait: cause.outcome.wait,
|
|
226
|
+
...(cause.outcome.token === undefined ? {} : { token: cause.outcome.token }),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (isToolError(cause)) {
|
|
230
|
+
const result = {
|
|
231
|
+
callId: call.callId,
|
|
232
|
+
toolName: call.toolName,
|
|
233
|
+
kind: "failed",
|
|
234
|
+
code: cause.code,
|
|
235
|
+
message: cause.message,
|
|
236
|
+
};
|
|
237
|
+
observe({
|
|
238
|
+
type: "tool.completed",
|
|
239
|
+
...eventIds,
|
|
240
|
+
outcome: result.kind,
|
|
241
|
+
attributes: result,
|
|
242
|
+
});
|
|
243
|
+
return copyJson({ ...call, status: "settled", result });
|
|
244
|
+
}
|
|
245
|
+
const result = {
|
|
246
|
+
callId: call.callId,
|
|
247
|
+
toolName: call.toolName,
|
|
248
|
+
kind: "failed",
|
|
249
|
+
code: signal.aborted
|
|
250
|
+
? "tool.cancelled"
|
|
251
|
+
: isHarnessError(cause)
|
|
252
|
+
? cause.code
|
|
253
|
+
: "tool.execution-failed",
|
|
254
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
255
|
+
};
|
|
256
|
+
observe({
|
|
257
|
+
type: "tool.completed",
|
|
258
|
+
...eventIds,
|
|
259
|
+
outcome: result.kind,
|
|
260
|
+
attributes: result,
|
|
261
|
+
});
|
|
262
|
+
return copyJson({ ...call, status: "settled", result });
|
|
263
|
+
}
|
|
264
|
+
}));
|
|
265
|
+
const byId = new Map(settled.map((call) => [call.invocationId, call]));
|
|
266
|
+
plan = { ...plan, calls: plan.calls.map((call) => byId.get(call.invocationId) ?? call) };
|
|
267
|
+
invocation.state = {
|
|
268
|
+
...invocation.state,
|
|
269
|
+
plan,
|
|
270
|
+
};
|
|
271
|
+
await record();
|
|
272
|
+
}
|
|
273
|
+
signal.throwIfAborted();
|
|
274
|
+
if (plan.calls.some((call) => call.status === "interaction" || call.status === "deferred")) {
|
|
275
|
+
for (const call of plan.calls)
|
|
276
|
+
if (call.status === "interaction")
|
|
277
|
+
observe({
|
|
278
|
+
type: "interaction.required",
|
|
279
|
+
turnId: plan.turnId,
|
|
280
|
+
stepId: plan.stepId,
|
|
281
|
+
interactionId: call.interaction.id,
|
|
282
|
+
kind: call.interaction.kind,
|
|
283
|
+
callId: call.callId,
|
|
284
|
+
toolName: call.toolName,
|
|
285
|
+
phase: call.interactionPhase === "before" ? "interaction" : "execute",
|
|
286
|
+
attributes: {
|
|
287
|
+
prompt: call.interaction.prompt,
|
|
288
|
+
...(call.interaction.metadata ? { metadata: call.interaction.metadata } : {}),
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
return "paused";
|
|
292
|
+
}
|
|
293
|
+
const results = [
|
|
294
|
+
...plan.immediate,
|
|
295
|
+
...plan.calls.flatMap((call) => (call.result ? [call.result] : [])),
|
|
296
|
+
];
|
|
297
|
+
const ordered = plan.order.map((item) => results.find((result) => result.callId === item.callId && result.toolName === item.toolName));
|
|
298
|
+
const { plan: _, ...rest } = invocation.state;
|
|
299
|
+
invocation.state = {
|
|
300
|
+
...rest,
|
|
301
|
+
transcript: [
|
|
302
|
+
...invocation.state.transcript,
|
|
303
|
+
{ kind: "tool-results", turnId: plan.turnId, stepId: plan.stepId, results: ordered },
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
await record();
|
|
307
|
+
}
|
|
308
|
+
function normalizeOutcome(raw) {
|
|
309
|
+
if (raw && typeof raw === "object" && "kind" in raw) {
|
|
310
|
+
const kind = raw.kind;
|
|
311
|
+
if (kind === "completed" ||
|
|
312
|
+
kind === "denied" ||
|
|
313
|
+
kind === "failed" ||
|
|
314
|
+
kind === "interaction-required" ||
|
|
315
|
+
kind === "deferred")
|
|
316
|
+
return raw;
|
|
317
|
+
}
|
|
318
|
+
return { kind: "completed", output: raw };
|
|
319
|
+
}
|
|
320
|
+
// Persist all parallel action intents before handing control back to a durable host.
|
|
321
|
+
async function settleDispatch(promises) {
|
|
322
|
+
const results = await Promise.allSettled(promises);
|
|
323
|
+
const failure = results.find((r) => r.status === "rejected");
|
|
324
|
+
if (failure)
|
|
325
|
+
throw failure.reason;
|
|
326
|
+
return results.map((r) => r.value);
|
|
327
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Control transfer to a durable host, never a model/tool failure. */
|
|
2
|
+
export class HostSuspension extends Error {
|
|
3
|
+
effectId;
|
|
4
|
+
status;
|
|
5
|
+
constructor(effectId, status) {
|
|
6
|
+
super(`Host suspended at ${effectId}`);
|
|
7
|
+
this.effectId = effectId;
|
|
8
|
+
this.status = status;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentDefinition } from "../definition/agent-definition.js";
|
|
2
|
+
import type { ExecutionState } from "../types/execution.js";
|
|
3
|
+
import type { JsonObject, JsonValue } from "@nylorun/core/define";
|
|
4
|
+
export declare function initializeExecutionState(agent: AgentDefinition, options?: {
|
|
5
|
+
readonly executionId?: string;
|
|
6
|
+
readonly state?: JsonObject;
|
|
7
|
+
}): ExecutionState;
|
|
8
|
+
/** Mutable session-memory bag backed by Invocation / ExecutionState.state. */
|
|
9
|
+
export declare function createSessionStateBag(bag: Record<string, JsonValue>): {
|
|
10
|
+
get(key: string): JsonValue | undefined;
|
|
11
|
+
set(key: string, value: JsonValue): void;
|
|
12
|
+
entries(): Readonly<Record<string, JsonValue>>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { copyJson } from "@nylorun/core/define";
|
|
2
|
+
import { createId } from "../utils/ids.js";
|
|
3
|
+
export function initializeExecutionState(agent, options = {}) {
|
|
4
|
+
return copyJson({
|
|
5
|
+
version: 1,
|
|
6
|
+
agentId: agent.id,
|
|
7
|
+
manifestHash: agent.hash,
|
|
8
|
+
executionId: options.executionId ?? createId("execution"),
|
|
9
|
+
revision: 0,
|
|
10
|
+
turnCount: 0,
|
|
11
|
+
transcript: [],
|
|
12
|
+
status: "ready",
|
|
13
|
+
state: options.state ?? {},
|
|
14
|
+
...(agent.output ? { outputContract: agent.output.schema.jsonSchema } : {}),
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Mutable session-memory bag backed by Invocation / ExecutionState.state. */
|
|
18
|
+
export function createSessionStateBag(bag) {
|
|
19
|
+
return {
|
|
20
|
+
get(key) {
|
|
21
|
+
return bag[key];
|
|
22
|
+
},
|
|
23
|
+
set(key, value) {
|
|
24
|
+
bag[key] = value;
|
|
25
|
+
},
|
|
26
|
+
entries() {
|
|
27
|
+
return { ...bag };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AgentDefinition } from "../definition/agent-definition.js";
|
|
2
|
-
import type { BoundToolDefinition } from "
|
|
2
|
+
import type { BoundToolDefinition } from "@nylorun/core/define";
|
|
3
3
|
import type { ExecutionState, RunOptions } from "../types/execution.js";
|
|
4
|
-
import type { ObserveEvent } from "
|
|
4
|
+
import type { ObserveEvent } from "@nylorun/core/define";
|
|
5
|
+
import type { JsonValue } from "@nylorun/core/define";
|
|
5
6
|
import { type ExecutionOutcomeEvent, type ObserveEmit } from "./observe.js";
|
|
6
7
|
export interface Invocation {
|
|
7
8
|
readonly agent: AgentDefinition;
|
|
@@ -9,6 +10,8 @@ export interface Invocation {
|
|
|
9
10
|
readonly signal: AbortSignal;
|
|
10
11
|
readonly definitions: Map<string, BoundToolDefinition>;
|
|
11
12
|
state: ExecutionState;
|
|
13
|
+
/** Mutable durable session memory for this invocation. */
|
|
14
|
+
sessionBag: Record<string, JsonValue>;
|
|
12
15
|
observe: ObserveEmit;
|
|
13
16
|
emit(event: ObserveEvent | ExecutionOutcomeEvent): void;
|
|
14
17
|
record(): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import { copyJson } from "
|
|
1
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
2
|
+
import { copyJson } from "@nylorun/core/define";
|
|
3
3
|
import { createId } from "../utils/ids.js";
|
|
4
4
|
import { createObservationSink } from "./observe.js";
|
|
5
5
|
export function createInvocation(input) {
|
|
@@ -9,6 +9,7 @@ export function createInvocation(input) {
|
|
|
9
9
|
signal: input.options.signal ?? new AbortController().signal,
|
|
10
10
|
definitions: input.definitions,
|
|
11
11
|
state: input.state,
|
|
12
|
+
sessionBag: { ...(input.state.state ?? {}) },
|
|
12
13
|
};
|
|
13
14
|
const sink = createObservationSink({
|
|
14
15
|
listener: input.options.onEvent,
|
|
@@ -18,7 +19,11 @@ export function createInvocation(input) {
|
|
|
18
19
|
invocation.observe = sink.observe;
|
|
19
20
|
invocation.emit = sink.emit;
|
|
20
21
|
invocation.record = async () => {
|
|
21
|
-
invocation.state = copyJson({
|
|
22
|
+
invocation.state = copyJson({
|
|
23
|
+
...invocation.state,
|
|
24
|
+
state: { ...invocation.sessionBag },
|
|
25
|
+
revision: invocation.state.revision + 1,
|
|
26
|
+
});
|
|
22
27
|
if (!input.options.record)
|
|
23
28
|
return;
|
|
24
29
|
try {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { ModelAdapter, ModelAdapterContext, ModelCall, ModelCandidate } from "
|
|
2
|
-
import type { JsonObject } from "
|
|
1
|
+
import type { ModelAdapter, ModelAdapterContext, ModelCall, ModelCandidate } from "@nylorun/core/define";
|
|
2
|
+
import type { JsonObject } from "@nylorun/core/define";
|
|
3
|
+
import { preparedModel } from "./prepared.js";
|
|
4
|
+
export { preparedModel };
|
|
3
5
|
export type ChatCompletionsMessage = {
|
|
4
6
|
readonly role: "system";
|
|
5
7
|
readonly content: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import { copyJsonObject } from "
|
|
1
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
2
|
+
import { copyJsonObject } from "@nylorun/core/define";
|
|
3
3
|
import { preparedModel } from "./prepared.js";
|
|
4
|
+
export { preparedModel };
|
|
4
5
|
/** Translate a Harness call to the OpenAI Chat Completions request shape. */
|
|
5
6
|
export function toChatCompletions(call) {
|
|
6
7
|
const messages = withoutProviderReasoning(call.prompt).map((item) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import type { ModelCandidate, ModelDirective, ModelOutputBlock } from "
|
|
1
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
2
|
+
import type { ModelCandidate, ModelDirective, ModelOutputBlock } from "@nylorun/core/define";
|
|
3
3
|
export declare function normalizeDirective(value: unknown): ModelDirective | HarnessError;
|
|
4
4
|
export declare function sameDirective(left: ModelDirective, right: ModelDirective): boolean;
|
|
5
5
|
export declare function textFromOutput(output: readonly ModelOutputBlock[]): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HarnessError, isHarnessError } from "
|
|
2
|
-
import { assertJson, copyJson, copyJsonObject } from "
|
|
1
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
2
|
+
import { assertJson, copyJson, copyJsonObject } from "@nylorun/core/define";
|
|
3
3
|
const FINISH_REASONS = new Set([
|
|
4
4
|
"stop",
|
|
5
5
|
"length",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ModelAdapter, ModelAdapterContext, ModelCandidate, ModelCall } from "
|
|
2
|
-
import type { JsonValue } from "
|
|
1
|
+
import type { ModelAdapter, ModelAdapterContext, ModelCandidate, ModelCall } from "@nylorun/core/define";
|
|
2
|
+
import type { JsonValue } from "@nylorun/core/define";
|
|
3
3
|
export interface PreparedModelOptions<Wire> {
|
|
4
4
|
readonly adapter: string;
|
|
5
5
|
prepare(call: ModelCall, context: ModelAdapterContext): Promise<{
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { ModelCall, ModelRequest } from "
|
|
1
|
+
import type { ModelCall, ModelRequest } from "@nylorun/core/define";
|
|
2
2
|
export declare function projectModelCall(request: ModelRequest): ModelCall;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExecutionEvent } from "../types/execution.js";
|
|
2
|
-
import type { ObserveEvent, Observer } from "
|
|
3
|
-
import type { JsonValue } from "
|
|
2
|
+
import type { ObserveEvent, Observer } from "@nylorun/core/define";
|
|
3
|
+
import type { JsonValue } from "@nylorun/core/define";
|
|
4
4
|
export type ObserveEmit = (event: ObserveEvent | (() => ObserveEvent)) => void;
|
|
5
5
|
export type ExecutionOutcomeEvent = {
|
|
6
6
|
type: "execution.completed" | "execution.paused" | "execution.cancelled" | "execution.failed" | "observation.failed";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentDefinition } from "../definition/agent-definition.js";
|
|
2
|
-
import type { BoundToolDefinition } from "
|
|
2
|
+
import type { BoundToolDefinition } from "@nylorun/core/define";
|
|
3
3
|
import type { ExecutionState, RunOptions } from "../types/execution.js";
|
|
4
4
|
import { normalizeInput } from "./state.js";
|
|
5
5
|
export declare function validateRunOptions(options: RunOptions<any>): void;
|
|
@@ -7,4 +7,5 @@ export declare function openExecution(agent: AgentDefinition, options: RunOption
|
|
|
7
7
|
readonly state: ExecutionState;
|
|
8
8
|
readonly input: ReturnType<typeof normalizeInput>;
|
|
9
9
|
readonly definitions: Map<string, BoundToolDefinition>;
|
|
10
|
+
readonly redelivery: boolean;
|
|
10
11
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { checkCompatibility } from "../definition/compatibility.js";
|
|
1
2
|
import { initializeExecutionState } from "./initial-state.js";
|
|
2
|
-
import { canonical } from "
|
|
3
|
-
import { HarnessError } from "
|
|
3
|
+
import { canonical } from "@nylorun/core/define";
|
|
4
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
4
5
|
import { normalizeInput, validateExecutionState } from "./state.js";
|
|
5
6
|
const acceptedOptions = new Set([
|
|
6
7
|
"state",
|
|
@@ -30,12 +31,26 @@ export function openExecution(agent, options) {
|
|
|
30
31
|
const state = options.state === undefined
|
|
31
32
|
? initializeExecutionState(agent)
|
|
32
33
|
: validateExecutionState(options.state);
|
|
33
|
-
if (state.agentId !== agent.id
|
|
34
|
-
canonical(state.outputContract) !== canonical(agent.output?.schema.jsonSchema))
|
|
34
|
+
if (state.agentId !== agent.id)
|
|
35
35
|
throw new HarnessError("execution.incompatible", "Saved state requires the original compatible agent and outputSchema");
|
|
36
|
-
if ((state.
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
if (canonical(state.outputContract) !== canonical(agent.output?.schema.jsonSchema))
|
|
37
|
+
throw new HarnessError("execution.incompatible", "Saved state requires the original compatible agent and outputSchema");
|
|
38
|
+
if (state.manifestHash) {
|
|
39
|
+
const compatibility = checkCompatibility(agent.manifest, state);
|
|
40
|
+
if (!compatibility.ok)
|
|
41
|
+
throw compatibility.error;
|
|
42
|
+
}
|
|
43
|
+
const hasActive = state.status === "active" || state.plan?.calls.some((call) => call.status === "active");
|
|
44
|
+
// Crash-continue: allow resume when a prior run left active work; mark redelivery.
|
|
45
|
+
if (hasActive && options.input && typeof options.input === "object" && "kind" in options.input) {
|
|
46
|
+
const kind = options.input.kind;
|
|
47
|
+
if (kind !== "continue" && kind !== "settle" && kind !== "approve" && kind !== "respond")
|
|
48
|
+
throw new HarnessError("execution.invalid-state", "Execution was interrupted with possibly active effects. Resume with continue/settle/approve/respond; automatic replay is not supported.");
|
|
49
|
+
}
|
|
50
|
+
else if (hasActive) {
|
|
51
|
+
throw new HarnessError("execution.invalid-state", "Execution was interrupted with possibly active effects. Reconcile it before starting another run; automatic replay is not supported.");
|
|
52
|
+
}
|
|
53
|
+
if (state.status !== "paused" && state.plan !== undefined && !hasActive)
|
|
39
54
|
throw new HarnessError("execution.invalid-state", "Execution was interrupted with possibly active effects. Reconcile it before starting another run; automatic replay is not supported.");
|
|
40
55
|
const definitions = new Map();
|
|
41
56
|
for (const call of state.plan?.calls ?? []) {
|
|
@@ -45,5 +60,10 @@ export function openExecution(agent, options) {
|
|
|
45
60
|
throw new HarnessError("execution.incompatible", "Saved tool arguments differ from their accepted contract");
|
|
46
61
|
definitions.set(call.invocationId, definition);
|
|
47
62
|
}
|
|
48
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
state,
|
|
65
|
+
input: normalizeInput(options.input),
|
|
66
|
+
definitions,
|
|
67
|
+
redelivery: Boolean(hasActive),
|
|
68
|
+
};
|
|
49
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BoundToolDefinition } from "
|
|
1
|
+
import type { BoundToolDefinition } from "@nylorun/core/define";
|
|
2
2
|
import type { ExecutionState } from "../types/execution.js";
|
|
3
3
|
import type { normalizeInput } from "./state.js";
|
|
4
4
|
export declare function applyResume(state: ExecutionState, input: ReturnType<typeof normalizeInput>, definitions: Map<string, BoundToolDefinition>): ExecutionState;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HarnessError } from "
|
|
2
|
-
import { copyJson } from "
|
|
1
|
+
import { HarnessError } from "@nylorun/core/define";
|
|
2
|
+
import { copyJson } from "@nylorun/core/define";
|
|
3
3
|
import { toolResult } from "./tool-result.js";
|
|
4
4
|
export function applyResume(state, input, definitions) {
|
|
5
5
|
const plan = state.plan;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentDefinition } from "../definition/agent-definition.js";
|
|
2
2
|
import type { RunOptions, RunResult } from "../types/execution.js";
|
|
3
|
-
import type { JsonValue } from "
|
|
3
|
+
import type { JsonValue } from "@nylorun/core/define";
|
|
4
4
|
/** All mutable progress belongs to this invocation and is discarded when it settles. */
|
|
5
5
|
export declare function execute(agent: AgentDefinition, options: RunOptions<any>): Promise<RunResult<JsonValue>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HostSuspension } from "./host-suspension.js";
|
|
2
|
+
import { HarnessError, isHarnessError } from "@nylorun/core/define";
|
|
2
3
|
import { runStep } from "./step/run.js";
|
|
3
4
|
import { createId } from "../utils/ids.js";
|
|
4
5
|
import { dispatchPlan } from "./dispatch.js";
|
|
@@ -74,6 +75,11 @@ export async function execute(agent, options) {
|
|
|
74
75
|
middleware: agent.middleware,
|
|
75
76
|
invoke: options.onModelCall,
|
|
76
77
|
registry: agent.registry,
|
|
78
|
+
definition: agent,
|
|
79
|
+
sessionState: invocation.sessionBag,
|
|
80
|
+
recordAfterDynamics: async () => {
|
|
81
|
+
await record();
|
|
82
|
+
},
|
|
77
83
|
},
|
|
78
84
|
state: snapshot,
|
|
79
85
|
executionId: invocation.state.executionId,
|
|
@@ -188,6 +194,8 @@ export async function execute(agent, options) {
|
|
|
188
194
|
}
|
|
189
195
|
}
|
|
190
196
|
catch (error) {
|
|
197
|
+
if (error instanceof HostSuspension)
|
|
198
|
+
throw error;
|
|
191
199
|
if (isHarnessError(error) && error.code === "execution.record-failed")
|
|
192
200
|
throw error;
|
|
193
201
|
if (signal.aborted)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { BuiltAgent } from "
|
|
1
|
+
import type { BuiltAgent } from "@nylorun/core/define";
|
|
2
2
|
import type { ExecutionInput, ExecutionState } from "../types/execution.js";
|
|
3
|
-
import type { InputEvent } from "
|
|
3
|
+
import type { InputEvent } from "@nylorun/core/define";
|
|
4
4
|
export declare function createExecutionState<Info, Output>(agent: BuiltAgent<Info, Output>): ExecutionState;
|
|
5
5
|
export declare function validateExecutionState(value: unknown): ExecutionState;
|
|
6
6
|
export declare function normalizeInput(value: ExecutionInput): InputEvent | Extract<ExecutionInput, {
|