pi-smart-router 0.16.2 → 0.18.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 (60) hide show
  1. package/.pi/extensions/smart-router/route-and-delegate.ts +155 -6
  2. package/.pi/extensions/smart-router/routing-context.ts +77 -16
  3. package/README.md +37 -13
  4. package/config/benchmark-profiles.json +2 -2
  5. package/config/operator-config.json.example +6 -0
  6. package/dist/domain/delegation/delegation-context.d.ts +12 -5
  7. package/dist/domain/delegation/delegation-context.d.ts.map +1 -1
  8. package/dist/domain/delegation/delegation-context.js +14 -10
  9. package/dist/domain/delegation/delegation-context.js.map +1 -1
  10. package/dist/domain/pinning/session-pinner.d.ts.map +1 -1
  11. package/dist/domain/pinning/session-pinner.js +16 -6
  12. package/dist/domain/pinning/session-pinner.js.map +1 -1
  13. package/dist/domain/pipeline/router-pipeline.d.ts +26 -0
  14. package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -1
  15. package/dist/domain/pipeline/router-pipeline.js +39 -0
  16. package/dist/domain/pipeline/router-pipeline.js.map +1 -1
  17. package/dist/domain/routing/p-success-classifier.d.ts +2 -2
  18. package/dist/domain/routing/tool-history-guard.d.ts +27 -6
  19. package/dist/domain/routing/tool-history-guard.d.ts.map +1 -1
  20. package/dist/domain/routing/tool-history-guard.js +70 -10
  21. package/dist/domain/routing/tool-history-guard.js.map +1 -1
  22. package/dist/domain/types/entities.d.ts +17 -1
  23. package/dist/domain/types/entities.d.ts.map +1 -1
  24. package/dist/domain/types/schemas.d.ts +505 -8
  25. package/dist/domain/types/schemas.d.ts.map +1 -1
  26. package/dist/domain/types/schemas.js +188 -9
  27. package/dist/domain/types/schemas.js.map +1 -1
  28. package/dist/domain/types/store-port.d.ts +47 -5
  29. package/dist/domain/types/store-port.d.ts.map +1 -1
  30. package/dist/domain/types/store-port.js +21 -0
  31. package/dist/domain/types/store-port.js.map +1 -1
  32. package/dist/index.d.ts +11 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/infra/gemini-provider.d.ts +7 -0
  37. package/dist/infra/gemini-provider.d.ts.map +1 -1
  38. package/dist/infra/gemini-provider.js +9 -1
  39. package/dist/infra/gemini-provider.js.map +1 -1
  40. package/dist/infrastructure/persistence/sqlite-store.d.ts +34 -1
  41. package/dist/infrastructure/persistence/sqlite-store.d.ts.map +1 -1
  42. package/dist/infrastructure/persistence/sqlite-store.js +210 -110
  43. package/dist/infrastructure/persistence/sqlite-store.js.map +1 -1
  44. package/dist/infrastructure/persistence/write-queue.d.ts +94 -0
  45. package/dist/infrastructure/persistence/write-queue.d.ts.map +1 -0
  46. package/dist/infrastructure/persistence/write-queue.js +144 -0
  47. package/dist/infrastructure/persistence/write-queue.js.map +1 -0
  48. package/dist/infrastructure/telemetry/routing-telemetry.d.ts +5 -5
  49. package/package.json +1 -1
  50. package/src/domain/delegation/delegation-context.ts +14 -11
  51. package/src/domain/pinning/session-pinner.ts +16 -6
  52. package/src/domain/pipeline/router-pipeline.ts +49 -0
  53. package/src/domain/routing/tool-history-guard.ts +91 -9
  54. package/src/domain/types/entities.ts +24 -1
  55. package/src/domain/types/schemas.ts +231 -37
  56. package/src/domain/types/store-port.ts +47 -5
  57. package/src/index.ts +11 -0
  58. package/src/infra/gemini-provider.ts +10 -1
  59. package/src/infrastructure/persistence/sqlite-store.ts +242 -121
  60. package/src/infrastructure/persistence/write-queue.ts +232 -0
