openllmprovider 0.1.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 (91) hide show
  1. package/README.md +192 -0
  2. package/dist/auth/index.cjs +6 -0
  3. package/dist/auth/index.d.cts +3 -0
  4. package/dist/auth/index.d.mts +3 -0
  5. package/dist/auth/index.mjs +3 -0
  6. package/dist/auto-C2hXJY13.d.cts +33 -0
  7. package/dist/auto-C2hXJY13.d.cts.map +1 -0
  8. package/dist/auto-CBqNYBXs.mjs +48 -0
  9. package/dist/auto-CBqNYBXs.mjs.map +1 -0
  10. package/dist/auto-CInerwvs.d.mts +33 -0
  11. package/dist/auto-CInerwvs.d.mts.map +1 -0
  12. package/dist/auto-D77wgMqO.cjs +59 -0
  13. package/dist/auto-D77wgMqO.cjs.map +1 -0
  14. package/dist/file-DB-rxfzi.mjs +77 -0
  15. package/dist/file-DB-rxfzi.mjs.map +1 -0
  16. package/dist/file-DZ7FGcSW.cjs +73 -0
  17. package/dist/file-DZ7FGcSW.cjs.map +1 -0
  18. package/dist/index.cjs +1909 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.cts +1239 -0
  21. package/dist/index.d.cts.map +1 -0
  22. package/dist/index.d.mts +1241 -0
  23. package/dist/index.d.mts.map +1 -0
  24. package/dist/index.mjs +1891 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/logger-BsHpI_fH.mjs +11 -0
  27. package/dist/logger-BsHpI_fH.mjs.map +1 -0
  28. package/dist/logger-jRimlMFR.cjs +69 -0
  29. package/dist/logger-jRimlMFR.cjs.map +1 -0
  30. package/dist/plugin/index.cjs +29 -0
  31. package/dist/plugin/index.cjs.map +1 -0
  32. package/dist/plugin/index.d.cts +10 -0
  33. package/dist/plugin/index.d.cts.map +1 -0
  34. package/dist/plugin/index.d.mts +10 -0
  35. package/dist/plugin/index.d.mts.map +1 -0
  36. package/dist/plugin/index.mjs +25 -0
  37. package/dist/plugin/index.mjs.map +1 -0
  38. package/dist/plugin-BkeUu5LW.d.mts +46 -0
  39. package/dist/plugin-BkeUu5LW.d.mts.map +1 -0
  40. package/dist/plugin-wK7RmJhZ.d.cts +46 -0
  41. package/dist/plugin-wK7RmJhZ.d.cts.map +1 -0
  42. package/dist/resolver-BA7LWSJO.mjs +645 -0
  43. package/dist/resolver-BA7LWSJO.mjs.map +1 -0
  44. package/dist/resolver-BMTvzTt9.cjs +662 -0
  45. package/dist/resolver-BMTvzTt9.cjs.map +1 -0
  46. package/dist/resolver-MgJryMWG.d.cts +75 -0
  47. package/dist/resolver-MgJryMWG.d.cts.map +1 -0
  48. package/dist/resolver-_gfXzr_S.d.mts +76 -0
  49. package/dist/resolver-_gfXzr_S.d.mts.map +1 -0
  50. package/dist/storage/index.cjs +7 -0
  51. package/dist/storage/index.d.cts +12 -0
  52. package/dist/storage/index.d.cts.map +1 -0
  53. package/dist/storage/index.d.mts +12 -0
  54. package/dist/storage/index.d.mts.map +1 -0
  55. package/dist/storage/index.mjs +4 -0
  56. package/package.json +137 -0
  57. package/src/auth/.gitkeep +0 -0
  58. package/src/auth/index.ts +10 -0
  59. package/src/auth/resolver.ts +46 -0
  60. package/src/auth/scanners.ts +462 -0
  61. package/src/auth/store.ts +357 -0
  62. package/src/catalog/.gitkeep +0 -0
  63. package/src/catalog/catalog.ts +302 -0
  64. package/src/catalog/index.ts +17 -0
  65. package/src/catalog/mapper.ts +129 -0
  66. package/src/catalog/merger.ts +99 -0
  67. package/src/index.ts +37 -0
  68. package/src/logger.ts +7 -0
  69. package/src/plugin/.gitkeep +0 -0
  70. package/src/plugin/anthropic.test.ts +505 -0
  71. package/src/plugin/anthropic.ts +324 -0
  72. package/src/plugin/codex.ts +656 -0
  73. package/src/plugin/copilot.ts +161 -0
  74. package/src/plugin/google.ts +454 -0
  75. package/src/plugin/index.ts +30 -0
  76. package/src/provider/.gitkeep +0 -0
  77. package/src/provider/bundled.ts +59 -0
  78. package/src/provider/index.ts +249 -0
  79. package/src/provider/state.ts +163 -0
  80. package/src/storage/.gitkeep +0 -0
  81. package/src/storage/auto.ts +32 -0
  82. package/src/storage/file.ts +84 -0
  83. package/src/storage/index.ts +10 -0
  84. package/src/storage/memory.ts +23 -0
  85. package/src/types/.gitkeep +0 -0
  86. package/src/types/auth.ts +18 -0
  87. package/src/types/errors.ts +87 -0
  88. package/src/types/index.ts +26 -0
  89. package/src/types/model.ts +88 -0
  90. package/src/types/plugin.ts +49 -0
  91. package/src/types/provider.ts +48 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1891 @@
