@graphorin/agent 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +41 -21
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/evaluator-optimizer/index.d.ts +1 -1
- package/dist/evaluator-optimizer/index.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +517 -108
- package/dist/factory.js.map +1 -1
- package/dist/fallback/index.d.ts +1 -1
- package/dist/fallback/index.js.map +1 -1
- package/dist/fanout/index.d.ts +8 -8
- package/dist/fanout/index.js +3 -3
- package/dist/fanout/index.js.map +1 -1
- package/dist/filters/index.d.ts +1 -1
- package/dist/filters/index.js +2 -2
- package/dist/filters/index.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/ids.js +1 -1
- package/dist/internal/ids.js.map +1 -1
- package/dist/internal/usage-accumulator.js +17 -2
- package/dist/internal/usage-accumulator.js.map +1 -1
- package/dist/lateral-leak/causality-monitor.d.ts +4 -4
- package/dist/lateral-leak/causality-monitor.js +3 -3
- package/dist/lateral-leak/causality-monitor.js.map +1 -1
- package/dist/lateral-leak/merge-guard.d.ts +2 -2
- package/dist/lateral-leak/merge-guard.js +1 -1
- package/dist/lateral-leak/merge-guard.js.map +1 -1
- package/dist/lateral-leak/protocol-guard.d.ts +4 -4
- package/dist/lateral-leak/protocol-guard.js +4 -4
- package/dist/lateral-leak/protocol-guard.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/preferred-model/index.d.ts +2 -2
- package/dist/preferred-model/index.js +2 -2
- package/dist/preferred-model/index.js.map +1 -1
- package/dist/progress/index.js +2 -2
- package/dist/progress/index.js.map +1 -1
- package/dist/run-state/index.d.ts +7 -5
- package/dist/run-state/index.d.ts.map +1 -1
- package/dist/run-state/index.js +33 -4
- package/dist/run-state/index.js.map +1 -1
- package/dist/testing/replay-provider.d.ts +18 -0
- package/dist/testing/replay-provider.d.ts.map +1 -0
- package/dist/testing/replay-provider.js +100 -0
- package/dist/testing/replay-provider.js.map +1 -0
- package/dist/tooling/adapters.js +6 -6
- package/dist/tooling/adapters.js.map +1 -1
- package/dist/tooling/catalogue.js +1 -1
- package/dist/tooling/catalogue.js.map +1 -1
- package/dist/tooling/dataflow.js +19 -3
- package/dist/tooling/dataflow.js.map +1 -1
- package/dist/tooling/plan.js +92 -0
- package/dist/tooling/plan.js.map +1 -0
- package/dist/tooling/policy.js +45 -0
- package/dist/tooling/policy.js.map +1 -0
- package/dist/tooling/registry-build.js +1 -1
- package/dist/tooling/registry-build.js.map +1 -1
- package/dist/types.d.ts +189 -18
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -8
package/dist/factory.js
CHANGED
|
@@ -11,6 +11,8 @@ import { addModelUsage, createInitialRunState, serializeRunState } from "./run-s
|
|
|
11
11
|
import { buildMemoryGuard, buildSecretResolver, buildToolTokenCounter, createExecutorEventBridge, createMemoryRegionReader } from "./tooling/adapters.js";
|
|
12
12
|
import { orderPromotedTools } from "./tooling/catalogue.js";
|
|
13
13
|
import { buildDataFlowGuard } from "./tooling/dataflow.js";
|
|
14
|
+
import { PLAN_TOOL_NAME, createPlanTool, renderPlanRecitation } from "./tooling/plan.js";
|
|
15
|
+
import { buildToolArgumentPolicy } from "./tooling/policy.js";
|
|
14
16
|
import { buildToolRegistry } from "./tooling/registry-build.js";
|
|
15
17
|
import { createHash } from "node:crypto";
|
|
16
18
|
import { NOOP_LOGGER, NOOP_TRACER, isStepCount, zeroUsage } from "@graphorin/core";
|
|
@@ -19,10 +21,11 @@ import { createReadResultTool, createToolSearchTool } from "@graphorin/tools/bui
|
|
|
19
21
|
import { createCodeExecuteTool, createCodeSearchTool, projectToolApi } from "@graphorin/tools/code-mode";
|
|
20
22
|
import { createToolExecutor } from "@graphorin/tools/executor";
|
|
21
23
|
import { createDefaultSpillWriter, createFileResultReader } from "@graphorin/tools/result";
|
|
24
|
+
import { projectSchemaToJsonSchema } from "@graphorin/tools/schema";
|
|
22
25
|
|
|
23
26
|
//#region src/factory.ts
|
|
24
27
|
/**
|
|
25
|
-
* `createAgent({...})`
|
|
28
|
+
* `createAgent({...})` - the agent factory entry point.
|
|
26
29
|
*
|
|
27
30
|
* Wires the typed `model -> tool calls -> model` loop, the streamed
|
|
28
31
|
* event surface, the steering / followUp queues, durable HITL via
|
|
@@ -46,23 +49,26 @@ const HANDOFF_TOOL_PREFIX = "transfer_to_";
|
|
|
46
49
|
/** The built-in deferred-discovery tool's stable name (WI-05 / P0-3). */
|
|
47
50
|
const TOOL_SEARCH_NAME = "tool_search";
|
|
48
51
|
/**
|
|
49
|
-
* Register the built-in `tool_search` into `registry` when
|
|
50
|
-
* when
|
|
52
|
+
* Register the built-in `tool_search` into `registry` when - and only
|
|
53
|
+
* when - the registry holds at least one deferred tool
|
|
51
54
|
* (`defer_loading: true`). `tool_search` is itself eager (so it is
|
|
52
55
|
* always advertised while a deferred pool exists) and resolvable by the
|
|
53
56
|
* executor like any other tool, so a model can both *see* it in the
|
|
54
57
|
* per-step catalogue and *call* it.
|
|
55
58
|
*
|
|
56
|
-
* No-op when nothing defers (zero overhead
|
|
59
|
+
* No-op when nothing defers (zero overhead - the tool never appears) or
|
|
57
60
|
* when a user tool already occupies the name (the user's tool wins; we
|
|
58
61
|
* never clobber it). Because deferral is decided at registration time
|
|
59
62
|
* (`normaliseTool`), the deferred set is fixed for the life of the
|
|
60
|
-
* registry
|
|
63
|
+
* registry - this runs once per registry, not per step.
|
|
61
64
|
*/
|
|
62
|
-
function registerToolSearch(registry) {
|
|
65
|
+
function registerToolSearch(registry, availability) {
|
|
63
66
|
if (registry.listDeferred().length === 0) return;
|
|
64
67
|
if (registry.get(TOOL_SEARCH_NAME) !== void 0) return;
|
|
65
|
-
registry.register(createToolSearchTool({
|
|
68
|
+
registry.register(createToolSearchTool({
|
|
69
|
+
registry,
|
|
70
|
+
...availability !== void 0 ? { availability } : {}
|
|
71
|
+
}), {
|
|
66
72
|
kind: "built-in",
|
|
67
73
|
subsystem: "tool-discovery"
|
|
68
74
|
});
|
|
@@ -72,7 +78,7 @@ const READ_RESULT_NAME = "read_result";
|
|
|
72
78
|
/**
|
|
73
79
|
* Register the built-in `read_result` into `registry` when at least one
|
|
74
80
|
* registered tool opts into the `'spill-to-file'` truncation strategy
|
|
75
|
-
* (the sole producer of spill handles today)
|
|
81
|
+
* (the sole producer of spill handles today) - or when `force` is set,
|
|
76
82
|
* which the agent passes when an operator wires external result readers
|
|
77
83
|
* (e.g. an MCP `resource_link` reader; WI-13). The tool is eager, so it
|
|
78
84
|
* is advertised alongside the producing tool and the model can fetch a
|
|
@@ -109,27 +115,28 @@ function composeResultReaders(readers) {
|
|
|
109
115
|
/** The code-mode meta-tools' stable names (WI-11 / P1-2). */
|
|
110
116
|
const CODE_EXECUTE_NAME = "code_execute";
|
|
111
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";
|
|
112
120
|
/**
|
|
113
121
|
* Wire code-mode (P1-2) into `registry`: register the `code_search` /
|
|
114
122
|
* `code_execute` meta-tools and return them as the tools to advertise in
|
|
115
123
|
* place of the full catalogue. The model reaches every other tool through
|
|
116
124
|
* `code_execute`, whose in-script `tools.<name>(args)` calls route back
|
|
117
125
|
* through `quietExecutor.executeOne(...)` under the calling step's
|
|
118
|
-
* `runContext`
|
|
126
|
+
* `runContext` - so per-tool ACL / sanitization / truncation still apply,
|
|
119
127
|
* exactly as in direct mode. `quietExecutor` carries no `streamingSink`,
|
|
120
128
|
* so the inner calls do not interleave `tool.execute.*` events into the
|
|
121
129
|
* outer stream.
|
|
122
130
|
*
|
|
123
131
|
* Excluded from the code API (`reservedNames`): the meta-tools, the
|
|
124
132
|
* discovery / handle built-ins, handoff tools (which stay first-class
|
|
125
|
-
* provider tools), and
|
|
133
|
+
* provider tools), and - supplied by the caller - any approval-gated
|
|
126
134
|
* tool, since code-mode has no durable-HITL path to suspend mid-script.
|
|
127
135
|
*
|
|
128
136
|
* Returns `[]` (registering nothing) when no real tool is exposable.
|
|
129
137
|
*/
|
|
130
|
-
function registerCodeMode(registry, quietExecutor, reservedNames) {
|
|
138
|
+
function registerCodeMode(registry, quietExecutor, reservedNames, getRunCapability) {
|
|
131
139
|
if (registry.get(CODE_EXECUTE_NAME) !== void 0) return [];
|
|
132
|
-
const isApprovalGated = (t) => t.needsApproval === true || typeof t.needsApproval === "function";
|
|
133
140
|
const codeTools = registry.list().filter((entry) => !reservedNames.has(entry.name) && !isApprovalGated(entry));
|
|
134
141
|
if (codeTools.length === 0) return [];
|
|
135
142
|
const approvalGatedTools = registry.list().filter((entry) => !reservedNames.has(entry.name) && isApprovalGated(entry)).map((entry) => entry.name);
|
|
@@ -138,7 +145,8 @@ function registerCodeMode(registry, quietExecutor, reservedNames) {
|
|
|
138
145
|
const allowedSet = new Set(codeTools.map((entry) => entry.name));
|
|
139
146
|
const projection = projectToolApi(codeTools.filter((entry) => entry.__effectiveDeferLoading !== true));
|
|
140
147
|
const executeTool = async (call, ctx) => {
|
|
141
|
-
if (approvalGatedSet.has(call.name)) throw new Error(`${call.name} requires human approval and cannot run inside code_execute
|
|
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?.();
|
|
142
150
|
const { outcome } = await quietExecutor.executeOne({
|
|
143
151
|
call: {
|
|
144
152
|
toolCallId: newId("codecall"),
|
|
@@ -146,7 +154,8 @@ function registerCodeMode(registry, quietExecutor, reservedNames) {
|
|
|
146
154
|
args: call.args
|
|
147
155
|
},
|
|
148
156
|
runContext: ctx.runContext,
|
|
149
|
-
stepNumber: ctx.runContext.stepNumber
|
|
157
|
+
stepNumber: ctx.runContext.stepNumber,
|
|
158
|
+
...runCapability !== void 0 ? { capability: runCapability } : {}
|
|
150
159
|
});
|
|
151
160
|
if ("kind" in outcome) throw new Error(`${call.name}: ${outcome.message}`);
|
|
152
161
|
return outcome.output;
|
|
@@ -176,7 +185,7 @@ function registerCodeMode(registry, quietExecutor, reservedNames) {
|
|
|
176
185
|
* Fold a completed `tool_search` result into the per-run promotion set:
|
|
177
186
|
* every matched tool name becomes advertised (and thus callable) on the
|
|
178
187
|
* next step. Tolerant of unexpected shapes (e.g. a user-shadowed
|
|
179
|
-
* `tool_search`)
|
|
188
|
+
* `tool_search`) - only string `name`s inside a `matches` array promote.
|
|
180
189
|
*/
|
|
181
190
|
function recordToolSearchPromotions(output, promoted) {
|
|
182
191
|
if (typeof output !== "object" || output === null) return;
|
|
@@ -242,7 +251,7 @@ function stripJsonFence(text) {
|
|
|
242
251
|
* do not yet consume `ProviderRequest.outputType` natively (PS-24).
|
|
243
252
|
*/
|
|
244
253
|
function buildStructuredInstruction(spec) {
|
|
245
|
-
const parts = ["Respond with a single valid JSON value only
|
|
254
|
+
const parts = ["Respond with a single valid JSON value only - no prose, no Markdown code fences."];
|
|
246
255
|
if (spec.description !== void 0 && spec.description.length > 0) parts.push(spec.description);
|
|
247
256
|
if (spec.jsonSchema !== void 0) parts.push(`The JSON MUST conform to this JSON Schema:\n${JSON.stringify(spec.jsonSchema)}`);
|
|
248
257
|
return parts.join("\n");
|
|
@@ -260,27 +269,53 @@ function lastUserText(messages) {
|
|
|
260
269
|
/**
|
|
261
270
|
* AG-21: classify a **thrown** provider error into a {@link ProviderErrorKind}
|
|
262
271
|
* so the fallback chain can act on it, instead of flattening every exception to
|
|
263
|
-
* `'unknown'` (which is always fallback-ineligible). Structural
|
|
272
|
+
* `'unknown'` (which is always fallback-ineligible). Structural - reads the
|
|
264
273
|
* `kind` carried by `@graphorin/provider`'s `GraphorinProviderError` subclasses
|
|
265
274
|
* without importing them, keeping the agent decoupled from the provider package.
|
|
266
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
|
+
]);
|
|
267
286
|
function classifyThrownProviderErrorKind(cause) {
|
|
268
|
-
if (typeof cause === "object" && cause !== null
|
|
269
|
-
|
|
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
|
+
}
|
|
270
293
|
}
|
|
271
294
|
return "unknown";
|
|
272
295
|
}
|
|
273
|
-
/**
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
} });
|
|
279
314
|
}
|
|
280
315
|
function toolToDefinition(tool) {
|
|
281
316
|
const ts = tool;
|
|
282
|
-
const inputSchema = projectSchema(ts.inputSchema) ?? {};
|
|
283
|
-
const outputSchema = projectSchema(ts.outputSchema);
|
|
317
|
+
const inputSchema = projectSchema(ts.inputSchema, tool.name, "input") ?? {};
|
|
318
|
+
const outputSchema = projectSchema(ts.outputSchema, tool.name, "output");
|
|
284
319
|
const examples = renderToolExamples(tool);
|
|
285
320
|
return {
|
|
286
321
|
name: tool.name,
|
|
@@ -296,8 +331,8 @@ function toolToDefinition(tool) {
|
|
|
296
331
|
* renders them: the registry resolves the `defer_loading` auto-rule into
|
|
297
332
|
* `examplesEagerlyRendered`, so a deferred tool resolves to `false` and is
|
|
298
333
|
* skipped (its examples stay out of context even once `tool_search`
|
|
299
|
-
* promotes it). `undefined`
|
|
300
|
-
* eager tool
|
|
334
|
+
* promotes it). `undefined` - the "runtime decides" case for a plain
|
|
335
|
+
* eager tool - renders, since the tool is already advertised this step.
|
|
301
336
|
*
|
|
302
337
|
* Bounded to ≤5 to honour the `ToolExample` `[1,5]` contract even when a
|
|
303
338
|
* tool slipped past the registry's overflow WARN (which does not truncate).
|
|
@@ -482,7 +517,7 @@ function stripReasoningFromMessages(messages) {
|
|
|
482
517
|
}
|
|
483
518
|
/**
|
|
484
519
|
* Count the leading contiguous run of `system` messages in the initial
|
|
485
|
-
* buffer
|
|
520
|
+
* buffer - the trusted, KV-cache-stable instruction prefix. Captured
|
|
486
521
|
* once at run start (WI-09 / P1-1): auto-compaction summarises only the
|
|
487
522
|
* messages after this prefix, so the prefix stays byte-identical across
|
|
488
523
|
* every step (the provider's cache breakpoint is real) and a long run
|
|
@@ -491,17 +526,94 @@ function stripReasoningFromMessages(messages) {
|
|
|
491
526
|
* The length is fixed for the run rather than re-derived per compaction
|
|
492
527
|
* on purpose: each compaction inserts its summary as a `system` message
|
|
493
528
|
* right after the prefix, so re-scanning the leading run would absorb
|
|
494
|
-
* that summary into the prefix and shield it from the next compaction
|
|
529
|
+
* that summary into the prefix and shield it from the next compaction -
|
|
495
530
|
* summaries would stack unbounded. Pinning the original length keeps
|
|
496
531
|
* each prior summary inside the compactable body, where the next pass
|
|
497
532
|
* folds it into a fresh summary-of-summary.
|
|
498
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";
|
|
499
544
|
function countLeadingSystemMessages(messages) {
|
|
500
545
|
let i = 0;
|
|
501
|
-
while (i < messages.length
|
|
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
|
+
}
|
|
502
552
|
return i;
|
|
503
553
|
}
|
|
504
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
|
+
/**
|
|
505
617
|
* Build a fresh {@link Agent} from the supplied configuration.
|
|
506
618
|
*
|
|
507
619
|
* @stable
|
|
@@ -509,7 +621,7 @@ function countLeadingSystemMessages(messages) {
|
|
|
509
621
|
function createAgent(config) {
|
|
510
622
|
if (typeof config.name !== "string" || config.name.length === 0) throw new InvalidAgentConfigError("missing 'name'");
|
|
511
623
|
if (config.provider === void 0 || config.provider === null) throw new InvalidAgentConfigError("missing 'provider'");
|
|
512
|
-
if (config.outputType?.kind === "text" && config.outputType.schema !== void 0) throw new InvalidAgentConfigError("outputType.kind 'text' with a schema
|
|
624
|
+
if (config.outputType?.kind === "text" && config.outputType.schema !== void 0) throw new InvalidAgentConfigError("outputType.kind 'text' with a schema - did you mean kind: 'structured'?");
|
|
513
625
|
validatePreferredModel(config.preferredModel);
|
|
514
626
|
if (config.modelTierMap !== void 0) for (const [tier, spec] of Object.entries(config.modelTierMap)) {
|
|
515
627
|
if (!isModelHintLike(tier)) throw new InvalidPreferredModelError({ tier });
|
|
@@ -540,6 +652,7 @@ function createAgent(config) {
|
|
|
540
652
|
let abortController;
|
|
541
653
|
let pendingAbort;
|
|
542
654
|
let activeRunState;
|
|
655
|
+
let activeRunCapability;
|
|
543
656
|
/** AG-11: guards the one-in-flight-run-per-instance invariant. */
|
|
544
657
|
let runInFlight = false;
|
|
545
658
|
const externalEventQueue = [];
|
|
@@ -560,7 +673,13 @@ function createAgent(config) {
|
|
|
560
673
|
...config.tools !== void 0 ? { tools: config.tools } : {},
|
|
561
674
|
...config.skills !== void 0 ? { skills: config.skills } : {}
|
|
562
675
|
}).registry;
|
|
563
|
-
registerToolSearch(toolRegistry);
|
|
676
|
+
registerToolSearch(toolRegistry, config.toolPromotion === "run-boundary" ? "next-run" : "next-step");
|
|
677
|
+
if (config.plan === true && toolRegistry.get(PLAN_TOOL_NAME) === void 0) toolRegistry.register(createPlanTool((todos) => {
|
|
678
|
+
if (activeRunState !== void 0) activeRunState.todos = todos;
|
|
679
|
+
}), {
|
|
680
|
+
kind: "built-in",
|
|
681
|
+
subsystem: "planning"
|
|
682
|
+
});
|
|
564
683
|
const spillWriter = createDefaultSpillWriter();
|
|
565
684
|
const fileResultReader = createFileResultReader({ artifactRoot: spillWriter.artifactRoot });
|
|
566
685
|
const externalResultReaders = config.resultReaders ?? [];
|
|
@@ -588,9 +707,10 @@ function createAgent(config) {
|
|
|
588
707
|
const toolMemoryGuardFactory = memoryGuardWiring.memoryGuardFactory;
|
|
589
708
|
if (memory === void 0) {
|
|
590
709
|
const guarded = (config.tools ?? []).filter((t) => t.memoryGuardTier !== void 0);
|
|
591
|
-
if (guarded.length > 0) console.warn(`[graphorin/agent] ${guarded.length} tool(s) declare memoryGuardTier but no memory is wired
|
|
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.`);
|
|
592
711
|
}
|
|
593
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);
|
|
594
714
|
const toolStreamingSink = (event) => activeExecutorBridge?.sink(event);
|
|
595
715
|
const makeToolExecutor = (registry, opts) => createToolExecutor({
|
|
596
716
|
registry,
|
|
@@ -601,8 +721,10 @@ function createAgent(config) {
|
|
|
601
721
|
...memoryGuardWiring.memoryRegionReader !== void 0 ? { memoryRegionReader: memoryGuardWiring.memoryRegionReader } : {},
|
|
602
722
|
spill: spillWriter,
|
|
603
723
|
...toolDataFlowGuard !== void 0 ? { dataFlowGuard: toolDataFlowGuard } : {},
|
|
724
|
+
...toolArgumentPolicyGuard !== void 0 ? { argumentPolicy: toolArgumentPolicyGuard } : {},
|
|
604
725
|
...opts?.quiet === true ? {} : { streamingSink: toolStreamingSink },
|
|
605
|
-
...config.maxParallelTools !== void 0 ? { maxParallelTools: config.maxParallelTools } : {}
|
|
726
|
+
...config.maxParallelTools !== void 0 ? { maxParallelTools: config.maxParallelTools } : {},
|
|
727
|
+
...config.toolRetry !== void 0 ? { retry: config.toolRetry } : {}
|
|
606
728
|
});
|
|
607
729
|
const toolExecutor = makeToolExecutor(toolRegistry);
|
|
608
730
|
const isCodeMode = config.toolInvocation === "code-mode";
|
|
@@ -615,7 +737,7 @@ function createAgent(config) {
|
|
|
615
737
|
READ_RESULT_NAME,
|
|
616
738
|
...handoffMap.keys()
|
|
617
739
|
]);
|
|
618
|
-
const metas = registerCodeMode(toolRegistry, makeToolExecutor(toolRegistry, { quiet: true }), reserved);
|
|
740
|
+
const metas = registerCodeMode(toolRegistry, makeToolExecutor(toolRegistry, { quiet: true }), reserved, () => activeRunCapability);
|
|
619
741
|
registerReadResult(toolRegistry, resultReader);
|
|
620
742
|
const readResult = toolRegistry.get(READ_RESULT_NAME);
|
|
621
743
|
codeModeAdvertised = [...metas, ...readResult !== void 0 ? [readResult] : []];
|
|
@@ -624,7 +746,7 @@ function createAgent(config) {
|
|
|
624
746
|
/**
|
|
625
747
|
* AG-11: one in-flight run per Agent instance. `steer` / `followUp` /
|
|
626
748
|
* `abort` / `compact` address "the run" with no run handle, so
|
|
627
|
-
* overlapping runs on one instance cannot be expressed safely
|
|
749
|
+
* overlapping runs on one instance cannot be expressed safely - they
|
|
628
750
|
* would share the abort controller, steer queue, active-run ref and
|
|
629
751
|
* executor bridge. A second concurrent `run()` / `stream()` rejects
|
|
630
752
|
* with {@link ConcurrentRunError}; run-scoped state is reset on entry
|
|
@@ -637,12 +759,14 @@ function createAgent(config) {
|
|
|
637
759
|
runInFlight = true;
|
|
638
760
|
pendingSteer = [];
|
|
639
761
|
pendingAbort = void 0;
|
|
762
|
+
activeRunCapability = options.capability ?? config.capability ?? ruleOfTwoCapabilityFloor;
|
|
640
763
|
causalityMonitor?.reset();
|
|
641
764
|
try {
|
|
642
765
|
return yield* runLoopInner(input, options);
|
|
643
766
|
} finally {
|
|
644
767
|
runInFlight = false;
|
|
645
768
|
activeRunState = void 0;
|
|
769
|
+
activeRunCapability = void 0;
|
|
646
770
|
while (pendingManualCompacts.length > 0) pendingManualCompacts.shift()?.resolve(noopCompactionResult("no-active-run"));
|
|
647
771
|
}
|
|
648
772
|
}
|
|
@@ -667,6 +791,15 @@ function createAgent(config) {
|
|
|
667
791
|
});
|
|
668
792
|
activeRunState = state;
|
|
669
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
|
+
async function* finishRun(s, snapshot) {
|
|
798
|
+
const taintSnap = toolDataFlowGuard?.snapshotLedger(s.id);
|
|
799
|
+
if (taintSnap !== void 0) s.taintSummary = taintSnap;
|
|
800
|
+
if (promotedDeferred.size > 0) s.promotedTools = [...promotedDeferred];
|
|
801
|
+
return yield* finishRunBase(s, snapshot);
|
|
802
|
+
}
|
|
670
803
|
const messages = resumed ? [...state.messages] : [];
|
|
671
804
|
if (!resumed) {
|
|
672
805
|
const instructionsRaw = config.instructions;
|
|
@@ -709,6 +842,18 @@ function createAgent(config) {
|
|
|
709
842
|
for (const m of messages) state.messages.push(m);
|
|
710
843
|
}
|
|
711
844
|
const finalSnapshot = { output: "" };
|
|
845
|
+
const runSpan = tracer.startSpan({
|
|
846
|
+
type: "agent.run",
|
|
847
|
+
attrs: {
|
|
848
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
849
|
+
"gen_ai.agent.id": agentId,
|
|
850
|
+
"gen_ai.agent.name": config.name,
|
|
851
|
+
"graphorin.run.id": state.id,
|
|
852
|
+
"graphorin.session.id": sessionId,
|
|
853
|
+
"graphorin.run.resumed": resumed !== void 0
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
let currentStepSpan;
|
|
712
857
|
yield {
|
|
713
858
|
type: "agent.start",
|
|
714
859
|
runId: state.id,
|
|
@@ -758,6 +903,7 @@ function createAgent(config) {
|
|
|
758
903
|
}
|
|
759
904
|
const structuredInstruction = config.outputType?.kind === "structured" ? buildStructuredInstruction(config.outputType) : void 0;
|
|
760
905
|
const resumedApprovedCalls = [];
|
|
906
|
+
const grantedApprovals = [];
|
|
761
907
|
const journaledCallIds = /* @__PURE__ */ new Set();
|
|
762
908
|
for (const step of state.steps) for (const completed of step.toolCalls) journaledCallIds.add(completed.call.toolCallId);
|
|
763
909
|
if (resumed && options.directive?.approvals !== void 0 && state.pendingApprovals.length > 0) {
|
|
@@ -780,6 +926,7 @@ function createAgent(config) {
|
|
|
780
926
|
toolName: approval.toolName,
|
|
781
927
|
args: approval.args
|
|
782
928
|
});
|
|
929
|
+
grantedApprovals.push(approval);
|
|
783
930
|
} else {
|
|
784
931
|
yield {
|
|
785
932
|
type: "tool.approval.denied",
|
|
@@ -815,8 +962,29 @@ function createAgent(config) {
|
|
|
815
962
|
messages,
|
|
816
963
|
state
|
|
817
964
|
};
|
|
818
|
-
if (resumed &&
|
|
965
|
+
if (resumed && state.status === "failed") return yield* finishRun(state, finalSnapshot);
|
|
819
966
|
if (resumed && resumedApprovedCalls.length > 0) {
|
|
967
|
+
if (config.checkpointStore !== void 0) {
|
|
968
|
+
const prevStatus = state.status;
|
|
969
|
+
state.status = "awaiting_approval";
|
|
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
|
+
}
|
|
820
988
|
state.steps.push({
|
|
821
989
|
stepNumber: 0,
|
|
822
990
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -829,19 +997,31 @@ function createAgent(config) {
|
|
|
829
997
|
...runContextBase,
|
|
830
998
|
stepNumber: 0,
|
|
831
999
|
messages
|
|
832
|
-
}, 0);
|
|
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
|
+
});
|
|
833
1014
|
}
|
|
834
|
-
|
|
835
|
-
if (resumed && state.promotedTools !== void 0) for (const name of state.promotedTools) promotedDeferred.add(name);
|
|
1015
|
+
if (resumed && state.status === "awaiting_approval") return yield* finishRun(state, finalSnapshot);
|
|
836
1016
|
/**
|
|
837
1017
|
* Dispatch a batch of (non-handoff) tool calls through the
|
|
838
1018
|
* {@link ToolExecutor} and surface the results as `AgentEvent`s.
|
|
839
1019
|
*
|
|
840
1020
|
* The agent owns the `tool.execute.start` / `.end` / `.error`
|
|
841
1021
|
* lifecycle (derived deterministically from the returned
|
|
842
|
-
* {@link CompletedToolCall} outcomes) so every outcome kind
|
|
1022
|
+
* {@link CompletedToolCall} outcomes) so every outcome kind -
|
|
843
1023
|
* success, unknown-tool, invalid-input, sanitization-blocked,
|
|
844
|
-
* execution error
|
|
1024
|
+
* execution error - yields a consistent event and tool message,
|
|
845
1025
|
* preserving the pre-WI-03 stream shape (R10). The executor's
|
|
846
1026
|
* genuinely-live streaming events (`tool.execute.progress` /
|
|
847
1027
|
* `.partial`, emitted only by streaming-hint tools) are bridged
|
|
@@ -850,13 +1030,13 @@ function createAgent(config) {
|
|
|
850
1030
|
* Parallelism (WI-04): the executor runs independent calls in this
|
|
851
1031
|
* batch concurrently, bounded by `maxParallelTools`. `tool.execute.start`
|
|
852
1032
|
* is emitted up-front in call order and `.end` / `.error` after the
|
|
853
|
-
* batch settles, also in call order
|
|
1033
|
+
* batch settles, also in call order - so result mapping and tool-message
|
|
854
1034
|
* history are deterministic regardless of which call finishes first,
|
|
855
1035
|
* while `.progress` / `.partial` events for concurrent calls interleave
|
|
856
1036
|
* (keyed by `toolCallId`). Tools declaring `executionMode: 'sequential'`
|
|
857
1037
|
* are serialised by the executor and never overlap.
|
|
858
1038
|
*/
|
|
859
|
-
async function* dispatchBatch(calls, executor, runContext, stepNum) {
|
|
1039
|
+
async function* dispatchBatch(calls, executor, runContext, stepNum, dispatchOpts = {}) {
|
|
860
1040
|
if (calls.length === 0) return;
|
|
861
1041
|
for (const call of calls) yield {
|
|
862
1042
|
type: "tool.execute.start",
|
|
@@ -867,7 +1047,9 @@ function createAgent(config) {
|
|
|
867
1047
|
const resultsPromise = executor.executeBatch({
|
|
868
1048
|
calls,
|
|
869
1049
|
runContext,
|
|
870
|
-
stepNumber: stepNum
|
|
1050
|
+
stepNumber: stepNum,
|
|
1051
|
+
...dispatchOpts.disableRepair !== void 0 ? { disableRepair: dispatchOpts.disableRepair } : {},
|
|
1052
|
+
...activeRunCapability !== void 0 ? { capability: activeRunCapability } : {}
|
|
871
1053
|
});
|
|
872
1054
|
const closeOnSettle = resultsPromise.then(() => bridge.close(), () => bridge.close());
|
|
873
1055
|
for await (const event of bridge.drain()) if (event.type === "tool.execute.progress" || event.type === "tool.execute.partial") yield event;
|
|
@@ -887,7 +1069,7 @@ function createAgent(config) {
|
|
|
887
1069
|
toolCallId: call.toolCallId,
|
|
888
1070
|
error: outcome
|
|
889
1071
|
};
|
|
890
|
-
const text =
|
|
1072
|
+
const text = renderToolErrorMessage(outcome);
|
|
891
1073
|
messages.push({
|
|
892
1074
|
role: "tool",
|
|
893
1075
|
toolCallId: call.toolCallId,
|
|
@@ -908,7 +1090,8 @@ function createAgent(config) {
|
|
|
908
1090
|
durationMs: outcome.durationMs
|
|
909
1091
|
};
|
|
910
1092
|
const handle = outcome.resultHandle;
|
|
911
|
-
const
|
|
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;
|
|
912
1095
|
messages.push({
|
|
913
1096
|
role: "tool",
|
|
914
1097
|
toolCallId: call.toolCallId,
|
|
@@ -930,20 +1113,20 @@ function createAgent(config) {
|
|
|
930
1113
|
* in-flight buffer has crossed its per-provider threshold
|
|
931
1114
|
* (`shouldCompact`); when it has, summarise the older turns
|
|
932
1115
|
* (`compactNow`, `source: 'auto-trigger'`), splice the result back in
|
|
933
|
-
*
|
|
934
|
-
* turns verbatim
|
|
1116
|
+
* - preserving the byte-stable system prefix and the most-recent
|
|
1117
|
+
* turns verbatim - and emit `context.compacted`. The compaction is
|
|
935
1118
|
* configured on the memory facade (`createMemory({ contextEngine })`,
|
|
936
1119
|
* RB-46); there is no parallel agent-level knob.
|
|
937
1120
|
*
|
|
938
1121
|
* No-op when no memory is wired, when compaction is disabled or below
|
|
939
1122
|
* threshold (the engine returns `false`), or for `secret`-tier runs
|
|
940
|
-
* (secret history is never shipped to the summarizer
|
|
1123
|
+
* (secret history is never shipped to the summarizer - a less-trusted
|
|
941
1124
|
* external sink; per-result handle references land in WI-10). Best
|
|
942
1125
|
* effort: a misconfigured engine (e.g. no summarizer) is swallowed and
|
|
943
1126
|
* the run proceeds uncompacted rather than aborting mid-flight.
|
|
944
1127
|
*
|
|
945
1128
|
* Operator-requested compactions (`agent.compact()`, CE-3/AG-13) are
|
|
946
|
-
* serviced here too, FIRST
|
|
1129
|
+
* serviced here too, FIRST - the queue carries the `compact()` promise
|
|
947
1130
|
* resolvers, and manual requests bypass the trigger evaluation.
|
|
948
1131
|
*/
|
|
949
1132
|
async function* maybeAutoCompact() {
|
|
@@ -955,7 +1138,7 @@ function createAgent(config) {
|
|
|
955
1138
|
if (mem === void 0) return;
|
|
956
1139
|
if (config.sensitivity === "secret") return;
|
|
957
1140
|
const engine = mem.contextEngine;
|
|
958
|
-
if (!await engine.shouldCompact(messages).catch(() => false)) return;
|
|
1141
|
+
if (!await engine.shouldCompact(messages, { compactableFromIndex: systemPrefixLength }).catch(() => false)) return;
|
|
959
1142
|
const startedAt = Date.now();
|
|
960
1143
|
const envelope = await engine.compactNow({
|
|
961
1144
|
scope: {
|
|
@@ -968,6 +1151,7 @@ function createAgent(config) {
|
|
|
968
1151
|
agentId,
|
|
969
1152
|
source: "auto-trigger",
|
|
970
1153
|
messages: messages.slice(systemPrefixLength),
|
|
1154
|
+
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
971
1155
|
memory: mem
|
|
972
1156
|
}).catch(() => void 0);
|
|
973
1157
|
if (envelope === void 0) return;
|
|
@@ -987,6 +1171,51 @@ function createAgent(config) {
|
|
|
987
1171
|
};
|
|
988
1172
|
}
|
|
989
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
|
+
/**
|
|
990
1219
|
* Prefix-pinned splice shared by the auto + manual compaction paths
|
|
991
1220
|
* (CE-3): stable system prefix + [summary, ...recent turns], with the
|
|
992
1221
|
* post-compaction hooks' text Context Essentials re-anchored as a
|
|
@@ -1032,6 +1261,7 @@ function createAgent(config) {
|
|
|
1032
1261
|
agentId,
|
|
1033
1262
|
source,
|
|
1034
1263
|
messages: messages.slice(systemPrefixLength),
|
|
1264
|
+
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
1035
1265
|
memory: mem,
|
|
1036
1266
|
...pending.options?.preserveRecentTurns !== void 0 ? { preserveRecentTurns: pending.options.preserveRecentTurns } : {}
|
|
1037
1267
|
});
|
|
@@ -1069,6 +1299,7 @@ function createAgent(config) {
|
|
|
1069
1299
|
}
|
|
1070
1300
|
const handoffNames = Array.from(handoffMap.keys());
|
|
1071
1301
|
let stepNumber = 0;
|
|
1302
|
+
let verifierRoundsUsed = 0;
|
|
1072
1303
|
let lastStepCalledToolNames = [];
|
|
1073
1304
|
async function* emitCancellation() {
|
|
1074
1305
|
yield {
|
|
@@ -1126,17 +1357,30 @@ function createAgent(config) {
|
|
|
1126
1357
|
type: "step.start",
|
|
1127
1358
|
stepNumber
|
|
1128
1359
|
};
|
|
1360
|
+
currentStepSpan?.end();
|
|
1361
|
+
currentStepSpan = tracer.startSpan({
|
|
1362
|
+
type: "agent.step",
|
|
1363
|
+
parent: runSpan,
|
|
1364
|
+
attrs: {
|
|
1365
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
1366
|
+
"gen_ai.agent.id": agentId,
|
|
1367
|
+
"gen_ai.agent.name": config.name,
|
|
1368
|
+
"graphorin.run.id": state.id,
|
|
1369
|
+
"graphorin.step.number": stepNumber
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1129
1372
|
yield* maybeAutoCompact();
|
|
1130
1373
|
const stepCtx = {
|
|
1131
1374
|
...runContextBase,
|
|
1132
1375
|
stepNumber,
|
|
1133
|
-
messages
|
|
1376
|
+
messages,
|
|
1377
|
+
...currentStepSpan !== void 0 ? { span: currentStepSpan } : {}
|
|
1134
1378
|
};
|
|
1135
1379
|
const overrides = config.prepareStep ? await config.prepareStep(stepCtx) : {};
|
|
1136
1380
|
const useOverrideRegistry = overrides.tools !== void 0 && !isCodeMode;
|
|
1137
1381
|
const stepRegistry = useOverrideRegistry ? buildToolRegistry({ tools: overrides.tools }).registry : toolRegistry;
|
|
1138
1382
|
if (useOverrideRegistry) {
|
|
1139
|
-
registerToolSearch(stepRegistry);
|
|
1383
|
+
registerToolSearch(stepRegistry, config.toolPromotion === "run-boundary" ? "next-run" : "next-step");
|
|
1140
1384
|
registerReadResult(stepRegistry, resultReader);
|
|
1141
1385
|
}
|
|
1142
1386
|
const stepExecutor = useOverrideRegistry ? makeToolExecutor(stepRegistry) : toolExecutor;
|
|
@@ -1145,15 +1389,21 @@ function createAgent(config) {
|
|
|
1145
1389
|
if (h === void 0) throw new ToolNotFoundError(n);
|
|
1146
1390
|
return buildHandoffTool(h.agent);
|
|
1147
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
|
+
};
|
|
1148
1397
|
let stepTools;
|
|
1149
|
-
if (isCodeMode) stepTools = [...codeModeAdvertised, ...handoffTools];
|
|
1398
|
+
if (isCodeMode) stepTools = readOnlyRun ? [...codeModeAdvertised.filter(keepReadOnly)] : [...codeModeAdvertised, ...handoffTools];
|
|
1150
1399
|
else {
|
|
1151
1400
|
const eagerTools = stepRegistry.listEager();
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1401
|
+
const advertisedPromotions = runStartPromotions ?? promotedDeferred;
|
|
1402
|
+
const promotedTools = advertisedPromotions.size === 0 ? [] : orderPromotedTools(advertisedPromotions, stepRegistry.listDeferred());
|
|
1403
|
+
stepTools = readOnlyRun ? [...eagerTools.filter(keepReadOnly), ...promotedTools.filter(keepReadOnly)] : [
|
|
1154
1404
|
...eagerTools,
|
|
1155
|
-
...
|
|
1156
|
-
...
|
|
1405
|
+
...handoffTools,
|
|
1406
|
+
...promotedTools
|
|
1157
1407
|
];
|
|
1158
1408
|
}
|
|
1159
1409
|
const calledLastStep = new Set(lastStepCalledToolNames);
|
|
@@ -1175,10 +1425,7 @@ function createAgent(config) {
|
|
|
1175
1425
|
}
|
|
1176
1426
|
const toolDefs = stepTools.map((t) => toolToDefinition(t));
|
|
1177
1427
|
const baseRequest = {
|
|
1178
|
-
messages:
|
|
1179
|
-
role: "system",
|
|
1180
|
-
content: structuredInstruction
|
|
1181
|
-
}],
|
|
1428
|
+
messages: buildStepMessages(messages, structuredInstruction, activeRunState?.todos),
|
|
1182
1429
|
...config.outputType !== void 0 ? { outputType: {
|
|
1183
1430
|
kind: config.outputType.kind,
|
|
1184
1431
|
...config.outputType.description !== void 0 ? { description: config.outputType.description } : {},
|
|
@@ -1196,6 +1443,8 @@ function createAgent(config) {
|
|
|
1196
1443
|
signal,
|
|
1197
1444
|
...overrides.temperature !== void 0 ? { temperature: overrides.temperature } : {},
|
|
1198
1445
|
...overrides.maxTokens !== void 0 ? { maxTokens: overrides.maxTokens } : {},
|
|
1446
|
+
...config.cachePolicy !== void 0 ? { cachePolicy: config.cachePolicy } : {},
|
|
1447
|
+
...currentStepSpan !== void 0 ? { parentSpan: currentStepSpan } : {},
|
|
1199
1448
|
reasoningRetention: reasoningPolicy
|
|
1200
1449
|
};
|
|
1201
1450
|
const stepUsage = zeroUsage();
|
|
@@ -1207,6 +1456,8 @@ function createAgent(config) {
|
|
|
1207
1456
|
let lastError;
|
|
1208
1457
|
let finalCalls = [];
|
|
1209
1458
|
let stepReasoningParts = [];
|
|
1459
|
+
let requestForStep = baseRequest;
|
|
1460
|
+
let emergencyCompactTried = false;
|
|
1210
1461
|
for (let chainIdx = 0; chainIdx < fallbackChain.length; chainIdx++) {
|
|
1211
1462
|
const candidate = fallbackChain[chainIdx];
|
|
1212
1463
|
if (candidate === void 0) continue;
|
|
@@ -1239,18 +1490,13 @@ function createAgent(config) {
|
|
|
1239
1490
|
let providerCallCompleted = false;
|
|
1240
1491
|
let providerStepUsage = zeroUsage();
|
|
1241
1492
|
try {
|
|
1242
|
-
const stream$1 = providerForStep.stream(
|
|
1493
|
+
const stream$1 = providerForStep.stream(requestForStep);
|
|
1243
1494
|
for await (const ev of stream$1) {
|
|
1244
1495
|
if (signal.aborted && pendingAbort?.drain !== true) throw new AgentRuntimeError("run-aborted", "aborted");
|
|
1245
1496
|
const out = handleProviderEvent(ev, evState);
|
|
1246
1497
|
if (out.emit !== void 0) yield out.emit;
|
|
1247
1498
|
if (out.providerError !== void 0) providerError = out.providerError;
|
|
1248
|
-
if (out.usage !== void 0) providerStepUsage =
|
|
1249
|
-
promptTokens: providerStepUsage.promptTokens + out.usage.promptTokens,
|
|
1250
|
-
completionTokens: providerStepUsage.completionTokens + out.usage.completionTokens,
|
|
1251
|
-
totalTokens: providerStepUsage.totalTokens + out.usage.totalTokens,
|
|
1252
|
-
...out.usage.reasoningTokens !== void 0 || providerStepUsage.reasoningTokens !== void 0 ? { reasoningTokens: (providerStepUsage.reasoningTokens ?? 0) + (out.usage.reasoningTokens ?? 0) } : {}
|
|
1253
|
-
};
|
|
1499
|
+
if (out.usage !== void 0) providerStepUsage = addUsage(providerStepUsage, out.usage);
|
|
1254
1500
|
if (out.finished === true) providerCallCompleted = true;
|
|
1255
1501
|
}
|
|
1256
1502
|
} catch (cause) {
|
|
@@ -1264,6 +1510,17 @@ function createAgent(config) {
|
|
|
1264
1510
|
}
|
|
1265
1511
|
if (providerError !== void 0) {
|
|
1266
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
|
+
}
|
|
1267
1524
|
if (!isAgentFallbackEligible(providerError, fallbackPolicy).eligible || chainIdx === fallbackChain.length - 1) {
|
|
1268
1525
|
yield {
|
|
1269
1526
|
type: "agent.error",
|
|
@@ -1289,10 +1546,7 @@ function createAgent(config) {
|
|
|
1289
1546
|
type: "reasoning",
|
|
1290
1547
|
text: evState.reasoningBuffer
|
|
1291
1548
|
}];
|
|
1292
|
-
stepUsage
|
|
1293
|
-
stepUsage.completionTokens += providerStepUsage.completionTokens;
|
|
1294
|
-
stepUsage.totalTokens += providerStepUsage.totalTokens;
|
|
1295
|
-
if (providerStepUsage.reasoningTokens !== void 0) stepUsage.reasoningTokens = (stepUsage.reasoningTokens ?? 0) + providerStepUsage.reasoningTokens;
|
|
1549
|
+
accumulateUsage(stepUsage, providerStepUsage);
|
|
1296
1550
|
break;
|
|
1297
1551
|
}
|
|
1298
1552
|
}
|
|
@@ -1317,15 +1571,13 @@ function createAgent(config) {
|
|
|
1317
1571
|
}
|
|
1318
1572
|
usageAcc.add(lastModelId, stepUsage);
|
|
1319
1573
|
addModelUsage(state, lastModelId, stepUsage);
|
|
1320
|
-
state.usage
|
|
1321
|
-
state.usage.completionTokens += stepUsage.completionTokens;
|
|
1322
|
-
state.usage.totalTokens += stepUsage.totalTokens;
|
|
1323
|
-
if (stepUsage.reasoningTokens !== void 0) state.usage.reasoningTokens = (state.usage.reasoningTokens ?? 0) + stepUsage.reasoningTokens;
|
|
1574
|
+
accumulateUsage(state.usage, stepUsage);
|
|
1324
1575
|
const leakCheck = causalityMonitor !== void 0 && textBuffer.length > 0 ? causalityMonitor.checkMessage(textBuffer) : void 0;
|
|
1325
1576
|
const leakBlocked = leakCheck?.leakDetected === true && leakCheck.decision === "block";
|
|
1326
1577
|
const assistant = buildAssistantMessage(leakBlocked ? LATERAL_LEAK_BLOCKED_NOTICE : textBuffer, stepReasoningParts, finalCalls, agentId, reasoningPolicy);
|
|
1327
1578
|
messages.push(assistant);
|
|
1328
1579
|
state.messages.push(assistant);
|
|
1580
|
+
if (toolDataFlowGuard !== void 0 && textBuffer.length > 0 && !leakBlocked) toolDataFlowGuard.recordAssistant(state.id, textBuffer);
|
|
1329
1581
|
if (leakCheck?.leakDetected === true) {
|
|
1330
1582
|
const sha = sha256Hex(textBuffer);
|
|
1331
1583
|
yield {
|
|
@@ -1350,7 +1602,12 @@ function createAgent(config) {
|
|
|
1350
1602
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1351
1603
|
usage: stepUsage,
|
|
1352
1604
|
toolCalls: [],
|
|
1353
|
-
agentId: state.currentAgentId
|
|
1605
|
+
agentId: state.currentAgentId,
|
|
1606
|
+
...config.recordProviderResponses === true ? { providerResponse: {
|
|
1607
|
+
modelId: lastModelId,
|
|
1608
|
+
...textBuffer.length > 0 ? { text: textBuffer } : {},
|
|
1609
|
+
...finalCalls.length > 0 ? { toolCalls: [...finalCalls] } : {}
|
|
1610
|
+
} } : {}
|
|
1354
1611
|
};
|
|
1355
1612
|
state.steps.push(stepRecord);
|
|
1356
1613
|
lastStepCalledToolNames = finalCalls.map((c) => c.toolName);
|
|
@@ -1365,9 +1622,11 @@ function createAgent(config) {
|
|
|
1365
1622
|
const execRunContext = {
|
|
1366
1623
|
...runContextBase,
|
|
1367
1624
|
stepNumber,
|
|
1368
|
-
messages
|
|
1625
|
+
messages,
|
|
1626
|
+
...currentStepSpan !== void 0 ? { span: currentStepSpan } : {}
|
|
1369
1627
|
};
|
|
1370
1628
|
let batch = [];
|
|
1629
|
+
let stepApprovalsRequested = 0;
|
|
1371
1630
|
for (const call of finalCalls) {
|
|
1372
1631
|
const handoff = handoffMap.get(call.toolName);
|
|
1373
1632
|
if (handoff !== void 0) {
|
|
@@ -1428,7 +1687,7 @@ function createAgent(config) {
|
|
|
1428
1687
|
toolCallId: call.toolCallId,
|
|
1429
1688
|
error: toolError
|
|
1430
1689
|
};
|
|
1431
|
-
const text =
|
|
1690
|
+
const text = renderToolErrorMessage(toolError);
|
|
1432
1691
|
messages.push({
|
|
1433
1692
|
role: "tool",
|
|
1434
1693
|
toolCallId: call.toolCallId,
|
|
@@ -1471,7 +1730,48 @@ function createAgent(config) {
|
|
|
1471
1730
|
});
|
|
1472
1731
|
continue;
|
|
1473
1732
|
}
|
|
1474
|
-
|
|
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
|
+
}
|
|
1774
|
+
if (await invokeNeedsApproval(resolvedTool, gateInput, execRunContext, signal)) {
|
|
1475
1775
|
if (batch.length > 0) {
|
|
1476
1776
|
yield* dispatchBatch(batch, stepExecutor, execRunContext, stepNumber);
|
|
1477
1777
|
batch = [];
|
|
@@ -1487,39 +1787,82 @@ function createAgent(config) {
|
|
|
1487
1787
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1488
1788
|
};
|
|
1489
1789
|
state.pendingApprovals.push(approval);
|
|
1490
|
-
|
|
1491
|
-
const taintSnap = toolDataFlowGuard?.snapshotLedger(state.id);
|
|
1492
|
-
if (taintSnap !== void 0) state.taintSummary = taintSnap;
|
|
1493
|
-
if (promotedDeferred.size > 0) state.promotedTools = [...promotedDeferred];
|
|
1790
|
+
stepApprovalsRequested += 1;
|
|
1494
1791
|
yield {
|
|
1495
1792
|
type: "tool.approval.requested",
|
|
1496
1793
|
toolCallId: call.toolCallId
|
|
1497
1794
|
};
|
|
1498
|
-
|
|
1499
|
-
id: state.id,
|
|
1500
|
-
threadId: state.id,
|
|
1501
|
-
namespace: "agent",
|
|
1502
|
-
state: serializeRunState(state, { stripTracingApiKey: true }),
|
|
1503
|
-
channelVersions: {},
|
|
1504
|
-
stepNumber,
|
|
1505
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1506
|
-
}, {
|
|
1507
|
-
source: "sync",
|
|
1508
|
-
status: "suspended",
|
|
1509
|
-
nodeName: "agent.run"
|
|
1510
|
-
});
|
|
1511
|
-
return yield* finishRun(state, finalSnapshot);
|
|
1795
|
+
continue;
|
|
1512
1796
|
}
|
|
1513
1797
|
batch.push(call);
|
|
1514
1798
|
}
|
|
1515
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
|
+
return yield* finishRun(state, finalSnapshot);
|
|
1819
|
+
}
|
|
1516
1820
|
}
|
|
1821
|
+
currentStepSpan?.setAttributes({
|
|
1822
|
+
"gen_ai.usage.input_tokens": stepUsage.promptTokens,
|
|
1823
|
+
"gen_ai.usage.output_tokens": stepUsage.completionTokens
|
|
1824
|
+
});
|
|
1825
|
+
currentStepSpan?.end();
|
|
1826
|
+
currentStepSpan = void 0;
|
|
1517
1827
|
yield {
|
|
1518
1828
|
type: "step.end",
|
|
1519
1829
|
stepNumber,
|
|
1520
1830
|
usage: stepUsage
|
|
1521
1831
|
};
|
|
1522
1832
|
if (finalCalls.length === 0) {
|
|
1833
|
+
if (config.verifiers !== void 0 && config.verifiers.length > 0) {
|
|
1834
|
+
const feedbacks = [];
|
|
1835
|
+
for (const verifier of config.verifiers) {
|
|
1836
|
+
let result;
|
|
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
|
+
}
|
|
1865
|
+
}
|
|
1523
1866
|
state.status = "completed";
|
|
1524
1867
|
break;
|
|
1525
1868
|
}
|
|
@@ -1541,6 +1884,15 @@ function createAgent(config) {
|
|
|
1541
1884
|
};
|
|
1542
1885
|
return yield* finishRun(state, finalSnapshot);
|
|
1543
1886
|
} finally {
|
|
1887
|
+
currentStepSpan?.end();
|
|
1888
|
+
currentStepSpan = void 0;
|
|
1889
|
+
runSpan.setAttributes({
|
|
1890
|
+
"gen_ai.usage.input_tokens": state.usage.promptTokens,
|
|
1891
|
+
"gen_ai.usage.output_tokens": state.usage.completionTokens,
|
|
1892
|
+
"graphorin.run.status": state.status
|
|
1893
|
+
});
|
|
1894
|
+
runSpan.setStatus(state.status === "failed" ? "error" : "ok");
|
|
1895
|
+
runSpan.end();
|
|
1544
1896
|
if (parentSignal !== void 0) parentSignal.removeEventListener("abort", onParentAbort);
|
|
1545
1897
|
}
|
|
1546
1898
|
if (state.status === "running") {
|
|
@@ -1614,10 +1966,10 @@ function createAgent(config) {
|
|
|
1614
1966
|
}
|
|
1615
1967
|
/**
|
|
1616
1968
|
* Terminal wrapper around {@link finalize}: every exit path of the run
|
|
1617
|
-
* loop
|
|
1969
|
+
* loop - completed, failed, aborted, suspended - ends the stream with
|
|
1618
1970
|
* an `agent.end` event carrying the final {@link AgentResult} (AG-20).
|
|
1619
1971
|
*/
|
|
1620
|
-
async function*
|
|
1972
|
+
async function* finishRunBase(state, snapshot) {
|
|
1621
1973
|
while (pendingManualCompacts.length > 0) pendingManualCompacts.shift()?.resolve(noopCompactionResult("no-active-run"));
|
|
1622
1974
|
const result = finalize(state, snapshot);
|
|
1623
1975
|
if (result.status === "completed" || result.status === "failed") await spillWriter.clear?.(result.state.id).catch(() => {});
|
|
@@ -1692,8 +2044,26 @@ function createAgent(config) {
|
|
|
1692
2044
|
pendingAbort = options ?? {};
|
|
1693
2045
|
abortController?.abort();
|
|
1694
2046
|
};
|
|
2047
|
+
const foldRunOutcome = (result, maxChars) => {
|
|
2048
|
+
const steps = result.state.steps;
|
|
2049
|
+
const toolNames = [...new Set(steps.flatMap((step) => step.toolCalls.map((c) => c.call.toolName)))];
|
|
2050
|
+
const header = `[sub-agent '${config.name}' outcome] status=${result.status}; steps=${steps.length}; toolCalls=${steps.reduce((n, st) => n + st.toolCalls.length, 0)}` + (toolNames.length > 0 ? `; tools=${toolNames.join(", ")}` : "");
|
|
2051
|
+
const text = typeof result.output === "string" ? result.output : JSON.stringify(result.output);
|
|
2052
|
+
return `${header}\n${text.length > maxChars ? `${text.slice(0, maxChars)}\n[... ${text.length - maxChars} chars truncated by contextFold]` : text}`;
|
|
2053
|
+
};
|
|
2054
|
+
const taintFromChildState = (state) => {
|
|
2055
|
+
const summary = state.taintSummary;
|
|
2056
|
+
if (summary === void 0 || !summary.untrustedSeen && !summary.sensitiveSeen) return;
|
|
2057
|
+
return {
|
|
2058
|
+
...summary.untrustedSeen ? { untrusted: true } : {},
|
|
2059
|
+
...summary.sensitiveSeen ? { sensitive: true } : {},
|
|
2060
|
+
sourceKind: "sub-agent"
|
|
2061
|
+
};
|
|
2062
|
+
};
|
|
1695
2063
|
const toTool = (options = {}) => {
|
|
1696
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;
|
|
1697
2067
|
return {
|
|
1698
2068
|
name: options.name ?? `subagent_${config.name}`,
|
|
1699
2069
|
description: options.description ?? `Invoke sub-agent '${config.name}'.`,
|
|
@@ -1714,7 +2084,8 @@ function createAgent(config) {
|
|
|
1714
2084
|
const callOpts = {
|
|
1715
2085
|
...ctx?.signal !== void 0 ? { signal: ctx.signal } : {},
|
|
1716
2086
|
...ctx?.runContext.deps !== void 0 ? { deps: ctx.runContext.deps } : {},
|
|
1717
|
-
...ctx?.runContext.sessionId !== void 0 ? { sessionId: ctx.runContext.sessionId } : {}
|
|
2087
|
+
...ctx?.runContext.sessionId !== void 0 ? { sessionId: ctx.runContext.sessionId } : {},
|
|
2088
|
+
...options.capability !== void 0 ? { capability: options.capability } : {}
|
|
1718
2089
|
};
|
|
1719
2090
|
const seed = options.inputFilter !== void 0 && ctx !== void 0 ? [...options.inputFilter(ctx.runContext.messages), {
|
|
1720
2091
|
role: "user",
|
|
@@ -1726,12 +2097,21 @@ function createAgent(config) {
|
|
|
1726
2097
|
for await (const ev of stream(seed, callOpts)) if (ev.type === "text.complete") turns.push(ev.text);
|
|
1727
2098
|
else if (ev.type === "agent.end") endResult = ev.result;
|
|
1728
2099
|
if (endResult !== void 0 && endResult.status !== "completed") throw new Error(`sub-agent '${config.name}' ${endResult.status}${endResult.error !== void 0 ? `: ${endResult.error.message}` : ""}`);
|
|
1729
|
-
|
|
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;
|
|
1730
2106
|
}
|
|
1731
2107
|
const result = await run(seed, callOpts);
|
|
1732
2108
|
if (result.status !== "completed") throw new Error(`sub-agent '${config.name}' ${result.status}${result.error !== void 0 ? `: ${result.error.message}` : ""}`);
|
|
1733
|
-
|
|
1734
|
-
|
|
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;
|
|
1735
2115
|
}
|
|
1736
2116
|
};
|
|
1737
2117
|
};
|
|
@@ -1847,6 +2227,35 @@ function probeSecretsAccessor() {
|
|
|
1847
2227
|
const rejector = (_key, _options) => Promise.reject(/* @__PURE__ */ new Error("secrets.require is unavailable inside a needsApproval predicate"));
|
|
1848
2228
|
return { require: rejector };
|
|
1849
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
|
+
}
|
|
1850
2259
|
|
|
1851
2260
|
//#endregion
|
|
1852
2261
|
export { createAgent };
|