@superdurable/dex-agents 0.3.0 → 0.5.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/README.md +598 -45
- package/dist/src/agent-context-compaction.d.ts +1 -1
- package/dist/src/agent-framework-runner.d.ts +210 -10
- package/dist/src/agent-framework-runner.js +30 -0
- package/dist/src/agent-framework-runner.js.map +1 -1
- package/dist/src/agent-local-directory-media-store.d.ts +111 -0
- package/dist/src/agent-local-directory-media-store.js +417 -0
- package/dist/src/agent-local-directory-media-store.js.map +1 -0
- package/dist/src/agent-media.d.ts +279 -0
- package/dist/src/agent-media.js +457 -0
- package/dist/src/agent-media.js.map +1 -0
- package/dist/src/agent-session-codecs.d.ts +3 -1
- package/dist/src/agent-session-codecs.js +53 -4
- package/dist/src/agent-session-codecs.js.map +1 -1
- package/dist/src/agent-session-failure.d.ts +4 -2
- package/dist/src/agent-session-failure.js +6 -2
- package/dist/src/agent-session-failure.js.map +1 -1
- package/dist/src/agent-session-flow-graph.d.ts +46 -0
- package/dist/src/agent-session-flow-graph.js +51 -0
- package/dist/src/agent-session-flow-graph.js.map +1 -1
- package/dist/src/agent-session-flow-graph.json +98 -81
- package/dist/src/agent-session-flow.d.ts +5 -3
- package/dist/src/agent-session-flow.js +10 -1
- package/dist/src/agent-session-flow.js.map +1 -1
- package/dist/src/agent-session-start.d.ts +1 -1
- package/dist/src/agent-session-start.js +2 -2
- package/dist/src/agent-session-start.js.map +1 -1
- package/dist/src/agent-session-steps.d.ts +9 -0
- package/dist/src/agent-session-steps.js +219 -34
- package/dist/src/agent-session-steps.js.map +1 -1
- package/dist/src/agent-session-tracer.js +1 -1
- package/dist/src/agent-session-tracer.js.map +1 -1
- package/dist/src/agent-session-transitions.d.ts +4 -0
- package/dist/src/agent-session-transitions.js +29 -1
- package/dist/src/agent-session-transitions.js.map +1 -1
- package/dist/src/agent-session-values.d.ts +112 -1
- package/dist/src/agent-session-values.js +6 -0
- package/dist/src/agent-session-values.js.map +1 -1
- package/dist/src/agent-session-wait-steps.js +17 -5
- package/dist/src/agent-session-wait-steps.js.map +1 -1
- package/dist/src/ai-sdk/ai-sdk-model-client.d.ts +15 -3
- package/dist/src/ai-sdk/ai-sdk-model-client.js +36 -7
- package/dist/src/ai-sdk/ai-sdk-model-client.js.map +1 -1
- package/dist/src/ai-sdk/index.d.ts +1 -1
- package/dist/src/ai-sdk/index.js +1 -1
- package/dist/src/ai-sdk/index.js.map +1 -1
- package/dist/src/{stepwise → builtin-loop}/agent-definition.d.ts +120 -5
- package/dist/src/builtin-loop/agent-definition.js.map +1 -0
- package/dist/src/builtin-loop/builtin-agent-session-flow.d.ts +69 -0
- package/dist/src/{stepwise/stepwise-agent-session-flow.js → builtin-loop/builtin-agent-session-flow.js} +15 -9
- package/dist/src/builtin-loop/builtin-agent-session-flow.js.map +1 -0
- package/dist/src/{stepwise/stepwise-agent-runner.d.ts → builtin-loop/builtin-loop-agent-runner.d.ts} +85 -26
- package/dist/src/{stepwise/stepwise-agent-runner.js → builtin-loop/builtin-loop-agent-runner.js} +320 -213
- package/dist/src/builtin-loop/builtin-loop-agent-runner.js.map +1 -0
- package/dist/src/{stepwise/stepwise-input-items.d.ts → builtin-loop/builtin-loop-input-items.d.ts} +7 -3
- package/dist/src/{stepwise/stepwise-input-items.js → builtin-loop/builtin-loop-input-items.js} +57 -10
- package/dist/src/builtin-loop/builtin-loop-input-items.js.map +1 -0
- package/dist/src/builtin-loop/function-tool.d.ts +282 -0
- package/dist/src/{stepwise → builtin-loop}/function-tool.js +63 -3
- package/dist/src/builtin-loop/function-tool.js.map +1 -0
- package/dist/src/builtin-loop/index.d.ts +8 -0
- package/dist/src/builtin-loop/index.js +13 -0
- package/dist/src/builtin-loop/index.js.map +1 -0
- package/dist/src/builtin-loop/json-schema-validation.js.map +1 -0
- package/dist/src/{stepwise → builtin-loop}/model-client.d.ts +36 -7
- package/dist/src/builtin-loop/model-client.js.map +1 -0
- package/dist/src/{stepwise → builtin-loop}/scripted-model-client.d.ts +8 -5
- package/dist/src/{stepwise → builtin-loop}/scripted-model-client.js +8 -5
- package/dist/src/builtin-loop/scripted-model-client.js.map +1 -0
- package/dist/src/{stepwise → builtin-loop}/tool-source.d.ts +13 -3
- package/dist/src/builtin-loop/tool-source.js.map +1 -0
- package/dist/src/connectors/connector-input-schema.d.ts +19 -0
- package/dist/src/connectors/connector-input-schema.js +228 -0
- package/dist/src/connectors/connector-input-schema.js.map +1 -0
- package/dist/src/connectors/connector-step-context.d.ts +94 -0
- package/dist/src/connectors/connector-step-context.js +180 -0
- package/dist/src/connectors/connector-step-context.js.map +1 -0
- package/dist/src/connectors/connector-tool-source.d.ts +401 -0
- package/dist/src/connectors/connector-tool-source.js +628 -0
- package/dist/src/connectors/connector-tool-source.js.map +1 -0
- package/dist/src/connectors/index.d.ts +3 -0
- package/dist/src/connectors/index.js +11 -0
- package/dist/src/connectors/index.js.map +1 -0
- package/dist/src/index.d.ts +7 -3
- package/dist/src/index.js +6 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/index.d.ts +1 -0
- package/dist/src/mcp/index.js +1 -0
- package/dist/src/mcp/index.js.map +1 -1
- package/dist/src/mcp/mcp-dex-conventions.d.ts +57 -0
- package/dist/src/mcp/mcp-dex-conventions.js +90 -0
- package/dist/src/mcp/mcp-dex-conventions.js.map +1 -0
- package/dist/src/mcp/mcp-image-content.d.ts +20 -0
- package/dist/src/mcp/mcp-image-content.js +82 -0
- package/dist/src/mcp/mcp-image-content.js.map +1 -0
- package/dist/src/mcp/mcp-tool-source.d.ts +178 -17
- package/dist/src/mcp/mcp-tool-source.js +237 -34
- package/dist/src/mcp/mcp-tool-source.js.map +1 -1
- package/dist/src/s3/index.d.ts +1 -0
- package/dist/src/s3/index.js +9 -0
- package/dist/src/s3/index.js.map +1 -0
- package/dist/src/s3/s3-media-store.d.ts +138 -0
- package/dist/src/s3/s3-media-store.js +255 -0
- package/dist/src/s3/s3-media-store.js.map +1 -0
- package/dist/src/stepwise/index.d.ts +41 -8
- package/dist/src/stepwise/index.js +35 -5
- package/dist/src/stepwise/index.js.map +1 -1
- package/dist/src/testing/agent-session-test-client.d.ts +6 -3
- package/dist/src/testing/agent-session-test-client.js +10 -3
- package/dist/src/testing/agent-session-test-client.js.map +1 -1
- package/dist/src/testing/scripted-model-client.d.ts +1 -1
- package/dist/src/testing/scripted-model-client.js +2 -2
- package/dist/src/testing/scripted-model-client.js.map +1 -1
- package/dist/src/tool-source-router.d.ts +211 -0
- package/dist/src/tool-source-router.js +380 -0
- package/dist/src/tool-source-router.js.map +1 -0
- package/dist/src/ui-stream/agent-event-json.d.ts +2 -1
- package/dist/src/ui-stream/agent-event-json.js +3 -1
- package/dist/src/ui-stream/agent-event-json.js.map +1 -1
- package/dist/src/ui-stream/agent-ui-messages.d.ts +11 -3
- package/dist/src/ui-stream/agent-ui-messages.js +29 -5
- package/dist/src/ui-stream/agent-ui-messages.js.map +1 -1
- package/package.json +25 -3
- package/dist/src/stepwise/agent-definition.js.map +0 -1
- package/dist/src/stepwise/function-tool.d.ts +0 -130
- package/dist/src/stepwise/function-tool.js.map +0 -1
- package/dist/src/stepwise/json-schema-validation.js.map +0 -1
- package/dist/src/stepwise/model-client.js.map +0 -1
- package/dist/src/stepwise/scripted-model-client.js.map +0 -1
- package/dist/src/stepwise/stepwise-agent-runner.js.map +0 -1
- package/dist/src/stepwise/stepwise-agent-session-flow.d.ts +0 -35
- package/dist/src/stepwise/stepwise-agent-session-flow.js.map +0 -1
- package/dist/src/stepwise/stepwise-input-items.js.map +0 -1
- package/dist/src/stepwise/tool-source.js.map +0 -1
- /package/dist/src/{stepwise → builtin-loop}/agent-definition.js +0 -0
- /package/dist/src/{stepwise → builtin-loop}/json-schema-validation.d.ts +0 -0
- /package/dist/src/{stepwise → builtin-loop}/json-schema-validation.js +0 -0
- /package/dist/src/{stepwise → builtin-loop}/model-client.js +0 -0
- /package/dist/src/{stepwise → builtin-loop}/tool-source.js +0 -0
package/dist/src/{stepwise/stepwise-agent-runner.js → builtin-loop/builtin-loop-agent-runner.js}
RENAMED
|
@@ -9,32 +9,47 @@ import { builtInToolDefinitions } from "../agent-built-in-tools.js";
|
|
|
9
9
|
import { buildContextSummaryNote, buildContextSummaryOmissionNote, contextSummaryInstructions, contextSummaryRequest, } from "../agent-context-compaction.js";
|
|
10
10
|
export { buildContextSummaryNote, buildContextSummaryOmissionNote, contextSummaryInstructions, contextSummaryNoteHeader, contextSummaryRequest, } from "../agent-context-compaction.js";
|
|
11
11
|
import { ToolExecutionError, } from "../agent-framework-runner.js";
|
|
12
|
+
import { loadImages } from "../agent-media.js";
|
|
12
13
|
import { buildPlanNote } from "../agent-plans.js";
|
|
13
|
-
import { describeToolCallOutcome, } from "../agent-session-values.js";
|
|
14
|
+
import { describeToolCallOutcome, emptyAgentUsage, } from "../agent-session-values.js";
|
|
15
|
+
import { isFunctionToolResult } from "./function-tool.js";
|
|
14
16
|
import { findJsonSchemaViolation, serializeJsonWithSpacedSeparators } from "./json-schema-validation.js";
|
|
15
|
-
import { pairToolCallsWithResults, readStoredModelInputItem,
|
|
16
|
-
|
|
17
|
+
import { pairToolCallsWithResults, readStoredModelInputItem, toBuiltinLoopFrameworkMessage, } from "./builtin-loop-input-items.js";
|
|
18
|
+
import { ToolSourceRouter, defaultToolSourceListingTimeoutMs, readToolSourceEnvelope as readRouterToolSourceEnvelope, writeToolSourceEnvelope as writeRouterToolSourceEnvelope, } from "../tool-source-router.js";
|
|
19
|
+
export { defaultToolSourceListingTimeoutMs } from "../tool-source-router.js";
|
|
17
20
|
// The runner's envelope around a source's toolSourceReference: the building source's name.
|
|
18
21
|
const toolSourceNameKey = "stepwise_tool_source";
|
|
19
22
|
// An unlisted name never reaches a source, so it fails without asking anyone to approve it.
|
|
20
23
|
const unlistedSourceToolPolicy = { effect: "unclassified", requiresApproval: false };
|
|
21
|
-
const toolSourceReferenceKey = "reference";
|
|
22
|
-
/** Default {@link AgentDefinition.toolSourceListingTimeoutMs}: 10 seconds. */
|
|
23
|
-
export const defaultToolSourceListingTimeoutMs = 10_000;
|
|
24
24
|
export const defaultMaxModelCallsPerTurn = 100;
|
|
25
|
+
/**
|
|
26
|
+
* How many {@link ContinueTurn} results of {@link AgentDefinition.decideTurnContinuation} may follow answers without
|
|
27
|
+
* tool calls in a row. A model response with tool calls resets the count.
|
|
28
|
+
* @since 0.5.0
|
|
29
|
+
*/
|
|
30
|
+
export const maxConsecutiveTurnContinuations = 8;
|
|
25
31
|
/** Result of a tool call the model made after the turn's model call limit; the same text as the Python core. */
|
|
26
32
|
export function buildToolCallLimitReachedResult(maxModelCallsPerTurn) {
|
|
27
33
|
return `The tool call was not run: this turn reached its limit of ${maxModelCallsPerTurn} model calls.`;
|
|
28
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The `lastTurnError` of a turn whose `decideTurnContinuation` returned a {@link ContinueTurn} once too often in a
|
|
37
|
+
* row; the same text as the Python core.
|
|
38
|
+
* @param limit - {@link maxConsecutiveTurnContinuations}.
|
|
39
|
+
* @returns The error text.
|
|
40
|
+
*/
|
|
41
|
+
export function buildTurnContinuationLimitError(limit) {
|
|
42
|
+
return `the turn reached its limit of ${limit} consecutive continuations without a tool call`;
|
|
43
|
+
}
|
|
29
44
|
// The summary is not the assistant's answer, so its live text goes nowhere.
|
|
30
45
|
const discardedModelOutput = {
|
|
31
46
|
writeTextDelta: () => undefined,
|
|
32
47
|
writeReasoningDelta: () => undefined,
|
|
33
48
|
};
|
|
34
49
|
/**
|
|
35
|
-
* The {@link AgentFrameworkRunner} of the built-in
|
|
50
|
+
* The {@link AgentFrameworkRunner} of the built-in agent loop.
|
|
36
51
|
*
|
|
37
|
-
* {@link
|
|
52
|
+
* {@link createBuiltinAgentSessionFlow} creates one; construct it directly only to pass it to
|
|
38
53
|
* `createAgentSessionFlow` with options of your own. Each `RunModel` makes at most one model call
|
|
39
54
|
* through {@link AgentDefinition.model}, from committed state only: the stored messages, the results
|
|
40
55
|
* of the previous model response from the turn's final records, and the instructions. The runner
|
|
@@ -46,34 +61,55 @@ const discardedModelOutput = {
|
|
|
46
61
|
* tasks. Its {@link cancelToolTask} passes a task that `cancel_turn` abandoned to that source's optional
|
|
47
62
|
* `cancelToolTask`.
|
|
48
63
|
*
|
|
64
|
+
* With a media store, the runner shows images to the model. It then has `buildUserMessageWithImages` of
|
|
65
|
+
* {@link AgentFrameworkRunner}, as an own property, so `send_user_message` accepts images: the stored `user_text` item keeps their references after the text. A tool
|
|
66
|
+
* result item keeps the references of `AgentToolCall.images`. Before each model call, the runner reads the bytes of
|
|
67
|
+
* every image in the request with `loadImages` and passes them in `ModelRequest.imageBytes`; the model client
|
|
68
|
+
* converts each image to the provider's image part. A failed read fails that `RunModel` attempt, which retries. The
|
|
69
|
+
* model call that writes a context summary sees the text of the items only. Without a store the session refuses
|
|
70
|
+
* user images, and a tool source result's images are stored but not shown to the model. A function tool's images fail
|
|
71
|
+
* the call instead, without a retry. The class declares neither member, so a subclass may still define its own
|
|
72
|
+
* `buildUserMessageWithImages` or `mediaStore`; a subclass method stays in use with a store.
|
|
73
|
+
*
|
|
49
74
|
* @example
|
|
50
75
|
* ```ts
|
|
51
|
-
* const runner = new
|
|
76
|
+
* const runner = new BuiltinLoopAgentRunner(definition);
|
|
52
77
|
* const flow = createAgentSessionFlow(runner, "SupportAgent", { builtInTools: ["ask_user"] });
|
|
53
78
|
* ```
|
|
79
|
+
*
|
|
80
|
+
* It replaces `StepwiseAgentRunner`, which `@superdurable/dex-agents/stepwise` keeps as a deprecated alias.
|
|
81
|
+
*
|
|
82
|
+
* @since 0.5.0
|
|
54
83
|
*/
|
|
55
|
-
export class
|
|
84
|
+
export class BuiltinLoopAgentRunner {
|
|
56
85
|
/** The agent definition, frozen at construction. */
|
|
57
86
|
definition;
|
|
58
87
|
/** The runner honors {@link AgentTurnContext.interactionMode}, so plan mode can be enabled. */
|
|
59
88
|
supportsPlanMode = true;
|
|
60
89
|
functionToolsByName;
|
|
61
90
|
toolSources;
|
|
62
|
-
toolSourcesByName;
|
|
63
91
|
maxModelCallsPerTurn;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
lastListings = new Map();
|
|
92
|
+
// An ES private field, so it never collides with a subclass member.
|
|
93
|
+
#mediaStore;
|
|
67
94
|
/**
|
|
68
95
|
* Creates the runner and checks the definition.
|
|
69
96
|
* @param definition - The agent definition.
|
|
70
|
-
* @
|
|
71
|
-
*
|
|
97
|
+
* @param mediaStore - The store that holds the session's images, such as `S3MediaStore`. With it, the runner
|
|
98
|
+
* accepts user messages with images and shows them and tool result images to the model, reading the bytes for
|
|
99
|
+
* each model call; stored messages hold only references. It also gets an own `buildUserMessageWithImages`, which
|
|
100
|
+
* stores user text and its images as one `user_text` item that holds the references, not the bytes, unless a
|
|
101
|
+
* subclass defines its own `buildUserMessageWithImages`, which then stays in use. Use the store
|
|
102
|
+
* the clients and tools upload to. Omit it to refuse user images and show tool result images to the model as text
|
|
103
|
+
* only. Since 0.5.0.
|
|
104
|
+
*
|
|
105
|
+
* Changed in 0.5.0: takes `mediaStore`.
|
|
106
|
+
* @throws {TypeError} When the name, instructions, model, tools, or `decideTurnContinuation` are invalid, two tools
|
|
107
|
+
* share a name, a function tool uses the name of an enabled built-in tool, `planModeEnabled` is set without
|
|
72
108
|
* `write_todos`, `planningToolChoice` is neither `auto` nor `required`, or a tool source lacks a
|
|
73
109
|
* {@link ToolSource} method.
|
|
74
110
|
* @throws {RangeError} When `maxModelCallsPerTurn` is not a positive integer.
|
|
75
111
|
*/
|
|
76
|
-
constructor(definition) {
|
|
112
|
+
constructor(definition, mediaStore = undefined) {
|
|
77
113
|
if (typeof definition?.name !== "string" || definition.name.trim() === "") {
|
|
78
114
|
throw new TypeError("AgentDefinition.name must be a non-blank string");
|
|
79
115
|
}
|
|
@@ -86,6 +122,9 @@ export class StepwiseAgentRunner {
|
|
|
86
122
|
if (definition.resolveModel !== undefined && typeof definition.resolveModel !== "function") {
|
|
87
123
|
throw new TypeError("AgentDefinition.resolveModel must be a function");
|
|
88
124
|
}
|
|
125
|
+
if (definition.decideTurnContinuation !== undefined && typeof definition.decideTurnContinuation !== "function") {
|
|
126
|
+
throw new TypeError("AgentDefinition.decideTurnContinuation must be a function");
|
|
127
|
+
}
|
|
89
128
|
const enabledBuiltInTools = new Set(definition.builtInTools ?? []);
|
|
90
129
|
const functionToolsByName = new Map();
|
|
91
130
|
for (const tool of definition.tools ?? []) {
|
|
@@ -100,28 +139,17 @@ export class StepwiseAgentRunner {
|
|
|
100
139
|
}
|
|
101
140
|
functionToolsByName.set(tool.name, tool);
|
|
102
141
|
}
|
|
103
|
-
const toolSources = [...(definition.toolSources ?? [])];
|
|
104
|
-
for (const source of toolSources) {
|
|
105
|
-
const missing = toolSourceMethods.find((method) => typeof source?.[method] !== "function");
|
|
106
|
-
if (missing !== undefined) {
|
|
107
|
-
throw new TypeError(`tool source ${String(source)} has no ${missing} method`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const toolSourcesByName = new Map();
|
|
111
|
-
for (const source of toolSources) {
|
|
112
|
-
if (typeof source.sourceName !== "string" || source.sourceName.trim() === "") {
|
|
113
|
-
throw new TypeError(`tool source ${String(source)} needs a non-blank sourceName`);
|
|
114
|
-
}
|
|
115
|
-
if (toolSourcesByName.has(source.sourceName)) {
|
|
116
|
-
throw new TypeError(`two tool sources are named ${source.sourceName}`);
|
|
117
|
-
}
|
|
118
|
-
toolSourcesByName.set(source.sourceName, source);
|
|
119
|
-
}
|
|
120
|
-
refuseDeclaredToolNameCollisions(toolSources, new Set(functionToolsByName.keys()), enabledBuiltInTools);
|
|
121
142
|
const toolSourceListingTimeoutMs = definition.toolSourceListingTimeoutMs ?? defaultToolSourceListingTimeoutMs;
|
|
122
143
|
if (!(toolSourceListingTimeoutMs > 0)) {
|
|
123
144
|
throw new RangeError("AgentDefinition.toolSourceListingTimeoutMs must be positive");
|
|
124
145
|
}
|
|
146
|
+
const toolSources = new ToolSourceRouter(definition.toolSources ?? [], {
|
|
147
|
+
sourceNameKey: toolSourceNameKey,
|
|
148
|
+
localToolNames: new Set(functionToolsByName.keys()),
|
|
149
|
+
localToolDescription: "a function tool",
|
|
150
|
+
builtInToolNames: enabledBuiltInTools,
|
|
151
|
+
listingTimeoutMs: toolSourceListingTimeoutMs,
|
|
152
|
+
});
|
|
125
153
|
if (definition.planModeEnabled === true && !enabledBuiltInTools.has("write_todos")) {
|
|
126
154
|
throw new TypeError("planModeEnabled requires the write_todos built-in tool");
|
|
127
155
|
}
|
|
@@ -136,9 +164,14 @@ export class StepwiseAgentRunner {
|
|
|
136
164
|
this.definition = Object.freeze({ ...definition });
|
|
137
165
|
this.functionToolsByName = functionToolsByName;
|
|
138
166
|
this.toolSources = toolSources;
|
|
139
|
-
this.toolSourcesByName = toolSourcesByName;
|
|
140
|
-
this.toolSourceListingTimeoutMs = toolSourceListingTimeoutMs;
|
|
141
167
|
this.maxModelCallsPerTurn = maxModelCallsPerTurn;
|
|
168
|
+
this.#mediaStore = mediaStore;
|
|
169
|
+
if (mediaStore !== undefined) {
|
|
170
|
+
// The session accepts images only from a runner with this method; the class leaves it undeclared for subclasses.
|
|
171
|
+
const runner = this;
|
|
172
|
+
// A subclass's own method is already on the prototype chain, and it stays in use.
|
|
173
|
+
runner.buildUserMessageWithImages ??= (text, images) => toBuiltinLoopFrameworkMessage({ type: "user_text", text, images: [...images] });
|
|
174
|
+
}
|
|
142
175
|
}
|
|
143
176
|
/**
|
|
144
177
|
* Converts user text into a stored `user_text` item.
|
|
@@ -146,31 +179,76 @@ export class StepwiseAgentRunner {
|
|
|
146
179
|
* @returns The message with role `user`.
|
|
147
180
|
*/
|
|
148
181
|
buildUserMessage(text) {
|
|
149
|
-
return
|
|
182
|
+
return toBuiltinLoopFrameworkMessage({ type: "user_text", text });
|
|
150
183
|
}
|
|
151
184
|
/**
|
|
152
185
|
* Makes the turn's next model call.
|
|
153
186
|
* @param turn - The turn context.
|
|
154
187
|
* @returns {@link TurnCompleted} with the results and final answer not stored yet, or
|
|
155
|
-
* {@link ToolCallsRequested} with the previous results, this response, and its tool calls.
|
|
188
|
+
* {@link ToolCallsRequested} with the previous results, this response, and its tool calls. When
|
|
189
|
+
* {@link AgentDefinition.decideTurnContinuation} returns `"end"` after tool calls, a {@link TurnCompleted} with only
|
|
190
|
+
* the results and zero usage, without a model call. When it continues after tool calls, its message follows the
|
|
191
|
+
* results in the request and in the stored messages. When it continues after an answer, a {@link TurnContinued}
|
|
192
|
+
* with the previous results, this response, and the message as user text, or a {@link TurnCompleted} with
|
|
193
|
+
* {@link TurnCompleted.turnError} once {@link maxConsecutiveTurnContinuations} is exceeded.
|
|
156
194
|
* @throws {TypeError} When a function tool uses the name of an enabled built-in tool, or a tool source lists a
|
|
157
195
|
* name that repeats a function tool, an enabled built-in tool, or another source's tool. No model request is
|
|
158
|
-
* made.
|
|
159
|
-
* @throws Any error of the model call
|
|
196
|
+
* made. Also when `decideTurnContinuation` returns another value.
|
|
197
|
+
* @throws Any error of the model call, of the media store, of a tool source's `listTools`, or of
|
|
198
|
+
* `decideTurnContinuation`, which retries the `RunModel` attempt.
|
|
160
199
|
*/
|
|
161
200
|
async runModelStep(turn) {
|
|
162
201
|
this.requireNoBuiltInToolNameClash(turn);
|
|
163
|
-
const
|
|
202
|
+
const runState = readBuiltinLoopRunState(turn.frameworkRunState);
|
|
203
|
+
const modelCallCount = runState.modelCalls;
|
|
164
204
|
const unstoredToolResults = buildUnstoredToolResults(turn);
|
|
205
|
+
const unstoredItems = [...unstoredToolResults];
|
|
206
|
+
if (unstoredToolResults.length > 0 && this.definition.decideTurnContinuation !== undefined) {
|
|
207
|
+
const decision = await this.decideTurnContinuation(turn, readLatestModelOutput(turn), unstoredToolResults);
|
|
208
|
+
if (decision === "end") {
|
|
209
|
+
return {
|
|
210
|
+
kind: "completed",
|
|
211
|
+
newMessages: unstoredToolResults.map(toBuiltinLoopFrameworkMessage),
|
|
212
|
+
usage: emptyAgentUsage(),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (decision !== "default") {
|
|
216
|
+
unstoredItems.push({ type: "user_text", text: decision.message });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
165
219
|
const isToolBudgetSpent = modelCallCount >= this.maxModelCallsPerTurn;
|
|
166
|
-
const sourceTools = isToolBudgetSpent || turn.interactionMode === "planning"
|
|
167
|
-
|
|
168
|
-
|
|
220
|
+
const sourceTools = isToolBudgetSpent || turn.interactionMode === "planning"
|
|
221
|
+
? new Map()
|
|
222
|
+
: await this.toolSources.listSourceTools(turn.enabledBuiltInTools ?? new Set());
|
|
223
|
+
const request = await this.buildModelRequest(turn, unstoredItems, modelCallCount, isToolBudgetSpent, sourceTools);
|
|
224
|
+
const response = await this.resolveModel(turn.sessionSettings).callModel(await this.addImageBytes(request), turn.events);
|
|
169
225
|
requireModelResponse(response);
|
|
170
226
|
const usage = { ...response.usage, modelCalls: 1 };
|
|
171
|
-
const newMessages = [...
|
|
227
|
+
const newMessages = [...unstoredItems, ...response.outputItems].map(toBuiltinLoopFrameworkMessage);
|
|
172
228
|
if (response.toolCalls.length === 0) {
|
|
173
|
-
|
|
229
|
+
const decision = await this.decideTurnContinuation(turn, response.outputItems, []);
|
|
230
|
+
if (decision === "end" || decision === "default") {
|
|
231
|
+
return { kind: "completed", newMessages, usage };
|
|
232
|
+
}
|
|
233
|
+
const consecutiveContinuations = runState.consecutiveContinuations + 1;
|
|
234
|
+
if (consecutiveContinuations > maxConsecutiveTurnContinuations) {
|
|
235
|
+
return {
|
|
236
|
+
kind: "completed",
|
|
237
|
+
newMessages,
|
|
238
|
+
usage,
|
|
239
|
+
turnError: buildTurnContinuationLimitError(maxConsecutiveTurnContinuations),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
kind: "turn_continued",
|
|
244
|
+
frameworkRunState: serializeJsonWithSpacedSeparators({
|
|
245
|
+
model_calls: modelCallCount + 1,
|
|
246
|
+
consecutive_continuations: consecutiveContinuations,
|
|
247
|
+
}),
|
|
248
|
+
// The message makes the next request end with user text, as every provider requires.
|
|
249
|
+
newMessages: [...newMessages, toBuiltinLoopFrameworkMessage({ type: "user_text", text: decision.message })],
|
|
250
|
+
usage,
|
|
251
|
+
};
|
|
174
252
|
}
|
|
175
253
|
if (isToolBudgetSpent) {
|
|
176
254
|
// The model asked for tools although none were offered; end the turn so it cannot loop.
|
|
@@ -182,7 +260,7 @@ export class StepwiseAgentRunner {
|
|
|
182
260
|
}));
|
|
183
261
|
return {
|
|
184
262
|
kind: "completed",
|
|
185
|
-
newMessages: [...newMessages, ...notRunResults.map(
|
|
263
|
+
newMessages: [...newMessages, ...notRunResults.map(toBuiltinLoopFrameworkMessage)],
|
|
186
264
|
usage,
|
|
187
265
|
};
|
|
188
266
|
}
|
|
@@ -193,11 +271,7 @@ export class StepwiseAgentRunner {
|
|
|
193
271
|
toolCalls: response.toolCalls.map((toolCall) => {
|
|
194
272
|
const listed = sourceTools.get(toolCall.toolName);
|
|
195
273
|
if (listed !== undefined) {
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
...built,
|
|
199
|
-
toolSourceReference: writeToolSourceEnvelope(listed.source.sourceName, built.toolSourceReference),
|
|
200
|
-
};
|
|
274
|
+
return this.toolSources.buildDeferredToolCall(turn, listed, toolCall);
|
|
201
275
|
}
|
|
202
276
|
return {
|
|
203
277
|
toolCallId: toolCall.toolCallId,
|
|
@@ -210,14 +284,22 @@ export class StepwiseAgentRunner {
|
|
|
210
284
|
};
|
|
211
285
|
}
|
|
212
286
|
/**
|
|
213
|
-
* Runs one function tool call after
|
|
287
|
+
* Runs one function tool call after converting its arguments with the tool's `prepareArguments`, when it has
|
|
288
|
+
* one, and checking them against the tool's schema, or passes a call that a
|
|
214
289
|
* tool source built in `runModelStep` to that source, with the source's own `toolSourceReference` and without a
|
|
215
290
|
* listing. Any other name is an unknown tool and never reaches a source.
|
|
291
|
+
* A function tool's {@link ToolCallContext.mediaStore} is this runner's store.
|
|
292
|
+
*
|
|
293
|
+
* Changed in 0.5.0: a function tool receives the media store and may return a {@link FunctionToolResult} with
|
|
294
|
+
* images.
|
|
216
295
|
* @param call - The deferred call.
|
|
217
|
-
* @returns The output: a `string` result unchanged,
|
|
218
|
-
* {@link ToolInputRequested}; or a source's outcome, such as
|
|
296
|
+
* @returns The output: a `string` result unchanged, a {@link FunctionToolResult} as its output and images, any
|
|
297
|
+
* other result as JSON; the tool's {@link ToolInputRequested}; or a source's outcome, such as
|
|
298
|
+
* {@link ToolTaskStarted}.
|
|
219
299
|
* @throws {ToolExecutionError} For an unknown tool, arguments that are not a JSON object or break the
|
|
220
|
-
* schema, or a
|
|
300
|
+
* schema, a non-cancellation error or a thenable result of the tool's `prepareArguments`, a
|
|
301
|
+
* `ToolExecutionError` from the tool or its source, or a function tool that returned images while this runner has
|
|
302
|
+
* no media store.
|
|
221
303
|
* @throws {RangeError} When a source built the call but the definition no longer has it, so the effect decides
|
|
222
304
|
* the outcome.
|
|
223
305
|
* @throws Any other error of the tool source.
|
|
@@ -226,13 +308,12 @@ export class StepwiseAgentRunner {
|
|
|
226
308
|
const toolCall = call.toolCall;
|
|
227
309
|
const tool = this.functionToolsByName.get(toolCall.toolName);
|
|
228
310
|
if (tool === undefined) {
|
|
229
|
-
|
|
230
|
-
if (routed === undefined) {
|
|
311
|
+
if (!this.toolSources.isSourceToolCall(toolCall)) {
|
|
231
312
|
throw new ToolExecutionError(buildUnknownToolError(toolCall.toolName));
|
|
232
313
|
}
|
|
233
|
-
return
|
|
314
|
+
return this.toolSources.executeDeferredToolCall(call);
|
|
234
315
|
}
|
|
235
|
-
const toolArguments =
|
|
316
|
+
const toolArguments = prepareToolArguments(tool, toolCall.arguments, call.cancellationSignal);
|
|
236
317
|
const violation = findJsonSchemaViolation(toolArguments, tool.parameters, "arguments");
|
|
237
318
|
if (violation !== undefined) {
|
|
238
319
|
throw new ToolExecutionError(`invalid arguments for ${tool.name}: ${violation}`);
|
|
@@ -243,12 +324,14 @@ export class StepwiseAgentRunner {
|
|
|
243
324
|
toolCallId: toolCall.toolCallId,
|
|
244
325
|
idempotencyKey: call.idempotencyKey,
|
|
245
326
|
attempt: call.attempt,
|
|
327
|
+
effect: toolCall.effect,
|
|
246
328
|
events: call.events,
|
|
247
329
|
cancellationSignal: call.cancellationSignal,
|
|
248
330
|
deadline: call.deadline,
|
|
249
331
|
inputAnswers: call.inputAnswers ?? [],
|
|
250
332
|
inputContinuation: toolCall.inputContinuation ?? null,
|
|
251
333
|
heartbeat: () => call.heartbeat(),
|
|
334
|
+
...(this.#mediaStore === undefined ? {} : { mediaStore: this.#mediaStore }),
|
|
252
335
|
};
|
|
253
336
|
const result = await tool.execute(toolArguments, context);
|
|
254
337
|
if (typeof result === "string") {
|
|
@@ -257,8 +340,20 @@ export class StepwiseAgentRunner {
|
|
|
257
340
|
if (result?.kind === "tool_input_requested") {
|
|
258
341
|
return result;
|
|
259
342
|
}
|
|
343
|
+
if (isFunctionToolResult(result)) {
|
|
344
|
+
return this.toToolExecutionResult(tool.name, result);
|
|
345
|
+
}
|
|
260
346
|
return { output: serializeJsonWithSpacedSeparators(result === undefined ? null : result) };
|
|
261
347
|
}
|
|
348
|
+
toToolExecutionResult(toolName, result) {
|
|
349
|
+
if (result.images === undefined || result.images.length === 0) {
|
|
350
|
+
return { output: result.output };
|
|
351
|
+
}
|
|
352
|
+
if (this.#mediaStore === undefined) {
|
|
353
|
+
throw new ToolExecutionError(`function tool ${toolName} returned images, but the runner has no media store`);
|
|
354
|
+
}
|
|
355
|
+
return { output: result.output, images: result.images };
|
|
356
|
+
}
|
|
262
357
|
/**
|
|
263
358
|
* Polls a tool source's task once, through the source that built the call.
|
|
264
359
|
* @param poll - The call's record from `PollToolTask`.
|
|
@@ -268,11 +363,22 @@ export class StepwiseAgentRunner {
|
|
|
268
363
|
* @throws Any error of the source.
|
|
269
364
|
*/
|
|
270
365
|
async pollToolTask(poll) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
366
|
+
return this.toolSources.pollToolTask(poll);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Returns a tool source's options for one call's tool Step. Function tools and sources without
|
|
370
|
+
* `toolCallStepOptions` keep the tool Steps' defaults.
|
|
371
|
+
* @param toolCall - The record whose tool Step starts.
|
|
372
|
+
* @returns The source's options, or `undefined` for the defaults.
|
|
373
|
+
* @throws {RangeError} When a source built the call but the definition no longer has it; the session then uses
|
|
374
|
+
* the defaults.
|
|
375
|
+
* @since 0.4.0
|
|
376
|
+
*/
|
|
377
|
+
toolCallStepOptions(toolCall) {
|
|
378
|
+
if (this.functionToolsByName.has(toolCall.toolName)) {
|
|
379
|
+
return undefined;
|
|
274
380
|
}
|
|
275
|
-
return
|
|
381
|
+
return this.toolSources.toolCallStepOptions(toolCall);
|
|
276
382
|
}
|
|
277
383
|
/**
|
|
278
384
|
* Cancels a tool source's task through the source that built the call; a source without `cancelToolTask` leaves
|
|
@@ -284,14 +390,7 @@ export class StepwiseAgentRunner {
|
|
|
284
390
|
* @throws Any error of the source.
|
|
285
391
|
*/
|
|
286
392
|
async cancelToolTask(cancellation) {
|
|
287
|
-
|
|
288
|
-
if (routed === undefined) {
|
|
289
|
-
throw new RangeError(`no tool source built the call of tool ${cancellation.toolCall.toolName}`);
|
|
290
|
-
}
|
|
291
|
-
await routed.source.cancelToolTask?.({
|
|
292
|
-
...cancellation,
|
|
293
|
-
toolCall: withToolSourceReference(cancellation.toolCall, routed.reference),
|
|
294
|
-
});
|
|
393
|
+
await this.toolSources.cancelToolTask(cancellation);
|
|
295
394
|
}
|
|
296
395
|
/**
|
|
297
396
|
* Returns the results of the turn's tool calls that are not stored yet.
|
|
@@ -299,7 +398,7 @@ export class StepwiseAgentRunner {
|
|
|
299
398
|
* @returns One `tool_result` message per unstored result, in deferral order.
|
|
300
399
|
*/
|
|
301
400
|
buildUnfinishedTurnMessages(turn) {
|
|
302
|
-
return buildUnstoredToolResults(turn).map(
|
|
401
|
+
return buildUnstoredToolResults(turn).map(toBuiltinLoopFrameworkMessage);
|
|
303
402
|
}
|
|
304
403
|
/**
|
|
305
404
|
* Estimates the model input of the next `runModelStep` with the model client.
|
|
@@ -309,10 +408,10 @@ export class StepwiseAgentRunner {
|
|
|
309
408
|
* when it has one, else from the runner's last listing, as in the Python core.
|
|
310
409
|
*/
|
|
311
410
|
async estimateInputTokens(turn) {
|
|
312
|
-
const modelCallCount =
|
|
411
|
+
const modelCallCount = readBuiltinLoopRunState(turn.frameworkRunState).modelCalls;
|
|
313
412
|
const isToolBudgetSpent = modelCallCount >= this.maxModelCallsPerTurn;
|
|
314
|
-
const request = await this.buildModelRequest(turn, buildUnstoredToolResults(turn), modelCallCount, isToolBudgetSpent, this.readCachedSourceTools());
|
|
315
|
-
return this.resolveModel(turn.sessionSettings).estimateInputTokens(request);
|
|
413
|
+
const request = await this.buildModelRequest(turn, buildUnstoredToolResults(turn), modelCallCount, isToolBudgetSpent, this.toolSources.readCachedSourceTools());
|
|
414
|
+
return this.resolveModel(turn.sessionSettings).estimateInputTokens(this.keepShownImagesOnly(request));
|
|
316
415
|
}
|
|
317
416
|
/**
|
|
318
417
|
* Chooses the last message to summarize, at a boundary between model responses.
|
|
@@ -370,7 +469,11 @@ export class StepwiseAgentRunner {
|
|
|
370
469
|
request.inputTokenBudget - (this.definition.modelSettings?.maxOutputTokens ?? 0);
|
|
371
470
|
if (budget !== undefined) {
|
|
372
471
|
// Estimate by parts, then leave out the oldest items up to the first cut that fits.
|
|
373
|
-
const itemTokens = rangeItems.map((item) => model.estimateInputTokens({
|
|
472
|
+
const itemTokens = rangeItems.map((item) => model.estimateInputTokens({
|
|
473
|
+
...this.buildSummaryRequest(request, [], 0),
|
|
474
|
+
instructions: "",
|
|
475
|
+
inputItems: stripImages([item]),
|
|
476
|
+
}));
|
|
374
477
|
let tokens = model.estimateInputTokens(this.buildSummaryRequest(request, [], rangeItems.length)) +
|
|
375
478
|
itemTokens.reduce((total, itemToken) => total + itemToken, 0);
|
|
376
479
|
// A cut never splits a model response or separates a tool call from its result.
|
|
@@ -397,7 +500,8 @@ export class StepwiseAgentRunner {
|
|
|
397
500
|
if (omittedCount > 0) {
|
|
398
501
|
items.push({ type: "user_text", text: buildContextSummaryOmissionNote(omittedCount) });
|
|
399
502
|
}
|
|
400
|
-
|
|
503
|
+
// The summary call reads no image bytes, so it sees each item's text only.
|
|
504
|
+
items.push(...stripImages(rangeItems), { type: "user_text", text: contextSummaryRequest });
|
|
401
505
|
return {
|
|
402
506
|
instructions: contextSummaryInstructions,
|
|
403
507
|
inputItems: pairToolCallsWithResults(items),
|
|
@@ -413,7 +517,7 @@ export class StepwiseAgentRunner {
|
|
|
413
517
|
cancellationSignal: request.cancellationSignal,
|
|
414
518
|
};
|
|
415
519
|
}
|
|
416
|
-
async buildModelRequest(turn,
|
|
520
|
+
async buildModelRequest(turn, unstoredItems, modelCallCount, isToolBudgetSpent, sourceTools) {
|
|
417
521
|
const contextSummary = turn.contextSummary ?? null;
|
|
418
522
|
const summaryItems = contextSummary === null ? [] : [{ type: "user_text", text: buildContextSummaryNote(contextSummary) }];
|
|
419
523
|
let toolChoice = isToolBudgetSpent ? "none" : "auto";
|
|
@@ -426,7 +530,7 @@ export class StepwiseAgentRunner {
|
|
|
426
530
|
inputItems: pairToolCallsWithResults([
|
|
427
531
|
...summaryItems,
|
|
428
532
|
...readStoredInputItemsWithPlanNote(turn),
|
|
429
|
-
...
|
|
533
|
+
...unstoredItems,
|
|
430
534
|
]),
|
|
431
535
|
tools: isToolBudgetSpent ? [] : this.listOfferedTools(turn, sourceTools),
|
|
432
536
|
toolChoice,
|
|
@@ -441,7 +545,7 @@ export class StepwiseAgentRunner {
|
|
|
441
545
|
}
|
|
442
546
|
return this.resolveModel(turn.sessionSettings).estimateInputTokens({
|
|
443
547
|
instructions: "",
|
|
444
|
-
inputItems: items,
|
|
548
|
+
inputItems: this.#mediaStore === undefined ? stripImages(items) : items,
|
|
445
549
|
tools: [],
|
|
446
550
|
toolChoice: "none",
|
|
447
551
|
settings: this.definition.modelSettings ?? {},
|
|
@@ -449,6 +553,21 @@ export class StepwiseAgentRunner {
|
|
|
449
553
|
cancellationSignal: turn.cancellationSignal,
|
|
450
554
|
});
|
|
451
555
|
}
|
|
556
|
+
/** Returns the request with the bytes of its images, read from the store right now. */
|
|
557
|
+
async addImageBytes(request) {
|
|
558
|
+
if (this.#mediaStore === undefined) {
|
|
559
|
+
return this.keepShownImagesOnly(request);
|
|
560
|
+
}
|
|
561
|
+
const references = request.inputItems.flatMap((item) => item.type === "model_output" ? [] : (item.images ?? []));
|
|
562
|
+
if (references.length === 0) {
|
|
563
|
+
return request;
|
|
564
|
+
}
|
|
565
|
+
return { ...request, imageBytes: await loadImages(this.#mediaStore, references) };
|
|
566
|
+
}
|
|
567
|
+
keepShownImagesOnly(request) {
|
|
568
|
+
// Without a store the model sees no image, such as a tool source's.
|
|
569
|
+
return this.#mediaStore === undefined ? { ...request, inputItems: stripImages(request.inputItems) } : request;
|
|
570
|
+
}
|
|
452
571
|
/** A built-in enabled only in the session options must not share a function tool's name. */
|
|
453
572
|
requireNoBuiltInToolNameClash(turn) {
|
|
454
573
|
for (const builtInTool of turn.enabledBuiltInTools ?? []) {
|
|
@@ -457,75 +576,6 @@ export class StepwiseAgentRunner {
|
|
|
457
576
|
}
|
|
458
577
|
}
|
|
459
578
|
}
|
|
460
|
-
async listSourceTools(turn) {
|
|
461
|
-
const enabledBuiltInTools = turn.enabledBuiltInTools ?? new Set();
|
|
462
|
-
const functionToolNames = new Set(this.functionToolsByName.keys());
|
|
463
|
-
const listed = new Map();
|
|
464
|
-
for (const [index, source] of this.toolSources.entries()) {
|
|
465
|
-
for (const tool of await this.listOneSource(index, source)) {
|
|
466
|
-
refuseSourceToolName(tool.name, functionToolNames, enabledBuiltInTools, listed);
|
|
467
|
-
listed.set(tool.name, { index, source, tool });
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return listed;
|
|
471
|
-
}
|
|
472
|
-
async listOneSource(index, source) {
|
|
473
|
-
const listing = source.listTools();
|
|
474
|
-
// A listing that outlives this call keeps running; nobody else reads its error.
|
|
475
|
-
listing.catch(() => undefined);
|
|
476
|
-
let timer;
|
|
477
|
-
const timedOut = new Promise((resolve) => {
|
|
478
|
-
timer = setTimeout(() => resolve("timed_out"), this.toolSourceListingTimeoutMs);
|
|
479
|
-
});
|
|
480
|
-
try {
|
|
481
|
-
const tools = await Promise.race([listing, timedOut]);
|
|
482
|
-
if (tools === "timed_out") {
|
|
483
|
-
console.warn(`tool source ${index} listed nothing within ${this.toolSourceListingTimeoutMs} ms`);
|
|
484
|
-
return [];
|
|
485
|
-
}
|
|
486
|
-
this.lastListings.set(index, tools);
|
|
487
|
-
return tools;
|
|
488
|
-
}
|
|
489
|
-
catch (error) {
|
|
490
|
-
console.warn(`tool source ${index} could not list its tools: ${error instanceof Error ? error.message : String(error)}`);
|
|
491
|
-
return [];
|
|
492
|
-
}
|
|
493
|
-
finally {
|
|
494
|
-
clearTimeout(timer);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
readCachedSourceTools() {
|
|
498
|
-
const cached = new Map();
|
|
499
|
-
for (const [index, source] of this.toolSources.entries()) {
|
|
500
|
-
const tools = source.cachedTools?.() ?? this.lastListings.get(index) ?? [];
|
|
501
|
-
for (const tool of tools) {
|
|
502
|
-
if (!cached.has(tool.name)) {
|
|
503
|
-
cached.set(tool.name, { index, source, tool });
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return cached;
|
|
508
|
-
}
|
|
509
|
-
routeToToolSource(toolCall) {
|
|
510
|
-
const envelope = readToolSourceEnvelope(toolCall.toolSourceReference);
|
|
511
|
-
if (envelope !== undefined) {
|
|
512
|
-
const source = this.toolSourcesByName.get(envelope.sourceName);
|
|
513
|
-
if (source === undefined) {
|
|
514
|
-
throw new RangeError(`tool source ${envelope.sourceName}, which built the call of tool ${toolCall.toolName}, is not configured on this Worker`);
|
|
515
|
-
}
|
|
516
|
-
return { source, reference: envelope.reference };
|
|
517
|
-
}
|
|
518
|
-
const reference = toolCall.toolSourceReference ?? undefined;
|
|
519
|
-
if (reference === undefined || this.toolSources.length === 0) {
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
const onlySource = this.toolSources.length === 1 ? this.toolSources[0] : undefined;
|
|
523
|
-
if (onlySource !== undefined) {
|
|
524
|
-
// A reference the source built itself, such as in a runner subclass.
|
|
525
|
-
return { source: onlySource, reference };
|
|
526
|
-
}
|
|
527
|
-
throw new RangeError(`cannot tell which tool source built the call of ${toolCall.toolName}`);
|
|
528
|
-
}
|
|
529
579
|
listOfferedTools(turn, sourceTools) {
|
|
530
580
|
const isPlanning = turn.interactionMode === "planning";
|
|
531
581
|
const enabledBuiltInTools = turn.enabledBuiltInTools ?? new Set();
|
|
@@ -556,7 +606,7 @@ export class StepwiseAgentRunner {
|
|
|
556
606
|
resolveModelToolPolicy(turn, toolName) {
|
|
557
607
|
const tool = this.functionToolsByName.get(toolName);
|
|
558
608
|
const isBuiltIn = turn.enabledBuiltInTools?.has(toolName) === true;
|
|
559
|
-
if (tool === undefined && this.toolSources.
|
|
609
|
+
if (tool === undefined && this.toolSources.hasToolSources && !isBuiltIn) {
|
|
560
610
|
return unlistedSourceToolPolicy;
|
|
561
611
|
}
|
|
562
612
|
const sessionPolicies = turn.toolPolicies ?? this.definition.toolPolicies ?? {};
|
|
@@ -601,6 +651,43 @@ export class StepwiseAgentRunner {
|
|
|
601
651
|
}
|
|
602
652
|
return resolved;
|
|
603
653
|
}
|
|
654
|
+
/** Returns the hook's decision for one iteration, or `"default"` without a hook. */
|
|
655
|
+
async decideTurnContinuation(turn, modelOutput, toolResults) {
|
|
656
|
+
const decide = this.definition.decideTurnContinuation;
|
|
657
|
+
if (decide === undefined) {
|
|
658
|
+
return "default";
|
|
659
|
+
}
|
|
660
|
+
const decision = await decide({
|
|
661
|
+
flowId: turn.flowId,
|
|
662
|
+
turnId: turn.turnId,
|
|
663
|
+
sessionSettings: turn.sessionSettings ?? {},
|
|
664
|
+
modelOutput,
|
|
665
|
+
toolResults,
|
|
666
|
+
});
|
|
667
|
+
if (decision === "end" || decision === "default") {
|
|
668
|
+
return decision;
|
|
669
|
+
}
|
|
670
|
+
const continuation = decision;
|
|
671
|
+
if (typeof continuation === "object" &&
|
|
672
|
+
continuation !== null &&
|
|
673
|
+
continuation.action === "continue" &&
|
|
674
|
+
typeof continuation.message === "string" &&
|
|
675
|
+
continuation.message.trim() !== "") {
|
|
676
|
+
return { action: "continue", message: continuation.message };
|
|
677
|
+
}
|
|
678
|
+
throw new TypeError(`decideTurnContinuation of agent ${this.definition.name} must return "end", "default", or ` +
|
|
679
|
+
`{ action: "continue", message } with a non-blank message, not ${describeTurnContinuation(decision)}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/** Names an invalid hook result without its message text, which may hold application data. */
|
|
683
|
+
function describeTurnContinuation(decision) {
|
|
684
|
+
if (typeof decision === "string") {
|
|
685
|
+
return JSON.stringify(decision);
|
|
686
|
+
}
|
|
687
|
+
if (typeof decision === "object" && decision !== null) {
|
|
688
|
+
return `an object with action ${JSON.stringify(decision.action) ?? "undefined"}`;
|
|
689
|
+
}
|
|
690
|
+
return String(decision);
|
|
604
691
|
}
|
|
605
692
|
/** A planning turn without a draft drafts one first; feedback on a draft may only answer. */
|
|
606
693
|
function mustWritePlanFirst(turn, modelCallCount) {
|
|
@@ -640,31 +727,100 @@ function buildUnstoredToolResults(turn) {
|
|
|
640
727
|
toolCallId: record.toolCallId,
|
|
641
728
|
toolName: record.toolName,
|
|
642
729
|
output: describeToolCallOutcome(record),
|
|
730
|
+
...((record.images ?? []).length === 0 ? {} : { images: record.images }),
|
|
643
731
|
}));
|
|
644
732
|
}
|
|
645
|
-
function
|
|
733
|
+
function stripImages(items) {
|
|
734
|
+
return items.map((item) => {
|
|
735
|
+
if (item.type === "model_output" || (item.images ?? []).length === 0) {
|
|
736
|
+
return item;
|
|
737
|
+
}
|
|
738
|
+
const { images: _images, ...withoutImages } = item;
|
|
739
|
+
return withoutImages;
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
function prepareToolArguments(tool, argumentsText, cancellationSignal) {
|
|
646
743
|
let parsed;
|
|
647
744
|
try {
|
|
648
745
|
parsed = JSON.parse(argumentsText === "" ? "{}" : argumentsText);
|
|
649
746
|
}
|
|
650
747
|
catch {
|
|
651
748
|
// A fixed text, because the two cores' JSON parsers word their errors differently.
|
|
652
|
-
throw new ToolExecutionError(`invalid arguments for ${
|
|
749
|
+
throw new ToolExecutionError(`invalid arguments for ${tool.name}: arguments are not valid JSON`);
|
|
750
|
+
}
|
|
751
|
+
if (tool.prepareArguments !== undefined) {
|
|
752
|
+
try {
|
|
753
|
+
parsed = tool.prepareArguments(parsed);
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
if (isCancellation(error, cancellationSignal)) {
|
|
757
|
+
throw error;
|
|
758
|
+
}
|
|
759
|
+
// Any other error, so a failed conversion never retries or records outcome_unknown.
|
|
760
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
761
|
+
throw new ToolExecutionError(`invalid arguments for ${tool.name}: ${message}`);
|
|
762
|
+
}
|
|
763
|
+
if (isThenable(parsed)) {
|
|
764
|
+
if (parsed instanceof Promise) {
|
|
765
|
+
observePromiseRejection(parsed);
|
|
766
|
+
}
|
|
767
|
+
throw new ToolExecutionError(`invalid arguments for ${tool.name}: argument conversion must be synchronous`);
|
|
768
|
+
}
|
|
653
769
|
}
|
|
654
770
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
655
|
-
throw new ToolExecutionError(`invalid arguments for ${
|
|
771
|
+
throw new ToolExecutionError(`invalid arguments for ${tool.name}: expected a JSON object`);
|
|
656
772
|
}
|
|
657
773
|
return parsed;
|
|
658
774
|
}
|
|
659
|
-
|
|
775
|
+
/** Reads `then` without calling it; a throwing `then` getter counts as a thenable, so it is refused too. */
|
|
776
|
+
function isThenable(value) {
|
|
777
|
+
if ((typeof value !== "object" || value === null) && typeof value !== "function") {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
return typeof value.then === "function";
|
|
782
|
+
}
|
|
783
|
+
catch {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/** Observes a refused Promise's rejection through the native `then`, so a subclass's own `then` never runs. */
|
|
788
|
+
function observePromiseRejection(promise) {
|
|
789
|
+
try {
|
|
790
|
+
void Promise.prototype.then.call(promise, undefined, () => undefined);
|
|
791
|
+
}
|
|
792
|
+
catch {
|
|
793
|
+
// A subclass constructor may throw; the result is refused either way.
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
function isCancellation(error, cancellationSignal) {
|
|
797
|
+
if (cancellationSignal.aborted && error === cancellationSignal.reason) {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
return error?.name === "AbortError";
|
|
801
|
+
}
|
|
802
|
+
function readBuiltinLoopRunState(frameworkRunState) {
|
|
660
803
|
if (frameworkRunState === null) {
|
|
661
|
-
return 0;
|
|
804
|
+
return { modelCalls: 0, consecutiveContinuations: 0 };
|
|
662
805
|
}
|
|
663
806
|
const parsed = JSON.parse(frameworkRunState);
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
807
|
+
const consecutiveContinuations = parsed.consecutive_continuations ?? 0;
|
|
808
|
+
if (typeof parsed.model_calls !== "number" ||
|
|
809
|
+
!Number.isSafeInteger(parsed.model_calls) ||
|
|
810
|
+
typeof consecutiveContinuations !== "number" ||
|
|
811
|
+
!Number.isSafeInteger(consecutiveContinuations)) {
|
|
812
|
+
throw new TypeError("FrameworkRunState is not a built-in loop run state");
|
|
813
|
+
}
|
|
814
|
+
return { modelCalls: parsed.model_calls, consecutiveContinuations };
|
|
815
|
+
}
|
|
816
|
+
/** The output items of the turn's latest model response: the turn's trailing stored `model_output` items. */
|
|
817
|
+
function readLatestModelOutput(turn) {
|
|
818
|
+
const items = turn.messages.filter((message) => message.turnId === turn.turnId).map(readStoredModelInputItem);
|
|
819
|
+
let start = items.length;
|
|
820
|
+
while (start > 0 && items[start - 1]?.type === "model_output") {
|
|
821
|
+
start -= 1;
|
|
822
|
+
}
|
|
823
|
+
return items.slice(start);
|
|
668
824
|
}
|
|
669
825
|
function requireModelResponse(response) {
|
|
670
826
|
if (!Array.isArray(response?.outputItems) ||
|
|
@@ -714,64 +870,15 @@ function modelRequestMetadata(turn) {
|
|
|
714
870
|
attempt: turn.attempt,
|
|
715
871
|
};
|
|
716
872
|
}
|
|
717
|
-
function refuseSourceToolName(toolName, functionToolNames, builtInTools, otherSourceTools) {
|
|
718
|
-
if (builtInTools.has(toolName)) {
|
|
719
|
-
throw new TypeError(`tool ${toolName} uses the name of an enabled built-in tool`);
|
|
720
|
-
}
|
|
721
|
-
if (functionToolNames.has(toolName)) {
|
|
722
|
-
throw new TypeError(`tool ${toolName} of a tool source uses the name of a function tool`);
|
|
723
|
-
}
|
|
724
|
-
if (otherSourceTools.has(toolName)) {
|
|
725
|
-
throw new TypeError(`two tool sources list a tool named ${toolName}`);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
function refuseDeclaredToolNameCollisions(toolSources, functionToolNames, builtInTools) {
|
|
729
|
-
const declared = new Map();
|
|
730
|
-
for (const source of toolSources) {
|
|
731
|
-
const names = [...(source.declaredToolNames?.() ?? [])].sort();
|
|
732
|
-
for (const toolName of names) {
|
|
733
|
-
refuseSourceToolName(toolName, functionToolNames, builtInTools, declared);
|
|
734
|
-
}
|
|
735
|
-
for (const toolName of names) {
|
|
736
|
-
declared.set(toolName, source);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
873
|
/** Error of a call to a tool that no function tool or source listing offers; the same text as the Python core. */
|
|
741
874
|
export function buildUnknownToolError(toolName) {
|
|
742
875
|
return `unknown tool ${toolName}`;
|
|
743
876
|
}
|
|
744
877
|
/** The runner's `toolSourceReference`: the source's name and its own reference, as JSON text. */
|
|
745
878
|
export function writeToolSourceEnvelope(sourceName, reference) {
|
|
746
|
-
return
|
|
879
|
+
return writeRouterToolSourceEnvelope(toolSourceNameKey, sourceName, reference);
|
|
747
880
|
}
|
|
748
881
|
export function readToolSourceEnvelope(toolSourceReference) {
|
|
749
|
-
|
|
750
|
-
return undefined;
|
|
751
|
-
}
|
|
752
|
-
let envelope;
|
|
753
|
-
try {
|
|
754
|
-
envelope = JSON.parse(toolSourceReference);
|
|
755
|
-
}
|
|
756
|
-
catch {
|
|
757
|
-
return undefined;
|
|
758
|
-
}
|
|
759
|
-
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope)) {
|
|
760
|
-
return undefined;
|
|
761
|
-
}
|
|
762
|
-
const sourceName = envelope[toolSourceNameKey];
|
|
763
|
-
const reference = envelope[toolSourceReferenceKey];
|
|
764
|
-
if (typeof sourceName !== "string") {
|
|
765
|
-
return undefined;
|
|
766
|
-
}
|
|
767
|
-
if (reference !== null && reference !== undefined && typeof reference !== "string") {
|
|
768
|
-
return undefined;
|
|
769
|
-
}
|
|
770
|
-
return { sourceName, reference: reference ?? undefined };
|
|
771
|
-
}
|
|
772
|
-
/** The record as the source built it: its own reference instead of the runner's envelope. */
|
|
773
|
-
function withToolSourceReference(toolCall, reference) {
|
|
774
|
-
const { toolSourceReference: _envelope, ...rest } = toolCall;
|
|
775
|
-
return (reference === undefined ? rest : { ...rest, toolSourceReference: reference });
|
|
882
|
+
return readRouterToolSourceEnvelope(toolSourceNameKey, toolSourceReference);
|
|
776
883
|
}
|
|
777
|
-
//# sourceMappingURL=
|
|
884
|
+
//# sourceMappingURL=builtin-loop-agent-runner.js.map
|