@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.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
StreamResult: () => StreamResult,
|
|
37
37
|
formatError: () => formatError,
|
|
38
38
|
formatErrorForDisplay: () => formatErrorForDisplay,
|
|
39
|
+
isHardBillingMessage: () => isHardBillingMessage,
|
|
39
40
|
isUsageLimitError: () => isUsageLimitError,
|
|
40
41
|
palsuAssistantMessage: () => palsuAssistantMessage,
|
|
41
42
|
palsuText: () => palsuText,
|
|
@@ -51,6 +52,20 @@ __export(index_exports, {
|
|
|
51
52
|
module.exports = __toCommonJS(index_exports);
|
|
52
53
|
|
|
53
54
|
// src/errors.ts
|
|
55
|
+
function readHeader(headers, ...names) {
|
|
56
|
+
if (!headers) return void 0;
|
|
57
|
+
const getter = typeof headers.get === "function" ? (name) => headers.get(name) ?? void 0 : typeof headers === "object" ? (name) => {
|
|
58
|
+
const rec = headers;
|
|
59
|
+
const value = rec[name] ?? rec[name.toLowerCase()];
|
|
60
|
+
return typeof value === "string" ? value : void 0;
|
|
61
|
+
} : void 0;
|
|
62
|
+
if (!getter) return void 0;
|
|
63
|
+
for (const name of names) {
|
|
64
|
+
const value = getter(name);
|
|
65
|
+
if (value != null) return value;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
54
69
|
var GGAIError = class extends Error {
|
|
55
70
|
source;
|
|
56
71
|
requestId;
|
|
@@ -103,6 +118,10 @@ function isUsageLimitError(err) {
|
|
|
103
118
|
if (!(err instanceof Error)) return false;
|
|
104
119
|
return /usage limit reached/i.test(err.message);
|
|
105
120
|
}
|
|
121
|
+
function isHardBillingMessage(message) {
|
|
122
|
+
const lower = message.toLowerCase();
|
|
123
|
+
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");
|
|
124
|
+
}
|
|
106
125
|
function formatResetTime(resetsAt) {
|
|
107
126
|
const when = new Date(resetsAt * 1e3);
|
|
108
127
|
const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
@@ -352,6 +371,9 @@ function zodToJsonSchema(schema) {
|
|
|
352
371
|
const { $schema: _schema, ...rest } = jsonSchema;
|
|
353
372
|
return normalizeRootForAnthropic(rest);
|
|
354
373
|
}
|
|
374
|
+
function resolveToolSchema(tool) {
|
|
375
|
+
return tool.rawInputSchema ?? zodToJsonSchema(tool.parameters);
|
|
376
|
+
}
|
|
355
377
|
function normalizeRootForAnthropic(schema) {
|
|
356
378
|
const branches = schema.oneOf ?? schema.anyOf;
|
|
357
379
|
if (!branches || branches.length === 0) {
|
|
@@ -465,6 +487,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
|
|
|
465
487
|
}
|
|
466
488
|
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
467
489
|
var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
490
|
+
var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
|
|
468
491
|
function stripImages(content, placeholder) {
|
|
469
492
|
const out = [];
|
|
470
493
|
let lastWasPlaceholder = false;
|
|
@@ -475,10 +498,33 @@ function stripImages(content, placeholder) {
|
|
|
475
498
|
continue;
|
|
476
499
|
}
|
|
477
500
|
out.push(block);
|
|
478
|
-
lastWasPlaceholder = block.text === placeholder;
|
|
501
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
479
502
|
}
|
|
480
503
|
return out;
|
|
481
504
|
}
|
|
505
|
+
function stripVideos(content, placeholder) {
|
|
506
|
+
const out = [];
|
|
507
|
+
let lastWasPlaceholder = false;
|
|
508
|
+
for (const block of content) {
|
|
509
|
+
if (block.type === "video") {
|
|
510
|
+
if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
|
|
511
|
+
lastWasPlaceholder = true;
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
out.push(block);
|
|
515
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
516
|
+
}
|
|
517
|
+
return out;
|
|
518
|
+
}
|
|
519
|
+
function downgradeUnsupportedVideos(messages, supportsVideo) {
|
|
520
|
+
if (supportsVideo === true) return messages;
|
|
521
|
+
return messages.map((msg) => {
|
|
522
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
523
|
+
return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
|
|
524
|
+
}
|
|
525
|
+
return msg;
|
|
526
|
+
});
|
|
527
|
+
}
|
|
482
528
|
function downgradeUnsupportedImages(messages, supportsImages) {
|
|
483
529
|
if (supportsImages !== false) return messages;
|
|
484
530
|
return messages.map((msg) => {
|
|
@@ -555,6 +601,16 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
555
601
|
role: "user",
|
|
556
602
|
content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
|
|
557
603
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
604
|
+
if (part.type === "video") {
|
|
605
|
+
return {
|
|
606
|
+
type: "video",
|
|
607
|
+
source: {
|
|
608
|
+
type: "base64",
|
|
609
|
+
media_type: part.mediaType,
|
|
610
|
+
data: part.data
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
558
614
|
return {
|
|
559
615
|
type: "image",
|
|
560
616
|
source: {
|
|
@@ -654,11 +710,11 @@ function toAnthropicToolChoice(choice) {
|
|
|
654
710
|
if (choice === "required") return { type: "any" };
|
|
655
711
|
return { type: "tool", name: choice.name };
|
|
656
712
|
}
|
|
657
|
-
function
|
|
658
|
-
return /opus-4
|
|
713
|
+
function isAdaptiveThinkingModel(model) {
|
|
714
|
+
return /opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-4[-.]6/.test(model);
|
|
659
715
|
}
|
|
660
716
|
function toAnthropicThinking(level, maxTokens, model) {
|
|
661
|
-
if (
|
|
717
|
+
if (isAdaptiveThinkingModel(model)) {
|
|
662
718
|
let effort = level;
|
|
663
719
|
if (effort === "xhigh" && !/opus-4-8|opus-4-7/.test(model)) {
|
|
664
720
|
effort = "high";
|
|
@@ -715,6 +771,14 @@ function toOpenAIMessages(messages, options) {
|
|
|
715
771
|
content: msg.content.map(
|
|
716
772
|
(part) => {
|
|
717
773
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
774
|
+
if (part.type === "video") {
|
|
775
|
+
return {
|
|
776
|
+
type: "video_url",
|
|
777
|
+
video_url: {
|
|
778
|
+
url: `data:${part.mediaType};base64,${part.data}`
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
}
|
|
718
782
|
return {
|
|
719
783
|
type: "image_url",
|
|
720
784
|
image_url: {
|
|
@@ -792,7 +856,7 @@ function toOpenAITools(tools) {
|
|
|
792
856
|
function: {
|
|
793
857
|
name: tool.name,
|
|
794
858
|
description: tool.description,
|
|
795
|
-
parameters:
|
|
859
|
+
parameters: resolveToolSchema(tool)
|
|
796
860
|
}
|
|
797
861
|
}));
|
|
798
862
|
}
|
|
@@ -834,10 +898,22 @@ function normalizeOpenAIStopReason(reason) {
|
|
|
834
898
|
}
|
|
835
899
|
}
|
|
836
900
|
|
|
837
|
-
// src/
|
|
901
|
+
// src/utils/json.ts
|
|
838
902
|
function isJsonObject(value) {
|
|
839
903
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
840
904
|
}
|
|
905
|
+
function parseToolArguments(argsJson) {
|
|
906
|
+
if (!argsJson) return {};
|
|
907
|
+
try {
|
|
908
|
+
const parsed = JSON.parse(argsJson);
|
|
909
|
+
const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
|
|
910
|
+
return isJsonObject(unwrapped) ? unwrapped : {};
|
|
911
|
+
} catch {
|
|
912
|
+
return {};
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/providers/anthropic.ts
|
|
841
917
|
function createClient(options) {
|
|
842
918
|
const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
|
|
843
919
|
return new import_sdk.default({
|
|
@@ -868,7 +944,8 @@ async function* runStream(options) {
|
|
|
868
944
|
const useStreaming = options.streaming !== false;
|
|
869
945
|
const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
|
|
870
946
|
const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
|
|
871
|
-
const
|
|
947
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
948
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
872
949
|
const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
|
|
873
950
|
const system = isOAuth ? [
|
|
874
951
|
{
|
|
@@ -930,7 +1007,7 @@ async function* runStream(options) {
|
|
|
930
1007
|
})(),
|
|
931
1008
|
stream: useStreaming
|
|
932
1009
|
};
|
|
933
|
-
const hasAdaptiveThinking =
|
|
1010
|
+
const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
|
|
934
1011
|
const betaHeaders = [
|
|
935
1012
|
...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
|
|
936
1013
|
...options.compaction ? ["compact-2026-01-12"] : [],
|
|
@@ -1074,11 +1151,18 @@ async function* runStream(options) {
|
|
|
1074
1151
|
args: tc.args
|
|
1075
1152
|
};
|
|
1076
1153
|
} else if (accum.type === "server_tool_use") {
|
|
1154
|
+
let input = accum.input;
|
|
1155
|
+
if (accum.argsJson) {
|
|
1156
|
+
try {
|
|
1157
|
+
input = JSON.parse(accum.argsJson);
|
|
1158
|
+
} catch {
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1077
1161
|
const stc = {
|
|
1078
1162
|
type: "server_tool_call",
|
|
1079
1163
|
id: accum.toolId,
|
|
1080
1164
|
name: accum.toolName,
|
|
1081
|
-
input
|
|
1165
|
+
input
|
|
1082
1166
|
};
|
|
1083
1167
|
contentParts.push(stc);
|
|
1084
1168
|
yield {
|
|
@@ -1255,19 +1339,13 @@ function messageToResponse(message) {
|
|
|
1255
1339
|
};
|
|
1256
1340
|
}
|
|
1257
1341
|
function readUnifiedRateLimit(headers) {
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
return typeof value === "string" ? value : null;
|
|
1266
|
-
}
|
|
1267
|
-
return null;
|
|
1268
|
-
};
|
|
1269
|
-
const status = get("anthropic-ratelimit-unified-status");
|
|
1270
|
-
const resetRaw = get("anthropic-ratelimit-unified-reset") ?? get("anthropic-ratelimit-unified-5h-reset") ?? get("anthropic-ratelimit-unified-7d-reset");
|
|
1342
|
+
const status = readHeader(headers, "anthropic-ratelimit-unified-status");
|
|
1343
|
+
const resetRaw = readHeader(
|
|
1344
|
+
headers,
|
|
1345
|
+
"anthropic-ratelimit-unified-reset",
|
|
1346
|
+
"anthropic-ratelimit-unified-5h-reset",
|
|
1347
|
+
"anthropic-ratelimit-unified-7d-reset"
|
|
1348
|
+
);
|
|
1271
1349
|
const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
|
|
1272
1350
|
const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
|
|
1273
1351
|
return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
|
|
@@ -1292,6 +1370,14 @@ function toError(err) {
|
|
|
1292
1370
|
});
|
|
1293
1371
|
}
|
|
1294
1372
|
}
|
|
1373
|
+
if (isHardBillingMessage(message)) {
|
|
1374
|
+
const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
1375
|
+
return new ProviderError("anthropic", usageMessage, {
|
|
1376
|
+
statusCode: err.status,
|
|
1377
|
+
...requestId ? { requestId } : {},
|
|
1378
|
+
cause: err
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1295
1381
|
return new ProviderError("anthropic", message, {
|
|
1296
1382
|
statusCode: err.status,
|
|
1297
1383
|
...requestId ? { requestId } : {},
|
|
@@ -1324,19 +1410,30 @@ function fnv1aHash(value) {
|
|
|
1324
1410
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1325
1411
|
}
|
|
1326
1412
|
|
|
1327
|
-
// src/
|
|
1328
|
-
function
|
|
1329
|
-
return
|
|
1413
|
+
// src/utils/env.ts
|
|
1414
|
+
function getEnvironment() {
|
|
1415
|
+
return globalThis.process?.env;
|
|
1330
1416
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1417
|
+
|
|
1418
|
+
// src/providers/openai.ts
|
|
1419
|
+
function extractOpenAIUsage(usage) {
|
|
1420
|
+
let cacheRead = 0;
|
|
1421
|
+
const details = usage.prompt_tokens_details;
|
|
1422
|
+
if (details?.cached_tokens) {
|
|
1423
|
+
cacheRead = details.cached_tokens;
|
|
1424
|
+
}
|
|
1425
|
+
const usageAny = usage;
|
|
1426
|
+
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1427
|
+
cacheRead = usageAny.cached_tokens;
|
|
1339
1428
|
}
|
|
1429
|
+
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1430
|
+
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
inputTokens: usage.prompt_tokens - cacheRead,
|
|
1434
|
+
outputTokens: usage.completion_tokens,
|
|
1435
|
+
cacheRead
|
|
1436
|
+
};
|
|
1340
1437
|
}
|
|
1341
1438
|
function createClient2(options) {
|
|
1342
1439
|
return new import_openai.default({
|
|
@@ -1353,7 +1450,8 @@ async function* runStream2(options) {
|
|
|
1353
1450
|
const useStreaming = options.streaming !== false;
|
|
1354
1451
|
const client = createClient2(options);
|
|
1355
1452
|
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
|
|
1356
|
-
const
|
|
1453
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1454
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1357
1455
|
const messages = toOpenAIMessages(downgradedMessages, {
|
|
1358
1456
|
provider: options.provider,
|
|
1359
1457
|
thinking: !!options.thinking,
|
|
@@ -1392,7 +1490,7 @@ async function* runStream2(options) {
|
|
|
1392
1490
|
params.thinking = { type: "disabled" };
|
|
1393
1491
|
}
|
|
1394
1492
|
}
|
|
1395
|
-
if (
|
|
1493
|
+
if (getEnvironment()?.GGAI_DUMP_REQUEST) {
|
|
1396
1494
|
const fs = await import("fs");
|
|
1397
1495
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1398
1496
|
const dumpPath = `/tmp/ggai-request-${ts}.json`;
|
|
@@ -1433,19 +1531,7 @@ async function* runStream2(options) {
|
|
|
1433
1531
|
for await (const chunk of stream2) {
|
|
1434
1532
|
const choice = chunk.choices?.[0];
|
|
1435
1533
|
if (chunk.usage) {
|
|
1436
|
-
outputTokens = chunk.usage
|
|
1437
|
-
const details = chunk.usage.prompt_tokens_details;
|
|
1438
|
-
if (details?.cached_tokens) {
|
|
1439
|
-
cacheRead = details.cached_tokens;
|
|
1440
|
-
}
|
|
1441
|
-
const usageAny = chunk.usage;
|
|
1442
|
-
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1443
|
-
cacheRead = usageAny.cached_tokens;
|
|
1444
|
-
}
|
|
1445
|
-
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1446
|
-
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1447
|
-
}
|
|
1448
|
-
inputTokens = chunk.usage.prompt_tokens - cacheRead;
|
|
1534
|
+
({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
|
|
1449
1535
|
}
|
|
1450
1536
|
if (!choice) continue;
|
|
1451
1537
|
if (choice.finish_reason) {
|
|
@@ -1591,17 +1677,7 @@ function completionToResponse(completion) {
|
|
|
1591
1677
|
let outputTokens = 0;
|
|
1592
1678
|
let cacheRead = 0;
|
|
1593
1679
|
if (completion.usage) {
|
|
1594
|
-
outputTokens = completion.usage
|
|
1595
|
-
const details = completion.usage.prompt_tokens_details;
|
|
1596
|
-
if (details?.cached_tokens) cacheRead = details.cached_tokens;
|
|
1597
|
-
const usageAny = completion.usage;
|
|
1598
|
-
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1599
|
-
cacheRead = usageAny.cached_tokens;
|
|
1600
|
-
}
|
|
1601
|
-
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1602
|
-
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1603
|
-
}
|
|
1604
|
-
inputTokens = completion.usage.prompt_tokens - cacheRead;
|
|
1680
|
+
({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
|
|
1605
1681
|
}
|
|
1606
1682
|
const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
|
|
1607
1683
|
return {
|
|
@@ -1613,6 +1689,16 @@ function completionToResponse(completion) {
|
|
|
1613
1689
|
usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
|
|
1614
1690
|
};
|
|
1615
1691
|
}
|
|
1692
|
+
function classifyOpenAICompatLimit(args) {
|
|
1693
|
+
const { status, code, type, message } = args;
|
|
1694
|
+
const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
|
|
1695
|
+
const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
|
|
1696
|
+
if (isHard) return "hard";
|
|
1697
|
+
if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
|
|
1698
|
+
return "transient";
|
|
1699
|
+
}
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1616
1702
|
function toError2(err, provider = "openai") {
|
|
1617
1703
|
if (err instanceof import_openai.default.APIError) {
|
|
1618
1704
|
const body = err.error;
|
|
@@ -1624,6 +1710,35 @@ function toError2(err, provider = "openai") {
|
|
|
1624
1710
|
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.";
|
|
1625
1711
|
}
|
|
1626
1712
|
const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
|
|
1713
|
+
const code = typeof err.code === "string" ? err.code : void 0;
|
|
1714
|
+
const type = typeof err.type === "string" ? err.type : void 0;
|
|
1715
|
+
const limit = classifyOpenAICompatLimit({
|
|
1716
|
+
status: err.status,
|
|
1717
|
+
code,
|
|
1718
|
+
type,
|
|
1719
|
+
message: cleanMessage
|
|
1720
|
+
});
|
|
1721
|
+
if (limit === "hard") {
|
|
1722
|
+
const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
|
|
1723
|
+
return new ProviderError(provider, message, {
|
|
1724
|
+
statusCode: err.status,
|
|
1725
|
+
...requestId ? { requestId } : {},
|
|
1726
|
+
...hint ? { hint } : {},
|
|
1727
|
+
cause: err
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
if (limit === "transient") {
|
|
1731
|
+
const retryAfterRaw = readHeader(err.headers, "retry-after");
|
|
1732
|
+
const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
|
|
1733
|
+
const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
|
|
1734
|
+
return new ProviderError(provider, cleanMessage, {
|
|
1735
|
+
statusCode: err.status,
|
|
1736
|
+
...requestId ? { requestId } : {},
|
|
1737
|
+
...hint ? { hint } : {},
|
|
1738
|
+
...resetsAt ? { resetsAt } : {},
|
|
1739
|
+
cause: err
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1627
1742
|
return new ProviderError(provider, cleanMessage, {
|
|
1628
1743
|
statusCode: err.status,
|
|
1629
1744
|
...requestId ? { requestId } : {},
|
|
@@ -1649,21 +1764,59 @@ function providerDiag(phase, data) {
|
|
|
1649
1764
|
_diagFn?.(phase, data);
|
|
1650
1765
|
}
|
|
1651
1766
|
|
|
1652
|
-
// src/
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1767
|
+
// src/utils/sse.ts
|
|
1768
|
+
function parseSseBuffer(buffer) {
|
|
1769
|
+
const events = [];
|
|
1770
|
+
let cursor = 0;
|
|
1771
|
+
while (true) {
|
|
1772
|
+
const next = buffer.indexOf("\n\n", cursor);
|
|
1773
|
+
if (next === -1) break;
|
|
1774
|
+
const raw = buffer.slice(cursor, next);
|
|
1775
|
+
cursor = next + 2;
|
|
1776
|
+
let eventName;
|
|
1777
|
+
const dataLines = [];
|
|
1778
|
+
for (const line of raw.split("\n")) {
|
|
1779
|
+
if (line.startsWith("event:")) {
|
|
1780
|
+
eventName = line.slice("event:".length).trim();
|
|
1781
|
+
} else if (line.startsWith("data:")) {
|
|
1782
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (dataLines.length > 0) {
|
|
1786
|
+
events.push({ event: eventName, data: dataLines.join("\n") });
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return { events, remaining: buffer.slice(cursor) };
|
|
1656
1790
|
}
|
|
1657
|
-
function
|
|
1658
|
-
|
|
1791
|
+
async function* readSseStream(body) {
|
|
1792
|
+
const reader = body.getReader();
|
|
1793
|
+
const decoder = new TextDecoder();
|
|
1794
|
+
let buffer = "";
|
|
1659
1795
|
try {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1796
|
+
while (true) {
|
|
1797
|
+
const { done, value } = await reader.read();
|
|
1798
|
+
if (done) break;
|
|
1799
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
1800
|
+
const parsed2 = parseSseBuffer(buffer);
|
|
1801
|
+
buffer = parsed2.remaining;
|
|
1802
|
+
yield* parsed2.events;
|
|
1803
|
+
}
|
|
1804
|
+
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
1805
|
+
const parsed = parseSseBuffer(buffer + "\n\n");
|
|
1806
|
+
yield* parsed.events;
|
|
1807
|
+
} finally {
|
|
1808
|
+
reader.releaseLock();
|
|
1665
1809
|
}
|
|
1666
1810
|
}
|
|
1811
|
+
|
|
1812
|
+
// src/utils/request-id.ts
|
|
1813
|
+
function extractRequestIdFromMessage(message) {
|
|
1814
|
+
const match = message.match(/request ID ([a-z0-9-]{8,})/i);
|
|
1815
|
+
return match?.[1];
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// src/providers/openai-codex.ts
|
|
1819
|
+
var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
1667
1820
|
function outputTextKey(itemId, contentIndex) {
|
|
1668
1821
|
return `${itemId ?? ""}:${contentIndex ?? 0}`;
|
|
1669
1822
|
}
|
|
@@ -1676,7 +1829,8 @@ function streamOpenAICodex(options) {
|
|
|
1676
1829
|
async function* runStream3(options) {
|
|
1677
1830
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1678
1831
|
const url = `${baseUrl}/codex/responses`;
|
|
1679
|
-
const
|
|
1832
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1833
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1680
1834
|
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
1681
1835
|
const body = {
|
|
1682
1836
|
model: options.model,
|
|
@@ -1728,7 +1882,7 @@ async function* runStream3(options) {
|
|
|
1728
1882
|
const text = await response.text().catch(() => "");
|
|
1729
1883
|
const parsed = parseCodexErrorBody(text);
|
|
1730
1884
|
const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
|
|
1731
|
-
const requestId = parsed.requestId ?? response.headers
|
|
1885
|
+
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
1732
1886
|
const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
|
|
1733
1887
|
if (usageLimit) throw usageLimit;
|
|
1734
1888
|
let hint;
|
|
@@ -1772,7 +1926,7 @@ async function* runStream3(options) {
|
|
|
1772
1926
|
const nested = event.error ?? void 0;
|
|
1773
1927
|
const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
|
|
1774
1928
|
const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
|
|
1775
|
-
const requestId =
|
|
1929
|
+
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
1776
1930
|
const usageLimit = codexUsageLimitError(
|
|
1777
1931
|
nested ?? event,
|
|
1778
1932
|
void 0,
|
|
@@ -1787,7 +1941,7 @@ async function* runStream3(options) {
|
|
|
1787
1941
|
if (type === "response.failed") {
|
|
1788
1942
|
const nested = event.error;
|
|
1789
1943
|
const message = nested?.message ?? "Codex response failed.";
|
|
1790
|
-
const requestId =
|
|
1944
|
+
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
1791
1945
|
throw new ProviderError("openai", message, {
|
|
1792
1946
|
...requestId != null ? { requestId } : {}
|
|
1793
1947
|
});
|
|
@@ -1906,7 +2060,7 @@ async function* runStream3(options) {
|
|
|
1906
2060
|
const id = `${callId}|${itemId}`;
|
|
1907
2061
|
const tc = toolCalls.get(id);
|
|
1908
2062
|
if (tc) {
|
|
1909
|
-
const args =
|
|
2063
|
+
const args = parseToolArguments(tc.argsJson);
|
|
1910
2064
|
yield {
|
|
1911
2065
|
type: "toolcall_done",
|
|
1912
2066
|
id: tc.id,
|
|
@@ -1930,7 +2084,7 @@ async function* runStream3(options) {
|
|
|
1930
2084
|
contentParts.push({ type: "text", text: textAccum });
|
|
1931
2085
|
}
|
|
1932
2086
|
for (const [, tc] of toolCalls) {
|
|
1933
|
-
const args =
|
|
2087
|
+
const args = parseToolArguments(tc.argsJson);
|
|
1934
2088
|
const toolCall = {
|
|
1935
2089
|
type: "tool_call",
|
|
1936
2090
|
id: tc.id,
|
|
@@ -1953,33 +2107,13 @@ async function* runStream3(options) {
|
|
|
1953
2107
|
return streamResponse;
|
|
1954
2108
|
}
|
|
1955
2109
|
async function* parseSSE(body) {
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
if (done) break;
|
|
1963
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1964
|
-
let idx = buffer.indexOf("\n\n");
|
|
1965
|
-
while (idx !== -1) {
|
|
1966
|
-
const chunk = buffer.slice(0, idx);
|
|
1967
|
-
buffer = buffer.slice(idx + 2);
|
|
1968
|
-
const dataLines = chunk.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
|
|
1969
|
-
if (dataLines.length > 0) {
|
|
1970
|
-
const data = dataLines.join("\n").trim();
|
|
1971
|
-
if (data && data !== "[DONE]") {
|
|
1972
|
-
try {
|
|
1973
|
-
yield JSON.parse(data);
|
|
1974
|
-
} catch {
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
idx = buffer.indexOf("\n\n");
|
|
1979
|
-
}
|
|
2110
|
+
for await (const event of readSseStream(body)) {
|
|
2111
|
+
const data = event.data.trim();
|
|
2112
|
+
if (!data || data === "[DONE]") continue;
|
|
2113
|
+
try {
|
|
2114
|
+
yield JSON.parse(data);
|
|
2115
|
+
} catch {
|
|
1980
2116
|
}
|
|
1981
|
-
} finally {
|
|
1982
|
-
reader.releaseLock();
|
|
1983
2117
|
}
|
|
1984
2118
|
}
|
|
1985
2119
|
function remapCodexId(id, idMap) {
|
|
@@ -1990,10 +2124,6 @@ function remapCodexId(id, idMap) {
|
|
|
1990
2124
|
idMap.set(id, mapped);
|
|
1991
2125
|
return mapped;
|
|
1992
2126
|
}
|
|
1993
|
-
function codexToolResultText(content) {
|
|
1994
|
-
if (typeof content === "string") return content;
|
|
1995
|
-
return content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
1996
|
-
}
|
|
1997
2127
|
function toCodexInput(messages, options) {
|
|
1998
2128
|
let system;
|
|
1999
2129
|
const input = [];
|
|
@@ -2050,7 +2180,7 @@ function toCodexInput(messages, options) {
|
|
|
2050
2180
|
const toolImages = [];
|
|
2051
2181
|
for (const result of msg.content) {
|
|
2052
2182
|
const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
|
|
2053
|
-
const text =
|
|
2183
|
+
const text = toolResultText(result.content);
|
|
2054
2184
|
input.push({
|
|
2055
2185
|
type: "function_call_output",
|
|
2056
2186
|
call_id: remapCodexId(callId, idMap),
|
|
@@ -2085,14 +2215,10 @@ function toCodexTools(tools) {
|
|
|
2085
2215
|
type: "function",
|
|
2086
2216
|
name: tool.name,
|
|
2087
2217
|
description: tool.description,
|
|
2088
|
-
parameters:
|
|
2218
|
+
parameters: resolveToolSchema(tool),
|
|
2089
2219
|
strict: null
|
|
2090
2220
|
}));
|
|
2091
2221
|
}
|
|
2092
|
-
function extractCodexRequestId(message) {
|
|
2093
|
-
const match = message.match(/request ID ([a-z0-9-]{8,})/i);
|
|
2094
|
-
return match?.[1];
|
|
2095
|
-
}
|
|
2096
2222
|
function parseCodexErrorBody(text) {
|
|
2097
2223
|
if (!text) return {};
|
|
2098
2224
|
try {
|
|
@@ -2100,7 +2226,7 @@ function parseCodexErrorBody(text) {
|
|
|
2100
2226
|
const error = parsed.error;
|
|
2101
2227
|
const detail = parsed.detail;
|
|
2102
2228
|
const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
|
|
2103
|
-
const requestId = parsed.request_id ?? error?.request_id ?? (message ?
|
|
2229
|
+
const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
|
|
2104
2230
|
const errorObj = error ?? parsed;
|
|
2105
2231
|
return {
|
|
2106
2232
|
...message ? { message } : {},
|
|
@@ -2150,12 +2276,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
|
|
|
2150
2276
|
"gemma-4-31b-it",
|
|
2151
2277
|
"gemma-4-26b-a4b-it"
|
|
2152
2278
|
]);
|
|
2153
|
-
function isJsonObject4(value) {
|
|
2154
|
-
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2155
|
-
}
|
|
2156
|
-
function getEnvironment() {
|
|
2157
|
-
return globalThis.process?.env;
|
|
2158
|
-
}
|
|
2159
2279
|
function getGoogleProject(options) {
|
|
2160
2280
|
const env = getEnvironment();
|
|
2161
2281
|
return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
|
|
@@ -2177,6 +2297,20 @@ ${formatUnsupportedModelMessage(model)}`;
|
|
|
2177
2297
|
}
|
|
2178
2298
|
return `Gemini API error (${status}): ${body}`;
|
|
2179
2299
|
}
|
|
2300
|
+
function parseRetryDelaySeconds(body) {
|
|
2301
|
+
const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
|
|
2302
|
+
if (!match) return void 0;
|
|
2303
|
+
const seconds = Number(match[1]);
|
|
2304
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2305
|
+
}
|
|
2306
|
+
function parseGeminiQuota(status, body) {
|
|
2307
|
+
if (status !== 429) return null;
|
|
2308
|
+
const lower = body.toLowerCase();
|
|
2309
|
+
if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
|
|
2310
|
+
const retryDelaySeconds = parseRetryDelaySeconds(body);
|
|
2311
|
+
const exhausted = retryDelaySeconds === void 0;
|
|
2312
|
+
return { exhausted, retryDelaySeconds };
|
|
2313
|
+
}
|
|
2180
2314
|
function toSystemAndContents(messages) {
|
|
2181
2315
|
let systemText = "";
|
|
2182
2316
|
const contents = [];
|
|
@@ -2255,7 +2389,7 @@ function toGeminiTools(tools) {
|
|
|
2255
2389
|
functionDeclarations: tools.map((tool) => ({
|
|
2256
2390
|
name: tool.name,
|
|
2257
2391
|
description: tool.description,
|
|
2258
|
-
parameters: sanitizeSchema(
|
|
2392
|
+
parameters: sanitizeSchema(resolveToolSchema(tool))
|
|
2259
2393
|
}))
|
|
2260
2394
|
}
|
|
2261
2395
|
];
|
|
@@ -2266,7 +2400,7 @@ function sanitizeSchema(schema) {
|
|
|
2266
2400
|
return clone;
|
|
2267
2401
|
}
|
|
2268
2402
|
function stripUnsupportedSchemaFields(value) {
|
|
2269
|
-
if (!
|
|
2403
|
+
if (!isJsonObject(value)) {
|
|
2270
2404
|
if (Array.isArray(value)) {
|
|
2271
2405
|
for (const item of value) stripUnsupportedSchemaFields(item);
|
|
2272
2406
|
}
|
|
@@ -2275,7 +2409,7 @@ function stripUnsupportedSchemaFields(value) {
|
|
|
2275
2409
|
delete value.$schema;
|
|
2276
2410
|
delete value.additionalProperties;
|
|
2277
2411
|
for (const item of Object.values(value)) {
|
|
2278
|
-
if (
|
|
2412
|
+
if (isJsonObject(item) || Array.isArray(item)) {
|
|
2279
2413
|
stripUnsupportedSchemaFields(item);
|
|
2280
2414
|
}
|
|
2281
2415
|
}
|
|
@@ -2328,7 +2462,8 @@ function toThinkingConfig(model, level) {
|
|
|
2328
2462
|
};
|
|
2329
2463
|
}
|
|
2330
2464
|
function buildGenerateRequest(options) {
|
|
2331
|
-
const
|
|
2465
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
2466
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
2332
2467
|
const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
|
|
2333
2468
|
const tools = toGeminiTools(options.tools);
|
|
2334
2469
|
const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
|
|
@@ -2390,54 +2525,11 @@ function normalizeGeminiStopReason(reason) {
|
|
|
2390
2525
|
return "end_turn";
|
|
2391
2526
|
}
|
|
2392
2527
|
}
|
|
2393
|
-
function parseSseEvents(buffer) {
|
|
2394
|
-
const events = [];
|
|
2395
|
-
let cursor = 0;
|
|
2396
|
-
while (true) {
|
|
2397
|
-
const next = buffer.indexOf("\n\n", cursor);
|
|
2398
|
-
if (next === -1) break;
|
|
2399
|
-
const raw = buffer.slice(cursor, next);
|
|
2400
|
-
cursor = next + 2;
|
|
2401
|
-
let eventName;
|
|
2402
|
-
const dataLines = [];
|
|
2403
|
-
for (const line of raw.split("\n")) {
|
|
2404
|
-
if (line.startsWith("event:")) {
|
|
2405
|
-
eventName = line.slice("event:".length).trim();
|
|
2406
|
-
} else if (line.startsWith("data:")) {
|
|
2407
|
-
dataLines.push(line.slice("data:".length).trimStart());
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
if (dataLines.length > 0) {
|
|
2411
|
-
events.push({ event: eventName, data: dataLines.join("\n") });
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
return { events, remaining: buffer.slice(cursor) };
|
|
2415
|
-
}
|
|
2416
2528
|
async function* streamSse(response) {
|
|
2417
2529
|
if (!response.body) return;
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
try {
|
|
2422
|
-
while (true) {
|
|
2423
|
-
const { done, value } = await reader.read();
|
|
2424
|
-
if (done) break;
|
|
2425
|
-
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
2426
|
-
const parsed2 = parseSseEvents(buffer);
|
|
2427
|
-
buffer = parsed2.remaining;
|
|
2428
|
-
for (const event of parsed2.events) {
|
|
2429
|
-
if (event.data === "[DONE]") continue;
|
|
2430
|
-
yield JSON.parse(event.data);
|
|
2431
|
-
}
|
|
2432
|
-
}
|
|
2433
|
-
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
2434
|
-
const parsed = parseSseEvents(buffer + "\n\n");
|
|
2435
|
-
for (const event of parsed.events) {
|
|
2436
|
-
if (event.data === "[DONE]") continue;
|
|
2437
|
-
yield JSON.parse(event.data);
|
|
2438
|
-
}
|
|
2439
|
-
} finally {
|
|
2440
|
-
reader.releaseLock();
|
|
2530
|
+
for await (const event of readSseStream(response.body)) {
|
|
2531
|
+
if (event.data === "[DONE]") continue;
|
|
2532
|
+
yield JSON.parse(event.data);
|
|
2441
2533
|
}
|
|
2442
2534
|
}
|
|
2443
2535
|
function candidatesFromResponse(response) {
|
|
@@ -2460,7 +2552,7 @@ function readFunctionCallPart(part) {
|
|
|
2460
2552
|
return {
|
|
2461
2553
|
...part.functionCall.id ? { id: part.functionCall.id } : {},
|
|
2462
2554
|
name: part.functionCall.name,
|
|
2463
|
-
args:
|
|
2555
|
+
args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
|
|
2464
2556
|
};
|
|
2465
2557
|
}
|
|
2466
2558
|
function makeToolCallId(index, providerId) {
|
|
@@ -2499,8 +2591,17 @@ async function fetchCodeAssist(plan, options) {
|
|
|
2499
2591
|
});
|
|
2500
2592
|
if (!response.ok) {
|
|
2501
2593
|
const text = await response.text().catch(() => "");
|
|
2502
|
-
|
|
2503
|
-
|
|
2594
|
+
const quota = parseGeminiQuota(response.status, text);
|
|
2595
|
+
let message = formatErrorMessage(response.status, text, options.model);
|
|
2596
|
+
let resetsAt;
|
|
2597
|
+
if (quota?.exhausted) {
|
|
2598
|
+
message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
|
|
2599
|
+
} else if (quota?.retryDelaySeconds !== void 0) {
|
|
2600
|
+
resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
|
|
2601
|
+
}
|
|
2602
|
+
throw new ProviderError("gemini", message, {
|
|
2603
|
+
statusCode: response.status,
|
|
2604
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2504
2605
|
});
|
|
2505
2606
|
}
|
|
2506
2607
|
return response;
|
|
@@ -2892,6 +2993,7 @@ function registerPalsuProvider(config) {
|
|
|
2892
2993
|
StreamResult,
|
|
2893
2994
|
formatError,
|
|
2894
2995
|
formatErrorForDisplay,
|
|
2996
|
+
isHardBillingMessage,
|
|
2895
2997
|
isUsageLimitError,
|
|
2896
2998
|
palsuAssistantMessage,
|
|
2897
2999
|
palsuText,
|