@wrongstack/providers 0.264.0 → 0.267.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { expectDefined, ProviderError, StreamHangError, safeParse, sanitizeJsonString, WrongStackError, ERROR_CODES } from '@wrongstack/core';
1
+ import { expectDefined, ProviderError, compactToolDefinitionForWire, StreamHangError, safeParse, sanitizeJsonString, WrongStackError, ERROR_CODES } from '@wrongstack/core';
2
2
  import { Readable } from 'stream';
3
3
  import { toErrorMessage } from '@wrongstack/core/utils';
4
4
  import { randomUUID } from 'crypto';
@@ -362,6 +362,45 @@ var CAPABILITIES_BY_FAMILY = {
362
362
  maxContext: 0,
363
363
  cacheControl: "none"
364
364
  },
365
+ // Claude Pro/Max via OAuth (Sign in with Claude). Same wire as anthropic.
366
+ "anthropic-oauth": {
367
+ tools: true,
368
+ parallelTools: true,
369
+ vision: true,
370
+ streaming: true,
371
+ promptCache: true,
372
+ systemPrompt: true,
373
+ jsonMode: false,
374
+ reasoning: false,
375
+ maxContext: 2e5,
376
+ cacheControl: "native"
377
+ },
378
+ // GitHub Copilot subscription (OpenAI chat/completions-compatible wire).
379
+ "github-copilot": {
380
+ tools: true,
381
+ parallelTools: true,
382
+ vision: true,
383
+ streaming: true,
384
+ promptCache: false,
385
+ systemPrompt: true,
386
+ jsonMode: true,
387
+ reasoning: false,
388
+ maxContext: 128e3,
389
+ cacheControl: "auto"
390
+ },
391
+ // ChatGPT-backend Responses API (Sign in with ChatGPT / Codex subscription).
392
+ "openai-codex": {
393
+ tools: true,
394
+ parallelTools: true,
395
+ vision: true,
396
+ streaming: true,
397
+ promptCache: true,
398
+ systemPrompt: true,
399
+ jsonMode: false,
400
+ reasoning: true,
401
+ maxContext: 272e3,
402
+ cacheControl: "auto"
403
+ },
365
404
  google: {
366
405
  tools: true,
367
406
  parallelTools: true,
@@ -535,17 +574,20 @@ function normalizeGemini(stop) {
535
574
  return "end_turn";
536
575
  }
537
576
  }
538
-
539
- // src/tool-format/to-anthropic.ts
577
+ var _cache = /* @__PURE__ */ new WeakMap();
540
578
  function toolsToAnthropic(tools) {
541
- return tools.map((t) => ({
542
- name: t.name,
543
- description: t.description,
544
- input_schema: t.inputSchema ?? {
545
- type: "object",
546
- properties: {}
547
- }
548
- }));
579
+ const hit = _cache.get(tools);
580
+ if (hit) return hit;
581
+ const result = tools.map((t) => {
582
+ const compact = compactToolDefinitionForWire(t);
583
+ return {
584
+ name: compact.name,
585
+ description: compact.description,
586
+ input_schema: compact.inputSchema
587
+ };
588
+ });
589
+ _cache.set(tools, result);
590
+ return result;
549
591
  }
550
592
 
551
593
  // src/stream-debug-state.ts
@@ -864,12 +906,34 @@ var AnthropicProvider = class extends WireAdapter {
864
906
  return parseProviderHttpError(this.id, status, text);
865
907
  }
866
908
  normalizeMessage(m) {
867
- return {
868
- role: m.role === "system" ? "user" : m.role,
869
- content: typeof m.content === "string" ? m.content : m.content
870
- };
909
+ const role = m.role === "system" ? "user" : m.role;
910
+ if (typeof m.content === "string") return { role, content: m.content };
911
+ return { role, content: m.content.map((b) => sanitizeAnthropicBlock(b)) };
871
912
  }
872
913
  };
914
+ function sanitizeAnthropicBlock(b) {
915
+ switch (b.type) {
916
+ case "text":
917
+ return b.cache_control ? { type: "text", text: b.text, cache_control: b.cache_control } : { type: "text", text: b.text };
918
+ case "tool_use":
919
+ return { type: "tool_use", id: b.id, name: b.name, input: b.input };
920
+ case "tool_result": {
921
+ const out = {
922
+ type: "tool_result",
923
+ tool_use_id: b.tool_use_id,
924
+ content: b.content
925
+ };
926
+ if (b.is_error) out["is_error"] = true;
927
+ return out;
928
+ }
929
+ case "thinking":
930
+ return b.signature ? { type: "thinking", thinking: b.thinking, signature: b.signature } : { type: "thinking", thinking: b.thinking };
931
+ case "image":
932
+ return { type: "image", source: b.source };
933
+ default:
934
+ return b;
935
+ }
936
+ }
873
937
  async function* parseAnthropicStream(body, fallbackModel) {
874
938
  const blocks = /* @__PURE__ */ new Map();
875
939
  let model = fallbackModel;
@@ -972,12 +1036,575 @@ async function* parseAnthropicStream(body, fallbackModel) {
972
1036
  yield { type: "message_stop", stopReason, usage };
973
1037
  }
974
1038
  }
