@mono-agent/agent-runtime 0.20.10 → 0.20.14

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 (101) hide show
  1. package/MIGRATION.md +264 -25
  2. package/README.md +139 -448
  3. package/package.json +7 -43
  4. package/src/agent/tools/agent-tool.js +5 -5
  5. package/src/agent/tools/bash.js +1 -24
  6. package/src/agent/tools/codex-subscription-search.js +1 -1
  7. package/src/agent/tools/index.js +7 -0
  8. package/src/agent/tools/monitor.js +140 -0
  9. package/src/agent/tools/pi-bridge.js +92 -7
  10. package/src/agent/tools/shared/bash-environment.js +31 -0
  11. package/src/agent/tools/shared/monitors.js +274 -0
  12. package/src/agent/tools/shared/process-runner.js +23 -5
  13. package/src/ai/cost.js +13 -68
  14. package/src/ai/index.js +4 -17
  15. package/src/ai/pi-interop.js +65 -1
  16. package/src/ai/pi-oauth-compat.js +1 -1
  17. package/src/ai/providers/codex/app-server-client.js +592 -0
  18. package/src/ai/providers/pi-models.js +18 -10
  19. package/src/ai/providers/pi-native/compaction-driver.js +49 -21
  20. package/src/ai/providers/pi-native/harness-adapter.js +338 -0
  21. package/src/ai/providers/pi-native/result-builder.js +10 -10
  22. package/src/ai/providers/pi-native/session-lifecycle.js +95 -40
  23. package/src/ai/providers/pi-native/stream-subscriber.js +23 -5
  24. package/src/ai/providers/pi-native/turn-runner.js +34 -15
  25. package/src/ai/providers/pi-native.js +48 -22
  26. package/src/ai/runtime/capabilities.js +11 -56
  27. package/src/ai/runtime/model-refs.js +118 -153
  28. package/src/ai/runtime/registry.js +22 -56
  29. package/src/ai/runtime/router.js +48 -408
  30. package/src/ai/runtime/session-liveness.js +3 -4
  31. package/src/ai/runtime/sessions.js +4 -5
  32. package/src/ai/runtime/tool-policy.js +0 -2
  33. package/src/ai/types.js +12 -108
  34. package/src/index.js +0 -6
  35. package/src/runtime.js +8 -14
  36. package/types/agent/tools/agent-tool.d.ts +1 -2
  37. package/types/agent/tools/codex-subscription-search.d.ts +1 -1
  38. package/types/agent/tools/index.d.ts +1 -0
  39. package/types/agent/tools/monitor.d.ts +44 -0
  40. package/types/agent/tools/pi-bridge.d.ts +3 -2
  41. package/types/agent/tools/shared/bash-environment.d.ts +4 -0
  42. package/types/agent/tools/shared/monitors.d.ts +83 -0
  43. package/types/agent/tools/shared/process-runner.d.ts +11 -2
  44. package/types/ai/cost.d.ts +1 -2
  45. package/types/ai/index.d.ts +1 -4
  46. package/types/ai/pi-interop.d.ts +20 -0
  47. package/types/ai/providers/codex/app-server-client.d.ts +37 -0
  48. package/types/ai/providers/pi-native/harness-adapter.d.ts +56 -0
  49. package/types/ai/providers/pi-native/result-builder.d.ts +3 -3
  50. package/types/ai/providers/pi-native/session-lifecycle.d.ts +2 -1
  51. package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
  52. package/types/ai/providers/pi-native/turn-runner.d.ts +33 -6
  53. package/types/ai/providers/pi-native.d.ts +21 -4
  54. package/types/ai/runtime/capabilities.d.ts +21 -70
  55. package/types/ai/runtime/model-refs.d.ts +0 -24
  56. package/types/ai/runtime/router.d.ts +3 -10
  57. package/types/ai/runtime/tool-policy.d.ts +0 -2
  58. package/types/ai/types.d.ts +28 -250
  59. package/types/index.d.ts +0 -1
  60. package/src/ai/providers/acp-client.js +0 -1149
  61. package/src/ai/providers/acp-privacy.js +0 -124
  62. package/src/ai/providers/acp-public.js +0 -21
  63. package/src/ai/providers/acp-session-tokens.js +0 -282
  64. package/src/ai/providers/acp-transport.js +0 -356
  65. package/src/ai/providers/acp.js +0 -543
  66. package/src/ai/providers/claude-cli.js +0 -883
  67. package/src/ai/providers/claude-sandbox.js +0 -71
  68. package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
  69. package/src/ai/providers/claude-sdk-discovery.js +0 -352
  70. package/src/ai/providers/claude-sdk.js +0 -1127
  71. package/src/ai/providers/claude-subagent-activity.js +0 -719
  72. package/src/ai/providers/claude-subagents.js +0 -88
  73. package/src/ai/providers/codex-app.js +0 -2946
  74. package/src/ai/providers/opencode-app.js +0 -1109
  75. package/src/ai/providers/opencode-discovery.js +0 -39
  76. package/src/ai/providers/opencode-server.js +0 -508
  77. package/src/ai/runtime/context-windows.js +0 -46
  78. package/src/ai/runtime/fast-mode.js +0 -8
  79. package/src/ai/streaming/codex-events.js +0 -146
  80. package/src/ai/streaming/opencode-events.js +0 -59
  81. package/types/ai/providers/acp-client.d.ts +0 -227
  82. package/types/ai/providers/acp-privacy.d.ts +0 -25
  83. package/types/ai/providers/acp-public.d.ts +0 -7
  84. package/types/ai/providers/acp-session-tokens.d.ts +0 -41
  85. package/types/ai/providers/acp-transport.d.ts +0 -45
  86. package/types/ai/providers/acp.d.ts +0 -93
  87. package/types/ai/providers/claude-cli.d.ts +0 -305
  88. package/types/ai/providers/claude-sandbox.d.ts +0 -79
  89. package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
  90. package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
  91. package/types/ai/providers/claude-sdk.d.ts +0 -138
  92. package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
  93. package/types/ai/providers/claude-subagents.d.ts +0 -18
  94. package/types/ai/providers/codex-app.d.ts +0 -151
  95. package/types/ai/providers/opencode-app.d.ts +0 -96
  96. package/types/ai/providers/opencode-discovery.d.ts +0 -4
  97. package/types/ai/providers/opencode-server.d.ts +0 -20
  98. package/types/ai/runtime/context-windows.d.ts +0 -9
  99. package/types/ai/runtime/fast-mode.d.ts +0 -2
  100. package/types/ai/streaming/codex-events.d.ts +0 -40
  101. 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.
