veryfront 0.1.1289-rc.25708 → 0.1.1289-rc.25721

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.
Files changed (75) hide show
  1. package/docs/README.md +1 -1
  2. package/docs/config.json +1 -1
  3. package/docs/guides/tools.md +27 -2
  4. package/docs/manifest.json +6 -6
  5. package/esm/deno.js +1 -1
  6. package/esm/src/agent/ag-ui/connected-local-platform-tools.d.ts.map +1 -1
  7. package/esm/src/agent/ag-ui/connected-local-platform-tools.js +9 -2
  8. package/esm/src/agent/ag-ui/tool-shared.d.ts +2 -0
  9. package/esm/src/agent/ag-ui/tool-shared.d.ts.map +1 -1
  10. package/esm/src/agent/ag-ui/tool-shared.js +7 -0
  11. package/esm/src/agent/hosted/chat-runtime-tool-assembly.d.ts +2 -0
  12. package/esm/src/agent/hosted/chat-runtime-tool-assembly.d.ts.map +1 -1
  13. package/esm/src/agent/hosted/chat-runtime-tool-assembly.js +18 -2
  14. package/esm/src/agent/hosted/child-fork-tool-sources.d.ts +1 -0
  15. package/esm/src/agent/hosted/child-fork-tool-sources.d.ts.map +1 -1
  16. package/esm/src/agent/hosted/child-fork-tool-sources.js +49 -12
  17. package/esm/src/agent/hosted/default-chat-runtime.d.ts.map +1 -1
  18. package/esm/src/agent/hosted/default-chat-runtime.js +26 -1
  19. package/esm/src/agent/hosted/executor-runtime-facades.d.ts.map +1 -1
  20. package/esm/src/agent/hosted/executor-runtime-facades.js +3 -0
  21. package/esm/src/agent/hosted/executor-tool-schema.d.ts.map +1 -1
  22. package/esm/src/agent/hosted/executor-tool-schema.js +7 -1
  23. package/esm/src/agent/hosted/platform-dependency-credential.d.ts.map +1 -1
  24. package/esm/src/agent/hosted/platform-dependency-credential.js +26 -1
  25. package/esm/src/agent/hosted/project-remote-tool-source.d.ts.map +1 -1
  26. package/esm/src/agent/hosted/project-remote-tool-source.js +18 -0
  27. package/esm/src/agent/hosted/terminal-credential.d.ts.map +1 -1
  28. package/esm/src/agent/hosted/terminal-credential.js +9 -3
  29. package/esm/src/agent/platform-mcp-tool-source.d.ts.map +1 -1
  30. package/esm/src/agent/platform-mcp-tool-source.js +4 -0
  31. package/esm/src/agent/project/live-studio-mcp-tools.d.ts.map +1 -1
  32. package/esm/src/agent/project/live-studio-mcp-tools.js +14 -2
  33. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  34. package/esm/src/agent/runtime/index.js +2269 -2191
  35. package/esm/src/agent/runtime/mcp-server-tool-sources.d.ts +2 -0
  36. package/esm/src/agent/runtime/mcp-server-tool-sources.d.ts.map +1 -1
  37. package/esm/src/agent/runtime/mcp-server-tool-sources.js +27 -0
  38. package/esm/src/agent/runtime/runtime-tool-config.d.ts +1 -0
  39. package/esm/src/agent/runtime/runtime-tool-config.d.ts.map +1 -1
  40. package/esm/src/agent/runtime/runtime-tool-config.js +4 -0
  41. package/esm/src/agent/runtime/tool-exposure.d.ts +4 -2
  42. package/esm/src/agent/runtime/tool-exposure.d.ts.map +1 -1
  43. package/esm/src/agent/runtime/tool-exposure.js +17 -4
  44. package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
  45. package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
  46. package/esm/src/internal-agents/run-stream.js +95 -23
  47. package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
  48. package/esm/src/server/handlers/request/agent-stream.handler.d.ts +2 -0
  49. package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
  50. package/esm/src/server/handlers/request/agent-stream.handler.js +24 -3
  51. package/esm/src/server/handlers/request/run-terminal-tool-source.d.ts.map +1 -1
  52. package/esm/src/server/handlers/request/run-terminal-tool-source.js +12 -3
  53. package/esm/src/tool/factory.d.ts +2 -0
  54. package/esm/src/tool/factory.d.ts.map +1 -1
  55. package/esm/src/tool/factory.js +7 -0
  56. package/esm/src/tool/host-tools.d.ts +2 -0
  57. package/esm/src/tool/host-tools.d.ts.map +1 -1
  58. package/esm/src/tool/host-tools.js +14 -0
  59. package/esm/src/tool/project-scoped-remote-tools.d.ts.map +1 -1
  60. package/esm/src/tool/project-scoped-remote-tools.js +12 -0
  61. package/esm/src/tool/registry.d.ts.map +1 -1
  62. package/esm/src/tool/registry.js +3 -0
  63. package/esm/src/tool/remote-mcp.d.ts +11 -0
  64. package/esm/src/tool/remote-mcp.d.ts.map +1 -1
  65. package/esm/src/tool/remote-mcp.js +685 -85
  66. package/esm/src/tool/remote-source-tools.d.ts.map +1 -1
  67. package/esm/src/tool/remote-source-tools.js +1 -0
  68. package/esm/src/tool/remote-tool-source-owner.d.ts +14 -0
  69. package/esm/src/tool/remote-tool-source-owner.d.ts.map +1 -0
  70. package/esm/src/tool/remote-tool-source-owner.js +65 -0
  71. package/esm/src/tool/types.d.ts +4 -0
  72. package/esm/src/tool/types.d.ts.map +1 -1
  73. package/esm/src/utils/version-constant.d.ts +1 -1
  74. package/esm/src/utils/version-constant.js +1 -1
  75. package/package.json +9 -9
@@ -1,3 +1,55 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
1
53
  import * as dntShim from "../../../_dnt.shims.js";
2
54
  import { MAX_RUNTIME_PROVIDER_STREAM_RETRIES } from "./provider-stream-retry-policy.js";
3
55
  import { introduceToolCallOccurrence, runWithToolCallOccurrenceDispatch, } from "../../runtime/tool-call-occurrence.js";
@@ -47,7 +99,7 @@ import { convertToTextGenerationRuntimeRequestMessages } from "./text-generation
47
99
  import { attachProviderMetadata, isProviderReplayDelivered, markProviderReplayDelivered, readAttachedProviderMetadata, } from "./provider-metadata.js";
48
100
  import { isRuntimeProviderSchemaHiddenTool } from "./local-tool.js";
49
101
  import { convertToolsToRuntimeTools } from "./model-tool-converter.js";
50
- import { bindRuntimeRemoteToolSourcesToCredentialOwner, constrainRuntimeRemoteToolSources, getRuntimeRemoteToolSources, getRuntimeUnavailableOptionalRemoteTools, } from "./mcp-server-tool-sources.js";
102
+ import { bindRuntimeRemoteToolSourcesToCredentialOwner, constrainRuntimeRemoteToolSources, createRuntimeMcpSourceCleanup, getRuntimeRemoteToolSources, getRuntimeUnavailableOptionalRemoteTools, } from "./mcp-server-tool-sources.js";
51
103
  import { runWithRuntimeRemoteToolSources } from "./remote-tool-source-context.js";
52
104
  import { hasRuntimeObservationCapability, } from "../../runtime/runtime-observation-carrier.js";
53
105
  import { announceStreamedToolCallInput, createRuntimeStreamSource, createStreamState, processStream, resolveRelayableExecutionFailure, resolveRuntimeExecutionErrorEvent, withRuntimeProviderStreamErrorProvenance, } from "./chat-stream-handler.js";
@@ -1274,10 +1326,10 @@ function executeFrameworkToolSearch(input) {
1274
1326
  ...result,
1275
1327
  contract,
1276
1328
  nextStep: isAvailable
1277
- ? "This tool is already available. Call it with this input schema."
1329
+ ? "This tool is already available. Call it with this input schema. The output schema is the declared result contract, or null when absent."
1278
1330
  : result.loadedCount > 0
1279
- ? "This tool is loaded for the next step. Call it with this input schema."
1280
- : "This is the exact input schema, but the tool could not be loaded within the current tool limit.",
1331
+ ? "This tool is loaded for the next step. Call it with this input schema. The output schema is the declared result contract, or null when absent."
1332
+ : "These are the exact declared schemas, but the tool could not be loaded within the current tool limit.",
1281
1333
  },
1282
1334
  checkpoint: checkpoint(),
1283
1335
  };