975
- var DEFAULT_BASE2 = "https://generativelanguage.googleapis.com/v1beta";
1039
+ var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1040
+ var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
1041
+ var DEFAULT_BASE2 = "https://api.anthropic.com";
1042
+ var ANTHROPIC_VERSION = "2023-06-01";
1043
+ var OAUTH_BETA = "claude-code-20250219,oauth-2025-04-20";
1044
+ var REFRESH_SKEW_MS = 6e4;
1045
+ var CLAUDE_CODE_VERSION = "2.1.75";
1046
+ var CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
1047
+ var CLAUDE_CODE_TOOLS = [
1048
+ "Read",
1049
+ "Write",
1050
+ "Edit",
1051
+ "Bash",
1052
+ "Grep",
1053
+ "Glob",
1054
+ "AskUserQuestion",
1055
+ "EnterPlanMode",
1056
+ "ExitPlanMode",
1057
+ "KillShell",
1058
+ "NotebookEdit",
1059
+ "Skill",
1060
+ "Task",
1061
+ "TaskOutput",
1062
+ "TodoWrite",
1063
+ "WebFetch",
1064
+ "WebSearch"
1065
+ ];
1066
+ var CC_TOOL_BY_LOWER = new Map(CLAUDE_CODE_TOOLS.map((t) => [t.toLowerCase(), t]));
1067
+ function toClaudeCodeName(name) {
1068
+ return CC_TOOL_BY_LOWER.get(name.toLowerCase()) ?? name;
1069
+ }
1070
+ function fromClaudeCodeName(name, tools) {
1071
+ const lower = name.toLowerCase();
1072
+ const match = tools?.find((t) => t.name.toLowerCase() === lower);
1073
+ return match?.name ?? name;
1074
+ }
1075
+ async function refreshAnthropicOAuthToken(refreshToken, signal) {
1076
+ const res = await fetch(TOKEN_URL, {
1077
+ method: "POST",
1078
+ headers: { "content-type": "application/json", accept: "application/json" },
1079
+ body: JSON.stringify({
1080
+ grant_type: "refresh_token",
1081
+ client_id: CLIENT_ID,
1082
+ refresh_token: refreshToken
1083
+ }),
1084
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
1085
+ });
1086
+ if (!res.ok) {
1087
+ const text = await res.text().catch(() => "");
1088
+ throw new Error(`Claude token refresh failed (${res.status}): ${text || res.statusText}`);
1089
+ }
1090
+ const json = await res.json();
1091
+ if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
1092
+ throw new Error("Claude token refresh response missing fields");
1093
+ }
1094
+ return {
1095
+ access: json.access_token,
1096
+ refresh: json.refresh_token,
1097
+ expires: Date.now() + json.expires_in * 1e3
1098
+ };
1099
+ }
1100
+ var AnthropicOAuthProvider = class extends AnthropicProvider {
1101
+ id;
1102
+ capabilities;
1103
+ access;
1104
+ refresh;
1105
+ expiresAt;
1106
+ onRefresh;
1107
+ refreshFn;
1108
+ constructor(opts) {
1109
+ super({
1110
+ apiKey: opts.credentials.accessToken,
1111
+ baseUrl: opts.baseUrl ?? DEFAULT_BASE2,
1112
+ fetchImpl: opts.fetchImpl,
1113
+ streamOpts: opts.streamOpts
1114
+ });
1115
+ this.id = opts.id ?? "anthropic-oauth";
1116
+ this.capabilities = capabilitiesForFamily("anthropic-oauth");
1117
+ this.access = opts.credentials.accessToken;
1118
+ this.refresh = opts.credentials.refreshToken;
1119
+ this.expiresAt = opts.credentials.expiresAt;
1120
+ this.onRefresh = opts.onRefresh;
1121
+ this.refreshFn = opts.refreshFn ?? refreshAnthropicOAuthToken;
1122
+ }
1123
+ async *stream(req, opts) {
1124
+ await this.ensureFreshToken(opts.signal);
1125
+ try {
1126
+ yield* this.remapToolNames(super.stream(req, opts), req.tools);
1127
+ } catch (err) {
1128
+ if (err instanceof ProviderError && err.status === 401 && this.refresh) {
1129
+ await this.doRefresh(opts.signal);
1130
+ yield* this.remapToolNames(super.stream(req, opts), req.tools);
1131
+ return;
1132
+ }
1133
+ throw err;
1134
+ }
1135
+ }
1136
+ /** Map Claude-Code-cased tool_use names in the stream back to real names. */
1137
+ async *remapToolNames(events, tools) {
1138
+ for await (const ev of events) {
1139
+ if ((ev.type === "tool_use_start" || ev.type === "content_block_start") && typeof ev.name === "string") {
1140
+ yield { ...ev, name: fromClaudeCodeName(ev.name, tools) };
1141
+ } else {
1142
+ yield ev;
1143
+ }
1144
+ }
1145
+ }
1146
+ async ensureFreshToken(signal) {
1147
+ if (!this.refresh) return;
1148
+ if (this.expiresAt !== void 0 && Date.now() < this.expiresAt - REFRESH_SKEW_MS) return;
1149
+ await this.doRefresh(signal);
1150
+ }
1151
+ async doRefresh(signal) {
1152
+ if (!this.refresh) return;
1153
+ const t = await this.refreshFn(this.refresh, signal);
1154
+ this.access = t.access;
1155
+ this.refresh = t.refresh;
1156
+ this.expiresAt = t.expires;
1157
+ this.onRefresh?.({ accessToken: t.access, refreshToken: t.refresh, expiresAt: t.expires });
1158
+ }
1159
+ buildHeaders(_req) {
1160
+ return {
1161
+ "content-type": "application/json",
1162
+ accept: "text/event-stream",
1163
+ "anthropic-version": ANTHROPIC_VERSION,
1164
+ authorization: `Bearer ${this.access}`,
1165
+ "anthropic-beta": OAUTH_BETA,
1166
+ // Present as the official Claude Code CLI so the subscription backend
1167
+ // accepts the request and the client isn't trivially fingerprinted.
1168
+ "user-agent": `claude-cli/${CLAUDE_CODE_VERSION}`,
1169
+ "x-app": "cli",
1170
+ "anthropic-dangerous-direct-browser-access": "true"
1171
+ };
1172
+ }
1173
+ buildBody(req) {
1174
+ const body = super.buildBody(req);
1175
+ const existing = body["system"] ?? [];
1176
+ const alreadyLed = existing[0]?.text?.startsWith(CLAUDE_CODE_SYSTEM_PROMPT) === true;
1177
+ body["system"] = alreadyLed ? existing : [{ type: "text", text: CLAUDE_CODE_SYSTEM_PROMPT }, ...existing];
1178
+ const tools = body["tools"];
1179
+ if (Array.isArray(tools)) {
1180
+ for (const t of tools) {
1181
+ if (typeof t.name === "string") t.name = toClaudeCodeName(t.name);
1182
+ }
1183
+ }
1184
+ const messages = body["messages"];
1185
+ if (Array.isArray(messages)) {
1186
+ for (const m of messages) {
1187
+ if (!Array.isArray(m.content)) continue;
1188
+ for (const block of m.content) {
1189
+ if (block?.type === "tool_use" && typeof block.name === "string") {
1190
+ block.name = toClaudeCodeName(block.name);
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ return body;
1196
+ }
1197
+ };
1198
+
1199
+ // src/openai.ts
1200
+ init_tool_input();
1201
+ var _cache2 = /* @__PURE__ */ new WeakMap();
1202
+ function toolsToOpenAI(tools) {
1203
+ const hit = _cache2.get(tools);
1204
+ if (hit) return hit;
1205
+ const result = tools.map((t) => {
1206
+ const compact = compactToolDefinitionForWire(t);
1207
+ return {
1208
+ type: "function",
1209
+ function: {
1210
+ name: compact.name,
1211
+ description: compact.description,
1212
+ parameters: compact.inputSchema
1213
+ }
1214
+ };
1215
+ });
1216
+ _cache2.set(tools, result);
1217
+ return result;
1218
+ }
1219
+ function messagesToOpenAI(system, messages, opts = {}) {
1220
+ const emptyContentMode = opts.emptyToolCallContent ?? "empty_string";
1221
+ const out = [];
1222
+ if (system && system.length > 0) {
1223
+ const sysText = system.map((b) => b.text).join("\n\n");
1224
+ if (opts.systemAsMessage) {
1225
+ out.push({ role: "user", content: sysText });
1226
+ } else {
1227
+ out.push({ role: "system", content: sysText });
1228
+ }
1229
+ }
1230
+ for (const msg of messages) {
1231
+ if (msg.role === "user") {
1232
+ const blocks = normalizeContent(msg.content);
1233
+ const toolResults = blocks.filter((b) => b.type === "tool_result");
1234
+ const others = blocks.filter((b) => b.type !== "tool_result");
1235
+ for (const r of toolResults) {
1236
+ const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
1237
+ out.push({
1238
+ role: "tool",
1239
+ tool_call_id: r.tool_use_id,
1240
+ content
1241
+ });
1242
+ }
1243
+ if (others.length > 0) {
1244
+ out.push({
1245
+ role: "user",
1246
+ content: opts.flattenContentToString ? blocksToString(others) : blocksToContentArray(others)
1247
+ });
1248
+ }
1249
+ } else if (msg.role === "assistant") {
1250
+ const blocks = normalizeContent(msg.content);
1251
+ const textBlocks = blocks.filter((b) => b.type === "text");
1252
+ const toolUses = blocks.filter((b) => b.type === "tool_use");
1253
+ const thinkingBlocks = blocks.filter((b) => b.type === "thinking");
1254
+ const text = textBlocks.map((b) => b.text).join("");
1255
+ const reasoning = thinkingBlocks.map((b) => b.thinking).filter((t) => t && t.length > 0).join("");
1256
+ const toolCalls = toolUses.map((u) => ({
1257
+ id: u.id,
1258
+ type: "function",
1259
+ function: { name: u.name, arguments: JSON.stringify(u.input) }
1260
+ }));
1261
+ const message = { role: "assistant" };
1262
+ if (toolCalls.length > 0) {
1263
+ message.tool_calls = toolCalls;
1264
+ if (text) {
1265
+ message.content = text;
1266
+ } else {
1267
+ message.content = emptyContentMode === "null" ? null : "";
1268
+ }
1269
+ } else {
1270
+ message.content = text;
1271
+ }
1272
+ if (reasoning.length > 0) {
1273
+ message.reasoning_content = reasoning;
1274
+ }
1275
+ out.push(message);
1276
+ }
1277
+ }
1278
+ return out;
1279
+ }
1280
+ function normalizeContent(content) {
1281
+ return typeof content === "string" ? [{ type: "text", text: content }] : content;
1282
+ }
1283
+ function blocksToString(blocks) {
1284
+ return blocks.map((b) => {
1285
+ if (b.type === "text") return b.text;
1286
+ if (b.type === "image") return "[image]";
1287
+ return "";
1288
+ }).join("");
1289
+ }
1290
+ function blocksToContentArray(blocks) {
1291
+ const hasImage = blocks.some((b) => b.type === "image");
1292
+ if (!hasImage) {
1293
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
1294
+ }
1295
+ return blocks.map((b) => {
1296
+ if (b.type === "text") return { type: "text", text: b.text };
1297
+ if (b.type === "image") {
1298
+ const url = b.source.type === "url" ? b.source.url ?? "" : `data:${b.source.media_type ?? "image/png"};base64,${b.source.data ?? ""}`;
1299
+ return { type: "image_url", image_url: { url } };
1300
+ }
1301
+ return null;
1302
+ }).filter((c) => c !== null);
1303
+ }
1304
+
1305
+ // src/openai.ts
1306
+ var DEFAULT_BASE3 = "https://api.openai.com/v1";
1307
+ var OpenAIProvider = class extends WireAdapter {
1308
+ id;
1309
+ capabilities;
1310
+ opts;
1311
+ constructor(opts) {
1312
+ super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE3, opts.fetchImpl, opts.streamOpts);
1313
+ this.opts = opts;
1314
+ this.id = opts.id ?? "openai";
1315
+ this.capabilities = capabilitiesForFamily("openai", {
1316
+ parallelTools: !opts.quirks?.parallelToolsDisabled,
1317
+ systemPrompt: !opts.quirks?.systemAsMessage,
1318
+ ...opts.capabilities
1319
+ });
1320
+ }
1321
+ buildUrl(_req) {
1322
+ const base = this.baseUrl.replace(/\/+$/, "");
1323
+ if (/\/chat\/completions$/.test(base)) return base;
1324
+ if (/\/v\d+(\/[a-z0-9_-]+)*$/i.test(base)) return `${base}/chat/completions`;
1325
+ return `${base}/v1/chat/completions`;
1326
+ }
1327
+ buildHeaders(req) {
1328
+ const headers = {
1329
+ ...super.buildHeaders(req),
1330
+ authorization: `Bearer ${this.apiKey}`
1331
+ };
1332
+ if (this.opts.organization) {
1333
+ headers["openai-organization"] = this.opts.organization;
1334
+ }
1335
+ return headers;
1336
+ }
1337
+ /**
1338
+ * The request field used to cap output length. Real OpenAI deprecated
1339
+ * `max_tokens` and the newer model families (gpt-4o, o1/o3/o4) 400 on it —
1340
+ * they require `max_completion_tokens`. OpenAI-compatible endpoints that
1341
+ * still only accept `max_tokens` override this. See issue #10.
1342
+ */
1343
+ tokenLimitParam() {
1344
+ return "max_completion_tokens";
1345
+ }
1346
+ buildBody(req) {
1347
+ const body = {
1348
+ model: req.model,
1349
+ messages: messagesToOpenAI(this.stripCacheControl(req), req.messages, {
1350
+ ...this.opts.quirks
1351
+ }),
1352
+ [this.tokenLimitParam()]: req.maxTokens,
1353
+ stream: true,
1354
+ stream_options: { include_usage: true }
1355
+ };
1356
+ if (req.tools && req.tools.length > 0) {
1357
+ body["tools"] = toolsToOpenAI(req.tools);
1358
+ if (req.toolChoice) {
1359
+ if (typeof req.toolChoice === "string") {
1360
+ body["tool_choice"] = req.toolChoice === "required" ? "required" : req.toolChoice;
1361
+ } else {
1362
+ body["tool_choice"] = {
1363
+ type: "function",
1364
+ function: { name: req.toolChoice.name }
1365
+ };
1366
+ }
1367
+ }
1368
+ }
1369
+ if (req.temperature !== void 0) body["temperature"] = req.temperature;
1370
+ if (req.topP !== void 0) body["top_p"] = req.topP;
1371
+ if (req.stopSequences) body["stop"] = req.stopSequences;
1372
+ return body;
1373
+ }
1374
+ parseStream(body, fallbackModel) {
1375
+ return parseOpenAIStream(body, fallbackModel);
1376
+ }
1377
+ translateError(status, text) {
1378
+ return parseProviderHttpError(this.id, status, text);
1379
+ }
1380
+ stripCacheControl(req) {
1381
+ if (!req.system) return void 0;
1382
+ return req.system.map((b) => {
1383
+ const { cache_control: _cc, ...rest } = b;
1384
+ return rest;
1385
+ });
1386
+ }
1387
+ };
1388
+ async function* parseOpenAIStream(body, fallbackModel) {
1389
+ let model = fallbackModel;
1390
+ let usage = { input: 0, output: 0 };
1391
+ let stopReason = "end_turn";
1392
+ let started = false;
1393
+ let thinkingOpen = false;
1394
+ const toolByIndex = /* @__PURE__ */ new Map();
1395
+ for await (const msg of parseSSE(body)) {
1396
+ if (!msg.data || msg.data === "[DONE]") continue;
1397
+ const parsed = safeParse(msg.data);
1398
+ if (!parsed.ok || !parsed.value) continue;
1399
+ const obj = parsed.value;
1400
+ if (typeof obj["model"] === "string") model = obj["model"];
1401
+ if (!started) {
1402
+ started = true;
1403
+ yield { type: "message_start", model };
1404
+ }
1405
+ const choices = obj["choices"];
1406
+ const choice = choices?.[0];
1407
+ const reasoningDelta = typeof choice?.delta?.reasoning_content === "string" ? choice.delta.reasoning_content : typeof choice?.delta?.reasoning === "string" ? choice.delta.reasoning : void 0;
1408
+ if (reasoningDelta && reasoningDelta.length > 0) {
1409
+ if (!thinkingOpen) {
1410
+ thinkingOpen = true;
1411
+ yield { type: "thinking_start" };
1412
+ }
1413
+ yield { type: "thinking_delta", text: reasoningDelta };
1414
+ }
1415
+ if (choice?.delta?.content) {
1416
+ if (thinkingOpen) {
1417
+ thinkingOpen = false;
1418
+ yield { type: "thinking_stop" };
1419
+ }
1420
+ yield { type: "text_delta", text: choice.delta.content };
1421
+ }
1422
+ if (choice?.delta?.tool_calls) {
1423
+ if (thinkingOpen) {
1424
+ thinkingOpen = false;
1425
+ yield { type: "thinking_stop" };
1426
+ }
1427
+ for (const tc of choice.delta.tool_calls) {
1428
+ const idx = tc.index ?? 0;
1429
+ let entry = toolByIndex.get(idx);
1430
+ if (!entry) {
1431
+ entry = {
1432
+ id: tc.id,
1433
+ name: tc.function?.name,
1434
+ argBuf: "",
1435
+ emittedStart: false,
1436
+ emittedArgLength: 0
1437
+ };
1438
+ toolByIndex.set(idx, entry);
1439
+ } else {
1440
+ if (tc.id && !entry.id) entry.id = tc.id;
1441
+ if (tc.function?.name && !entry.name) entry.name = tc.function.name;
1442
+ }
1443
+ if (tc.function?.arguments) {
1444
+ entry.argBuf += tc.function.arguments;
1445
+ }
1446
+ if (!entry.emittedStart && entry.id && entry.name) {
1447
+ entry.emittedStart = true;
1448
+ yield { type: "tool_use_start", id: entry.id, name: entry.name };
1449
+ }
1450
+ if (entry.emittedStart && entry.id && entry.emittedArgLength < entry.argBuf.length) {
1451
+ const partial = entry.argBuf.slice(entry.emittedArgLength);
1452
+ entry.emittedArgLength = entry.argBuf.length;
1453
+ yield {
1454
+ type: "tool_use_input_delta",
1455
+ id: entry.id,
1456
+ partial
1457
+ };
1458
+ }
1459
+ }
1460
+ }
1461
+ if (choice?.finish_reason) {
1462
+ stopReason = normalizeOpenAI(choice.finish_reason);
1463
+ }
1464
+ const u = obj["usage"];
1465
+ if (u) {
1466
+ const hasDeepSeekCacheFields = u.prompt_cache_hit_tokens !== void 0 || u.prompt_cache_miss_tokens !== void 0;
1467
+ const cached = u.prompt_tokens_details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0;
1468
+ const promptTotal = u.prompt_tokens ?? u.input_tokens ?? (hasDeepSeekCacheFields ? (u.prompt_cache_hit_tokens ?? 0) + (u.prompt_cache_miss_tokens ?? 0) : usage.input + cached);
1469
+ usage = {
1470
+ input: u.prompt_cache_miss_tokens ?? Math.max(0, promptTotal - cached),
1471
+ output: u.completion_tokens ?? usage.output,
1472
+ cacheRead: cached || usage.cacheRead
1473
+ };
1474
+ }
1475
+ }
1476
+ if (thinkingOpen) {
1477
+ yield { type: "thinking_stop" };
1478
+ }
1479
+ for (const entry of toolByIndex.values()) {
1480
+ if (!entry.name) continue;
1481
+ if (!entry.id) entry.id = `call_${randomUUID()}`;
1482
+ if (!entry.emittedStart) {
1483
+ yield { type: "tool_use_start", id: entry.id, name: entry.name };
1484
+ }
1485
+ const input = parseToolInput(entry.argBuf);
1486
+ yield { type: "tool_use_stop", id: entry.id, input };
1487
+ }
1488
+ if (started) {
1489
+ yield { type: "message_stop", stopReason, usage };
1490
+ }
1491
+ }
1492
+
1493
+ // src/github-copilot.ts
1494
+ var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
1495
+ var COPILOT_API_VERSION = "2026-06-01";
1496
+ var COPILOT_HEADERS = {
1497
+ "User-Agent": "GitHubCopilotChat/0.35.0",
1498
+ "Editor-Version": "vscode/1.107.0",
1499
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
1500
+ "Copilot-Integration-Id": "vscode-chat"
1501
+ };
1502
+ var DEFAULT_API_BASE = "https://api.individual.githubcopilot.com";
1503
+ var REFRESH_SKEW_MS2 = 6e4;
1504
+ function copilotBaseUrlFromToken(token) {
1505
+ if (token) {
1506
+ const m = token.match(/proxy-ep=([^;]+)/);
1507
+ if (m?.[1]) return `https://${m[1].replace(/^proxy\./, "api.")}`;
1508
+ }
1509
+ return DEFAULT_API_BASE;
1510
+ }
1511
+ async function refreshCopilotToken(githubToken, signal) {
1512
+ const res = await fetch(COPILOT_TOKEN_URL, {
1513
+ headers: {
1514
+ accept: "application/json",
1515
+ authorization: `Bearer ${githubToken}`,
1516
+ ...COPILOT_HEADERS
1517
+ },
1518
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
1519
+ });
1520
+ if (!res.ok) {
1521
+ const text = await res.text().catch(() => "");
1522
+ throw new Error(`Copilot token request failed (${res.status}): ${text || res.statusText}`);
1523
+ }
1524
+ const json = await res.json();
1525
+ if (!json?.token || typeof json.expires_at !== "number") {
1526
+ throw new Error("Copilot token response missing fields");
1527
+ }
1528
+ return { token: json.token, expires: json.expires_at * 1e3 };
1529
+ }
1530
+ var GitHubCopilotProvider = class extends OpenAIProvider {
1531
+ capabilities;
1532
+ copilotToken;
1533
+ githubToken;
1534
+ expiresAt;
1535
+ apiBase;
1536
+ onRefresh;
1537
+ refreshFn;
1538
+ constructor(opts) {
1539
+ const apiBase = copilotBaseUrlFromToken(opts.credentials.copilotToken);
1540
+ super({
1541
+ // OpenAIProvider requires a non-empty apiKey; use a placeholder when the
1542
+ // Copilot token is empty (it will be minted before the first request).
1543
+ apiKey: opts.credentials.copilotToken || "pending",
1544
+ baseUrl: apiBase,
1545
+ id: opts.id ?? "github-copilot",
1546
+ fetchImpl: opts.fetchImpl,
1547
+ streamOpts: opts.streamOpts,
1548
+ capabilities: opts.capabilities
1549
+ });
1550
+ this.copilotToken = opts.credentials.copilotToken;
1551
+ this.githubToken = opts.credentials.githubToken;
1552
+ this.expiresAt = opts.credentials.expiresAt;
1553
+ this.apiBase = apiBase;
1554
+ this.onRefresh = opts.onRefresh;
1555
+ this.refreshFn = opts.refreshFn ?? refreshCopilotToken;
1556
+ this.capabilities = capabilitiesForFamily("github-copilot", { ...opts.capabilities });
1557
+ }
1558
+ async *stream(req, opts) {
1559
+ await this.ensureFreshToken(opts.signal);
1560
+ try {
1561
+ yield* super.stream(req, opts);
1562
+ } catch (err) {
1563
+ if (err instanceof ProviderError && err.status === 401 && this.githubToken) {
1564
+ await this.doRefresh(opts.signal);
1565
+ yield* super.stream(req, opts);
1566
+ return;
1567
+ }
1568
+ throw err;
1569
+ }
1570
+ }
1571
+ async ensureFreshToken(signal) {
1572
+ const stale = this.expiresAt === void 0 || Date.now() >= this.expiresAt - REFRESH_SKEW_MS2;
1573
+ if (!this.copilotToken || stale && this.githubToken) {
1574
+ await this.doRefresh(signal);
1575
+ }
1576
+ }
1577
+ async doRefresh(signal) {
1578
+ if (!this.githubToken) return;
1579
+ const t = await this.refreshFn(this.githubToken, signal);
1580
+ this.copilotToken = t.token;
1581
+ this.expiresAt = t.expires;
1582
+ this.apiBase = copilotBaseUrlFromToken(t.token);
1583
+ this.onRefresh?.({
1584
+ accessToken: t.token,
1585
+ refreshToken: this.githubToken,
1586
+ expiresAt: t.expires
1587
+ });
1588
+ }
1589
+ buildUrl(_req) {
1590
+ return `${this.apiBase.replace(/\/+$/, "")}/chat/completions`;
1591
+ }
1592
+ buildHeaders(_req) {
1593
+ return {
1594
+ "content-type": "application/json",
1595
+ accept: "text/event-stream",
1596
+ authorization: `Bearer ${this.copilotToken}`,
1597
+ "X-GitHub-Api-Version": COPILOT_API_VERSION,
1598
+ ...COPILOT_HEADERS
1599
+ };
1600
+ }
1601
+ };
1602
+ var DEFAULT_BASE4 = "https://generativelanguage.googleapis.com/v1beta";
976
1603
  var GoogleProvider = class extends WireAdapter {
977
1604
  id;
978
1605
  capabilities;
979
1606
  constructor(opts) {
980
- super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE2, opts.fetchImpl, opts.streamOpts);
1607
+ super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE4, opts.fetchImpl, opts.streamOpts);
981
1608
  this.id = opts.id ?? "google";
982
1609
  this.capabilities = capabilitiesForFamily("google", {
983
1610
  ...opts.capabilities
@@ -1022,14 +1649,17 @@ var GoogleProvider = class extends WireAdapter {
1022
1649
  }
1023
1650
  };
1024
1651
  function toolsToGemini(tools) {
1025
- return tools.map((t) => ({
1026
- name: t.name,
1027
- description: t.description,
1028
- parameters: sanitizeSchemaForGemini(t.inputSchema) ?? {
1029
- type: "object",
1030
- properties: {}
1031
- }
1032
- }));
1652
+ return tools.map((t) => {
1653
+ const compact = compactToolDefinitionForWire(t);
1654
+ return {
1655
+ name: compact.name,
1656
+ description: compact.description,
1657
+ parameters: sanitizeSchemaForGemini(compact.inputSchema) ?? {
1658
+ type: "object",
1659
+ properties: {}
1660
+ }
1661
+ };
1662
+ });
1033
1663
  }
1034
1664
  var GEMINI_ALLOWED_KEYS = /* @__PURE__ */ new Set([
1035
1665
  "type",
@@ -1192,296 +1822,356 @@ async function* parseGoogleStream(body, fallbackModel) {
1192
1822
  }
1193
1823
  }
1194
1824
 
1195
- // src/openai.ts
1825
+ // src/openai-codex.ts
1196
1826
  init_tool_input();
1197
-
1198
- // src/tool-format/to-openai.ts
1199
- function toolsToOpenAI(tools) {
1200
- return tools.map((t) => ({
1201
- type: "function",
1202
- function: {
1203
- name: t.name,
1204
- description: t.description,
1205
- parameters: t.inputSchema ?? {
1206
- type: "object",
1207
- properties: {}
1208
- }
1209
- }
1210
- }));
1827
+ var _toolCache = /* @__PURE__ */ new WeakMap();
1828
+ function toolsToResponses(tools) {
1829
+ const hit = _toolCache.get(tools);
1830
+ if (hit) return hit;
1831
+ const result = tools.map((t) => {
1832
+ const compact = compactToolDefinitionForWire(t);
1833
+ return {
1834
+ type: "function",
1835
+ name: compact.name,
1836
+ description: compact.description,
1837
+ parameters: compact.inputSchema,
1838
+ strict: false
1839
+ };
1840
+ });
1841
+ _toolCache.set(tools, result);
1842
+ return result;
1211
1843
  }
1212
- function messagesToOpenAI(system, messages, opts = {}) {
1213
- const emptyContentMode = opts.emptyToolCallContent ?? "empty_string";
1844
+ function normalizeContent2(content) {
1845
+ return typeof content === "string" ? [{ type: "text", text: content }] : content;
1846
+ }
1847
+ function imageUrl(b) {
1848
+ return b.source.type === "url" ? b.source.url ?? "" : `data:${b.source.media_type ?? "image/png"};base64,${b.source.data ?? ""}`;
1849
+ }
1850
+ function messagesToResponsesInput(messages) {
1214
1851
  const out = [];
1215
- if (system && system.length > 0) {
1216
- const sysText = system.map((b) => b.text).join("\n\n");
1217
- if (opts.systemAsMessage) {
1218
- out.push({ role: "user", content: sysText });
1219
- } else {
1220
- out.push({ role: "system", content: sysText });
1221
- }
1222
- }
1223
1852
  for (const msg of messages) {
1853
+ const blocks = normalizeContent2(msg.content);
1224
1854
  if (msg.role === "user") {
1225
- const blocks = normalizeContent(msg.content);
1226
1855
  const toolResults = blocks.filter((b) => b.type === "tool_result");
1227
- const others = blocks.filter((b) => b.type !== "tool_result");
1228
1856
  for (const r of toolResults) {
1229
- const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
1230
1857
  out.push({
1231
- role: "tool",
1232
- tool_call_id: r.tool_use_id,
1233
- content
1858
+ type: "function_call_output",
1859
+ call_id: r.tool_use_id,
1860
+ output: typeof r.content === "string" ? r.content : JSON.stringify(r.content)
1234
1861
  });
1235
1862
  }
1236
- if (others.length > 0) {
1237
- out.push({
1238
- role: "user",
1239
- content: opts.flattenContentToString ? blocksToString(others) : blocksToContentArray(others)
1240
- });
1863
+ const others = blocks.filter((b) => b.type !== "tool_result");
1864
+ if (others.length > 0) {
1865
+ const content = others.map((b) => {
1866
+ if (b.type === "text") return { type: "input_text", text: b.text };
1867
+ if (b.type === "image") {
1868
+ return { type: "input_image", detail: "auto", image_url: imageUrl(b) };
1869
+ }
1870
+ return null;
1871
+ }).filter((c) => c !== null);
1872
+ if (content.length > 0) out.push({ role: "user", content });
1241
1873
  }
1242
1874
  } else if (msg.role === "assistant") {
1243
- const blocks = normalizeContent(msg.content);
1244
1875
  const textBlocks = blocks.filter((b) => b.type === "text");
1245
1876
  const toolUses = blocks.filter((b) => b.type === "tool_use");
1246
- const thinkingBlocks = blocks.filter((b) => b.type === "thinking");
1247
1877
  const text = textBlocks.map((b) => b.text).join("");
1248
- const reasoning = thinkingBlocks.map((b) => b.thinking).filter((t) => t && t.length > 0).join("");
1249
- const toolCalls = toolUses.map((u) => ({
1250
- id: u.id,
1251
- type: "function",
1252
- function: { name: u.name, arguments: JSON.stringify(u.input) }
1253
- }));
1254
- const message = { role: "assistant" };
1255
- if (toolCalls.length > 0) {
1256
- message.tool_calls = toolCalls;
1257
- if (text) {
1258
- message.content = text;
1259
- } else {
1260
- message.content = emptyContentMode === "null" ? null : "";
1261
- }
1262
- } else {
1263
- message.content = text;
1878
+ if (text.length > 0) {
1879
+ out.push({
1880
+ type: "message",
1881
+ role: "assistant",
1882
+ content: [{ type: "output_text", text, annotations: [] }],
1883
+ status: "completed"
1884
+ });
1264
1885
  }
1265
- if (reasoning.length > 0) {
1266
- message.reasoning_content = reasoning;
1886
+ for (const u of toolUses) {
1887
+ out.push({
1888
+ type: "function_call",
1889
+ call_id: u.id,
1890
+ name: u.name,
1891
+ arguments: JSON.stringify(u.input ?? {})
1892
+ });
1267
1893
  }
1268
- out.push(message);
1269
1894
  }
1270
1895
  }
1271
1896
  return out;
1272
1897
  }
1273
- function normalizeContent(content) {
1274
- return typeof content === "string" ? [{ type: "text", text: content }] : content;
1275
- }
1276
- function blocksToString(blocks) {
1277
- return blocks.map((b) => {
1278
- if (b.type === "text") return b.text;
1279
- if (b.type === "image") return "[image]";
1280
- return "";
1281
- }).join("");
1282
- }
1283
- function blocksToContentArray(blocks) {
1284
- const hasImage = blocks.some((b) => b.type === "image");
1285
- if (!hasImage) {
1286
- return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
1898
+
1899
+ // src/openai-codex.ts
1900
+ var CLIENT_ID2 = "app_EMoamEEZ73f0CkXaXp7hrann";
1901
+ var TOKEN_URL2 = "https://auth.openai.com/oauth/token";
1902
+ var JWT_CLAIM_PATH = "https://api.openai.com/auth";
1903
+ var DEFAULT_CODEX_BASE = "https://chatgpt.com/backend-api";
1904
+ var REFRESH_SKEW_MS3 = 6e4;
1905
+ async function refreshCodexAccessToken(refreshToken, signal) {
1906
+ const res = await fetch(TOKEN_URL2, {
1907
+ method: "POST",
1908
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1909
+ body: new URLSearchParams({
1910
+ grant_type: "refresh_token",
1911
+ client_id: CLIENT_ID2,
1912
+ refresh_token: refreshToken
1913
+ }).toString(),
1914
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
1915
+ });
1916
+ if (!res.ok) {
1917
+ const text = await res.text().catch(() => "");
1918
+ throw new Error(`Codex token refresh failed (${res.status}): ${text || res.statusText}`);
1287
1919
  }
1288
- return blocks.map((b) => {
1289
- if (b.type === "text") return { type: "text", text: b.text };
1290
- if (b.type === "image") {
1291
- const url = b.source.type === "url" ? b.source.url ?? "" : `data:${b.source.media_type ?? "image/png"};base64,${b.source.data ?? ""}`;
1292
- return { type: "image_url", image_url: { url } };
1293
- }
1920
+ const json = await res.json();
1921
+ if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
1922
+ throw new Error("Codex token refresh response missing fields");
1923
+ }
1924
+ return {
1925
+ access: json.access_token,
1926
+ refresh: json.refresh_token,
1927
+ expires: Date.now() + json.expires_in * 1e3
1928
+ };
1929
+ }
1930
+ function extractAccountId(token) {
1931
+ try {
1932
+ const parts = token.split(".");
1933
+ if (parts.length !== 3) return null;
1934
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
1935
+ const auth = payload[JWT_CLAIM_PATH];
1936
+ const id = auth?.chatgpt_account_id;
1937
+ return typeof id === "string" && id.length > 0 ? id : null;
1938
+ } catch {
1294
1939
  return null;
1295
- }).filter((c) => c !== null);
1940
+ }
1296
1941
  }
1297
-
1298
- // src/openai.ts
1299
- var DEFAULT_BASE3 = "https://api.openai.com/v1";
1300
- var OpenAIProvider = class extends WireAdapter {
1942
+ var OpenAICodexProvider = class extends WireAdapter {
1301
1943
  id;
1302
1944
  capabilities;
1303
- opts;
1945
+ access;
1946
+ refresh;
1947
+ expiresAt;
1948
+ accountId;
1949
+ onRefresh;
1950
+ refreshFn;
1951
+ reasoningEffort;
1304
1952
  constructor(opts) {
1305
- super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE3, opts.fetchImpl, opts.streamOpts);
1306
- this.opts = opts;
1307
- this.id = opts.id ?? "openai";
1308
- this.capabilities = capabilitiesForFamily("openai", {
1309
- parallelTools: !opts.quirks?.parallelToolsDisabled,
1310
- systemPrompt: !opts.quirks?.systemAsMessage,
1311
- ...opts.capabilities
1953
+ super(
1954
+ opts.credentials.accessToken,
1955
+ opts.baseUrl ?? DEFAULT_CODEX_BASE,
1956
+ opts.fetchImpl,
1957
+ opts.streamOpts
1958
+ );
1959
+ this.id = opts.id ?? "openai-codex";
1960
+ this.access = opts.credentials.accessToken;
1961
+ this.refresh = opts.credentials.refreshToken;
1962
+ this.expiresAt = opts.credentials.expiresAt;
1963
+ this.accountId = opts.credentials.accountId ?? extractAccountId(this.access) ?? void 0;
1964
+ this.onRefresh = opts.onRefresh;
1965
+ this.refreshFn = opts.refreshFn ?? refreshCodexAccessToken;
1966
+ this.reasoningEffort = opts.reasoningEffort ?? "medium";
1967
+ this.capabilities = capabilitiesForFamily("openai-codex", { ...opts.capabilities });
1968
+ }
1969
+ async *stream(req, opts) {
1970
+ await this.ensureFreshToken(opts.signal);
1971
+ try {
1972
+ yield* super.stream(req, opts);
1973
+ } catch (err) {
1974
+ if (err instanceof ProviderError && err.status === 401 && this.refresh) {
1975
+ await this.doRefresh(opts.signal);
1976
+ yield* super.stream(req, opts);
1977
+ return;
1978
+ }
1979
+ throw err;
1980
+ }
1981
+ }
1982
+ async ensureFreshToken(signal) {
1983
+ if (!this.refresh) return;
1984
+ if (this.expiresAt !== void 0 && Date.now() < this.expiresAt - REFRESH_SKEW_MS3) return;
1985
+ await this.doRefresh(signal);
1986
+ }
1987
+ async doRefresh(signal) {
1988
+ if (!this.refresh) return;
1989
+ const t = await this.refreshFn(this.refresh, signal);
1990
+ this.access = t.access;
1991
+ this.refresh = t.refresh;
1992
+ this.expiresAt = t.expires;
1993
+ this.accountId = extractAccountId(t.access) ?? this.accountId;
1994
+ this.onRefresh?.({
1995
+ accessToken: t.access,
1996
+ refreshToken: t.refresh,
1997
+ expiresAt: t.expires,
1998
+ accountId: this.accountId
1312
1999
  });
1313
2000
  }
1314
2001
  buildUrl(_req) {
1315
- const base = this.baseUrl.replace(/\/+$/, "");
1316
- if (/\/chat\/completions$/.test(base)) return base;
1317
- if (/\/v\d+(\/[a-z0-9_-]+)*$/i.test(base)) return `${base}/chat/completions`;
1318
- return `${base}/v1/chat/completions`;
2002
+ return resolveCodexUrl(this.baseUrl);
1319
2003
  }
1320
- buildHeaders(req) {
2004
+ buildHeaders(_req) {
1321
2005
  const headers = {
1322
- ...super.buildHeaders(req),
1323
- authorization: `Bearer ${this.apiKey}`
2006
+ ...super.buildHeaders(_req),
2007
+ authorization: `Bearer ${this.access}`,
2008
+ originator: "wrongstack",
2009
+ "OpenAI-Beta": "responses=experimental"
1324
2010
  };
1325
- if (this.opts.organization) {
1326
- headers["openai-organization"] = this.opts.organization;
1327
- }
2011
+ if (this.accountId) headers["chatgpt-account-id"] = this.accountId;
1328
2012
  return headers;
1329
2013
  }
1330
- /**
1331
- * The request field used to cap output length. Real OpenAI deprecated
1332
- * `max_tokens` and the newer model families (gpt-4o, o1/o3/o4) 400 on it —
1333
- * they require `max_completion_tokens`. OpenAI-compatible endpoints that
1334
- * still only accept `max_tokens` override this. See issue #10.
1335
- */
1336
- tokenLimitParam() {
1337
- return "max_completion_tokens";
1338
- }
1339
2014
  buildBody(req) {
2015
+ const instructions = req.system && req.system.length > 0 ? req.system.map((b) => b.text).join("\n\n") : "You are a helpful assistant.";
1340
2016
  const body = {
1341
2017
  model: req.model,
1342
- messages: messagesToOpenAI(this.stripCacheControl(req), req.messages, {
1343
- ...this.opts.quirks
1344
- }),
1345
- [this.tokenLimitParam()]: req.maxTokens,
2018
+ // The ChatGPT Codex backend rejects `store: true` ("Store must be set to
2019
+ // false"). We send the full conversation as `input` each turn.
2020
+ store: false,
1346
2021
  stream: true,
1347
- stream_options: { include_usage: true }
2022
+ instructions,
2023
+ input: messagesToResponsesInput(req.messages),
2024
+ include: ["reasoning.encrypted_content"],
2025
+ parallel_tool_calls: true
1348
2026
  };
1349
2027
  if (req.tools && req.tools.length > 0) {
1350
- body["tools"] = toolsToOpenAI(req.tools);
1351
- if (req.toolChoice) {
1352
- if (typeof req.toolChoice === "string") {
1353
- body["tool_choice"] = req.toolChoice === "required" ? "required" : req.toolChoice;
1354
- } else {
1355
- body["tool_choice"] = {
1356
- type: "function",
1357
- function: { name: req.toolChoice.name }
1358
- };
1359
- }
1360
- }
2028
+ body["tools"] = toolsToResponses(req.tools);
2029
+ body["tool_choice"] = mapToolChoice(req.toolChoice);
1361
2030
  }
1362
2031
  if (req.temperature !== void 0) body["temperature"] = req.temperature;
1363
2032
  if (req.topP !== void 0) body["top_p"] = req.topP;
1364
- if (req.stopSequences) body["stop"] = req.stopSequences;
2033
+ if (this.reasoningEffort !== "none") {
2034
+ body["reasoning"] = { effort: this.reasoningEffort, summary: "auto" };
2035
+ }
1365
2036
  return body;
1366
2037
  }
1367
2038
  parseStream(body, fallbackModel) {
1368
- return parseOpenAIStream(body, fallbackModel);
2039
+ return parseCodexResponsesStream(body, fallbackModel);
1369
2040
  }
1370
2041
  translateError(status, text) {
1371
2042
  return parseProviderHttpError(this.id, status, text);
1372
2043
  }
1373
- stripCacheControl(req) {
1374
- if (!req.system) return void 0;
1375
- return req.system.map((b) => {
1376
- const { cache_control: _cc, ...rest } = b;
1377
- return rest;
1378
- });
1379
- }
1380
2044
  };
1381
- async function* parseOpenAIStream(body, fallbackModel) {
2045
+ function resolveCodexUrl(baseUrl) {
2046
+ const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE;
2047
+ const normalized = raw.replace(/\/+$/, "");
2048
+ if (normalized.endsWith("/codex/responses")) return normalized;
2049
+ if (normalized.endsWith("/codex")) return `${normalized}/responses`;
2050
+ return `${normalized}/codex/responses`;
2051
+ }
2052
+ function mapToolChoice(choice) {
2053
+ if (choice === void 0) return "auto";
2054
+ if (choice === "auto" || choice === "required" || choice === "none") return choice;
2055
+ return { type: "function", name: choice.name };
2056
+ }
2057
+ async function* parseCodexResponsesStream(body, fallbackModel) {
1382
2058
  let model = fallbackModel;
2059
+ let started = false;
1383
2060
  let usage = { input: 0, output: 0 };
1384
2061
  let stopReason = "end_turn";
1385
- let started = false;
1386
- let thinkingOpen = false;
1387
- const toolByIndex = /* @__PURE__ */ new Map();
2062
+ let sawToolUse = false;
2063
+ let toolCallId;
2064
+ let toolArgBuf = "";
2065
+ const ensureStart = () => {
2066
+ if (started) return void 0;
2067
+ started = true;
2068
+ return { type: "message_start", model };
2069
+ };
1388
2070
  for await (const msg of parseSSE(body)) {
1389
2071
  if (!msg.data || msg.data === "[DONE]") continue;
1390
2072
  const parsed = safeParse(msg.data);
1391
2073
  if (!parsed.ok || !parsed.value) continue;
1392
- const obj = parsed.value;
1393
- if (typeof obj["model"] === "string") model = obj["model"];
1394
- if (!started) {
1395
- started = true;
1396
- yield { type: "message_start", model };
1397
- }
1398
- const choices = obj["choices"];
1399
- const choice = choices?.[0];
1400
- const reasoningDelta = typeof choice?.delta?.reasoning_content === "string" ? choice.delta.reasoning_content : typeof choice?.delta?.reasoning === "string" ? choice.delta.reasoning : void 0;
1401
- if (reasoningDelta && reasoningDelta.length > 0) {
1402
- if (!thinkingOpen) {
1403
- thinkingOpen = true;
1404
- yield { type: "thinking_start" };
2074
+ const evt = parsed.value;
2075
+ const type = typeof evt["type"] === "string" ? evt["type"] : "";
2076
+ switch (type) {
2077
+ case "response.created":
2078
+ case "response.in_progress": {
2079
+ const resp = evt["response"];
2080
+ if (typeof resp?.model === "string") model = resp.model;
2081
+ const s = ensureStart();
2082
+ if (s) yield s;
2083
+ break;
1405
2084
  }
1406
- yield { type: "thinking_delta", text: reasoningDelta };
1407
- }
1408
- if (choice?.delta?.content) {
1409
- if (thinkingOpen) {
1410
- thinkingOpen = false;
1411
- yield { type: "thinking_stop" };
2085
+ case "response.output_item.added": {
2086
+ const s = ensureStart();
2087
+ if (s) yield s;
2088
+ const item = evt["item"];
2089
+ if (!item) break;
2090
+ if (item.type === "reasoning") {
2091
+ yield { type: "thinking_start" };
2092
+ } else if (item.type === "function_call") {
2093
+ toolCallId = item.call_id ?? item.id ?? `call_${Math.random().toString(36).slice(2)}`;
2094
+ toolArgBuf = item.arguments ?? "";
2095
+ sawToolUse = true;
2096
+ yield { type: "tool_use_start", id: toolCallId, name: item.name ?? "unknown" };
2097
+ if (toolArgBuf.length > 0) {
2098
+ yield { type: "tool_use_input_delta", id: toolCallId, partial: toolArgBuf };
2099
+ }
2100
+ }
2101
+ break;
1412
2102
  }
1413
- yield { type: "text_delta", text: choice.delta.content };
1414
- }
1415
- if (choice?.delta?.tool_calls) {
1416
- if (thinkingOpen) {
1417
- thinkingOpen = false;
1418
- yield { type: "thinking_stop" };
2103
+ case "response.output_text.delta":
2104
+ case "response.refusal.delta": {
2105
+ const delta = typeof evt["delta"] === "string" ? evt["delta"] : "";
2106
+ if (delta) yield { type: "text_delta", text: delta };
2107
+ break;
1419
2108
  }
1420
- for (const tc of choice.delta.tool_calls) {
1421
- const idx = tc.index ?? 0;
1422
- let entry = toolByIndex.get(idx);
1423
- if (!entry) {
1424
- entry = {
1425
- id: tc.id,
1426
- name: tc.function?.name,
1427
- argBuf: "",
1428
- emittedStart: false,
1429
- emittedArgLength: 0
1430
- };
1431
- toolByIndex.set(idx, entry);
1432
- } else {
1433
- if (tc.id && !entry.id) entry.id = tc.id;
1434
- if (tc.function?.name && !entry.name) entry.name = tc.function.name;
1435
- }
1436
- if (tc.function?.arguments) {
1437
- entry.argBuf += tc.function.arguments;
1438
- }
1439
- if (!entry.emittedStart && entry.id && entry.name) {
1440
- entry.emittedStart = true;
1441
- yield { type: "tool_use_start", id: entry.id, name: entry.name };
2109
+ case "response.reasoning_text.delta":
2110
+ case "response.reasoning_summary_text.delta": {
2111
+ const delta = typeof evt["delta"] === "string" ? evt["delta"] : "";
2112
+ if (delta) yield { type: "thinking_delta", text: delta };
2113
+ break;
2114
+ }
2115
+ case "response.function_call_arguments.delta": {
2116
+ const delta = typeof evt["delta"] === "string" ? evt["delta"] : "";
2117
+ if (toolCallId && delta) {
2118
+ toolArgBuf += delta;
2119
+ yield { type: "tool_use_input_delta", id: toolCallId, partial: delta };
1442
2120
  }
1443
- if (entry.emittedStart && entry.id && entry.emittedArgLength < entry.argBuf.length) {
1444
- const partial = entry.argBuf.slice(entry.emittedArgLength);
1445
- entry.emittedArgLength = entry.argBuf.length;
1446
- yield {
1447
- type: "tool_use_input_delta",
1448
- id: entry.id,
1449
- partial
1450
- };
2121
+ break;
2122
+ }
2123
+ case "response.function_call_arguments.done": {
2124
+ const args = typeof evt["arguments"] === "string" ? evt["arguments"] : void 0;
2125
+ if (args !== void 0) toolArgBuf = args;
2126
+ break;
2127
+ }
2128
+ case "response.output_item.done": {
2129
+ const item = evt["item"];
2130
+ if (!item) break;
2131
+ if (item.type === "reasoning") {
2132
+ yield { type: "thinking_stop" };
2133
+ } else if (item.type === "function_call") {
2134
+ const id = item.call_id ?? toolCallId ?? `call_${Math.random().toString(36).slice(2)}`;
2135
+ const raw = item.arguments && item.arguments.length > 0 ? item.arguments : toolArgBuf;
2136
+ yield { type: "tool_use_stop", id, input: parseToolInput(raw || "{}") };
2137
+ toolCallId = void 0;
2138
+ toolArgBuf = "";
1451
2139
  }
2140
+ break;
2141
+ }
2142
+ case "response.completed":
2143
+ case "response.incomplete": {
2144
+ const resp = evt["response"];
2145
+ if (resp?.usage) usage = normalizeUsage(resp.usage);
2146
+ stopReason = mapResponsesStatus(resp?.status, sawToolUse);
2147
+ break;
2148
+ }
2149
+ case "error":
2150
+ case "response.failed": {
2151
+ const message = evt["message"] ?? evt["response"]?.error?.message ?? "Codex response failed";
2152
+ throw new ProviderError(message, 502, true, "openai-codex", {
2153
+ body: { message }
2154
+ });
1452
2155
  }
1453
2156
  }
1454
- if (choice?.finish_reason) {
1455
- stopReason = normalizeOpenAI(choice.finish_reason);
1456
- }
1457
- const u = obj["usage"];
1458
- if (u) {
1459
- const hasDeepSeekCacheFields = u.prompt_cache_hit_tokens !== void 0 || u.prompt_cache_miss_tokens !== void 0;
1460
- const cached = u.prompt_tokens_details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0;
1461
- const promptTotal = u.prompt_tokens ?? u.input_tokens ?? (hasDeepSeekCacheFields ? (u.prompt_cache_hit_tokens ?? 0) + (u.prompt_cache_miss_tokens ?? 0) : usage.input + cached);
1462
- usage = {
1463
- input: u.prompt_cache_miss_tokens ?? Math.max(0, promptTotal - cached),
1464
- output: u.completion_tokens ?? usage.output,
1465
- cacheRead: cached || usage.cacheRead
1466
- };
1467
- }
1468
- }
1469
- if (thinkingOpen) {
1470
- yield { type: "thinking_stop" };
1471
- }
1472
- for (const entry of toolByIndex.values()) {
1473
- if (!entry.name) continue;
1474
- if (!entry.id) entry.id = `call_${randomUUID()}`;
1475
- if (!entry.emittedStart) {
1476
- yield { type: "tool_use_start", id: entry.id, name: entry.name };
1477
- }
1478
- const input = parseToolInput(entry.argBuf);
1479
- yield { type: "tool_use_stop", id: entry.id, input };
1480
2157
  }
1481
2158
  if (started) {
1482
2159
  yield { type: "message_stop", stopReason, usage };
1483
2160
  }
1484
2161
  }
2162
+ function normalizeUsage(u) {
2163
+ const cached = u.input_tokens_details?.cached_tokens ?? 0;
2164
+ const total = u.input_tokens ?? 0;
2165
+ return {
2166
+ input: Math.max(0, total - cached),
2167
+ output: u.output_tokens ?? 0,
2168
+ cacheRead: cached || void 0
2169
+ };
2170
+ }
2171
+ function mapResponsesStatus(status, sawToolUse) {
2172
+ if (status === "incomplete") return "max_tokens";
2173
+ return sawToolUse ? "tool_use" : "end_turn";
2174
+ }
1485
2175
 
1486
2176
  // src/openai-compatible.ts
1487
2177
  var VALID_QUIRK_KEYS = /* @__PURE__ */ new Set([
@@ -2148,14 +2838,17 @@ function buildGenConfig(req) {
2148
2838
  return cfg;
2149
2839
  }
2150
2840
  function toolsToGemini2(tools) {
2151
- return tools.map((t) => ({
2152
- name: t.name,
2153
- description: t.description,
2154
- parameters: sanitizeSchemaForGemini2(t.inputSchema) ?? {
2155
- type: "object",
2156
- properties: {}
2157
- }
2158
- }));
2841
+ return tools.map((t) => {
2842
+ const compact = compactToolDefinitionForWire(t);
2843
+ return {
2844
+ name: compact.name,
2845
+ description: compact.description,
2846
+ parameters: sanitizeSchemaForGemini2(compact.inputSchema) ?? {
2847
+ type: "object",
2848
+ properties: {}
2849
+ }
2850
+ };
2851
+ });
2159
2852
  }
2160
2853
  var GEMINI_ALLOWED_KEYS2 = /* @__PURE__ */ new Set([
2161
2854
  "type",
@@ -2409,6 +3102,11 @@ function parseToolArguments(raw, toolName, toolCallId, opts) {
2409
3102
  }
2410
3103
 
2411
3104
  // src/index.ts
3105
+ var _oauthPersist;
3106
+ function setOAuthTokenPersister(fn) {
3107
+ _oauthPersist = fn;
3108
+ }
3109
+ var setCodexTokenPersister = setOAuthTokenPersister;
2412
3110
  async function buildProviderFactoriesFromRegistry(opts) {
2413
3111
  const providers = await opts.registry.listProviders();
2414
3112
  const factories = [];
@@ -2442,10 +3140,24 @@ async function buildProviderFactoriesFromRegistry(opts) {
2442
3140
  }
2443
3141
  return factories;
2444
3142
  }
3143
+ function resolveActiveKey(cfg) {
3144
+ if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3145
+ const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
3146
+ return (active ?? cfg.apiKeys[0])?.apiKey;
3147
+ }
3148
+ return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
3149
+ }
3150
+ function resolveActiveKeyEntry(cfg) {
3151
+ if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3152
+ const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
3153
+ return active ?? cfg.apiKeys[0];
3154
+ }
3155
+ return void 0;
3156
+ }
2445
3157
  function makeProvider(p, cfg) {
2446
3158
  const family = cfg.family ?? p.family;
2447
3159
  const envVars = cfg.envVars && cfg.envVars.length > 0 ? cfg.envVars : p.envVars;
2448
- const apiKey = cfg.apiKey ?? readFromEnv(envVars);
3160
+ const apiKey = resolveActiveKey(cfg) ?? readFromEnv(envVars);
2449
3161
  if (!apiKey && family !== "unsupported") {
2450
3162
  throw new Error(
2451
3163
  `Provider "${p.id}" requires an API key. Set ${envVars.join(" or ") || "apiKey in config"} or run \`wstack auth ${p.id}\`.`
@@ -2480,6 +3192,48 @@ function makeProvider(p, cfg) {
2480
3192
  headers: cfg.headers,
2481
3193
  quirks: validateQuirks(p.id, cfg.quirks)
2482
3194
  });
3195
+ case "openai-codex": {
3196
+ const entry = resolveActiveKeyEntry(cfg);
3197
+ const parsedExpiry = entry?.expiresAt ? Date.parse(entry.expiresAt) : Number.NaN;
3198
+ return new OpenAICodexProvider({
3199
+ id: p.id,
3200
+ baseUrl,
3201
+ credentials: {
3202
+ accessToken: expectDefined(apiKey),
3203
+ refreshToken: entry?.refreshToken,
3204
+ expiresAt: Number.isFinite(parsedExpiry) ? parsedExpiry : void 0,
3205
+ accountId: entry?.accountId
3206
+ },
3207
+ onRefresh: (creds) => _oauthPersist?.(p.id, creds)
3208
+ });
3209
+ }
3210
+ case "anthropic-oauth": {
3211
+ const entry = resolveActiveKeyEntry(cfg);
3212
+ const parsedExpiry = entry?.expiresAt ? Date.parse(entry.expiresAt) : Number.NaN;
3213
+ return new AnthropicOAuthProvider({
3214
+ id: p.id,
3215
+ baseUrl,
3216
+ credentials: {
3217
+ accessToken: expectDefined(apiKey),
3218
+ refreshToken: entry?.refreshToken,
3219
+ expiresAt: Number.isFinite(parsedExpiry) ? parsedExpiry : void 0
3220
+ },
3221
+ onRefresh: (creds) => _oauthPersist?.(p.id, creds)
3222
+ });
3223
+ }
3224
+ case "github-copilot": {
3225
+ const entry = resolveActiveKeyEntry(cfg);
3226
+ const parsedExpiry = entry?.expiresAt ? Date.parse(entry.expiresAt) : Number.NaN;
3227
+ return new GitHubCopilotProvider({
3228
+ id: p.id,
3229
+ credentials: {
3230
+ copilotToken: resolveActiveKey(cfg) ?? "",
3231
+ githubToken: entry?.refreshToken,
3232
+ expiresAt: Number.isFinite(parsedExpiry) ? parsedExpiry : void 0
3233
+ },
3234
+ onRefresh: (creds) => _oauthPersist?.(p.id, creds)
3235
+ });
3236
+ }
2483
3237
  case "google":
2484
3238
  return new GoogleProvider({ id: p.id, apiKey: expectDefined(apiKey), baseUrl });
2485
3239
  default:
@@ -2510,7 +3264,8 @@ function readFromEnv(vars) {
2510
3264
  return void 0;
2511
3265
  }
2512
3266
  function requireKey(cfg) {
2513
- if (cfg.apiKey) return cfg.apiKey;
3267
+ const key = resolveActiveKey(cfg);
3268
+ if (key) return key;
2514
3269
  throw new Error("Provider config requires apiKey (or set the corresponding env var).");
2515
3270
  }
2516
3271
  function validateQuirks(providerId, quirks) {
@@ -2523,6 +3278,6 @@ function validateQuirks(providerId, quirks) {
2523
3278
  });
2524
3279
  }
2525
3280
 
2526
- export { AnthropicProvider, CAPABILITIES_BY_FAMILY, GoogleProvider, OpenAICompatibleProvider, OpenAIProvider, WireAdapter, WireFormatProvider, anthropicWireFormat, buildProviderFactoriesFromRegistry, capabilitiesFor, capabilitiesForFamily, contentFromAnthropic, contentFromOpenAI, createWireFormatFactory, defaultDebugStreamCallback, defineWireFormat, googleWireFormat, isDebugStreamEnabled, makeProviderFromConfig, messagesToOpenAI, mistralWireFormat, normalizeAnthropic, normalizeOpenAI, openaiWireFormat, parseProviderHttpError, pushDebugChunkStats, setDebugStreamCallback, setDebugStreamEnabled, toolsToAnthropic, toolsToOpenAI };
3281
+ export { AnthropicOAuthProvider, AnthropicProvider, CAPABILITIES_BY_FAMILY, CLAUDE_CODE_SYSTEM_PROMPT, GitHubCopilotProvider, GoogleProvider, OpenAICodexProvider, OpenAICompatibleProvider, OpenAIProvider, WireAdapter, WireFormatProvider, anthropicWireFormat, buildProviderFactoriesFromRegistry, capabilitiesFor, capabilitiesForFamily, contentFromAnthropic, contentFromOpenAI, copilotBaseUrlFromToken, createWireFormatFactory, defaultDebugStreamCallback, defineWireFormat, extractAccountId, googleWireFormat, isDebugStreamEnabled, makeProviderFromConfig, messagesToOpenAI, mistralWireFormat, normalizeAnthropic, normalizeOpenAI, openaiWireFormat, parseProviderHttpError, pushDebugChunkStats, refreshAnthropicOAuthToken, refreshCodexAccessToken, refreshCopilotToken, resolveCodexUrl, setCodexTokenPersister, setDebugStreamCallback, setDebugStreamEnabled, setOAuthTokenPersister, toolsToAnthropic, toolsToOpenAI };
2527
3282
  //# sourceMappingURL=index.js.map
2528
3283
  //# sourceMappingURL=index.js.map