1
+ import { t as FileStorage } from "./file-DB-rxfzi.mjs";
2
+ import { t as createLogger } from "./logger-BsHpI_fH.mjs";
3
+ import { n as MemoryStorage, t as createDefaultStorage } from "./auto-CBqNYBXs.mjs";
4
+ import { n as createAuthStore, r as DEFAULT_SCANNERS } from "./resolver-BA7LWSJO.mjs";
5
+ import { getPluginForProvider, getPlugins, loadPluginOptions, registerPlugin } from "./plugin/index.mjs";
6
+ import { z } from "zod";
7
+ import { spawn } from "node:child_process";
8
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
9
+ import { stdin, stdout } from "node:process";
10
+ import { createInterface } from "node:readline/promises";
11
+ import { createServer } from "node:http";
12
+
13
+ //#region src/catalog/mapper.ts
14
+ const ModelsDevModelSchema = z.object({
15
+ id: z.string().optional(),
16
+ name: z.string().optional(),
17
+ family: z.string().optional(),
18
+ reasoning: z.boolean().optional(),
19
+ tool_call: z.boolean().optional(),
20
+ structured_output: z.boolean().optional(),
21
+ temperature: z.boolean().optional(),
22
+ attachment: z.boolean().optional(),
23
+ streaming: z.boolean().optional(),
24
+ system_message: z.boolean().optional(),
25
+ modalities: z.object({
26
+ input: z.array(z.string()),
27
+ output: z.array(z.string())
28
+ }).passthrough(),
29
+ limit: z.object({
30
+ context: z.number(),
31
+ output: z.number(),
32
+ input_images: z.number().optional()
33
+ }).passthrough(),
34
+ cost: z.object({
35
+ input: z.number(),
36
+ output: z.number(),
37
+ cache_read: z.number().optional(),
38
+ cache_write: z.number().optional()
39
+ }).passthrough().optional(),
40
+ status: z.enum([
41
+ "stable",
42
+ "beta",
43
+ "deprecated"
44
+ ]).optional(),
45
+ knowledge: z.string().optional()
46
+ }).passthrough();
47
+ const ModelsDevProviderSchema = z.object({
48
+ id: z.string().optional(),
49
+ name: z.string().optional(),
50
+ env: z.array(z.string()).optional(),
51
+ api: z.string().optional(),
52
+ npm: z.string().optional(),
53
+ doc: z.string().optional(),
54
+ models: z.record(z.string(), z.unknown()).optional()
55
+ }).passthrough();
56
+ function mapModelsDevToModelDefinition(modelId, raw, provenance) {
57
+ const parsed = ModelsDevModelSchema.safeParse(raw);
58
+ if (!parsed.success) return null;
59
+ const { id: _id, knowledge, ...rest } = parsed.data;
60
+ const result = {
61
+ ...rest,
62
+ modelId
63
+ };
64
+ if (knowledge !== void 0) result.knowledgeCutoff = knowledge;
65
+ if (provenance !== void 0) result.provenance = provenance;
66
+ return result;
67
+ }
68
+ function mapModelsDevProvider(providerId, raw, provenance) {
69
+ const parsed = ModelsDevProviderSchema.safeParse(raw);
70
+ const name = parsed.success ? parsed.data.name ?? providerId : providerId;
71
+ const rawModels = parsed.success ? parsed.data.models ?? {} : {};
72
+ const models = [];
73
+ for (const [modelId, modelRaw] of Object.entries(rawModels)) {
74
+ const mapped = mapModelsDevToModelDefinition(modelId, modelRaw, provenance);
75
+ if (mapped) models.push(mapped);
76
+ }
77
+ return {
78
+ provider: {
79
+ id: providerId,
80
+ name,
81
+ ...parsed.success && parsed.data.env !== void 0 ? { env: parsed.data.env } : {},
82
+ ...parsed.success && parsed.data.api !== void 0 ? { api: parsed.data.api } : {},
83
+ ...parsed.success && parsed.data.doc !== void 0 ? { doc: parsed.data.doc } : {}
84
+ },
85
+ models
86
+ };
87
+ }
88
+ function mapModelsDevProviderMetadata(providerId, raw) {
89
+ const parsed = ModelsDevProviderSchema.safeParse(raw);
90
+ return {
91
+ id: providerId,
92
+ name: parsed.success ? parsed.data.name ?? providerId : providerId,
93
+ ...parsed.success && parsed.data.env !== void 0 ? { env: parsed.data.env } : {},
94
+ ...parsed.success && parsed.data.api !== void 0 ? { api: parsed.data.api } : {},
95
+ ...parsed.success && parsed.data.doc !== void 0 ? { doc: parsed.data.doc } : {}
96
+ };
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/catalog/merger.ts
101
+ const FIELD_LEVEL_MERGE_KEYS = new Set(["limit", "cost"]);
102
+ const REPLACE_KEYS = new Set(["modalities"]);
103
+ function mergeNestedObject(base, overlay) {
104
+ if (!overlay) return base ? { ...base } : void 0;
105
+ if (!base) return { ...overlay };
106
+ const result = { ...base };
107
+ for (const key of Object.keys(overlay)) if (overlay[key] !== void 0) result[key] = overlay[key];
108
+ return result;
109
+ }
110
+ function mergeModelDefinitions(base, overlay) {
111
+ const result = { ...base };
112
+ for (const key of Object.keys(overlay)) {
113
+ if (key === "modelId") continue;
114
+ const overlayValue = overlay[key];
115
+ if (overlayValue === void 0) continue;
116
+ if (FIELD_LEVEL_MERGE_KEYS.has(key)) {
117
+ const merged = mergeNestedObject(base[key], overlayValue);
118
+ if (merged !== void 0) result[key] = merged;
119
+ continue;
120
+ }
121
+ if (REPLACE_KEYS.has(key)) {
122
+ result[key] = overlayValue;
123
+ continue;
124
+ }
125
+ result[key] = overlayValue;
126
+ }
127
+ return result;
128
+ }
129
+ function mergeCatalogData(snapshot, remote, overrides) {
130
+ const allModelIds = new Set([...snapshot.keys(), ...remote.keys()]);
131
+ const result = /* @__PURE__ */ new Map();
132
+ for (const modelId of allModelIds) {
133
+ const snap = snapshot.get(modelId);
134
+ const rem = remote.get(modelId);
135
+ const override = overrides.get(modelId);
136
+ const inSnapshot = snap !== void 0;
137
+ const inRemote = rem !== void 0;
138
+ const hasOverride = override !== void 0;
139
+ let merged;
140
+ if (inSnapshot && inRemote) merged = mergeModelDefinitions(snap, rem);
141
+ else if (inRemote) merged = { ...rem };
142
+ else if (snap) merged = { ...snap };
143
+ else continue;
144
+ if (hasOverride) merged = mergeModelDefinitions(merged, override);
145
+ if (hasOverride) merged.provenance = "user-override";
146
+ else if (inRemote) merged.provenance = "remote";
147
+ else merged.provenance = "snapshot";
148
+ if (inSnapshot && !inRemote) merged.status = "deprecated";
149
+ result.set(modelId, merged);
150
+ }
151
+ return result;
152
+ }
153
+
154
+ //#endregion
155
+ //#region src/catalog/catalog.ts
156
+ const DEFAULT_REMOTE_URL = "https://models.dev/api.json";
157
+ const DEFAULT_TIMEOUT_MS = 1e4;
158
+ const log$7 = createLogger("catalog");
159
+ var Catalog = class {
160
+ remoteOptions;
161
+ snapshotData;
162
+ remoteData = {};
163
+ providers = /* @__PURE__ */ new Map();
164
+ modelsByProvider = /* @__PURE__ */ new Map();
165
+ refreshInFlight = null;
166
+ extendedProviders = /* @__PURE__ */ new Map();
167
+ extendedModels = /* @__PURE__ */ new Map();
168
+ constructor(options = {}) {
169
+ this.snapshotData = options.snapshot ?? {};
170
+ this.remoteOptions = {
171
+ url: options.remote?.url ?? DEFAULT_REMOTE_URL,
172
+ timeoutMs: options.remote?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
173
+ fetch: options.remote?.fetch
174
+ };
175
+ this.applyProviderMetadata(this.snapshotData, {});
176
+ log$7("initialized with %d providers from snapshot", this.providers.size);
177
+ }
178
+ getProvider(id) {
179
+ return this.providers.get(id);
180
+ }
181
+ listProviders() {
182
+ return [...this.providers.values()];
183
+ }
184
+ getModel(providerId, modelId) {
185
+ this.ensureProviderModelsLoaded(providerId);
186
+ return this.modelsByProvider.get(providerId)?.get(modelId);
187
+ }
188
+ listModels(providerId) {
189
+ if (providerId !== void 0) {
190
+ this.ensureProviderModelsLoaded(providerId);
191
+ return [...this.modelsByProvider.get(providerId)?.values() ?? []];
192
+ }
193
+ const results = [];
194
+ for (const pid of this.providers.keys()) {
195
+ this.ensureProviderModelsLoaded(pid);
196
+ const models = this.modelsByProvider.get(pid);
197
+ if (models) results.push(...models.values());
198
+ }
199
+ return results;
200
+ }
201
+ enrichModel(providerId, modelId, partial) {
202
+ const catalogModel = this.getModel(providerId, modelId);
203
+ const partialWithId = {
204
+ ...partial,
205
+ modelId
206
+ };
207
+ if (!catalogModel) return partialWithId;
208
+ return mergeModelDefinitions(catalogModel, partialWithId);
209
+ }
210
+ extend(config) {
211
+ if (!config.providers) return;
212
+ for (const [providerId, providerConfig] of Object.entries(config.providers)) {
213
+ const existingProvider = this.providers.get(providerId);
214
+ const provider = {
215
+ id: providerId,
216
+ name: providerConfig.name,
217
+ ...providerConfig.env !== void 0 ? { env: providerConfig.env } : {},
218
+ ...existingProvider?.api !== void 0 ? { api: existingProvider.api } : {},
219
+ ...existingProvider?.doc !== void 0 ? { doc: existingProvider.doc } : {},
220
+ ...providerConfig.bundledProvider !== void 0 ? { bundledProvider: providerConfig.bundledProvider } : {},
221
+ ...providerConfig.baseURL !== void 0 ? { baseURL: providerConfig.baseURL } : {},
222
+ ...providerConfig.headers !== void 0 ? { headers: providerConfig.headers } : {},
223
+ ...providerConfig.options !== void 0 ? { options: providerConfig.options } : {}
224
+ };
225
+ this.extendedProviders.set(providerId, provider);
226
+ this.providers.set(providerId, provider);
227
+ if (providerConfig.models) {
228
+ const modelOverrides = this.extendedModels.get(providerId) ?? /* @__PURE__ */ new Map();
229
+ for (const [modelId, modelConfig] of Object.entries(providerConfig.models)) {
230
+ const partial = { modelId };
231
+ if (modelConfig.name !== void 0) partial.name = modelConfig.name;
232
+ if (modelConfig.modalities !== void 0) partial.modalities = modelConfig.modalities;
233
+ if (modelConfig.limit !== void 0) partial.limit = modelConfig.limit;
234
+ modelOverrides.set(modelId, partial);
235
+ }
236
+ this.extendedModels.set(providerId, modelOverrides);
237
+ }
238
+ this.modelsByProvider.delete(providerId);
239
+ }
240
+ }
241
+ refresh() {
242
+ if (this.refreshInFlight) return this.refreshInFlight;
243
+ const task = this.refreshInternal().finally(() => {
244
+ this.refreshInFlight = null;
245
+ });
246
+ this.refreshInFlight = task;
247
+ return task;
248
+ }
249
+ async refreshInternal() {
250
+ try {
251
+ log$7("refresh: fetching from %s", this.remoteOptions.url);
252
+ this.remoteData = await this.fetchRemoteData();
253
+ const { updatedProviders } = this.applyProviderMetadata(this.snapshotData, this.remoteData);
254
+ this.modelsByProvider.clear();
255
+ log$7("refresh: updated %d providers", updatedProviders.length);
256
+ return {
257
+ success: true,
258
+ updatedProviders,
259
+ newModels: 0
260
+ };
261
+ } catch (error) {
262
+ log$7("refresh: failed — %s", error instanceof Error ? error.message : String(error));
263
+ return {
264
+ success: false,
265
+ updatedProviders: [],
266
+ newModels: 0,
267
+ error: error instanceof Error ? error : new Error(String(error))
268
+ };
269
+ }
270
+ }
271
+ async fetchRemoteData() {
272
+ const fetchFn = this.remoteOptions.fetch ?? this.resolveGlobalFetch();
273
+ if (!fetchFn) throw new Error("No fetch implementation available");
274
+ const response = await fetchFn(this.remoteOptions.url);
275
+ if (!response.ok) throw new Error(`Failed to fetch remote catalog: HTTP ${response.status}`);
276
+ const payload = await response.json();
277
+ if (payload && typeof payload === "object") return payload;
278
+ throw new Error("Invalid remote catalog payload");
279
+ }
280
+ resolveGlobalFetch() {
281
+ const maybeFetch = globalThis.fetch;
282
+ return typeof maybeFetch === "function" ? maybeFetch : void 0;
283
+ }
284
+ applyProviderMetadata(snapshotRaw, remoteRaw) {
285
+ const snapshotProviders = this.mapProviderMetadata(snapshotRaw);
286
+ const remoteProviders = this.mapProviderMetadata(remoteRaw);
287
+ const allProviderIds = new Set([...snapshotProviders.keys(), ...remoteProviders.keys()]);
288
+ this.providers.clear();
289
+ for (const providerId of allProviderIds) {
290
+ const provider = remoteProviders.get(providerId) ?? snapshotProviders.get(providerId);
291
+ if (provider) this.providers.set(providerId, provider);
292
+ }
293
+ for (const [providerId, provider] of this.extendedProviders.entries()) this.providers.set(providerId, provider);
294
+ return { updatedProviders: [...remoteProviders.keys()] };
295
+ }
296
+ mapProviderMetadata(data) {
297
+ const result = /* @__PURE__ */ new Map();
298
+ for (const [providerId, rawProvider] of Object.entries(data)) {
299
+ if (providerId.startsWith("_")) continue;
300
+ result.set(providerId, mapModelsDevProviderMetadata(providerId, rawProvider));
301
+ }
302
+ return result;
303
+ }
304
+ providerRaw(data, providerId) {
305
+ const value = data[providerId];
306
+ return value && typeof value === "object" ? value : void 0;
307
+ }
308
+ mapProviderModelsFromRaw(providerId, raw, provenance) {
309
+ if (!raw || typeof raw !== "object") return /* @__PURE__ */ new Map();
310
+ const mapped = mapModelsDevProvider(providerId, raw, provenance);
311
+ const byId = /* @__PURE__ */ new Map();
312
+ for (const model of mapped.models) byId.set(model.modelId, model);
313
+ return byId;
314
+ }
315
+ ensureProviderModelsLoaded(providerId) {
316
+ if (this.modelsByProvider.has(providerId)) return;
317
+ const snapshotModels = this.mapProviderModelsFromRaw(providerId, this.providerRaw(this.snapshotData, providerId), "snapshot");
318
+ const remoteModels = this.mapProviderModelsFromRaw(providerId, this.providerRaw(this.remoteData, providerId), "remote");
319
+ const extendedOverrides = this.extendedModels.get(providerId) ?? /* @__PURE__ */ new Map();
320
+ const merged = mergeCatalogData(snapshotModels, remoteModels, extendedOverrides);
321
+ for (const [modelId, partial] of extendedOverrides.entries()) if (!merged.has(modelId)) {
322
+ const base = {
323
+ modelId,
324
+ modalities: {
325
+ input: ["text"],
326
+ output: ["text"]
327
+ },
328
+ limit: {
329
+ context: 0,
330
+ output: 0
331
+ },
332
+ provenance: "user-override"
333
+ };
334
+ merged.set(modelId, mergeModelDefinitions(base, partial));
335
+ }
336
+ this.modelsByProvider.set(providerId, merged);
337
+ }
338
+ };
339
+
340
+ //#endregion
341
+ //#region src/plugin/anthropic.ts
342
+ const log$6 = createLogger("plugin:anthropic");
343
+ const CLIENT_ID$1 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
344
+ const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
345
+ const TOKEN_URL = "https://console.anthropic.com/v1/oauth/token";
346
+ const API_KEY_EXCHANGE_URL = "https://api.anthropic.com/api/oauth/claude_cli/create_api_key";
347
+ const REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
348
+ const OAUTH_SCOPES = "org:create_api_key user:profile user:inference";
349
+ const OAUTH_BETA = "oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14";
350
+ function isOAuthToken(value) {
351
+ return value.startsWith("sk-ant-oat") || value.startsWith("sk-ant-ort");
352
+ }
353
+ function looksLikeApiKey(value) {
354
+ if (isOAuthToken(value)) return false;
355
+ return value.startsWith("sk-ant-");
356
+ }
357
+ function applyBearerHeaders(headers, token) {
358
+ headers.delete("x-api-key");
359
+ headers.delete("authorization");
360
+ headers.delete("Authorization");
361
+ headers.set("Authorization", `Bearer ${token}`);
362
+ headers.set("anthropic-beta", OAUTH_BETA);
363
+ }
364
+ function toBase64Url$2(inputValue) {
365
+ return inputValue.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
366
+ }
367
+ function createPkce$1() {
368
+ const verifier = toBase64Url$2(randomBytes(32));
369
+ return {
370
+ verifier,
371
+ challenge: toBase64Url$2(createHash("sha256").update(verifier).digest())
372
+ };
373
+ }
374
+ function buildAuthorizationRequest$1() {
375
+ const { verifier, challenge } = createPkce$1();
376
+ const state = verifier;
377
+ const url = new URL(AUTHORIZE_URL);
378
+ url.searchParams.set("code", "true");
379
+ url.searchParams.set("client_id", CLIENT_ID$1);
380
+ url.searchParams.set("response_type", "code");
381
+ url.searchParams.set("redirect_uri", REDIRECT_URI);
382
+ url.searchParams.set("scope", OAUTH_SCOPES);
383
+ url.searchParams.set("code_challenge", challenge);
384
+ url.searchParams.set("code_challenge_method", "S256");
385
+ url.searchParams.set("state", state);
386
+ return {
387
+ url: url.toString(),
388
+ verifier,
389
+ state
390
+ };
391
+ }
392
+ function openUrlInBrowser$2(url) {
393
+ const platform = process.platform;
394
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "rundll32" : "xdg-open";
395
+ const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
396
+ try {
397
+ spawn(command, args, {
398
+ detached: true,
399
+ stdio: "ignore"
400
+ }).unref();
401
+ } catch {
402
+ log$6("failed to open browser automatically");
403
+ }
404
+ }
405
+ async function readCallbackInput$1(question) {
406
+ const rl = createInterface({
407
+ input: stdin,
408
+ output: stdout
409
+ });
410
+ try {
411
+ return (await rl.question(question)).trim();
412
+ } finally {
413
+ rl.close();
414
+ }
415
+ }
416
+ function parseCallbackInput$1(value) {
417
+ const raw = value.trim();
418
+ if (raw.length === 0) return {};
419
+ if (/^https?:\/\//i.test(raw)) try {
420
+ const u = new URL(raw);
421
+ return {
422
+ code: u.searchParams.get("code") ?? void 0,
423
+ state: u.searchParams.get("state") ?? void 0
424
+ };
425
+ } catch {
426
+ return {};
427
+ }
428
+ const maybeQuery = raw.startsWith("?") ? raw.slice(1) : raw;
429
+ if (maybeQuery.includes("=")) {
430
+ const params = new URLSearchParams(maybeQuery);
431
+ const code = params.get("code") ?? void 0;
432
+ const state = params.get("state") ?? void 0;
433
+ if (code !== void 0 || state !== void 0) return {
434
+ code,
435
+ state
436
+ };
437
+ }
438
+ return { code: raw };
439
+ }
440
+ async function exchangeAuthorizationCode$1(code, verifier, state) {
441
+ const res = await globalThis.fetch(TOKEN_URL, {
442
+ method: "POST",
443
+ headers: {
444
+ "Content-Type": "application/json",
445
+ Accept: "application/json"
446
+ },
447
+ body: JSON.stringify({
448
+ grant_type: "authorization_code",
449
+ code,
450
+ state,
451
+ client_id: CLIENT_ID$1,
452
+ redirect_uri: REDIRECT_URI,
453
+ code_verifier: verifier
454
+ })
455
+ });
456
+ if (!res.ok) {
457
+ const body = await res.text();
458
+ throw new Error(`Anthropic token exchange failed: ${res.status} ${res.statusText} ${body}`);
459
+ }
460
+ const raw = await res.json();
461
+ return {
462
+ access_token: String(raw.access_token ?? ""),
463
+ refresh_token: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
464
+ expires_in: typeof raw.expires_in === "number" ? raw.expires_in : void 0
465
+ };
466
+ }
467
+ async function refreshAccessToken$1(refreshToken) {
468
+ const res = await globalThis.fetch(TOKEN_URL, {
469
+ method: "POST",
470
+ headers: {
471
+ "Content-Type": "application/json",
472
+ Accept: "application/json"
473
+ },
474
+ body: JSON.stringify({
475
+ grant_type: "refresh_token",
476
+ refresh_token: refreshToken,
477
+ client_id: CLIENT_ID$1
478
+ })
479
+ });
480
+ if (!res.ok) {
481
+ const body = await res.text().catch(() => "");
482
+ throw new Error(`Anthropic token refresh failed: ${res.status} ${res.statusText} ${body}`);
483
+ }
484
+ const raw = await res.json();
485
+ return {
486
+ access_token: String(raw.access_token ?? ""),
487
+ refresh_token: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
488
+ expires_in: typeof raw.expires_in === "number" ? raw.expires_in : void 0
489
+ };
490
+ }
491
+ async function createApiKeyFromOAuthAccessToken(accessToken) {
492
+ const res = await globalThis.fetch(API_KEY_EXCHANGE_URL, {
493
+ method: "POST",
494
+ headers: {
495
+ Authorization: `Bearer ${accessToken}`,
496
+ "Content-Type": "application/json",
497
+ Accept: "application/json"
498
+ },
499
+ body: "{}"
500
+ });
501
+ if (!res.ok) {
502
+ const body = await res.text();
503
+ throw new Error(`Anthropic API key exchange failed: ${res.status} ${res.statusText} ${body}`);
504
+ }
505
+ const raw = await res.json();
506
+ if (typeof raw.raw_key !== "string" || raw.raw_key.length === 0) throw new Error("Anthropic API key exchange returned empty raw_key");
507
+ return raw.raw_key;
508
+ }
509
+ /**
510
+ * Resolve a fresh OAuth access_token from the credential, refreshing if expired.
511
+ * Used both during loader setup and inside the per-request Bearer fallback fetch.
512
+ * When a refresh occurs and setAuth is provided, the updated credential is persisted.
513
+ */
514
+ async function resolveOAuthToken(auth, setAuth) {
515
+ let token = auth.key;
516
+ if (auth.expires !== void 0 && auth.expires < Date.now() && typeof auth.refresh === "string") try {
517
+ const refreshed = await refreshAccessToken$1(auth.refresh);
518
+ token = refreshed.access_token;
519
+ if (setAuth) await setAuth({
520
+ ...auth,
521
+ key: refreshed.access_token,
522
+ refresh: refreshed.refresh_token ?? auth.refresh,
523
+ expires: refreshed.expires_in !== void 0 ? Date.now() + refreshed.expires_in * 1e3 : void 0
524
+ }).catch((err) => log$6("failed to persist refreshed credential: %s", err instanceof Error ? err.message : String(err)));
525
+ } catch (error) {
526
+ log$6("anthropic token refresh failed: %s", error instanceof Error ? error.message : String(error));
527
+ }
528
+ return typeof token === "string" && token.length > 0 ? token : void 0;
529
+ }
530
+ const anthropicPlugin = {
531
+ provider: "anthropic",
532
+ async loader(getAuth, _provider, setAuth) {
533
+ const auth = await getAuth();
534
+ if (auth.type !== "oauth") return {};
535
+ const token = await resolveOAuthToken(auth, setAuth);
536
+ if (token !== void 0) {
537
+ if (looksLikeApiKey(token)) return { apiKey: token };
538
+ try {
539
+ return { apiKey: await createApiKeyFromOAuthAccessToken(token) };
540
+ } catch (error) {
541
+ log$6("anthropic api key exchange failed, using oauth bearer fallback: %s", String(error));
542
+ }
543
+ }
544
+ return {
545
+ headers: { "anthropic-beta": OAUTH_BETA },
546
+ async fetch(request, init) {
547
+ const bearerToken = await resolveOAuthToken(await getAuth(), setAuth);
548
+ const headers = new Headers(init?.headers);
549
+ applyBearerHeaders(headers, bearerToken ?? "");
550
+ return globalThis.fetch(request, {
551
+ ...init,
552
+ headers
553
+ });
554
+ }
555
+ };
556
+ },
557
+ methods: [{
558
+ type: "oauth",
559
+ label: "Claude Pro/Max (Browser OAuth)",
560
+ async handler() {
561
+ const authRequest = buildAuthorizationRequest$1();
562
+ console.log("Open this URL to continue Claude Pro/Max OAuth:");
563
+ console.log(authRequest.url);
564
+ openUrlInBrowser$2(authRequest.url);
565
+ const parsed = parseCallbackInput$1(await readCallbackInput$1("Paste the callback URL or authorization code: "));
566
+ if (!parsed.code) throw new Error("Missing authorization code in callback input");
567
+ if (parsed.state !== void 0 && parsed.state !== authRequest.state) throw new Error("OAuth state mismatch");
568
+ const tokens = await exchangeAuthorizationCode$1(parsed.code, authRequest.verifier, authRequest.state);
569
+ return {
570
+ type: "oauth",
571
+ key: tokens.access_token,
572
+ refresh: tokens.refresh_token,
573
+ expires: tokens.expires_in !== void 0 ? Date.now() + tokens.expires_in * 1e3 : void 0
574
+ };
575
+ }
576
+ }]
577
+ };
578
+
579
+ //#endregion
580
+ //#region src/plugin/codex.ts
581
+ const log$5 = createLogger("plugin:codex");
582
+ const OAUTH_DUMMY_KEY = "codex-oauth-placeholder";
583
+ const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
584
+ const ISSUER = "https://auth.openai.com";
585
+ const OPENAI_AUTHORIZE_URL = `${ISSUER}/oauth/authorize`;
586
+ const OPENAI_TOKEN_URL = `${ISSUER}/oauth/token`;
587
+ const OPENAI_DEVICE_USERCODE_URL = `${ISSUER}/api/accounts/deviceauth/usercode`;
588
+ const OPENAI_DEVICE_TOKEN_URL = `${ISSUER}/api/accounts/deviceauth/token`;
589
+ const OPENAI_DEVICE_VERIFY_URL = `${ISSUER}/codex/device`;
590
+ const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
591
+ const DEVICE_USER_AGENT = "openllmprovider/codex-auth";
592
+ const OAUTH_CALLBACK_PORT = 1455;
593
+ const OAUTH_CALLBACK_PATH = "/auth/callback";
594
+ const OAUTH_CALLBACK_TIMEOUT_MS = 300 * 1e3;
595
+ function parseTokenResponse(raw) {
596
+ return {
597
+ access_token: String(raw.access_token ?? ""),
598
+ refresh_token: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
599
+ expires_in: Number(raw.expires_in ?? 3600),
600
+ token_type: String(raw.token_type ?? "Bearer")
601
+ };
602
+ }
603
+ /**
604
+ * Decode a JWT payload (base64url) and return claims as a plain object.
605
+ * Returns undefined if the token is not a valid JWT.
606
+ */
607
+ function decodeJwtPayload(token) {
608
+ const parts = token.split(".");
609
+ if (parts.length !== 3) return void 0;
610
+ try {
611
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
612
+ const padded = base64 + "===".slice((base64.length + 3) % 4);
613
+ const decoded = atob(padded);
614
+ const parsed = JSON.parse(decoded);
615
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
616
+ } catch {}
617
+ }
618
+ /**
619
+ * Extract the ChatGPT account ID from an OpenAI OAuth JWT access token.
620
+ * The claim is nested: token["https://api.openai.com/auth"]["chatgpt_account_id"]
621
+ */
622
+ function extractAccountIdFromJwt(token) {
623
+ const claims = decodeJwtPayload(token);
624
+ if (claims === void 0) return void 0;
625
+ const authClaim = claims["https://api.openai.com/auth"];
626
+ if (authClaim !== null && typeof authClaim === "object" && !Array.isArray(authClaim)) {
627
+ const id = authClaim.chatgpt_account_id;
628
+ if (typeof id === "string" && id.length > 0) return id;
629
+ }
630
+ }
631
+ function toBase64Url$1(input) {
632
+ return input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
633
+ }
634
+ function createPkcePair() {
635
+ const verifier = toBase64Url$1(randomBytes(32));
636
+ return {
637
+ verifier,
638
+ challenge: toBase64Url$1(createHash("sha256").update(verifier).digest())
639
+ };
640
+ }
641
+ function openUrlInBrowser$1(url) {
642
+ const platform = process.platform;
643
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "rundll32" : "xdg-open";
644
+ const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
645
+ try {
646
+ spawn(command, args, {
647
+ detached: true,
648
+ stdio: "ignore"
649
+ }).unref();
650
+ } catch {
651
+ log$5("failed to open browser automatically");
652
+ }
653
+ }
654
+ async function startBrowserOAuthFlow() {
655
+ const { verifier, challenge } = createPkcePair();
656
+ const state = toBase64Url$1(randomBytes(24));
657
+ const redirectUri = `http://localhost:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`;
658
+ const authUrl = new URL(OPENAI_AUTHORIZE_URL);
659
+ authUrl.searchParams.set("response_type", "code");
660
+ authUrl.searchParams.set("client_id", CLIENT_ID);
661
+ authUrl.searchParams.set("redirect_uri", redirectUri);
662
+ authUrl.searchParams.set("scope", "openid profile email offline_access");
663
+ authUrl.searchParams.set("code_challenge", challenge);
664
+ authUrl.searchParams.set("code_challenge_method", "S256");
665
+ authUrl.searchParams.set("state", state);
666
+ authUrl.searchParams.set("id_token_add_organizations", "true");
667
+ authUrl.searchParams.set("codex_cli_simplified_flow", "true");
668
+ authUrl.searchParams.set("originator", "openllmprovider");
669
+ const successPage = "<!doctype html><html><body style=\"font-family:system-ui;padding:24px\">Authentication complete. You can close this tab.</body></html>";
670
+ const errorPage = "<!doctype html><html><body style=\"font-family:system-ui;padding:24px\">Authentication failed. Return to terminal.</body></html>";
671
+ let resolved = false;
672
+ let rejectAuth = () => {};
673
+ let closeServer = async () => {};
674
+ const callbackPromise = new Promise((resolve, reject) => {
675
+ rejectAuth = reject;
676
+ const server = createServer((req, res) => {
677
+ if (!req.url) {
678
+ res.statusCode = 400;
679
+ res.end(errorPage);
680
+ if (!resolved) {
681
+ resolved = true;
682
+ reject(/* @__PURE__ */ new Error("OAuth callback request missing URL"));
683
+ }
684
+ return;
685
+ }
686
+ const callbackUrl = new URL(req.url, `http://localhost:${OAUTH_CALLBACK_PORT}`);
687
+ if (callbackUrl.pathname !== OAUTH_CALLBACK_PATH) {
688
+ res.statusCode = 404;
689
+ res.end("Not Found");
690
+ return;
691
+ }
692
+ const error = callbackUrl.searchParams.get("error");
693
+ const errorDescription = callbackUrl.searchParams.get("error_description");
694
+ const code = callbackUrl.searchParams.get("code") ?? void 0;
695
+ const responseState = callbackUrl.searchParams.get("state") ?? void 0;
696
+ if (error) {
697
+ res.statusCode = 400;
698
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
699
+ res.end(errorPage);
700
+ if (!resolved) {
701
+ resolved = true;
702
+ reject(new Error(errorDescription ?? error));
703
+ }
704
+ return;
705
+ }
706
+ if (!code) {
707
+ res.statusCode = 400;
708
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
709
+ res.end(errorPage);
710
+ if (!resolved) {
711
+ resolved = true;
712
+ reject(/* @__PURE__ */ new Error("OAuth callback missing code"));
713
+ }
714
+ return;
715
+ }
716
+ res.statusCode = 200;
717
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
718
+ res.end(successPage);
719
+ if (!resolved) {
720
+ resolved = true;
721
+ resolve({
722
+ code,
723
+ state: responseState
724
+ });
725
+ }
726
+ });
727
+ server.once("error", (error) => {
728
+ if (!resolved) {
729
+ resolved = true;
730
+ reject(error);
731
+ }
732
+ });
733
+ server.listen(OAUTH_CALLBACK_PORT, "127.0.0.1");
734
+ closeServer = async () => {
735
+ await new Promise((closeResolve) => {
736
+ server.close(() => closeResolve());
737
+ });
738
+ };
739
+ });
740
+ const timeout = setTimeout(() => {
741
+ if (!resolved) {
742
+ resolved = true;
743
+ rejectAuth(/* @__PURE__ */ new Error("Browser OAuth timed out waiting for callback"));
744
+ }
745
+ }, OAUTH_CALLBACK_TIMEOUT_MS);
746
+ const stop = async () => {
747
+ clearTimeout(timeout);
748
+ await closeServer();
749
+ };
750
+ return {
751
+ authorizationUrl: authUrl.toString(),
752
+ callbackPromise,
753
+ expectedState: state,
754
+ codeVerifier: verifier,
755
+ stop
756
+ };
757
+ }
758
+ function extractApiErrorMessage(rawBody) {
759
+ try {
760
+ const parsed = JSON.parse(rawBody);
761
+ const direct = parsed.message;
762
+ if (typeof direct === "string" && direct.length > 0) return direct;
763
+ const nested = parsed.error;
764
+ if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
765
+ const msg = nested.message;
766
+ if (typeof msg === "string" && msg.length > 0) return msg;
767
+ }
768
+ } catch {
769
+ return rawBody;
770
+ }
771
+ return rawBody;
772
+ }
773
+ function isDeviceAuthSecurityGate(message) {
774
+ const text = message.toLowerCase();
775
+ return text.includes("enable device code authorization for codex") || text.includes("chatgpt security settings") || text.includes("device code authorization");
776
+ }
777
+ async function startDeviceAuth() {
778
+ const res = await globalThis.fetch(OPENAI_DEVICE_USERCODE_URL, {
779
+ method: "POST",
780
+ headers: {
781
+ "Content-Type": "application/json",
782
+ "User-Agent": DEVICE_USER_AGENT
783
+ },
784
+ body: JSON.stringify({ client_id: CLIENT_ID })
785
+ });
786
+ if (!res.ok) {
787
+ const body = await res.text();
788
+ throw new Error(`Device auth start failed: ${res.status} ${res.statusText} ${body}`);
789
+ }
790
+ const raw = await res.json();
791
+ return {
792
+ device_auth_id: String(raw.device_auth_id ?? ""),
793
+ user_code: String(raw.user_code ?? ""),
794
+ interval: typeof raw.interval === "string" || typeof raw.interval === "number" ? raw.interval : void 0
795
+ };
796
+ }
797
+ async function pollDeviceAuthorizationCode(deviceAuthId, userCode, intervalSeconds) {
798
+ const intervalMs = Math.max(intervalSeconds, 1) * 1e3;
799
+ const maxPolls = 120;
800
+ for (let i = 0; i < maxPolls; i++) {
801
+ await new Promise((resolve) => setTimeout(resolve, intervalMs + 3e3));
802
+ const res = await globalThis.fetch(OPENAI_DEVICE_TOKEN_URL, {
803
+ method: "POST",
804
+ headers: {
805
+ "Content-Type": "application/json",
806
+ "User-Agent": DEVICE_USER_AGENT
807
+ },
808
+ body: JSON.stringify({
809
+ device_auth_id: deviceAuthId,
810
+ user_code: userCode
811
+ })
812
+ });
813
+ if (res.ok) {
814
+ const raw = await res.json();
815
+ const authorizationCode = String(raw.authorization_code ?? "");
816
+ const codeVerifier = String(raw.code_verifier ?? "");
817
+ if (!authorizationCode || !codeVerifier) throw new Error("Device auth token response missing authorization_code/code_verifier");
818
+ log$5("poll[%d]: authorization code acquired", i + 1);
819
+ return {
820
+ authorization_code: authorizationCode,
821
+ code_verifier: codeVerifier
822
+ };
823
+ }
824
+ if (res.status === 403 || res.status === 404) {
825
+ if (res.status === 403) {
826
+ const message = extractApiErrorMessage(await res.text());
827
+ if (isDeviceAuthSecurityGate(message)) throw new Error(message);
828
+ }
829
+ log$5("poll[%d]: pending (%d)", i + 1, res.status);
830
+ continue;
831
+ }
832
+ const body = await res.text();
833
+ throw new Error(`Device auth poll failed: ${res.status} ${res.statusText} ${body}`);
834
+ }
835
+ throw new Error("Device flow timed out after polling limit reached");
836
+ }
837
+ async function exchangeAuthorizationCodeForTokens(authorizationCode, codeVerifier) {
838
+ const body = new URLSearchParams({
839
+ grant_type: "authorization_code",
840
+ code: authorizationCode,
841
+ redirect_uri: `${ISSUER}/deviceauth/callback`,
842
+ client_id: CLIENT_ID,
843
+ code_verifier: codeVerifier
844
+ });
845
+ const res = await globalThis.fetch(OPENAI_TOKEN_URL, {
846
+ method: "POST",
847
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
848
+ body: body.toString()
849
+ });
850
+ if (!res.ok) {
851
+ const rawBody = await res.text();
852
+ throw new Error(`Token exchange failed: ${res.status} ${res.statusText} ${rawBody}`);
853
+ }
854
+ return parseTokenResponse(await res.json());
855
+ }
856
+ async function exchangeBrowserAuthorizationCode(code, codeVerifier) {
857
+ const redirectUri = `http://localhost:${OAUTH_CALLBACK_PORT}${OAUTH_CALLBACK_PATH}`;
858
+ const body = new URLSearchParams({
859
+ grant_type: "authorization_code",
860
+ code,
861
+ redirect_uri: redirectUri,
862
+ client_id: CLIENT_ID,
863
+ code_verifier: codeVerifier
864
+ });
865
+ const res = await globalThis.fetch(OPENAI_TOKEN_URL, {
866
+ method: "POST",
867
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
868
+ body: body.toString()
869
+ });
870
+ if (!res.ok) {
871
+ const rawBody = await res.text();
872
+ throw new Error(`Browser token exchange failed: ${res.status} ${res.statusText} ${rawBody}`);
873
+ }
874
+ return parseTokenResponse(await res.json());
875
+ }
876
+ /**
877
+ * Exchange a refresh_token for a new access_token using form-urlencoded POST.
878
+ */
879
+ async function refreshAccessToken(refreshToken) {
880
+ log$5("refreshing access token");
881
+ const body = new URLSearchParams({
882
+ grant_type: "refresh_token",
883
+ refresh_token: refreshToken,
884
+ client_id: CLIENT_ID
885
+ });
886
+ const res = await globalThis.fetch(OPENAI_TOKEN_URL, {
887
+ method: "POST",
888
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
889
+ body: body.toString()
890
+ });
891
+ if (!res.ok) throw new Error(`Token refresh failed: ${res.status} ${res.statusText}`);
892
+ return parseTokenResponse(await res.json());
893
+ }
894
+ /**
895
+ * Check if a request URL should be rewritten to the Codex endpoint.
896
+ */
897
+ function shouldRewriteUrl(input) {
898
+ let url;
899
+ try {
900
+ if (input instanceof Request) url = new URL(input.url);
901
+ else url = new URL(String(input));
902
+ } catch {
903
+ return false;
904
+ }
905
+ const pathname = url.pathname;
906
+ return pathname.includes("/v1/responses") || pathname.includes("/chat/completions");
907
+ }
908
+ /**
909
+ * Buffer a streaming SSE response from the Codex endpoint and extract the
910
+ * final response.completed event into a synthetic Responses API JSON object.
911
+ */
912
+ async function bufferCodexStream(response) {
913
+ const lines = (await response.text()).split("\n");
914
+ let lastResponseData;
915
+ for (const line of lines) {
916
+ if (!line.startsWith("data: ")) continue;
917
+ const jsonStr = line.slice(6);
918
+ if (jsonStr === "[DONE]") break;
919
+ try {
920
+ const event = JSON.parse(jsonStr);
921
+ if (event !== null && typeof event === "object" && !Array.isArray(event)) {
922
+ const e = event;
923
+ if (e.type === "response.completed" || e.type === "response.done") {
924
+ const resp = e.response;
925
+ if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) lastResponseData = resp;
926
+ }
927
+ }
928
+ } catch {}
929
+ }
930
+ if (lastResponseData !== void 0) return lastResponseData;
931
+ log$5("bufferCodexStream: no response.completed event found in SSE stream");
932
+ return { error: { message: "Failed to parse streaming response from Codex endpoint" } };
933
+ }
934
+ /**
935
+ * Built-in auth plugin for OpenAI Codex (OAuth).
936
+ *
937
+ * Only activates for OAuth credentials (`auth.type === 'oauth'`). API key
938
+ * credentials fall through with an empty options object so the standard
939
+ * OpenAI SDK path handles them.
940
+ *
941
+ * When active the plugin:
942
+ * 1. Sets a dummy apiKey so the SDK doesn't reject construction
943
+ * 2. Wraps fetch to auto-refresh expired tokens via refresh_token
944
+ * 3. Injects `Authorization: Bearer <access_token>`
945
+ * 4. Sets `ChatGPT-Account-Id` from stored accountId or JWT claims
946
+ * 5. Rewrites `/v1/responses` and `/chat/completions` URLs to the Codex endpoint
947
+ */
948
+ const codexPlugin = {
949
+ provider: "openai",
950
+ async loader(getAuth, _provider, setAuth) {
951
+ const auth = await getAuth();
952
+ if (auth.type !== "oauth") {
953
+ log$5("codex loader: skipping (type=%s)", auth.type);
954
+ return {};
955
+ }
956
+ log$5("codex loader: activating OAuth fetch wrapper");
957
+ return {
958
+ apiKey: OAUTH_DUMMY_KEY,
959
+ async fetch(request, init) {
960
+ let currentAuth = await getAuth();
961
+ if (currentAuth.expires !== void 0 && currentAuth.expires < Date.now() && typeof currentAuth.refresh === "string") {
962
+ log$5("token expired, attempting refresh...");
963
+ try {
964
+ const tokens = await refreshAccessToken(currentAuth.refresh);
965
+ currentAuth = {
966
+ ...currentAuth,
967
+ key: tokens.access_token,
968
+ refresh: tokens.refresh_token ?? currentAuth.refresh,
969
+ expires: Date.now() + tokens.expires_in * 1e3
970
+ };
971
+ await setAuth(currentAuth).catch((err) => log$5("failed to persist refreshed credential: %s", err instanceof Error ? err.message : String(err)));
972
+ log$5("token refreshed successfully");
973
+ } catch (err) {
974
+ log$5("token refresh failed: %s", err instanceof Error ? err.message : String(err));
975
+ }
976
+ }
977
+ const headers = new Headers(init?.headers);
978
+ headers.delete("Authorization");
979
+ const token = currentAuth.key ?? "";
980
+ headers.set("Authorization", `Bearer ${token}`);
981
+ let accountId = typeof currentAuth.accountId === "string" ? currentAuth.accountId : void 0;
982
+ if (accountId === void 0 && token.length > 0) accountId = extractAccountIdFromJwt(token);
983
+ if (typeof accountId === "string" && accountId.length > 0) headers.set("ChatGPT-Account-Id", accountId);
984
+ const isCodexRewrite = shouldRewriteUrl(request);
985
+ const rewritten = isCodexRewrite ? CODEX_API_ENDPOINT : request;
986
+ let patchedInit = init;
987
+ let needsStreamBuffer = false;
988
+ if (isCodexRewrite && init?.body) try {
989
+ const bodyStr = typeof init.body === "string" ? init.body : new TextDecoder().decode(init.body);
990
+ const parsed = JSON.parse(bodyStr);
991
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
992
+ const body = parsed;
993
+ if (body.store !== false) body.store = false;
994
+ if (typeof body.instructions !== "string" || body.instructions.length === 0) body.instructions = "You are a helpful assistant.";
995
+ if (body.stream !== true) {
996
+ body.stream = true;
997
+ needsStreamBuffer = true;
998
+ }
999
+ patchedInit = {
1000
+ ...init,
1001
+ body: JSON.stringify(body)
1002
+ };
1003
+ }
1004
+ } catch {}
1005
+ log$5("codex fetch: rewritten=%s, needsStreamBuffer=%s", isCodexRewrite, needsStreamBuffer);
1006
+ const response = await globalThis.fetch(rewritten, {
1007
+ ...patchedInit,
1008
+ headers
1009
+ });
1010
+ if (needsStreamBuffer && response.ok && response.body) {
1011
+ const syntheticBody = await bufferCodexStream(response);
1012
+ return new Response(JSON.stringify(syntheticBody), {
1013
+ status: 200,
1014
+ headers: { "Content-Type": "application/json" }
1015
+ });
1016
+ }
1017
+ return response;
1018
+ }
1019
+ };
1020
+ },
1021
+ methods: [{
1022
+ type: "oauth",
1023
+ label: "ChatGPT Pro/Plus (browser)",
1024
+ async handler() {
1025
+ log$5("starting browser OAuth flow");
1026
+ const oauth = await startBrowserOAuthFlow();
1027
+ try {
1028
+ console.log("Open this URL to continue Codex login:");
1029
+ console.log(oauth.authorizationUrl);
1030
+ openUrlInBrowser$1(oauth.authorizationUrl);
1031
+ const callback = await oauth.callbackPromise;
1032
+ if (callback.state !== void 0 && callback.state !== oauth.expectedState) throw new Error("OAuth state mismatch");
1033
+ const tokens = await exchangeBrowserAuthorizationCode(callback.code, oauth.codeVerifier);
1034
+ return {
1035
+ type: "oauth",
1036
+ key: tokens.access_token,
1037
+ refresh: tokens.refresh_token,
1038
+ expires: Date.now() + tokens.expires_in * 1e3
1039
+ };
1040
+ } finally {
1041
+ await oauth.stop();
1042
+ }
1043
+ }
1044
+ }, {
1045
+ type: "device-flow",
1046
+ label: "ChatGPT Pro/Plus (headless)",
1047
+ async handler() {
1048
+ log$5("starting OpenAI device flow");
1049
+ const deviceData = await startDeviceAuth();
1050
+ const interval = Number(deviceData.interval ?? 5);
1051
+ const verificationUri = OPENAI_DEVICE_VERIFY_URL;
1052
+ const userCode = deviceData.user_code;
1053
+ log$5("device flow: user_code=%s", userCode);
1054
+ log$5("device flow: verify at %s", verificationUri);
1055
+ const authCode = await pollDeviceAuthorizationCode(deviceData.device_auth_id, userCode, interval);
1056
+ const tokens = await exchangeAuthorizationCodeForTokens(authCode.authorization_code, authCode.code_verifier);
1057
+ return {
1058
+ type: "oauth",
1059
+ key: tokens.access_token,
1060
+ refresh: tokens.refresh_token,
1061
+ expires: Date.now() + tokens.expires_in * 1e3
1062
+ };
1063
+ }
1064
+ }]
1065
+ };
1066
+
1067
+ //#endregion
1068
+ //#region src/plugin/copilot.ts
1069
+ const log$4 = createLogger("plugin:copilot");
1070
+ const GITHUB_CLIENT_ID = "Ov23li8tweQw6odWQebz";
1071
+ const GITHUB_DEFAULT_DOMAIN = "github.com";
1072
+ function normalizeDomain(url) {
1073
+ return url.replace(/^https?:\/\//, "").replace(/\/$/, "");
1074
+ }
1075
+ function getUrls(domain) {
1076
+ return {
1077
+ deviceCodeUrl: `https://${domain}/login/device/code`,
1078
+ accessTokenUrl: `https://${domain}/login/oauth/access_token`
1079
+ };
1080
+ }
1081
+ function resolveEnterpriseDomainFromEnv() {
1082
+ const raw = process.env.OPENLLMPROVIDER_COPILOT_ENTERPRISE_URL?.trim();
1083
+ if (!raw) throw new Error("Missing OPENLLMPROVIDER_COPILOT_ENTERPRISE_URL. Example: github.company.com or https://github.company.com");
1084
+ return normalizeDomain(raw);
1085
+ }
1086
+ async function runDeviceFlow(domain) {
1087
+ const urls = getUrls(domain);
1088
+ const deviceRes = await globalThis.fetch(urls.deviceCodeUrl, {
1089
+ method: "POST",
1090
+ headers: {
1091
+ "Content-Type": "application/json",
1092
+ Accept: "application/json"
1093
+ },
1094
+ body: JSON.stringify({
1095
+ client_id: GITHUB_CLIENT_ID,
1096
+ scope: "read:user"
1097
+ })
1098
+ });
1099
+ if (!deviceRes.ok) {
1100
+ const body = await deviceRes.text();
1101
+ throw new Error(`Device flow init failed: ${deviceRes.status} ${deviceRes.statusText} ${body}`);
1102
+ }
1103
+ const deviceData = await deviceRes.json();
1104
+ log$4("device flow: domain=%s user_code=%s verification_uri=%s", domain, deviceData.user_code, deviceData.verification_uri);
1105
+ const token = await pollForToken(urls.accessTokenUrl, deviceData.device_code, deviceData.interval);
1106
+ return {
1107
+ type: "oauth",
1108
+ refresh: token,
1109
+ key: token,
1110
+ expires: 0,
1111
+ ...domain !== GITHUB_DEFAULT_DOMAIN ? { enterpriseUrl: domain } : {}
1112
+ };
1113
+ }
1114
+ const copilotPlugin = {
1115
+ provider: "github-copilot",
1116
+ async loader(getAuth, _provider) {
1117
+ const auth = await getAuth();
1118
+ const enterpriseUrl = typeof auth.enterpriseUrl === "string" ? auth.enterpriseUrl : void 0;
1119
+ return {
1120
+ baseURL: enterpriseUrl ? `https://copilot-api.${normalizeDomain(enterpriseUrl)}` : "https://api.githubcopilot.com",
1121
+ apiKey: "",
1122
+ async fetch(...[request, init]) {
1123
+ const auth = await getAuth();
1124
+ const headers = new Headers(init?.headers);
1125
+ headers.delete("x-api-key");
1126
+ headers.delete("Authorization");
1127
+ headers.set("Authorization", `Bearer ${auth.refresh ?? auth.key ?? ""}`);
1128
+ headers.set("Openai-Intent", "conversation-edits");
1129
+ log$4("copilot fetch: injecting auth headers");
1130
+ return globalThis.fetch(request, {
1131
+ ...init,
1132
+ headers
1133
+ });
1134
+ }
1135
+ };
1136
+ },
1137
+ methods: [{
1138
+ type: "oauth",
1139
+ label: "GitHub Copilot (GitHub.com)",
1140
+ async handler() {
1141
+ log$4("starting github.com device flow");
1142
+ return runDeviceFlow(GITHUB_DEFAULT_DOMAIN);
1143
+ }
1144
+ }, {
1145
+ type: "device-flow",
1146
+ label: "GitHub Copilot Enterprise (Device Flow)",
1147
+ async handler() {
1148
+ log$4("starting enterprise device flow");
1149
+ return runDeviceFlow(resolveEnterpriseDomainFromEnv());
1150
+ }
1151
+ }]
1152
+ };
1153
+ async function pollForToken(accessTokenUrl, deviceCode, interval) {
1154
+ const pollInterval = Math.max(interval, 5) * 1e3;
1155
+ while (true) {
1156
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1157
+ const data = await (await globalThis.fetch(accessTokenUrl, {
1158
+ method: "POST",
1159
+ headers: {
1160
+ "Content-Type": "application/json",
1161
+ Accept: "application/json"
1162
+ },
1163
+ body: JSON.stringify({
1164
+ client_id: GITHUB_CLIENT_ID,
1165
+ device_code: deviceCode,
1166
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1167
+ })
1168
+ })).json();
1169
+ if (data.access_token) {
1170
+ log$4("device flow: token obtained");
1171
+ return data.access_token;
1172
+ }
1173
+ if (data.error === "authorization_pending") {
1174
+ log$4("device flow: waiting for user authorization...");
1175
+ continue;
1176
+ }
1177
+ if (data.error === "slow_down") {
1178
+ log$4("device flow: slowing down");
1179
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
1180
+ continue;
1181
+ }
1182
+ throw new Error(`Device flow failed: ${data.error ?? "unknown error"}`);
1183
+ }
1184
+ }
1185
+
1186
+ //#endregion
1187
+ //#region src/plugin/google.ts
1188
+ const log$3 = createLogger("plugin:google");
1189
+ const GEMINI_CLIENT_ID = "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com";
1190
+ const GEMINI_CLIENT_SECRET = "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";
1191
+ const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
1192
+ const GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
1193
+ const GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
1194
+ const GEMINI_REDIRECT_URI = "http://localhost:8085/oauth2callback";
1195
+ const GEMINI_SCOPES = [
1196
+ "https://www.googleapis.com/auth/cloud-platform",
1197
+ "https://www.googleapis.com/auth/userinfo.email",
1198
+ "https://www.googleapis.com/auth/userinfo.profile"
1199
+ ];
1200
+ const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
1201
+ const CODE_ASSIST_HEADERS = {
1202
+ "User-Agent": "google-api-nodejs-client/9.15.1",
1203
+ "X-Goog-Api-Client": "gl-node/22.17.0",
1204
+ "Client-Metadata": "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI"
1205
+ };
1206
+ function resolveProjectId() {
1207
+ const explicit = process.env.OPENLLMPROVIDER_GOOGLE_PROJECT_ID?.trim();
1208
+ if (explicit) return explicit;
1209
+ const gcp = process.env.GOOGLE_CLOUD_PROJECT?.trim() ?? process.env.GOOGLE_CLOUD_PROJECT_ID?.trim();
1210
+ if (gcp) return gcp;
1211
+ return "";
1212
+ }
1213
+ async function refreshGoogleToken(refreshToken) {
1214
+ log$3("refreshing Google OAuth token");
1215
+ const body = new URLSearchParams({
1216
+ grant_type: "refresh_token",
1217
+ refresh_token: refreshToken,
1218
+ client_id: GEMINI_CLIENT_ID,
1219
+ client_secret: GEMINI_CLIENT_SECRET
1220
+ });
1221
+ const res = await globalThis.fetch(GOOGLE_TOKEN_URL, {
1222
+ method: "POST",
1223
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1224
+ body: body.toString()
1225
+ });
1226
+ if (!res.ok) throw new Error(`Google token refresh failed: ${res.status} ${res.statusText}`);
1227
+ const raw = await res.json();
1228
+ return {
1229
+ access_token: String(raw.access_token ?? ""),
1230
+ refresh_token: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
1231
+ expires_in: Number(raw.expires_in ?? 3600),
1232
+ token_type: String(raw.token_type ?? "Bearer")
1233
+ };
1234
+ }
1235
+ function toBase64Url(inputValue) {
1236
+ return inputValue.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
1237
+ }
1238
+ function createPkce() {
1239
+ const verifier = toBase64Url(randomBytes(32));
1240
+ return {
1241
+ verifier,
1242
+ challenge: toBase64Url(createHash("sha256").update(verifier).digest())
1243
+ };
1244
+ }
1245
+ function buildAuthorizationRequest() {
1246
+ const { verifier, challenge } = createPkce();
1247
+ const state = toBase64Url(randomBytes(24));
1248
+ const url = new URL(GOOGLE_AUTH_URL);
1249
+ url.searchParams.set("client_id", GEMINI_CLIENT_ID);
1250
+ url.searchParams.set("response_type", "code");
1251
+ url.searchParams.set("redirect_uri", GEMINI_REDIRECT_URI);
1252
+ url.searchParams.set("scope", GEMINI_SCOPES.join(" "));
1253
+ url.searchParams.set("code_challenge", challenge);
1254
+ url.searchParams.set("code_challenge_method", "S256");
1255
+ url.searchParams.set("state", state);
1256
+ url.searchParams.set("access_type", "offline");
1257
+ url.searchParams.set("prompt", "consent");
1258
+ return {
1259
+ url: url.toString(),
1260
+ verifier,
1261
+ state
1262
+ };
1263
+ }
1264
+ async function readCallbackInput(question) {
1265
+ const rl = createInterface({
1266
+ input: stdin,
1267
+ output: stdout
1268
+ });
1269
+ try {
1270
+ return (await rl.question(question)).trim();
1271
+ } finally {
1272
+ rl.close();
1273
+ }
1274
+ }
1275
+ function parseCallbackInput(value) {
1276
+ const raw = value.trim();
1277
+ if (raw.length === 0) return {};
1278
+ if (/^https?:\/\//i.test(raw)) try {
1279
+ const u = new URL(raw);
1280
+ return {
1281
+ code: u.searchParams.get("code") ?? void 0,
1282
+ state: u.searchParams.get("state") ?? void 0
1283
+ };
1284
+ } catch {
1285
+ return {};
1286
+ }
1287
+ const maybeQuery = raw.startsWith("?") ? raw.slice(1) : raw;
1288
+ if (maybeQuery.includes("=")) {
1289
+ const params = new URLSearchParams(maybeQuery);
1290
+ const code = params.get("code") ?? void 0;
1291
+ const state = params.get("state") ?? void 0;
1292
+ if (code !== void 0 || state !== void 0) return {
1293
+ code,
1294
+ state
1295
+ };
1296
+ }
1297
+ return { code: raw };
1298
+ }
1299
+ function openUrlInBrowser(url) {
1300
+ const platform = process.platform;
1301
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "rundll32" : "xdg-open";
1302
+ const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
1303
+ try {
1304
+ spawn(command, args, {
1305
+ detached: true,
1306
+ stdio: "ignore"
1307
+ }).unref();
1308
+ } catch {
1309
+ log$3("failed to open browser automatically");
1310
+ }
1311
+ }
1312
+ async function exchangeAuthorizationCode(code, verifier) {
1313
+ const body = new URLSearchParams({
1314
+ client_id: GEMINI_CLIENT_ID,
1315
+ client_secret: GEMINI_CLIENT_SECRET,
1316
+ code,
1317
+ grant_type: "authorization_code",
1318
+ redirect_uri: GEMINI_REDIRECT_URI,
1319
+ code_verifier: verifier
1320
+ });
1321
+ const res = await globalThis.fetch(GOOGLE_TOKEN_URL, {
1322
+ method: "POST",
1323
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1324
+ body: body.toString()
1325
+ });
1326
+ if (!res.ok) {
1327
+ const bodyText = await res.text();
1328
+ throw new Error(`Google token exchange failed: ${res.status} ${res.statusText} ${bodyText}`);
1329
+ }
1330
+ const raw = await res.json();
1331
+ return {
1332
+ access_token: String(raw.access_token ?? ""),
1333
+ refresh_token: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
1334
+ expires_in: Number(raw.expires_in ?? 3600),
1335
+ token_type: String(raw.token_type ?? "Bearer")
1336
+ };
1337
+ }
1338
+ async function fetchGoogleEmail(accessToken) {
1339
+ const res = await globalThis.fetch(GOOGLE_USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
1340
+ if (!res.ok) return void 0;
1341
+ const raw = await res.json();
1342
+ return typeof raw.email === "string" ? raw.email : void 0;
1343
+ }
1344
+ function toRequestUrlString(value) {
1345
+ if (typeof value === "string") return value;
1346
+ if (value instanceof URL) return value.toString();
1347
+ return value.url;
1348
+ }
1349
+ function parseGenerativeAction(input) {
1350
+ const match = toRequestUrlString(input).match(/\/models\/([^:]+):(\w+)/);
1351
+ if (!match) return void 0;
1352
+ const model = match[1] ?? "";
1353
+ const action = match[2] ?? "";
1354
+ if (!model || !action) return void 0;
1355
+ return {
1356
+ model,
1357
+ action,
1358
+ streaming: action === "streamGenerateContent"
1359
+ };
1360
+ }
1361
+ function buildCodeAssistUrl(action, streaming) {
1362
+ return `${CODE_ASSIST_ENDPOINT}/v1internal:${action}${streaming ? "?alt=sse" : ""}`;
1363
+ }
1364
+ function rewriteRequestBody(body, projectId, model) {
1365
+ if (typeof body !== "string" || body.length === 0) return body;
1366
+ try {
1367
+ const parsed = JSON.parse(body);
1368
+ if (typeof parsed.project === "string" && parsed.request !== void 0) {
1369
+ const wrapped = {
1370
+ ...parsed,
1371
+ model
1372
+ };
1373
+ return JSON.stringify(wrapped);
1374
+ }
1375
+ const { model: _ignored, ...requestPayload } = parsed;
1376
+ const wrapped = {
1377
+ project: projectId,
1378
+ model,
1379
+ user_prompt_id: randomUUID(),
1380
+ request: requestPayload
1381
+ };
1382
+ return JSON.stringify(wrapped);
1383
+ } catch {
1384
+ return body;
1385
+ }
1386
+ }
1387
+ function rewriteStreamingLine(line) {
1388
+ if (!line.startsWith("data:")) return line;
1389
+ const payload = line.slice(5).trim();
1390
+ if (!payload || payload === "[DONE]") return line;
1391
+ try {
1392
+ const response = JSON.parse(payload).response;
1393
+ if (response !== void 0) return `data: ${JSON.stringify(response)}`;
1394
+ return line;
1395
+ } catch {
1396
+ return line;
1397
+ }
1398
+ }
1399
+ function rewriteStreamingBody(stream) {
1400
+ const decoder = new TextDecoder();
1401
+ const encoder = new TextEncoder();
1402
+ let buffer = "";
1403
+ return new ReadableStream({ async start(controller) {
1404
+ const reader = stream.getReader();
1405
+ try {
1406
+ while (true) {
1407
+ const { done, value } = await reader.read();
1408
+ if (done) break;
1409
+ buffer += decoder.decode(value, { stream: true });
1410
+ let idx = buffer.indexOf("\n");
1411
+ while (idx !== -1) {
1412
+ const line = buffer.slice(0, idx);
1413
+ buffer = buffer.slice(idx + 1);
1414
+ controller.enqueue(encoder.encode(`${rewriteStreamingLine(line)}\n`));
1415
+ idx = buffer.indexOf("\n");
1416
+ }
1417
+ }
1418
+ buffer += decoder.decode();
1419
+ if (buffer.length > 0) controller.enqueue(encoder.encode(rewriteStreamingLine(buffer)));
1420
+ controller.close();
1421
+ } catch (error) {
1422
+ controller.error(error);
1423
+ } finally {
1424
+ reader.releaseLock();
1425
+ }
1426
+ } });
1427
+ }
1428
+ async function normalizeCodeAssistResponse(response, streaming) {
1429
+ const contentType = response.headers.get("content-type") ?? "";
1430
+ if (streaming && response.ok && contentType.includes("text/event-stream") && response.body) return new Response(rewriteStreamingBody(response.body), {
1431
+ status: response.status,
1432
+ statusText: response.statusText,
1433
+ headers: new Headers(response.headers)
1434
+ });
1435
+ if (!contentType.includes("application/json")) return response;
1436
+ const text = await response.text();
1437
+ try {
1438
+ const next = JSON.parse(text).response;
1439
+ if (next !== void 0) return new Response(JSON.stringify(next), {
1440
+ status: response.status,
1441
+ statusText: response.statusText,
1442
+ headers: new Headers(response.headers)
1443
+ });
1444
+ return new Response(text, {
1445
+ status: response.status,
1446
+ statusText: response.statusText,
1447
+ headers: new Headers(response.headers)
1448
+ });
1449
+ } catch {
1450
+ return new Response(text, {
1451
+ status: response.status,
1452
+ statusText: response.statusText,
1453
+ headers: new Headers(response.headers)
1454
+ });
1455
+ }
1456
+ }
1457
+ const googlePlugin = {
1458
+ provider: "google",
1459
+ async loader(getAuth, _provider, setAuth) {
1460
+ const initialAuth = await getAuth();
1461
+ if (initialAuth.type !== "oauth") {
1462
+ log$3("google loader: skipping (type=%s)", initialAuth.type);
1463
+ return {};
1464
+ }
1465
+ log$3("google loader: activating OAuth fetch wrapper");
1466
+ return {
1467
+ apiKey: "google-oauth-placeholder",
1468
+ async fetch(inputValue, init) {
1469
+ const currentAuth = await getAuth();
1470
+ if (currentAuth.type !== "oauth") return globalThis.fetch(inputValue, init);
1471
+ let currentToken = currentAuth.key ?? "";
1472
+ if (currentAuth.expires !== void 0 && currentAuth.expires < Date.now() && typeof currentAuth.refresh === "string") {
1473
+ log$3("token expired, attempting refresh...");
1474
+ try {
1475
+ const tokens = await refreshGoogleToken(currentAuth.refresh);
1476
+ currentToken = tokens.access_token;
1477
+ await setAuth({
1478
+ ...currentAuth,
1479
+ key: tokens.access_token,
1480
+ refresh: tokens.refresh_token ?? currentAuth.refresh,
1481
+ expires: Date.now() + tokens.expires_in * 1e3
1482
+ }).catch((err) => log$3("failed to persist refreshed credential: %s", err instanceof Error ? err.message : String(err)));
1483
+ log$3("token refreshed successfully, expires in %ds", tokens.expires_in);
1484
+ } catch (err) {
1485
+ log$3("token refresh failed: %s", err instanceof Error ? err.message : String(err));
1486
+ }
1487
+ }
1488
+ const rewritten = parseGenerativeAction(inputValue);
1489
+ if (!rewritten) {
1490
+ const headers = new Headers(init?.headers);
1491
+ headers.delete("x-goog-api-key");
1492
+ headers.delete("x-api-key");
1493
+ headers.set("Authorization", `Bearer ${currentToken}`);
1494
+ return globalThis.fetch(inputValue, {
1495
+ ...init,
1496
+ headers
1497
+ });
1498
+ }
1499
+ const projectId = resolveProjectId();
1500
+ if (!projectId) throw new Error("Google OAuth via Code Assist requires project id. Set OPENLLMPROVIDER_GOOGLE_PROJECT_ID (or GOOGLE_CLOUD_PROJECT).");
1501
+ const headers = new Headers(init?.headers);
1502
+ headers.delete("x-goog-api-key");
1503
+ headers.delete("x-api-key");
1504
+ headers.set("Authorization", `Bearer ${currentToken}`);
1505
+ headers.set("User-Agent", CODE_ASSIST_HEADERS["User-Agent"]);
1506
+ headers.set("X-Goog-Api-Client", CODE_ASSIST_HEADERS["X-Goog-Api-Client"]);
1507
+ headers.set("Client-Metadata", CODE_ASSIST_HEADERS["Client-Metadata"]);
1508
+ headers.set("x-activity-request-id", randomUUID());
1509
+ if (rewritten.streaming) headers.set("Accept", "text/event-stream");
1510
+ const requestUrl = buildCodeAssistUrl(rewritten.action, rewritten.streaming);
1511
+ const body = rewriteRequestBody(init?.body, projectId, rewritten.model);
1512
+ return normalizeCodeAssistResponse(await globalThis.fetch(requestUrl, {
1513
+ ...init,
1514
+ headers,
1515
+ body
1516
+ }), rewritten.streaming);
1517
+ }
1518
+ };
1519
+ },
1520
+ methods: [{
1521
+ type: "oauth",
1522
+ label: "Google OAuth (Gemini)",
1523
+ async handler() {
1524
+ const authRequest = buildAuthorizationRequest();
1525
+ console.log("Open this URL to continue Google OAuth:");
1526
+ console.log(authRequest.url);
1527
+ openUrlInBrowser(authRequest.url);
1528
+ const parsed = parseCallbackInput(await readCallbackInput("Paste the callback URL or authorization code: "));
1529
+ if (!parsed.code) throw new Error("Missing authorization code in callback input");
1530
+ if (parsed.state !== void 0 && parsed.state !== authRequest.state) throw new Error("OAuth state mismatch");
1531
+ const tokens = await exchangeAuthorizationCode(parsed.code, authRequest.verifier);
1532
+ if (!tokens.refresh_token) throw new Error("Google OAuth did not return a refresh token; retry and grant consent");
1533
+ const email = await fetchGoogleEmail(tokens.access_token);
1534
+ return {
1535
+ type: "oauth",
1536
+ key: tokens.access_token,
1537
+ refresh: tokens.refresh_token,
1538
+ expires: Date.now() + tokens.expires_in * 1e3,
1539
+ email
1540
+ };
1541
+ }
1542
+ }]
1543
+ };
1544
+
1545
+ //#endregion
1546
+ //#region src/provider/bundled.ts
1547
+ const log$2 = createLogger("provider:bundled");
1548
+ const PROVIDER_LOADERS = {
1549
+ "@ai-sdk/anthropic": () => import("@ai-sdk/anthropic").then((m) => m.createAnthropic),
1550
+ "@ai-sdk/openai": () => import("@ai-sdk/openai").then((m) => m.createOpenAI),
1551
+ "@ai-sdk/google": () => import("@ai-sdk/google").then((m) => m.createGoogleGenerativeAI),
1552
+ "@ai-sdk/google-vertex": () => import("@ai-sdk/google-vertex").then((m) => m.createVertex),
1553
+ "@ai-sdk/amazon-bedrock": () => import("@ai-sdk/amazon-bedrock").then((m) => m.createAmazonBedrock),
1554
+ "@ai-sdk/azure": () => import("@ai-sdk/azure").then((m) => m.createAzure),
1555
+ "@ai-sdk/openai-compatible": () => import("@ai-sdk/openai-compatible").then((m) => m.createOpenAICompatible),
1556
+ "@ai-sdk/xai": () => import("@ai-sdk/xai").then((m) => m.createXai),
1557
+ "@ai-sdk/mistral": () => import("@ai-sdk/mistral").then((m) => m.createMistral),
1558
+ "@ai-sdk/groq": () => import("@ai-sdk/groq").then((m) => m.createGroq),
1559
+ "@openrouter/ai-sdk-provider": () => import("@openrouter/ai-sdk-provider").then((m) => m.createOpenRouter)
1560
+ };
1561
+ const loadedProviders = /* @__PURE__ */ new Map();
1562
+ const unavailableProviders = /* @__PURE__ */ new Set();
1563
+ async function loadProvider(packageName) {
1564
+ if (loadedProviders.has(packageName)) return loadedProviders.get(packageName);
1565
+ if (unavailableProviders.has(packageName)) return void 0;
1566
+ const loader = PROVIDER_LOADERS[packageName];
1567
+ if (loader === void 0) return void 0;
1568
+ try {
1569
+ const factory = await loader();
1570
+ loadedProviders.set(packageName, factory);
1571
+ log$2("loaded provider package: %s", packageName);
1572
+ return factory;
1573
+ } catch {
1574
+ unavailableProviders.add(packageName);
1575
+ log$2("provider package not available: %s (not installed)", packageName);
1576
+ return;
1577
+ }
1578
+ }
1579
+ async function isProviderInstalled(packageName) {
1580
+ return await loadProvider(packageName) !== void 0;
1581
+ }
1582
+ function getAllProviderPackages() {
1583
+ return Object.keys(PROVIDER_LOADERS);
1584
+ }
1585
+
1586
+ //#endregion
1587
+ //#region src/provider/state.ts
1588
+ const log$1 = createLogger("provider:state");
1589
+ async function resolveSecretRef(ref) {
1590
+ if (typeof ref === "string") return ref;
1591
+ if (ref.type === "plain") return ref.value;
1592
+ if (ref.type === "env") return process.env[ref.name];
1593
+ }
1594
+ const GOOGLE_PROVIDERS = new Set(["@ai-sdk/google", "@ai-sdk/google-vertex"]);
1595
+ function normalizeProviderBaseURL(providerId, baseURL) {
1596
+ if (providerId !== "openai") return baseURL;
1597
+ try {
1598
+ const parsed = new URL(baseURL);
1599
+ const path = parsed.pathname.replace(/\/+$/, "");
1600
+ if (path === "" || path === "/") parsed.pathname = "/v1";
1601
+ return parsed.toString().replace(/\/$/, "");
1602
+ } catch {
1603
+ return baseURL;
1604
+ }
1605
+ }
1606
+ function resolveAuthBaseURL(authCred) {
1607
+ if (authCred === void 0) return void 0;
1608
+ if (typeof authCred.baseURL === "string" && authCred.baseURL.trim().length > 0) return authCred.baseURL.trim();
1609
+ if (typeof authCred.apiHost === "string" && authCred.apiHost.trim().length > 0) return authCred.apiHost.trim();
1610
+ if (typeof authCred.host === "string" && authCred.host.trim().length > 0) return authCred.host.trim();
1611
+ }
1612
+ async function buildProviderState(config) {
1613
+ const { catalog, authStore, userConfig } = config;
1614
+ log$1("building provider state");
1615
+ const allProviders = catalog.listProviders();
1616
+ const authCredentials = await authStore.all();
1617
+ log$1("found %d catalog providers, %d auth entries", allProviders.length, Object.keys(authCredentials).length);
1618
+ const result = {};
1619
+ for (const catalogProvider of allProviders) {
1620
+ const pid = catalogProvider.id;
1621
+ const options = {};
1622
+ let key;
1623
+ let source = "none";
1624
+ let location;
1625
+ if (catalogProvider.baseURL !== void 0) options.baseURL = normalizeProviderBaseURL(pid, catalogProvider.baseURL);
1626
+ if (catalogProvider.headers !== void 0) options.headers = { ...catalogProvider.headers };
1627
+ if (catalogProvider.options !== void 0) Object.assign(options, catalogProvider.options);
1628
+ if (catalogProvider.env !== void 0) for (const envVar of catalogProvider.env) {
1629
+ const val = process.env[envVar];
1630
+ if (val !== void 0) {
1631
+ key = val;
1632
+ source = "env";
1633
+ location = `env:${envVar}`;
1634
+ break;
1635
+ }
1636
+ }
1637
+ const authCred = authCredentials[pid];
1638
+ const authBaseURL = resolveAuthBaseURL(authCred);
1639
+ if (authBaseURL !== void 0) options.baseURL = normalizeProviderBaseURL(pid, authBaseURL);
1640
+ if (authCred?.key !== void 0) {
1641
+ key = authCred.key;
1642
+ source = "auth";
1643
+ location = authCred.location;
1644
+ const bp = catalogProvider.bundledProvider;
1645
+ if (authCred.type === "oauth" && bp !== void 0) {
1646
+ if (GOOGLE_PROVIDERS.has(bp)) {
1647
+ const token = authCred.key;
1648
+ options.fetch = (url, init) => {
1649
+ const h = new Headers(init?.headers);
1650
+ h.delete("x-goog-api-key");
1651
+ h.set("Authorization", `Bearer ${token}`);
1652
+ return globalThis.fetch(url, {
1653
+ ...init,
1654
+ headers: h
1655
+ });
1656
+ };
1657
+ }
1658
+ }
1659
+ }
1660
+ const getAuth = async () => {
1661
+ return await authStore.getPreferred?.(pid, "oauth") ?? authCred ?? { type: "api" };
1662
+ };
1663
+ const setAuth = async (credential) => {
1664
+ await authStore.set(pid, credential);
1665
+ };
1666
+ const pluginOpts = await loadPluginOptions(pid, getAuth, {
1667
+ id: pid,
1668
+ name: catalogProvider.name
1669
+ }, setAuth);
1670
+ if (pluginOpts !== void 0) {
1671
+ Object.assign(options, pluginOpts);
1672
+ const pluginKey = pluginOpts.apiKey;
1673
+ if (typeof pluginKey === "string") {
1674
+ key = pluginKey;
1675
+ source = "plugin";
1676
+ }
1677
+ const resolvedAuth = await getAuth();
1678
+ if (resolvedAuth.location) location = resolvedAuth.location;
1679
+ }
1680
+ const userCfg = userConfig?.[pid];
1681
+ if (userCfg !== void 0) {
1682
+ if (userCfg.baseURL !== void 0) options.baseURL = normalizeProviderBaseURL(pid, userCfg.baseURL);
1683
+ if (userCfg.headers !== void 0) {
1684
+ const existingHeaders = options.headers;
1685
+ options.headers = {
1686
+ ...existingHeaders !== null && typeof existingHeaders === "object" && !Array.isArray(existingHeaders) ? existingHeaders : {},
1687
+ ...userCfg.headers
1688
+ };
1689
+ }
1690
+ if (userCfg.options !== void 0) Object.assign(options, userCfg.options);
1691
+ if (userCfg.apiKey !== void 0) {
1692
+ const resolved = await resolveSecretRef(userCfg.apiKey);
1693
+ if (resolved !== void 0) {
1694
+ key = resolved;
1695
+ source = "config";
1696
+ log$1("%s: resolved key from user config", pid);
1697
+ }
1698
+ }
1699
+ }
1700
+ if (source !== "none") log$1("%s: source=%s, location=%s", pid, source, location ?? "n/a");
1701
+ result[pid] = {
1702
+ id: pid,
1703
+ key,
1704
+ options,
1705
+ source,
1706
+ location
1707
+ };
1708
+ }
1709
+ log$1("provider state built for %d providers", Object.keys(result).length);
1710
+ return result;
1711
+ }
1712
+
1713
+ //#endregion
1714
+ //#region src/provider/index.ts
1715
+ const log = createLogger("provider");
1716
+ const DEFAULT_PROVIDERS = {
1717
+ anthropic: {
1718
+ name: "Anthropic",
1719
+ env: ["ANTHROPIC_API_KEY"],
1720
+ bundledProvider: "@ai-sdk/anthropic"
1721
+ },
1722
+ openai: {
1723
+ name: "OpenAI",
1724
+ env: ["OPENAI_API_KEY"],
1725
+ bundledProvider: "@ai-sdk/openai"
1726
+ },
1727
+ google: {
1728
+ name: "Google AI",
1729
+ env: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
1730
+ bundledProvider: "@ai-sdk/google"
1731
+ },
1732
+ "google-vertex": {
1733
+ name: "Google Vertex AI",
1734
+ env: [],
1735
+ bundledProvider: "@ai-sdk/google-vertex"
1736
+ },
1737
+ "amazon-bedrock": {
1738
+ name: "Amazon Bedrock",
1739
+ env: [],
1740
+ bundledProvider: "@ai-sdk/amazon-bedrock"
1741
+ },
1742
+ azure: {
1743
+ name: "Azure OpenAI",
1744
+ env: ["AZURE_API_KEY"],
1745
+ bundledProvider: "@ai-sdk/azure"
1746
+ },
1747
+ xai: {
1748
+ name: "xAI",
1749
+ env: ["XAI_API_KEY"],
1750
+ bundledProvider: "@ai-sdk/xai"
1751
+ },
1752
+ mistral: {
1753
+ name: "Mistral",
1754
+ env: ["MISTRAL_API_KEY"],
1755
+ bundledProvider: "@ai-sdk/mistral"
1756
+ },
1757
+ groq: {
1758
+ name: "Groq",
1759
+ env: ["GROQ_API_KEY"],
1760
+ bundledProvider: "@ai-sdk/groq"
1761
+ },
1762
+ openrouter: {
1763
+ name: "OpenRouter",
1764
+ env: ["OPENROUTER_API_KEY"],
1765
+ bundledProvider: "@openrouter/ai-sdk-provider"
1766
+ },
1767
+ "github-copilot": {
1768
+ name: "GitHub Copilot",
1769
+ env: [],
1770
+ bundledProvider: "@ai-sdk/openai-compatible"
1771
+ }
1772
+ };
1773
+ function resolveBundledProviderKey(providerId, catalogProvider) {
1774
+ if (catalogProvider?.bundledProvider !== void 0) return catalogProvider.bundledProvider;
1775
+ return DEFAULT_PROVIDERS[providerId]?.bundledProvider;
1776
+ }
1777
+ function createProviderStore(authStore, config) {
1778
+ const catalog = new Catalog();
1779
+ registerPlugin(copilotPlugin);
1780
+ registerPlugin(codexPlugin);
1781
+ registerPlugin(googlePlugin);
1782
+ registerPlugin(anthropicPlugin);
1783
+ catalog.extend({ providers: Object.fromEntries(Object.entries(DEFAULT_PROVIDERS).map(([id, p]) => [id, {
1784
+ name: p.name,
1785
+ env: p.env,
1786
+ bundledProvider: p.bundledProvider
1787
+ }])) });
1788
+ const userConfig = config?.userConfig;
1789
+ let stateCache = null;
1790
+ let catalogRefreshTask = null;
1791
+ function invalidateState() {
1792
+ stateCache = null;
1793
+ }
1794
+ function getState() {
1795
+ if (stateCache === null) {
1796
+ log("initializing provider state");
1797
+ stateCache = buildProviderState({
1798
+ catalog,
1799
+ authStore,
1800
+ userConfig
1801
+ });
1802
+ }
1803
+ return stateCache;
1804
+ }
1805
+ async function ensureCatalogEnriched() {
1806
+ if (catalogRefreshTask === null) catalogRefreshTask = (async () => {
1807
+ const result = await catalog.refresh();
1808
+ if (!result.success) {
1809
+ log("catalog refresh failed: %s", result.error?.message ?? "unknown error");
1810
+ return;
1811
+ }
1812
+ log("catalog refreshed with %d providers", result.updatedProviders.length);
1813
+ invalidateState();
1814
+ })();
1815
+ await catalogRefreshTask;
1816
+ }
1817
+ function hasProviderAuth(state, providerId) {
1818
+ const providerState = state[providerId];
1819
+ return providerState !== void 0 && providerState.source !== "none";
1820
+ }
1821
+ async function checkProviderUsable(providerId) {
1822
+ const bundledKey = resolveBundledProviderKey(providerId, catalog.getProvider(providerId));
1823
+ if (bundledKey === void 0) return false;
1824
+ return isProviderInstalled(bundledKey);
1825
+ }
1826
+ return {
1827
+ async getLanguageModel(providerId, modelId) {
1828
+ await ensureCatalogEnriched();
1829
+ const providerState = (await getState())[providerId];
1830
+ log("getLanguageModel(%s, %s) — auth: source=%s, location=%s", providerId, modelId, providerState?.source ?? "none", providerState?.location ?? "unknown");
1831
+ if (providerState === void 0) throw new Error(`Provider not found in catalog: ${providerId}`);
1832
+ const bundledKey = resolveBundledProviderKey(providerId, catalog.getProvider(providerId));
1833
+ if (bundledKey === void 0) throw new Error(`No bundled provider mapping found for: ${providerId}. Set bundledProvider in catalog extend() config.`);
1834
+ const factory = await loadProvider(bundledKey);
1835
+ if (factory === void 0) throw new Error(`Provider package not available: ${bundledKey}. Install it with: npm install ${bundledKey}`);
1836
+ log("creating SDK for %s using %s", providerId, bundledKey);
1837
+ const sdkOptions = { ...providerState.options };
1838
+ if (providerState.key !== void 0) sdkOptions.apiKey = providerState.key;
1839
+ if (sdkOptions.authToken !== void 0) sdkOptions.apiKey = void 0;
1840
+ const sdk = factory(sdkOptions);
1841
+ log("calling sdk.languageModel(%s)", modelId);
1842
+ return sdk.languageModel(modelId);
1843
+ },
1844
+ extend(extendConfig) {
1845
+ catalog.extend(extendConfig);
1846
+ invalidateState();
1847
+ },
1848
+ async listProviders(options) {
1849
+ await ensureCatalogEnriched();
1850
+ const allProviders = catalog.listProviders();
1851
+ const installedProviders = (await Promise.all(allProviders.map(async (p) => ({
1852
+ provider: p,
1853
+ usable: await checkProviderUsable(p.id)
1854
+ })))).filter((r) => r.usable).map((r) => r.provider);
1855
+ if (options?.includeUnavailable === true) return installedProviders;
1856
+ const state = await getState();
1857
+ return installedProviders.filter((provider) => hasProviderAuth(state, provider.id));
1858
+ },
1859
+ async listModels(providerId, options) {
1860
+ await ensureCatalogEnriched();
1861
+ if (options?.includeUnavailable === true) return catalog.listModels(providerId);
1862
+ const state = await getState();
1863
+ if (providerId !== void 0) {
1864
+ if (!hasProviderAuth(state, providerId)) return [];
1865
+ return catalog.listModels(providerId);
1866
+ }
1867
+ const allProviders = catalog.listProviders();
1868
+ const usabilityChecks = await Promise.all(allProviders.map(async (p) => ({
1869
+ provider: p,
1870
+ usable: await checkProviderUsable(p.id) && hasProviderAuth(state, p.id)
1871
+ })));
1872
+ const results = [];
1873
+ for (const { provider, usable } of usabilityChecks) if (usable) results.push(...catalog.listModels(provider.id));
1874
+ return results;
1875
+ },
1876
+ async getModel(providerId, modelId, options) {
1877
+ await ensureCatalogEnriched();
1878
+ if (options?.includeUnavailable !== true) {
1879
+ if (!hasProviderAuth(await getState(), providerId)) return;
1880
+ }
1881
+ return catalog.getModel(providerId, modelId);
1882
+ }
1883
+ };
1884
+ }
1885
+ function getLanguageModel(providerId, modelId, config) {
1886
+ return createProviderStore(createAuthStore(), config).getLanguageModel(providerId, modelId);
1887
+ }
1888
+
1889
+ //#endregion
1890
+ export { DEFAULT_SCANNERS, FileStorage, MemoryStorage, anthropicPlugin, codexPlugin, copilotPlugin, createAuthStore, createDefaultStorage, createLogger, createProviderStore, getAllProviderPackages, getLanguageModel, getPluginForProvider, getPlugins, googlePlugin, isProviderInstalled, loadProvider, registerPlugin };
1891
+ //# sourceMappingURL=index.mjs.map