@graphorin/agent 0.6.0 → 0.7.0
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 +96 -0
- package/README.md +31 -11
- package/dist/errors/index.d.ts +17 -4
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +19 -3
- package/dist/errors/index.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +153 -1930
- package/dist/factory.js.map +1 -1
- package/dist/fanout/index.d.ts +13 -1
- package/dist/fanout/index.d.ts.map +1 -1
- package/dist/fanout/index.js +13 -4
- package/dist/fanout/index.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/dist/lateral-leak/index.js +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/run-state/index.d.ts +32 -6
- package/dist/run-state/index.d.ts.map +1 -1
- package/dist/run-state/index.js +46 -22
- package/dist/run-state/index.js.map +1 -1
- package/dist/runtime/agent-surface.js +122 -0
- package/dist/runtime/agent-surface.js.map +1 -0
- package/dist/runtime/agent-to-tool.d.ts +51 -0
- package/dist/runtime/agent-to-tool.d.ts.map +1 -0
- package/dist/runtime/agent-to-tool.js +145 -0
- package/dist/runtime/agent-to-tool.js.map +1 -0
- package/dist/runtime/approvals.js +0 -0
- package/dist/runtime/approvals.js.map +1 -0
- package/dist/runtime/dispatch.js +108 -0
- package/dist/runtime/dispatch.js.map +1 -0
- package/dist/runtime/executor-wiring.js +128 -0
- package/dist/runtime/executor-wiring.js.map +1 -0
- package/dist/runtime/fallback-chain.js +139 -0
- package/dist/runtime/fallback-chain.js.map +1 -0
- package/dist/runtime/handoff.js +307 -0
- package/dist/runtime/handoff.js.map +1 -0
- package/dist/runtime/messages.d.ts +22 -0
- package/dist/runtime/messages.d.ts.map +1 -0
- package/dist/runtime/messages.js +204 -0
- package/dist/runtime/messages.js.map +1 -0
- package/dist/runtime/provider-events.js +117 -0
- package/dist/runtime/provider-events.js.map +1 -0
- package/dist/runtime/run-compaction.js +210 -0
- package/dist/runtime/run-compaction.js.map +1 -0
- package/dist/runtime/run-finish.js +48 -0
- package/dist/runtime/run-finish.js.map +1 -0
- package/dist/runtime/run-gates.js +336 -0
- package/dist/runtime/run-gates.js.map +1 -0
- package/dist/runtime/run-init.js +81 -0
- package/dist/runtime/run-init.js.map +1 -0
- package/dist/runtime/run-input.js +46 -0
- package/dist/runtime/run-input.js.map +1 -0
- package/dist/runtime/step-catalogue.js +173 -0
- package/dist/runtime/step-catalogue.js.map +1 -0
- package/dist/runtime/tool-call-walk.js +189 -0
- package/dist/runtime/tool-call-walk.js.map +1 -0
- package/dist/runtime/tool-wiring.js +159 -0
- package/dist/runtime/tool-wiring.js.map +1 -0
- package/dist/tooling/adapters.js +1 -1
- package/dist/tooling/dataflow.js +1 -1
- package/dist/tooling/policy.js +2 -0
- package/dist/tooling/policy.js.map +1 -1
- package/dist/types.d.ts +63 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/errors/index.ts +320 -0
- package/src/evaluator-optimizer/index.ts +212 -0
- package/src/factory.ts +957 -0
- package/src/fallback/index.ts +108 -0
- package/src/fanout/index.ts +523 -0
- package/src/filters/index.ts +347 -0
- package/src/index.ts +180 -0
- package/src/internal/ids.ts +46 -0
- package/src/internal/usage-accumulator.ts +90 -0
- package/src/lateral-leak/causality-monitor.ts +221 -0
- package/src/lateral-leak/index.ts +35 -0
- package/src/lateral-leak/merge-guard.ts +151 -0
- package/src/lateral-leak/protocol-guard.ts +222 -0
- package/src/preferred-model/index.ts +210 -0
- package/src/progress/index.ts +238 -0
- package/src/run-state/index.ts +607 -0
- package/src/runtime/agent-surface.ts +218 -0
- package/src/runtime/agent-to-tool.ts +323 -0
- package/src/runtime/approvals.ts +0 -0
- package/src/runtime/dispatch.ts +183 -0
- package/src/runtime/executor-wiring.ts +331 -0
- package/src/runtime/fallback-chain.ts +250 -0
- package/src/runtime/handoff.ts +428 -0
- package/src/runtime/messages.ts +309 -0
- package/src/runtime/provider-events.ts +175 -0
- package/src/runtime/run-compaction.ts +288 -0
- package/src/runtime/run-finish.ts +93 -0
- package/src/runtime/run-gates.ts +419 -0
- package/src/runtime/run-init.ts +169 -0
- package/src/runtime/run-input.ts +102 -0
- package/src/runtime/step-catalogue.ts +338 -0
- package/src/runtime/tool-call-walk.ts +301 -0
- package/src/runtime/tool-wiring.ts +218 -0
- package/src/testing/replay-provider.ts +121 -0
- package/src/tooling/adapters.ts +403 -0
- package/src/tooling/catalogue.ts +36 -0
- package/src/tooling/dataflow.ts +171 -0
- package/src/tooling/plan.ts +123 -0
- package/src/tooling/policy.ts +67 -0
- package/src/tooling/registry-build.ts +191 -0
- package/src/types.ts +696 -0
package/dist/factory.js
CHANGED
|
@@ -1,619 +1,28 @@
|
|
|
1
|
-
import { AgentRuntimeError, ConcurrentRunError, InvalidAgentConfigError, InvalidPreferredModelError, MultipleHandoffsInStepError
|
|
2
|
-
import { isAgentFallbackEligible } from "./fallback/index.js";
|
|
3
|
-
import { runFanOut } from "./fanout/index.js";
|
|
4
|
-
import { filters } from "./filters/index.js";
|
|
1
|
+
import { AgentRuntimeError, ConcurrentRunError, InvalidAgentConfigError, InvalidPreferredModelError, MultipleHandoffsInStepError } from "./errors/index.js";
|
|
5
2
|
import { newId } from "./internal/ids.js";
|
|
6
3
|
import { InMemoryUsageAccumulator } from "./internal/usage-accumulator.js";
|
|
7
4
|
import { CausalityMonitor } from "./lateral-leak/causality-monitor.js";
|
|
8
|
-
import { resolvePreferredModel } from "./preferred-model/index.js";
|
|
9
5
|
import { createProgressIO } from "./progress/index.js";
|
|
10
|
-
import { addModelUsage,
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
6
|
+
import { addModelUsage, serializeRunState } from "./run-state/index.js";
|
|
7
|
+
import { accumulateUsage, applyReasoningRetention, countLeadingSystemMessages } from "./runtime/messages.js";
|
|
8
|
+
import { maybeAutoCompact, noopCompactionResult, tryEmergencyCompact } from "./runtime/run-compaction.js";
|
|
9
|
+
import { createCompactMethod, createFanOutMethod, createProgressSurface, createRunMethods } from "./runtime/agent-surface.js";
|
|
10
|
+
import { createToTool } from "./runtime/agent-to-tool.js";
|
|
11
|
+
import { HANDOFF_TOOL_PREFIX, isDescribedFilter } from "./runtime/handoff.js";
|
|
12
|
+
import { dispatchResumedApprovals, processResumeDirective, processSubRunResumes } from "./runtime/approvals.js";
|
|
13
|
+
import { dispatchToolBatch } from "./runtime/dispatch.js";
|
|
14
|
+
import { wireToolExecution } from "./runtime/executor-wiring.js";
|
|
15
|
+
import { runProviderFallbackChain } from "./runtime/fallback-chain.js";
|
|
16
|
+
import { createRunFinisher } from "./runtime/run-finish.js";
|
|
17
|
+
import { buildStructuredInstruction, commitAssistantMessage, emitCancellation, finalizeRunOutput, runVerifierGate, screenInputGuardrails } from "./runtime/run-gates.js";
|
|
18
|
+
import { initializeRunState, seedInitialMessages } from "./runtime/run-init.js";
|
|
19
|
+
import { asMessages, isModelHintLike, isModelSpecLike, validatePreferredModel } from "./runtime/run-input.js";
|
|
20
|
+
import { buildBaseRequest, resolveStepToolContext, toolToDefinition } from "./runtime/step-catalogue.js";
|
|
21
|
+
import { processStepToolCalls } from "./runtime/tool-call-walk.js";
|
|
22
|
+
import { NOOP_TRACER, isStepCount } from "@graphorin/core";
|
|
25
23
|
|
|
26
24
|
//#region src/factory.ts
|
|
27
25
|
/**
|
|
28
|
-
* `createAgent({...})` - the agent factory entry point.
|
|
29
|
-
*
|
|
30
|
-
* Wires the typed `model -> tool calls -> model` loop, the streamed
|
|
31
|
-
* event surface, the steering / followUp queues, durable HITL via
|
|
32
|
-
* `RunState`, the multi-agent handoff layer, the agent-level model
|
|
33
|
-
* fallback chain, and the per-tool preferred-model resolution.
|
|
34
|
-
*
|
|
35
|
-
* Custom adapters override behaviour by supplying alternative
|
|
36
|
-
* `Provider` / `Memory` / `CheckpointStore` instances; the loop
|
|
37
|
-
* never reaches into adapter internals.
|
|
38
|
-
*
|
|
39
|
-
* @packageDocumentation
|
|
40
|
-
*/
|
|
41
|
-
/**
|
|
42
|
-
* Replacement content committed in place of assistant commentary the
|
|
43
|
-
* causality monitor blocked (AG-10). Worded to not match any built-in
|
|
44
|
-
* denial pattern, so the notice itself never re-triggers the monitor.
|
|
45
|
-
*/
|
|
46
|
-
const LATERAL_LEAK_BLOCKED_NOTICE = "[graphorin] assistant commentary withheld by the lateral-leak defense.";
|
|
47
|
-
const sha256Hex = (input) => createHash("sha256").update(input, "utf8").digest("hex");
|
|
48
|
-
const HANDOFF_TOOL_PREFIX = "transfer_to_";
|
|
49
|
-
/** The built-in deferred-discovery tool's stable name (WI-05 / P0-3). */
|
|
50
|
-
const TOOL_SEARCH_NAME = "tool_search";
|
|
51
|
-
/**
|
|
52
|
-
* Register the built-in `tool_search` into `registry` when - and only
|
|
53
|
-
* when - the registry holds at least one deferred tool
|
|
54
|
-
* (`defer_loading: true`). `tool_search` is itself eager (so it is
|
|
55
|
-
* always advertised while a deferred pool exists) and resolvable by the
|
|
56
|
-
* executor like any other tool, so a model can both *see* it in the
|
|
57
|
-
* per-step catalogue and *call* it.
|
|
58
|
-
*
|
|
59
|
-
* No-op when nothing defers (zero overhead - the tool never appears) or
|
|
60
|
-
* when a user tool already occupies the name (the user's tool wins; we
|
|
61
|
-
* never clobber it). Because deferral is decided at registration time
|
|
62
|
-
* (`normaliseTool`), the deferred set is fixed for the life of the
|
|
63
|
-
* registry - this runs once per registry, not per step.
|
|
64
|
-
*/
|
|
65
|
-
function registerToolSearch(registry, availability) {
|
|
66
|
-
if (registry.listDeferred().length === 0) return;
|
|
67
|
-
if (registry.get(TOOL_SEARCH_NAME) !== void 0) return;
|
|
68
|
-
registry.register(createToolSearchTool({
|
|
69
|
-
registry,
|
|
70
|
-
...availability !== void 0 ? { availability } : {}
|
|
71
|
-
}), {
|
|
72
|
-
kind: "built-in",
|
|
73
|
-
subsystem: "tool-discovery"
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
/** The built-in result-handle reader tool's stable name (WI-10 / P1-4). */
|
|
77
|
-
const READ_RESULT_NAME = "read_result";
|
|
78
|
-
/**
|
|
79
|
-
* Register the built-in `read_result` into `registry` when at least one
|
|
80
|
-
* registered tool opts into the `'spill-to-file'` truncation strategy
|
|
81
|
-
* (the sole producer of spill handles today) - or when `force` is set,
|
|
82
|
-
* which the agent passes when an operator wires external result readers
|
|
83
|
-
* (e.g. an MCP `resource_link` reader; WI-13). The tool is eager, so it
|
|
84
|
-
* is advertised alongside the producing tool and the model can fetch a
|
|
85
|
-
* handle back on demand instead of inlining the full blob. No-op when
|
|
86
|
-
* nothing produces handles (zero overhead) or when a user tool already
|
|
87
|
-
* occupies the name (the user's tool wins).
|
|
88
|
-
*/
|
|
89
|
-
function registerReadResult(registry, reader, opts) {
|
|
90
|
-
if (opts?.force !== true && !registry.list().some((entry) => entry.truncationStrategy === "spill-to-file")) return;
|
|
91
|
-
if (registry.get(READ_RESULT_NAME) !== void 0) return;
|
|
92
|
-
registry.register(createReadResultTool({ reader }), {
|
|
93
|
-
kind: "built-in",
|
|
94
|
-
subsystem: "result-handle"
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Compose result readers into one that tries each in order, returning
|
|
99
|
-
* the first that resolves the handle (WI-13). The spill-file reader is
|
|
100
|
-
* placed first so `graphorin-spill:` handles resolve locally; operator
|
|
101
|
-
* readers (e.g. an MCP resource reader) resolve the rest. Each reader
|
|
102
|
-
* rejects handles it does not own, so resolution falls through cleanly.
|
|
103
|
-
*/
|
|
104
|
-
function composeResultReaders(readers) {
|
|
105
|
-
return { async read(uri, range) {
|
|
106
|
-
let lastError;
|
|
107
|
-
for (const r of readers) try {
|
|
108
|
-
return await r.read(uri, range);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
lastError = err;
|
|
111
|
-
}
|
|
112
|
-
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`No result reader resolved handle ${JSON.stringify(uri)}.`);
|
|
113
|
-
} };
|
|
114
|
-
}
|
|
115
|
-
/** The code-mode meta-tools' stable names (WI-11 / P1-2). */
|
|
116
|
-
const CODE_EXECUTE_NAME = "code_execute";
|
|
117
|
-
const CODE_SEARCH_NAME = "code_search";
|
|
118
|
-
/** Structural check: is this tool approval-gated (static or predicate form)? */
|
|
119
|
-
const isApprovalGated = (t) => t.needsApproval === true || typeof t.needsApproval === "function";
|
|
120
|
-
/**
|
|
121
|
-
* Wire code-mode (P1-2) into `registry`: register the `code_search` /
|
|
122
|
-
* `code_execute` meta-tools and return them as the tools to advertise in
|
|
123
|
-
* place of the full catalogue. The model reaches every other tool through
|
|
124
|
-
* `code_execute`, whose in-script `tools.<name>(args)` calls route back
|
|
125
|
-
* through `quietExecutor.executeOne(...)` under the calling step's
|
|
126
|
-
* `runContext` - so per-tool ACL / sanitization / truncation still apply,
|
|
127
|
-
* exactly as in direct mode. `quietExecutor` carries no `streamingSink`,
|
|
128
|
-
* so the inner calls do not interleave `tool.execute.*` events into the
|
|
129
|
-
* outer stream.
|
|
130
|
-
*
|
|
131
|
-
* Excluded from the code API (`reservedNames`): the meta-tools, the
|
|
132
|
-
* discovery / handle built-ins, handoff tools (which stay first-class
|
|
133
|
-
* provider tools), and - supplied by the caller - any approval-gated
|
|
134
|
-
* tool, since code-mode has no durable-HITL path to suspend mid-script.
|
|
135
|
-
*
|
|
136
|
-
* Returns `[]` (registering nothing) when no real tool is exposable.
|
|
137
|
-
*/
|
|
138
|
-
function registerCodeMode(registry, quietExecutor, reservedNames, getRunCapability) {
|
|
139
|
-
if (registry.get(CODE_EXECUTE_NAME) !== void 0) return [];
|
|
140
|
-
const codeTools = registry.list().filter((entry) => !reservedNames.has(entry.name) && !isApprovalGated(entry));
|
|
141
|
-
if (codeTools.length === 0) return [];
|
|
142
|
-
const approvalGatedTools = registry.list().filter((entry) => !reservedNames.has(entry.name) && isApprovalGated(entry)).map((entry) => entry.name);
|
|
143
|
-
const approvalGatedSet = new Set(approvalGatedTools);
|
|
144
|
-
const allowedTools = [...codeTools.map((entry) => entry.name), ...approvalGatedTools];
|
|
145
|
-
const allowedSet = new Set(codeTools.map((entry) => entry.name));
|
|
146
|
-
const projection = projectToolApi(codeTools.filter((entry) => entry.__effectiveDeferLoading !== true));
|
|
147
|
-
const executeTool = async (call, ctx) => {
|
|
148
|
-
if (approvalGatedSet.has(call.name)) throw new Error(`${call.name} requires human approval and cannot run inside code_execute - call it directly as a standalone tool call so the run can suspend for the approval.`);
|
|
149
|
-
const runCapability = getRunCapability?.();
|
|
150
|
-
const { outcome } = await quietExecutor.executeOne({
|
|
151
|
-
call: {
|
|
152
|
-
toolCallId: newId("codecall"),
|
|
153
|
-
toolName: call.name,
|
|
154
|
-
args: call.args
|
|
155
|
-
},
|
|
156
|
-
runContext: ctx.runContext,
|
|
157
|
-
stepNumber: ctx.runContext.stepNumber,
|
|
158
|
-
...runCapability !== void 0 ? { capability: runCapability } : {}
|
|
159
|
-
});
|
|
160
|
-
if ("kind" in outcome) throw new Error(`${call.name}: ${outcome.message}`);
|
|
161
|
-
return outcome.output;
|
|
162
|
-
};
|
|
163
|
-
const codeSearch = createCodeSearchTool({
|
|
164
|
-
projection,
|
|
165
|
-
approvalGatedTools,
|
|
166
|
-
searchDeferred: async (query, k) => (await registry.searchDeferred(query, k)).filter((match) => allowedSet.has(match.name))
|
|
167
|
-
});
|
|
168
|
-
const codeExecute = createCodeExecuteTool({
|
|
169
|
-
projection,
|
|
170
|
-
allowedTools,
|
|
171
|
-
executeTool,
|
|
172
|
-
approvalGatedTools
|
|
173
|
-
});
|
|
174
|
-
registry.register(codeSearch, {
|
|
175
|
-
kind: "built-in",
|
|
176
|
-
subsystem: "code-mode"
|
|
177
|
-
});
|
|
178
|
-
registry.register(codeExecute, {
|
|
179
|
-
kind: "built-in",
|
|
180
|
-
subsystem: "code-mode"
|
|
181
|
-
});
|
|
182
|
-
return [codeSearch, codeExecute];
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Fold a completed `tool_search` result into the per-run promotion set:
|
|
186
|
-
* every matched tool name becomes advertised (and thus callable) on the
|
|
187
|
-
* next step. Tolerant of unexpected shapes (e.g. a user-shadowed
|
|
188
|
-
* `tool_search`) - only string `name`s inside a `matches` array promote.
|
|
189
|
-
*/
|
|
190
|
-
function recordToolSearchPromotions(output, promoted) {
|
|
191
|
-
if (typeof output !== "object" || output === null) return;
|
|
192
|
-
const matches = output.matches;
|
|
193
|
-
if (!Array.isArray(matches)) return;
|
|
194
|
-
for (const match of matches) {
|
|
195
|
-
const name = match?.name;
|
|
196
|
-
if (typeof name === "string") promoted.add(name);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function isModelHintLike(value) {
|
|
200
|
-
return value === "fast" || value === "balanced" || value === "smart";
|
|
201
|
-
}
|
|
202
|
-
function isModelSpecLike(value) {
|
|
203
|
-
if (typeof value !== "object" || value === null) return false;
|
|
204
|
-
const v = value;
|
|
205
|
-
if (typeof v.modelId === "string" && typeof v.name === "string") return true;
|
|
206
|
-
if (typeof v.provider === "object" && v.provider !== null && typeof v.model === "string") return true;
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
function validatePreferredModel(value) {
|
|
210
|
-
if (value === void 0) return;
|
|
211
|
-
if (isModelHintLike(value)) return;
|
|
212
|
-
if (isModelSpecLike(value)) return;
|
|
213
|
-
throw new InvalidPreferredModelError(value);
|
|
214
|
-
}
|
|
215
|
-
function isMessageObject(value) {
|
|
216
|
-
if (typeof value !== "object" || value === null) return false;
|
|
217
|
-
const role = value.role;
|
|
218
|
-
return role === "system" || role === "user" || role === "assistant" || role === "tool";
|
|
219
|
-
}
|
|
220
|
-
function isRunStateObject(value) {
|
|
221
|
-
if (typeof value !== "object" || value === null) return false;
|
|
222
|
-
const v = value;
|
|
223
|
-
return typeof v.id === "string" && typeof v.agentId === "string" && Array.isArray(v.messages) && Array.isArray(v.steps);
|
|
224
|
-
}
|
|
225
|
-
function asMessages(input) {
|
|
226
|
-
if (typeof input === "string") return { seed: [{
|
|
227
|
-
role: "user",
|
|
228
|
-
content: input
|
|
229
|
-
}] };
|
|
230
|
-
if (Array.isArray(input)) return { seed: [...input] };
|
|
231
|
-
if (isMessageObject(input)) return { seed: [input] };
|
|
232
|
-
if (isRunStateObject(input)) return {
|
|
233
|
-
seed: [],
|
|
234
|
-
resumed: input
|
|
235
|
-
};
|
|
236
|
-
throw new InvalidAgentConfigError(`unrecognized AgentInput shape`);
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Strip a single Markdown code fence around a JSON payload (AG-3).
|
|
240
|
-
* Models often wrap structured output in ```json fences even when told
|
|
241
|
-
* not to. ReDoS-safe: the info string is matched with `[^\n]*`.
|
|
242
|
-
*/
|
|
243
|
-
function stripJsonFence(text) {
|
|
244
|
-
const trimmed = text.trim();
|
|
245
|
-
return /^```[^\n]*\n([\s\S]*?)\n?```$/.exec(trimmed)?.[1] ?? trimmed;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* The AG-3 fallback instruction: one trailing system message that
|
|
249
|
-
* pins JSON-only output and embeds the wire schema / description.
|
|
250
|
-
* This is the documented structured-output contract for adapters that
|
|
251
|
-
* do not yet consume `ProviderRequest.outputType` natively (PS-24).
|
|
252
|
-
*/
|
|
253
|
-
function buildStructuredInstruction(spec) {
|
|
254
|
-
const parts = ["Respond with a single valid JSON value only - no prose, no Markdown code fences."];
|
|
255
|
-
if (spec.description !== void 0 && spec.description.length > 0) parts.push(spec.description);
|
|
256
|
-
if (spec.jsonSchema !== void 0) parts.push(`The JSON MUST conform to this JSON Schema:\n${JSON.stringify(spec.jsonSchema)}`);
|
|
257
|
-
return parts.join("\n");
|
|
258
|
-
}
|
|
259
|
-
/** Most-recent user-role text in `messages` (for context-engine auto-recall). */
|
|
260
|
-
function lastUserText(messages) {
|
|
261
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
262
|
-
const m = messages[i];
|
|
263
|
-
if (m?.role !== "user") continue;
|
|
264
|
-
if (typeof m.content === "string") return m.content;
|
|
265
|
-
const text = m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
266
|
-
return text.length > 0 ? text : void 0;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* AG-21: classify a **thrown** provider error into a {@link ProviderErrorKind}
|
|
271
|
-
* so the fallback chain can act on it, instead of flattening every exception to
|
|
272
|
-
* `'unknown'` (which is always fallback-ineligible). Structural - reads the
|
|
273
|
-
* `kind` carried by `@graphorin/provider`'s `GraphorinProviderError` subclasses
|
|
274
|
-
* without importing them, keeping the agent decoupled from the provider package.
|
|
275
|
-
*/
|
|
276
|
-
/** Canonical `ProviderErrorKind` values honoured off a thrown error's `errorKind`. */
|
|
277
|
-
const CANONICAL_PROVIDER_ERROR_KINDS = new Set([
|
|
278
|
-
"rate-limit",
|
|
279
|
-
"capacity",
|
|
280
|
-
"context-length",
|
|
281
|
-
"transient",
|
|
282
|
-
"invalid-request",
|
|
283
|
-
"unauthorized",
|
|
284
|
-
"content-filter"
|
|
285
|
-
]);
|
|
286
|
-
function classifyThrownProviderErrorKind(cause) {
|
|
287
|
-
if (typeof cause === "object" && cause !== null) {
|
|
288
|
-
const errorKind = cause.errorKind;
|
|
289
|
-
if (typeof errorKind === "string" && CANONICAL_PROVIDER_ERROR_KINDS.has(errorKind)) return errorKind;
|
|
290
|
-
switch (cause.kind) {
|
|
291
|
-
case "rate-limit-exceeded": return "rate-limit";
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return "unknown";
|
|
295
|
-
}
|
|
296
|
-
/** WARN-once keys for schemas the projection cannot read (per process). */
|
|
297
|
-
const unprojectableSchemaWarned = /* @__PURE__ */ new Set();
|
|
298
|
-
/**
|
|
299
|
-
* Resolve a tool's declared schema - plain Zod (v3/v4), `toJSON()`-bearing,
|
|
300
|
-
* or already-JSON-Schema data - to a JSON Schema record via the shared
|
|
301
|
-
* projection (tools-01). Pre-fix this only honoured `toJSON()` and passed
|
|
302
|
-
* everything else through verbatim, so every plain-Zod tool serialised as
|
|
303
|
-
* `{"_def":...}` internals on OpenAI-shaped/Ollama/vercel wire bodies.
|
|
304
|
-
* `undefined` when nothing usable can be projected (caller substitutes a
|
|
305
|
-
* permissive `{}`), with a WARN so the degradation is never silent.
|
|
306
|
-
*/
|
|
307
|
-
function projectSchema(raw, toolName, slot) {
|
|
308
|
-
return projectSchemaToJsonSchema(raw, { onUnsupported: (detail) => {
|
|
309
|
-
const key = `${toolName}:${slot}:${detail}`;
|
|
310
|
-
if (unprojectableSchemaWarned.has(key)) return;
|
|
311
|
-
unprojectableSchemaWarned.add(key);
|
|
312
|
-
console.warn(`[graphorin/agent] tool '${toolName}' ${slot} schema: '${detail}' cannot be projected to JSON Schema - that fragment degrades to a permissive {} on the provider wire body.`);
|
|
313
|
-
} });
|
|
314
|
-
}
|
|
315
|
-
function toolToDefinition(tool) {
|
|
316
|
-
const ts = tool;
|
|
317
|
-
const inputSchema = projectSchema(ts.inputSchema, tool.name, "input") ?? {};
|
|
318
|
-
const outputSchema = projectSchema(ts.outputSchema, tool.name, "output");
|
|
319
|
-
const examples = renderToolExamples(tool);
|
|
320
|
-
return {
|
|
321
|
-
name: tool.name,
|
|
322
|
-
description: tool.description,
|
|
323
|
-
inputSchema,
|
|
324
|
-
...outputSchema !== void 0 ? { outputSchema } : {},
|
|
325
|
-
...examples !== void 0 ? { examples } : {}
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Project a tool's worked `examples` onto the provider wire contract
|
|
330
|
-
* (WI-06 / P2-3). Examples are rendered only when the tool eagerly
|
|
331
|
-
* renders them: the registry resolves the `defer_loading` auto-rule into
|
|
332
|
-
* `examplesEagerlyRendered`, so a deferred tool resolves to `false` and is
|
|
333
|
-
* skipped (its examples stay out of context even once `tool_search`
|
|
334
|
-
* promotes it). `undefined` - the "runtime decides" case for a plain
|
|
335
|
-
* eager tool - renders, since the tool is already advertised this step.
|
|
336
|
-
*
|
|
337
|
-
* Bounded to ≤5 to honour the `ToolExample` `[1,5]` contract even when a
|
|
338
|
-
* tool slipped past the registry's overflow WARN (which does not truncate).
|
|
339
|
-
*/
|
|
340
|
-
function renderToolExamples(tool) {
|
|
341
|
-
const examples = tool.examples;
|
|
342
|
-
if (examples === void 0 || examples.length === 0) return void 0;
|
|
343
|
-
if (tool.examplesEagerlyRendered === false) return void 0;
|
|
344
|
-
return examples.slice(0, 5).map((ex) => ({
|
|
345
|
-
input: ex.input,
|
|
346
|
-
output: ex.output,
|
|
347
|
-
...ex.comment !== void 0 ? { comment: ex.comment } : {}
|
|
348
|
-
}));
|
|
349
|
-
}
|
|
350
|
-
const PASSTHROUGH_SCHEMA = {
|
|
351
|
-
parse: (value) => value,
|
|
352
|
-
safeParse: (value) => ({
|
|
353
|
-
success: true,
|
|
354
|
-
data: value
|
|
355
|
-
}),
|
|
356
|
-
toJSON: () => ({ type: "object" })
|
|
357
|
-
};
|
|
358
|
-
function isDescribedFilter(value) {
|
|
359
|
-
return typeof value === "function" && "descriptor" in value && typeof value.descriptor === "object";
|
|
360
|
-
}
|
|
361
|
-
function buildHandoffTool(target) {
|
|
362
|
-
const cfg = target.config;
|
|
363
|
-
return {
|
|
364
|
-
name: `${HANDOFF_TOOL_PREFIX}${cfg.name}`,
|
|
365
|
-
description: `Hand off control to agent '${cfg.name}'.`,
|
|
366
|
-
inputSchema: PASSTHROUGH_SCHEMA,
|
|
367
|
-
sideEffectClass: "pure",
|
|
368
|
-
async execute() {
|
|
369
|
-
return `[handoff: ${cfg.name}]`;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
function specToProvider(spec) {
|
|
374
|
-
if ("provider" in spec) return spec.provider;
|
|
375
|
-
return spec;
|
|
376
|
-
}
|
|
377
|
-
function handleProviderEvent(ev, state) {
|
|
378
|
-
switch (ev.type) {
|
|
379
|
-
case "stream-start": return {};
|
|
380
|
-
case "reasoning-delta":
|
|
381
|
-
state.reasoningBuffer += ev.delta;
|
|
382
|
-
return { emit: {
|
|
383
|
-
type: "reasoning.delta",
|
|
384
|
-
delta: ev.delta
|
|
385
|
-
} };
|
|
386
|
-
case "text-delta":
|
|
387
|
-
state.textBuffer += ev.delta;
|
|
388
|
-
return { emit: {
|
|
389
|
-
type: "text.delta",
|
|
390
|
-
delta: ev.delta
|
|
391
|
-
} };
|
|
392
|
-
case "tool-call-start":
|
|
393
|
-
state.calls.set(ev.toolCallId, {
|
|
394
|
-
toolCallId: ev.toolCallId,
|
|
395
|
-
toolName: ev.toolName,
|
|
396
|
-
argsBuffer: ""
|
|
397
|
-
});
|
|
398
|
-
return { emit: {
|
|
399
|
-
type: "tool.call.start",
|
|
400
|
-
toolCallId: ev.toolCallId,
|
|
401
|
-
toolName: ev.toolName,
|
|
402
|
-
args: void 0
|
|
403
|
-
} };
|
|
404
|
-
case "tool-call-input-delta": {
|
|
405
|
-
const acc = state.calls.get(ev.toolCallId);
|
|
406
|
-
if (acc !== void 0) acc.argsBuffer += ev.argsDelta;
|
|
407
|
-
return { emit: {
|
|
408
|
-
type: "tool.call.delta",
|
|
409
|
-
toolCallId: ev.toolCallId,
|
|
410
|
-
argsDelta: ev.argsDelta
|
|
411
|
-
} };
|
|
412
|
-
}
|
|
413
|
-
case "tool-call-end": {
|
|
414
|
-
const acc = state.calls.get(ev.toolCallId);
|
|
415
|
-
if (acc === void 0) {
|
|
416
|
-
process.stderr.write(`[graphorin/agent] dropped tool-call-end '${ev.toolCallId}' with no matching tool-call-start.\n`);
|
|
417
|
-
return {};
|
|
418
|
-
}
|
|
419
|
-
state.finalCalls.push({
|
|
420
|
-
toolCallId: ev.toolCallId,
|
|
421
|
-
toolName: acc.toolName,
|
|
422
|
-
args: ev.finalArgs
|
|
423
|
-
});
|
|
424
|
-
return { emit: {
|
|
425
|
-
type: "tool.call.end",
|
|
426
|
-
toolCallId: ev.toolCallId,
|
|
427
|
-
finalArgs: ev.finalArgs
|
|
428
|
-
} };
|
|
429
|
-
}
|
|
430
|
-
case "file": return { emit: {
|
|
431
|
-
type: "file.generated",
|
|
432
|
-
mimeType: ev.mimeType,
|
|
433
|
-
data: ev.data
|
|
434
|
-
} };
|
|
435
|
-
case "source": return { emit: {
|
|
436
|
-
type: "source.cited",
|
|
437
|
-
uri: ev.uri,
|
|
438
|
-
...ev.title !== void 0 ? { title: ev.title } : {}
|
|
439
|
-
} };
|
|
440
|
-
case "finish": return {
|
|
441
|
-
usage: ev.usage,
|
|
442
|
-
finished: true
|
|
443
|
-
};
|
|
444
|
-
case "error": return { providerError: ev.error };
|
|
445
|
-
default: return {};
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Resolve the effective {@link ReasoningRetention} for a step. The
|
|
450
|
-
* agent-level setting wins over the provider-level default; when
|
|
451
|
-
* neither is supplied, the provider's `reasoningContract`
|
|
452
|
-
* capability drives the default per RB-42 / suggested DEC-158.
|
|
453
|
-
*/
|
|
454
|
-
function effectiveReasoningRetention(agentOverride, provider) {
|
|
455
|
-
if (agentOverride !== void 0) return agentOverride;
|
|
456
|
-
switch (provider.capabilities.reasoningContract) {
|
|
457
|
-
case "round-trip-required": return "pass-through-claude";
|
|
458
|
-
case "optional": return "pass-through-all";
|
|
459
|
-
case "hidden": return "strip";
|
|
460
|
-
default: return "strip";
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Build the assistant message that the runtime appends to the
|
|
465
|
-
* message buffer after a successful provider call. When the
|
|
466
|
-
* effective {@link ReasoningRetention} is not `'strip'`, the
|
|
467
|
-
* assembled `reasoning` content parts ride along on `content` so
|
|
468
|
-
* the next provider call honours the wire-correct round-trip
|
|
469
|
-
* contract per RB-42.
|
|
470
|
-
*/
|
|
471
|
-
function buildAssistantMessage(text, reasoningParts, toolCalls, agentId, retention) {
|
|
472
|
-
if (retention !== "strip" && reasoningParts.length > 0) {
|
|
473
|
-
const parts = [...reasoningParts];
|
|
474
|
-
if (text.length > 0) parts.push({
|
|
475
|
-
type: "text",
|
|
476
|
-
text
|
|
477
|
-
});
|
|
478
|
-
return {
|
|
479
|
-
role: "assistant",
|
|
480
|
-
content: parts,
|
|
481
|
-
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
482
|
-
agentId
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
role: "assistant",
|
|
487
|
-
content: text,
|
|
488
|
-
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
489
|
-
agentId
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Strip every {@link ReasoningContent} part from each message in
|
|
494
|
-
* the supplied list. Used at the swap point when `prepareStep`
|
|
495
|
-
* downgrades the provider's `reasoningContract` mid-run.
|
|
496
|
-
*/
|
|
497
|
-
function stripReasoningFromMessages(messages) {
|
|
498
|
-
let stripped = 0;
|
|
499
|
-
for (let i = 0; i < messages.length; i++) {
|
|
500
|
-
const msg = messages[i];
|
|
501
|
-
if (msg === void 0) continue;
|
|
502
|
-
if (msg.role === "system" || msg.role === "tool") continue;
|
|
503
|
-
if (typeof msg.content === "string") continue;
|
|
504
|
-
const filtered = msg.content.filter((p) => p.type !== "reasoning");
|
|
505
|
-
if (filtered.length === msg.content.length) continue;
|
|
506
|
-
stripped += msg.content.length - filtered.length;
|
|
507
|
-
if (msg.role === "assistant") messages[i] = {
|
|
508
|
-
...msg,
|
|
509
|
-
content: filtered
|
|
510
|
-
};
|
|
511
|
-
else messages[i] = {
|
|
512
|
-
...msg,
|
|
513
|
-
content: filtered
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
return { stripped };
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Count the leading contiguous run of `system` messages in the initial
|
|
520
|
-
* buffer - the trusted, KV-cache-stable instruction prefix. Captured
|
|
521
|
-
* once at run start (WI-09 / P1-1): auto-compaction summarises only the
|
|
522
|
-
* messages after this prefix, so the prefix stays byte-identical across
|
|
523
|
-
* every step (the provider's cache breakpoint is real) and a long run
|
|
524
|
-
* never re-pays for the system prompt.
|
|
525
|
-
*
|
|
526
|
-
* The length is fixed for the run rather than re-derived per compaction
|
|
527
|
-
* on purpose: each compaction inserts its summary as a `system` message
|
|
528
|
-
* right after the prefix, so re-scanning the leading run would absorb
|
|
529
|
-
* that summary into the prefix and shield it from the next compaction -
|
|
530
|
-
* summaries would stack unbounded. Pinning the original length keeps
|
|
531
|
-
* each prior summary inside the compactable body, where the next pass
|
|
532
|
-
* folds it into a fresh summary-of-summary.
|
|
533
|
-
*/
|
|
534
|
-
/**
|
|
535
|
-
* Marker prefix stamped on every compaction summary (see the memory
|
|
536
|
-
* package's summary template). context-engine-05: the prefix scan must
|
|
537
|
-
* stop at it - after a compact-then-suspend cycle the summary is a
|
|
538
|
-
* SYSTEM message sitting right after the true prefix, and counting it
|
|
539
|
-
* in would pin it (and every later summary) outside the compactable
|
|
540
|
-
* window forever, growing the uncompactable prefix by one summary per
|
|
541
|
-
* cycle.
|
|
542
|
-
*/
|
|
543
|
-
const COMPACTION_SUMMARY_MARKER = "<graphorin_compaction_summary";
|
|
544
|
-
function countLeadingSystemMessages(messages) {
|
|
545
|
-
let i = 0;
|
|
546
|
-
while (i < messages.length) {
|
|
547
|
-
const msg = messages[i];
|
|
548
|
-
if (msg?.role !== "system") break;
|
|
549
|
-
if (typeof msg.content === "string" && msg.content.startsWith(COMPACTION_SUMMARY_MARKER)) break;
|
|
550
|
-
i += 1;
|
|
551
|
-
}
|
|
552
|
-
return i;
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Immutable usage sum. Optional token fields (reasoning + prompt-cache
|
|
556
|
-
* legs, core-provider-02) appear in the result only when at least one
|
|
557
|
-
* side carries them, so pre-cache serialized shapes stay byte-identical.
|
|
558
|
-
*/
|
|
559
|
-
/**
|
|
560
|
-
* D6: assemble the per-step request messages. Trailing, request-only
|
|
561
|
-
* additions ride the LAST prompt-cache anchor and never touch the shared
|
|
562
|
-
* `messages` buffer or the persisted RunState: the structured-output
|
|
563
|
-
* instruction (AG-3) and the attention-recitation plan block are both
|
|
564
|
-
* appended here, in that order, so the stable prompt prefix is unchanged
|
|
565
|
-
* and only the small trailing tail is re-sent each step.
|
|
566
|
-
*/
|
|
567
|
-
function buildStepMessages(messages, structuredInstruction, todos) {
|
|
568
|
-
const out = [...messages];
|
|
569
|
-
if (structuredInstruction !== void 0) out.push({
|
|
570
|
-
role: "system",
|
|
571
|
-
content: structuredInstruction
|
|
572
|
-
});
|
|
573
|
-
const recitation = renderPlanRecitation(todos);
|
|
574
|
-
if (recitation !== null) out.push({
|
|
575
|
-
role: "system",
|
|
576
|
-
content: recitation
|
|
577
|
-
});
|
|
578
|
-
return out;
|
|
579
|
-
}
|
|
580
|
-
function addUsage(a, b) {
|
|
581
|
-
const optional = (x, y) => x === void 0 && y === void 0 ? void 0 : (x ?? 0) + (y ?? 0);
|
|
582
|
-
const reasoningTokens = optional(a.reasoningTokens, b.reasoningTokens);
|
|
583
|
-
const cachedReadTokens = optional(a.cachedReadTokens, b.cachedReadTokens);
|
|
584
|
-
const cacheWriteTokens = optional(a.cacheWriteTokens, b.cacheWriteTokens);
|
|
585
|
-
return {
|
|
586
|
-
promptTokens: a.promptTokens + b.promptTokens,
|
|
587
|
-
completionTokens: a.completionTokens + b.completionTokens,
|
|
588
|
-
totalTokens: a.totalTokens + b.totalTokens,
|
|
589
|
-
...reasoningTokens !== void 0 ? { reasoningTokens } : {},
|
|
590
|
-
...cachedReadTokens !== void 0 ? { cachedReadTokens } : {},
|
|
591
|
-
...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* C3: render a ToolError for the model. The first line keeps the
|
|
596
|
-
* long-standing `Error: <message>` shape; a bracketed second line carries
|
|
597
|
-
* the typed kind + the recovery envelope, which is what actually changes
|
|
598
|
-
* model behaviour after a failure (retry vs. fix args vs. give up).
|
|
599
|
-
*/
|
|
600
|
-
function renderToolErrorMessage(error) {
|
|
601
|
-
const parts = [`kind: ${error.kind}`];
|
|
602
|
-
if (error.recoverable !== void 0) parts.push(error.recoverable ? "recoverable: yes" : "recoverable: no");
|
|
603
|
-
if (error.recoveryHint !== void 0) parts.push(`suggested action: ${error.recoveryHint}`);
|
|
604
|
-
if (error.hint !== void 0) parts.push(error.hint);
|
|
605
|
-
return `Error: ${error.message}\n[${parts.join("; ")}]`;
|
|
606
|
-
}
|
|
607
|
-
/** In-place variant of {@link addUsage} for mutable accumulators. */
|
|
608
|
-
function accumulateUsage(target, delta) {
|
|
609
|
-
target.promptTokens += delta.promptTokens;
|
|
610
|
-
target.completionTokens += delta.completionTokens;
|
|
611
|
-
target.totalTokens += delta.totalTokens;
|
|
612
|
-
if (delta.reasoningTokens !== void 0) target.reasoningTokens = (target.reasoningTokens ?? 0) + delta.reasoningTokens;
|
|
613
|
-
if (delta.cachedReadTokens !== void 0) target.cachedReadTokens = (target.cachedReadTokens ?? 0) + delta.cachedReadTokens;
|
|
614
|
-
if (delta.cacheWriteTokens !== void 0) target.cacheWriteTokens = (target.cacheWriteTokens ?? 0) + delta.cacheWriteTokens;
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
26
|
* Build a fresh {@link Agent} from the supplied configuration.
|
|
618
27
|
*
|
|
619
28
|
* @stable
|
|
@@ -641,10 +50,12 @@ function createAgent(config) {
|
|
|
641
50
|
const subAgent = isWrappedHandoff ? entry.target : entry;
|
|
642
51
|
const userFilter = isWrappedHandoff ? entry.inputFilter : void 0;
|
|
643
52
|
const filter = isDescribedFilter(userFilter) ? userFilter : void 0;
|
|
53
|
+
const forwardEvents = isWrappedHandoff ? entry.forwardEvents : void 0;
|
|
644
54
|
const toolName = `${HANDOFF_TOOL_PREFIX}${subAgent.config.name}`;
|
|
645
55
|
handoffMap.set(toolName, {
|
|
646
56
|
agent: subAgent,
|
|
647
|
-
filter
|
|
57
|
+
filter,
|
|
58
|
+
forwardEvents
|
|
648
59
|
});
|
|
649
60
|
}
|
|
650
61
|
let pendingSteer = [];
|
|
@@ -657,91 +68,18 @@ function createAgent(config) {
|
|
|
657
68
|
let runInFlight = false;
|
|
658
69
|
const externalEventQueue = [];
|
|
659
70
|
const pendingManualCompacts = [];
|
|
660
|
-
const noopCompactionResult = (skippedReason) => ({
|
|
661
|
-
beforeTokens: 0,
|
|
662
|
-
afterTokens: 0,
|
|
663
|
-
summaryTokens: 0,
|
|
664
|
-
durationMs: 0,
|
|
665
|
-
hooksFiredCount: 0,
|
|
666
|
-
summary: "",
|
|
667
|
-
applied: false,
|
|
668
|
-
skippedReason
|
|
669
|
-
});
|
|
670
71
|
const memory = config.memory;
|
|
671
72
|
const progressIO = createProgressIO({ ...config.sensitivity !== void 0 ? { defaultSensitivity: config.sensitivity } : {} });
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
subsystem: "planning"
|
|
682
|
-
});
|
|
683
|
-
const spillWriter = createDefaultSpillWriter();
|
|
684
|
-
const fileResultReader = createFileResultReader({ artifactRoot: spillWriter.artifactRoot });
|
|
685
|
-
const externalResultReaders = config.resultReaders ?? [];
|
|
686
|
-
const resultReader = externalResultReaders.length === 0 ? fileResultReader : composeResultReaders([fileResultReader, ...externalResultReaders]);
|
|
687
|
-
registerReadResult(toolRegistry, resultReader, { force: externalResultReaders.length > 0 });
|
|
688
|
-
let activeExecutorBridge;
|
|
689
|
-
const toolApprovalGate = { request: async () => ({ granted: true }) };
|
|
690
|
-
const toolSecretResolver = buildSecretResolver();
|
|
691
|
-
const toolTokenCounter = buildToolTokenCounter();
|
|
692
|
-
const memoryGuardWiring = buildMemoryGuard(memory, memory === void 0 ? {} : { regionReader: createMemoryRegionReader(["working"], async (region) => {
|
|
693
|
-
if (region !== "working") return "";
|
|
694
|
-
const scope = {
|
|
695
|
-
userId: activeRunState?.userId ?? agentId,
|
|
696
|
-
...activeRunState?.sessionId !== void 0 ? { sessionId: activeRunState.sessionId } : {},
|
|
697
|
-
agentId
|
|
698
|
-
};
|
|
699
|
-
const working = memory.working;
|
|
700
|
-
if (working?.compile === void 0) return "";
|
|
701
|
-
try {
|
|
702
|
-
return await working.compile(scope, agentId);
|
|
703
|
-
} catch {
|
|
704
|
-
return "";
|
|
705
|
-
}
|
|
706
|
-
}) });
|
|
707
|
-
const toolMemoryGuardFactory = memoryGuardWiring.memoryGuardFactory;
|
|
708
|
-
if (memory === void 0) {
|
|
709
|
-
const guarded = (config.tools ?? []).filter((t) => t.memoryGuardTier !== void 0);
|
|
710
|
-
if (guarded.length > 0) console.warn(`[graphorin/agent] ${guarded.length} tool(s) declare memoryGuardTier but no memory is wired - the DEC-153 snapshot/verify guard is skipped (SDF-1). Wire \`memory\` to activate it.`);
|
|
711
|
-
}
|
|
712
|
-
const toolDataFlowGuard = config.dataFlowPolicy !== void 0 && config.dataFlowPolicy.mode !== "off" ? buildDataFlowGuard(config.dataFlowPolicy) : void 0;
|
|
713
|
-
const { guard: toolArgumentPolicyGuard, capabilityFloor: ruleOfTwoCapabilityFloor } = buildToolArgumentPolicy(config.toolPolicy, config.ruleOfTwo);
|
|
714
|
-
const toolStreamingSink = (event) => activeExecutorBridge?.sink(event);
|
|
715
|
-
const makeToolExecutor = (registry, opts) => createToolExecutor({
|
|
716
|
-
registry,
|
|
717
|
-
approvalGate: toolApprovalGate,
|
|
718
|
-
secretResolver: toolSecretResolver,
|
|
719
|
-
tokenCounter: toolTokenCounter,
|
|
720
|
-
memoryGuardFactory: toolMemoryGuardFactory,
|
|
721
|
-
...memoryGuardWiring.memoryRegionReader !== void 0 ? { memoryRegionReader: memoryGuardWiring.memoryRegionReader } : {},
|
|
722
|
-
spill: spillWriter,
|
|
723
|
-
...toolDataFlowGuard !== void 0 ? { dataFlowGuard: toolDataFlowGuard } : {},
|
|
724
|
-
...toolArgumentPolicyGuard !== void 0 ? { argumentPolicy: toolArgumentPolicyGuard } : {},
|
|
725
|
-
...opts?.quiet === true ? {} : { streamingSink: toolStreamingSink },
|
|
726
|
-
...config.maxParallelTools !== void 0 ? { maxParallelTools: config.maxParallelTools } : {},
|
|
727
|
-
...config.toolRetry !== void 0 ? { retry: config.toolRetry } : {}
|
|
73
|
+
const executorBridgeSlot = { current: void 0 };
|
|
74
|
+
const { toolRegistry, spillWriter, resultReader, makeToolExecutor, toolExecutor, toolDataFlowGuard, ruleOfTwoCapabilityFloor, isCodeMode, codeModeAdvertised } = wireToolExecution({
|
|
75
|
+
config,
|
|
76
|
+
memory,
|
|
77
|
+
agentId,
|
|
78
|
+
handoffToolNames: handoffMap.keys(),
|
|
79
|
+
getActiveRunState: () => activeRunState,
|
|
80
|
+
getActiveRunCapability: () => activeRunCapability,
|
|
81
|
+
executorBridgeSlot
|
|
728
82
|
});
|
|
729
|
-
const toolExecutor = makeToolExecutor(toolRegistry);
|
|
730
|
-
const isCodeMode = config.toolInvocation === "code-mode";
|
|
731
|
-
let codeModeAdvertised = [];
|
|
732
|
-
if (isCodeMode) {
|
|
733
|
-
const reserved = new Set([
|
|
734
|
-
CODE_EXECUTE_NAME,
|
|
735
|
-
CODE_SEARCH_NAME,
|
|
736
|
-
TOOL_SEARCH_NAME,
|
|
737
|
-
READ_RESULT_NAME,
|
|
738
|
-
...handoffMap.keys()
|
|
739
|
-
]);
|
|
740
|
-
const metas = registerCodeMode(toolRegistry, makeToolExecutor(toolRegistry, { quiet: true }), reserved, () => activeRunCapability);
|
|
741
|
-
registerReadResult(toolRegistry, resultReader);
|
|
742
|
-
const readResult = toolRegistry.get(READ_RESULT_NAME);
|
|
743
|
-
codeModeAdvertised = [...metas, ...readResult !== void 0 ? [readResult] : []];
|
|
744
|
-
}
|
|
745
83
|
const causalityMonitor = config.causalityMonitor ? new CausalityMonitor(config.causalityMonitor) : void 0;
|
|
746
84
|
/**
|
|
747
85
|
* AG-11: one in-flight run per Agent instance. `steer` / `followUp` /
|
|
@@ -783,17 +121,14 @@ function createAgent(config) {
|
|
|
783
121
|
if (parentSignal !== void 0) if (parentSignal.aborted) localCtl.abort();
|
|
784
122
|
else parentSignal.addEventListener("abort", onParentAbort);
|
|
785
123
|
const usageAcc = new InMemoryUsageAccumulator();
|
|
786
|
-
const state
|
|
787
|
-
|
|
124
|
+
const { state, promotedDeferred, runStartPromotions } = initializeRunState({
|
|
125
|
+
config,
|
|
788
126
|
agentId,
|
|
789
127
|
sessionId,
|
|
790
|
-
|
|
791
|
-
|
|
128
|
+
userId,
|
|
129
|
+
toolDataFlowGuard
|
|
130
|
+
}, resumed);
|
|
792
131
|
activeRunState = state;
|
|
793
|
-
if (resumed && state.taintSummary !== void 0) toolDataFlowGuard?.seedLedger(state.id, state.taintSummary);
|
|
794
|
-
const promotedDeferred = /* @__PURE__ */ new Set();
|
|
795
|
-
if (resumed && state.promotedTools !== void 0) for (const name of state.promotedTools) promotedDeferred.add(name);
|
|
796
|
-
const runStartPromotions = config.toolPromotion === "run-boundary" ? new Set(promotedDeferred) : void 0;
|
|
797
132
|
async function* finishRun(s, snapshot) {
|
|
798
133
|
const taintSnap = toolDataFlowGuard?.snapshotLedger(s.id);
|
|
799
134
|
if (taintSnap !== void 0) s.taintSummary = taintSnap;
|
|
@@ -801,49 +136,22 @@ function createAgent(config) {
|
|
|
801
136
|
return yield* finishRunBase(s, snapshot);
|
|
802
137
|
}
|
|
803
138
|
const messages = resumed ? [...state.messages] : [];
|
|
804
|
-
if (!resumed) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
usage: usageAcc,
|
|
817
|
-
stepNumber: 0,
|
|
818
|
-
messages,
|
|
819
|
-
state
|
|
820
|
-
});
|
|
821
|
-
let systemPrompt = instructionsText;
|
|
822
|
-
if (config.autoAssembleContext === true && memory !== void 0) {
|
|
823
|
-
const lastUser = lastUserText(seed);
|
|
824
|
-
systemPrompt = (await memory.contextEngine.assemble(memory, {
|
|
825
|
-
scope: {
|
|
826
|
-
userId: userId ?? agentId,
|
|
827
|
-
sessionId,
|
|
828
|
-
agentId
|
|
829
|
-
},
|
|
830
|
-
agentId,
|
|
831
|
-
sessionId,
|
|
832
|
-
runId: state.id,
|
|
833
|
-
...instructionsText.length > 0 ? { agentInstructions: instructionsText } : {},
|
|
834
|
-
...lastUser !== void 0 ? { lastUserMessage: lastUser } : {}
|
|
835
|
-
})).systemMessage.content;
|
|
836
|
-
}
|
|
837
|
-
if (systemPrompt.length > 0) messages.push({
|
|
838
|
-
role: "system",
|
|
839
|
-
content: systemPrompt
|
|
840
|
-
});
|
|
841
|
-
messages.push(...seed);
|
|
842
|
-
for (const m of messages) state.messages.push(m);
|
|
843
|
-
}
|
|
139
|
+
if (!resumed) await seedInitialMessages({
|
|
140
|
+
config,
|
|
141
|
+
options,
|
|
142
|
+
memory,
|
|
143
|
+
agentId,
|
|
144
|
+
sessionId,
|
|
145
|
+
userId,
|
|
146
|
+
tracer,
|
|
147
|
+
signal,
|
|
148
|
+
usageAcc,
|
|
149
|
+
state
|
|
150
|
+
}, messages, seed);
|
|
844
151
|
const finalSnapshot = { output: "" };
|
|
845
152
|
const runSpan = tracer.startSpan({
|
|
846
153
|
type: "agent.run",
|
|
154
|
+
...options.parentSpan !== void 0 ? { parent: options.parentSpan } : {},
|
|
847
155
|
attrs: {
|
|
848
156
|
"gen_ai.operation.name": "invoke_agent",
|
|
849
157
|
"gen_ai.agent.id": agentId,
|
|
@@ -860,94 +168,22 @@ function createAgent(config) {
|
|
|
860
168
|
agentId
|
|
861
169
|
};
|
|
862
170
|
const inputGuards = config.guardrails?.input;
|
|
863
|
-
if (!resumed && inputGuards !== void 0 && inputGuards.length > 0)
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
stage: "input",
|
|
868
|
-
runId: state.id,
|
|
171
|
+
if (!resumed && inputGuards !== void 0 && inputGuards.length > 0) {
|
|
172
|
+
if (yield* screenInputGuardrails({
|
|
173
|
+
state,
|
|
174
|
+
messages,
|
|
869
175
|
sessionId,
|
|
870
176
|
agentId
|
|
871
|
-
});
|
|
872
|
-
if (!composed.ok) {
|
|
873
|
-
yield {
|
|
874
|
-
type: "guardrail.tripped",
|
|
875
|
-
guardrailName: composed.name,
|
|
876
|
-
phase: "input",
|
|
877
|
-
reason: composed.message
|
|
878
|
-
};
|
|
879
|
-
const message = `input guardrail '${composed.name}' blocked the run: ${composed.message}`;
|
|
880
|
-
yield {
|
|
881
|
-
type: "agent.error",
|
|
882
|
-
error: {
|
|
883
|
-
message,
|
|
884
|
-
code: "guardrail-blocked"
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
state.status = "failed";
|
|
888
|
-
state.error = {
|
|
889
|
-
message,
|
|
890
|
-
code: "guardrail-blocked"
|
|
891
|
-
};
|
|
892
|
-
return yield* finishRun(state, finalSnapshot);
|
|
893
|
-
}
|
|
894
|
-
if (composed.value !== msg.content) {
|
|
895
|
-
const rewritten = {
|
|
896
|
-
...msg,
|
|
897
|
-
content: composed.value
|
|
898
|
-
};
|
|
899
|
-
const stateIdx = state.messages.indexOf(msg);
|
|
900
|
-
messages[i] = rewritten;
|
|
901
|
-
if (stateIdx !== -1) state.messages[stateIdx] = rewritten;
|
|
902
|
-
}
|
|
177
|
+
}, inputGuards)) return yield* finishRun(state, finalSnapshot);
|
|
903
178
|
}
|
|
904
179
|
const structuredInstruction = config.outputType?.kind === "structured" ? buildStructuredInstruction(config.outputType) : void 0;
|
|
905
180
|
const resumedApprovedCalls = [];
|
|
906
181
|
const grantedApprovals = [];
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
for (const approval of state.pendingApprovals) {
|
|
913
|
-
const decision = decisions.get(approval.toolCallId);
|
|
914
|
-
if (decision === void 0) {
|
|
915
|
-
stillPending.push(approval);
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
if (decision.granted) {
|
|
919
|
-
yield {
|
|
920
|
-
type: "tool.approval.granted",
|
|
921
|
-
toolCallId: approval.toolCallId
|
|
922
|
-
};
|
|
923
|
-
if (journaledCallIds.has(approval.toolCallId) && messages.some((m) => m.role === "tool" && m.toolCallId === approval.toolCallId)) continue;
|
|
924
|
-
resumedApprovedCalls.push({
|
|
925
|
-
toolCallId: approval.toolCallId,
|
|
926
|
-
toolName: approval.toolName,
|
|
927
|
-
args: approval.args
|
|
928
|
-
});
|
|
929
|
-
grantedApprovals.push(approval);
|
|
930
|
-
} else {
|
|
931
|
-
yield {
|
|
932
|
-
type: "tool.approval.denied",
|
|
933
|
-
toolCallId: approval.toolCallId,
|
|
934
|
-
...decision.reason !== void 0 ? { reason: decision.reason } : {}
|
|
935
|
-
};
|
|
936
|
-
messages.push({
|
|
937
|
-
role: "tool",
|
|
938
|
-
toolCallId: approval.toolCallId,
|
|
939
|
-
content: `Error: tool approval denied${decision.reason ? `: ${decision.reason}` : ""}`
|
|
940
|
-
});
|
|
941
|
-
state.messages.push({
|
|
942
|
-
role: "tool",
|
|
943
|
-
toolCallId: approval.toolCallId,
|
|
944
|
-
content: `Error: tool approval denied${decision.reason ? `: ${decision.reason}` : ""}`
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
state.pendingApprovals.splice(0, state.pendingApprovals.length, ...stillPending);
|
|
949
|
-
if (stillPending.length === 0) state.status = "running";
|
|
950
|
-
}
|
|
182
|
+
const subRunDecisions = /* @__PURE__ */ new Map();
|
|
183
|
+
if (resumed && options.directive?.approvals !== void 0 && state.pendingApprovals.length > 0) yield* processResumeDirective({
|
|
184
|
+
state,
|
|
185
|
+
messages
|
|
186
|
+
}, options.directive.approvals, resumedApprovedCalls, grantedApprovals, subRunDecisions);
|
|
951
187
|
const systemPrefixLength = countLeadingSystemMessages(messages);
|
|
952
188
|
const runContextBase = {
|
|
953
189
|
runId: state.id,
|
|
@@ -962,378 +198,50 @@ function createAgent(config) {
|
|
|
962
198
|
messages,
|
|
963
199
|
state
|
|
964
200
|
};
|
|
201
|
+
const runEnv = {
|
|
202
|
+
config,
|
|
203
|
+
options,
|
|
204
|
+
memory,
|
|
205
|
+
state,
|
|
206
|
+
messages,
|
|
207
|
+
sessionId,
|
|
208
|
+
agentId,
|
|
209
|
+
userId,
|
|
210
|
+
signal,
|
|
211
|
+
usageAcc,
|
|
212
|
+
stopWhen,
|
|
213
|
+
fallbackPolicy,
|
|
214
|
+
structuredInstruction,
|
|
215
|
+
systemPrefixLength,
|
|
216
|
+
runContextBase,
|
|
217
|
+
handoffMap,
|
|
218
|
+
handoffNames: Array.from(handoffMap.keys()),
|
|
219
|
+
isCodeMode,
|
|
220
|
+
toolRegistry,
|
|
221
|
+
toolExecutor,
|
|
222
|
+
makeToolExecutor,
|
|
223
|
+
resultReader,
|
|
224
|
+
codeModeAdvertised,
|
|
225
|
+
causalityMonitor,
|
|
226
|
+
toolDataFlowGuard,
|
|
227
|
+
promotedDeferred,
|
|
228
|
+
runStartPromotions,
|
|
229
|
+
activeRunCapability,
|
|
230
|
+
executorBridgeSlot,
|
|
231
|
+
pendingManualCompacts,
|
|
232
|
+
getPendingAbort: () => pendingAbort,
|
|
233
|
+
getCurrentStepSpan: () => currentStepSpan,
|
|
234
|
+
getActiveTodos: () => activeRunState?.todos,
|
|
235
|
+
tryEmergencyCompact: () => tryEmergencyCompact(runEnv),
|
|
236
|
+
dispatchBatch: (calls, executor, runContext, stepNum, dispatchOpts) => dispatchToolBatch(runEnv, calls, executor, runContext, stepNum, dispatchOpts)
|
|
237
|
+
};
|
|
965
238
|
if (resumed && state.status === "failed") return yield* finishRun(state, finalSnapshot);
|
|
966
|
-
if (resumed && resumedApprovedCalls.length > 0)
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
state.pendingApprovals.unshift(...grantedApprovals);
|
|
971
|
-
const intentState = serializeRunState(state, { stripTracingApiKey: true });
|
|
972
|
-
state.pendingApprovals.splice(0, grantedApprovals.length);
|
|
973
|
-
state.status = prevStatus;
|
|
974
|
-
await config.checkpointStore.put(state.id, "agent", {
|
|
975
|
-
id: state.id,
|
|
976
|
-
threadId: state.id,
|
|
977
|
-
namespace: "agent",
|
|
978
|
-
state: intentState,
|
|
979
|
-
channelVersions: {},
|
|
980
|
-
stepNumber: 0,
|
|
981
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
982
|
-
}, {
|
|
983
|
-
source: "sync",
|
|
984
|
-
status: "suspended",
|
|
985
|
-
nodeName: "agent.resume.intent"
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
state.steps.push({
|
|
989
|
-
stepNumber: 0,
|
|
990
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
991
|
-
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
992
|
-
usage: zeroUsage(),
|
|
993
|
-
toolCalls: [],
|
|
994
|
-
agentId: state.currentAgentId
|
|
995
|
-
});
|
|
996
|
-
yield* dispatchBatch(resumedApprovedCalls, toolExecutor, {
|
|
997
|
-
...runContextBase,
|
|
998
|
-
stepNumber: 0,
|
|
999
|
-
messages
|
|
1000
|
-
}, 0, { disableRepair: true });
|
|
1001
|
-
if (config.checkpointStore !== void 0) await config.checkpointStore.put(state.id, "agent", {
|
|
1002
|
-
id: state.id,
|
|
1003
|
-
threadId: state.id,
|
|
1004
|
-
namespace: "agent",
|
|
1005
|
-
state: serializeRunState(state, { stripTracingApiKey: true }),
|
|
1006
|
-
channelVersions: {},
|
|
1007
|
-
stepNumber: 0,
|
|
1008
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1009
|
-
}, {
|
|
1010
|
-
source: "sync",
|
|
1011
|
-
status: state.status === "awaiting_approval" ? "suspended" : "running",
|
|
1012
|
-
nodeName: "agent.resume.dispatched"
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
if (resumed && state.status === "awaiting_approval") return yield* finishRun(state, finalSnapshot);
|
|
1016
|
-
/**
|
|
1017
|
-
* Dispatch a batch of (non-handoff) tool calls through the
|
|
1018
|
-
* {@link ToolExecutor} and surface the results as `AgentEvent`s.
|
|
1019
|
-
*
|
|
1020
|
-
* The agent owns the `tool.execute.start` / `.end` / `.error`
|
|
1021
|
-
* lifecycle (derived deterministically from the returned
|
|
1022
|
-
* {@link CompletedToolCall} outcomes) so every outcome kind -
|
|
1023
|
-
* success, unknown-tool, invalid-input, sanitization-blocked,
|
|
1024
|
-
* execution error - yields a consistent event and tool message,
|
|
1025
|
-
* preserving the pre-WI-03 stream shape (R10). The executor's
|
|
1026
|
-
* genuinely-live streaming events (`tool.execute.progress` /
|
|
1027
|
-
* `.partial`, emitted only by streaming-hint tools) are bridged
|
|
1028
|
-
* through Adapter E while the batch runs and are purely additive.
|
|
1029
|
-
*
|
|
1030
|
-
* Parallelism (WI-04): the executor runs independent calls in this
|
|
1031
|
-
* batch concurrently, bounded by `maxParallelTools`. `tool.execute.start`
|
|
1032
|
-
* is emitted up-front in call order and `.end` / `.error` after the
|
|
1033
|
-
* batch settles, also in call order - so result mapping and tool-message
|
|
1034
|
-
* history are deterministic regardless of which call finishes first,
|
|
1035
|
-
* while `.progress` / `.partial` events for concurrent calls interleave
|
|
1036
|
-
* (keyed by `toolCallId`). Tools declaring `executionMode: 'sequential'`
|
|
1037
|
-
* are serialised by the executor and never overlap.
|
|
1038
|
-
*/
|
|
1039
|
-
async function* dispatchBatch(calls, executor, runContext, stepNum, dispatchOpts = {}) {
|
|
1040
|
-
if (calls.length === 0) return;
|
|
1041
|
-
for (const call of calls) yield {
|
|
1042
|
-
type: "tool.execute.start",
|
|
1043
|
-
toolCallId: call.toolCallId
|
|
1044
|
-
};
|
|
1045
|
-
const bridge = createExecutorEventBridge();
|
|
1046
|
-
activeExecutorBridge = bridge;
|
|
1047
|
-
const resultsPromise = executor.executeBatch({
|
|
1048
|
-
calls,
|
|
1049
|
-
runContext,
|
|
1050
|
-
stepNumber: stepNum,
|
|
1051
|
-
...dispatchOpts.disableRepair !== void 0 ? { disableRepair: dispatchOpts.disableRepair } : {},
|
|
1052
|
-
...activeRunCapability !== void 0 ? { capability: activeRunCapability } : {}
|
|
1053
|
-
});
|
|
1054
|
-
const closeOnSettle = resultsPromise.then(() => bridge.close(), () => bridge.close());
|
|
1055
|
-
for await (const event of bridge.drain()) if (event.type === "tool.execute.progress" || event.type === "tool.execute.partial") yield event;
|
|
1056
|
-
await closeOnSettle;
|
|
1057
|
-
activeExecutorBridge = void 0;
|
|
1058
|
-
const completed = await resultsPromise;
|
|
1059
|
-
const byCallId = new Map(completed.map((c) => [c.outcome.toolCallId, c]));
|
|
1060
|
-
const stepEntry = state.steps[state.steps.length - 1];
|
|
1061
|
-
for (const call of calls) {
|
|
1062
|
-
const result = byCallId.get(call.toolCallId);
|
|
1063
|
-
if (result === void 0) continue;
|
|
1064
|
-
if (stepEntry !== void 0) stepEntry.toolCalls.push(result);
|
|
1065
|
-
const outcome = result.outcome;
|
|
1066
|
-
if ("kind" in outcome) {
|
|
1067
|
-
yield {
|
|
1068
|
-
type: "tool.execute.error",
|
|
1069
|
-
toolCallId: call.toolCallId,
|
|
1070
|
-
error: outcome
|
|
1071
|
-
};
|
|
1072
|
-
const text = renderToolErrorMessage(outcome);
|
|
1073
|
-
messages.push({
|
|
1074
|
-
role: "tool",
|
|
1075
|
-
toolCallId: call.toolCallId,
|
|
1076
|
-
content: text
|
|
1077
|
-
});
|
|
1078
|
-
state.messages.push({
|
|
1079
|
-
role: "tool",
|
|
1080
|
-
toolCallId: call.toolCallId,
|
|
1081
|
-
content: text
|
|
1082
|
-
});
|
|
1083
|
-
causalityMonitor?.recordCall(`tool.error:${call.toolName}`);
|
|
1084
|
-
} else {
|
|
1085
|
-
const output = outcome.output;
|
|
1086
|
-
yield {
|
|
1087
|
-
type: "tool.execute.end",
|
|
1088
|
-
toolCallId: call.toolCallId,
|
|
1089
|
-
result: output,
|
|
1090
|
-
durationMs: outcome.durationMs
|
|
1091
|
-
};
|
|
1092
|
-
const handle = outcome.resultHandle;
|
|
1093
|
-
const rendered = handle !== void 0 ? `${handle.preview}\n\n[Full result${handle.bytes !== void 0 ? ` (${handle.bytes} bytes)` : ""} stored behind a handle. Call read_result with handle ${JSON.stringify(handle.uri)} to retrieve it - optionally narrow with offset/length (bytes) or startLine/endLine.]` : typeof output === "string" ? output : JSON.stringify(output);
|
|
1094
|
-
const text = rendered === void 0 || rendered.trim().length === 0 ? "(tool ran successfully with no output)" : rendered;
|
|
1095
|
-
messages.push({
|
|
1096
|
-
role: "tool",
|
|
1097
|
-
toolCallId: call.toolCallId,
|
|
1098
|
-
content: text
|
|
1099
|
-
});
|
|
1100
|
-
state.messages.push({
|
|
1101
|
-
role: "tool",
|
|
1102
|
-
toolCallId: call.toolCallId,
|
|
1103
|
-
content: text
|
|
1104
|
-
});
|
|
1105
|
-
causalityMonitor?.recordCall(`tool:${call.toolName}`);
|
|
1106
|
-
if (call.toolName === TOOL_SEARCH_NAME) recordToolSearchPromotions(output, promotedDeferred);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
/**
|
|
1111
|
-
* Auto-compaction trigger (WI-09 / P1-1). Before assembling each
|
|
1112
|
-
* provider request, ask the memory {@link ContextEngine} whether the
|
|
1113
|
-
* in-flight buffer has crossed its per-provider threshold
|
|
1114
|
-
* (`shouldCompact`); when it has, summarise the older turns
|
|
1115
|
-
* (`compactNow`, `source: 'auto-trigger'`), splice the result back in
|
|
1116
|
-
* - preserving the byte-stable system prefix and the most-recent
|
|
1117
|
-
* turns verbatim - and emit `context.compacted`. The compaction is
|
|
1118
|
-
* configured on the memory facade (`createMemory({ contextEngine })`,
|
|
1119
|
-
* RB-46); there is no parallel agent-level knob.
|
|
1120
|
-
*
|
|
1121
|
-
* No-op when no memory is wired, when compaction is disabled or below
|
|
1122
|
-
* threshold (the engine returns `false`), or for `secret`-tier runs
|
|
1123
|
-
* (secret history is never shipped to the summarizer - a less-trusted
|
|
1124
|
-
* external sink; per-result handle references land in WI-10). Best
|
|
1125
|
-
* effort: a misconfigured engine (e.g. no summarizer) is swallowed and
|
|
1126
|
-
* the run proceeds uncompacted rather than aborting mid-flight.
|
|
1127
|
-
*
|
|
1128
|
-
* Operator-requested compactions (`agent.compact()`, CE-3/AG-13) are
|
|
1129
|
-
* serviced here too, FIRST - the queue carries the `compact()` promise
|
|
1130
|
-
* resolvers, and manual requests bypass the trigger evaluation.
|
|
1131
|
-
*/
|
|
1132
|
-
async function* maybeAutoCompact() {
|
|
1133
|
-
while (pendingManualCompacts.length > 0) {
|
|
1134
|
-
const pending = pendingManualCompacts.shift();
|
|
1135
|
-
if (pending !== void 0) yield* serviceManualCompact(pending);
|
|
1136
|
-
}
|
|
1137
|
-
const mem = memory;
|
|
1138
|
-
if (mem === void 0) return;
|
|
1139
|
-
if (config.sensitivity === "secret") return;
|
|
1140
|
-
const engine = mem.contextEngine;
|
|
1141
|
-
if (!await engine.shouldCompact(messages, { compactableFromIndex: systemPrefixLength }).catch(() => false)) return;
|
|
1142
|
-
const startedAt = Date.now();
|
|
1143
|
-
const envelope = await engine.compactNow({
|
|
1144
|
-
scope: {
|
|
1145
|
-
userId: state.userId ?? agentId,
|
|
1146
|
-
sessionId,
|
|
1147
|
-
agentId
|
|
1148
|
-
},
|
|
1149
|
-
runId: state.id,
|
|
1150
|
-
sessionId,
|
|
1151
|
-
agentId,
|
|
1152
|
-
source: "auto-trigger",
|
|
1153
|
-
messages: messages.slice(systemPrefixLength),
|
|
1154
|
-
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
1155
|
-
memory: mem
|
|
1156
|
-
}).catch(() => void 0);
|
|
1157
|
-
if (envelope === void 0) return;
|
|
1158
|
-
if (envelope.result.droppedMessageIndices.length === 0) return;
|
|
1159
|
-
spliceCompacted(envelope);
|
|
1160
|
-
yield {
|
|
1161
|
-
type: "context.compacted",
|
|
1162
|
-
runId: state.id,
|
|
1163
|
-
sessionId,
|
|
1164
|
-
agentId,
|
|
1165
|
-
beforeTokens: envelope.result.beforeTokens,
|
|
1166
|
-
afterTokens: envelope.result.afterTokens,
|
|
1167
|
-
summaryTokens: envelope.result.summaryTokens,
|
|
1168
|
-
durationMs: Date.now() - startedAt,
|
|
1169
|
-
source: "auto-trigger",
|
|
1170
|
-
hooksFiredCount: envelope.result.hooksFiredCount
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
/**
|
|
1174
|
-
* context-engine-06: last-resort tier at hard context overflow. When
|
|
1175
|
-
* a provider rejects the request as over-window, force ONE aggressive
|
|
1176
|
-
* compaction (`preserveRecentTurns: 2`, trigger evaluation bypassed)
|
|
1177
|
-
* and let the caller retry the same provider - the fallback chain's
|
|
1178
|
-
* members usually share the same window, so without this the run just
|
|
1179
|
-
* dies. Returns `true` when the buffer actually shrank (retry is
|
|
1180
|
-
* worthwhile); `false` when memory is not wired, the run is
|
|
1181
|
-
* secret-tier, compaction trimmed nothing, or the engine threw.
|
|
1182
|
-
*/
|
|
1183
|
-
async function* tryEmergencyCompact() {
|
|
1184
|
-
const mem = memory;
|
|
1185
|
-
if (mem === void 0 || config.sensitivity === "secret") return false;
|
|
1186
|
-
const startedAt = Date.now();
|
|
1187
|
-
const envelope = await mem.contextEngine.compactNow({
|
|
1188
|
-
scope: {
|
|
1189
|
-
userId: state.userId ?? agentId,
|
|
1190
|
-
sessionId,
|
|
1191
|
-
agentId
|
|
1192
|
-
},
|
|
1193
|
-
runId: state.id,
|
|
1194
|
-
sessionId,
|
|
1195
|
-
agentId,
|
|
1196
|
-
source: "auto-trigger",
|
|
1197
|
-
messages: messages.slice(systemPrefixLength),
|
|
1198
|
-
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
1199
|
-
memory: mem,
|
|
1200
|
-
preserveRecentTurns: 2
|
|
1201
|
-
}).catch(() => void 0);
|
|
1202
|
-
if (envelope === void 0 || envelope.result.droppedMessageIndices.length === 0) return false;
|
|
1203
|
-
spliceCompacted(envelope);
|
|
1204
|
-
yield {
|
|
1205
|
-
type: "context.compacted",
|
|
1206
|
-
runId: state.id,
|
|
1207
|
-
sessionId,
|
|
1208
|
-
agentId,
|
|
1209
|
-
beforeTokens: envelope.result.beforeTokens,
|
|
1210
|
-
afterTokens: envelope.result.afterTokens,
|
|
1211
|
-
summaryTokens: envelope.result.summaryTokens,
|
|
1212
|
-
durationMs: Date.now() - startedAt,
|
|
1213
|
-
source: "auto-trigger",
|
|
1214
|
-
hooksFiredCount: envelope.result.hooksFiredCount
|
|
1215
|
-
};
|
|
1216
|
-
return true;
|
|
1217
|
-
}
|
|
1218
|
-
/**
|
|
1219
|
-
* Prefix-pinned splice shared by the auto + manual compaction paths
|
|
1220
|
-
* (CE-3): stable system prefix + [summary, ...recent turns], with the
|
|
1221
|
-
* post-compaction hooks' text Context Essentials re-anchored as a
|
|
1222
|
-
* trailing system message so they survive the trim (RB-46). Mutates
|
|
1223
|
-
* BOTH the live loop buffer and `state.messages`.
|
|
1224
|
-
*/
|
|
1225
|
-
function spliceCompacted(envelope) {
|
|
1226
|
-
const rebuilt = [...messages.slice(0, systemPrefixLength), ...envelope.result.trimmedMessages];
|
|
1227
|
-
const essentials = envelope.extraContent.map((part) => typeof part === "object" && part !== null && "text" in part ? String(part.text) : "").filter((text) => text.length > 0).join("\n\n");
|
|
1228
|
-
if (essentials.length > 0) rebuilt.push({
|
|
1229
|
-
role: "system",
|
|
1230
|
-
content: essentials
|
|
1231
|
-
});
|
|
1232
|
-
messages.splice(0, messages.length, ...rebuilt);
|
|
1233
|
-
state.messages.splice(0, state.messages.length, ...rebuilt);
|
|
1234
|
-
}
|
|
1235
|
-
/**
|
|
1236
|
-
* Service one `agent.compact()` request inside the loop (CE-3/AG-13):
|
|
1237
|
-
* same prefix-pinned splice as the auto path, `source: 'manual'` (or
|
|
1238
|
-
* the caller's `'pre-step'`), `preserveRecentTurns` forwarded as a
|
|
1239
|
-
* per-call strategy override. An engine failure rejects the caller's
|
|
1240
|
-
* promise but never aborts the live run; a summarize that trims
|
|
1241
|
-
* nothing resolves `applied: false` without an event.
|
|
1242
|
-
*/
|
|
1243
|
-
async function* serviceManualCompact(pending) {
|
|
1244
|
-
const mem = memory;
|
|
1245
|
-
if (mem === void 0) {
|
|
1246
|
-
pending.resolve(noopCompactionResult("no-memory"));
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1249
|
-
const source = pending.options?.source ?? "manual";
|
|
1250
|
-
const startedAt = Date.now();
|
|
1251
|
-
let envelope;
|
|
1252
|
-
try {
|
|
1253
|
-
envelope = await mem.contextEngine.compactNow({
|
|
1254
|
-
scope: {
|
|
1255
|
-
userId: state.userId ?? agentId,
|
|
1256
|
-
sessionId,
|
|
1257
|
-
agentId
|
|
1258
|
-
},
|
|
1259
|
-
runId: state.id,
|
|
1260
|
-
sessionId,
|
|
1261
|
-
agentId,
|
|
1262
|
-
source,
|
|
1263
|
-
messages: messages.slice(systemPrefixLength),
|
|
1264
|
-
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
1265
|
-
memory: mem,
|
|
1266
|
-
...pending.options?.preserveRecentTurns !== void 0 ? { preserveRecentTurns: pending.options.preserveRecentTurns } : {}
|
|
1267
|
-
});
|
|
1268
|
-
} catch (cause) {
|
|
1269
|
-
pending.reject(cause);
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
const { result } = envelope;
|
|
1273
|
-
const applied = result.droppedMessageIndices.length > 0;
|
|
1274
|
-
if (applied) {
|
|
1275
|
-
spliceCompacted(envelope);
|
|
1276
|
-
yield {
|
|
1277
|
-
type: "context.compacted",
|
|
1278
|
-
runId: state.id,
|
|
1279
|
-
sessionId,
|
|
1280
|
-
agentId,
|
|
1281
|
-
beforeTokens: result.beforeTokens,
|
|
1282
|
-
afterTokens: result.afterTokens,
|
|
1283
|
-
summaryTokens: result.summaryTokens,
|
|
1284
|
-
durationMs: Date.now() - startedAt,
|
|
1285
|
-
source,
|
|
1286
|
-
hooksFiredCount: result.hooksFiredCount
|
|
1287
|
-
};
|
|
1288
|
-
}
|
|
1289
|
-
pending.resolve({
|
|
1290
|
-
beforeTokens: result.beforeTokens,
|
|
1291
|
-
afterTokens: result.afterTokens,
|
|
1292
|
-
summaryTokens: result.summaryTokens,
|
|
1293
|
-
durationMs: Date.now() - startedAt,
|
|
1294
|
-
hooksFiredCount: result.hooksFiredCount,
|
|
1295
|
-
summary: result.summary ?? "",
|
|
1296
|
-
applied,
|
|
1297
|
-
...applied ? {} : { skippedReason: "nothing-to-trim" }
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
const handoffNames = Array.from(handoffMap.keys());
|
|
1301
|
-
let stepNumber = 0;
|
|
239
|
+
if (resumed && resumedApprovedCalls.length > 0) yield* dispatchResumedApprovals(runEnv, resumedApprovedCalls, grantedApprovals);
|
|
240
|
+
if (resumed && subRunDecisions.size > 0) yield* processSubRunResumes(runEnv, subRunDecisions);
|
|
241
|
+
if (resumed && (state.status === "awaiting_approval" || state.status === "aborted" && state.pendingApprovals.length > 0)) return yield* finishRun(state, finalSnapshot);
|
|
242
|
+
let stepNumber = state.steps.reduce((max, s) => Math.max(max, s.stepNumber), 0);
|
|
1302
243
|
let verifierRoundsUsed = 0;
|
|
1303
244
|
let lastStepCalledToolNames = [];
|
|
1304
|
-
async function* emitCancellation() {
|
|
1305
|
-
yield {
|
|
1306
|
-
type: "agent.cancelling",
|
|
1307
|
-
runId: state.id,
|
|
1308
|
-
drain: pendingAbort?.drain ?? false,
|
|
1309
|
-
onPendingApprovals: pendingAbort?.onPendingApprovals ?? "deny"
|
|
1310
|
-
};
|
|
1311
|
-
const policy = pendingAbort?.onPendingApprovals ?? "deny";
|
|
1312
|
-
if (policy === "deny") {
|
|
1313
|
-
const drained = state.pendingApprovals.splice(0, state.pendingApprovals.length);
|
|
1314
|
-
for (const approval of drained) yield {
|
|
1315
|
-
type: "tool.approval.denied",
|
|
1316
|
-
toolCallId: approval.toolCallId,
|
|
1317
|
-
reason: "auto-denied: agent.abort()"
|
|
1318
|
-
};
|
|
1319
|
-
} else if (policy === "fail") {
|
|
1320
|
-
state.status = "failed";
|
|
1321
|
-
state.error = {
|
|
1322
|
-
message: "aborted with pending approvals",
|
|
1323
|
-
code: "run-aborted"
|
|
1324
|
-
};
|
|
1325
|
-
yield {
|
|
1326
|
-
type: "agent.error",
|
|
1327
|
-
error: {
|
|
1328
|
-
message: "aborted with pending approvals",
|
|
1329
|
-
code: "run-aborted"
|
|
1330
|
-
}
|
|
1331
|
-
};
|
|
1332
|
-
return true;
|
|
1333
|
-
}
|
|
1334
|
-
state.status = "aborted";
|
|
1335
|
-
return false;
|
|
1336
|
-
}
|
|
1337
245
|
try {
|
|
1338
246
|
while (!stopWhen.check(state)) {
|
|
1339
247
|
while (externalEventQueue.length > 0) {
|
|
@@ -1341,7 +249,7 @@ function createAgent(config) {
|
|
|
1341
249
|
if (ev !== void 0) yield ev;
|
|
1342
250
|
}
|
|
1343
251
|
if (signal.aborted) {
|
|
1344
|
-
if (yield* emitCancellation()) return yield* finishRun(state, finalSnapshot);
|
|
252
|
+
if (yield* emitCancellation(runEnv)) return yield* finishRun(state, finalSnapshot);
|
|
1345
253
|
break;
|
|
1346
254
|
}
|
|
1347
255
|
stepNumber += 1;
|
|
@@ -1369,7 +277,7 @@ function createAgent(config) {
|
|
|
1369
277
|
"graphorin.step.number": stepNumber
|
|
1370
278
|
}
|
|
1371
279
|
});
|
|
1372
|
-
yield* maybeAutoCompact();
|
|
280
|
+
yield* maybeAutoCompact(runEnv);
|
|
1373
281
|
const stepCtx = {
|
|
1374
282
|
...runContextBase,
|
|
1375
283
|
stepNumber,
|
|
@@ -1377,181 +285,14 @@ function createAgent(config) {
|
|
|
1377
285
|
...currentStepSpan !== void 0 ? { span: currentStepSpan } : {}
|
|
1378
286
|
};
|
|
1379
287
|
const overrides = config.prepareStep ? await config.prepareStep(stepCtx) : {};
|
|
1380
|
-
const
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1386
|
-
const stepExecutor = useOverrideRegistry ? makeToolExecutor(stepRegistry) : toolExecutor;
|
|
1387
|
-
const handoffTools = handoffNames.map((n) => {
|
|
1388
|
-
const h = handoffMap.get(n);
|
|
1389
|
-
if (h === void 0) throw new ToolNotFoundError(n);
|
|
1390
|
-
return buildHandoffTool(h.agent);
|
|
1391
|
-
});
|
|
1392
|
-
const readOnlyRun = activeRunCapability === "read-only";
|
|
1393
|
-
const keepReadOnly = (t) => {
|
|
1394
|
-
const cls = t.__sideEffectClass ?? t.sideEffectClass;
|
|
1395
|
-
return cls === "pure" || cls === "read-only";
|
|
1396
|
-
};
|
|
1397
|
-
let stepTools;
|
|
1398
|
-
if (isCodeMode) stepTools = readOnlyRun ? [...codeModeAdvertised.filter(keepReadOnly)] : [...codeModeAdvertised, ...handoffTools];
|
|
1399
|
-
else {
|
|
1400
|
-
const eagerTools = stepRegistry.listEager();
|
|
1401
|
-
const advertisedPromotions = runStartPromotions ?? promotedDeferred;
|
|
1402
|
-
const promotedTools = advertisedPromotions.size === 0 ? [] : orderPromotedTools(advertisedPromotions, stepRegistry.listDeferred());
|
|
1403
|
-
stepTools = readOnlyRun ? [...eagerTools.filter(keepReadOnly), ...promotedTools.filter(keepReadOnly)] : [
|
|
1404
|
-
...eagerTools,
|
|
1405
|
-
...handoffTools,
|
|
1406
|
-
...promotedTools
|
|
1407
|
-
];
|
|
1408
|
-
}
|
|
1409
|
-
const calledLastStep = new Set(lastStepCalledToolNames);
|
|
1410
|
-
const toolPreferences = stepTools.filter((t) => calledLastStep.has(t.name)).map((t) => {
|
|
1411
|
-
return t.preferredModel;
|
|
1412
|
-
});
|
|
1413
|
-
const primary = resolvePreferredModel({
|
|
1414
|
-
...overrides.provider !== void 0 ? { prepareStepProvider: overrides.provider } : {},
|
|
1415
|
-
toolPreferredModels: toolPreferences,
|
|
1416
|
-
...config.preferredModel !== void 0 ? { agentPreferredModel: config.preferredModel } : {},
|
|
1417
|
-
agentDefaultProvider: config.provider,
|
|
1418
|
-
...config.modelTierMap !== void 0 ? { modelTierMap: config.modelTierMap } : {}
|
|
1419
|
-
});
|
|
1420
|
-
const fallbackChain = primary.source === "prepare-step" ? [primary.resolvedProvider] : [primary.resolvedProvider, ...(config.fallbackModels ?? []).map(specToProvider)];
|
|
1421
|
-
const reasoningPolicy = effectiveReasoningRetention(config.reasoningRetention, primary.resolvedProvider);
|
|
1422
|
-
if (reasoningPolicy === "strip") {
|
|
1423
|
-
const { stripped } = stripReasoningFromMessages(messages);
|
|
1424
|
-
if (stripped > 0) stripReasoningFromMessages(state.messages);
|
|
1425
|
-
}
|
|
1426
|
-
const toolDefs = stepTools.map((t) => toolToDefinition(t));
|
|
1427
|
-
const baseRequest = {
|
|
1428
|
-
messages: buildStepMessages(messages, structuredInstruction, activeRunState?.todos),
|
|
1429
|
-
...config.outputType !== void 0 ? { outputType: {
|
|
1430
|
-
kind: config.outputType.kind,
|
|
1431
|
-
...config.outputType.description !== void 0 ? { description: config.outputType.description } : {},
|
|
1432
|
-
...config.outputType.jsonSchema !== void 0 ? { jsonSchema: config.outputType.jsonSchema } : {}
|
|
1433
|
-
} } : {},
|
|
1434
|
-
tools: toolDefs,
|
|
1435
|
-
...overrides.toolChoice !== void 0 ? { toolChoice: overrides.toolChoice } : config.toolChoice !== void 0 ? { toolChoice: config.toolChoice } : {},
|
|
1436
|
-
metadata: {
|
|
1437
|
-
sessionId,
|
|
1438
|
-
agentId,
|
|
1439
|
-
...userId !== void 0 ? { userId } : {},
|
|
1440
|
-
runId: state.id,
|
|
1441
|
-
stepNumber
|
|
1442
|
-
},
|
|
1443
|
-
signal,
|
|
1444
|
-
...overrides.temperature !== void 0 ? { temperature: overrides.temperature } : {},
|
|
1445
|
-
...overrides.maxTokens !== void 0 ? { maxTokens: overrides.maxTokens } : {},
|
|
1446
|
-
...config.cachePolicy !== void 0 ? { cachePolicy: config.cachePolicy } : {},
|
|
1447
|
-
...currentStepSpan !== void 0 ? { parentSpan: currentStepSpan } : {},
|
|
1448
|
-
reasoningRetention: reasoningPolicy
|
|
1449
|
-
};
|
|
1450
|
-
const stepUsage = zeroUsage();
|
|
1451
|
-
let attempt = 0;
|
|
1452
|
-
let textBuffer = "";
|
|
1453
|
-
let providerForStep = primary.resolvedProvider;
|
|
1454
|
-
let lastModelId = primary.resolvedModelId;
|
|
1455
|
-
let modelSucceeded = false;
|
|
1456
|
-
let lastError;
|
|
1457
|
-
let finalCalls = [];
|
|
1458
|
-
let stepReasoningParts = [];
|
|
1459
|
-
let requestForStep = baseRequest;
|
|
1460
|
-
let emergencyCompactTried = false;
|
|
1461
|
-
for (let chainIdx = 0; chainIdx < fallbackChain.length; chainIdx++) {
|
|
1462
|
-
const candidate = fallbackChain[chainIdx];
|
|
1463
|
-
if (candidate === void 0) continue;
|
|
1464
|
-
providerForStep = candidate;
|
|
1465
|
-
const providerModelId = providerForStep.modelId;
|
|
1466
|
-
if (chainIdx > 0) {
|
|
1467
|
-
attempt += 1;
|
|
1468
|
-
const reason = lastError ? isAgentFallbackEligible(lastError, fallbackPolicy).reason ?? "transient" : "transient";
|
|
1469
|
-
yield {
|
|
1470
|
-
type: "agent.model.fellback",
|
|
1471
|
-
runId: state.id,
|
|
1472
|
-
sessionId,
|
|
1473
|
-
agentId,
|
|
1474
|
-
from: lastModelId,
|
|
1475
|
-
to: providerModelId,
|
|
1476
|
-
reason,
|
|
1477
|
-
stepNumber,
|
|
1478
|
-
attempt
|
|
1479
|
-
};
|
|
1480
|
-
lastModelId = providerModelId;
|
|
1481
|
-
}
|
|
1482
|
-
const evState = {
|
|
1483
|
-
textBuffer: "",
|
|
1484
|
-
reasoningBuffer: "",
|
|
1485
|
-
reasoningParts: [],
|
|
1486
|
-
calls: /* @__PURE__ */ new Map(),
|
|
1487
|
-
finalCalls: []
|
|
1488
|
-
};
|
|
1489
|
-
let providerError;
|
|
1490
|
-
let providerCallCompleted = false;
|
|
1491
|
-
let providerStepUsage = zeroUsage();
|
|
1492
|
-
try {
|
|
1493
|
-
const stream$1 = providerForStep.stream(requestForStep);
|
|
1494
|
-
for await (const ev of stream$1) {
|
|
1495
|
-
if (signal.aborted && pendingAbort?.drain !== true) throw new AgentRuntimeError("run-aborted", "aborted");
|
|
1496
|
-
const out = handleProviderEvent(ev, evState);
|
|
1497
|
-
if (out.emit !== void 0) yield out.emit;
|
|
1498
|
-
if (out.providerError !== void 0) providerError = out.providerError;
|
|
1499
|
-
if (out.usage !== void 0) providerStepUsage = addUsage(providerStepUsage, out.usage);
|
|
1500
|
-
if (out.finished === true) providerCallCompleted = true;
|
|
1501
|
-
}
|
|
1502
|
-
} catch (cause) {
|
|
1503
|
-
if (signal.aborted || cause instanceof AgentRuntimeError && cause.code === "run-aborted") break;
|
|
1504
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1505
|
-
providerError = {
|
|
1506
|
-
kind: classifyThrownProviderErrorKind(cause),
|
|
1507
|
-
message,
|
|
1508
|
-
cause
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
if (providerError !== void 0) {
|
|
1512
|
-
lastError = providerError;
|
|
1513
|
-
if (providerError.kind === "context-length" && !emergencyCompactTried) {
|
|
1514
|
-
emergencyCompactTried = true;
|
|
1515
|
-
if (yield* tryEmergencyCompact()) {
|
|
1516
|
-
requestForStep = {
|
|
1517
|
-
...baseRequest,
|
|
1518
|
-
messages: buildStepMessages(messages, structuredInstruction, activeRunState?.todos)
|
|
1519
|
-
};
|
|
1520
|
-
chainIdx -= 1;
|
|
1521
|
-
continue;
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
if (!isAgentFallbackEligible(providerError, fallbackPolicy).eligible || chainIdx === fallbackChain.length - 1) {
|
|
1525
|
-
yield {
|
|
1526
|
-
type: "agent.error",
|
|
1527
|
-
error: {
|
|
1528
|
-
message: providerError.message,
|
|
1529
|
-
code: providerError.kind
|
|
1530
|
-
}
|
|
1531
|
-
};
|
|
1532
|
-
state.status = "failed";
|
|
1533
|
-
state.error = {
|
|
1534
|
-
message: providerError.message,
|
|
1535
|
-
code: providerError.kind
|
|
1536
|
-
};
|
|
1537
|
-
return yield* finishRun(state, finalSnapshot);
|
|
1538
|
-
}
|
|
1539
|
-
continue;
|
|
1540
|
-
}
|
|
1541
|
-
if (providerCallCompleted) {
|
|
1542
|
-
modelSucceeded = true;
|
|
1543
|
-
textBuffer = evState.textBuffer;
|
|
1544
|
-
finalCalls = evState.finalCalls;
|
|
1545
|
-
if (evState.reasoningBuffer.length > 0) stepReasoningParts = [{
|
|
1546
|
-
type: "reasoning",
|
|
1547
|
-
text: evState.reasoningBuffer
|
|
1548
|
-
}];
|
|
1549
|
-
accumulateUsage(stepUsage, providerStepUsage);
|
|
1550
|
-
break;
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
288
|
+
const { stepRegistry, stepExecutor, stepTools, primary, fallbackChain } = resolveStepToolContext(runEnv, overrides, lastStepCalledToolNames);
|
|
289
|
+
const reasoningPolicy = applyReasoningRetention(config.reasoningRetention, primary.resolvedProvider, messages, state.messages);
|
|
290
|
+
const chain = yield* runProviderFallbackChain(runEnv, fallbackChain, buildBaseRequest(runEnv, overrides, stepTools.map((t) => toolToDefinition(t)), reasoningPolicy, stepNumber, currentStepSpan), primary, stepNumber);
|
|
291
|
+
if (chain.failed) return yield* finishRun(state, finalSnapshot);
|
|
292
|
+
const { modelSucceeded, textBuffer, finalCalls, stepReasoningParts } = chain;
|
|
293
|
+
const { stepUsage, lastModelId } = chain;
|
|
1553
294
|
if (signal.aborted && !modelSucceeded) {
|
|
1554
|
-
yield* emitCancellation();
|
|
295
|
+
yield* emitCancellation(runEnv);
|
|
1555
296
|
return yield* finishRun(state, finalSnapshot);
|
|
1556
297
|
}
|
|
1557
298
|
if (!modelSucceeded) {
|
|
@@ -1572,28 +313,7 @@ function createAgent(config) {
|
|
|
1572
313
|
usageAcc.add(lastModelId, stepUsage);
|
|
1573
314
|
addModelUsage(state, lastModelId, stepUsage);
|
|
1574
315
|
accumulateUsage(state.usage, stepUsage);
|
|
1575
|
-
const
|
|
1576
|
-
const leakBlocked = leakCheck?.leakDetected === true && leakCheck.decision === "block";
|
|
1577
|
-
const assistant = buildAssistantMessage(leakBlocked ? LATERAL_LEAK_BLOCKED_NOTICE : textBuffer, stepReasoningParts, finalCalls, agentId, reasoningPolicy);
|
|
1578
|
-
messages.push(assistant);
|
|
1579
|
-
state.messages.push(assistant);
|
|
1580
|
-
if (toolDataFlowGuard !== void 0 && textBuffer.length > 0 && !leakBlocked) toolDataFlowGuard.recordAssistant(state.id, textBuffer);
|
|
1581
|
-
if (leakCheck?.leakDetected === true) {
|
|
1582
|
-
const sha = sha256Hex(textBuffer);
|
|
1583
|
-
yield {
|
|
1584
|
-
type: "agent.lateral-leak.detected",
|
|
1585
|
-
runId: state.id,
|
|
1586
|
-
sessionId,
|
|
1587
|
-
agentId,
|
|
1588
|
-
vector: leakCheck.vector,
|
|
1589
|
-
severity: leakCheck.severity,
|
|
1590
|
-
causalityChain: leakCheck.causalityChain,
|
|
1591
|
-
messageContentSha256: sha,
|
|
1592
|
-
...leakCheck.matchedPattern !== void 0 ? { matchedPattern: leakCheck.matchedPattern } : {},
|
|
1593
|
-
decision: leakCheck.decision,
|
|
1594
|
-
detectedAtIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
316
|
+
const leakBlocked = yield* commitAssistantMessage(runEnv, textBuffer, stepReasoningParts, finalCalls, reasoningPolicy);
|
|
1597
317
|
const handoffCalls = finalCalls.filter((c) => handoffMap.has(c.toolName));
|
|
1598
318
|
if (handoffCalls.length > 1) throw new MultipleHandoffsInStepError(handoffCalls.map((c) => c.toolName));
|
|
1599
319
|
const stepRecord = {
|
|
@@ -1619,202 +339,30 @@ function createAgent(config) {
|
|
|
1619
339
|
};
|
|
1620
340
|
}
|
|
1621
341
|
if (finalCalls.length > 0) {
|
|
1622
|
-
const
|
|
342
|
+
const walked = yield* processStepToolCalls(runEnv, finalCalls, stepRegistry, stepExecutor, {
|
|
1623
343
|
...runContextBase,
|
|
1624
344
|
stepNumber,
|
|
1625
345
|
messages,
|
|
1626
346
|
...currentStepSpan !== void 0 ? { span: currentStepSpan } : {}
|
|
1627
|
-
};
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
yield {
|
|
1638
|
-
type: "tool.execute.start",
|
|
1639
|
-
toolCallId: call.toolCallId
|
|
1640
|
-
};
|
|
1641
|
-
const filter = handoff.filter ?? filters.defaultHandoffFilter();
|
|
1642
|
-
const filtered = filter(messages);
|
|
1643
|
-
const targetId = handoff.agent.id;
|
|
1644
|
-
const handoffRec = {
|
|
1645
|
-
fromAgentId: agentId,
|
|
1646
|
-
toAgentId: targetId,
|
|
347
|
+
}, stepNumber);
|
|
348
|
+
if (walked.suspended) {
|
|
349
|
+
if (walked.abortPending === true) {
|
|
350
|
+
yield* emitCancellation(runEnv);
|
|
351
|
+
if (config.checkpointStore !== void 0) await config.checkpointStore.put(state.id, "agent", {
|
|
352
|
+
id: state.id,
|
|
353
|
+
threadId: state.id,
|
|
354
|
+
namespace: "agent",
|
|
355
|
+
state: serializeRunState(state, { stripTracingApiKey: true }),
|
|
356
|
+
channelVersions: {},
|
|
1647
357
|
stepNumber,
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
yield {
|
|
1655
|
-
type: "handoff",
|
|
1656
|
-
fromAgentId: agentId,
|
|
1657
|
-
toAgentId: targetId
|
|
1658
|
-
};
|
|
1659
|
-
state.currentAgentId = targetId;
|
|
1660
|
-
const subAgent = handoff.agent;
|
|
1661
|
-
const subStart = Date.now();
|
|
1662
|
-
const subOutputs = [];
|
|
1663
|
-
let subResult;
|
|
1664
|
-
const subStream = subAgent.stream(filtered, {
|
|
1665
|
-
signal,
|
|
1666
|
-
...options.deps !== void 0 || config.deps !== void 0 ? { deps: options.deps ?? config.deps } : {},
|
|
1667
|
-
sessionId
|
|
358
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
359
|
+
}, {
|
|
360
|
+
source: "sync",
|
|
361
|
+
status: state.status === "failed" ? "failed" : "aborted",
|
|
362
|
+
nodeName: "agent.abort",
|
|
363
|
+
sessionId: state.sessionId
|
|
1668
364
|
});
|
|
1669
|
-
for await (const subEv of subStream) if (subEv.type === "text.complete") subOutputs.push(subEv.text);
|
|
1670
|
-
else if (subEv.type === "agent.end") subResult = subEv.result;
|
|
1671
|
-
const subDurationMs = Date.now() - subStart;
|
|
1672
|
-
const stepEntry = state.steps[state.steps.length - 1];
|
|
1673
|
-
if (subResult !== void 0 && subResult.status !== "completed") {
|
|
1674
|
-
const toolError = {
|
|
1675
|
-
toolCallId: call.toolCallId,
|
|
1676
|
-
toolName: call.toolName,
|
|
1677
|
-
kind: subResult.status === "aborted" ? "aborted" : "execution_failed",
|
|
1678
|
-
message: `handoff to '${targetId}' ${subResult.status}${subResult.error !== void 0 ? `: ${subResult.error.message}` : ""}`
|
|
1679
|
-
};
|
|
1680
|
-
if (stepEntry !== void 0) stepEntry.toolCalls.push({
|
|
1681
|
-
call,
|
|
1682
|
-
outcome: toolError,
|
|
1683
|
-
stepNumber
|
|
1684
|
-
});
|
|
1685
|
-
yield {
|
|
1686
|
-
type: "tool.execute.error",
|
|
1687
|
-
toolCallId: call.toolCallId,
|
|
1688
|
-
error: toolError
|
|
1689
|
-
};
|
|
1690
|
-
const text = renderToolErrorMessage(toolError);
|
|
1691
|
-
messages.push({
|
|
1692
|
-
role: "tool",
|
|
1693
|
-
toolCallId: call.toolCallId,
|
|
1694
|
-
content: text
|
|
1695
|
-
});
|
|
1696
|
-
state.messages.push({
|
|
1697
|
-
role: "tool",
|
|
1698
|
-
toolCallId: call.toolCallId,
|
|
1699
|
-
content: text
|
|
1700
|
-
});
|
|
1701
|
-
continue;
|
|
1702
|
-
}
|
|
1703
|
-
const result = subOutputs.join("");
|
|
1704
|
-
const completed = {
|
|
1705
|
-
call,
|
|
1706
|
-
outcome: {
|
|
1707
|
-
toolCallId: call.toolCallId,
|
|
1708
|
-
toolName: call.toolName,
|
|
1709
|
-
output: result,
|
|
1710
|
-
durationMs: subDurationMs
|
|
1711
|
-
},
|
|
1712
|
-
stepNumber
|
|
1713
|
-
};
|
|
1714
|
-
if (stepEntry !== void 0) stepEntry.toolCalls.push(completed);
|
|
1715
|
-
yield {
|
|
1716
|
-
type: "tool.execute.end",
|
|
1717
|
-
toolCallId: call.toolCallId,
|
|
1718
|
-
result,
|
|
1719
|
-
durationMs: subDurationMs
|
|
1720
|
-
};
|
|
1721
|
-
messages.push({
|
|
1722
|
-
role: "tool",
|
|
1723
|
-
toolCallId: call.toolCallId,
|
|
1724
|
-
content: result
|
|
1725
|
-
});
|
|
1726
|
-
state.messages.push({
|
|
1727
|
-
role: "tool",
|
|
1728
|
-
toolCallId: call.toolCallId,
|
|
1729
|
-
content: result
|
|
1730
|
-
});
|
|
1731
|
-
continue;
|
|
1732
|
-
}
|
|
1733
|
-
const resolvedTool = stepRegistry.get(call.toolName);
|
|
1734
|
-
let gateInput = call.args;
|
|
1735
|
-
if (resolvedTool !== void 0 && isApprovalGated(resolvedTool)) {
|
|
1736
|
-
const parsed = safeParseGatedArgs(resolvedTool, call.args);
|
|
1737
|
-
if (parsed !== void 0 && !parsed.success) {
|
|
1738
|
-
const toolError = {
|
|
1739
|
-
toolCallId: call.toolCallId,
|
|
1740
|
-
toolName: call.toolName,
|
|
1741
|
-
kind: "invalid_input",
|
|
1742
|
-
message: `Invalid arguments for approval-gated tool '${call.toolName}': ${parsed.message}`
|
|
1743
|
-
};
|
|
1744
|
-
const stepEntry = state.steps[state.steps.length - 1];
|
|
1745
|
-
if (stepEntry !== void 0) stepEntry.toolCalls.push({
|
|
1746
|
-
call,
|
|
1747
|
-
outcome: toolError,
|
|
1748
|
-
stepNumber
|
|
1749
|
-
});
|
|
1750
|
-
yield {
|
|
1751
|
-
type: "tool.execute.start",
|
|
1752
|
-
toolCallId: call.toolCallId
|
|
1753
|
-
};
|
|
1754
|
-
yield {
|
|
1755
|
-
type: "tool.execute.error",
|
|
1756
|
-
toolCallId: call.toolCallId,
|
|
1757
|
-
error: toolError
|
|
1758
|
-
};
|
|
1759
|
-
const text = renderToolErrorMessage(toolError);
|
|
1760
|
-
messages.push({
|
|
1761
|
-
role: "tool",
|
|
1762
|
-
toolCallId: call.toolCallId,
|
|
1763
|
-
content: text
|
|
1764
|
-
});
|
|
1765
|
-
state.messages.push({
|
|
1766
|
-
role: "tool",
|
|
1767
|
-
toolCallId: call.toolCallId,
|
|
1768
|
-
content: text
|
|
1769
|
-
});
|
|
1770
|
-
continue;
|
|
1771
|
-
}
|
|
1772
|
-
if (parsed !== void 0) gateInput = parsed.data;
|
|
1773
365
|
}
|
|
1774
|
-
if (await invokeNeedsApproval(resolvedTool, gateInput, execRunContext, signal)) {
|
|
1775
|
-
if (batch.length > 0) {
|
|
1776
|
-
yield* dispatchBatch(batch, stepExecutor, execRunContext, stepNumber);
|
|
1777
|
-
batch = [];
|
|
1778
|
-
}
|
|
1779
|
-
yield {
|
|
1780
|
-
type: "tool.execute.start",
|
|
1781
|
-
toolCallId: call.toolCallId
|
|
1782
|
-
};
|
|
1783
|
-
const approval = {
|
|
1784
|
-
toolCallId: call.toolCallId,
|
|
1785
|
-
toolName: call.toolName,
|
|
1786
|
-
args: call.args,
|
|
1787
|
-
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1788
|
-
};
|
|
1789
|
-
state.pendingApprovals.push(approval);
|
|
1790
|
-
stepApprovalsRequested += 1;
|
|
1791
|
-
yield {
|
|
1792
|
-
type: "tool.approval.requested",
|
|
1793
|
-
toolCallId: call.toolCallId
|
|
1794
|
-
};
|
|
1795
|
-
continue;
|
|
1796
|
-
}
|
|
1797
|
-
batch.push(call);
|
|
1798
|
-
}
|
|
1799
|
-
if (batch.length > 0) yield* dispatchBatch(batch, stepExecutor, execRunContext, stepNumber);
|
|
1800
|
-
if (stepApprovalsRequested > 0) {
|
|
1801
|
-
state.status = "awaiting_approval";
|
|
1802
|
-
const taintSnap = toolDataFlowGuard?.snapshotLedger(state.id);
|
|
1803
|
-
if (taintSnap !== void 0) state.taintSummary = taintSnap;
|
|
1804
|
-
if (promotedDeferred.size > 0) state.promotedTools = [...promotedDeferred];
|
|
1805
|
-
if (config.checkpointStore !== void 0) await config.checkpointStore.put(state.id, "agent", {
|
|
1806
|
-
id: state.id,
|
|
1807
|
-
threadId: state.id,
|
|
1808
|
-
namespace: "agent",
|
|
1809
|
-
state: serializeRunState(state, { stripTracingApiKey: true }),
|
|
1810
|
-
channelVersions: {},
|
|
1811
|
-
stepNumber,
|
|
1812
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1813
|
-
}, {
|
|
1814
|
-
source: "sync",
|
|
1815
|
-
status: "suspended",
|
|
1816
|
-
nodeName: "agent.run"
|
|
1817
|
-
});
|
|
1818
366
|
return yield* finishRun(state, finalSnapshot);
|
|
1819
367
|
}
|
|
1820
368
|
}
|
|
@@ -1831,37 +379,9 @@ function createAgent(config) {
|
|
|
1831
379
|
};
|
|
1832
380
|
if (finalCalls.length === 0) {
|
|
1833
381
|
if (config.verifiers !== void 0 && config.verifiers.length > 0) {
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
try {
|
|
1838
|
-
result = await verifier.verify({
|
|
1839
|
-
output: String(finalSnapshot.output ?? ""),
|
|
1840
|
-
state,
|
|
1841
|
-
stepNumber
|
|
1842
|
-
});
|
|
1843
|
-
} catch {
|
|
1844
|
-
result = { ok: true };
|
|
1845
|
-
}
|
|
1846
|
-
yield {
|
|
1847
|
-
type: "verifier.result",
|
|
1848
|
-
verifierId: verifier.id,
|
|
1849
|
-
ok: result.ok,
|
|
1850
|
-
...result.ok ? {} : { feedback: result.feedback },
|
|
1851
|
-
stepNumber
|
|
1852
|
-
};
|
|
1853
|
-
if (!result.ok) feedbacks.push(`[verifier:${verifier.id}] ${result.feedback}`);
|
|
1854
|
-
}
|
|
1855
|
-
if (feedbacks.length > 0 && verifierRoundsUsed < (config.maxVerifierRounds ?? 1)) {
|
|
1856
|
-
verifierRoundsUsed += 1;
|
|
1857
|
-
const feedbackMessage = {
|
|
1858
|
-
role: "user",
|
|
1859
|
-
content: `Your response failed ${feedbacks.length} verification check(s). Address the feedback and answer again:\n${feedbacks.join("\n")}`
|
|
1860
|
-
};
|
|
1861
|
-
messages.push(feedbackMessage);
|
|
1862
|
-
state.messages.push(feedbackMessage);
|
|
1863
|
-
continue;
|
|
1864
|
-
}
|
|
382
|
+
const gate = yield* runVerifierGate(runEnv, finalSnapshot, stepNumber, verifierRoundsUsed);
|
|
383
|
+
verifierRoundsUsed = gate.verifierRoundsUsed;
|
|
384
|
+
if (gate.continueRun) continue;
|
|
1865
385
|
}
|
|
1866
386
|
state.status = "completed";
|
|
1867
387
|
break;
|
|
@@ -1895,135 +415,17 @@ function createAgent(config) {
|
|
|
1895
415
|
runSpan.end();
|
|
1896
416
|
if (parentSignal !== void 0) parentSignal.removeEventListener("abort", onParentAbort);
|
|
1897
417
|
}
|
|
1898
|
-
|
|
1899
|
-
const message = `run stopped by stop condition: ${stopWhen.description}`;
|
|
1900
|
-
state.status = "failed";
|
|
1901
|
-
state.error = {
|
|
1902
|
-
message,
|
|
1903
|
-
code: "stop-condition"
|
|
1904
|
-
};
|
|
1905
|
-
yield {
|
|
1906
|
-
type: "agent.error",
|
|
1907
|
-
error: {
|
|
1908
|
-
message,
|
|
1909
|
-
code: "stop-condition"
|
|
1910
|
-
}
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
if (state.status === "completed" && config.outputType?.kind === "structured") {
|
|
1914
|
-
const raw = String(finalSnapshot.output ?? "");
|
|
1915
|
-
try {
|
|
1916
|
-
const parsed = JSON.parse(stripJsonFence(raw));
|
|
1917
|
-
finalSnapshot.output = config.outputType.schema !== void 0 ? config.outputType.schema.parse(parsed) : parsed;
|
|
1918
|
-
} catch (cause) {
|
|
1919
|
-
const message = `structured output validation failed: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
1920
|
-
yield {
|
|
1921
|
-
type: "agent.error",
|
|
1922
|
-
error: {
|
|
1923
|
-
message,
|
|
1924
|
-
code: "output-validation-failed"
|
|
1925
|
-
}
|
|
1926
|
-
};
|
|
1927
|
-
state.status = "failed";
|
|
1928
|
-
state.error = {
|
|
1929
|
-
message,
|
|
1930
|
-
code: "output-validation-failed"
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
const outputGuards = config.guardrails?.output;
|
|
1935
|
-
if (state.status === "completed" && outputGuards !== void 0 && outputGuards.length > 0) {
|
|
1936
|
-
const composed = await composeGuardrails(outputGuards, finalSnapshot.output, {
|
|
1937
|
-
stage: "output",
|
|
1938
|
-
runId: state.id,
|
|
1939
|
-
sessionId,
|
|
1940
|
-
agentId
|
|
1941
|
-
});
|
|
1942
|
-
if (!composed.ok) {
|
|
1943
|
-
yield {
|
|
1944
|
-
type: "guardrail.tripped",
|
|
1945
|
-
guardrailName: composed.name,
|
|
1946
|
-
phase: "output",
|
|
1947
|
-
reason: composed.message
|
|
1948
|
-
};
|
|
1949
|
-
const message = `output guardrail '${composed.name}' blocked the run: ${composed.message}`;
|
|
1950
|
-
yield {
|
|
1951
|
-
type: "agent.error",
|
|
1952
|
-
error: {
|
|
1953
|
-
message,
|
|
1954
|
-
code: "guardrail-blocked"
|
|
1955
|
-
}
|
|
1956
|
-
};
|
|
1957
|
-
state.status = "failed";
|
|
1958
|
-
state.error = {
|
|
1959
|
-
message,
|
|
1960
|
-
code: "guardrail-blocked"
|
|
1961
|
-
};
|
|
1962
|
-
} else if (composed.value !== finalSnapshot.output) finalSnapshot.output = composed.value;
|
|
1963
|
-
}
|
|
418
|
+
yield* finalizeRunOutput(runEnv, finalSnapshot);
|
|
1964
419
|
activeRunState = void 0;
|
|
1965
420
|
return yield* finishRun(state, finalSnapshot);
|
|
1966
421
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
const result = finalize(state, snapshot);
|
|
1975
|
-
if (result.status === "completed" || result.status === "failed") await spillWriter.clear?.(result.state.id).catch(() => {});
|
|
1976
|
-
yield {
|
|
1977
|
-
type: "agent.end",
|
|
1978
|
-
runId: result.state.id,
|
|
1979
|
-
result
|
|
1980
|
-
};
|
|
1981
|
-
return result;
|
|
1982
|
-
}
|
|
1983
|
-
function finalize(state, snapshot) {
|
|
1984
|
-
state.finishedAt = state.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1985
|
-
return {
|
|
1986
|
-
output: snapshot.output,
|
|
1987
|
-
usage: state.usage,
|
|
1988
|
-
status: state.status,
|
|
1989
|
-
...state.error !== void 0 ? { error: state.error } : {},
|
|
1990
|
-
state
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
const stream = (input, options) => {
|
|
1994
|
-
const opts = options ?? {};
|
|
1995
|
-
return { [Symbol.asyncIterator]: () => {
|
|
1996
|
-
const gen = runLoop(input, opts);
|
|
1997
|
-
return {
|
|
1998
|
-
async next() {
|
|
1999
|
-
const r = await gen.next();
|
|
2000
|
-
if (r.done === true) return {
|
|
2001
|
-
done: true,
|
|
2002
|
-
value: void 0
|
|
2003
|
-
};
|
|
2004
|
-
return {
|
|
2005
|
-
done: false,
|
|
2006
|
-
value: r.value
|
|
2007
|
-
};
|
|
2008
|
-
},
|
|
2009
|
-
async return() {
|
|
2010
|
-
await gen.return(void 0);
|
|
2011
|
-
return {
|
|
2012
|
-
done: true,
|
|
2013
|
-
value: void 0
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
};
|
|
2017
|
-
} };
|
|
2018
|
-
};
|
|
2019
|
-
const run = async (input, options) => {
|
|
2020
|
-
const gen = runLoop(input, options ?? {});
|
|
2021
|
-
let next = await gen.next();
|
|
2022
|
-
while (next.done !== true) next = await gen.next();
|
|
2023
|
-
const result = next.value;
|
|
2024
|
-
if (result === void 0) throw new Error("unreachable: agent run loop ended without a result");
|
|
2025
|
-
return result;
|
|
2026
|
-
};
|
|
422
|
+
const finishRunBase = createRunFinisher({
|
|
423
|
+
pendingManualCompacts,
|
|
424
|
+
spillWriter,
|
|
425
|
+
checkpointStore: config.checkpointStore,
|
|
426
|
+
checkpointPolicy: config.checkpointPolicy
|
|
427
|
+
});
|
|
428
|
+
const { stream, run } = createRunMethods(runLoop);
|
|
2027
429
|
const steer = (message) => {
|
|
2028
430
|
const { seed } = asMessages(message);
|
|
2029
431
|
pendingSteer.push(...seed);
|
|
@@ -2044,136 +446,23 @@ function createAgent(config) {
|
|
|
2044
446
|
pendingAbort = options ?? {};
|
|
2045
447
|
abortController?.abort();
|
|
2046
448
|
};
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
sourceKind: "sub-agent"
|
|
2061
|
-
};
|
|
2062
|
-
};
|
|
2063
|
-
const toTool = (options = {}) => {
|
|
2064
|
-
const exposeTurns = options.exposeTurns ?? "final";
|
|
2065
|
-
const foldMaxChars = options.contextFold === void 0 || options.contextFold === false ? null : typeof options.contextFold === "object" ? options.contextFold.maxChars ?? 2e3 : 2e3;
|
|
2066
|
-
const propagateTaint = options.propagateTaint !== false;
|
|
2067
|
-
return {
|
|
2068
|
-
name: options.name ?? `subagent_${config.name}`,
|
|
2069
|
-
description: options.description ?? `Invoke sub-agent '${config.name}'.`,
|
|
2070
|
-
inputSchema: {
|
|
2071
|
-
parse: (v) => v,
|
|
2072
|
-
safeParse: (v) => ({
|
|
2073
|
-
success: true,
|
|
2074
|
-
data: v
|
|
2075
|
-
}),
|
|
2076
|
-
toJSON: () => ({
|
|
2077
|
-
type: "object",
|
|
2078
|
-
properties: { input: { type: "string" } },
|
|
2079
|
-
required: ["input"]
|
|
2080
|
-
})
|
|
2081
|
-
},
|
|
2082
|
-
sideEffectClass: "side-effecting",
|
|
2083
|
-
async execute(input, ctx) {
|
|
2084
|
-
const callOpts = {
|
|
2085
|
-
...ctx?.signal !== void 0 ? { signal: ctx.signal } : {},
|
|
2086
|
-
...ctx?.runContext.deps !== void 0 ? { deps: ctx.runContext.deps } : {},
|
|
2087
|
-
...ctx?.runContext.sessionId !== void 0 ? { sessionId: ctx.runContext.sessionId } : {},
|
|
2088
|
-
...options.capability !== void 0 ? { capability: options.capability } : {}
|
|
2089
|
-
};
|
|
2090
|
-
const seed = options.inputFilter !== void 0 && ctx !== void 0 ? [...options.inputFilter(ctx.runContext.messages), {
|
|
2091
|
-
role: "user",
|
|
2092
|
-
content: input.input
|
|
2093
|
-
}] : input.input;
|
|
2094
|
-
if (exposeTurns === "all") {
|
|
2095
|
-
const turns = [];
|
|
2096
|
-
let endResult;
|
|
2097
|
-
for await (const ev of stream(seed, callOpts)) if (ev.type === "text.complete") turns.push(ev.text);
|
|
2098
|
-
else if (ev.type === "agent.end") endResult = ev.result;
|
|
2099
|
-
if (endResult !== void 0 && endResult.status !== "completed") throw new Error(`sub-agent '${config.name}' ${endResult.status}${endResult.error !== void 0 ? `: ${endResult.error.message}` : ""}`);
|
|
2100
|
-
const allOutput = foldMaxChars !== null && endResult !== void 0 ? foldRunOutcome(endResult, foldMaxChars) : turns.join("\n\n");
|
|
2101
|
-
const allTaint = propagateTaint && endResult !== void 0 ? taintFromChildState(endResult.state) : void 0;
|
|
2102
|
-
return allTaint !== void 0 ? {
|
|
2103
|
-
output: allOutput,
|
|
2104
|
-
taint: allTaint
|
|
2105
|
-
} : allOutput;
|
|
2106
|
-
}
|
|
2107
|
-
const result = await run(seed, callOpts);
|
|
2108
|
-
if (result.status !== "completed") throw new Error(`sub-agent '${config.name}' ${result.status}${result.error !== void 0 ? `: ${result.error.message}` : ""}`);
|
|
2109
|
-
const taint = propagateTaint ? taintFromChildState(result.state) : void 0;
|
|
2110
|
-
const output = exposeTurns === "none" ? "" : foldMaxChars !== null ? foldRunOutcome(result, foldMaxChars) : result.output;
|
|
2111
|
-
return taint !== void 0 ? {
|
|
2112
|
-
output,
|
|
2113
|
-
taint
|
|
2114
|
-
} : output;
|
|
2115
|
-
}
|
|
2116
|
-
};
|
|
2117
|
-
};
|
|
2118
|
-
const compact = async (options) => {
|
|
2119
|
-
if (memory === void 0) return noopCompactionResult("no-memory");
|
|
2120
|
-
if (config.sensitivity === "secret") return noopCompactionResult("sensitivity-gated");
|
|
2121
|
-
if (activeRunState === void 0) return noopCompactionResult("no-active-run");
|
|
2122
|
-
return await new Promise((resolve, reject) => {
|
|
2123
|
-
pendingManualCompacts.push({
|
|
2124
|
-
options,
|
|
2125
|
-
resolve,
|
|
2126
|
-
reject
|
|
2127
|
-
});
|
|
2128
|
-
});
|
|
2129
|
-
};
|
|
2130
|
-
const fanOut = async (options) => {
|
|
2131
|
-
const runId = activeRunState?.id ?? `run_${newId()}`;
|
|
2132
|
-
const sessionId = activeRunState?.sessionId ?? `session_${newId()}`;
|
|
2133
|
-
return runFanOut({
|
|
2134
|
-
children: options.children,
|
|
2135
|
-
...options.maxConcurrentChildren !== void 0 ? { maxConcurrentChildren: options.maxConcurrentChildren } : {},
|
|
2136
|
-
...options.perBudget !== void 0 ? { perBudget: options.perBudget } : {},
|
|
2137
|
-
...options.mergeStrategy !== void 0 ? { mergeStrategy: options.mergeStrategy } : {},
|
|
2138
|
-
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
2139
|
-
emit: (event) => {
|
|
2140
|
-
externalEventQueue.push(event);
|
|
2141
|
-
},
|
|
2142
|
-
...config.mergeGuard !== void 0 ? { mergeGuard: config.mergeGuard } : {},
|
|
2143
|
-
runId,
|
|
2144
|
-
sessionId,
|
|
2145
|
-
agentId
|
|
2146
|
-
});
|
|
2147
|
-
};
|
|
2148
|
-
const progressFallbackRunId = `run_${newId()}`;
|
|
2149
|
-
const progress = {
|
|
2150
|
-
write: async (content, opts) => {
|
|
2151
|
-
const runId = activeRunState?.id ?? progressFallbackRunId;
|
|
2152
|
-
const ref = await progressIO.write(runId, content, opts);
|
|
2153
|
-
externalEventQueue.push({
|
|
2154
|
-
type: "agent.progress.written",
|
|
2155
|
-
runId,
|
|
2156
|
-
sessionId: activeRunState?.sessionId ?? "",
|
|
2157
|
-
agentId,
|
|
2158
|
-
ref
|
|
2159
|
-
});
|
|
2160
|
-
return ref;
|
|
2161
|
-
},
|
|
2162
|
-
read: async (opts) => {
|
|
2163
|
-
const queriedRunId = opts?.runId ?? activeRunState?.id ?? progressFallbackRunId;
|
|
2164
|
-
const refs = await progressIO.read(queriedRunId, opts);
|
|
2165
|
-
externalEventQueue.push({
|
|
2166
|
-
type: "agent.progress.read",
|
|
2167
|
-
runId: activeRunState?.id ?? queriedRunId,
|
|
2168
|
-
sessionId: activeRunState?.sessionId ?? "",
|
|
2169
|
-
agentId,
|
|
2170
|
-
refs,
|
|
2171
|
-
queriedRunId,
|
|
2172
|
-
queriedRole: opts?.role
|
|
2173
|
-
});
|
|
2174
|
-
return refs;
|
|
2175
|
-
}
|
|
449
|
+
const toTool = createToTool({
|
|
450
|
+
config,
|
|
451
|
+
run,
|
|
452
|
+
stream
|
|
453
|
+
});
|
|
454
|
+
const surfaceDeps = {
|
|
455
|
+
config,
|
|
456
|
+
memory,
|
|
457
|
+
agentId,
|
|
458
|
+
getActiveRunState: () => activeRunState,
|
|
459
|
+
externalEventQueue,
|
|
460
|
+
pendingManualCompacts,
|
|
461
|
+
progressIO
|
|
2176
462
|
};
|
|
463
|
+
const compact = createCompactMethod(surfaceDeps);
|
|
464
|
+
const fanOut = createFanOutMethod(surfaceDeps);
|
|
465
|
+
const progress = createProgressSurface(surfaceDeps);
|
|
2177
466
|
config.sensitivity;
|
|
2178
467
|
return {
|
|
2179
468
|
id: agentId,
|
|
@@ -2190,72 +479,6 @@ function createAgent(config) {
|
|
|
2190
479
|
registry: toolRegistry
|
|
2191
480
|
};
|
|
2192
481
|
}
|
|
2193
|
-
/**
|
|
2194
|
-
* Pre-execution approval screen (Adapter G / durable HITL). Evaluates a
|
|
2195
|
-
* (registry-resolved) tool's `needsApproval` against the realized args.
|
|
2196
|
-
* Returns `true` when the run must suspend before the tool executes.
|
|
2197
|
-
*
|
|
2198
|
-
* Actual execution flows through the `@graphorin/tools` executor, whose
|
|
2199
|
-
* `ApprovalGate` auto-grants because only no-approval / pre-approved
|
|
2200
|
-
* calls ever reach it; this probe is what keeps the suspend in the
|
|
2201
|
-
* agent so the durable-HITL contract (persist `RunState`, resume via
|
|
2202
|
-
* directive) is preserved.
|
|
2203
|
-
*/
|
|
2204
|
-
async function invokeNeedsApproval(tool, args, baseCtx, signal) {
|
|
2205
|
-
const predicate = tool?.needsApproval;
|
|
2206
|
-
if (predicate === void 0 || predicate === false) return false;
|
|
2207
|
-
if (predicate === true) return true;
|
|
2208
|
-
const probeCtx = {
|
|
2209
|
-
toolCallId: "probe",
|
|
2210
|
-
runContext: baseCtx,
|
|
2211
|
-
signal,
|
|
2212
|
-
tracer: baseCtx.tracer,
|
|
2213
|
-
logger: NOOP_LOGGER,
|
|
2214
|
-
secrets: probeSecretsAccessor(),
|
|
2215
|
-
reportProgress: () => {},
|
|
2216
|
-
streamContent: () => {}
|
|
2217
|
-
};
|
|
2218
|
-
return Boolean(await predicate(args, probeCtx));
|
|
2219
|
-
}
|
|
2220
|
-
/**
|
|
2221
|
-
* Rejecting secrets accessor used only by the {@link invokeNeedsApproval}
|
|
2222
|
-
* probe. Real tool execution resolves secrets through the executor's
|
|
2223
|
-
* ACL-scoped accessor; an approval predicate has no legitimate need to
|
|
2224
|
-
* read secret material, so every `require(...)` rejects.
|
|
2225
|
-
*/
|
|
2226
|
-
function probeSecretsAccessor() {
|
|
2227
|
-
const rejector = (_key, _options) => Promise.reject(/* @__PURE__ */ new Error("secrets.require is unavailable inside a needsApproval predicate"));
|
|
2228
|
-
return { require: rejector };
|
|
2229
|
-
}
|
|
2230
|
-
/**
|
|
2231
|
-
* tools-02: validate an approval-gated call's args at the pre-screen so
|
|
2232
|
-
* the gate decision - and what a human is asked to approve - is the input
|
|
2233
|
-
* that will actually execute. Structural + defensive: `undefined` when
|
|
2234
|
-
* the tool exposes no callable `safeParse` (nothing to validate here; the
|
|
2235
|
-
* executor still validates at dispatch); a throwing schema counts as a
|
|
2236
|
-
* validation failure rather than crashing the loop.
|
|
2237
|
-
*/
|
|
2238
|
-
function safeParseGatedArgs(tool, args) {
|
|
2239
|
-
const schema = tool.inputSchema;
|
|
2240
|
-
const safeParse = schema?.safeParse;
|
|
2241
|
-
if (typeof safeParse !== "function") return void 0;
|
|
2242
|
-
try {
|
|
2243
|
-
const parsed = safeParse.call(schema, args);
|
|
2244
|
-
if (parsed.success === true) return {
|
|
2245
|
-
success: true,
|
|
2246
|
-
data: parsed.data
|
|
2247
|
-
};
|
|
2248
|
-
return {
|
|
2249
|
-
success: false,
|
|
2250
|
-
message: parsed.error?.message ?? "schema validation failed"
|
|
2251
|
-
};
|
|
2252
|
-
} catch (cause) {
|
|
2253
|
-
return {
|
|
2254
|
-
success: false,
|
|
2255
|
-
message: cause instanceof Error ? cause.message : String(cause)
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
482
|
|
|
2260
483
|
//#endregion
|
|
2261
484
|
export { createAgent };
|