@nylorun/harness 0.5.0-beta.1 → 0.7.0-beta.1
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 +45 -0
- package/README.md +22 -108
- package/dist/build/agent.d.ts +4 -7
- package/dist/build/agent.js +12 -7
- package/dist/build/assemble.d.ts +2 -6
- package/dist/build/assemble.js +4 -47
- package/dist/build/bind-tool.d.ts +2 -3
- package/dist/build/bind-tool.js +6 -4
- package/dist/build/builder.d.ts +5 -8
- package/dist/build/builder.js +34 -13
- package/dist/build/helpers.d.ts +2 -3
- package/dist/build/helpers.js +0 -1
- package/dist/build/manifest.d.ts +0 -4
- package/dist/build/manifest.js +0 -8
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/session/capability-state.d.ts +14 -0
- package/dist/session/capability-state.js +67 -0
- package/dist/session/input-queue.d.ts +11 -2
- package/dist/session/input-queue.js +5 -1
- package/dist/session/record.d.ts +11 -0
- package/dist/session/record.js +26 -0
- package/dist/session/scheduler.d.ts +17 -3
- package/dist/session/scheduler.js +181 -48
- package/dist/session/seed.d.ts +10 -0
- package/dist/session/seed.js +219 -0
- package/dist/session/session.d.ts +3 -2
- package/dist/session/session.js +15 -3
- package/dist/session/state.d.ts +8 -3
- package/dist/session/state.js +16 -4
- package/dist/session/submission-stream.d.ts +2 -0
- package/dist/session/submission-stream.js +11 -1
- package/dist/step/model-configuration.d.ts +1 -3
- package/dist/step/model-configuration.js +2 -4
- package/dist/step/project.js +4 -2
- package/dist/step/run.d.ts +6 -1
- package/dist/step/run.js +35 -13
- package/dist/step/seal.d.ts +6 -2
- package/dist/step/seal.js +4 -3
- package/dist/step/step-context.d.ts +5 -2
- package/dist/step/step-context.js +19 -11
- package/dist/turn/plan-runner.d.ts +28 -13
- package/dist/turn/plan-runner.js +165 -182
- package/dist/turn/runner.d.ts +18 -4
- package/dist/turn/runner.js +73 -45
- package/dist/types/manifest.d.ts +0 -6
- package/dist/types/middleware.d.ts +25 -4
- package/dist/types/model.d.ts +3 -2
- package/dist/types/session.d.ts +86 -2
- package/dist/types/shared.d.ts +65 -9
- package/dist/types/tool.d.ts +37 -39
- package/dist/utils/immutable.js +16 -2
- package/package.json +1 -2
- package/dist/build/adapters.d.ts +0 -11
- package/dist/build/adapters.js +0 -91
- package/docs/loop.md +0 -47
- package/docs/model-call-projection.md +0 -112
- package/docs/reference.md +0 -122
|
@@ -13,22 +13,24 @@ export class StepContext {
|
|
|
13
13
|
#observe;
|
|
14
14
|
#context;
|
|
15
15
|
#configuration;
|
|
16
|
+
#states;
|
|
16
17
|
#denials = new Map();
|
|
17
18
|
#interactions = new Map();
|
|
18
|
-
#preflights = new Map();
|
|
19
19
|
#identities = new Map();
|
|
20
20
|
#canonical = Object.freeze([]);
|
|
21
21
|
#candidate;
|
|
22
22
|
#tripwire;
|
|
23
|
+
#modelDeferred;
|
|
23
24
|
#response;
|
|
24
25
|
#sealed = false;
|
|
25
26
|
#configurationSnapshot;
|
|
26
27
|
#contextSnapshot;
|
|
27
|
-
constructor(input, observe, configuration, context) {
|
|
28
|
+
constructor(input, observe, configuration, context, states) {
|
|
28
29
|
this.input = input;
|
|
29
30
|
this.#observe = observe;
|
|
30
31
|
this.#configuration = configuration;
|
|
31
32
|
this.#context = context;
|
|
33
|
+
this.#states = states;
|
|
32
34
|
}
|
|
33
35
|
get currentTripwire() {
|
|
34
36
|
return this.#tripwire;
|
|
@@ -36,6 +38,9 @@ export class StepContext {
|
|
|
36
38
|
get currentCandidate() {
|
|
37
39
|
return this.#candidate;
|
|
38
40
|
}
|
|
41
|
+
get currentModelDeferred() {
|
|
42
|
+
return this.#modelDeferred;
|
|
43
|
+
}
|
|
39
44
|
get selectedDirective() {
|
|
40
45
|
return this.configurationSnapshot().model;
|
|
41
46
|
}
|
|
@@ -66,9 +71,6 @@ export class StepContext {
|
|
|
66
71
|
interactionFor(callId) {
|
|
67
72
|
return this.#interactions.get(callId);
|
|
68
73
|
}
|
|
69
|
-
preflightFor(callId) {
|
|
70
|
-
return this.#preflights.get(callId);
|
|
71
|
-
}
|
|
72
74
|
canonicalCalls() {
|
|
73
75
|
return this.#canonical;
|
|
74
76
|
}
|
|
@@ -81,6 +83,10 @@ export class StepContext {
|
|
|
81
83
|
this.#identities.set(call.id, identityKey(call.name, call.args));
|
|
82
84
|
return this.#ensureResponse();
|
|
83
85
|
}
|
|
86
|
+
deferModel(active) {
|
|
87
|
+
this.#modelDeferred = active;
|
|
88
|
+
return this.#ensureResponse();
|
|
89
|
+
}
|
|
84
90
|
tripwire(error) {
|
|
85
91
|
this.#tripwire ??= Object.freeze({ ...error });
|
|
86
92
|
return this.#ensureResponse();
|
|
@@ -131,7 +137,7 @@ export class StepContext {
|
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
});
|
|
134
|
-
const
|
|
140
|
+
const request = {
|
|
135
141
|
sessionId: this.input.sessionId,
|
|
136
142
|
turnId: this.input.turnId,
|
|
137
143
|
stepId: this.input.stepId,
|
|
@@ -164,7 +170,13 @@ export class StepContext {
|
|
|
164
170
|
});
|
|
165
171
|
return minted;
|
|
166
172
|
},
|
|
167
|
-
}
|
|
173
|
+
};
|
|
174
|
+
if (this.#states?.has(middlewareId))
|
|
175
|
+
Object.defineProperty(request, "state", {
|
|
176
|
+
enumerable: true,
|
|
177
|
+
get: () => this.#states.get(middlewareId),
|
|
178
|
+
});
|
|
179
|
+
const value = Object.freeze(request);
|
|
168
180
|
return Object.freeze({
|
|
169
181
|
value,
|
|
170
182
|
revokeMutators: () => {
|
|
@@ -187,10 +199,6 @@ export class StepContext {
|
|
|
187
199
|
if (!this.#interactions.has(callId))
|
|
188
200
|
this.#interactions.set(callId, freezeGraph({ ...interaction }));
|
|
189
201
|
},
|
|
190
|
-
requirePreflight: (callId, kind) => {
|
|
191
|
-
if (!this.#preflights.has(callId))
|
|
192
|
-
this.#preflights.set(callId, kind);
|
|
193
|
-
},
|
|
194
202
|
tripwire: (error) => {
|
|
195
203
|
this.#tripwire ??= Object.freeze({ ...error });
|
|
196
204
|
return value;
|
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import type { AdapterRegistry } from "../build/adapters.js";
|
|
2
1
|
import type { ObserveEmit } from "../utils/observe.js";
|
|
3
2
|
import type { InputEvent } from "../types/session.js";
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
type
|
|
3
|
+
import type { ActiveInteractionExecutionRecord, ActiveToolsExecutionRecord } from "../types/session.js";
|
|
4
|
+
import type { RequiredInteraction, ToolExecutionResume, ToolResult } from "../types/tool.js";
|
|
5
|
+
import type { JsonValue } from "../types/shared.js";
|
|
6
|
+
import type { CapabilityStateRegistry } from "../session/capability-state.js";
|
|
7
|
+
import type { ExecutablePlanEntry, InternalToolPlan } from "../step/seal.js";
|
|
8
|
+
export interface DeferredToolCall {
|
|
9
|
+
readonly callId: string;
|
|
10
|
+
readonly toolName: string;
|
|
11
|
+
readonly args: JsonValue;
|
|
12
|
+
readonly invocationId: string;
|
|
13
|
+
readonly owner: ExecutablePlanEntry["owner"];
|
|
14
|
+
readonly token?: JsonValue;
|
|
15
|
+
}
|
|
7
16
|
export type ToolPlanProgress = {
|
|
8
17
|
readonly kind: "completed";
|
|
9
18
|
readonly results: readonly ToolResult[];
|
|
10
19
|
} | {
|
|
11
20
|
readonly kind: "interaction-required";
|
|
12
21
|
readonly interaction: RequiredInteraction;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "deferred";
|
|
24
|
+
readonly settled: readonly ToolResult[];
|
|
25
|
+
readonly deferred: readonly DeferredToolCall[];
|
|
13
26
|
};
|
|
14
27
|
export interface ToolPlanRunContext {
|
|
15
|
-
readonly adapters: AdapterRegistry;
|
|
16
28
|
readonly signal: AbortSignal;
|
|
17
29
|
readonly observe: ObserveEmit;
|
|
18
30
|
readonly ids: {
|
|
@@ -20,15 +32,17 @@ export interface ToolPlanRunContext {
|
|
|
20
32
|
readonly turnId: string;
|
|
21
33
|
readonly stepId: string;
|
|
22
34
|
};
|
|
35
|
+
readonly states?: CapabilityStateRegistry;
|
|
23
36
|
}
|
|
24
|
-
/** Owns
|
|
37
|
+
/** Owns one sealed plan's deterministic interaction and concurrent execution progress. */
|
|
25
38
|
export declare class ToolPlanRunner {
|
|
26
39
|
private readonly plan;
|
|
27
40
|
private readonly results;
|
|
41
|
+
private readonly deferred;
|
|
28
42
|
private readonly resumes;
|
|
29
|
-
private phase;
|
|
30
|
-
private index;
|
|
31
43
|
private readonly pending;
|
|
44
|
+
private readonly settlementEvents;
|
|
45
|
+
private interactionIndex;
|
|
32
46
|
private executeStarted;
|
|
33
47
|
private resumeExecute?;
|
|
34
48
|
constructor(plan: InternalToolPlan);
|
|
@@ -36,20 +50,21 @@ export declare class ToolPlanRunner {
|
|
|
36
50
|
get interactionKind(): RequiredInteraction["kind"] | undefined;
|
|
37
51
|
get interactionCallId(): string | undefined;
|
|
38
52
|
get interactionToolName(): string | undefined;
|
|
39
|
-
get interactionPhase(): PlanPhase | undefined;
|
|
40
53
|
cancelledResults(reason: string): readonly ToolResult[];
|
|
54
|
+
/** Publishes settlement facts only after their authoritative state was recorded. */
|
|
55
|
+
publishSettlements(observe: ObserveEmit): void;
|
|
56
|
+
activeToolsRecord(turnId: string, stepId: string): ActiveToolsExecutionRecord;
|
|
57
|
+
activeInteractionRecord(turnId: string, stepId: string, resume?: ToolExecutionResume): ActiveInteractionExecutionRecord;
|
|
58
|
+
interactionResume(event: InputEvent): ToolExecutionResume;
|
|
41
59
|
run(context: ToolPlanRunContext, resume?: InputEvent): Promise<ToolPlanProgress>;
|
|
42
60
|
private acceptResume;
|
|
43
|
-
private runExecute;
|
|
44
|
-
private preflight;
|
|
45
61
|
private executeBatch;
|
|
46
62
|
private execute;
|
|
47
63
|
private takeResume;
|
|
48
|
-
private requireInteraction;
|
|
49
64
|
private enqueueInteraction;
|
|
50
65
|
private interactionRequired;
|
|
51
66
|
private orderedResults;
|
|
67
|
+
private orderedSettledResults;
|
|
52
68
|
private resultsInOrder;
|
|
53
69
|
private throwIfAborted;
|
|
54
70
|
}
|
|
55
|
-
export {};
|
package/dist/turn/plan-runner.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { HarnessError, isHarnessError } from "../errors.js";
|
|
2
2
|
import { createId } from "../utils/ids.js";
|
|
3
3
|
import { copyJson, copyJsonObject } from "../utils/immutable.js";
|
|
4
|
-
/** Owns
|
|
4
|
+
/** Owns one sealed plan's deterministic interaction and concurrent execution progress. */
|
|
5
5
|
export class ToolPlanRunner {
|
|
6
6
|
plan;
|
|
7
7
|
results;
|
|
8
|
+
deferred = new Map();
|
|
8
9
|
resumes = new Map();
|
|
9
|
-
phase = "interaction";
|
|
10
|
-
index = 0;
|
|
11
10
|
pending = [];
|
|
11
|
+
settlementEvents = [];
|
|
12
|
+
interactionIndex = 0;
|
|
12
13
|
executeStarted = false;
|
|
13
14
|
resumeExecute;
|
|
14
15
|
constructor(plan) {
|
|
@@ -23,65 +24,104 @@ export class ToolPlanRunner {
|
|
|
23
24
|
}
|
|
24
25
|
get interactionCallId() {
|
|
25
26
|
const pending = this.pending[0];
|
|
26
|
-
|
|
27
|
-
return undefined;
|
|
28
|
-
return this.plan.executable[pending.index]?.call.callId;
|
|
27
|
+
return pending ? this.plan.executable[pending.index]?.call.callId : undefined;
|
|
29
28
|
}
|
|
30
29
|
get interactionToolName() {
|
|
31
30
|
const pending = this.pending[0];
|
|
32
|
-
|
|
33
|
-
return undefined;
|
|
34
|
-
return this.plan.executable[pending.index]?.call.toolName;
|
|
35
|
-
}
|
|
36
|
-
get interactionPhase() {
|
|
37
|
-
return this.pending[0]?.phase;
|
|
31
|
+
return pending ? this.plan.executable[pending.index]?.call.toolName : undefined;
|
|
38
32
|
}
|
|
39
33
|
cancelledResults(reason) {
|
|
40
34
|
return this.resultsInOrder("tool.cancelled", reason);
|
|
41
35
|
}
|
|
36
|
+
/** Publishes settlement facts only after their authoritative state was recorded. */
|
|
37
|
+
publishSettlements(observe) {
|
|
38
|
+
for (const event of this.settlementEvents.splice(0))
|
|
39
|
+
observe(event);
|
|
40
|
+
}
|
|
41
|
+
activeToolsRecord(turnId, stepId) {
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
kind: "tools",
|
|
44
|
+
turnId,
|
|
45
|
+
stepId,
|
|
46
|
+
calls: Object.freeze(this.plan.executable.map((entry) => {
|
|
47
|
+
const result = this.results.get(entry.call.callId);
|
|
48
|
+
const deferred = this.deferred.get(entry.call.callId);
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
callId: entry.call.callId,
|
|
51
|
+
toolName: entry.call.toolName,
|
|
52
|
+
args: copyJson(entry.call.args),
|
|
53
|
+
invocationId: entry.invocationId,
|
|
54
|
+
owner: entry.owner,
|
|
55
|
+
status: result
|
|
56
|
+
? "settled"
|
|
57
|
+
: deferred
|
|
58
|
+
? "deferred"
|
|
59
|
+
: "pending",
|
|
60
|
+
...(result ? { result } : {}),
|
|
61
|
+
...(deferred?.token === undefined ? {} : { token: deferred.token }),
|
|
62
|
+
});
|
|
63
|
+
})),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
activeInteractionRecord(turnId, stepId, resume) {
|
|
67
|
+
const pending = this.pending[0];
|
|
68
|
+
const entry = pending ? this.plan.executable[pending.index] : undefined;
|
|
69
|
+
if (!pending || !entry)
|
|
70
|
+
throw new HarnessError("interaction.invalid", "Missing pending interaction state");
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
kind: "interaction",
|
|
73
|
+
turnId,
|
|
74
|
+
stepId,
|
|
75
|
+
callId: entry.call.callId,
|
|
76
|
+
toolName: entry.call.toolName,
|
|
77
|
+
interaction: pending.interaction,
|
|
78
|
+
...(pending.token === undefined ? {} : { token: pending.token }),
|
|
79
|
+
...(resume === undefined ? {} : { resume }),
|
|
80
|
+
tools: this.activeToolsRecord(turnId, stepId),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
interactionResume(event) {
|
|
84
|
+
const pending = this.pending[0];
|
|
85
|
+
if (!pending)
|
|
86
|
+
throw new HarnessError("interaction.invalid", "Missing pending interaction state");
|
|
87
|
+
return resumeValue(event, pending);
|
|
88
|
+
}
|
|
42
89
|
async run(context, resume) {
|
|
43
90
|
this.acceptResume(resume);
|
|
44
|
-
while (
|
|
91
|
+
while (this.interactionIndex < this.plan.executable.length) {
|
|
45
92
|
this.throwIfAborted(context.signal);
|
|
46
|
-
|
|
47
|
-
return this.runExecute(context);
|
|
48
|
-
if (this.index >= this.plan.executable.length) {
|
|
49
|
-
if (this.phase === "interaction") {
|
|
50
|
-
this.phase = "preflight";
|
|
51
|
-
this.index = 0;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (this.phase === "preflight") {
|
|
55
|
-
this.phase = "execute";
|
|
56
|
-
this.index = 0;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
return { kind: "completed", results: this.orderedResults() };
|
|
60
|
-
}
|
|
61
|
-
const entry = this.plan.executable[this.index];
|
|
93
|
+
const entry = this.plan.executable[this.interactionIndex];
|
|
62
94
|
if (this.results.has(entry.call.callId)) {
|
|
63
|
-
this.
|
|
95
|
+
this.interactionIndex += 1;
|
|
64
96
|
continue;
|
|
65
97
|
}
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
return this.requireInteraction({
|
|
72
|
-
phase: this.phase,
|
|
73
|
-
index: this.index,
|
|
74
|
-
interaction: entry.interaction,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
const adapter = context.adapters.require(entry.call.executeWith);
|
|
78
|
-
if (this.phase === "preflight") {
|
|
79
|
-
const progress = await this.preflight(entry, adapter, context);
|
|
80
|
-
if (progress)
|
|
81
|
-
return progress;
|
|
98
|
+
if (entry.interaction) {
|
|
99
|
+
this.enqueueInteraction({ index: this.interactionIndex, interaction: entry.interaction });
|
|
100
|
+
return this.interactionRequired();
|
|
82
101
|
}
|
|
83
|
-
this.
|
|
102
|
+
this.interactionIndex += 1;
|
|
84
103
|
}
|
|
104
|
+
if (this.resumeExecute) {
|
|
105
|
+
const entry = this.resumeExecute;
|
|
106
|
+
this.resumeExecute = undefined;
|
|
107
|
+
await this.executeBatch([entry], context);
|
|
108
|
+
}
|
|
109
|
+
else if (!this.executeStarted) {
|
|
110
|
+
this.executeStarted = true;
|
|
111
|
+
await this.executeBatch(this.plan.executable.filter((entry) => !this.results.has(entry.call.callId)), context);
|
|
112
|
+
}
|
|
113
|
+
if (this.pending.length)
|
|
114
|
+
return this.interactionRequired();
|
|
115
|
+
if (this.deferred.size)
|
|
116
|
+
return {
|
|
117
|
+
kind: "deferred",
|
|
118
|
+
settled: this.orderedSettledResults(),
|
|
119
|
+
deferred: Object.freeze(this.plan.executable.flatMap((entry) => {
|
|
120
|
+
const value = this.deferred.get(entry.call.callId);
|
|
121
|
+
return value ? [value] : [];
|
|
122
|
+
})),
|
|
123
|
+
};
|
|
124
|
+
return { kind: "completed", results: this.orderedResults() };
|
|
85
125
|
}
|
|
86
126
|
acceptResume(resume) {
|
|
87
127
|
const pending = this.pending.shift();
|
|
@@ -98,162 +138,100 @@ export class ToolPlanRunner {
|
|
|
98
138
|
kind: "denied",
|
|
99
139
|
reason: "Approval rejected",
|
|
100
140
|
}));
|
|
101
|
-
this.index
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (pending.phase === "interaction") {
|
|
105
|
-
this.resumes.set(entry.call.callId, value);
|
|
106
|
-
this.index += 1;
|
|
141
|
+
this.interactionIndex = Math.max(this.interactionIndex, pending.index + 1);
|
|
107
142
|
return;
|
|
108
143
|
}
|
|
109
|
-
this.resumes.set(
|
|
110
|
-
if (
|
|
144
|
+
this.resumes.set(entry.call.callId, value);
|
|
145
|
+
if (this.executeStarted)
|
|
111
146
|
this.resumeExecute = entry;
|
|
147
|
+
else
|
|
148
|
+
this.interactionIndex = Math.max(this.interactionIndex, pending.index + 1);
|
|
112
149
|
}
|
|
113
|
-
async
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
150
|
+
async executeBatch(entries, context) {
|
|
151
|
+
const settled = await Promise.allSettled(entries.map((entry) => this.execute(entry, context)));
|
|
152
|
+
if (context.signal.aborted)
|
|
153
|
+
throw context.signal.reason;
|
|
154
|
+
for (const outcome of settled) {
|
|
155
|
+
if (outcome.status === "rejected")
|
|
156
|
+
throw outcome.reason;
|
|
157
|
+
if (outcome.value)
|
|
158
|
+
this.enqueueInteraction(outcome.value);
|
|
122
159
|
}
|
|
123
|
-
return this.pending.length
|
|
124
|
-
? this.interactionRequired()
|
|
125
|
-
: { kind: "completed", results: this.orderedResults() };
|
|
126
160
|
}
|
|
127
|
-
async
|
|
128
|
-
if (!entry.preflight)
|
|
129
|
-
return undefined;
|
|
130
|
-
if (!adapter.preflight) {
|
|
131
|
-
this.results.set(entry.call.callId, failed(entry, "tool.preflight-missing", `Adapter '${entry.call.executeWith}' does not implement preflight`));
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
161
|
+
async execute(entry, context) {
|
|
134
162
|
context.observe({
|
|
135
|
-
type: "
|
|
163
|
+
type: "tool.started",
|
|
164
|
+
sessionId: context.ids.sessionId,
|
|
136
165
|
turnId: context.ids.turnId,
|
|
137
166
|
stepId: context.ids.stepId,
|
|
138
|
-
adapterId: adapter.id,
|
|
139
167
|
toolName: entry.call.toolName,
|
|
140
168
|
callId: entry.call.callId,
|
|
141
169
|
invocationId: entry.invocationId,
|
|
170
|
+
middlewareId: entry.owner.middlewareId,
|
|
171
|
+
slot: entry.owner.slot,
|
|
142
172
|
attributes: { args: copyJson(entry.call.args) },
|
|
143
173
|
});
|
|
144
174
|
try {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
invocationId: entry.invocationId,
|
|
148
|
-
signal: context.signal,
|
|
149
|
-
resume: this.takeResume("preflight", entry.call.callId),
|
|
150
|
-
});
|
|
151
|
-
this.throwIfAborted(context.signal);
|
|
152
|
-
if (outcome.kind === "interaction-required")
|
|
153
|
-
return this.requireInteraction(interactionBarrier(this.phase, this.index, outcome));
|
|
154
|
-
if (outcome.kind !== "completed")
|
|
155
|
-
this.results.set(entry.call.callId, resultFrom(entry, outcome));
|
|
156
|
-
context.observe({
|
|
157
|
-
type: "adapter.preflight.completed",
|
|
175
|
+
const toolContext = {
|
|
176
|
+
sessionId: context.ids.sessionId,
|
|
158
177
|
turnId: context.ids.turnId,
|
|
159
178
|
stepId: context.ids.stepId,
|
|
160
|
-
adapterId: adapter.id,
|
|
161
|
-
toolName: entry.call.toolName,
|
|
162
179
|
callId: entry.call.callId,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
invocationId: entry.invocationId,
|
|
181
|
+
signal: context.signal,
|
|
182
|
+
resume: this.takeResume(entry.call.callId),
|
|
183
|
+
};
|
|
184
|
+
if (context.states?.has(entry.owner.middlewareId))
|
|
185
|
+
Object.defineProperty(toolContext, "state", {
|
|
186
|
+
enumerable: true,
|
|
187
|
+
get: () => context.states.get(entry.owner.middlewareId),
|
|
188
|
+
});
|
|
189
|
+
const outcome = await entry.execute(entry.call.args, Object.freeze(toolContext));
|
|
168
190
|
this.throwIfAborted(context.signal);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
adapterId: adapter.id,
|
|
176
|
-
toolName: entry.call.toolName,
|
|
177
|
-
callId: entry.call.callId,
|
|
178
|
-
outcome: "failed",
|
|
179
|
-
...adapterCompleted(result),
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return undefined;
|
|
183
|
-
}
|
|
184
|
-
async executeBatch(entries, context) {
|
|
185
|
-
const settled = await Promise.allSettled(entries.map((entry) => this.execute(entry, context)));
|
|
186
|
-
if (context.signal.aborted)
|
|
187
|
-
throw context.signal.reason;
|
|
188
|
-
for (const outcome of settled) {
|
|
189
|
-
if (outcome.status === "rejected")
|
|
190
|
-
throw outcome.reason;
|
|
191
|
-
if (outcome.value)
|
|
192
|
-
this.enqueueInteraction(outcome.value);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
async execute(entry, context) {
|
|
196
|
-
const adapter = context.adapters.require(entry.call.executeWith);
|
|
197
|
-
try {
|
|
198
|
-
return await context.adapters.execute(adapter.id, context.signal, async () => {
|
|
199
|
-
context.observe({
|
|
200
|
-
type: "adapter.started",
|
|
201
|
-
turnId: context.ids.turnId,
|
|
202
|
-
stepId: context.ids.stepId,
|
|
203
|
-
adapterId: adapter.id,
|
|
204
|
-
toolName: entry.call.toolName,
|
|
191
|
+
if (!outcome || typeof outcome !== "object" || typeof outcome.kind !== "string")
|
|
192
|
+
throw new HarnessError("tool.invalid-tool-result", "Tool returned an invalid outcome");
|
|
193
|
+
if (outcome.kind === "interaction-required")
|
|
194
|
+
return interactionBarrier(this.plan.executable.indexOf(entry), outcome);
|
|
195
|
+
if (outcome.kind === "deferred") {
|
|
196
|
+
const value = Object.freeze({
|
|
205
197
|
callId: entry.call.callId,
|
|
198
|
+
toolName: entry.call.toolName,
|
|
199
|
+
args: copyJson(entry.call.args),
|
|
206
200
|
invocationId: entry.invocationId,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const outcome = await adapter.execute(entry.call, {
|
|
210
|
-
invocationId: entry.invocationId,
|
|
211
|
-
signal: context.signal,
|
|
212
|
-
resume: this.takeResume("execute", entry.call.callId),
|
|
201
|
+
owner: entry.owner,
|
|
202
|
+
...(outcome.token === undefined ? {} : { token: copyJson(outcome.token) }),
|
|
213
203
|
});
|
|
214
|
-
this.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
context.observe({
|
|
219
|
-
type: "adapter.completed",
|
|
204
|
+
this.deferred.set(entry.call.callId, value);
|
|
205
|
+
this.settlementEvents.push({
|
|
206
|
+
type: "tool.deferred",
|
|
207
|
+
sessionId: context.ids.sessionId,
|
|
220
208
|
turnId: context.ids.turnId,
|
|
221
209
|
stepId: context.ids.stepId,
|
|
222
|
-
adapterId: adapter.id,
|
|
223
210
|
toolName: entry.call.toolName,
|
|
224
211
|
callId: entry.call.callId,
|
|
225
|
-
|
|
226
|
-
|
|
212
|
+
invocationId: entry.invocationId,
|
|
213
|
+
middlewareId: entry.owner.middlewareId,
|
|
214
|
+
slot: entry.owner.slot,
|
|
215
|
+
attributes: outcome.token === undefined ? {} : { token: copyJson(outcome.token) },
|
|
227
216
|
});
|
|
228
217
|
return undefined;
|
|
229
|
-
}
|
|
218
|
+
}
|
|
219
|
+
const result = resultFrom(entry, outcome);
|
|
220
|
+
this.results.set(entry.call.callId, result);
|
|
221
|
+
this.settlementEvents.push(completedEvent(entry, context, result));
|
|
230
222
|
}
|
|
231
223
|
catch (error) {
|
|
232
224
|
this.throwIfAborted(context.signal);
|
|
233
225
|
const result = failed(entry, isHarnessError(error) ? error.code : "tool.execution-failed", error);
|
|
234
226
|
this.results.set(entry.call.callId, result);
|
|
235
|
-
|
|
236
|
-
type: "adapter.completed",
|
|
237
|
-
turnId: context.ids.turnId,
|
|
238
|
-
stepId: context.ids.stepId,
|
|
239
|
-
adapterId: adapter.id,
|
|
240
|
-
toolName: entry.call.toolName,
|
|
241
|
-
callId: entry.call.callId,
|
|
242
|
-
outcome: "failed",
|
|
243
|
-
...adapterCompleted(result),
|
|
244
|
-
});
|
|
227
|
+
this.settlementEvents.push(completedEvent(entry, context, result));
|
|
245
228
|
}
|
|
246
229
|
return undefined;
|
|
247
230
|
}
|
|
248
|
-
takeResume(
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return resume;
|
|
253
|
-
}
|
|
254
|
-
requireInteraction(pending) {
|
|
255
|
-
this.enqueueInteraction(pending);
|
|
256
|
-
return this.interactionRequired();
|
|
231
|
+
takeResume(callId) {
|
|
232
|
+
const value = this.resumes.get(callId);
|
|
233
|
+
this.resumes.delete(callId);
|
|
234
|
+
return value;
|
|
257
235
|
}
|
|
258
236
|
enqueueInteraction(pending) {
|
|
259
237
|
const position = this.pending.findIndex((item) => item.index > pending.index);
|
|
@@ -271,24 +249,23 @@ export class ToolPlanRunner {
|
|
|
271
249
|
orderedResults() {
|
|
272
250
|
return this.resultsInOrder("tool.missing-result", "Tool call did not produce a result");
|
|
273
251
|
}
|
|
252
|
+
orderedSettledResults() {
|
|
253
|
+
return Object.freeze(this.plan.order.flatMap(({ callId }) => {
|
|
254
|
+
const result = this.results.get(callId);
|
|
255
|
+
return result ? [result] : [];
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
274
258
|
resultsInOrder(code, message) {
|
|
275
259
|
return Object.freeze(this.plan.order.map(({ callId, toolName }) => this.results.get(callId) ??
|
|
276
|
-
Object.freeze({
|
|
277
|
-
callId,
|
|
278
|
-
toolName,
|
|
279
|
-
kind: "failed",
|
|
280
|
-
code,
|
|
281
|
-
message,
|
|
282
|
-
})));
|
|
260
|
+
Object.freeze({ kind: "failed", callId, toolName, code, message })));
|
|
283
261
|
}
|
|
284
262
|
throwIfAborted(signal) {
|
|
285
263
|
if (signal.aborted)
|
|
286
264
|
throw signal.reason;
|
|
287
265
|
}
|
|
288
266
|
}
|
|
289
|
-
function interactionBarrier(
|
|
267
|
+
function interactionBarrier(index, outcome) {
|
|
290
268
|
return {
|
|
291
|
-
phase,
|
|
292
269
|
index,
|
|
293
270
|
interaction: normalizeInteraction(outcome.interaction),
|
|
294
271
|
...(outcome.token === undefined ? {} : { token: copyJson(outcome.token) }),
|
|
@@ -347,15 +324,21 @@ function resultFrom(entry, outcome) {
|
|
|
347
324
|
code: outcome.code,
|
|
348
325
|
message: outcome.message,
|
|
349
326
|
});
|
|
350
|
-
default:
|
|
351
|
-
throw new HarnessError("adapter.invalid-outcome", "Tool adapter returned an invalid outcome");
|
|
352
327
|
}
|
|
353
328
|
}
|
|
354
|
-
function
|
|
355
|
-
if (!result)
|
|
356
|
-
return {};
|
|
329
|
+
function completedEvent(entry, context, result) {
|
|
357
330
|
return {
|
|
358
|
-
|
|
331
|
+
type: "tool.completed",
|
|
332
|
+
sessionId: context.ids.sessionId,
|
|
333
|
+
turnId: context.ids.turnId,
|
|
334
|
+
stepId: context.ids.stepId,
|
|
335
|
+
toolName: entry.call.toolName,
|
|
336
|
+
callId: entry.call.callId,
|
|
337
|
+
invocationId: entry.invocationId,
|
|
338
|
+
middlewareId: entry.owner.middlewareId,
|
|
339
|
+
slot: entry.owner.slot,
|
|
340
|
+
outcome: result.kind,
|
|
341
|
+
...(result.kind === "failed" ? { code: result.code } : {}),
|
|
359
342
|
attributes: copyJson(result),
|
|
360
343
|
};
|
|
361
344
|
}
|