@dylanrussell/agent-router 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 2.2.0
2
+
3
+ - Add separately opt-in, explicitly paid-approved native same-session quota fallback for automatic main turns and positively correlated new child admissions.
4
+ - Require single-use, short-lived HTTP rejection evidence and structured quota errors; preserve pin/manual/cancel guards, chain bounds, native model attribution, and completed tool history.
5
+ - Report selected-versus-dispatched state and document best-effort correlation plus the accepted non-atomic late-veto residual selection.
6
+ - Add production-router native synthetic quota fallback checks and conservative correlation/control tests, including strict quota RPC projection, unresolved-request expiry, and child-ticket capacity guards.
7
+
1
8
  # 2.1.2
2
9
 
3
10
  - Remove selection-source labels from agent headings; show only the agent name.
package/README.md CHANGED
@@ -231,13 +231,13 @@ the configured primary. Retry hooks and same-turn retry behavior are unchanged.
231
231
  selection events guard asynchronous admission, but the host offers no atomic
232
232
  compare-and-switch.
233
233
 
234
- **Explicit session controls:** with preflight enabled, the server exposes three
234
+ **Explicit session controls:** with preflight or quota fallback enabled, the server exposes three
235
235
  additional tools. Pin/auto tools are for explicit user requests, under the host's
236
236
  normal tool-permission policy. They take no model or session arguments; their
237
237
  scope is the calling session.
238
238
 
239
239
  - `router_pin` freezes the current selection, resolving the native agent/default
240
- model when no selection is stored. It disables quota preflight and clears staged
240
+ model when no selection is stored. It disables quota preflight and same-turn quota fallback, and clears staged
241
241
  reactive fallback for this session. The pin persists in server plugin storage.
242
242
  Pins share one durable value capped at 1,024 sessions across restarts. Serialized
243
243
  writes prevent lost updates; session deletion removes its durable pin, including
@@ -272,6 +272,80 @@ use `{ providerID, source, models, connection: { type, id }, approval }` for sav
272
272
  credentials (`{ type: "env", name }` for environment connections). Router options
273
273
  do not contain credentials or binding attestations.
274
274
 
275
+ ### Opt-in same-turn quota fallback (phase 2, native 2.0.8)
276
+
277
+ Configure this separately from `quotaPreflight` in the server plugin's `options`:
278
+
279
+ ```json
280
+ {
281
+ "quotaFallback": {
282
+ "enabled": true,
283
+ "allowPaidFallbacks": true,
284
+ "maxSwitches": 8
285
+ }
286
+ }
287
+ ```
288
+
289
+ Both booleans default to **false**. Phase 2 requires explicit paid-fallback approval
290
+ before switching, including when quota-service bindings exist. `maxSwitches` is an
291
+ integer from 1 to 8 (default 8); only later entries in the configured chain are
292
+ eligible. Each explicit main-session admission and each newly admitted automatic
293
+ child gets its own budget, shared by all tool continuations in that turn. There is
294
+ no wraparound. Confirmed, fresh, account-scoped exhausted backups are skipped;
295
+ unknown or unavailable quota-service evidence does not block an approved backup.
296
+
297
+ The router requests a **native same-session retry** only for a structured
298
+ `provider.quota` failure with matching HTTP 402/429 rejection evidence. It never
299
+ submits another prompt, starts a replacement child, or retries a partial stream.
300
+ Authentication, timeouts, generic rate limits, 5xx, WebSockets, auxiliary requests,
301
+ and unsupported endpoints are ineligible. Existing reactive next-turn handling
302
+ continues to apply to its qualifying non-quota errors. Disabling phase 2 preserves
303
+ the previous retry policy.
304
+
305
+ Correlation is deliberately conservative: exact request-object identity,
306
+ agent/model/variant and admission generation, configured base URL plus a recognized
307
+ `/chat/completions`, `/responses`, or `/messages` operation, no URL credentials or
308
+ query, single-use evidence, and a five-second monotonic expiry. Observations are
309
+ bounded to 1,024 sessions and periodically pruned; no response bodies are read.
310
+ Concurrent observations and any auxiliary traffic poison the admission, including
311
+ an auxiliary HTTP 200 that may still be streaming. Capacity exhaustion fails closed.
312
+ Expiry removes retry permission but retains an unresolved/ambiguous-request
313
+ tombstone until the admission ends or a new admission replaces it. Quota RPC
314
+ candidates contain only provider/model IDs; configured variants remain attached
315
+ to the eventual model selection.
316
+
317
+ Explicit main model selections, original child model overrides, and resumed child
318
+ tasks are not claimed merely because their models match a chain. Automatic child
319
+ ownership requires a fresh admission ticket, a unique running parent tool call,
320
+ matching parent/agent/model, and a matching digest of the native child's initial
321
+ user message. Parent tool metadata is checked when available. Tickets expire after
322
+ five seconds; ambiguous concurrent child admissions are skipped. Neither prompt
323
+ text nor response bodies are retained in this correlation state.
324
+ Native metadata binding the child to a different parent call vetoes fingerprint
325
+ matching. Ticket overflow disables new automatic child claims until plugin reload,
326
+ including claims already awaiting host reads; dropping negative evidence cannot
327
+ make a child eligible.
328
+
329
+ **Accepted host limits:** native 2.0.8 retry hooks lack request ID, request kind,
330
+ output-started, cancellation, and atomic switch-and-retry fields. Correlation and
331
+ observable manual/cancel guards are therefore best effort, not transactional
332
+ guarantees. Same-model picker no-ops remain unobservable; use `router_pin`.
333
+ A later retry-hook veto can leave the backup selected without dispatching it.
334
+ The router does not roll back that selection, which could overwrite a newer user
335
+ choice. Status/log reasons distinguish
336
+ `quota_fallback_selected_retry_requested_not_confirmed` from
337
+ `quota_fallback_attempt_dispatched`; the latter observes the HTTP request hook,
338
+ not provider acceptance or successful completion.
339
+
340
+ After building, `npm run test:quota-fallback` runs the production router in a
341
+ disposable native 2.0.8 host with synthetic local providers. Set
342
+ `ROUTER_PREFLIGHT=1` to exercise both opt-ins together. The fixture verifies first
343
+ quota rejection, main/child post-tool continuation with a durable counter of one,
344
+ chain exhaustion, partial stream, explicit selections, manual/cancel/pin gates,
345
+ compaction, attribution, and the accepted later-veto residual selection. Its
346
+ timeout control vetoes native timeout retries only after recording router policy.
347
+ No real inference or credentials are used.
348
+
275
349
  ### Install This Checkout
276
350
 
277
351
  To test a local V2 checkout, run `npm run typecheck`, `npm test`, and `npm run build` (a fresh checkout uses `pnpm install --frozen-lockfile`). Replace the registry entry in the server `opencode.json` **plugins** array with the package directory:
package/dist/cli.js CHANGED
@@ -17006,7 +17006,7 @@ async function exportStack(paths, name, toFile) {
17006
17006
  }
17007
17007
 
17008
17008
  // src/version.ts
17009
- var VERSION = "2.1.2";
17009
+ var VERSION = "2.2.0";
17010
17010
 
17011
17011
  // src/cli.ts
17012
17012
  var log = (...args) => console.log(...args);