@@ -55,10 +54,8 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
55
54
  /**
56
55
  * @typedef {Object} RouterChainEntryInput
57
56
  * A chain entry as accepted by createRouterRuntime: either the shorthand bare
58
- * RuntimeModelRef, or the full `{model, executionMode?, effort?, requires?,
59
- * attempts?}` form.
57
+ * RuntimeModelRef, or the full `{model, effort?, requires?, attempts?}` form.
60
58
  * @property {RuntimeModelRef} model
61
- * @property {string} [executionMode]
62
59
  * @property {string|null} [effort]
63
60
  * @property {Object<string, *>} [requires]
64
61
  * @property {number} [attempts]
@@ -67,7 +64,6 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
67
64
  /**
68
65
  * @typedef {Object} RouterChainEntry
69
66
  * @property {RuntimeModelRef} model
70
- * @property {string|null} executionMode
71
67
  * @property {string|null|undefined} effort
72
68
  * @property {Object<string, *>|null} requires
73
69
  * @property {number} attempts Total attempts on this route including the first.
@@ -91,18 +87,16 @@ import { instrumentLiveInputAppliedEvents } from "./live-input-events.js";
91
87
  * @property {() => (void|Promise<void>)} [cleanup]
92
88
  */
93
89
 
94
- const ROUTE_SAFETY_MODES = new Set(["uniform", "per-route-native"]);
95
90
  const ATTEMPT_SCOPED_OPTION_KEYS = ["customProvider", "customModel", "modelCapabilities", "isPrivateProvider"];
