@mono-agent/agent-runtime 0.20.11 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/ARCHITECTURE.md +50 -11
  2. package/MIGRATION.md +288 -26
  3. package/README.md +352 -477
  4. package/package.json +13 -44
  5. package/src/agent/tool-bloat.js +145 -9
  6. package/src/agent/tools/agent-tool.js +108 -9
  7. package/src/agent/tools/bash.js +11 -26
  8. package/src/agent/tools/codex-subscription-search.js +123 -29
  9. package/src/agent/tools/exec.js +10 -2
  10. package/src/agent/tools/index.js +7 -0
  11. package/src/agent/tools/monitor.js +149 -0
  12. package/src/agent/tools/pi-bridge.js +123 -19
  13. package/src/agent/tools/shared/bash-environment.js +31 -0
  14. package/src/agent/tools/shared/monitors.js +293 -0
  15. package/src/agent/tools/shared/path-resolver.js +25 -6
  16. package/src/agent/tools/shared/process-jobs.js +6 -1
  17. package/src/agent/tools/shared/process-runner.js +26 -6
  18. package/src/agent/tools/shared/tool-context.js +8 -0
  19. package/src/agent/tools/web-access-interstitial.js +70 -0
  20. package/src/agent/tools/web-browser-render.js +83 -58
  21. package/src/agent/tools/web-controller.js +112 -21
  22. package/src/agent/tools/web-document-extractor.js +379 -0
  23. package/src/agent/tools/web-fetch.js +271 -243
  24. package/src/agent/tools/web-request.js +65 -0
  25. package/src/agent/tools/web-search-output.js +165 -0
  26. package/src/agent/tools/web-search-state.js +75 -0
  27. package/src/agent/tools/web-search.js +532 -71
  28. package/src/ai/cost.js +13 -68
  29. package/src/ai/failure.js +3 -3
  30. package/src/ai/index.js +5 -17
  31. package/src/ai/observer.js +8 -0
  32. package/src/ai/pi-interop.js +221 -1
  33. package/src/ai/pi-oauth-compat.js +1 -1
  34. package/src/ai/provider-check.js +131 -0
  35. package/src/ai/providers/codex/app-server-client.js +592 -0
  36. package/src/ai/providers/pi-models.js +18 -10
  37. package/src/ai/providers/pi-native/compaction-driver.js +94 -42
  38. package/src/ai/providers/pi-native/compaction-summary.js +140 -0
  39. package/src/ai/providers/pi-native/harness-adapter.js +376 -0
  40. package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
  41. package/src/ai/providers/pi-native/provider-attribution.js +102 -0
  42. package/src/ai/providers/pi-native/result-builder.js +38 -14
  43. package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
  44. package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
  45. package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
  46. package/src/ai/providers/pi-native/turn-runner.js +279 -28
  47. package/src/ai/providers/pi-native.js +206 -61
  48. package/src/ai/runtime/capabilities.js +11 -56
  49. package/src/ai/runtime/live-input-events.js +250 -54
  50. package/src/ai/runtime/model-refs.js +118 -153
  51. package/src/ai/runtime/registry.js +22 -56
  52. package/src/ai/runtime/router.js +76 -417
  53. package/src/ai/runtime/session-liveness.js +3 -4
  54. package/src/ai/runtime/sessions.js +4 -5
  55. package/src/ai/runtime/tool-policy.js +0 -2
  56. package/src/ai/tool-lifecycle.js +32 -18
  57. package/src/ai/types.js +37 -112
  58. package/src/index.js +0 -6
  59. package/src/runtime.js +29 -16
  60. package/types/agent/tool-bloat.d.ts +1 -1
  61. package/types/agent/tools/agent-tool.d.ts +4 -2
  62. package/types/agent/tools/bash.d.ts +5 -3
  63. package/types/agent/tools/codex-subscription-search.d.ts +7 -3
  64. package/types/agent/tools/exec.d.ts +5 -3
  65. package/types/agent/tools/index.d.ts +1 -0
  66. package/types/agent/tools/monitor.d.ts +47 -0
  67. package/types/agent/tools/pi-bridge.d.ts +7 -4
  68. package/types/agent/tools/shared/bash-environment.d.ts +4 -0
  69. package/types/agent/tools/shared/monitors.d.ts +98 -0
  70. package/types/agent/tools/shared/process-jobs.d.ts +5 -1
  71. package/types/agent/tools/shared/process-runner.d.ts +14 -4
  72. package/types/agent/tools/shared/tool-context.d.ts +2 -0
  73. package/types/agent/tools/web-access-interstitial.d.ts +23 -0
  74. package/types/agent/tools/web-browser-render.d.ts +4 -1
  75. package/types/agent/tools/web-controller.d.ts +4 -2
  76. package/types/agent/tools/web-document-extractor.d.ts +27 -0
  77. package/types/agent/tools/web-fetch.d.ts +19 -24
  78. package/types/agent/tools/web-request.d.ts +20 -0
  79. package/types/agent/tools/web-search-output.d.ts +31 -0
  80. package/types/agent/tools/web-search-state.d.ts +21 -0
  81. package/types/agent/tools/web-search.d.ts +10 -45
  82. package/types/ai/cost.d.ts +1 -2
  83. package/types/ai/index.d.ts +2 -4
  84. package/types/ai/observer.d.ts +6 -0
  85. package/types/ai/pi-interop.d.ts +81 -0
  86. package/types/ai/provider-check.d.ts +53 -0
  87. package/types/ai/providers/codex/app-server-client.d.ts +37 -0
  88. package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
  89. package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
  90. package/types/ai/providers/pi-native/harness-adapter.d.ts +58 -0
  91. package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
  92. package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
  93. package/types/ai/providers/pi-native/result-builder.d.ts +14 -4
  94. package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
  95. package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
  96. package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
  97. package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
  98. package/types/ai/providers/pi-native.d.ts +21 -4
  99. package/types/ai/runtime/capabilities.d.ts +21 -70
  100. package/types/ai/runtime/live-input-events.d.ts +32 -8
  101. package/types/ai/runtime/model-refs.d.ts +0 -24
  102. package/types/ai/runtime/router.d.ts +3 -10
  103. package/types/ai/runtime/tool-policy.d.ts +0 -2
  104. package/types/ai/tool-lifecycle.d.ts +4 -3
  105. package/types/ai/types.d.ts +162 -256
  106. package/types/index.d.ts +0 -1
  107. package/src/ai/providers/acp-client.js +0 -1149
  108. package/src/ai/providers/acp-privacy.js +0 -124
  109. package/src/ai/providers/acp-public.js +0 -21
  110. package/src/ai/providers/acp-session-tokens.js +0 -282
  111. package/src/ai/providers/acp-transport.js +0 -356
  112. package/src/ai/providers/acp.js +0 -543
  113. package/src/ai/providers/claude-cli.js +0 -883
  114. package/src/ai/providers/claude-sandbox.js +0 -71
  115. package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
  116. package/src/ai/providers/claude-sdk-discovery.js +0 -352
  117. package/src/ai/providers/claude-sdk.js +0 -1127
  118. package/src/ai/providers/claude-subagent-activity.js +0 -719
  119. package/src/ai/providers/claude-subagents.js +0 -88
  120. package/src/ai/providers/codex-app.js +0 -2946
  121. package/src/ai/providers/opencode-app.js +0 -1109
  122. package/src/ai/providers/opencode-discovery.js +0 -39
  123. package/src/ai/providers/opencode-server.js +0 -508
  124. package/src/ai/runtime/context-windows.js +0 -46
  125. package/src/ai/runtime/fast-mode.js +0 -8
  126. package/src/ai/streaming/codex-events.js +0 -146
  127. package/src/ai/streaming/opencode-events.js +0 -59
  128. package/types/ai/providers/acp-client.d.ts +0 -227
  129. package/types/ai/providers/acp-privacy.d.ts +0 -25
  130. package/types/ai/providers/acp-public.d.ts +0 -7
  131. package/types/ai/providers/acp-session-tokens.d.ts +0 -41
  132. package/types/ai/providers/acp-transport.d.ts +0 -45
  133. package/types/ai/providers/acp.d.ts +0 -93
  134. package/types/ai/providers/claude-cli.d.ts +0 -305
  135. package/types/ai/providers/claude-sandbox.d.ts +0 -79
  136. package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
  137. package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
  138. package/types/ai/providers/claude-sdk.d.ts +0 -138
  139. package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
  140. package/types/ai/providers/claude-subagents.d.ts +0 -18
  141. package/types/ai/providers/codex-app.d.ts +0 -151
  142. package/types/ai/providers/opencode-app.d.ts +0 -96
  143. package/types/ai/providers/opencode-discovery.d.ts +0 -4
  144. package/types/ai/providers/opencode-server.d.ts +0 -20
  145. package/types/ai/runtime/context-windows.d.ts +0 -9
  146. package/types/ai/runtime/fast-mode.d.ts +0 -2
  147. package/types/ai/streaming/codex-events.d.ts +0 -40
  148. package/types/ai/streaming/opencode-events.d.ts +0 -42