@@ -3244,869 +3296,882 @@ export class AgentRuntime {
3244
3296
  async #executeAgentLoop(// NOSONAR: Existing loop shape; this patch only adds authority cleanup.
3245
3297
  systemPrompt, messages, validateProviderRequest, persistMessage, prepareTerminalDispatch, toolContextBase, runtimeContext, runRuntimeContext, supportsToolCalling, providerReplayCheckpointEmission, modelString, resolvedModel, headers, providerOptions, reasoning, maxOutputTokensOverride, temperatureModelString, toolReplacements, abortSignal, outputSchema) {
3246
3298
  return withSpan("agent.execution_loop", async (loopSpan) => {
3247
- const { maxAgentSteps } = getPlatformCapabilities();
3248
- const maxSteps = this.computeMaxSteps(maxAgentSteps);
3249
- const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
3250
- const languageModel = resolvedModel ?? resolveModel(effectiveModel);
3251
- const toolCalls = [];
3252
- const currentMessages = mapPrivateArray(messages, (message) => message);
3253
- applyProviderReplayCheckpointsToMessages(currentMessages, getRuntimeProviderReplayCheckpoints(this.config), { activeProvider: resolveActiveProviderReplayProvider(languageModel) });
3254
- const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
3255
- if (!supportsToolCalling && this.config.tools) {
3256
- warnUnsupportedToolCalling(this.id, effectiveModel);
3257
- }
3258
- // Request-scoped skill policy (not class-level mutable state)
3259
- const skillState = AgentLoopSkillState.hydrate(currentMessages, runtimeContext);
3260
- const hasToolReplacements = toolReplacements !== undefined;
3261
- const initialToolExposureCheckpoint = hasToolReplacements
3262
- ? undefined
3263
- : getRuntimeToolExposureCheckpoint(this.config);
3264
- const toolExposureState = createToolExposureState();
3265
- const persistToolExposureCheckpoint = hasToolReplacements
3266
- ? undefined
3267
- : getRuntimeToolExposureCheckpointPersister(this.config);
3268
- const requireToolExposureCheckpointPersistence = hasToolReplacements
3269
- ? false
3270
- : isRuntimeToolExposureCheckpointPersistenceRequired(this.config);
3271
- const runtimeToolsConfig = hasToolReplacements ? toolReplacements : this.config.tools;
3272
- const frameworkLocalTools = hasToolReplacements
3273
- ? undefined
3274
- : createRuntimeFrameworkLocalTools(this.config);
3275
- const toolLoadingResolution = resolveRuntimeToolLoading(this.config);
3276
- const runConfig = {
3277
- ...this.config,
3278
- __vfToolLoadingMode: hasToolReplacements ? "eager" : toolLoadingResolution.mode,
3279
- };
3280
- if (ObjectGetPrototypeOf(this.config) === null)
3281
- ObjectSetPrototypeOf(runConfig, null);
3282
- const runtimeStepConfig = hasToolReplacements
3283
- ? {
3284
- ...runConfig,
3285
- tools: runtimeToolsConfig,
3286
- skills: undefined,
3287
- bootstrapTools: undefined,
3288
- optionalBootstrapTools: undefined,
3289
- providerTools: undefined,
3290
- mcpServers: undefined,
3291
- sandbox: undefined,
3299
+ const env_1 = { stack: [], error: void 0, hasError: false };
3300
+ try {
3301
+ const { maxAgentSteps } = getPlatformCapabilities();
3302
+ const maxSteps = this.computeMaxSteps(maxAgentSteps);
3303
+ const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
3304
+ const languageModel = resolvedModel ?? resolveModel(effectiveModel);
3305
+ const toolCalls = [];
3306
+ const currentMessages = mapPrivateArray(messages, (message) => message);
3307
+ applyProviderReplayCheckpointsToMessages(currentMessages, getRuntimeProviderReplayCheckpoints(this.config), { activeProvider: resolveActiveProviderReplayProvider(languageModel) });
3308
+ const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
3309
+ if (!supportsToolCalling && this.config.tools) {
3310
+ warnUnsupportedToolCalling(this.id, effectiveModel);
3292
3311
  }
3293
- : runConfig;
3294
- if (ObjectGetPrototypeOf(this.config) === null)
3295
- ObjectSetPrototypeOf(runtimeStepConfig, null);
3296
- const runtimeStepToolLoading = resolveRuntimeToolLoading(runtimeStepConfig);
3297
- const allowedRemoteToolNames = hasToolReplacements
3298
- ? undefined
3299
- : getRuntimeAllowedRemoteTools(this.config);
3300
- const forwardedRemoteToolDefinitions = hasToolReplacements
3301
- ? undefined
3302
- : getRuntimeForwardedIntegrationToolDefs(this.config);
3303
- const remoteToolSources = hasToolReplacements
3304
- ? undefined
3305
- : getRuntimeRemoteToolSources(this.config, undefined, this.id);
3306
- const unavailableOptionalRemoteTools = hasToolReplacements
3307
- ? { names: [], prefixes: [] }
3308
- : getRuntimeUnavailableOptionalRemoteTools(this.config, remoteToolSources);
3309
- const sourceIntegrationPolicy = hasToolReplacements
3310
- ? undefined
3311
- : getRuntimeSourceIntegrationPolicy(this.config);
3312
- const configuredProviderTools = hasToolReplacements
3313
- ? []
3314
- : getRuntimeProviderTools(this.config);
3315
- const toolResultContext = createActiveToolResultContext({
3316
- config: this.config,
3317
- hasToolReplacements,
3318
- supportsToolCalling,
3319
- });
3320
- const providerTools = sourceIntegrationPolicy
3321
- ? applySourceIntegrationPolicy(configuredProviderTools, sourceIntegrationPolicy)
3322
- : configuredProviderTools;
3323
- let currentSystemPrompt = systemPrompt;
3324
- let currentRuntimeContext = runtimeContext;
3325
- let agentWriteFinalResponseToolGuardEnabled = false;
3326
- let recoveredEmptyResponse = false;
3327
- let maxStepsParsedOutput;
3328
- // A submitted form continues a turn whose startup already ran, and the
3329
- // post-form skill policy forbids loading or switching skill bodies.
3330
- // Replacements suppress startup unless they retain the configured loader.
3331
- let startupSkillLoaded = (hasToolReplacements && replacementsRetainingSkillLoader.get(toolReplacements) !== true) ||
3332
- this.config.bootstrapSkill === undefined || skillState.hasSubmittedFormInput;
3333
- for (let step = 0; step < maxSteps; step++) {
3334
- throwIfAborted(abortSignal);
3335
- this.status = "thinking";
3336
- addSpanEvent(loopSpan, "step_start", { step });
3337
- const stepRuntimeContext = skillState.hasSubmittedFormInput
3338
- ? markSubmittedFormInputRuntimeContext(currentRuntimeContext)
3339
- : currentRuntimeContext;
3340
- const preparedStep = await prepareAgentRuntimeStep({
3341
- agentId: this.id,
3342
- activeSkillId: hasToolReplacements ? undefined : skillState.activeSkillId,
3343
- activeSkillToolAvailability: hasToolReplacements
3344
- ? undefined
3345
- : skillState.activeSkillToolAvailability,
3346
- allowedRemoteToolNames,
3347
- config: runtimeStepConfig,
3348
- effectiveModel,
3349
- ...resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
3350
- reservedToolSlots: getToolResultReaderReservedSlots(toolResultContext),
3351
- excludedToolNames: agentWriteFinalResponseToolGuardEnabled &&
3352
- runtimeStepToolLoading.mode === "eager"
3353
- ? AGENT_WRITE_FINAL_RESPONSE_EXCLUDED_TOOL_NAMES
3354
- : undefined,
3355
- forwardedRemoteToolDefinitions,
3356
- getAvailableTools,
3357
- unavailableOptionalRemoteToolNames: unavailableOptionalRemoteTools.names,
3358
- unavailableOptionalRemoteToolPrefixes: unavailableOptionalRemoteTools.prefixes,
3312
+ // Request-scoped skill policy (not class-level mutable state)
3313
+ const skillState = AgentLoopSkillState.hydrate(currentMessages, runtimeContext);
3314
+ const hasToolReplacements = toolReplacements !== undefined;
3315
+ const initialToolExposureCheckpoint = hasToolReplacements
3316
+ ? undefined
3317
+ : getRuntimeToolExposureCheckpoint(this.config);
3318
+ const toolExposureState = createToolExposureState();
3319
+ const persistToolExposureCheckpoint = hasToolReplacements
3320
+ ? undefined
3321
+ : getRuntimeToolExposureCheckpointPersister(this.config);
3322
+ const requireToolExposureCheckpointPersistence = hasToolReplacements
3323
+ ? false
3324
+ : isRuntimeToolExposureCheckpointPersistenceRequired(this.config);
3325
+ const runtimeToolsConfig = hasToolReplacements ? toolReplacements : this.config.tools;
3326
+ const frameworkLocalTools = hasToolReplacements
3327
+ ? undefined
3328
+ : createRuntimeFrameworkLocalTools(this.config);
3329
+ const toolLoadingResolution = resolveRuntimeToolLoading(this.config);
3330
+ const runConfig = {
3331
+ ...this.config,
3332
+ __vfToolLoadingMode: hasToolReplacements ? "eager" : toolLoadingResolution.mode,
3333
+ };
3334
+ if (ObjectGetPrototypeOf(this.config) === null)
3335
+ ObjectSetPrototypeOf(runConfig, null);
3336
+ const runtimeStepConfig = hasToolReplacements
3337
+ ? {
3338
+ ...runConfig,
3339
+ tools: runtimeToolsConfig,
3340
+ skills: undefined,
3341
+ bootstrapTools: undefined,
3342
+ optionalBootstrapTools: undefined,
3343
+ providerTools: undefined,
3344
+ mcpServers: undefined,
3345
+ sandbox: undefined,
3346
+ }
3347
+ : runConfig;
3348
+ if (ObjectGetPrototypeOf(this.config) === null)
3349
+ ObjectSetPrototypeOf(runtimeStepConfig, null);
3350
+ const runtimeStepToolLoading = resolveRuntimeToolLoading(runtimeStepConfig);
3351
+ const allowedRemoteToolNames = hasToolReplacements
3352
+ ? undefined
3353
+ : getRuntimeAllowedRemoteTools(this.config);
3354
+ const forwardedRemoteToolDefinitions = hasToolReplacements
3355
+ ? undefined
3356
+ : getRuntimeForwardedIntegrationToolDefs(this.config);
3357
+ const remoteToolSources = hasToolReplacements
3358
+ ? undefined
3359
+ : getRuntimeRemoteToolSources(this.config, undefined, this.id);
3360
+ const _remoteMcpCleanup = __addDisposableResource(env_1, createRuntimeMcpSourceCleanup(remoteToolSources), true);
3361
+ const unavailableOptionalRemoteTools = hasToolReplacements
3362
+ ? { names: [], prefixes: [] }
3363
+ : getRuntimeUnavailableOptionalRemoteTools(this.config, remoteToolSources);
3364
+ const sourceIntegrationPolicy = hasToolReplacements
3365
+ ? undefined
3366
+ : getRuntimeSourceIntegrationPolicy(this.config);
3367
+ const configuredProviderTools = hasToolReplacements
3368
+ ? []
3369
+ : getRuntimeProviderTools(this.config);
3370
+ const toolResultContext = createActiveToolResultContext({
3371
+ config: this.config,
3372
+ hasToolReplacements,
3359
3373
  supportsToolCalling,
3360
- includeStartupAuthorizedTools: !startupSkillLoaded,
3361
- messages: currentMessages,
3362
- mode: "generate",
3363
- modelRuntime: languageModel,
3364
- providerOptionKey: resolveModelProviderOptionKey(effectiveModel, languageModel),
3365
- // The authorization catalog is independent of whether the model can
3366
- // call tools; only the model-visible provider tools are withheld.
3367
- authorizedProviderToolNames: providerTools,
3368
- providerToolNames: supportsToolCalling && !agentWriteFinalResponseToolGuardEnabled
3369
- ? providerTools
3370
- : [],
3371
- remoteToolSources,
3372
- sourceIntegrationPolicy,
3373
- resolveRuntimeState: this.resolveRuntimeState.bind(this),
3374
- runtimeContext: stepRuntimeContext,
3375
- step,
3376
- systemPrompt: currentSystemPrompt,
3377
- toolContextBase: { ...toolContextBase, abortSignal },
3378
- strictConfiguredToolsOnly: hasToolReplacements,
3379
- frameworkLocalTools,
3380
- toolExposureState,
3381
- toolExposureCheckpoint: step === 0 ? initialToolExposureCheckpoint : undefined,
3382
3374
  });
3383
- throwIfAborted(abortSignal);
3384
- currentSystemPrompt = preparedStep.systemPrompt;
3385
- currentRuntimeContext = preparedStep.runtimeContext;
3386
- const toolContext = preparedStep.toolContext;
3387
- // Keep this snapshot fixed while calls from the same provider response execute.
3388
- const providerObservedSkillBodies = getProviderObservedSkillBodies(currentMessages);
3389
- let effectiveToolExposurePlan = agentWriteFinalResponseToolGuardEnabled
3390
- ? applyAgentWriteFinalResponseGuard(preparedStep.toolExposurePlan, {
3391
- reloadable: runtimeStepToolLoading.mode === "deferred",
3392
- })
3393
- : preparedStep.toolExposurePlan;
3394
- if (!startupSkillLoaded) {
3395
- await executeStartupSkill({
3375
+ const providerTools = sourceIntegrationPolicy
3376
+ ? applySourceIntegrationPolicy(configuredProviderTools, sourceIntegrationPolicy)
3377
+ : configuredProviderTools;
3378
+ let currentSystemPrompt = systemPrompt;
3379
+ let currentRuntimeContext = runtimeContext;
3380
+ let agentWriteFinalResponseToolGuardEnabled = false;
3381
+ let recoveredEmptyResponse = false;
3382
+ let maxStepsParsedOutput;
3383
+ // A submitted form continues a turn whose startup already ran, and the
3384
+ // post-form skill policy forbids loading or switching skill bodies.
3385
+ // Replacements suppress startup unless they retain the configured loader.
3386
+ let startupSkillLoaded = (hasToolReplacements && replacementsRetainingSkillLoader.get(toolReplacements) !== true) ||
3387
+ this.config.bootstrapSkill === undefined || skillState.hasSubmittedFormInput;
3388
+ for (let step = 0; step < maxSteps; step++) {
3389
+ throwIfAborted(abortSignal);
3390
+ this.status = "thinking";
3391
+ addSpanEvent(loopSpan, "step_start", { step });
3392
+ const stepRuntimeContext = skillState.hasSubmittedFormInput
3393
+ ? markSubmittedFormInputRuntimeContext(currentRuntimeContext)
3394
+ : currentRuntimeContext;
3395
+ const preparedStep = await prepareAgentRuntimeStep({
3396
3396
  agentId: this.id,
3397
- skillId: this.config.bootstrapSkill,
3398
- authorized: preparedStep.startupAuthorizedTools,
3397
+ activeSkillId: hasToolReplacements ? undefined : skillState.activeSkillId,
3398
+ activeSkillToolAvailability: hasToolReplacements
3399
+ ? undefined
3400
+ : skillState.activeSkillToolAvailability,
3401
+ allowedRemoteToolNames,
3402
+ config: runtimeStepConfig,
3403
+ effectiveModel,
3404
+ ...resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
3405
+ reservedToolSlots: getToolResultReaderReservedSlots(toolResultContext),
3406
+ excludedToolNames: agentWriteFinalResponseToolGuardEnabled &&
3407
+ runtimeStepToolLoading.mode === "eager"
3408
+ ? AGENT_WRITE_FINAL_RESPONSE_EXCLUDED_TOOL_NAMES
3409
+ : undefined,
3410
+ forwardedRemoteToolDefinitions,
3411
+ getAvailableTools,
3412
+ unavailableOptionalRemoteToolNames: unavailableOptionalRemoteTools.names,
3413
+ unavailableOptionalRemoteToolPrefixes: unavailableOptionalRemoteTools.prefixes,
3414
+ supportsToolCalling,
3415
+ includeStartupAuthorizedTools: !startupSkillLoaded,
3399
3416
  messages: currentMessages,
3400
- persistMessage,
3401
- skillState,
3402
- toolCalls,
3403
- resolveLoader: (name) => resolveConfiguredTool(runtimeToolsConfig, name, { agentId: this.id }),
3404
- execute: async (toolName, toolCallId, args, admittedTurn) => {
3405
- const context = applicationExecutionContext(toolContext);
3406
- const result = await traceConfiguredToolExecution({
3407
- mode: "generate",
3408
- agentId: this.id,
3409
- toolName,
3410
- toolCallId,
3411
- args,
3412
- admittedTurn,
3413
- owner: currentMessages,
3414
- prepareTerminalDispatch,
3415
- toolsConfig: runtimeToolsConfig,
3416
- context,
3417
- allowedRemoteToolNames,
3418
- remoteToolSources,
3419
- sourceIntegrationPolicy,
3420
- frameworkLocalTools,
3421
- });
3422
- await this.notifyToolResult({
3423
- mode: "generate",
3424
- toolName,
3425
- toolCallId,
3426
- input: args,
3427
- result,
3428
- context,
3429
- });
3430
- return result;
3431
- },
3417
+ mode: "generate",
3418
+ modelRuntime: languageModel,
3419
+ providerOptionKey: resolveModelProviderOptionKey(effectiveModel, languageModel),
3420
+ // The authorization catalog is independent of whether the model can
3421
+ // call tools; only the model-visible provider tools are withheld.
3422
+ authorizedProviderToolNames: providerTools,
3423
+ providerToolNames: supportsToolCalling && !agentWriteFinalResponseToolGuardEnabled
3424
+ ? providerTools
3425
+ : [],
3426
+ remoteToolSources,
3427
+ sourceIntegrationPolicy,
3428
+ resolveRuntimeState: this.resolveRuntimeState.bind(this),
3429
+ runtimeContext: stepRuntimeContext,
3430
+ step,
3431
+ systemPrompt: currentSystemPrompt,
3432
+ toolContextBase: { ...toolContextBase, abortSignal },
3433
+ strictConfiguredToolsOnly: hasToolReplacements,
3434
+ frameworkLocalTools,
3435
+ toolExposureState,
3436
+ toolExposureCheckpoint: step === 0 ? initialToolExposureCheckpoint : undefined,
3432
3437
  });
3433
- startupSkillLoaded = true;
3434
- // Refresh the active skill policy without consuming a model step.
3435
- step--;
3436
- continue;
3437
- }
3438
- assertToolResultReaderNameAvailable({
3439
- plan: effectiveToolExposurePlan,
3440
- context: toolResultContext,
3441
- });
3442
- const baseSystemPrompt = currentSystemPrompt;
3443
- const assembleRequest = (messages, plan = effectiveToolExposurePlan) => assembleRuntimeStepRequest({
3444
- messages,
3445
- plan,
3446
- toolResultContext,
3447
- supportsToolCalling,
3448
- agentWriteFinalResponseToolGuardEnabled,
3449
- providerTools,
3450
- model: effectiveModel,
3451
- languageModel,
3452
- toolSchemaOptions: resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
3453
- baseSystemPrompt,
3454
- integrationToolDiscovery: preparedStep.integrationToolDiscovery,
3455
- runRuntimeContext,
3456
- bootstrapToolNames: preparedStep.configuredBootstrapToolNames,
3457
- });
3458
- const requestedOutputTokens = this.resolveMaxOutputTokens(effectiveModel, maxOutputTokensOverride);
3459
- // An invalid thinking budget is left for the provider path to reject.
3460
- const additionalOutputTokens = resolveAdditiveReasoningOutputTokens(languageModel, {
3461
- reasoning,
3462
- providerOptions,
3463
- }) ?? 0;
3464
- const fittedRequest = await fitRuntimeStepRequestToServedBudget({
3465
- sourceMessages: currentMessages,
3466
- modelMessages: toolResultContext
3467
- ? createModelToolResultContextMessages(currentMessages, toolResultContext)
3468
- : currentMessages,
3469
- plan: effectiveToolExposurePlan,
3470
- assembleRequest,
3471
- toolResultContext,
3472
- supportsToolCalling,
3473
- deferredToolLoading: runtimeStepToolLoading.mode === "deferred",
3474
- languageModel,
3475
- model: effectiveModel,
3476
- reasoning,
3477
- providerOptions,
3478
- responseFormat: outputSchema?.responseFormat,
3479
- requestedOutputTokens,
3480
- explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
3481
- additionalOutputTokens,
3482
- toolExposureState,
3483
- persistToolExposureCheckpoint,
3484
- requireToolExposureCheckpointPersistence,
3485
- turnAdmission: validateProviderRequest,
3486
- });
3487
- const modelMessages = fittedRequest.modelMessages;
3488
- effectiveToolExposurePlan = fittedRequest.plan;
3489
- if (this.#persistRecoveryBoundary) {
3490
- await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
3491
3438
  throwIfAborted(abortSignal);
3492
- }
3493
- const request = assembleRequest(modelMessages);
3494
- currentSystemPrompt = request.systemPrompt;
3495
- const { tools, runtimeTools, providerSystemPrompt, providerMessages } = request;
3496
- setSpanAttributes(loopSpan, {
3497
- "tool.loading.mode": runtimeStepToolLoading.mode,
3498
- "tool.loading.provenance": toolLoadingResolution.provenance,
3499
- "tool.catalog.authorized_count": preparedStep.toolExposurePlan.authorized.length,
3500
- "tool.catalog.visible_count": tools.length,
3501
- "tool.catalog.deferred_count": effectiveToolExposurePlan.deferred.length,
3502
- "tool.loading.path": "framework-fallback",
3503
- });
3504
- const temperature = this.resolveTemperature(temperatureModelString ?? effectiveModel, providerOptions);
3505
- const response = await withSpan("agent.generate_text", async (span) => {
3506
- setSpanAttributes(span, {
3507
- "model.id": effectiveModel,
3508
- "messages.count": currentMessages.length,
3439
+ currentSystemPrompt = preparedStep.systemPrompt;
3440
+ currentRuntimeContext = preparedStep.runtimeContext;
3441
+ const toolContext = preparedStep.toolContext;
3442
+ // Keep this snapshot fixed while calls from the same provider response execute.
3443
+ const providerObservedSkillBodies = getProviderObservedSkillBodies(currentMessages);
3444
+ let effectiveToolExposurePlan = agentWriteFinalResponseToolGuardEnabled
3445
+ ? applyAgentWriteFinalResponseGuard(preparedStep.toolExposurePlan, {
3446
+ reloadable: runtimeStepToolLoading.mode === "deferred",
3447
+ })
3448
+ : preparedStep.toolExposurePlan;
3449
+ if (!startupSkillLoaded) {
3450
+ await executeStartupSkill({
3451
+ agentId: this.id,
3452
+ skillId: this.config.bootstrapSkill,
3453
+ authorized: preparedStep.startupAuthorizedTools,
3454
+ messages: currentMessages,
3455
+ persistMessage,
3456
+ skillState,
3457
+ toolCalls,
3458
+ resolveLoader: (name) => resolveConfiguredTool(runtimeToolsConfig, name, { agentId: this.id }),
3459
+ execute: async (toolName, toolCallId, args, admittedTurn) => {
3460
+ const context = applicationExecutionContext(toolContext);
3461
+ const result = await traceConfiguredToolExecution({
3462
+ mode: "generate",
3463
+ agentId: this.id,
3464
+ toolName,
3465
+ toolCallId,
3466
+ args,
3467
+ admittedTurn,
3468
+ owner: currentMessages,
3469
+ prepareTerminalDispatch,
3470
+ toolsConfig: runtimeToolsConfig,
3471
+ context,
3472
+ allowedRemoteToolNames,
3473
+ remoteToolSources,
3474
+ sourceIntegrationPolicy,
3475
+ frameworkLocalTools,
3476
+ });
3477
+ await this.notifyToolResult({
3478
+ mode: "generate",
3479
+ toolName,
3480
+ toolCallId,
3481
+ input: args,
3482
+ result,
3483
+ context,
3484
+ });
3485
+ return result;
3486
+ },
3487
+ });
3488
+ startupSkillLoaded = true;
3489
+ // Refresh the active skill policy without consuming a model step.
3490
+ step--;
3491
+ continue;
3492
+ }
3493
+ assertToolResultReaderNameAvailable({
3494
+ plan: effectiveToolExposurePlan,
3495
+ context: toolResultContext,
3496
+ });
3497
+ const baseSystemPrompt = currentSystemPrompt;
3498
+ const assembleRequest = (messages, plan = effectiveToolExposurePlan) => assembleRuntimeStepRequest({
3499
+ messages,
3500
+ plan,
3501
+ toolResultContext,
3502
+ supportsToolCalling,
3503
+ agentWriteFinalResponseToolGuardEnabled,
3504
+ providerTools,
3505
+ model: effectiveModel,
3506
+ languageModel,
3507
+ toolSchemaOptions: resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
3508
+ baseSystemPrompt,
3509
+ integrationToolDiscovery: preparedStep.integrationToolDiscovery,
3510
+ runRuntimeContext,
3511
+ bootstrapToolNames: preparedStep.configuredBootstrapToolNames,
3509
3512
  });
3510
- // Reject an over-budget request before the turn is accepted, so a
3511
- // rejected input is rolled back instead of committed to memory.
3512
- const { maxOutputTokens } = enforceProviderRequestBudget({
3513
- model: languageModel,
3514
- ...(reasoning === undefined ? {} : { reasoning }),
3515
- ...(providerOptions === undefined ? {} : { providerOptions }),
3516
- system: providerSystemPrompt,
3517
- messages: providerMessages,
3518
- tools: runtimeTools,
3513
+ const requestedOutputTokens = this.resolveMaxOutputTokens(effectiveModel, maxOutputTokensOverride);
3514
+ // An invalid thinking budget is left for the provider path to reject.
3515
+ const additionalOutputTokens = resolveAdditiveReasoningOutputTokens(languageModel, {
3516
+ reasoning,
3517
+ providerOptions,
3518
+ }) ?? 0;
3519
+ const fittedRequest = await fitRuntimeStepRequestToServedBudget({
3520
+ sourceMessages: currentMessages,
3521
+ modelMessages: toolResultContext
3522
+ ? createModelToolResultContextMessages(currentMessages, toolResultContext)
3523
+ : currentMessages,
3524
+ plan: effectiveToolExposurePlan,
3525
+ assembleRequest,
3526
+ toolResultContext,
3527
+ supportsToolCalling,
3528
+ deferredToolLoading: runtimeStepToolLoading.mode === "deferred",
3529
+ languageModel,
3530
+ model: effectiveModel,
3531
+ reasoning,
3532
+ providerOptions,
3519
3533
  responseFormat: outputSchema?.responseFormat,
3520
- limit: fittedRequest.servedLimit,
3521
3534
  requestedOutputTokens,
3522
3535
  explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
3523
3536
  additionalOutputTokens,
3537
+ toolExposureState,
3538
+ persistToolExposureCheckpoint,
3539
+ requireToolExposureCheckpointPersistence,
3540
+ turnAdmission: validateProviderRequest,
3524
3541
  });
3525
- await validateProviderRequest(providerSystemPrompt, modelMessages, { beforeAcceptance: fittedRequest.commitToolExposure });
3526
- const result = await generateText({
3527
- model: languageModel,
3528
- system: providerSystemPrompt,
3529
- messages: providerMessages,
3530
- tools: runtimeTools,
3531
- experimental_repairToolCall: repairToolCall,
3532
- maxOutputTokens,
3533
- ...(temperature === undefined ? {} : { temperature }),
3534
- ...(this.config.parallelToolCalls === undefined
3535
- ? {}
3536
- : { parallelToolCalls: this.config.parallelToolCalls }),
3537
- ...(headers ? { headers } : {}),
3538
- ...(providerOptions ? { providerOptions } : {}),
3539
- ...(reasoning ? { reasoning } : {}),
3540
- ...(outputSchema ? { responseFormat: outputSchema.responseFormat } : {}),
3541
- abortSignal,
3542
- });
3543
- setSpanAttributes(span, buildRuntimeUsageTraceAttributes(result.usage));
3544
- return result;
3545
- });
3546
- throwIfAborted(abortSignal);
3547
- // Accumulate usage
3548
- if (response.usage) {
3549
- const input = response.usage.inputTokens ?? 0;
3550
- const output = response.usage.outputTokens ?? 0;
3551
- accumulateUsage(totalUsage, {
3552
- promptTokens: input,
3553
- completionTokens: output,
3554
- totalTokens: response.usage.totalTokens ?? input + output,
3555
- cachedInputTokens: response.usage.cachedInputTokens ??
3556
- response.usage.cacheReadInputTokens,
3557
- cacheCreationInputTokens: response.usage.cacheCreationInputTokens,
3558
- cacheCreation1hInputTokens: response.usage.cacheCreation1hInputTokens,
3559
- cacheReadInputTokens: response.usage.cacheReadInputTokens,
3560
- reasoningTokens: response.usage.reasoningTokens,
3561
- billableInputTokens: response.usage.billableInputTokens,
3562
- billableOutputTokens: response.usage.billableOutputTokens,
3563
- costUsd: response.usage.costUsd,
3564
- providerInputCostUsd: response.usage.providerInputCostUsd,
3565
- providerOutputCostUsd: response.usage.providerOutputCostUsd,
3566
- providerCostUsd: response.usage.providerCostUsd,
3567
- veryfrontInputChargeUsd: response.usage.veryfrontInputChargeUsd,
3568
- veryfrontOutputChargeUsd: response.usage.veryfrontOutputChargeUsd,
3569
- veryfrontChargeUsd: response.usage.veryfrontChargeUsd,
3570
- veryfrontBilledUsd: response.usage.veryfrontBilledUsd,
3571
- costCredits: response.usage.costCredits,
3572
- costSource: response.usage.costSource,
3573
- billingMode: response.usage.billingMode,
3574
- usageCaptureStatus: response.usage.usageCaptureStatus,
3542
+ const modelMessages = fittedRequest.modelMessages;
3543
+ effectiveToolExposurePlan = fittedRequest.plan;
3544
+ if (this.#persistRecoveryBoundary) {
3545
+ await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
3546
+ throwIfAborted(abortSignal);
3547
+ }
3548
+ const request = assembleRequest(modelMessages);
3549
+ currentSystemPrompt = request.systemPrompt;
3550
+ const { tools, runtimeTools, providerSystemPrompt, providerMessages } = request;
3551
+ setSpanAttributes(loopSpan, {
3552
+ "tool.loading.mode": runtimeStepToolLoading.mode,
3553
+ "tool.loading.provenance": toolLoadingResolution.provenance,
3554
+ "tool.catalog.authorized_count": preparedStep.toolExposurePlan.authorized.length,
3555
+ "tool.catalog.visible_count": tools.length,
3556
+ "tool.catalog.deferred_count": effectiveToolExposurePlan.deferred.length,
3557
+ "tool.loading.path": "framework-fallback",
3575
3558
  });
3576
- setSpanAttributes(loopSpan, buildRuntimeUsageTraceAttributes(totalUsage));
3577
- }
3578
- const generatedToolResults = collectGeneratedToolResults(response.toolResults);
3579
- const generatedInvokeAgentBatch = providerReplayCheckpointEmission.complete
3580
- ? collectGeneratedParallelInvokeAgentToolCalls(response.toolCalls, generatedToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, effectiveToolExposurePlan, {
3581
- activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
3582
- toolsConfig: runtimeToolsConfig,
3583
- agentId: this.id,
3584
- hasToolReplacements,
3585
- })
3586
- : undefined;
3587
- const generatedSkillDelegationOrder = generatedInvokeAgentBatch && !hasToolReplacements
3588
- ? generatedSameTurnSkillDelegationOrder(response.toolCalls, generatedToolResults, providerReplayCheckpointEmission.invokeAgentToolNames)
3589
- : undefined;
3590
- let generatedBatchCompletionDeferred = generatedSkillDelegationOrder === "prefix";
3591
- const assistantMessage = buildGeneratedAssistantMessage(response, {
3592
- id: `msg_${Date.now()}_${step}`,
3593
- timestamp: Date.now(),
3594
- });
3595
- const admittedTurn = snapshotAdmittedToolTurn(assistantMessage, currentMessages.length);
3596
- let generatedTurnAdmissionObserved = false;
3597
- let generatedTurnFinishObserved = false;
3598
- const observeGeneratedProviderTurnAdmissionOnce = async () => {
3599
- if (generatedTurnAdmissionObserved)
3600
- return;
3601
- await observeGeneratedAgentTurn(assistantMessage.id, {
3602
- ...response,
3603
- finishReason: undefined,
3604
- usage: undefined,
3559
+ const temperature = this.resolveTemperature(temperatureModelString ?? effectiveModel, providerOptions);
3560
+ const response = await withSpan("agent.generate_text", async (span) => {
3561
+ setSpanAttributes(span, {
3562
+ "model.id": effectiveModel,
3563
+ "messages.count": currentMessages.length,
3564
+ });
3565
+ // Reject an over-budget request before the turn is accepted, so a
3566
+ // rejected input is rolled back instead of committed to memory.
3567
+ const { maxOutputTokens } = enforceProviderRequestBudget({
3568
+ model: languageModel,
3569
+ ...(reasoning === undefined ? {} : { reasoning }),
3570
+ ...(providerOptions === undefined ? {} : { providerOptions }),
3571
+ system: providerSystemPrompt,
3572
+ messages: providerMessages,
3573
+ tools: runtimeTools,
3574
+ responseFormat: outputSchema?.responseFormat,
3575
+ limit: fittedRequest.servedLimit,
3576
+ requestedOutputTokens,
3577
+ explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
3578
+ additionalOutputTokens,
3579
+ });
3580
+ await validateProviderRequest(providerSystemPrompt, modelMessages, { beforeAcceptance: fittedRequest.commitToolExposure });
3581
+ const result = await generateText({
3582
+ model: languageModel,
3583
+ system: providerSystemPrompt,
3584
+ messages: providerMessages,
3585
+ tools: runtimeTools,
3586
+ experimental_repairToolCall: repairToolCall,
3587
+ maxOutputTokens,
3588
+ ...(temperature === undefined ? {} : { temperature }),
3589
+ ...(this.config.parallelToolCalls === undefined
3590
+ ? {}
3591
+ : { parallelToolCalls: this.config.parallelToolCalls }),
3592
+ ...(headers ? { headers } : {}),
3593
+ ...(providerOptions ? { providerOptions } : {}),
3594
+ ...(reasoning ? { reasoning } : {}),
3595
+ ...(outputSchema ? { responseFormat: outputSchema.responseFormat } : {}),
3596
+ abortSignal,
3597
+ });
3598
+ setSpanAttributes(span, buildRuntimeUsageTraceAttributes(result.usage));
3599
+ return result;
3605
3600
  });
3606
- generatedTurnAdmissionObserved = true;
3607
- };
3608
- const observeGeneratedProviderTurnFinishOnce = async (turn) => {
3609
- if (generatedTurnFinishObserved)
3610
- return;
3611
- if (!generatedTurnAdmissionObserved) {
3612
- await observeGeneratedAgentTurn(assistantMessage.id, withOutputSchemaObservationObject(turn));
3613
- generatedTurnAdmissionObserved = true;
3614
- }
3615
- else {
3616
- const structured = readStructuredAgentOutput(turn);
3617
- await observeGeneratedAgentTurnFinish({
3618
- finishReason: turn.finishReason,
3619
- usage: turn.usage,
3620
- ...(structured ? createOutputSchemaObservationFields(structured.data) : {}),
3601
+ throwIfAborted(abortSignal);
3602
+ // Accumulate usage
3603
+ if (response.usage) {
3604
+ const input = response.usage.inputTokens ?? 0;
3605
+ const output = response.usage.outputTokens ?? 0;
3606
+ accumulateUsage(totalUsage, {
3607
+ promptTokens: input,
3608
+ completionTokens: output,
3609
+ totalTokens: response.usage.totalTokens ?? input + output,
3610
+ cachedInputTokens: response.usage.cachedInputTokens ??
3611
+ response.usage.cacheReadInputTokens,
3612
+ cacheCreationInputTokens: response.usage.cacheCreationInputTokens,
3613
+ cacheCreation1hInputTokens: response.usage.cacheCreation1hInputTokens,
3614
+ cacheReadInputTokens: response.usage.cacheReadInputTokens,
3615
+ reasoningTokens: response.usage.reasoningTokens,
3616
+ billableInputTokens: response.usage.billableInputTokens,
3617
+ billableOutputTokens: response.usage.billableOutputTokens,
3618
+ costUsd: response.usage.costUsd,
3619
+ providerInputCostUsd: response.usage.providerInputCostUsd,
3620
+ providerOutputCostUsd: response.usage.providerOutputCostUsd,
3621
+ providerCostUsd: response.usage.providerCostUsd,
3622
+ veryfrontInputChargeUsd: response.usage.veryfrontInputChargeUsd,
3623
+ veryfrontOutputChargeUsd: response.usage.veryfrontOutputChargeUsd,
3624
+ veryfrontChargeUsd: response.usage.veryfrontChargeUsd,
3625
+ veryfrontBilledUsd: response.usage.veryfrontBilledUsd,
3626
+ costCredits: response.usage.costCredits,
3627
+ costSource: response.usage.costSource,
3628
+ billingMode: response.usage.billingMode,
3629
+ usageCaptureStatus: response.usage.usageCaptureStatus,
3621
3630
  });
3631
+ setSpanAttributes(loopSpan, buildRuntimeUsageTraceAttributes(totalUsage));
3622
3632
  }
3623
- generatedTurnFinishObserved = true;
3624
- };
3625
- let generatedProviderReplayCheckpointPersisted = false;
3626
- const persistGeneratedProviderReplayCheckpoint = async () => {
3627
- if (generatedProviderReplayCheckpointPersisted)
3628
- return;
3629
- await persistProviderReplayCheckpointAfterTurn({
3630
- emission: providerReplayCheckpointEmission,
3631
- providerMetadata: readAttachedProviderMetadata(assistantMessage),
3632
- invokeAgentToolCalls: generatedSkillDelegationOrder === "interleaved"
3633
- ? undefined
3634
- : generatedInvokeAgentBatch,
3635
- deferCompletion: generatedBatchCompletionDeferred,
3636
- });
3637
- generatedProviderReplayCheckpointPersisted = true;
3638
- };
3639
- pushPrivateArray(currentMessages, assistantMessage);
3640
- try {
3641
- await persistMessage(assistantMessage);
3642
- }
3643
- catch (error) {
3644
- await observeGeneratedProviderTurnFinishOnce(response);
3645
- throw error;
3646
- }
3647
- const persistGeneratedToolResult = async (generatedToolResult) => {
3648
- const executionAuthority = resolveToolExecutionAuthority({
3649
- toolName: generatedToolResult.toolName,
3650
- plan: effectiveToolExposurePlan,
3633
+ const generatedToolResults = collectGeneratedToolResults(response.toolResults);
3634
+ const generatedInvokeAgentBatch = providerReplayCheckpointEmission.complete
3635
+ ? collectGeneratedParallelInvokeAgentToolCalls(response.toolCalls, generatedToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, effectiveToolExposurePlan, {
3636
+ activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
3637
+ toolsConfig: runtimeToolsConfig,
3638
+ agentId: this.id,
3639
+ hasToolReplacements,
3640
+ })
3641
+ : undefined;
3642
+ const generatedSkillDelegationOrder = generatedInvokeAgentBatch && !hasToolReplacements
3643
+ ? generatedSameTurnSkillDelegationOrder(response.toolCalls, generatedToolResults, providerReplayCheckpointEmission.invokeAgentToolNames)
3644
+ : undefined;
3645
+ let generatedBatchCompletionDeferred = generatedSkillDelegationOrder === "prefix";
3646
+ const assistantMessage = buildGeneratedAssistantMessage(response, {
3647
+ id: `msg_${Date.now()}_${step}`,
3648
+ timestamp: Date.now(),
3651
3649
  });
3652
- const toolResultMessage = createPolicyAwareToolResultMessage(generatedToolResult.toolCallId, generatedToolResult.toolName, generatedToolResult.isError === true
3653
- ? { error: stringifyToolError(generatedToolResult.result) }
3654
- : generatedToolResult.result, executionAuthority?.toolDefinition, generatedToolResult.providerExecuted === true);
3655
- pushPrivateArray(currentMessages, toolResultMessage);
3656
- await persistMessage(toolResultMessage);
3657
- throwIfAborted(abortSignal);
3658
- };
3659
- const rejectUnpairedRequestScopedGeneratedToolResult = async (generatedToolResult) => {
3660
- if (!hasToolReplacements) {
3661
- return false;
3662
- }
3663
- const error = `Tool "${generatedToolResult.toolName}" is not available in request-scoped replacement tools`;
3664
- const toolCall = {
3665
- id: generatedToolResult.toolCallId,
3666
- name: generatedToolResult.toolName,
3667
- args: {},
3668
- status: "error",
3669
- error,
3650
+ const admittedTurn = snapshotAdmittedToolTurn(assistantMessage, currentMessages.length);
3651
+ let generatedTurnAdmissionObserved = false;
3652
+ let generatedTurnFinishObserved = false;
3653
+ const observeGeneratedProviderTurnAdmissionOnce = async () => {
3654
+ if (generatedTurnAdmissionObserved)
3655
+ return;
3656
+ await observeGeneratedAgentTurn(assistantMessage.id, {
3657
+ ...response,
3658
+ finishReason: undefined,
3659
+ usage: undefined,
3660
+ });
3661
+ generatedTurnAdmissionObserved = true;
3670
3662
  };
3671
- pushPrivateArray(toolCalls, toolCall);
3672
- const errorMessage = createToolErrorMessage(generatedToolResult.toolCallId, generatedToolResult.toolName, error);
3673
- pushPrivateArray(currentMessages, errorMessage);
3674
- await persistMessage(errorMessage);
3675
- return true;
3676
- };
3677
- if (!response.toolCalls?.length) {
3678
- try {
3679
- for (const generatedToolResult of generatedToolResults.values()) {
3680
- if (await rejectUnpairedRequestScopedGeneratedToolResult(generatedToolResult)) {
3681
- continue;
3682
- }
3683
- await persistGeneratedToolResult(generatedToolResult);
3663
+ const observeGeneratedProviderTurnFinishOnce = async (turn) => {
3664
+ if (generatedTurnFinishObserved)
3665
+ return;
3666
+ if (!generatedTurnAdmissionObserved) {
3667
+ await observeGeneratedAgentTurn(assistantMessage.id, withOutputSchemaObservationObject(turn));
3668
+ generatedTurnAdmissionObserved = true;
3684
3669
  }
3670
+ else {
3671
+ const structured = readStructuredAgentOutput(turn);
3672
+ await observeGeneratedAgentTurnFinish({
3673
+ finishReason: turn.finishReason,
3674
+ usage: turn.usage,
3675
+ ...(structured ? createOutputSchemaObservationFields(structured.data) : {}),
3676
+ });
3677
+ }
3678
+ generatedTurnFinishObserved = true;
3679
+ };
3680
+ let generatedProviderReplayCheckpointPersisted = false;
3681
+ const persistGeneratedProviderReplayCheckpoint = async () => {
3682
+ if (generatedProviderReplayCheckpointPersisted)
3683
+ return;
3684
+ await persistProviderReplayCheckpointAfterTurn({
3685
+ emission: providerReplayCheckpointEmission,
3686
+ providerMetadata: readAttachedProviderMetadata(assistantMessage),
3687
+ invokeAgentToolCalls: generatedSkillDelegationOrder === "interleaved"
3688
+ ? undefined
3689
+ : generatedInvokeAgentBatch,
3690
+ deferCompletion: generatedBatchCompletionDeferred,
3691
+ });
3692
+ generatedProviderReplayCheckpointPersisted = true;
3693
+ };
3694
+ pushPrivateArray(currentMessages, assistantMessage);
3695
+ try {
3696
+ await persistMessage(assistantMessage);
3685
3697
  }
3686
3698
  catch (error) {
3687
3699
  await observeGeneratedProviderTurnFinishOnce(response);
3688
- await persistGeneratedProviderReplayCheckpoint();
3689
3700
  throw error;
3690
3701
  }
3691
- const stoppedEmptyAfterCompletedTool = response.finishReason === "stop" &&
3692
- !hasSubstantiveAssistantText(response.text) &&
3693
- generatedToolResults.size === 0 &&
3694
- somePrivateArray(toolCalls, (toolCall) => toolCall.status === "completed");
3695
- if (stoppedEmptyAfterCompletedTool) {
3696
- await observeGeneratedProviderTurnFinishOnce(response);
3697
- await persistGeneratedProviderReplayCheckpoint();
3702
+ const persistGeneratedToolResult = async (generatedToolResult) => {
3703
+ const executionAuthority = resolveToolExecutionAuthority({
3704
+ toolName: generatedToolResult.toolName,
3705
+ plan: effectiveToolExposurePlan,
3706
+ });
3707
+ const toolResultMessage = createPolicyAwareToolResultMessage(generatedToolResult.toolCallId, generatedToolResult.toolName, generatedToolResult.isError === true
3708
+ ? { error: stringifyToolError(generatedToolResult.result) }
3709
+ : generatedToolResult.result, executionAuthority?.toolDefinition, generatedToolResult.providerExecuted === true);
3710
+ pushPrivateArray(currentMessages, toolResultMessage);
3711
+ await persistMessage(toolResultMessage);
3698
3712
  throwIfAborted(abortSignal);
3699
- if (recoveredEmptyResponse || step + 1 >= maxSteps) {
3700
- throw new RuntimeEmptyResponseError();
3713
+ };
3714
+ const rejectUnpairedRequestScopedGeneratedToolResult = async (generatedToolResult) => {
3715
+ if (!hasToolReplacements) {
3716
+ return false;
3701
3717
  }
3702
- recoveredEmptyResponse = true;
3703
- pushPrivateArray(currentMessages, markRuntimeGeneratedUserMessage({
3704
- id: `runtime_empty_response_${Date.now()}_${step}`,
3705
- role: "user",
3706
- parts: [{
3707
- type: "text",
3708
- text: EMPTY_RESPONSE_RECOVERY_PROMPT,
3709
- }],
3710
- timestamp: Date.now(),
3711
- }));
3712
- continue;
3713
- }
3714
- this.status = "completed";
3715
- addSpanEvent(loopSpan, "loop_complete");
3716
- setSpanAttributes(loopSpan, buildRuntimeUsageTraceAttributes(totalUsage));
3717
- let parsedObject;
3718
- if (outputSchema) {
3718
+ const error = `Tool "${generatedToolResult.toolName}" is not available in request-scoped replacement tools`;
3719
+ const toolCall = {
3720
+ id: generatedToolResult.toolCallId,
3721
+ name: generatedToolResult.toolName,
3722
+ args: {},
3723
+ status: "error",
3724
+ error,
3725
+ };
3726
+ pushPrivateArray(toolCalls, toolCall);
3727
+ const errorMessage = createToolErrorMessage(generatedToolResult.toolCallId, generatedToolResult.toolName, error);
3728
+ pushPrivateArray(currentMessages, errorMessage);
3729
+ await persistMessage(errorMessage);
3730
+ return true;
3731
+ };
3732
+ if (!response.toolCalls?.length) {
3719
3733
  try {
3720
- parsedObject = await outputSchema.parseOutput(response.text);
3734
+ for (const generatedToolResult of generatedToolResults.values()) {
3735
+ if (await rejectUnpairedRequestScopedGeneratedToolResult(generatedToolResult)) {
3736
+ continue;
3737
+ }
3738
+ await persistGeneratedToolResult(generatedToolResult);
3739
+ }
3721
3740
  }
3722
3741
  catch (error) {
3723
- await observeGeneratedAgentOutputSchemaRejection(assistantMessage.id, response);
3742
+ await observeGeneratedProviderTurnFinishOnce(response);
3724
3743
  await persistGeneratedProviderReplayCheckpoint();
3725
3744
  throw error;
3726
3745
  }
3727
- }
3728
- await observeGeneratedProviderTurnFinishOnce({
3729
- ...response,
3730
- ...(outputSchema ? { data: parsedObject, object: parsedObject } : {}),
3731
- });
3732
- await persistGeneratedProviderReplayCheckpoint();
3733
- throwIfAborted(abortSignal);
3734
- return attachOutputSchemaParser({
3735
- text: response.text,
3736
- ...(outputSchema ? { data: parsedObject, object: parsedObject } : {}),
3737
- messages: currentMessages,
3738
- toolCalls,
3739
- status: this.status,
3740
- usage: totalUsage,
3741
- metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, response.finishReason ? { finishReason: response.finishReason } : undefined),
3742
- }, outputSchema);
3743
- }
3744
- const isFinalMaxStepsToolTurn = step + 1 >= maxSteps;
3745
- try {
3746
- if (isFinalMaxStepsToolTurn) {
3747
- await observeGeneratedProviderTurnAdmissionOnce();
3748
- }
3749
- else {
3750
- await observeGeneratedProviderTurnFinishOnce(response);
3751
- }
3752
- await persistGeneratedProviderReplayCheckpoint();
3753
- throwIfAborted(abortSignal);
3754
- this.status = "tool_execution";
3755
- addSpanEvent(loopSpan, "tool_execution_start", { count: response.toolCalls.length });
3756
- for (let toolCallIndex = 0; toolCallIndex < response.toolCalls.length; toolCallIndex++) {
3757
- if (!ObjectHasOwn(response.toolCalls, toolCallIndex))
3746
+ const stoppedEmptyAfterCompletedTool = response.finishReason === "stop" &&
3747
+ !hasSubstantiveAssistantText(response.text) &&
3748
+ generatedToolResults.size === 0 &&
3749
+ somePrivateArray(toolCalls, (toolCall) => toolCall.status === "completed");
3750
+ if (stoppedEmptyAfterCompletedTool) {
3751
+ await observeGeneratedProviderTurnFinishOnce(response);
3752
+ await persistGeneratedProviderReplayCheckpoint();
3753
+ throwIfAborted(abortSignal);
3754
+ if (recoveredEmptyResponse || step + 1 >= maxSteps) {
3755
+ throw new RuntimeEmptyResponseError();
3756
+ }
3757
+ recoveredEmptyResponse = true;
3758
+ pushPrivateArray(currentMessages, markRuntimeGeneratedUserMessage({
3759
+ id: `runtime_empty_response_${Date.now()}_${step}`,
3760
+ role: "user",
3761
+ parts: [{
3762
+ type: "text",
3763
+ text: EMPTY_RESPONSE_RECOVERY_PROMPT,
3764
+ }],
3765
+ timestamp: Date.now(),
3766
+ }));
3758
3767
  continue;
3759
- const tc = response.toolCalls[toolCallIndex];
3768
+ }
3769
+ this.status = "completed";
3770
+ addSpanEvent(loopSpan, "loop_complete");
3771
+ setSpanAttributes(loopSpan, buildRuntimeUsageTraceAttributes(totalUsage));
3772
+ let parsedObject;
3773
+ if (outputSchema) {
3774
+ try {
3775
+ parsedObject = await outputSchema.parseOutput(response.text);
3776
+ }
3777
+ catch (error) {
3778
+ await observeGeneratedAgentOutputSchemaRejection(assistantMessage.id, response);
3779
+ await persistGeneratedProviderReplayCheckpoint();
3780
+ throw error;
3781
+ }
3782
+ }
3783
+ await observeGeneratedProviderTurnFinishOnce({
3784
+ ...response,
3785
+ ...(outputSchema ? { data: parsedObject, object: parsedObject } : {}),
3786
+ });
3787
+ await persistGeneratedProviderReplayCheckpoint();
3760
3788
  throwIfAborted(abortSignal);
3761
- const toolCall = {
3762
- id: tc.toolCallId,
3763
- name: tc.toolName,
3764
- args: tc.input,
3765
- status: "pending",
3766
- };
3767
- const generatedToolResult = generatedToolResults.get(tc.toolCallId);
3768
- if (generatedBatchCompletionDeferred && generatedToolResult === undefined &&
3769
- IntrinsicReflectApply(IntrinsicSetHas, providerReplayCheckpointEmission.invokeAgentToolNames, [tc.toolName])) {
3770
- await completeDeferredProviderReplayCheckpointTurn(providerReplayCheckpointEmission, collectGeneratedParallelInvokeAgentToolCalls(response.toolCalls, generatedToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, effectiveToolExposurePlan, {
3771
- activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
3772
- toolsConfig: runtimeToolsConfig,
3773
- agentId: this.id,
3774
- hasToolReplacements,
3775
- }));
3776
- generatedBatchCompletionDeferred = false;
3789
+ return attachOutputSchemaParser({
3790
+ text: response.text,
3791
+ ...(outputSchema ? { data: parsedObject, object: parsedObject } : {}),
3792
+ messages: currentMessages,
3793
+ toolCalls,
3794
+ status: this.status,
3795
+ usage: totalUsage,
3796
+ metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, response.finishReason ? { finishReason: response.finishReason } : undefined),
3797
+ }, outputSchema);
3798
+ }
3799
+ const isFinalMaxStepsToolTurn = step + 1 >= maxSteps;
3800
+ try {
3801
+ if (isFinalMaxStepsToolTurn) {
3802
+ await observeGeneratedProviderTurnAdmissionOnce();
3777
3803
  }
3778
- await withSpan("agent.tool_execute", async (toolSpan) => {
3779
- const inputSizeBytes = estimateSerializedSizeBytes(tc.input);
3780
- setSpanAttributes(toolSpan, compactRuntimeTraceAttributes({
3781
- "tool.name": tc.toolName,
3782
- "tool.call.id": tc.toolCallId,
3783
- "tool.id": tc.toolCallId,
3784
- "tool.status": "executing",
3785
- "tool.input.size_bytes": inputSizeBytes,
3786
- "gen_ai.operation.name": "execute_tool",
3787
- "gen_ai.tool.name": tc.toolName,
3788
- "gen_ai.tool.type": "function",
3789
- "gen_ai.tool.call.id": tc.toolCallId,
3790
- }));
3791
- const executionAuthority = resolveToolExecutionAuthority({
3792
- toolName: tc.toolName,
3793
- plan: effectiveToolExposurePlan,
3794
- });
3795
- if (generatedToolResult === undefined &&
3796
- shouldHandleToolResultRead({
3804
+ else {
3805
+ await observeGeneratedProviderTurnFinishOnce(response);
3806
+ }
3807
+ await persistGeneratedProviderReplayCheckpoint();
3808
+ throwIfAborted(abortSignal);
3809
+ this.status = "tool_execution";
3810
+ addSpanEvent(loopSpan, "tool_execution_start", { count: response.toolCalls.length });
3811
+ for (let toolCallIndex = 0; toolCallIndex < response.toolCalls.length; toolCallIndex++) {
3812
+ if (!ObjectHasOwn(response.toolCalls, toolCallIndex))
3813
+ continue;
3814
+ const tc = response.toolCalls[toolCallIndex];
3815
+ throwIfAborted(abortSignal);
3816
+ const toolCall = {
3817
+ id: tc.toolCallId,
3818
+ name: tc.toolName,
3819
+ args: tc.input,
3820
+ status: "pending",
3821
+ };
3822
+ const generatedToolResult = generatedToolResults.get(tc.toolCallId);
3823
+ if (generatedBatchCompletionDeferred && generatedToolResult === undefined &&
3824
+ IntrinsicReflectApply(IntrinsicSetHas, providerReplayCheckpointEmission.invokeAgentToolNames, [tc.toolName])) {
3825
+ await completeDeferredProviderReplayCheckpointTurn(providerReplayCheckpointEmission, collectGeneratedParallelInvokeAgentToolCalls(response.toolCalls, generatedToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, effectiveToolExposurePlan, {
3826
+ activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
3827
+ toolsConfig: runtimeToolsConfig,
3828
+ agentId: this.id,
3829
+ hasToolReplacements,
3830
+ }));
3831
+ generatedBatchCompletionDeferred = false;
3832
+ }
3833
+ await withSpan("agent.tool_execute", async (toolSpan) => {
3834
+ const inputSizeBytes = estimateSerializedSizeBytes(tc.input);
3835
+ setSpanAttributes(toolSpan, compactRuntimeTraceAttributes({
3836
+ "tool.name": tc.toolName,
3837
+ "tool.call.id": tc.toolCallId,
3838
+ "tool.id": tc.toolCallId,
3839
+ "tool.status": "executing",
3840
+ "tool.input.size_bytes": inputSizeBytes,
3841
+ "gen_ai.operation.name": "execute_tool",
3842
+ "gen_ai.tool.name": tc.toolName,
3843
+ "gen_ai.tool.type": "function",
3844
+ "gen_ai.tool.call.id": tc.toolCallId,
3845
+ }));
3846
+ const executionAuthority = resolveToolExecutionAuthority({
3797
3847
  toolName: tc.toolName,
3798
3848
  plan: effectiveToolExposurePlan,
3799
- context: toolResultContext,
3800
- })) {
3801
- try {
3802
- if (toolResultContext === undefined) {
3803
- throw new ReferenceError("Tool result context is not available");
3849
+ });
3850
+ if (generatedToolResult === undefined &&
3851
+ shouldHandleToolResultRead({
3852
+ toolName: tc.toolName,
3853
+ plan: effectiveToolExposurePlan,
3854
+ context: toolResultContext,
3855
+ })) {
3856
+ try {
3857
+ if (toolResultContext === undefined) {
3858
+ throw new ReferenceError("Tool result context is not available");
3859
+ }
3860
+ const result = readToolResultContext(toolResultContext, toolCall.args);
3861
+ toolCall.status = "completed";
3862
+ toolCall.result = result;
3863
+ setSpanAttributes(toolSpan, {
3864
+ "tool.status": "completed",
3865
+ "tool.output.size_bytes": estimateSerializedSizeBytes(result),
3866
+ });
3867
+ const toolResultMessage = createToolResultMessage(tc.toolCallId, tc.toolName, result);
3868
+ pushPrivateArray(currentMessages, toolResultMessage);
3869
+ await persistMessage(toolResultMessage);
3804
3870
  }
3805
- const result = readToolResultContext(toolResultContext, toolCall.args);
3806
- toolCall.status = "completed";
3807
- toolCall.result = result;
3808
- setSpanAttributes(toolSpan, {
3809
- "tool.status": "completed",
3810
- "tool.output.size_bytes": estimateSerializedSizeBytes(result),
3811
- });
3812
- const toolResultMessage = createToolResultMessage(tc.toolCallId, tc.toolName, result);
3813
- pushPrivateArray(currentMessages, toolResultMessage);
3814
- await persistMessage(toolResultMessage);
3871
+ catch (error) {
3872
+ toolCall.status = "error";
3873
+ toolCall.error = error instanceof Error ? error.message : String(error);
3874
+ const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
3875
+ pushPrivateArray(currentMessages, errorMessage);
3876
+ await persistMessage(errorMessage);
3877
+ }
3878
+ pushPrivateArray(toolCalls, toolCall);
3879
+ return;
3815
3880
  }
3816
- catch (error) {
3881
+ if (generatedToolResult === undefined &&
3882
+ shouldBlockToolResultReadName({
3883
+ toolName: tc.toolName,
3884
+ context: toolResultContext,
3885
+ })) {
3817
3886
  toolCall.status = "error";
3818
- toolCall.error = error instanceof Error ? error.message : String(error);
3887
+ toolCall.error = toolResultReaderUnavailableError();
3819
3888
  const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
3820
3889
  pushPrivateArray(currentMessages, errorMessage);
3821
3890
  await persistMessage(errorMessage);
3891
+ pushPrivateArray(toolCalls, toolCall);
3892
+ return;
3822
3893
  }
3823
- pushPrivateArray(toolCalls, toolCall);
3824
- return;
3825
- }
3826
- if (generatedToolResult === undefined &&
3827
- shouldBlockToolResultReadName({
3828
- toolName: tc.toolName,
3829
- context: toolResultContext,
3830
- })) {
3831
- toolCall.status = "error";
3832
- toolCall.error = toolResultReaderUnavailableError();
3833
- const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
3834
- pushPrivateArray(currentMessages, errorMessage);
3835
- await persistMessage(errorMessage);
3836
- pushPrivateArray(toolCalls, toolCall);
3837
- return;
3838
- }
3839
- if (!hasToolReplacements &&
3840
- generatedToolResult === undefined &&
3841
- executionAuthority === undefined) {
3842
- toolCall.status = "error";
3843
- toolCall.error = toolNotVisibleError(tc.toolName);
3844
- setSpanAttributes(toolSpan, {
3845
- "tool.status": "blocked",
3846
- error: true,
3847
- "error.type": "ToolExposureBlocked",
3848
- });
3849
- const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
3850
- pushPrivateArray(currentMessages, errorMessage);
3851
- await persistMessage(errorMessage);
3852
- pushPrivateArray(toolCalls, toolCall);
3853
- return;
3854
- }
3855
- if (generatedToolResult === undefined &&
3856
- isFrameworkToolSearch(tc.toolName, effectiveToolExposurePlan)) {
3857
- let checkpoint;
3858
- try {
3859
- const search = executeFrameworkToolSearch({
3860
- args: toolCall.args,
3861
- plan: effectiveToolExposurePlan,
3862
- state: toolExposureState,
3894
+ if (!hasToolReplacements &&
3895
+ generatedToolResult === undefined &&
3896
+ executionAuthority === undefined) {
3897
+ toolCall.status = "error";
3898
+ toolCall.error = toolNotVisibleError(tc.toolName);
3899
+ setSpanAttributes(toolSpan, {
3900
+ "tool.status": "blocked",
3901
+ error: true,
3902
+ "error.type": "ToolExposureBlocked",
3863
3903
  });
3864
- if (didReloadProjectAgentWriteTool(search.result)) {
3865
- agentWriteFinalResponseToolGuardEnabled = false;
3904
+ const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
3905
+ pushPrivateArray(currentMessages, errorMessage);
3906
+ await persistMessage(errorMessage);
3907
+ pushPrivateArray(toolCalls, toolCall);
3908
+ return;
3909
+ }
3910
+ if (generatedToolResult === undefined &&
3911
+ isFrameworkToolSearch(tc.toolName, effectiveToolExposurePlan)) {
3912
+ let checkpoint;
3913
+ try {
3914
+ const search = executeFrameworkToolSearch({
3915
+ args: toolCall.args,
3916
+ plan: effectiveToolExposurePlan,
3917
+ state: toolExposureState,
3918
+ });
3919
+ if (didReloadProjectAgentWriteTool(search.result)) {
3920
+ agentWriteFinalResponseToolGuardEnabled = false;
3921
+ }
3922
+ toolCall.status = "completed";
3923
+ toolCall.result = search.result;
3924
+ setSpanAttributes(toolSpan, {
3925
+ "tool.status": "completed",
3926
+ "tool.search.result_count": search.result.resultCount,
3927
+ "tool.search.loaded_count": search.result.loadedCount,
3928
+ "tool.search.miss": search.result.miss,
3929
+ });
3930
+ const toolResultMessage = createToolResultMessage(tc.toolCallId, tc.toolName, search.result);
3931
+ pushPrivateArray(currentMessages, toolResultMessage);
3932
+ await persistMessage(toolResultMessage);
3933
+ checkpoint = search.checkpoint;
3866
3934
  }
3867
- toolCall.status = "completed";
3868
- toolCall.result = search.result;
3869
- setSpanAttributes(toolSpan, {
3870
- "tool.status": "completed",
3871
- "tool.search.result_count": search.result.resultCount,
3872
- "tool.search.loaded_count": search.result.loadedCount,
3873
- "tool.search.miss": search.result.miss,
3935
+ catch (error) {
3936
+ toolCall.status = "error";
3937
+ toolCall.error = error instanceof Error ? error.message : String(error);
3938
+ const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
3939
+ pushPrivateArray(currentMessages, errorMessage);
3940
+ await persistMessage(errorMessage);
3941
+ pushPrivateArray(toolCalls, toolCall);
3942
+ return;
3943
+ }
3944
+ await persistToolExposureCheckpointBeforeContinuation({
3945
+ checkpoint,
3946
+ persist: persistToolExposureCheckpoint,
3947
+ required: requireToolExposureCheckpointPersistence,
3874
3948
  });
3875
- const toolResultMessage = createToolResultMessage(tc.toolCallId, tc.toolName, search.result);
3876
- pushPrivateArray(currentMessages, toolResultMessage);
3877
- await persistMessage(toolResultMessage);
3878
- checkpoint = search.checkpoint;
3949
+ pushPrivateArray(toolCalls, toolCall);
3950
+ return;
3879
3951
  }
3880
- catch (error) {
3952
+ // Provider-executed tools (web_search/web_fetch) return results without skill-state
3953
+ // transitions. Unlike locally-executed paths, load_skill and form_input are client-side
3954
+ // function tools that the runtime executes itself, so they never appear in
3955
+ // response.toolResults. This branch mirrors the streaming loop's providerExecuted===true
3956
+ // path, not the locally-executed ones. If provider-executed tools expand beyond web_*,
3957
+ // the transitions (skillState.applySuccessfulResult, markFormInputSubmitted) would apply.
3958
+ if (generatedToolResult && !hasToolReplacements) {
3959
+ if (generatedToolResult.providerExecuted === true) {
3960
+ await traceProviderExecutedTool({
3961
+ mode: "generate",
3962
+ agentId: this.id,
3963
+ toolName: tc.toolName,
3964
+ toolCallId: tc.toolCallId,
3965
+ context: {
3966
+ toolCallId: tc.toolCallId,
3967
+ ...toolContext,
3968
+ agentId: this.id,
3969
+ },
3970
+ args: tc.input,
3971
+ result: generatedToolResult.result,
3972
+ isError: generatedToolResult.isError === true,
3973
+ });
3974
+ }
3975
+ await persistGeneratedToolResult(generatedToolResult);
3976
+ toolCall.status = generatedToolResult.isError === true ? "error" : "completed";
3977
+ toolCall.result = generatedToolResult.result;
3978
+ toolCall.error = generatedToolResult.isError === true
3979
+ ? stringifyToolError(generatedToolResult.result)
3980
+ : undefined;
3981
+ if (toolCall.error !== undefined) {
3982
+ setOtelActiveSpanErrorStatus(new NativeError(`Tool "${tc.toolName}" failed`));
3983
+ }
3984
+ if (generatedToolResult.isError !== true &&
3985
+ shouldHideProjectToolAfterAgentWriteSuccess(tc.toolName)) {
3986
+ agentWriteFinalResponseToolGuardEnabled = true;
3987
+ }
3988
+ setSpanAttributes(toolSpan, compactRuntimeTraceAttributes({
3989
+ "tool.status": generatedToolResult.isError === true ? "failed" : "completed",
3990
+ "tool.provider_executed": generatedToolResult.providerExecuted === true,
3991
+ "tool.output.size_bytes": estimateSerializedSizeBytes(generatedToolResult.result),
3992
+ ...(toolCall.error
3993
+ ? {
3994
+ error: true,
3995
+ "error.type": "ProviderExecutedToolError",
3996
+ }
3997
+ : {}),
3998
+ }));
3999
+ pushPrivateArray(toolCalls, toolCall);
4000
+ return;
4001
+ }
4002
+ const policyCheck = enforceSkillPolicy(tc.toolName, {
4003
+ activeSkillId: skillState.activeSkillId,
4004
+ hasSubmittedFormInput: skillState.hasSubmittedFormInput,
4005
+ skillToolAvailability: skillState.activeSkillToolAvailability,
4006
+ toolInput: tc.input,
4007
+ toolDefinition: executionAuthority?.toolDefinition,
4008
+ });
4009
+ if (!policyCheck.allowed) {
3881
4010
  toolCall.status = "error";
3882
- toolCall.error = error instanceof Error ? error.message : String(error);
3883
- const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
4011
+ toolCall.error = policyCheck.error;
4012
+ setSpanAttributes(toolSpan, {
4013
+ "tool.status": "blocked",
4014
+ error: true,
4015
+ "error.type": "ToolPolicyBlocked",
4016
+ });
4017
+ const errorMessage = {
4018
+ id: `tool_error_${tc.toolCallId}`,
4019
+ role: "tool",
4020
+ parts: [{
4021
+ type: "tool-result",
4022
+ toolCallId: tc.toolCallId,
4023
+ toolName: tc.toolName,
4024
+ result: { error: policyCheck.error },
4025
+ }],
4026
+ timestamp: Date.now(),
4027
+ };
3884
4028
  pushPrivateArray(currentMessages, errorMessage);
3885
4029
  await persistMessage(errorMessage);
3886
4030
  pushPrivateArray(toolCalls, toolCall);
3887
4031
  return;
3888
4032
  }
3889
- await persistToolExposureCheckpointBeforeContinuation({
3890
- checkpoint,
3891
- persist: persistToolExposureCheckpoint,
3892
- required: requireToolExposureCheckpointPersistence,
3893
- });
3894
- pushPrivateArray(toolCalls, toolCall);
3895
- return;
3896
- }
3897
- // Provider-executed tools (web_search/web_fetch) return results without skill-state
3898
- // transitions. Unlike locally-executed paths, load_skill and form_input are client-side
3899
- // function tools that the runtime executes itself, so they never appear in
3900
- // response.toolResults. This branch mirrors the streaming loop's providerExecuted===true
3901
- // path, not the locally-executed ones. If provider-executed tools expand beyond web_*,
3902
- // the transitions (skillState.applySuccessfulResult, markFormInputSubmitted) would apply.
3903
- if (generatedToolResult && !hasToolReplacements) {
3904
- if (generatedToolResult.providerExecuted === true) {
3905
- await traceProviderExecutedTool({
4033
+ try {
4034
+ toolCall.status = "executing";
4035
+ const startTime = Date.now();
4036
+ const cacheCtx = tryGetCacheKeyContext();
4037
+ toolCall.args = applySkillDelegationOverridesToToolInput(tc.toolName, toolCall.args, hasToolReplacements ? undefined : skillState.activeSkillDelegationOverrides, hasToolReplacements
4038
+ ? undefined
4039
+ : resolveConfiguredTool(runtimeToolsConfig, tc.toolName, {
4040
+ agentId: this.id,
4041
+ }) ??
4042
+ undefined);
4043
+ const executionContext = applicationExecutionContext(toolContext, providerObservedSkillBodies);
4044
+ executionContext.projectId = cacheCtx?.projectId ?? toolContext?.projectId;
4045
+ throwIfAborted(abortSignal);
4046
+ const result = await traceConfiguredToolExecution({
3906
4047
  mode: "generate",
3907
4048
  agentId: this.id,
3908
4049
  toolName: tc.toolName,
3909
4050
  toolCallId: tc.toolCallId,
3910
- context: {
3911
- toolCallId: tc.toolCallId,
3912
- ...toolContext,
3913
- agentId: this.id,
3914
- },
3915
- args: tc.input,
3916
- result: generatedToolResult.result,
3917
- isError: generatedToolResult.isError === true,
4051
+ args: toolCall.args,
4052
+ admittedTurn,
4053
+ owner: currentMessages,
4054
+ prepareTerminalDispatch,
4055
+ toolsConfig: runtimeToolsConfig,
4056
+ context: executionContext,
4057
+ allowedRemoteToolNames,
4058
+ remoteToolSources,
4059
+ sourceIntegrationPolicy,
4060
+ strictConfiguredToolsOnly: hasToolReplacements,
4061
+ frameworkLocalTools,
3918
4062
  });
3919
- }
3920
- await persistGeneratedToolResult(generatedToolResult);
3921
- toolCall.status = generatedToolResult.isError === true ? "error" : "completed";
3922
- toolCall.result = generatedToolResult.result;
3923
- toolCall.error = generatedToolResult.isError === true
3924
- ? stringifyToolError(generatedToolResult.result)
3925
- : undefined;
3926
- if (toolCall.error !== undefined) {
3927
- setOtelActiveSpanErrorStatus(new NativeError(`Tool "${tc.toolName}" failed`));
3928
- }
3929
- if (generatedToolResult.isError !== true &&
3930
- shouldHideProjectToolAfterAgentWriteSuccess(tc.toolName)) {
3931
- agentWriteFinalResponseToolGuardEnabled = true;
3932
- }
3933
- setSpanAttributes(toolSpan, compactRuntimeTraceAttributes({
3934
- "tool.status": generatedToolResult.isError === true ? "failed" : "completed",
3935
- "tool.provider_executed": generatedToolResult.providerExecuted === true,
3936
- "tool.output.size_bytes": estimateSerializedSizeBytes(generatedToolResult.result),
3937
- ...(toolCall.error
3938
- ? {
4063
+ await this.notifyToolResult({
4064
+ mode: "generate",
4065
+ toolName: tc.toolName,
4066
+ toolCallId: tc.toolCallId,
4067
+ input: toolCall.args,
4068
+ result,
4069
+ context: executionContext,
4070
+ });
4071
+ const resultError = getToolResultError(result);
4072
+ if (resultError !== undefined) {
4073
+ setOtelActiveSpanErrorStatus(new NativeError(`Tool "${tc.toolName}" failed`));
4074
+ }
4075
+ toolCall.status = resultError === undefined ? "completed" : "error";
4076
+ toolCall.result = result;
4077
+ toolCall.error = resultError;
4078
+ toolCall.executionTime = Date.now() - startTime;
4079
+ setSpanAttributes(toolSpan, compactRuntimeTraceAttributes({
4080
+ "tool.status": resultError === undefined ? "completed" : "failed",
4081
+ "tool.provider_executed": false,
4082
+ "tool.output.size_bytes": estimateSerializedSizeBytes(result),
4083
+ ...(resultError === undefined ? {} : {
3939
4084
  error: true,
3940
- "error.type": "ProviderExecutedToolError",
4085
+ "error.type": "ToolResultError",
4086
+ }),
4087
+ }));
4088
+ if (resultError === undefined) {
4089
+ if (shouldHideProjectToolAfterAgentWriteSuccess(tc.toolName)) {
4090
+ agentWriteFinalResponseToolGuardEnabled = true;
3941
4091
  }
3942
- : {}),
3943
- }));
3944
- pushPrivateArray(toolCalls, toolCall);
3945
- return;
3946
- }
3947
- const policyCheck = enforceSkillPolicy(tc.toolName, {
3948
- activeSkillId: skillState.activeSkillId,
3949
- hasSubmittedFormInput: skillState.hasSubmittedFormInput,
3950
- skillToolAvailability: skillState.activeSkillToolAvailability,
3951
- toolInput: tc.input,
3952
- toolDefinition: executionAuthority?.toolDefinition,
3953
- });
3954
- if (!policyCheck.allowed) {
3955
- toolCall.status = "error";
3956
- toolCall.error = policyCheck.error;
3957
- setSpanAttributes(toolSpan, {
3958
- "tool.status": "blocked",
3959
- error: true,
3960
- "error.type": "ToolPolicyBlocked",
3961
- });
3962
- const errorMessage = {
3963
- id: `tool_error_${tc.toolCallId}`,
3964
- role: "tool",
3965
- parts: [{
3966
- type: "tool-result",
3967
- toolCallId: tc.toolCallId,
3968
- toolName: tc.toolName,
3969
- result: { error: policyCheck.error },
3970
- }],
3971
- timestamp: Date.now(),
3972
- };
3973
- pushPrivateArray(currentMessages, errorMessage);
3974
- await persistMessage(errorMessage);
3975
- pushPrivateArray(toolCalls, toolCall);
3976
- return;
3977
- }
3978
- try {
3979
- toolCall.status = "executing";
3980
- const startTime = Date.now();
3981
- const cacheCtx = tryGetCacheKeyContext();
3982
- toolCall.args = applySkillDelegationOverridesToToolInput(tc.toolName, toolCall.args, hasToolReplacements ? undefined : skillState.activeSkillDelegationOverrides, hasToolReplacements
3983
- ? undefined
3984
- : resolveConfiguredTool(runtimeToolsConfig, tc.toolName, {
3985
- agentId: this.id,
3986
- }) ??
3987
- undefined);
3988
- const executionContext = applicationExecutionContext(toolContext, providerObservedSkillBodies);
3989
- executionContext.projectId = cacheCtx?.projectId ?? toolContext?.projectId;
3990
- throwIfAborted(abortSignal);
3991
- const result = await traceConfiguredToolExecution({
3992
- mode: "generate",
3993
- agentId: this.id,
3994
- toolName: tc.toolName,
3995
- toolCallId: tc.toolCallId,
3996
- args: toolCall.args,
3997
- admittedTurn,
3998
- owner: currentMessages,
3999
- prepareTerminalDispatch,
4000
- toolsConfig: runtimeToolsConfig,
4001
- context: executionContext,
4002
- allowedRemoteToolNames,
4003
- remoteToolSources,
4004
- sourceIntegrationPolicy,
4005
- strictConfiguredToolsOnly: hasToolReplacements,
4006
- frameworkLocalTools,
4007
- });
4008
- await this.notifyToolResult({
4009
- mode: "generate",
4010
- toolName: tc.toolName,
4011
- toolCallId: tc.toolCallId,
4012
- input: toolCall.args,
4013
- result,
4014
- context: executionContext,
4015
- });
4016
- const resultError = getToolResultError(result);
4017
- if (resultError !== undefined) {
4018
- setOtelActiveSpanErrorStatus(new NativeError(`Tool "${tc.toolName}" failed`));
4092
+ // Track skill policy only from successful framework-owned load_skill results.
4093
+ if (isLoadSkillToolName(tc.toolName) &&
4094
+ hasTrustedPlatformPolicyToolDefinition(executionAuthority?.toolDefinition)) {
4095
+ skillState.applySuccessfulResult(result);
4096
+ }
4097
+ const submittedFormInput = isSubmittedFormInputExecutionResult(tc.toolName, result, executionAuthority?.toolDefinition);
4098
+ skillState.markFormInputSubmitted(submittedFormInput);
4099
+ if (submittedFormInput) {
4100
+ currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
4101
+ }
4102
+ }
4103
+ const toolResultMessage = createPolicyAwareToolResultMessage(tc.toolCallId, tc.toolName, result, executionAuthority?.toolDefinition, false, true);
4104
+ pushPrivateArray(currentMessages, toolResultMessage);
4105
+ await persistMessage(toolResultMessage);
4019
4106
  }
4020
- toolCall.status = resultError === undefined ? "completed" : "error";
4021
- toolCall.result = result;
4022
- toolCall.error = resultError;
4023
- toolCall.executionTime = Date.now() - startTime;
4024
- setSpanAttributes(toolSpan, compactRuntimeTraceAttributes({
4025
- "tool.status": resultError === undefined ? "completed" : "failed",
4026
- "tool.provider_executed": false,
4027
- "tool.output.size_bytes": estimateSerializedSizeBytes(result),
4028
- ...(resultError === undefined ? {} : {
4107
+ catch (error) {
4108
+ await this.recordTerminalToolResult(error, toolCall, persistMessage, currentMessages, toolCalls, totalUsage);
4109
+ throwIfAborted(abortSignal);
4110
+ toolCall.status = "error";
4111
+ toolCall.error = error instanceof Error ? error.message : String(error);
4112
+ setSpanAttributes(toolSpan, {
4113
+ "tool.status": "failed",
4029
4114
  error: true,
4030
- "error.type": "ToolResultError",
4031
- }),
4032
- }));
4033
- if (resultError === undefined) {
4034
- if (shouldHideProjectToolAfterAgentWriteSuccess(tc.toolName)) {
4035
- agentWriteFinalResponseToolGuardEnabled = true;
4036
- }
4037
- // Track skill policy only from successful framework-owned load_skill results.
4038
- if (isLoadSkillToolName(tc.toolName) &&
4039
- hasTrustedPlatformPolicyToolDefinition(executionAuthority?.toolDefinition)) {
4040
- skillState.applySuccessfulResult(result);
4041
- }
4042
- const submittedFormInput = isSubmittedFormInputExecutionResult(tc.toolName, result, executionAuthority?.toolDefinition);
4043
- skillState.markFormInputSubmitted(submittedFormInput);
4044
- if (submittedFormInput) {
4045
- currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
4046
- }
4115
+ "error.type": telemetryErrorType(error),
4116
+ });
4117
+ const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
4118
+ pushPrivateArray(currentMessages, errorMessage);
4119
+ await persistMessage(errorMessage);
4047
4120
  }
4048
- const toolResultMessage = createPolicyAwareToolResultMessage(tc.toolCallId, tc.toolName, result, executionAuthority?.toolDefinition, false, true);
4049
- pushPrivateArray(currentMessages, toolResultMessage);
4050
- await persistMessage(toolResultMessage);
4051
- }
4052
- catch (error) {
4053
- await this.recordTerminalToolResult(error, toolCall, persistMessage, currentMessages, toolCalls, totalUsage);
4054
- throwIfAborted(abortSignal);
4055
- toolCall.status = "error";
4056
- toolCall.error = error instanceof Error ? error.message : String(error);
4057
- setSpanAttributes(toolSpan, {
4058
- "tool.status": "failed",
4059
- error: true,
4060
- "error.type": telemetryErrorType(error),
4061
- });
4062
- const errorMessage = createToolErrorMessage(tc.toolCallId, tc.toolName, toolCall.error);
4063
- pushPrivateArray(currentMessages, errorMessage);
4064
- await persistMessage(errorMessage);
4065
- }
4066
- pushPrivateArray(toolCalls, toolCall);
4067
- });
4068
- throwIfAborted(abortSignal);
4121
+ pushPrivateArray(toolCalls, toolCall);
4122
+ });
4123
+ throwIfAborted(abortSignal);
4124
+ }
4069
4125
  }
4070
- }
4071
- catch (error) {
4072
- if (generatedTurnAdmissionObserved) {
4073
- await observeGeneratedProviderTurnFinishOnce(response);
4126
+ catch (error) {
4127
+ if (generatedTurnAdmissionObserved) {
4128
+ await observeGeneratedProviderTurnFinishOnce(response);
4129
+ }
4130
+ throw error;
4131
+ }
4132
+ if (isFinalMaxStepsToolTurn) {
4133
+ maxStepsParsedOutput = await tryParseMaxStepsOutput(response.text, outputSchema);
4134
+ await observeGeneratedProviderTurnFinishOnce({
4135
+ ...response,
4136
+ ...(maxStepsParsedOutput.parsed
4137
+ ? { data: maxStepsParsedOutput.object, object: maxStepsParsedOutput.object }
4138
+ : {}),
4139
+ });
4074
4140
  }
4075
- throw error;
4076
4141
  }
4077
- if (isFinalMaxStepsToolTurn) {
4078
- maxStepsParsedOutput = await tryParseMaxStepsOutput(response.text, outputSchema);
4079
- await observeGeneratedProviderTurnFinishOnce({
4080
- ...response,
4081
- ...(maxStepsParsedOutput.parsed
4082
- ? { data: maxStepsParsedOutput.object, object: maxStepsParsedOutput.object }
4142
+ throwIfAborted(abortSignal);
4143
+ this.status = "completed";
4144
+ addSpanEvent(loopSpan, "max_steps_reached", { maxSteps });
4145
+ setSpanAttributes(loopSpan, buildRuntimeUsageTraceAttributes(totalUsage));
4146
+ // The last message on this exit is a tool result, so the response text
4147
+ // and the structured-output candidate come from the final assistant turn.
4148
+ const finalText = getFinalAssistantText(currentMessages);
4149
+ const parsedOutput = maxStepsParsedOutput ??
4150
+ await tryParseMaxStepsOutput(finalText, outputSchema);
4151
+ return attachOutputSchemaParser({
4152
+ text: finalText,
4153
+ ...(parsedOutput.parsed ? { data: parsedOutput.object, object: parsedOutput.object } : {}),
4154
+ messages: currentMessages,
4155
+ toolCalls,
4156
+ status: this.status,
4157
+ usage: totalUsage,
4158
+ metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, {
4159
+ warning: `Max steps (${maxSteps}) reached`,
4160
+ ...(!parsedOutput.parsed && parsedOutput.outputSchemaError !== undefined
4161
+ ? { outputSchemaError: parsedOutput.outputSchemaError }
4083
4162
  : {}),
4084
- });
4085
- }
4163
+ }),
4164
+ }, withCachedMaxStepsOutputParse(outputSchema, parsedOutput, finalText));
4165
+ }
4166
+ catch (e_1) {
4167
+ env_1.error = e_1;
4168
+ env_1.hasError = true;
4169
+ }
4170
+ finally {
4171
+ const result_1 = __disposeResources(env_1);
4172
+ if (result_1)
4173
+ await result_1;
4086
4174
  }
4087
- throwIfAborted(abortSignal);
4088
- this.status = "completed";
4089
- addSpanEvent(loopSpan, "max_steps_reached", { maxSteps });
4090
- setSpanAttributes(loopSpan, buildRuntimeUsageTraceAttributes(totalUsage));
4091
- // The last message on this exit is a tool result, so the response text
4092
- // and the structured-output candidate come from the final assistant turn.
4093
- const finalText = getFinalAssistantText(currentMessages);
4094
- const parsedOutput = maxStepsParsedOutput ??
4095
- await tryParseMaxStepsOutput(finalText, outputSchema);
4096
- return attachOutputSchemaParser({
4097
- text: finalText,
4098
- ...(parsedOutput.parsed ? { data: parsedOutput.object, object: parsedOutput.object } : {}),
4099
- messages: currentMessages,
4100
- toolCalls,
4101
- status: this.status,
4102
- usage: totalUsage,
4103
- metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, {
4104
- warning: `Max steps (${maxSteps}) reached`,
4105
- ...(!parsedOutput.parsed && parsedOutput.outputSchemaError !== undefined
4106
- ? { outputSchemaError: parsedOutput.outputSchemaError }
4107
- : {}),
4108
- }),
4109
- }, withCachedMaxStepsOutputParse(outputSchema, parsedOutput, finalText));
4110
4175
  });
4111
4176
  }
4112
4177
  /**
@@ -4116,1516 +4181,1529 @@ export class AgentRuntime {
4116
4181
  */
4117
4182
  async #executeAgentLoopStreaming(// NOSONAR: Existing loop shape; this patch only adds authority cleanup.
4118
4183
  systemPrompt, messages, validateProviderRequest, persistMessage, prepareTerminalDispatch, controller, encoder, callbacks, textPartId, toolContextBase, runtimeContext, runRuntimeContext, runtimeObservationsEnabled, supportsToolCalling, providerReplayCheckpointEmission, modelString, resolvedModel, headers, providerOptions, reasoning, maxOutputTokensOverride, abortSignal, temperatureModelString, outputSchema) {
4119
- const { maxAgentSteps } = getPlatformCapabilities();
4120
- const maxSteps = this.computeMaxSteps(maxAgentSteps);
4121
- const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
4122
- const languageModel = resolvedModel ?? resolveModel(effectiveModel);
4123
- const continuation = this.#recoveryContinuation ?? await this.#manualPause?.load();
4124
- const checkpoint = continuation == null ? undefined : parseAgentPauseCheckpoint(continuation);
4125
- if (checkpoint?.providerMetadata) {
4126
- mapPrivateArray(checkpoint.messages, (message) => {
4127
- const saved = filterPrivateArray(checkpoint.providerMetadata, (entry) => entry.messageId === message.id)[0];
4128
- if (saved) {
4129
- attachProviderMetadata(message, saved.metadata);
4130
- }
4131
- });
4132
- }
4133
- const toolCalls = checkpoint?.toolCalls ?? [];
4134
- const currentMessages = mapPrivateArray(checkpoint?.messages ?? messages, (message) => message);
4135
- const runtimeGeneratedMessageIds = createPrivateSet(checkpoint?.runtimeGeneratedMessageIds ?? []);
4136
- mapPrivateArray(currentMessages, (message) => {
4137
- if (runtimeGeneratedMessageIds.has(message.id))
4138
- markRuntimeGeneratedUserMessage(message);
4139
- return message;
4140
- });
4141
- // JSON checkpoints drop in-memory trust markers; restore only the runtime-recorded results.
4142
- restoreTrustedSkillLoadResultsFromPauseCheckpoint(currentMessages, checkpoint?.trustedSkillLoadResultIds, checkpoint?.trustedSkillLoadResultRefs);
4143
- applyProviderReplayCheckpointsToMessages(currentMessages, getRuntimeProviderReplayCheckpoints(this.config), { activeProvider: resolveActiveProviderReplayProvider(languageModel) });
4144
- const totalUsage = checkpoint?.usage ??
4145
- { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
4146
- if (!supportsToolCalling && this.config.tools) {
4147
- warnUnsupportedToolCalling(this.id, effectiveModel);
4148
- }
4149
- // Request-scoped skill policy (not class-level mutable state)
4150
- const skillState = AgentLoopSkillState.hydrate(currentMessages, runtimeContext);
4151
- if (checkpoint) {
4152
- skillState.activeSkillDelegationOverrides = checkpoint.activeSkillDelegationOverrides;
4153
- skillState.markFormInputSubmitted(checkpoint.hasSubmittedFormInput === true);
4154
- }
4155
- let finalFinishReason = checkpoint?.finishReason;
4156
- let latestAssistantText = checkpoint?.latestAssistantText ?? "";
4157
- let completedWithinStepBudget = checkpoint?.completed ?? false;
4158
- const initialToolExposureCheckpoint = checkpoint?.toolExposureCheckpoint ??
4159
- getRuntimeToolExposureCheckpoint(this.config);
4160
- let pauseToolExposureAuthorized;
4161
- const toolExposureState = createToolExposureState();
4162
- const persistToolExposureCheckpoint = getRuntimeToolExposureCheckpointPersister(this.config);
4163
- const requireToolExposureCheckpointPersistence = isRuntimeToolExposureCheckpointPersistenceRequired(this.config);
4164
- const toolLoadingResolution = resolveRuntimeToolLoading(this.config);
4165
- const runtimeStepConfig = {
4166
- ...this.config,
4167
- __vfToolLoadingMode: toolLoadingResolution.mode,
4168
- };
4169
- if (ObjectGetPrototypeOf(this.config) === null)
4170
- ObjectSetPrototypeOf(runtimeStepConfig, null);
4171
- const allowedRemoteToolNames = getRuntimeAllowedRemoteTools(this.config);
4172
- const forwardedRemoteToolDefinitions = getRuntimeForwardedIntegrationToolDefs(this.config);
4173
- const remoteToolSources = getRuntimeRemoteToolSources(this.config, undefined, this.id);
4174
- const unavailableOptionalRemoteTools = getRuntimeUnavailableOptionalRemoteTools(this.config, remoteToolSources);
4175
- const sourceIntegrationPolicy = getRuntimeSourceIntegrationPolicy(this.config);
4176
- const frameworkLocalTools = createRuntimeFrameworkLocalTools(this.config);
4177
- const configuredProviderTools = getRuntimeProviderTools(this.config);
4178
- const toolResultContext = createActiveToolResultContext({
4179
- config: this.config,
4180
- supportsToolCalling,
4181
- });
4182
- const providerTools = sourceIntegrationPolicy
4183
- ? applySourceIntegrationPolicy(configuredProviderTools, sourceIntegrationPolicy)
4184
- : configuredProviderTools;
4185
- let currentSystemPrompt = systemPrompt;
4186
- let currentRuntimeContext = runtimeContext;
4187
- let agentWriteFinalResponseToolGuardEnabled = checkpoint?.agentWriteFinalResponseToolGuardEnabled ?? false;
4188
- let recoveredEmptyResponse = checkpoint?.recoveredEmptyResponse ?? false;
4189
- // One retry gives the model a chance to reconstruct a transport-truncated
4190
- // batch without allowing a repeatedly broken provider stream to loop.
4191
- let recoveredInterruptedLocalToolBatch = checkpoint?.recoveredInterruptedLocalToolBatch ??
4192
- false;
4193
- let interruptedLocalToolBatchRecoveryStep = checkpoint?.interruptedLocalToolBatchRecoveryStep;
4194
- let interruptedLocalToolBatchRecoveryText = checkpoint?.interruptedLocalToolBatchRecoveryText;
4195
- let resumeToolCallExecuted = checkpoint?.resumeToolCallExecuted ?? false;
4196
- // A pause can precede startup preparation; checkpoint existence is not completion.
4197
- let startupSkillLoaded = checkpoint?.startupSkillLoaded === true ||
4198
- this.config.bootstrapSkill === undefined || skillState.hasSubmittedFormInput;
4199
- const captureRecoveryContinuation = (nextStep) => parseAgentPauseCheckpoint({
4200
- version: 1,
4201
- nextStep,
4202
- messages: currentMessages,
4203
- toolCalls,
4204
- usage: totalUsage,
4205
- latestAssistantText,
4206
- completed: completedWithinStepBudget,
4207
- ...(finalFinishReason ? { finishReason: finalFinishReason } : {}),
4208
- recoveredEmptyResponse,
4209
- startupSkillLoaded,
4210
- recoveredInterruptedLocalToolBatch,
4211
- hasSubmittedFormInput: skillState.hasSubmittedFormInput,
4212
- activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
4213
- // Omitted when empty so checkpoints without trusted loads keep the prior shape.
4214
- ...(() => {
4215
- const ids = getTrustedSkillLoadResultIds(currentMessages);
4216
- if (hasAmbiguousTrustedSkillLoadResultIds(currentMessages)) {
4217
- return { trustedSkillLoadResultRefs: getTrustedSkillLoadResultRefs(currentMessages) };
4218
- }
4219
- return ids.length > 0 ? { trustedSkillLoadResultIds: ids } : {};
4220
- })(),
4221
- resumeToolCallExecuted,
4222
- agentWriteFinalResponseToolGuardEnabled,
4223
- interruptedLocalToolBatchRecoveryStep,
4224
- interruptedLocalToolBatchRecoveryText,
4225
- providerMetadata: mapPrivateArray(filterPrivateArray(currentMessages, (message) => readAttachedProviderMetadata(message) !== undefined), (message) => ({
4226
- messageId: message.id,
4227
- metadata: readAttachedProviderMetadata(message),
4228
- })),
4229
- runtimeGeneratedMessageIds: mapPrivateArray(filterPrivateArray(currentMessages, isRuntimeGeneratedUserMessage), (message) => message.id),
4230
- toolExposureCheckpoint: pauseToolExposureAuthorized
4231
- ? createToolExposureCheckpoint(pauseToolExposureAuthorized, toolExposureState)
4232
- : initialToolExposureCheckpoint,
4233
- });
4234
- const pauseAtBoundary = async (nextStep) => {
4235
- if (this.#persistRecoveryBoundary) {
4236
- const runtimeContinuation = captureRecoveryContinuation(nextStep);
4237
- await this.#persistRecoveryBoundary({
4238
- ...createRecoveryBoundary(currentMessages, runtimeContinuation.toolExposureCheckpoint ?? { version: 2, loadedToolNames: [] }),
4239
- runtimeContinuation,
4184
+ const env_2 = { stack: [], error: void 0, hasError: false };
4185
+ try {
4186
+ const { maxAgentSteps } = getPlatformCapabilities();
4187
+ const maxSteps = this.computeMaxSteps(maxAgentSteps);
4188
+ const effectiveModel = resolveRuntimeModel(modelString || this.config.model);
4189
+ const languageModel = resolvedModel ?? resolveModel(effectiveModel);
4190
+ const continuation = this.#recoveryContinuation ?? await this.#manualPause?.load();
4191
+ const checkpoint = continuation == null ? undefined : parseAgentPauseCheckpoint(continuation);
4192
+ if (checkpoint?.providerMetadata) {
4193
+ mapPrivateArray(checkpoint.messages, (message) => {
4194
+ const saved = filterPrivateArray(checkpoint.providerMetadata, (entry) => entry.messageId === message.id)[0];
4195
+ if (saved) {
4196
+ attachProviderMetadata(message, saved.metadata);
4197
+ }
4240
4198
  });
4241
4199
  }
4242
- if (!this.#manualPause)
4243
- return;
4244
- if (this.#manualPause.requested && !(await this.#manualPause.requested()))
4245
- return;
4246
- let saved;
4247
- try {
4248
- saved = captureRecoveryContinuation(nextStep);
4249
- }
4250
- catch {
4251
- if (this.#manualPause.release && !(await this.#manualPause.release()))
4252
- return;
4253
- logger.warn("Agent is held at a safe boundary because its pause continuation cannot be saved");
4254
- throw agentManualPauseBoundary();
4255
- }
4256
- let acknowledged = false;
4257
- await validateProviderRequest(withAgentRunRuntimeContext(systemPrompt, runRuntimeContext), currentMessages, {
4258
- beforeAcceptance: async () => {
4259
- acknowledged = await this.#manualPause.acknowledge(saved);
4260
- return acknowledged;
4261
- },
4200
+ const toolCalls = checkpoint?.toolCalls ?? [];
4201
+ const currentMessages = mapPrivateArray(checkpoint?.messages ?? messages, (message) => message);
4202
+ const runtimeGeneratedMessageIds = createPrivateSet(checkpoint?.runtimeGeneratedMessageIds ?? []);
4203
+ mapPrivateArray(currentMessages, (message) => {
4204
+ if (runtimeGeneratedMessageIds.has(message.id))
4205
+ markRuntimeGeneratedUserMessage(message);
4206
+ return message;
4262
4207
  });
4263
- if (acknowledged)
4264
- throw agentManualPauseBoundary();
4265
- };
4266
- if (this.#manualPause) {
4267
- await validateProviderRequest(withAgentRunRuntimeContext(systemPrompt, runRuntimeContext), currentMessages, { deferAcceptance: true });
4208
+ // JSON checkpoints drop in-memory trust markers; restore only the runtime-recorded results.
4209
+ restoreTrustedSkillLoadResultsFromPauseCheckpoint(currentMessages, checkpoint?.trustedSkillLoadResultIds, checkpoint?.trustedSkillLoadResultRefs);
4210
+ applyProviderReplayCheckpointsToMessages(currentMessages, getRuntimeProviderReplayCheckpoints(this.config), { activeProvider: resolveActiveProviderReplayProvider(languageModel) });
4211
+ const totalUsage = checkpoint?.usage ??
4212
+ { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
4213
+ if (!supportsToolCalling && this.config.tools) {
4214
+ warnUnsupportedToolCalling(this.id, effectiveModel);
4215
+ }
4216
+ // Request-scoped skill policy (not class-level mutable state)
4217
+ const skillState = AgentLoopSkillState.hydrate(currentMessages, runtimeContext);
4268
4218
  if (checkpoint) {
4269
- const persistedIds = createPrivateSet(mapPrivateArray(messages, (message) => message.id));
4270
- for (let index = 0; index < currentMessages.length; index++) {
4271
- const message = currentMessages[index];
4272
- if (persistedIds.has(message.id))
4273
- continue;
4274
- await persistMessage(message);
4275
- persistedIds.add(message.id);
4276
- }
4219
+ skillState.activeSkillDelegationOverrides = checkpoint.activeSkillDelegationOverrides;
4220
+ skillState.markFormInputSubmitted(checkpoint.hasSubmittedFormInput === true);
4277
4221
  }
4278
- }
4279
- await pauseAtBoundary(checkpoint?.nextStep ?? 0);
4280
- for (let step = checkpoint?.nextStep ?? 0; !completedWithinStepBudget && step < maxSteps; step++) {
4281
- throwIfAborted(abortSignal);
4282
- const currentStepToolResults = createPrivateMap();
4283
- const stepRuntimeContext = skillState.hasSubmittedFormInput
4284
- ? markSubmittedFormInputRuntimeContext(currentRuntimeContext)
4285
- : currentRuntimeContext;
4286
- const preparedStep = await prepareAgentRuntimeStep({
4287
- agentId: this.id,
4288
- activeSkillId: skillState.activeSkillId,
4289
- activeSkillToolAvailability: skillState.activeSkillToolAvailability,
4290
- allowedRemoteToolNames,
4291
- config: runtimeStepConfig,
4292
- effectiveModel,
4293
- ...resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
4294
- reservedToolSlots: getToolResultReaderReservedSlots(toolResultContext),
4295
- excludedToolNames: agentWriteFinalResponseToolGuardEnabled &&
4296
- toolLoadingResolution.mode === "eager"
4297
- ? AGENT_WRITE_FINAL_RESPONSE_EXCLUDED_TOOL_NAMES
4298
- : undefined,
4299
- forwardedRemoteToolDefinitions,
4300
- getAvailableTools,
4301
- unavailableOptionalRemoteToolNames: unavailableOptionalRemoteTools.names,
4302
- unavailableOptionalRemoteToolPrefixes: unavailableOptionalRemoteTools.prefixes,
4222
+ let finalFinishReason = checkpoint?.finishReason;
4223
+ let latestAssistantText = checkpoint?.latestAssistantText ?? "";
4224
+ let completedWithinStepBudget = checkpoint?.completed ?? false;
4225
+ const initialToolExposureCheckpoint = checkpoint?.toolExposureCheckpoint ??
4226
+ getRuntimeToolExposureCheckpoint(this.config);
4227
+ let pauseToolExposureAuthorized;
4228
+ const toolExposureState = createToolExposureState();
4229
+ const persistToolExposureCheckpoint = getRuntimeToolExposureCheckpointPersister(this.config);
4230
+ const requireToolExposureCheckpointPersistence = isRuntimeToolExposureCheckpointPersistenceRequired(this.config);
4231
+ const toolLoadingResolution = resolveRuntimeToolLoading(this.config);
4232
+ const runtimeStepConfig = {
4233
+ ...this.config,
4234
+ __vfToolLoadingMode: toolLoadingResolution.mode,
4235
+ };
4236
+ if (ObjectGetPrototypeOf(this.config) === null)
4237
+ ObjectSetPrototypeOf(runtimeStepConfig, null);
4238
+ const allowedRemoteToolNames = getRuntimeAllowedRemoteTools(this.config);
4239
+ const forwardedRemoteToolDefinitions = getRuntimeForwardedIntegrationToolDefs(this.config);
4240
+ const remoteToolSources = getRuntimeRemoteToolSources(this.config, undefined, this.id);
4241
+ const _remoteMcpCleanup = __addDisposableResource(env_2, createRuntimeMcpSourceCleanup(remoteToolSources), true);
4242
+ const unavailableOptionalRemoteTools = getRuntimeUnavailableOptionalRemoteTools(this.config, remoteToolSources);
4243
+ const sourceIntegrationPolicy = getRuntimeSourceIntegrationPolicy(this.config);
4244
+ const frameworkLocalTools = createRuntimeFrameworkLocalTools(this.config);
4245
+ const configuredProviderTools = getRuntimeProviderTools(this.config);
4246
+ const toolResultContext = createActiveToolResultContext({
4247
+ config: this.config,
4303
4248
  supportsToolCalling,
4304
- includeStartupAuthorizedTools: !startupSkillLoaded,
4249
+ });
4250
+ const providerTools = sourceIntegrationPolicy
4251
+ ? applySourceIntegrationPolicy(configuredProviderTools, sourceIntegrationPolicy)
4252
+ : configuredProviderTools;
4253
+ let currentSystemPrompt = systemPrompt;
4254
+ let currentRuntimeContext = runtimeContext;
4255
+ let agentWriteFinalResponseToolGuardEnabled = checkpoint?.agentWriteFinalResponseToolGuardEnabled ?? false;
4256
+ let recoveredEmptyResponse = checkpoint?.recoveredEmptyResponse ?? false;
4257
+ // One retry gives the model a chance to reconstruct a transport-truncated
4258
+ // batch without allowing a repeatedly broken provider stream to loop.
4259
+ let recoveredInterruptedLocalToolBatch = checkpoint?.recoveredInterruptedLocalToolBatch ??
4260
+ false;
4261
+ let interruptedLocalToolBatchRecoveryStep = checkpoint?.interruptedLocalToolBatchRecoveryStep;
4262
+ let interruptedLocalToolBatchRecoveryText = checkpoint?.interruptedLocalToolBatchRecoveryText;
4263
+ let resumeToolCallExecuted = checkpoint?.resumeToolCallExecuted ?? false;
4264
+ // A pause can precede startup preparation; checkpoint existence is not completion.
4265
+ let startupSkillLoaded = checkpoint?.startupSkillLoaded === true ||
4266
+ this.config.bootstrapSkill === undefined || skillState.hasSubmittedFormInput;
4267
+ const captureRecoveryContinuation = (nextStep) => parseAgentPauseCheckpoint({
4268
+ version: 1,
4269
+ nextStep,
4305
4270
  messages: currentMessages,
4306
- mode: "stream",
4307
- modelRuntime: languageModel,
4308
- providerOptionKey: resolveModelProviderOptionKey(effectiveModel, languageModel),
4309
- authorizedProviderToolNames: providerTools,
4310
- providerToolNames: supportsToolCalling && !agentWriteFinalResponseToolGuardEnabled
4311
- ? providerTools
4312
- : [],
4313
- remoteToolSources,
4314
- sourceIntegrationPolicy,
4315
- resolveRuntimeState: this.resolveRuntimeState.bind(this),
4316
- runtimeContext: stepRuntimeContext,
4317
- step,
4318
- systemPrompt: currentSystemPrompt,
4319
- toolContextBase,
4320
- frameworkLocalTools,
4321
- toolExposureState,
4322
- toolExposureCheckpoint: step === (checkpoint?.nextStep ?? 0)
4323
- ? initialToolExposureCheckpoint
4324
- : undefined,
4271
+ toolCalls,
4272
+ usage: totalUsage,
4273
+ latestAssistantText,
4274
+ completed: completedWithinStepBudget,
4275
+ ...(finalFinishReason ? { finishReason: finalFinishReason } : {}),
4276
+ recoveredEmptyResponse,
4277
+ startupSkillLoaded,
4278
+ recoveredInterruptedLocalToolBatch,
4279
+ hasSubmittedFormInput: skillState.hasSubmittedFormInput,
4280
+ activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
4281
+ // Omitted when empty so checkpoints without trusted loads keep the prior shape.
4282
+ ...(() => {
4283
+ const ids = getTrustedSkillLoadResultIds(currentMessages);
4284
+ if (hasAmbiguousTrustedSkillLoadResultIds(currentMessages)) {
4285
+ return { trustedSkillLoadResultRefs: getTrustedSkillLoadResultRefs(currentMessages) };
4286
+ }
4287
+ return ids.length > 0 ? { trustedSkillLoadResultIds: ids } : {};
4288
+ })(),
4289
+ resumeToolCallExecuted,
4290
+ agentWriteFinalResponseToolGuardEnabled,
4291
+ interruptedLocalToolBatchRecoveryStep,
4292
+ interruptedLocalToolBatchRecoveryText,
4293
+ providerMetadata: mapPrivateArray(filterPrivateArray(currentMessages, (message) => readAttachedProviderMetadata(message) !== undefined), (message) => ({
4294
+ messageId: message.id,
4295
+ metadata: readAttachedProviderMetadata(message),
4296
+ })),
4297
+ runtimeGeneratedMessageIds: mapPrivateArray(filterPrivateArray(currentMessages, isRuntimeGeneratedUserMessage), (message) => message.id),
4298
+ toolExposureCheckpoint: pauseToolExposureAuthorized
4299
+ ? createToolExposureCheckpoint(pauseToolExposureAuthorized, toolExposureState)
4300
+ : initialToolExposureCheckpoint,
4325
4301
  });
4326
- pauseToolExposureAuthorized = preparedStep.toolExposurePlan.authorized;
4327
- currentSystemPrompt = preparedStep.systemPrompt;
4328
- currentRuntimeContext = preparedStep.runtimeContext;
4329
- const toolContext = preparedStep.toolContext;
4330
- let effectiveToolExposurePlan = agentWriteFinalResponseToolGuardEnabled
4331
- ? applyAgentWriteFinalResponseGuard(preparedStep.toolExposurePlan, {
4332
- reloadable: toolLoadingResolution.mode === "deferred",
4333
- })
4334
- : preparedStep.toolExposurePlan;
4335
- if (!startupSkillLoaded) {
4336
- await executeStartupSkill({
4302
+ const pauseAtBoundary = async (nextStep) => {
4303
+ if (this.#persistRecoveryBoundary) {
4304
+ const runtimeContinuation = captureRecoveryContinuation(nextStep);
4305
+ await this.#persistRecoveryBoundary({
4306
+ ...createRecoveryBoundary(currentMessages, runtimeContinuation.toolExposureCheckpoint ?? { version: 2, loadedToolNames: [] }),
4307
+ runtimeContinuation,
4308
+ });
4309
+ }
4310
+ if (!this.#manualPause)
4311
+ return;
4312
+ if (this.#manualPause.requested && !(await this.#manualPause.requested()))
4313
+ return;
4314
+ let saved;
4315
+ try {
4316
+ saved = captureRecoveryContinuation(nextStep);
4317
+ }
4318
+ catch {
4319
+ if (this.#manualPause.release && !(await this.#manualPause.release()))
4320
+ return;
4321
+ logger.warn("Agent is held at a safe boundary because its pause continuation cannot be saved");
4322
+ throw agentManualPauseBoundary();
4323
+ }
4324
+ let acknowledged = false;
4325
+ await validateProviderRequest(withAgentRunRuntimeContext(systemPrompt, runRuntimeContext), currentMessages, {
4326
+ beforeAcceptance: async () => {
4327
+ acknowledged = await this.#manualPause.acknowledge(saved);
4328
+ return acknowledged;
4329
+ },
4330
+ });
4331
+ if (acknowledged)
4332
+ throw agentManualPauseBoundary();
4333
+ };
4334
+ if (this.#manualPause) {
4335
+ await validateProviderRequest(withAgentRunRuntimeContext(systemPrompt, runRuntimeContext), currentMessages, { deferAcceptance: true });
4336
+ if (checkpoint) {
4337
+ const persistedIds = createPrivateSet(mapPrivateArray(messages, (message) => message.id));
4338
+ for (let index = 0; index < currentMessages.length; index++) {
4339
+ const message = currentMessages[index];
4340
+ if (persistedIds.has(message.id))
4341
+ continue;
4342
+ await persistMessage(message);
4343
+ persistedIds.add(message.id);
4344
+ }
4345
+ }
4346
+ }
4347
+ await pauseAtBoundary(checkpoint?.nextStep ?? 0);
4348
+ for (let step = checkpoint?.nextStep ?? 0; !completedWithinStepBudget && step < maxSteps; step++) {
4349
+ throwIfAborted(abortSignal);
4350
+ const currentStepToolResults = createPrivateMap();
4351
+ const stepRuntimeContext = skillState.hasSubmittedFormInput
4352
+ ? markSubmittedFormInputRuntimeContext(currentRuntimeContext)
4353
+ : currentRuntimeContext;
4354
+ const preparedStep = await prepareAgentRuntimeStep({
4337
4355
  agentId: this.id,
4338
- skillId: this.config.bootstrapSkill,
4339
- authorized: preparedStep.startupAuthorizedTools,
4356
+ activeSkillId: skillState.activeSkillId,
4357
+ activeSkillToolAvailability: skillState.activeSkillToolAvailability,
4358
+ allowedRemoteToolNames,
4359
+ config: runtimeStepConfig,
4360
+ effectiveModel,
4361
+ ...resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
4362
+ reservedToolSlots: getToolResultReaderReservedSlots(toolResultContext),
4363
+ excludedToolNames: agentWriteFinalResponseToolGuardEnabled &&
4364
+ toolLoadingResolution.mode === "eager"
4365
+ ? AGENT_WRITE_FINAL_RESPONSE_EXCLUDED_TOOL_NAMES
4366
+ : undefined,
4367
+ forwardedRemoteToolDefinitions,
4368
+ getAvailableTools,
4369
+ unavailableOptionalRemoteToolNames: unavailableOptionalRemoteTools.names,
4370
+ unavailableOptionalRemoteToolPrefixes: unavailableOptionalRemoteTools.prefixes,
4371
+ supportsToolCalling,
4372
+ includeStartupAuthorizedTools: !startupSkillLoaded,
4340
4373
  messages: currentMessages,
4341
- persistMessage,
4342
- skillState,
4343
- toolCalls,
4344
- onToolCall: callbacks?.onToolCall,
4345
- ...(this.#persistRecoveryBoundary
4374
+ mode: "stream",
4375
+ modelRuntime: languageModel,
4376
+ providerOptionKey: resolveModelProviderOptionKey(effectiveModel, languageModel),
4377
+ authorizedProviderToolNames: providerTools,
4378
+ providerToolNames: supportsToolCalling && !agentWriteFinalResponseToolGuardEnabled
4379
+ ? providerTools
4380
+ : [],
4381
+ remoteToolSources,
4382
+ sourceIntegrationPolicy,
4383
+ resolveRuntimeState: this.resolveRuntimeState.bind(this),
4384
+ runtimeContext: stepRuntimeContext,
4385
+ step,
4386
+ systemPrompt: currentSystemPrompt,
4387
+ toolContextBase,
4388
+ frameworkLocalTools,
4389
+ toolExposureState,
4390
+ toolExposureCheckpoint: step === (checkpoint?.nextStep ?? 0)
4391
+ ? initialToolExposureCheckpoint
4392
+ : undefined,
4393
+ });
4394
+ pauseToolExposureAuthorized = preparedStep.toolExposurePlan.authorized;
4395
+ currentSystemPrompt = preparedStep.systemPrompt;
4396
+ currentRuntimeContext = preparedStep.runtimeContext;
4397
+ const toolContext = preparedStep.toolContext;
4398
+ let effectiveToolExposurePlan = agentWriteFinalResponseToolGuardEnabled
4399
+ ? applyAgentWriteFinalResponseGuard(preparedStep.toolExposurePlan, {
4400
+ reloadable: toolLoadingResolution.mode === "deferred",
4401
+ })
4402
+ : preparedStep.toolExposurePlan;
4403
+ if (!startupSkillLoaded) {
4404
+ await executeStartupSkill({
4405
+ agentId: this.id,
4406
+ skillId: this.config.bootstrapSkill,
4407
+ authorized: preparedStep.startupAuthorizedTools,
4408
+ messages: currentMessages,
4409
+ persistMessage,
4410
+ skillState,
4411
+ toolCalls,
4412
+ onToolCall: callbacks?.onToolCall,
4413
+ ...(this.#persistRecoveryBoundary
4414
+ ? {
4415
+ recoveryStart: async (call) => {
4416
+ const occurrenceId = introduceToolCallOccurrence(call);
4417
+ if (!occurrenceId) {
4418
+ throw new Error("Managed startup skill lacks original occurrence scope");
4419
+ }
4420
+ sendSSE(controller, encoder, {
4421
+ type: "tool-input-start",
4422
+ toolCallId: call.id,
4423
+ toolName: call.name,
4424
+ privateToolCallOccurrenceId: occurrenceId,
4425
+ });
4426
+ sendSSE(controller, encoder, {
4427
+ type: "tool-input-available",
4428
+ toolCallId: call.id,
4429
+ toolName: call.name,
4430
+ input: call.args,
4431
+ });
4432
+ await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
4433
+ },
4434
+ recoveryResult: async (call) => {
4435
+ sendSSE(controller, encoder, call.error === undefined
4436
+ ? { type: "tool-output-available", toolCallId: call.id, output: call.result }
4437
+ : { type: "tool-output-error", toolCallId: call.id, errorText: call.error });
4438
+ if (call.status === "completed")
4439
+ startupSkillLoaded = true;
4440
+ const runtimeContinuation = captureRecoveryContinuation(step);
4441
+ await this.#persistRecoveryBoundary({
4442
+ ...createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)),
4443
+ runtimeContinuation,
4444
+ });
4445
+ },
4446
+ }
4447
+ : {}),
4448
+ resolveLoader: (name) => resolveConfiguredTool(this.config.tools, name, { agentId: this.id }),
4449
+ execute: async (toolName, toolCallId, args, admittedTurn) => {
4450
+ const context = applicationExecutionContext(toolContext);
4451
+ const result = await traceConfiguredToolExecution({
4452
+ mode: "stream",
4453
+ agentId: this.id,
4454
+ toolName,
4455
+ toolCallId,
4456
+ args,
4457
+ admittedTurn,
4458
+ owner: currentMessages,
4459
+ prepareTerminalDispatch,
4460
+ toolsConfig: this.config.tools,
4461
+ context,
4462
+ allowedRemoteToolNames,
4463
+ remoteToolSources,
4464
+ sourceIntegrationPolicy,
4465
+ frameworkLocalTools,
4466
+ });
4467
+ await this.notifyToolResult({
4468
+ mode: "stream",
4469
+ toolName,
4470
+ toolCallId,
4471
+ input: args,
4472
+ result,
4473
+ context,
4474
+ });
4475
+ return result;
4476
+ },
4477
+ });
4478
+ startupSkillLoaded = true;
4479
+ // A pause requested while the loader ran must hold before the first
4480
+ // provider call; the checkpoint records the completed startup load.
4481
+ await pauseAtBoundary(step);
4482
+ // Refresh the active skill policy without consuming a model step.
4483
+ step--;
4484
+ continue;
4485
+ }
4486
+ const runtimeObservationStepId = runtimeObservationsEnabled ? dntShim.crypto.randomUUID() : undefined;
4487
+ const runtimeObservationMessageSpanId = runtimeObservationStepId !== undefined
4488
+ ? dntShim.crypto.randomUUID()
4489
+ : undefined;
4490
+ sendSSE(controller, encoder, {
4491
+ type: "step-start",
4492
+ ...(runtimeObservationStepId
4346
4493
  ? {
4347
- recoveryStart: async (call) => {
4348
- const occurrenceId = introduceToolCallOccurrence(call);
4349
- if (!occurrenceId) {
4350
- throw new Error("Managed startup skill lacks original occurrence scope");
4351
- }
4352
- sendSSE(controller, encoder, {
4353
- type: "tool-input-start",
4354
- toolCallId: call.id,
4355
- toolName: call.name,
4356
- privateToolCallOccurrenceId: occurrenceId,
4357
- });
4358
- sendSSE(controller, encoder, {
4359
- type: "tool-input-available",
4360
- toolCallId: call.id,
4361
- toolName: call.name,
4362
- input: call.args,
4363
- });
4364
- await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
4365
- },
4366
- recoveryResult: async (call) => {
4367
- sendSSE(controller, encoder, call.error === undefined
4368
- ? { type: "tool-output-available", toolCallId: call.id, output: call.result }
4369
- : { type: "tool-output-error", toolCallId: call.id, errorText: call.error });
4370
- if (call.status === "completed")
4371
- startupSkillLoaded = true;
4372
- const runtimeContinuation = captureRecoveryContinuation(step);
4373
- await this.#persistRecoveryBoundary({
4374
- ...createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)),
4375
- runtimeContinuation,
4376
- });
4494
+ privateRuntimeObservation: {
4495
+ version: 1,
4496
+ kind: "step_started",
4497
+ stepId: runtimeObservationStepId,
4377
4498
  },
4378
4499
  }
4379
4500
  : {}),
4380
- resolveLoader: (name) => resolveConfiguredTool(this.config.tools, name, { agentId: this.id }),
4381
- execute: async (toolName, toolCallId, args, admittedTurn) => {
4382
- const context = applicationExecutionContext(toolContext);
4383
- const result = await traceConfiguredToolExecution({
4501
+ });
4502
+ assertToolResultReaderNameAvailable({
4503
+ plan: effectiveToolExposurePlan,
4504
+ context: toolResultContext,
4505
+ });
4506
+ if (!resumeToolCallExecuted && this.#resumeToolCall) {
4507
+ const resumeToolCall = this.#resumeToolCall;
4508
+ resumeToolCallExecuted = true;
4509
+ const inputText = privateJsonStringify(resumeToolCall.input);
4510
+ const streamedCall = {
4511
+ id: resumeToolCall.id,
4512
+ name: resumeToolCall.name,
4513
+ arguments: inputText,
4514
+ inputDeltas: [inputText],
4515
+ inputAvailable: true,
4516
+ };
4517
+ announceStreamedToolCallInput(controller, encoder, streamedCall, (name) => isDynamicTool(name, this.config.tools));
4518
+ sendSSE(controller, encoder, {
4519
+ type: "tool-input-available",
4520
+ toolCallId: resumeToolCall.id,
4521
+ toolName: resumeToolCall.name,
4522
+ input: resumeToolCall.input,
4523
+ ...(isDynamicTool(resumeToolCall.name, this.config.tools) ? { dynamic: true } : {}),
4524
+ });
4525
+ const assistantToolCallMessage = {
4526
+ id: generateMessageId(),
4527
+ role: "assistant",
4528
+ parts: [{
4529
+ type: `tool-${resumeToolCall.name}`,
4530
+ toolCallId: resumeToolCall.id,
4531
+ toolName: resumeToolCall.name,
4532
+ args: resumeToolCall.input,
4533
+ }],
4534
+ };
4535
+ let admittedTurn = snapshotAdmittedToolTurn(assistantToolCallMessage, currentMessages.length);
4536
+ for (let index = currentMessages.length - 1; index >= 0; index--) {
4537
+ const message = currentMessages[index];
4538
+ if (message.role !== "assistant")
4539
+ continue;
4540
+ const candidate = snapshotAdmittedToolTurn(message, index);
4541
+ if (somePrivateArray(candidate.calls, (call) => call.toolCallId === resumeToolCall.id && call.toolName === resumeToolCall.name &&
4542
+ providerValuesEqual(call.input, resumeToolCall.input, new IntrinsicWeakMap())))
4543
+ admittedTurn = candidate;
4544
+ // A newer assistant envelope is a turn boundary, never inferred replay provenance.
4545
+ break;
4546
+ }
4547
+ if (admittedTurn.start === currentMessages.length) {
4548
+ pushPrivateArray(currentMessages, assistantToolCallMessage);
4549
+ await persistMessage(assistantToolCallMessage);
4550
+ }
4551
+ if (this.#persistRecoveryBoundary) {
4552
+ await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
4553
+ throwIfAborted(abortSignal);
4554
+ }
4555
+ const toolCall = {
4556
+ id: resumeToolCall.id,
4557
+ name: resumeToolCall.name,
4558
+ args: resumeToolCall.input,
4559
+ status: "executing",
4560
+ };
4561
+ const executionContext = applicationExecutionContext(toolContext,
4562
+ // Partial results from the parked assistant turn were not yet provider-observed.
4563
+ getProviderObservedSkillBodies(slicePrivateArray(currentMessages, 0, admittedTurn.start)));
4564
+ let resumeAdmissionRejection;
4565
+ try {
4566
+ // The trusted parked call was already exposed in the prior segment.
4567
+ // Recheck current authorization without requiring its lost step visibility.
4568
+ const resumeExecutionAuthority = resolveToolExecutionAuthority({
4569
+ toolName: resumeToolCall.name,
4570
+ plan: {
4571
+ ...effectiveToolExposurePlan,
4572
+ visible: effectiveToolExposurePlan.authorized,
4573
+ },
4574
+ });
4575
+ if (resumeExecutionAuthority === undefined) {
4576
+ throw new Error(toolNotVisibleError(resumeToolCall.name));
4577
+ }
4578
+ const policyCheck = enforceSkillPolicy(resumeToolCall.name, {
4579
+ activeSkillId: skillState.activeSkillId,
4580
+ hasSubmittedFormInput: skillState.hasSubmittedFormInput,
4581
+ skillToolAvailability: skillState.activeSkillToolAvailability,
4582
+ toolInput: toolCall.args,
4583
+ toolDefinition: resumeExecutionAuthority.toolDefinition,
4584
+ });
4585
+ if (!policyCheck.allowed)
4586
+ throw new Error(policyCheck.error);
4587
+ toolCall.args = applySkillDelegationOverridesToToolInput(resumeToolCall.name, toolCall.args, skillState.activeSkillDelegationOverrides, resolveConfiguredTool(this.config.tools, resumeToolCall.name, { agentId: this.id }) ??
4588
+ undefined);
4589
+ // Resuming a trusted parked tool is a real dispatch even before the
4590
+ // next provider request. Validate before its effect, then retain that
4591
+ // accepted history if later provider preparation fails.
4592
+ try {
4593
+ await validateProviderRequest(withAgentRunRuntimeContext(currentSystemPrompt, runRuntimeContext), currentMessages);
4594
+ }
4595
+ catch (error) {
4596
+ resumeAdmissionRejection = { error };
4597
+ throw error;
4598
+ }
4599
+ callbacks?.onToolCall?.(toolCall);
4600
+ const startTime = Date.now();
4601
+ const result = await runWithToolCallOccurrenceDispatch(streamedCall, () => traceConfiguredToolExecution({
4384
4602
  mode: "stream",
4385
4603
  agentId: this.id,
4386
- toolName,
4387
- toolCallId,
4388
- args,
4604
+ toolName: resumeToolCall.name,
4605
+ toolCallId: resumeToolCall.id,
4606
+ args: toolCall.args,
4389
4607
  admittedTurn,
4390
4608
  owner: currentMessages,
4391
4609
  prepareTerminalDispatch,
4392
4610
  toolsConfig: this.config.tools,
4393
- context,
4611
+ context: executionContext,
4394
4612
  allowedRemoteToolNames,
4395
4613
  remoteToolSources,
4396
4614
  sourceIntegrationPolicy,
4397
4615
  frameworkLocalTools,
4398
- });
4616
+ }));
4617
+ throwIfAborted(abortSignal);
4399
4618
  await this.notifyToolResult({
4400
4619
  mode: "stream",
4401
- toolName,
4402
- toolCallId,
4403
- input: args,
4620
+ toolName: resumeToolCall.name,
4621
+ toolCallId: resumeToolCall.id,
4622
+ input: toolCall.args,
4404
4623
  result,
4405
- context,
4624
+ context: executionContext,
4406
4625
  });
4407
- return result;
4408
- },
4409
- });
4410
- startupSkillLoaded = true;
4411
- // A pause requested while the loader ran must hold before the first
4412
- // provider call; the checkpoint records the completed startup load.
4413
- await pauseAtBoundary(step);
4414
- // Refresh the active skill policy without consuming a model step.
4415
- step--;
4416
- continue;
4417
- }
4418
- const runtimeObservationStepId = runtimeObservationsEnabled ? dntShim.crypto.randomUUID() : undefined;
4419
- const runtimeObservationMessageSpanId = runtimeObservationStepId !== undefined
4420
- ? dntShim.crypto.randomUUID()
4421
- : undefined;
4422
- sendSSE(controller, encoder, {
4423
- type: "step-start",
4424
- ...(runtimeObservationStepId
4425
- ? {
4426
- privateRuntimeObservation: {
4427
- version: 1,
4428
- kind: "step_started",
4429
- stepId: runtimeObservationStepId,
4430
- },
4626
+ const resultError = getToolResultError(result);
4627
+ toolCall.status = resultError === undefined ? "completed" : "error";
4628
+ toolCall.result = result;
4629
+ toolCall.error = resultError;
4630
+ toolCall.executionTime = Date.now() - startTime;
4631
+ pushPrivateArray(toolCalls, toolCall);
4632
+ if (resultError === undefined) {
4633
+ sendSSE(controller, encoder, {
4634
+ type: "tool-output-available",
4635
+ toolCallId: resumeToolCall.id,
4636
+ output: result,
4637
+ ...(isDynamicTool(resumeToolCall.name, this.config.tools) ? { dynamic: true } : {}),
4638
+ });
4639
+ }
4640
+ else {
4641
+ sendSSE(controller, encoder, {
4642
+ type: "tool-output-error",
4643
+ toolCallId: resumeToolCall.id,
4644
+ errorText: resultError,
4645
+ ...(isDynamicTool(resumeToolCall.name, this.config.tools) ? { dynamic: true } : {}),
4646
+ });
4647
+ }
4648
+ const toolResultMessage = createPolicyAwareToolResultMessage(resumeToolCall.id, resumeToolCall.name, result, resumeExecutionAuthority.toolDefinition, false, true);
4649
+ pushPrivateArray(currentMessages, toolResultMessage);
4650
+ await persistMessage(toolResultMessage);
4431
4651
  }
4432
- : {}),
4433
- });
4434
- assertToolResultReaderNameAvailable({
4435
- plan: effectiveToolExposurePlan,
4436
- context: toolResultContext,
4437
- });
4438
- if (!resumeToolCallExecuted && this.#resumeToolCall) {
4439
- const resumeToolCall = this.#resumeToolCall;
4440
- resumeToolCallExecuted = true;
4441
- const inputText = privateJsonStringify(resumeToolCall.input);
4442
- const streamedCall = {
4443
- id: resumeToolCall.id,
4444
- name: resumeToolCall.name,
4445
- arguments: inputText,
4446
- inputDeltas: [inputText],
4447
- inputAvailable: true,
4448
- };
4449
- announceStreamedToolCallInput(controller, encoder, streamedCall, (name) => isDynamicTool(name, this.config.tools));
4450
- sendSSE(controller, encoder, {
4451
- type: "tool-input-available",
4452
- toolCallId: resumeToolCall.id,
4453
- toolName: resumeToolCall.name,
4454
- input: resumeToolCall.input,
4455
- ...(isDynamicTool(resumeToolCall.name, this.config.tools) ? { dynamic: true } : {}),
4456
- });
4457
- const assistantToolCallMessage = {
4458
- id: generateMessageId(),
4459
- role: "assistant",
4460
- parts: [{
4461
- type: `tool-${resumeToolCall.name}`,
4462
- toolCallId: resumeToolCall.id,
4463
- toolName: resumeToolCall.name,
4464
- args: resumeToolCall.input,
4465
- }],
4466
- };
4467
- let admittedTurn = snapshotAdmittedToolTurn(assistantToolCallMessage, currentMessages.length);
4468
- for (let index = currentMessages.length - 1; index >= 0; index--) {
4469
- const message = currentMessages[index];
4470
- if (message.role !== "assistant")
4471
- continue;
4472
- const candidate = snapshotAdmittedToolTurn(message, index);
4473
- if (somePrivateArray(candidate.calls, (call) => call.toolCallId === resumeToolCall.id && call.toolName === resumeToolCall.name &&
4474
- providerValuesEqual(call.input, resumeToolCall.input, new IntrinsicWeakMap())))
4475
- admittedTurn = candidate;
4476
- // A newer assistant envelope is a turn boundary, never inferred replay provenance.
4477
- break;
4478
- }
4479
- if (admittedTurn.start === currentMessages.length) {
4480
- pushPrivateArray(currentMessages, assistantToolCallMessage);
4481
- await persistMessage(assistantToolCallMessage);
4652
+ catch (error) {
4653
+ // A turn admission rejection belongs to the request, not the tool:
4654
+ // surface it as the request error instead of a tool-output-error.
4655
+ if (resumeAdmissionRejection?.error === error)
4656
+ throw error;
4657
+ await this.recordTerminalToolResult(error, toolCall, persistMessage, currentMessages, toolCalls, totalUsage, { controller, encoder });
4658
+ throwIfAborted(abortSignal);
4659
+ await this.recordToolError(persistMessage, toolCall, error instanceof Error ? error.message : String(error), { controller, encoder }, currentMessages, toolCalls);
4660
+ }
4661
+ if (this.#persistRecoveryBoundary)
4662
+ await pauseAtBoundary(step);
4482
4663
  }
4664
+ // Include the parked result delivered to this request, but exclude new same-step results.
4665
+ const providerObservedSkillBodies = getProviderObservedSkillBodies(currentMessages);
4666
+ const baseSystemPrompt = currentSystemPrompt;
4667
+ const assembleRequest = (messages, plan = effectiveToolExposurePlan) => assembleRuntimeStepRequest({
4668
+ messages,
4669
+ plan,
4670
+ toolResultContext,
4671
+ supportsToolCalling,
4672
+ agentWriteFinalResponseToolGuardEnabled,
4673
+ providerTools,
4674
+ model: effectiveModel,
4675
+ languageModel,
4676
+ toolSchemaOptions: resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
4677
+ baseSystemPrompt,
4678
+ integrationToolDiscovery: preparedStep.integrationToolDiscovery,
4679
+ runRuntimeContext,
4680
+ bootstrapToolNames: preparedStep.configuredBootstrapToolNames,
4681
+ });
4682
+ const requestedOutputTokens = this.resolveMaxOutputTokens(effectiveModel, maxOutputTokensOverride);
4683
+ // An invalid thinking budget is left for the provider path to reject.
4684
+ const additionalOutputTokens = resolveAdditiveReasoningOutputTokens(languageModel, {
4685
+ reasoning,
4686
+ providerOptions,
4687
+ }) ?? 0;
4688
+ const fittedRequest = await fitRuntimeStepRequestToServedBudget({
4689
+ sourceMessages: currentMessages,
4690
+ modelMessages: toolResultContext
4691
+ ? createModelToolResultContextMessages(currentMessages, toolResultContext)
4692
+ : currentMessages,
4693
+ plan: effectiveToolExposurePlan,
4694
+ assembleRequest,
4695
+ toolResultContext,
4696
+ supportsToolCalling,
4697
+ deferredToolLoading: toolLoadingResolution.mode === "deferred",
4698
+ languageModel,
4699
+ model: effectiveModel,
4700
+ reasoning,
4701
+ providerOptions,
4702
+ responseFormat: outputSchema?.responseFormat,
4703
+ requestedOutputTokens,
4704
+ explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
4705
+ additionalOutputTokens,
4706
+ toolExposureState,
4707
+ persistToolExposureCheckpoint,
4708
+ requireToolExposureCheckpointPersistence,
4709
+ turnAdmission: validateProviderRequest,
4710
+ });
4711
+ const modelMessages = fittedRequest.modelMessages;
4712
+ effectiveToolExposurePlan = fittedRequest.plan;
4483
4713
  if (this.#persistRecoveryBoundary) {
4484
4714
  await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
4485
4715
  throwIfAborted(abortSignal);
4486
4716
  }
4487
- const toolCall = {
4488
- id: resumeToolCall.id,
4489
- name: resumeToolCall.name,
4490
- args: resumeToolCall.input,
4491
- status: "executing",
4717
+ const request = assembleRequest(modelMessages);
4718
+ currentSystemPrompt = request.systemPrompt;
4719
+ const { tools, runtimeTools, providerSystemPrompt, providerMessages } = request;
4720
+ setOtelActiveSpanAttributes({
4721
+ "tool.loading.mode": resolveRuntimeToolLoading(runtimeStepConfig).mode,
4722
+ "tool.loading.provenance": toolLoadingResolution.provenance,
4723
+ "tool.catalog.authorized_count": preparedStep.toolExposurePlan.authorized.length,
4724
+ "tool.catalog.visible_count": tools.length,
4725
+ "tool.catalog.deferred_count": effectiveToolExposurePlan.deferred.length,
4726
+ "tool.loading.path": "framework-fallback",
4727
+ });
4728
+ const runtimeToolNames = Object.keys(runtimeTools ?? {}).sort(compareStrings);
4729
+ const acceptedRuntimeToolNames = collectAcceptedRuntimeToolNames(runtimeTools, effectiveToolExposurePlan);
4730
+ const temperature = this.resolveTemperature(temperatureModelString ?? effectiveModel, providerOptions);
4731
+ const genAiProviderName = resolveRuntimeGenAiProviderName(effectiveModel);
4732
+ // Reject an over-budget request before the turn is accepted, so a
4733
+ // rejected input is rolled back instead of committed to memory.
4734
+ const { maxOutputTokens } = enforceProviderRequestBudget({
4735
+ model: languageModel,
4736
+ ...(reasoning === undefined ? {} : { reasoning }),
4737
+ ...(providerOptions === undefined ? {} : { providerOptions }),
4738
+ system: providerSystemPrompt,
4739
+ messages: providerMessages,
4740
+ tools: runtimeTools,
4741
+ responseFormat: outputSchema?.responseFormat,
4742
+ limit: fittedRequest.servedLimit,
4743
+ requestedOutputTokens,
4744
+ explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
4745
+ additionalOutputTokens,
4746
+ });
4747
+ await validateProviderRequest(providerSystemPrompt, modelMessages, { beforeAcceptance: fittedRequest.commitToolExposure });
4748
+ const streamLifecycleMode = resolveStreamLifecycleModeFromEnv();
4749
+ const streamModel = withRuntimeProviderStreamErrorProvenance(languageModel);
4750
+ const streamSource = createRuntimeStreamSource((streamSignal) => streamTextForObservedAgentRuntime({
4751
+ model: streamModel,
4752
+ system: providerSystemPrompt,
4753
+ messages: providerMessages,
4754
+ tools: runtimeTools,
4755
+ experimental_repairToolCall: repairToolCall,
4756
+ maxOutputTokens,
4757
+ ...(temperature === undefined ? {} : { temperature }),
4758
+ ...(this.config.parallelToolCalls === undefined
4759
+ ? {}
4760
+ : { parallelToolCalls: this.config.parallelToolCalls }),
4761
+ ...(headers ? { headers } : {}),
4762
+ ...(providerOptions ? { providerOptions } : {}),
4763
+ ...(reasoning ? { reasoning } : {}),
4764
+ ...(outputSchema ? { responseFormat: outputSchema.responseFormat } : {}),
4765
+ abortSignal: streamSignal,
4766
+ }));
4767
+ let state = createStreamState();
4768
+ // Hold a possible replay only while it remains a prefix of the text the
4769
+ // client already received. Once it diverges, resume live delivery.
4770
+ const deferInterruptedRecoveryOutput = step === interruptedLocalToolBatchRecoveryStep &&
4771
+ interruptedLocalToolBatchRecoveryText !== undefined;
4772
+ const deferredRecoveryOutput = deferInterruptedRecoveryOutput ? [] : undefined;
4773
+ const previousRecoveryText = interruptedLocalToolBatchRecoveryText ?? "";
4774
+ let deferredRecoverySseText = "";
4775
+ let deferredRecoveryCallbackText = "";
4776
+ let releasedDeferredRecoveryOutput = false;
4777
+ let releasedRecoveryReplacementTextPartId;
4778
+ let suppressedRecoveryReplayTextLength = 0;
4779
+ const stepTextPartId = textPartId === undefined || step === 0
4780
+ ? textPartId
4781
+ : `${textPartId}:step:${step}`;
4782
+ const replacementRecoveryTextPartId = stepTextPartId === undefined
4783
+ ? `recovery:step:${step}`
4784
+ : `${stepTextPartId}:recovery`;
4785
+ const remainingRecoveryReplayText = () => privateTextSlice(previousRecoveryText, suppressedRecoveryReplayTextLength);
4786
+ const flushDeferredRecoveryOutput = (interruptedRecoveryPrefixLength, repeatsInterruptedRecoveryText, useReplacementTextPartId) => {
4787
+ if (deferredRecoveryOutput === undefined)
4788
+ return;
4789
+ let remainingSsePrefixLength = interruptedRecoveryPrefixLength;
4790
+ let remainingCallbackPrefixLength = interruptedRecoveryPrefixLength;
4791
+ for (let outputIndex = 0; outputIndex < deferredRecoveryOutput.length; outputIndex++) {
4792
+ if (!ObjectHasOwn(deferredRecoveryOutput, outputIndex))
4793
+ continue;
4794
+ const output = deferredRecoveryOutput[outputIndex];
4795
+ if (repeatsInterruptedRecoveryText &&
4796
+ (output.kind === "callback" || output.isTextEvent)) {
4797
+ continue;
4798
+ }
4799
+ if (output.kind === "callback") {
4800
+ const stripped = stripLeadingText(output.chunk, remainingCallbackPrefixLength);
4801
+ remainingCallbackPrefixLength = stripped.remainingPrefixLength;
4802
+ if (stripped.text.length > 0) {
4803
+ callbacks?.onChunk?.(stripped.text);
4804
+ }
4805
+ }
4806
+ else {
4807
+ const textChunk = useReplacementTextPartId && output.isTextEvent
4808
+ ? rewriteRecoveryTextSseChunkId(output.chunk, replacementRecoveryTextPartId, encoder)
4809
+ : output.chunk;
4810
+ const stripped = output.isTextEvent
4811
+ ? stripTextDeltaPrefixFromSseChunk(textChunk, remainingSsePrefixLength, encoder)
4812
+ : { chunk: textChunk, remainingPrefixLength: remainingSsePrefixLength };
4813
+ remainingSsePrefixLength = stripped.remainingPrefixLength;
4814
+ if (stripped.chunk !== undefined) {
4815
+ enqueuePrivateStream(controller, stripped.chunk);
4816
+ }
4817
+ }
4818
+ }
4819
+ deferredRecoveryOutput.length = 0;
4492
4820
  };
4493
- const executionContext = applicationExecutionContext(toolContext,
4494
- // Partial results from the parked assistant turn were not yet provider-observed.
4495
- getProviderObservedSkillBodies(slicePrivateArray(currentMessages, 0, admittedTurn.start)));
4496
- let resumeAdmissionRejection;
4497
- try {
4498
- // The trusted parked call was already exposed in the prior segment.
4499
- // Recheck current authorization without requiring its lost step visibility.
4500
- const resumeExecutionAuthority = resolveToolExecutionAuthority({
4501
- toolName: resumeToolCall.name,
4502
- plan: {
4503
- ...effectiveToolExposurePlan,
4504
- visible: effectiveToolExposurePlan.authorized,
4505
- },
4506
- });
4507
- if (resumeExecutionAuthority === undefined) {
4508
- throw new Error(toolNotVisibleError(resumeToolCall.name));
4821
+ const releaseDeferredRecoveryOutputAfterDivergence = () => {
4822
+ if (deferredRecoveryOutput === undefined || releasedDeferredRecoveryOutput)
4823
+ return;
4824
+ const expectedReplayText = remainingRecoveryReplayText();
4825
+ const sseDiverged = !privateTextStartsWith(expectedReplayText, deferredRecoverySseText);
4826
+ const callbackDiverged = callbacks?.onChunk === undefined ||
4827
+ !privateTextStartsWith(expectedReplayText, deferredRecoveryCallbackText);
4828
+ if (!sseDiverged || !callbackDiverged)
4829
+ return;
4830
+ const observedRecoveryText = callbacks?.onChunk === undefined
4831
+ ? deferredRecoverySseText
4832
+ : deferredRecoveryCallbackText;
4833
+ const extendsPreviousRecoveryText = privateTextStartsWith(observedRecoveryText, expectedReplayText);
4834
+ flushDeferredRecoveryOutput(extendsPreviousRecoveryText ? expectedReplayText.length : 0, false, !extendsPreviousRecoveryText && suppressedRecoveryReplayTextLength === 0);
4835
+ if (extendsPreviousRecoveryText && suppressedRecoveryReplayTextLength > 0) {
4836
+ suppressedRecoveryReplayTextLength += expectedReplayText.length;
4509
4837
  }
4510
- const policyCheck = enforceSkillPolicy(resumeToolCall.name, {
4511
- activeSkillId: skillState.activeSkillId,
4512
- hasSubmittedFormInput: skillState.hasSubmittedFormInput,
4513
- skillToolAvailability: skillState.activeSkillToolAvailability,
4514
- toolInput: toolCall.args,
4515
- toolDefinition: resumeExecutionAuthority.toolDefinition,
4516
- });
4517
- if (!policyCheck.allowed)
4518
- throw new Error(policyCheck.error);
4519
- toolCall.args = applySkillDelegationOverridesToToolInput(resumeToolCall.name, toolCall.args, skillState.activeSkillDelegationOverrides, resolveConfiguredTool(this.config.tools, resumeToolCall.name, { agentId: this.id }) ??
4520
- undefined);
4521
- // Resuming a trusted parked tool is a real dispatch even before the
4522
- // next provider request. Validate before its effect, then retain that
4523
- // accepted history if later provider preparation fails.
4524
- try {
4525
- await validateProviderRequest(withAgentRunRuntimeContext(currentSystemPrompt, runRuntimeContext), currentMessages);
4838
+ if (!extendsPreviousRecoveryText && suppressedRecoveryReplayTextLength === 0) {
4839
+ releasedRecoveryReplacementTextPartId = replacementRecoveryTextPartId;
4526
4840
  }
4527
- catch (error) {
4528
- resumeAdmissionRejection = { error };
4529
- throw error;
4841
+ releasedDeferredRecoveryOutput = true;
4842
+ };
4843
+ const releaseDeferredRecoveryOutputAfterExactReplay = (isTextEvent) => {
4844
+ if (isTextEvent || deferredRecoveryOutput === undefined || releasedDeferredRecoveryOutput ||
4845
+ deferredRecoverySseText !== remainingRecoveryReplayText() ||
4846
+ (callbacks?.onChunk !== undefined &&
4847
+ deferredRecoveryCallbackText !== remainingRecoveryReplayText())) {
4848
+ return;
4530
4849
  }
4531
- callbacks?.onToolCall?.(toolCall);
4532
- const startTime = Date.now();
4533
- const result = await runWithToolCallOccurrenceDispatch(streamedCall, () => traceConfiguredToolExecution({
4534
- mode: "stream",
4535
- agentId: this.id,
4536
- toolName: resumeToolCall.name,
4537
- toolCallId: resumeToolCall.id,
4538
- args: toolCall.args,
4539
- admittedTurn,
4540
- owner: currentMessages,
4541
- prepareTerminalDispatch,
4542
- toolsConfig: this.config.tools,
4543
- context: executionContext,
4544
- allowedRemoteToolNames,
4545
- remoteToolSources,
4546
- sourceIntegrationPolicy,
4547
- frameworkLocalTools,
4548
- }));
4549
- throwIfAborted(abortSignal);
4550
- await this.notifyToolResult({
4551
- mode: "stream",
4552
- toolName: resumeToolCall.name,
4553
- toolCallId: resumeToolCall.id,
4554
- input: toolCall.args,
4555
- result,
4556
- context: executionContext,
4557
- });
4558
- const resultError = getToolResultError(result);
4559
- toolCall.status = resultError === undefined ? "completed" : "error";
4560
- toolCall.result = result;
4561
- toolCall.error = resultError;
4562
- toolCall.executionTime = Date.now() - startTime;
4563
- pushPrivateArray(toolCalls, toolCall);
4564
- if (resultError === undefined) {
4565
- sendSSE(controller, encoder, {
4566
- type: "tool-output-available",
4567
- toolCallId: resumeToolCall.id,
4568
- output: result,
4569
- ...(isDynamicTool(resumeToolCall.name, this.config.tools) ? { dynamic: true } : {}),
4570
- });
4850
+ flushDeferredRecoveryOutput(remainingRecoveryReplayText().length, false, false);
4851
+ releasedDeferredRecoveryOutput = true;
4852
+ };
4853
+ const releaseDeferredRecoveryNonTextOutput = (isTextEvent) => {
4854
+ if (isTextEvent || deferredRecoveryOutput === undefined || releasedDeferredRecoveryOutput) {
4855
+ return;
4571
4856
  }
4572
- else {
4573
- sendSSE(controller, encoder, {
4574
- type: "tool-output-error",
4575
- toolCallId: resumeToolCall.id,
4576
- errorText: resultError,
4577
- ...(isDynamicTool(resumeToolCall.name, this.config.tools) ? { dynamic: true } : {}),
4578
- });
4857
+ const retainedOutput = filterPrivateArray(deferredRecoveryOutput, (output) => output.kind === "callback" || output.isTextEvent);
4858
+ for (let outputIndex = 0; outputIndex < deferredRecoveryOutput.length; outputIndex++) {
4859
+ if (!ObjectHasOwn(deferredRecoveryOutput, outputIndex))
4860
+ continue;
4861
+ const output = deferredRecoveryOutput[outputIndex];
4862
+ if (output.kind === "sse" && !output.isTextEvent) {
4863
+ enqueuePrivateStream(controller, output.chunk);
4864
+ }
4579
4865
  }
4580
- const toolResultMessage = createPolicyAwareToolResultMessage(resumeToolCall.id, resumeToolCall.name, result, resumeExecutionAuthority.toolDefinition, false, true);
4581
- pushPrivateArray(currentMessages, toolResultMessage);
4582
- await persistMessage(toolResultMessage);
4583
- }
4584
- catch (error) {
4585
- // A turn admission rejection belongs to the request, not the tool:
4586
- // surface it as the request error instead of a tool-output-error.
4587
- if (resumeAdmissionRejection?.error === error)
4588
- throw error;
4589
- await this.recordTerminalToolResult(error, toolCall, persistMessage, currentMessages, toolCalls, totalUsage, { controller, encoder });
4590
- throwIfAborted(abortSignal);
4591
- await this.recordToolError(persistMessage, toolCall, error instanceof Error ? error.message : String(error), { controller, encoder }, currentMessages, toolCalls);
4592
- }
4593
- if (this.#persistRecoveryBoundary)
4594
- await pauseAtBoundary(step);
4595
- }
4596
- // Include the parked result delivered to this request, but exclude new same-step results.
4597
- const providerObservedSkillBodies = getProviderObservedSkillBodies(currentMessages);
4598
- const baseSystemPrompt = currentSystemPrompt;
4599
- const assembleRequest = (messages, plan = effectiveToolExposurePlan) => assembleRuntimeStepRequest({
4600
- messages,
4601
- plan,
4602
- toolResultContext,
4603
- supportsToolCalling,
4604
- agentWriteFinalResponseToolGuardEnabled,
4605
- providerTools,
4606
- model: effectiveModel,
4607
- languageModel,
4608
- toolSchemaOptions: resolveStepToolSchemaOptions(this.config, languageModel, effectiveModel),
4609
- baseSystemPrompt,
4610
- integrationToolDiscovery: preparedStep.integrationToolDiscovery,
4611
- runRuntimeContext,
4612
- bootstrapToolNames: preparedStep.configuredBootstrapToolNames,
4613
- });
4614
- const requestedOutputTokens = this.resolveMaxOutputTokens(effectiveModel, maxOutputTokensOverride);
4615
- // An invalid thinking budget is left for the provider path to reject.
4616
- const additionalOutputTokens = resolveAdditiveReasoningOutputTokens(languageModel, {
4617
- reasoning,
4618
- providerOptions,
4619
- }) ?? 0;
4620
- const fittedRequest = await fitRuntimeStepRequestToServedBudget({
4621
- sourceMessages: currentMessages,
4622
- modelMessages: toolResultContext
4623
- ? createModelToolResultContextMessages(currentMessages, toolResultContext)
4624
- : currentMessages,
4625
- plan: effectiveToolExposurePlan,
4626
- assembleRequest,
4627
- toolResultContext,
4628
- supportsToolCalling,
4629
- deferredToolLoading: toolLoadingResolution.mode === "deferred",
4630
- languageModel,
4631
- model: effectiveModel,
4632
- reasoning,
4633
- providerOptions,
4634
- responseFormat: outputSchema?.responseFormat,
4635
- requestedOutputTokens,
4636
- explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
4637
- additionalOutputTokens,
4638
- toolExposureState,
4639
- persistToolExposureCheckpoint,
4640
- requireToolExposureCheckpointPersistence,
4641
- turnAdmission: validateProviderRequest,
4642
- });
4643
- const modelMessages = fittedRequest.modelMessages;
4644
- effectiveToolExposurePlan = fittedRequest.plan;
4645
- if (this.#persistRecoveryBoundary) {
4646
- await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
4647
- throwIfAborted(abortSignal);
4648
- }
4649
- const request = assembleRequest(modelMessages);
4650
- currentSystemPrompt = request.systemPrompt;
4651
- const { tools, runtimeTools, providerSystemPrompt, providerMessages } = request;
4652
- setOtelActiveSpanAttributes({
4653
- "tool.loading.mode": resolveRuntimeToolLoading(runtimeStepConfig).mode,
4654
- "tool.loading.provenance": toolLoadingResolution.provenance,
4655
- "tool.catalog.authorized_count": preparedStep.toolExposurePlan.authorized.length,
4656
- "tool.catalog.visible_count": tools.length,
4657
- "tool.catalog.deferred_count": effectiveToolExposurePlan.deferred.length,
4658
- "tool.loading.path": "framework-fallback",
4659
- });
4660
- const runtimeToolNames = Object.keys(runtimeTools ?? {}).sort(compareStrings);
4661
- const acceptedRuntimeToolNames = collectAcceptedRuntimeToolNames(runtimeTools, effectiveToolExposurePlan);
4662
- const temperature = this.resolveTemperature(temperatureModelString ?? effectiveModel, providerOptions);
4663
- const genAiProviderName = resolveRuntimeGenAiProviderName(effectiveModel);
4664
- // Reject an over-budget request before the turn is accepted, so a
4665
- // rejected input is rolled back instead of committed to memory.
4666
- const { maxOutputTokens } = enforceProviderRequestBudget({
4667
- model: languageModel,
4668
- ...(reasoning === undefined ? {} : { reasoning }),
4669
- ...(providerOptions === undefined ? {} : { providerOptions }),
4670
- system: providerSystemPrompt,
4671
- messages: providerMessages,
4672
- tools: runtimeTools,
4673
- responseFormat: outputSchema?.responseFormat,
4674
- limit: fittedRequest.servedLimit,
4675
- requestedOutputTokens,
4676
- explicitOutputTokens: isMaxOutputTokensOverride(maxOutputTokensOverride),
4677
- additionalOutputTokens,
4678
- });
4679
- await validateProviderRequest(providerSystemPrompt, modelMessages, { beforeAcceptance: fittedRequest.commitToolExposure });
4680
- const streamLifecycleMode = resolveStreamLifecycleModeFromEnv();
4681
- const streamModel = withRuntimeProviderStreamErrorProvenance(languageModel);
4682
- const streamSource = createRuntimeStreamSource((streamSignal) => streamTextForObservedAgentRuntime({
4683
- model: streamModel,
4684
- system: providerSystemPrompt,
4685
- messages: providerMessages,
4686
- tools: runtimeTools,
4687
- experimental_repairToolCall: repairToolCall,
4688
- maxOutputTokens,
4689
- ...(temperature === undefined ? {} : { temperature }),
4690
- ...(this.config.parallelToolCalls === undefined
4691
- ? {}
4692
- : { parallelToolCalls: this.config.parallelToolCalls }),
4693
- ...(headers ? { headers } : {}),
4694
- ...(providerOptions ? { providerOptions } : {}),
4695
- ...(reasoning ? { reasoning } : {}),
4696
- ...(outputSchema ? { responseFormat: outputSchema.responseFormat } : {}),
4697
- abortSignal: streamSignal,
4698
- }));
4699
- let state = createStreamState();
4700
- // Hold a possible replay only while it remains a prefix of the text the
4701
- // client already received. Once it diverges, resume live delivery.
4702
- const deferInterruptedRecoveryOutput = step === interruptedLocalToolBatchRecoveryStep &&
4703
- interruptedLocalToolBatchRecoveryText !== undefined;
4704
- const deferredRecoveryOutput = deferInterruptedRecoveryOutput ? [] : undefined;
4705
- const previousRecoveryText = interruptedLocalToolBatchRecoveryText ?? "";
4706
- let deferredRecoverySseText = "";
4707
- let deferredRecoveryCallbackText = "";
4708
- let releasedDeferredRecoveryOutput = false;
4709
- let releasedRecoveryReplacementTextPartId;
4710
- let suppressedRecoveryReplayTextLength = 0;
4711
- const stepTextPartId = textPartId === undefined || step === 0
4712
- ? textPartId
4713
- : `${textPartId}:step:${step}`;
4714
- const replacementRecoveryTextPartId = stepTextPartId === undefined
4715
- ? `recovery:step:${step}`
4716
- : `${stepTextPartId}:recovery`;
4717
- const remainingRecoveryReplayText = () => privateTextSlice(previousRecoveryText, suppressedRecoveryReplayTextLength);
4718
- const flushDeferredRecoveryOutput = (interruptedRecoveryPrefixLength, repeatsInterruptedRecoveryText, useReplacementTextPartId) => {
4719
- if (deferredRecoveryOutput === undefined)
4720
- return;
4721
- let remainingSsePrefixLength = interruptedRecoveryPrefixLength;
4722
- let remainingCallbackPrefixLength = interruptedRecoveryPrefixLength;
4723
- for (let outputIndex = 0; outputIndex < deferredRecoveryOutput.length; outputIndex++) {
4724
- if (!ObjectHasOwn(deferredRecoveryOutput, outputIndex))
4725
- continue;
4726
- const output = deferredRecoveryOutput[outputIndex];
4727
- if (repeatsInterruptedRecoveryText &&
4728
- (output.kind === "callback" || output.isTextEvent)) {
4729
- continue;
4866
+ deferredRecoveryOutput.length = 0;
4867
+ appendPrivateArray(deferredRecoveryOutput, retainedOutput);
4868
+ };
4869
+ const reconcileDeferredRecoveryTextSegment = (isTextEndEvent) => {
4870
+ if (!isTextEndEvent || deferredRecoveryOutput === undefined ||
4871
+ releasedDeferredRecoveryOutput ||
4872
+ !privateTextStartsWith(remainingRecoveryReplayText(), deferredRecoverySseText) ||
4873
+ (callbacks?.onChunk !== undefined &&
4874
+ !privateTextStartsWith(remainingRecoveryReplayText(), deferredRecoveryCallbackText))) {
4875
+ return;
4730
4876
  }
4731
- if (output.kind === "callback") {
4732
- const stripped = stripLeadingText(output.chunk, remainingCallbackPrefixLength);
4733
- remainingCallbackPrefixLength = stripped.remainingPrefixLength;
4734
- if (stripped.text.length > 0) {
4735
- callbacks?.onChunk?.(stripped.text);
4736
- }
4877
+ // A tool or reasoning event closes the current text segment. If that
4878
+ // segment only replayed a prefix the client already received, discard
4879
+ // it now and treat later text as a distinct segment. Otherwise retained
4880
+ // prefix events could be released after the boundary when later text
4881
+ // diverges, duplicating and reordering the replay.
4882
+ suppressedRecoveryReplayTextLength += deferredRecoverySseText.length;
4883
+ deferredRecoveryOutput.length = 0;
4884
+ deferredRecoverySseText = "";
4885
+ deferredRecoveryCallbackText = "";
4886
+ };
4887
+ let sawProviderStreamEvent = false;
4888
+ const enqueueStepChunk = (chunk) => {
4889
+ sawProviderStreamEvent = true;
4890
+ enqueuePrivateStream(controller, chunk);
4891
+ };
4892
+ const stepController = deferredRecoveryOutput === undefined
4893
+ ? {
4894
+ enqueue: enqueueStepChunk,
4895
+ }
4896
+ : {
4897
+ enqueue(chunk) {
4898
+ sawProviderStreamEvent = true;
4899
+ if (releasedDeferredRecoveryOutput) {
4900
+ enqueuePrivateStream(controller, releasedRecoveryReplacementTextPartId !== undefined
4901
+ ? rewriteRecoveryTextSseChunkId(chunk, releasedRecoveryReplacementTextPartId, encoder)
4902
+ : chunk);
4903
+ return;
4904
+ }
4905
+ deferredRecoverySseText += textDeltaFromSseChunk(chunk) ?? "";
4906
+ const isTextEvent = isTextSseChunk(chunk);
4907
+ pushPrivateArray(deferredRecoveryOutput, {
4908
+ kind: "sse",
4909
+ chunk,
4910
+ isTextEvent,
4911
+ });
4912
+ releaseDeferredRecoveryOutputAfterDivergence();
4913
+ releaseDeferredRecoveryOutputAfterExactReplay(isTextEvent);
4914
+ releaseDeferredRecoveryNonTextOutput(isTextEvent);
4915
+ reconcileDeferredRecoveryTextSegment(isTextEndSseChunk(chunk));
4916
+ },
4917
+ };
4918
+ bindRuntimeStreamForwardingController(stepController, controller);
4919
+ let providerStreamRetryAttempts = 0;
4920
+ let sawProviderStreamUsage = false;
4921
+ while (true) {
4922
+ try {
4923
+ await processStream(streamSource, state, stepController, encoder, stepTextPartId, {
4924
+ onChunk: deferredRecoveryOutput === undefined ? callbacks?.onChunk : (chunk) => {
4925
+ if (releasedDeferredRecoveryOutput) {
4926
+ callbacks?.onChunk?.(chunk);
4927
+ return;
4928
+ }
4929
+ deferredRecoveryCallbackText += chunk;
4930
+ if (callbacks?.onChunk !== undefined) {
4931
+ pushPrivateArray(deferredRecoveryOutput, { kind: "callback", chunk });
4932
+ }
4933
+ releaseDeferredRecoveryOutputAfterDivergence();
4934
+ },
4935
+ ...(runtimeObservationStepId !== undefined &&
4936
+ runtimeObservationMessageSpanId !== undefined
4937
+ ? {
4938
+ runtimeObservationStepId,
4939
+ runtimeObservationMessageSpanId,
4940
+ }
4941
+ : {}),
4942
+ onUsage: (usage) => {
4943
+ sawProviderStreamUsage = true;
4944
+ accumulateUsage(totalUsage, usage);
4945
+ // Snapshot, not the live object: a later step must not mutate a total
4946
+ // a caller has already recorded on a span.
4947
+ callbacks?.onUsage?.({ ...totalUsage });
4948
+ },
4949
+ streamRequiresFinish: languageModel.runtimeCapabilities?.streamRequiresFinish === true,
4950
+ requireProviderFinish: languageModel.runtimeCapabilities?.toolCallStreamRequiresFinish === true,
4951
+ providerExecutedToolNames: getProviderExecutedToolNames(runtimeTools),
4952
+ availableToolNames: acceptedRuntimeToolNames,
4953
+ dynamicToolNames: runtimeToolNames.filter((name) => isDynamicTool(name, this.config.tools)),
4954
+ streamLifecycleMode,
4955
+ ...(this.#modelStreamTimeouts
4956
+ ? { streamLifecyclePolicy: { ...this.#modelStreamTimeouts } }
4957
+ : {}),
4958
+ traceSpanName: `chat ${effectiveModel}`,
4959
+ traceAttributes: {
4960
+ ...(genAiProviderName ? { "gen_ai.provider.name": genAiProviderName } : {}),
4961
+ "gen_ai.request.model": effectiveModel,
4962
+ "gen_ai.response.model": effectiveModel,
4963
+ "gen_ai.request.max_tokens": maxOutputTokens,
4964
+ "gen_ai.output.type": "text",
4965
+ ...(temperature === undefined ? {} : { "gen_ai.request.temperature": temperature }),
4966
+ },
4967
+ }, abortSignal);
4968
+ break;
4737
4969
  }
4738
- else {
4739
- const textChunk = useReplacementTextPartId && output.isTextEvent
4740
- ? rewriteRecoveryTextSseChunkId(output.chunk, replacementRecoveryTextPartId, encoder)
4741
- : output.chunk;
4742
- const stripped = output.isTextEvent
4743
- ? stripTextDeltaPrefixFromSseChunk(textChunk, remainingSsePrefixLength, encoder)
4744
- : { chunk: textChunk, remainingPrefixLength: remainingSsePrefixLength };
4745
- remainingSsePrefixLength = stripped.remainingPrefixLength;
4746
- if (stripped.chunk !== undefined) {
4747
- enqueuePrivateStream(controller, stripped.chunk);
4970
+ catch (error) {
4971
+ if (!shouldRetryRuntimeProviderStreamFailure({
4972
+ error,
4973
+ state,
4974
+ attempts: providerStreamRetryAttempts,
4975
+ abortSignal,
4976
+ hasUsageProgress: sawProviderStreamUsage,
4977
+ hasEmittedStreamEvent: sawProviderStreamEvent,
4978
+ })) {
4979
+ throw error;
4748
4980
  }
4981
+ providerStreamRetryAttempts += 1;
4982
+ state = createStreamState();
4749
4983
  }
4750
4984
  }
4751
- deferredRecoveryOutput.length = 0;
4752
- };
4753
- const releaseDeferredRecoveryOutputAfterDivergence = () => {
4754
- if (deferredRecoveryOutput === undefined || releasedDeferredRecoveryOutput)
4755
- return;
4756
- const expectedReplayText = remainingRecoveryReplayText();
4757
- const sseDiverged = !privateTextStartsWith(expectedReplayText, deferredRecoverySseText);
4758
- const callbackDiverged = callbacks?.onChunk === undefined ||
4759
- !privateTextStartsWith(expectedReplayText, deferredRecoveryCallbackText);
4760
- if (!sseDiverged || !callbackDiverged)
4761
- return;
4762
- const observedRecoveryText = callbacks?.onChunk === undefined
4763
- ? deferredRecoverySseText
4764
- : deferredRecoveryCallbackText;
4765
- const extendsPreviousRecoveryText = privateTextStartsWith(observedRecoveryText, expectedReplayText);
4766
- flushDeferredRecoveryOutput(extendsPreviousRecoveryText ? expectedReplayText.length : 0, false, !extendsPreviousRecoveryText && suppressedRecoveryReplayTextLength === 0);
4767
- if (extendsPreviousRecoveryText && suppressedRecoveryReplayTextLength > 0) {
4768
- suppressedRecoveryReplayTextLength += expectedReplayText.length;
4769
- }
4770
- if (!extendsPreviousRecoveryText && suppressedRecoveryReplayTextLength === 0) {
4771
- releasedRecoveryReplacementTextPartId = replacementRecoveryTextPartId;
4985
+ throwIfAborted(abortSignal);
4986
+ const interruptedRecoveryPrefixLength = deferredRecoveryOutput === undefined
4987
+ ? 0
4988
+ : privateTextStartsWith(state.accumulatedText, previousRecoveryText)
4989
+ ? previousRecoveryText.length
4990
+ : privateTextStartsWith(previousRecoveryText, state.accumulatedText)
4991
+ ? state.accumulatedText.length
4992
+ : 0;
4993
+ const recoveryPresentationPrefixLength = suppressedRecoveryReplayTextLength > 0
4994
+ ? suppressedRecoveryReplayTextLength
4995
+ : interruptedRecoveryPrefixLength;
4996
+ const recoveryPresentationText = privateTextSlice(state.accumulatedText, recoveryPresentationPrefixLength);
4997
+ const repeatsInterruptedRecoveryText = interruptedRecoveryPrefixLength > 0 &&
4998
+ recoveryPresentationText.length === 0;
4999
+ if (deferredRecoveryOutput !== undefined && !releasedDeferredRecoveryOutput) {
5000
+ flushDeferredRecoveryOutput(interruptedRecoveryPrefixLength, repeatsInterruptedRecoveryText, previousRecoveryText.length > 0 && interruptedRecoveryPrefixLength === 0 &&
5001
+ !repeatsInterruptedRecoveryText && state.accumulatedText.length > 0);
4772
5002
  }
4773
- releasedDeferredRecoveryOutput = true;
4774
- };
4775
- const releaseDeferredRecoveryOutputAfterExactReplay = (isTextEvent) => {
4776
- if (isTextEvent || deferredRecoveryOutput === undefined || releasedDeferredRecoveryOutput ||
4777
- deferredRecoverySseText !== remainingRecoveryReplayText() ||
4778
- (callbacks?.onChunk !== undefined &&
4779
- deferredRecoveryCallbackText !== remainingRecoveryReplayText())) {
4780
- return;
5003
+ finalFinishReason = state.finishReason ?? finalFinishReason;
5004
+ const streamedToolCalls = [];
5005
+ for (const toolCall of state.toolCalls.values()) {
5006
+ pushPrivateArray(streamedToolCalls, toolCall);
4781
5007
  }
4782
- flushDeferredRecoveryOutput(remainingRecoveryReplayText().length, false, false);
4783
- releasedDeferredRecoveryOutput = true;
4784
- };
4785
- const releaseDeferredRecoveryNonTextOutput = (isTextEvent) => {
4786
- if (isTextEvent || deferredRecoveryOutput === undefined || releasedDeferredRecoveryOutput) {
4787
- return;
5008
+ const finalToolResults = collectFinalStreamToolResults(state);
5009
+ // Recovery replays the whole step, so it also re-emits this step's
5010
+ // reasoning — duplicating it in the live stream and in history, with a
5011
+ // signature that no longer matches the replayed content. Reasoning that
5012
+ // was persisted is reasoning the client already saw, so fail closed.
5013
+ // This is a stopgap: reasoning is default-on across the hosted catalog,
5014
+ // which makes recovery inert on most hosted paths. See #3736 for the
5015
+ // reconciliation protocol that would let it run again.
5016
+ const persistedReasoningParts = filterPrivateArray(state.reasoningParts, isPersistedReasoningPart);
5017
+ const hasExposedReasoning = persistedReasoningParts.length > 0;
5018
+ const canRecoverInterruptedLocalToolBatch = !recoveredInterruptedLocalToolBatch &&
5019
+ step + 1 < maxSteps &&
5020
+ !hasExposedReasoning;
5021
+ const shouldContinue = shouldContinueAfterStreamStep(state, {
5022
+ recoverInterruptedToolCalls: canRecoverInterruptedLocalToolBatch,
5023
+ });
5024
+ const stoppedEmptyAfterCompletedTool = state.finishReason === "stop" &&
5025
+ !hasSubstantiveAssistantText(state.accumulatedText) &&
5026
+ state.toolCalls.size === 0 &&
5027
+ finalToolResults.size === 0 &&
5028
+ (state.suppressedToolCalls?.length ?? 0) === 0 &&
5029
+ somePrivateArray(toolCalls, (toolCall) => toolCall.status === "completed");
5030
+ const shouldRecoverInterruptedLocalToolBatch = canRecoverInterruptedLocalToolBatch &&
5031
+ shouldContinue &&
5032
+ somePrivateArray(streamedToolCalls, isInterruptedClientToolCall);
5033
+ const exhaustedStepBudgetDuringInterruptedLocalToolRecovery = !recoveredInterruptedLocalToolBatch &&
5034
+ step + 1 >= maxSteps &&
5035
+ !hasExposedReasoning &&
5036
+ somePrivateArray(streamedToolCalls, isInterruptedClientToolCall) &&
5037
+ shouldContinueAfterStreamStep(state, { recoverInterruptedToolCalls: true });
5038
+ // Exactly `shouldRecoverInterruptedLocalToolBatch` with the reasoning
5039
+ // gate lifted: the batch this step would have replayed had it not
5040
+ // already exposed reasoning. Re-asking is what separates "recovery was
5041
+ // declined" from "this step merely carried reasoning";
5042
+ // `shouldContinueAfterStreamStep` only reads state, so asking twice has
5043
+ // no side effects, and the cheap conditions short-circuit ahead of it.
5044
+ const declinedRecoveryForExposedReasoning = hasExposedReasoning &&
5045
+ !recoveredInterruptedLocalToolBatch &&
5046
+ step + 1 < maxSteps &&
5047
+ somePrivateArray(streamedToolCalls, isInterruptedClientToolCall) &&
5048
+ shouldContinueAfterStreamStep(state, { recoverInterruptedToolCalls: true });
5049
+ if (declinedRecoveryForExposedReasoning) {
5050
+ logger.warn("Declined interrupted local tool batch recovery after exposed reasoning", {
5051
+ step,
5052
+ toolName: filterPrivateArray(streamedToolCalls, isInterruptedClientToolCall)[0]?.name,
5053
+ reasoningPartCount: persistedReasoningParts.length,
5054
+ });
4788
5055
  }
4789
- const retainedOutput = filterPrivateArray(deferredRecoveryOutput, (output) => output.kind === "callback" || output.isTextEvent);
4790
- for (let outputIndex = 0; outputIndex < deferredRecoveryOutput.length; outputIndex++) {
4791
- if (!ObjectHasOwn(deferredRecoveryOutput, outputIndex))
5056
+ const assistantMessage = buildStreamedAssistantMessage({
5057
+ ...state,
5058
+ accumulatedText: recoveryPresentationText,
5059
+ }, {
5060
+ id: `msg_${Date.now()}_${step}`,
5061
+ timestamp: Date.now(),
5062
+ }, {
5063
+ preserveRecoverablePlaceholderToolCalls: shouldRecoverInterruptedLocalToolBatch ||
5064
+ !shouldContinue,
5065
+ });
5066
+ attachProviderMetadata(assistantMessage, await reconcileSuppressedProviderMetadata(languageModel, state.providerMetadata, state.suppressedToolCalls, state.toolCalls.size > 0, abortSignal));
5067
+ for (const tc of state.toolCalls.values()) {
5068
+ const materialized = materializeStreamedToolCall(tc);
5069
+ if (materialized.kind === "incomplete" && isRecoverablePlaceholderToolCall(tc)) {
5070
+ // Provisional empty-object placeholder that never finalized. The
5071
+ // model never committed arguments. Preserve it when recovery or
5072
+ // terminalization records a matching tool result; otherwise the
5073
+ // assistant message builder can omit it beside final text. Surface no
5074
+ // input warning or error for the provisional fragment.
4792
5075
  continue;
4793
- const output = deferredRecoveryOutput[outputIndex];
4794
- if (output.kind === "sse" && !output.isTextEvent) {
4795
- enqueuePrivateStream(controller, output.chunk);
5076
+ }
5077
+ if (materialized.kind === "incomplete") {
5078
+ // Stream terminated before the provider emitted the finalizing
5079
+ // `tool-call` event for this block. The model never committed this
5080
+ // tool use. Surface the failure via SSE so the live client can
5081
+ // react, and leave the partial fragment under `inputText` in the
5082
+ // persisted part above so the history is replayable and transparent.
5083
+ logger.warn("Streamed tool call terminated before tool-call event", {
5084
+ toolCallId: tc.id,
5085
+ toolName: tc.name,
5086
+ partialArgumentsLength: materialized.partialArgumentsLength,
5087
+ partialArgumentsPreview: materialized.partialArgumentsPreview,
5088
+ });
5089
+ if (tc.inputAnnounced === true) {
5090
+ const dynamicIncomplete = isDynamicTool(tc.name, this.config.tools);
5091
+ sendSSE(controller, encoder, {
5092
+ type: "tool-input-error",
5093
+ toolCallId: tc.id,
5094
+ errorText: `Stream terminated before tool-call event fired for "${tc.name}". ` +
5095
+ `Received ${materialized.partialArgumentsLength} chars of partial tool-input deltas.`,
5096
+ ...(dynamicIncomplete ? { dynamic: true } : {}),
5097
+ });
5098
+ }
5099
+ }
5100
+ else if (materialized.kind === "parse-error") {
5101
+ logger.warn("Failed to parse streamed tool arguments", {
5102
+ toolCallId: tc.id,
5103
+ error: materialized.parseError,
5104
+ });
4796
5105
  }
4797
5106
  }
4798
- deferredRecoveryOutput.length = 0;
4799
- appendPrivateArray(deferredRecoveryOutput, retainedOutput);
4800
- };
4801
- const reconcileDeferredRecoveryTextSegment = (isTextEndEvent) => {
4802
- if (!isTextEndEvent || deferredRecoveryOutput === undefined ||
4803
- releasedDeferredRecoveryOutput ||
4804
- !privateTextStartsWith(remainingRecoveryReplayText(), deferredRecoverySseText) ||
4805
- (callbacks?.onChunk !== undefined &&
4806
- !privateTextStartsWith(remainingRecoveryReplayText(), deferredRecoveryCallbackText))) {
4807
- return;
5107
+ const stepAssistantText = getTextFromParts(assistantMessage.parts);
5108
+ if (step === interruptedLocalToolBatchRecoveryStep &&
5109
+ suppressedRecoveryReplayTextLength > 0) {
5110
+ latestAssistantText = `${previousRecoveryText}${recoveryPresentationText}`;
4808
5111
  }
4809
- // A tool or reasoning event closes the current text segment. If that
4810
- // segment only replayed a prefix the client already received, discard
4811
- // it now and treat later text as a distinct segment. Otherwise retained
4812
- // prefix events could be released after the boundary when later text
4813
- // diverges, duplicating and reordering the replay.
4814
- suppressedRecoveryReplayTextLength += deferredRecoverySseText.length;
4815
- deferredRecoveryOutput.length = 0;
4816
- deferredRecoverySseText = "";
4817
- deferredRecoveryCallbackText = "";
4818
- };
4819
- let sawProviderStreamEvent = false;
4820
- const enqueueStepChunk = (chunk) => {
4821
- sawProviderStreamEvent = true;
4822
- enqueuePrivateStream(controller, chunk);
4823
- };
4824
- const stepController = deferredRecoveryOutput === undefined
4825
- ? {
4826
- enqueue: enqueueStepChunk,
5112
+ else if (step === interruptedLocalToolBatchRecoveryStep && interruptedRecoveryPrefixLength > 0) {
5113
+ latestAssistantText = privateTextStartsWith(previousRecoveryText, state.accumulatedText)
5114
+ ? previousRecoveryText
5115
+ : state.accumulatedText;
4827
5116
  }
4828
- : {
4829
- enqueue(chunk) {
4830
- sawProviderStreamEvent = true;
4831
- if (releasedDeferredRecoveryOutput) {
4832
- enqueuePrivateStream(controller, releasedRecoveryReplacementTextPartId !== undefined
4833
- ? rewriteRecoveryTextSseChunkId(chunk, releasedRecoveryReplacementTextPartId, encoder)
4834
- : chunk);
4835
- return;
5117
+ else if (hasSubstantiveAssistantText(stepAssistantText) ||
5118
+ step !== interruptedLocalToolBatchRecoveryStep) {
5119
+ latestAssistantText = stepAssistantText;
5120
+ }
5121
+ const streamedInvokeAgentBatch = providerReplayCheckpointEmission.complete
5122
+ ? collectStreamedParallelInvokeAgentToolCalls(streamedToolCalls, finalToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, shouldContinue, effectiveToolExposurePlan, {
5123
+ activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
5124
+ toolsConfig: this.config.tools,
5125
+ agentId: this.id,
5126
+ hasToolReplacements: false,
5127
+ })
5128
+ : undefined;
5129
+ const streamedSkillDelegationOrder = streamedInvokeAgentBatch
5130
+ ? streamedSameTurnSkillDelegationOrder(streamedToolCalls, finalToolResults, providerReplayCheckpointEmission.invokeAgentToolNames)
5131
+ : undefined;
5132
+ let streamedBatchCompletionDeferred = streamedSkillDelegationOrder === "prefix";
5133
+ const admittedTurn = snapshotAdmittedToolTurn(assistantMessage, currentMessages.length);
5134
+ pushPrivateArray(currentMessages, assistantMessage);
5135
+ await persistMessage(assistantMessage);
5136
+ await persistProviderReplayCheckpointAfterTurn({
5137
+ emission: providerReplayCheckpointEmission,
5138
+ providerMetadata: readAttachedProviderMetadata(assistantMessage),
5139
+ invokeAgentToolCalls: streamedSkillDelegationOrder === "interleaved"
5140
+ ? undefined
5141
+ : streamedInvokeAgentBatch,
5142
+ deferCompletion: streamedBatchCompletionDeferred,
5143
+ });
5144
+ if (this.#persistRecoveryBoundary) {
5145
+ // Recovery snapshots require an authenticated admission for every local
5146
+ // call in this turn, including calls interrupted before input finalized.
5147
+ // Announce them before waiting for persistence so both retry and terminal
5148
+ // paths can acknowledge the matching error without an admission cycle.
5149
+ for (const toolCall of streamedToolCalls) {
5150
+ if (toolCall.providerExecuted !== true && isStreamedToolCallIncomplete(toolCall) &&
5151
+ !finalToolResults.has(toolCall.id)) {
5152
+ announceStreamedToolCallInput(controller, encoder, toolCall, (name) => isDynamicTool(name, this.config.tools));
4836
5153
  }
4837
- deferredRecoverySseText += textDeltaFromSseChunk(chunk) ?? "";
4838
- const isTextEvent = isTextSseChunk(chunk);
4839
- pushPrivateArray(deferredRecoveryOutput, {
4840
- kind: "sse",
4841
- chunk,
4842
- isTextEvent,
4843
- });
4844
- releaseDeferredRecoveryOutputAfterDivergence();
4845
- releaseDeferredRecoveryOutputAfterExactReplay(isTextEvent);
4846
- releaseDeferredRecoveryNonTextOutput(isTextEvent);
4847
- reconcileDeferredRecoveryTextSegment(isTextEndSseChunk(chunk));
4848
- },
4849
- };
4850
- bindRuntimeStreamForwardingController(stepController, controller);
4851
- let providerStreamRetryAttempts = 0;
4852
- let sawProviderStreamUsage = false;
4853
- while (true) {
4854
- try {
4855
- await processStream(streamSource, state, stepController, encoder, stepTextPartId, {
4856
- onChunk: deferredRecoveryOutput === undefined ? callbacks?.onChunk : (chunk) => {
4857
- if (releasedDeferredRecoveryOutput) {
4858
- callbacks?.onChunk?.(chunk);
4859
- return;
4860
- }
4861
- deferredRecoveryCallbackText += chunk;
4862
- if (callbacks?.onChunk !== undefined) {
4863
- pushPrivateArray(deferredRecoveryOutput, { kind: "callback", chunk });
4864
- }
4865
- releaseDeferredRecoveryOutputAfterDivergence();
4866
- },
4867
- ...(runtimeObservationStepId !== undefined &&
4868
- runtimeObservationMessageSpanId !== undefined
5154
+ }
5155
+ await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
5156
+ }
5157
+ if (stoppedEmptyAfterCompletedTool) {
5158
+ sendSSE(controller, encoder, {
5159
+ type: "step-end",
5160
+ ...(runtimeObservationStepId
4869
5161
  ? {
4870
- runtimeObservationStepId,
4871
- runtimeObservationMessageSpanId,
5162
+ privateRuntimeObservation: {
5163
+ version: 1,
5164
+ kind: "step_ended",
5165
+ stepId: runtimeObservationStepId,
5166
+ },
4872
5167
  }
4873
5168
  : {}),
4874
- onUsage: (usage) => {
4875
- sawProviderStreamUsage = true;
4876
- accumulateUsage(totalUsage, usage);
4877
- // Snapshot, not the live object: a later step must not mutate a total
4878
- // a caller has already recorded on a span.
4879
- callbacks?.onUsage?.({ ...totalUsage });
4880
- },
4881
- streamRequiresFinish: languageModel.runtimeCapabilities?.streamRequiresFinish === true,
4882
- requireProviderFinish: languageModel.runtimeCapabilities?.toolCallStreamRequiresFinish === true,
4883
- providerExecutedToolNames: getProviderExecutedToolNames(runtimeTools),
4884
- availableToolNames: acceptedRuntimeToolNames,
4885
- dynamicToolNames: runtimeToolNames.filter((name) => isDynamicTool(name, this.config.tools)),
4886
- streamLifecycleMode,
4887
- ...(this.#modelStreamTimeouts
4888
- ? { streamLifecyclePolicy: { ...this.#modelStreamTimeouts } }
4889
- : {}),
4890
- traceSpanName: `chat ${effectiveModel}`,
4891
- traceAttributes: {
4892
- ...(genAiProviderName ? { "gen_ai.provider.name": genAiProviderName } : {}),
4893
- "gen_ai.request.model": effectiveModel,
4894
- "gen_ai.response.model": effectiveModel,
4895
- "gen_ai.request.max_tokens": maxOutputTokens,
4896
- "gen_ai.output.type": "text",
4897
- ...(temperature === undefined ? {} : { "gen_ai.request.temperature": temperature }),
4898
- },
4899
- }, abortSignal);
4900
- break;
4901
- }
4902
- catch (error) {
4903
- if (!shouldRetryRuntimeProviderStreamFailure({
4904
- error,
4905
- state,
4906
- attempts: providerStreamRetryAttempts,
4907
- abortSignal,
4908
- hasUsageProgress: sawProviderStreamUsage,
4909
- hasEmittedStreamEvent: sawProviderStreamEvent,
4910
- })) {
4911
- throw error;
5169
+ });
5170
+ if (recoveredEmptyResponse || step + 1 >= maxSteps) {
5171
+ throw new RuntimeEmptyResponseError();
4912
5172
  }
4913
- providerStreamRetryAttempts += 1;
4914
- state = createStreamState();
4915
- }
4916
- }
4917
- throwIfAborted(abortSignal);
4918
- const interruptedRecoveryPrefixLength = deferredRecoveryOutput === undefined
4919
- ? 0
4920
- : privateTextStartsWith(state.accumulatedText, previousRecoveryText)
4921
- ? previousRecoveryText.length
4922
- : privateTextStartsWith(previousRecoveryText, state.accumulatedText)
4923
- ? state.accumulatedText.length
4924
- : 0;
4925
- const recoveryPresentationPrefixLength = suppressedRecoveryReplayTextLength > 0
4926
- ? suppressedRecoveryReplayTextLength
4927
- : interruptedRecoveryPrefixLength;
4928
- const recoveryPresentationText = privateTextSlice(state.accumulatedText, recoveryPresentationPrefixLength);
4929
- const repeatsInterruptedRecoveryText = interruptedRecoveryPrefixLength > 0 &&
4930
- recoveryPresentationText.length === 0;
4931
- if (deferredRecoveryOutput !== undefined && !releasedDeferredRecoveryOutput) {
4932
- flushDeferredRecoveryOutput(interruptedRecoveryPrefixLength, repeatsInterruptedRecoveryText, previousRecoveryText.length > 0 && interruptedRecoveryPrefixLength === 0 &&
4933
- !repeatsInterruptedRecoveryText && state.accumulatedText.length > 0);
4934
- }
4935
- finalFinishReason = state.finishReason ?? finalFinishReason;
4936
- const streamedToolCalls = [];
4937
- for (const toolCall of state.toolCalls.values()) {
4938
- pushPrivateArray(streamedToolCalls, toolCall);
4939
- }
4940
- const finalToolResults = collectFinalStreamToolResults(state);
4941
- // Recovery replays the whole step, so it also re-emits this step's
4942
- // reasoning — duplicating it in the live stream and in history, with a
4943
- // signature that no longer matches the replayed content. Reasoning that
4944
- // was persisted is reasoning the client already saw, so fail closed.
4945
- // This is a stopgap: reasoning is default-on across the hosted catalog,
4946
- // which makes recovery inert on most hosted paths. See #3736 for the
4947
- // reconciliation protocol that would let it run again.
4948
- const persistedReasoningParts = filterPrivateArray(state.reasoningParts, isPersistedReasoningPart);
4949
- const hasExposedReasoning = persistedReasoningParts.length > 0;
4950
- const canRecoverInterruptedLocalToolBatch = !recoveredInterruptedLocalToolBatch &&
4951
- step + 1 < maxSteps &&
4952
- !hasExposedReasoning;
4953
- const shouldContinue = shouldContinueAfterStreamStep(state, {
4954
- recoverInterruptedToolCalls: canRecoverInterruptedLocalToolBatch,
4955
- });
4956
- const stoppedEmptyAfterCompletedTool = state.finishReason === "stop" &&
4957
- !hasSubstantiveAssistantText(state.accumulatedText) &&
4958
- state.toolCalls.size === 0 &&
4959
- finalToolResults.size === 0 &&
4960
- (state.suppressedToolCalls?.length ?? 0) === 0 &&
4961
- somePrivateArray(toolCalls, (toolCall) => toolCall.status === "completed");
4962
- const shouldRecoverInterruptedLocalToolBatch = canRecoverInterruptedLocalToolBatch &&
4963
- shouldContinue &&
4964
- somePrivateArray(streamedToolCalls, isInterruptedClientToolCall);
4965
- const exhaustedStepBudgetDuringInterruptedLocalToolRecovery = !recoveredInterruptedLocalToolBatch &&
4966
- step + 1 >= maxSteps &&
4967
- !hasExposedReasoning &&
4968
- somePrivateArray(streamedToolCalls, isInterruptedClientToolCall) &&
4969
- shouldContinueAfterStreamStep(state, { recoverInterruptedToolCalls: true });
4970
- // Exactly `shouldRecoverInterruptedLocalToolBatch` with the reasoning
4971
- // gate lifted: the batch this step would have replayed had it not
4972
- // already exposed reasoning. Re-asking is what separates "recovery was
4973
- // declined" from "this step merely carried reasoning";
4974
- // `shouldContinueAfterStreamStep` only reads state, so asking twice has
4975
- // no side effects, and the cheap conditions short-circuit ahead of it.
4976
- const declinedRecoveryForExposedReasoning = hasExposedReasoning &&
4977
- !recoveredInterruptedLocalToolBatch &&
4978
- step + 1 < maxSteps &&
4979
- somePrivateArray(streamedToolCalls, isInterruptedClientToolCall) &&
4980
- shouldContinueAfterStreamStep(state, { recoverInterruptedToolCalls: true });
4981
- if (declinedRecoveryForExposedReasoning) {
4982
- logger.warn("Declined interrupted local tool batch recovery after exposed reasoning", {
4983
- step,
4984
- toolName: filterPrivateArray(streamedToolCalls, isInterruptedClientToolCall)[0]?.name,
4985
- reasoningPartCount: persistedReasoningParts.length,
4986
- });
4987
- }
4988
- const assistantMessage = buildStreamedAssistantMessage({
4989
- ...state,
4990
- accumulatedText: recoveryPresentationText,
4991
- }, {
4992
- id: `msg_${Date.now()}_${step}`,
4993
- timestamp: Date.now(),
4994
- }, {
4995
- preserveRecoverablePlaceholderToolCalls: shouldRecoverInterruptedLocalToolBatch ||
4996
- !shouldContinue,
4997
- });
4998
- attachProviderMetadata(assistantMessage, await reconcileSuppressedProviderMetadata(languageModel, state.providerMetadata, state.suppressedToolCalls, state.toolCalls.size > 0, abortSignal));
4999
- for (const tc of state.toolCalls.values()) {
5000
- const materialized = materializeStreamedToolCall(tc);
5001
- if (materialized.kind === "incomplete" && isRecoverablePlaceholderToolCall(tc)) {
5002
- // Provisional empty-object placeholder that never finalized. The
5003
- // model never committed arguments. Preserve it when recovery or
5004
- // terminalization records a matching tool result; otherwise the
5005
- // assistant message builder can omit it beside final text. Surface no
5006
- // input warning or error for the provisional fragment.
5173
+ recoveredEmptyResponse = true;
5174
+ pushPrivateArray(currentMessages, markRuntimeGeneratedUserMessage({
5175
+ id: `runtime_empty_response_${Date.now()}_${step}`,
5176
+ role: "user",
5177
+ parts: [{
5178
+ type: "text",
5179
+ text: EMPTY_RESPONSE_RECOVERY_PROMPT,
5180
+ }],
5181
+ timestamp: Date.now(),
5182
+ }));
5183
+ await pauseAtBoundary(step + 1);
5184
+ this.status = "thinking";
5007
5185
  continue;
5008
5186
  }
5009
- if (materialized.kind === "incomplete") {
5010
- // Stream terminated before the provider emitted the finalizing
5011
- // `tool-call` event for this block. The model never committed this
5012
- // tool use. Surface the failure via SSE so the live client can
5013
- // react, and leave the partial fragment under `inputText` in the
5014
- // persisted part above so the history is replayable and transparent.
5015
- logger.warn("Streamed tool call terminated before tool-call event", {
5016
- toolCallId: tc.id,
5017
- toolName: tc.name,
5018
- partialArgumentsLength: materialized.partialArgumentsLength,
5019
- partialArgumentsPreview: materialized.partialArgumentsPreview,
5020
- });
5021
- if (tc.inputAnnounced === true) {
5022
- const dynamicIncomplete = isDynamicTool(tc.name, this.config.tools);
5023
- sendSSE(controller, encoder, {
5024
- type: "tool-input-error",
5025
- toolCallId: tc.id,
5026
- errorText: `Stream terminated before tool-call event fired for "${tc.name}". ` +
5027
- `Received ${materialized.partialArgumentsLength} chars of partial tool-input deltas.`,
5028
- ...(dynamicIncomplete ? { dynamic: true } : {}),
5029
- });
5187
+ const persistToolResult = async (toolResult) => {
5188
+ if (currentStepToolResults.has(toolResult.toolCallId)) {
5189
+ return;
5030
5190
  }
5031
- }
5032
- else if (materialized.kind === "parse-error") {
5033
- logger.warn("Failed to parse streamed tool arguments", {
5034
- toolCallId: tc.id,
5035
- error: materialized.parseError,
5191
+ const executionAuthority = resolveToolExecutionAuthority({
5192
+ toolName: toolResult.toolName,
5193
+ plan: effectiveToolExposurePlan,
5036
5194
  });
5037
- }
5038
- }
5039
- const stepAssistantText = getTextFromParts(assistantMessage.parts);
5040
- if (step === interruptedLocalToolBatchRecoveryStep &&
5041
- suppressedRecoveryReplayTextLength > 0) {
5042
- latestAssistantText = `${previousRecoveryText}${recoveryPresentationText}`;
5043
- }
5044
- else if (step === interruptedLocalToolBatchRecoveryStep && interruptedRecoveryPrefixLength > 0) {
5045
- latestAssistantText = privateTextStartsWith(previousRecoveryText, state.accumulatedText)
5046
- ? previousRecoveryText
5047
- : state.accumulatedText;
5048
- }
5049
- else if (hasSubstantiveAssistantText(stepAssistantText) ||
5050
- step !== interruptedLocalToolBatchRecoveryStep) {
5051
- latestAssistantText = stepAssistantText;
5052
- }
5053
- const streamedInvokeAgentBatch = providerReplayCheckpointEmission.complete
5054
- ? collectStreamedParallelInvokeAgentToolCalls(streamedToolCalls, finalToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, shouldContinue, effectiveToolExposurePlan, {
5055
- activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
5056
- toolsConfig: this.config.tools,
5057
- agentId: this.id,
5058
- hasToolReplacements: false,
5059
- })
5060
- : undefined;
5061
- const streamedSkillDelegationOrder = streamedInvokeAgentBatch
5062
- ? streamedSameTurnSkillDelegationOrder(streamedToolCalls, finalToolResults, providerReplayCheckpointEmission.invokeAgentToolNames)
5063
- : undefined;
5064
- let streamedBatchCompletionDeferred = streamedSkillDelegationOrder === "prefix";
5065
- const admittedTurn = snapshotAdmittedToolTurn(assistantMessage, currentMessages.length);
5066
- pushPrivateArray(currentMessages, assistantMessage);
5067
- await persistMessage(assistantMessage);
5068
- await persistProviderReplayCheckpointAfterTurn({
5069
- emission: providerReplayCheckpointEmission,
5070
- providerMetadata: readAttachedProviderMetadata(assistantMessage),
5071
- invokeAgentToolCalls: streamedSkillDelegationOrder === "interleaved"
5072
- ? undefined
5073
- : streamedInvokeAgentBatch,
5074
- deferCompletion: streamedBatchCompletionDeferred,
5075
- });
5076
- if (this.#persistRecoveryBoundary) {
5077
- // Recovery snapshots require an authenticated admission for every local
5078
- // call in this turn, including calls interrupted before input finalized.
5079
- // Announce them before waiting for persistence so both retry and terminal
5080
- // paths can acknowledge the matching error without an admission cycle.
5081
- for (const toolCall of streamedToolCalls) {
5082
- if (toolCall.providerExecuted !== true && isStreamedToolCallIncomplete(toolCall) &&
5083
- !finalToolResults.has(toolCall.id)) {
5195
+ const toolResultMessage = createPolicyAwareToolResultMessage(toolResult.toolCallId, toolResult.toolName, toolResult.error === undefined
5196
+ ? toolResult.output
5197
+ : { error: stringifyToolError(toolResult.error) }, executionAuthority?.toolDefinition, toolResult.providerExecuted === true);
5198
+ pushPrivateArray(currentMessages, toolResultMessage);
5199
+ await persistMessage(toolResultMessage);
5200
+ currentStepToolResults.set(toolResult.toolCallId, toolResultMessage.parts[0]);
5201
+ };
5202
+ const recordIncompleteLocalToolError = async (toolCall, options = {}) => {
5203
+ if (toolCall.providerExecuted === true ||
5204
+ !isStreamedToolCallIncomplete(toolCall) ||
5205
+ finalToolResults.has(toolCall.id)) {
5206
+ return false;
5207
+ }
5208
+ if (options.announceInput === true) {
5209
+ // An interrupted call never reached `tool-input-end`, so its
5210
+ // `tool-input-start` is still buffered and `inputAnnounced` is false
5211
+ // — which would suppress the `tool-output-error` below. Every
5212
+ // terminal path passes `announceInput`, because on all of them the
5213
+ // run stops here and the client would otherwise be left with
5214
+ // whatever preceded the truncation and then nothing at all. Which
5215
+ // path declined recovery — exposed reasoning, a spent step budget, a
5216
+ // second interruption, an exposed sibling — is invisible to the
5217
+ // user, so it must not decide whether the failure renders (#3737).
5218
+ //
5219
+ // The name is safe to publish here. `tool-call` is what can supersede
5220
+ // a name, and it also sets `inputAvailable`, which fails the guard
5221
+ // above — so reaching this line means no such event arrived and the
5222
+ // buffered name is the only one this call will ever have. It is the
5223
+ // same name recorded below and in the persisted assistant message,
5224
+ // so the card matches a reload. Announcing is idempotent, so a call
5225
+ // surfaced upstream is not reported twice.
5084
5226
  announceStreamedToolCallInput(controller, encoder, toolCall, (name) => isDynamicTool(name, this.config.tools));
5085
5227
  }
5086
- }
5087
- await this.#persistRecoveryBoundary(createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)));
5088
- }
5089
- if (stoppedEmptyAfterCompletedTool) {
5090
- sendSSE(controller, encoder, {
5091
- type: "step-end",
5092
- ...(runtimeObservationStepId
5093
- ? {
5094
- privateRuntimeObservation: {
5095
- version: 1,
5096
- kind: "step_ended",
5097
- stepId: runtimeObservationStepId,
5098
- },
5099
- }
5100
- : {}),
5101
- });
5102
- if (recoveredEmptyResponse || step + 1 >= maxSteps) {
5103
- throw new RuntimeEmptyResponseError();
5104
- }
5105
- recoveredEmptyResponse = true;
5106
- pushPrivateArray(currentMessages, markRuntimeGeneratedUserMessage({
5107
- id: `runtime_empty_response_${Date.now()}_${step}`,
5108
- role: "user",
5109
- parts: [{
5110
- type: "text",
5111
- text: EMPTY_RESPONSE_RECOVERY_PROMPT,
5112
- }],
5113
- timestamp: Date.now(),
5114
- }));
5115
- await pauseAtBoundary(step + 1);
5116
- this.status = "thinking";
5117
- continue;
5118
- }
5119
- const persistToolResult = async (toolResult) => {
5120
- if (currentStepToolResults.has(toolResult.toolCallId)) {
5121
- return;
5122
- }
5123
- const executionAuthority = resolveToolExecutionAuthority({
5124
- toolName: toolResult.toolName,
5125
- plan: effectiveToolExposurePlan,
5126
- });
5127
- const toolResultMessage = createPolicyAwareToolResultMessage(toolResult.toolCallId, toolResult.toolName, toolResult.error === undefined
5128
- ? toolResult.output
5129
- : { error: stringifyToolError(toolResult.error) }, executionAuthority?.toolDefinition, toolResult.providerExecuted === true);
5130
- pushPrivateArray(currentMessages, toolResultMessage);
5131
- await persistMessage(toolResultMessage);
5132
- currentStepToolResults.set(toolResult.toolCallId, toolResultMessage.parts[0]);
5133
- };
5134
- const recordIncompleteLocalToolError = async (toolCall, options = {}) => {
5135
- if (toolCall.providerExecuted === true ||
5136
- !isStreamedToolCallIncomplete(toolCall) ||
5137
- finalToolResults.has(toolCall.id)) {
5138
- return false;
5139
- }
5140
- if (options.announceInput === true) {
5141
- // An interrupted call never reached `tool-input-end`, so its
5142
- // `tool-input-start` is still buffered and `inputAnnounced` is false
5143
- // — which would suppress the `tool-output-error` below. Every
5144
- // terminal path passes `announceInput`, because on all of them the
5145
- // run stops here and the client would otherwise be left with
5146
- // whatever preceded the truncation and then nothing at all. Which
5147
- // path declined recovery — exposed reasoning, a spent step budget, a
5148
- // second interruption, an exposed sibling — is invisible to the
5149
- // user, so it must not decide whether the failure renders (#3737).
5150
- //
5151
- // The name is safe to publish here. `tool-call` is what can supersede
5152
- // a name, and it also sets `inputAvailable`, which fails the guard
5153
- // above — so reaching this line means no such event arrived and the
5154
- // buffered name is the only one this call will ever have. It is the
5155
- // same name recorded below and in the persisted assistant message,
5156
- // so the card matches a reload. Announcing is idempotent, so a call
5157
- // surfaced upstream is not reported twice.
5158
- announceStreamedToolCallInput(controller, encoder, toolCall, (name) => isDynamicTool(name, this.config.tools));
5159
- }
5160
- const incompleteToolCall = {
5161
- id: toolCall.id,
5162
- name: toolCall.name,
5163
- args: {},
5164
- ...(toolCall.arguments.length > 0 ? { inputText: toolCall.arguments } : {}),
5165
- status: "pending",
5228
+ const incompleteToolCall = {
5229
+ id: toolCall.id,
5230
+ name: toolCall.name,
5231
+ args: {},
5232
+ ...(toolCall.arguments.length > 0 ? { inputText: toolCall.arguments } : {}),
5233
+ status: "pending",
5234
+ };
5235
+ await this.recordToolError(persistMessage, incompleteToolCall, `Stream terminated before tool-call event fired for "${toolCall.name}". ` +
5236
+ `Received ${toolCall.arguments.length} chars of partial tool-input deltas.`, { controller, encoder }, currentMessages, toolCalls, {
5237
+ emitSse: toolCall.inputAnnounced === true,
5238
+ includeInResponse: options.includeInResponse,
5239
+ });
5240
+ return true;
5166
5241
  };
5167
- await this.recordToolError(persistMessage, incompleteToolCall, `Stream terminated before tool-call event fired for "${toolCall.name}". ` +
5168
- `Received ${toolCall.arguments.length} chars of partial tool-input deltas.`, { controller, encoder }, currentMessages, toolCalls, {
5169
- emitSse: toolCall.inputAnnounced === true,
5170
- includeInResponse: options.includeInResponse,
5171
- });
5172
- return true;
5173
- };
5174
- if (!shouldContinue) {
5175
- for (const toolResult of finalToolResults.values()) {
5176
- await persistToolResult(toolResult);
5177
- }
5178
- for (let toolCallIndex = 0; toolCallIndex < streamedToolCalls.length; toolCallIndex++) {
5179
- // A rejected batch must close committed local calls too, without running
5180
- // side effects or inventing a result for a provider-owned sibling.
5181
- if (!ObjectHasOwn(streamedToolCalls, toolCallIndex))
5182
- continue;
5183
- const toolCall = streamedToolCalls[toolCallIndex];
5184
- if (toolCall.providerExecuted !== true && toolCall.inputAvailable === true &&
5185
- !finalToolResults.has(toolCall.id) && !currentStepToolResults.has(toolCall.id)) {
5186
- const capturedInput = captureStreamedToolCallInput(toolCall);
5187
- await this.recordToolError(persistMessage, {
5188
- id: toolCall.id,
5189
- name: toolCall.name,
5190
- args: capturedInput.args,
5191
- ...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
5192
- status: "pending",
5193
- }, "Tool execution skipped because the model turn ended before local execution could continue.", { controller, encoder }, currentMessages, toolCalls);
5194
- continue;
5242
+ if (!shouldContinue) {
5243
+ for (const toolResult of finalToolResults.values()) {
5244
+ await persistToolResult(toolResult);
5195
5245
  }
5196
- // Terminal. Every incomplete local call recorded here is also
5197
- // terminalized into history, so announce unconditionally and let the
5198
- // wire carry the same failure. `recordIncompleteLocalToolError`
5199
- // guards on `providerExecuted`, completeness and a final result, so
5200
- // only genuinely truncated local calls are announced, and
5201
- // `announceStreamedToolCallInput` is idempotent for any already
5202
- // surfaced upstream.
5203
- await recordIncompleteLocalToolError(toolCall, { announceInput: true });
5204
- }
5205
- sendSSE(controller, encoder, {
5206
- type: "step-end",
5207
- ...(runtimeObservationStepId
5208
- ? {
5209
- privateRuntimeObservation: {
5210
- version: 1,
5211
- kind: "step_ended",
5212
- stepId: runtimeObservationStepId,
5213
- },
5246
+ for (let toolCallIndex = 0; toolCallIndex < streamedToolCalls.length; toolCallIndex++) {
5247
+ // A rejected batch must close committed local calls too, without running
5248
+ // side effects or inventing a result for a provider-owned sibling.
5249
+ if (!ObjectHasOwn(streamedToolCalls, toolCallIndex))
5250
+ continue;
5251
+ const toolCall = streamedToolCalls[toolCallIndex];
5252
+ if (toolCall.providerExecuted !== true && toolCall.inputAvailable === true &&
5253
+ !finalToolResults.has(toolCall.id) && !currentStepToolResults.has(toolCall.id)) {
5254
+ const capturedInput = captureStreamedToolCallInput(toolCall);
5255
+ await this.recordToolError(persistMessage, {
5256
+ id: toolCall.id,
5257
+ name: toolCall.name,
5258
+ args: capturedInput.args,
5259
+ ...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
5260
+ status: "pending",
5261
+ }, "Tool execution skipped because the model turn ended before local execution could continue.", { controller, encoder }, currentMessages, toolCalls);
5262
+ continue;
5214
5263
  }
5215
- : {}),
5216
- });
5217
- completedWithinStepBudget = !exhaustedStepBudgetDuringInterruptedLocalToolRecovery;
5218
- if (completedWithinStepBudget && this.#persistRecoveryBoundary) {
5219
- const runtimeContinuation = captureRecoveryContinuation(step + 1);
5220
- await this.#persistRecoveryBoundary({
5221
- ...createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)),
5222
- runtimeContinuation,
5264
+ // Terminal. Every incomplete local call recorded here is also
5265
+ // terminalized into history, so announce unconditionally and let the
5266
+ // wire carry the same failure. `recordIncompleteLocalToolError`
5267
+ // guards on `providerExecuted`, completeness and a final result, so
5268
+ // only genuinely truncated local calls are announced, and
5269
+ // `announceStreamedToolCallInput` is idempotent for any already
5270
+ // surfaced upstream.
5271
+ await recordIncompleteLocalToolError(toolCall, { announceInput: true });
5272
+ }
5273
+ sendSSE(controller, encoder, {
5274
+ type: "step-end",
5275
+ ...(runtimeObservationStepId
5276
+ ? {
5277
+ privateRuntimeObservation: {
5278
+ version: 1,
5279
+ kind: "step_ended",
5280
+ stepId: runtimeObservationStepId,
5281
+ },
5282
+ }
5283
+ : {}),
5223
5284
  });
5285
+ completedWithinStepBudget = !exhaustedStepBudgetDuringInterruptedLocalToolRecovery;
5286
+ if (completedWithinStepBudget && this.#persistRecoveryBoundary) {
5287
+ const runtimeContinuation = captureRecoveryContinuation(step + 1);
5288
+ await this.#persistRecoveryBoundary({
5289
+ ...createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)),
5290
+ runtimeContinuation,
5291
+ });
5292
+ }
5293
+ if (!completedWithinStepBudget)
5294
+ await pauseAtBoundary(step + 1);
5295
+ break;
5224
5296
  }
5225
- if (!completedWithinStepBudget)
5226
- await pauseAtBoundary(step + 1);
5227
- break;
5228
- }
5229
- this.status = "tool_execution";
5230
- if (shouldRecoverInterruptedLocalToolBatch) {
5231
- // Treat parallel local calls as one batch. Executing the finalized
5232
- // prefix here could apply only part of the model's intended mutation.
5233
- recoveredInterruptedLocalToolBatch = true;
5234
- interruptedLocalToolBatchRecoveryStep = step + 1;
5235
- interruptedLocalToolBatchRecoveryText = hasSubstantiveAssistantText(stepAssistantText)
5236
- ? stepAssistantText
5237
- : undefined;
5238
- }
5239
- for (let toolCallIndex = 0; toolCallIndex < streamedToolCalls.length; toolCallIndex++) {
5240
- if (!ObjectHasOwn(streamedToolCalls, toolCallIndex))
5241
- continue;
5242
- const tc = streamedToolCalls[toolCallIndex];
5243
- throwIfAborted(abortSignal);
5244
- if (shouldRecoverInterruptedLocalToolBatch && tc.providerExecuted !== true) {
5245
- if (await recordIncompleteLocalToolError(tc, { includeInResponse: false })) {
5297
+ this.status = "tool_execution";
5298
+ if (shouldRecoverInterruptedLocalToolBatch) {
5299
+ // Treat parallel local calls as one batch. Executing the finalized
5300
+ // prefix here could apply only part of the model's intended mutation.
5301
+ recoveredInterruptedLocalToolBatch = true;
5302
+ interruptedLocalToolBatchRecoveryStep = step + 1;
5303
+ interruptedLocalToolBatchRecoveryText = hasSubstantiveAssistantText(stepAssistantText)
5304
+ ? stepAssistantText
5305
+ : undefined;
5306
+ }
5307
+ for (let toolCallIndex = 0; toolCallIndex < streamedToolCalls.length; toolCallIndex++) {
5308
+ if (!ObjectHasOwn(streamedToolCalls, toolCallIndex))
5309
+ continue;
5310
+ const tc = streamedToolCalls[toolCallIndex];
5311
+ throwIfAborted(abortSignal);
5312
+ if (shouldRecoverInterruptedLocalToolBatch && tc.providerExecuted !== true) {
5313
+ if (await recordIncompleteLocalToolError(tc, { includeInResponse: false })) {
5314
+ continue;
5315
+ }
5316
+ const capturedInput = captureStreamedToolCallInput(tc);
5317
+ const interruptedBatchToolCall = {
5318
+ id: tc.id,
5319
+ name: tc.name,
5320
+ args: capturedInput.args,
5321
+ ...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
5322
+ status: "pending",
5323
+ };
5324
+ await this.recordToolError(persistMessage, interruptedBatchToolCall, "Tool execution skipped because another tool call in the same model step " +
5325
+ "was interrupted before its input completed.", { controller, encoder }, currentMessages, toolCalls);
5326
+ continue;
5327
+ }
5328
+ if (isRecoverablePlaceholderToolCall(tc)) {
5329
+ // Provisional empty-object placeholder that never finalized. If the
5330
+ // bounded recovery path was unavailable, do not execute or surface
5331
+ // it as a committed call.
5332
+ continue;
5333
+ }
5334
+ if (await recordIncompleteLocalToolError(tc)) {
5335
+ // Stream ended before the provider finalized this tool call. We
5336
+ // cannot execute it, so record a distinct stream-termination error
5337
+ // (not a tool-argument parse error) so the parent step and any
5338
+ // upstream orchestrator (e.g. the child-fork watchdog) see a
5339
+ // completed step with a clearly-labelled failure and can recover.
5246
5340
  continue;
5247
5341
  }
5248
5342
  const capturedInput = captureStreamedToolCallInput(tc);
5249
- const interruptedBatchToolCall = {
5343
+ const toolCall = {
5250
5344
  id: tc.id,
5251
5345
  name: tc.name,
5252
5346
  args: capturedInput.args,
5253
5347
  ...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
5254
5348
  status: "pending",
5255
5349
  };
5256
- await this.recordToolError(persistMessage, interruptedBatchToolCall, "Tool execution skipped because another tool call in the same model step " +
5257
- "was interrupted before its input completed.", { controller, encoder }, currentMessages, toolCalls);
5258
- continue;
5259
- }
5260
- if (isRecoverablePlaceholderToolCall(tc)) {
5261
- // Provisional empty-object placeholder that never finalized. If the
5262
- // bounded recovery path was unavailable, do not execute or surface
5263
- // it as a committed call.
5264
- continue;
5265
- }
5266
- if (await recordIncompleteLocalToolError(tc)) {
5267
- // Stream ended before the provider finalized this tool call. We
5268
- // cannot execute it, so record a distinct stream-termination error
5269
- // (not a tool-argument parse error) so the parent step and any
5270
- // upstream orchestrator (e.g. the child-fork watchdog) see a
5271
- // completed step with a clearly-labelled failure and can recover.
5272
- continue;
5273
- }
5274
- const capturedInput = captureStreamedToolCallInput(tc);
5275
- const toolCall = {
5276
- id: tc.id,
5277
- name: tc.name,
5278
- args: capturedInput.args,
5279
- ...(capturedInput.inputText ? { inputText: capturedInput.inputText } : {}),
5280
- status: "pending",
5281
- };
5282
- const matchingResult = finalToolResults.get(tc.id);
5283
- const persistedResult = currentStepToolResults.get(tc.id);
5284
- const executionAuthority = resolveToolExecutionAuthority({
5285
- toolName: tc.name,
5286
- plan: effectiveToolExposurePlan,
5287
- });
5288
- if (streamedBatchCompletionDeferred && tc.providerExecuted !== true && !matchingResult &&
5289
- !persistedResult &&
5290
- IntrinsicReflectApply(IntrinsicSetHas, providerReplayCheckpointEmission.invokeAgentToolNames, [tc.name])) {
5291
- await completeDeferredProviderReplayCheckpointTurn(providerReplayCheckpointEmission, collectStreamedParallelInvokeAgentToolCalls(streamedToolCalls, finalToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, shouldContinue, effectiveToolExposurePlan, {
5292
- activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
5293
- toolsConfig: this.config.tools,
5294
- agentId: this.id,
5295
- hasToolReplacements: false,
5296
- }));
5297
- streamedBatchCompletionDeferred = false;
5298
- }
5299
- if (matchingResult) {
5300
- await persistToolResult(matchingResult);
5301
- const trustedPlatformPolicyResult = (() => {
5302
- const part = currentStepToolResults.get(tc.id);
5303
- return part !== undefined && hasTrustedPlatformPolicyToolResultPart(part);
5304
- })();
5305
- toolCall.status = matchingResult.error === undefined ? "completed" : "error";
5306
- toolCall.result = matchingResult.output;
5307
- toolCall.error = matchingResult.error === undefined
5308
- ? undefined
5309
- : stringifyToolError(matchingResult.error);
5310
- pushPrivateArray(toolCalls, toolCall);
5311
- if (matchingResult.error === undefined) {
5312
- if (shouldHideProjectToolAfterAgentWriteSuccess(tc.name)) {
5313
- agentWriteFinalResponseToolGuardEnabled = true;
5314
- }
5315
- if (isLoadSkillToolName(tc.name) &&
5316
- hasTrustedPlatformPolicyToolDefinition(executionAuthority?.toolDefinition) &&
5317
- trustedPlatformPolicyResult) {
5318
- skillState.applySuccessfulResult(matchingResult.output);
5319
- }
5320
- const submittedFormInput = trustedPlatformPolicyResult &&
5321
- isSubmittedFormInputExecutionResult(tc.name, matchingResult.output, executionAuthority?.toolDefinition);
5322
- skillState.markFormInputSubmitted(submittedFormInput);
5323
- if (submittedFormInput) {
5324
- currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
5325
- }
5350
+ const matchingResult = finalToolResults.get(tc.id);
5351
+ const persistedResult = currentStepToolResults.get(tc.id);
5352
+ const executionAuthority = resolveToolExecutionAuthority({
5353
+ toolName: tc.name,
5354
+ plan: effectiveToolExposurePlan,
5355
+ });
5356
+ if (streamedBatchCompletionDeferred && tc.providerExecuted !== true && !matchingResult &&
5357
+ !persistedResult &&
5358
+ IntrinsicReflectApply(IntrinsicSetHas, providerReplayCheckpointEmission.invokeAgentToolNames, [tc.name])) {
5359
+ await completeDeferredProviderReplayCheckpointTurn(providerReplayCheckpointEmission, collectStreamedParallelInvokeAgentToolCalls(streamedToolCalls, finalToolResults, providerReplayCheckpointEmission.invokeAgentToolNames, shouldContinue, effectiveToolExposurePlan, {
5360
+ activeSkillDelegationOverrides: skillState.activeSkillDelegationOverrides,
5361
+ toolsConfig: this.config.tools,
5362
+ agentId: this.id,
5363
+ hasToolReplacements: false,
5364
+ }));
5365
+ streamedBatchCompletionDeferred = false;
5326
5366
  }
5327
- continue;
5328
- }
5329
- if (persistedResult) {
5330
- const trustedPlatformPolicyResult = hasTrustedPlatformPolicyToolResultPart(persistedResult);
5331
- const persistedError = getToolResultError(persistedResult.result);
5332
- toolCall.status = persistedError === undefined ? "completed" : "error";
5333
- toolCall.result = persistedResult.result;
5334
- toolCall.error = persistedError;
5335
- pushPrivateArray(toolCalls, toolCall);
5336
- if (persistedError === undefined) {
5337
- if (shouldHideProjectToolAfterAgentWriteSuccess(tc.name)) {
5338
- agentWriteFinalResponseToolGuardEnabled = true;
5339
- }
5340
- if (isLoadSkillToolName(tc.name) &&
5341
- hasTrustedPlatformPolicyToolDefinition(executionAuthority?.toolDefinition) &&
5342
- trustedPlatformPolicyResult) {
5343
- skillState.applySuccessfulResult(persistedResult.result);
5367
+ if (matchingResult) {
5368
+ await persistToolResult(matchingResult);
5369
+ const trustedPlatformPolicyResult = (() => {
5370
+ const part = currentStepToolResults.get(tc.id);
5371
+ return part !== undefined && hasTrustedPlatformPolicyToolResultPart(part);
5372
+ })();
5373
+ toolCall.status = matchingResult.error === undefined ? "completed" : "error";
5374
+ toolCall.result = matchingResult.output;
5375
+ toolCall.error = matchingResult.error === undefined
5376
+ ? undefined
5377
+ : stringifyToolError(matchingResult.error);
5378
+ pushPrivateArray(toolCalls, toolCall);
5379
+ if (matchingResult.error === undefined) {
5380
+ if (shouldHideProjectToolAfterAgentWriteSuccess(tc.name)) {
5381
+ agentWriteFinalResponseToolGuardEnabled = true;
5382
+ }
5383
+ if (isLoadSkillToolName(tc.name) &&
5384
+ hasTrustedPlatformPolicyToolDefinition(executionAuthority?.toolDefinition) &&
5385
+ trustedPlatformPolicyResult) {
5386
+ skillState.applySuccessfulResult(matchingResult.output);
5387
+ }
5388
+ const submittedFormInput = trustedPlatformPolicyResult &&
5389
+ isSubmittedFormInputExecutionResult(tc.name, matchingResult.output, executionAuthority?.toolDefinition);
5390
+ skillState.markFormInputSubmitted(submittedFormInput);
5391
+ if (submittedFormInput) {
5392
+ currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
5393
+ }
5344
5394
  }
5345
- const submittedFormInput = trustedPlatformPolicyResult &&
5346
- isSubmittedFormInputExecutionResult(tc.name, persistedResult.result, executionAuthority?.toolDefinition);
5347
- skillState.markFormInputSubmitted(submittedFormInput);
5348
- if (submittedFormInput) {
5349
- currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
5395
+ continue;
5396
+ }
5397
+ if (persistedResult) {
5398
+ const trustedPlatformPolicyResult = hasTrustedPlatformPolicyToolResultPart(persistedResult);
5399
+ const persistedError = getToolResultError(persistedResult.result);
5400
+ toolCall.status = persistedError === undefined ? "completed" : "error";
5401
+ toolCall.result = persistedResult.result;
5402
+ toolCall.error = persistedError;
5403
+ pushPrivateArray(toolCalls, toolCall);
5404
+ if (persistedError === undefined) {
5405
+ if (shouldHideProjectToolAfterAgentWriteSuccess(tc.name)) {
5406
+ agentWriteFinalResponseToolGuardEnabled = true;
5407
+ }
5408
+ if (isLoadSkillToolName(tc.name) &&
5409
+ hasTrustedPlatformPolicyToolDefinition(executionAuthority?.toolDefinition) &&
5410
+ trustedPlatformPolicyResult) {
5411
+ skillState.applySuccessfulResult(persistedResult.result);
5412
+ }
5413
+ const submittedFormInput = trustedPlatformPolicyResult &&
5414
+ isSubmittedFormInputExecutionResult(tc.name, persistedResult.result, executionAuthority?.toolDefinition);
5415
+ skillState.markFormInputSubmitted(submittedFormInput);
5416
+ if (submittedFormInput) {
5417
+ currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
5418
+ }
5350
5419
  }
5420
+ continue;
5351
5421
  }
5352
- continue;
5353
- }
5354
- if (tc.providerExecuted === true) {
5355
- await traceProviderExecutedTool({
5356
- mode: "stream",
5357
- agentId: this.id,
5358
- toolName: tc.name,
5359
- toolCallId: tc.id,
5360
- context: {
5361
- toolCallId: tc.id,
5362
- ...toolContext,
5422
+ if (tc.providerExecuted === true) {
5423
+ await traceProviderExecutedTool({
5424
+ mode: "stream",
5363
5425
  agentId: this.id,
5364
- },
5365
- args: toolCall.args,
5366
- });
5367
- toolCall.status = "completed";
5368
- pushPrivateArray(toolCalls, toolCall);
5369
- continue;
5370
- }
5371
- if (capturedInput.parseError) {
5372
- logger.warn("Invalid streamed tool arguments", {
5373
- toolCallId: tc.id,
5374
- error: capturedInput.parseError,
5375
- });
5376
- const dynamic = isDynamicTool(tc.name, this.config.tools);
5377
- sendSSE(controller, encoder, {
5378
- type: "tool-input-error",
5379
- toolCallId: tc.id,
5380
- errorText: `Invalid tool arguments: ${capturedInput.parseError}`,
5381
- ...(dynamic ? { dynamic: true } : {}),
5382
- });
5383
- await this.recordToolError(persistMessage, toolCall, `Invalid tool arguments: ${capturedInput.parseError}`, { controller, encoder }, currentMessages, toolCalls);
5384
- continue;
5385
- }
5386
- if (isFrameworkToolSearch(tc.name, effectiveToolExposurePlan)) {
5387
- let checkpoint;
5388
- try {
5389
- callbacks?.onToolCall?.(toolCall);
5390
- const search = executeFrameworkToolSearch({
5426
+ toolName: tc.name,
5427
+ toolCallId: tc.id,
5428
+ context: {
5429
+ toolCallId: tc.id,
5430
+ ...toolContext,
5431
+ agentId: this.id,
5432
+ },
5391
5433
  args: toolCall.args,
5392
- plan: effectiveToolExposurePlan,
5393
- state: toolExposureState,
5394
5434
  });
5395
- if (didReloadProjectAgentWriteTool(search.result)) {
5396
- agentWriteFinalResponseToolGuardEnabled = false;
5397
- }
5398
5435
  toolCall.status = "completed";
5399
- toolCall.result = search.result;
5400
5436
  pushPrivateArray(toolCalls, toolCall);
5401
- setOtelActiveSpanAttributes({
5402
- "tool.search.result_count": search.result.resultCount,
5403
- "tool.search.loaded_count": search.result.loadedCount,
5404
- "tool.search.miss": search.result.miss,
5437
+ continue;
5438
+ }
5439
+ if (capturedInput.parseError) {
5440
+ logger.warn("Invalid streamed tool arguments", {
5441
+ toolCallId: tc.id,
5442
+ error: capturedInput.parseError,
5405
5443
  });
5444
+ const dynamic = isDynamicTool(tc.name, this.config.tools);
5406
5445
  sendSSE(controller, encoder, {
5407
- type: "tool-output-available",
5408
- toolCallId: toolCall.id,
5409
- output: search.result,
5446
+ type: "tool-input-error",
5447
+ toolCallId: tc.id,
5448
+ errorText: `Invalid tool arguments: ${capturedInput.parseError}`,
5449
+ ...(dynamic ? { dynamic: true } : {}),
5410
5450
  });
5411
- const toolResultMessage = createToolResultMessage(tc.id, tc.name, search.result);
5412
- pushPrivateArray(currentMessages, toolResultMessage);
5413
- await persistMessage(toolResultMessage);
5414
- checkpoint = search.checkpoint;
5415
- currentStepToolResults.set(tc.id, toolResultMessage.parts[0]);
5451
+ await this.recordToolError(persistMessage, toolCall, `Invalid tool arguments: ${capturedInput.parseError}`, { controller, encoder }, currentMessages, toolCalls);
5452
+ continue;
5416
5453
  }
5417
- catch (error) {
5418
- await this.recordToolError(persistMessage, toolCall, error instanceof Error ? error.message : String(error), { controller, encoder }, currentMessages, toolCalls);
5454
+ if (isFrameworkToolSearch(tc.name, effectiveToolExposurePlan)) {
5455
+ let checkpoint;
5456
+ try {
5457
+ callbacks?.onToolCall?.(toolCall);
5458
+ const search = executeFrameworkToolSearch({
5459
+ args: toolCall.args,
5460
+ plan: effectiveToolExposurePlan,
5461
+ state: toolExposureState,
5462
+ });
5463
+ if (didReloadProjectAgentWriteTool(search.result)) {
5464
+ agentWriteFinalResponseToolGuardEnabled = false;
5465
+ }
5466
+ toolCall.status = "completed";
5467
+ toolCall.result = search.result;
5468
+ pushPrivateArray(toolCalls, toolCall);
5469
+ setOtelActiveSpanAttributes({
5470
+ "tool.search.result_count": search.result.resultCount,
5471
+ "tool.search.loaded_count": search.result.loadedCount,
5472
+ "tool.search.miss": search.result.miss,
5473
+ });
5474
+ sendSSE(controller, encoder, {
5475
+ type: "tool-output-available",
5476
+ toolCallId: toolCall.id,
5477
+ output: search.result,
5478
+ });
5479
+ const toolResultMessage = createToolResultMessage(tc.id, tc.name, search.result);
5480
+ pushPrivateArray(currentMessages, toolResultMessage);
5481
+ await persistMessage(toolResultMessage);
5482
+ checkpoint = search.checkpoint;
5483
+ currentStepToolResults.set(tc.id, toolResultMessage.parts[0]);
5484
+ }
5485
+ catch (error) {
5486
+ await this.recordToolError(persistMessage, toolCall, error instanceof Error ? error.message : String(error), { controller, encoder }, currentMessages, toolCalls);
5487
+ continue;
5488
+ }
5489
+ await persistToolExposureCheckpointBeforeContinuation({
5490
+ checkpoint,
5491
+ persist: persistToolExposureCheckpoint,
5492
+ required: requireToolExposureCheckpointPersistence,
5493
+ });
5494
+ continue;
5495
+ }
5496
+ if (shouldHandleToolResultRead({
5497
+ toolName: tc.name,
5498
+ plan: effectiveToolExposurePlan,
5499
+ context: toolResultContext,
5500
+ })) {
5501
+ try {
5502
+ callbacks?.onToolCall?.(toolCall);
5503
+ if (toolResultContext === undefined) {
5504
+ throw new ReferenceError("Tool result context is not available");
5505
+ }
5506
+ const result = readToolResultContext(toolResultContext, toolCall.args);
5507
+ toolCall.status = "completed";
5508
+ toolCall.result = result;
5509
+ pushPrivateArray(toolCalls, toolCall);
5510
+ sendSSE(controller, encoder, {
5511
+ type: "tool-output-available",
5512
+ toolCallId: toolCall.id,
5513
+ output: result,
5514
+ });
5515
+ const toolResultMessage = createPolicyAwareToolResultMessage(tc.id, tc.name, result, executionAuthority?.toolDefinition);
5516
+ pushPrivateArray(currentMessages, toolResultMessage);
5517
+ await persistMessage(toolResultMessage);
5518
+ currentStepToolResults.set(tc.id, toolResultMessage.parts[0]);
5519
+ }
5520
+ catch (error) {
5521
+ await this.recordToolError(persistMessage, toolCall, error instanceof Error ? error.message : String(error), { controller, encoder }, currentMessages, toolCalls);
5522
+ }
5523
+ continue;
5524
+ }
5525
+ if (shouldBlockToolResultReadName({
5526
+ toolName: tc.name,
5527
+ context: toolResultContext,
5528
+ })) {
5529
+ await this.recordToolError(persistMessage, toolCall, toolResultReaderUnavailableError(), { controller, encoder }, currentMessages, toolCalls);
5530
+ continue;
5531
+ }
5532
+ if (executionAuthority === undefined) {
5533
+ await this.recordToolError(persistMessage, toolCall, toolNotVisibleError(tc.name), { controller, encoder }, currentMessages, toolCalls);
5419
5534
  continue;
5420
5535
  }
5421
- await persistToolExposureCheckpointBeforeContinuation({
5422
- checkpoint,
5423
- persist: persistToolExposureCheckpoint,
5424
- required: requireToolExposureCheckpointPersistence,
5536
+ const policyCheck = enforceSkillPolicy(tc.name, {
5537
+ activeSkillId: skillState.activeSkillId,
5538
+ hasSubmittedFormInput: skillState.hasSubmittedFormInput,
5539
+ skillToolAvailability: skillState.activeSkillToolAvailability,
5540
+ toolInput: toolCall.args,
5541
+ toolDefinition: executionAuthority.toolDefinition,
5425
5542
  });
5426
- continue;
5427
- }
5428
- if (shouldHandleToolResultRead({
5429
- toolName: tc.name,
5430
- plan: effectiveToolExposurePlan,
5431
- context: toolResultContext,
5432
- })) {
5543
+ if (!policyCheck.allowed) {
5544
+ await this.recordToolError(persistMessage, toolCall, policyCheck.error, { controller, encoder }, currentMessages, toolCalls);
5545
+ continue;
5546
+ }
5433
5547
  try {
5548
+ toolCall.status = "executing";
5549
+ const startTime = Date.now();
5550
+ toolCall.args = applySkillDelegationOverridesToToolInput(tc.name, toolCall.args, skillState.activeSkillDelegationOverrides, resolveConfiguredTool(this.config.tools, tc.name, { agentId: this.id }) ?? undefined);
5434
5551
  callbacks?.onToolCall?.(toolCall);
5435
- if (toolResultContext === undefined) {
5436
- throw new ReferenceError("Tool result context is not available");
5437
- }
5438
- const result = readToolResultContext(toolResultContext, toolCall.args);
5439
- toolCall.status = "completed";
5552
+ const executionContext = applicationExecutionContext(toolContext, providerObservedSkillBodies);
5553
+ const result = await runWithToolCallOccurrenceDispatch(tc, () => traceConfiguredToolExecution({
5554
+ mode: "stream",
5555
+ agentId: this.id,
5556
+ toolName: tc.name,
5557
+ toolCallId: tc.id,
5558
+ args: toolCall.args,
5559
+ admittedTurn,
5560
+ owner: currentMessages,
5561
+ prepareTerminalDispatch,
5562
+ toolsConfig: this.config.tools,
5563
+ context: executionContext,
5564
+ allowedRemoteToolNames,
5565
+ remoteToolSources,
5566
+ sourceIntegrationPolicy,
5567
+ frameworkLocalTools,
5568
+ }));
5569
+ throwIfAborted(abortSignal);
5570
+ await this.notifyToolResult({
5571
+ mode: "stream",
5572
+ toolName: tc.name,
5573
+ toolCallId: tc.id,
5574
+ input: toolCall.args,
5575
+ result,
5576
+ context: executionContext,
5577
+ });
5578
+ const resultError = getToolResultError(result);
5579
+ toolCall.status = resultError === undefined ? "completed" : "error";
5440
5580
  toolCall.result = result;
5581
+ toolCall.error = resultError;
5582
+ toolCall.executionTime = Date.now() - startTime;
5441
5583
  pushPrivateArray(toolCalls, toolCall);
5442
- sendSSE(controller, encoder, {
5443
- type: "tool-output-available",
5444
- toolCallId: toolCall.id,
5445
- output: result,
5446
- });
5447
- const toolResultMessage = createPolicyAwareToolResultMessage(tc.id, tc.name, result, executionAuthority?.toolDefinition);
5448
- pushPrivateArray(currentMessages, toolResultMessage);
5449
- await persistMessage(toolResultMessage);
5450
- currentStepToolResults.set(tc.id, toolResultMessage.parts[0]);
5584
+ if (resultError === undefined) {
5585
+ // Track skill policy from successful load_skill results
5586
+ if (isLoadSkillToolName(tc.name) &&
5587
+ hasTrustedPlatformPolicyToolDefinition(executionAuthority.toolDefinition)) {
5588
+ skillState.applySuccessfulResult(result);
5589
+ }
5590
+ const submittedFormInput = isSubmittedFormInputExecutionResult(tc.name, result, executionAuthority.toolDefinition);
5591
+ skillState.markFormInputSubmitted(submittedFormInput);
5592
+ if (submittedFormInput) {
5593
+ currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
5594
+ }
5595
+ if (shouldHideProjectToolAfterAgentWriteSuccess(tc.name)) {
5596
+ agentWriteFinalResponseToolGuardEnabled = true;
5597
+ }
5598
+ }
5599
+ const dynamic = isDynamicTool(tc.name, this.config.tools);
5600
+ if (resultError === undefined) {
5601
+ sendSSE(controller, encoder, {
5602
+ type: "tool-output-available",
5603
+ toolCallId: toolCall.id,
5604
+ output: result,
5605
+ ...(dynamic ? { dynamic: true } : {}),
5606
+ });
5607
+ }
5608
+ else {
5609
+ sendSSE(controller, encoder, {
5610
+ type: "tool-output-error",
5611
+ toolCallId: toolCall.id,
5612
+ errorText: resultError,
5613
+ ...(dynamic ? { dynamic: true } : {}),
5614
+ });
5615
+ }
5616
+ const toolResultMessage = createPolicyAwareToolResultMessage(tc.id, tc.name, result, executionAuthority.toolDefinition, false, true);
5617
+ if (!currentStepToolResults.has(tc.id)) {
5618
+ pushPrivateArray(currentMessages, toolResultMessage);
5619
+ await persistMessage(toolResultMessage);
5620
+ currentStepToolResults.set(tc.id, toolResultMessage.parts[0]);
5621
+ }
5451
5622
  }
5452
5623
  catch (error) {
5453
- await this.recordToolError(persistMessage, toolCall, error instanceof Error ? error.message : String(error), { controller, encoder }, currentMessages, toolCalls);
5624
+ await this.recordToolError(persistMessage, toolCall, undefined, { controller, encoder }, currentMessages, toolCalls, { terminal: { error, usage: totalUsage, abortSignal } });
5454
5625
  }
5455
- continue;
5456
- }
5457
- if (shouldBlockToolResultReadName({
5458
- toolName: tc.name,
5459
- context: toolResultContext,
5460
- })) {
5461
- await this.recordToolError(persistMessage, toolCall, toolResultReaderUnavailableError(), { controller, encoder }, currentMessages, toolCalls);
5462
- continue;
5463
5626
  }
5464
- if (executionAuthority === undefined) {
5465
- await this.recordToolError(persistMessage, toolCall, toolNotVisibleError(tc.name), { controller, encoder }, currentMessages, toolCalls);
5466
- continue;
5467
- }
5468
- const policyCheck = enforceSkillPolicy(tc.name, {
5469
- activeSkillId: skillState.activeSkillId,
5470
- hasSubmittedFormInput: skillState.hasSubmittedFormInput,
5471
- skillToolAvailability: skillState.activeSkillToolAvailability,
5472
- toolInput: toolCall.args,
5473
- toolDefinition: executionAuthority.toolDefinition,
5474
- });
5475
- if (!policyCheck.allowed) {
5476
- await this.recordToolError(persistMessage, toolCall, policyCheck.error, { controller, encoder }, currentMessages, toolCalls);
5477
- continue;
5627
+ for (const toolResult of finalToolResults.values()) {
5628
+ await persistToolResult(toolResult);
5478
5629
  }
5479
- try {
5480
- toolCall.status = "executing";
5481
- const startTime = Date.now();
5482
- toolCall.args = applySkillDelegationOverridesToToolInput(tc.name, toolCall.args, skillState.activeSkillDelegationOverrides, resolveConfiguredTool(this.config.tools, tc.name, { agentId: this.id }) ?? undefined);
5483
- callbacks?.onToolCall?.(toolCall);
5484
- const executionContext = applicationExecutionContext(toolContext, providerObservedSkillBodies);
5485
- const result = await runWithToolCallOccurrenceDispatch(tc, () => traceConfiguredToolExecution({
5486
- mode: "stream",
5487
- agentId: this.id,
5488
- toolName: tc.name,
5489
- toolCallId: tc.id,
5490
- args: toolCall.args,
5491
- admittedTurn,
5492
- owner: currentMessages,
5493
- prepareTerminalDispatch,
5494
- toolsConfig: this.config.tools,
5495
- context: executionContext,
5496
- allowedRemoteToolNames,
5497
- remoteToolSources,
5498
- sourceIntegrationPolicy,
5499
- frameworkLocalTools,
5630
+ if (state.suppressedToolCalls.length > 0) {
5631
+ const unavailableNames = [
5632
+ ...new Set(state.suppressedToolCalls.map((toolCall) => toolCall.name)),
5633
+ ];
5634
+ pushPrivateArray(currentMessages, markRuntimeGeneratedUserMessage({
5635
+ id: `runtime_note_${Date.now()}_${step}`,
5636
+ role: "user",
5637
+ parts: [{
5638
+ type: "text",
5639
+ text: `Runtime recovery: ignored unavailable tool call(s): ${unavailableNames.join(", ")}. Continue using only currently available tools: ${runtimeToolNames.join(", ")}.`,
5640
+ }],
5641
+ timestamp: Date.now(),
5500
5642
  }));
5501
- throwIfAborted(abortSignal);
5502
- await this.notifyToolResult({
5503
- mode: "stream",
5504
- toolName: tc.name,
5505
- toolCallId: tc.id,
5506
- input: toolCall.args,
5507
- result,
5508
- context: executionContext,
5509
- });
5510
- const resultError = getToolResultError(result);
5511
- toolCall.status = resultError === undefined ? "completed" : "error";
5512
- toolCall.result = result;
5513
- toolCall.error = resultError;
5514
- toolCall.executionTime = Date.now() - startTime;
5515
- pushPrivateArray(toolCalls, toolCall);
5516
- if (resultError === undefined) {
5517
- // Track skill policy from successful load_skill results
5518
- if (isLoadSkillToolName(tc.name) &&
5519
- hasTrustedPlatformPolicyToolDefinition(executionAuthority.toolDefinition)) {
5520
- skillState.applySuccessfulResult(result);
5521
- }
5522
- const submittedFormInput = isSubmittedFormInputExecutionResult(tc.name, result, executionAuthority.toolDefinition);
5523
- skillState.markFormInputSubmitted(submittedFormInput);
5524
- if (submittedFormInput) {
5525
- currentRuntimeContext = markSubmittedFormInputRuntimeContext(currentRuntimeContext);
5526
- }
5527
- if (shouldHideProjectToolAfterAgentWriteSuccess(tc.name)) {
5528
- agentWriteFinalResponseToolGuardEnabled = true;
5529
- }
5530
- }
5531
- const dynamic = isDynamicTool(tc.name, this.config.tools);
5532
- if (resultError === undefined) {
5533
- sendSSE(controller, encoder, {
5534
- type: "tool-output-available",
5535
- toolCallId: toolCall.id,
5536
- output: result,
5537
- ...(dynamic ? { dynamic: true } : {}),
5538
- });
5539
- }
5540
- else {
5541
- sendSSE(controller, encoder, {
5542
- type: "tool-output-error",
5543
- toolCallId: toolCall.id,
5544
- errorText: resultError,
5545
- ...(dynamic ? { dynamic: true } : {}),
5546
- });
5547
- }
5548
- const toolResultMessage = createPolicyAwareToolResultMessage(tc.id, tc.name, result, executionAuthority.toolDefinition, false, true);
5549
- if (!currentStepToolResults.has(tc.id)) {
5550
- pushPrivateArray(currentMessages, toolResultMessage);
5551
- await persistMessage(toolResultMessage);
5552
- currentStepToolResults.set(tc.id, toolResultMessage.parts[0]);
5553
- }
5554
5643
  }
5555
- catch (error) {
5556
- await this.recordToolError(persistMessage, toolCall, undefined, { controller, encoder }, currentMessages, toolCalls, { terminal: { error, usage: totalUsage, abortSignal } });
5644
+ if (this.#persistRecoveryBoundary) {
5645
+ const runtimeContinuation = captureRecoveryContinuation(step + 1);
5646
+ await this.#persistRecoveryBoundary({
5647
+ ...createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)),
5648
+ runtimeContinuation,
5649
+ });
5557
5650
  }
5558
- }
5559
- for (const toolResult of finalToolResults.values()) {
5560
- await persistToolResult(toolResult);
5561
- }
5562
- if (state.suppressedToolCalls.length > 0) {
5563
- const unavailableNames = [
5564
- ...new Set(state.suppressedToolCalls.map((toolCall) => toolCall.name)),
5565
- ];
5566
- pushPrivateArray(currentMessages, markRuntimeGeneratedUserMessage({
5567
- id: `runtime_note_${Date.now()}_${step}`,
5568
- role: "user",
5569
- parts: [{
5570
- type: "text",
5571
- text: `Runtime recovery: ignored unavailable tool call(s): ${unavailableNames.join(", ")}. Continue using only currently available tools: ${runtimeToolNames.join(", ")}.`,
5572
- }],
5573
- timestamp: Date.now(),
5574
- }));
5575
- }
5576
- if (this.#persistRecoveryBoundary) {
5577
- const runtimeContinuation = captureRecoveryContinuation(step + 1);
5578
- await this.#persistRecoveryBoundary({
5579
- ...createRecoveryBoundary(currentMessages, createToolExposureCheckpoint(effectiveToolExposurePlan.authorized, toolExposureState)),
5580
- runtimeContinuation,
5651
+ throwIfAborted(abortSignal);
5652
+ sendSSE(controller, encoder, {
5653
+ type: "step-end",
5654
+ ...(runtimeObservationStepId
5655
+ ? {
5656
+ privateRuntimeObservation: {
5657
+ version: 1,
5658
+ kind: "step_ended",
5659
+ stepId: runtimeObservationStepId,
5660
+ },
5661
+ }
5662
+ : {}),
5581
5663
  });
5664
+ await pauseAtBoundary(step + 1);
5665
+ this.status = "thinking";
5666
+ }
5667
+ if (!completedWithinStepBudget) {
5668
+ // Step-budget exhaustion mirrors the generate loop's max-steps exit: the
5669
+ // partial result is still returned, so the structured-output parse is
5670
+ // best effort and a failure is surfaced in metadata instead of thrown.
5671
+ const parsedOutput = await tryParseMaxStepsOutput(latestAssistantText, outputSchema);
5672
+ return attachOutputSchemaParser({
5673
+ text: latestAssistantText,
5674
+ ...(parsedOutput.parsed ? { data: parsedOutput.object, object: parsedOutput.object } : {}),
5675
+ messages: currentMessages,
5676
+ toolCalls,
5677
+ status: "completed",
5678
+ usage: totalUsage,
5679
+ metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, {
5680
+ warning: `Max steps (${maxSteps}) reached`,
5681
+ ...(finalFinishReason ? { finishReason: finalFinishReason } : {}),
5682
+ ...(!parsedOutput.parsed && parsedOutput.outputSchemaError !== undefined
5683
+ ? { outputSchemaError: parsedOutput.outputSchemaError }
5684
+ : {}),
5685
+ }),
5686
+ }, outputSchema);
5582
5687
  }
5583
- throwIfAborted(abortSignal);
5584
- sendSSE(controller, encoder, {
5585
- type: "step-end",
5586
- ...(runtimeObservationStepId
5587
- ? {
5588
- privateRuntimeObservation: {
5589
- version: 1,
5590
- kind: "step_ended",
5591
- stepId: runtimeObservationStepId,
5592
- },
5593
- }
5594
- : {}),
5595
- });
5596
- await pauseAtBoundary(step + 1);
5597
- this.status = "thinking";
5598
- }
5599
- if (!completedWithinStepBudget) {
5600
- // Step-budget exhaustion mirrors the generate loop's max-steps exit: the
5601
- // partial result is still returned, so the structured-output parse is
5602
- // best effort and a failure is surfaced in metadata instead of thrown.
5603
- const parsedOutput = await tryParseMaxStepsOutput(latestAssistantText, outputSchema);
5604
5688
  return attachOutputSchemaParser({
5605
5689
  text: latestAssistantText,
5606
- ...(parsedOutput.parsed ? { data: parsedOutput.object, object: parsedOutput.object } : {}),
5690
+ ...(outputSchema ? await parseStructuredOutput(outputSchema, latestAssistantText) : {}),
5607
5691
  messages: currentMessages,
5608
5692
  toolCalls,
5609
5693
  status: "completed",
5610
5694
  usage: totalUsage,
5611
- metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, {
5612
- warning: `Max steps (${maxSteps}) reached`,
5613
- ...(finalFinishReason ? { finishReason: finalFinishReason } : {}),
5614
- ...(!parsedOutput.parsed && parsedOutput.outputSchemaError !== undefined
5615
- ? { outputSchemaError: parsedOutput.outputSchemaError }
5616
- : {}),
5617
- }),
5695
+ metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, finalFinishReason ? { finishReason: finalFinishReason } : undefined),
5618
5696
  }, outputSchema);
5619
5697
  }
5620
- return attachOutputSchemaParser({
5621
- text: latestAssistantText,
5622
- ...(outputSchema ? await parseStructuredOutput(outputSchema, latestAssistantText) : {}),
5623
- messages: currentMessages,
5624
- toolCalls,
5625
- status: "completed",
5626
- usage: totalUsage,
5627
- metadata: withAgentRunRuntimeContextMetadata(runRuntimeContext, finalFinishReason ? { finishReason: finalFinishReason } : undefined),
5628
- }, outputSchema);
5698
+ catch (e_2) {
5699
+ env_2.error = e_2;
5700
+ env_2.hasError = true;
5701
+ }
5702
+ finally {
5703
+ const result_2 = __disposeResources(env_2);
5704
+ if (result_2)
5705
+ await result_2;
5706
+ }
5629
5707
  }
5630
5708
  /**
5631
5709
  * Record a tool error and send SSE event.