96
91
  const ROUTER_TOOL_CONTEXT_KEYS = [
97
92
  "workspace", "repoRoot", "ripgrepPath", "qaOutputDir", "sandboxPolicy", "sandboxEngine",
98
93
  ];
99
94
  const RESOLVER_PROTECTED_OPTION_KEYS = new Set([
100
- "model", "executionMode", "effort", "messages", "abortSignal", "onEvent",
95
+ "model", "effort", "messages", "abortSignal", "onEvent",
101
96
  "sessionId", "providerSessionId", "sessionKeepAlive", "sessionIdleTimeoutMs",
102
97
  "diagnosticsSeed", "systemPromptPrefix", "sandboxPolicy", "sandboxEngine", "sandbox",
103
98
  "allowedTools", "disallowedTools", "permissionMode", "mcpServers", "mcpApps", "skills",
104
- "outputSchema", "nativeSubagents", "liveInput", "fastMode", "toolEnvironment",
105
- "codexSandboxNetworkAccess",
99
+ "outputSchema", "liveInput", "toolEnvironment",
106
100
  ]);
107
101
 
108
102
  class ResolverProtectedOptionError extends Error {
@@ -117,16 +111,12 @@ class ResolverProtectedOptionError extends Error {
117
111
  * @param {Object} [options]
118
112
  * @param {AgentRuntimeHostOptions} [options.host]
119
113
  * @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]
114
+ * @param {(input: {model: RuntimeModelRef, attemptIndex: number, retryIndex: number}) => (RouterAttemptResolution|Promise<RouterAttemptResolution>)} [options.resolveAttempt]
122
115
  * @param {Partial<RouterRetryPolicy>} [options.retry] Backoff shape for same-model
123
116
  * retries. Per-route retry counts live on each chain entry's `attempts`.
124
117
  * @returns {AgentRuntimeInstance & {chain: () => Array<RouterChainEntry>}}
125
118
  */
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
- }
119
+ export function createRouterRuntime({ host = {}, chain = [], resolveAttempt, retry } = {}) {
130
120
  const retryPolicy = normalizeRetryPolicy(retry);
131
121
  const entries = normaliseChain(chain);
132
122
  if (entries.length === 0) {
@@ -134,8 +124,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
134
124
  }
135
125
  assertUniqueEntries(entries);
136
126
  const inner = createRuntime(host);
137
- /** @type {Map<string, AgentRuntimeInstance>} */
138
- const routeRuntimes = new Map();
139
127
  /** @type {import('../types.js').AgentRuntimeToolOptions|undefined} */
140
128
  let configuredTools;
141
129
  // The router builds transcript-tail snapshots outside the inner runtime's
@@ -148,8 +136,8 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
148
136
  /**
149
137
  * @param {string} systemPrompt
150
138
  * @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).
139
+ * is legal; the router always overrides `model` per chain entry (see
140
+ * AgentRuntimeInstance.run for the public, model-required contract).
153
141
  * @returns {Promise<RuntimeResult>}
154
142
  */
155
143
  async run(systemPrompt, options = {}) {
@@ -169,7 +157,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
169
157
  };
170
158
  }
171
159
  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}>} */
160
+ /** @type {Array<{model: RuntimeModelRef, failureKind: (string|null), requestId?: (string|null|undefined), retryableSubkind?: (string|null|undefined), requirements?: (Object<string,*>|null), retryIndex?: number}>} */
173
161
  const failoverHistory = [];
174
162
  /** @type {RuntimeResult|null} */
175
163
  let lastResult = null;
@@ -178,46 +166,9 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
178
166
  const promptBase = systemPrompt;
179
167
  /** @type {*} */
180
168
  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