@@ -20,8 +20,7 @@
20
20
  // so the router is a drop-in replacement for createRuntime(host).
21
21
  //
22
22
  // chain entries:
23
- // { model: ModelRef, executionMode?: "sdk" | "cli" | "acp", effort?: string|null,
24
- // requires?: Capabilities }
23
+ // { model: ModelRef, effort?: string|null, requires?: Capabilities }
25
24
  // shorthand: a bare ModelRef is also accepted (no requirements).
26
25
  // effort string = fixed for that route, undefined = inherit the legacy run
27
26
  // effort, null = omit effort so the provider chooses its default.
@@ -29,6 +28,8 @@
29
28
  // Result:
30
29
  // The success run's result, with `failoverHistory` appended describing every
31
30
  // prior attempt: [{ model, failureKind, requestId, retryableSubkind }].
31
+ // Successful retries/backups withhold providerSessionId: their stateless
32
+ // answer cannot synchronize the primary provider session.
32
33
  // If every eligible retryable/auth entry in the chain fails, returns the last
33
34
  // result with `failureKind: "provider_unavailable_exhausted"`. Terminal
34
35
  // non-retryable failures are returned as-is with their failover history.
@@ -43,6 +44,7 @@ import { passthroughSandbox } from "../../agent/sandbox-seam.js";
43
44
  import { resolveRuntimeBrand } from "../../runtime-brand.js";
44
45
  import { createObserverHub } from "../observer.js";
45
46
  import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
47
+ import { createWebSearchRunState } from "../../agent/tools/web-search-state.js";
46
48
 
47
49
  /**
48
50
  * @typedef {import('../types.js').RuntimeModelRef} RuntimeModelRef
@@ -55,10 +57,8 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
55
57
  /**
56
58
  * @typedef {Object} RouterChainEntryInput
57
59
  * A chain entry as accepted by createRouterRuntime: either the shorthand bare
58
- * RuntimeModelRef, or the full `{model, executionMode?, effort?, requires?,
59
- * attempts?}` form.
60
+ * RuntimeModelRef, or the full `{model, effort?, requires?, attempts?}` form.
60
61
  * @property {RuntimeModelRef} model
61
- * @property {string} [executionMode]
62
62
  * @property {string|null} [effort]
63
63
  * @property {Object<string, *>} [requires]
64
64
  * @property {number} [attempts]
@@ -67,7 +67,6 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
67
67
  /**
68
68
  * @typedef {Object} RouterChainEntry
69
69
  * @property {RuntimeModelRef} model
70
- * @property {string|null} executionMode
71
70
  * @property {string|null|undefined} effort
72
71
  * @property {Object<string, *>|null} requires
73
72
  * @property {number} attempts Total attempts on this route including the first.
@@ -91,18 +90,19 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
91
90
  * @property {() => (void|Promise<void>)} [cleanup]
92
91
  */
93
92
 
94
- const ROUTE_SAFETY_MODES = new Set(["uniform", "per-route-native"]);
95
93
  const ATTEMPT_SCOPED_OPTION_KEYS = ["customProvider", "customModel", "modelCapabilities", "isPrivateProvider"];
