@providerkit/core 0.23.0 → 0.25.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/README.md +43 -7
- package/dist/auth/device.d.ts +3 -2
- package/dist/auth/device.d.ts.map +1 -1
- package/dist/auth/device.js +5 -2
- package/dist/auth/device.js.map +1 -1
- package/dist/auth/flows.d.ts +5 -1
- package/dist/auth/flows.d.ts.map +1 -1
- package/dist/auth/flows.js +237 -0
- package/dist/auth/flows.js.map +1 -1
- package/dist/auth/oauth.d.ts +4 -2
- package/dist/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js +4 -3
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/with-token.d.ts +4 -1
- package/dist/auth/with-token.d.ts.map +1 -1
- package/dist/auth/with-token.js +9 -4
- package/dist/auth/with-token.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -1
- package/dist/gate.d.ts +29 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +96 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/key-pool.d.ts +11 -0
- package/dist/key-pool.d.ts.map +1 -1
- package/dist/key-pool.js +14 -0
- package/dist/key-pool.js.map +1 -1
- package/dist/presets.d.ts +53 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +100 -0
- package/dist/presets.js.map +1 -1
- package/dist/providers/anthropic.d.ts +7 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +3 -1
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/factory.d.ts +36 -0
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +208 -22
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/gemini.d.ts +17 -5
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +241 -119
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts +7 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +7 -4
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/responses.d.ts +11 -0
- package/dist/providers/responses.d.ts.map +1 -1
- package/dist/providers/responses.js +3 -1
- package/dist/providers/responses.js.map +1 -1
- package/dist/registry.d.ts +105 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +291 -0
- package/dist/registry.js.map +1 -0
- package/dist/schema.d.ts +27 -9
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +197 -62
- package/dist/schema.js.map +1 -1
- package/dist/tools.d.ts +8 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/dist/turn.d.ts +55 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +125 -0
- package/dist/turn.js.map +1 -0
- package/dist/types.d.ts +8 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -45
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/device.ts +8 -4
- package/src/auth/flows.ts +288 -1
- package/src/auth/oauth.ts +8 -3
- package/src/auth/with-token.ts +9 -4
- package/src/errors.ts +3 -0
- package/src/gate.ts +152 -0
- package/src/index.ts +3 -0
- package/src/key-pool.ts +15 -0
- package/src/presets.ts +100 -0
- package/src/providers/anthropic.ts +10 -1
- package/src/providers/factory.ts +288 -34
- package/src/providers/gemini.ts +273 -124
- package/src/providers/openai.ts +15 -4
- package/src/providers/responses.ts +14 -1
- package/src/registry.ts +420 -0
- package/src/schema.ts +200 -72
- package/src/tools.ts +10 -2
- package/src/turn.ts +166 -0
- package/src/types.ts +8 -62
package/src/providers/factory.ts
CHANGED
|
@@ -23,7 +23,15 @@ import {
|
|
|
23
23
|
type ProviderPreset,
|
|
24
24
|
type ProviderPresetId,
|
|
25
25
|
} from "../presets.ts";
|
|
26
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
ChatMessage,
|
|
28
|
+
Effort,
|
|
29
|
+
Provider,
|
|
30
|
+
ProviderChunk,
|
|
31
|
+
ServiceTier,
|
|
32
|
+
StreamOptions,
|
|
33
|
+
ToolDefinition,
|
|
34
|
+
} from "../types.ts";
|
|
27
35
|
import { withConfiguredFallbacks, type ProviderFallbackConfig } from "../fallback.ts";
|
|
28
36
|
|
|
29
37
|
export interface PresetProviderConfig extends ProviderFallbackConfig {
|
|
@@ -46,6 +54,19 @@ export interface PresetProviderConfig extends ProviderFallbackConfig {
|
|
|
46
54
|
/** Per-request headers (request ids, `ChatGPT-Account-Id`) on top of the
|
|
47
55
|
* preset's static protocol headers. */
|
|
48
56
|
headers?: Record<string, string>;
|
|
57
|
+
/**
|
|
58
|
+
* The session id for a call without `StreamOptions.sessionId`, on a preset
|
|
59
|
+
* that sends one (`opencode-go`, `opencode-zen`, `chatgpt`). Absent, the provider mints its
|
|
60
|
+
* own when it is built. Pass one when you build the provider per attempt
|
|
61
|
+
* (`withKeyPool`, `withToken`): each build would mint a new id, and a gateway
|
|
62
|
+
* that pins a session to one upstream host then treats every call as a new
|
|
63
|
+
* session, re-routes it and misses its prompt cache. Every model in a chain
|
|
64
|
+
* sends the same one. A preset without a session header has nowhere to put
|
|
65
|
+
* it and ignores it.
|
|
66
|
+
*/
|
|
67
|
+
instanceSessionId?: string;
|
|
68
|
+
/** Google Antigravity (Cloud Code Assist) project id for the request envelope. */
|
|
69
|
+
projectId?: string;
|
|
49
70
|
/**
|
|
50
71
|
* App attribution for OpenRouter's rankings. The site URL rides as
|
|
51
72
|
* `HTTP-Referer`, the app name as `X-Title`. Public, not secret. Sent on
|
|
@@ -69,15 +90,15 @@ export interface PresetProviderConfig extends ProviderFallbackConfig {
|
|
|
69
90
|
fetchImpl?: typeof fetch;
|
|
70
91
|
}
|
|
71
92
|
|
|
93
|
+
/** What one adapter for one model takes: a `PresetProviderConfig` without the
|
|
94
|
+
* chain (`models`) and without the layers that go around an adapter. */
|
|
95
|
+
export type PresetAdapterConfig = Omit<
|
|
96
|
+
PresetProviderConfig,
|
|
97
|
+
"models" | keyof ProviderFallbackConfig
|
|
98
|
+
>;
|
|
99
|
+
|
|
72
100
|
export function createPresetProvider(id: ProviderPresetId, config: PresetProviderConfig): Provider {
|
|
73
|
-
|
|
74
|
-
// string — validate rather than index into undefined.
|
|
75
|
-
if (!(id in PROVIDER_PRESETS)) {
|
|
76
|
-
throw new Error(
|
|
77
|
-
`[providerkit] Unknown provider preset "${id}". Valid ids: ${PRESET_IDS.join(", ")}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
const preset: ProviderPreset = PROVIDER_PRESETS[id];
|
|
101
|
+
const preset = presetFor(id);
|
|
81
102
|
const chain = config.models?.length ? config.models : config.model ? undefined : preset.rotation;
|
|
82
103
|
if (chain && chain.length > 1) {
|
|
83
104
|
const { models: _models, fallbacks = [], ...single } = config;
|
|
@@ -90,7 +111,27 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
90
111
|
],
|
|
91
112
|
});
|
|
92
113
|
}
|
|
93
|
-
const
|
|
114
|
+
const { models: _models, ...single } = config;
|
|
115
|
+
return withConfiguredFallbacks(
|
|
116
|
+
presetAdapter(id, { ...single, model: chain?.[0] ?? config.model }),
|
|
117
|
+
config,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The bare adapter for one preset and one model: the wire that serves the
|
|
123
|
+
* model, its auth style and the preset's quirks, and nothing around them. No
|
|
124
|
+
* fallbacks, no watchdog, no `models` chain. A registry builds one per
|
|
125
|
+
* (connection, model) and puts those layers around all of them once; calling
|
|
126
|
+
* `createPresetProvider` per model would nest a second pool and a second
|
|
127
|
+
* watchdog inside its own.
|
|
128
|
+
*
|
|
129
|
+
* The adapter's `id` is the preset id, because the OpenAI shape reads its
|
|
130
|
+
* effort dialect from it (`opencode-go`, `openrouter`, `deepseek`).
|
|
131
|
+
*/
|
|
132
|
+
export function presetAdapter(id: ProviderPresetId, config: PresetAdapterConfig): Provider {
|
|
133
|
+
const preset = presetFor(id);
|
|
134
|
+
const model = config.model ?? preset.defaultModel;
|
|
94
135
|
if (!model) {
|
|
95
136
|
throw new Error(`[providerkit] Preset "${id}" has no defaultModel. Pass a model.`);
|
|
96
137
|
}
|
|
@@ -101,24 +142,45 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
101
142
|
"Remove serviceTier.",
|
|
102
143
|
);
|
|
103
144
|
}
|
|
145
|
+
let provider = buildWireAdapter(id, preset, wire, model, config, config.fetchImpl);
|
|
146
|
+
if (id === "opencode-zen") {
|
|
147
|
+
provider = wrapOpenCodeZen(provider, preset, wire, config, model);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Here rather than in `createPresetProvider`: the wire is resolved here, and
|
|
151
|
+
// the bare adapter is exported, so it must refuse on its own a per-call model
|
|
152
|
+
// it would post to the wrong endpoint. That 404 reads as an unknown model,
|
|
153
|
+
// and a pool benches the model for an hour.
|
|
154
|
+
return preset.routes ? refuseOtherWires(provider, preset, wire) : provider;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildWireAdapter(
|
|
158
|
+
id: ProviderPresetId,
|
|
159
|
+
preset: ProviderPreset,
|
|
160
|
+
wire: Wire,
|
|
161
|
+
model: string,
|
|
162
|
+
config: PresetAdapterConfig,
|
|
163
|
+
fetchImpl: typeof fetch | undefined,
|
|
164
|
+
): Provider {
|
|
104
165
|
const headers = { ...preset.headers, ...config.headers };
|
|
105
166
|
const baseUrl = config.baseUrl ?? wire.baseUrl;
|
|
106
167
|
const maxTokens = config.maxTokens ?? preset.maxTokens;
|
|
168
|
+
// Every adapter builds its own pool and watchdog from these. The type leaves
|
|
169
|
+
// them out, but a wider object (a whole `PresetProviderConfig` in a variable)
|
|
170
|
+
// can still carry them, and the adapter would nest a second pool and a
|
|
171
|
+
// second watchdog inside the caller's. Cleared, it stays bare.
|
|
172
|
+
const baseConfig = {
|
|
173
|
+
...config,
|
|
174
|
+
fallbacks: undefined,
|
|
175
|
+
fallbackOptions: undefined,
|
|
176
|
+
watchdog: undefined,
|
|
177
|
+
};
|
|
107
178
|
|
|
108
|
-
const {
|
|
109
|
-
fallbacks: _fallbacks,
|
|
110
|
-
fallbackOptions: _fallbackOptions,
|
|
111
|
-
watchdog: _watchdog,
|
|
112
|
-
models: _models,
|
|
113
|
-
...baseConfig
|
|
114
|
-
} = config;
|
|
115
|
-
|
|
116
|
-
let provider: Provider;
|
|
117
179
|
switch (wire.shape) {
|
|
118
180
|
case "anthropic":
|
|
119
181
|
// `key` rides Anthropic's native x-api-key; coding plans and OAuth
|
|
120
182
|
// tokens read Bearer (measured across the family's coding endpoints).
|
|
121
|
-
|
|
183
|
+
return createAnthropicProvider({
|
|
122
184
|
...baseConfig,
|
|
123
185
|
apiKey: config.apiKey,
|
|
124
186
|
model,
|
|
@@ -132,11 +194,10 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
132
194
|
// The endpoint's thinking dialect rides the table (zai: silence means
|
|
133
195
|
// the model default, which is ON — "none" must be said out loud).
|
|
134
196
|
...(preset.explicitNone ? { explicitNone: true } : {}),
|
|
135
|
-
fetchImpl
|
|
197
|
+
fetchImpl,
|
|
136
198
|
});
|
|
137
|
-
break;
|
|
138
199
|
case "openai":
|
|
139
|
-
|
|
200
|
+
return createOpenAIProvider({
|
|
140
201
|
...baseConfig,
|
|
141
202
|
id,
|
|
142
203
|
apiKey: config.apiKey,
|
|
@@ -150,11 +211,10 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
150
211
|
...(preset.sessionHeader ? { sessionHeader: preset.sessionHeader } : {}),
|
|
151
212
|
...(preset.initiatorHeader ? { initiatorHeader: true } : {}),
|
|
152
213
|
...(preset.noSampling ? { noSampling: true } : {}),
|
|
153
|
-
fetchImpl
|
|
214
|
+
fetchImpl,
|
|
154
215
|
});
|
|
155
|
-
break;
|
|
156
216
|
case "responses":
|
|
157
|
-
|
|
217
|
+
return createResponsesProvider({
|
|
158
218
|
...baseConfig,
|
|
159
219
|
apiKey: config.apiKey,
|
|
160
220
|
model,
|
|
@@ -167,27 +227,221 @@ export function createPresetProvider(id: ProviderPresetId, config: PresetProvide
|
|
|
167
227
|
...(preset.sessionHeader ? { sessionHeader: preset.sessionHeader } : {}),
|
|
168
228
|
...(preset.imageLimits ? { imageLimits: preset.imageLimits } : {}),
|
|
169
229
|
...(preset.replayReasoning ? { replayReasoning: true } : {}),
|
|
170
|
-
fetchImpl
|
|
230
|
+
fetchImpl,
|
|
171
231
|
});
|
|
172
|
-
break;
|
|
173
232
|
case "gemini":
|
|
174
|
-
|
|
233
|
+
return createGeminiProvider({
|
|
175
234
|
...baseConfig,
|
|
176
235
|
apiKey: config.apiKey,
|
|
177
236
|
model,
|
|
178
237
|
effort: config.effort,
|
|
238
|
+
maxTokens,
|
|
179
239
|
baseUrl,
|
|
240
|
+
...(wire.path ? { path: wire.path } : {}),
|
|
241
|
+
bearer: wire.auth !== "key",
|
|
242
|
+
...(config.projectId ? { projectId: config.projectId } : {}),
|
|
243
|
+
...(preset.sessionHeader ? { sessionHeader: preset.sessionHeader } : {}),
|
|
180
244
|
id,
|
|
181
245
|
headers,
|
|
182
|
-
fetchImpl
|
|
246
|
+
fetchImpl,
|
|
183
247
|
});
|
|
184
|
-
break;
|
|
185
248
|
}
|
|
249
|
+
}
|
|
186
250
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
251
|
+
const OPENCODE_CLI_USER_AGENT = "opencode/latest/2.0.32/cli";
|
|
252
|
+
const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
253
|
+
|
|
254
|
+
/** Whether an OpenCode Zen model id belongs to the free shelf (`big-pickle` or `*-free`). */
|
|
255
|
+
export function isZenFreeModel(model: string): boolean {
|
|
256
|
+
return model === "big-pickle" || model.endsWith("-free");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Deterministically turns any conversation session id into OpenCode's
|
|
261
|
+
* `ses_<12 hex><14 base62>` identifier shape (`packages/schema/src/session-id.ts`),
|
|
262
|
+
* matching cc-proxy's `forge_opencode_session_id`.
|
|
263
|
+
*/
|
|
264
|
+
export async function forgeOpenCodeSessionId(sessionId?: string): Promise<string> {
|
|
265
|
+
if (sessionId && /^ses_[0-9a-f]{12}[0-9A-Za-z]{14}$/.test(sessionId)) {
|
|
266
|
+
return sessionId;
|
|
267
|
+
}
|
|
268
|
+
const seed = sessionId && sessionId.length > 0 ? sessionId : crypto.randomUUID();
|
|
269
|
+
const digest = new Uint8Array(
|
|
270
|
+
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed)),
|
|
190
271
|
);
|
|
272
|
+
let hexPart = "";
|
|
273
|
+
for (let i = 0; i < 6; i++) {
|
|
274
|
+
hexPart += digest[i]!.toString(16).padStart(2, "0");
|
|
275
|
+
}
|
|
276
|
+
let tail = "";
|
|
277
|
+
for (let i = 6; i < 20; i++) {
|
|
278
|
+
tail += BASE62[digest[i]! % 62];
|
|
279
|
+
}
|
|
280
|
+
return `ses_${hexPart}${tail}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* OpenCode Zen's free tier answers only a streamed call whose tools include
|
|
285
|
+
* OpenCode's own `read` (`cc-proxy/src/providers/opencode/mod.rs`). A call with
|
|
286
|
+
* `Read` sends it as `read` and gets `Read` back; a call with neither gets a
|
|
287
|
+
* decoy `read` the model is told not to call (and `toolChoice: "none"` when the
|
|
288
|
+
* caller passed no tools).
|
|
289
|
+
*/
|
|
290
|
+
function offerOpenCodeRead(
|
|
291
|
+
messages: ChatMessage[],
|
|
292
|
+
tools: ToolDefinition[],
|
|
293
|
+
opts: StreamOptions,
|
|
294
|
+
): {
|
|
295
|
+
messages: ChatMessage[];
|
|
296
|
+
tools: ToolDefinition[];
|
|
297
|
+
opts: StreamOptions;
|
|
298
|
+
renamedRead: boolean;
|
|
299
|
+
} {
|
|
300
|
+
if (tools.some((t) => t.name === "read")) {
|
|
301
|
+
return { messages, tools, opts, renamedRead: false };
|
|
302
|
+
}
|
|
303
|
+
const hasClaudeRead = tools.some((t) => t.name === "Read");
|
|
304
|
+
if (!hasClaudeRead) {
|
|
305
|
+
const decoy: ToolDefinition = {
|
|
306
|
+
name: "read",
|
|
307
|
+
description: "Not available in this conversation. Never call it.",
|
|
308
|
+
inputSchema: { type: "object", properties: {} },
|
|
309
|
+
};
|
|
310
|
+
return {
|
|
311
|
+
messages,
|
|
312
|
+
tools: [...tools, decoy],
|
|
313
|
+
opts: tools.length === 0 ? { ...opts, toolChoice: "none" } : opts,
|
|
314
|
+
renamedRead: false,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const nextTools = tools.map((t) => (t.name === "Read" ? { ...t, name: "read" } : t));
|
|
318
|
+
const nextMessages: ChatMessage[] = messages.map((msg) => {
|
|
319
|
+
if (msg.role === "assistant" && msg.toolCalls?.some((c) => c.name === "Read")) {
|
|
320
|
+
return {
|
|
321
|
+
...msg,
|
|
322
|
+
toolCalls: msg.toolCalls.map((c) => (c.name === "Read" ? { ...c, name: "read" } : c)),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (msg.role === "tool" && msg.name === "Read") {
|
|
326
|
+
return { ...msg, name: "read" };
|
|
327
|
+
}
|
|
328
|
+
return msg;
|
|
329
|
+
});
|
|
330
|
+
const nextOpts =
|
|
331
|
+
opts.toolChoice && typeof opts.toolChoice === "object" && opts.toolChoice.name === "Read"
|
|
332
|
+
? { ...opts, toolChoice: { type: "tool" as const, name: "read" } }
|
|
333
|
+
: opts;
|
|
334
|
+
return { messages: nextMessages, tools: nextTools, opts: nextOpts, renamedRead: true };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function wrapOpenCodeZen(
|
|
338
|
+
baseProvider: Provider,
|
|
339
|
+
preset: ProviderPreset,
|
|
340
|
+
wire: Wire,
|
|
341
|
+
config: PresetAdapterConfig,
|
|
342
|
+
defaultModel: string,
|
|
343
|
+
): Provider {
|
|
344
|
+
const instanceSessionId = config.instanceSessionId ?? crypto.randomUUID();
|
|
345
|
+
const baseFetch = config.fetchImpl ?? fetch;
|
|
346
|
+
const callerUa = config.headers?.["user-agent"] ?? config.headers?.["User-Agent"];
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
...baseProvider,
|
|
350
|
+
async *createStream(messages, tools, opts = {}) {
|
|
351
|
+
const activeModel = opts.model ?? defaultModel;
|
|
352
|
+
const free = isZenFreeModel(activeModel);
|
|
353
|
+
const ses = await forgeOpenCodeSessionId(opts.sessionId ?? instanceSessionId);
|
|
354
|
+
const prepared = free
|
|
355
|
+
? offerOpenCodeRead(messages, tools, opts)
|
|
356
|
+
: { messages, tools, opts, renamedRead: false };
|
|
357
|
+
|
|
358
|
+
const runAttempt = async function* (anonymous: boolean): AsyncIterable<ProviderChunk> {
|
|
359
|
+
const fetchImpl: typeof fetch = (input, init) => {
|
|
360
|
+
const merged = new Headers(init?.headers);
|
|
361
|
+
if (!callerUa) merged.set("user-agent", OPENCODE_CLI_USER_AGENT);
|
|
362
|
+
merged.set("x-opencode-session", ses);
|
|
363
|
+
merged.set("x-opencode-session-id", ses);
|
|
364
|
+
merged.set("x-session-affinity", ses);
|
|
365
|
+
merged.set("x-session-id", ses);
|
|
366
|
+
merged.set("x-opencode-project", "global");
|
|
367
|
+
merged.set("x-opencode-client", "cli");
|
|
368
|
+
if (anonymous) {
|
|
369
|
+
merged.delete("authorization");
|
|
370
|
+
merged.delete("x-api-key");
|
|
371
|
+
merged.delete("x-goog-api-key");
|
|
372
|
+
}
|
|
373
|
+
const headerRecord: Record<string, string> = {};
|
|
374
|
+
merged.forEach((v, k) => {
|
|
375
|
+
headerRecord[k] = v;
|
|
376
|
+
});
|
|
377
|
+
return baseFetch(input, { ...init, headers: headerRecord });
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const bound = buildWireAdapter(
|
|
381
|
+
"opencode-zen",
|
|
382
|
+
preset,
|
|
383
|
+
wire,
|
|
384
|
+
defaultModel,
|
|
385
|
+
config,
|
|
386
|
+
fetchImpl,
|
|
387
|
+
);
|
|
388
|
+
for await (const chunk of bound.createStream(prepared.messages, prepared.tools, {
|
|
389
|
+
...prepared.opts,
|
|
390
|
+
sessionId: ses,
|
|
391
|
+
})) {
|
|
392
|
+
if (
|
|
393
|
+
prepared.renamedRead &&
|
|
394
|
+
chunk.type === "delta" &&
|
|
395
|
+
chunk.toolCalls?.some((c) => c.name === "read")
|
|
396
|
+
) {
|
|
397
|
+
yield {
|
|
398
|
+
...chunk,
|
|
399
|
+
toolCalls: chunk.toolCalls.map((c) =>
|
|
400
|
+
c.name === "read" ? { ...c, name: "Read" } : c,
|
|
401
|
+
),
|
|
402
|
+
};
|
|
403
|
+
} else {
|
|
404
|
+
yield chunk;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const startAnonymous = free && !config.apiKey;
|
|
410
|
+
let emitted = false;
|
|
411
|
+
try {
|
|
412
|
+
for await (const chunk of runAttempt(startAnonymous)) {
|
|
413
|
+
emitted = true;
|
|
414
|
+
yield chunk;
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
} catch (err) {
|
|
418
|
+
// Free models accept a valid key (using the account's own free quota),
|
|
419
|
+
// and refuse a stale key with 401. Retry anonymously when that happens.
|
|
420
|
+
if (
|
|
421
|
+
free &&
|
|
422
|
+
!startAnonymous &&
|
|
423
|
+
!emitted &&
|
|
424
|
+
err instanceof ProviderError &&
|
|
425
|
+
err.status === 401
|
|
426
|
+
) {
|
|
427
|
+
yield* runAttempt(true);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The table is closed (`satisfies`), but a caller may hold an id as a plain
|
|
437
|
+
* string. Validate rather than index into undefined. */
|
|
438
|
+
function presetFor(id: ProviderPresetId): ProviderPreset {
|
|
439
|
+
if (!(id in PROVIDER_PRESETS)) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
`[providerkit] Unknown provider preset "${id}". Valid ids: ${PRESET_IDS.join(", ")}`,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return PROVIDER_PRESETS[id];
|
|
191
445
|
}
|
|
192
446
|
|
|
193
447
|
/** The endpoint a model is served from: its route when the preset has one for
|