169
  for (let i = 0; i < entries.length; i += 1) {
185
170
  const entry = entries[i];
186
171
  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
172
  if (!entrySatisfiesRequirements(entry, options)) {
222
173
  lastRouteSkip = {
223
174
  text: null,
@@ -231,12 +182,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
231
182
  model: entry.model,
232
183
  failureKind: "skipped_capability_mismatch",
233
184
  requirements: entry.requires,
234
- routeSafety,
235
- safetyContract: entrySafetyContract,
236
185
  });
237
- const skippedRecord = routeSafetyRecord(i, entry, entrySafetyContract, "skipped_capability_mismatch");
238
- routeSafetyHistory.push(skippedRecord);
239
- emit(options, { type: "provider_route_safety", ...skippedRecord });
240
186
  continue;
241
187
  }
242
188
 
@@ -249,7 +195,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
249
195
  const entryOptionsBase = {
250
196
  ...options,
251
197
  model: entry.model,
252
- executionMode: entry.executionMode || options.executionMode,
253
198
  };
254
199
  // The legacy run-level custom-provider bag describes the primary
255
200
  // route. Without a route resolver there is no authoritative metadata
@@ -263,7 +208,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
263
208
  let terminalResult = null;
264
209
 
265
210
  for (let retryIndex = 0; retryIndex < entry.attempts; retryIndex += 1) {
266
- let safetyContract = entrySafetyContract;
267
211
  /** @type {*} */
268
212
  let callOptions = { ...entryCallBase };
269
213
  /** @type {AgentRuntimeInstance} */
@@ -275,10 +219,8 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
275
219
  ? undefined
276
220
  : await resolveAttempt({
277
221
  model: entry.model,
278
- executionMode: entry.executionMode,
279
222
  attemptIndex: i,
280
223
  retryIndex,
281
- routeSafety,
282
224
  });
283
225
  const resolution = normalizeAttemptResolution(resolved);
284
226
  attemptCleanup = resolution?.cleanup;
@@ -286,50 +228,19 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
286
228
  callOptions = mergeAttemptOptions(callOptions, resolution?.options);
287
229
  callOptions = mergeAttemptPolicyOptions(callOptions, resolution?.policyOptions);
288
230
  }
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");
231
+ if (resolution?.runtime !== undefined) {
232
+ assertRuntimeLike(resolution.runtime);
233
+ attemptRuntime = resolution.runtime;
234
+ projectPiRuntimeToolContext(attemptRuntime, effectiveToolOptions);
319
235
  }
320
236
  } catch (error) {
321
237
  try { await attemptCleanup?.(); } catch { /* cleanup is additive */ }
322
- const failure = safetyUnavailableResult(error);
323
- lastRouteSkip = failure;
238
+ const failure = attemptResolutionFailureResult(error);
239
+ lastResult = failure;
324
240
  failoverHistory.push({
325
241
  model: entry.model,
326
- failureKind: "safety_unavailable",
327
- routeSafety,
328
- safetyContract,
242
+ failureKind: failure.failureKind || null,
329
243
  });
330
- const unavailableRecord = routeSafetyRecord(i, entry, safetyContract, "safety_unavailable");
331
- routeSafetyHistory.push(unavailableRecord);
332
- emit(callOptions, { type: "provider_route_safety", ...unavailableRecord });
333
244
  // A resolver fault is a config/credential problem, not a transient
334
245
  // provider blip: retrying the same route cannot fix it. Advance.
335
246
  break;
@@ -363,17 +274,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
363
274
  }
364
275
  }
365
276
 
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
277
  // A same-model retry is not a failover: only the first attempt of a new
378
278
  // route announces a transition.
379
279
  if (retryIndex === 0 && failoverHistory.length > 0) {
@@ -433,7 +333,7 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
433
333
  model: modelKey(entry.model),
434
334
  });
435
335
  }
436
- return { ...result, failoverHistory, routeSafetyHistory };
336
+ return { ...result, failoverHistory };
437
337
  }
438
338
 
439
339
  failoverHistory.push({
@@ -442,8 +342,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
442
342
  requestId: retryability.requestId,
443
343
  retryableSubkind: retryability.subkind,
444
344
  ...(retryIndex > 0 ? { retryIndex } : {}),
445
- routeSafety,
446
- safetyContract,
447
345
  });