96
94
  const ROUTER_TOOL_CONTEXT_KEYS = [
97
- "workspace", "repoRoot", "ripgrepPath", "qaOutputDir", "sandboxPolicy", "sandboxEngine",
95
+ "workspace", "repoRoot", "additionalReadRoots", "additionalWriteRoots",
96
+ "ripgrepPath", "qaOutputDir", "sandboxPolicy", "sandboxEngine",
98
97
  ];
99
98
  const RESOLVER_PROTECTED_OPTION_KEYS = new Set([
100
- "model", "executionMode", "effort", "messages", "abortSignal", "onEvent",
101
- "sessionId", "providerSessionId", "sessionKeepAlive", "sessionIdleTimeoutMs",
99
+ "model", "effort", "messages", "abortSignal", "onEvent",
100
+ "sessionRecovery", "sessionId", "providerSessionId", "providerAttributionSessionId", "sessionKeepAlive", "sessionIdleTimeoutMs",
102
101
  "diagnosticsSeed", "systemPromptPrefix", "sandboxPolicy", "sandboxEngine", "sandbox",
103
102
  "allowedTools", "disallowedTools", "permissionMode", "mcpServers", "mcpApps", "skills",
104
- "outputSchema", "nativeSubagents", "liveInput", "fastMode", "toolEnvironment",
105
- "codexSandboxNetworkAccess",
103
+ "mcpCallNoTotalTimeoutTools",
104
+ "webSearchState",
105
+ "outputSchema", "liveInput", "toolEnvironment", "persistArtifact",
106
106
  ]);
107
107
 
