@kenkaiiii/gg-ai 4.3.237 → 4.3.239
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.cjs +274 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +273 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
// src/errors.ts
|
|
2
|
+
function readHeader(headers, ...names) {
|
|
3
|
+
if (!headers) return void 0;
|
|
4
|
+
const getter = typeof headers.get === "function" ? (name) => headers.get(name) ?? void 0 : typeof headers === "object" ? (name) => {
|
|
5
|
+
const rec = headers;
|
|
6
|
+
const value = rec[name] ?? rec[name.toLowerCase()];
|
|
7
|
+
return typeof value === "string" ? value : void 0;
|
|
8
|
+
} : void 0;
|
|
9
|
+
if (!getter) return void 0;
|
|
10
|
+
for (const name of names) {
|
|
11
|
+
const value = getter(name);
|
|
12
|
+
if (value != null) return value;
|
|
13
|
+
}
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
2
16
|
var GGAIError = class extends Error {
|
|
3
17
|
source;
|
|
4
18
|
requestId;
|
|
@@ -51,6 +65,10 @@ function isUsageLimitError(err) {
|
|
|
51
65
|
if (!(err instanceof Error)) return false;
|
|
52
66
|
return /usage limit reached/i.test(err.message);
|
|
53
67
|
}
|
|
68
|
+
function isHardBillingMessage(message) {
|
|
69
|
+
const lower = message.toLowerCase();
|
|
70
|
+
return lower.includes("insufficient balance") || lower.includes("insufficient credits") || lower.includes("more credits") || lower.includes("insufficient_quota") || lower.includes("exceeded your current quota") || lower.includes("quota exceeded") || lower.includes("no resource package") || lower.includes("recharge") || lower.includes("balance is too low") || lower.includes("out of credits") || lower.includes("arrears") || lower.includes("arrearage") || lower.includes("token quota") || lower.includes("exceeded_current_quota_error") || lower.includes("check your account balance") || lower.includes("does not yet include access") || lower.includes("subscription plan") || lower.includes("billing");
|
|
71
|
+
}
|
|
54
72
|
function formatResetTime(resetsAt) {
|
|
55
73
|
const when = new Date(resetsAt * 1e3);
|
|
56
74
|
const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
@@ -300,6 +318,9 @@ function zodToJsonSchema(schema) {
|
|
|
300
318
|
const { $schema: _schema, ...rest } = jsonSchema;
|
|
301
319
|
return normalizeRootForAnthropic(rest);
|
|
302
320
|
}
|
|
321
|
+
function resolveToolSchema(tool) {
|
|
322
|
+
return tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
|
|
323
|
+
}
|
|
303
324
|
function normalizeRootForAnthropic(schema) {
|
|
304
325
|
const branches = schema.oneOf ?? schema.anyOf;
|
|
305
326
|
if (!branches || branches.length === 0) {
|
|
@@ -413,6 +434,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
|
|
|
413
434
|
}
|
|
414
435
|
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
415
436
|
var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
437
|
+
var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
|
|
416
438
|
function stripImages(content, placeholder) {
|
|
417
439
|
const out = [];
|
|
418
440
|
let lastWasPlaceholder = false;
|
|
@@ -423,10 +445,33 @@ function stripImages(content, placeholder) {
|
|
|
423
445
|
continue;
|
|
424
446
|
}
|
|
425
447
|
out.push(block);
|
|
426
|
-
lastWasPlaceholder = block.text === placeholder;
|
|
448
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
427
449
|
}
|
|
428
450
|
return out;
|
|
429
451
|
}
|
|
452
|
+
function stripVideos(content, placeholder) {
|
|
453
|
+
const out = [];
|
|
454
|
+
let lastWasPlaceholder = false;
|
|
455
|
+
for (const block of content) {
|
|
456
|
+
if (block.type === "video") {
|
|
457
|
+
if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
|
|
458
|
+
lastWasPlaceholder = true;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
out.push(block);
|
|
462
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
function downgradeUnsupportedVideos(messages, supportsVideo) {
|
|
467
|
+
if (supportsVideo === true) return messages;
|
|
468
|
+
return messages.map((msg) => {
|
|
469
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
470
|
+
return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
|
|
471
|
+
}
|
|
472
|
+
return msg;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
430
475
|
function downgradeUnsupportedImages(messages, supportsImages) {
|
|
431
476
|
if (supportsImages !== false) return messages;
|
|
432
477
|
return messages.map((msg) => {
|
|
@@ -503,6 +548,16 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
503
548
|
role: "user",
|
|
504
549
|
content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
|
|
505
550
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
551
|
+
if (part.type === "video") {
|
|
552
|
+
return {
|
|
553
|
+
type: "video",
|
|
554
|
+
source: {
|
|
555
|
+
type: "base64",
|
|
556
|
+
media_type: part.mediaType,
|
|
557
|
+
data: part.data
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
506
561
|
return {
|
|
507
562
|
type: "image",
|
|
508
563
|
source: {
|
|
@@ -602,11 +657,11 @@ function toAnthropicToolChoice(choice) {
|
|
|
602
657
|
if (choice === "required") return { type: "any" };
|
|
603
658
|
return { type: "tool", name: choice.name };
|
|
604
659
|
}
|
|
605
|
-
function
|
|
606
|
-
return /opus-4
|
|
660
|
+
function isAdaptiveThinkingModel(model) {
|
|
661
|
+
return /opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-4[-.]6/.test(model);
|
|
607
662
|
}
|
|
608
663
|
function toAnthropicThinking(level, maxTokens, model) {
|
|
609
|
-
if (
|
|
664
|
+
if (isAdaptiveThinkingModel(model)) {
|
|
610
665
|
let effort = level;
|
|
611
666
|
if (effort === "xhigh" && !/opus-4-8|opus-4-7/.test(model)) {
|
|
612
667
|
effort = "high";
|
|
@@ -663,6 +718,14 @@ function toOpenAIMessages(messages, options) {
|
|
|
663
718
|
content: msg.content.map(
|
|
664
719
|
(part) => {
|
|
665
720
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
721
|
+
if (part.type === "video") {
|
|
722
|
+
return {
|
|
723
|
+
type: "video_url",
|
|
724
|
+
video_url: {
|
|
725
|
+
url: `data:${part.mediaType};base64,${part.data}`
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
666
729
|
return {
|
|
667
730
|
type: "image_url",
|
|
668
731
|
image_url: {
|
|
@@ -740,7 +803,7 @@ function toOpenAITools(tools) {
|
|
|
740
803
|
function: {
|
|
741
804
|
name: tool.name,
|
|
742
805
|
description: tool.description,
|
|
743
|
-
parameters:
|
|
806
|
+
parameters: resolveToolSchema(tool)
|
|
744
807
|
}
|
|
745
808
|
}));
|
|
746
809
|
}
|
|
@@ -782,10 +845,22 @@ function normalizeOpenAIStopReason(reason) {
|
|
|
782
845
|
}
|
|
783
846
|
}
|
|
784
847
|
|
|
785
|
-
// src/
|
|
848
|
+
// src/utils/json.ts
|
|
786
849
|
function isJsonObject(value) {
|
|
787
850
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
788
851
|
}
|
|
852
|
+
function parseToolArguments(argsJson) {
|
|
853
|
+
if (!argsJson) return {};
|
|
854
|
+
try {
|
|
855
|
+
const parsed = JSON.parse(argsJson);
|
|
856
|
+
const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
|
|
857
|
+
return isJsonObject(unwrapped) ? unwrapped : {};
|
|
858
|
+
} catch {
|
|
859
|
+
return {};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/providers/anthropic.ts
|
|
789
864
|
function createClient(options) {
|
|
790
865
|
const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
|
|
791
866
|
return new Anthropic({
|
|
@@ -816,7 +891,8 @@ async function* runStream(options) {
|
|
|
816
891
|
const useStreaming = options.streaming !== false;
|
|
817
892
|
const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
|
|
818
893
|
const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
|
|
819
|
-
const
|
|
894
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
895
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
820
896
|
const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
|
|
821
897
|
const system = isOAuth ? [
|
|
822
898
|
{
|
|
@@ -878,7 +954,7 @@ async function* runStream(options) {
|
|
|
878
954
|
})(),
|
|
879
955
|
stream: useStreaming
|
|
880
956
|
};
|
|
881
|
-
const hasAdaptiveThinking =
|
|
957
|
+
const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
|
|
882
958
|
const betaHeaders = [
|
|
883
959
|
...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
|
|
884
960
|
...options.compaction ? ["compact-2026-01-12"] : [],
|
|
@@ -1022,11 +1098,18 @@ async function* runStream(options) {
|
|
|
1022
1098
|
args: tc.args
|
|
1023
1099
|
};
|
|
1024
1100
|
} else if (accum.type === "server_tool_use") {
|
|
1101
|
+
let input = accum.input;
|
|
1102
|
+
if (accum.argsJson) {
|
|
1103
|
+
try {
|
|
1104
|
+
input = JSON.parse(accum.argsJson);
|
|
1105
|
+
} catch {
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1025
1108
|
const stc = {
|
|
1026
1109
|
type: "server_tool_call",
|
|
1027
1110
|
id: accum.toolId,
|
|
1028
1111
|
name: accum.toolName,
|
|
1029
|
-
input
|
|
1112
|
+
input
|
|
1030
1113
|
};
|
|
1031
1114
|
contentParts.push(stc);
|
|
1032
1115
|
yield {
|
|
@@ -1203,19 +1286,13 @@ function messageToResponse(message) {
|
|
|
1203
1286
|
};
|
|
1204
1287
|
}
|
|
1205
1288
|
function readUnifiedRateLimit(headers) {
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
return typeof value === "string" ? value : null;
|
|
1214
|
-
}
|
|
1215
|
-
return null;
|
|
1216
|
-
};
|
|
1217
|
-
const status = get("anthropic-ratelimit-unified-status");
|
|
1218
|
-
const resetRaw = get("anthropic-ratelimit-unified-reset") ?? get("anthropic-ratelimit-unified-5h-reset") ?? get("anthropic-ratelimit-unified-7d-reset");
|
|
1289
|
+
const status = readHeader(headers, "anthropic-ratelimit-unified-status");
|
|
1290
|
+
const resetRaw = readHeader(
|
|
1291
|
+
headers,
|
|
1292
|
+
"anthropic-ratelimit-unified-reset",
|
|
1293
|
+
"anthropic-ratelimit-unified-5h-reset",
|
|
1294
|
+
"anthropic-ratelimit-unified-7d-reset"
|
|
1295
|
+
);
|
|
1219
1296
|
const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
|
|
1220
1297
|
const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
|
|
1221
1298
|
return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
|
|
@@ -1240,6 +1317,14 @@ function toError(err) {
|
|
|
1240
1317
|
});
|
|
1241
1318
|
}
|
|
1242
1319
|
}
|
|
1320
|
+
if (isHardBillingMessage(message)) {
|
|
1321
|
+
const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
1322
|
+
return new ProviderError("anthropic", usageMessage, {
|
|
1323
|
+
statusCode: err.status,
|
|
1324
|
+
...requestId ? { requestId } : {},
|
|
1325
|
+
cause: err
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1243
1328
|
return new ProviderError("anthropic", message, {
|
|
1244
1329
|
statusCode: err.status,
|
|
1245
1330
|
...requestId ? { requestId } : {},
|
|
@@ -1272,19 +1357,30 @@ function fnv1aHash(value) {
|
|
|
1272
1357
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1273
1358
|
}
|
|
1274
1359
|
|
|
1275
|
-
// src/
|
|
1276
|
-
function
|
|
1277
|
-
return
|
|
1360
|
+
// src/utils/env.ts
|
|
1361
|
+
function getEnvironment() {
|
|
1362
|
+
return globalThis.process?.env;
|
|
1278
1363
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1364
|
+
|
|
1365
|
+
// src/providers/openai.ts
|
|
1366
|
+
function extractOpenAIUsage(usage) {
|
|
1367
|
+
let cacheRead = 0;
|
|
1368
|
+
const details = usage.prompt_tokens_details;
|
|
1369
|
+
if (details?.cached_tokens) {
|
|
1370
|
+
cacheRead = details.cached_tokens;
|
|
1371
|
+
}
|
|
1372
|
+
const usageAny = usage;
|
|
1373
|
+
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1374
|
+
cacheRead = usageAny.cached_tokens;
|
|
1287
1375
|
}
|
|
1376
|
+
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1377
|
+
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
inputTokens: usage.prompt_tokens - cacheRead,
|
|
1381
|
+
outputTokens: usage.completion_tokens,
|
|
1382
|
+
cacheRead
|
|
1383
|
+
};
|
|
1288
1384
|
}
|
|
1289
1385
|
function createClient2(options) {
|
|
1290
1386
|
return new OpenAI({
|
|
@@ -1301,7 +1397,8 @@ async function* runStream2(options) {
|
|
|
1301
1397
|
const useStreaming = options.streaming !== false;
|
|
1302
1398
|
const client = createClient2(options);
|
|
1303
1399
|
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
|
|
1304
|
-
const
|
|
1400
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1401
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1305
1402
|
const messages = toOpenAIMessages(downgradedMessages, {
|
|
1306
1403
|
provider: options.provider,
|
|
1307
1404
|
thinking: !!options.thinking,
|
|
@@ -1340,7 +1437,7 @@ async function* runStream2(options) {
|
|
|
1340
1437
|
params.thinking = { type: "disabled" };
|
|
1341
1438
|
}
|
|
1342
1439
|
}
|
|
1343
|
-
if (
|
|
1440
|
+
if (getEnvironment()?.GGAI_DUMP_REQUEST) {
|
|
1344
1441
|
const fs = await import("fs");
|
|
1345
1442
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1346
1443
|
const dumpPath = `/tmp/ggai-request-${ts}.json`;
|
|
@@ -1381,19 +1478,7 @@ async function* runStream2(options) {
|
|
|
1381
1478
|
for await (const chunk of stream2) {
|
|
1382
1479
|
const choice = chunk.choices?.[0];
|
|
1383
1480
|
if (chunk.usage) {
|
|
1384
|
-
outputTokens = chunk.usage
|
|
1385
|
-
const details = chunk.usage.prompt_tokens_details;
|
|
1386
|
-
if (details?.cached_tokens) {
|
|
1387
|
-
cacheRead = details.cached_tokens;
|
|
1388
|
-
}
|
|
1389
|
-
const usageAny = chunk.usage;
|
|
1390
|
-
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1391
|
-
cacheRead = usageAny.cached_tokens;
|
|
1392
|
-
}
|
|
1393
|
-
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1394
|
-
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1395
|
-
}
|
|
1396
|
-
inputTokens = chunk.usage.prompt_tokens - cacheRead;
|
|
1481
|
+
({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
|
|
1397
1482
|
}
|
|
1398
1483
|
if (!choice) continue;
|
|
1399
1484
|
if (choice.finish_reason) {
|
|
@@ -1539,17 +1624,7 @@ function completionToResponse(completion) {
|
|
|
1539
1624
|
let outputTokens = 0;
|
|
1540
1625
|
let cacheRead = 0;
|
|
1541
1626
|
if (completion.usage) {
|
|
1542
|
-
outputTokens = completion.usage
|
|
1543
|
-
const details = completion.usage.prompt_tokens_details;
|
|
1544
|
-
if (details?.cached_tokens) cacheRead = details.cached_tokens;
|
|
1545
|
-
const usageAny = completion.usage;
|
|
1546
|
-
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1547
|
-
cacheRead = usageAny.cached_tokens;
|
|
1548
|
-
}
|
|
1549
|
-
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1550
|
-
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1551
|
-
}
|
|
1552
|
-
inputTokens = completion.usage.prompt_tokens - cacheRead;
|
|
1627
|
+
({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
|
|
1553
1628
|
}
|
|
1554
1629
|
const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
|
|
1555
1630
|
return {
|
|
@@ -1561,6 +1636,16 @@ function completionToResponse(completion) {
|
|
|
1561
1636
|
usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
|
|
1562
1637
|
};
|
|
1563
1638
|
}
|
|
1639
|
+
function classifyOpenAICompatLimit(args) {
|
|
1640
|
+
const { status, code, type, message } = args;
|
|
1641
|
+
const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
|
|
1642
|
+
const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
|
|
1643
|
+
if (isHard) return "hard";
|
|
1644
|
+
if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
|
|
1645
|
+
return "transient";
|
|
1646
|
+
}
|
|
1647
|
+
return null;
|
|
1648
|
+
}
|
|
1564
1649
|
function toError2(err, provider = "openai") {
|
|
1565
1650
|
if (err instanceof OpenAI.APIError) {
|
|
1566
1651
|
const body = err.error;
|
|
@@ -1572,6 +1657,35 @@ function toError2(err, provider = "openai") {
|
|
|
1572
1657
|
hint = "codex-mini-latest requires an OpenAI Pro or Max subscription. Your account currently has access to GPT-5.4 and GPT-5.4 Mini.";
|
|
1573
1658
|
}
|
|
1574
1659
|
const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
|
|
1660
|
+
const code = typeof err.code === "string" ? err.code : void 0;
|
|
1661
|
+
const type = typeof err.type === "string" ? err.type : void 0;
|
|
1662
|
+
const limit = classifyOpenAICompatLimit({
|
|
1663
|
+
status: err.status,
|
|
1664
|
+
code,
|
|
1665
|
+
type,
|
|
1666
|
+
message: cleanMessage
|
|
1667
|
+
});
|
|
1668
|
+
if (limit === "hard") {
|
|
1669
|
+
const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
|
|
1670
|
+
return new ProviderError(provider, message, {
|
|
1671
|
+
statusCode: err.status,
|
|
1672
|
+
...requestId ? { requestId } : {},
|
|
1673
|
+
...hint ? { hint } : {},
|
|
1674
|
+
cause: err
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
if (limit === "transient") {
|
|
1678
|
+
const retryAfterRaw = readHeader(err.headers, "retry-after");
|
|
1679
|
+
const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
|
|
1680
|
+
const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
|
|
1681
|
+
return new ProviderError(provider, cleanMessage, {
|
|
1682
|
+
statusCode: err.status,
|
|
1683
|
+
...requestId ? { requestId } : {},
|
|
1684
|
+
...hint ? { hint } : {},
|
|
1685
|
+
...resetsAt ? { resetsAt } : {},
|
|
1686
|
+
cause: err
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1575
1689
|
return new ProviderError(provider, cleanMessage, {
|
|
1576
1690
|
statusCode: err.status,
|
|
1577
1691
|
...requestId ? { requestId } : {},
|
|
@@ -1597,21 +1711,59 @@ function providerDiag(phase, data) {
|
|
|
1597
1711
|
_diagFn?.(phase, data);
|
|
1598
1712
|
}
|
|
1599
1713
|
|
|
1600
|
-
// src/
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1714
|
+
// src/utils/sse.ts
|
|
1715
|
+
function parseSseBuffer(buffer) {
|
|
1716
|
+
const events = [];
|
|
1717
|
+
let cursor = 0;
|
|
1718
|
+
while (true) {
|
|
1719
|
+
const next = buffer.indexOf("\n\n", cursor);
|
|
1720
|
+
if (next === -1) break;
|
|
1721
|
+
const raw = buffer.slice(cursor, next);
|
|
1722
|
+
cursor = next + 2;
|
|
1723
|
+
let eventName;
|
|
1724
|
+
const dataLines = [];
|
|
1725
|
+
for (const line of raw.split("\n")) {
|
|
1726
|
+
if (line.startsWith("event:")) {
|
|
1727
|
+
eventName = line.slice("event:".length).trim();
|
|
1728
|
+
} else if (line.startsWith("data:")) {
|
|
1729
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (dataLines.length > 0) {
|
|
1733
|
+
events.push({ event: eventName, data: dataLines.join("\n") });
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return { events, remaining: buffer.slice(cursor) };
|
|
1604
1737
|
}
|
|
1605
|
-
function
|
|
1606
|
-
|
|
1738
|
+
async function* readSseStream(body) {
|
|
1739
|
+
const reader = body.getReader();
|
|
1740
|
+
const decoder = new TextDecoder();
|
|
1741
|
+
let buffer = "";
|
|
1607
1742
|
try {
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1743
|
+
while (true) {
|
|
1744
|
+
const { done, value } = await reader.read();
|
|
1745
|
+
if (done) break;
|
|
1746
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
1747
|
+
const parsed2 = parseSseBuffer(buffer);
|
|
1748
|
+
buffer = parsed2.remaining;
|
|
1749
|
+
yield* parsed2.events;
|
|
1750
|
+
}
|
|
1751
|
+
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
1752
|
+
const parsed = parseSseBuffer(buffer + "\n\n");
|
|
1753
|
+
yield* parsed.events;
|
|
1754
|
+
} finally {
|
|
1755
|
+
reader.releaseLock();
|
|
1613
1756
|
}
|
|
1614
1757
|
}
|
|
1758
|
+
|
|
1759
|
+
// src/utils/request-id.ts
|
|
1760
|
+
function extractRequestIdFromMessage(message) {
|
|
1761
|
+
const match = message.match(/request ID ([a-z0-9-]{8,})/i);
|
|
1762
|
+
return match?.[1];
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// src/providers/openai-codex.ts
|
|
1766
|
+
var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
1615
1767
|
function outputTextKey(itemId, contentIndex) {
|
|
1616
1768
|
return `${itemId ?? ""}:${contentIndex ?? 0}`;
|
|
1617
1769
|
}
|
|
@@ -1624,7 +1776,8 @@ function streamOpenAICodex(options) {
|
|
|
1624
1776
|
async function* runStream3(options) {
|
|
1625
1777
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1626
1778
|
const url = `${baseUrl}/codex/responses`;
|
|
1627
|
-
const
|
|
1779
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1780
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1628
1781
|
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
1629
1782
|
const body = {
|
|
1630
1783
|
model: options.model,
|
|
@@ -1676,7 +1829,7 @@ async function* runStream3(options) {
|
|
|
1676
1829
|
const text = await response.text().catch(() => "");
|
|
1677
1830
|
const parsed = parseCodexErrorBody(text);
|
|
1678
1831
|
const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
|
|
1679
|
-
const requestId = parsed.requestId ?? response.headers
|
|
1832
|
+
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
1680
1833
|
const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
|
|
1681
1834
|
if (usageLimit) throw usageLimit;
|
|
1682
1835
|
let hint;
|
|
@@ -1720,7 +1873,7 @@ async function* runStream3(options) {
|
|
|
1720
1873
|
const nested = event.error ?? void 0;
|
|
1721
1874
|
const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
|
|
1722
1875
|
const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
|
|
1723
|
-
const requestId =
|
|
1876
|
+
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
1724
1877
|
const usageLimit = codexUsageLimitError(
|
|
1725
1878
|
nested ?? event,
|
|
1726
1879
|
void 0,
|
|
@@ -1735,7 +1888,7 @@ async function* runStream3(options) {
|
|
|
1735
1888
|
if (type === "response.failed") {
|
|
1736
1889
|
const nested = event.error;
|
|
1737
1890
|
const message = nested?.message ?? "Codex response failed.";
|
|
1738
|
-
const requestId =
|
|
1891
|
+
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
1739
1892
|
throw new ProviderError("openai", message, {
|
|
1740
1893
|
...requestId != null ? { requestId } : {}
|
|
1741
1894
|
});
|
|
@@ -1854,7 +2007,7 @@ async function* runStream3(options) {
|
|
|
1854
2007
|
const id = `${callId}|${itemId}`;
|
|
1855
2008
|
const tc = toolCalls.get(id);
|
|
1856
2009
|
if (tc) {
|
|
1857
|
-
const args =
|
|
2010
|
+
const args = parseToolArguments(tc.argsJson);
|
|
1858
2011
|
yield {
|
|
1859
2012
|
type: "toolcall_done",
|
|
1860
2013
|
id: tc.id,
|
|
@@ -1878,7 +2031,7 @@ async function* runStream3(options) {
|
|
|
1878
2031
|
contentParts.push({ type: "text", text: textAccum });
|
|
1879
2032
|
}
|
|
1880
2033
|
for (const [, tc] of toolCalls) {
|
|
1881
|
-
const args =
|
|
2034
|
+
const args = parseToolArguments(tc.argsJson);
|
|
1882
2035
|
const toolCall = {
|
|
1883
2036
|
type: "tool_call",
|
|
1884
2037
|
id: tc.id,
|
|
@@ -1901,33 +2054,13 @@ async function* runStream3(options) {
|
|
|
1901
2054
|
return streamResponse;
|
|
1902
2055
|
}
|
|
1903
2056
|
async function* parseSSE(body) {
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
if (done) break;
|
|
1911
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1912
|
-
let idx = buffer.indexOf("\n\n");
|
|
1913
|
-
while (idx !== -1) {
|
|
1914
|
-
const chunk = buffer.slice(0, idx);
|
|
1915
|
-
buffer = buffer.slice(idx + 2);
|
|
1916
|
-
const dataLines = chunk.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
|
|
1917
|
-
if (dataLines.length > 0) {
|
|
1918
|
-
const data = dataLines.join("\n").trim();
|
|
1919
|
-
if (data && data !== "[DONE]") {
|
|
1920
|
-
try {
|
|
1921
|
-
yield JSON.parse(data);
|
|
1922
|
-
} catch {
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
idx = buffer.indexOf("\n\n");
|
|
1927
|
-
}
|
|
2057
|
+
for await (const event of readSseStream(body)) {
|
|
2058
|
+
const data = event.data.trim();
|
|
2059
|
+
if (!data || data === "[DONE]") continue;
|
|
2060
|
+
try {
|
|
2061
|
+
yield JSON.parse(data);
|
|
2062
|
+
} catch {
|
|
1928
2063
|
}
|
|
1929
|
-
} finally {
|
|
1930
|
-
reader.releaseLock();
|
|
1931
2064
|
}
|
|
1932
2065
|
}
|
|
1933
2066
|
function remapCodexId(id, idMap) {
|
|
@@ -1938,10 +2071,6 @@ function remapCodexId(id, idMap) {
|
|
|
1938
2071
|
idMap.set(id, mapped);
|
|
1939
2072
|
return mapped;
|
|
1940
2073
|
}
|
|
1941
|
-
function codexToolResultText(content) {
|
|
1942
|
-
if (typeof content === "string") return content;
|
|
1943
|
-
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
1944
|
-
}
|
|
1945
2074
|
function toCodexInput(messages, options) {
|
|
1946
2075
|
let system;
|
|
1947
2076
|
const input = [];
|
|
@@ -1998,7 +2127,7 @@ function toCodexInput(messages, options) {
|
|
|
1998
2127
|
const toolImages = [];
|
|
1999
2128
|
for (const result of msg.content) {
|
|
2000
2129
|
const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
|
|
2001
|
-
const text =
|
|
2130
|
+
const text = toolResultText(result.content);
|
|
2002
2131
|
input.push({
|
|
2003
2132
|
type: "function_call_output",
|
|
2004
2133
|
call_id: remapCodexId(callId, idMap),
|
|
@@ -2033,14 +2162,10 @@ function toCodexTools(tools) {
|
|
|
2033
2162
|
type: "function",
|
|
2034
2163
|
name: tool.name,
|
|
2035
2164
|
description: tool.description,
|
|
2036
|
-
parameters:
|
|
2165
|
+
parameters: resolveToolSchema(tool),
|
|
2037
2166
|
strict: null
|
|
2038
2167
|
}));
|
|
2039
2168
|
}
|
|
2040
|
-
function extractCodexRequestId(message) {
|
|
2041
|
-
const match = message.match(/request ID ([a-z0-9-]{8,})/i);
|
|
2042
|
-
return match?.[1];
|
|
2043
|
-
}
|
|
2044
2169
|
function parseCodexErrorBody(text) {
|
|
2045
2170
|
if (!text) return {};
|
|
2046
2171
|
try {
|
|
@@ -2048,7 +2173,7 @@ function parseCodexErrorBody(text) {
|
|
|
2048
2173
|
const error = parsed.error;
|
|
2049
2174
|
const detail = parsed.detail;
|
|
2050
2175
|
const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
|
|
2051
|
-
const requestId = parsed.request_id ?? error?.request_id ?? (message ?
|
|
2176
|
+
const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
|
|
2052
2177
|
const errorObj = error ?? parsed;
|
|
2053
2178
|
return {
|
|
2054
2179
|
...message ? { message } : {},
|
|
@@ -2098,12 +2223,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
|
|
|
2098
2223
|
"gemma-4-31b-it",
|
|
2099
2224
|
"gemma-4-26b-a4b-it"
|
|
2100
2225
|
]);
|
|
2101
|
-
function isJsonObject4(value) {
|
|
2102
|
-
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2103
|
-
}
|
|
2104
|
-
function getEnvironment() {
|
|
2105
|
-
return globalThis.process?.env;
|
|
2106
|
-
}
|
|
2107
2226
|
function getGoogleProject(options) {
|
|
2108
2227
|
const env = getEnvironment();
|
|
2109
2228
|
return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
|
|
@@ -2125,6 +2244,20 @@ ${formatUnsupportedModelMessage(model)}`;
|
|
|
2125
2244
|
}
|
|
2126
2245
|
return `Gemini API error (${status}): ${body}`;
|
|
2127
2246
|
}
|
|
2247
|
+
function parseRetryDelaySeconds(body) {
|
|
2248
|
+
const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
|
|
2249
|
+
if (!match) return void 0;
|
|
2250
|
+
const seconds = Number(match[1]);
|
|
2251
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2252
|
+
}
|
|
2253
|
+
function parseGeminiQuota(status, body) {
|
|
2254
|
+
if (status !== 429) return null;
|
|
2255
|
+
const lower = body.toLowerCase();
|
|
2256
|
+
if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
|
|
2257
|
+
const retryDelaySeconds = parseRetryDelaySeconds(body);
|
|
2258
|
+
const exhausted = retryDelaySeconds === void 0;
|
|
2259
|
+
return { exhausted, retryDelaySeconds };
|
|
2260
|
+
}
|
|
2128
2261
|
function toSystemAndContents(messages) {
|
|
2129
2262
|
let systemText = "";
|
|
2130
2263
|
const contents = [];
|
|
@@ -2203,7 +2336,7 @@ function toGeminiTools(tools) {
|
|
|
2203
2336
|
functionDeclarations: tools.map((tool) => ({
|
|
2204
2337
|
name: tool.name,
|
|
2205
2338
|
description: tool.description,
|
|
2206
|
-
parameters: sanitizeSchema(
|
|
2339
|
+
parameters: sanitizeSchema(resolveToolSchema(tool))
|
|
2207
2340
|
}))
|
|
2208
2341
|
}
|
|
2209
2342
|
];
|
|
@@ -2214,7 +2347,7 @@ function sanitizeSchema(schema) {
|
|
|
2214
2347
|
return clone;
|
|
2215
2348
|
}
|
|
2216
2349
|
function stripUnsupportedSchemaFields(value) {
|
|
2217
|
-
if (!
|
|
2350
|
+
if (!isJsonObject(value)) {
|
|
2218
2351
|
if (Array.isArray(value)) {
|
|
2219
2352
|
for (const item of value) stripUnsupportedSchemaFields(item);
|
|
2220
2353
|
}
|
|
@@ -2223,7 +2356,7 @@ function stripUnsupportedSchemaFields(value) {
|
|
|
2223
2356
|
delete value.$schema;
|
|
2224
2357
|
delete value.additionalProperties;
|
|
2225
2358
|
for (const item of Object.values(value)) {
|
|
2226
|
-
if (
|
|
2359
|
+
if (isJsonObject(item) || Array.isArray(item)) {
|
|
2227
2360
|
stripUnsupportedSchemaFields(item);
|
|
2228
2361
|
}
|
|
2229
2362
|
}
|
|
@@ -2276,7 +2409,8 @@ function toThinkingConfig(model, level) {
|
|
|
2276
2409
|
};
|
|
2277
2410
|
}
|
|
2278
2411
|
function buildGenerateRequest(options) {
|
|
2279
|
-
const
|
|
2412
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
2413
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
2280
2414
|
const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
|
|
2281
2415
|
const tools = toGeminiTools(options.tools);
|
|
2282
2416
|
const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
|
|
@@ -2338,54 +2472,11 @@ function normalizeGeminiStopReason(reason) {
|
|
|
2338
2472
|
return "end_turn";
|
|
2339
2473
|
}
|
|
2340
2474
|
}
|
|
2341
|
-
function parseSseEvents(buffer) {
|
|
2342
|
-
const events = [];
|
|
2343
|
-
let cursor = 0;
|
|
2344
|
-
while (true) {
|
|
2345
|
-
const next = buffer.indexOf("\n\n", cursor);
|
|
2346
|
-
if (next === -1) break;
|
|
2347
|
-
const raw = buffer.slice(cursor, next);
|
|
2348
|
-
cursor = next + 2;
|
|
2349
|
-
let eventName;
|
|
2350
|
-
const dataLines = [];
|
|
2351
|
-
for (const line of raw.split("\n")) {
|
|
2352
|
-
if (line.startsWith("event:")) {
|
|
2353
|
-
eventName = line.slice("event:".length).trim();
|
|
2354
|
-
} else if (line.startsWith("data:")) {
|
|
2355
|
-
dataLines.push(line.slice("data:".length).trimStart());
|
|
2356
|
-
}
|
|
2357
|
-
}
|
|
2358
|
-
if (dataLines.length > 0) {
|
|
2359
|
-
events.push({ event: eventName, data: dataLines.join("\n") });
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
return { events, remaining: buffer.slice(cursor) };
|
|
2363
|
-
}
|
|
2364
2475
|
async function* streamSse(response) {
|
|
2365
2476
|
if (!response.body) return;
|
|
2366
|
-
const
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
try {
|
|
2370
|
-
while (true) {
|
|
2371
|
-
const { done, value } = await reader.read();
|
|
2372
|
-
if (done) break;
|
|
2373
|
-
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
2374
|
-
const parsed2 = parseSseEvents(buffer);
|
|
2375
|
-
buffer = parsed2.remaining;
|
|
2376
|
-
for (const event of parsed2.events) {
|
|
2377
|
-
if (event.data === "[DONE]") continue;
|
|
2378
|
-
yield JSON.parse(event.data);
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
2382
|
-
const parsed = parseSseEvents(buffer + "\n\n");
|
|
2383
|
-
for (const event of parsed.events) {
|
|
2384
|
-
if (event.data === "[DONE]") continue;
|
|
2385
|
-
yield JSON.parse(event.data);
|
|
2386
|
-
}
|
|
2387
|
-
} finally {
|
|
2388
|
-
reader.releaseLock();
|
|
2477
|
+
for await (const event of readSseStream(response.body)) {
|
|
2478
|
+
if (event.data === "[DONE]") continue;
|
|
2479
|
+
yield JSON.parse(event.data);
|
|
2389
2480
|
}
|
|
2390
2481
|
}
|
|
2391
2482
|
function candidatesFromResponse(response) {
|
|
@@ -2408,7 +2499,7 @@ function readFunctionCallPart(part) {
|
|
|
2408
2499
|
return {
|
|
2409
2500
|
...part.functionCall.id ? { id: part.functionCall.id } : {},
|
|
2410
2501
|
name: part.functionCall.name,
|
|
2411
|
-
args:
|
|
2502
|
+
args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
|
|
2412
2503
|
};
|
|
2413
2504
|
}
|
|
2414
2505
|
function makeToolCallId(index, providerId) {
|
|
@@ -2447,8 +2538,17 @@ async function fetchCodeAssist(plan, options) {
|
|
|
2447
2538
|
});
|
|
2448
2539
|
if (!response.ok) {
|
|
2449
2540
|
const text = await response.text().catch(() => "");
|
|
2450
|
-
|
|
2451
|
-
|
|
2541
|
+
const quota = parseGeminiQuota(response.status, text);
|
|
2542
|
+
let message = formatErrorMessage(response.status, text, options.model);
|
|
2543
|
+
let resetsAt;
|
|
2544
|
+
if (quota?.exhausted) {
|
|
2545
|
+
message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
|
|
2546
|
+
} else if (quota?.retryDelaySeconds !== void 0) {
|
|
2547
|
+
resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
|
|
2548
|
+
}
|
|
2549
|
+
throw new ProviderError("gemini", message, {
|
|
2550
|
+
statusCode: response.status,
|
|
2551
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2452
2552
|
});
|
|
2453
2553
|
}
|
|
2454
2554
|
return response;
|
|
@@ -2839,6 +2939,7 @@ export {
|
|
|
2839
2939
|
StreamResult,
|
|
2840
2940
|
formatError,
|
|
2841
2941
|
formatErrorForDisplay,
|
|
2942
|
+
isHardBillingMessage,
|
|
2842
2943
|
isUsageLimitError,
|
|
2843
2944
|
palsuAssistantMessage,
|
|
2844
2945
|
palsuText,
|