448
346
  if (result.failureKind === "skipped_capability_mismatch") {
449
347
  lastRouteSkip = result;
@@ -494,16 +392,16 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
494
392
  reason: retryability.subkind || result.failureKind || null,
495
393
  });
496
394
  if (callOptions.abortSignal?.aborted) {
497
- return { ...result, cancelled: true, failoverHistory, routeSafetyHistory };
395
+ return { ...result, cancelled: true, failoverHistory };
498
396
  }
499
397
  await delay(backoffMs, callOptions.abortSignal);
500
398
  if (callOptions.abortSignal?.aborted) {
501
- return { ...result, cancelled: true, failoverHistory, routeSafetyHistory };
399
+ return { ...result, cancelled: true, failoverHistory };
502
400
  }
503
401
  }
504
402
 
505
403
  if (terminalResult !== null) {
506
- return { ...terminalResult, failoverHistory, routeSafetyHistory };
404
+ return { ...terminalResult, failoverHistory };
507
405
  }
508
406
  // Every other inner break falls through to the next chain entry.
509
407
  }
@@ -520,7 +418,6 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
520
418
  ...exhaustedResult,
521
419
  failureKind: lastResult ? "provider_unavailable_exhausted" : exhaustedResult.failureKind,
522
420
  failoverHistory,
523
- routeSafetyHistory,
524
421
  };
525
422
  } finally {
526
423
  await liveInputHub?.flush();
@@ -529,65 +426,39 @@ export function createRouterRuntime({ host = {}, chain = [], routeSafety = "unif
529
426
  chain: () => entries.slice(),
530
427
  configureTools(next = {}) {
531
428
  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.
429
+ inner.configureTools?.(next);
543
430
  },
544
431
  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;
432
+ return Boolean(await inner.syncSession?.(providerSessionId));
550
433
  },
551
434
  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);
435
+ if (typeof inner.refreshSession !== "function") {
436
+ throw new Error("A routed runtime cannot guarantee a cold provider-session reopen");
557
437
  }
438
+ await inner.refreshSession(providerSessionId);
558
439
  },
559
440
  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);
441
+ if (typeof inner.retireDurableSession !== "function") {
442
+ throw new Error("A routed runtime cannot retire durable provider-session state");
565
443
  }
444
+ await inner.retireDurableSession(providerSessionId, sessionsRoot);
566
445
  },
567
446
  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;
447
+ return Boolean(await inner.disposeSession?.(providerSessionId));
573
448
  },
574
449
  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;
450
+ return Boolean(await inner.invalidateSession?.(providerSessionId));
580
451
  },
581
452
  async disposeAllSessions() {
582
- await Promise.all(allRuntimes(inner, routeRuntimes).map(async (runtime) => runtime.disposeAllSessions?.()));
453
+ await inner.disposeAllSessions?.();
583
454
  },
584
455
  };
585
456
  }
586
457
 
587
458
  /**
588
459
  * @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.
460
+ * here because distinguishing the two shapes is a runtime duck-type check,
461
+ * not something a union type narrows cleanly.
591
462
  * @returns {Array<RouterChainEntry>}
592
463
  */