108
108
  class ResolverProtectedOptionError extends Error {
@@ -117,16 +117,12 @@ class ResolverProtectedOptionError extends Error {
117
117
  * @param {Object} [options]
118
118
  * @param {AgentRuntimeHostOptions} [options.host]
119
119
  * @param {ReadonlyArray<RuntimeModelRef|RouterChainEntryInput>} [options.chain]
120
- * @param {"uniform"|"per-route-native"} [options.routeSafety]
121
- * @param {(input: {model: RuntimeModelRef, executionMode: string|null, attemptIndex: number, retryIndex: number, routeSafety: "uniform"|"per-route-native"}) => (RouterAttemptResolution|Promise<RouterAttemptResolution>)} [options.resolveAttempt]
120
+ * @param {(input: {model: RuntimeModelRef, attemptIndex: number, retryIndex: number}) => (RouterAttemptResolution|Promise<RouterAttemptResolution>)} [options.resolveAttempt]
122
121
  * @param {Partial<RouterRetryPolicy>} [options.retry] Backoff shape for same-model
123
122
  * retries. Per-route retry counts live on each chain entry's `attempts`.
124
123
  * @returns {AgentRuntimeInstance & {chain: () => Array<RouterChainEntry>}}
125
124
  */
126
- export function createRouterRuntime({ host = {}, chain = [], routeSafety = "uniform", resolveAttempt, retry } = {}) {
127
- if (!ROUTE_SAFETY_MODES.has(routeSafety)) {
128
- throw new Error("createRouterRuntime routeSafety must be uniform or per-route-native");
129
- }
125
+ export function createRouterRuntime({ host = {}, chain = [], resolveAttempt, retry } = {}) {
130
126
  const retryPolicy = normalizeRetryPolicy(retry);
131
127
  const entries = normaliseChain(chain);
132
128
  if (entries.length === 0) {
@@ -134,8 +130,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
134
130
  }
135
131
  assertUniqueEntries(entries);
136
132
  const inner = createRuntime(host);
137
- /** @type {Map<string, AgentRuntimeInstance>} */
138
- const routeRuntimes = new Map();
139
133
  /** @type {import('../types.js').AgentRuntimeToolOptions|undefined} */
140
134
  let configuredTools;
141
135
  // The router builds transcript-tail snapshots outside the inner runtime's
@@ -148,11 +142,15 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
148
142
  /**
149
143
  * @param {string} systemPrompt
150
144
  * @param {Partial<RuntimeRunOptions>} [options] Optional so a bare `{}` call
151
- * is legal; the router always overrides `model`/`executionMode` per chain
152
- * entry (see AgentRuntimeInstance.run for the public, model-required contract).
145
+ * is legal; the router always overrides `model` per chain entry (see
146
+ * AgentRuntimeInstance.run for the public, model-required contract).
153
147
  * @returns {Promise<RuntimeResult>}
154
148
  */
155
149
  async run(systemPrompt, options = {}) {
150
+ options = {
151
+ ...options,
152
+ webSearchState: createWebSearchRunState(options.webSearchConfig, options.webSearchState),
153
+ };
156
154
  const liveInputHub = options.liveInput === undefined
157
155
  ? undefined
158
156
  : createObserverHub({
@@ -169,7 +167,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
169
167
  };
170
168
  }
171
169
  try {
172
- /** @type {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null|undefined), retryableSubkind?: (string|null|undefined), requirements?: (Object<string,*>|null), routeSafety?: import('../types.js').RuntimeRouteSafetyMode, safetyContract?: import('../types.js').RuntimeRouteSafetyContract}>} */
170
+ /** @type {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null|undefined), retryableSubkind?: (string|null|undefined), requirements?: (Object<string,*>|null), retryIndex?: number}>} */
173
171
  const failoverHistory = [];
174
172
  /** @type {RuntimeResult|null} */
175
173
  let lastResult = null;
@@ -178,46 +176,9 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
178
176
  const promptBase = systemPrompt;
179
177
  /** @type {*} */
180
178
  let pendingSnapshot = null;
181
- /** @type {Array<{attemptIndex: number, model: RuntimeModelRef, routeSafety: import('../types.js').RuntimeRouteSafetyMode, safetyContract: import('../types.js').RuntimeRouteSafetyContract, status: string}>} */
182
- const routeSafetyHistory = [];
183
-
184
179
  for (let i = 0; i < entries.length; i += 1) {
185
180
  const entry = entries[i];
186
181
  const effectiveToolOptions = effectiveRouterToolOptions(host, configuredTools);
187
- const entrySafetyContract = routeSafetyContract(
188
- routeSafety,
189
- entry,
190
- entry.model.sdk === "pi"
191
- ? effectivePiSandboxPolicy(effectiveToolOptions, options)
192
- : undefined,
193
- );
194
- // Internal protected roots are enforced by mono-agent's Pi tool layer
195
- // and SRT projection. Provider-native non-Pi routes deliberately drop
196
- // that layer, so attempting one would turn the router into a confused
197
- // deputy for private host state. Reject the route before resolution or
198
- // provider construction; a later Pi entry may still satisfy the run.
199
- if (
200
- entry.model.sdk !== "pi"
201
- && attemptCarriesProtectedRoots(effectiveToolOptions, options)
202
- ) {
203
- const failure = safetyUnavailableResult();
204
- lastRouteSkip = failure;
205
- failoverHistory.push({
206
- model: entry.model,
207
- failureKind: "safety_unavailable",
208
- routeSafety,
209
- safetyContract: entrySafetyContract,
210
- });
211
- const unavailableRecord = routeSafetyRecord(
212
- i,
213
- entry,
214
- entrySafetyContract,
215
- "safety_unavailable",
216
- );
217
- routeSafetyHistory.push(unavailableRecord);
218
- emit(options, { type: "provider_route_safety", ...unavailableRecord });
219
- continue;
220
- }
221
182
  if (!entrySatisfiesRequirements(entry, options)) {
222
183
  lastRouteSkip = {
223
184
  text: null,
@@ -231,12 +192,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
231
192
  model: entry.model,
232
193
  failureKind: "skipped_capability_mismatch",
233
194
  requirements: entry.requires,
234
- routeSafety,
235
- safetyContract: entrySafetyContract,
236
195
  });
237
- const skippedRecord = routeSafetyRecord(i, entry, entrySafetyContract, "skipped_capability_mismatch");
238
- routeSafetyHistory.push(skippedRecord);
239
- emit(options, { type: "provider_route_safety", ...skippedRecord });
240
196
  continue;
241
197
  }
242
198
 
@@ -249,7 +205,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
249
205
  const entryOptionsBase = {
250
206
  ...options,
251
207
  model: entry.model,
252
- executionMode: entry.executionMode || options.executionMode,
253
208
  };
254
209
  // The legacy run-level custom-provider bag describes the primary
255
210
  // route. Without a route resolver there is no authoritative metadata
@@ -263,7 +218,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
263
218
  let terminalResult = null;
264
219
 
265
220
  for (let retryIndex = 0; retryIndex < entry.attempts; retryIndex += 1) {
266
- let safetyContract = entrySafetyContract;
267
221
  /** @type {*} */
268
222
  let callOptions = { ...entryCallBase };
269
223
  /** @type {AgentRuntimeInstance} */
@@ -275,10 +229,8 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
275
229
  ? undefined
276
230
  : await resolveAttempt({
277
231
  model: entry.model,
278
- executionMode: entry.executionMode,
279
232
  attemptIndex: i,
280
233
  retryIndex,
281
- routeSafety,
282
234
  });
283
235
  const resolution = normalizeAttemptResolution(resolved);
284
236
  attemptCleanup = resolution?.cleanup;
@@ -286,63 +238,31 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
286
238
  callOptions = mergeAttemptOptions(callOptions, resolution?.options);
287
239
  callOptions = mergeAttemptPolicyOptions(callOptions, resolution?.policyOptions);
288
240
  }
289
- if (routeSafety === "per-route-native") {
290
- callOptions = projectPerRouteNativeOptions(entry, callOptions);
291
- const key = routeRuntimeKey(entry, i);
292
- const resolvedRuntime = resolution?.runtime;
293
- if (resolvedRuntime !== undefined) {
294
- assertRuntimeLike(resolvedRuntime);
295
- const previousRuntime = routeRuntimes.get(key);
296
- if (previousRuntime !== undefined && previousRuntime !== resolvedRuntime) {
297
- try { await previousRuntime.disposeAllSessions?.(); } catch { /* best-effort replacement */ }
298
- }
299
- routeRuntimes.set(key, resolvedRuntime);
300
- if (entry.model.sdk !== "pi") {
301
- resolvedRuntime.configureTools?.(projectPerRouteNativeToolOptions(entry, configuredTools));
302
- }
303
- }
304
- attemptRuntime = routeRuntimes.get(key) ?? createRouteRuntime(key, entry, host, routeRuntimes, configuredTools);
305
- if (entry.model.sdk === "pi") {
306
- projectPiRuntimeToolContext(attemptRuntime, effectiveToolOptions);
307
- // Derive the attestation from the same complete base context and
308
- // request-scoped inputs that the supplied/runtime-owned Pi
309
- // bridge will actually receive. Resolver options cannot alter
310
- // these protected fields.
311
- safetyContract = routeSafetyContract(
312
- routeSafety,
313
- entry,
314
- effectivePiSandboxPolicy(effectiveToolOptions, callOptions),
315
- );
316
- }
317
- } else if (resolution?.runtime !== undefined && resolution.runtime !== inner) {
318
- throw new Error("uniform route safety cannot replace the shared monotonic runtime");
241
+ if (resolution?.runtime !== undefined) {
242
+ assertRuntimeLike(resolution.runtime);
243
+ attemptRuntime = resolution.runtime;
244
+ projectPiRuntimeToolContext(attemptRuntime, effectiveToolOptions);
319
245
  }
320
246
  } catch (error) {
321
247
  try { await attemptCleanup?.(); } catch { /* cleanup is additive */ }
322
- const failure = safetyUnavailableResult(error);
323
- lastRouteSkip = failure;
248
+ const failure = attemptResolutionFailureResult(error);
249
+ lastResult = failure;
324
250
  failoverHistory.push({
325
251
  model: entry.model,
326
- failureKind: "safety_unavailable",
327
- routeSafety,
328
- safetyContract,
252
+ failureKind: failure.failureKind || null,
329
253
  });
330
- const unavailableRecord = routeSafetyRecord(i, entry, safetyContract, "safety_unavailable");
331
- routeSafetyHistory.push(unavailableRecord);
332
- emit(callOptions, { type: "provider_route_safety", ...unavailableRecord });
333
254
  // A resolver fault is a config/credential problem, not a transient
334
255
  // provider blip: retrying the same route cannot fix it. Advance.
335
256
  break;
336
257
  }
337
258
 
338
259
  applyEntryEffort(callOptions, entry.effort);
339
- // A provider session belongs to the route AND to the attempt that
340
- // created it. The entire chain is stateless whenever a fallback exists,
341
- // keeping the full logical run replayable regardless of which route is
342
- // attempted. A same-model retry re-sends the whole logical turn, so
343
- // resuming the session the failed attempt already appended into would
344
- // duplicate the turn or hit session_busy.
345
- if (entries.length > 1 || i > 0 || retryIndex > 0 || !entrySupportsSessionResume(entry)) {
260
+ // Only the primary's first attempt may own a provider session. Retries
261
+ // replay the logical turn and must not resume a transcript the failed
262
+ // attempt may have appended to; backup routes never inherit that session.
263
+ const sessionEligibleAttempt = i === 0 && retryIndex === 0 && entrySupportsSessionResume(entry);
264
+ if (!sessionEligibleAttempt) {
265
+ delete callOptions.sessionRecovery;
346
266
  delete callOptions.sessionId;
347
267
  delete callOptions.providerSessionId;
348
268
  delete callOptions.sessionKeepAlive;
@@ -363,17 +283,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
363
283
  }
364
284
  }
365
285
 
366
- // The safety contract is a property of the route: resolver options can
367
- // never reach the sandbox/tool policy (RESOLVER_PROTECTED_OPTION_KEYS)
368
- // and effectivePiSandboxPolicy reads only protected fields, so every
369
- // retry of one entry derives an identical contract. Record it once so
370
- // the bounded safety telemetry stays one record per chain entry.
371
- if (retryIndex === 0) {
372
- const safetyRecord = routeSafetyRecord(i, entry, safetyContract, "attempted");
373
- routeSafetyHistory.push(safetyRecord);
374
- emit(callOptions, { type: "provider_route_safety", ...safetyRecord });
375
- }
376
-
377
286
  // A same-model retry is not a failover: only the first attempt of a new
378
287
  // route announces a transition.
379
288
  if (retryIndex === 0 && failoverHistory.length > 0) {
@@ -411,6 +320,10 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
411
320
  }
412
321
 
413
322
  result = normalizeProviderAuthFailure(result);
323
+ if (!sessionEligibleAttempt) {
324
+ const { providerSessionRecovery: _receipt, ...unownedResult } = result;
325
+ result = unownedResult;
326
+ }
414
327
 
415
328
  const retryability = retryableProviderFailureInfo({
416
329
  errorText: result.error || "",
@@ -433,7 +346,10 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
433
346
  model: modelKey(entry.model),
434
347
  });
435
348
  }
436
- return { ...result, failoverHistory, routeSafetyHistory };
349
+ // Pi may report the attribution id as its session id even on a
350
+ // stateless call. Withhold that resumable id so host history cannot
351
+ // synchronize an untouched or failed primary transcript.
352
+ return { ...result, ...(sessionEligibleAttempt ? {} : { providerSessionId: undefined }), failoverHistory };
437
353
  }
438
354
 
439
355
  failoverHistory.push({
@@ -442,8 +358,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
442
358
  requestId: retryability.requestId,
443
359
  retryableSubkind: retryability.subkind,
444
360
  ...(retryIndex > 0 ? { retryIndex } : {}),
445
- routeSafety,
446
- safetyContract,
447
361
  });
448
362
  if (result.failureKind === "skipped_capability_mismatch") {
449
363
  lastRouteSkip = result;
@@ -494,16 +408,16 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
494
408
  reason: retryability.subkind || result.failureKind || null,
495
409
  });
496
410
  if (callOptions.abortSignal?.aborted) {
497
- return { ...result, cancelled: true, failoverHistory, routeSafetyHistory };
411
+ return { ...result, cancelled: true, failoverHistory };
498
412
  }
499
413
  await delay(backoffMs, callOptions.abortSignal);
500
414
  if (callOptions.abortSignal?.aborted) {
501
- return { ...result, cancelled: true, failoverHistory, routeSafetyHistory };
415
+ return { ...result, cancelled: true, failoverHistory };
502
416
  }
503
417
  }
504
418
 
505
419
  if (terminalResult !== null) {
506
- return { ...terminalResult, failoverHistory, routeSafetyHistory };
420
+ return { ...terminalResult, failoverHistory };
507
421
  }
508
422
  // Every other inner break falls through to the next chain entry.
509
423
  }
@@ -520,7 +434,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
520
434
  ...exhaustedResult,
521
435
  failureKind: lastResult ? "provider_unavailable_exhausted" : exhaustedResult.failureKind,
522
436
  failoverHistory,
523
- routeSafetyHistory,
524
437
  };
