@duetso/agent 0.3.12 → 0.3.13

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 (50) hide show
  1. package/dist/package.json +6 -6
  2. package/dist/src/cli/connect.d.ts +7 -0
  3. package/dist/src/cli/connect.d.ts.map +1 -1
  4. package/dist/src/cli/connect.js +46 -27
  5. package/dist/src/cli/connect.js.map +1 -1
  6. package/dist/src/connected-providers/capability-probe.d.ts.map +1 -1
  7. package/dist/src/connected-providers/capability-probe.js +13 -8
  8. package/dist/src/connected-providers/capability-probe.js.map +1 -1
  9. package/dist/src/connected-providers/fake-issuer.d.ts +16 -1
  10. package/dist/src/connected-providers/fake-issuer.d.ts.map +1 -1
  11. package/dist/src/connected-providers/fake-issuer.js +89 -42
  12. package/dist/src/connected-providers/fake-issuer.js.map +1 -1
  13. package/dist/src/connected-providers/refresh.d.ts.map +1 -1
  14. package/dist/src/connected-providers/refresh.js +9 -2
  15. package/dist/src/connected-providers/refresh.js.map +1 -1
  16. package/dist/src/connected-providers/registry.d.ts +8 -2
  17. package/dist/src/connected-providers/registry.d.ts.map +1 -1
  18. package/dist/src/connected-providers/registry.js +18 -3
  19. package/dist/src/connected-providers/registry.js.map +1 -1
  20. package/dist/src/connected-providers/tokens.d.ts.map +1 -1
  21. package/dist/src/connected-providers/tokens.js +10 -6
  22. package/dist/src/connected-providers/tokens.js.map +1 -1
  23. package/dist/src/core/structured-output.d.ts.map +1 -1
  24. package/dist/src/core/structured-output.js +2 -1
  25. package/dist/src/core/structured-output.js.map +1 -1
  26. package/dist/src/memory/observational.d.ts +0 -1
  27. package/dist/src/memory/observational.d.ts.map +1 -1
  28. package/dist/src/memory/observational.js +19 -39
  29. package/dist/src/memory/observational.js.map +1 -1
  30. package/dist/src/model-resolution/catalog.d.ts.map +1 -1
  31. package/dist/src/model-resolution/catalog.js +6 -9
  32. package/dist/src/model-resolution/catalog.js.map +1 -1
  33. package/dist/src/model-resolution/duet-gateway.d.ts +52 -45
  34. package/dist/src/model-resolution/duet-gateway.d.ts.map +1 -1
  35. package/dist/src/model-resolution/duet-gateway.js +192 -264
  36. package/dist/src/model-resolution/duet-gateway.js.map +1 -1
  37. package/dist/src/model-resolution/models.d.ts +24 -0
  38. package/dist/src/model-resolution/models.d.ts.map +1 -0
  39. package/dist/src/model-resolution/models.js +63 -0
  40. package/dist/src/model-resolution/models.js.map +1 -0
  41. package/dist/src/model-resolution/resolver.d.ts +1 -1
  42. package/dist/src/model-resolution/resolver.d.ts.map +1 -1
  43. package/dist/src/model-resolution/resolver.js +18 -17
  44. package/dist/src/model-resolution/resolver.js.map +1 -1
  45. package/dist/src/model-routing/advisor.js +9 -1
  46. package/dist/src/model-routing/advisor.js.map +1 -1
  47. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  48. package/dist/src/turn-runner/turn-runner.js +4 -0
  49. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  50. package/package.json +6 -6