@@ -16,6 +16,7 @@ import {
16
16
  import {
17
17
  assertRoutableFleetAfterGeminiToolHistoryGuard,
18
18
  GEMINI_TOOL_HISTORY_EXCLUDED,
19
+ isGoogleGeminiProfile,
19
20
  resolveEffectiveFleet,
20
21
  } from '../../../src/domain/routing/tool-history-guard.js';
21
22
  import type { ModelProfile, RoutingDecision, RoutingRequest } from '../../../src/domain/types/index.js';
@@ -24,6 +25,7 @@ import {
24
25
  isGeminiThoughtSignatureAssistantError,
25
26
  parseAssistantMessageError,
26
27
  } from '../../../src/infrastructure/delegation/provider-error.js';
28
+ import { GEMINI_REPLAY_INCOMPATIBLE } from '../../../src/infra/gemini-provider.js';
27
29
  import { shouldFailoverOnProviderError } from '../../../src/infrastructure/gateway/gateway-dispatch.js';
28
30
  import {
29
31
  commitPipedTerminal,
@@ -52,6 +54,11 @@ function isPipedResult(
52
54
  return 'heldTerminal' in result;
53
55
  }
54
56
 
57
+ /** Fail-open reason codes (SP-226) — emitted in telemetry and SMART_ROUTER_LOG_ROUTING=1. */
58
+ export const NO_REGISTRY_MODEL = 'no_registry_model';
59
+ export const FAILOVER_EXHAUSTED = 'failover_exhausted';
60
+ export const DELEGATION_ABORTED = 'delegation_aborted';
61
+
55
62
  function isRoutingLogEnabled(): boolean {
56
63
  return process.env.SMART_ROUTER_LOG_ROUTING === '1';
57
64
  }
@@ -152,6 +159,68 @@ function isZeroOutputLengthStop(message: AssistantMessage): boolean {
152
159
  return message.stopReason === 'length' && message.usage.output === 0;
153
160
  }
154
161
 
162
+ /** Minimal model identity for degraded terminal messages when no Model resolved. */
163
+ interface DegradedModelRef {
164
+ readonly api: Api;
165
+ readonly provider: string;
166
+ readonly id: string;
167
+ }
168
+
169
+ function createDegradedErrorMessage(
170
+ model: DegradedModelRef,
171
+ reasonCode: string,
172
+ detail: string,
173
+ ): AssistantMessage {
174
+ return {
175
+ role: 'assistant',
176
+ content: [],
177
+ api: model.api,
178
+ provider: model.provider,
179
+ model: model.id,
180
+ usage: {
181
+ input: 0,
182
+ output: 0,
183
+ cacheRead: 0,
184
+ cacheWrite: 0,
185
+ totalTokens: 0,
186
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
187
+ },
188
+ stopReason: 'error',
189
+ errorMessage: `Smart router degraded response (${reasonCode}): ${detail}`,
190
+ timestamp: Date.now(),
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Fail-open terminal (SP-226): never throw to the host on exhaustion paths.
196
+ * Emit a structured warning with the reason code and end the outer stream with
197
+ * a degraded error message so the pi host receives an actionable response.
198
+ */
199
+ function emitDegradedFailure(
200
+ outer: AssistantMessageEventStream,
201
+ model: DegradedModelRef | undefined,
202
+ selectedModelId: string,
203
+ reasonCode: string,
204
+ detail: string,
205
+ ): void {
206
+ console.warn(
207
+ '[smart-router] fail-open degraded response',
208
+ JSON.stringify({
209
+ reason_code: reasonCode,
210
+ selected_model_id: selectedModelId,
211
+ detail,
212
+ }),
213
+ );
214
+ const ref: DegradedModelRef = model ?? {
215
+ api: 'unknown',
216
+ provider: 'unknown',
217
+ id: selectedModelId,
218
+ };
219
+ const errorMessage = createDegradedErrorMessage(ref, reasonCode, detail);
220
+ outer.push({ type: 'error', reason: 'error', error: errorMessage });
221
+ outer.end(errorMessage);
222
+ }
223
+
155
224
  function buildOverflowRoutingDecision(
156
225
  base: RoutingDecision,
157
226
  fallback: ReturnType<typeof resolveContextOverflowFallback>,
@@ -357,9 +426,18 @@ export async function routeAndDelegate(
357
426
  if (decision.selected_model_id === 'unknown' && guardResult) {
358
427
  assertRoutableFleetAfterGeminiToolHistoryGuard(guardResult);
359
428
  }
360
- throw new Error(
429
+ // SP-226 fail-open: no registry model resolved — degrade instead of throwing.
430
+ deps.router.dispatch.recordOutcome(decision.selected_model_id, {
431
+ code: 'NO_REGISTRY_MODEL',
432
+ });
433
+ emitDegradedFailure(
434
+ outer,
435
+ undefined,
436
+ decision.selected_model_id,
437
+ NO_REGISTRY_MODEL,
361
438
  `No registry model available for routing decision ${decision.selected_model_id}`,
362
439
  );
440
+ return;
363
441
  }
364
442
 
365
443
  logRoutingDecision(decision, {
@@ -370,6 +448,7 @@ export async function routeAndDelegate(
370
448
 
371
449
  const failedModelIds: string[] = [];
372
450
  const headroomExcludedModelIds: string[] = [];
451
+ let geminiReplayFailoverAttempted = false;
373
452
  const estimatedInputTokens =
374
453
  request.estimated_input_tokens ?? request.prompt_text.length;
375
454
  let pendingFailoverInfo: FailoverNoticeInfo | undefined;
@@ -494,6 +573,60 @@ export async function routeAndDelegate(
494
573
  result.finalMessage &&
495
574
  isGeminiThoughtSignatureAssistantError(result.finalMessage)
496
575
  ) {
576
+ // SP-233 residual safety net: a thought_signature 400 that survived
577
+ // repair/guard means the session replay state is incompatible with
578
+ // Gemini — fail over ONCE to a non-Google fleet member so the agent
579
+ // loop continues. Never counted as provider infra failure; never
580
+ // Gemini↔Gemini.
581
+ if (!geminiReplayFailoverAttempted) {
582
+ geminiReplayFailoverAttempted = true;
583
+ if (!failedModelIds.includes(targetModel.id)) {
584
+ failedModelIds.push(targetModel.id);
585
+ }
586
+ const nonGoogleFleet = effectiveFleet.filter(
587
+ (profile) => !isGoogleGeminiProfile(profile),
588
+ );
589
+ const replayFailover =
590
+ nonGoogleFleet.length > 0
591
+ ? deps.router.dispatch.selectFailover(
592
+ decision,
593
+ failedModelIds,
594
+ nonGoogleFleet,
595
+ )
596
+ : undefined;
597
+ const failover = replayFailover
598
+ ? { ...replayFailover, reason_code: GEMINI_REPLAY_INCOMPATIBLE }
599
+ : undefined;
600
+ const alternateModel = failover ? resolveTargetModel(deps, failover) : undefined;
601
+
602
+ if (failover && alternateModel && alternateModel.id !== targetModel.id) {
603
+ console.warn(
604
+ '[smart-router] gemini replay incompatible, failing over to non-Google model',
605
+ alternateModel.id,
606
+ );
607
+ if (isRoutingLogEnabled()) {
608
+ console.warn(
609
+ '[smart-router] routing decision',
610
+ JSON.stringify({
611
+ reason_code: GEMINI_REPLAY_INCOMPATIBLE,
612
+ failed_model_id: targetModel.id,
613
+ selected_model_id: alternateModel.id,
614
+ }),
615
+ );
616
+ }
617
+ pendingFailoverInfo = {
618
+ failedModelId: targetModel.id,
619
+ alternateModelId: alternateModel.id,
620
+ errorObj: resolveFailoverProviderError(result.finalMessage),
621
+ };
622
+ decision = failover;
623
+ deps.onRoutingDecision?.(decision);
624
+ targetModel = alternateModel;
625
+ continue;
626
+ }
627
+ }
628
+ // No non-Google candidate (or one-shot already used): actionable
629
+ // terminal guidance — never a silent loop.
497
630
  commitPipedTerminal(result, { sanitizeErrors: true });
498
631
  return;
499
632
  }
@@ -546,6 +679,16 @@ export async function routeAndDelegate(
546
679
  return;
547
680
  } catch (error) {
548
681
  if (isAbortError(error, options)) {
682
+ // SP-226: telemetry for phase-boundary aborts (previously silent).
683
+ if (isRoutingLogEnabled()) {
684
+ console.warn(
685
+ '[smart-router] delegation aborted',
686
+ JSON.stringify({
687
+ reason_code: DELEGATION_ABORTED,
688
+ model_id: targetModel.id,
689
+ }),
690
+ );
691
+ }
549
692
  const abortMessage = createErrorMessage(targetModel, options, error);
550
693
  outer.push({ type: 'error', reason: 'aborted', error: abortMessage });
551
694
  outer.end(abortMessage);
@@ -565,7 +708,7 @@ export async function routeAndDelegate(
565
708
  );
566
709
  const alternateModel = failover ? resolveTargetModel(deps, failover) : undefined;
567
710
 
568
- if (alternateModel && alternateModel.id !== targetModel.id) {
711
+ if (failover && alternateModel && alternateModel.id !== targetModel.id) {
569
712
  console.warn(
570
713
  '[smart-router] stream delegation failed, failing over',
571
714
  error instanceof Error ? error.message : String(error),
@@ -575,9 +718,6 @@ export async function routeAndDelegate(
575
718
  alternateModelId: alternateModel.id,
576
719
  errorObj: { message: error instanceof Error ? error.message : String(error) },
577
720
  };
578
- if (!failover) {
579
- throw error;
580
- }
581
721
  decision = failover;
582
722
  targetModel = alternateModel;
583
723
  continue;
@@ -585,7 +725,16 @@ export async function routeAndDelegate(
585
725
 
586
726
  const fallbackModel = resolveFallbackModel(deps, effectiveFleet);
587
727
  if (!fallbackModel || fallbackModel.id === targetModel.id) {
588
- throw error;
728
+ // SP-226 fail-open: fleet/failover exhausted and no distinct safe
729
+ // default — degrade instead of throwing to the host.
730
+ emitDegradedFailure(
731
+ outer,
732
+ targetModel,
733
+ decision.selected_model_id,
734
+ FAILOVER_EXHAUSTED,
735
+ error instanceof Error ? error.message : String(error),
736
+ );
737
+ return;
589
738
  }
590
739
 
591
740
  console.warn(
@@ -76,6 +76,35 @@ function messageContentToString(content: string | readonly (TextContent | { type
76
76
  .join('\n');
77
77
  }
78
78
 
79
+ /**
80
+ * SP-225 / #137: read an HTTP-ish status a host may attach to a tool result
81
+ * (either directly on the message or inside `details`). Returns undefined
82
+ * when no finite numeric status is present.
83
+ */
84
+ function readOptionalStatus(source: unknown): number | undefined {
85
+ if (source === null || typeof source !== 'object') {
86
+ return undefined;
87
+ }
88
+ const record = source as Record<string, unknown>;
89
+ const direct = record.status;
90
+ if (typeof direct === 'number' && Number.isFinite(direct) && direct >= 0) {
91
+ return Math.floor(direct);
92
+ }
93
+ return readOptionalStatus(record.details);
94
+ }
95
+
96
+ export interface MapContextMessagesOptions {
97
+ /**
98
+ * Opt-in: include assistant `thinking` blocks in routing `content`.
99
+ * Default false — thinking is model-internal reasoning, not a routing
100
+ * signal, and leaking it inflates token estimates (#137).
101
+ */
102
+ includeThinking?: boolean;
103
+ }
104
+
105
+ /** Operator env gate restoring the pre-SP-225 thinking-in-content behavior. */
106
+ const INCLUDE_THINKING_ENV = 'SMART_ROUTER_INCLUDE_THINKING';
107
+
79
108
  export function extractPromptText(messages: readonly Message[]): string {
80
109
  for (let i = messages.length - 1; i >= 0; i--) {
81
110
  const message = messages[i];
@@ -117,7 +146,11 @@ export function deriveTurnType(messages: readonly Message[]): TurnType {
117
146
  return 'main_loop';
118
147
  }
119
148
 
120
- export function mapContextMessages(messages: readonly Message[]): RoutingMessage[] {
149
+ export function mapContextMessages(
150
+ messages: readonly Message[],
151
+ options?: MapContextMessagesOptions,
152
+ ): RoutingMessage[] {
153
+ const includeThinking = options?.includeThinking === true;
121
154
  return messages.map((message) => {
122
155
  if (message.role === 'user') {
123
156
  return {
@@ -127,27 +160,53 @@ export function mapContextMessages(messages: readonly Message[]): RoutingMessage
127
160
  }
128
161
 
129
162
  if (message.role === 'assistant') {
130
- const content = message.content
131
- .map((block) => {
132
- if (block.type === 'text') {
133
- return block.text;
163
+ const contentParts: string[] = [];
164
+ const toolBlocks: Record<string, unknown>[] = [];
165
+ for (const block of message.content) {
166
+ if (block.type === 'text') {
167
+ contentParts.push(block.text);
168
+ } else if (block.type === 'thinking') {
169
+ if (includeThinking) {
170
+ contentParts.push(block.thinking);
134
171
  }
135
- if (block.type === 'thinking') {
136
- return block.thinking;
137
- }
138
- return '';
139
- })
140
- .filter(Boolean)
141
- .join('\n');
172
+ } else if (block.type === 'toolCall') {
173
+ toolBlocks.push({
174
+ type: 'tool_call',
175
+ tool_call_id: block.id,
176
+ tool_name: block.name,
177
+ });
178
+ }
179
+ }
142
180
 
143
- return { role: message.role, content };
181
+ const mapped: RoutingMessage = {
182
+ role: message.role,
183
+ content: contentParts.filter(Boolean).join('\n'),
184
+ };
185
+ if (toolBlocks.length > 0) {
186
+ return { ...mapped, tool_blocks: toolBlocks };
187
+ }
188
+ return mapped;
144
189
  }
145
190
 
191
+ const status = readOptionalStatus(message);
192
+ // When a host attaches an HTTP-ish status, let the domain status>=400 rule
193
+ // arbitrate (#137): a bare isError=false would otherwise mask the
194
+ // structured signal. Without a status, preserve the host isError verbatim.
195
+ const isError = message.isError === true || status === undefined
196
+ ? message.isError
197
+ : undefined;
146
198
  return {
147
199
  role: 'tool',
148
200
  content: messageContentToString(message.content),
149
- tool_blocks: [],
150
- is_error: message.isError,
201
+ tool_blocks: [
202
+ {
203
+ type: 'tool_result',
204
+ tool_call_id: message.toolCallId,
205
+ tool_name: message.toolName,
206
+ },
207
+ ],
208
+ ...(isError !== undefined ? { is_error: isError } : {}),
209
+ ...(status !== undefined ? { status } : {}),
151
210
  };
152
211
  });
153
212
  }
@@ -164,7 +223,9 @@ export function buildRoutingRequest(
164
223
  request_id: randomUUID(),
165
224
  session_id: sessionId,
166
225
  prompt_text: extractPromptText(context.messages),
167
- messages: mapContextMessages(context.messages),
226
+ messages: mapContextMessages(context.messages, {
227
+ includeThinking: process.env[INCLUDE_THINKING_ENV] === '1',
228
+ }),
168
229
  turn_type: deriveTurnType(context.messages),
169
230
  estimated_input_tokens: estimateInputTokens(context, options),
170
231
  ...lifecycleFlags,
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Auto-model router middleware for the [pi](https://pi.dev) coding agent.**
4
4
 
5
- > **v0.1.0** is initial development (SemVer `0.y.z`). The public API and routing behavior may change until `1.0.0`.
5
+ > Current release: **v0.16.2** (mirrors `package.json`; SemVer `0.y.z`). The public API and routing behavior may change until `1.0.0`.
6
6
 
7
7
  pi-smart-router intercepts every LLM inference request and dynamically routes it to the optimal execution engine — balancing cost, capability, latency, and time-to-first-token (TTFT) — without requiring you to manually pick a model for each turn.
8
8
 
@@ -18,8 +18,8 @@ pi-smart-router intercepts every LLM inference request and dynamically routes it
18
18
  ```text
19
19
  request → hardware probe → loop escalation → turn envelope → context-fit gate
20
20
  → low-intensity tier gate → session pin → deterministic triage
21
- → local zero-tier → HyDRA embedding matchersafe cloud default
22
- → context overflow fallback
21
+ → local zero-tier → triage cloud fallbackHyDRA embedding matcher
22
+ safe cloud default → context overflow fallback
23
23
  ```
24
24
 
25
25
  The pipeline runs **12 stages sequentially with early exit** — the moment any stage reaches a routing decision, subsequent stages are skipped. Every decision includes the stage name, reason code, candidates considered, estimated cost, and routing latency for full observability.
@@ -34,6 +34,7 @@ The pipeline runs **12 stages sequentially with early exit** — the moment any
34
34
  | Session Pin | <1ms | Returns pinned model if session has one; breaks pin on compaction or overflow |
35
35
  | Deterministic Triage | <5ms | Aho-Corasick keyword scan + cyclomatic complexity analysis |
36
36
  | Local Zero-Tier | <15ms | Pings LM Studio + Ollama in parallel; routes locally when eligible |
37
+ | Triage Cloud Fallback | <2ms | Trivial prompts not claimed locally route to the first healthy economical-cloud model |
37
38
  | HyDRA Matcher | 80-120ms | ONNX embeddings, 3D requirement projection, shortfall gate, multi-objective scoring |
38
39
  | Safe Cloud Default | — | First healthy economical-cloud model (context-fit aware) |
39
40
  | Context Overflow Fallback | — | Escalates to largest-fit model when economical tiers cannot fit |
@@ -200,7 +201,7 @@ pi exposes two different **auto** models. They are easy to confuse but play diff
200
201
 
201
202
  - You want cost/capability-aware model selection across your full authenticated fleet
202
203
  - You rely on session pinning, failover, or `/smart-router status` / `history` / `stats` telemetry
203
- - Tool-heavy sessions with Gemini economical models work via in-repo replay repair; add `cursor/auto` for unrepairable Google replay edge cases (see [pi-smart-router#85](https://github.com/beettlle/pi-smart-router/issues/85))
204
+ - Tool-heavy sessions with Gemini economical models work via in-repo replay repair (cross-provider included); the tool-history guard reroutes to non-Google models such as `cursor/auto` for unrepairable replay state (see [pi-smart-router#85](https://github.com/beettlle/pi-smart-router/issues/85), [pi-smart-router#158](https://github.com/beettlle/pi-smart-router/issues/158))
204
205
 
205
206
  Cursor models (`cursor/*`, `composer-*`, and the opaque fleet id `default`) map to **frontier-cloud** tier in `pi-model-mapper.ts` so HyDRA can score them against Gemini and Claude instead of treating them as unknown economical models ([pi-smart-router#40](https://github.com/beettlle/pi-smart-router/issues/40), [pi-smart-router#70](https://github.com/beettlle/pi-smart-router/issues/70)). Related: [pi-smart-router#23](https://github.com/beettlle/pi-smart-router/issues/23) (turn envelope / pin order), [pi-smart-router#37](https://github.com/beettlle/pi-smart-router/issues/37) (Gemini `thought_signature` errors).
206
207
 
@@ -258,6 +259,10 @@ After typing `/smart-router ` (with a trailing space), press **TAB** to see subc
258
259
  npm run verify:ci
259
260
  ```
260
261
 
262
+ ## Concurrency contract
263
+
264
+ `RouterPipeline.route()` calls on a single router instance are **single-flight**: concurrent calls are serialized internally (SP-230, [#141](https://github.com/beettlle/pi-smart-router/issues/141)). The pipeline keeps per-route transient state on instance fields while stages run, so overlapping executions are queued rather than interleaved — each queued call waits at most one routing latency. This applies to `createRouter()` / `createRouterFromFleet()` handles: a shared `router.dispatch` is safe to call concurrently, and serialization does not change routing policy outcomes. For parallel routing throughput, create separate router instances.
265
+
261
266
  ## Fleet behavior
262
267
 
263
268
  When you use `smart-router/auto`, the extension does **not** read `config/models.yaml`. Instead:
@@ -873,23 +878,25 @@ The `GatewayDispatch` layer wraps the pipeline with:
873
878
 
874
879
  #### Gemini `thought_signature` 400 errors
875
880
 
876
- If Gemini returns **400 INVALID_ARGUMENT** mentioning `thought_signature`, the router treats this as a **protocol validation error** (incomplete tool-call replay), not provider unavailability — it will **not** failover to another model.
881
+ If Gemini returns **400 INVALID_ARGUMENT** mentioning `thought_signature`, the router treats this as a **protocol validation error** (incomplete tool-call replay), not provider unavailability — it is never classified as an infrastructure failure and never trips the circuit breaker. If the error survives repair and the tool-history guard, the router fails over **once** to a non-Google fleet model automatically (telemetry `reason_code: gemini_replay_incompatible`, distinct from infra failover) so the agent loop continues; only when no non-Google candidate exists does it surface a terminal error with actionable guidance.
877
882
 
878
883
  See [Google's thought signatures documentation](https://ai.google.dev/gemini-api/docs/generate-content/thought-signatures).
879
884
 
880
- **Primary fixreplay repair (SP-127/128):** before every Google-target delegation, smart-router repairs tool-call replay state: prior turns keep captured `thoughtSignature` values; tool calls missing a signature receive the Google-accepted skip sentinel so pi-ai can replay without a 400. Typical Gemini-first tool loops on `/model smart-router/auto` no longer require `/new` or switching away from Google models.
885
+ **Primary pathsilent repair and reroute (SP-127/128, SP-231/232):** in the common cross-provider case (tool-heavy turns on OpenAI/Anthropic/GLM/Cursor, then a Gemini selection) you do **not** need `/new` or manual model switching. Before every Google-target delegation, smart-router repairs tool-call replay state for **any** prior provider: unsigned tool calls receive the Google-accepted skip sentinel, captured signatures are preserved, and assistant identity aligns so pi-ai replays the turns without a 400. When history carries replay state repair cannot make Google-safe, the tool-history guard silently reroutes to a non-Google model instead (`reason_code: gemini_tool_history_excluded`) see below.
881
886
 
882
- **Narrowed guard fail-safe (SP-129):** sessions with **unrepairable** Google-origin replay state (e.g. redacted thinking blocks paired with tool calls) exclude Gemini from routing (`reason_code: gemini_tool_history_excluded`) unless the operator sets `force_model_id` via `/model`. Repairable Google tool history is delegated normally.
887
+ **Guard fail-safe (SP-129, expanded SP-232):** sessions with **unrepairable** replay state exclude Gemini from routing unless the operator sets `force_model_id` via `/model`. Unrepairable means: Google-origin turns with redacted thinking or captured signatures (SP-129), and — after SP-232 — **cross-provider** turns whose state repair preserves but Google rejects, such as foreign provider signatures (Claude signed thinking, signed text, or signed tool calls) or redacted thinking from any origin replayed toward a Google target. Unsigned cross-provider tool calls alone are repairable and stay routable to Gemini.
883
888
 
884
- **Empty fleet fail-safe (SP-084):** when the guard filters every model in the scoped fleet (e.g. Google/Gemini-only dogfood configs with unrepairable replay risk), the router throws an actionable error instead of delegating with `selected_model_id: unknown`. Add a non-Google model such as `openai/gpt-4o-mini` or `cursor/auto` to the fleet, start `/new`, or pin `/model` to force a specific model.
889
+ **Empty fleet fail-safe (SP-084):** when the guard filters every model in the scoped fleet (e.g. Google/Gemini-only dogfood configs with unrepairable replay risk), the router throws an actionable error instead of delegating with `selected_model_id: unknown`. Add a non-Google model such as `openai/gpt-4o-mini` or `cursor/auto` to the fleet, or pin `/model` to force a specific model.
890
+
891
+ **Residual path — one-shot non-Google failover (SP-233):** when a `thought_signature` 400 still reaches the stream (repair could not make the session replay state Google-safe), smart-router selects at most one non-Google fleet member and continues the stream with it. This is protocol-affinity failover, not infra failover: it is not recorded as a provider outage, does not trip the circuit breaker, and never retries Gemini↔Gemini for this error. If the scoped fleet has no non-Google model, the router fails fast with terminal guidance instead of looping silently.
885
892
 
886
893
  **If you still see a `thought_signature` error:**
887
894
 
888
- 1. Start a fresh session with `/new` in pi (clears unrepairable history).
889
- 2. Switch to a non-Google model (e.g. `/model openai/gpt-4o-mini`) for that session.
895
+ 1. Add a non-Google model (e.g. `openai/gpt-4o-mini` or `cursor/auto`) to the scoped fleet so the residual failover can reroute automatically — or switch manually with `/model openai/gpt-4o-mini` for that session.
896
+ 2. Start a fresh session with `/new` in pi (clears unrepairable history) last resort, rarely needed outside Google-only fleets or residual edge cases.
890
897
  3. Upstream: [pi#6342](https://github.com/earendil-works/pi/issues/6342) tracks pi preserving thought signatures in session replay; smart-router repair covers the common cross-model routing case without waiting on that fix.
891
898
 
892
- Related: [pi-smart-router#37](https://github.com/beettlle/pi-smart-router/issues/37), [pi-smart-router#38](https://github.com/beettlle/pi-smart-router/issues/38), [pi-smart-router#40](https://github.com/beettlle/pi-smart-router/issues/40), [pi-smart-router#41](https://github.com/beettlle/pi-smart-router/issues/41), [pi-smart-router#85](https://github.com/beettlle/pi-smart-router/issues/85).
899
+ Related: [pi-smart-router#37](https://github.com/beettlle/pi-smart-router/issues/37), [pi-smart-router#38](https://github.com/beettlle/pi-smart-router/issues/38), [pi-smart-router#40](https://github.com/beettlle/pi-smart-router/issues/40), [pi-smart-router#41](https://github.com/beettlle/pi-smart-router/issues/41), [pi-smart-router#85](https://github.com/beettlle/pi-smart-router/issues/85), [pi-smart-router#158](https://github.com/beettlle/pi-smart-router/issues/158), [pi-smart-router#159](https://github.com/beettlle/pi-smart-router/issues/159).
893
900
 
894
901
  ### Explain endpoint (library API)
895
902
 
@@ -949,7 +956,7 @@ Contributors must run `npm run build` before publishing or consuming the library
949
956
  | `npm run release:check` | Pre-release gate: `verify:ci` + consumer pack + Tier 0 functional smoke |
950
957
  | `npm run release:functional-smoke` | Tier 0 functional smoke: calibration verify (`--skip-embed`), benchmark profiles, release gate assertions |
951
958
  | `npm run release:consumer-pack` | Pack tarball and verify production dependencies resolve (catches missing runtime deps) |
952
- | `npm run verify:ci` | Full CI parity: build, typecheck, lint, test, coverage |
959
+ | `npm run verify:ci` | Full CI parity: build, typecheck, lint, test, coverage (baseline PR gate; see [PR and pre-release quality gate set](#pr-and-pre-release-quality-gate-set)) |
953
960
  | `npm run typecheck` | TypeScript strict mode check (`tsc --noEmit`) |
954
961
  | `npm test` | Run test suite (`vitest run`) |
955
962
  | `npm run coverage:check` | Tests with line-coverage thresholds |
@@ -1008,7 +1015,9 @@ npm run routing:eval-replay
1008
1015
  npm run routing:twinrouterbench:full-track
1009
1016
  ```
1010
1017
 
1011
- **CI smoke:** `.github/workflows/eval-harness-smoke.yml` runs on PRs that touch eval scripts, fixtures, or the workflow. It executes `routing:eval-harness:smoke`, `routing:eval-harness:corpus-smoke`, and eval unit tests — fast, offline, no provider network calls. Job timeout stays at 10 minutes. The optional full-track nightly (`.github/workflows/twinrouterbench-full-nightly.yml`, `schedule` + `workflow_dispatch` only) is **not** on `pull_request` and must not be configured as a required status check — failures there do not gate PR CI or `release:functional-smoke`.
1018
+ **CI smoke:** `.github/workflows/eval-harness-smoke.yml` runs on PRs that touch eval scripts, fixtures, **any `src/**` or `.pi/extensions/smart-router/**` change**, or the workflow. It executes `routing:eval-harness:smoke`, `routing:eval-harness:corpus-smoke`, and eval unit tests — fast, offline, no provider network calls. Job timeout stays at 10 minutes. The optional full-track nightly (`.github/workflows/twinrouterbench-full-nightly.yml`, `schedule` + `workflow_dispatch` only) is **not** on `pull_request` and must not be configured as a required status check — failures there do not gate PR CI or `release:functional-smoke`.
1019
+
1020
+ **Calibration verify:** `.github/workflows/calibration-verify.yml` runs on PRs that touch calibration config/scripts (`config/routing-calibration.json*`, `config/p-success-weights.json`, `scripts/train-routing-calibration.ts`, `scripts/verify-routing-calibration.ts`, `scripts/lib/isotonic-calibrator.ts`, `scripts/lib/oats-centroid-refinement.ts`) **or calibration-consuming routing code** (`src/domain/routing/**`, `src/domain/pipeline/**`, `src/domain/types/**`, `src/cli/**`). It builds the library and verifies `config/routing-calibration.json.example` against benchmark prompts via `npm run routing:verify-calibration`.
1012
1021
 
1013
1022
  **TwinRouterBench static track:** import step-level router-visible prefixes with execution-verified target tiers (`track: "static"`). The adapter in `scripts/eval/twinrouterbench-adapter.ts` converts static track records into native eval fixtures for the three-track harness. See `docs/gemini-research.md` §9 for methodology context.
1014
1023
 
@@ -1178,6 +1187,21 @@ npm run routing:verify-benchmark-profiles
1178
1187
 
1179
1188
  Tag-triggered publish via GitHub Actions (requires `NPMSECRET` repository secret). pi.dev gallery listing syncs automatically from npm (`pi-package` keyword); no separate submit step.
1180
1189
 
1190
+ #### PR and pre-release quality gate set
1191
+
1192
+ Operators should treat the following as the full gate set before merging routing changes and before tagging a release ([#135](https://github.com/beettlle/pi-smart-router/issues/135)):
1193
+
1194
+ | Gate | Workflow / command | Runs when |
1195
+ |------|--------------------|-----------|
1196
+ | Build / typecheck / lint / coverage | `.github/workflows/ci.yml` (`npm run verify:ci`) | Every PR and push to `main` |
1197
+ | Eval harness smoke (offline) | `.github/workflows/eval-harness-smoke.yml` | PRs touching `scripts/eval/**`, `tests/eval/**`, **`src/**`**, **`.pi/extensions/smart-router/**`**, `package.json`, or the workflow |
1198
+ | Calibration verify | `.github/workflows/calibration-verify.yml` | PRs touching calibration config/scripts or calibration-consuming routing code (`src/domain/routing/**`, `src/domain/pipeline/**`, `src/domain/types/**`, `src/cli/**`) |
1199
+ | Benchmark profile smoke | `.github/workflows/benchmark-profile-refresh.yml` (`routing:verify-benchmark-profiles`) | PRs touching fixtures / ingest / profiles |
1200
+ | Pre-release functional smoke | `npm run release:check` (Tier 0: calibration `--skip-embed` + benchmark profiles + release-gate assertions) | Operator-run before `npm version` / tag |
1201
+ | TwinRouterBench full track | `.github/workflows/twinrouterbench-full-nightly.yml` | Nightly / manual only — **never** a required PR check |
1202
+
1203
+ Required-check configuration for branch protection is a human-operator repo-settings decision; the workflow path filters above guarantee the jobs **run** on routing code edits regardless of which subset the operator marks required.
1204
+
1181
1205
  **Scope composition:** use `/skill:router-release-operator` for themed release planning (not open-ended backlog cycles). **Patch** = docs + bugfixes only; **minor** = new capability (1–3 related issues under one theme). Budgets and audit rules: [`skills/router-release-operator/references/release-profiles.md`](skills/router-release-operator/references/release-profiles.md).
1182
1206
 
1183
1207
  **Tier 0 functional smoke** (`release:functional-smoke`) runs before tag publish and chains:
@@ -7,8 +7,8 @@
7
7
  "livecodebench": "https://livecodebench.github.io/leaderboard.html",
8
8
  "bfcl": "https://gorilla.cs.berkeley.edu/leaderboard.html"
9
9
  },
10
- "scrape_date": "2026-08-20",
11
- "catalog_freeze_date": "2026-08-20"
10
+ "scrape_date": "2026-08-28",
11
+ "catalog_freeze_date": "2026-08-28"
12
12
  },
13
13
  "aliases": {
14
14
  "anthropic/claude-opus-4": "claude-opus-4-5",
@@ -58,6 +58,12 @@
58
58
  "max_messages": 12,
59
59
  "max_tokens": 16384,
60
60
  "exclude_execution_history": true
61
+ },
62
+ "global_timeout_ms": 120000,
63
+ "sub_call_timeout_ms": 30000,
64
+ "_timeouts_documentation": {
65
+ "global_timeout_ms": "Global cap (ms) for the whole delegate stage; mirrors llm-use WORKER_GLOBAL_TIMEOUT. Default 120000 (SP-213, #120).",
66
+ "sub_call_timeout_ms": "Per-call cap (ms) for each delegate sub-call worker; mirrors llm-use WORKER_CALL_TIMEOUT. Default 30000 (SP-213, #120)."
61
67
  }
62
68
  },
63
69
  "local_zero": {
@@ -2,7 +2,8 @@
2
2
  * Delegation context normalization — provider-agnostic replay identity fix.
3
3
  *
4
4
  * pi-ai transformMessages compares assistant message provider/api/model to the
5
- * target model. Virtual smart-router tags break isSameModel and strip replay
5
+ * target model. Foreign identities (virtual smart-router tags or any prior
6
+ * provider such as OpenAI/Anthropic/GLM) break isSameModel and strip replay
6
7
  * state (thoughtSignature, thinkingSignature, etc.).
7
8
  */
8
9
  import type { Api, AssistantMessage, Context, Message, Model } from '@earendil-works/pi-ai/compat';
@@ -35,11 +36,17 @@ export interface NormalizeDelegationContextOptions {
35
36
  */
36
37
  export declare function normalizeDelegationContext<TApi extends Api>(context: Context, targetModel: Model<TApi>, options?: NormalizeDelegationContextOptions): Context;
37
38
  /**
38
- * Repair Gemini tool-call replay for cross-model Google delegation.
39
+ * Repair Gemini tool-call replay for Google delegation targets.
39
40
  *
40
- * Call after {@link normalizeDelegationContext}. Aligns Google-origin assistant
41
- * identity to the delegation target and injects the thought-signature sentinel
42
- * when tool calls lack a captured signature.
41
+ * Call after {@link normalizeDelegationContext}. Aligns assistant identity to
42
+ * the delegation target and injects the thought-signature sentinel on every
43
+ * unsigned tool call, regardless of which provider produced the prior turn
44
+ * (Google, OpenAI, Anthropic, GLM, …). Captured signatures are preserved.
45
+ *
46
+ * Cross-provider repair (SP-231, #158): pi-ai strips replay state from any
47
+ * assistant message whose identity fails isSameModel, so a Gemini target
48
+ * would receive unsigned tool calls from non-Google turns and the Google API
49
+ * would reject the replay with a thought_signature error.
43
50
  */
44
51
  export declare function repairGeminiReplayContext<TApi extends Api>(context: Context, targetModel: Model<TApi>, sessionExecution?: ExecutionModel | null): Context;
45
52
  //# sourceMappingURL=delegation-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"delegation-context.d.ts","sourceRoot":"","sources":["../../../src/domain/delegation/delegation-context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,GAAG,EACH,gBAAgB,EAChB,OAAO,EACP,OAAO,EACP,KAAK,EACN,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAG,cAAuB,CAAC;AAC/D,eAAO,MAAM,uBAAuB,EAAG,MAAe,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sCAAsC,EACjD,8CAAuD,CAAC;AAW1D,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAElF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAetF;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAmBjF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CA+B7E;AAoCD,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,uBAAuB,CAAC;IAC1D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,SAAS,GAAG,EACzD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,EACxB,OAAO,CAAC,EAAE,iCAAiC,GAC1C,OAAO,CA2BT;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,SAAS,GAAG,EACxD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,EACxB,gBAAgB,CAAC,EAAE,cAAc,GAAG,IAAI,GACvC,OAAO,CA4BT"}
1
+ {"version":3,"file":"delegation-context.d.ts","sourceRoot":"","sources":["../../../src/domain/delegation/delegation-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,GAAG,EACH,gBAAgB,EAChB,OAAO,EACP,OAAO,EACP,KAAK,EACN,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAG,cAAuB,CAAC;AAC/D,eAAO,MAAM,uBAAuB,EAAG,MAAe,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sCAAsC,EACjD,8CAAuD,CAAC;AAW1D,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAElF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAetF;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAmBjF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CA+B7E;AAoCD,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,uBAAuB,CAAC;IAC1D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CACnD;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,SAAS,GAAG,EACzD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,EACxB,OAAO,CAAC,EAAE,iCAAiC,GAC1C,OAAO,CA2BT;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,SAAS,GAAG,EACxD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,EACxB,gBAAgB,CAAC,EAAE,cAAc,GAAG,IAAI,GACvC,OAAO,CAwBT"}
@@ -2,7 +2,8 @@
2
2
  * Delegation context normalization — provider-agnostic replay identity fix.
3
3
  *
4
4
  * pi-ai transformMessages compares assistant message provider/api/model to the
5
- * target model. Virtual smart-router tags break isSameModel and strip replay
5
+ * target model. Foreign identities (virtual smart-router tags or any prior
6
+ * provider such as OpenAI/Anthropic/GLM) break isSameModel and strip replay
6
7
  * state (thoughtSignature, thinkingSignature, etc.).
7
8
  */
8
9
  export const VIRTUAL_ROUTER_PROVIDER = 'smart-router';
@@ -92,7 +93,7 @@ function rewriteAssistantIdentity(message, executionModel) {
92
93
  model: executionModel.id,
93
94
  };
94
95
  }
95
- function repairGoogleOriginAssistantMessage(message, targetExecution) {
96
+ function repairAssistantForGoogleReplay(message, targetExecution) {
96
97
  const content = message.content.map((block) => {
97
98
  if (block.type !== 'toolCall') {
98
99
  return block;
@@ -134,11 +135,17 @@ export function normalizeDelegationContext(context, targetModel, options) {
134
135
  return { ...context, messages };
135
136
  }
136
137
  /**
137
- * Repair Gemini tool-call replay for cross-model Google delegation.
138
+ * Repair Gemini tool-call replay for Google delegation targets.
138
139
  *
139
- * Call after {@link normalizeDelegationContext}. Aligns Google-origin assistant
140
- * identity to the delegation target and injects the thought-signature sentinel
141
- * when tool calls lack a captured signature.
140
+ * Call after {@link normalizeDelegationContext}. Aligns assistant identity to
141
+ * the delegation target and injects the thought-signature sentinel on every
142
+ * unsigned tool call, regardless of which provider produced the prior turn
143
+ * (Google, OpenAI, Anthropic, GLM, …). Captured signatures are preserved.
144
+ *
145
+ * Cross-provider repair (SP-231, #158): pi-ai strips replay state from any
146
+ * assistant message whose identity fails isSameModel, so a Gemini target
147
+ * would receive unsigned tool calls from non-Google turns and the Google API
148
+ * would reject the replay with a thought_signature error.
142
149
  */
143
150
  export function repairGeminiReplayContext(context, targetModel, sessionExecution) {
144
151
  // Accepted for SP-128 call-site symmetry with normalizeDelegationContext; identity
@@ -156,10 +163,7 @@ export function repairGeminiReplayContext(context, targetModel, sessionExecution
156
163
  if (message.role !== 'assistant') {
157
164
  return message;
158
165
  }
159
- if (!isGoogleOriginAssistantMessage(message)) {
160
- return message;
161
- }
162
- return repairGoogleOriginAssistantMessage(message, targetExecution);
166
+ return repairAssistantForGoogleReplay(message, targetExecution);
163
167
  });
164
168
  return { ...context, messages };
165
169
  }
@@ -1 +1 @@
1
- {"version":3,"file":"delegation-context.js","sourceRoot":"","sources":["../../../src/domain/delegation/delegation-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"delegation-context.js","sourceRoot":"","sources":["../../../src/domain/delegation/delegation-context.ts"],"names":[],"mappings":"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"}