@jacobbd/relay-ai 0.10.1 → 0.11.1
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 +2 -2
- package/dist/chunk-JIDIH7DS.js +28 -0
- package/dist/chunk-O2XZFXHG.js +4763 -0
- package/dist/chunk-O2XZFXHG.js.map +1 -0
- package/dist/{chunk-TFMHIGRX.js → chunk-TRM2WGI6.js} +6 -23
- package/dist/chunk-TRM2WGI6.js.map +1 -0
- package/dist/chunk-VXQ7ZVTW.js +152 -0
- package/dist/chunk-VXQ7ZVTW.js.map +1 -0
- package/dist/{chunk-YYI6EJ2X.js → chunk-YD6A3ZB3.js} +2246 -6802
- package/dist/chunk-YD6A3ZB3.js.map +1 -0
- package/dist/cli.js +1203 -1072
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +84 -30
- package/dist/core/index.js.map +1 -1
- package/dist/manual-models-VEBTRQFK.js +12 -0
- package/dist/manual-models-VEBTRQFK.js.map +1 -0
- package/dist/{provider-templates-JWT6U5B7.js → provider-templates-JY3NXZK7.js} +3 -2
- package/dist/provider-templates-JY3NXZK7.js.map +1 -0
- package/dist/ui/public/app.js +153 -2
- package/dist/ui/public/style.css +28 -0
- package/dist/{ui-command-LZWTWTQ6.js → ui-command-WKPJI2CU.js} +118 -30
- package/dist/ui-command-WKPJI2CU.js.map +1 -0
- package/package.json +22 -23
- package/dist/chunk-TFMHIGRX.js.map +0 -1
- package/dist/chunk-YYI6EJ2X.js.map +0 -1
- package/dist/ui-command-LZWTWTQ6.js.map +0 -1
- /package/dist/{provider-templates-JWT6U5B7.js.map → chunk-JIDIH7DS.js.map} +0 -0
|
@@ -0,0 +1,4763 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
// package.json
|
|
8
|
+
var package_default = {
|
|
9
|
+
name: "@jacobbd/relay-ai",
|
|
10
|
+
version: "0.11.1",
|
|
11
|
+
publishConfig: {
|
|
12
|
+
access: "public"
|
|
13
|
+
},
|
|
14
|
+
description: "Relay any model into any coding agent \u2014 launch Claude Code, Codex, and more with multi-provider gateways",
|
|
15
|
+
author: "jacob-bd",
|
|
16
|
+
license: "MIT",
|
|
17
|
+
repository: {
|
|
18
|
+
type: "git",
|
|
19
|
+
url: "git+https://github.com/jacob-bd/relay-ai.git"
|
|
20
|
+
},
|
|
21
|
+
homepage: "https://github.com/jacob-bd/relay-ai#readme",
|
|
22
|
+
keywords: [
|
|
23
|
+
"claude",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"codex",
|
|
26
|
+
"ai",
|
|
27
|
+
"llm",
|
|
28
|
+
"cli",
|
|
29
|
+
"gateway",
|
|
30
|
+
"relay",
|
|
31
|
+
"vertex"
|
|
32
|
+
],
|
|
33
|
+
type: "module",
|
|
34
|
+
bin: {
|
|
35
|
+
"relay-ai": "dist/cli.js"
|
|
36
|
+
},
|
|
37
|
+
files: [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
engines: {
|
|
42
|
+
node: ">=22"
|
|
43
|
+
},
|
|
44
|
+
scripts: {
|
|
45
|
+
build: "tsup && tsup --config tsup.core.config.ts && node scripts/copy-ui-assets.mjs",
|
|
46
|
+
dev: "tsup --watch",
|
|
47
|
+
test: 'vitest run --exclude "tests/debug-*.test.ts"',
|
|
48
|
+
"test:live": "vitest run tests/debug-xai.test.ts tests/debug-openai-oauth.test.ts",
|
|
49
|
+
"test:watch": "vitest",
|
|
50
|
+
typecheck: "tsc --noEmit",
|
|
51
|
+
"release:check": "node scripts/release-metadata.mjs",
|
|
52
|
+
"refresh:models-dev": "node scripts/refresh-models-dev-cache.mjs",
|
|
53
|
+
prepublishOnly: "npm run release:check && npm run build"
|
|
54
|
+
},
|
|
55
|
+
dependencies: {
|
|
56
|
+
"@ai-sdk/alibaba": "^2.0.41",
|
|
57
|
+
"@ai-sdk/amazon-bedrock": "^5.0.76",
|
|
58
|
+
"@ai-sdk/azure": "^4.0.63",
|
|
59
|
+
"@ai-sdk/cerebras": "^3.0.44",
|
|
60
|
+
"@ai-sdk/cohere": "^4.0.37",
|
|
61
|
+
"@ai-sdk/deepinfra": "^3.0.44",
|
|
62
|
+
"@ai-sdk/gateway": "^4.0.75",
|
|
63
|
+
"@ai-sdk/google": "^4.0.64",
|
|
64
|
+
"@ai-sdk/google-vertex": "^5.0.76",
|
|
65
|
+
"@ai-sdk/groq": "^4.0.37",
|
|
66
|
+
"@ai-sdk/mistral": "^4.0.39",
|
|
67
|
+
"@ai-sdk/openai": "^4.0.60",
|
|
68
|
+
"@ai-sdk/openai-compatible": "^3.0.44",
|
|
69
|
+
"@ai-sdk/perplexity": "^4.0.39",
|
|
70
|
+
"@ai-sdk/togetherai": "^3.0.45",
|
|
71
|
+
"@ai-sdk/vercel": "^3.0.30",
|
|
72
|
+
"@ai-sdk/xai": "^4.0.54",
|
|
73
|
+
"@clack/prompts": "^0.9.1",
|
|
74
|
+
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
75
|
+
ai: "^7.0.93",
|
|
76
|
+
"cross-spawn": "^7.0.6",
|
|
77
|
+
"gitlab-ai-provider": "^6.15.0",
|
|
78
|
+
graphql: "^16.14.2",
|
|
79
|
+
"ipaddr.js": "^2.4.0",
|
|
80
|
+
"node-forge": "^1.4.0",
|
|
81
|
+
open: "^11.0.0",
|
|
82
|
+
picocolors: "^1.1.1",
|
|
83
|
+
"smol-toml": "^1.6.1",
|
|
84
|
+
ws: "^8.21.0",
|
|
85
|
+
zod: "^3.25.76"
|
|
86
|
+
},
|
|
87
|
+
devDependencies: {
|
|
88
|
+
"@types/cross-spawn": "^6.0.6",
|
|
89
|
+
"@types/node": "^22.0.0",
|
|
90
|
+
"@types/node-forge": "^1.3.14",
|
|
91
|
+
"@types/ws": "^8.18.1",
|
|
92
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
93
|
+
tsup: "^8.0.0",
|
|
94
|
+
typescript: "^5.5.0",
|
|
95
|
+
"vite-node": "^6.0.0",
|
|
96
|
+
vitest: "^4.1.10"
|
|
97
|
+
},
|
|
98
|
+
optionalDependencies: {
|
|
99
|
+
"@napi-rs/keyring": "^1.3.0"
|
|
100
|
+
},
|
|
101
|
+
overrides: {
|
|
102
|
+
ws: "^8.21.0"
|
|
103
|
+
},
|
|
104
|
+
exports: {
|
|
105
|
+
"./core": {
|
|
106
|
+
types: "./dist/core/index.d.ts",
|
|
107
|
+
import: "./dist/core/index.js",
|
|
108
|
+
default: "./dist/core/index.js"
|
|
109
|
+
},
|
|
110
|
+
"./package.json": "./package.json"
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/constants.ts
|
|
115
|
+
var BACKENDS = {
|
|
116
|
+
zen: {
|
|
117
|
+
id: "zen",
|
|
118
|
+
name: "OpenCode Zen",
|
|
119
|
+
// No /v1 suffix — the Anthropic SDK appends /v1/messages automatically
|
|
120
|
+
baseUrl: "https://opencode.ai/zen"
|
|
121
|
+
},
|
|
122
|
+
go: {
|
|
123
|
+
id: "go",
|
|
124
|
+
name: "OpenCode Go",
|
|
125
|
+
baseUrl: "https://opencode.ai/zen/go"
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var CODEX_RESPONSES_LITE_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
|
|
129
|
+
var CODEX_RESPONSES_LITE_VERSION = "0.153.4";
|
|
130
|
+
var CODEX_RESPONSES_WEBSOCKETS_BETA = "responses_websockets=2026-02-06";
|
|
131
|
+
var CONFLICTING_ENV_VARS = [
|
|
132
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
133
|
+
"ANTHROPIC_VERTEX_PROJECT_ID",
|
|
134
|
+
"ANTHROPIC_VERTEX_BASE_URL",
|
|
135
|
+
"CLOUD_ML_REGION",
|
|
136
|
+
"ANTHROPIC_BEDROCK_BASE_URL",
|
|
137
|
+
"ANTHROPIC_AWS_BASE_URL",
|
|
138
|
+
"ANTHROPIC_AWS_API_KEY",
|
|
139
|
+
"ANTHROPIC_AWS_WORKSPACE_ID",
|
|
140
|
+
"ANTHROPIC_FOUNDRY_API_KEY",
|
|
141
|
+
"ANTHROPIC_FOUNDRY_BASE_URL",
|
|
142
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
143
|
+
"ANTHROPIC_API_KEY",
|
|
144
|
+
"ANTHROPIC_BASE_URL",
|
|
145
|
+
"ANTHROPIC_MODEL",
|
|
146
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
147
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
148
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
|
149
|
+
];
|
|
150
|
+
var PARENT_SESSION_ENV_VARS = [
|
|
151
|
+
"CLAUDECODE",
|
|
152
|
+
"CLAUDE_CODE_CHILD_SESSION",
|
|
153
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
154
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
155
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
156
|
+
"CLAUDE_PID"
|
|
157
|
+
];
|
|
158
|
+
var OPENCODE_CACHE_PATH = join(homedir(), ".cache", "opencode", "models.json");
|
|
159
|
+
var MAX_MODEL_CATALOG = 20;
|
|
160
|
+
var CODEX_SUBAGENT_MODEL_CAP = 1;
|
|
161
|
+
var MIN_CONTEXT_WINDOW = 128e3;
|
|
162
|
+
var VERTEX_ANTHROPIC_NPM = "@ai-sdk/google-vertex/anthropic";
|
|
163
|
+
function classifyModelFormat(modelId, providerNpm) {
|
|
164
|
+
if (providerNpm === "@ai-sdk/anthropic") return "anthropic";
|
|
165
|
+
if (providerNpm === "@ai-sdk/openai") return "unsupported";
|
|
166
|
+
if (providerNpm === "@ai-sdk/google") return "unsupported";
|
|
167
|
+
const lower = modelId.toLowerCase();
|
|
168
|
+
if (lower.startsWith("claude-")) return "anthropic";
|
|
169
|
+
if (lower.startsWith("gpt-")) return "unsupported";
|
|
170
|
+
if (lower.startsWith("gemini-")) return "unsupported";
|
|
171
|
+
return "openai";
|
|
172
|
+
}
|
|
173
|
+
var VERSION = package_default.version;
|
|
174
|
+
|
|
175
|
+
// src/paths.ts
|
|
176
|
+
import { homedir as homedir2 } from "os";
|
|
177
|
+
import { join as join2 } from "path";
|
|
178
|
+
var APP_DIR_NAME = "relay-ai";
|
|
179
|
+
var LEGACY_APP_DIR_NAME = "opencode-starter";
|
|
180
|
+
function userHome(env = process.env) {
|
|
181
|
+
return env.HOME ?? env.USERPROFILE ?? homedir2();
|
|
182
|
+
}
|
|
183
|
+
function resolveAppHomeOverride(env = process.env) {
|
|
184
|
+
const override = env.RELAY_AI_HOME ?? env.OPENCODE_STARTER_HOME;
|
|
185
|
+
return override?.trim() || void 0;
|
|
186
|
+
}
|
|
187
|
+
function getAppHome(env = process.env) {
|
|
188
|
+
const override = resolveAppHomeOverride(env);
|
|
189
|
+
if (override) return override;
|
|
190
|
+
return join2(userHome(env), `.${APP_DIR_NAME}`);
|
|
191
|
+
}
|
|
192
|
+
function getLegacyAppHome(env = process.env) {
|
|
193
|
+
return join2(userHome(env), `.${LEGACY_APP_DIR_NAME}`);
|
|
194
|
+
}
|
|
195
|
+
function getConfigPath(env = process.env) {
|
|
196
|
+
return join2(getAppHome(env), "config.json");
|
|
197
|
+
}
|
|
198
|
+
function getProvidersPath(env = process.env) {
|
|
199
|
+
return join2(getAppHome(env), "providers.json");
|
|
200
|
+
}
|
|
201
|
+
function getSecretsPath(env = process.env) {
|
|
202
|
+
return join2(getAppHome(env), "secrets.json");
|
|
203
|
+
}
|
|
204
|
+
function getLogsPath(env = process.env) {
|
|
205
|
+
return join2(getAppHome(env), "logs");
|
|
206
|
+
}
|
|
207
|
+
function getVertexModelsPath(env = process.env) {
|
|
208
|
+
return join2(getAppHome(env), "vertex-models.json");
|
|
209
|
+
}
|
|
210
|
+
function getLegacyConfPath(env = process.env, platform = process.platform) {
|
|
211
|
+
const home = userHome(env);
|
|
212
|
+
const appName = `${LEGACY_APP_DIR_NAME}-nodejs`;
|
|
213
|
+
if (platform === "darwin") {
|
|
214
|
+
return join2(home, "Library", "Preferences", appName, "config.json");
|
|
215
|
+
}
|
|
216
|
+
if (platform === "win32") {
|
|
217
|
+
return join2(env.APPDATA ?? join2(home, "AppData", "Roaming"), appName, "Config", "config.json");
|
|
218
|
+
}
|
|
219
|
+
return join2(env.XDG_CONFIG_HOME ?? join2(home, ".config"), appName, "config.json");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/context-window.ts
|
|
223
|
+
import { readFileSync } from "fs";
|
|
224
|
+
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
225
|
+
var CACHE_PROVIDER_PRIORITY = /* @__PURE__ */ new Set(["opencode", "opencode-go"]);
|
|
226
|
+
var HEURISTIC_RULES = [
|
|
227
|
+
[/gemini-2\.5-pro|gemini-1\.5-pro|gemini-3-pro/i, 2e6],
|
|
228
|
+
[/gemini/i, 1e6],
|
|
229
|
+
[/claude-opus-4-[678]|claude-sonnet-4-[678]/i, 1e6],
|
|
230
|
+
[/claude-haiku-4-[567]/i, 2e5],
|
|
231
|
+
[/claude.*\[1m\]/i, 1e6],
|
|
232
|
+
[/claude-opus-4-[56]|claude-sonnet-4-[45]|claude-3/i, 2e5],
|
|
233
|
+
[/claude/i, 2e5],
|
|
234
|
+
[/deepseek-v4|deepseek-r1|deepseek-reasoner/i, 1e6],
|
|
235
|
+
[/deepseek/i, 64e3],
|
|
236
|
+
[/gpt-5|gpt-4\.1|o3-|o4-/i, 1e6],
|
|
237
|
+
[/gpt-4o|gpt-4-turbo|gpt-4/i, 128e3],
|
|
238
|
+
[/gpt-oss/i, 131072],
|
|
239
|
+
[/qwen3|qwen-3|qwen2\.5-72b|qwen2\.5-32b|qwen-coder/i, 262144],
|
|
240
|
+
[/qwen/i, 131072],
|
|
241
|
+
[/kimi-k2|kimi-k2\.5|moonshot/i, 262144],
|
|
242
|
+
[/minimax-m2/i, 204800],
|
|
243
|
+
[/minimax/i, 128e3],
|
|
244
|
+
[/mistral-large|ministral|mistral/i, 262144],
|
|
245
|
+
[/llama-3\.[23]|llama3/i, 131072],
|
|
246
|
+
[/grok-4\.20/i, 1e6],
|
|
247
|
+
[/grok-4\.5/i, 5e5],
|
|
248
|
+
[/grok-3|grok-4/i, 131072],
|
|
249
|
+
[/nemotron/i, 131072],
|
|
250
|
+
[/glm-4/i, 128e3],
|
|
251
|
+
[/solar-pro3/i, 131072],
|
|
252
|
+
[/solar-pro2/i, 65536],
|
|
253
|
+
[/solar/i, 32768]
|
|
254
|
+
];
|
|
255
|
+
var parsedCache;
|
|
256
|
+
var cacheIndex;
|
|
257
|
+
var heuristicCache = /* @__PURE__ */ new Map();
|
|
258
|
+
function loadOpencodeCache() {
|
|
259
|
+
if (parsedCache === void 0) {
|
|
260
|
+
try {
|
|
261
|
+
parsedCache = JSON.parse(readFileSync(OPENCODE_CACHE_PATH, "utf8"));
|
|
262
|
+
} catch {
|
|
263
|
+
parsedCache = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return parsedCache;
|
|
267
|
+
}
|
|
268
|
+
function buildContextWindowIndex(cache) {
|
|
269
|
+
const index = /* @__PURE__ */ new Map();
|
|
270
|
+
const allLimits = /* @__PURE__ */ new Map();
|
|
271
|
+
for (const [providerKey, providerData] of Object.entries(cache)) {
|
|
272
|
+
const models = providerData?.models;
|
|
273
|
+
if (!models) continue;
|
|
274
|
+
for (const [modelId, entry] of Object.entries(models)) {
|
|
275
|
+
const ctx = entry.limit?.context;
|
|
276
|
+
if (typeof ctx !== "number" || ctx <= 0) continue;
|
|
277
|
+
const limits = allLimits.get(modelId) ?? [];
|
|
278
|
+
limits.push(ctx);
|
|
279
|
+
allLimits.set(modelId, limits);
|
|
280
|
+
if (CACHE_PROVIDER_PRIORITY.has(providerKey)) {
|
|
281
|
+
index.set(modelId, ctx);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
for (const [modelId, limits] of allLimits) {
|
|
286
|
+
if (!index.has(modelId)) {
|
|
287
|
+
index.set(modelId, Math.max(...limits));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return index;
|
|
291
|
+
}
|
|
292
|
+
function getCacheIndex() {
|
|
293
|
+
if (cacheIndex === void 0) {
|
|
294
|
+
const cache = loadOpencodeCache();
|
|
295
|
+
cacheIndex = cache ? buildContextWindowIndex(cache) : /* @__PURE__ */ new Map();
|
|
296
|
+
}
|
|
297
|
+
return cacheIndex;
|
|
298
|
+
}
|
|
299
|
+
function contextWindowFromHeuristics(modelId) {
|
|
300
|
+
const cached = heuristicCache.get(modelId);
|
|
301
|
+
if (cached !== void 0) return cached;
|
|
302
|
+
for (const [pattern, size] of HEURISTIC_RULES) {
|
|
303
|
+
if (pattern.test(modelId)) {
|
|
304
|
+
heuristicCache.set(modelId, size);
|
|
305
|
+
return size;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
heuristicCache.set(modelId, DEFAULT_CONTEXT_WINDOW);
|
|
309
|
+
return DEFAULT_CONTEXT_WINDOW;
|
|
310
|
+
}
|
|
311
|
+
function lookupContextWindow(modelId) {
|
|
312
|
+
return getCacheIndex().get(modelId) ?? contextWindowFromHeuristics(modelId);
|
|
313
|
+
}
|
|
314
|
+
function resolveContextWindow(modelId, explicit) {
|
|
315
|
+
if (typeof explicit === "number" && explicit > 0) return explicit;
|
|
316
|
+
return lookupContextWindow(modelId);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/context-model-id.ts
|
|
320
|
+
var ONE_M_CONTEXT_SUFFIX = "[1m]";
|
|
321
|
+
function stripOneMContextSuffix(modelId) {
|
|
322
|
+
return modelId.replace(/\[1m\]$/i, "");
|
|
323
|
+
}
|
|
324
|
+
function claudeCodeClientModelId(modelId, contextWindow) {
|
|
325
|
+
const bare = stripOneMContextSuffix(modelId);
|
|
326
|
+
const window = resolveContextWindow(bare, contextWindow);
|
|
327
|
+
if (window > DEFAULT_CONTEXT_WINDOW) {
|
|
328
|
+
return `${bare}${ONE_M_CONTEXT_SUFFIX}`;
|
|
329
|
+
}
|
|
330
|
+
return bare;
|
|
331
|
+
}
|
|
332
|
+
function routeLookupIds(id) {
|
|
333
|
+
const bare = stripOneMContextSuffix(id);
|
|
334
|
+
const googleBare = bare.startsWith("models/") ? bare.slice("models/".length) : bare;
|
|
335
|
+
return [.../* @__PURE__ */ new Set([
|
|
336
|
+
id,
|
|
337
|
+
bare,
|
|
338
|
+
`${bare}${ONE_M_CONTEXT_SUFFIX}`,
|
|
339
|
+
googleBare,
|
|
340
|
+
`${googleBare}${ONE_M_CONTEXT_SUFFIX}`,
|
|
341
|
+
`models/${googleBare}`,
|
|
342
|
+
`models/${bare}`
|
|
343
|
+
])];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/oauth/antigravity-oauth.ts
|
|
347
|
+
import open from "open";
|
|
348
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
349
|
+
import { homedir as homedir3 } from "os";
|
|
350
|
+
import { join as pathJoin } from "path";
|
|
351
|
+
|
|
352
|
+
// src/oauth/pkce.ts
|
|
353
|
+
function generateRandomString(length) {
|
|
354
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
355
|
+
return Array.from(crypto.getRandomValues(new Uint8Array(length))).map((b) => chars[b % chars.length]).join("");
|
|
356
|
+
}
|
|
357
|
+
function base64UrlEncode(buffer) {
|
|
358
|
+
const binary = String.fromCharCode(...new Uint8Array(buffer));
|
|
359
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
360
|
+
}
|
|
361
|
+
async function generatePkce() {
|
|
362
|
+
const verifier = generateRandomString(64);
|
|
363
|
+
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
364
|
+
return { verifier, challenge: base64UrlEncode(hash) };
|
|
365
|
+
}
|
|
366
|
+
function generateOAuthState() {
|
|
367
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
368
|
+
const binary = String.fromCharCode(...bytes);
|
|
369
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
370
|
+
}
|
|
371
|
+
function positiveSecondsToMs(value, defaultMs) {
|
|
372
|
+
const seconds = Number(value);
|
|
373
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : defaultMs;
|
|
374
|
+
}
|
|
375
|
+
async function sleepMs(ms) {
|
|
376
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/oauth/callback-server.ts
|
|
380
|
+
import http from "http";
|
|
381
|
+
var SUCCESS_HTML = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Authorized</title></head>
|
|
382
|
+
<body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0">
|
|
383
|
+
<div style="text-align:center;padding:2rem;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1)">
|
|
384
|
+
<div style="color:#22c55e;font-size:2.5rem">✓</div>
|
|
385
|
+
<h1 style="margin:.5rem 0">Authentication successful</h1>
|
|
386
|
+
<p style="color:#666">You can close this tab and return to the terminal.</p>
|
|
387
|
+
</div></body></html>`;
|
|
388
|
+
function startCallbackServer() {
|
|
389
|
+
return new Promise((resolve, reject) => {
|
|
390
|
+
let codeResolve;
|
|
391
|
+
let codeReject;
|
|
392
|
+
const server = http.createServer((req, res) => {
|
|
393
|
+
const u = new URL(req.url ?? "/", "http://localhost");
|
|
394
|
+
if (u.pathname !== "/callback" && u.pathname !== "/oauth/callback") {
|
|
395
|
+
res.writeHead(404);
|
|
396
|
+
res.end();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const code = u.searchParams.get("code") ?? "";
|
|
400
|
+
const state = u.searchParams.get("state") ?? "";
|
|
401
|
+
const error = u.searchParams.get("error") ?? "";
|
|
402
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
403
|
+
res.end(SUCCESS_HTML);
|
|
404
|
+
codeResolve?.({ code, state, error: error || void 0 });
|
|
405
|
+
});
|
|
406
|
+
server.listen(0, "127.0.0.1", () => {
|
|
407
|
+
const addr = server.address();
|
|
408
|
+
const port = addr.port;
|
|
409
|
+
resolve({
|
|
410
|
+
port,
|
|
411
|
+
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
412
|
+
waitForCallback(timeoutMs = 3e5) {
|
|
413
|
+
return new Promise((res, rej) => {
|
|
414
|
+
codeResolve = res;
|
|
415
|
+
codeReject = rej;
|
|
416
|
+
setTimeout(
|
|
417
|
+
() => rej(new Error("OAuth timeout \u2014 browser closed without completing sign-in")),
|
|
418
|
+
timeoutMs
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
close() {
|
|
423
|
+
server.close();
|
|
424
|
+
codeReject?.(new Error("Server closed"));
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
server.on("error", reject);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/oauth/refresh-http.ts
|
|
433
|
+
async function postOAuthRefresh(url, body, options) {
|
|
434
|
+
const isJson = options.contentType === "json";
|
|
435
|
+
const response = await fetch(url, {
|
|
436
|
+
method: "POST",
|
|
437
|
+
headers: {
|
|
438
|
+
"Content-Type": isJson ? "application/json" : "application/x-www-form-urlencoded",
|
|
439
|
+
Accept: "application/json",
|
|
440
|
+
...options.headers
|
|
441
|
+
},
|
|
442
|
+
body: isJson ? JSON.stringify(body) : body.toString()
|
|
443
|
+
});
|
|
444
|
+
if (!response.ok) {
|
|
445
|
+
const detail = options.includeBody ? await response.text().catch(() => "") : "";
|
|
446
|
+
const status = options.includeStatus ? ` (${response.status})` : "";
|
|
447
|
+
throw new Error(`${options.errorPrefix}${status}${detail ? `: ${detail}` : ""}`);
|
|
448
|
+
}
|
|
449
|
+
return response.json();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/oauth/antigravity-oauth.ts
|
|
453
|
+
var DEFAULT_ANTIGRAVITY_CLIENT_ID = ["107100606059", "1-tmhssin2h2", "1lcre235vtol", "ojh4g403ep.a", "pps.googleus", "ercontent.co", "m"].join("");
|
|
454
|
+
var DEFAULT_ANTIGRAVITY_CLIENT_SECRET = ["GOCS", "PX-K", "58FW", "R486", "LdLJ", "1mLB", "8sXC", "4z6q", "DAf"].join("");
|
|
455
|
+
var ANTIGRAVITY_CLIENT_ID = process.env.ANTIGRAVITY_OAUTH_CLIENT_ID ?? DEFAULT_ANTIGRAVITY_CLIENT_ID;
|
|
456
|
+
var ANTIGRAVITY_CLIENT_SECRET = process.env.ANTIGRAVITY_OAUTH_CLIENT_SECRET ?? DEFAULT_ANTIGRAVITY_CLIENT_SECRET;
|
|
457
|
+
var AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
458
|
+
var TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
459
|
+
var USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo";
|
|
460
|
+
var SCOPES = [
|
|
461
|
+
"openid",
|
|
462
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
463
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
464
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
465
|
+
"https://www.googleapis.com/auth/cclog",
|
|
466
|
+
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
467
|
+
].join(" ");
|
|
468
|
+
var ANTIGRAVITY_VERSION = "4.2.0";
|
|
469
|
+
var ANTIGRAVITY_USER_AGENT = `vscode/1.X.X (Antigravity/${ANTIGRAVITY_VERSION})`;
|
|
470
|
+
var ANTIGRAVITY_METADATA = { ideType: "ANTIGRAVITY" };
|
|
471
|
+
var ANTIGRAVITY_BASE_URLS = [
|
|
472
|
+
"https://daily-cloudcode-pa.googleapis.com",
|
|
473
|
+
"https://cloudcode-pa.googleapis.com",
|
|
474
|
+
"https://daily-cloudcode-pa.sandbox.googleapis.com"
|
|
475
|
+
];
|
|
476
|
+
var ANTIGRAVITY_API_VERSION = "v1internal";
|
|
477
|
+
async function buildAntigravityAuthUrl(redirectUri) {
|
|
478
|
+
const { verifier, challenge } = await generatePkce();
|
|
479
|
+
const state = generateOAuthState();
|
|
480
|
+
const params = new URLSearchParams({
|
|
481
|
+
client_id: ANTIGRAVITY_CLIENT_ID,
|
|
482
|
+
response_type: "code",
|
|
483
|
+
redirect_uri: redirectUri,
|
|
484
|
+
scope: SCOPES,
|
|
485
|
+
state,
|
|
486
|
+
access_type: "offline",
|
|
487
|
+
prompt: "consent",
|
|
488
|
+
code_challenge: challenge,
|
|
489
|
+
code_challenge_method: "S256"
|
|
490
|
+
});
|
|
491
|
+
return { authUrl: `${AUTHORIZE_URL}?${params}`, codeVerifier: verifier, oauthState: state, redirectUri };
|
|
492
|
+
}
|
|
493
|
+
async function exchangeAntigravityToken(code, codeVerifier, redirectUri) {
|
|
494
|
+
const body = new URLSearchParams({
|
|
495
|
+
grant_type: "authorization_code",
|
|
496
|
+
client_id: ANTIGRAVITY_CLIENT_ID,
|
|
497
|
+
client_secret: ANTIGRAVITY_CLIENT_SECRET,
|
|
498
|
+
code,
|
|
499
|
+
redirect_uri: redirectUri,
|
|
500
|
+
code_verifier: codeVerifier
|
|
501
|
+
});
|
|
502
|
+
const res = await fetch(TOKEN_URL, {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers: {
|
|
505
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
506
|
+
Accept: "application/json",
|
|
507
|
+
"User-Agent": ANTIGRAVITY_USER_AGENT
|
|
508
|
+
},
|
|
509
|
+
body
|
|
510
|
+
});
|
|
511
|
+
if (!res.ok) throw new Error(`Antigravity token exchange failed: ${await res.text()}`);
|
|
512
|
+
return res.json();
|
|
513
|
+
}
|
|
514
|
+
async function refreshAntigravityToken(refreshToken) {
|
|
515
|
+
return postOAuthRefresh(
|
|
516
|
+
TOKEN_URL,
|
|
517
|
+
new URLSearchParams({
|
|
518
|
+
grant_type: "refresh_token",
|
|
519
|
+
client_id: ANTIGRAVITY_CLIENT_ID,
|
|
520
|
+
client_secret: ANTIGRAVITY_CLIENT_SECRET,
|
|
521
|
+
refresh_token: refreshToken
|
|
522
|
+
}),
|
|
523
|
+
{
|
|
524
|
+
contentType: "form",
|
|
525
|
+
errorPrefix: "Antigravity token refresh failed",
|
|
526
|
+
includeBody: true
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
async function fetchUserInfo(accessToken) {
|
|
531
|
+
try {
|
|
532
|
+
const res = await fetch(`${USER_INFO_URL}?alt=json`, {
|
|
533
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
534
|
+
});
|
|
535
|
+
if (!res.ok) return {};
|
|
536
|
+
const data = await res.json();
|
|
537
|
+
return {
|
|
538
|
+
email: typeof data.email === "string" ? data.email : void 0,
|
|
539
|
+
name: typeof data.name === "string" ? data.name : void 0
|
|
540
|
+
};
|
|
541
|
+
} catch {
|
|
542
|
+
return {};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function apiHeaders(accessToken) {
|
|
546
|
+
return {
|
|
547
|
+
"Content-Type": "application/json",
|
|
548
|
+
Accept: "application/json",
|
|
549
|
+
Authorization: `Bearer ${accessToken}`,
|
|
550
|
+
"User-Agent": ANTIGRAVITY_USER_AGENT
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
async function fetchFirstOk(paths, init) {
|
|
554
|
+
let lastErr;
|
|
555
|
+
for (const url of paths) {
|
|
556
|
+
try {
|
|
557
|
+
const res = await fetch(url, init);
|
|
558
|
+
if (res.ok) return res;
|
|
559
|
+
lastErr = new Error(`${res.status} ${await res.text()}`);
|
|
560
|
+
} catch (err) {
|
|
561
|
+
lastErr = err;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
throw lastErr ?? new Error("All Antigravity endpoints failed");
|
|
565
|
+
}
|
|
566
|
+
function toRecord(v) {
|
|
567
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
568
|
+
}
|
|
569
|
+
function pickTierId(tier) {
|
|
570
|
+
const v = toRecord(tier).id;
|
|
571
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
572
|
+
}
|
|
573
|
+
function findDefaultAllowedTier(sub) {
|
|
574
|
+
if (!Array.isArray(sub.allowedTiers)) return null;
|
|
575
|
+
for (const t of sub.allowedTiers) {
|
|
576
|
+
const tier = toRecord(t);
|
|
577
|
+
if (tier.isDefault) return tier;
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
function resolveAntigravityOnboardTierId(data) {
|
|
582
|
+
const sub = toRecord(data);
|
|
583
|
+
const hasIneligible = Array.isArray(sub.ineligibleTiers) && sub.ineligibleTiers.length > 0;
|
|
584
|
+
if (!hasIneligible) {
|
|
585
|
+
const current = pickTierId(sub.currentTier);
|
|
586
|
+
if (current) return current;
|
|
587
|
+
}
|
|
588
|
+
const def = findDefaultAllowedTier(sub);
|
|
589
|
+
if (def) {
|
|
590
|
+
const defId = pickTierId(def);
|
|
591
|
+
if (defId) return defId;
|
|
592
|
+
}
|
|
593
|
+
const paid = pickTierId(sub.paidTier);
|
|
594
|
+
if (paid) return paid;
|
|
595
|
+
return pickTierId(sub.currentTier) ?? "legacy-tier";
|
|
596
|
+
}
|
|
597
|
+
async function loadCodeAssist(accessToken) {
|
|
598
|
+
const endpoints = ANTIGRAVITY_BASE_URLS.map((b) => `${b}/${ANTIGRAVITY_API_VERSION}:loadCodeAssist`);
|
|
599
|
+
const res = await fetchFirstOk(endpoints, {
|
|
600
|
+
method: "POST",
|
|
601
|
+
headers: apiHeaders(accessToken),
|
|
602
|
+
body: JSON.stringify({ metadata: ANTIGRAVITY_METADATA })
|
|
603
|
+
});
|
|
604
|
+
const data = await res.json();
|
|
605
|
+
let projectId = data.cloudaicompanionProject;
|
|
606
|
+
if (typeof projectId === "object" && projectId !== null) {
|
|
607
|
+
projectId = projectId.id ?? "";
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
projectId: typeof projectId === "string" ? projectId : "",
|
|
611
|
+
tierId: resolveAntigravityOnboardTierId(data)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async function onboardUser(accessToken, tierId, maxAttempts = 10) {
|
|
615
|
+
const endpoints = ANTIGRAVITY_BASE_URLS.map((b) => `${b}/${ANTIGRAVITY_API_VERSION}:onboardUser`);
|
|
616
|
+
let finalProjectId = "";
|
|
617
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
618
|
+
const res = await fetchFirstOk(endpoints, {
|
|
619
|
+
method: "POST",
|
|
620
|
+
headers: apiHeaders(accessToken),
|
|
621
|
+
body: JSON.stringify({ tier_id: tierId, metadata: ANTIGRAVITY_METADATA })
|
|
622
|
+
});
|
|
623
|
+
const result = await res.json();
|
|
624
|
+
if (result.done === true) {
|
|
625
|
+
const p = result.response ? result.response.cloudaicompanionProject : void 0;
|
|
626
|
+
if (typeof p === "string") finalProjectId = p.trim();
|
|
627
|
+
else if (p && typeof p === "object") finalProjectId = String(p.id ?? "") || finalProjectId;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
if (i < maxAttempts - 1) await new Promise((r) => setTimeout(r, 5e3));
|
|
631
|
+
}
|
|
632
|
+
return finalProjectId;
|
|
633
|
+
}
|
|
634
|
+
function readAgyProjectId() {
|
|
635
|
+
try {
|
|
636
|
+
const cache = pathJoin(homedir3(), ".gemini", "antigravity-cli", "cache", "projects.json");
|
|
637
|
+
const data = JSON.parse(readFileSync2(cache, "utf8"));
|
|
638
|
+
return data[homedir3()] ?? Object.values(data)[0] ?? "";
|
|
639
|
+
} catch {
|
|
640
|
+
return "";
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function runBootstrap(tokens) {
|
|
644
|
+
const [userInfoResult, bootstrapResult] = await Promise.allSettled([
|
|
645
|
+
fetchUserInfo(tokens.access_token),
|
|
646
|
+
loadCodeAssist(tokens.access_token)
|
|
647
|
+
]);
|
|
648
|
+
const userInfo = userInfoResult.status === "fulfilled" ? userInfoResult.value : {};
|
|
649
|
+
let projectId = bootstrapResult.status === "fulfilled" ? bootstrapResult.value.projectId : "";
|
|
650
|
+
const tierId = bootstrapResult.status === "fulfilled" ? bootstrapResult.value.tierId : "free-tier";
|
|
651
|
+
const finalProjectId = await onboardUser(tokens.access_token, tierId, 3).catch(() => "");
|
|
652
|
+
if (finalProjectId) projectId = finalProjectId;
|
|
653
|
+
if (!projectId && tierId !== "free-tier") {
|
|
654
|
+
const freeTierProjectId = await onboardUser(tokens.access_token, "free-tier", 3).catch(() => "");
|
|
655
|
+
if (freeTierProjectId) projectId = freeTierProjectId;
|
|
656
|
+
}
|
|
657
|
+
if (!projectId) {
|
|
658
|
+
projectId = readAgyProjectId();
|
|
659
|
+
}
|
|
660
|
+
return { tokens, userInfo, projectId, tierId };
|
|
661
|
+
}
|
|
662
|
+
async function runAntigravityOAuthFlow(onAuthUrl) {
|
|
663
|
+
const server = await startCallbackServer();
|
|
664
|
+
try {
|
|
665
|
+
const { authUrl, codeVerifier, redirectUri } = await buildAntigravityAuthUrl(server.redirectUri);
|
|
666
|
+
onAuthUrl(authUrl);
|
|
667
|
+
open(authUrl).catch(() => {
|
|
668
|
+
});
|
|
669
|
+
const { code } = await server.waitForCallback();
|
|
670
|
+
if (!code) throw new Error("No authorization code received from Google");
|
|
671
|
+
const tokens = await exchangeAntigravityToken(code, codeVerifier, redirectUri);
|
|
672
|
+
return runBootstrap(tokens);
|
|
673
|
+
} finally {
|
|
674
|
+
server.close();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function completeAntigravityExchange(code, codeVerifier, redirectUri) {
|
|
678
|
+
const tokens = await exchangeAntigravityToken(code, codeVerifier, redirectUri);
|
|
679
|
+
return runBootstrap(tokens);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/registry/opencode-auth.ts
|
|
683
|
+
import { existsSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
684
|
+
import { homedir as homedir4 } from "os";
|
|
685
|
+
import { join as join3 } from "path";
|
|
686
|
+
function resolveOpencodeAuthPath(env = process.env) {
|
|
687
|
+
const dataHome = env["XDG_DATA_HOME"] ?? join3(homedir4(), ".local", "share");
|
|
688
|
+
if (process.platform === "win32") {
|
|
689
|
+
return join3(env["APPDATA"] ?? join3(homedir4(), "AppData", "Roaming"), "opencode", "auth.json");
|
|
690
|
+
}
|
|
691
|
+
return join3(dataHome, "opencode", "auth.json");
|
|
692
|
+
}
|
|
693
|
+
function decodeAuthEntry(value) {
|
|
694
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
695
|
+
if (!value || typeof value !== "object") return null;
|
|
696
|
+
const record = value;
|
|
697
|
+
if (record["type"] === "oauth" && typeof record["access"] === "string" && typeof record["refresh"] === "string" && typeof record["expires"] === "number") {
|
|
698
|
+
return {
|
|
699
|
+
type: "oauth",
|
|
700
|
+
access: record["access"],
|
|
701
|
+
refresh: record["refresh"],
|
|
702
|
+
expires: record["expires"],
|
|
703
|
+
accountId: typeof record["accountId"] === "string" ? record["accountId"] : void 0,
|
|
704
|
+
enterpriseUrl: typeof record["enterpriseUrl"] === "string" ? record["enterpriseUrl"] : void 0,
|
|
705
|
+
providerData: record["providerData"] && typeof record["providerData"] === "object" && !Array.isArray(record["providerData"]) ? record["providerData"] : void 0
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
if (record["type"] === "wellknown" && typeof record["key"] === "string" && typeof record["token"] === "string") {
|
|
709
|
+
return { type: "wellknown", key: record["key"], token: record["token"] };
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
function authFilePermissionWarning(path) {
|
|
714
|
+
if (!existsSync(path)) return void 0;
|
|
715
|
+
if (process.platform === "win32") return void 0;
|
|
716
|
+
try {
|
|
717
|
+
const mode = statSync(path).mode & 511;
|
|
718
|
+
if (mode & 63) {
|
|
719
|
+
return `OpenCode auth file ${path} is readable by others (mode ${mode.toString(8)}). Consider chmod 600.`;
|
|
720
|
+
}
|
|
721
|
+
} catch {
|
|
722
|
+
}
|
|
723
|
+
return void 0;
|
|
724
|
+
}
|
|
725
|
+
function readOpencodeAuthFile(env = process.env) {
|
|
726
|
+
const path = resolveOpencodeAuthPath(env);
|
|
727
|
+
if (!existsSync(path)) return null;
|
|
728
|
+
let parsed;
|
|
729
|
+
try {
|
|
730
|
+
parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
731
|
+
} catch {
|
|
732
|
+
return { path, entries: {}, permissionWarning: authFilePermissionWarning(path) };
|
|
733
|
+
}
|
|
734
|
+
const entries = {};
|
|
735
|
+
if (parsed && typeof parsed === "object") {
|
|
736
|
+
for (const [providerId, value] of Object.entries(parsed)) {
|
|
737
|
+
const entry = decodeAuthEntry(value);
|
|
738
|
+
if (entry) entries[providerId] = entry;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
path,
|
|
743
|
+
entries,
|
|
744
|
+
permissionWarning: authFilePermissionWarning(path)
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function isOpencodeOAuth(entry) {
|
|
748
|
+
return !!entry && typeof entry === "object" && entry.type === "oauth";
|
|
749
|
+
}
|
|
750
|
+
function oauthCredentialToKeychainJson(cred) {
|
|
751
|
+
return JSON.stringify(cred);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/oauth/types.ts
|
|
755
|
+
function tokensToStoredCredential(tokens, existingRefresh, accountId, providerData) {
|
|
756
|
+
const mergedProviderData = providerData || tokens.providerData ? { ...providerData, ...tokens.providerData } : void 0;
|
|
757
|
+
return {
|
|
758
|
+
type: "oauth",
|
|
759
|
+
access: tokens.access_token,
|
|
760
|
+
refresh: tokens.refresh_token ?? existingRefresh ?? "",
|
|
761
|
+
expires: Date.now() + (tokens.expires_in ?? 3600) * 1e3,
|
|
762
|
+
...accountId ? { accountId } : {},
|
|
763
|
+
...mergedProviderData ? { providerData: mergedProviderData } : {}
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function parseStoredOAuthCredential(raw) {
|
|
767
|
+
if (!raw?.trim().startsWith("{")) return null;
|
|
768
|
+
try {
|
|
769
|
+
const parsed = JSON.parse(raw);
|
|
770
|
+
if (parsed.type === "oauth" && typeof parsed.access === "string" && typeof parsed.refresh === "string" && typeof parsed.expires === "number") {
|
|
771
|
+
return parsed;
|
|
772
|
+
}
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
var OAUTH_REFRESH_SKEW_MS = 12e4;
|
|
778
|
+
function oauthCredentialNeedsRefresh(cred, skewMs = OAUTH_REFRESH_SKEW_MS) {
|
|
779
|
+
return cred.expires <= Date.now() + Math.max(0, skewMs);
|
|
780
|
+
}
|
|
781
|
+
function accessTokenIsExpiring(token, skewMs = OAUTH_REFRESH_SKEW_MS) {
|
|
782
|
+
if (!token) return false;
|
|
783
|
+
const parts = token.split(".");
|
|
784
|
+
if (parts.length < 2) return false;
|
|
785
|
+
try {
|
|
786
|
+
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
787
|
+
while (payload.length % 4 !== 0) payload += "=";
|
|
788
|
+
const claims = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
789
|
+
if (typeof claims.exp !== "number") return false;
|
|
790
|
+
return claims.exp * 1e3 <= Date.now() + Math.max(0, skewMs);
|
|
791
|
+
} catch {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
var NATIVE_OAUTH_PROVIDER_IDS = ["xai", "xai-oauth", "openai", "openai-oauth", "github-copilot", "claude-code", "antigravity", "cline-pass"];
|
|
796
|
+
function supportsNativeOAuth(providerId) {
|
|
797
|
+
return NATIVE_OAUTH_PROVIDER_IDS.includes(providerId);
|
|
798
|
+
}
|
|
799
|
+
var BROWSER_REDIRECT_OAUTH_IDS = ["claude-code", "antigravity"];
|
|
800
|
+
function isBrowserRedirectOAuth(id) {
|
|
801
|
+
return BROWSER_REDIRECT_OAUTH_IDS.includes(id);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/oauth/openai.ts
|
|
805
|
+
var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
806
|
+
var ISSUER = "https://auth.openai.com";
|
|
807
|
+
var OAUTH_POLLING_SAFETY_MARGIN_MS = 3e3;
|
|
808
|
+
var DEVICE_CODE_DEFAULT_EXPIRES_MS = 5 * 60 * 1e3;
|
|
809
|
+
function extractOpenAiAccountId(tokens) {
|
|
810
|
+
const token = tokens.id_token ?? tokens.access_token;
|
|
811
|
+
if (!token) return void 0;
|
|
812
|
+
const parts = token.split(".");
|
|
813
|
+
if (parts.length !== 3) return void 0;
|
|
814
|
+
try {
|
|
815
|
+
const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
816
|
+
return claims.chatgpt_account_id ?? claims["https://api.openai.com/auth"]?.chatgpt_account_id ?? claims.organizations?.[0]?.id;
|
|
817
|
+
} catch {
|
|
818
|
+
return void 0;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async function requestOpenAiDeviceCode() {
|
|
822
|
+
const response = await fetch(`${ISSUER}/api/accounts/deviceauth/usercode`, {
|
|
823
|
+
method: "POST",
|
|
824
|
+
headers: {
|
|
825
|
+
"Content-Type": "application/json",
|
|
826
|
+
"User-Agent": `relay-ai/${VERSION}`
|
|
827
|
+
},
|
|
828
|
+
body: JSON.stringify({ client_id: CLIENT_ID })
|
|
829
|
+
});
|
|
830
|
+
if (!response.ok) {
|
|
831
|
+
throw new Error("Failed to initiate OpenAI device authorization");
|
|
832
|
+
}
|
|
833
|
+
return response.json();
|
|
834
|
+
}
|
|
835
|
+
function openAiDeviceCodeUrl() {
|
|
836
|
+
return `${ISSUER}/codex/device`;
|
|
837
|
+
}
|
|
838
|
+
async function pollOpenAiDeviceCodeToken(deviceData, opts) {
|
|
839
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
840
|
+
const now = opts?.now ?? (() => Date.now());
|
|
841
|
+
const intervalMs = Math.max(parseInt(deviceData.interval, 10) || 5, 1) * 1e3;
|
|
842
|
+
const deadline = now() + positiveSecondsToMs(deviceData.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS);
|
|
843
|
+
while (now() < deadline) {
|
|
844
|
+
const response = await fetch(`${ISSUER}/api/accounts/deviceauth/token`, {
|
|
845
|
+
method: "POST",
|
|
846
|
+
headers: {
|
|
847
|
+
"Content-Type": "application/json",
|
|
848
|
+
"User-Agent": `relay-ai/${VERSION}`
|
|
849
|
+
},
|
|
850
|
+
body: JSON.stringify({
|
|
851
|
+
device_auth_id: deviceData.device_auth_id,
|
|
852
|
+
user_code: deviceData.user_code
|
|
853
|
+
})
|
|
854
|
+
});
|
|
855
|
+
if (response.ok) {
|
|
856
|
+
const data = await response.json();
|
|
857
|
+
const tokenResponse = await fetch(`${ISSUER}/oauth/token`, {
|
|
858
|
+
method: "POST",
|
|
859
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
860
|
+
body: new URLSearchParams({
|
|
861
|
+
grant_type: "authorization_code",
|
|
862
|
+
code: data.authorization_code,
|
|
863
|
+
redirect_uri: `${ISSUER}/deviceauth/callback`,
|
|
864
|
+
client_id: CLIENT_ID,
|
|
865
|
+
code_verifier: data.code_verifier
|
|
866
|
+
}).toString()
|
|
867
|
+
});
|
|
868
|
+
if (!tokenResponse.ok) {
|
|
869
|
+
throw new Error(`OpenAI token exchange failed (${tokenResponse.status})`);
|
|
870
|
+
}
|
|
871
|
+
const tokens = await tokenResponse.json();
|
|
872
|
+
return { tokens, accountId: extractOpenAiAccountId(tokens) };
|
|
873
|
+
}
|
|
874
|
+
if (response.status !== 403 && response.status !== 404) {
|
|
875
|
+
throw new Error(`OpenAI device authorization failed (${response.status})`);
|
|
876
|
+
}
|
|
877
|
+
await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS, Math.max(0, deadline - now())));
|
|
878
|
+
}
|
|
879
|
+
throw new Error("OpenAI device authorization timed out");
|
|
880
|
+
}
|
|
881
|
+
async function refreshOpenAiAccessToken(refreshToken) {
|
|
882
|
+
return postOAuthRefresh(
|
|
883
|
+
`${ISSUER}/oauth/token`,
|
|
884
|
+
new URLSearchParams({
|
|
885
|
+
grant_type: "refresh_token",
|
|
886
|
+
refresh_token: refreshToken,
|
|
887
|
+
client_id: CLIENT_ID
|
|
888
|
+
}),
|
|
889
|
+
{
|
|
890
|
+
contentType: "form",
|
|
891
|
+
errorPrefix: "OpenAI token refresh failed",
|
|
892
|
+
includeStatus: true
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
async function runOpenAiDeviceCodeFlow(onDeviceCode, opts) {
|
|
897
|
+
const deviceData = await requestOpenAiDeviceCode();
|
|
898
|
+
onDeviceCode({ url: openAiDeviceCodeUrl(), userCode: deviceData.user_code });
|
|
899
|
+
return pollOpenAiDeviceCodeToken(deviceData, opts);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/oauth/github.ts
|
|
903
|
+
var CLIENT_ID2 = "Iv1.b507a08c87ecfe98";
|
|
904
|
+
var DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
905
|
+
var TOKEN_URL2 = "https://github.com/login/oauth/access_token";
|
|
906
|
+
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
907
|
+
var COPILOT_USER_URL = "https://api.github.com/copilot_internal/user";
|
|
908
|
+
var SCOPE = "copilot";
|
|
909
|
+
var DEVICE_CODE_DEFAULT_INTERVAL_MS = 5e3;
|
|
910
|
+
var DEVICE_CODE_DEFAULT_EXPIRES_MS2 = 15 * 60 * 1e3;
|
|
911
|
+
var OAUTH_POLLING_SAFETY_MARGIN_MS2 = 1e3;
|
|
912
|
+
var FREE_COPILOT_SKUS = /* @__PURE__ */ new Set([
|
|
913
|
+
"free_limited_copilot",
|
|
914
|
+
"free_educational_quota",
|
|
915
|
+
"no_auth_limited_copilot"
|
|
916
|
+
]);
|
|
917
|
+
function commonHeaders() {
|
|
918
|
+
return {
|
|
919
|
+
Accept: "application/json",
|
|
920
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
921
|
+
"User-Agent": `relay-ai/${VERSION}`
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function classifyCopilotAccount(user) {
|
|
925
|
+
const login = typeof user["login"] === "string" && user["login"].trim() ? user["login"].trim() : void 0;
|
|
926
|
+
const sku = typeof user["access_type_sku"] === "string" && user["access_type_sku"].trim() ? user["access_type_sku"].trim() : void 0;
|
|
927
|
+
const plan = typeof user["copilot_plan"] === "string" && user["copilot_plan"].trim() ? user["copilot_plan"].trim() : void 0;
|
|
928
|
+
if (!sku && !plan) {
|
|
929
|
+
return {
|
|
930
|
+
...login ? { login } : {},
|
|
931
|
+
lookup_status: "unknown"
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
const isFree = FREE_COPILOT_SKUS.has(sku?.toLowerCase() ?? "") || plan?.toLowerCase() === "free";
|
|
935
|
+
return {
|
|
936
|
+
...login ? { login } : {},
|
|
937
|
+
...sku ? { access_type_sku: sku } : {},
|
|
938
|
+
...plan ? { copilot_plan: plan } : {},
|
|
939
|
+
is_free_plan: isFree,
|
|
940
|
+
lookup_status: "known"
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
async function fetchCopilotAccount(ghuToken) {
|
|
944
|
+
const response = await fetch(COPILOT_USER_URL, {
|
|
945
|
+
method: "GET",
|
|
946
|
+
headers: {
|
|
947
|
+
Authorization: `Bearer ${ghuToken}`,
|
|
948
|
+
Accept: "application/json",
|
|
949
|
+
"User-Agent": `relay-ai/${VERSION}`,
|
|
950
|
+
"Editor-Version": "vscode/1.85.1",
|
|
951
|
+
"X-GitHub-Api-Version": "2025-04-01"
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
if (!response.ok) {
|
|
955
|
+
const detail = await response.text().catch(() => "");
|
|
956
|
+
throw new Error(`GitHub Copilot account lookup failed (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
957
|
+
}
|
|
958
|
+
const json = await response.json();
|
|
959
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
960
|
+
throw new Error("GitHub Copilot account lookup returned invalid JSON");
|
|
961
|
+
}
|
|
962
|
+
return classifyCopilotAccount(json);
|
|
963
|
+
}
|
|
964
|
+
async function requestGithubDeviceCode() {
|
|
965
|
+
const response = await fetch(DEVICE_CODE_URL, {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: commonHeaders(),
|
|
968
|
+
body: new URLSearchParams({ client_id: CLIENT_ID2, scope: SCOPE }).toString()
|
|
969
|
+
});
|
|
970
|
+
if (!response.ok) {
|
|
971
|
+
const detail = await response.text().catch(() => "");
|
|
972
|
+
throw new Error(`GitHub device code request failed (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
973
|
+
}
|
|
974
|
+
const json = await response.json();
|
|
975
|
+
if (!json.device_code || !json.user_code || !json.verification_uri) {
|
|
976
|
+
throw new Error("GitHub device code response is missing required fields");
|
|
977
|
+
}
|
|
978
|
+
return json;
|
|
979
|
+
}
|
|
980
|
+
async function exchangeForCopilotToken(ghuToken) {
|
|
981
|
+
const response = await fetch(COPILOT_TOKEN_URL, {
|
|
982
|
+
method: "GET",
|
|
983
|
+
headers: {
|
|
984
|
+
Authorization: `Bearer ${ghuToken}`,
|
|
985
|
+
"User-Agent": `relay-ai/${VERSION}`,
|
|
986
|
+
Accept: "application/json"
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
if (!response.ok) {
|
|
990
|
+
const msg = await response.text().catch(() => "");
|
|
991
|
+
throw new Error(`GitHub Copilot token exchange failed (${response.status})${msg ? `: ${msg}` : ""}`);
|
|
992
|
+
}
|
|
993
|
+
const json = await response.json();
|
|
994
|
+
if (!json.token) {
|
|
995
|
+
throw new Error("GitHub Copilot token exchange response missing token field \u2014 is Copilot subscription active?");
|
|
996
|
+
}
|
|
997
|
+
let expiresIn = 1800;
|
|
998
|
+
if (json.expires_at) {
|
|
999
|
+
const expiresMs = new Date(json.expires_at).getTime() - Date.now();
|
|
1000
|
+
if (expiresMs > 0) expiresIn = Math.floor(expiresMs / 1e3);
|
|
1001
|
+
}
|
|
1002
|
+
let account = { lookup_status: "unknown" };
|
|
1003
|
+
try {
|
|
1004
|
+
account = await fetchCopilotAccount(ghuToken);
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
access_token: json.token,
|
|
1009
|
+
expires_in: expiresIn,
|
|
1010
|
+
providerData: { copilot: account }
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
async function refreshGithubCopilotToken(ghuToken) {
|
|
1014
|
+
const copilot = await exchangeForCopilotToken(ghuToken);
|
|
1015
|
+
return {
|
|
1016
|
+
...copilot,
|
|
1017
|
+
refresh_token: ghuToken
|
|
1018
|
+
// keep the same ghu_ token as refresh
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
async function pollGithubDeviceCodeToken(device, opts) {
|
|
1022
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
1023
|
+
const now = opts?.now ?? (() => Date.now());
|
|
1024
|
+
const deadline = now() + positiveSecondsToMs(device.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS2);
|
|
1025
|
+
let intervalMs = Math.max(
|
|
1026
|
+
positiveSecondsToMs(device.interval, DEVICE_CODE_DEFAULT_INTERVAL_MS),
|
|
1027
|
+
1e3
|
|
1028
|
+
);
|
|
1029
|
+
while (now() < deadline) {
|
|
1030
|
+
const response = await fetch(TOKEN_URL2, {
|
|
1031
|
+
method: "POST",
|
|
1032
|
+
headers: commonHeaders(),
|
|
1033
|
+
body: new URLSearchParams({
|
|
1034
|
+
client_id: CLIENT_ID2,
|
|
1035
|
+
device_code: device.device_code,
|
|
1036
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1037
|
+
}).toString()
|
|
1038
|
+
});
|
|
1039
|
+
const body = await response.json().catch(() => ({}));
|
|
1040
|
+
const error = body["error"];
|
|
1041
|
+
if (!error && body["access_token"]) {
|
|
1042
|
+
const ghuToken = body["access_token"];
|
|
1043
|
+
const copilot = await exchangeForCopilotToken(ghuToken);
|
|
1044
|
+
return {
|
|
1045
|
+
access_token: copilot.access_token,
|
|
1046
|
+
refresh_token: ghuToken,
|
|
1047
|
+
// store ghu_ as refresh for re-exchange later
|
|
1048
|
+
expires_in: copilot.expires_in,
|
|
1049
|
+
providerData: copilot.providerData
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
if (error === "authorization_pending") {
|
|
1053
|
+
await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS2, Math.max(0, deadline - now())));
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (error === "slow_down") {
|
|
1057
|
+
intervalMs += 5e3;
|
|
1058
|
+
await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS2, Math.max(0, deadline - now())));
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (error === "expired_token") {
|
|
1062
|
+
throw new Error("GitHub device code expired \u2014 please run relay-ai providers auth github-copilot again");
|
|
1063
|
+
}
|
|
1064
|
+
throw new Error(`GitHub device authorization failed${error ? `: ${error}` : ""}`);
|
|
1065
|
+
}
|
|
1066
|
+
throw new Error("GitHub device authorization timed out");
|
|
1067
|
+
}
|
|
1068
|
+
async function runGithubDeviceCodeFlow(onDeviceCode, opts) {
|
|
1069
|
+
const device = await requestGithubDeviceCode();
|
|
1070
|
+
onDeviceCode({ url: device.verification_uri, userCode: device.user_code });
|
|
1071
|
+
return pollGithubDeviceCodeToken(device, opts);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/oauth/xai.ts
|
|
1075
|
+
var CLIENT_ID3 = "b1a00492-073a-47ea-816f-4c329264a828";
|
|
1076
|
+
var TOKEN_URL3 = "https://auth.x.ai/oauth2/token";
|
|
1077
|
+
var DEVICE_AUTHORIZATION_URL = "https://auth.x.ai/oauth2/device/code";
|
|
1078
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
1079
|
+
var SCOPE2 = "openid profile email offline_access grok-cli:access api:access";
|
|
1080
|
+
var DEVICE_CODE_DEFAULT_INTERVAL_MS2 = 5e3;
|
|
1081
|
+
var DEVICE_CODE_MIN_INTERVAL_MS = 1e3;
|
|
1082
|
+
var DEVICE_CODE_SLOW_DOWN_INCREMENT_MS = 5e3;
|
|
1083
|
+
var DEVICE_CODE_DEFAULT_EXPIRES_MS3 = 5 * 60 * 1e3;
|
|
1084
|
+
var OAUTH_POLLING_SAFETY_MARGIN_MS3 = 3e3;
|
|
1085
|
+
function authHeaders() {
|
|
1086
|
+
return {
|
|
1087
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1088
|
+
Accept: "application/json",
|
|
1089
|
+
"User-Agent": `relay-ai/${VERSION}`
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
async function requestXaiDeviceCode() {
|
|
1093
|
+
const response = await fetch(DEVICE_AUTHORIZATION_URL, {
|
|
1094
|
+
method: "POST",
|
|
1095
|
+
headers: authHeaders(),
|
|
1096
|
+
body: new URLSearchParams({
|
|
1097
|
+
client_id: CLIENT_ID3,
|
|
1098
|
+
scope: SCOPE2
|
|
1099
|
+
}).toString()
|
|
1100
|
+
});
|
|
1101
|
+
if (!response.ok) {
|
|
1102
|
+
const detail = await response.text().catch(() => "");
|
|
1103
|
+
throw new Error(`xAI device code request failed (${response.status})${detail ? `: ${detail}` : ""}`);
|
|
1104
|
+
}
|
|
1105
|
+
const json = await response.json();
|
|
1106
|
+
if (!json.device_code || !json.user_code || !json.verification_uri) {
|
|
1107
|
+
throw new Error("xAI device code response is missing required fields");
|
|
1108
|
+
}
|
|
1109
|
+
return json;
|
|
1110
|
+
}
|
|
1111
|
+
async function pollXaiDeviceCodeToken(device, opts) {
|
|
1112
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
1113
|
+
const now = opts?.now ?? (() => Date.now());
|
|
1114
|
+
const deadline = now() + positiveSecondsToMs(device.expires_in, DEVICE_CODE_DEFAULT_EXPIRES_MS3);
|
|
1115
|
+
let intervalMs = Math.max(
|
|
1116
|
+
positiveSecondsToMs(device.interval, DEVICE_CODE_DEFAULT_INTERVAL_MS2),
|
|
1117
|
+
DEVICE_CODE_MIN_INTERVAL_MS
|
|
1118
|
+
);
|
|
1119
|
+
while (now() < deadline) {
|
|
1120
|
+
const response = await fetch(TOKEN_URL3, {
|
|
1121
|
+
method: "POST",
|
|
1122
|
+
headers: authHeaders(),
|
|
1123
|
+
body: new URLSearchParams({
|
|
1124
|
+
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
1125
|
+
client_id: CLIENT_ID3,
|
|
1126
|
+
device_code: device.device_code
|
|
1127
|
+
}).toString()
|
|
1128
|
+
});
|
|
1129
|
+
if (response.ok) return response.json();
|
|
1130
|
+
const body = await response.json().catch(() => ({}));
|
|
1131
|
+
const remaining = Math.max(0, deadline - now());
|
|
1132
|
+
if (body.error === "authorization_pending") {
|
|
1133
|
+
await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS3, remaining));
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
if (body.error === "slow_down") {
|
|
1137
|
+
intervalMs += DEVICE_CODE_SLOW_DOWN_INCREMENT_MS;
|
|
1138
|
+
await sleep(Math.min(intervalMs + OAUTH_POLLING_SAFETY_MARGIN_MS3, remaining));
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
throw new Error(`xAI device authorization failed${body.error ? `: ${body.error}` : ""}`);
|
|
1142
|
+
}
|
|
1143
|
+
throw new Error("xAI device authorization timed out");
|
|
1144
|
+
}
|
|
1145
|
+
async function refreshXaiAccessToken(refreshToken) {
|
|
1146
|
+
return postOAuthRefresh(
|
|
1147
|
+
TOKEN_URL3,
|
|
1148
|
+
new URLSearchParams({
|
|
1149
|
+
grant_type: "refresh_token",
|
|
1150
|
+
refresh_token: refreshToken,
|
|
1151
|
+
client_id: CLIENT_ID3
|
|
1152
|
+
}),
|
|
1153
|
+
{
|
|
1154
|
+
contentType: "form",
|
|
1155
|
+
errorPrefix: "xAI token refresh failed",
|
|
1156
|
+
includeStatus: true,
|
|
1157
|
+
includeBody: true,
|
|
1158
|
+
headers: authHeaders()
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
async function runXaiDeviceCodeFlow(onDeviceCode, opts) {
|
|
1163
|
+
const device = await requestXaiDeviceCode();
|
|
1164
|
+
onDeviceCode({
|
|
1165
|
+
url: device.verification_uri_complete ?? device.verification_uri,
|
|
1166
|
+
userCode: device.user_code
|
|
1167
|
+
});
|
|
1168
|
+
return pollXaiDeviceCodeToken(device, opts);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/oauth/claude-code.ts
|
|
1172
|
+
import { randomBytes } from "crypto";
|
|
1173
|
+
import open2 from "open";
|
|
1174
|
+
var CLAUDE_CODE_CLIENT_ID = process.env.CLAUDE_OAUTH_CLIENT_ID ?? "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
1175
|
+
var AUTHORIZE_URL2 = "https://claude.ai/oauth/authorize";
|
|
1176
|
+
var TOKEN_URL4 = "https://api.anthropic.com/v1/oauth/token";
|
|
1177
|
+
var REDIRECT_URI = process.env.CLAUDE_CODE_REDIRECT_URI ?? "https://platform.claude.com/oauth/code/callback";
|
|
1178
|
+
var SCOPES2 = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers";
|
|
1179
|
+
var CLAUDE_CODE_CLI_VERSION = "2.1.195";
|
|
1180
|
+
async function buildClaudeCodeAuthUrl(redirectUri = REDIRECT_URI) {
|
|
1181
|
+
const { verifier, challenge } = await generatePkce();
|
|
1182
|
+
const state = generateOAuthState();
|
|
1183
|
+
const params = new URLSearchParams({
|
|
1184
|
+
code: "true",
|
|
1185
|
+
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
1186
|
+
response_type: "code",
|
|
1187
|
+
redirect_uri: redirectUri,
|
|
1188
|
+
scope: SCOPES2,
|
|
1189
|
+
code_challenge: challenge,
|
|
1190
|
+
code_challenge_method: "S256",
|
|
1191
|
+
state,
|
|
1192
|
+
// Forces fresh auth — prevents session takeover that invalidates previous refresh tokens.
|
|
1193
|
+
prompt: "login"
|
|
1194
|
+
});
|
|
1195
|
+
return { authUrl: `${AUTHORIZE_URL2}?${params}`, codeVerifier: verifier, oauthState: state, redirectUri };
|
|
1196
|
+
}
|
|
1197
|
+
async function exchangeClaudeCodeToken(code, codeVerifier, redirectUri, state) {
|
|
1198
|
+
let authCode = extractClaudeAuthCode(code);
|
|
1199
|
+
let codeState = state;
|
|
1200
|
+
if (authCode.includes("#")) {
|
|
1201
|
+
const idx = authCode.indexOf("#");
|
|
1202
|
+
codeState = authCode.slice(idx + 1) || state;
|
|
1203
|
+
authCode = authCode.slice(0, idx);
|
|
1204
|
+
}
|
|
1205
|
+
const res = await fetch(TOKEN_URL4, {
|
|
1206
|
+
method: "POST",
|
|
1207
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1208
|
+
body: JSON.stringify({
|
|
1209
|
+
code: authCode,
|
|
1210
|
+
state: codeState,
|
|
1211
|
+
grant_type: "authorization_code",
|
|
1212
|
+
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
1213
|
+
redirect_uri: redirectUri,
|
|
1214
|
+
code_verifier: codeVerifier
|
|
1215
|
+
})
|
|
1216
|
+
});
|
|
1217
|
+
if (!res.ok) throw new Error(`Claude Code token exchange failed: ${await res.text()}`);
|
|
1218
|
+
return res.json();
|
|
1219
|
+
}
|
|
1220
|
+
function extractClaudeAuthCode(input) {
|
|
1221
|
+
const trimmed = input.trim();
|
|
1222
|
+
try {
|
|
1223
|
+
const parsed = new URL(trimmed);
|
|
1224
|
+
return parsed.searchParams.get("code") ?? trimmed;
|
|
1225
|
+
} catch {
|
|
1226
|
+
if (trimmed.startsWith("?") || trimmed.includes("code=")) {
|
|
1227
|
+
const query = trimmed.startsWith("?") ? trimmed.slice(1) : trimmed;
|
|
1228
|
+
return new URLSearchParams(query).get("code") ?? trimmed;
|
|
1229
|
+
}
|
|
1230
|
+
return trimmed;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
async function refreshClaudeCodeToken(refreshToken) {
|
|
1234
|
+
return postOAuthRefresh(
|
|
1235
|
+
TOKEN_URL4,
|
|
1236
|
+
{
|
|
1237
|
+
grant_type: "refresh_token",
|
|
1238
|
+
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
1239
|
+
refresh_token: refreshToken
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
contentType: "json",
|
|
1243
|
+
errorPrefix: "Claude Code token refresh failed",
|
|
1244
|
+
includeBody: true
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
async function fetchClaudeBootstrap(accessToken) {
|
|
1249
|
+
try {
|
|
1250
|
+
const res = await fetch("https://api.anthropic.com/api/claude_cli/bootstrap", {
|
|
1251
|
+
method: "GET",
|
|
1252
|
+
headers: {
|
|
1253
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1254
|
+
Accept: "application/json",
|
|
1255
|
+
"User-Agent": `claude-cli/${CLAUDE_CODE_CLI_VERSION} (external, cli)`,
|
|
1256
|
+
"anthropic-beta": "oauth-2025-04-20"
|
|
1257
|
+
},
|
|
1258
|
+
signal: AbortSignal.timeout(1e4)
|
|
1259
|
+
});
|
|
1260
|
+
if (!res.ok) return {};
|
|
1261
|
+
const data = await res.json();
|
|
1262
|
+
const acct = data.oauth_account;
|
|
1263
|
+
if (!acct) return {};
|
|
1264
|
+
return {
|
|
1265
|
+
accountId: typeof acct.account_uuid === "string" ? acct.account_uuid : void 0,
|
|
1266
|
+
email: typeof acct.account_email === "string" ? acct.account_email : void 0,
|
|
1267
|
+
organizationId: typeof acct.organization_uuid === "string" ? acct.organization_uuid : void 0,
|
|
1268
|
+
organizationName: typeof acct.organization_name === "string" ? acct.organization_name : void 0,
|
|
1269
|
+
plan: typeof acct.organization_rate_limit_tier === "string" ? acct.organization_rate_limit_tier : void 0
|
|
1270
|
+
};
|
|
1271
|
+
} catch {
|
|
1272
|
+
return {};
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function generateCliUserID() {
|
|
1276
|
+
return randomBytes(32).toString("hex");
|
|
1277
|
+
}
|
|
1278
|
+
async function runClaudeCodeOAuthFlow(onAuthUrl, readAuthCode) {
|
|
1279
|
+
const { authUrl, codeVerifier, oauthState, redirectUri } = await buildClaudeCodeAuthUrl();
|
|
1280
|
+
onAuthUrl(authUrl);
|
|
1281
|
+
open2(authUrl).catch(() => {
|
|
1282
|
+
});
|
|
1283
|
+
const code = (await readAuthCode()).trim();
|
|
1284
|
+
if (!code) throw new Error("No authorization code received from Anthropic");
|
|
1285
|
+
const tokens = await exchangeClaudeCodeToken(code, codeVerifier, redirectUri, oauthState);
|
|
1286
|
+
const bootstrap = await fetchClaudeBootstrap(tokens.access_token);
|
|
1287
|
+
return { tokens, bootstrap };
|
|
1288
|
+
}
|
|
1289
|
+
async function fetchClaudeCodeModels(accessToken) {
|
|
1290
|
+
const res = await fetch("https://api.anthropic.com/v1/models", {
|
|
1291
|
+
method: "GET",
|
|
1292
|
+
headers: {
|
|
1293
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1294
|
+
"anthropic-version": "2023-06-01",
|
|
1295
|
+
Accept: "application/json",
|
|
1296
|
+
"User-Agent": `claude-cli/${CLAUDE_CODE_CLI_VERSION} (external, cli)`
|
|
1297
|
+
},
|
|
1298
|
+
signal: AbortSignal.timeout(1e4)
|
|
1299
|
+
});
|
|
1300
|
+
if (!res.ok) {
|
|
1301
|
+
throw new Error(`Claude Code model discovery failed (HTTP ${res.status}): ${await res.text().catch(() => "")}`);
|
|
1302
|
+
}
|
|
1303
|
+
const body = await res.json();
|
|
1304
|
+
const entries = (body.data ?? []).filter((m) => typeof m.id === "string" && m.id.length > 0).map((m) => ({
|
|
1305
|
+
id: m.id,
|
|
1306
|
+
displayName: typeof m.display_name === "string" ? m.display_name : m.id,
|
|
1307
|
+
maxInputTokens: typeof m.max_input_tokens === "number" ? m.max_input_tokens : void 0,
|
|
1308
|
+
maxTokens: typeof m.max_tokens === "number" ? m.max_tokens : void 0
|
|
1309
|
+
}));
|
|
1310
|
+
if (entries.length === 0) {
|
|
1311
|
+
throw new Error("Claude Code model discovery returned no models");
|
|
1312
|
+
}
|
|
1313
|
+
return entries;
|
|
1314
|
+
}
|
|
1315
|
+
function guiCallbackRedirectUri(host) {
|
|
1316
|
+
return `http://${host}/oauth/callback`;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// src/cline-pass.ts
|
|
1320
|
+
var CLINE_PASS_HOST = "https://api.cline.bot";
|
|
1321
|
+
var CLINE_PASS_SDK_BASE_URL = `${CLINE_PASS_HOST}/api/v1`;
|
|
1322
|
+
var CLINE_PASS_CATALOG_URL = `${CLINE_PASS_HOST}/api/v1/ai/cline/recommended-models`;
|
|
1323
|
+
var CLINE_PASS_VALIDATION_URL = `${CLINE_PASS_HOST}/api/v1/users/me`;
|
|
1324
|
+
var CLINE_PASS_REGISTER_URL = `${CLINE_PASS_HOST}/api/v1/auth/register`;
|
|
1325
|
+
var CLINE_PASS_REFRESH_URL = `${CLINE_PASS_HOST}/api/v1/auth/refresh`;
|
|
1326
|
+
var CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW = 131072;
|
|
1327
|
+
var CLINE_PASS_WORKOS_PREFIX = "workos:";
|
|
1328
|
+
function isClinePassOAuth(providerId, authType) {
|
|
1329
|
+
return providerId === "cline-pass" && authType === "oauth";
|
|
1330
|
+
}
|
|
1331
|
+
function formatClineRuntimeCredential(providerId, authType, key) {
|
|
1332
|
+
if (!isClinePassOAuth(providerId, authType)) return key;
|
|
1333
|
+
return key.toLowerCase().startsWith(CLINE_PASS_WORKOS_PREFIX) ? key : `${CLINE_PASS_WORKOS_PREFIX}${key}`;
|
|
1334
|
+
}
|
|
1335
|
+
function createClinePassOAuthFetch(initialRuntimeCredential, refreshToken, onTokenRefreshed, fetchImpl = globalThis.fetch) {
|
|
1336
|
+
let currentRuntimeCredential = initialRuntimeCredential;
|
|
1337
|
+
return async (input, init) => {
|
|
1338
|
+
const request = new Request(input, init);
|
|
1339
|
+
const send = (runtimeCredential) => {
|
|
1340
|
+
const headers = new Headers(request.headers);
|
|
1341
|
+
headers.set("Authorization", `Bearer ${runtimeCredential}`);
|
|
1342
|
+
return fetchImpl(request.clone(), { headers });
|
|
1343
|
+
};
|
|
1344
|
+
const response = await send(currentRuntimeCredential);
|
|
1345
|
+
if (response.status !== 401) return response;
|
|
1346
|
+
const refreshedRawToken = await refreshToken().catch(() => null);
|
|
1347
|
+
const refreshedRuntimeCredential = refreshedRawToken ? formatClineRuntimeCredential("cline-pass", "oauth", refreshedRawToken) : null;
|
|
1348
|
+
if (!refreshedRawToken || !refreshedRuntimeCredential || refreshedRuntimeCredential === currentRuntimeCredential) {
|
|
1349
|
+
return response;
|
|
1350
|
+
}
|
|
1351
|
+
currentRuntimeCredential = refreshedRuntimeCredential;
|
|
1352
|
+
onTokenRefreshed?.(refreshedRawToken);
|
|
1353
|
+
return send(currentRuntimeCredential);
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/oauth/cline-pass.ts
|
|
1358
|
+
var WORKOS_CLIENT_ID = "client_01K3A541FN8TA3EPPHTD2325AR";
|
|
1359
|
+
var WORKOS_DEVICE_URL = "https://api.workos.com/user_management/authorize/device";
|
|
1360
|
+
var WORKOS_TOKEN_URL = "https://api.workos.com/user_management/authenticate";
|
|
1361
|
+
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
1362
|
+
var DEFAULT_INTERVAL_MS = 5e3;
|
|
1363
|
+
var SLOW_DOWN_INCREMENT_MS = 1e3;
|
|
1364
|
+
var DEFAULT_EXPIRES_MS = 10 * 60 * 1e3;
|
|
1365
|
+
function formHeaders() {
|
|
1366
|
+
return {
|
|
1367
|
+
Accept: "application/json",
|
|
1368
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
function jsonHeaders() {
|
|
1372
|
+
return {
|
|
1373
|
+
Accept: "application/json",
|
|
1374
|
+
"Content-Type": "application/json"
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function expiresInFromIso(expiresAt) {
|
|
1378
|
+
if (typeof expiresAt !== "string") throw new Error("ClinePass response is missing a valid expiresAt");
|
|
1379
|
+
const timestamp = Date.parse(expiresAt);
|
|
1380
|
+
if (!Number.isFinite(timestamp)) throw new Error("ClinePass response is missing a valid expiresAt");
|
|
1381
|
+
return Math.max(1, Math.floor((timestamp - Date.now()) / 1e3));
|
|
1382
|
+
}
|
|
1383
|
+
function toOAuthResult(data) {
|
|
1384
|
+
if (typeof data.accessToken !== "string" || !data.accessToken) {
|
|
1385
|
+
throw new Error("ClinePass response is missing accessToken");
|
|
1386
|
+
}
|
|
1387
|
+
const userInfo = data.userInfo && typeof data.userInfo === "object" && !Array.isArray(data.userInfo) ? data.userInfo : void 0;
|
|
1388
|
+
const accountId = typeof userInfo?.clineUserId === "string" ? userInfo.clineUserId : void 0;
|
|
1389
|
+
return {
|
|
1390
|
+
tokens: {
|
|
1391
|
+
access_token: data.accessToken,
|
|
1392
|
+
...typeof data.refreshToken === "string" ? { refresh_token: data.refreshToken } : {},
|
|
1393
|
+
expires_in: expiresInFromIso(data.expiresAt),
|
|
1394
|
+
...userInfo ? { providerData: userInfo } : {}
|
|
1395
|
+
},
|
|
1396
|
+
...accountId ? { accountId } : {},
|
|
1397
|
+
...userInfo ? { providerData: userInfo } : {}
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
async function readError(response) {
|
|
1401
|
+
const text = await response.text().catch(() => "");
|
|
1402
|
+
if (!text) return `HTTP ${response.status}`;
|
|
1403
|
+
try {
|
|
1404
|
+
const parsed = JSON.parse(text);
|
|
1405
|
+
const detail = typeof parsed.error === "string" ? parsed.error : typeof parsed.message === "string" ? parsed.message : "";
|
|
1406
|
+
return detail || `HTTP ${response.status}`;
|
|
1407
|
+
} catch {
|
|
1408
|
+
return text.slice(0, 120);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
async function requestClinePassDeviceCode() {
|
|
1412
|
+
const response = await fetch(WORKOS_DEVICE_URL, {
|
|
1413
|
+
method: "POST",
|
|
1414
|
+
headers: formHeaders(),
|
|
1415
|
+
body: new URLSearchParams({ client_id: WORKOS_CLIENT_ID }).toString()
|
|
1416
|
+
});
|
|
1417
|
+
if (!response.ok) throw new Error(`ClinePass device code request failed (${response.status})`);
|
|
1418
|
+
const json = await response.json();
|
|
1419
|
+
if (!json.device_code || !json.user_code || !json.verification_uri) {
|
|
1420
|
+
throw new Error("ClinePass device code response is missing required fields");
|
|
1421
|
+
}
|
|
1422
|
+
return json;
|
|
1423
|
+
}
|
|
1424
|
+
async function registerClinePassTokens(accessToken, refreshToken) {
|
|
1425
|
+
const response = await fetch(CLINE_PASS_REGISTER_URL, {
|
|
1426
|
+
method: "POST",
|
|
1427
|
+
headers: jsonHeaders(),
|
|
1428
|
+
body: JSON.stringify({ accessToken, refreshToken })
|
|
1429
|
+
});
|
|
1430
|
+
if (!response.ok) throw new Error(`ClinePass registration failed (${response.status})`);
|
|
1431
|
+
const body = await response.json();
|
|
1432
|
+
if (body.success !== true || !body.data) {
|
|
1433
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
|
|
1434
|
+
throw new Error(`ClinePass registration failed: ${detail}`);
|
|
1435
|
+
}
|
|
1436
|
+
return toOAuthResult(body.data);
|
|
1437
|
+
}
|
|
1438
|
+
async function pollClinePassDeviceCode(device, opts) {
|
|
1439
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
1440
|
+
const now = opts?.now ?? (() => Date.now());
|
|
1441
|
+
const deadline = now() + positiveSecondsToMs(device.expires_in, DEFAULT_EXPIRES_MS);
|
|
1442
|
+
let intervalMs = Math.max(positiveSecondsToMs(device.interval, DEFAULT_INTERVAL_MS), 1e3);
|
|
1443
|
+
while (now() < deadline) {
|
|
1444
|
+
const response = await fetch(WORKOS_TOKEN_URL, {
|
|
1445
|
+
method: "POST",
|
|
1446
|
+
headers: formHeaders(),
|
|
1447
|
+
body: new URLSearchParams({
|
|
1448
|
+
grant_type: DEVICE_GRANT_TYPE,
|
|
1449
|
+
client_id: WORKOS_CLIENT_ID,
|
|
1450
|
+
device_code: device.device_code
|
|
1451
|
+
}).toString()
|
|
1452
|
+
});
|
|
1453
|
+
if (response.ok) {
|
|
1454
|
+
const workos = await response.json();
|
|
1455
|
+
if (!workos.access_token || !workos.refresh_token) {
|
|
1456
|
+
throw new Error("ClinePass WorkOS response is missing required tokens");
|
|
1457
|
+
}
|
|
1458
|
+
return registerClinePassTokens(workos.access_token, workos.refresh_token);
|
|
1459
|
+
}
|
|
1460
|
+
const body = await response.json().catch(() => ({}));
|
|
1461
|
+
const remaining = Math.max(0, deadline - now());
|
|
1462
|
+
if (body.error === "authorization_pending") {
|
|
1463
|
+
await sleep(Math.min(intervalMs, remaining));
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (body.error === "slow_down") {
|
|
1467
|
+
intervalMs += SLOW_DOWN_INCREMENT_MS;
|
|
1468
|
+
await sleep(Math.min(intervalMs, remaining));
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
throw new Error(`ClinePass device authorization failed${body.error ? `: ${body.error}` : ""}`);
|
|
1472
|
+
}
|
|
1473
|
+
throw new Error("ClinePass device authorization timed out");
|
|
1474
|
+
}
|
|
1475
|
+
async function runClinePassDeviceCodeFlow(onDeviceCode, opts) {
|
|
1476
|
+
const device = await requestClinePassDeviceCode();
|
|
1477
|
+
onDeviceCode({
|
|
1478
|
+
url: device.verification_uri_complete ?? device.verification_uri,
|
|
1479
|
+
userCode: device.user_code
|
|
1480
|
+
});
|
|
1481
|
+
return pollClinePassDeviceCode(device, opts);
|
|
1482
|
+
}
|
|
1483
|
+
async function refreshClinePassAccessToken(refreshToken) {
|
|
1484
|
+
const response = await fetch(CLINE_PASS_REFRESH_URL, {
|
|
1485
|
+
method: "POST",
|
|
1486
|
+
headers: jsonHeaders(),
|
|
1487
|
+
body: JSON.stringify({ refreshToken, grantType: "refresh_token" })
|
|
1488
|
+
});
|
|
1489
|
+
if (!response.ok) {
|
|
1490
|
+
const detail = await readError(response);
|
|
1491
|
+
throw new Error(`ClinePass token refresh failed (${response.status}): ${detail}`);
|
|
1492
|
+
}
|
|
1493
|
+
const body = await response.json();
|
|
1494
|
+
if (body.success !== true || !body.data) {
|
|
1495
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : "unsuccessful response";
|
|
1496
|
+
throw new Error(`ClinePass token refresh failed: ${detail}`);
|
|
1497
|
+
}
|
|
1498
|
+
return toOAuthResult(body.data).tokens;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// src/oauth/refresh.ts
|
|
1502
|
+
function oauthCredentialShouldRefresh(cred, providerId) {
|
|
1503
|
+
if (oauthCredentialNeedsRefresh(cred)) return true;
|
|
1504
|
+
if (NATIVE_OAUTH_PROVIDER_IDS.includes(providerId) && accessTokenIsExpiring(cred.access)) return true;
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
async function refreshStoredOAuthCredential(providerId, cred) {
|
|
1508
|
+
if (!cred.refresh) {
|
|
1509
|
+
throw new Error(`${providerId}: OAuth refresh token missing \u2014 run relay-ai providers auth ${providerId}`);
|
|
1510
|
+
}
|
|
1511
|
+
let tokens;
|
|
1512
|
+
if (providerId === "openai" || providerId === "openai-oauth") {
|
|
1513
|
+
tokens = await refreshOpenAiAccessToken(cred.refresh);
|
|
1514
|
+
} else if (providerId === "xai" || providerId === "xai-oauth") {
|
|
1515
|
+
tokens = await refreshXaiAccessToken(cred.refresh);
|
|
1516
|
+
} else if (providerId === "github-copilot") {
|
|
1517
|
+
tokens = await refreshGithubCopilotToken(cred.refresh);
|
|
1518
|
+
} else if (providerId === "claude-code") {
|
|
1519
|
+
tokens = await refreshClaudeCodeToken(cred.refresh);
|
|
1520
|
+
} else if (providerId === "antigravity") {
|
|
1521
|
+
tokens = await refreshAntigravityToken(cred.refresh);
|
|
1522
|
+
} else if (providerId === "cline-pass") {
|
|
1523
|
+
tokens = await refreshClinePassAccessToken(cred.refresh);
|
|
1524
|
+
} else {
|
|
1525
|
+
throw new Error(`OAuth refresh not implemented for provider "${providerId}"`);
|
|
1526
|
+
}
|
|
1527
|
+
const accountId = providerId === "cline-pass" && typeof tokens.providerData?.clineUserId === "string" ? tokens.providerData.clineUserId : cred.accountId;
|
|
1528
|
+
return tokensToStoredCredential(tokens, cred.refresh, accountId, cred.providerData);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/secrets-file.ts
|
|
1532
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
1533
|
+
var DIR_MODE = 448;
|
|
1534
|
+
var FILE_MODE = 384;
|
|
1535
|
+
function emptySecrets() {
|
|
1536
|
+
return { version: 1, accounts: {} };
|
|
1537
|
+
}
|
|
1538
|
+
function readSecretsFile(env = process.env) {
|
|
1539
|
+
const path = getSecretsPath(env);
|
|
1540
|
+
if (!existsSync2(path)) return emptySecrets();
|
|
1541
|
+
try {
|
|
1542
|
+
const raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
1543
|
+
if (raw?.version !== 1 || !raw.accounts || typeof raw.accounts !== "object") {
|
|
1544
|
+
return emptySecrets();
|
|
1545
|
+
}
|
|
1546
|
+
const accounts = {};
|
|
1547
|
+
for (const [k, v] of Object.entries(raw.accounts)) {
|
|
1548
|
+
if (typeof v === "string" && v.length > 0) accounts[k] = v;
|
|
1549
|
+
}
|
|
1550
|
+
return { version: 1, accounts };
|
|
1551
|
+
} catch {
|
|
1552
|
+
return emptySecrets();
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function writeSecretsFile(data, env = process.env) {
|
|
1556
|
+
const home = getAppHome(env);
|
|
1557
|
+
mkdirSync(home, { recursive: true, mode: DIR_MODE });
|
|
1558
|
+
try {
|
|
1559
|
+
chmodSync(home, DIR_MODE);
|
|
1560
|
+
} catch {
|
|
1561
|
+
}
|
|
1562
|
+
const path = getSecretsPath(env);
|
|
1563
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}
|
|
1564
|
+
`, { encoding: "utf8", mode: FILE_MODE });
|
|
1565
|
+
try {
|
|
1566
|
+
chmodSync(path, FILE_MODE);
|
|
1567
|
+
} catch {
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function readFileAccount(account, env = process.env) {
|
|
1571
|
+
const value = readSecretsFile(env).accounts[account];
|
|
1572
|
+
return value?.length ? value : null;
|
|
1573
|
+
}
|
|
1574
|
+
function writeFileAccount(account, value, env = process.env) {
|
|
1575
|
+
if (!account || !value) return false;
|
|
1576
|
+
try {
|
|
1577
|
+
const data = readSecretsFile(env);
|
|
1578
|
+
data.accounts[account] = value;
|
|
1579
|
+
writeSecretsFile(data, env);
|
|
1580
|
+
return true;
|
|
1581
|
+
} catch {
|
|
1582
|
+
return false;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function deleteFileAccount(account, env = process.env) {
|
|
1586
|
+
try {
|
|
1587
|
+
const data = readSecretsFile(env);
|
|
1588
|
+
if (!(account in data.accounts)) return true;
|
|
1589
|
+
delete data.accounts[account];
|
|
1590
|
+
writeSecretsFile(data, env);
|
|
1591
|
+
return true;
|
|
1592
|
+
} catch {
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// src/env.ts
|
|
1598
|
+
function detectConflicts() {
|
|
1599
|
+
return CONFLICTING_ENV_VARS.filter((name) => process.env[name] !== void 0).map((name) => ({ name, value: process.env[name] }));
|
|
1600
|
+
}
|
|
1601
|
+
function resolveApiKey() {
|
|
1602
|
+
const key = process.env["OPENCODE_API_KEY"];
|
|
1603
|
+
if (!key?.trim()) return null;
|
|
1604
|
+
return key.trim().split(/\r?\n/)[0]?.trim() || null;
|
|
1605
|
+
}
|
|
1606
|
+
function applyClaudeCodeThirdPartyCompat(env) {
|
|
1607
|
+
env["ENABLE_TOOL_SEARCH"] = "true";
|
|
1608
|
+
env["CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT"] = "0";
|
|
1609
|
+
}
|
|
1610
|
+
function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableGatewayDiscovery) {
|
|
1611
|
+
const env = { ...process.env };
|
|
1612
|
+
for (const name of CONFLICTING_ENV_VARS) {
|
|
1613
|
+
delete env[name];
|
|
1614
|
+
}
|
|
1615
|
+
for (const name of PARENT_SESSION_ENV_VARS) {
|
|
1616
|
+
delete env[name];
|
|
1617
|
+
}
|
|
1618
|
+
env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
|
|
1619
|
+
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
1620
|
+
const bareModel = stripOneMContextSuffix(model);
|
|
1621
|
+
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow);
|
|
1622
|
+
if (!enableGatewayDiscovery) {
|
|
1623
|
+
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow));
|
|
1624
|
+
}
|
|
1625
|
+
if (enableGatewayDiscovery) {
|
|
1626
|
+
delete env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"];
|
|
1627
|
+
env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1";
|
|
1628
|
+
}
|
|
1629
|
+
applyClaudeCodeThirdPartyCompat(env);
|
|
1630
|
+
return env;
|
|
1631
|
+
}
|
|
1632
|
+
function buildAntigravityChildEnv(gatewayUrl) {
|
|
1633
|
+
const env = { ...process.env };
|
|
1634
|
+
for (const name of CONFLICTING_ENV_VARS) {
|
|
1635
|
+
delete env[name];
|
|
1636
|
+
}
|
|
1637
|
+
env["CLOUD_CODE_URL"] = gatewayUrl;
|
|
1638
|
+
env["ANTIGRAVITY_API_KEY"] = "relay-dummy-key";
|
|
1639
|
+
env["GEMINI_API_KEY"] = "relay-dummy-key";
|
|
1640
|
+
env["GOOGLE_API_KEY"] = "relay-dummy-key";
|
|
1641
|
+
env["GOOGLE_GEMINI_API_KEY"] = "relay-dummy-key";
|
|
1642
|
+
return env;
|
|
1643
|
+
}
|
|
1644
|
+
function classifyKeyringError(err) {
|
|
1645
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1646
|
+
const lower = msg.toLowerCase();
|
|
1647
|
+
if (lower.includes("cannot find module") || lower.includes("module not found") || lower.includes("failed to load")) {
|
|
1648
|
+
return "native keyring module not available on this system";
|
|
1649
|
+
}
|
|
1650
|
+
if (lower.includes("secret service") || lower.includes("dbus") || lower.includes("daemon")) {
|
|
1651
|
+
return "Secret Service daemon is not running (start GNOME Keyring or KWallet)";
|
|
1652
|
+
}
|
|
1653
|
+
if (lower.includes("denied") || lower.includes("locked") || lower.includes("cancelled") || lower.includes("user refused")) {
|
|
1654
|
+
return "keychain access was denied or the keychain is locked";
|
|
1655
|
+
}
|
|
1656
|
+
return `keyring error: ${msg}`;
|
|
1657
|
+
}
|
|
1658
|
+
var KEYRING_SERVICE = "relay-ai";
|
|
1659
|
+
var KEYRING_ACCOUNT = "relay-ai";
|
|
1660
|
+
var KEYRING_CHUNK_PREFIX = "__relay_chunked__:";
|
|
1661
|
+
var KEYRING_CHUNK_SIZE = 1200;
|
|
1662
|
+
var LEGACY_KEYRING_SERVICE = "opencode-starter";
|
|
1663
|
+
var LEGACY_KEYRING_ACCOUNT = "opencode-starter";
|
|
1664
|
+
var GLOBAL_OPENCODE_KEYRING_ACCOUNT = "global:opencode";
|
|
1665
|
+
function providerKeyringAccount(providerId) {
|
|
1666
|
+
return `provider:${providerId}`;
|
|
1667
|
+
}
|
|
1668
|
+
function preferredRelayCredentialAuthRef(providerId, fallbackAuthRef) {
|
|
1669
|
+
return providerId === "go" || providerId === "zen" ? `keyring:${providerKeyringAccount("opencode")}` : fallbackAuthRef;
|
|
1670
|
+
}
|
|
1671
|
+
function oauthProviderIdFromAccount(account) {
|
|
1672
|
+
const prefix = "oauth:provider:";
|
|
1673
|
+
return account.startsWith(prefix) ? account.slice(prefix.length) : null;
|
|
1674
|
+
}
|
|
1675
|
+
var oauthRefreshInflight = /* @__PURE__ */ new Map();
|
|
1676
|
+
function parseAuthRef(authRef) {
|
|
1677
|
+
if (authRef.startsWith("keyring:")) {
|
|
1678
|
+
const account = authRef.slice("keyring:".length);
|
|
1679
|
+
return account ? { kind: "keyring", account } : null;
|
|
1680
|
+
}
|
|
1681
|
+
if (authRef.startsWith("env:")) {
|
|
1682
|
+
const varName = authRef.slice("env:".length);
|
|
1683
|
+
return varName ? { kind: "env", varName } : null;
|
|
1684
|
+
}
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
function relayAiKeyEnvVar(providerId) {
|
|
1688
|
+
return `RELAY_AI_KEY_${providerId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
1689
|
+
}
|
|
1690
|
+
function readEnvCredential(varName) {
|
|
1691
|
+
const raw = process.env[varName];
|
|
1692
|
+
if (!raw?.trim()) return null;
|
|
1693
|
+
return raw.trim().split(/\r?\n/)[0]?.trim() || null;
|
|
1694
|
+
}
|
|
1695
|
+
async function readOsKeyringAccount(account, diag) {
|
|
1696
|
+
try {
|
|
1697
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1698
|
+
const value = new Entry(KEYRING_SERVICE, account).getPassword() ?? null;
|
|
1699
|
+
if (!value?.startsWith(KEYRING_CHUNK_PREFIX)) return value;
|
|
1700
|
+
const chunkCount = Number(value.slice(KEYRING_CHUNK_PREFIX.length));
|
|
1701
|
+
let combined = "";
|
|
1702
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
1703
|
+
combined += new Entry(KEYRING_SERVICE, `${account}::chunk::${i}`).getPassword() ?? "";
|
|
1704
|
+
}
|
|
1705
|
+
return combined;
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
diag?.(classifyKeyringError(err));
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
async function writeOsKeyringAccount(account, key, diag) {
|
|
1712
|
+
try {
|
|
1713
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1714
|
+
if (key.length <= KEYRING_CHUNK_SIZE) {
|
|
1715
|
+
new Entry(KEYRING_SERVICE, account).setPassword(key);
|
|
1716
|
+
return true;
|
|
1717
|
+
}
|
|
1718
|
+
const chunkCount = Math.ceil(key.length / KEYRING_CHUNK_SIZE);
|
|
1719
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
1720
|
+
const chunk = key.slice(i * KEYRING_CHUNK_SIZE, (i + 1) * KEYRING_CHUNK_SIZE);
|
|
1721
|
+
new Entry(KEYRING_SERVICE, `${account}::chunk::${i}`).setPassword(chunk);
|
|
1722
|
+
}
|
|
1723
|
+
new Entry(KEYRING_SERVICE, account).setPassword(`${KEYRING_CHUNK_PREFIX}${chunkCount}`);
|
|
1724
|
+
return true;
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
diag?.(classifyKeyringError(err));
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
async function deleteOsKeyringAccount(account, diag) {
|
|
1731
|
+
try {
|
|
1732
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1733
|
+
const value = new Entry(KEYRING_SERVICE, account).getPassword();
|
|
1734
|
+
if (value?.startsWith(KEYRING_CHUNK_PREFIX)) {
|
|
1735
|
+
const chunkCount = Number(value.slice(KEYRING_CHUNK_PREFIX.length));
|
|
1736
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
1737
|
+
new Entry(KEYRING_SERVICE, `${account}::chunk::${i}`).deletePassword();
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
new Entry(KEYRING_SERVICE, account).deletePassword();
|
|
1741
|
+
return true;
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
diag?.(classifyKeyringError(err));
|
|
1744
|
+
return false;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
async function readKeyringAccount(account, diag) {
|
|
1748
|
+
const fromOs = await readOsKeyringAccount(account, diag);
|
|
1749
|
+
if (fromOs) return fromOs;
|
|
1750
|
+
return readFileAccount(account);
|
|
1751
|
+
}
|
|
1752
|
+
async function writeKeyringAccount(account, key, diag) {
|
|
1753
|
+
if (await writeOsKeyringAccount(account, key, diag)) {
|
|
1754
|
+
deleteFileAccount(account);
|
|
1755
|
+
return true;
|
|
1756
|
+
}
|
|
1757
|
+
if (writeFileAccount(account, key)) {
|
|
1758
|
+
diag?.("OS keyring unavailable \u2014 saved to secrets.json under RELAY_AI_HOME");
|
|
1759
|
+
return true;
|
|
1760
|
+
}
|
|
1761
|
+
return false;
|
|
1762
|
+
}
|
|
1763
|
+
async function deleteKeyringAccount(account, diag) {
|
|
1764
|
+
const osOk = await deleteOsKeyringAccount(account, diag);
|
|
1765
|
+
const fileOk = deleteFileAccount(account);
|
|
1766
|
+
return osOk || fileOk;
|
|
1767
|
+
}
|
|
1768
|
+
async function readGlobalOpencodeCredential(diag) {
|
|
1769
|
+
const fromEnv = resolveApiKey();
|
|
1770
|
+
if (fromEnv) return fromEnv;
|
|
1771
|
+
const global = await readKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, diag);
|
|
1772
|
+
if (global) return global;
|
|
1773
|
+
const current = await readKeyringAccount(KEYRING_ACCOUNT, diag);
|
|
1774
|
+
if (current) return current;
|
|
1775
|
+
try {
|
|
1776
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1777
|
+
return new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).getPassword() ?? null;
|
|
1778
|
+
} catch (err) {
|
|
1779
|
+
diag?.(classifyKeyringError(err));
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async function readStoredProviderCredential(authRef, diag) {
|
|
1784
|
+
const parsed = parseAuthRef(authRef);
|
|
1785
|
+
if (!parsed || parsed.kind !== "keyring") return null;
|
|
1786
|
+
return readProviderSecret(parsed.account, diag);
|
|
1787
|
+
}
|
|
1788
|
+
async function migrateGlobalOpencodeCredential(diag) {
|
|
1789
|
+
const existing = await readKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, diag);
|
|
1790
|
+
if (existing) return true;
|
|
1791
|
+
const legacy = await readKeyringAccount(KEYRING_ACCOUNT, diag) ?? await (async () => {
|
|
1792
|
+
try {
|
|
1793
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1794
|
+
return new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).getPassword() ?? null;
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
diag?.(classifyKeyringError(err));
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
})();
|
|
1800
|
+
if (!legacy) return false;
|
|
1801
|
+
const wrote = await writeKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, legacy, diag);
|
|
1802
|
+
if (!wrote) return false;
|
|
1803
|
+
const verified = await readKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, diag);
|
|
1804
|
+
if (verified !== legacy) {
|
|
1805
|
+
diag?.("credential migration verification failed \u2014 keeping legacy keychain entries");
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
if (await readKeyringAccount(KEYRING_ACCOUNT, diag)) {
|
|
1809
|
+
await deleteKeyringAccount(KEYRING_ACCOUNT, diag);
|
|
1810
|
+
}
|
|
1811
|
+
try {
|
|
1812
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1813
|
+
if (new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).getPassword()) {
|
|
1814
|
+
new Entry(LEGACY_KEYRING_SERVICE, LEGACY_KEYRING_ACCOUNT).deletePassword();
|
|
1815
|
+
}
|
|
1816
|
+
} catch {
|
|
1817
|
+
}
|
|
1818
|
+
return true;
|
|
1819
|
+
}
|
|
1820
|
+
async function resolveProviderCredential(providerId, authRef, diag) {
|
|
1821
|
+
const parsed = parseAuthRef(authRef);
|
|
1822
|
+
if (!parsed) return null;
|
|
1823
|
+
if (parsed.kind === "keyring" && parsed.account === GLOBAL_OPENCODE_KEYRING_ACCOUNT) {
|
|
1824
|
+
const relayOverride = await readProviderSecret(providerKeyringAccount("opencode"), diag);
|
|
1825
|
+
if (relayOverride) return relayOverride;
|
|
1826
|
+
}
|
|
1827
|
+
const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
|
|
1828
|
+
if (namespaced) return namespaced;
|
|
1829
|
+
if (parsed.kind === "env") {
|
|
1830
|
+
return readEnvCredential(parsed.varName);
|
|
1831
|
+
}
|
|
1832
|
+
if (parsed.account === GLOBAL_OPENCODE_KEYRING_ACCOUNT) {
|
|
1833
|
+
return readGlobalOpencodeCredential(diag);
|
|
1834
|
+
}
|
|
1835
|
+
return readProviderSecret(parsed.account, diag);
|
|
1836
|
+
}
|
|
1837
|
+
async function forceRefreshProviderCredential(providerId, authRef, diag) {
|
|
1838
|
+
const parsed = parseAuthRef(authRef);
|
|
1839
|
+
if (!parsed || parsed.kind !== "keyring") {
|
|
1840
|
+
return resolveProviderCredential(providerId, authRef, diag);
|
|
1841
|
+
}
|
|
1842
|
+
if (parsed.account === GLOBAL_OPENCODE_KEYRING_ACCOUNT) {
|
|
1843
|
+
const relayOverride = await readProviderSecret(providerKeyringAccount("opencode"), diag);
|
|
1844
|
+
if (relayOverride) return relayOverride;
|
|
1845
|
+
}
|
|
1846
|
+
const namespaced = readEnvCredential(relayAiKeyEnvVar(providerId));
|
|
1847
|
+
if (namespaced) return namespaced;
|
|
1848
|
+
const oauthProviderId = oauthProviderIdFromAccount(parsed.account);
|
|
1849
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
1850
|
+
if (!raw || !oauthProviderId) return decodeProviderSecret(raw);
|
|
1851
|
+
return refreshOAuthKeyringAccount(parsed.account, oauthProviderId, raw, diag, true);
|
|
1852
|
+
}
|
|
1853
|
+
async function resolveProviderOAuthAccountId(authRef, diag) {
|
|
1854
|
+
const parsed = parseAuthRef(authRef);
|
|
1855
|
+
if (!parsed || parsed.kind !== "keyring" || !oauthProviderIdFromAccount(parsed.account)) return void 0;
|
|
1856
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
1857
|
+
return parseStoredOAuthCredential(raw)?.accountId;
|
|
1858
|
+
}
|
|
1859
|
+
async function resolveProviderOAuthProviderData(authRef, diag) {
|
|
1860
|
+
const parsed = parseAuthRef(authRef);
|
|
1861
|
+
if (!parsed || parsed.kind !== "keyring" || !oauthProviderIdFromAccount(parsed.account)) return void 0;
|
|
1862
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
1863
|
+
return parseStoredOAuthCredential(raw)?.providerData;
|
|
1864
|
+
}
|
|
1865
|
+
async function enrichGithubCopilotOAuthProviderData(authRef, diag) {
|
|
1866
|
+
const parsed = parseAuthRef(authRef);
|
|
1867
|
+
if (!parsed || parsed.kind !== "keyring" || oauthProviderIdFromAccount(parsed.account) !== "github-copilot") {
|
|
1868
|
+
return void 0;
|
|
1869
|
+
}
|
|
1870
|
+
const raw = await readKeyringAccount(parsed.account, diag);
|
|
1871
|
+
const credential = parseStoredOAuthCredential(raw);
|
|
1872
|
+
if (!credential?.refresh) return credential?.providerData;
|
|
1873
|
+
try {
|
|
1874
|
+
const summary = await fetchCopilotAccount(credential.refresh);
|
|
1875
|
+
const providerData = { ...credential.providerData, copilot: summary };
|
|
1876
|
+
await writeKeyringAccount(
|
|
1877
|
+
parsed.account,
|
|
1878
|
+
oauthCredentialToKeychainJson({ ...credential, providerData }),
|
|
1879
|
+
diag
|
|
1880
|
+
);
|
|
1881
|
+
return providerData;
|
|
1882
|
+
} catch (err) {
|
|
1883
|
+
diag?.(`GitHub Copilot plan lookup unavailable \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
1884
|
+
return credential.providerData;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function decodeProviderSecret(raw) {
|
|
1888
|
+
if (!raw) return null;
|
|
1889
|
+
const trimmed = raw.trim();
|
|
1890
|
+
if (!trimmed.startsWith("{")) return trimmed;
|
|
1891
|
+
const oauth = parseStoredOAuthCredential(trimmed);
|
|
1892
|
+
if (oauth) return oauth.access;
|
|
1893
|
+
try {
|
|
1894
|
+
const parsed = JSON.parse(trimmed);
|
|
1895
|
+
if (parsed.type === "oauth" && typeof parsed.access === "string") return parsed.access;
|
|
1896
|
+
if (parsed.type === "wellknown" && typeof parsed.token === "string") return parsed.token;
|
|
1897
|
+
} catch {
|
|
1898
|
+
}
|
|
1899
|
+
return trimmed;
|
|
1900
|
+
}
|
|
1901
|
+
async function refreshOAuthKeyringAccount(account, providerId, raw, diag, force = false) {
|
|
1902
|
+
const existing = oauthRefreshInflight.get(account);
|
|
1903
|
+
if (existing) return existing;
|
|
1904
|
+
const work = (async () => {
|
|
1905
|
+
const cred = parseStoredOAuthCredential(raw);
|
|
1906
|
+
if (!cred || !force && !oauthCredentialShouldRefresh(cred, providerId)) {
|
|
1907
|
+
return decodeProviderSecret(raw);
|
|
1908
|
+
}
|
|
1909
|
+
try {
|
|
1910
|
+
const refreshed = await refreshStoredOAuthCredential(providerId, cred);
|
|
1911
|
+
const json = oauthCredentialToKeychainJson(refreshed);
|
|
1912
|
+
await writeKeyringAccount(account, json, diag);
|
|
1913
|
+
return refreshed.access;
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
diag?.(err instanceof Error ? err.message : String(err));
|
|
1916
|
+
if (cred.access && cred.expires > Date.now()) return cred.access;
|
|
1917
|
+
throw err;
|
|
1918
|
+
}
|
|
1919
|
+
})();
|
|
1920
|
+
oauthRefreshInflight.set(account, work);
|
|
1921
|
+
try {
|
|
1922
|
+
return await work;
|
|
1923
|
+
} finally {
|
|
1924
|
+
oauthRefreshInflight.delete(account);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
async function readProviderSecret(account, diag) {
|
|
1928
|
+
const raw = await readKeyringAccount(account, diag);
|
|
1929
|
+
if (!raw) return null;
|
|
1930
|
+
const oauthProviderId = oauthProviderIdFromAccount(account);
|
|
1931
|
+
if (oauthProviderId && raw.trim().startsWith("{")) {
|
|
1932
|
+
return refreshOAuthKeyringAccount(account, oauthProviderId, raw, diag);
|
|
1933
|
+
}
|
|
1934
|
+
return decodeProviderSecret(raw);
|
|
1935
|
+
}
|
|
1936
|
+
async function saveProviderCredential(authRef, key, diag) {
|
|
1937
|
+
const parsed = parseAuthRef(authRef);
|
|
1938
|
+
if (!parsed || parsed.kind !== "keyring") return false;
|
|
1939
|
+
return writeKeyringAccount(parsed.account, key, diag);
|
|
1940
|
+
}
|
|
1941
|
+
async function deleteProviderCredential(authRef, diag) {
|
|
1942
|
+
const parsed = parseAuthRef(authRef);
|
|
1943
|
+
if (!parsed || parsed.kind !== "keyring") return false;
|
|
1944
|
+
return deleteKeyringAccount(parsed.account, diag);
|
|
1945
|
+
}
|
|
1946
|
+
async function readFromCredentialStore(diag) {
|
|
1947
|
+
return readGlobalOpencodeCredential(diag);
|
|
1948
|
+
}
|
|
1949
|
+
async function saveToCredentialStore(key, diag) {
|
|
1950
|
+
const wrote = await writeKeyringAccount(GLOBAL_OPENCODE_KEYRING_ACCOUNT, key, diag);
|
|
1951
|
+
if (wrote) {
|
|
1952
|
+
await deleteKeyringAccount(KEYRING_ACCOUNT, diag);
|
|
1953
|
+
}
|
|
1954
|
+
return wrote;
|
|
1955
|
+
}
|
|
1956
|
+
async function isSecretServiceAvailable() {
|
|
1957
|
+
try {
|
|
1958
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
1959
|
+
new Entry(`${KEYRING_SERVICE}-probe`, "probe").getPassword();
|
|
1960
|
+
return true;
|
|
1961
|
+
} catch {
|
|
1962
|
+
return false;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/registry/provider-models.ts
|
|
1967
|
+
var MANUAL_MODEL_PACKAGES = /* @__PURE__ */ new Set([
|
|
1968
|
+
"@ai-sdk/openai-compatible",
|
|
1969
|
+
"@ai-sdk/openai",
|
|
1970
|
+
"@ai-sdk/anthropic",
|
|
1971
|
+
"@openrouter/ai-sdk-provider"
|
|
1972
|
+
]);
|
|
1973
|
+
function supportsManualModels(provider) {
|
|
1974
|
+
return provider.authType !== "oauth" && !["zen", "go", "antigravity"].includes(provider.templateId) && MANUAL_MODEL_PACKAGES.has(provider.api.npm ?? "");
|
|
1975
|
+
}
|
|
1976
|
+
function getProviderModels(provider) {
|
|
1977
|
+
const models = new Map((provider.modelsCache?.models ?? []).map((model) => [model.id, model]));
|
|
1978
|
+
if (supportsManualModels(provider)) {
|
|
1979
|
+
for (const model of provider.manualModels ?? []) {
|
|
1980
|
+
models.set(model.id, { ...model, modelFormat: provider.api.npm === "@ai-sdk/anthropic" ? "anthropic" : "openai" });
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
return [...models.values()];
|
|
1984
|
+
}
|
|
1985
|
+
function modelIdError(value) {
|
|
1986
|
+
if (typeof value !== "string" || !value.trim()) return "Model ID is required.";
|
|
1987
|
+
if (value.trim().length > 512 || /[\s\u0000-\u001f\u007f]/u.test(value.trim())) {
|
|
1988
|
+
return "Model ID must be at most 512 characters with no whitespace or control characters.";
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
function contextWindowError(value) {
|
|
1992
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || Number(value) <= 0)) {
|
|
1993
|
+
return "Context size must be a positive whole number of tokens, or left blank.";
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
function parseManualModel(raw) {
|
|
1997
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
1998
|
+
const m = raw;
|
|
1999
|
+
if (modelIdError(m.id) || typeof m.name !== "string" || !m.name.trim() || m.name.length > 200 || m.source !== "manual" || typeof m.validatedAt !== "string" || !Number.isFinite(Date.parse(m.validatedAt)) || contextWindowError(m.contextWindow) || m.modelFormat !== "openai" && m.modelFormat !== "anthropic") return void 0;
|
|
2000
|
+
return {
|
|
2001
|
+
id: m.id.trim(),
|
|
2002
|
+
name: m.name.trim(),
|
|
2003
|
+
upstreamModelId: m.id.trim(),
|
|
2004
|
+
modelFormat: m.modelFormat,
|
|
2005
|
+
source: "manual",
|
|
2006
|
+
validatedAt: m.validatedAt,
|
|
2007
|
+
...m.contextWindow === void 0 ? {} : { contextWindow: m.contextWindow, contextWindowSource: "user" }
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// src/registry/io.ts
|
|
2012
|
+
import {
|
|
2013
|
+
chmodSync as chmodSync2,
|
|
2014
|
+
copyFileSync,
|
|
2015
|
+
existsSync as existsSync3,
|
|
2016
|
+
mkdirSync as mkdirSync2,
|
|
2017
|
+
openSync,
|
|
2018
|
+
readFileSync as readFileSync5,
|
|
2019
|
+
renameSync,
|
|
2020
|
+
writeSync,
|
|
2021
|
+
closeSync
|
|
2022
|
+
} from "fs";
|
|
2023
|
+
import { dirname } from "path";
|
|
2024
|
+
|
|
2025
|
+
// src/registry/types.ts
|
|
2026
|
+
var REGISTRY_SCHEMA_VERSION = 1;
|
|
2027
|
+
|
|
2028
|
+
// src/registry/migrate.ts
|
|
2029
|
+
var LEGACY_CLOUD_PROVIDER_IDS = [
|
|
2030
|
+
{ legacyId: "opencode", id: "zen", name: "OpenCode Zen" },
|
|
2031
|
+
{ legacyId: "opencode-go", id: "go", name: "OpenCode Go" }
|
|
2032
|
+
];
|
|
2033
|
+
function migrateLegacyCloudProviders(registry) {
|
|
2034
|
+
let changed = false;
|
|
2035
|
+
for (const { legacyId, id, name } of LEGACY_CLOUD_PROVIDER_IDS) {
|
|
2036
|
+
const legacyIdx = registry.providers.findIndex((provider) => provider.id === legacyId);
|
|
2037
|
+
if (legacyIdx < 0) continue;
|
|
2038
|
+
if (registry.providers.some((provider) => provider.id === id)) {
|
|
2039
|
+
registry.providers.splice(legacyIdx, 1);
|
|
2040
|
+
} else {
|
|
2041
|
+
registry.providers[legacyIdx] = {
|
|
2042
|
+
...registry.providers[legacyIdx],
|
|
2043
|
+
id,
|
|
2044
|
+
templateId: id,
|
|
2045
|
+
name,
|
|
2046
|
+
api: {}
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
changed = true;
|
|
2050
|
+
}
|
|
2051
|
+
return changed;
|
|
2052
|
+
}
|
|
2053
|
+
function migrateOAuthOpenAiProvider(registry) {
|
|
2054
|
+
if (registry.providers.some((p) => p.id === "openai-oauth")) return false;
|
|
2055
|
+
const idx = registry.providers.findIndex(
|
|
2056
|
+
(p) => p.id === "openai" && p.authType === "oauth"
|
|
2057
|
+
);
|
|
2058
|
+
if (idx < 0) return false;
|
|
2059
|
+
const existing = registry.providers[idx];
|
|
2060
|
+
registry.providers[idx] = {
|
|
2061
|
+
...existing,
|
|
2062
|
+
id: "openai-oauth",
|
|
2063
|
+
templateId: existing.templateId || "openai",
|
|
2064
|
+
name: existing.name === "OpenAI" ? "OpenAI (ChatGPT)" : existing.name
|
|
2065
|
+
};
|
|
2066
|
+
return true;
|
|
2067
|
+
}
|
|
2068
|
+
function migrateOAuthXaiProvider(registry) {
|
|
2069
|
+
if (registry.providers.some((p) => p.id === "xai-oauth")) return false;
|
|
2070
|
+
const idx = registry.providers.findIndex(
|
|
2071
|
+
(p) => p.id === "xai" && p.authType === "oauth"
|
|
2072
|
+
);
|
|
2073
|
+
if (idx < 0) return false;
|
|
2074
|
+
const existing = registry.providers[idx];
|
|
2075
|
+
registry.providers[idx] = {
|
|
2076
|
+
...existing,
|
|
2077
|
+
id: "xai-oauth",
|
|
2078
|
+
templateId: existing.templateId || "xai",
|
|
2079
|
+
name: existing.name === "xAI" ? "xAI Grok (SuperGrok)" : existing.name
|
|
2080
|
+
};
|
|
2081
|
+
return true;
|
|
2082
|
+
}
|
|
2083
|
+
function migrateAlibabaDashScopeChinaLabel(registry) {
|
|
2084
|
+
const provider = registry.providers.find(
|
|
2085
|
+
(p) => p.id === "alibaba" && p.templateId === "alibaba" && p.name === "Alibaba DashScope" && p.api.url === "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
2086
|
+
);
|
|
2087
|
+
if (!provider) return false;
|
|
2088
|
+
provider.name = "Alibaba DashScope (China)";
|
|
2089
|
+
return true;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// src/registry/validate.ts
|
|
2093
|
+
var PROVIDER_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
2094
|
+
function isValidProviderId(id) {
|
|
2095
|
+
return PROVIDER_ID_PATTERN.test(id);
|
|
2096
|
+
}
|
|
2097
|
+
function slugifyProviderId(displayName) {
|
|
2098
|
+
const base = displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2099
|
+
if (!base) return "custom-provider";
|
|
2100
|
+
if (isValidProviderId(base)) return base;
|
|
2101
|
+
const trimmed = base.replace(/^-+|-+$/g, "");
|
|
2102
|
+
return isValidProviderId(trimmed) ? trimmed : `custom-${trimmed.slice(0, 40)}`;
|
|
2103
|
+
}
|
|
2104
|
+
function customProviderId(displayName) {
|
|
2105
|
+
const slug = slugifyProviderId(displayName);
|
|
2106
|
+
return slug.startsWith("custom-") ? slug : `custom-${slug}`;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/registry/io.ts
|
|
2110
|
+
var DIR_MODE2 = 448;
|
|
2111
|
+
var FILE_MODE2 = 384;
|
|
2112
|
+
function ensureSecureAppHome() {
|
|
2113
|
+
const home = getAppHome();
|
|
2114
|
+
mkdirSync2(home, { recursive: true, mode: DIR_MODE2 });
|
|
2115
|
+
try {
|
|
2116
|
+
chmodSync2(home, DIR_MODE2);
|
|
2117
|
+
} catch {
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
function writeSecureFile(path, content) {
|
|
2121
|
+
ensureSecureAppHome();
|
|
2122
|
+
mkdirSync2(dirname(path), { recursive: true, mode: DIR_MODE2 });
|
|
2123
|
+
const fd = openSync(path, "w", FILE_MODE2);
|
|
2124
|
+
try {
|
|
2125
|
+
writeSync(fd, content);
|
|
2126
|
+
} finally {
|
|
2127
|
+
closeSync(fd);
|
|
2128
|
+
}
|
|
2129
|
+
try {
|
|
2130
|
+
chmodSync2(path, FILE_MODE2);
|
|
2131
|
+
} catch {
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
function parseProvider(raw) {
|
|
2135
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2136
|
+
const p = raw;
|
|
2137
|
+
if (typeof p.id !== "string" || !isValidProviderId(p.id)) return null;
|
|
2138
|
+
if (typeof p.templateId !== "string" || !p.templateId) return null;
|
|
2139
|
+
if (typeof p.name !== "string" || !p.name) return null;
|
|
2140
|
+
if (typeof p.enabled !== "boolean") return null;
|
|
2141
|
+
if (typeof p.authRef !== "string" || !p.authRef) return null;
|
|
2142
|
+
if (typeof p.addedAt !== "string" || !p.addedAt) return null;
|
|
2143
|
+
const api = p.api;
|
|
2144
|
+
if (!api || typeof api !== "object") return null;
|
|
2145
|
+
const provider = {
|
|
2146
|
+
id: p.id,
|
|
2147
|
+
templateId: p.templateId,
|
|
2148
|
+
name: p.name,
|
|
2149
|
+
enabled: p.enabled,
|
|
2150
|
+
authRef: p.authRef,
|
|
2151
|
+
api,
|
|
2152
|
+
addedAt: p.addedAt
|
|
2153
|
+
};
|
|
2154
|
+
if (p.subscriptionFilter === "free" || p.subscriptionFilter === "zen" || p.subscriptionFilter === "go") {
|
|
2155
|
+
provider.subscriptionFilter = p.subscriptionFilter;
|
|
2156
|
+
}
|
|
2157
|
+
if (p.authType === "api" || p.authType === "oauth" || p.authType === "none") {
|
|
2158
|
+
provider.authType = p.authType;
|
|
2159
|
+
}
|
|
2160
|
+
if (typeof p.refreshedAt === "string") provider.refreshedAt = p.refreshedAt;
|
|
2161
|
+
if (Array.isArray(p.manualModels)) {
|
|
2162
|
+
provider.manualModels = p.manualModels.flatMap((raw2) => {
|
|
2163
|
+
const model = parseManualModel(raw2);
|
|
2164
|
+
return model ? [model] : [];
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
if (p.modelsCache && typeof p.modelsCache === "object") {
|
|
2168
|
+
const cache = p.modelsCache;
|
|
2169
|
+
if (typeof cache.fetchedAt === "string" && Array.isArray(cache.models)) {
|
|
2170
|
+
provider.modelsCache = {
|
|
2171
|
+
fetchedAt: cache.fetchedAt,
|
|
2172
|
+
models: cache.models.filter((m) => m && typeof m === "object")
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
return provider;
|
|
2177
|
+
}
|
|
2178
|
+
function parseRegistry(raw) {
|
|
2179
|
+
const empty = { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
|
|
2180
|
+
if (!raw || typeof raw !== "object") return empty;
|
|
2181
|
+
const data = raw;
|
|
2182
|
+
const providers = [];
|
|
2183
|
+
if (Array.isArray(data.providers)) {
|
|
2184
|
+
for (const entry of data.providers) {
|
|
2185
|
+
const parsed = parseProvider(entry);
|
|
2186
|
+
if (parsed) providers.push(parsed);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
const registry = {
|
|
2190
|
+
schemaVersion: typeof data.schemaVersion === "number" ? data.schemaVersion : REGISTRY_SCHEMA_VERSION,
|
|
2191
|
+
providers
|
|
2192
|
+
};
|
|
2193
|
+
if (typeof data.importedAt === "string") registry.importedAt = data.importedAt;
|
|
2194
|
+
if (typeof data.pricingCacheAt === "string") registry.pricingCacheAt = data.pricingCacheAt;
|
|
2195
|
+
return registry;
|
|
2196
|
+
}
|
|
2197
|
+
function loadRegistry(path = getProvidersPath(), { persist = true } = {}) {
|
|
2198
|
+
if (!existsSync3(path)) {
|
|
2199
|
+
return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
|
|
2200
|
+
}
|
|
2201
|
+
try {
|
|
2202
|
+
const raw = JSON.parse(readFileSync5(path, "utf8"));
|
|
2203
|
+
const registry = parseRegistry(raw);
|
|
2204
|
+
let migrated = migrateLegacyCloudProviders(registry);
|
|
2205
|
+
if (migrateOAuthOpenAiProvider(registry)) migrated = true;
|
|
2206
|
+
if (migrateOAuthXaiProvider(registry)) migrated = true;
|
|
2207
|
+
if (migrateAlibabaDashScopeChinaLabel(registry)) migrated = true;
|
|
2208
|
+
if (migrated && persist) {
|
|
2209
|
+
try {
|
|
2210
|
+
saveRegistry(registry, path);
|
|
2211
|
+
} catch {
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
return registry;
|
|
2215
|
+
} catch {
|
|
2216
|
+
return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
function saveRegistry(registry, path = getProvidersPath()) {
|
|
2220
|
+
const payload = `${JSON.stringify(registry, null, 2)}
|
|
2221
|
+
`;
|
|
2222
|
+
const backup = `${path}.bak`;
|
|
2223
|
+
if (existsSync3(path)) {
|
|
2224
|
+
try {
|
|
2225
|
+
copyFileSync(path, backup);
|
|
2226
|
+
} catch {
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
const tmp = `${path}.tmp`;
|
|
2230
|
+
writeSecureFile(tmp, payload);
|
|
2231
|
+
renameSync(tmp, path);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// src/registry/url-security.ts
|
|
2235
|
+
import { lookup } from "dns/promises";
|
|
2236
|
+
import ipaddr from "ipaddr.js";
|
|
2237
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
2238
|
+
"169.254.169.254",
|
|
2239
|
+
"metadata.google.internal",
|
|
2240
|
+
"169.254.170.2",
|
|
2241
|
+
"fd00:ec2::254"
|
|
2242
|
+
]);
|
|
2243
|
+
function isBlockedIp(ipStr, allowInsecureLocal) {
|
|
2244
|
+
try {
|
|
2245
|
+
const ip = ipaddr.process(ipStr);
|
|
2246
|
+
const range = ip.range();
|
|
2247
|
+
if (allowInsecureLocal && (range === "loopback" || range === "private")) {
|
|
2248
|
+
return false;
|
|
2249
|
+
}
|
|
2250
|
+
if (range === "loopback") return true;
|
|
2251
|
+
if (range === "private") return true;
|
|
2252
|
+
if (range === "linkLocal") return true;
|
|
2253
|
+
if (range === "uniqueLocal") return true;
|
|
2254
|
+
if (range === "carrierGradeNat") return true;
|
|
2255
|
+
return false;
|
|
2256
|
+
} catch {
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
async function resolveHostAddresses(hostname) {
|
|
2261
|
+
try {
|
|
2262
|
+
ipaddr.parse(hostname);
|
|
2263
|
+
return [hostname];
|
|
2264
|
+
} catch {
|
|
2265
|
+
}
|
|
2266
|
+
try {
|
|
2267
|
+
const records = await lookup(hostname, { all: true, verbatim: true });
|
|
2268
|
+
return records.map((r) => r.address);
|
|
2269
|
+
} catch {
|
|
2270
|
+
return [];
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
async function validateCustomEndpointUrl(rawUrl, opts = {}) {
|
|
2274
|
+
const trimmed = rawUrl.trim();
|
|
2275
|
+
if (!trimmed) {
|
|
2276
|
+
return { ok: false, error: "Base URL is required.", hint: "Example: https://api.example.com/v1" };
|
|
2277
|
+
}
|
|
2278
|
+
let parsed;
|
|
2279
|
+
try {
|
|
2280
|
+
parsed = new URL(trimmed);
|
|
2281
|
+
} catch {
|
|
2282
|
+
return { ok: false, error: "Invalid URL.", hint: "Include https:// and the full base path." };
|
|
2283
|
+
}
|
|
2284
|
+
const allowLocal = opts.allowInsecureLocal === true;
|
|
2285
|
+
if (parsed.protocol === "http:" && !allowLocal) {
|
|
2286
|
+
return {
|
|
2287
|
+
ok: false,
|
|
2288
|
+
error: "Only HTTPS URLs are allowed.",
|
|
2289
|
+
hint: "For local or LAN servers (Ollama, LM Studio, vLLM), allow insecure HTTP when prompted."
|
|
2290
|
+
};
|
|
2291
|
+
} else if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2292
|
+
return { ok: false, error: "URL must use https:// or user-approved http:// for local/LAN servers." };
|
|
2293
|
+
}
|
|
2294
|
+
const rawHostname = parsed.hostname.toLowerCase();
|
|
2295
|
+
const hostname = rawHostname.replace(/^\[(.*)\]$/, "$1");
|
|
2296
|
+
if (BLOCKED_HOSTNAMES.has(hostname)) {
|
|
2297
|
+
return {
|
|
2298
|
+
ok: false,
|
|
2299
|
+
error: "This URL points to a blocked internal/metadata host.",
|
|
2300
|
+
hint: "Use a public API endpoint for your provider."
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
const addresses = await resolveHostAddresses(hostname);
|
|
2304
|
+
if (addresses.length === 0) {
|
|
2305
|
+
return {
|
|
2306
|
+
ok: false,
|
|
2307
|
+
error: `Could not resolve hostname: ${hostname}`,
|
|
2308
|
+
hint: "Check the URL spelling and your network connection."
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
for (const addr of addresses) {
|
|
2312
|
+
try {
|
|
2313
|
+
ipaddr.process(addr);
|
|
2314
|
+
} catch {
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
if (isBlockedIp(addr, allowLocal)) {
|
|
2318
|
+
return {
|
|
2319
|
+
ok: false,
|
|
2320
|
+
error: "URL resolves to a private or restricted network address.",
|
|
2321
|
+
hint: "Use a public HTTPS endpoint, or explicitly allow insecure HTTP for a trusted local/LAN server."
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (parsed.protocol === "http:") {
|
|
2326
|
+
const allResolvedAddressesAreLocal = addresses.every((addr) => {
|
|
2327
|
+
try {
|
|
2328
|
+
const range = ipaddr.process(addr).range();
|
|
2329
|
+
return range === "loopback" || range === "private" || range === "uniqueLocal";
|
|
2330
|
+
} catch {
|
|
2331
|
+
return false;
|
|
2332
|
+
}
|
|
2333
|
+
});
|
|
2334
|
+
if (!allowLocal || !allResolvedAddressesAreLocal) {
|
|
2335
|
+
return {
|
|
2336
|
+
ok: false,
|
|
2337
|
+
error: "HTTP is only allowed for local loopback or private-network addresses.",
|
|
2338
|
+
hint: "Use https://, or allow insecure HTTP for a trusted local/LAN server."
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
const normalizedUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}`.replace(/\/$/, "");
|
|
2343
|
+
return { ok: true, normalizedUrl };
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// src/oauth/claude-identity.ts
|
|
2347
|
+
import { createHash, randomUUID } from "crypto";
|
|
2348
|
+
var CLAUDE_CODE_CLI_VERSION2 = "2.1.195";
|
|
2349
|
+
var CLAUDE_CODE_USER_AGENT = `claude-cli/${CLAUDE_CODE_CLI_VERSION2} (external, cli)`;
|
|
2350
|
+
var CLAUDE_CODE_ENTRYPOINT = process.env.CLAUDE_CODE_ENTRYPOINT ?? "cli";
|
|
2351
|
+
var CLAUDE_CODE_BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
|
|
2352
|
+
var sessionCache = /* @__PURE__ */ new Map();
|
|
2353
|
+
function getOrCreateSessionId(seed) {
|
|
2354
|
+
let id = sessionCache.get(seed);
|
|
2355
|
+
if (!id) {
|
|
2356
|
+
id = randomUUID();
|
|
2357
|
+
sessionCache.set(seed, id);
|
|
2358
|
+
}
|
|
2359
|
+
return id;
|
|
2360
|
+
}
|
|
2361
|
+
function uuidFromHash(input) {
|
|
2362
|
+
const h = createHash("sha256").update(input).digest("hex");
|
|
2363
|
+
return [
|
|
2364
|
+
h.slice(0, 8),
|
|
2365
|
+
h.slice(8, 12),
|
|
2366
|
+
"4" + h.slice(13, 16),
|
|
2367
|
+
(parseInt(h[16], 16) & 3 | 8).toString(16) + h.slice(17, 20),
|
|
2368
|
+
h.slice(20, 32)
|
|
2369
|
+
].join("-");
|
|
2370
|
+
}
|
|
2371
|
+
var HEX64_RE = /^[a-f0-9]{64}$/i;
|
|
2372
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2373
|
+
function resolveCliUserID(providerData, seed) {
|
|
2374
|
+
const v = providerData?.cliUserID;
|
|
2375
|
+
if (typeof v === "string" && HEX64_RE.test(v)) return v;
|
|
2376
|
+
return createHash("sha256").update(`cliUserID:${seed}`).digest("hex");
|
|
2377
|
+
}
|
|
2378
|
+
function resolveAccountUUID(providerData, seed) {
|
|
2379
|
+
const v = providerData?.accountUUID;
|
|
2380
|
+
if (typeof v === "string" && UUID_RE.test(v)) return v;
|
|
2381
|
+
return uuidFromHash(`account:${seed}`);
|
|
2382
|
+
}
|
|
2383
|
+
function buildUserIdJson(deviceId, accountUUID, sessionId) {
|
|
2384
|
+
return JSON.stringify({ device_id: deviceId, account_uuid: accountUUID, session_id: sessionId });
|
|
2385
|
+
}
|
|
2386
|
+
function buildClaudeCodeBillingSystemLine() {
|
|
2387
|
+
return `${CLAUDE_CODE_BILLING_HEADER_PREFIX} cc_version=${CLAUDE_CODE_CLI_VERSION2}.0; cc_entrypoint=${CLAUDE_CODE_ENTRYPOINT};`;
|
|
2388
|
+
}
|
|
2389
|
+
function systemBlockText(block) {
|
|
2390
|
+
if (typeof block === "string") return block;
|
|
2391
|
+
if (block && typeof block === "object" && "text" in block) {
|
|
2392
|
+
const text = block.text;
|
|
2393
|
+
return typeof text === "string" ? text : void 0;
|
|
2394
|
+
}
|
|
2395
|
+
return void 0;
|
|
2396
|
+
}
|
|
2397
|
+
function hasClaudeCodeBillingSystemLine(system) {
|
|
2398
|
+
if (typeof system === "string") return system.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX);
|
|
2399
|
+
if (!Array.isArray(system)) return false;
|
|
2400
|
+
return system.some((block) => systemBlockText(block)?.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX));
|
|
2401
|
+
}
|
|
2402
|
+
function injectClaudeCodeBillingSystemLine(body) {
|
|
2403
|
+
if (hasClaudeCodeBillingSystemLine(body.system)) return;
|
|
2404
|
+
const billingBlock = { type: "text", text: buildClaudeCodeBillingSystemLine() };
|
|
2405
|
+
if (body.system === void 0 || body.system === null) {
|
|
2406
|
+
body.system = [billingBlock];
|
|
2407
|
+
} else if (typeof body.system === "string") {
|
|
2408
|
+
body.system = [billingBlock, { type: "text", text: body.system }];
|
|
2409
|
+
} else if (Array.isArray(body.system)) {
|
|
2410
|
+
body.system = [billingBlock, ...body.system];
|
|
2411
|
+
} else {
|
|
2412
|
+
body.system = [billingBlock];
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
var ALWAYS = [
|
|
2416
|
+
"oauth-2025-04-20",
|
|
2417
|
+
"context-management-2025-06-27",
|
|
2418
|
+
"prompt-caching-scope-2026-01-05"
|
|
2419
|
+
];
|
|
2420
|
+
var AGENT = [
|
|
2421
|
+
"claude-code-20250219",
|
|
2422
|
+
"extended-cache-ttl-2025-04-11",
|
|
2423
|
+
"cache-diagnosis-2026-04-07",
|
|
2424
|
+
"advisor-tool-2026-03-01"
|
|
2425
|
+
];
|
|
2426
|
+
var THINKING = [
|
|
2427
|
+
"interleaved-thinking-2025-05-14",
|
|
2428
|
+
"redact-thinking-2026-02-12",
|
|
2429
|
+
"thinking-token-count-2026-05-13"
|
|
2430
|
+
];
|
|
2431
|
+
var HEAVY = ["advanced-tool-use-2025-11-20", "effort-2025-11-24"];
|
|
2432
|
+
var OPUS_ONLY = ["context-1m-2025-08-07", "mid-conversation-system-2026-04-07"];
|
|
2433
|
+
function selectBetaFlags(body, model, clientBeta) {
|
|
2434
|
+
const hasSystem = !!body.system && (typeof body.system === "string" || Array.isArray(body.system) && body.system.length > 0);
|
|
2435
|
+
const tools = body.tools;
|
|
2436
|
+
const isFullAgent = hasSystem && Array.isArray(tools) && tools.length > 0;
|
|
2437
|
+
const m = (model ?? (typeof body.model === "string" ? body.model : "")).toLowerCase();
|
|
2438
|
+
const isOpus = m.includes("opus");
|
|
2439
|
+
const isSonnetOrOpus = isOpus || m.includes("sonnet");
|
|
2440
|
+
const clientSet = clientBeta ? new Set(clientBeta.split(",").map((s) => s.trim()).filter(Boolean)) : null;
|
|
2441
|
+
const allowThinking = !clientSet || clientSet.has("interleaved-thinking-2025-05-14");
|
|
2442
|
+
const allowHeavy = !clientSet || clientSet.has("advanced-tool-use-2025-11-20") || clientSet.has("effort-2025-11-24");
|
|
2443
|
+
const flags = [...ALWAYS];
|
|
2444
|
+
if (isFullAgent) flags.push(...AGENT);
|
|
2445
|
+
if (isOpus) flags.push(...OPUS_ONLY);
|
|
2446
|
+
if (allowThinking) flags.push(...THINKING);
|
|
2447
|
+
if (isFullAgent && isSonnetOrOpus && allowHeavy) flags.push(...HEAVY);
|
|
2448
|
+
return flags.join(",");
|
|
2449
|
+
}
|
|
2450
|
+
function injectClaudeIdentity(body, providerData, seed) {
|
|
2451
|
+
const deviceId = resolveCliUserID(providerData, seed);
|
|
2452
|
+
const accountUUID = resolveAccountUUID(providerData, seed);
|
|
2453
|
+
const sessionId = getOrCreateSessionId(seed);
|
|
2454
|
+
const userId = buildUserIdJson(deviceId, accountUUID, sessionId);
|
|
2455
|
+
const existing = body.metadata;
|
|
2456
|
+
body.metadata = { ...existing ?? {}, user_id: userId };
|
|
2457
|
+
return { sessionId, userId };
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// src/provider-factory.ts
|
|
2461
|
+
import { wrapLanguageModel, extractReasoningMiddleware } from "ai";
|
|
2462
|
+
|
|
2463
|
+
// src/oauth/responses-websocket.ts
|
|
2464
|
+
var RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
|
|
2465
|
+
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["response.completed", "response.failed", "response.incomplete", "error"]);
|
|
2466
|
+
function isRecord(value) {
|
|
2467
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2468
|
+
}
|
|
2469
|
+
function recordKeys(value) {
|
|
2470
|
+
return isRecord(value) ? Object.keys(value).sort().join(",") : "";
|
|
2471
|
+
}
|
|
2472
|
+
function summarizeResponsesLiteEvent(event) {
|
|
2473
|
+
if (!isRecord(event)) return `kind=${event == null ? "null" : typeof event}`;
|
|
2474
|
+
const parts = [`type=${typeof event.type === "string" ? event.type : "unknown"}`, `keys=${recordKeys(event)}`];
|
|
2475
|
+
if (typeof event.delta === "string") parts.push(`deltaChars=${event.delta.length}`);
|
|
2476
|
+
if (typeof event.output_index === "number") parts.push(`hasOutputIndex=1`);
|
|
2477
|
+
if (typeof event.item_id === "string") parts.push(`hasItemId=1`);
|
|
2478
|
+
if (isRecord(event.item)) {
|
|
2479
|
+
parts.push(`itemType=${typeof event.item.type === "string" ? event.item.type : "unknown"}`);
|
|
2480
|
+
parts.push(`itemKeys=${recordKeys(event.item)}`);
|
|
2481
|
+
if (typeof event.item.arguments === "string") parts.push(`argumentsChars=${event.item.arguments.length}`);
|
|
2482
|
+
}
|
|
2483
|
+
if (isRecord(event.response)) {
|
|
2484
|
+
parts.push(`responseKeys=${recordKeys(event.response)}`);
|
|
2485
|
+
if (Array.isArray(event.response.output)) {
|
|
2486
|
+
parts.push(`outputCount=${event.response.output.length}`);
|
|
2487
|
+
parts.push(`outputTypes=${event.response.output.map((item) => isRecord(item) && typeof item.type === "string" ? item.type : "unknown").join(",")}`);
|
|
2488
|
+
}
|
|
2489
|
+
if (isRecord(event.response.usage)) parts.push(`usageKeys=${recordKeys(event.response.usage)}`);
|
|
2490
|
+
if (typeof event.response.status === "string") parts.push(`status=${event.response.status}`);
|
|
2491
|
+
}
|
|
2492
|
+
if (isRecord(event.error)) {
|
|
2493
|
+
parts.push(`errorKeys=${recordKeys(event.error)}`);
|
|
2494
|
+
if (typeof event.error.message === "string") parts.push(`messageChars=${event.error.message.length}`);
|
|
2495
|
+
}
|
|
2496
|
+
return parts.join(" ");
|
|
2497
|
+
}
|
|
2498
|
+
function createResponsesLiteNormalizeState() {
|
|
2499
|
+
return {
|
|
2500
|
+
nextId: 1,
|
|
2501
|
+
lastOutputIndex: 0,
|
|
2502
|
+
textDeltaForwarded: false,
|
|
2503
|
+
messageAddedIds: /* @__PURE__ */ new Set(),
|
|
2504
|
+
messageDoneIds: /* @__PURE__ */ new Set(),
|
|
2505
|
+
functionCalls: []
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
function nextId(state, prefix) {
|
|
2509
|
+
const id = `${prefix}_${state.nextId}`;
|
|
2510
|
+
state.nextId += 1;
|
|
2511
|
+
return id;
|
|
2512
|
+
}
|
|
2513
|
+
function asString(value) {
|
|
2514
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2515
|
+
}
|
|
2516
|
+
function normalizeErrorEvent(event) {
|
|
2517
|
+
const raw = isRecord(event.error) ? event.error : { message: typeof event.error === "string" ? event.error : "upstream error" };
|
|
2518
|
+
return {
|
|
2519
|
+
type: "error",
|
|
2520
|
+
sequence_number: typeof event.sequence_number === "number" ? event.sequence_number : 0,
|
|
2521
|
+
error: {
|
|
2522
|
+
type: asString(raw.type) ?? "server_error",
|
|
2523
|
+
code: asString(raw.code) ?? "unknown",
|
|
2524
|
+
message: asString(raw.message) ?? "upstream error",
|
|
2525
|
+
...raw.param == null ? {} : { param: raw.param }
|
|
2526
|
+
}
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
function resolveFunctionCall(state, hint) {
|
|
2530
|
+
if (hint.callId) {
|
|
2531
|
+
const byCallId = state.functionCalls.find((entry2) => entry2.callId === hint.callId);
|
|
2532
|
+
if (byCallId) return byCallId;
|
|
2533
|
+
}
|
|
2534
|
+
if (hint.itemId) {
|
|
2535
|
+
const byItemId = state.functionCalls.find((entry2) => entry2.itemId === hint.itemId);
|
|
2536
|
+
if (byItemId) return byItemId;
|
|
2537
|
+
}
|
|
2538
|
+
if (hint.outputIndex !== void 0) {
|
|
2539
|
+
const open3 = [...state.functionCalls].reverse().find((entry2) => entry2.outputIndex === hint.outputIndex && !entry2.done && !(hint.callId && entry2.callId !== hint.callId));
|
|
2540
|
+
if (open3) return open3;
|
|
2541
|
+
}
|
|
2542
|
+
if (hint.callId === void 0 && hint.itemId === void 0 && hint.outputIndex === void 0 && state.lastFunctionCall) {
|
|
2543
|
+
return state.lastFunctionCall;
|
|
2544
|
+
}
|
|
2545
|
+
const entry = {
|
|
2546
|
+
itemId: hint.itemId ?? nextId(state, "fc"),
|
|
2547
|
+
callId: hint.callId ?? hint.itemId ?? nextId(state, "call"),
|
|
2548
|
+
name: "",
|
|
2549
|
+
args: "",
|
|
2550
|
+
upstream: {},
|
|
2551
|
+
outputIndex: hint.outputIndex ?? state.lastOutputIndex,
|
|
2552
|
+
added: false,
|
|
2553
|
+
deltaForwarded: false,
|
|
2554
|
+
doneSeen: false,
|
|
2555
|
+
done: false
|
|
2556
|
+
};
|
|
2557
|
+
state.functionCalls.push(entry);
|
|
2558
|
+
return entry;
|
|
2559
|
+
}
|
|
2560
|
+
function absorbFunctionItem(entry, item, authoritative) {
|
|
2561
|
+
entry.upstream = { ...entry.upstream, ...item };
|
|
2562
|
+
const name = asString(item.name);
|
|
2563
|
+
if (name) entry.name = name;
|
|
2564
|
+
const callId = asString(item.call_id);
|
|
2565
|
+
if (callId) entry.callId = callId;
|
|
2566
|
+
if (authoritative && typeof item.arguments === "string") entry.upstreamArgs = item.arguments;
|
|
2567
|
+
}
|
|
2568
|
+
function resolveFunctionArgs(entry) {
|
|
2569
|
+
if (entry.upstreamArgs) return entry.upstreamArgs;
|
|
2570
|
+
if (entry.args) return entry.args;
|
|
2571
|
+
return entry.upstreamArgs;
|
|
2572
|
+
}
|
|
2573
|
+
function functionItemPayload(entry, extra) {
|
|
2574
|
+
return {
|
|
2575
|
+
...entry.upstream,
|
|
2576
|
+
type: "function_call",
|
|
2577
|
+
id: entry.itemId,
|
|
2578
|
+
call_id: entry.callId,
|
|
2579
|
+
name: entry.name,
|
|
2580
|
+
...extra
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
function functionAddedEvent(entry) {
|
|
2584
|
+
entry.added = true;
|
|
2585
|
+
return {
|
|
2586
|
+
type: "response.output_item.added",
|
|
2587
|
+
output_index: entry.outputIndex,
|
|
2588
|
+
item: functionItemPayload(entry, { arguments: "" })
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
function functionDoneEvent(entry, args) {
|
|
2592
|
+
entry.done = true;
|
|
2593
|
+
return {
|
|
2594
|
+
type: "response.output_item.done",
|
|
2595
|
+
output_index: entry.outputIndex,
|
|
2596
|
+
item: functionItemPayload(entry, { arguments: args, status: "completed" })
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
function completeFunctionCall(entry, args) {
|
|
2600
|
+
if (entry.done) return [];
|
|
2601
|
+
const events = [];
|
|
2602
|
+
if (!entry.added) events.push(functionAddedEvent(entry));
|
|
2603
|
+
if (!entry.deltaForwarded && args.length > 0) {
|
|
2604
|
+
entry.deltaForwarded = true;
|
|
2605
|
+
events.push({
|
|
2606
|
+
type: "response.function_call_arguments.delta",
|
|
2607
|
+
item_id: entry.itemId,
|
|
2608
|
+
output_index: entry.outputIndex,
|
|
2609
|
+
delta: args
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
events.push(functionDoneEvent(entry, args));
|
|
2613
|
+
return events;
|
|
2614
|
+
}
|
|
2615
|
+
function messageText(item) {
|
|
2616
|
+
if (typeof item.text === "string") return item.text;
|
|
2617
|
+
if (!Array.isArray(item.content)) return "";
|
|
2618
|
+
let out = "";
|
|
2619
|
+
for (const part of item.content) {
|
|
2620
|
+
if (isRecord(part) && typeof part.text === "string" && (part.type === "output_text" || part.type === "text")) {
|
|
2621
|
+
out += part.text;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
return out;
|
|
2625
|
+
}
|
|
2626
|
+
function synthesizeMessage(item, outputIndex, state) {
|
|
2627
|
+
const text = messageText(item);
|
|
2628
|
+
if (!text) return [];
|
|
2629
|
+
const id = asString(item.id) ?? nextId(state, "msg");
|
|
2630
|
+
state.lastMessageItemId = id;
|
|
2631
|
+
state.textDeltaForwarded = true;
|
|
2632
|
+
state.messageAddedIds.add(id);
|
|
2633
|
+
state.messageDoneIds.add(id);
|
|
2634
|
+
return [
|
|
2635
|
+
{ type: "response.output_item.added", output_index: outputIndex, item: { type: "message", id } },
|
|
2636
|
+
{ type: "response.output_text.delta", item_id: id, delta: text },
|
|
2637
|
+
{ type: "response.output_item.done", output_index: outputIndex, item: { type: "message", id } }
|
|
2638
|
+
];
|
|
2639
|
+
}
|
|
2640
|
+
function synthesizeFunctionCall(item, outputIndex, state) {
|
|
2641
|
+
const entry = resolveFunctionCall(state, {
|
|
2642
|
+
itemId: asString(item.id),
|
|
2643
|
+
callId: asString(item.call_id),
|
|
2644
|
+
outputIndex
|
|
2645
|
+
});
|
|
2646
|
+
absorbFunctionItem(entry, item, true);
|
|
2647
|
+
state.lastFunctionCall = entry;
|
|
2648
|
+
state.lastOutputIndex = entry.outputIndex;
|
|
2649
|
+
const args = resolveFunctionArgs(entry);
|
|
2650
|
+
if (args === void 0) {
|
|
2651
|
+
entry.doneSeen = true;
|
|
2652
|
+
return [];
|
|
2653
|
+
}
|
|
2654
|
+
return completeFunctionCall(entry, args);
|
|
2655
|
+
}
|
|
2656
|
+
function recoverFromCompletedOutput(response, state) {
|
|
2657
|
+
const recovered = [];
|
|
2658
|
+
if (Array.isArray(response.output)) {
|
|
2659
|
+
response.output.forEach((item, index) => {
|
|
2660
|
+
if (!isRecord(item) || typeof item.type !== "string") return;
|
|
2661
|
+
if (item.type === "message" && !state.textDeltaForwarded) {
|
|
2662
|
+
recovered.push(...synthesizeMessage(item, index, state));
|
|
2663
|
+
} else if (item.type === "function_call") {
|
|
2664
|
+
recovered.push(...synthesizeFunctionCall(item, index, state));
|
|
2665
|
+
}
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
for (const entry of state.functionCalls) {
|
|
2669
|
+
if (entry.done || !entry.doneSeen) continue;
|
|
2670
|
+
recovered.push(normalizeErrorEvent({
|
|
2671
|
+
error: {
|
|
2672
|
+
type: "invalid_response",
|
|
2673
|
+
code: "incomplete_function_call",
|
|
2674
|
+
message: `Provider ended the response without arguments for function call "${entry.callId}"${entry.name ? ` (${entry.name})` : ""}.`
|
|
2675
|
+
}
|
|
2676
|
+
}));
|
|
2677
|
+
}
|
|
2678
|
+
return recovered;
|
|
2679
|
+
}
|
|
2680
|
+
function normalizeResponsesLiteEvent(event, state) {
|
|
2681
|
+
if (!isRecord(event) || typeof event.type !== "string") return [event];
|
|
2682
|
+
if (event.type === "error") return [normalizeErrorEvent(event)];
|
|
2683
|
+
if (event.type === "response.output_item.added" && isRecord(event.item)) {
|
|
2684
|
+
const outputIndex = typeof event.output_index === "number" ? event.output_index : state.lastOutputIndex;
|
|
2685
|
+
state.lastOutputIndex = outputIndex;
|
|
2686
|
+
if (event.item.type === "message") {
|
|
2687
|
+
const id = asString(event.item.id) ?? nextId(state, "msg");
|
|
2688
|
+
state.lastMessageItemId = id;
|
|
2689
|
+
state.messageAddedIds.add(id);
|
|
2690
|
+
return [{ ...event, output_index: outputIndex, item: { ...event.item, id } }];
|
|
2691
|
+
}
|
|
2692
|
+
if (event.item.type === "function_call") {
|
|
2693
|
+
const entry = resolveFunctionCall(state, {
|
|
2694
|
+
itemId: asString(event.item.id),
|
|
2695
|
+
callId: asString(event.item.call_id),
|
|
2696
|
+
outputIndex
|
|
2697
|
+
});
|
|
2698
|
+
absorbFunctionItem(entry, event.item, false);
|
|
2699
|
+
entry.outputIndex = outputIndex;
|
|
2700
|
+
entry.added = true;
|
|
2701
|
+
state.lastFunctionCall = entry;
|
|
2702
|
+
return [{ ...event, output_index: outputIndex, item: functionItemPayload(entry, { arguments: "" }) }];
|
|
2703
|
+
}
|
|
2704
|
+
return [{ ...event, output_index: outputIndex }];
|
|
2705
|
+
}
|
|
2706
|
+
if (event.type === "response.output_item.done" && isRecord(event.item)) {
|
|
2707
|
+
const outputIndex = typeof event.output_index === "number" ? event.output_index : state.lastOutputIndex;
|
|
2708
|
+
if (event.item.type === "function_call") {
|
|
2709
|
+
const entry = resolveFunctionCall(state, {
|
|
2710
|
+
itemId: asString(event.item.id),
|
|
2711
|
+
callId: asString(event.item.call_id),
|
|
2712
|
+
outputIndex
|
|
2713
|
+
});
|
|
2714
|
+
absorbFunctionItem(entry, event.item, true);
|
|
2715
|
+
entry.outputIndex = outputIndex;
|
|
2716
|
+
entry.doneSeen = true;
|
|
2717
|
+
state.lastFunctionCall = entry;
|
|
2718
|
+
state.lastOutputIndex = outputIndex;
|
|
2719
|
+
const args = resolveFunctionArgs(entry);
|
|
2720
|
+
if (args === void 0 || !entry.name) return [];
|
|
2721
|
+
if (entry.done) return [];
|
|
2722
|
+
const events = [];
|
|
2723
|
+
if (!entry.added) events.push(functionAddedEvent(entry));
|
|
2724
|
+
events.push({ ...event, output_index: outputIndex, item: functionItemPayload(entry, { arguments: args, status: "completed" }) });
|
|
2725
|
+
entry.done = true;
|
|
2726
|
+
return events;
|
|
2727
|
+
}
|
|
2728
|
+
if (event.item.type === "message") {
|
|
2729
|
+
const id = asString(event.item.id) ?? state.lastMessageItemId ?? nextId(state, "msg");
|
|
2730
|
+
state.lastMessageItemId = id;
|
|
2731
|
+
state.messageDoneIds.add(id);
|
|
2732
|
+
return [{ ...event, output_index: outputIndex, item: { ...event.item, id } }];
|
|
2733
|
+
}
|
|
2734
|
+
return [{ ...event, output_index: outputIndex }];
|
|
2735
|
+
}
|
|
2736
|
+
if (event.type === "response.output_text.delta") {
|
|
2737
|
+
const itemId = asString(event.item_id) ?? state.lastMessageItemId ?? nextId(state, "msg");
|
|
2738
|
+
state.lastMessageItemId = itemId;
|
|
2739
|
+
state.textDeltaForwarded = true;
|
|
2740
|
+
const events = [];
|
|
2741
|
+
if (!state.messageAddedIds.has(itemId)) {
|
|
2742
|
+
events.push({
|
|
2743
|
+
type: "response.output_item.added",
|
|
2744
|
+
output_index: state.lastOutputIndex,
|
|
2745
|
+
item: { type: "message", id: itemId }
|
|
2746
|
+
});
|
|
2747
|
+
state.messageAddedIds.add(itemId);
|
|
2748
|
+
}
|
|
2749
|
+
events.push({ ...event, item_id: itemId, delta: typeof event.delta === "string" ? event.delta : "" });
|
|
2750
|
+
return events;
|
|
2751
|
+
}
|
|
2752
|
+
if (event.type === "response.function_call_arguments.delta") {
|
|
2753
|
+
const entry = resolveFunctionCall(state, {
|
|
2754
|
+
itemId: asString(event.item_id),
|
|
2755
|
+
outputIndex: typeof event.output_index === "number" ? event.output_index : void 0
|
|
2756
|
+
});
|
|
2757
|
+
const delta = typeof event.delta === "string" ? event.delta : "";
|
|
2758
|
+
entry.args += delta;
|
|
2759
|
+
entry.deltaForwarded = true;
|
|
2760
|
+
state.lastFunctionCall = entry;
|
|
2761
|
+
state.lastOutputIndex = entry.outputIndex;
|
|
2762
|
+
return [{ ...event, item_id: entry.itemId, output_index: entry.outputIndex, delta }];
|
|
2763
|
+
}
|
|
2764
|
+
if (event.type === "response.completed" || event.type === "response.incomplete") {
|
|
2765
|
+
const response = isRecord(event.response) ? event.response : {};
|
|
2766
|
+
const recovered = recoverFromCompletedOutput(response, state);
|
|
2767
|
+
if (state.lastMessageItemId && state.textDeltaForwarded && !state.messageDoneIds.has(state.lastMessageItemId)) {
|
|
2768
|
+
recovered.push({
|
|
2769
|
+
type: "response.output_item.done",
|
|
2770
|
+
output_index: state.lastOutputIndex,
|
|
2771
|
+
item: { type: "message", id: state.lastMessageItemId }
|
|
2772
|
+
});
|
|
2773
|
+
state.messageDoneIds.add(state.lastMessageItemId);
|
|
2774
|
+
}
|
|
2775
|
+
return [...recovered, event];
|
|
2776
|
+
}
|
|
2777
|
+
return [event];
|
|
2778
|
+
}
|
|
2779
|
+
function toHeaderRecord(headers) {
|
|
2780
|
+
const out = {};
|
|
2781
|
+
if (!headers) return out;
|
|
2782
|
+
if (headers instanceof Headers) {
|
|
2783
|
+
headers.forEach((value, key) => {
|
|
2784
|
+
out[key] = value;
|
|
2785
|
+
});
|
|
2786
|
+
} else if (Array.isArray(headers)) {
|
|
2787
|
+
for (const [key, value] of headers) out[key] = value;
|
|
2788
|
+
} else {
|
|
2789
|
+
for (const [key, value] of Object.entries(headers)) out[key] = String(value);
|
|
2790
|
+
}
|
|
2791
|
+
return out;
|
|
2792
|
+
}
|
|
2793
|
+
function hasResponsesLiteHeader(headers) {
|
|
2794
|
+
return Object.entries(headers).some(
|
|
2795
|
+
([k, v]) => k.toLowerCase() === RESPONSES_LITE_HEADER && v.toLowerCase() === "true"
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
function bodyToString(body) {
|
|
2799
|
+
if (body == null) return "";
|
|
2800
|
+
if (typeof body === "string") return body;
|
|
2801
|
+
if (body instanceof Uint8Array) return Buffer.from(body).toString("utf8");
|
|
2802
|
+
if (body instanceof ArrayBuffer) return Buffer.from(new Uint8Array(body)).toString("utf8");
|
|
2803
|
+
return String(body);
|
|
2804
|
+
}
|
|
2805
|
+
function applyResponsesLiteShape(payload) {
|
|
2806
|
+
const reasoning = payload.reasoning && typeof payload.reasoning === "object" ? { ...payload.reasoning } : {};
|
|
2807
|
+
reasoning.context = "all_turns";
|
|
2808
|
+
return {
|
|
2809
|
+
...payload,
|
|
2810
|
+
reasoning,
|
|
2811
|
+
parallel_tool_calls: false,
|
|
2812
|
+
store: false
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
function createResponsesWebSocketFetch(wsUrl, log) {
|
|
2816
|
+
const debug = (msg) => {
|
|
2817
|
+
try {
|
|
2818
|
+
log?.(`ws: ${msg}`);
|
|
2819
|
+
} catch {
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
return async (_input, init) => {
|
|
2823
|
+
const { WebSocket } = await import("ws");
|
|
2824
|
+
const headers = toHeaderRecord(init?.headers);
|
|
2825
|
+
headers["OpenAI-Beta"] = CODEX_RESPONSES_WEBSOCKETS_BETA;
|
|
2826
|
+
debug(`connecting ${wsUrl} headers=[${Object.keys(headers).join(", ")}]`);
|
|
2827
|
+
let payload = {};
|
|
2828
|
+
try {
|
|
2829
|
+
payload = JSON.parse(bodyToString(init?.body));
|
|
2830
|
+
} catch {
|
|
2831
|
+
payload = {};
|
|
2832
|
+
}
|
|
2833
|
+
if (hasResponsesLiteHeader(headers)) {
|
|
2834
|
+
payload = applyResponsesLiteShape(payload);
|
|
2835
|
+
}
|
|
2836
|
+
debug(
|
|
2837
|
+
`request type=response.create keys=${Object.keys(payload).sort().join(",")} toolCount=${Array.isArray(payload.tools) ? payload.tools.length : 0} store=${String(payload.store)} parallelToolCalls=${String(payload.parallel_tool_calls)} reasoningKeys=${recordKeys(payload.reasoning)}`
|
|
2838
|
+
);
|
|
2839
|
+
const outgoing = JSON.stringify({ type: "response.create", ...payload });
|
|
2840
|
+
const encoder = new TextEncoder();
|
|
2841
|
+
let socket;
|
|
2842
|
+
let frameCount = 0;
|
|
2843
|
+
const normalizeState = createResponsesLiteNormalizeState();
|
|
2844
|
+
const stream = new ReadableStream({
|
|
2845
|
+
start(controller) {
|
|
2846
|
+
let closed = false;
|
|
2847
|
+
const close = () => {
|
|
2848
|
+
if (closed) return;
|
|
2849
|
+
closed = true;
|
|
2850
|
+
try {
|
|
2851
|
+
controller.close();
|
|
2852
|
+
} catch {
|
|
2853
|
+
}
|
|
2854
|
+
try {
|
|
2855
|
+
socket.close();
|
|
2856
|
+
} catch {
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
const fail = (message) => {
|
|
2860
|
+
if (closed) return;
|
|
2861
|
+
debug(`fail messageChars=${message.length}`);
|
|
2862
|
+
try {
|
|
2863
|
+
const [errorEvent] = normalizeResponsesLiteEvent({ type: "error", error: { message } }, normalizeState);
|
|
2864
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(errorEvent)}
|
|
2865
|
+
|
|
2866
|
+
`));
|
|
2867
|
+
} catch {
|
|
2868
|
+
}
|
|
2869
|
+
close();
|
|
2870
|
+
};
|
|
2871
|
+
socket = new WebSocket(wsUrl, { headers });
|
|
2872
|
+
socket.on("open", () => {
|
|
2873
|
+
debug(`open \u2014 sending ${outgoing.length}B payload`);
|
|
2874
|
+
socket.send(outgoing);
|
|
2875
|
+
});
|
|
2876
|
+
socket.on("unexpected-response", (_req, res) => {
|
|
2877
|
+
debug(`unexpected-response status=${res.statusCode}`);
|
|
2878
|
+
});
|
|
2879
|
+
socket.on("message", (data) => {
|
|
2880
|
+
const text = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
|
|
2881
|
+
frameCount += 1;
|
|
2882
|
+
let event;
|
|
2883
|
+
try {
|
|
2884
|
+
event = JSON.parse(text);
|
|
2885
|
+
} catch {
|
|
2886
|
+
debug(`frame#${frameCount} non-json chars=${text.length}`);
|
|
2887
|
+
controller.enqueue(encoder.encode(`data: ${text.replace(/\r?\n/g, " ")}
|
|
2888
|
+
|
|
2889
|
+
`));
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
if (frameCount <= 8) debug(`frame#${frameCount} ${summarizeResponsesLiteEvent(event)}`);
|
|
2893
|
+
for (const next of normalizeResponsesLiteEvent(event, normalizeState)) {
|
|
2894
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(next)}
|
|
2895
|
+
|
|
2896
|
+
`));
|
|
2897
|
+
}
|
|
2898
|
+
const type = isRecord(event) && typeof event.type === "string" ? event.type : void 0;
|
|
2899
|
+
if (type && TERMINAL_EVENT_TYPES.has(type)) {
|
|
2900
|
+
debug(`terminal event: ${type} (after ${frameCount} frames)`);
|
|
2901
|
+
close();
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2904
|
+
socket.on("error", (err) => fail(err.message));
|
|
2905
|
+
socket.on("close", (code, reason) => {
|
|
2906
|
+
debug(`close code=${code} frames=${frameCount}${reason?.length ? ` reasonChars=${reason.length}` : ""}`);
|
|
2907
|
+
if (closed) return;
|
|
2908
|
+
if (code === 1e3 || code === 1005) {
|
|
2909
|
+
close();
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
fail(`WebSocket closed (${code})${reason?.length ? `: ${reason.toString("utf8")}` : ""}`);
|
|
2913
|
+
});
|
|
2914
|
+
const signal = init?.signal;
|
|
2915
|
+
if (signal) {
|
|
2916
|
+
if (signal.aborted) {
|
|
2917
|
+
close();
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
signal.addEventListener("abort", close, { once: true });
|
|
2921
|
+
}
|
|
2922
|
+
},
|
|
2923
|
+
cancel() {
|
|
2924
|
+
try {
|
|
2925
|
+
socket?.close();
|
|
2926
|
+
} catch {
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2930
|
+
return new Response(stream, {
|
|
2931
|
+
status: 200,
|
|
2932
|
+
headers: { "content-type": "text/event-stream; charset=utf-8" }
|
|
2933
|
+
});
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
// src/provider-factory.ts
|
|
2938
|
+
var RESPONSES_ONLY_PREFIXES = [
|
|
2939
|
+
"gpt-5-codex",
|
|
2940
|
+
"gpt-5-pro",
|
|
2941
|
+
"gpt-5.2-pro",
|
|
2942
|
+
"o3",
|
|
2943
|
+
"o4"
|
|
2944
|
+
];
|
|
2945
|
+
var factoryCache = /* @__PURE__ */ new Map();
|
|
2946
|
+
function modelPrefersResponsesApi(modelId) {
|
|
2947
|
+
const lower = modelId.toLowerCase();
|
|
2948
|
+
if (RESPONSES_ONLY_PREFIXES.some((prefix) => lower === prefix || lower.startsWith(`${prefix}-`))) {
|
|
2949
|
+
return true;
|
|
2950
|
+
}
|
|
2951
|
+
const gpt5Minor = lower.match(/^gpt-5\.(\d+)(?:-|$)/);
|
|
2952
|
+
if (gpt5Minor && Number(gpt5Minor[1]) >= 4) return true;
|
|
2953
|
+
if (lower.startsWith("gpt-") && lower.includes("-codex")) return true;
|
|
2954
|
+
if (lower.startsWith("grok-") && (lower.includes("multi-agent") || lower.includes("multiagent"))) return true;
|
|
2955
|
+
return false;
|
|
2956
|
+
}
|
|
2957
|
+
var OPENAI_CHAT_COMPLETIONS_ONLY = [
|
|
2958
|
+
"davinci-002",
|
|
2959
|
+
"babbage-002",
|
|
2960
|
+
"gpt-3.5-turbo-instruct"
|
|
2961
|
+
];
|
|
2962
|
+
function shouldUseOpenAiResponsesEndpoint(modelId) {
|
|
2963
|
+
return !OPENAI_CHAT_COMPLETIONS_ONLY.includes(modelId.toLowerCase());
|
|
2964
|
+
}
|
|
2965
|
+
function isSdkMigratedNpm(npm) {
|
|
2966
|
+
return !!npm && npm !== "@ai-sdk/anthropic";
|
|
2967
|
+
}
|
|
2968
|
+
function maxToolsForNpm(npm) {
|
|
2969
|
+
return npm === "@ai-sdk/groq" ? 128 : void 0;
|
|
2970
|
+
}
|
|
2971
|
+
function resolveProviderNpm(npm) {
|
|
2972
|
+
return npm === "venice-ai-sdk-provider" ? "@ai-sdk/openai-compatible" : npm;
|
|
2973
|
+
}
|
|
2974
|
+
function findCreateFactory(mod) {
|
|
2975
|
+
for (const value of Object.values(mod)) {
|
|
2976
|
+
if (typeof value === "function" && value.name.startsWith("create")) {
|
|
2977
|
+
return value;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
throw new Error("No create* factory export found in provider package");
|
|
2981
|
+
}
|
|
2982
|
+
async function loadSdkProviderFactory(npm) {
|
|
2983
|
+
let cached = factoryCache.get(npm);
|
|
2984
|
+
if (!cached) {
|
|
2985
|
+
cached = (async () => {
|
|
2986
|
+
try {
|
|
2987
|
+
const mod = await import(npm);
|
|
2988
|
+
return findCreateFactory(mod);
|
|
2989
|
+
} catch (err) {
|
|
2990
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
2991
|
+
if (code === "ERR_MODULE_NOT_FOUND") {
|
|
2992
|
+
throw new Error(`SDK provider package not installed: ${npm}. Run: npm install ${npm}`);
|
|
2993
|
+
}
|
|
2994
|
+
throw err;
|
|
2995
|
+
}
|
|
2996
|
+
})();
|
|
2997
|
+
factoryCache.set(npm, cached);
|
|
2998
|
+
cached.catch(() => factoryCache.delete(npm));
|
|
2999
|
+
}
|
|
3000
|
+
return cached;
|
|
3001
|
+
}
|
|
3002
|
+
async function createLanguageModel(spec) {
|
|
3003
|
+
const npm = resolveProviderNpm(spec.npm);
|
|
3004
|
+
const { modelId, apiKey, baseURL } = spec;
|
|
3005
|
+
if (npm === VERTEX_ANTHROPIC_NPM) {
|
|
3006
|
+
if (!spec.vertex?.project) {
|
|
3007
|
+
throw new Error("Vertex project is required for @ai-sdk/google-vertex/anthropic");
|
|
3008
|
+
}
|
|
3009
|
+
const { createVertexAnthropic } = await import("@ai-sdk/google-vertex/anthropic");
|
|
3010
|
+
const vertex = createVertexAnthropic({
|
|
3011
|
+
project: spec.vertex.project,
|
|
3012
|
+
location: spec.vertex.location
|
|
3013
|
+
});
|
|
3014
|
+
return vertex(modelId);
|
|
3015
|
+
}
|
|
3016
|
+
if (npm === "@ai-sdk/openai") {
|
|
3017
|
+
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
3018
|
+
const accountId = spec.authType === "oauth" ? spec.oauthAccountId ?? extractOpenAiAccountId({ access_token: apiKey }) : void 0;
|
|
3019
|
+
const oauthOptions = spec.authType === "oauth" ? {
|
|
3020
|
+
apiKey,
|
|
3021
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
3022
|
+
headers: {
|
|
3023
|
+
...accountId ? { "ChatGPT-Account-Id": accountId } : {},
|
|
3024
|
+
originator: "relay-ai",
|
|
3025
|
+
// Responses-Lite models (backend prefer_websockets/use_responses_lite,
|
|
3026
|
+
// e.g. gpt-5.6-luna) require these on the request.
|
|
3027
|
+
...spec.useResponsesLite ? { version: CODEX_RESPONSES_LITE_VERSION, "x-openai-internal-codex-responses-lite": "true" } : {}
|
|
3028
|
+
},
|
|
3029
|
+
// Models the backend flags with prefer_websockets are only served over
|
|
3030
|
+
// the WebSocket Responses transport, not HTTP.
|
|
3031
|
+
...spec.preferWebSockets ? { fetch: createResponsesWebSocketFetch(CODEX_RESPONSES_LITE_WS_URL, spec.onDebug) } : {}
|
|
3032
|
+
} : { apiKey };
|
|
3033
|
+
const openai = createOpenAI(oauthOptions);
|
|
3034
|
+
return shouldUseOpenAiResponsesEndpoint(modelId) ? openai.responses(modelId) : openai.chat(modelId);
|
|
3035
|
+
}
|
|
3036
|
+
if (npm === "@ai-sdk/xai") {
|
|
3037
|
+
const { createXai } = await import("@ai-sdk/xai");
|
|
3038
|
+
const xai = createXai({ apiKey });
|
|
3039
|
+
return modelPrefersResponsesApi(modelId) ? xai.responses(modelId) : xai(modelId);
|
|
3040
|
+
}
|
|
3041
|
+
if (npm === "@ai-sdk/google") {
|
|
3042
|
+
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
3043
|
+
const google = createGoogleGenerativeAI({ apiKey });
|
|
3044
|
+
return google(modelId);
|
|
3045
|
+
}
|
|
3046
|
+
if (npm === "@ai-sdk/anthropic") {
|
|
3047
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
3048
|
+
const root = baseURL?.replace(/\/v1\/?$/, "").replace(/\/$/, "");
|
|
3049
|
+
const anthropicOptions = spec.authType === "oauth" ? {
|
|
3050
|
+
authToken: apiKey,
|
|
3051
|
+
...spec.providerId === "claude-code" ? {
|
|
3052
|
+
headers: {
|
|
3053
|
+
"User-Agent": CLAUDE_CODE_USER_AGENT,
|
|
3054
|
+
"x-app": "cli",
|
|
3055
|
+
"X-Claude-Code-Session-Id": injectClaudeIdentity(
|
|
3056
|
+
{},
|
|
3057
|
+
spec.providerData,
|
|
3058
|
+
spec.oauthAccountId ?? apiKey
|
|
3059
|
+
).sessionId
|
|
3060
|
+
}
|
|
3061
|
+
} : {}
|
|
3062
|
+
} : { apiKey };
|
|
3063
|
+
if (spec.headers) {
|
|
3064
|
+
anthropicOptions.headers = { ...anthropicOptions.headers, ...spec.headers };
|
|
3065
|
+
}
|
|
3066
|
+
if (!root || root === "https://api.anthropic.com") {
|
|
3067
|
+
return createAnthropic(anthropicOptions)(modelId);
|
|
3068
|
+
}
|
|
3069
|
+
const sdkBase = baseURL.endsWith("/v1") ? baseURL : `${root}/v1`;
|
|
3070
|
+
return createAnthropic({ ...anthropicOptions, baseURL: sdkBase })(modelId);
|
|
3071
|
+
}
|
|
3072
|
+
let model;
|
|
3073
|
+
if (npm === "@ai-sdk/openai-compatible") {
|
|
3074
|
+
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible");
|
|
3075
|
+
const runtimeApiKey = formatClineRuntimeCredential(spec.providerId, spec.authType, apiKey);
|
|
3076
|
+
const options = {
|
|
3077
|
+
name: spec.providerId ?? "openai-compatible",
|
|
3078
|
+
baseURL: baseURL ?? "",
|
|
3079
|
+
...runtimeApiKey.trim() ? { apiKey: runtimeApiKey } : {},
|
|
3080
|
+
...spec.headers ? { headers: spec.headers } : {},
|
|
3081
|
+
...isClinePassOAuth(spec.providerId, spec.authType) && spec.refreshToken ? {
|
|
3082
|
+
fetch: createClinePassOAuthFetch(
|
|
3083
|
+
runtimeApiKey,
|
|
3084
|
+
spec.refreshToken,
|
|
3085
|
+
spec.onTokenRefreshed
|
|
3086
|
+
)
|
|
3087
|
+
} : {}
|
|
3088
|
+
};
|
|
3089
|
+
model = createOpenAICompatible({
|
|
3090
|
+
...options
|
|
3091
|
+
})(modelId);
|
|
3092
|
+
} else if (npm === "@openrouter/ai-sdk-provider") {
|
|
3093
|
+
const { createOpenRouter } = await import("@openrouter/ai-sdk-provider");
|
|
3094
|
+
model = createOpenRouter({ apiKey, baseURL, ...spec.headers ? { headers: spec.headers } : {} })(modelId);
|
|
3095
|
+
} else {
|
|
3096
|
+
const create = await loadSdkProviderFactory(npm);
|
|
3097
|
+
const provider = create({
|
|
3098
|
+
apiKey,
|
|
3099
|
+
...baseURL ? { baseURL } : {},
|
|
3100
|
+
...spec.headers ? { headers: spec.headers } : {}
|
|
3101
|
+
});
|
|
3102
|
+
model = provider(modelId);
|
|
3103
|
+
}
|
|
3104
|
+
const isReasoning = modelId.toLowerCase().match(/deepseek-r1|think|reasoning|qwq/);
|
|
3105
|
+
if (isReasoning) {
|
|
3106
|
+
return wrapLanguageModel({
|
|
3107
|
+
model,
|
|
3108
|
+
middleware: [extractReasoningMiddleware({ tagName: "think" })]
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
return model;
|
|
3112
|
+
}
|
|
3113
|
+
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3114
|
+
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3115
|
+
var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3116
|
+
var MISTRAL_EFFORT_LEVELS = ["high", "off"];
|
|
3117
|
+
var XAI_CHAT_EFFORT_LEVELS = ["low", "high"];
|
|
3118
|
+
var XAI_RESPONSES_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3119
|
+
var OPENROUTER_EFFORT_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
3120
|
+
var DEEPSEEK_EFFORT_LEVELS = ["high", "max", "off"];
|
|
3121
|
+
var GLM_52_EFFORT_LEVELS = ["high", "xhigh"];
|
|
3122
|
+
var EMPTY_REASONING = {
|
|
3123
|
+
levels: [],
|
|
3124
|
+
defaultLevel: "",
|
|
3125
|
+
supportsSummaries: false,
|
|
3126
|
+
mode: "none",
|
|
3127
|
+
source: "none",
|
|
3128
|
+
confidence: "inferred"
|
|
3129
|
+
};
|
|
3130
|
+
var EFFORT_DESCRIPTIONS = {
|
|
3131
|
+
off: "Turn off extended reasoning",
|
|
3132
|
+
none: "No reasoning",
|
|
3133
|
+
minimal: "Minimal reasoning",
|
|
3134
|
+
low: "Light reasoning",
|
|
3135
|
+
medium: "Balanced reasoning",
|
|
3136
|
+
high: "Deep reasoning",
|
|
3137
|
+
xhigh: "Maximum reasoning",
|
|
3138
|
+
max: "Maximum effort"
|
|
3139
|
+
};
|
|
3140
|
+
var GEMINI_25_BUDGETS = {
|
|
3141
|
+
low: 1024,
|
|
3142
|
+
medium: 4096,
|
|
3143
|
+
high: 8192,
|
|
3144
|
+
xhigh: 16384,
|
|
3145
|
+
max: 16384,
|
|
3146
|
+
minimal: 512,
|
|
3147
|
+
none: 0
|
|
3148
|
+
};
|
|
3149
|
+
function isClaudeReasoningModel(modelId) {
|
|
3150
|
+
const lower = modelId.toLowerCase();
|
|
3151
|
+
if (!lower.startsWith("claude-")) return false;
|
|
3152
|
+
if (lower.includes("fable") || lower.includes("mythos")) return true;
|
|
3153
|
+
const m = lower.match(/claude-(?:opus|sonnet|haiku)-(\d+)-(\d+)/);
|
|
3154
|
+
if (!m) return false;
|
|
3155
|
+
const major = Number(m[1]);
|
|
3156
|
+
const minor = Number(m[2]);
|
|
3157
|
+
return major > 4 || major === 4 && minor >= 6;
|
|
3158
|
+
}
|
|
3159
|
+
function isGeminiReasoningModel(modelId) {
|
|
3160
|
+
const lower = modelId.toLowerCase();
|
|
3161
|
+
return lower.startsWith("gemini-2.5-") || lower.startsWith("gemini-3") || lower.startsWith("gemini-3.");
|
|
3162
|
+
}
|
|
3163
|
+
function isGemini3Model(modelId) {
|
|
3164
|
+
const lower = modelId.toLowerCase();
|
|
3165
|
+
return lower.startsWith("gemini-3") || lower.startsWith("gemini-3.");
|
|
3166
|
+
}
|
|
3167
|
+
function isMistralReasoningModel(modelId) {
|
|
3168
|
+
const lower = modelId.toLowerCase();
|
|
3169
|
+
return lower.startsWith("mistral-") || lower.startsWith("magistral-") || lower.startsWith("ministral-") || lower.includes("reasoning");
|
|
3170
|
+
}
|
|
3171
|
+
function isXaiReasoningEffortModel(modelId) {
|
|
3172
|
+
const lower = modelId.toLowerCase();
|
|
3173
|
+
if (lower.includes("non-reasoning")) return false;
|
|
3174
|
+
if (lower.startsWith("grok-build")) return false;
|
|
3175
|
+
if (lower.startsWith("grok-imagine")) return false;
|
|
3176
|
+
if (modelPrefersResponsesApi(modelId)) return true;
|
|
3177
|
+
if (lower === "grok-4.3" || lower.startsWith("grok-4.3-")) return true;
|
|
3178
|
+
if (lower === "grok-4.5" || lower.startsWith("grok-4.5-")) return true;
|
|
3179
|
+
if (lower.includes("-reasoning")) return true;
|
|
3180
|
+
return false;
|
|
3181
|
+
}
|
|
3182
|
+
function xaiDefaultReasoningEffort(modelId) {
|
|
3183
|
+
const lower = modelId.toLowerCase();
|
|
3184
|
+
if (lower === "grok-4.5" || lower.startsWith("grok-4.5-")) return "high";
|
|
3185
|
+
return "low";
|
|
3186
|
+
}
|
|
3187
|
+
function isDeepSeekReasoningModel(modelId) {
|
|
3188
|
+
const lower = modelId.toLowerCase();
|
|
3189
|
+
return lower === "deepseek-v4-flash" || lower === "deepseek-v4-pro" || lower.startsWith("deepseek-v4-flash-") || lower.startsWith("deepseek-v4-pro-") || lower === "deepseek-reasoner" || lower === "deepseek-chat";
|
|
3190
|
+
}
|
|
3191
|
+
function isKimiReasoningModel(modelId) {
|
|
3192
|
+
const lower = modelId.toLowerCase();
|
|
3193
|
+
return lower.startsWith("kimi-");
|
|
3194
|
+
}
|
|
3195
|
+
function isGlm52ReasoningModel(modelId) {
|
|
3196
|
+
const lower = modelId.toLowerCase();
|
|
3197
|
+
return lower === "glm-5.2" || lower === "z-ai/glm-5.2" || lower === "zai/glm-5.2" || lower === "zai-org/glm-5.2" || lower === "zai-org/glm5.2" || lower === "glm5.2";
|
|
3198
|
+
}
|
|
3199
|
+
function toCamelCase(str) {
|
|
3200
|
+
return str.replace(/[-_]([a-z])/g, (_, g) => g.toUpperCase());
|
|
3201
|
+
}
|
|
3202
|
+
function hasSupportedParameter(metadata, param) {
|
|
3203
|
+
return (metadata?.supportedParameters ?? []).some((p) => p === param);
|
|
3204
|
+
}
|
|
3205
|
+
function isOpenRouterRoute(npm, metadata) {
|
|
3206
|
+
return npm === "@openrouter/ai-sdk-provider" || metadata?.providerId === "openrouter" || metadata?.apiBaseUrl?.includes("openrouter.ai") === true;
|
|
3207
|
+
}
|
|
3208
|
+
function openRouterReasoningCapabilities(metadata) {
|
|
3209
|
+
if (metadata?.supportedParameters && !hasSupportedParameter(metadata, "reasoning")) {
|
|
3210
|
+
return {
|
|
3211
|
+
...EMPTY_REASONING,
|
|
3212
|
+
source: "provider-metadata",
|
|
3213
|
+
confidence: "documented"
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
3217
|
+
return {
|
|
3218
|
+
levels: [...OPENROUTER_EFFORT_LEVELS],
|
|
3219
|
+
defaultLevel: "medium",
|
|
3220
|
+
supportsSummaries: false,
|
|
3221
|
+
mode: "controllable",
|
|
3222
|
+
source: "provider-metadata",
|
|
3223
|
+
confidence: "documented",
|
|
3224
|
+
wireFormat: { kind: "openrouter-reasoning" }
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
if (metadata?.reasoning) {
|
|
3228
|
+
return {
|
|
3229
|
+
...EMPTY_REASONING,
|
|
3230
|
+
mode: "internal-only",
|
|
3231
|
+
source: "model-metadata",
|
|
3232
|
+
confidence: "inferred"
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
return EMPTY_REASONING;
|
|
3236
|
+
}
|
|
3237
|
+
function mapCodexEffortToDeepSeek(effort) {
|
|
3238
|
+
switch (effort) {
|
|
3239
|
+
case "off":
|
|
3240
|
+
case "none":
|
|
3241
|
+
return "off";
|
|
3242
|
+
case "low":
|
|
3243
|
+
case "medium":
|
|
3244
|
+
case "high":
|
|
3245
|
+
return "high";
|
|
3246
|
+
case "xhigh":
|
|
3247
|
+
case "max":
|
|
3248
|
+
return "max";
|
|
3249
|
+
default:
|
|
3250
|
+
if (effort === "high" || effort === "max") return effort;
|
|
3251
|
+
return void 0;
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
function deepSeekEffortProviderOptions(effort) {
|
|
3255
|
+
const mapped = mapCodexEffortToDeepSeek(effort);
|
|
3256
|
+
if (!mapped) return void 0;
|
|
3257
|
+
const thinking = { type: mapped === "off" ? "disabled" : "enabled" };
|
|
3258
|
+
const spread = { thinking };
|
|
3259
|
+
if (mapped === "off") {
|
|
3260
|
+
return {
|
|
3261
|
+
deepseek: spread,
|
|
3262
|
+
openaiCompatible: spread
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
return {
|
|
3266
|
+
openaiCompatible: { reasoningEffort: mapped, ...spread },
|
|
3267
|
+
deepseek: spread
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
function mapCodexEffortToAnthropic(effort) {
|
|
3271
|
+
switch (effort) {
|
|
3272
|
+
case "none":
|
|
3273
|
+
case "minimal":
|
|
3274
|
+
case "low":
|
|
3275
|
+
return "low";
|
|
3276
|
+
case "medium":
|
|
3277
|
+
return "medium";
|
|
3278
|
+
case "high":
|
|
3279
|
+
case "xhigh":
|
|
3280
|
+
case "max":
|
|
3281
|
+
return effort === "xhigh" ? "high" : effort === "max" ? "max" : "high";
|
|
3282
|
+
default:
|
|
3283
|
+
if (ANTHROPIC_EFFORT_LEVELS.includes(effort)) {
|
|
3284
|
+
return effort;
|
|
3285
|
+
}
|
|
3286
|
+
return void 0;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
var OPENAI_MODEL_REASONING = {
|
|
3290
|
+
"gpt-5-pro": { levels: ["high"], defaultLevel: "high" },
|
|
3291
|
+
"gpt-5.1": { levels: ["none", "low", "medium", "high"], defaultLevel: "none" },
|
|
3292
|
+
"gpt-5.1-codex-max": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
3293
|
+
"gpt-5.2": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
3294
|
+
"gpt-5.2-codex": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
3295
|
+
"gpt-5.2-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
3296
|
+
"gpt-5.3-codex": { levels: ["low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
3297
|
+
"gpt-5.4": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
3298
|
+
"gpt-5.4-mini": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
3299
|
+
"gpt-5.4-nano": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "none" },
|
|
3300
|
+
"gpt-5.4-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
3301
|
+
"gpt-5.5": { levels: ["none", "low", "medium", "high", "xhigh"], defaultLevel: "medium" },
|
|
3302
|
+
"gpt-5.5-pro": { levels: ["medium", "high", "xhigh"], defaultLevel: "high" },
|
|
3303
|
+
"gpt-5.6": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
|
|
3304
|
+
"gpt-5.6-luna": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
|
|
3305
|
+
"gpt-5.6-sol": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" },
|
|
3306
|
+
"gpt-5.6-terra": { levels: ["none", "low", "medium", "high", "xhigh", "max"], defaultLevel: "medium" }
|
|
3307
|
+
};
|
|
3308
|
+
var OPENAI_NON_REASONING_MODELS = /* @__PURE__ */ new Set([
|
|
3309
|
+
"chat-latest",
|
|
3310
|
+
"gpt-5-chat-latest",
|
|
3311
|
+
"gpt-5.1-chat-latest",
|
|
3312
|
+
"gpt-5.2-chat-latest",
|
|
3313
|
+
"gpt-5.3-chat-latest"
|
|
3314
|
+
]);
|
|
3315
|
+
var OPENAI_DATED_SNAPSHOT_SUFFIX = /-\d{4}-\d{2}-\d{2}$/;
|
|
3316
|
+
function canonicalOpenAiModelId(modelId, metadata) {
|
|
3317
|
+
return (metadata?.upstreamModelId ?? modelId ?? "").toLowerCase();
|
|
3318
|
+
}
|
|
3319
|
+
function openAiReasoningProfile(modelId, metadata) {
|
|
3320
|
+
const id = canonicalOpenAiModelId(modelId, metadata);
|
|
3321
|
+
if (!id) return void 0;
|
|
3322
|
+
return OPENAI_MODEL_REASONING[id] ?? OPENAI_MODEL_REASONING[id.replace(OPENAI_DATED_SNAPSHOT_SUFFIX, "")];
|
|
3323
|
+
}
|
|
3324
|
+
function openAiModelReasons(modelId, metadata) {
|
|
3325
|
+
const id = canonicalOpenAiModelId(modelId, metadata);
|
|
3326
|
+
if (OPENAI_NON_REASONING_MODELS.has(id.replace(OPENAI_DATED_SNAPSHOT_SUFFIX, ""))) return false;
|
|
3327
|
+
return !!openAiReasoningProfile(modelId, metadata) || modelPrefersResponsesApi(id) || !!metadata?.reasoning;
|
|
3328
|
+
}
|
|
3329
|
+
function mapCodexEffortToOpenAI(effort, allowed) {
|
|
3330
|
+
return allowed.includes(effort) ? effort : void 0;
|
|
3331
|
+
}
|
|
3332
|
+
function mapCodexEffortToOpenAICompatible(effort) {
|
|
3333
|
+
if (effort === "xhigh") return "high";
|
|
3334
|
+
const allowed = ["low", "medium", "high"];
|
|
3335
|
+
return allowed.includes(effort) ? effort : void 0;
|
|
3336
|
+
}
|
|
3337
|
+
function mapCodexEffortToGlm52(effort) {
|
|
3338
|
+
switch (effort) {
|
|
3339
|
+
case "high":
|
|
3340
|
+
return "high";
|
|
3341
|
+
case "xhigh":
|
|
3342
|
+
case "max":
|
|
3343
|
+
return "max";
|
|
3344
|
+
default:
|
|
3345
|
+
return void 0;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
function mapCodexEffortToXai(effort, supportsMedium) {
|
|
3349
|
+
switch (effort) {
|
|
3350
|
+
case "low":
|
|
3351
|
+
return "low";
|
|
3352
|
+
case "medium":
|
|
3353
|
+
return supportsMedium ? "medium" : void 0;
|
|
3354
|
+
case "high":
|
|
3355
|
+
case "xhigh":
|
|
3356
|
+
case "max":
|
|
3357
|
+
return "high";
|
|
3358
|
+
default:
|
|
3359
|
+
return void 0;
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
function mapCodexEffortToGeminiLevel(effort) {
|
|
3363
|
+
switch (effort) {
|
|
3364
|
+
case "none":
|
|
3365
|
+
case "minimal":
|
|
3366
|
+
case "low":
|
|
3367
|
+
return "low";
|
|
3368
|
+
case "medium":
|
|
3369
|
+
return "medium";
|
|
3370
|
+
case "high":
|
|
3371
|
+
case "xhigh":
|
|
3372
|
+
case "max":
|
|
3373
|
+
return "high";
|
|
3374
|
+
default:
|
|
3375
|
+
return GEMINI_EFFORT_LEVELS.includes(effort) ? effort : void 0;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
function mapCodexEffortToGeminiBudget(effort) {
|
|
3379
|
+
const direct = GEMINI_25_BUDGETS[effort];
|
|
3380
|
+
if (direct !== void 0) return direct > 0 ? direct : void 0;
|
|
3381
|
+
const level = mapCodexEffortToGeminiLevel(effort);
|
|
3382
|
+
if (!level) return void 0;
|
|
3383
|
+
return GEMINI_25_BUDGETS[level];
|
|
3384
|
+
}
|
|
3385
|
+
function withMappableLevels(caps, npm, modelId, metadata) {
|
|
3386
|
+
if (caps.mode !== "controllable") return caps;
|
|
3387
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3388
|
+
const levels = caps.levels.filter((level) => {
|
|
3389
|
+
const mapped = effortProviderOptions(npm, level, modelId, metadata);
|
|
3390
|
+
if (mapped === void 0) return false;
|
|
3391
|
+
const wire = JSON.stringify(mapped);
|
|
3392
|
+
if (seen.has(wire)) return false;
|
|
3393
|
+
seen.add(wire);
|
|
3394
|
+
return true;
|
|
3395
|
+
});
|
|
3396
|
+
if (levels.length === caps.levels.length) return caps;
|
|
3397
|
+
if (levels.length === 0) {
|
|
3398
|
+
return { ...caps, levels: [], defaultLevel: "", mode: "internal-only" };
|
|
3399
|
+
}
|
|
3400
|
+
return {
|
|
3401
|
+
...caps,
|
|
3402
|
+
levels,
|
|
3403
|
+
defaultLevel: levels.includes(caps.defaultLevel) ? caps.defaultLevel : levels[levels.length - 1]
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
function getReasoningCapabilities(npm, modelId, metadata) {
|
|
3407
|
+
return withMappableLevels(resolveRawReasoningCapabilities(npm, modelId, metadata), npm, modelId, metadata);
|
|
3408
|
+
}
|
|
3409
|
+
function resolveRawReasoningCapabilities(npm, modelId, metadata) {
|
|
3410
|
+
const id = modelId.toLowerCase();
|
|
3411
|
+
if (isOpenRouterRoute(npm, metadata)) {
|
|
3412
|
+
return openRouterReasoningCapabilities(metadata);
|
|
3413
|
+
}
|
|
3414
|
+
if (npm === "@ai-sdk/anthropic" || id.startsWith("claude-")) {
|
|
3415
|
+
const isClaude = isClaudeReasoningModel(modelId);
|
|
3416
|
+
if (isClaude || metadata?.reasoning) {
|
|
3417
|
+
return {
|
|
3418
|
+
levels: [...ANTHROPIC_EFFORT_LEVELS],
|
|
3419
|
+
defaultLevel: "high",
|
|
3420
|
+
supportsSummaries: true,
|
|
3421
|
+
mode: "controllable",
|
|
3422
|
+
source: isClaude ? "provider-rule" : "model-metadata",
|
|
3423
|
+
confidence: isClaude ? "documented" : "inferred",
|
|
3424
|
+
wireFormat: { kind: "anthropic-thinking" }
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
return EMPTY_REASONING;
|
|
3428
|
+
}
|
|
3429
|
+
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
3430
|
+
const canonicalId = canonicalOpenAiModelId(modelId, metadata);
|
|
3431
|
+
const profile = openAiReasoningProfile(modelId, metadata);
|
|
3432
|
+
const prefersResponses = modelPrefersResponsesApi(canonicalId);
|
|
3433
|
+
if (openAiModelReasons(modelId, metadata) && shouldUseOpenAiResponsesEndpoint(canonicalId)) {
|
|
3434
|
+
const levels = profile?.levels ?? [...OPENAI_EFFORT_LEVELS];
|
|
3435
|
+
return {
|
|
3436
|
+
levels: [...levels],
|
|
3437
|
+
defaultLevel: profile?.defaultLevel ?? (levels.includes("medium") ? "medium" : levels[levels.length - 1]),
|
|
3438
|
+
supportsSummaries: true,
|
|
3439
|
+
source: profile || prefersResponses ? "provider-rule" : "model-metadata",
|
|
3440
|
+
confidence: profile || prefersResponses ? "documented" : "inferred",
|
|
3441
|
+
mode: "controllable",
|
|
3442
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
return EMPTY_REASONING;
|
|
3446
|
+
}
|
|
3447
|
+
if (npm === "@ai-sdk/google" || id.startsWith("gemini-")) {
|
|
3448
|
+
if (isGeminiReasoningModel(modelId)) {
|
|
3449
|
+
return {
|
|
3450
|
+
levels: [...GEMINI_EFFORT_LEVELS],
|
|
3451
|
+
defaultLevel: "medium",
|
|
3452
|
+
supportsSummaries: true,
|
|
3453
|
+
mode: "controllable",
|
|
3454
|
+
source: "provider-rule",
|
|
3455
|
+
confidence: "documented",
|
|
3456
|
+
wireFormat: { kind: "google-thinking-config" }
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
return EMPTY_REASONING;
|
|
3460
|
+
}
|
|
3461
|
+
if (npm === "@ai-sdk/mistral") {
|
|
3462
|
+
if (isMistralReasoningModel(modelId)) {
|
|
3463
|
+
return {
|
|
3464
|
+
levels: [...MISTRAL_EFFORT_LEVELS],
|
|
3465
|
+
defaultLevel: "high",
|
|
3466
|
+
supportsSummaries: false,
|
|
3467
|
+
mode: "controllable",
|
|
3468
|
+
source: "provider-rule",
|
|
3469
|
+
confidence: "documented",
|
|
3470
|
+
wireFormat: { kind: "mistral-reasoning-effort" }
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
return EMPTY_REASONING;
|
|
3474
|
+
}
|
|
3475
|
+
if (npm === "@ai-sdk/xai") {
|
|
3476
|
+
if (isXaiReasoningEffortModel(modelId)) {
|
|
3477
|
+
const levels = modelPrefersResponsesApi(modelId) ? [...XAI_RESPONSES_EFFORT_LEVELS] : [...XAI_CHAT_EFFORT_LEVELS];
|
|
3478
|
+
return {
|
|
3479
|
+
levels,
|
|
3480
|
+
defaultLevel: xaiDefaultReasoningEffort(modelId),
|
|
3481
|
+
supportsSummaries: true,
|
|
3482
|
+
mode: "controllable",
|
|
3483
|
+
source: "provider-rule",
|
|
3484
|
+
confidence: "documented",
|
|
3485
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
return EMPTY_REASONING;
|
|
3489
|
+
}
|
|
3490
|
+
if (isDeepSeekReasoningModel(modelId)) {
|
|
3491
|
+
return {
|
|
3492
|
+
levels: [...DEEPSEEK_EFFORT_LEVELS],
|
|
3493
|
+
defaultLevel: "high",
|
|
3494
|
+
supportsSummaries: true,
|
|
3495
|
+
mode: "controllable",
|
|
3496
|
+
source: "provider-rule",
|
|
3497
|
+
confidence: "documented",
|
|
3498
|
+
wireFormat: { kind: "deepseek-thinking" }
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
if (isKimiReasoningModel(modelId)) {
|
|
3502
|
+
return {
|
|
3503
|
+
levels: [...OPENAI_EFFORT_LEVELS],
|
|
3504
|
+
defaultLevel: "high",
|
|
3505
|
+
supportsSummaries: false,
|
|
3506
|
+
mode: "controllable",
|
|
3507
|
+
source: "provider-rule",
|
|
3508
|
+
confidence: "documented",
|
|
3509
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
if (isGlm52ReasoningModel(modelId)) {
|
|
3513
|
+
return {
|
|
3514
|
+
levels: [...GLM_52_EFFORT_LEVELS],
|
|
3515
|
+
defaultLevel: "high",
|
|
3516
|
+
supportsSummaries: false,
|
|
3517
|
+
mode: "controllable",
|
|
3518
|
+
source: "provider-rule",
|
|
3519
|
+
confidence: "documented",
|
|
3520
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
if (hasSupportedParameter(metadata, "reasoning_effort")) {
|
|
3524
|
+
return {
|
|
3525
|
+
levels: ["low", "medium", "high", "xhigh"],
|
|
3526
|
+
defaultLevel: "medium",
|
|
3527
|
+
supportsSummaries: false,
|
|
3528
|
+
mode: "controllable",
|
|
3529
|
+
source: "provider-metadata",
|
|
3530
|
+
confidence: "documented",
|
|
3531
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
3535
|
+
return {
|
|
3536
|
+
levels: [...OPENROUTER_EFFORT_LEVELS],
|
|
3537
|
+
defaultLevel: "medium",
|
|
3538
|
+
supportsSummaries: false,
|
|
3539
|
+
mode: "controllable",
|
|
3540
|
+
source: "provider-metadata",
|
|
3541
|
+
confidence: "documented",
|
|
3542
|
+
wireFormat: { kind: "openrouter-reasoning" }
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
if (metadata?.reasoning) {
|
|
3546
|
+
return {
|
|
3547
|
+
levels: ["low", "medium", "high"],
|
|
3548
|
+
defaultLevel: "medium",
|
|
3549
|
+
supportsSummaries: false,
|
|
3550
|
+
mode: "controllable",
|
|
3551
|
+
source: "model-metadata",
|
|
3552
|
+
confidence: "inferred",
|
|
3553
|
+
wireFormat: { kind: "openai-reasoning-effort" }
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
return EMPTY_REASONING;
|
|
3557
|
+
}
|
|
3558
|
+
function buildCodexReasoningLevels(capabilities) {
|
|
3559
|
+
return capabilities.levels.map((effort) => ({
|
|
3560
|
+
effort,
|
|
3561
|
+
description: EFFORT_DESCRIPTIONS[effort] ?? effort
|
|
3562
|
+
}));
|
|
3563
|
+
}
|
|
3564
|
+
function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
3565
|
+
if (!effort) return void 0;
|
|
3566
|
+
if (isOpenRouterRoute(npm, metadata)) {
|
|
3567
|
+
const caps = openRouterReasoningCapabilities(metadata);
|
|
3568
|
+
if (caps.mode !== "controllable") return void 0;
|
|
3569
|
+
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
3570
|
+
const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
|
|
3571
|
+
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
3572
|
+
}
|
|
3573
|
+
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
3574
|
+
if (!modelId || !shouldUseOpenAiResponsesEndpoint(canonicalOpenAiModelId(modelId, metadata))) return void 0;
|
|
3575
|
+
if (!openAiModelReasons(modelId, metadata)) return void 0;
|
|
3576
|
+
const allowed = openAiReasoningProfile(modelId, metadata)?.levels ?? OPENAI_EFFORT_LEVELS;
|
|
3577
|
+
const reasoningEffort = mapCodexEffortToOpenAI(effort, allowed);
|
|
3578
|
+
return reasoningEffort ? { openai: { reasoningEffort } } : void 0;
|
|
3579
|
+
}
|
|
3580
|
+
if (npm === "@ai-sdk/xai") {
|
|
3581
|
+
if (!modelId || !isXaiReasoningEffortModel(modelId)) return void 0;
|
|
3582
|
+
const reasoningEffort = mapCodexEffortToXai(effort, modelPrefersResponsesApi(modelId));
|
|
3583
|
+
return reasoningEffort ? { xai: { reasoningEffort } } : void 0;
|
|
3584
|
+
}
|
|
3585
|
+
if (npm === "@ai-sdk/anthropic" || npm === VERTEX_ANTHROPIC_NPM) {
|
|
3586
|
+
if (!modelId || !isClaudeReasoningModel(modelId)) return void 0;
|
|
3587
|
+
const mapped = mapCodexEffortToAnthropic(effort);
|
|
3588
|
+
return mapped ? { anthropic: { thinking: { type: "adaptive", effort: mapped } } } : void 0;
|
|
3589
|
+
}
|
|
3590
|
+
if (npm === "@ai-sdk/google") {
|
|
3591
|
+
const id = modelId ?? "";
|
|
3592
|
+
if (isGemini3Model(id)) {
|
|
3593
|
+
const thinkingLevel = mapCodexEffortToGeminiLevel(effort);
|
|
3594
|
+
return thinkingLevel ? { google: { thinkingConfig: { thinkingLevel, includeThoughts: true } } } : void 0;
|
|
3595
|
+
}
|
|
3596
|
+
const thinkingBudget = mapCodexEffortToGeminiBudget(effort);
|
|
3597
|
+
return thinkingBudget ? { google: { thinkingConfig: { thinkingBudget, includeThoughts: true } } } : void 0;
|
|
3598
|
+
}
|
|
3599
|
+
if (npm === "@ai-sdk/mistral") {
|
|
3600
|
+
if (!modelId || !isMistralReasoningModel(modelId)) return void 0;
|
|
3601
|
+
const reasoningEffort = effort === "off" || effort === "none" ? "none" : "high";
|
|
3602
|
+
return { mistral: { reasoningEffort } };
|
|
3603
|
+
}
|
|
3604
|
+
if (npm === "@ai-sdk/openai-compatible" || npm === "@ai-sdk/openai") {
|
|
3605
|
+
if (!modelId) return void 0;
|
|
3606
|
+
if (isDeepSeekReasoningModel(modelId)) {
|
|
3607
|
+
return deepSeekEffortProviderOptions(effort);
|
|
3608
|
+
}
|
|
3609
|
+
if (isKimiReasoningModel(modelId)) {
|
|
3610
|
+
const reasoningEffort = mapCodexEffortToOpenAICompatible(effort);
|
|
3611
|
+
if (reasoningEffort) {
|
|
3612
|
+
const key = metadata?.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
|
|
3613
|
+
return { [key]: { reasoningEffort } };
|
|
3614
|
+
}
|
|
3615
|
+
return void 0;
|
|
3616
|
+
}
|
|
3617
|
+
if (isGlm52ReasoningModel(modelId)) {
|
|
3618
|
+
const reasoningEffort = mapCodexEffortToGlm52(effort);
|
|
3619
|
+
if (reasoningEffort) {
|
|
3620
|
+
const key = metadata?.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
|
|
3621
|
+
return { [key]: { reasoningEffort } };
|
|
3622
|
+
}
|
|
3623
|
+
return void 0;
|
|
3624
|
+
}
|
|
3625
|
+
if (hasSupportedParameter(metadata, "reasoning_effort")) {
|
|
3626
|
+
const reasoningEffort = mapCodexEffortToOpenAICompatible(effort);
|
|
3627
|
+
return reasoningEffort ? { openai: { reasoningEffort }, openaiCompatible: { reasoningEffort } } : void 0;
|
|
3628
|
+
}
|
|
3629
|
+
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
3630
|
+
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
3631
|
+
const mapped = allowed.has(effort) ? effort : effort === "max" ? "xhigh" : void 0;
|
|
3632
|
+
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
3633
|
+
}
|
|
3634
|
+
return void 0;
|
|
3635
|
+
}
|
|
3636
|
+
return void 0;
|
|
3637
|
+
}
|
|
3638
|
+
function deepMergeProviderOptions(a, b) {
|
|
3639
|
+
if (!a && !b) return void 0;
|
|
3640
|
+
if (!a) return b;
|
|
3641
|
+
if (!b) return a;
|
|
3642
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
3643
|
+
const out = {};
|
|
3644
|
+
for (const key of keys) {
|
|
3645
|
+
out[key] = { ...a[key] ?? {}, ...b[key] ?? {} };
|
|
3646
|
+
}
|
|
3647
|
+
return out;
|
|
3648
|
+
}
|
|
3649
|
+
function thinkingProviderOptions(npm) {
|
|
3650
|
+
if (npm === "@ai-sdk/google") {
|
|
3651
|
+
return { google: { thinkingConfig: { includeThoughts: true } } };
|
|
3652
|
+
}
|
|
3653
|
+
if (npm === "@ai-sdk/openai") {
|
|
3654
|
+
return {
|
|
3655
|
+
openai: {
|
|
3656
|
+
store: false,
|
|
3657
|
+
include: ["reasoning.encrypted_content"]
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
return void 0;
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
// src/subagent-route-registry.ts
|
|
3665
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3666
|
+
var DEFAULT_TTL_MS = 5 * 6e4;
|
|
3667
|
+
var DEFAULT_MAX_ENTRIES = 1024;
|
|
3668
|
+
var ROUTE_MARKER_PATTERN = /(?:\n\n)?<relay-ai-subagent-route token="([0-9a-f-]{36})"\s*\/>/gi;
|
|
3669
|
+
function firstHeader(value) {
|
|
3670
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
3671
|
+
return typeof first === "string" && first.trim() ? first.trim() : void 0;
|
|
3672
|
+
}
|
|
3673
|
+
function extractClaudeSessionId(headers, body) {
|
|
3674
|
+
const headerSession = firstHeader(headers["x-claude-code-session-id"]);
|
|
3675
|
+
if (headerSession) return headerSession;
|
|
3676
|
+
const userId = body?.metadata?.user_id;
|
|
3677
|
+
if (typeof userId !== "string") return void 0;
|
|
3678
|
+
try {
|
|
3679
|
+
const parsed = JSON.parse(userId);
|
|
3680
|
+
return typeof parsed.session_id === "string" && parsed.session_id.trim() ? parsed.session_id.trim() : void 0;
|
|
3681
|
+
} catch {
|
|
3682
|
+
return void 0;
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
function appendSubagentRouteMarker(prompt, token) {
|
|
3686
|
+
return `${prompt}
|
|
3687
|
+
|
|
3688
|
+
<relay-ai-subagent-route token="${token}"/>`;
|
|
3689
|
+
}
|
|
3690
|
+
function findMarkersInUserMessages(body) {
|
|
3691
|
+
if (!Array.isArray(body.messages)) return void 0;
|
|
3692
|
+
const tokens = [];
|
|
3693
|
+
const messages = [...body.messages];
|
|
3694
|
+
for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
|
|
3695
|
+
const message = body.messages[messageIndex];
|
|
3696
|
+
if (!message || message.role !== "user") continue;
|
|
3697
|
+
if (typeof message.content === "string") {
|
|
3698
|
+
const matches = [...message.content.matchAll(ROUTE_MARKER_PATTERN)];
|
|
3699
|
+
if (matches.length === 0) continue;
|
|
3700
|
+
tokens.push(...matches.flatMap((match) => match[1] ? [match[1]] : []));
|
|
3701
|
+
messages[messageIndex] = {
|
|
3702
|
+
...message,
|
|
3703
|
+
content: message.content.replace(ROUTE_MARKER_PATTERN, "").trimEnd()
|
|
3704
|
+
};
|
|
3705
|
+
continue;
|
|
3706
|
+
}
|
|
3707
|
+
if (!Array.isArray(message.content)) continue;
|
|
3708
|
+
const content = [...message.content];
|
|
3709
|
+
for (let partIndex = 0; partIndex < message.content.length; partIndex++) {
|
|
3710
|
+
const part = message.content[partIndex];
|
|
3711
|
+
if (!part || part.type !== "text" || typeof part.text !== "string") continue;
|
|
3712
|
+
const matches = [...part.text.matchAll(ROUTE_MARKER_PATTERN)];
|
|
3713
|
+
if (matches.length === 0) continue;
|
|
3714
|
+
tokens.push(...matches.flatMap((match) => match[1] ? [match[1]] : []));
|
|
3715
|
+
content[partIndex] = {
|
|
3716
|
+
...part,
|
|
3717
|
+
text: part.text.replace(ROUTE_MARKER_PATTERN, "").trimEnd()
|
|
3718
|
+
};
|
|
3719
|
+
}
|
|
3720
|
+
messages[messageIndex] = { ...message, content };
|
|
3721
|
+
}
|
|
3722
|
+
return tokens.length > 0 ? { tokens, body: { ...body, messages } } : void 0;
|
|
3723
|
+
}
|
|
3724
|
+
var SubagentRouteRegistry = class {
|
|
3725
|
+
entries = /* @__PURE__ */ new Map();
|
|
3726
|
+
ttlMs;
|
|
3727
|
+
maxEntries;
|
|
3728
|
+
now;
|
|
3729
|
+
constructor(options = {}) {
|
|
3730
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
3731
|
+
this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
3732
|
+
this.now = options.now ?? Date.now;
|
|
3733
|
+
}
|
|
3734
|
+
register(sessionId, modelId) {
|
|
3735
|
+
this.cleanup();
|
|
3736
|
+
while (this.entries.size >= this.maxEntries) {
|
|
3737
|
+
const oldest = this.entries.keys().next().value;
|
|
3738
|
+
if (!oldest) break;
|
|
3739
|
+
this.entries.delete(oldest);
|
|
3740
|
+
}
|
|
3741
|
+
const token = randomUUID2();
|
|
3742
|
+
this.entries.set(token, { sessionId, modelId, createdAt: this.now() });
|
|
3743
|
+
return token;
|
|
3744
|
+
}
|
|
3745
|
+
consume(headers, body) {
|
|
3746
|
+
this.cleanup();
|
|
3747
|
+
if (!firstHeader(headers["x-claude-code-agent-id"])) return void 0;
|
|
3748
|
+
const sessionId = extractClaudeSessionId(headers, body);
|
|
3749
|
+
if (!sessionId) return void 0;
|
|
3750
|
+
const marked = findMarkersInUserMessages(body);
|
|
3751
|
+
if (!marked) return void 0;
|
|
3752
|
+
const token = [...marked.tokens].reverse().find((candidate) => this.entries.get(candidate)?.sessionId === sessionId);
|
|
3753
|
+
if (!token) return void 0;
|
|
3754
|
+
const entry = this.entries.get(token);
|
|
3755
|
+
this.entries.delete(token);
|
|
3756
|
+
return { modelId: entry.modelId, body: marked.body };
|
|
3757
|
+
}
|
|
3758
|
+
cleanup() {
|
|
3759
|
+
const cutoff = this.now() - this.ttlMs;
|
|
3760
|
+
for (const [token, entry] of this.entries) {
|
|
3761
|
+
if (entry.createdAt < cutoff) this.entries.delete(token);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
|
|
3766
|
+
// src/subagent-model-routing.ts
|
|
3767
|
+
var CLAUDE_MODEL_FAMILIES = ["sonnet", "opus", "haiku", "fable"];
|
|
3768
|
+
var CLAUDE_MODEL_FAMILY_SET = new Set(CLAUDE_MODEL_FAMILIES);
|
|
3769
|
+
function isRecord2(value) {
|
|
3770
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3771
|
+
}
|
|
3772
|
+
function claudeModelFamily(modelId) {
|
|
3773
|
+
const normalized = modelId.toLowerCase();
|
|
3774
|
+
if (!normalized.startsWith("claude-")) return void 0;
|
|
3775
|
+
return CLAUDE_MODEL_FAMILIES.find((family) => normalized.includes(family));
|
|
3776
|
+
}
|
|
3777
|
+
function isClaudeAgentTool(tool2) {
|
|
3778
|
+
if (tool2.name !== "Agent" || !isRecord2(tool2.input_schema)) return false;
|
|
3779
|
+
const properties = tool2.input_schema.properties;
|
|
3780
|
+
if (!isRecord2(properties)) return false;
|
|
3781
|
+
return ["description", "prompt", "subagent_type"].every((name) => isRecord2(properties[name]));
|
|
3782
|
+
}
|
|
3783
|
+
var UnavailableSubagentModelError = class extends Error {
|
|
3784
|
+
constructor(selector, routing) {
|
|
3785
|
+
const visible = routing.models.slice(0, MAX_MODEL_CATALOG).map((model) => model.id);
|
|
3786
|
+
const omitted = routing.models.length - visible.length;
|
|
3787
|
+
const suffix = omitted > 0 ? `, and ${omitted} more` : "";
|
|
3788
|
+
super(
|
|
3789
|
+
`Subagent model "${selector}" is unavailable in this Relay AI session. Available model ids: ${visible.join(", ")}${suffix}.`
|
|
3790
|
+
);
|
|
3791
|
+
this.selector = selector;
|
|
3792
|
+
this.name = "UnavailableSubagentModelError";
|
|
3793
|
+
}
|
|
3794
|
+
selector;
|
|
3795
|
+
statusCode = 400;
|
|
3796
|
+
};
|
|
3797
|
+
function normalizeClaudeAgentInput(input, routing) {
|
|
3798
|
+
const source = isRecord2(input) ? input : {};
|
|
3799
|
+
const normalized = { ...source };
|
|
3800
|
+
if (source.subagent_type === "fork") {
|
|
3801
|
+
return { input: normalized, decision: { kind: "fork" } };
|
|
3802
|
+
}
|
|
3803
|
+
const rawModel = source.model;
|
|
3804
|
+
if (rawModel == null) {
|
|
3805
|
+
normalized.model = routing.parentModelId;
|
|
3806
|
+
return {
|
|
3807
|
+
input: normalized,
|
|
3808
|
+
decision: { kind: "inherit", resolvedModelId: routing.parentModelId }
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
if (typeof rawModel !== "string") {
|
|
3812
|
+
throw new UnavailableSubagentModelError(String(rawModel), routing);
|
|
3813
|
+
}
|
|
3814
|
+
const selector = rawModel.trim();
|
|
3815
|
+
if (selector === "" || selector === "inherit") {
|
|
3816
|
+
normalized.model = routing.parentModelId;
|
|
3817
|
+
return {
|
|
3818
|
+
input: normalized,
|
|
3819
|
+
decision: { kind: "inherit", resolvedModelId: routing.parentModelId }
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
const exposed = routing.models.find((model) => model.id === selector);
|
|
3823
|
+
if (exposed) {
|
|
3824
|
+
normalized.model = exposed.id;
|
|
3825
|
+
return {
|
|
3826
|
+
input: normalized,
|
|
3827
|
+
decision: { kind: "explicit", resolvedModelId: exposed.id }
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
const compatible = routing.models.find((model) => model.compatibilityIds.includes(selector));
|
|
3831
|
+
if (compatible) {
|
|
3832
|
+
normalized.model = compatible.id;
|
|
3833
|
+
return {
|
|
3834
|
+
input: normalized,
|
|
3835
|
+
decision: {
|
|
3836
|
+
kind: "compatibility",
|
|
3837
|
+
requestedModelId: selector,
|
|
3838
|
+
resolvedModelId: compatible.id
|
|
3839
|
+
}
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
if (CLAUDE_MODEL_FAMILY_SET.has(selector)) {
|
|
3843
|
+
const family = selector;
|
|
3844
|
+
const nativeModel = routing.models.find((model) => model.family === family);
|
|
3845
|
+
const resolvedModelId = nativeModel?.id ?? routing.parentModelId;
|
|
3846
|
+
normalized.model = resolvedModelId;
|
|
3847
|
+
return {
|
|
3848
|
+
input: normalized,
|
|
3849
|
+
decision: nativeModel ? { kind: "family", requestedModelId: family, resolvedModelId } : { kind: "family-fallback", requestedModelId: family, resolvedModelId }
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
throw new UnavailableSubagentModelError(selector, routing);
|
|
3853
|
+
}
|
|
3854
|
+
function prepareClaudeAgentInput(input, routing) {
|
|
3855
|
+
const normalized = normalizeClaudeAgentInput(input, routing);
|
|
3856
|
+
const decision = normalized.decision;
|
|
3857
|
+
if (decision.kind === "fork") return normalized;
|
|
3858
|
+
if (!routing.registerSubagentRoute) return normalized;
|
|
3859
|
+
const prompt = normalized.input.prompt;
|
|
3860
|
+
if (typeof prompt !== "string") return normalized;
|
|
3861
|
+
const token = routing.registerSubagentRoute(decision.resolvedModelId);
|
|
3862
|
+
const clientInput = { ...normalized.input };
|
|
3863
|
+
const target = routing.models.find((model) => model.id === decision.resolvedModelId);
|
|
3864
|
+
if (target?.family) clientInput.model = target.family;
|
|
3865
|
+
else delete clientInput.model;
|
|
3866
|
+
clientInput.prompt = appendSubagentRouteMarker(prompt, token);
|
|
3867
|
+
return { input: clientInput, decision };
|
|
3868
|
+
}
|
|
3869
|
+
function augmentClaudeAgentTool(tool2, routing) {
|
|
3870
|
+
const inputSchema = isRecord2(tool2.input_schema) ? tool2.input_schema : {};
|
|
3871
|
+
const properties = isRecord2(inputSchema.properties) ? inputSchema.properties : {};
|
|
3872
|
+
const originalModel = isRecord2(properties.model) ? properties.model : {};
|
|
3873
|
+
const smallCatalog = routing.models.length <= MAX_MODEL_CATALOG;
|
|
3874
|
+
const modelProperty = {
|
|
3875
|
+
...originalModel,
|
|
3876
|
+
type: "string"
|
|
3877
|
+
};
|
|
3878
|
+
if (smallCatalog) {
|
|
3879
|
+
const originalEnum = Array.isArray(originalModel.enum) ? originalModel.enum.filter((value) => typeof value === "string") : CLAUDE_MODEL_FAMILIES;
|
|
3880
|
+
modelProperty.enum = [.../* @__PURE__ */ new Set([...originalEnum, ...routing.models.map((model) => model.id)])];
|
|
3881
|
+
} else {
|
|
3882
|
+
delete modelProperty.enum;
|
|
3883
|
+
}
|
|
3884
|
+
const guidance = smallCatalog ? `Relay AI subagent model routing (default: ${routing.parentModelId}). ` + routing.models.map((model) => `${model.displayName}: ${model.id}`).join("; ") : `Relay AI subagent model routing (default: ${routing.parentModelId}). Other explicit model values must be exact ids from the current session catalog.`;
|
|
3885
|
+
return {
|
|
3886
|
+
...tool2,
|
|
3887
|
+
description: [tool2.description?.trim(), guidance].filter(Boolean).join("\n\n"),
|
|
3888
|
+
input_schema: {
|
|
3889
|
+
...inputSchema,
|
|
3890
|
+
properties: {
|
|
3891
|
+
...properties,
|
|
3892
|
+
model: modelProperty
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
// src/proxy-shared.ts
|
|
3899
|
+
function grabRoundTripSignature(part) {
|
|
3900
|
+
const md = part.providerMetadata;
|
|
3901
|
+
return md?.google?.thoughtSignature ?? md?.google?.thought_signature ?? md?.openai?.reasoningEncryptedContent ?? void 0;
|
|
3902
|
+
}
|
|
3903
|
+
var sdkWarningsSilenced = false;
|
|
3904
|
+
function silenceSdkWarnings() {
|
|
3905
|
+
if (sdkWarningsSilenced) return;
|
|
3906
|
+
sdkWarningsSilenced = true;
|
|
3907
|
+
globalThis.AI_SDK_LOG_WARNINGS = false;
|
|
3908
|
+
}
|
|
3909
|
+
var TOOL_USE_SIG_SEP = "__ts__";
|
|
3910
|
+
var MAX_INLINE_TOOL_SIGNATURE_BYTES = 256;
|
|
3911
|
+
var MAX_STORED_TOOL_SIGNATURES = 1e3;
|
|
3912
|
+
var toolSignatureRegistry = /* @__PURE__ */ new Map();
|
|
3913
|
+
function rememberToolSignature(rawId, thoughtSignature) {
|
|
3914
|
+
toolSignatureRegistry.set(rawId, thoughtSignature);
|
|
3915
|
+
if (toolSignatureRegistry.size <= MAX_STORED_TOOL_SIGNATURES) return;
|
|
3916
|
+
const oldest = toolSignatureRegistry.keys().next().value;
|
|
3917
|
+
if (oldest) toolSignatureRegistry.delete(oldest);
|
|
3918
|
+
}
|
|
3919
|
+
function parseToolArguments(value) {
|
|
3920
|
+
if (value === null || value === void 0) return {};
|
|
3921
|
+
if (typeof value === "object" && !Array.isArray(value)) return value;
|
|
3922
|
+
if (typeof value === "string") {
|
|
3923
|
+
if (!value) return {};
|
|
3924
|
+
try {
|
|
3925
|
+
const parsed = JSON.parse(value);
|
|
3926
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
3927
|
+
return parsed;
|
|
3928
|
+
}
|
|
3929
|
+
} catch {
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
return {};
|
|
3933
|
+
}
|
|
3934
|
+
function sseChunk(eventType, data) {
|
|
3935
|
+
return `event: ${eventType}
|
|
3936
|
+
data: ${JSON.stringify(data)}
|
|
3937
|
+
|
|
3938
|
+
`;
|
|
3939
|
+
}
|
|
3940
|
+
var DSML_NOISE = "[|\uFF5C\\s]*";
|
|
3941
|
+
var DSML_BLOCK_RE = new RegExp(`<${DSML_NOISE}DSML${DSML_NOISE}tool_calls>([\\s\\S]*?)<\\/${DSML_NOISE}DSML${DSML_NOISE}tool_calls>`, "i");
|
|
3942
|
+
var DSML_INVOKE_RE = new RegExp(`<${DSML_NOISE}DSML${DSML_NOISE}invoke\\s+name="([^"]+)"[^>]*>([\\s\\S]*?)<\\/${DSML_NOISE}DSML${DSML_NOISE}invoke>`, "gi");
|
|
3943
|
+
var DSML_PARAM_RE = new RegExp(`<${DSML_NOISE}DSML${DSML_NOISE}parameter\\s+name="([^"]+)"(?:\\s+string="(true|false)")?[^>]*>([\\s\\S]*?)<\\/${DSML_NOISE}DSML${DSML_NOISE}parameter>`, "gi");
|
|
3944
|
+
function parseDsmlToolCalls(text) {
|
|
3945
|
+
const outer = DSML_BLOCK_RE.exec(text);
|
|
3946
|
+
if (!outer) return null;
|
|
3947
|
+
const calls = [];
|
|
3948
|
+
for (const invokeMatch of outer[1].matchAll(DSML_INVOKE_RE)) {
|
|
3949
|
+
const name = invokeMatch[1];
|
|
3950
|
+
const body = invokeMatch[2];
|
|
3951
|
+
const args = {};
|
|
3952
|
+
for (const paramMatch of body.matchAll(DSML_PARAM_RE)) {
|
|
3953
|
+
const paramName = paramMatch[1];
|
|
3954
|
+
const isJson = paramMatch[2] === "false";
|
|
3955
|
+
const rawValue = paramMatch[3] ?? "";
|
|
3956
|
+
if (isJson) {
|
|
3957
|
+
try {
|
|
3958
|
+
args[paramName] = JSON.parse(rawValue.trim());
|
|
3959
|
+
} catch {
|
|
3960
|
+
args[paramName] = rawValue;
|
|
3961
|
+
}
|
|
3962
|
+
} else {
|
|
3963
|
+
args[paramName] = rawValue;
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
calls.push({ name, args });
|
|
3967
|
+
}
|
|
3968
|
+
if (!calls.length) return null;
|
|
3969
|
+
return { leadingText: text.slice(0, outer.index).trim(), calls };
|
|
3970
|
+
}
|
|
3971
|
+
function splitToolUseId(id) {
|
|
3972
|
+
let sep = id.lastIndexOf(TOOL_USE_SIG_SEP);
|
|
3973
|
+
if (sep !== -1) {
|
|
3974
|
+
return {
|
|
3975
|
+
rawId: id.slice(0, sep),
|
|
3976
|
+
thoughtSignature: Buffer.from(id.slice(sep + TOOL_USE_SIG_SEP.length), "base64url").toString("utf8")
|
|
3977
|
+
};
|
|
3978
|
+
}
|
|
3979
|
+
sep = id.lastIndexOf("::ts::");
|
|
3980
|
+
if (sep !== -1) {
|
|
3981
|
+
return {
|
|
3982
|
+
rawId: id.slice(0, sep),
|
|
3983
|
+
thoughtSignature: id.slice(sep + 6)
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3986
|
+
return { rawId: id, thoughtSignature: toolSignatureRegistry.get(id) };
|
|
3987
|
+
}
|
|
3988
|
+
function encodeToolUseId(rawId, thoughtSignature, inline = true) {
|
|
3989
|
+
if (!thoughtSignature) return rawId;
|
|
3990
|
+
rememberToolSignature(rawId, thoughtSignature);
|
|
3991
|
+
if (!inline) return rawId;
|
|
3992
|
+
if (Buffer.byteLength(thoughtSignature, "utf8") > MAX_INLINE_TOOL_SIGNATURE_BYTES) {
|
|
3993
|
+
return rawId;
|
|
3994
|
+
}
|
|
3995
|
+
const encoded = Buffer.from(thoughtSignature, "utf8").toString("base64url");
|
|
3996
|
+
return `${rawId}${TOOL_USE_SIG_SEP}${encoded}`;
|
|
3997
|
+
}
|
|
3998
|
+
function serializeToolResultContent(content) {
|
|
3999
|
+
if (typeof content === "string") return content;
|
|
4000
|
+
if (content === void 0) return "";
|
|
4001
|
+
return JSON.stringify(content);
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
// src/codex/upstream-error.ts
|
|
4005
|
+
var TRACE_FIELD_LIMIT = 4e3;
|
|
4006
|
+
function clipTraceField(value) {
|
|
4007
|
+
return value.length <= TRACE_FIELD_LIMIT ? value : `${value.slice(0, TRACE_FIELD_LIMIT)}\u2026`;
|
|
4008
|
+
}
|
|
4009
|
+
function safeUpstreamErrorFields(err, includeCause) {
|
|
4010
|
+
if (!err || typeof err !== "object") {
|
|
4011
|
+
return { message: clipTraceField(String(err)) };
|
|
4012
|
+
}
|
|
4013
|
+
const rec = err;
|
|
4014
|
+
const result = {};
|
|
4015
|
+
if (rec.name) result.name = rec.name;
|
|
4016
|
+
if (rec.message) result.message = clipTraceField(rec.message);
|
|
4017
|
+
if (rec.statusCode !== void 0) result.statusCode = rec.statusCode;
|
|
4018
|
+
if (rec.responseBody) result.responseBody = clipTraceField(rec.responseBody);
|
|
4019
|
+
if (rec.data?.error) {
|
|
4020
|
+
result.data = {
|
|
4021
|
+
error: {
|
|
4022
|
+
...rec.data.error.type ? { type: rec.data.error.type } : {},
|
|
4023
|
+
...rec.data.error.message ? { message: clipTraceField(rec.data.error.message) } : {}
|
|
4024
|
+
}
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
if (rec.lastError) {
|
|
4028
|
+
result.lastError = {
|
|
4029
|
+
...rec.lastError.message ? { message: clipTraceField(rec.lastError.message) } : {},
|
|
4030
|
+
...rec.lastError.statusCode !== void 0 ? { statusCode: rec.lastError.statusCode } : {}
|
|
4031
|
+
};
|
|
4032
|
+
}
|
|
4033
|
+
if (rec.errors?.length) {
|
|
4034
|
+
result.errors = rec.errors.map((item) => ({
|
|
4035
|
+
...item.message ? { message: clipTraceField(item.message) } : {},
|
|
4036
|
+
...item.statusCode !== void 0 ? { statusCode: item.statusCode } : {}
|
|
4037
|
+
}));
|
|
4038
|
+
}
|
|
4039
|
+
if (includeCause && rec.cause !== void 0) {
|
|
4040
|
+
result.cause = safeUpstreamErrorFields(rec.cause, false);
|
|
4041
|
+
}
|
|
4042
|
+
return result;
|
|
4043
|
+
}
|
|
4044
|
+
function formatUpstreamErrorTrace(err) {
|
|
4045
|
+
return JSON.stringify(safeUpstreamErrorFields(err, true));
|
|
4046
|
+
}
|
|
4047
|
+
function formatUpstreamError(err) {
|
|
4048
|
+
if (!err || typeof err !== "object") return "Upstream model request failed.";
|
|
4049
|
+
const rec = err;
|
|
4050
|
+
if (rec.data?.error?.message) {
|
|
4051
|
+
const short = sanitizeMessage(rec.data.error.message);
|
|
4052
|
+
return rec.statusCode ? `${short} (HTTP ${rec.statusCode})` : short;
|
|
4053
|
+
}
|
|
4054
|
+
if (rec.responseBody) {
|
|
4055
|
+
try {
|
|
4056
|
+
const parsed = JSON.parse(rec.responseBody);
|
|
4057
|
+
if (parsed.error?.message) {
|
|
4058
|
+
const short = sanitizeMessage(parsed.error.message);
|
|
4059
|
+
return rec.statusCode ? `${short} (HTTP ${rec.statusCode})` : short;
|
|
4060
|
+
}
|
|
4061
|
+
} catch {
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
const last = rec.lastError;
|
|
4065
|
+
if (last?.message) {
|
|
4066
|
+
const code = last.statusCode;
|
|
4067
|
+
const short = sanitizeMessage(last.message);
|
|
4068
|
+
return code ? `${short} (HTTP ${code})` : short;
|
|
4069
|
+
}
|
|
4070
|
+
const fromList = rec.errors?.[rec.errors.length - 1];
|
|
4071
|
+
if (fromList?.message) {
|
|
4072
|
+
const short = sanitizeMessage(fromList.message);
|
|
4073
|
+
return fromList.statusCode ? `${short} (HTTP ${fromList.statusCode})` : short;
|
|
4074
|
+
}
|
|
4075
|
+
if (rec.message) {
|
|
4076
|
+
const short = sanitizeMessage(rec.message);
|
|
4077
|
+
if (short && !short.includes("file://") && !short.includes("APICallError") && short.length < 240) {
|
|
4078
|
+
return rec.statusCode ? `${short} (HTTP ${rec.statusCode})` : short;
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
return "Upstream model request failed.";
|
|
4082
|
+
}
|
|
4083
|
+
function upstreamHttpStatus(err, message) {
|
|
4084
|
+
if (err && typeof err === "object" && "statusCode" in err) {
|
|
4085
|
+
const code = err.statusCode;
|
|
4086
|
+
if (code === 400 || code === 401 || code === 403 || code === 404 || code === 429) return code;
|
|
4087
|
+
}
|
|
4088
|
+
if (message.includes("HTTP 429") || message.includes("429")) return 429;
|
|
4089
|
+
if (message.includes("HTTP 400")) return 400;
|
|
4090
|
+
return 500;
|
|
4091
|
+
}
|
|
4092
|
+
function anthropicErrorType(status) {
|
|
4093
|
+
switch (status) {
|
|
4094
|
+
case 400:
|
|
4095
|
+
return "invalid_request_error";
|
|
4096
|
+
case 401:
|
|
4097
|
+
return "authentication_error";
|
|
4098
|
+
case 403:
|
|
4099
|
+
return "permission_error";
|
|
4100
|
+
case 404:
|
|
4101
|
+
return "not_found_error";
|
|
4102
|
+
case 429:
|
|
4103
|
+
return "rate_limit_error";
|
|
4104
|
+
default:
|
|
4105
|
+
return "api_error";
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
function sanitizeMessage(message) {
|
|
4109
|
+
const line = message.split("\n")[0]?.trim() ?? message;
|
|
4110
|
+
if (line.startsWith("RetryError") || line.includes("AI_RetryError")) {
|
|
4111
|
+
return "Upstream model request failed after retries.";
|
|
4112
|
+
}
|
|
4113
|
+
return line;
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
// src/sdk-adapter.ts
|
|
4117
|
+
import { streamText, generateText, tool, jsonSchema } from "ai";
|
|
4118
|
+
|
|
4119
|
+
// src/tool-search.ts
|
|
4120
|
+
var TOOL_SEARCH_TYPE_PREFIX = "tool_search_tool";
|
|
4121
|
+
function isToolSearchTool(tool2) {
|
|
4122
|
+
if (typeof tool2.type === "string" && tool2.type.startsWith(TOOL_SEARCH_TYPE_PREFIX)) return true;
|
|
4123
|
+
const name = tool2.name ?? "";
|
|
4124
|
+
return name.includes("tool_search") || name === "ToolSearch";
|
|
4125
|
+
}
|
|
4126
|
+
function extractReferencedToolNames(messages) {
|
|
4127
|
+
const names = /* @__PURE__ */ new Set();
|
|
4128
|
+
const visitContent = (content) => {
|
|
4129
|
+
if (typeof content === "string") return;
|
|
4130
|
+
if (!Array.isArray(content)) return;
|
|
4131
|
+
for (const block of content) {
|
|
4132
|
+
if (!block || typeof block !== "object") continue;
|
|
4133
|
+
const part = block;
|
|
4134
|
+
if (part.type === "tool_reference" && typeof part.tool_name === "string") {
|
|
4135
|
+
names.add(part.tool_name);
|
|
4136
|
+
}
|
|
4137
|
+
if (part.type === "tool_search_tool_result") {
|
|
4138
|
+
const inner = part.content;
|
|
4139
|
+
const refs = inner?.tool_references;
|
|
4140
|
+
if (Array.isArray(refs)) {
|
|
4141
|
+
for (const ref of refs) {
|
|
4142
|
+
if (ref && typeof ref === "object" && typeof ref.tool_name === "string") {
|
|
4143
|
+
names.add(ref.tool_name);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
if (part.type === "tool_result" && part.content) {
|
|
4149
|
+
visitContent(part.content);
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
};
|
|
4153
|
+
for (const msg of messages ?? []) {
|
|
4154
|
+
visitContent(msg.content);
|
|
4155
|
+
}
|
|
4156
|
+
return names;
|
|
4157
|
+
}
|
|
4158
|
+
function resolveUpstreamTools(tools, messages) {
|
|
4159
|
+
if (!tools?.length) return [];
|
|
4160
|
+
const referenced = extractReferencedToolNames(messages);
|
|
4161
|
+
const upstream = [];
|
|
4162
|
+
for (const tool2 of tools) {
|
|
4163
|
+
if (isToolSearchTool(tool2)) {
|
|
4164
|
+
upstream.push(tool2);
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
if (tool2.defer_loading === true) {
|
|
4168
|
+
if (referenced.has(tool2.name)) upstream.push(tool2);
|
|
4169
|
+
continue;
|
|
4170
|
+
}
|
|
4171
|
+
upstream.push(tool2);
|
|
4172
|
+
}
|
|
4173
|
+
return upstream;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
// src/sdk-adapter.ts
|
|
4177
|
+
var NEEDS_TRAILING_TOOL_NUDGE = /* @__PURE__ */ new Set(["@ai-sdk/alibaba"]);
|
|
4178
|
+
var TRAILING_TOOL_NUDGE_TEXT = "Continue.";
|
|
4179
|
+
function anthropicEffortFromRequest(body) {
|
|
4180
|
+
const effort = body.output_config?.effort;
|
|
4181
|
+
if (typeof effort === "string" && effort.trim()) return effort.trim();
|
|
4182
|
+
return void 0;
|
|
4183
|
+
}
|
|
4184
|
+
function systemToString(system) {
|
|
4185
|
+
if (!system) return void 0;
|
|
4186
|
+
if (typeof system === "string") return system;
|
|
4187
|
+
return system.map((b) => typeof b === "string" ? b : b.text ?? "").join("\n");
|
|
4188
|
+
}
|
|
4189
|
+
function inlineSystemText(messages) {
|
|
4190
|
+
const parts = [];
|
|
4191
|
+
for (const msg of messages) {
|
|
4192
|
+
if (msg.role !== "system") continue;
|
|
4193
|
+
const text = typeof msg.content === "string" ? msg.content : msg.content.map((b) => b.text ?? "").join("\n");
|
|
4194
|
+
if (text.trim()) parts.push(text.trim());
|
|
4195
|
+
}
|
|
4196
|
+
return parts;
|
|
4197
|
+
}
|
|
4198
|
+
function imagePart(block) {
|
|
4199
|
+
const src = block.source;
|
|
4200
|
+
if (!src) return null;
|
|
4201
|
+
if (src.type === "base64" && src.data) {
|
|
4202
|
+
return { type: "file", mediaType: src.media_type ?? "image", data: Buffer.from(src.data, "base64") };
|
|
4203
|
+
}
|
|
4204
|
+
if (src.type === "url" && src.url) {
|
|
4205
|
+
return { type: "file", mediaType: src.media_type ?? "image", data: { type: "url", url: new URL(src.url) } };
|
|
4206
|
+
}
|
|
4207
|
+
return null;
|
|
4208
|
+
}
|
|
4209
|
+
function annotateToolNames(messages) {
|
|
4210
|
+
const nameById = /* @__PURE__ */ new Map();
|
|
4211
|
+
for (const msg of messages) {
|
|
4212
|
+
if (!Array.isArray(msg.content)) continue;
|
|
4213
|
+
for (const b of msg.content) {
|
|
4214
|
+
if (b.type === "tool_use" && b.id && b.name) nameById.set(splitToolUseId(b.id).rawId, b.name);
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
for (const msg of messages) {
|
|
4218
|
+
if (!Array.isArray(msg.content)) continue;
|
|
4219
|
+
for (const b of msg.content) {
|
|
4220
|
+
if (b.type === "tool_result" && b.tool_use_id) {
|
|
4221
|
+
b._name = nameById.get(splitToolUseId(b.tool_use_id).rawId);
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
function thinkingToSdkPart(block, npm) {
|
|
4227
|
+
const text = block.thinking ?? "";
|
|
4228
|
+
if (npm === "@ai-sdk/openai" && !block.signature && !text.trim()) return null;
|
|
4229
|
+
const part = { type: "reasoning", text };
|
|
4230
|
+
if (block.signature) {
|
|
4231
|
+
if (npm === "@ai-sdk/google") {
|
|
4232
|
+
part.providerOptions = { google: { thoughtSignature: block.signature } };
|
|
4233
|
+
} else if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/openai-compatible") {
|
|
4234
|
+
part.providerOptions = { openai: { reasoningEncryptedContent: block.signature } };
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
return part;
|
|
4238
|
+
}
|
|
4239
|
+
function translateMessages(messages, npm, onDebug) {
|
|
4240
|
+
const isGoogle = npm === "@ai-sdk/google";
|
|
4241
|
+
const out = [];
|
|
4242
|
+
for (const msg of messages) {
|
|
4243
|
+
const blocks = typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : msg.content ?? [];
|
|
4244
|
+
if (msg.role === "user") {
|
|
4245
|
+
const toolResults = blocks.filter((b) => b.type === "tool_result");
|
|
4246
|
+
const parts = [];
|
|
4247
|
+
for (const b of blocks) {
|
|
4248
|
+
if (b.type === "text") parts.push({ type: "text", text: b.text ?? "" });
|
|
4249
|
+
else if (b.type === "image") {
|
|
4250
|
+
const p = imagePart(b);
|
|
4251
|
+
if (p) parts.push(p);
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
if (blocks.length && !toolResults.length && !parts.length) {
|
|
4255
|
+
const types = blocks.map((b) => b.type).join(", ");
|
|
4256
|
+
onDebug?.(`sdk: dropped user turn with unrecognized block types: [${types}]`);
|
|
4257
|
+
}
|
|
4258
|
+
if (toolResults.length) {
|
|
4259
|
+
out.push({
|
|
4260
|
+
role: "tool",
|
|
4261
|
+
content: toolResults.map((tr) => ({
|
|
4262
|
+
type: "tool-result",
|
|
4263
|
+
toolCallId: splitToolUseId(tr.tool_use_id ?? "").rawId,
|
|
4264
|
+
toolName: tr._name ?? "unknown",
|
|
4265
|
+
output: { type: "text", value: serializeToolResultContent(tr.content) }
|
|
4266
|
+
}))
|
|
4267
|
+
});
|
|
4268
|
+
}
|
|
4269
|
+
if (parts.length) out.push({ role: "user", content: parts });
|
|
4270
|
+
} else if (msg.role === "assistant") {
|
|
4271
|
+
const parts = [];
|
|
4272
|
+
for (const b of blocks) {
|
|
4273
|
+
if (b.type === "text") {
|
|
4274
|
+
parts.push({ type: "text", text: b.text ?? "" });
|
|
4275
|
+
} else if (b.type === "thinking") {
|
|
4276
|
+
const part = thinkingToSdkPart(b, npm);
|
|
4277
|
+
if (part) parts.push(part);
|
|
4278
|
+
} else if (b.type === "tool_use" && b.id) {
|
|
4279
|
+
const { rawId, thoughtSignature } = splitToolUseId(b.id);
|
|
4280
|
+
const part = {
|
|
4281
|
+
type: "tool-call",
|
|
4282
|
+
toolCallId: rawId,
|
|
4283
|
+
toolName: b.name,
|
|
4284
|
+
input: b.input ?? {}
|
|
4285
|
+
};
|
|
4286
|
+
if (thoughtSignature && isGoogle) part.providerOptions = { google: { thoughtSignature } };
|
|
4287
|
+
parts.push(part);
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
if (parts.length) out.push({ role: "assistant", content: parts });
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
if (NEEDS_TRAILING_TOOL_NUDGE.has(npm) && out[out.length - 1]?.role === "tool") {
|
|
4294
|
+
out.push({ role: "user", content: [{ type: "text", text: TRAILING_TOOL_NUDGE_TEXT }] });
|
|
4295
|
+
onDebug?.("sdk: appended continuation nudge (request ended on tool result)");
|
|
4296
|
+
}
|
|
4297
|
+
return out;
|
|
4298
|
+
}
|
|
4299
|
+
function stripNullInputs(input) {
|
|
4300
|
+
const out = {};
|
|
4301
|
+
for (const [k, v] of Object.entries(input)) {
|
|
4302
|
+
if (v !== null) out[k] = v;
|
|
4303
|
+
}
|
|
4304
|
+
return out;
|
|
4305
|
+
}
|
|
4306
|
+
function translateTools(anthropicTools) {
|
|
4307
|
+
if (!anthropicTools?.length) return void 0;
|
|
4308
|
+
const tools = {};
|
|
4309
|
+
for (const t of anthropicTools) {
|
|
4310
|
+
if (!t.name || !t.input_schema) continue;
|
|
4311
|
+
tools[t.name] = tool({ description: t.description ?? "", inputSchema: jsonSchema(t.input_schema) });
|
|
4312
|
+
}
|
|
4313
|
+
return Object.keys(tools).length ? tools : void 0;
|
|
4314
|
+
}
|
|
4315
|
+
function translateToolChoice(tc) {
|
|
4316
|
+
if (!tc) return void 0;
|
|
4317
|
+
if (tc.type === "auto") return "auto";
|
|
4318
|
+
if (tc.type === "any") return "required";
|
|
4319
|
+
if (tc.type === "tool" && tc.name) return { type: "tool", toolName: tc.name };
|
|
4320
|
+
return void 0;
|
|
4321
|
+
}
|
|
4322
|
+
function translateRequest(body, npm, options) {
|
|
4323
|
+
const messages = body.messages ?? [];
|
|
4324
|
+
annotateToolNames(messages);
|
|
4325
|
+
const baseSystem = systemToString(body.system);
|
|
4326
|
+
const inlineParts = inlineSystemText(messages);
|
|
4327
|
+
const systemText = [baseSystem, ...inlineParts].filter((s) => s && s.trim()).join("\n\n") || (options?.openAiOAuth ? "You are a coding assistant." : void 0);
|
|
4328
|
+
let upstreamTools = resolveUpstreamTools(
|
|
4329
|
+
body.tools,
|
|
4330
|
+
messages
|
|
4331
|
+
);
|
|
4332
|
+
if (options?.maxTools !== void 0 && upstreamTools.length > options.maxTools) {
|
|
4333
|
+
upstreamTools = upstreamTools.slice(0, options.maxTools);
|
|
4334
|
+
}
|
|
4335
|
+
let responseSubagentRouting;
|
|
4336
|
+
if (options?.subagentRouting) {
|
|
4337
|
+
const agentIndex = upstreamTools.findIndex((toolDefinition) => isClaudeAgentTool(toolDefinition));
|
|
4338
|
+
if (agentIndex >= 0) {
|
|
4339
|
+
upstreamTools = upstreamTools.map((toolDefinition, index) => index === agentIndex ? augmentClaudeAgentTool(
|
|
4340
|
+
toolDefinition,
|
|
4341
|
+
options.subagentRouting
|
|
4342
|
+
) : toolDefinition);
|
|
4343
|
+
responseSubagentRouting = options.subagentRouting;
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
const effort = anthropicEffortFromRequest(body) ?? options?.defaultEffort;
|
|
4347
|
+
let providerOptions = deepMergeProviderOptions(
|
|
4348
|
+
thinkingProviderOptions(npm),
|
|
4349
|
+
effortProviderOptions(npm, effort, options?.reasoningMetadata?.upstreamModelId ?? body.model, options?.reasoningMetadata)
|
|
4350
|
+
);
|
|
4351
|
+
if (options?.openAiOAuth && systemText) {
|
|
4352
|
+
providerOptions = deepMergeProviderOptions(providerOptions, {
|
|
4353
|
+
openai: { instructions: systemText }
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
4356
|
+
return {
|
|
4357
|
+
instructions: options?.openAiOAuth ? void 0 : systemText,
|
|
4358
|
+
messages: translateMessages(messages, npm, options?.onDebug),
|
|
4359
|
+
tools: translateTools(upstreamTools.length ? upstreamTools : void 0),
|
|
4360
|
+
toolChoice: translateToolChoice(body.tool_choice),
|
|
4361
|
+
maxOutputTokens: options?.openAiOAuth ? void 0 : body.max_tokens,
|
|
4362
|
+
temperature: body.temperature,
|
|
4363
|
+
providerOptions,
|
|
4364
|
+
headers: options?.requestHeaders,
|
|
4365
|
+
subagentRouting: responseSubagentRouting
|
|
4366
|
+
};
|
|
4367
|
+
}
|
|
4368
|
+
function logSubagentDecision(log, decision) {
|
|
4369
|
+
if (!log || decision.kind === "fork" || decision.kind === "explicit") return;
|
|
4370
|
+
log(() => decision.kind === "family-fallback" ? `sdk Agent model "${decision.requestedModelId}" unavailable; using parent ${decision.resolvedModelId}` : `sdk Agent model ${decision.kind}: ${"requestedModelId" in decision ? `${decision.requestedModelId} -> ` : ""}${decision.resolvedModelId}`);
|
|
4371
|
+
}
|
|
4372
|
+
async function writeAnthropicStream(fullStream, modelId, write, log, estimatedInputTokens = 0, subagentRouting) {
|
|
4373
|
+
const messageId = "msg_" + Date.now();
|
|
4374
|
+
let blockIndex = -1;
|
|
4375
|
+
let started = false;
|
|
4376
|
+
let openType = null;
|
|
4377
|
+
let pendingThinkingSig;
|
|
4378
|
+
const idToBlock = /* @__PURE__ */ new Map();
|
|
4379
|
+
const bufferedAgentCalls = /* @__PURE__ */ new Map();
|
|
4380
|
+
let finishReason = "end_turn";
|
|
4381
|
+
let usage = { input_tokens: estimatedInputTokens, output_tokens: 0 };
|
|
4382
|
+
const emit = (event, data) => write(sseChunk(event, data));
|
|
4383
|
+
const ensureStart = () => {
|
|
4384
|
+
if (started) return;
|
|
4385
|
+
emit("message_start", {
|
|
4386
|
+
type: "message_start",
|
|
4387
|
+
message: {
|
|
4388
|
+
id: messageId,
|
|
4389
|
+
type: "message",
|
|
4390
|
+
role: "assistant",
|
|
4391
|
+
content: [],
|
|
4392
|
+
model: modelId,
|
|
4393
|
+
stop_reason: null,
|
|
4394
|
+
stop_sequence: null,
|
|
4395
|
+
usage: { input_tokens: estimatedInputTokens, output_tokens: 0 }
|
|
4396
|
+
}
|
|
4397
|
+
});
|
|
4398
|
+
started = true;
|
|
4399
|
+
};
|
|
4400
|
+
const closeOpen = () => {
|
|
4401
|
+
if (openType === "thinking") {
|
|
4402
|
+
emit("content_block_delta", {
|
|
4403
|
+
type: "content_block_delta",
|
|
4404
|
+
index: blockIndex,
|
|
4405
|
+
delta: { type: "signature_delta", signature: pendingThinkingSig ?? "" }
|
|
4406
|
+
});
|
|
4407
|
+
pendingThinkingSig = void 0;
|
|
4408
|
+
}
|
|
4409
|
+
if (openType) emit("content_block_stop", { type: "content_block_stop", index: blockIndex });
|
|
4410
|
+
openType = null;
|
|
4411
|
+
};
|
|
4412
|
+
const openBlock = (type, contentBlock) => {
|
|
4413
|
+
ensureStart();
|
|
4414
|
+
closeOpen();
|
|
4415
|
+
blockIndex++;
|
|
4416
|
+
openType = type;
|
|
4417
|
+
emit("content_block_start", { type: "content_block_start", index: blockIndex, content_block: contentBlock });
|
|
4418
|
+
};
|
|
4419
|
+
for await (const part of fullStream) {
|
|
4420
|
+
switch (part.type) {
|
|
4421
|
+
case "start":
|
|
4422
|
+
ensureStart();
|
|
4423
|
+
break;
|
|
4424
|
+
case "reasoning-start":
|
|
4425
|
+
openBlock("thinking", { type: "thinking", thinking: "", signature: "" });
|
|
4426
|
+
break;
|
|
4427
|
+
case "reasoning-delta":
|
|
4428
|
+
if (openType !== "thinking") openBlock("thinking", { type: "thinking", thinking: "", signature: "" });
|
|
4429
|
+
emit("content_block_delta", {
|
|
4430
|
+
type: "content_block_delta",
|
|
4431
|
+
index: blockIndex,
|
|
4432
|
+
delta: { type: "thinking_delta", thinking: part.text ?? "" }
|
|
4433
|
+
});
|
|
4434
|
+
break;
|
|
4435
|
+
case "reasoning-end": {
|
|
4436
|
+
const sig = grabRoundTripSignature(part);
|
|
4437
|
+
if (sig) pendingThinkingSig = sig;
|
|
4438
|
+
break;
|
|
4439
|
+
}
|
|
4440
|
+
case "text-start":
|
|
4441
|
+
openBlock("text", { type: "text", text: "" });
|
|
4442
|
+
break;
|
|
4443
|
+
case "text-delta":
|
|
4444
|
+
if (openType !== "text") openBlock("text", { type: "text", text: "" });
|
|
4445
|
+
emit("content_block_delta", {
|
|
4446
|
+
type: "content_block_delta",
|
|
4447
|
+
index: blockIndex,
|
|
4448
|
+
delta: { type: "text_delta", text: part.text ?? "" }
|
|
4449
|
+
});
|
|
4450
|
+
break;
|
|
4451
|
+
case "text-end":
|
|
4452
|
+
break;
|
|
4453
|
+
case "tool-input-start": {
|
|
4454
|
+
const sig = grabRoundTripSignature(part);
|
|
4455
|
+
openBlock("tool", {
|
|
4456
|
+
type: "tool_use",
|
|
4457
|
+
id: encodeToolUseId(part.id ?? "", sig),
|
|
4458
|
+
name: part.toolName,
|
|
4459
|
+
input: {}
|
|
4460
|
+
});
|
|
4461
|
+
idToBlock.set(part.id ?? "", blockIndex);
|
|
4462
|
+
if (subagentRouting && part.toolName === "Agent") {
|
|
4463
|
+
bufferedAgentCalls.set(part.id ?? "", { blockIndex });
|
|
4464
|
+
}
|
|
4465
|
+
break;
|
|
4466
|
+
}
|
|
4467
|
+
case "tool-input-delta":
|
|
4468
|
+
if (bufferedAgentCalls.has(part.id ?? "")) break;
|
|
4469
|
+
emit("content_block_delta", {
|
|
4470
|
+
type: "content_block_delta",
|
|
4471
|
+
index: idToBlock.get(part.id ?? "") ?? blockIndex,
|
|
4472
|
+
delta: { type: "input_json_delta", partial_json: part.delta ?? part.text ?? "" }
|
|
4473
|
+
});
|
|
4474
|
+
break;
|
|
4475
|
+
case "tool-input-end":
|
|
4476
|
+
break;
|
|
4477
|
+
case "tool-call": {
|
|
4478
|
+
finishReason = "tool_use";
|
|
4479
|
+
const toolCallId = part.toolCallId ?? "";
|
|
4480
|
+
if (subagentRouting && part.toolName === "Agent") {
|
|
4481
|
+
try {
|
|
4482
|
+
const normalized = prepareClaudeAgentInput(part.input, subagentRouting);
|
|
4483
|
+
logSubagentDecision(log, normalized.decision);
|
|
4484
|
+
const buffered = bufferedAgentCalls.get(toolCallId);
|
|
4485
|
+
if (!buffered && !idToBlock.has(toolCallId)) {
|
|
4486
|
+
const sig = grabRoundTripSignature(part);
|
|
4487
|
+
openBlock("tool", {
|
|
4488
|
+
type: "tool_use",
|
|
4489
|
+
id: encodeToolUseId(toolCallId, sig),
|
|
4490
|
+
name: part.toolName,
|
|
4491
|
+
input: {}
|
|
4492
|
+
});
|
|
4493
|
+
idToBlock.set(toolCallId, blockIndex);
|
|
4494
|
+
}
|
|
4495
|
+
emit("content_block_delta", {
|
|
4496
|
+
type: "content_block_delta",
|
|
4497
|
+
index: buffered?.blockIndex ?? idToBlock.get(toolCallId) ?? blockIndex,
|
|
4498
|
+
delta: {
|
|
4499
|
+
type: "input_json_delta",
|
|
4500
|
+
partial_json: JSON.stringify(stripNullInputs(normalized.input))
|
|
4501
|
+
}
|
|
4502
|
+
});
|
|
4503
|
+
bufferedAgentCalls.delete(toolCallId);
|
|
4504
|
+
} catch (error) {
|
|
4505
|
+
if (error instanceof UnavailableSubagentModelError) {
|
|
4506
|
+
bufferedAgentCalls.delete(toolCallId);
|
|
4507
|
+
closeOpen();
|
|
4508
|
+
emit("error", {
|
|
4509
|
+
type: "error",
|
|
4510
|
+
error: {
|
|
4511
|
+
type: anthropicErrorType(error.statusCode),
|
|
4512
|
+
message: error.message
|
|
4513
|
+
}
|
|
4514
|
+
});
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4517
|
+
throw error;
|
|
4518
|
+
}
|
|
4519
|
+
break;
|
|
4520
|
+
}
|
|
4521
|
+
if (!idToBlock.has(toolCallId) && openType !== "tool") {
|
|
4522
|
+
const sig = grabRoundTripSignature(part);
|
|
4523
|
+
openBlock("tool", {
|
|
4524
|
+
type: "tool_use",
|
|
4525
|
+
id: encodeToolUseId(toolCallId, sig),
|
|
4526
|
+
name: part.toolName,
|
|
4527
|
+
input: {}
|
|
4528
|
+
});
|
|
4529
|
+
emit("content_block_delta", {
|
|
4530
|
+
type: "content_block_delta",
|
|
4531
|
+
index: blockIndex,
|
|
4532
|
+
delta: { type: "input_json_delta", partial_json: JSON.stringify(stripNullInputs(part.input ?? {})) }
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
break;
|
|
4536
|
+
}
|
|
4537
|
+
case "finish":
|
|
4538
|
+
if (part.totalUsage) {
|
|
4539
|
+
usage = {
|
|
4540
|
+
input_tokens: part.totalUsage.inputTokens ?? estimatedInputTokens,
|
|
4541
|
+
output_tokens: part.totalUsage.outputTokens ?? 0
|
|
4542
|
+
};
|
|
4543
|
+
}
|
|
4544
|
+
if (part.finishReason === "tool-calls") finishReason = "tool_use";
|
|
4545
|
+
else if (part.finishReason === "length") finishReason = "max_tokens";
|
|
4546
|
+
else if (part.finishReason === "stop" && finishReason !== "tool_use") finishReason = "end_turn";
|
|
4547
|
+
break;
|
|
4548
|
+
case "error": {
|
|
4549
|
+
const e = part.error;
|
|
4550
|
+
const errMsg = e?.message || (typeof part.error === "string" ? part.error : JSON.stringify(e?.data ?? part.error));
|
|
4551
|
+
const errorType = anthropicErrorType(upstreamHttpStatus(part.error, errMsg));
|
|
4552
|
+
log?.(() => `sdk stream error (${errorType}): ${errMsg}`);
|
|
4553
|
+
bufferedAgentCalls.clear();
|
|
4554
|
+
closeOpen();
|
|
4555
|
+
emit("error", { type: "error", error: { type: errorType, message: errMsg } });
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4558
|
+
default:
|
|
4559
|
+
break;
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
closeOpen();
|
|
4563
|
+
ensureStart();
|
|
4564
|
+
emit("message_delta", { type: "message_delta", delta: { stop_reason: finishReason, stop_sequence: null }, usage });
|
|
4565
|
+
emit("message_stop", { type: "message_stop" });
|
|
4566
|
+
}
|
|
4567
|
+
async function streamAnthropicResponse(model, params, modelId, write, log, estimatedInputTokens = 0) {
|
|
4568
|
+
const { subagentRouting, ...providerParams } = params;
|
|
4569
|
+
const result = streamText({ model, ...providerParams, onError: () => {
|
|
4570
|
+
} });
|
|
4571
|
+
Promise.resolve(result.text).catch(() => {
|
|
4572
|
+
});
|
|
4573
|
+
Promise.resolve(result.toolCalls).catch(() => {
|
|
4574
|
+
});
|
|
4575
|
+
Promise.resolve(result.toolResults).catch(() => {
|
|
4576
|
+
});
|
|
4577
|
+
Promise.resolve(result.finishReason).catch(() => {
|
|
4578
|
+
});
|
|
4579
|
+
Promise.resolve(result.usage).catch(() => {
|
|
4580
|
+
});
|
|
4581
|
+
await writeAnthropicStream(
|
|
4582
|
+
result.stream,
|
|
4583
|
+
modelId,
|
|
4584
|
+
write,
|
|
4585
|
+
log,
|
|
4586
|
+
estimatedInputTokens,
|
|
4587
|
+
subagentRouting
|
|
4588
|
+
);
|
|
4589
|
+
}
|
|
4590
|
+
async function generateAnthropicResponse(model, params, modelId, options) {
|
|
4591
|
+
const { subagentRouting, ...providerParams } = params;
|
|
4592
|
+
let text;
|
|
4593
|
+
let toolCalls;
|
|
4594
|
+
let finishReason;
|
|
4595
|
+
let usage;
|
|
4596
|
+
if (options?.forceStream) {
|
|
4597
|
+
let firstStreamError;
|
|
4598
|
+
const r = streamText({
|
|
4599
|
+
model,
|
|
4600
|
+
...providerParams,
|
|
4601
|
+
onError: (event) => {
|
|
4602
|
+
firstStreamError ??= event;
|
|
4603
|
+
}
|
|
4604
|
+
});
|
|
4605
|
+
Promise.resolve(r.toolResults).catch(() => {
|
|
4606
|
+
});
|
|
4607
|
+
try {
|
|
4608
|
+
[text, toolCalls, finishReason, usage] = await Promise.all([r.text, r.toolCalls, r.finishReason, r.usage]);
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
throw firstStreamError ? firstStreamError.error : error;
|
|
4611
|
+
}
|
|
4612
|
+
} else {
|
|
4613
|
+
const r = await generateText({ model, ...providerParams });
|
|
4614
|
+
({ text, toolCalls, finishReason, usage } = r);
|
|
4615
|
+
}
|
|
4616
|
+
return {
|
|
4617
|
+
id: "msg_" + Date.now(),
|
|
4618
|
+
type: "message",
|
|
4619
|
+
role: "assistant",
|
|
4620
|
+
model: modelId,
|
|
4621
|
+
content: [
|
|
4622
|
+
...text ? [{ type: "text", text }] : [],
|
|
4623
|
+
...toolCalls.map((tc) => {
|
|
4624
|
+
let input = tc.input;
|
|
4625
|
+
if (subagentRouting && tc.toolName === "Agent") {
|
|
4626
|
+
const normalized = prepareClaudeAgentInput(tc.input, subagentRouting);
|
|
4627
|
+
logSubagentDecision(options?.log, normalized.decision);
|
|
4628
|
+
input = normalized.input;
|
|
4629
|
+
}
|
|
4630
|
+
return {
|
|
4631
|
+
type: "tool_use",
|
|
4632
|
+
id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
|
|
4633
|
+
name: tc.toolName,
|
|
4634
|
+
input: stripNullInputs(input)
|
|
4635
|
+
};
|
|
4636
|
+
})
|
|
4637
|
+
],
|
|
4638
|
+
stop_reason: finishReason === "tool-calls" ? "tool_use" : "end_turn",
|
|
4639
|
+
usage: { input_tokens: usage?.inputTokens ?? 0, output_tokens: usage?.outputTokens ?? 0 }
|
|
4640
|
+
};
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
export {
|
|
4644
|
+
BACKENDS,
|
|
4645
|
+
CODEX_RESPONSES_LITE_WS_URL,
|
|
4646
|
+
CODEX_RESPONSES_LITE_VERSION,
|
|
4647
|
+
CODEX_RESPONSES_WEBSOCKETS_BETA,
|
|
4648
|
+
CONFLICTING_ENV_VARS,
|
|
4649
|
+
MAX_MODEL_CATALOG,
|
|
4650
|
+
CODEX_SUBAGENT_MODEL_CAP,
|
|
4651
|
+
MIN_CONTEXT_WINDOW,
|
|
4652
|
+
VERTEX_ANTHROPIC_NPM,
|
|
4653
|
+
classifyModelFormat,
|
|
4654
|
+
VERSION,
|
|
4655
|
+
requestOpenAiDeviceCode,
|
|
4656
|
+
openAiDeviceCodeUrl,
|
|
4657
|
+
pollOpenAiDeviceCodeToken,
|
|
4658
|
+
runOpenAiDeviceCodeFlow,
|
|
4659
|
+
CLAUDE_CODE_CLI_VERSION2 as CLAUDE_CODE_CLI_VERSION,
|
|
4660
|
+
CLAUDE_CODE_USER_AGENT,
|
|
4661
|
+
buildClaudeCodeBillingSystemLine,
|
|
4662
|
+
injectClaudeCodeBillingSystemLine,
|
|
4663
|
+
selectBetaFlags,
|
|
4664
|
+
injectClaudeIdentity,
|
|
4665
|
+
CLINE_PASS_CATALOG_URL,
|
|
4666
|
+
CLINE_PASS_VALIDATION_URL,
|
|
4667
|
+
CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW,
|
|
4668
|
+
modelPrefersResponsesApi,
|
|
4669
|
+
isSdkMigratedNpm,
|
|
4670
|
+
maxToolsForNpm,
|
|
4671
|
+
createLanguageModel,
|
|
4672
|
+
getReasoningCapabilities,
|
|
4673
|
+
buildCodexReasoningLevels,
|
|
4674
|
+
effortProviderOptions,
|
|
4675
|
+
deepMergeProviderOptions,
|
|
4676
|
+
thinkingProviderOptions,
|
|
4677
|
+
getAppHome,
|
|
4678
|
+
getLegacyAppHome,
|
|
4679
|
+
getConfigPath,
|
|
4680
|
+
getProvidersPath,
|
|
4681
|
+
getLogsPath,
|
|
4682
|
+
getVertexModelsPath,
|
|
4683
|
+
getLegacyConfPath,
|
|
4684
|
+
loadOpencodeCache,
|
|
4685
|
+
resolveContextWindow,
|
|
4686
|
+
stripOneMContextSuffix,
|
|
4687
|
+
claudeCodeClientModelId,
|
|
4688
|
+
routeLookupIds,
|
|
4689
|
+
readOpencodeAuthFile,
|
|
4690
|
+
isOpencodeOAuth,
|
|
4691
|
+
oauthCredentialToKeychainJson,
|
|
4692
|
+
tokensToStoredCredential,
|
|
4693
|
+
supportsNativeOAuth,
|
|
4694
|
+
isBrowserRedirectOAuth,
|
|
4695
|
+
requestGithubDeviceCode,
|
|
4696
|
+
pollGithubDeviceCodeToken,
|
|
4697
|
+
runGithubDeviceCodeFlow,
|
|
4698
|
+
requestXaiDeviceCode,
|
|
4699
|
+
pollXaiDeviceCodeToken,
|
|
4700
|
+
runXaiDeviceCodeFlow,
|
|
4701
|
+
generateCliUserID,
|
|
4702
|
+
runClaudeCodeOAuthFlow,
|
|
4703
|
+
fetchClaudeCodeModels,
|
|
4704
|
+
guiCallbackRedirectUri,
|
|
4705
|
+
ANTIGRAVITY_BASE_URLS,
|
|
4706
|
+
buildAntigravityAuthUrl,
|
|
4707
|
+
runAntigravityOAuthFlow,
|
|
4708
|
+
completeAntigravityExchange,
|
|
4709
|
+
requestClinePassDeviceCode,
|
|
4710
|
+
pollClinePassDeviceCode,
|
|
4711
|
+
runClinePassDeviceCodeFlow,
|
|
4712
|
+
detectConflicts,
|
|
4713
|
+
resolveApiKey,
|
|
4714
|
+
buildChildEnv,
|
|
4715
|
+
buildAntigravityChildEnv,
|
|
4716
|
+
GLOBAL_OPENCODE_KEYRING_ACCOUNT,
|
|
4717
|
+
preferredRelayCredentialAuthRef,
|
|
4718
|
+
parseAuthRef,
|
|
4719
|
+
readGlobalOpencodeCredential,
|
|
4720
|
+
readStoredProviderCredential,
|
|
4721
|
+
migrateGlobalOpencodeCredential,
|
|
4722
|
+
resolveProviderCredential,
|
|
4723
|
+
forceRefreshProviderCredential,
|
|
4724
|
+
resolveProviderOAuthAccountId,
|
|
4725
|
+
resolveProviderOAuthProviderData,
|
|
4726
|
+
enrichGithubCopilotOAuthProviderData,
|
|
4727
|
+
saveProviderCredential,
|
|
4728
|
+
deleteProviderCredential,
|
|
4729
|
+
readFromCredentialStore,
|
|
4730
|
+
saveToCredentialStore,
|
|
4731
|
+
isSecretServiceAvailable,
|
|
4732
|
+
migrateLegacyCloudProviders,
|
|
4733
|
+
isValidProviderId,
|
|
4734
|
+
slugifyProviderId,
|
|
4735
|
+
customProviderId,
|
|
4736
|
+
supportsManualModels,
|
|
4737
|
+
getProviderModels,
|
|
4738
|
+
modelIdError,
|
|
4739
|
+
contextWindowError,
|
|
4740
|
+
loadRegistry,
|
|
4741
|
+
saveRegistry,
|
|
4742
|
+
validateCustomEndpointUrl,
|
|
4743
|
+
extractClaudeSessionId,
|
|
4744
|
+
SubagentRouteRegistry,
|
|
4745
|
+
claudeModelFamily,
|
|
4746
|
+
grabRoundTripSignature,
|
|
4747
|
+
silenceSdkWarnings,
|
|
4748
|
+
parseToolArguments,
|
|
4749
|
+
sseChunk,
|
|
4750
|
+
parseDsmlToolCalls,
|
|
4751
|
+
splitToolUseId,
|
|
4752
|
+
encodeToolUseId,
|
|
4753
|
+
serializeToolResultContent,
|
|
4754
|
+
formatUpstreamErrorTrace,
|
|
4755
|
+
formatUpstreamError,
|
|
4756
|
+
upstreamHttpStatus,
|
|
4757
|
+
anthropicErrorType,
|
|
4758
|
+
anthropicEffortFromRequest,
|
|
4759
|
+
translateRequest,
|
|
4760
|
+
streamAnthropicResponse,
|
|
4761
|
+
generateAnthropicResponse
|
|
4762
|
+
};
|
|
4763
|
+
//# sourceMappingURL=chunk-O2XZFXHG.js.map
|