@@ -1,4 +1,9 @@
1
- import { getEnvApiKey, getModel } from "@earendil-works/pi-ai";
1
+ import { createProvider, envApiKeyAuth, } from "@earendil-works/pi-ai";
2
+ import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
3
+ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
4
+ import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
5
+ import { getEnvApiKey } from "@earendil-works/pi-ai/compat";
6
+ import { builtinProviders, getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
2
7
  import { isConnectedProviderId } from "../connected-providers/store.js";
3
8
  import { activeDuetTier } from "../model-routing/active-tier.js";
4
9
  import { connectedProviderApiKey, refreshConnectedTokenInBackground, } from "../connected-providers/tokens.js";
@@ -19,70 +24,15 @@ export function duetTierHeaders() {
19
24
  }
20
25
  const DEFAULT_DUET_GATEWAY_BASE_URL = "https://gateway.duet.so";
21
26
  const OPENAI_MODEL_PREFIX = "openai/";
27
+ const ANTHROPIC_MODEL_PREFIX = "anthropic/";
28
+ const DUET_GATEWAY_PROVIDER_ID = "duet-gateway";
22
29
  const VERCEL_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
23
- const OPENAI_BASE_URL = "https://api.openai.com/v1";
24
- // Published USD-per-million-token prices (input / output / cacheRead / cacheWrite),
25
- // verified against the OpenRouter listing 2026-07-18. Clones inherit their
26
- // sibling's cost and synthesized passthroughs zero it out, so every model that
27
- // resolves through an override below must carry its real price here or cost
28
- // accounting (sidebar, usage events, advisor-preview) silently lies.
29
- const GPT_5_6_GATEWAY_CAPABILITIES = {
30
- input: ["text", "image"],
31
- contextWindow: 1_050_000,
32
- maxTokens: 128_000,
33
- };
34
- const OPENAI_GATEWAY_MODEL_OVERRIDES = {
35
- "openai/gpt-5.6-sol": {
36
- ...GPT_5_6_GATEWAY_CAPABILITIES,
37
- cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
38
- },
39
- "openai/gpt-5.6-terra": {
40
- ...GPT_5_6_GATEWAY_CAPABILITIES,
41
- cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 3.125 },
42
- },
43
- "openai/gpt-5.6-luna": {
44
- ...GPT_5_6_GATEWAY_CAPABILITIES,
45
- cost: { input: 1, output: 6, cacheRead: 0.1, cacheWrite: 1.25 },
46
- },
47
- };
48
- // Vercel exposes GLM 5.2's maximum reasoning mode, but the pinned pi-ai
49
- // catalog does not yet describe it. Keep the product-level `xhigh` setting
50
- // wire-faithful until that metadata ships upstream.
51
- const VERCEL_GATEWAY_MODEL_OVERRIDES = {
52
- "zai/glm-5.2": {
53
- thinkingLevelMap: { xhigh: "max" },
54
- compat: { forceAdaptiveThinking: true },
55
- },
56
- };
57
- const KIMI_K3_CAPABILITIES = {
58
- input: ["text", "image"],
59
- contextWindow: 1_000_000,
60
- maxTokens: 131_072,
61
- cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
62
- // K3 currently exposes one reasoning setting. Mapping app-level `high` to
63
- // `max` keeps the selection honest on both supported transports.
64
- thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: "max", xhigh: null },
65
- };
66
- const FABLE_5_COST = {
67
- cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
68
- };
69
- // Sonnet 5 introductory pricing (through 2026-08-31); bump to 3/15/0.3/3.75 after.
70
- const SONNET_5_COST = {
71
- cost: { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
72
- };
30
+ const VERCEL_GATEWAY_PROVIDER_ID = "vercel-ai-gateway";
73
31
  /**
74
32
  * The Duet gateway proxies Vercel's AI Gateway path layout and authenticates
75
- * with a `DUET_API_KEY` token scoped to a single org. Rather than ship a
76
- * parallel model registry, the `duet-gateway` provider piggybacks on upstream
77
- * model definitions and only swaps `baseUrl` to point at Duet.
78
- *
79
- * `DUET_GATEWAY_BASE_URL` can point model traffic at a dedicated gateway
80
- * origin. When it is unset, model traffic goes directly to
33
+ * with a `DUET_API_KEY` token scoped to a single org. `DUET_GATEWAY_BASE_URL`
34
+ * points model traffic at a different origin; unset, it goes to
81
35
  * `https://gateway.duet.so`.
82
- *
83
- * Auth flows through pi-ai's existing vercel-ai-gateway path, which reads
84
- * `AI_GATEWAY_API_KEY` — the CLI shims that env var from `DUET_API_KEY` at
85
- * startup so users only need to set the duet token.
86
36
  */
87
37
  export const DUET_GATEWAY_API_KEY_ENV = "DUET_API_KEY";
88
38
  export const DUET_GATEWAY_BASE_URL_ENV = "DUET_GATEWAY_BASE_URL";
@@ -92,246 +42,224 @@ export function getDuetGatewayBaseUrl() {
92
42
  return stripTrailingSlash(override);
93
43
  return DEFAULT_DUET_GATEWAY_BASE_URL;
94
44
  }
45
+ const DUET_GATEWAY = {
46
+ id: "duet-gateway",
47
+ name: "Duet Gateway",
48
+ origin: getDuetGatewayBaseUrl,
49
+ auth: () => ({ apiKey: envApiKeyAuth("Duet", [DUET_GATEWAY_API_KEY_ENV]) }),
50
+ // Every duet-gateway model resolves through this route — parent step,
51
+ // classifier, vision fallback, subagents and memory alike — so the routing
52
+ // tier is claimed here rather than at each call site.
53
+ headers: duetTierHeaders,
54
+ };
55
+ const VERCEL_GATEWAY = {
56
+ id: VERCEL_GATEWAY_PROVIDER_ID,
57
+ name: "Vercel AI Gateway",
58
+ origin: () => VERCEL_GATEWAY_BASE_URL,
59
+ // The transports and the model list are ours to decide; how Vercel
60
+ // authenticates stays the catalog's to declare.
61
+ auth: () => builtinGatewayAuth(),
62
+ // The tier claim means something only to the Duet proxy, which bills
63
+ // against it; Vercel would reject an unknown header's cost to no purpose.
64
+ headers: () => undefined,
65
+ };
66
+ export const GATEWAY_ROUTES = [DUET_GATEWAY, VERCEL_GATEWAY];
67
+ /** The route serving `providerId`, or undefined when it is not a gateway. */
68
+ export function gatewayRoute(providerId) {
69
+ return GATEWAY_ROUTES.find((route) => route.id === providerId);
70
+ }
71
+ function builtinGatewayAuth() {
72
+ const builtin = builtinProviders().find(({ id }) => id === VERCEL_GATEWAY_PROVIDER_ID);
73
+ if (!builtin)
74
+ throw new Error(`Model catalog has no ${VERCEL_GATEWAY_PROVIDER_ID} provider`);
75
+ return builtin.auth;
76
+ }
95
77
  /**
96
- * Resolve a `duet-gateway:<modelId>` string to a Model.
78
+ * The catalog rebased onto one gateway route, built once per route.
97
79
  *
98
- * Anthropic models use pi-ai's Vercel AI Gateway catalog because that path is
99
- * already Anthropic-native. OpenAI models intentionally use pi-ai's OpenAI
100
- * catalog and the Duet gateway's OpenAI-compatible route instead; routing them
101
- * through the Anthropic-compatible gateway path drops OpenAI reasoning stream
102
- * semantics, so the TUI never sees reasoning/thinking events.
80
+ * The catalog is the only source of a model's capabilities; nothing here
81
+ * declares a model. That is what lets a newly served gateway model work with
82
+ * no code change, and it is why a capability we send on the wire must either
83
+ * come from the catalog or be named in `gatewayCapabilityGaps` below.
103
84
  *
104
- * Auth: the duet.so proxy only accepts `DUET_API_KEY`-style tokens and 500s on
105
- * a Vercel `vck_...` key. `resolveProviderApiKey("duet-gateway")` returns the
106
- * Duet token directly so the underlying transport always sends the right
107
- * credential, even when the user also has a real `AI_GATEWAY_API_KEY=vck_...`
108
- * set for explicit `vercel-ai-gateway:*` pins.
85
+ * `resolveGatewayModel` runs on every model resolution parent step,
86
+ * classifier, each subagent so rebasing two hundred catalog entries per call
87
+ * is pure waste. Route id and origin are the rebase's only inputs, so together
88
+ * they are the key; tests move the origin between cases, which is why this is
89
+ * not a plain once-flag.
109
90
  */
110
- export function resolveDuetGatewayModel(modelId) {
111
- const upstream = resolveDuetGatewayUpstream(modelId);
112
- // Every duet-gateway model is built here — parent step, classifier, vision
113
- // fallback, subagents and memory alike — so stamping the routing tier once
114
- // at this seam attributes all of them without per-call-site plumbing. Never
115
- // added to other transports.
116
- const tierHeaders = duetTierHeaders();
117
- return {
118
- ...upstream,
119
- provider: "duet-gateway",
120
- id: modelId,
121
- baseUrl: getDuetGatewayBaseUrlForModel(upstream),
122
- ...(tierHeaders && { headers: { ...upstream.headers, ...tierHeaders } }),
123
- };
91
+ const gatewayModelsByRoute = new Map();
92
+ function gatewayModels(route) {
93
+ const origin = route.origin();
94
+ const key = `${route.id}@${origin}`;
95
+ const cached = gatewayModelsByRoute.get(key);
96
+ if (cached)
97
+ return cached;
98
+ const models = getBuiltinModels(VERCEL_GATEWAY_PROVIDER_ID).map((model) => rebaseOntoGateway(model, route, origin));
99
+ gatewayModelsByRoute.set(key, models);
100
+ return models;
124
101
  }
125
102
  /**
126
- * Resolve a gateway model id to its upstream spec, preferring pi-ai's catalog
127
- * and synthesizing a pass-through model when the catalog has not shipped the id
128
- * yet. The Duet gateway proxies Vercel's AI Gateway, which serves every
129
- * `provider/model` id over the anthropic-messages transport (OpenAI models keep
130
- * their native openai-responses transport for reasoning stream semantics), so a
131
- * newly listed model works the moment Vercel serves it — without a catalog or
132
- * code change here. When pi-ai later ships the model its real spec takes
133
- * precedence over the synthesized placeholder automatically.
103
+ * The transport a gateway model runs over.
104
+ *
105
+ * Both gateways serve the whole catalog over the same protocol surface — pi-ai
106
+ * declares every `vercel-ai-gateway` model as `anthropic-messages` so the
107
+ * choice is per model, not per gateway, and `duet-gateway` and
108
+ * `vercel-ai-gateway` make it identically.
109
+ *
110
+ * Anthropic's own models keep the Anthropic transport, which is native for
111
+ * them. Everything else that accepts images moves to OpenAI completions,
112
+ * because the two transports disagree about where a tool result's image goes:
113
+ * the Anthropic serializer leaves it inside the `tool_result` block, and
114
+ * Vercel's translation to an OpenAI-shaped provider has nowhere to put it, so
115
+ * it arrives as text. Measured against `moonshotai/kimi-k3`, one 512x512 PNG
116
+ * costs 147,554 tokens that way and 650 when the OpenAI serializer hoists it
117
+ * into the following user message — which it already does, unprompted.
118
+ *
119
+ * OpenAI's own models stay on the Responses transport: routing them through
120
+ * either compatible path drops reasoning stream semantics and silently ignores
121
+ * `reasoningEffort`.
134
122
  */
135
- function resolveDuetGatewayUpstream(modelId) {
136
- if (modelId.startsWith(OPENAI_MODEL_PREFIX)) {
137
- return resolveOpenAIResponsesModel(modelId);
138
- }
139
- const upstream = getModel("vercel-ai-gateway", modelId) ??
140
- resolveMissingModel("vercel-ai-gateway", modelId) ??
141
- synthesizePassthroughModel(modelId, "anthropic-messages");
142
- return applyVercelGatewayModelOverrides(modelId, upstream);
123
+ function gatewayApi(model) {
124
+ if (model.id.startsWith(OPENAI_MODEL_PREFIX))
125
+ return "openai-responses";
126
+ if (model.id.startsWith(ANTHROPIC_MODEL_PREFIX))
127
+ return model.api;
128
+ return model.input.includes("image") ? "openai-completions" : model.api;
143
129
  }
144
- /** Apply gateway capabilities that are newer than the pinned model catalog. */
145
- export function applyVercelGatewayModelOverrides(modelId, model) {
146
- const overrides = VERCEL_GATEWAY_MODEL_OVERRIDES[modelId];
147
- if (!overrides)
148
- return model;
130
+ /**
131
+ * Point a catalog model at a gateway: its own transport, that gateway's origin,
132
+ * and the capabilities the catalog is missing.
133
+ *
134
+ * `provider` is deliberately rewritten for the Duet gateway so cost and usage
135
+ * telemetry attribute the call to the Duet proxy rather than to Vercel.
136
+ */
137
+ function rebaseOntoGateway(model, route, origin) {
138
+ const api = gatewayApi(model);
139
+ const gaps = gatewayCapabilityGaps(model.id);
149
140
  return {
150
141
  ...model,
151
- ...overrides,
152
- thinkingLevelMap: {
153
- ...model.thinkingLevelMap,
154
- ...overrides.thinkingLevelMap,
155
- },
156
- compat: { ...model.compat, ...overrides.compat },
142
+ // A gap fills in what the catalog omits; it must not take the two nested
143
+ // records with it, or the day the catalog ships one field here silently
144
+ // drops the others.
145
+ thinkingLevelMap: { ...model.thinkingLevelMap, ...gaps?.thinkingLevelMap },
146
+ compat: { ...model.compat, ...gaps?.compat },
147
+ api,
148
+ // Rewritten so cost and usage telemetry attribute the call to the route
149
+ // that served it rather than to the catalog it was declared in.
150
+ provider: route.id,
151
+ baseUrl: gatewayBaseUrlForApi(origin, api),
157
152
  };
158
153
  }
159
154
  /**
160
- * Build a minimal spec for a gateway model pi-ai's catalog has not shipped yet.
161
- * The context/output ceilings are intentionally conservative so an unknown
162
- * model never 400s on an over-advertised window; a model that needs a tighter
163
- * cap can still set `maxOutputTokens` in the catalog. `provider`/`baseUrl` are
164
- * placeholders that `resolveDuetGatewayModel` overwrites with the Duet proxy
165
- * route, so only `api` (which picks that route) and the limits matter here.
155
+ * Capabilities both gateways serve that pi-ai's catalog does not yet describe.
156
+ *
157
+ * An entry here is a bug report against the catalog, not a place to declare a
158
+ * model: it exists only while a capability we send on the wire has no catalog
159
+ * field behind it, and it is deleted the moment one ships. Anything beyond
160
+ * that — a price, a context window, a transport belongs to the catalog, and
161
+ * a second copy of it here would silently go stale.
166
162
  */
167
- function synthesizePassthroughModel(modelId, api) {
168
- const isOpenAI = api === "openai-responses";
169
- return {
170
- id: modelId,
171
- name: modelId,
172
- api,
173
- provider: isOpenAI ? "openai" : "vercel-ai-gateway",
174
- baseUrl: isOpenAI ? OPENAI_BASE_URL : VERCEL_GATEWAY_BASE_URL,
175
- reasoning: true,
176
- input: isOpenAI ? ["text", "image"] : ["text"],
177
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
178
- contextWindow: 256_000,
179
- maxTokens: 64_000,
180
- };
163
+ function gatewayCapabilityGaps(modelId) {
164
+ // GLM 5.2's maximum reasoning mode is reached with `"max"`; without the map
165
+ // the product's `xhigh` setting reaches the wire as a level GLM ignores.
166
+ if (modelId === "zai/glm-5.2") {
167
+ return { thinkingLevelMap: { xhigh: "max" }, compat: { forceAdaptiveThinking: true } };
168
+ }
169
+ return undefined;
181
170
  }
182
171
  /**
183
- * Known sibling specs cloned for models pi-ai's catalog has not shipped yet.
184
- * The `duet-gateway` route resolves through the `vercel-ai-gateway` catalog, so
185
- * those entries cover it too. Per-model overrides replace sibling metadata
186
- * only where the new model's published contract differs. Drop an entry the
187
- * moment pi-ai ships that provider/model pair.
172
+ * A gateway as a first-class pi-ai provider.
173
+ *
174
+ * `auth` is why this is a provider rather than a bag of rewritten models: a
175
+ * credential is resolved per provider, and `duet-gateway` is not an id pi-ai's
176
+ * env-key map knows. Declaring it on the provider is what lets any call
177
+ * resolve the token without being handed one.
178
+ *
179
+ * All three transports are declared because serialization belongs to the
180
+ * provider: rewriting a model's `api` to one the provider does not implement
181
+ * yields a model that claims Responses and is sent as Anthropic, silently
182
+ * dropping reasoning effort and mangling tool-result images.
188
183
  */
189
- const MISSING_MODEL_CLONES = {
190
- "openai-codex": [
191
- {
192
- // Drop once pi-ai ships openai-codex:gpt-5.6-sol.
193
- from: "gpt-5.5",
194
- to: "gpt-5.6-sol",
195
- overrides: OPENAI_GATEWAY_MODEL_OVERRIDES["openai/gpt-5.6-sol"],
196
- },
197
- {
198
- // Drop once pi-ai ships openai-codex:gpt-5.6-terra.
199
- from: "gpt-5.5",
200
- to: "gpt-5.6-terra",
201
- overrides: OPENAI_GATEWAY_MODEL_OVERRIDES["openai/gpt-5.6-terra"],
202
- },
203
- {
204
- // Drop once pi-ai ships openai-codex:gpt-5.6-luna.
205
- from: "gpt-5.5",
206
- to: "gpt-5.6-luna",
207
- overrides: OPENAI_GATEWAY_MODEL_OVERRIDES["openai/gpt-5.6-luna"],
208
- },
209
- ],
210
- "vercel-ai-gateway": [
211
- // Opus 5 keeps Opus 4.8's published contract and pricing, so no overrides.
212
- { from: "anthropic/claude-opus-4.8", to: "anthropic/claude-opus-5" },
213
- { from: "anthropic/claude-opus-4.8", to: "anthropic/claude-fable-5", overrides: FABLE_5_COST },
214
- {
215
- from: "anthropic/claude-opus-4.8",
216
- to: "anthropic/claude-sonnet-5",
217
- overrides: SONNET_5_COST,
218
- },
219
- {
220
- from: "moonshotai/kimi-k2.6",
221
- to: "moonshotai/kimi-k3",
222
- overrides: {
223
- ...KIMI_K3_CAPABILITIES,
224
- compat: { forceAdaptiveThinking: true },
225
- },
226
- },
227
- ],
228
- openrouter: [
229
- // Without this, `openrouter:anthropic/claude-opus-5` resolves to undefined
230
- // and OPENROUTER-only users cannot run the default model at all.
231
- { from: "anthropic/claude-opus-4.8", to: "anthropic/claude-opus-5" },
232
- {
233
- // OpenRouter serves gpt-5.6-luna; pi-ai's catalog just hasn't shipped
234
- // the route (David, 2026-07-24). Drop once it does.
235
- from: "openai/gpt-5.5",
236
- to: "openai/gpt-5.6-luna",
237
- overrides: OPENAI_GATEWAY_MODEL_OVERRIDES["openai/gpt-5.6-luna"],
184
+ export function gatewayProvider(route) {
185
+ return createProvider({
186
+ id: route.id,
187
+ name: route.name,
188
+ baseUrl: route.origin(),
189
+ auth: route.auth(),
190
+ models: gatewayModels(route),
191
+ api: {
192
+ "anthropic-messages": anthropicMessagesApi(),
193
+ "openai-completions": openAICompletionsApi(),
194
+ "openai-responses": openAIResponsesApi(),
238
195
  },
239
- {
240
- from: "moonshotai/kimi-k2.6",
241
- to: "moonshotai/kimi-k3",
242
- overrides: KIMI_K3_CAPABILITIES,
243
- },
244
- {
245
- from: "openai/gpt-5.4",
246
- to: "openai/gpt-5.6-sol",
247
- overrides: OPENAI_GATEWAY_MODEL_OVERRIDES["openai/gpt-5.6-sol"],
248
- },
249
- {
250
- from: "openai/gpt-5.4",
251
- to: "openai/gpt-5.6-terra",
252
- overrides: OPENAI_GATEWAY_MODEL_OVERRIDES["openai/gpt-5.6-terra"],
253
- },
254
- ],
255
- };
196
+ });
197
+ }
256
198
  /**
257
- * Clone a known sibling on the same provider to build a Model pi-ai has not
258
- * shipped yet; returns undefined when the provider/modelId pair is not a
259
- * pending clone. Shared by the `duet-gateway` path (above) and direct
260
- * `vercel-ai-gateway`/`openrouter` resolution in resolver.ts. Delete a clone
261
- * entry once pi-ai ships that provider/model pair and it resolves directly.
199
+ * Resolve a `<gateway>:<modelId>` string to a Model.
200
+ *
201
+ * An id the catalog has not shipped still resolves: a gateway serves a model
202
+ * the moment its vendor lists it, and waiting for a catalog release to use it
203
+ * is the coupling this module exists to avoid. The synthesized entry is
204
+ * deliberately conservative so an unknown model never 400s on an
205
+ * over-advertised window.
262
206
  */
263
- export function resolveMissingModel(provider, modelId) {
264
- if (provider === "vercel-ai-gateway" && modelId.startsWith(OPENAI_MODEL_PREFIX)) {
265
- return resolveVercelGatewayOpenAIModel(modelId);
266
- }
267
- const clone = MISSING_MODEL_CLONES[provider]?.find((entry) => entry.to === modelId);
268
- if (!clone)
269
- return undefined;
270
- const sibling = getModel(provider, clone.from);
271
- if (!sibling)
272
- return undefined;
273
- const compat = clone.overrides?.compat
274
- ? { ...sibling.compat, ...clone.overrides.compat }
275
- : sibling.compat;
276
- return { ...sibling, ...clone.overrides, compat, id: modelId, name: modelId };
207
+ export function resolveGatewayModel(route, modelId) {
208
+ const declared = gatewayModels(route).find((model) => model.id === modelId);
209
+ const model = declared ?? rebaseOntoGateway(synthesizePassthroughModel(modelId), route, route.origin());
210
+ // Applied here rather than in the rebase: the rebased list is memoized per
211
+ // route, but the routing tier changes within a session — a mid-turn `/model`
212
+ // switch retargets attribution — so a header baked in at build time would
213
+ // report whichever tier happened to be active when the list was first built.
214
+ const headers = route.headers();
215
+ return headers ? { ...model, headers: { ...model.headers, ...headers } } : model;
277
216
  }
278
217
  /**
279
- * Synthesize a `vercel-ai-gateway` OpenAI model pi-ai's catalog has not shipped
280
- * yet, keeping it on the openai-responses transport pointed at the Vercel
281
- * gateway's OpenAI-compatible `/v1` route. pi-ai serves gateway OpenAI models
282
- * over the anthropic-messages transport, which drops OpenAI reasoning stream
283
- * semantics AND silently ignores `reasoningEffort` — so a memory model resolved
284
- * here (e.g. the default gpt-5.6-luna) could never run at the low effort the
285
- * observer/reflectors request. This mirrors the deliberate openai-responses
286
- * routing documented on `resolveDuetGatewayModel`. Auth is unaffected:
287
- * `resolveProviderApiKey`/pi-ai key resolution keys off `provider`, which stays
288
- * `vercel-ai-gateway`, so `AI_GATEWAY_API_KEY` still applies.
218
+ * Build a minimal spec for a gateway model the catalog has not shipped yet.
219
+ * OpenAI ids keep the Responses transport and its native vision support; every
220
+ * other unknown id is assumed text-only until the catalog says otherwise.
289
221
  */
290
- function resolveVercelGatewayOpenAIModel(modelId) {
291
- const upstream = resolveOpenAIResponsesModel(modelId);
222
+ function synthesizePassthroughModel(modelId) {
292
223
  return {
293
- ...upstream,
294
- provider: "vercel-ai-gateway",
295
224
  id: modelId,
296
225
  name: modelId,
297
- baseUrl: `${VERCEL_GATEWAY_BASE_URL}/v1`,
226
+ // `provider`, `baseUrl` and `api` are required fields the rebase then
227
+ // decides for real; only `input` survives, and it is what tells the rebase
228
+ // which transport can carry this model.
229
+ provider: VERCEL_GATEWAY_PROVIDER_ID,
230
+ baseUrl: VERCEL_GATEWAY_BASE_URL,
231
+ api: "anthropic-messages",
232
+ reasoning: true,
233
+ input: modelId.startsWith(OPENAI_MODEL_PREFIX) ? ["text", "image"] : ["text"],
234
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
235
+ contextWindow: 256_000,
236
+ maxTokens: 64_000,
298
237
  };
299
238
  }
300
- function resolveOpenAIResponsesModel(modelId) {
301
- const slug = modelId.slice(OPENAI_MODEL_PREFIX.length);
302
- const upstream = getModel("openai", slug) ??
303
- synthesizePassthroughModel(modelId, "openai-responses");
304
- return { ...upstream, ...OPENAI_GATEWAY_MODEL_OVERRIDES[modelId] };
305
- }
306
- function getDuetGatewayBaseUrlForModel(model) {
307
- if (model.api === "openai-completions" || model.api === "openai-responses") {
308
- return `${getDuetGatewayBaseUrl()}/v1`;
309
- }
310
- return getDuetGatewayBaseUrl();
239
+ /** OpenAI-compatible transports live under the gateway's `/v1` route. */
240
+ function gatewayBaseUrlForApi(origin, api) {
241
+ const openAiCompatible = api === "openai-completions" || api === "openai-responses";
242
+ return openAiCompatible ? `${origin}/v1` : origin;
311
243
  }
312
244
  function stripTrailingSlash(url) {
313
245
  return url.endsWith("/") ? url.slice(0, -1) : url;
314
246
  }
315
247
  /**
316
- * Resolve the API key for a provider, including the project-local
317
- * `duet-gateway` provider that pi-ai's built-in env-key map does not
318
- * know about.
248
+ * The API key to send for a provider, resolved the way Duet stores keys.
319
249
  *
320
- * `resolveDuetGatewayModel` deliberately overrides `model.provider` to
321
- * `"duet-gateway"` so cost and usage telemetry attribute the call to
322
- * the Duet proxy rather than the upstream vercel-ai-gateway. The
323
- * tradeoff is that pi-ai's `getEnvApiKey(provider)` returns `undefined`
324
- * for `"duet-gateway"`, so the agent-loop's `getApiKey` callback and
325
- * any direct `complete()` call would silently send an empty API key
326
- * and fail with `Could not resolve authentication method` even when
327
- * `DUET_API_KEY` is set.
250
+ * Two kinds of caller need this. Connected providers (ChatGPT, Copilot) keep
251
+ * their token in Duet's own store, which pi-ai cannot see at all. And the
252
+ * api-dispatch `complete()` used for structured output does no provider auth
253
+ * resolution whatsoever, so it needs the key handed to it — including for
254
+ * `duet-gateway`, an id pi-ai's env-key map does not know.
328
255
  *
329
- * This wrapper closes the gap: `"duet-gateway"` returns the Duet token
330
- * directly, every other provider falls through to pi-ai's normal
331
- * env-key resolution unchanged.
256
+ * A gateway rebase deliberately rewrites `model.provider` to
257
+ * `"duet-gateway"` so cost and usage telemetry attribute the call to the Duet
258
+ * proxy rather than to Vercel; that rename is what puts the id outside pi-ai's
259
+ * map in the first place.
332
260
  */
333
261
  export function resolveProviderApiKey(provider) {
334
- if (provider === "duet-gateway") {
262
+ if (provider === DUET_GATEWAY_PROVIDER_ID) {
335
263
  return process.env[DUET_GATEWAY_API_KEY_ENV];
336
264
  }
337
265
  if (isConnectedProviderId(provider)) {
@@ -1 +1 @@
1
- {"version":3,"file":"duet-gateway.js","sourceRoot":"","sources":["../../../src/model-resolution/duet-gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAc,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EACL,uBAAuB,EACvB,iCAAiC,GAClC,MAAM,kCAAkC,CAAC;AAE1C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,CAAC;AACvE,CAAC;AAED,MAAM,6BAA6B,GAAG,yBAAyB,CAAC;AAChE,MAAM,mBAAmB,GAAG,SAAS,CAAC;AACtC,MAAM,uBAAuB,GAAG,8BAA8B,CAAC;AAC/D,MAAM,eAAe,GAAG,2BAA2B,CAAC;AAMpD,oFAAoF;AACpF,2EAA2E;AAC3E,+EAA+E;AAC/E,4EAA4E;AAC5E,qEAAqE;AACrE,MAAM,4BAA4B,GAAG;IACnC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,SAAS;IACxB,SAAS,EAAE,OAAO;CACW,CAAC;AAEhC,MAAM,8BAA8B,GAAwC;IAC1E,oBAAoB,EAAE;QACpB,GAAG,4BAA4B;QAC/B,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE;KACjE;IACD,sBAAsB,EAAE;QACtB,GAAG,4BAA4B;QAC/B,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE;KACrE;IACD,qBAAqB,EAAE;QACrB,GAAG,4BAA4B;QAC/B,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE;KAChE;CACF,CAAC;AAEF,wEAAwE;AACxE,2EAA2E;AAC3E,oDAAoD;AACpD,MAAM,8BAA8B,GAAwC;IAC1E,aAAa,EAAE;QACb,gBAAgB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;QAClC,MAAM,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE;KACxC;CACF,CAAC;AAEF,MAAM,oBAAoB,GAAG;IAC3B,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,SAAS;IACxB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE;IAC7D,0EAA0E;IAC1E,iEAAiE;IACjE,gBAAgB,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE;CACpE,CAAC;AAEhC,MAAM,YAAY,GAAG;IACnB,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE;CAClC,CAAC;AAEhC,mFAAmF;AACnF,MAAM,aAAa,GAAG;IACpB,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE;CAClC,CAAC;AAEhC;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,cAAc,CAAC;AACvD,MAAM,CAAC,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAEjE,MAAM,UAAU,qBAAqB;IACnC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,EAAE,IAAI,EAAE,CAAC;IAChE,IAAI,QAAQ;QAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAClD,OAAO,6BAA6B,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,MAAM,QAAQ,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACrD,2EAA2E;IAC3E,2EAA2E;IAC3E,4EAA4E;IAC5E,6BAA6B;IAC7B,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IAEtC,OAAO;QACL,GAAG,QAAQ;QACX,QAAQ,EAAE,cAAc;QACxB,EAAE,EAAE,OAAO;QACX,OAAO,EAAE,6BAA6B,CAAC,QAAQ,CAAC;QAChD,GAAG,CAAC,WAAW,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,WAAW,EAAE,EAAE,CAAC;KACzE,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,0BAA0B,CAAC,OAAe;IACjD,IAAI,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC5C,OAAO,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,QAAQ,GACX,QAAQ,CAAC,mBAA0B,EAAE,OAAc,CAA4B;QAChF,mBAAmB,CAAC,mBAAmB,EAAE,OAAO,CAAC;QACjD,0BAA0B,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;IAC5D,OAAO,gCAAgC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gCAAgC,CAAC,OAAe,EAAE,KAAiB;IACjF,MAAM,SAAS,GAAG,8BAA8B,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO;QACL,GAAG,KAAK;QACR,GAAG,SAAS;QACZ,gBAAgB,EAAE;YAChB,GAAG,KAAK,CAAC,gBAAgB;YACzB,GAAG,SAAS,CAAC,gBAAgB;SAC9B;QACD,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,EAAE;KACjD,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,0BAA0B,CACjC,OAAe,EACf,GAA8C;IAE9C,MAAM,QAAQ,GAAG,GAAG,KAAK,kBAAkB,CAAC;IAC5C,OAAO;QACL,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,OAAO;QACb,GAAG;QACH,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB;QACnD,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,uBAAuB;QAC7D,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAC9C,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE;QAC1D,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,MAAM;KAClB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,oBAAoB,GAOtB;IACF,cAAc,EAAE;QACd;YACE,kDAAkD;YAClD,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,aAAa;YACjB,SAAS,EAAE,8BAA8B,CAAC,oBAAoB,CAAC;SAChE;QACD;YACE,oDAAoD;YACpD,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,eAAe;YACnB,SAAS,EAAE,8BAA8B,CAAC,sBAAsB,CAAC;SAClE;QACD;YACE,mDAAmD;YACnD,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,cAAc;YAClB,SAAS,EAAE,8BAA8B,CAAC,qBAAqB,CAAC;SACjE;KACF;IACD,mBAAmB,EAAE;QACnB,2EAA2E;QAC3E,EAAE,IAAI,EAAE,2BAA2B,EAAE,EAAE,EAAE,yBAAyB,EAAE;QACpE,EAAE,IAAI,EAAE,2BAA2B,EAAE,EAAE,EAAE,0BAA0B,EAAE,SAAS,EAAE,YAAY,EAAE;QAC9F;YACE,IAAI,EAAE,2BAA2B;YACjC,EAAE,EAAE,2BAA2B;YAC/B,SAAS,EAAE,aAAa;SACzB;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,EAAE,EAAE,oBAAoB;YACxB,SAAS,EAAE;gBACT,GAAG,oBAAoB;gBACvB,MAAM,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE;aACxC;SACF;KACF;IACD,UAAU,EAAE;QACV,2EAA2E;QAC3E,iEAAiE;QACjE,EAAE,IAAI,EAAE,2BAA2B,EAAE,EAAE,EAAE,yBAAyB,EAAE;QACpE;YACE,sEAAsE;YACtE,oDAAoD;YACpD,IAAI,EAAE,gBAAgB;YACtB,EAAE,EAAE,qBAAqB;YACzB,SAAS,EAAE,8BAA8B,CAAC,qBAAqB,CAAC;SACjE;QACD;YACE,IAAI,EAAE,sBAAsB;YAC5B,EAAE,EAAE,oBAAoB;YACxB,SAAS,EAAE,oBAAoB;SAChC;QACD;YACE,IAAI,EAAE,gBAAgB;YACtB,EAAE,EAAE,oBAAoB;YACxB,SAAS,EAAE,8BAA8B,CAAC,oBAAoB,CAAC;SAChE;QACD;YACE,IAAI,EAAE,gBAAgB;YACtB,EAAE,EAAE,sBAAsB;YAC1B,SAAS,EAAE,8BAA8B,CAAC,sBAAsB,CAAC;SAClE;KACF;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,OAAe;IACnE,IAAI,QAAQ,KAAK,mBAAmB,IAAI,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAChF,OAAO,+BAA+B,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACpF,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAe,EAAE,KAAK,CAAC,IAAW,CAA2B,CAAC;IACvF,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM;QACpC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE;QAClD,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACnB,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAChF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,+BAA+B,CAAC,OAAe;IACtD,MAAM,QAAQ,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACtD,OAAO;QACL,GAAG,QAAQ;QACX,QAAQ,EAAE,mBAAmB;QAC7B,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,GAAG,uBAAuB,KAAK;KACzC,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAAC,OAAe;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,QAAQ,GACX,QAAQ,CAAC,QAAe,EAAE,IAAW,CAA4B;QAClE,0BAA0B,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;IAC1D,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,8BAA8B,CAAC,OAAO,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,SAAS,6BAA6B,CAAC,KAAiB;IACtD,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,IAAI,KAAK,CAAC,GAAG,KAAK,kBAAkB,EAAE,CAAC;QAC3E,OAAO,GAAG,qBAAqB,EAAE,KAAK,CAAC;IACzC,CAAC;IACD,OAAO,qBAAqB,EAAE,CAAC;AACjC,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,IAAI,QAAQ,KAAK,cAAc,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK;YAAE,iCAAiC,CAAC,QAAQ,CAAC,CAAC;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,YAAY,CAAC,QAA8C,CAAC,CAAC;AACtE,CAAC"}
1
+ {"version":3,"file":"duet-gateway.js","sourceRoot":"","sources":["../../../src/model-resolution/duet-gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { MutableModels } from "@earendil-works/pi-ai";
2
+ import type { StreamFn } from "@earendil-works/pi-agent-core";
3
+ /**
4
+ * The model registry every dispatch goes through: pi-ai's built-in providers,
5
+ * the gateway routes, and connected providers with Duet's own credential path
6
+ * declared.
7
+ *
8
+ * The agent loop is handed a `StreamFn` rather than resolving a transport
9
+ * itself, so this is the one place that decides who can serve a model.
10
+ *
11
+ * Rebuilt per call rather than cached. The expensive part — rebasing two
12
+ * hundred catalog entries per route — is memoized behind `gatewayProvider`,
13
+ * which leaves assembly at ~0.05ms; a cache here would save that and buy a
14
+ * staleness bug, because its key would have to track every origin and
15
+ * credential the registry closes over.
16
+ */
17
+ export declare function duetModels(): MutableModels;
18
+ /**
19
+ * Dispatch for the agent loop. The model object carries its own provider,
20
+ * baseUrl and tier headers by the time it arrives here, so this only has to
21
+ * pick the transport the model declares.
22
+ */
23
+ export declare const duetStreamFn: StreamFn;
24
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../../src/model-resolution/models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAK9D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,IAAI,aAAa,CAQ1C;AA8BD;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,QACyB,CAAC"}