pi-smart-router 0.19.1 → 0.19.2

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.
@@ -6,7 +6,7 @@ import {
6
6
  type Context,
7
7
  type Model,
8
8
  type SimpleStreamOptions,
9
- streamSimple as defaultDelegateStream,
9
+ streamSimple as compatDelegateStream,
10
10
  } from '@earendil-works/pi-ai/compat';
11
11
 
12
12
  import { parseAssistantMessageError } from '../../../src/infrastructure/delegation/provider-error.js';
@@ -21,9 +21,55 @@ import {
21
21
  type FailoverNoticeInfo,
22
22
  type FlushDelegatedEventsOptions,
23
23
  } from './delegation-runtime.js';
24
- import type { StreamDelegationDeps } from './types.js';
24
+ import type { DelegateStreamFn, StreamDelegationDeps } from './types.js';
25
25
  import { throwIfAborted } from './utils.js';
26
26
 
27
+ /**
28
+ * Minimal structural view of pi's composed provider (pi-coding-agent
29
+ * `ModelRegistry.getProvider()` → `composeModelProvider`). Typed structurally
30
+ * because `getProvider` only exists on newer pi-coding-agent versions (0.84+);
31
+ * the extension must keep loading against older ones.
32
+ */
33
+ interface ComposedProviderLike {
34
+ streamSimple: DelegateStreamFn;
35
+ }
36
+
37
+ type RegistryWithProviders = StreamDelegationDeps['modelRegistry'] & {
38
+ getProvider?: (providerId: string) => ComposedProviderLike | undefined;
39
+ };
40
+
41
+ /**
42
+ * Resolve the stream entrypoint for a delegation target (SP-238, #160).
43
+ *
44
+ * Priority:
45
+ * 1. Explicit `deps.delegateStream` injection (tests / operator override).
46
+ * 2. The **composed provider** from `modelRegistry.getProvider(model.provider)` —
47
+ * the same path pi's own agent loop delegates through. Its `streamSimple`
48
+ * checks extension-registered providers (`extension.streamSimple` when
49
+ * `model.api === extension.api`) before falling back to pi-ai's built-in
50
+ * registry, so custom-API models (claude-bridge et al.) resolve correctly.
51
+ * 3. Bare `pi-ai/compat streamSimple` — dispatches only on `model.api` against
52
+ * pi-ai's private built-in registry; used when no composed provider exists
53
+ * (older pi-coding-agent, or providers never composed through ModelRuntime).
54
+ */
55
+ function resolveDelegateStream(
56
+ targetModel: Model<Api>,
57
+ deps: StreamDelegationDeps,
58
+ ): DelegateStreamFn {
59
+ if (deps.delegateStream) {
60
+ return deps.delegateStream;
61
+ }
62
+ const registry = deps.modelRegistry as RegistryWithProviders;
63
+ const provider =
64
+ typeof registry.getProvider === 'function'
65
+ ? registry.getProvider(targetModel.provider)
66
+ : undefined;
67
+ if (provider && typeof provider.streamSimple === 'function') {
68
+ return (model, context, options) => provider.streamSimple(model, context, options);
69
+ }
70
+ return compatDelegateStream;
71
+ }
72
+
27
73
  function isTerminalEvent(
28
74
  event: AssistantMessageEvent,
29
75
  ): event is Extract<AssistantMessageEvent, { type: 'done' | 'error' }> {
@@ -52,7 +98,7 @@ export async function collectDelegatedStream(
52
98
  options,
53
99
  headroomContext,
54
100
  );
55
- const delegateStream = deps.delegateStream ?? defaultDelegateStream;
101
+ const delegateStream = resolveDelegateStream(targetModel, deps);
56
102
  const inner = delegateStream(targetModel, context, delegationOptions);
57
103
  const events: AssistantMessageEvent[] = [];
58
104
  let finalMessage: AssistantMessage | undefined;
@@ -119,7 +165,7 @@ export async function pipeDelegatedStream(
119
165
  options,
120
166
  headroomContext,
121
167
  );
122
- const delegateStream = deps.delegateStream ?? defaultDelegateStream;
168
+ const delegateStream = resolveDelegateStream(targetModel, deps);
123
169
  const inner = delegateStream(targetModel, context, delegationOptions);
124
170
  const events: AssistantMessageEvent[] = [];
125
171
  let finalMessage: AssistantMessage | undefined;
@@ -57,7 +57,12 @@ export interface StreamDelegationDeps {
57
57
  /** Cheap scope fingerprint check before each routed turn. */
58
58
  ensureFleetFresh?: () => Promise<void>;
59
59
  readonly executionLedger: ExecutionLedger;
60
- /** Injectable for tests; production uses pi-ai streamSimple. */
60
+ /**
61
+ * Injectable stream override for tests; when unset, delegation resolves the
62
+ * stream through `modelRegistry.getProvider(...)` (composed provider, which
63
+ * knows extension-registered custom APIs — SP-238, #160), falling back to
64
+ * pi-ai/compat `streamSimple` when no composed provider exists.
65
+ */
61
66
  delegateStream?: DelegateStreamFn;
62
67
  /** Injectable planning delegate sub-call; production uses frontier stream delegate. */
63
68
  spawnPlanningDelegate?: PlanningDelegateSpawnFn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-smart-router",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "Auto-model router middleware for the pi.dev coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",