@wrongstack/providers 0.265.1 → 0.268.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.d.ts +227 -4
- package/dist/index.js +1060 -276
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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';
|
|
@@ -271,6 +271,13 @@ var init_aggregate = __esm({
|
|
|
271
271
|
|
|
272
272
|
// src/anthropic.ts
|
|
273
273
|
init_tool_input();
|
|
274
|
+
|
|
275
|
+
// src/object-utils.ts
|
|
276
|
+
function isPlainObject(value) {
|
|
277
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/error-parse.ts
|
|
274
281
|
function parseProviderHttpError(providerId, status, rawText) {
|
|
275
282
|
const body = parseBody(rawText);
|
|
276
283
|
const retryable = isRetryable(status, body.type);
|
|
@@ -288,9 +295,9 @@ function parseBody(rawText) {
|
|
|
288
295
|
} catch {
|
|
289
296
|
return body;
|
|
290
297
|
}
|
|
291
|
-
if (!
|
|
298
|
+
if (!isPlainObject(parsed)) return body;
|
|
292
299
|
const errField = parsed["error"];
|
|
293
|
-
if (
|
|
300
|
+
if (isPlainObject(errField)) {
|
|
294
301
|
const t = stringOf(errField["type"]) ?? stringOf(errField["status"]);
|
|
295
302
|
const m = stringOf(errField["message"]);
|
|
296
303
|
if (t) body.type = t;
|
|
@@ -317,9 +324,6 @@ function isRetryable(status, type) {
|
|
|
317
324
|
if (type === "overloaded_error" || type === "rate_limit_error") return true;
|
|
318
325
|
return false;
|
|
319
326
|
}
|
|
320
|
-
function isRecord(v) {
|
|
321
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
322
|
-
}
|
|
323
327
|
function stringOf(v) {
|
|
324
328
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
325
329
|
}
|
|
@@ -362,6 +366,45 @@ var CAPABILITIES_BY_FAMILY = {
|
|
|
362
366
|
maxContext: 0,
|
|
363
367
|
cacheControl: "none"
|
|
364
368
|
},
|
|
369
|
+
// Claude Pro/Max via OAuth (Sign in with Claude). Same wire as anthropic.
|
|
370
|
+
"anthropic-oauth": {
|
|
371
|
+
tools: true,
|
|
372
|
+
parallelTools: true,
|
|
373
|
+
vision: true,
|
|
374
|
+
streaming: true,
|
|
375
|
+
promptCache: true,
|
|
376
|
+
systemPrompt: true,
|
|
377
|
+
jsonMode: false,
|
|
378
|
+
reasoning: false,
|
|
379
|
+
maxContext: 2e5,
|
|
380
|
+
cacheControl: "native"
|
|
381
|
+
},
|
|
382
|
+
// GitHub Copilot subscription (OpenAI chat/completions-compatible wire).
|
|
383
|
+
"github-copilot": {
|
|
384
|
+
tools: true,
|
|
385
|
+
parallelTools: true,
|
|
386
|
+
vision: true,
|
|
387
|
+
streaming: true,
|
|
388
|
+
promptCache: false,
|
|
389
|
+
systemPrompt: true,
|
|
390
|
+
jsonMode: true,
|
|
391
|
+
reasoning: false,
|
|
392
|
+
maxContext: 128e3,
|
|
393
|
+
cacheControl: "auto"
|
|
394
|
+
},
|
|
395
|
+
// ChatGPT-backend Responses API (Sign in with ChatGPT / Codex subscription).
|
|
396
|
+
"openai-codex": {
|
|
397
|
+
tools: true,
|
|
398
|
+
parallelTools: true,
|
|
399
|
+
vision: true,
|
|
400
|
+
streaming: true,
|
|
401
|
+
promptCache: true,
|
|
402
|
+
systemPrompt: true,
|
|
403
|
+
jsonMode: false,
|
|
404
|
+
reasoning: true,
|
|
405
|
+
maxContext: 272e3,
|
|
406
|
+
cacheControl: "auto"
|
|
407
|
+
},
|
|
365
408
|
google: {
|
|
366
409
|
tools: true,
|
|
367
410
|
parallelTools: true,
|
|
@@ -535,20 +578,18 @@ function normalizeGemini(stop) {
|
|
|
535
578
|
return "end_turn";
|
|
536
579
|
}
|
|
537
580
|
}
|
|
538
|
-
|
|
539
|
-
// src/tool-format/to-anthropic.ts
|
|
540
581
|
var _cache = /* @__PURE__ */ new WeakMap();
|
|
541
582
|
function toolsToAnthropic(tools) {
|
|
542
583
|
const hit = _cache.get(tools);
|
|
543
584
|
if (hit) return hit;
|
|
544
|
-
const result = tools.map((t) =>
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
})
|
|
585
|
+
const result = tools.map((t) => {
|
|
586
|
+
const compact = compactToolDefinitionForWire(t);
|
|
587
|
+
return {
|
|
588
|
+
name: compact.name,
|
|
589
|
+
description: compact.description,
|
|
590
|
+
input_schema: compact.inputSchema
|
|
591
|
+
};
|
|
592
|
+
});
|
|
552
593
|
_cache.set(tools, result);
|
|
553
594
|
return result;
|
|
554
595
|
}
|
|
@@ -682,7 +723,7 @@ var WireAdapter = class {
|
|
|
682
723
|
if (this.streamHangTimeoutMs > 0) {
|
|
683
724
|
sseBody = this.wrapWithHangDetection(sseBody, req.model);
|
|
684
725
|
}
|
|
685
|
-
yield* this.parseStream(sseBody, req.model);
|
|
726
|
+
yield* this.parseStream(sseBody, req.model, req);
|
|
686
727
|
}
|
|
687
728
|
/**
|
|
688
729
|
* Wrap a readable stream body to log a compact status line per incoming
|
|
@@ -854,7 +895,12 @@ var AnthropicProvider = class extends WireAdapter {
|
|
|
854
895
|
messages: req.messages.map((m) => this.normalizeMessage(m)),
|
|
855
896
|
stream: true
|
|
856
897
|
};
|
|
857
|
-
if (req.system && req.system.length > 0)
|
|
898
|
+
if (req.system && req.system.length > 0) {
|
|
899
|
+
const systemBlocks = req.system;
|
|
900
|
+
body["system"] = systemBlocks.map(
|
|
901
|
+
(b, index) => req.cache?.ttl && index === systemBlocks.length - 1 ? { ...b, cache_control: { type: "ephemeral", ttl: req.cache.ttl } } : b
|
|
902
|
+
);
|
|
903
|
+
}
|
|
858
904
|
if (req.tools && req.tools.length > 0) body["tools"] = toolsToAnthropic(req.tools);
|
|
859
905
|
if (req.temperature !== void 0) body["temperature"] = req.temperature;
|
|
860
906
|
if (req.topP !== void 0) body["top_p"] = req.topP;
|
|
@@ -862,20 +908,42 @@ var AnthropicProvider = class extends WireAdapter {
|
|
|
862
908
|
if (req.toolChoice) body["tool_choice"] = req.toolChoice;
|
|
863
909
|
return body;
|
|
864
910
|
}
|
|
865
|
-
parseStream(body, fallbackModel) {
|
|
866
|
-
return parseAnthropicStream(body, fallbackModel);
|
|
911
|
+
parseStream(body, fallbackModel, req) {
|
|
912
|
+
return parseAnthropicStream(body, fallbackModel, req.cache?.ttl);
|
|
867
913
|
}
|
|
868
914
|
translateError(status, text) {
|
|
869
915
|
return parseProviderHttpError(this.id, status, text);
|
|
870
916
|
}
|
|
871
917
|
normalizeMessage(m) {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
};
|
|
918
|
+
const role = m.role === "system" ? "user" : m.role;
|
|
919
|
+
if (typeof m.content === "string") return { role, content: m.content };
|
|
920
|
+
return { role, content: m.content.map((b) => sanitizeAnthropicBlock(b)) };
|
|
876
921
|
}
|
|
877
922
|
};
|
|
878
|
-
|
|
923
|
+
function sanitizeAnthropicBlock(b) {
|
|
924
|
+
switch (b.type) {
|
|
925
|
+
case "text":
|
|
926
|
+
return b.cache_control ? { type: "text", text: b.text, cache_control: b.cache_control } : { type: "text", text: b.text };
|
|
927
|
+
case "tool_use":
|
|
928
|
+
return { type: "tool_use", id: b.id, name: b.name, input: b.input };
|
|
929
|
+
case "tool_result": {
|
|
930
|
+
const out = {
|
|
931
|
+
type: "tool_result",
|
|
932
|
+
tool_use_id: b.tool_use_id,
|
|
933
|
+
content: b.content
|
|
934
|
+
};
|
|
935
|
+
if (b.is_error) out["is_error"] = true;
|
|
936
|
+
return out;
|
|
937
|
+
}
|
|
938
|
+
case "thinking":
|
|
939
|
+
return b.signature ? { type: "thinking", thinking: b.thinking, signature: b.signature } : { type: "thinking", thinking: b.thinking };
|
|
940
|
+
case "image":
|
|
941
|
+
return { type: "image", source: b.source };
|
|
942
|
+
default:
|
|
943
|
+
return b;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
async function* parseAnthropicStream(body, fallbackModel, cacheTtl) {
|
|
879
947
|
const blocks = /* @__PURE__ */ new Map();
|
|
880
948
|
let model = fallbackModel;
|
|
881
949
|
let usage = { input: 0, output: 0 };
|
|
@@ -892,11 +960,14 @@ async function* parseAnthropicStream(body, fallbackModel) {
|
|
|
892
960
|
case "message_start": {
|
|
893
961
|
const message = ev["message"];
|
|
894
962
|
if (message?.model) model = message.model;
|
|
963
|
+
const cacheWrite = message?.usage?.cache_creation_input_tokens;
|
|
895
964
|
usage = {
|
|
896
965
|
input: message?.usage?.input_tokens ?? 0,
|
|
897
966
|
output: 0,
|
|
898
967
|
cacheRead: message?.usage?.cache_read_input_tokens,
|
|
899
|
-
cacheWrite
|
|
968
|
+
cacheWrite,
|
|
969
|
+
cacheWrite5m: cacheTtl === "1h" ? void 0 : cacheWrite,
|
|
970
|
+
cacheWrite1h: cacheTtl === "1h" ? cacheWrite : void 0
|
|
900
971
|
};
|
|
901
972
|
if (!started) {
|
|
902
973
|
started = true;
|
|
@@ -977,12 +1048,575 @@ async function* parseAnthropicStream(body, fallbackModel) {
|
|
|
977
1048
|
yield { type: "message_stop", stopReason, usage };
|
|
978
1049
|
}
|
|
979
1050
|
}
|
|
980
|
-
var
|
|
1051
|
+
var CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
1052
|
+
var TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
1053
|
+
var DEFAULT_BASE2 = "https://api.anthropic.com";
|
|
1054
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
1055
|
+
var OAUTH_BETA = "claude-code-20250219,oauth-2025-04-20";
|
|
1056
|
+
var REFRESH_SKEW_MS = 6e4;
|
|
1057
|
+
var CLAUDE_CODE_VERSION = "2.1.75";
|
|
1058
|
+
var CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
1059
|
+
var CLAUDE_CODE_TOOLS = [
|
|
1060
|
+
"Read",
|
|
1061
|
+
"Write",
|
|
1062
|
+
"Edit",
|
|
1063
|
+
"Bash",
|
|
1064
|
+
"Grep",
|
|
1065
|
+
"Glob",
|
|
1066
|
+
"AskUserQuestion",
|
|
1067
|
+
"EnterPlanMode",
|
|
1068
|
+
"ExitPlanMode",
|
|
1069
|
+
"KillShell",
|
|
1070
|
+
"NotebookEdit",
|
|
1071
|
+
"Skill",
|
|
1072
|
+
"Task",
|
|
1073
|
+
"TaskOutput",
|
|
1074
|
+
"TodoWrite",
|
|
1075
|
+
"WebFetch",
|
|
1076
|
+
"WebSearch"
|
|
1077
|
+
];
|
|
1078
|
+
var CC_TOOL_BY_LOWER = new Map(CLAUDE_CODE_TOOLS.map((t) => [t.toLowerCase(), t]));
|
|
1079
|
+
function toClaudeCodeName(name) {
|
|
1080
|
+
return CC_TOOL_BY_LOWER.get(name.toLowerCase()) ?? name;
|
|
1081
|
+
}
|
|
1082
|
+
function fromClaudeCodeName(name, tools) {
|
|
1083
|
+
const lower = name.toLowerCase();
|
|
1084
|
+
const match = tools?.find((t) => t.name.toLowerCase() === lower);
|
|
1085
|
+
return match?.name ?? name;
|
|
1086
|
+
}
|
|
1087
|
+
async function refreshAnthropicOAuthToken(refreshToken, signal) {
|
|
1088
|
+
const res = await fetch(TOKEN_URL, {
|
|
1089
|
+
method: "POST",
|
|
1090
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
1091
|
+
body: JSON.stringify({
|
|
1092
|
+
grant_type: "refresh_token",
|
|
1093
|
+
client_id: CLIENT_ID,
|
|
1094
|
+
refresh_token: refreshToken
|
|
1095
|
+
}),
|
|
1096
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
|
|
1097
|
+
});
|
|
1098
|
+
if (!res.ok) {
|
|
1099
|
+
const text = await res.text().catch(() => "");
|
|
1100
|
+
throw new Error(`Claude token refresh failed (${res.status}): ${text || res.statusText}`);
|
|
1101
|
+
}
|
|
1102
|
+
const json = await res.json();
|
|
1103
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
1104
|
+
throw new Error("Claude token refresh response missing fields");
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
access: json.access_token,
|
|
1108
|
+
refresh: json.refresh_token,
|
|
1109
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
var AnthropicOAuthProvider = class extends AnthropicProvider {
|
|
1113
|
+
id;
|
|
1114
|
+
capabilities;
|
|
1115
|
+
access;
|
|
1116
|
+
refresh;
|
|
1117
|
+
expiresAt;
|
|
1118
|
+
onRefresh;
|
|
1119
|
+
refreshFn;
|
|
1120
|
+
constructor(opts) {
|
|
1121
|
+
super({
|
|
1122
|
+
apiKey: opts.credentials.accessToken,
|
|
1123
|
+
baseUrl: opts.baseUrl ?? DEFAULT_BASE2,
|
|
1124
|
+
fetchImpl: opts.fetchImpl,
|
|
1125
|
+
streamOpts: opts.streamOpts
|
|
1126
|
+
});
|
|
1127
|
+
this.id = opts.id ?? "anthropic-oauth";
|
|
1128
|
+
this.capabilities = capabilitiesForFamily("anthropic-oauth");
|
|
1129
|
+
this.access = opts.credentials.accessToken;
|
|
1130
|
+
this.refresh = opts.credentials.refreshToken;
|
|
1131
|
+
this.expiresAt = opts.credentials.expiresAt;
|
|
1132
|
+
this.onRefresh = opts.onRefresh;
|
|
1133
|
+
this.refreshFn = opts.refreshFn ?? refreshAnthropicOAuthToken;
|
|
1134
|
+
}
|
|
1135
|
+
async *stream(req, opts) {
|
|
1136
|
+
await this.ensureFreshToken(opts.signal);
|
|
1137
|
+
try {
|
|
1138
|
+
yield* this.remapToolNames(super.stream(req, opts), req.tools);
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
if (err instanceof ProviderError && err.status === 401 && this.refresh) {
|
|
1141
|
+
await this.doRefresh(opts.signal);
|
|
1142
|
+
yield* this.remapToolNames(super.stream(req, opts), req.tools);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
throw err;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/** Map Claude-Code-cased tool_use names in the stream back to real names. */
|
|
1149
|
+
async *remapToolNames(events, tools) {
|
|
1150
|
+
for await (const ev of events) {
|
|
1151
|
+
if ((ev.type === "tool_use_start" || ev.type === "content_block_start") && typeof ev.name === "string") {
|
|
1152
|
+
yield { ...ev, name: fromClaudeCodeName(ev.name, tools) };
|
|
1153
|
+
} else {
|
|
1154
|
+
yield ev;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
async ensureFreshToken(signal) {
|
|
1159
|
+
if (!this.refresh) return;
|
|
1160
|
+
if (this.expiresAt !== void 0 && Date.now() < this.expiresAt - REFRESH_SKEW_MS) return;
|
|
1161
|
+
await this.doRefresh(signal);
|
|
1162
|
+
}
|
|
1163
|
+
async doRefresh(signal) {
|
|
1164
|
+
if (!this.refresh) return;
|
|
1165
|
+
const t = await this.refreshFn(this.refresh, signal);
|
|
1166
|
+
this.access = t.access;
|
|
1167
|
+
this.refresh = t.refresh;
|
|
1168
|
+
this.expiresAt = t.expires;
|
|
1169
|
+
this.onRefresh?.({ accessToken: t.access, refreshToken: t.refresh, expiresAt: t.expires });
|
|
1170
|
+
}
|
|
1171
|
+
buildHeaders(_req) {
|
|
1172
|
+
return {
|
|
1173
|
+
"content-type": "application/json",
|
|
1174
|
+
accept: "text/event-stream",
|
|
1175
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
1176
|
+
authorization: `Bearer ${this.access}`,
|
|
1177
|
+
"anthropic-beta": OAUTH_BETA,
|
|
1178
|
+
// Present as the official Claude Code CLI so the subscription backend
|
|
1179
|
+
// accepts the request and the client isn't trivially fingerprinted.
|
|
1180
|
+
"user-agent": `claude-cli/${CLAUDE_CODE_VERSION}`,
|
|
1181
|
+
"x-app": "cli",
|
|
1182
|
+
"anthropic-dangerous-direct-browser-access": "true"
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
buildBody(req) {
|
|
1186
|
+
const body = super.buildBody(req);
|
|
1187
|
+
const existing = body["system"] ?? [];
|
|
1188
|
+
const alreadyLed = existing[0]?.text?.startsWith(CLAUDE_CODE_SYSTEM_PROMPT) === true;
|
|
1189
|
+
body["system"] = alreadyLed ? existing : [{ type: "text", text: CLAUDE_CODE_SYSTEM_PROMPT }, ...existing];
|
|
1190
|
+
const tools = body["tools"];
|
|
1191
|
+
if (Array.isArray(tools)) {
|
|
1192
|
+
for (const t of tools) {
|
|
1193
|
+
if (typeof t.name === "string") t.name = toClaudeCodeName(t.name);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
const messages = body["messages"];
|
|
1197
|
+
if (Array.isArray(messages)) {
|
|
1198
|
+
for (const m of messages) {
|
|
1199
|
+
if (!Array.isArray(m.content)) continue;
|
|
1200
|
+
for (const block of m.content) {
|
|
1201
|
+
if (block?.type === "tool_use" && typeof block.name === "string") {
|
|
1202
|
+
block.name = toClaudeCodeName(block.name);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return body;
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/openai.ts
|
|
1212
|
+
init_tool_input();
|
|
1213
|
+
var _cache2 = /* @__PURE__ */ new WeakMap();
|
|
1214
|
+
function toolsToOpenAI(tools) {
|
|
1215
|
+
const hit = _cache2.get(tools);
|
|
1216
|
+
if (hit) return hit;
|
|
1217
|
+
const result = tools.map((t) => {
|
|
1218
|
+
const compact = compactToolDefinitionForWire(t);
|
|
1219
|
+
return {
|
|
1220
|
+
type: "function",
|
|
1221
|
+
function: {
|
|
1222
|
+
name: compact.name,
|
|
1223
|
+
description: compact.description,
|
|
1224
|
+
parameters: compact.inputSchema
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
});
|
|
1228
|
+
_cache2.set(tools, result);
|
|
1229
|
+
return result;
|
|
1230
|
+
}
|
|
1231
|
+
function messagesToOpenAI(system, messages, opts = {}) {
|
|
1232
|
+
const emptyContentMode = opts.emptyToolCallContent ?? "empty_string";
|
|
1233
|
+
const out = [];
|
|
1234
|
+
if (system && system.length > 0) {
|
|
1235
|
+
const sysText = system.map((b) => b.text).join("\n\n");
|
|
1236
|
+
if (opts.systemAsMessage) {
|
|
1237
|
+
out.push({ role: "user", content: sysText });
|
|
1238
|
+
} else {
|
|
1239
|
+
out.push({ role: "system", content: sysText });
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
for (const msg of messages) {
|
|
1243
|
+
if (msg.role === "user") {
|
|
1244
|
+
const blocks = normalizeContent(msg.content);
|
|
1245
|
+
const toolResults = blocks.filter((b) => b.type === "tool_result");
|
|
1246
|
+
const others = blocks.filter((b) => b.type !== "tool_result");
|
|
1247
|
+
for (const r of toolResults) {
|
|
1248
|
+
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
1249
|
+
out.push({
|
|
1250
|
+
role: "tool",
|
|
1251
|
+
tool_call_id: r.tool_use_id,
|
|
1252
|
+
content
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
if (others.length > 0) {
|
|
1256
|
+
out.push({
|
|
1257
|
+
role: "user",
|
|
1258
|
+
content: opts.flattenContentToString ? blocksToString(others) : blocksToContentArray(others)
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
} else if (msg.role === "assistant") {
|
|
1262
|
+
const blocks = normalizeContent(msg.content);
|
|
1263
|
+
const textBlocks = blocks.filter((b) => b.type === "text");
|
|
1264
|
+
const toolUses = blocks.filter((b) => b.type === "tool_use");
|
|
1265
|
+
const thinkingBlocks = blocks.filter((b) => b.type === "thinking");
|
|
1266
|
+
const text = textBlocks.map((b) => b.text).join("");
|
|
1267
|
+
const reasoning = thinkingBlocks.map((b) => b.thinking).filter((t) => t && t.length > 0).join("");
|
|
1268
|
+
const toolCalls = toolUses.map((u) => ({
|
|
1269
|
+
id: u.id,
|
|
1270
|
+
type: "function",
|
|
1271
|
+
function: { name: u.name, arguments: JSON.stringify(u.input) }
|
|
1272
|
+
}));
|
|
1273
|
+
const message = { role: "assistant" };
|
|
1274
|
+
if (toolCalls.length > 0) {
|
|
1275
|
+
message.tool_calls = toolCalls;
|
|
1276
|
+
if (text) {
|
|
1277
|
+
message.content = text;
|
|
1278
|
+
} else {
|
|
1279
|
+
message.content = emptyContentMode === "null" ? null : "";
|
|
1280
|
+
}
|
|
1281
|
+
} else {
|
|
1282
|
+
message.content = text;
|
|
1283
|
+
}
|
|
1284
|
+
if (reasoning.length > 0) {
|
|
1285
|
+
message.reasoning_content = reasoning;
|
|
1286
|
+
}
|
|
1287
|
+
out.push(message);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return out;
|
|
1291
|
+
}
|
|
1292
|
+
function normalizeContent(content) {
|
|
1293
|
+
return typeof content === "string" ? [{ type: "text", text: content }] : content;
|
|
1294
|
+
}
|
|
1295
|
+
function blocksToString(blocks) {
|
|
1296
|
+
return blocks.map((b) => {
|
|
1297
|
+
if (b.type === "text") return b.text;
|
|
1298
|
+
if (b.type === "image") return "[image]";
|
|
1299
|
+
return "";
|
|
1300
|
+
}).join("");
|
|
1301
|
+
}
|
|
1302
|
+
function blocksToContentArray(blocks) {
|
|
1303
|
+
const hasImage = blocks.some((b) => b.type === "image");
|
|
1304
|
+
if (!hasImage) {
|
|
1305
|
+
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
1306
|
+
}
|
|
1307
|
+
return blocks.map((b) => {
|
|
1308
|
+
if (b.type === "text") return { type: "text", text: b.text };
|
|
1309
|
+
if (b.type === "image") {
|
|
1310
|
+
const url = b.source.type === "url" ? b.source.url ?? "" : `data:${b.source.media_type ?? "image/png"};base64,${b.source.data ?? ""}`;
|
|
1311
|
+
return { type: "image_url", image_url: { url } };
|
|
1312
|
+
}
|
|
1313
|
+
return null;
|
|
1314
|
+
}).filter((c) => c !== null);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/openai.ts
|
|
1318
|
+
var DEFAULT_BASE3 = "https://api.openai.com/v1";
|
|
1319
|
+
var OpenAIProvider = class extends WireAdapter {
|
|
1320
|
+
id;
|
|
1321
|
+
capabilities;
|
|
1322
|
+
opts;
|
|
1323
|
+
constructor(opts) {
|
|
1324
|
+
super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE3, opts.fetchImpl, opts.streamOpts);
|
|
1325
|
+
this.opts = opts;
|
|
1326
|
+
this.id = opts.id ?? "openai";
|
|
1327
|
+
this.capabilities = capabilitiesForFamily("openai", {
|
|
1328
|
+
parallelTools: !opts.quirks?.parallelToolsDisabled,
|
|
1329
|
+
systemPrompt: !opts.quirks?.systemAsMessage,
|
|
1330
|
+
...opts.capabilities
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
buildUrl(_req) {
|
|
1334
|
+
const base = this.baseUrl.replace(/\/+$/, "");
|
|
1335
|
+
if (/\/chat\/completions$/.test(base)) return base;
|
|
1336
|
+
if (/\/v\d+(\/[a-z0-9_-]+)*$/i.test(base)) return `${base}/chat/completions`;
|
|
1337
|
+
return `${base}/v1/chat/completions`;
|
|
1338
|
+
}
|
|
1339
|
+
buildHeaders(req) {
|
|
1340
|
+
const headers = {
|
|
1341
|
+
...super.buildHeaders(req),
|
|
1342
|
+
authorization: `Bearer ${this.apiKey}`
|
|
1343
|
+
};
|
|
1344
|
+
if (this.opts.organization) {
|
|
1345
|
+
headers["openai-organization"] = this.opts.organization;
|
|
1346
|
+
}
|
|
1347
|
+
return headers;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* The request field used to cap output length. Real OpenAI deprecated
|
|
1351
|
+
* `max_tokens` and the newer model families (gpt-4o, o1/o3/o4) 400 on it —
|
|
1352
|
+
* they require `max_completion_tokens`. OpenAI-compatible endpoints that
|
|
1353
|
+
* still only accept `max_tokens` override this. See issue #10.
|
|
1354
|
+
*/
|
|
1355
|
+
tokenLimitParam() {
|
|
1356
|
+
return "max_completion_tokens";
|
|
1357
|
+
}
|
|
1358
|
+
buildBody(req) {
|
|
1359
|
+
const body = {
|
|
1360
|
+
model: req.model,
|
|
1361
|
+
messages: messagesToOpenAI(this.stripCacheControl(req), req.messages, {
|
|
1362
|
+
...this.opts.quirks
|
|
1363
|
+
}),
|
|
1364
|
+
[this.tokenLimitParam()]: req.maxTokens,
|
|
1365
|
+
stream: true,
|
|
1366
|
+
stream_options: { include_usage: true }
|
|
1367
|
+
};
|
|
1368
|
+
if (req.tools && req.tools.length > 0) {
|
|
1369
|
+
body["tools"] = toolsToOpenAI(req.tools);
|
|
1370
|
+
if (req.toolChoice) {
|
|
1371
|
+
if (typeof req.toolChoice === "string") {
|
|
1372
|
+
body["tool_choice"] = req.toolChoice === "required" ? "required" : req.toolChoice;
|
|
1373
|
+
} else {
|
|
1374
|
+
body["tool_choice"] = {
|
|
1375
|
+
type: "function",
|
|
1376
|
+
function: { name: req.toolChoice.name }
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
if (req.temperature !== void 0) body["temperature"] = req.temperature;
|
|
1382
|
+
if (req.topP !== void 0) body["top_p"] = req.topP;
|
|
1383
|
+
if (req.stopSequences) body["stop"] = req.stopSequences;
|
|
1384
|
+
return body;
|
|
1385
|
+
}
|
|
1386
|
+
parseStream(body, fallbackModel) {
|
|
1387
|
+
return parseOpenAIStream(body, fallbackModel);
|
|
1388
|
+
}
|
|
1389
|
+
translateError(status, text) {
|
|
1390
|
+
return parseProviderHttpError(this.id, status, text);
|
|
1391
|
+
}
|
|
1392
|
+
stripCacheControl(req) {
|
|
1393
|
+
if (!req.system) return void 0;
|
|
1394
|
+
return req.system.map((b) => {
|
|
1395
|
+
const { cache_control: _cc, ...rest } = b;
|
|
1396
|
+
return rest;
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
async function* parseOpenAIStream(body, fallbackModel) {
|
|
1401
|
+
let model = fallbackModel;
|
|
1402
|
+
let usage = { input: 0, output: 0 };
|
|
1403
|
+
let stopReason = "end_turn";
|
|
1404
|
+
let started = false;
|
|
1405
|
+
let thinkingOpen = false;
|
|
1406
|
+
const toolByIndex = /* @__PURE__ */ new Map();
|
|
1407
|
+
for await (const msg of parseSSE(body)) {
|
|
1408
|
+
if (!msg.data || msg.data === "[DONE]") continue;
|
|
1409
|
+
const parsed = safeParse(msg.data);
|
|
1410
|
+
if (!parsed.ok || !parsed.value) continue;
|
|
1411
|
+
const obj = parsed.value;
|
|
1412
|
+
if (typeof obj["model"] === "string") model = obj["model"];
|
|
1413
|
+
if (!started) {
|
|
1414
|
+
started = true;
|
|
1415
|
+
yield { type: "message_start", model };
|
|
1416
|
+
}
|
|
1417
|
+
const choices = obj["choices"];
|
|
1418
|
+
const choice = choices?.[0];
|
|
1419
|
+
const reasoningDelta = typeof choice?.delta?.reasoning_content === "string" ? choice.delta.reasoning_content : typeof choice?.delta?.reasoning === "string" ? choice.delta.reasoning : void 0;
|
|
1420
|
+
if (reasoningDelta && reasoningDelta.length > 0) {
|
|
1421
|
+
if (!thinkingOpen) {
|
|
1422
|
+
thinkingOpen = true;
|
|
1423
|
+
yield { type: "thinking_start" };
|
|
1424
|
+
}
|
|
1425
|
+
yield { type: "thinking_delta", text: reasoningDelta };
|
|
1426
|
+
}
|
|
1427
|
+
if (choice?.delta?.content) {
|
|
1428
|
+
if (thinkingOpen) {
|
|
1429
|
+
thinkingOpen = false;
|
|
1430
|
+
yield { type: "thinking_stop" };
|
|
1431
|
+
}
|
|
1432
|
+
yield { type: "text_delta", text: choice.delta.content };
|
|
1433
|
+
}
|
|
1434
|
+
if (choice?.delta?.tool_calls) {
|
|
1435
|
+
if (thinkingOpen) {
|
|
1436
|
+
thinkingOpen = false;
|
|
1437
|
+
yield { type: "thinking_stop" };
|
|
1438
|
+
}
|
|
1439
|
+
for (const tc of choice.delta.tool_calls) {
|
|
1440
|
+
const idx = tc.index ?? 0;
|
|
1441
|
+
let entry = toolByIndex.get(idx);
|
|
1442
|
+
if (!entry) {
|
|
1443
|
+
entry = {
|
|
1444
|
+
id: tc.id,
|
|
1445
|
+
name: tc.function?.name,
|
|
1446
|
+
argBuf: "",
|
|
1447
|
+
emittedStart: false,
|
|
1448
|
+
emittedArgLength: 0
|
|
1449
|
+
};
|
|
1450
|
+
toolByIndex.set(idx, entry);
|
|
1451
|
+
} else {
|
|
1452
|
+
if (tc.id && !entry.id) entry.id = tc.id;
|
|
1453
|
+
if (tc.function?.name && !entry.name) entry.name = tc.function.name;
|
|
1454
|
+
}
|
|
1455
|
+
if (tc.function?.arguments) {
|
|
1456
|
+
entry.argBuf += tc.function.arguments;
|
|
1457
|
+
}
|
|
1458
|
+
if (!entry.emittedStart && entry.id && entry.name) {
|
|
1459
|
+
entry.emittedStart = true;
|
|
1460
|
+
yield { type: "tool_use_start", id: entry.id, name: entry.name };
|
|
1461
|
+
}
|
|
1462
|
+
if (entry.emittedStart && entry.id && entry.emittedArgLength < entry.argBuf.length) {
|
|
1463
|
+
const partial = entry.argBuf.slice(entry.emittedArgLength);
|
|
1464
|
+
entry.emittedArgLength = entry.argBuf.length;
|
|
1465
|
+
yield {
|
|
1466
|
+
type: "tool_use_input_delta",
|
|
1467
|
+
id: entry.id,
|
|
1468
|
+
partial
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (choice?.finish_reason) {
|
|
1474
|
+
stopReason = normalizeOpenAI(choice.finish_reason);
|
|
1475
|
+
}
|
|
1476
|
+
const u = obj["usage"];
|
|
1477
|
+
if (u) {
|
|
1478
|
+
const hasDeepSeekCacheFields = u.prompt_cache_hit_tokens !== void 0 || u.prompt_cache_miss_tokens !== void 0;
|
|
1479
|
+
const cached = u.prompt_tokens_details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0;
|
|
1480
|
+
const promptTotal = u.prompt_tokens ?? u.input_tokens ?? (hasDeepSeekCacheFields ? (u.prompt_cache_hit_tokens ?? 0) + (u.prompt_cache_miss_tokens ?? 0) : usage.input + cached);
|
|
1481
|
+
usage = {
|
|
1482
|
+
input: u.prompt_cache_miss_tokens ?? Math.max(0, promptTotal - cached),
|
|
1483
|
+
output: u.completion_tokens ?? usage.output,
|
|
1484
|
+
cacheRead: cached || usage.cacheRead
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (thinkingOpen) {
|
|
1489
|
+
yield { type: "thinking_stop" };
|
|
1490
|
+
}
|
|
1491
|
+
for (const entry of toolByIndex.values()) {
|
|
1492
|
+
if (!entry.name) continue;
|
|
1493
|
+
if (!entry.id) entry.id = `call_${randomUUID()}`;
|
|
1494
|
+
if (!entry.emittedStart) {
|
|
1495
|
+
yield { type: "tool_use_start", id: entry.id, name: entry.name };
|
|
1496
|
+
}
|
|
1497
|
+
const input = parseToolInput(entry.argBuf);
|
|
1498
|
+
yield { type: "tool_use_stop", id: entry.id, input };
|
|
1499
|
+
}
|
|
1500
|
+
if (started) {
|
|
1501
|
+
yield { type: "message_stop", stopReason, usage };
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/github-copilot.ts
|
|
1506
|
+
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
1507
|
+
var COPILOT_API_VERSION = "2026-06-01";
|
|
1508
|
+
var COPILOT_HEADERS = {
|
|
1509
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
1510
|
+
"Editor-Version": "vscode/1.107.0",
|
|
1511
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
1512
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
1513
|
+
};
|
|
1514
|
+
var DEFAULT_API_BASE = "https://api.individual.githubcopilot.com";
|
|
1515
|
+
var REFRESH_SKEW_MS2 = 6e4;
|
|
1516
|
+
function copilotBaseUrlFromToken(token) {
|
|
1517
|
+
if (token) {
|
|
1518
|
+
const m = token.match(/proxy-ep=([^;]+)/);
|
|
1519
|
+
if (m?.[1]) return `https://${m[1].replace(/^proxy\./, "api.")}`;
|
|
1520
|
+
}
|
|
1521
|
+
return DEFAULT_API_BASE;
|
|
1522
|
+
}
|
|
1523
|
+
async function refreshCopilotToken(githubToken, signal) {
|
|
1524
|
+
const res = await fetch(COPILOT_TOKEN_URL, {
|
|
1525
|
+
headers: {
|
|
1526
|
+
accept: "application/json",
|
|
1527
|
+
authorization: `Bearer ${githubToken}`,
|
|
1528
|
+
...COPILOT_HEADERS
|
|
1529
|
+
},
|
|
1530
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
|
|
1531
|
+
});
|
|
1532
|
+
if (!res.ok) {
|
|
1533
|
+
const text = await res.text().catch(() => "");
|
|
1534
|
+
throw new Error(`Copilot token request failed (${res.status}): ${text || res.statusText}`);
|
|
1535
|
+
}
|
|
1536
|
+
const json = await res.json();
|
|
1537
|
+
if (!json?.token || typeof json.expires_at !== "number") {
|
|
1538
|
+
throw new Error("Copilot token response missing fields");
|
|
1539
|
+
}
|
|
1540
|
+
return { token: json.token, expires: json.expires_at * 1e3 };
|
|
1541
|
+
}
|
|
1542
|
+
var GitHubCopilotProvider = class extends OpenAIProvider {
|
|
1543
|
+
capabilities;
|
|
1544
|
+
copilotToken;
|
|
1545
|
+
githubToken;
|
|
1546
|
+
expiresAt;
|
|
1547
|
+
apiBase;
|
|
1548
|
+
onRefresh;
|
|
1549
|
+
refreshFn;
|
|
1550
|
+
constructor(opts) {
|
|
1551
|
+
const apiBase = copilotBaseUrlFromToken(opts.credentials.copilotToken);
|
|
1552
|
+
super({
|
|
1553
|
+
// OpenAIProvider requires a non-empty apiKey; use a placeholder when the
|
|
1554
|
+
// Copilot token is empty (it will be minted before the first request).
|
|
1555
|
+
apiKey: opts.credentials.copilotToken || "pending",
|
|
1556
|
+
baseUrl: apiBase,
|
|
1557
|
+
id: opts.id ?? "github-copilot",
|
|
1558
|
+
fetchImpl: opts.fetchImpl,
|
|
1559
|
+
streamOpts: opts.streamOpts,
|
|
1560
|
+
capabilities: opts.capabilities
|
|
1561
|
+
});
|
|
1562
|
+
this.copilotToken = opts.credentials.copilotToken;
|
|
1563
|
+
this.githubToken = opts.credentials.githubToken;
|
|
1564
|
+
this.expiresAt = opts.credentials.expiresAt;
|
|
1565
|
+
this.apiBase = apiBase;
|
|
1566
|
+
this.onRefresh = opts.onRefresh;
|
|
1567
|
+
this.refreshFn = opts.refreshFn ?? refreshCopilotToken;
|
|
1568
|
+
this.capabilities = capabilitiesForFamily("github-copilot", { ...opts.capabilities });
|
|
1569
|
+
}
|
|
1570
|
+
async *stream(req, opts) {
|
|
1571
|
+
await this.ensureFreshToken(opts.signal);
|
|
1572
|
+
try {
|
|
1573
|
+
yield* super.stream(req, opts);
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
if (err instanceof ProviderError && err.status === 401 && this.githubToken) {
|
|
1576
|
+
await this.doRefresh(opts.signal);
|
|
1577
|
+
yield* super.stream(req, opts);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
throw err;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
async ensureFreshToken(signal) {
|
|
1584
|
+
const stale = this.expiresAt === void 0 || Date.now() >= this.expiresAt - REFRESH_SKEW_MS2;
|
|
1585
|
+
if (!this.copilotToken || stale && this.githubToken) {
|
|
1586
|
+
await this.doRefresh(signal);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
async doRefresh(signal) {
|
|
1590
|
+
if (!this.githubToken) return;
|
|
1591
|
+
const t = await this.refreshFn(this.githubToken, signal);
|
|
1592
|
+
this.copilotToken = t.token;
|
|
1593
|
+
this.expiresAt = t.expires;
|
|
1594
|
+
this.apiBase = copilotBaseUrlFromToken(t.token);
|
|
1595
|
+
this.onRefresh?.({
|
|
1596
|
+
accessToken: t.token,
|
|
1597
|
+
refreshToken: this.githubToken,
|
|
1598
|
+
expiresAt: t.expires
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
buildUrl(_req) {
|
|
1602
|
+
return `${this.apiBase.replace(/\/+$/, "")}/chat/completions`;
|
|
1603
|
+
}
|
|
1604
|
+
buildHeaders(_req) {
|
|
1605
|
+
return {
|
|
1606
|
+
"content-type": "application/json",
|
|
1607
|
+
accept: "text/event-stream",
|
|
1608
|
+
authorization: `Bearer ${this.copilotToken}`,
|
|
1609
|
+
"X-GitHub-Api-Version": COPILOT_API_VERSION,
|
|
1610
|
+
...COPILOT_HEADERS
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
var DEFAULT_BASE4 = "https://generativelanguage.googleapis.com/v1beta";
|
|
981
1615
|
var GoogleProvider = class extends WireAdapter {
|
|
982
1616
|
id;
|
|
983
1617
|
capabilities;
|
|
984
1618
|
constructor(opts) {
|
|
985
|
-
super(opts.apiKey, opts.baseUrl ??
|
|
1619
|
+
super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE4, opts.fetchImpl, opts.streamOpts);
|
|
986
1620
|
this.id = opts.id ?? "google";
|
|
987
1621
|
this.capabilities = capabilitiesForFamily("google", {
|
|
988
1622
|
...opts.capabilities
|
|
@@ -1027,14 +1661,17 @@ var GoogleProvider = class extends WireAdapter {
|
|
|
1027
1661
|
}
|
|
1028
1662
|
};
|
|
1029
1663
|
function toolsToGemini(tools) {
|
|
1030
|
-
return tools.map((t) =>
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1664
|
+
return tools.map((t) => {
|
|
1665
|
+
const compact = compactToolDefinitionForWire(t);
|
|
1666
|
+
return {
|
|
1667
|
+
name: compact.name,
|
|
1668
|
+
description: compact.description,
|
|
1669
|
+
parameters: sanitizeSchemaForGemini(compact.inputSchema) ?? {
|
|
1670
|
+
type: "object",
|
|
1671
|
+
properties: {}
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
});
|
|
1038
1675
|
}
|
|
1039
1676
|
var GEMINI_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
1040
1677
|
"type",
|
|
@@ -1197,301 +1834,356 @@ async function* parseGoogleStream(body, fallbackModel) {
|
|
|
1197
1834
|
}
|
|
1198
1835
|
}
|
|
1199
1836
|
|
|
1200
|
-
// src/openai.ts
|
|
1837
|
+
// src/openai-codex.ts
|
|
1201
1838
|
init_tool_input();
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
function toolsToOpenAI(tools) {
|
|
1206
|
-
const hit = _cache2.get(tools);
|
|
1839
|
+
var _toolCache = /* @__PURE__ */ new WeakMap();
|
|
1840
|
+
function toolsToResponses(tools) {
|
|
1841
|
+
const hit = _toolCache.get(tools);
|
|
1207
1842
|
if (hit) return hit;
|
|
1208
|
-
const result = tools.map((t) =>
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
_cache2.set(tools, result);
|
|
1843
|
+
const result = tools.map((t) => {
|
|
1844
|
+
const compact = compactToolDefinitionForWire(t);
|
|
1845
|
+
return {
|
|
1846
|
+
type: "function",
|
|
1847
|
+
name: compact.name,
|
|
1848
|
+
description: compact.description,
|
|
1849
|
+
parameters: compact.inputSchema,
|
|
1850
|
+
strict: false
|
|
1851
|
+
};
|
|
1852
|
+
});
|
|
1853
|
+
_toolCache.set(tools, result);
|
|
1220
1854
|
return result;
|
|
1221
1855
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1856
|
+
function normalizeContent2(content) {
|
|
1857
|
+
return typeof content === "string" ? [{ type: "text", text: content }] : content;
|
|
1858
|
+
}
|
|
1859
|
+
function imageUrl(b) {
|
|
1860
|
+
return b.source.type === "url" ? b.source.url ?? "" : `data:${b.source.media_type ?? "image/png"};base64,${b.source.data ?? ""}`;
|
|
1861
|
+
}
|
|
1862
|
+
function messagesToResponsesInput(messages) {
|
|
1224
1863
|
const out = [];
|
|
1225
|
-
if (system && system.length > 0) {
|
|
1226
|
-
const sysText = system.map((b) => b.text).join("\n\n");
|
|
1227
|
-
if (opts.systemAsMessage) {
|
|
1228
|
-
out.push({ role: "user", content: sysText });
|
|
1229
|
-
} else {
|
|
1230
|
-
out.push({ role: "system", content: sysText });
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
1864
|
for (const msg of messages) {
|
|
1865
|
+
const blocks = normalizeContent2(msg.content);
|
|
1234
1866
|
if (msg.role === "user") {
|
|
1235
|
-
const blocks = normalizeContent(msg.content);
|
|
1236
1867
|
const toolResults = blocks.filter((b) => b.type === "tool_result");
|
|
1237
|
-
const others = blocks.filter((b) => b.type !== "tool_result");
|
|
1238
1868
|
for (const r of toolResults) {
|
|
1239
|
-
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
1240
1869
|
out.push({
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
content
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
if (others.length > 0) {
|
|
1247
|
-
out.push({
|
|
1248
|
-
role: "user",
|
|
1249
|
-
content: opts.flattenContentToString ? blocksToString(others) : blocksToContentArray(others)
|
|
1870
|
+
type: "function_call_output",
|
|
1871
|
+
call_id: r.tool_use_id,
|
|
1872
|
+
output: typeof r.content === "string" ? r.content : JSON.stringify(r.content)
|
|
1250
1873
|
});
|
|
1251
1874
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
function: { name: u.name, arguments: JSON.stringify(u.input) }
|
|
1263
|
-
}));
|
|
1264
|
-
const message = { role: "assistant" };
|
|
1265
|
-
if (toolCalls.length > 0) {
|
|
1266
|
-
message.tool_calls = toolCalls;
|
|
1267
|
-
if (text) {
|
|
1268
|
-
message.content = text;
|
|
1269
|
-
} else {
|
|
1270
|
-
message.content = emptyContentMode === "null" ? null : "";
|
|
1271
|
-
}
|
|
1272
|
-
} else {
|
|
1273
|
-
message.content = text;
|
|
1875
|
+
const others = blocks.filter((b) => b.type !== "tool_result");
|
|
1876
|
+
if (others.length > 0) {
|
|
1877
|
+
const content = others.map((b) => {
|
|
1878
|
+
if (b.type === "text") return { type: "input_text", text: b.text };
|
|
1879
|
+
if (b.type === "image") {
|
|
1880
|
+
return { type: "input_image", detail: "auto", image_url: imageUrl(b) };
|
|
1881
|
+
}
|
|
1882
|
+
return null;
|
|
1883
|
+
}).filter((c) => c !== null);
|
|
1884
|
+
if (content.length > 0) out.push({ role: "user", content });
|
|
1274
1885
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1886
|
+
} else if (msg.role === "assistant") {
|
|
1887
|
+
const textBlocks = blocks.filter((b) => b.type === "text");
|
|
1888
|
+
const toolUses = blocks.filter((b) => b.type === "tool_use");
|
|
1889
|
+
const text = textBlocks.map((b) => b.text).join("");
|
|
1890
|
+
if (text.length > 0) {
|
|
1891
|
+
out.push({
|
|
1892
|
+
type: "message",
|
|
1893
|
+
role: "assistant",
|
|
1894
|
+
content: [{ type: "output_text", text, annotations: [] }],
|
|
1895
|
+
status: "completed"
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
for (const u of toolUses) {
|
|
1899
|
+
out.push({
|
|
1900
|
+
type: "function_call",
|
|
1901
|
+
call_id: u.id,
|
|
1902
|
+
name: u.name,
|
|
1903
|
+
arguments: JSON.stringify(u.input ?? {})
|
|
1904
|
+
});
|
|
1277
1905
|
}
|
|
1278
|
-
out.push(message);
|
|
1279
1906
|
}
|
|
1280
1907
|
}
|
|
1281
1908
|
return out;
|
|
1282
1909
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1910
|
+
|
|
1911
|
+
// src/openai-codex.ts
|
|
1912
|
+
var CLIENT_ID2 = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1913
|
+
var TOKEN_URL2 = "https://auth.openai.com/oauth/token";
|
|
1914
|
+
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
1915
|
+
var DEFAULT_CODEX_BASE = "https://chatgpt.com/backend-api";
|
|
1916
|
+
var REFRESH_SKEW_MS3 = 6e4;
|
|
1917
|
+
async function refreshCodexAccessToken(refreshToken, signal) {
|
|
1918
|
+
const res = await fetch(TOKEN_URL2, {
|
|
1919
|
+
method: "POST",
|
|
1920
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
1921
|
+
body: new URLSearchParams({
|
|
1922
|
+
grant_type: "refresh_token",
|
|
1923
|
+
client_id: CLIENT_ID2,
|
|
1924
|
+
refresh_token: refreshToken
|
|
1925
|
+
}).toString(),
|
|
1926
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
|
|
1927
|
+
});
|
|
1928
|
+
if (!res.ok) {
|
|
1929
|
+
const text = await res.text().catch(() => "");
|
|
1930
|
+
throw new Error(`Codex token refresh failed (${res.status}): ${text || res.statusText}`);
|
|
1297
1931
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1932
|
+
const json = await res.json();
|
|
1933
|
+
if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
1934
|
+
throw new Error("Codex token refresh response missing fields");
|
|
1935
|
+
}
|
|
1936
|
+
return {
|
|
1937
|
+
access: json.access_token,
|
|
1938
|
+
refresh: json.refresh_token,
|
|
1939
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
function extractAccountId(token) {
|
|
1943
|
+
try {
|
|
1944
|
+
const parts = token.split(".");
|
|
1945
|
+
if (parts.length !== 3) return null;
|
|
1946
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
1947
|
+
const auth = payload[JWT_CLAIM_PATH];
|
|
1948
|
+
const id = auth?.chatgpt_account_id;
|
|
1949
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
1950
|
+
} catch {
|
|
1304
1951
|
return null;
|
|
1305
|
-
}
|
|
1952
|
+
}
|
|
1306
1953
|
}
|
|
1307
|
-
|
|
1308
|
-
// src/openai.ts
|
|
1309
|
-
var DEFAULT_BASE3 = "https://api.openai.com/v1";
|
|
1310
|
-
var OpenAIProvider = class extends WireAdapter {
|
|
1954
|
+
var OpenAICodexProvider = class extends WireAdapter {
|
|
1311
1955
|
id;
|
|
1312
1956
|
capabilities;
|
|
1313
|
-
|
|
1957
|
+
access;
|
|
1958
|
+
refresh;
|
|
1959
|
+
expiresAt;
|
|
1960
|
+
accountId;
|
|
1961
|
+
onRefresh;
|
|
1962
|
+
refreshFn;
|
|
1963
|
+
reasoningEffort;
|
|
1314
1964
|
constructor(opts) {
|
|
1315
|
-
super(
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1965
|
+
super(
|
|
1966
|
+
opts.credentials.accessToken,
|
|
1967
|
+
opts.baseUrl ?? DEFAULT_CODEX_BASE,
|
|
1968
|
+
opts.fetchImpl,
|
|
1969
|
+
opts.streamOpts
|
|
1970
|
+
);
|
|
1971
|
+
this.id = opts.id ?? "openai-codex";
|
|
1972
|
+
this.access = opts.credentials.accessToken;
|
|
1973
|
+
this.refresh = opts.credentials.refreshToken;
|
|
1974
|
+
this.expiresAt = opts.credentials.expiresAt;
|
|
1975
|
+
this.accountId = opts.credentials.accountId ?? extractAccountId(this.access) ?? void 0;
|
|
1976
|
+
this.onRefresh = opts.onRefresh;
|
|
1977
|
+
this.refreshFn = opts.refreshFn ?? refreshCodexAccessToken;
|
|
1978
|
+
this.reasoningEffort = opts.reasoningEffort ?? "medium";
|
|
1979
|
+
this.capabilities = capabilitiesForFamily("openai-codex", { ...opts.capabilities });
|
|
1980
|
+
}
|
|
1981
|
+
async *stream(req, opts) {
|
|
1982
|
+
await this.ensureFreshToken(opts.signal);
|
|
1983
|
+
try {
|
|
1984
|
+
yield* super.stream(req, opts);
|
|
1985
|
+
} catch (err) {
|
|
1986
|
+
if (err instanceof ProviderError && err.status === 401 && this.refresh) {
|
|
1987
|
+
await this.doRefresh(opts.signal);
|
|
1988
|
+
yield* super.stream(req, opts);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
throw err;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
async ensureFreshToken(signal) {
|
|
1995
|
+
if (!this.refresh) return;
|
|
1996
|
+
if (this.expiresAt !== void 0 && Date.now() < this.expiresAt - REFRESH_SKEW_MS3) return;
|
|
1997
|
+
await this.doRefresh(signal);
|
|
1998
|
+
}
|
|
1999
|
+
async doRefresh(signal) {
|
|
2000
|
+
if (!this.refresh) return;
|
|
2001
|
+
const t = await this.refreshFn(this.refresh, signal);
|
|
2002
|
+
this.access = t.access;
|
|
2003
|
+
this.refresh = t.refresh;
|
|
2004
|
+
this.expiresAt = t.expires;
|
|
2005
|
+
this.accountId = extractAccountId(t.access) ?? this.accountId;
|
|
2006
|
+
this.onRefresh?.({
|
|
2007
|
+
accessToken: t.access,
|
|
2008
|
+
refreshToken: t.refresh,
|
|
2009
|
+
expiresAt: t.expires,
|
|
2010
|
+
accountId: this.accountId
|
|
1322
2011
|
});
|
|
1323
2012
|
}
|
|
1324
2013
|
buildUrl(_req) {
|
|
1325
|
-
|
|
1326
|
-
if (/\/chat\/completions$/.test(base)) return base;
|
|
1327
|
-
if (/\/v\d+(\/[a-z0-9_-]+)*$/i.test(base)) return `${base}/chat/completions`;
|
|
1328
|
-
return `${base}/v1/chat/completions`;
|
|
2014
|
+
return resolveCodexUrl(this.baseUrl);
|
|
1329
2015
|
}
|
|
1330
|
-
buildHeaders(
|
|
2016
|
+
buildHeaders(_req) {
|
|
1331
2017
|
const headers = {
|
|
1332
|
-
...super.buildHeaders(
|
|
1333
|
-
authorization: `Bearer ${this.
|
|
2018
|
+
...super.buildHeaders(_req),
|
|
2019
|
+
authorization: `Bearer ${this.access}`,
|
|
2020
|
+
originator: "wrongstack",
|
|
2021
|
+
"OpenAI-Beta": "responses=experimental"
|
|
1334
2022
|
};
|
|
1335
|
-
if (this.
|
|
1336
|
-
headers["openai-organization"] = this.opts.organization;
|
|
1337
|
-
}
|
|
2023
|
+
if (this.accountId) headers["chatgpt-account-id"] = this.accountId;
|
|
1338
2024
|
return headers;
|
|
1339
2025
|
}
|
|
1340
|
-
/**
|
|
1341
|
-
* The request field used to cap output length. Real OpenAI deprecated
|
|
1342
|
-
* `max_tokens` and the newer model families (gpt-4o, o1/o3/o4) 400 on it —
|
|
1343
|
-
* they require `max_completion_tokens`. OpenAI-compatible endpoints that
|
|
1344
|
-
* still only accept `max_tokens` override this. See issue #10.
|
|
1345
|
-
*/
|
|
1346
|
-
tokenLimitParam() {
|
|
1347
|
-
return "max_completion_tokens";
|
|
1348
|
-
}
|
|
1349
2026
|
buildBody(req) {
|
|
2027
|
+
const instructions = req.system && req.system.length > 0 ? req.system.map((b) => b.text).join("\n\n") : "You are a helpful assistant.";
|
|
1350
2028
|
const body = {
|
|
1351
2029
|
model: req.model,
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
[this.tokenLimitParam()]: req.maxTokens,
|
|
2030
|
+
// The ChatGPT Codex backend rejects `store: true` ("Store must be set to
|
|
2031
|
+
// false"). We send the full conversation as `input` each turn.
|
|
2032
|
+
store: false,
|
|
1356
2033
|
stream: true,
|
|
1357
|
-
|
|
2034
|
+
instructions,
|
|
2035
|
+
input: messagesToResponsesInput(req.messages),
|
|
2036
|
+
include: ["reasoning.encrypted_content"],
|
|
2037
|
+
parallel_tool_calls: true
|
|
1358
2038
|
};
|
|
1359
2039
|
if (req.tools && req.tools.length > 0) {
|
|
1360
|
-
body["tools"] =
|
|
1361
|
-
|
|
1362
|
-
if (typeof req.toolChoice === "string") {
|
|
1363
|
-
body["tool_choice"] = req.toolChoice === "required" ? "required" : req.toolChoice;
|
|
1364
|
-
} else {
|
|
1365
|
-
body["tool_choice"] = {
|
|
1366
|
-
type: "function",
|
|
1367
|
-
function: { name: req.toolChoice.name }
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
2040
|
+
body["tools"] = toolsToResponses(req.tools);
|
|
2041
|
+
body["tool_choice"] = mapToolChoice(req.toolChoice);
|
|
1371
2042
|
}
|
|
1372
2043
|
if (req.temperature !== void 0) body["temperature"] = req.temperature;
|
|
1373
2044
|
if (req.topP !== void 0) body["top_p"] = req.topP;
|
|
1374
|
-
if (
|
|
2045
|
+
if (this.reasoningEffort !== "none") {
|
|
2046
|
+
body["reasoning"] = { effort: this.reasoningEffort, summary: "auto" };
|
|
2047
|
+
}
|
|
1375
2048
|
return body;
|
|
1376
2049
|
}
|
|
1377
2050
|
parseStream(body, fallbackModel) {
|
|
1378
|
-
return
|
|
2051
|
+
return parseCodexResponsesStream(body, fallbackModel);
|
|
1379
2052
|
}
|
|
1380
2053
|
translateError(status, text) {
|
|
1381
2054
|
return parseProviderHttpError(this.id, status, text);
|
|
1382
2055
|
}
|
|
1383
|
-
stripCacheControl(req) {
|
|
1384
|
-
if (!req.system) return void 0;
|
|
1385
|
-
return req.system.map((b) => {
|
|
1386
|
-
const { cache_control: _cc, ...rest } = b;
|
|
1387
|
-
return rest;
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
2056
|
};
|
|
1391
|
-
|
|
2057
|
+
function resolveCodexUrl(baseUrl) {
|
|
2058
|
+
const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE;
|
|
2059
|
+
const normalized = raw.replace(/\/+$/, "");
|
|
2060
|
+
if (normalized.endsWith("/codex/responses")) return normalized;
|
|
2061
|
+
if (normalized.endsWith("/codex")) return `${normalized}/responses`;
|
|
2062
|
+
return `${normalized}/codex/responses`;
|
|
2063
|
+
}
|
|
2064
|
+
function mapToolChoice(choice) {
|
|
2065
|
+
if (choice === void 0) return "auto";
|
|
2066
|
+
if (choice === "auto" || choice === "required" || choice === "none") return choice;
|
|
2067
|
+
return { type: "function", name: choice.name };
|
|
2068
|
+
}
|
|
2069
|
+
async function* parseCodexResponsesStream(body, fallbackModel) {
|
|
1392
2070
|
let model = fallbackModel;
|
|
2071
|
+
let started = false;
|
|
1393
2072
|
let usage = { input: 0, output: 0 };
|
|
1394
2073
|
let stopReason = "end_turn";
|
|
1395
|
-
let
|
|
1396
|
-
let
|
|
1397
|
-
|
|
2074
|
+
let sawToolUse = false;
|
|
2075
|
+
let toolCallId;
|
|
2076
|
+
let toolArgBuf = "";
|
|
2077
|
+
const ensureStart = () => {
|
|
2078
|
+
if (started) return void 0;
|
|
2079
|
+
started = true;
|
|
2080
|
+
return { type: "message_start", model };
|
|
2081
|
+
};
|
|
1398
2082
|
for await (const msg of parseSSE(body)) {
|
|
1399
2083
|
if (!msg.data || msg.data === "[DONE]") continue;
|
|
1400
2084
|
const parsed = safeParse(msg.data);
|
|
1401
2085
|
if (!parsed.ok || !parsed.value) continue;
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
if (!thinkingOpen) {
|
|
1413
|
-
thinkingOpen = true;
|
|
1414
|
-
yield { type: "thinking_start" };
|
|
2086
|
+
const evt = parsed.value;
|
|
2087
|
+
const type = typeof evt["type"] === "string" ? evt["type"] : "";
|
|
2088
|
+
switch (type) {
|
|
2089
|
+
case "response.created":
|
|
2090
|
+
case "response.in_progress": {
|
|
2091
|
+
const resp = evt["response"];
|
|
2092
|
+
if (typeof resp?.model === "string") model = resp.model;
|
|
2093
|
+
const s = ensureStart();
|
|
2094
|
+
if (s) yield s;
|
|
2095
|
+
break;
|
|
1415
2096
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
2097
|
+
case "response.output_item.added": {
|
|
2098
|
+
const s = ensureStart();
|
|
2099
|
+
if (s) yield s;
|
|
2100
|
+
const item = evt["item"];
|
|
2101
|
+
if (!item) break;
|
|
2102
|
+
if (item.type === "reasoning") {
|
|
2103
|
+
yield { type: "thinking_start" };
|
|
2104
|
+
} else if (item.type === "function_call") {
|
|
2105
|
+
toolCallId = item.call_id ?? item.id ?? `call_${Math.random().toString(36).slice(2)}`;
|
|
2106
|
+
toolArgBuf = item.arguments ?? "";
|
|
2107
|
+
sawToolUse = true;
|
|
2108
|
+
yield { type: "tool_use_start", id: toolCallId, name: item.name ?? "unknown" };
|
|
2109
|
+
if (toolArgBuf.length > 0) {
|
|
2110
|
+
yield { type: "tool_use_input_delta", id: toolCallId, partial: toolArgBuf };
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
break;
|
|
1422
2114
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
yield { type: "thinking_stop" };
|
|
2115
|
+
case "response.output_text.delta":
|
|
2116
|
+
case "response.refusal.delta": {
|
|
2117
|
+
const delta = typeof evt["delta"] === "string" ? evt["delta"] : "";
|
|
2118
|
+
if (delta) yield { type: "text_delta", text: delta };
|
|
2119
|
+
break;
|
|
1429
2120
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
if (
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
};
|
|
1441
|
-
toolByIndex.set(idx, entry);
|
|
1442
|
-
} else {
|
|
1443
|
-
if (tc.id && !entry.id) entry.id = tc.id;
|
|
1444
|
-
if (tc.function?.name && !entry.name) entry.name = tc.function.name;
|
|
1445
|
-
}
|
|
1446
|
-
if (tc.function?.arguments) {
|
|
1447
|
-
entry.argBuf += tc.function.arguments;
|
|
1448
|
-
}
|
|
1449
|
-
if (!entry.emittedStart && entry.id && entry.name) {
|
|
1450
|
-
entry.emittedStart = true;
|
|
1451
|
-
yield { type: "tool_use_start", id: entry.id, name: entry.name };
|
|
2121
|
+
case "response.reasoning_text.delta":
|
|
2122
|
+
case "response.reasoning_summary_text.delta": {
|
|
2123
|
+
const delta = typeof evt["delta"] === "string" ? evt["delta"] : "";
|
|
2124
|
+
if (delta) yield { type: "thinking_delta", text: delta };
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
case "response.function_call_arguments.delta": {
|
|
2128
|
+
const delta = typeof evt["delta"] === "string" ? evt["delta"] : "";
|
|
2129
|
+
if (toolCallId && delta) {
|
|
2130
|
+
toolArgBuf += delta;
|
|
2131
|
+
yield { type: "tool_use_input_delta", id: toolCallId, partial: delta };
|
|
1452
2132
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
2133
|
+
break;
|
|
2134
|
+
}
|
|
2135
|
+
case "response.function_call_arguments.done": {
|
|
2136
|
+
const args = typeof evt["arguments"] === "string" ? evt["arguments"] : void 0;
|
|
2137
|
+
if (args !== void 0) toolArgBuf = args;
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
case "response.output_item.done": {
|
|
2141
|
+
const item = evt["item"];
|
|
2142
|
+
if (!item) break;
|
|
2143
|
+
if (item.type === "reasoning") {
|
|
2144
|
+
yield { type: "thinking_stop" };
|
|
2145
|
+
} else if (item.type === "function_call") {
|
|
2146
|
+
const id = item.call_id ?? toolCallId ?? `call_${Math.random().toString(36).slice(2)}`;
|
|
2147
|
+
const raw = item.arguments && item.arguments.length > 0 ? item.arguments : toolArgBuf;
|
|
2148
|
+
yield { type: "tool_use_stop", id, input: parseToolInput(raw || "{}") };
|
|
2149
|
+
toolCallId = void 0;
|
|
2150
|
+
toolArgBuf = "";
|
|
1461
2151
|
}
|
|
2152
|
+
break;
|
|
2153
|
+
}
|
|
2154
|
+
case "response.completed":
|
|
2155
|
+
case "response.incomplete": {
|
|
2156
|
+
const resp = evt["response"];
|
|
2157
|
+
if (resp?.usage) usage = normalizeUsage(resp.usage);
|
|
2158
|
+
stopReason = mapResponsesStatus(resp?.status, sawToolUse);
|
|
2159
|
+
break;
|
|
2160
|
+
}
|
|
2161
|
+
case "error":
|
|
2162
|
+
case "response.failed": {
|
|
2163
|
+
const message = evt["message"] ?? evt["response"]?.error?.message ?? "Codex response failed";
|
|
2164
|
+
throw new ProviderError(message, 502, true, "openai-codex", {
|
|
2165
|
+
body: { message }
|
|
2166
|
+
});
|
|
1462
2167
|
}
|
|
1463
2168
|
}
|
|
1464
|
-
if (choice?.finish_reason) {
|
|
1465
|
-
stopReason = normalizeOpenAI(choice.finish_reason);
|
|
1466
|
-
}
|
|
1467
|
-
const u = obj["usage"];
|
|
1468
|
-
if (u) {
|
|
1469
|
-
const hasDeepSeekCacheFields = u.prompt_cache_hit_tokens !== void 0 || u.prompt_cache_miss_tokens !== void 0;
|
|
1470
|
-
const cached = u.prompt_tokens_details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0;
|
|
1471
|
-
const promptTotal = u.prompt_tokens ?? u.input_tokens ?? (hasDeepSeekCacheFields ? (u.prompt_cache_hit_tokens ?? 0) + (u.prompt_cache_miss_tokens ?? 0) : usage.input + cached);
|
|
1472
|
-
usage = {
|
|
1473
|
-
input: u.prompt_cache_miss_tokens ?? Math.max(0, promptTotal - cached),
|
|
1474
|
-
output: u.completion_tokens ?? usage.output,
|
|
1475
|
-
cacheRead: cached || usage.cacheRead
|
|
1476
|
-
};
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
if (thinkingOpen) {
|
|
1480
|
-
yield { type: "thinking_stop" };
|
|
1481
|
-
}
|
|
1482
|
-
for (const entry of toolByIndex.values()) {
|
|
1483
|
-
if (!entry.name) continue;
|
|
1484
|
-
if (!entry.id) entry.id = `call_${randomUUID()}`;
|
|
1485
|
-
if (!entry.emittedStart) {
|
|
1486
|
-
yield { type: "tool_use_start", id: entry.id, name: entry.name };
|
|
1487
|
-
}
|
|
1488
|
-
const input = parseToolInput(entry.argBuf);
|
|
1489
|
-
yield { type: "tool_use_stop", id: entry.id, input };
|
|
1490
2169
|
}
|
|
1491
2170
|
if (started) {
|
|
1492
2171
|
yield { type: "message_stop", stopReason, usage };
|
|
1493
2172
|
}
|
|
1494
2173
|
}
|
|
2174
|
+
function normalizeUsage(u) {
|
|
2175
|
+
const cached = u.input_tokens_details?.cached_tokens ?? 0;
|
|
2176
|
+
const total = u.input_tokens ?? 0;
|
|
2177
|
+
return {
|
|
2178
|
+
input: Math.max(0, total - cached),
|
|
2179
|
+
output: u.output_tokens ?? 0,
|
|
2180
|
+
cacheRead: cached || void 0
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
function mapResponsesStatus(status, sawToolUse) {
|
|
2184
|
+
if (status === "incomplete") return "max_tokens";
|
|
2185
|
+
return sawToolUse ? "tool_use" : "end_turn";
|
|
2186
|
+
}
|
|
1495
2187
|
|
|
1496
2188
|
// src/openai-compatible.ts
|
|
1497
2189
|
var VALID_QUIRK_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -1501,7 +2193,8 @@ var VALID_QUIRK_KEYS = /* @__PURE__ */ new Set([
|
|
|
1501
2193
|
"preserveToolCallIds",
|
|
1502
2194
|
"parallelToolsDisabled",
|
|
1503
2195
|
"jsonArgumentsBuggy",
|
|
1504
|
-
"emptyToolCallContent"
|
|
2196
|
+
"emptyToolCallContent",
|
|
2197
|
+
"thinkingParam"
|
|
1505
2198
|
]);
|
|
1506
2199
|
function isCompatibilityQuirks(value) {
|
|
1507
2200
|
if (value === void 0) return true;
|
|
@@ -1511,6 +2204,8 @@ function isCompatibilityQuirks(value) {
|
|
|
1511
2204
|
if (!VALID_QUIRK_KEYS.has(key)) return false;
|
|
1512
2205
|
if (key === "emptyToolCallContent") {
|
|
1513
2206
|
if (v !== "null" && v !== "empty_string") return false;
|
|
2207
|
+
} else if (key === "thinkingParam") {
|
|
2208
|
+
if (v !== "zai-glm" && v !== "kimi-toggle" && v !== "always-on") return false;
|
|
1514
2209
|
} else if (typeof v !== "boolean") {
|
|
1515
2210
|
return false;
|
|
1516
2211
|
}
|
|
@@ -1551,6 +2246,11 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
|
|
|
1551
2246
|
tokenLimitParam() {
|
|
1552
2247
|
return "max_tokens";
|
|
1553
2248
|
}
|
|
2249
|
+
buildBody(req) {
|
|
2250
|
+
const body = super.buildBody(req);
|
|
2251
|
+
applyThinkingParams(body, req, this.opts.quirks?.thinkingParam);
|
|
2252
|
+
return body;
|
|
2253
|
+
}
|
|
1554
2254
|
buildHeaders(req) {
|
|
1555
2255
|
return {
|
|
1556
2256
|
...super.buildHeaders(req),
|
|
@@ -1558,6 +2258,36 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
|
|
|
1558
2258
|
};
|
|
1559
2259
|
}
|
|
1560
2260
|
};
|
|
2261
|
+
function applyThinkingParams(body, req, mode) {
|
|
2262
|
+
if (!mode || !req.reasoning) return;
|
|
2263
|
+
if (mode === "always-on") {
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
if (req.reasoning.enabled === false) {
|
|
2267
|
+
body["thinking"] = { type: "disabled" };
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
if (mode === "kimi-toggle" && req.reasoning.enabled === true) {
|
|
2271
|
+
body["thinking"] = { type: "enabled" };
|
|
2272
|
+
}
|
|
2273
|
+
if (mode === "zai-glm" && req.reasoning.effort) {
|
|
2274
|
+
body["reasoning_effort"] = mapZaiReasoningEffort(req.reasoning.effort);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
function mapZaiReasoningEffort(effort) {
|
|
2278
|
+
switch (effort) {
|
|
2279
|
+
case "none":
|
|
2280
|
+
case "minimal":
|
|
2281
|
+
return "none";
|
|
2282
|
+
case "low":
|
|
2283
|
+
case "medium":
|
|
2284
|
+
return "high";
|
|
2285
|
+
case "xhigh":
|
|
2286
|
+
return "max";
|
|
2287
|
+
default:
|
|
2288
|
+
return effort;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
1561
2291
|
|
|
1562
2292
|
// src/wire-format.ts
|
|
1563
2293
|
var WireFormatProvider = class extends WireAdapter {
|
|
@@ -2158,14 +2888,17 @@ function buildGenConfig(req) {
|
|
|
2158
2888
|
return cfg;
|
|
2159
2889
|
}
|
|
2160
2890
|
function toolsToGemini2(tools) {
|
|
2161
|
-
return tools.map((t) =>
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2891
|
+
return tools.map((t) => {
|
|
2892
|
+
const compact = compactToolDefinitionForWire(t);
|
|
2893
|
+
return {
|
|
2894
|
+
name: compact.name,
|
|
2895
|
+
description: compact.description,
|
|
2896
|
+
parameters: sanitizeSchemaForGemini2(compact.inputSchema) ?? {
|
|
2897
|
+
type: "object",
|
|
2898
|
+
properties: {}
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
});
|
|
2169
2902
|
}
|
|
2170
2903
|
var GEMINI_ALLOWED_KEYS2 = /* @__PURE__ */ new Set([
|
|
2171
2904
|
"type",
|
|
@@ -2350,9 +3083,6 @@ function normalizeToolResultContent(raw, opts) {
|
|
|
2350
3083
|
if (raw === void 0 || raw === null) return "";
|
|
2351
3084
|
return JSON.stringify(raw);
|
|
2352
3085
|
}
|
|
2353
|
-
function isPlainObject(v) {
|
|
2354
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2355
|
-
}
|
|
2356
3086
|
function contentFromOpenAI(choice, opts = {}) {
|
|
2357
3087
|
const out = [];
|
|
2358
3088
|
const text = choice.message.content;
|
|
@@ -2419,6 +3149,11 @@ function parseToolArguments(raw, toolName, toolCallId, opts) {
|
|
|
2419
3149
|
}
|
|
2420
3150
|
|
|
2421
3151
|
// src/index.ts
|
|
3152
|
+
var _oauthPersist;
|
|
3153
|
+
function setOAuthTokenPersister(fn) {
|
|
3154
|
+
_oauthPersist = fn;
|
|
3155
|
+
}
|
|
3156
|
+
var setCodexTokenPersister = setOAuthTokenPersister;
|
|
2422
3157
|
async function buildProviderFactoriesFromRegistry(opts) {
|
|
2423
3158
|
const providers = await opts.registry.listProviders();
|
|
2424
3159
|
const factories = [];
|
|
@@ -2459,6 +3194,13 @@ function resolveActiveKey(cfg) {
|
|
|
2459
3194
|
}
|
|
2460
3195
|
return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
|
|
2461
3196
|
}
|
|
3197
|
+
function resolveActiveKeyEntry(cfg) {
|
|
3198
|
+
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
3199
|
+
const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
|
|
3200
|
+
return active ?? cfg.apiKeys[0];
|
|
3201
|
+
}
|
|
3202
|
+
return void 0;
|
|
3203
|
+
}
|
|
2462
3204
|
function makeProvider(p, cfg) {
|
|
2463
3205
|
const family = cfg.family ?? p.family;
|
|
2464
3206
|
const envVars = cfg.envVars && cfg.envVars.length > 0 ? cfg.envVars : p.envVars;
|
|
@@ -2497,6 +3239,48 @@ function makeProvider(p, cfg) {
|
|
|
2497
3239
|
headers: cfg.headers,
|
|
2498
3240
|
quirks: validateQuirks(p.id, cfg.quirks)
|
|
2499
3241
|
});
|
|
3242
|
+
case "openai-codex": {
|
|
3243
|
+
const entry = resolveActiveKeyEntry(cfg);
|
|
3244
|
+
const parsedExpiry = entry?.expiresAt ? Date.parse(entry.expiresAt) : Number.NaN;
|
|
3245
|
+
return new OpenAICodexProvider({
|
|
3246
|
+
id: p.id,
|
|
3247
|
+
baseUrl,
|
|
3248
|
+
credentials: {
|
|
3249
|
+
accessToken: expectDefined(apiKey),
|
|
3250
|
+
refreshToken: entry?.refreshToken,
|
|
3251
|
+
expiresAt: Number.isFinite(parsedExpiry) ? parsedExpiry : void 0,
|
|
3252
|
+
accountId: entry?.accountId
|
|
3253
|
+
},
|
|
3254
|
+
onRefresh: (creds) => _oauthPersist?.(p.id, creds)
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
case "anthropic-oauth": {
|
|
3258
|
+
const entry = resolveActiveKeyEntry(cfg);
|
|
3259
|
+
const parsedExpiry = entry?.expiresAt ? Date.parse(entry.expiresAt) : Number.NaN;
|
|
3260
|
+
return new AnthropicOAuthProvider({
|
|
3261
|
+
id: p.id,
|
|
3262
|
+
baseUrl,
|
|
3263
|
+
credentials: {
|
|
3264
|
+
accessToken: expectDefined(apiKey),
|
|
3265
|
+
refreshToken: entry?.refreshToken,
|
|
3266
|
+
expiresAt: Number.isFinite(parsedExpiry) ? parsedExpiry : void 0
|
|
3267
|
+
},
|
|
3268
|
+
onRefresh: (creds) => _oauthPersist?.(p.id, creds)
|
|
3269
|
+
});
|
|
3270
|
+
}
|
|
3271
|
+
case "github-copilot": {
|
|
3272
|
+
const entry = resolveActiveKeyEntry(cfg);
|
|
3273
|
+
const parsedExpiry = entry?.expiresAt ? Date.parse(entry.expiresAt) : Number.NaN;
|
|
3274
|
+
return new GitHubCopilotProvider({
|
|
3275
|
+
id: p.id,
|
|
3276
|
+
credentials: {
|
|
3277
|
+
copilotToken: resolveActiveKey(cfg) ?? "",
|
|
3278
|
+
githubToken: entry?.refreshToken,
|
|
3279
|
+
expiresAt: Number.isFinite(parsedExpiry) ? parsedExpiry : void 0
|
|
3280
|
+
},
|
|
3281
|
+
onRefresh: (creds) => _oauthPersist?.(p.id, creds)
|
|
3282
|
+
});
|
|
3283
|
+
}
|
|
2500
3284
|
case "google":
|
|
2501
3285
|
return new GoogleProvider({ id: p.id, apiKey: expectDefined(apiKey), baseUrl });
|
|
2502
3286
|
default:
|
|
@@ -2541,6 +3325,6 @@ function validateQuirks(providerId, quirks) {
|
|
|
2541
3325
|
});
|
|
2542
3326
|
}
|
|
2543
3327
|
|
|
2544
|
-
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 };
|
|
3328
|
+
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 };
|
|
2545
3329
|
//# sourceMappingURL=index.js.map
|
|
2546
3330
|
//# sourceMappingURL=index.js.map
|