525
438
  } finally {
526
439
  await liveInputHub?.flush();
@@ -529,65 +442,42 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
529
442
  chain: () => entries.slice(),
530
443
  configureTools(next = {}) {
531
444
  configuredTools = { ...(configuredTools || {}), ...next };
532
- if (routeSafety === "uniform") {
533
- inner.configureTools?.(next);
534
- return;
535
- }
536
- entries.forEach((entry, index) => {
537
- const runtime = routeRuntimes.get(routeRuntimeKey(entry, index));
538
- runtime?.configureTools?.(projectPerRouteNativeToolOptions(entry, next));
539
- });
540
- // `inner` is deliberately not configured in per-route-native mode: all
541
- // attempts use their isolated route runtime, and applying one route's
542
- // policy to this shared standby would defeat that isolation.
445
+ inner.configureTools?.(next);
446
+ },
447
+ async recoverSession(receipt, context) {
448
+ return await inner.recoverSession?.(receipt, context) === true;
543
449
  },
544
450
  async syncSession(providerSessionId) {
545
- let synced = false;
546
- for (const runtime of allRuntimes(inner, routeRuntimes)) {
547
- synced = Boolean(await runtime.syncSession?.(providerSessionId)) || synced;
548
- }
549
- return synced;
451
+ return Boolean(await inner.syncSession?.(providerSessionId));
550
452
  },
551
453
  async refreshSession(providerSessionId) {
552
- for (const runtime of allRuntimes(inner, routeRuntimes)) {
553
- if (typeof runtime.refreshSession !== "function") {
554
- throw new Error("A routed runtime cannot guarantee a cold provider-session reopen");
555
- }
556
- await runtime.refreshSession(providerSessionId);
454
+ if (typeof inner.refreshSession !== "function") {
455
+ throw new Error("A routed runtime cannot guarantee a cold provider-session reopen");
557
456
  }
457
+ await inner.refreshSession(providerSessionId);
558
458
  },
559
459
  async retireDurableSession(providerSessionId, sessionsRoot) {
560
- for (const runtime of allRuntimes(inner, routeRuntimes)) {
561
- if (typeof runtime.retireDurableSession !== "function") {
562
- throw new Error("A routed runtime cannot retire durable provider-session state");
563
- }
564
- await runtime.retireDurableSession(providerSessionId, sessionsRoot);
460
+ if (typeof inner.retireDurableSession !== "function") {
461
+ throw new Error("A routed runtime cannot retire durable provider-session state");
565
462
  }
463
+ await inner.retireDurableSession(providerSessionId, sessionsRoot);
566
464
  },
567
465
  async disposeSession(providerSessionId) {
568
- let disposed = false;
569
- for (const runtime of allRuntimes(inner, routeRuntimes)) {
570
- disposed = Boolean(await runtime.disposeSession?.(providerSessionId)) || disposed;
571
- }
572
- return disposed;
466
+ return Boolean(await inner.disposeSession?.(providerSessionId));
573
467
  },
574
468
  async invalidateSession(providerSessionId) {
575
- let invalidated = false;
576
- for (const runtime of allRuntimes(inner, routeRuntimes)) {
577
- invalidated = Boolean(await runtime.invalidateSession?.(providerSessionId)) || invalidated;
578
- }
579
- return invalidated;
469
+ return Boolean(await inner.invalidateSession?.(providerSessionId));
580
470
  },
581
471
  async disposeAllSessions() {
582
- await Promise.all(allRuntimes(inner, routeRuntimes).map(async (runtime) => runtime.disposeAllSessions?.()));
472
+ await inner.disposeAllSessions?.();
583
473
  },
584
474
  };
585
475
  }
586
476
 
587
477
  /**
588
478
  * @param {ReadonlyArray<*>} chain ReadonlyArray<RuntimeModelRef|RouterChainEntryInput>, loosened
589
- * here because distinguishing the two shapes is a runtime duck-type check
590
- * (`entry.sdk && entry.model`), not something a union type narrows cleanly.
479
+ * here because distinguishing the two shapes is a runtime duck-type check,
480
+ * not something a union type narrows cleanly.
591
481
  * @returns {Array<RouterChainEntry>}
592
482
  */
593
483
  function normaliseChain(chain) {
@@ -595,14 +485,13 @@ function normaliseChain(chain) {
595
485
  return /** @type {Array<RouterChainEntry>} */ (chain
596
486
  .map((entry) => {
597
487
  if (!entry) return null;
598
- if (entry.sdk && entry.model) {
599
- // ModelRef shorthand: { sdk, model, ... }
600
- return { model: entry, executionMode: null, effort: undefined, requires: null, attempts: 1 };
488
+ if (typeof entry.provider === "string" && typeof entry.model === "string") {
489
+ // ModelRef shorthand: { provider, model, reference }
490
+ return { model: entry, effort: undefined, requires: null, attempts: 1 };
601
491
  }
602
492
  if (entry.model) {
603
493
  return {
604
494
  model: entry.model,
605
- executionMode: typeof entry.executionMode === "string" ? entry.executionMode : null,
606
495
  effort: normalizeChainEffort(entry.effort),
607
496
  requires: entry.requires && typeof entry.requires === "object" ? entry.requires : null,
608
497
  attempts: normalizeChainAttempts(entry.attempts),
@@ -692,87 +581,7 @@ function assertUniqueEntries(entries) {
692
581
 
693
582
  /** @param {RuntimeModelRef} model */
694
583
  function modelKey(model) {
695
- const provider = typeof model.provider === "string" && model.provider.length > 0 ? `${model.provider}:` : "";
696
- return `${model.sdk}:${provider}${model.model}`;
697
- }
698
-
699
- /**
700
- * A small fixed vocabulary keeps safety telemetry bounded and prevents route
701
- * credentials/options from accidentally entering events or persisted results.
702
- * @param {"uniform"|"per-route-native"} mode
703
- * @param {RouterChainEntry} entry
704
- * @param {Object<string, *>|undefined} piSandboxPolicy
705
- * @returns {import('../types.js').RuntimeRouteSafetyContract}
706
- */
707
- function routeSafetyContract(mode, entry, piSandboxPolicy) {
708
- if (mode === "uniform") {
709
- return {
710
- mode,
711
- sandbox: "mono-agent-monotonic",
712
- tools: "mono-agent-monotonic",
713
- };
714
- }
715
- switch (entry.model.sdk) {
716
- case "pi":
717
- return {
718
- mode,
719
- sandbox: piSandboxContract(piSandboxPolicy),
720
- tools: "mono-agent-policy",
721
- };
722
- case "claude":
723
- return { mode, sandbox: "provider-native", tools: "provider-representable" };
724
- case "codex":
725
- return { mode, sandbox: "codex-native", tools: "exact-allow-all" };
726
- case "opencode":
727
- return { mode, sandbox: "provider-native", tools: "exact-allow-all" };
728
- case "acp":
729
- return { mode, sandbox: "provider-native", tools: "exact-allow-all" };
730
- default:
731
- return { mode, sandbox: "unsupported", tools: "unsupported" };
732
- }
733
- }
734
-
735
- /**
736
- * Describe the Pi sandbox posture without claiming that SRT is enforced when
737
- * the effective policy explicitly permits an unavailable engine to fall back
738
- * to an unsandboxed host process. Both fields are required because the runtime
739
- * adapter treats an unsafe fallback without its explicit opt-in as fail-closed.
740
- *
741
- * @param {Object<string, *>|undefined} policy
742
- * @returns {import('../types.js').RuntimeRouteSandboxContract}
743
- */
744
- function piSandboxContract(policy) {
745
- if (policy === undefined) return "disabled";
746
- if (policy.fallback === "unsafe-host-process" && policy.unsafeAllowHostProcess === true) {
747
- return "mono-agent-srt-unsafe-host-fallback";
748
- }
749
- return "mono-agent-srt";
750
- }
751
-
752
- /**
753
- * Describe the policy Pi tools actually receive after host/configure/run
754
- * precedence. A request-scoped `off` policy cannot weaken an active host
755
- * policy, while an active request policy can tighten an absent/off host.
756
- * `configureTools({ sandboxPolicy: undefined })` explicitly clears the host
757
- * tool-context policy and must therefore be distinguished from an omitted key.
758
- *
759
- * @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
760
- * @param {Object<string, *>} runOptions
761
- * @returns {Object<string, *>|undefined}
762
- */
763
- function effectivePiSandboxPolicy(toolOptions, runOptions) {
764
- // Match the Pi turn runner's implementation precedence: a run-scoped
765
- // RuntimeSandbox wins, followed by configureTools/host, then the kernel's
766
- // fail-closed passthrough. Delegating the merge is essential here: the real
767
- // adapter makes fail-closed dominate unsafe-host-process, so inspecting only
768
- // the first non-off policy would produce false safety telemetry.
769
- const sandbox = runOptions.sandbox
770
- ?? toolOptions.sandbox
771
- ?? passthroughSandbox;
772
- const policy = sandbox.mergePolicies(toolOptions.sandboxPolicy, runOptions.sandboxPolicy);
773
- return policy && typeof policy === "object" && policy.mode !== "off"
774
- ? policy
775
- : undefined;
584
+ return model.reference;
776
585
  }
777
586
 
778
587
  /**
@@ -801,70 +610,22 @@ function effectiveRouterToolOptions(host, configuredTools) {
801
610
  return effective;
802
611
  }
803
612
 
804
- /**
805
- * A protected-root policy is host-internal and intentionally has no public
806
- * provider-native projection. Treat a present malformed value or an accessor
807
- * failure as protected so untrusted option shapes cannot turn this gate into a
808
- * fail-open boundary. Empty arrays preserve ordinary provider-native routing.
809
- *
810
- * @param {unknown} policy
811
- * @returns {boolean}
812
- */
813
- function sandboxPolicyHasProtectedRoots(policy) {
814
- if (policy === null || typeof policy !== "object") return false;
815
- try {
816
- if (!("protectedRoots" in policy)) return false;
817
- const protectedRoots = /** @type {{protectedRoots?: unknown}} */ (policy).protectedRoots;
818
- if (protectedRoots === undefined) return false;
819
- return !Array.isArray(protectedRoots) || protectedRoots.length > 0;
820
- } catch {
821
- return true;
822
- }
823
- }
824
-
825
- /**
826
- * @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
827
- * @param {Object<string, *>} runOptions
828
- * @returns {boolean}
829
- */
830
- function attemptCarriesProtectedRoots(toolOptions, runOptions) {
831
- return sandboxPolicyHasProtectedRoots(toolOptions.sandboxPolicy)
832
- || sandboxPolicyHasProtectedRoots(runOptions.sandboxPolicy);
833
- }
834
-
835
613
  /**
836
614
  * A resolver-supplied Pi runtime is allowed to own credentials/provider
837
- * lifecycle, never the route's safety posture. Replace its complete mutable
838
- * ToolContext before every execution so a blank, stale, or weaker runtime
839
- * cannot diverge from the router's host/configured/run policy or telemetry.
615
+ * lifecycle, never the router-owned tool context. Replace its complete mutable
616
+ * ToolContext before every execution so a blank or stale runtime cannot diverge
617
+ * from the router's host/configured/run policy or telemetry.
840
618
  *
841
619
  * @param {AgentRuntimeInstance} runtime
842
620
  * @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
843
621
  */
844
622
  function projectPiRuntimeToolContext(runtime, toolOptions) {
845
623
  if (typeof runtime.configureTools !== "function") {
846
- throw new Error("per-route-native Pi runtime must expose configureTools() for safety projection");
624
+ throw new Error("route attempt runtime must expose configureTools() for tool-context projection");
847
625
  }
848
626
  runtime.configureTools(toolOptions);
849
627
  }
850
628
 
851
- /**
852
- * @param {number} attemptIndex
853
- * @param {RouterChainEntry} entry
854
- * @param {import('../types.js').RuntimeRouteSafetyContract} contract
855
- * @param {string} status
856
- * @returns {{attemptIndex: number, model: RuntimeModelRef, routeSafety: import('../types.js').RuntimeRouteSafetyMode, safetyContract: import('../types.js').RuntimeRouteSafetyContract, status: string}}
857
- */
858
- function routeSafetyRecord(attemptIndex, entry, contract, status) {
859
- return {
860
- attemptIndex,
861
- model: entry.model,
862
- routeSafety: contract.mode,
863
- safetyContract: contract,
864
- status,
865
- };
866
- }
867
-
868
629
  /** @param {RouterAttemptResolution|undefined} value */
869
630
  function normalizeAttemptResolution(value) {
870
631
  if (value === undefined) return undefined;
@@ -947,89 +708,6 @@ function withoutAttemptScopedOptions(options) {
947
708
  return projected;
948
709
  }
949
710
 
950
- /**
951
- * Explicit mixed-route projection. Capability-bearing inputs (MCP, skills,
952
- * schema, live input, native subagents) are never removed here; the capability
953
- * gate either proves support or skips the route before execution.
954
- * @param {RouterChainEntry} entry
955
- * @param {Object<string, *>} options
956
- */
957
- function projectPerRouteNativeOptions(entry, options) {
958
- const projected = { ...options };
959
- switch (entry.model.sdk) {
960
- case "pi":
961
- return projected;
962
- case "claude":
963
- delete projected.sandboxPolicy;
964
- delete projected.sandboxEngine;
965
- return projected;
966
- case "codex":
967
- case "opencode":
968
- case "acp":
969
- delete projected.sandboxPolicy;
970
- delete projected.sandboxEngine;
971
- projected.allowedTools = ["*"];
972
- projected.disallowedTools = [];
973
- return projected;
974
- default:
975
- throw new Error(`per-route-native safety has no contract for sdk ${entry.model.sdk}`);
976
- }
977
- }
978
-
979
- /**
980
- * @param {string} key
981
- * @param {RouterChainEntry} entry
982
- * @param {AgentRuntimeHostOptions} host
983
- * @param {Map<string, AgentRuntimeInstance>} runtimes
984
- * @param {import('../types.js').AgentRuntimeToolOptions|undefined} configuredTools
985
- */
986
- function createRouteRuntime(key, entry, host, runtimes, configuredTools) {
987
- const runtime = createRuntime(projectPerRouteNativeHost(entry, host));
988
- if (configuredTools !== undefined) {
989
- runtime.configureTools?.(projectPerRouteNativeToolOptions(entry, configuredTools));
990
- }
991
- runtimes.set(key, runtime);
992
- return runtime;
993
- }
994
-
995
- /**
996
- * Provider-native routes retain the injected sandbox implementation seam, but
997
- * must never inherit mono-agent policy data or a concrete srt engine. Assigning
998
- * explicit `undefined` values (rather than deleting the keys) also clears a
999
- * previously configured runtime when configureTools is called again.
1000
- * @param {RouterChainEntry} entry
1001
- * @param {import('../types.js').AgentRuntimeToolOptions|undefined} configuredTools
1002
- * @returns {import('../types.js').AgentRuntimeToolOptions}
1003
- */
1004
- function projectPerRouteNativeToolOptions(entry, configuredTools = {}) {
1005
- const projected = { ...configuredTools };
1006
- if (entry.model.sdk !== "pi") {
1007
- projected.sandboxPolicy = undefined;
1008
- projected.sandboxEngine = undefined;
1009
- }
1010
- return projected;
1011
- }
1012
-
1013
- /**
1014
- * Host-level policy must be isolated alongside request-level policy. The
1015
- * sandbox implementation itself remains available; only enforcing policy data
1016
- * and its route-specific engine are removed for provider-native bridges.
1017
- * @param {RouterChainEntry} entry
1018
- * @param {AgentRuntimeHostOptions} host
1019
- */
1020
- function projectPerRouteNativeHost(entry, host) {
1021
- if (entry.model.sdk === "pi") return host;
1022
- const projected = { ...host };
1023
- delete projected.sandboxPolicy;
1024
- delete projected.sandboxEngine;
1025
- return projected;
1026
- }
1027
-
1028
- /** @param {RouterChainEntry} entry @param {number} index */
1029
- function routeRuntimeKey(entry, index) {
1030
- return `${index}:${modelKey(entry.model)}:${entry.executionMode ?? "default"}`;
1031
- }
1032
-
1033
711
  /** @param {AgentRuntimeInstance} runtime */
1034
712
  function assertRuntimeLike(runtime) {
1035
713
  if (runtime === null || typeof runtime !== "object" || typeof runtime.run !== "function") {
@@ -1047,7 +725,7 @@ function applyEntryEffort(options, effort) {
1047
725
  }
1048
726
 
1049
727
  /** @param {unknown} error @returns {RuntimeResult} */
1050
- function safetyUnavailableResult(error) {
728
+ function attemptResolutionFailureResult(error) {
1051
729
  // Host resolvers may handle credentials. Never echo their exception text
1052
730
  // into persisted results or route telemetry. ResolverProtectedOptionError is
1053
731
  // constructed only from a repository-owned allowlist key, so it is safe and
@@ -1056,8 +734,8 @@ function safetyUnavailableResult(error) {
1056
734
  text: null,
1057
735
  error: error instanceof ResolverProtectedOptionError
1058
736
  ? error.message
1059
- : "The route safety contract could not be established before execution.",
1060
- failureKind: "safety_unavailable",
737
+ : "The route attempt could not be resolved before execution.",
738
+ failureKind: "provider_unavailable",
1061
739
  events: [],
1062
740
  cancelled: false,
1063
741
  usage: {},
@@ -1070,14 +748,6 @@ function isMidTurnSafetyFailure(failureKind) {
1070
748
  && (failureKind.startsWith("sandbox_") || failureKind.startsWith("safety_"));
1071
749
  }
1072
750
 
1073
- /**
1074
- * @param {AgentRuntimeInstance} inner
1075
- * @param {Map<string, AgentRuntimeInstance>} routeRuntimes
1076
- */
1077
- function allRuntimes(inner, routeRuntimes) {
1078
- return [...new Set([inner, ...routeRuntimes.values()])];
1079
- }
1080
-
1081
751
  /**
1082
752
  * Merge progress into one bounded snapshot so prompts never accumulate nested
1083
753
  * resume blocks across a long provider chain.
@@ -1166,17 +836,6 @@ function entrySatisfiesRequirements(entry, options) {
1166
836
  if (options.toolEnvironment !== undefined) {
1167
837
  effectiveRequires.supports_request_tool_environment = true;
1168
838
  }
1169
- if (options.fastMode === true) {
1170
- effectiveRequires.supports_fast_mode = true;
1171
- }
1172
- // Native teammates likewise require a capable route; Pi/OpenCode must skip
1173
- // rather than silently dropping them.
1174
- if (
1175
- Array.isArray(options.nativeSubagents?.teammates)
1176
- && options.nativeSubagents.teammates.length > 0
1177
- ) {
1178
- effectiveRequires.supports_native_subagents = true;
1179
- }
1180
839
  if (Object.keys(effectiveRequires).length === 0) return true;
1181
840
  let caps;
1182
841
  try {
@@ -1191,10 +850,10 @@ function entrySatisfiesRequirements(entry, options) {
1191
850
  }
1192
851
 
1193
852
  /**
1194
- * Session identifiers belong to the bridge that created them. Never forward
1195
- * one into a bridge that declares no resume support (notably isolated
1196
- * per-run OpenCode), including when that bridge is reached through fallback.
1197
- * Unknown SDKs retain the existing fail-later behavior.
853
+ * Session identifiers belong to the route that created them. Never forward one
854
+ * into a route whose capabilities declare no resume support, including when
855
+ * that route is reached through fallback. Unknown model references retain the
856
+ * existing fail-later behavior.
1198
857
  * @param {RouterChainEntry} entry
1199
858
  * @returns {boolean}
1200
859
  */