593
464
  function normaliseChain(chain) {
@@ -595,14 +466,13 @@ function normaliseChain(chain) {
595
466
  return /** @type {Array<RouterChainEntry>} */ (chain
596
467
  .map((entry) => {
597
468
  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 };
469
+ if (typeof entry.provider === "string" && typeof entry.model === "string") {
470
+ // ModelRef shorthand: { provider, model, reference }
471
+ return { model: entry, effort: undefined, requires: null, attempts: 1 };
601
472
  }
602
473
  if (entry.model) {
603
474
  return {
604
475
  model: entry.model,
605
- executionMode: typeof entry.executionMode === "string" ? entry.executionMode : null,
606
476
  effort: normalizeChainEffort(entry.effort),
607
477
  requires: entry.requires && typeof entry.requires === "object" ? entry.requires : null,
608
478
  attempts: normalizeChainAttempts(entry.attempts),
@@ -692,87 +562,7 @@ function assertUniqueEntries(entries) {
692
562
 
693
563
  /** @param {RuntimeModelRef} model */
694
564
  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;
565
+ return model.reference;
776
566
  }
777
567
 
778
568
  /**
@@ -801,70 +591,22 @@ function effectiveRouterToolOptions(host, configuredTools) {
801
591
  return effective;
802
592
  }
803
593
 
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
594
  /**
836
595
  * 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.
596
+ * lifecycle, never the router-owned tool context. Replace its complete mutable
597
+ * ToolContext before every execution so a blank or stale runtime cannot diverge
598
+ * from the router's host/configured/run policy or telemetry.
840
599
  *
841
600
  * @param {AgentRuntimeInstance} runtime
842
601
  * @param {import('../types.js').AgentRuntimeToolOptions} toolOptions
843
602
  */
844
603
  function projectPiRuntimeToolContext(runtime, toolOptions) {
845
604
  if (typeof runtime.configureTools !== "function") {
846
- throw new Error("per-route-native Pi runtime must expose configureTools() for safety projection");
605
+ throw new Error("route attempt runtime must expose configureTools() for tool-context projection");
847
606
  }
848
607
  runtime.configureTools(toolOptions);
849
608
  }
850
609
 
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
610
  /** @param {RouterAttemptResolution|undefined} value */
869
611
  function normalizeAttemptResolution(value) {
870
612
  if (value === undefined) return undefined;
@@ -947,89 +689,6 @@ function withoutAttemptScopedOptions(options) {
947
689
  return projected;
948
690
  }
949
691
 
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
692
  /** @param {AgentRuntimeInstance} runtime */
1034
693
  function assertRuntimeLike(runtime) {
1035
694
  if (runtime === null || typeof runtime !== "object" || typeof runtime.run !== "function") {
@@ -1047,7 +706,7 @@ function applyEntryEffort(options, effort) {
1047
706
  }
1048
707
 
1049
708
  /** @param {unknown} error @returns {RuntimeResult} */
1050
- function safetyUnavailableResult(error) {
709
+ function attemptResolutionFailureResult(error) {
1051
710
  // Host resolvers may handle credentials. Never echo their exception text
1052
711
  // into persisted results or route telemetry. ResolverProtectedOptionError is
1053
712
  // constructed only from a repository-owned allowlist key, so it is safe and
@@ -1056,8 +715,8 @@ function safetyUnavailableResult(error) {
1056
715
  text: null,
1057
716
  error: error instanceof ResolverProtectedOptionError
1058
717
  ? error.message
1059
- : "The route safety contract could not be established before execution.",
1060
- failureKind: "safety_unavailable",
718
+ : "The route attempt could not be resolved before execution.",
719
+ failureKind: "provider_unavailable",
1061
720
  events: [],
1062
721
  cancelled: false,
1063
722
  usage: {},
@@ -1070,14 +729,6 @@ function isMidTurnSafetyFailure(failureKind) {
1070
729
  && (failureKind.startsWith("sandbox_") || failureKind.startsWith("safety_"));
1071
730
  }
1072
731
 
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
732
  /**
1082
733
  * Merge progress into one bounded snapshot so prompts never accumulate nested
1083
734
  * resume blocks across a long provider chain.
@@ -1166,17 +817,6 @@ function entrySatisfiesRequirements(entry, options) {
1166
817
  if (options.toolEnvironment !== undefined) {
1167
818
  effectiveRequires.supports_request_tool_environment = true;
1168
819
  }
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
820
  if (Object.keys(effectiveRequires).length === 0) return true;
1181
821
  let caps;
1182
822
  try {
@@ -1191,10 +831,10 @@ function entrySatisfiesRequirements(entry, options) {
1191
831
  }
1192
832
 
1193
833
  /**
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.
834
+ * Session identifiers belong to the route that created them. Never forward one
835
+ * into a route whose capabilities declare no resume support, including when
836
+ * that route is reached through fallback. Unknown model references retain the
837
+ * existing fail-later behavior.
1198
838
  * @param {RouterChainEntry} entry
1199
839
  * @returns {boolean}
1200
840
  */