@strands-agents/sdk 1.14.0 → 1.16.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/dist/src/agent/agent-delegation.d.ts +9 -30
- package/dist/src/agent/agent-delegation.d.ts.map +1 -1
- package/dist/src/agent/agent-delegation.js +26 -98
- package/dist/src/agent/agent-delegation.js.map +1 -1
- package/dist/src/agent/agent.d.ts +26 -8
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +132 -53
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/agent/continuation.d.ts +41 -0
- package/dist/src/agent/continuation.d.ts.map +1 -1
- package/dist/src/agent/continuation.js +41 -0
- package/dist/src/agent/continuation.js.map +1 -1
- package/dist/src/background-tasks/background-tasks.d.ts +41 -0
- package/dist/src/background-tasks/background-tasks.d.ts.map +1 -0
- package/dist/src/background-tasks/background-tasks.js +402 -0
- package/dist/src/background-tasks/background-tasks.js.map +1 -0
- package/dist/src/background-tasks/errors.d.ts +15 -0
- package/dist/src/background-tasks/errors.d.ts.map +1 -0
- package/dist/src/background-tasks/errors.js +21 -0
- package/dist/src/background-tasks/errors.js.map +1 -0
- package/dist/src/background-tasks/in-process/engine.d.ts +95 -0
- package/dist/src/background-tasks/in-process/engine.d.ts.map +1 -0
- package/dist/src/background-tasks/in-process/engine.js +306 -0
- package/dist/src/background-tasks/in-process/engine.js.map +1 -0
- package/dist/src/background-tasks/in-process/manager.d.ts +41 -0
- package/dist/src/background-tasks/in-process/manager.d.ts.map +1 -0
- package/dist/src/background-tasks/in-process/manager.js +214 -0
- package/dist/src/background-tasks/in-process/manager.js.map +1 -0
- package/dist/src/background-tasks/in-process/types.d.ts +71 -0
- package/dist/src/background-tasks/in-process/types.d.ts.map +1 -0
- package/dist/src/background-tasks/in-process/types.js +2 -0
- package/dist/src/background-tasks/in-process/types.js.map +1 -0
- package/dist/src/background-tasks/manager.d.ts +59 -0
- package/dist/src/background-tasks/manager.d.ts.map +1 -0
- package/dist/src/background-tasks/manager.js +2 -0
- package/dist/src/background-tasks/manager.js.map +1 -0
- package/dist/src/background-tasks/timer.d.ts +3 -0
- package/dist/src/background-tasks/timer.d.ts.map +1 -0
- package/dist/src/background-tasks/timer.js +11 -0
- package/dist/src/background-tasks/timer.js.map +1 -0
- package/dist/src/background-tasks/types.d.ts +60 -0
- package/dist/src/background-tasks/types.d.ts.map +1 -0
- package/dist/src/background-tasks/types.js +11 -0
- package/dist/src/background-tasks/types.js.map +1 -0
- package/dist/src/hooks/events.d.ts +6 -5
- package/dist/src/hooks/events.d.ts.map +1 -1
- package/dist/src/hooks/events.js +5 -4
- package/dist/src/hooks/events.js.map +1 -1
- package/dist/src/hooks/types.d.ts +1 -0
- package/dist/src/hooks/types.d.ts.map +1 -1
- package/dist/src/hooks/types.js +1 -0
- package/dist/src/hooks/types.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/models/bedrock.d.ts +11 -0
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +61 -10
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/google/adapters.d.ts.map +1 -1
- package/dist/src/models/google/adapters.js +18 -4
- package/dist/src/models/google/adapters.js.map +1 -1
- package/dist/src/models/google/model.d.ts.map +1 -1
- package/dist/src/models/google/model.js +9 -7
- package/dist/src/models/google/model.js.map +1 -1
- package/dist/src/models/google/types.d.ts +2 -0
- package/dist/src/models/google/types.d.ts.map +1 -1
- package/dist/src/models/model.d.ts +4 -0
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js.map +1 -1
- package/dist/src/models/openai/cache.d.ts +31 -0
- package/dist/src/models/openai/cache.d.ts.map +1 -0
- package/dist/src/models/openai/cache.js +56 -0
- package/dist/src/models/openai/cache.js.map +1 -0
- package/dist/src/models/openai/chat-adapter.d.ts.map +1 -1
- package/dist/src/models/openai/chat-adapter.js +2 -0
- package/dist/src/models/openai/chat-adapter.js.map +1 -1
- package/dist/src/models/openai/model.d.ts.map +1 -1
- package/dist/src/models/openai/model.js +9 -3
- package/dist/src/models/openai/model.js.map +1 -1
- package/dist/src/models/openai/responses-adapter.d.ts.map +1 -1
- package/dist/src/models/openai/responses-adapter.js +3 -1
- package/dist/src/models/openai/responses-adapter.js.map +1 -1
- package/dist/src/models/openai/types.d.ts +5 -1
- package/dist/src/models/openai/types.d.ts.map +1 -1
- package/dist/src/models/routing/fallback-strategy.d.ts +13 -0
- package/dist/src/models/routing/fallback-strategy.d.ts.map +1 -0
- package/dist/src/models/routing/fallback-strategy.js +33 -0
- package/dist/src/models/routing/fallback-strategy.js.map +1 -0
- package/dist/src/models/routing/index.d.ts +11 -0
- package/dist/src/models/routing/index.d.ts.map +1 -0
- package/dist/src/models/routing/index.js +9 -0
- package/dist/src/models/routing/index.js.map +1 -0
- package/dist/src/models/routing/router.d.ts +161 -0
- package/dist/src/models/routing/router.d.ts.map +1 -0
- package/dist/src/models/routing/router.js +474 -0
- package/dist/src/models/routing/router.js.map +1 -0
- package/dist/src/models/routing/strategy.d.ts +53 -0
- package/dist/src/models/routing/strategy.d.ts.map +1 -0
- package/dist/src/models/routing/strategy.js +2 -0
- package/dist/src/models/routing/strategy.js.map +1 -0
- package/dist/src/models/streaming.d.ts +12 -0
- package/dist/src/models/streaming.d.ts.map +1 -1
- package/dist/src/models/streaming.js +17 -0
- package/dist/src/models/streaming.js.map +1 -1
- package/dist/src/retry/default-model-retry-strategy.d.ts +3 -8
- package/dist/src/retry/default-model-retry-strategy.d.ts.map +1 -1
- package/dist/src/retry/default-model-retry-strategy.js +3 -8
- package/dist/src/retry/default-model-retry-strategy.js.map +1 -1
- package/dist/src/retry/model-retry-strategy.d.ts +6 -9
- package/dist/src/retry/model-retry-strategy.d.ts.map +1 -1
- package/dist/src/retry/model-retry-strategy.js +8 -12
- package/dist/src/retry/model-retry-strategy.js.map +1 -1
- package/dist/src/session/session-manager.d.ts +4 -0
- package/dist/src/session/session-manager.d.ts.map +1 -1
- package/dist/src/session/session-manager.js +6 -0
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/storage/local-file-storage.d.ts +4 -6
- package/dist/src/storage/local-file-storage.d.ts.map +1 -1
- package/dist/src/storage/local-file-storage.js +12 -10
- package/dist/src/storage/local-file-storage.js.map +1 -1
- package/dist/src/storage/search/keyword.d.ts +1 -0
- package/dist/src/storage/search/keyword.d.ts.map +1 -1
- package/dist/src/storage/search/keyword.js +124 -0
- package/dist/src/storage/search/keyword.js.map +1 -1
- package/dist/src/storage/search/qmd.d.ts +66 -0
- package/dist/src/storage/search/qmd.d.ts.map +1 -0
- package/dist/src/storage/search/qmd.js +124 -0
- package/dist/src/storage/search/qmd.js.map +1 -0
- package/dist/src/telemetry/meter.d.ts +8 -6
- package/dist/src/telemetry/meter.d.ts.map +1 -1
- package/dist/src/telemetry/meter.js +8 -7
- package/dist/src/telemetry/meter.js.map +1 -1
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +9 -2
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/tools/executors/executor.d.ts +11 -1
- package/dist/src/tools/executors/executor.d.ts.map +1 -1
- package/dist/src/tools/executors/executor.js +50 -16
- package/dist/src/tools/executors/executor.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/agent.d.ts +12 -2
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js +3 -2
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/vended-memory-stores/file-memory-store/index.d.ts +1 -1
- package/dist/src/vended-memory-stores/file-memory-store/index.d.ts.map +1 -1
- package/dist/src/vended-memory-stores/file-memory-store/store.d.ts +31 -0
- package/dist/src/vended-memory-stores/file-memory-store/store.d.ts.map +1 -1
- package/dist/src/vended-memory-stores/file-memory-store/store.js +48 -25
- package/dist/src/vended-memory-stores/file-memory-store/store.js.map +1 -1
- package/dist/src/vended-tools/file-editor/file-editor.js +35 -30
- package/dist/src/vended-tools/file-editor/file-editor.js.map +1 -1
- package/package.json +21 -4
package/dist/src/agent/agent.js
CHANGED
|
@@ -7,8 +7,9 @@ import { isValidToolName } from '../tools/tool.js';
|
|
|
7
7
|
import { cloneSystemPrompt, systemPromptFromData } from '../types/messages.js';
|
|
8
8
|
import { normalizeError, ConcurrentInvocationError, StructuredOutputError } from '../errors.js';
|
|
9
9
|
import { Model } from '../models/model.js';
|
|
10
|
+
import { ModelRouter } from '../models/routing/router.js';
|
|
10
11
|
import { ModelPlugin } from '../plugins/model-plugin.js';
|
|
11
|
-
import { isModelStreamEvent } from '../models/streaming.js';
|
|
12
|
+
import { totalPromptTokens, isModelStreamEvent } from '../models/streaming.js';
|
|
12
13
|
import { ToolRegistry } from '../registry/tool-registry.js';
|
|
13
14
|
import { StateStore } from '../state-store.js';
|
|
14
15
|
import { serializeStateSerializable, loadStateSerializable } from '../types/serializable.js';
|
|
@@ -46,6 +47,7 @@ import { takeSnapshot as takeSnapshotInternal, loadSnapshot as loadSnapshotInter
|
|
|
46
47
|
import { defaultSandbox } from '../sandbox/default.js';
|
|
47
48
|
import { summarizeContextTool, truncateContextTool, pinContextTool, createTokenUsageMiddleware, } from '../context-manager/modes/agentic/agentic-context.js';
|
|
48
49
|
import { ContextManager } from '../context-manager/context-manager.js';
|
|
50
|
+
import { BackgroundTasks } from '../background-tasks/background-tasks.js';
|
|
49
51
|
/**
|
|
50
52
|
* Supported string presets for the `contextManager` parameter.
|
|
51
53
|
*/
|
|
@@ -142,6 +144,7 @@ export class Agent {
|
|
|
142
144
|
* The model provider used by the agent for inference.
|
|
143
145
|
*/
|
|
144
146
|
model;
|
|
147
|
+
_modelRouter;
|
|
145
148
|
/**
|
|
146
149
|
* The system prompt to pass to the model provider.
|
|
147
150
|
*/
|
|
@@ -166,6 +169,7 @@ export class Agent {
|
|
|
166
169
|
* The session manager for saving and restoring agent sessions, if configured.
|
|
167
170
|
*/
|
|
168
171
|
sessionManager;
|
|
172
|
+
_sessionId;
|
|
169
173
|
/**
|
|
170
174
|
* The memory manager for cross-session memory retrieval and storage, if configured.
|
|
171
175
|
*/
|
|
@@ -184,6 +188,20 @@ export class Agent {
|
|
|
184
188
|
get sandbox() {
|
|
185
189
|
return this._sandbox || defaultSandbox.get();
|
|
186
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* A stable, unique identifier for the current conversation session.
|
|
193
|
+
*
|
|
194
|
+
* If a SessionManager is attached, delegates to its sessionId.
|
|
195
|
+
* Otherwise, lazily generates and caches a random 8-character hex string.
|
|
196
|
+
*/
|
|
197
|
+
get sessionId() {
|
|
198
|
+
if (this.sessionManager)
|
|
199
|
+
return this.sessionManager.sessionId;
|
|
200
|
+
if (!this._sessionId) {
|
|
201
|
+
this._sessionId = globalThis.crypto.randomUUID().slice(0, 8);
|
|
202
|
+
}
|
|
203
|
+
return this._sessionId;
|
|
204
|
+
}
|
|
187
205
|
_hooksRegistry;
|
|
188
206
|
_middlewareRegistry;
|
|
189
207
|
_pluginRegistry;
|
|
@@ -208,6 +226,7 @@ export class Agent {
|
|
|
208
226
|
_checkpointing;
|
|
209
227
|
/** Direct tool caller — created via {@link ToolCaller.create} factory. */
|
|
210
228
|
_toolCaller;
|
|
229
|
+
_backgroundTasks;
|
|
211
230
|
/**
|
|
212
231
|
* Creates an instance of the Agent.
|
|
213
232
|
* @param config - The configuration for the agent.
|
|
@@ -231,11 +250,19 @@ export class Agent {
|
|
|
231
250
|
? new MemoryManager(config.memoryManager)
|
|
232
251
|
: undefined;
|
|
233
252
|
this._sandbox = config?.sandbox;
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
const configuredModel = config?.model;
|
|
254
|
+
if (typeof configuredModel === 'string') {
|
|
255
|
+
this.model = new BedrockModel({ modelId: configuredModel });
|
|
256
|
+
}
|
|
257
|
+
else if (configuredModel instanceof ModelRouter) {
|
|
258
|
+
this._modelRouter = configuredModel;
|
|
259
|
+
this.model = configuredModel.defaultModel;
|
|
236
260
|
}
|
|
237
261
|
else {
|
|
238
|
-
this.model =
|
|
262
|
+
this.model = configuredModel ?? new BedrockModel();
|
|
263
|
+
}
|
|
264
|
+
if (config?.plugins?.some((plugin) => plugin instanceof ModelRouter)) {
|
|
265
|
+
throw new Error('ModelRouter must be passed through Agent({ model }), not plugins');
|
|
239
266
|
}
|
|
240
267
|
// Validate and assign conversation manager
|
|
241
268
|
if (this.model.stateful) {
|
|
@@ -258,6 +285,7 @@ export class Agent {
|
|
|
258
285
|
this._interventionRegistry = new InterventionRegistry(config?.interventions ?? [], this._hooksRegistry);
|
|
259
286
|
// Initialize middleware registry
|
|
260
287
|
this._middlewareRegistry = new MiddlewareRegistry();
|
|
288
|
+
this._modelRouter?.attachToAgent(this);
|
|
261
289
|
if (config?.contextManager === 'agentic') {
|
|
262
290
|
this._middlewareRegistry.addInput(InvokeModelStage.Input, createTokenUsageMiddleware());
|
|
263
291
|
}
|
|
@@ -284,10 +312,26 @@ export class Agent {
|
|
|
284
312
|
// The plugin is a no-op when no delegation tools fire.
|
|
285
313
|
const hasAgentDelegation = (config?.plugins ?? []).some((p) => p.name === 'strands:agent-delegation');
|
|
286
314
|
const contextManagerPlugin = config?.contextManager instanceof ContextManager ? config.contextManager : undefined;
|
|
315
|
+
this._backgroundTasks = config?.backgroundTasks
|
|
316
|
+
? new BackgroundTasks(config.backgroundTasks === true ? {} : config.backgroundTasks, (tool, context, middlewareInterrupt) => this._toolExecutor.executeBackground({
|
|
317
|
+
agent: this,
|
|
318
|
+
middlewareRegistry: this._middlewareRegistry,
|
|
319
|
+
// Isolate mutable local trace state from overlapping agent work. OTel spans
|
|
320
|
+
// still use the global provider; local traces are not added to AgentResult.traces.
|
|
321
|
+
tracer: new Tracer(config?.traceAttributes),
|
|
322
|
+
meter: this._meter,
|
|
323
|
+
cancelSignal: context.cancelSignal,
|
|
324
|
+
toolInterrupt: context.interrupt,
|
|
325
|
+
middlewareInterrupt,
|
|
326
|
+
toolGuard: (selectedTool) => this._backgroundTasks.assertToolCanRun(selectedTool),
|
|
327
|
+
}, context.toolUse, tool, context.invocationState, (event) => this._invokeCallbacks(event)))
|
|
328
|
+
: undefined;
|
|
287
329
|
this._pluginRegistry = new PluginRegistry([
|
|
330
|
+
...(this._modelRouter ? [this._modelRouter] : []),
|
|
288
331
|
this._conversationManager,
|
|
289
332
|
...retryStrategies,
|
|
290
333
|
...(config?.plugins ?? []),
|
|
334
|
+
...(this._backgroundTasks ? [this._backgroundTasks] : []),
|
|
291
335
|
...(!hasAgentDelegation ? [new AgentDelegation()] : []),
|
|
292
336
|
...((config?.contextManager === 'auto' || config?.contextManager === 'agentic') && !hasOffloader
|
|
293
337
|
? [
|
|
@@ -940,7 +984,11 @@ export class Agent {
|
|
|
940
984
|
* ```
|
|
941
985
|
*/
|
|
942
986
|
loadSnapshot(snapshot) {
|
|
987
|
+
if (this._initialized)
|
|
988
|
+
this._backgroundTasks?.assertCanLoadSnapshot();
|
|
943
989
|
loadSnapshotInternal(this, snapshot);
|
|
990
|
+
if (this._initialized && 'state' in snapshot.data)
|
|
991
|
+
this._backgroundTasks?._loadAppState();
|
|
944
992
|
}
|
|
945
993
|
/**
|
|
946
994
|
* Invokes hook callbacks and printer for a stream event.
|
|
@@ -1042,6 +1090,24 @@ export class Agent {
|
|
|
1042
1090
|
cycleId,
|
|
1043
1091
|
messages: this.messages,
|
|
1044
1092
|
});
|
|
1093
|
+
// Closes cycle telemetry exactly once, however the cycle exits: return,
|
|
1094
|
+
// continue, throw, or the consumer closing the public iterator (which
|
|
1095
|
+
// resumes the generator at the suspended yield and runs the finally).
|
|
1096
|
+
// Called before building an AgentResult so its metrics include this cycle.
|
|
1097
|
+
let cycleClosed = false;
|
|
1098
|
+
const closeCycle = (error) => {
|
|
1099
|
+
if (cycleClosed) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
cycleClosed = true;
|
|
1103
|
+
this._meter.endCycle(cycleStartTime);
|
|
1104
|
+
if (error) {
|
|
1105
|
+
this._tracer.endAgentLoopSpan(cycleSpan, { error });
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1045
1111
|
try {
|
|
1046
1112
|
// Normalize input and append user messages on first invocation only
|
|
1047
1113
|
if (currentArgs !== undefined) {
|
|
@@ -1073,8 +1139,7 @@ export class Agent {
|
|
|
1073
1139
|
if (structuredOutputTool && structuredOutputChoice) {
|
|
1074
1140
|
throw new StructuredOutputError('The model failed to invoke the structured output tool even after it was forced.');
|
|
1075
1141
|
}
|
|
1076
|
-
|
|
1077
|
-
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
1142
|
+
closeCycle();
|
|
1078
1143
|
// Schema set, model ignored the tool — drop the response and force the tool next cycle.
|
|
1079
1144
|
// Appending the plain-text turn here would leave the conversation ending on an
|
|
1080
1145
|
// assistant message, which providers like Bedrock reject as assistant prefill.
|
|
@@ -1105,8 +1170,7 @@ export class Agent {
|
|
|
1105
1170
|
const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
|
|
1106
1171
|
yield this._appendMessage(modelResult.message, invocationState);
|
|
1107
1172
|
yield this._appendMessage(toolResultMessage, invocationState);
|
|
1108
|
-
|
|
1109
|
-
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
1173
|
+
closeCycle();
|
|
1110
1174
|
result = new AgentResult({
|
|
1111
1175
|
stopReason: 'cancelled',
|
|
1112
1176
|
lastMessage: modelResult.message,
|
|
@@ -1126,8 +1190,7 @@ export class Agent {
|
|
|
1126
1190
|
const priorResumePosition = resumePosition;
|
|
1127
1191
|
resumePosition = undefined;
|
|
1128
1192
|
if (priorResumePosition !== 'afterModel') {
|
|
1129
|
-
|
|
1130
|
-
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
1193
|
+
closeCycle();
|
|
1131
1194
|
result = new AgentResult({
|
|
1132
1195
|
stopReason: 'checkpoint',
|
|
1133
1196
|
lastMessage: modelResult.message,
|
|
@@ -1143,19 +1206,18 @@ export class Agent {
|
|
|
1143
1206
|
}
|
|
1144
1207
|
// Execute tools
|
|
1145
1208
|
const toolsResult = yield* this.executeTools(assistantMessage, invocationState, completedToolResults);
|
|
1146
|
-
//
|
|
1147
|
-
//
|
|
1209
|
+
// Reached when the consumer breaks the stream during tool execution.
|
|
1210
|
+
// _streamCore's drain loop calls .return(), which runs the finally in
|
|
1211
|
+
// executeTools and yields its AfterToolsEvent, and the drain's next()
|
|
1212
|
+
// then completes the closed generator, so this yield* evaluates to
|
|
1213
|
+
// undefined instead of unwinding.
|
|
1148
1214
|
if (!toolsResult) {
|
|
1149
|
-
this._meter.endCycle(cycleStartTime);
|
|
1150
|
-
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
1151
1215
|
continue;
|
|
1152
1216
|
}
|
|
1153
1217
|
const toolResultMessage = toolsResult.message;
|
|
1154
1218
|
// Tools were skipped (not executed) — preserve pending state so the next resume
|
|
1155
1219
|
// can run them.
|
|
1156
1220
|
if (this.isCancelled && toolsResult.toolsSkipped && this._interruptState.pendingToolExecution) {
|
|
1157
|
-
this._meter.endCycle(cycleStartTime);
|
|
1158
|
-
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
1159
1221
|
continue;
|
|
1160
1222
|
}
|
|
1161
1223
|
/**
|
|
@@ -1173,15 +1235,17 @@ export class Agent {
|
|
|
1173
1235
|
if (this._interruptState.activated) {
|
|
1174
1236
|
this._interruptState.deactivate();
|
|
1175
1237
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
// Hook requested halt: exit without calling the model again
|
|
1238
|
+
closeCycle();
|
|
1239
|
+
// Hook requested halt with content: exit without calling the model again.
|
|
1179
1240
|
const { afterToolsEvent } = toolsResult;
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1241
|
+
const endTurnValue = afterToolsEvent.endTurn;
|
|
1242
|
+
if (endTurnValue === true || (endTurnValue !== false && endTurnValue.length > 0)) {
|
|
1243
|
+
const endTurnContent = Array.isArray(endTurnValue)
|
|
1244
|
+
? [...endTurnValue]
|
|
1245
|
+
: [
|
|
1246
|
+
new TextBlock(typeof endTurnValue === 'string' ? endTurnValue : 'Turn ended early by hook after tool execution'),
|
|
1247
|
+
];
|
|
1248
|
+
const lastMessage = new Message({ role: 'assistant', content: endTurnContent });
|
|
1185
1249
|
yield this._appendMessage(lastMessage, invocationState);
|
|
1186
1250
|
result = new AgentResult({
|
|
1187
1251
|
stopReason: 'endTurn',
|
|
@@ -1224,10 +1288,12 @@ export class Agent {
|
|
|
1224
1288
|
}
|
|
1225
1289
|
}
|
|
1226
1290
|
catch (error) {
|
|
1227
|
-
|
|
1228
|
-
this._tracer.endAgentLoopSpan(cycleSpan, { error: error });
|
|
1291
|
+
closeCycle(error);
|
|
1229
1292
|
throw error;
|
|
1230
1293
|
}
|
|
1294
|
+
finally {
|
|
1295
|
+
closeCycle();
|
|
1296
|
+
}
|
|
1231
1297
|
}
|
|
1232
1298
|
}
|
|
1233
1299
|
catch (error) {
|
|
@@ -1451,23 +1517,23 @@ export class Agent {
|
|
|
1451
1517
|
if (this.systemPrompt !== undefined) {
|
|
1452
1518
|
streamOptions.systemPrompt = this.systemPrompt;
|
|
1453
1519
|
}
|
|
1454
|
-
// Add tool choice if provided
|
|
1455
1520
|
if (toolChoice) {
|
|
1456
1521
|
streamOptions.toolChoice = toolChoice;
|
|
1457
1522
|
}
|
|
1458
1523
|
let attemptCount = 1;
|
|
1459
1524
|
while (true) {
|
|
1460
|
-
|
|
1525
|
+
const selectedModel = this._modelForAttempt(invocationState);
|
|
1461
1526
|
let projectedInputTokens;
|
|
1462
1527
|
try {
|
|
1528
|
+
// Context management continues to size against the agent's default model.
|
|
1463
1529
|
projectedInputTokens = await this._estimateInputTokens(streamOptions);
|
|
1464
1530
|
}
|
|
1465
|
-
catch (
|
|
1466
|
-
logger.debug(`error=<${
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
logger.debug(`error=<${error}> | token estimation failed, proceeding without estimate`);
|
|
1467
1533
|
}
|
|
1468
1534
|
const beforeModelCallEvent = new BeforeModelCallEvent({
|
|
1469
1535
|
agent: this,
|
|
1470
|
-
model:
|
|
1536
|
+
model: selectedModel,
|
|
1471
1537
|
invocationState,
|
|
1472
1538
|
...(projectedInputTokens !== undefined && { projectedInputTokens }),
|
|
1473
1539
|
});
|
|
@@ -1488,14 +1554,14 @@ export class Agent {
|
|
|
1488
1554
|
const stopData = { message, stopReason: 'endTurn' };
|
|
1489
1555
|
const afterModelCallEvent = new AfterModelCallEvent({
|
|
1490
1556
|
agent: this,
|
|
1491
|
-
model:
|
|
1557
|
+
model: selectedModel,
|
|
1492
1558
|
attemptCount,
|
|
1493
1559
|
stopData,
|
|
1494
1560
|
invocationState,
|
|
1495
1561
|
});
|
|
1496
1562
|
yield afterModelCallEvent;
|
|
1497
1563
|
if (afterModelCallEvent.retry) {
|
|
1498
|
-
attemptCount
|
|
1564
|
+
attemptCount = this._nextAttemptCount(selectedModel, invocationState, attemptCount);
|
|
1499
1565
|
continue;
|
|
1500
1566
|
}
|
|
1501
1567
|
return { message, stopReason: 'endTurn' };
|
|
@@ -1510,9 +1576,11 @@ export class Agent {
|
|
|
1510
1576
|
logger.debug(`error=<${error}> | token estimation failed after continuation input, proceeding without estimate`);
|
|
1511
1577
|
}
|
|
1512
1578
|
}
|
|
1579
|
+
const invokedModelRef = {};
|
|
1513
1580
|
try {
|
|
1514
|
-
const result = yield* this._invokeModelWithMiddleware(invocationState, toolChoice, projectedInputTokens);
|
|
1515
|
-
|
|
1581
|
+
const result = yield* this._invokeModelWithMiddleware(invocationState, selectedModel, invokedModelRef, toolChoice, projectedInputTokens);
|
|
1582
|
+
const routedModel = this._modelRouter?.getRoutedModel(this, invocationState) ?? selectedModel;
|
|
1583
|
+
const model = invokedModelRef.model ?? selectedModel;
|
|
1516
1584
|
this._meter.updateCycle(result.metadata);
|
|
1517
1585
|
yield new ModelMessageEvent({
|
|
1518
1586
|
agent: this,
|
|
@@ -1520,7 +1588,6 @@ export class Agent {
|
|
|
1520
1588
|
stopReason: result.stopReason,
|
|
1521
1589
|
invocationState,
|
|
1522
1590
|
});
|
|
1523
|
-
// Handle user content redaction if guardrails blocked input
|
|
1524
1591
|
if (result.redaction?.userMessage) {
|
|
1525
1592
|
this._redactLastMessage(result.redaction.userMessage);
|
|
1526
1593
|
}
|
|
@@ -1531,45 +1598,50 @@ export class Agent {
|
|
|
1531
1598
|
};
|
|
1532
1599
|
const afterModelCallEvent = new AfterModelCallEvent({
|
|
1533
1600
|
agent: this,
|
|
1534
|
-
model
|
|
1601
|
+
model,
|
|
1535
1602
|
attemptCount,
|
|
1536
1603
|
stopData,
|
|
1537
1604
|
invocationState,
|
|
1538
1605
|
});
|
|
1539
1606
|
yield afterModelCallEvent;
|
|
1540
1607
|
if (afterModelCallEvent.retry) {
|
|
1541
|
-
attemptCount
|
|
1608
|
+
attemptCount = this._nextAttemptCount(routedModel, invocationState, attemptCount);
|
|
1542
1609
|
continue;
|
|
1543
1610
|
}
|
|
1544
1611
|
return result;
|
|
1545
1612
|
}
|
|
1546
1613
|
catch (error) {
|
|
1614
|
+
const routedModel = this._modelRouter?.getRoutedModel(this, invocationState) ?? selectedModel;
|
|
1615
|
+
// A failure before the terminal ran (e.g. in input middleware) is attributed to the routed model.
|
|
1616
|
+
const failedModel = invokedModelRef.model ?? routedModel;
|
|
1547
1617
|
const modelError = normalizeError(error);
|
|
1548
|
-
// Create error event
|
|
1549
1618
|
const errorEvent = new AfterModelCallEvent({
|
|
1550
1619
|
agent: this,
|
|
1551
|
-
model:
|
|
1620
|
+
model: failedModel,
|
|
1552
1621
|
attemptCount,
|
|
1553
1622
|
error: modelError,
|
|
1554
1623
|
invocationState,
|
|
1555
1624
|
});
|
|
1556
|
-
// Yield error event - stream will invoke hooks
|
|
1557
1625
|
yield errorEvent;
|
|
1558
|
-
//
|
|
1559
|
-
// (we emit the AfterModelCall because we already emitted Before and we guarentee the pair)
|
|
1626
|
+
// Preserve the Before/After event pair, but never retry cancellation.
|
|
1560
1627
|
if (error instanceof CancelledError) {
|
|
1561
1628
|
throw error;
|
|
1562
1629
|
}
|
|
1563
|
-
// After yielding, hooks have been invoked and may have set retry
|
|
1564
1630
|
if (errorEvent.retry) {
|
|
1565
|
-
attemptCount
|
|
1631
|
+
attemptCount = this._nextAttemptCount(routedModel, invocationState, attemptCount);
|
|
1566
1632
|
continue;
|
|
1567
1633
|
}
|
|
1568
|
-
// Re-throw error
|
|
1569
1634
|
throw error;
|
|
1570
1635
|
}
|
|
1571
1636
|
}
|
|
1572
1637
|
}
|
|
1638
|
+
_modelForAttempt(invocationState) {
|
|
1639
|
+
return this._modelRouter?.getRoutedModel(this, invocationState) ?? this.model;
|
|
1640
|
+
}
|
|
1641
|
+
_nextAttemptCount(model, invocationState, attemptCount) {
|
|
1642
|
+
const nextModel = this._modelRouter?.getRoutedModel(this, invocationState);
|
|
1643
|
+
return nextModel !== undefined && nextModel !== model ? 1 : attemptCount + 1;
|
|
1644
|
+
}
|
|
1573
1645
|
/**
|
|
1574
1646
|
* Invokes the model through the InvokeModelStage middleware chain.
|
|
1575
1647
|
* Builds an InvokeModelContext from current agent state and composes the
|
|
@@ -1577,13 +1649,15 @@ export class Agent {
|
|
|
1577
1649
|
* using context fields directly (not re-derived from the agent).
|
|
1578
1650
|
*
|
|
1579
1651
|
* @param invocationState - Per-invocation state shared across hooks and tools
|
|
1652
|
+
* @param selectedModel - Model the chain starts with; middleware may replace it
|
|
1653
|
+
* @param invokedModelRef - Slot filled with the model the terminal actually invoked
|
|
1580
1654
|
* @param toolChoice - Optional tool choice to force specific tool usage
|
|
1581
1655
|
* @returns StreamAggregatedResult from the model (or middleware short-circuit)
|
|
1582
1656
|
*/
|
|
1583
|
-
async *_invokeModelWithMiddleware(invocationState, toolChoice, projectedInputTokens) {
|
|
1657
|
+
async *_invokeModelWithMiddleware(invocationState, selectedModel, invokedModelRef, toolChoice, projectedInputTokens) {
|
|
1584
1658
|
const context = {
|
|
1585
1659
|
agent: this,
|
|
1586
|
-
model:
|
|
1660
|
+
model: selectedModel,
|
|
1587
1661
|
messages: this.messages.map((msg) => msg.clone()),
|
|
1588
1662
|
...(this.systemPrompt !== undefined && { systemPrompt: cloneSystemPrompt(this.systemPrompt) }),
|
|
1589
1663
|
toolSpecs: deepCopy(this._toolRegistry.list().map((tool) => tool.toolSpec)),
|
|
@@ -1600,6 +1674,7 @@ export class Agent {
|
|
|
1600
1674
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1601
1675
|
const self = this;
|
|
1602
1676
|
const middlewareResult = yield* this._middlewareRegistry.invoke(InvokeModelStage, context, async function* (ctx) {
|
|
1677
|
+
invokedModelRef.model = ctx.model;
|
|
1603
1678
|
const modelId = ctx.model.modelId;
|
|
1604
1679
|
const modelSpan = self._tracer.startModelInvokeSpan({
|
|
1605
1680
|
messages: ctx.messages,
|
|
@@ -1749,6 +1824,10 @@ export class Agent {
|
|
|
1749
1824
|
tracer: this._tracer,
|
|
1750
1825
|
meter: this._meter,
|
|
1751
1826
|
cancelSignal: this._abortSignal,
|
|
1827
|
+
...(this._backgroundTasks && {
|
|
1828
|
+
backgroundTasks: this._backgroundTasks,
|
|
1829
|
+
backgroundTaskPassId: assistantMessage.trackingId,
|
|
1830
|
+
}),
|
|
1752
1831
|
}, {
|
|
1753
1832
|
toolUseBlocks,
|
|
1754
1833
|
toolResultBlocks,
|
|
@@ -1826,10 +1905,10 @@ export class Agent {
|
|
|
1826
1905
|
/**
|
|
1827
1906
|
* Estimate the input token count for the next model call.
|
|
1828
1907
|
*
|
|
1829
|
-
* Uses the token counting strategy: reads
|
|
1830
|
-
* from the last assistant message's metadata as a known baseline, then
|
|
1831
|
-
* only new messages added after it. Falls back to full estimation when no metadata
|
|
1832
|
-
*
|
|
1908
|
+
* Uses the token counting strategy: reads the total prompt the model processed (including cached
|
|
1909
|
+
* tokens) plus outputTokens from the last assistant message's metadata as a known baseline, then
|
|
1910
|
+
* estimates only new messages added after it. Falls back to full estimation when no metadata is
|
|
1911
|
+
* available (cold start or first call).
|
|
1833
1912
|
*
|
|
1834
1913
|
* @param streamOptions - The stream options containing system prompt and tool specs
|
|
1835
1914
|
* @returns Estimated input token count
|
|
@@ -1846,7 +1925,7 @@ export class Agent {
|
|
|
1846
1925
|
let estimate;
|
|
1847
1926
|
if (lastAssistantIdx >= 0) {
|
|
1848
1927
|
const usage = this.messages[lastAssistantIdx].metadata.usage;
|
|
1849
|
-
const knownBaseline = usage
|
|
1928
|
+
const knownBaseline = totalPromptTokens(usage) + usage.outputTokens;
|
|
1850
1929
|
const newMessages = this.messages.slice(lastAssistantIdx + 1);
|
|
1851
1930
|
if (newMessages.length === 0) {
|
|
1852
1931
|
estimate = knownBaseline;
|