@kenkaiiii/gg-ai 4.3.236 → 4.3.238
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 +266 -171
- 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 +265 -171
- 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;
|
|
502
|
+
}
|
|
503
|
+
return out;
|
|
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;
|
|
479
516
|
}
|
|
480
517
|
return out;
|
|
481
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"] : [],
|
|
@@ -1255,19 +1332,13 @@ function messageToResponse(message) {
|
|
|
1255
1332
|
};
|
|
1256
1333
|
}
|
|
1257
1334
|
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");
|
|
1335
|
+
const status = readHeader(headers, "anthropic-ratelimit-unified-status");
|
|
1336
|
+
const resetRaw = readHeader(
|
|
1337
|
+
headers,
|
|
1338
|
+
"anthropic-ratelimit-unified-reset",
|
|
1339
|
+
"anthropic-ratelimit-unified-5h-reset",
|
|
1340
|
+
"anthropic-ratelimit-unified-7d-reset"
|
|
1341
|
+
);
|
|
1271
1342
|
const resetNum = resetRaw != null ? Number(resetRaw) : Number.NaN;
|
|
1272
1343
|
const resetsAt = Number.isFinite(resetNum) && resetNum > 0 ? resetNum : void 0;
|
|
1273
1344
|
return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
|
|
@@ -1292,6 +1363,14 @@ function toError(err) {
|
|
|
1292
1363
|
});
|
|
1293
1364
|
}
|
|
1294
1365
|
}
|
|
1366
|
+
if (isHardBillingMessage(message)) {
|
|
1367
|
+
const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
1368
|
+
return new ProviderError("anthropic", usageMessage, {
|
|
1369
|
+
statusCode: err.status,
|
|
1370
|
+
...requestId ? { requestId } : {},
|
|
1371
|
+
cause: err
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1295
1374
|
return new ProviderError("anthropic", message, {
|
|
1296
1375
|
statusCode: err.status,
|
|
1297
1376
|
...requestId ? { requestId } : {},
|
|
@@ -1324,19 +1403,30 @@ function fnv1aHash(value) {
|
|
|
1324
1403
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1325
1404
|
}
|
|
1326
1405
|
|
|
1327
|
-
// src/
|
|
1328
|
-
function
|
|
1329
|
-
return
|
|
1406
|
+
// src/utils/env.ts
|
|
1407
|
+
function getEnvironment() {
|
|
1408
|
+
return globalThis.process?.env;
|
|
1330
1409
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1410
|
+
|
|
1411
|
+
// src/providers/openai.ts
|
|
1412
|
+
function extractOpenAIUsage(usage) {
|
|
1413
|
+
let cacheRead = 0;
|
|
1414
|
+
const details = usage.prompt_tokens_details;
|
|
1415
|
+
if (details?.cached_tokens) {
|
|
1416
|
+
cacheRead = details.cached_tokens;
|
|
1417
|
+
}
|
|
1418
|
+
const usageAny = usage;
|
|
1419
|
+
if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
|
|
1420
|
+
cacheRead = usageAny.cached_tokens;
|
|
1339
1421
|
}
|
|
1422
|
+
if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
|
|
1423
|
+
cacheRead = usageAny.prompt_cache_hit_tokens;
|
|
1424
|
+
}
|
|
1425
|
+
return {
|
|
1426
|
+
inputTokens: usage.prompt_tokens - cacheRead,
|
|
1427
|
+
outputTokens: usage.completion_tokens,
|
|
1428
|
+
cacheRead
|
|
1429
|
+
};
|
|
1340
1430
|
}
|
|
1341
1431
|
function createClient2(options) {
|
|
1342
1432
|
return new import_openai.default({
|
|
@@ -1353,7 +1443,8 @@ async function* runStream2(options) {
|
|
|
1353
1443
|
const useStreaming = options.streaming !== false;
|
|
1354
1444
|
const client = createClient2(options);
|
|
1355
1445
|
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
|
|
1356
|
-
const
|
|
1446
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1447
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1357
1448
|
const messages = toOpenAIMessages(downgradedMessages, {
|
|
1358
1449
|
provider: options.provider,
|
|
1359
1450
|
thinking: !!options.thinking,
|
|
@@ -1392,7 +1483,7 @@ async function* runStream2(options) {
|
|
|
1392
1483
|
params.thinking = { type: "disabled" };
|
|
1393
1484
|
}
|
|
1394
1485
|
}
|
|
1395
|
-
if (
|
|
1486
|
+
if (getEnvironment()?.GGAI_DUMP_REQUEST) {
|
|
1396
1487
|
const fs = await import("fs");
|
|
1397
1488
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1398
1489
|
const dumpPath = `/tmp/ggai-request-${ts}.json`;
|
|
@@ -1433,19 +1524,7 @@ async function* runStream2(options) {
|
|
|
1433
1524
|
for await (const chunk of stream2) {
|
|
1434
1525
|
const choice = chunk.choices?.[0];
|
|
1435
1526
|
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;
|
|
1527
|
+
({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(chunk.usage));
|
|
1449
1528
|
}
|
|
1450
1529
|
if (!choice) continue;
|
|
1451
1530
|
if (choice.finish_reason) {
|
|
@@ -1591,17 +1670,7 @@ function completionToResponse(completion) {
|
|
|
1591
1670
|
let outputTokens = 0;
|
|
1592
1671
|
let cacheRead = 0;
|
|
1593
1672
|
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;
|
|
1673
|
+
({ inputTokens, outputTokens, cacheRead } = extractOpenAIUsage(completion.usage));
|
|
1605
1674
|
}
|
|
1606
1675
|
const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
|
|
1607
1676
|
return {
|
|
@@ -1613,6 +1682,16 @@ function completionToResponse(completion) {
|
|
|
1613
1682
|
usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
|
|
1614
1683
|
};
|
|
1615
1684
|
}
|
|
1685
|
+
function classifyOpenAICompatLimit(args) {
|
|
1686
|
+
const { status, code, type, message } = args;
|
|
1687
|
+
const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
|
|
1688
|
+
const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
|
|
1689
|
+
if (isHard) return "hard";
|
|
1690
|
+
if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
|
|
1691
|
+
return "transient";
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1616
1695
|
function toError2(err, provider = "openai") {
|
|
1617
1696
|
if (err instanceof import_openai.default.APIError) {
|
|
1618
1697
|
const body = err.error;
|
|
@@ -1624,6 +1703,35 @@ function toError2(err, provider = "openai") {
|
|
|
1624
1703
|
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
1704
|
}
|
|
1626
1705
|
const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
|
|
1706
|
+
const code = typeof err.code === "string" ? err.code : void 0;
|
|
1707
|
+
const type = typeof err.type === "string" ? err.type : void 0;
|
|
1708
|
+
const limit = classifyOpenAICompatLimit({
|
|
1709
|
+
status: err.status,
|
|
1710
|
+
code,
|
|
1711
|
+
type,
|
|
1712
|
+
message: cleanMessage
|
|
1713
|
+
});
|
|
1714
|
+
if (limit === "hard") {
|
|
1715
|
+
const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
|
|
1716
|
+
return new ProviderError(provider, message, {
|
|
1717
|
+
statusCode: err.status,
|
|
1718
|
+
...requestId ? { requestId } : {},
|
|
1719
|
+
...hint ? { hint } : {},
|
|
1720
|
+
cause: err
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
if (limit === "transient") {
|
|
1724
|
+
const retryAfterRaw = readHeader(err.headers, "retry-after");
|
|
1725
|
+
const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
|
|
1726
|
+
const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
|
|
1727
|
+
return new ProviderError(provider, cleanMessage, {
|
|
1728
|
+
statusCode: err.status,
|
|
1729
|
+
...requestId ? { requestId } : {},
|
|
1730
|
+
...hint ? { hint } : {},
|
|
1731
|
+
...resetsAt ? { resetsAt } : {},
|
|
1732
|
+
cause: err
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1627
1735
|
return new ProviderError(provider, cleanMessage, {
|
|
1628
1736
|
statusCode: err.status,
|
|
1629
1737
|
...requestId ? { requestId } : {},
|
|
@@ -1649,21 +1757,59 @@ function providerDiag(phase, data) {
|
|
|
1649
1757
|
_diagFn?.(phase, data);
|
|
1650
1758
|
}
|
|
1651
1759
|
|
|
1652
|
-
// src/
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1760
|
+
// src/utils/sse.ts
|
|
1761
|
+
function parseSseBuffer(buffer) {
|
|
1762
|
+
const events = [];
|
|
1763
|
+
let cursor = 0;
|
|
1764
|
+
while (true) {
|
|
1765
|
+
const next = buffer.indexOf("\n\n", cursor);
|
|
1766
|
+
if (next === -1) break;
|
|
1767
|
+
const raw = buffer.slice(cursor, next);
|
|
1768
|
+
cursor = next + 2;
|
|
1769
|
+
let eventName;
|
|
1770
|
+
const dataLines = [];
|
|
1771
|
+
for (const line of raw.split("\n")) {
|
|
1772
|
+
if (line.startsWith("event:")) {
|
|
1773
|
+
eventName = line.slice("event:".length).trim();
|
|
1774
|
+
} else if (line.startsWith("data:")) {
|
|
1775
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (dataLines.length > 0) {
|
|
1779
|
+
events.push({ event: eventName, data: dataLines.join("\n") });
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
return { events, remaining: buffer.slice(cursor) };
|
|
1656
1783
|
}
|
|
1657
|
-
function
|
|
1658
|
-
|
|
1784
|
+
async function* readSseStream(body) {
|
|
1785
|
+
const reader = body.getReader();
|
|
1786
|
+
const decoder = new TextDecoder();
|
|
1787
|
+
let buffer = "";
|
|
1659
1788
|
try {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1789
|
+
while (true) {
|
|
1790
|
+
const { done, value } = await reader.read();
|
|
1791
|
+
if (done) break;
|
|
1792
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
1793
|
+
const parsed2 = parseSseBuffer(buffer);
|
|
1794
|
+
buffer = parsed2.remaining;
|
|
1795
|
+
yield* parsed2.events;
|
|
1796
|
+
}
|
|
1797
|
+
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
1798
|
+
const parsed = parseSseBuffer(buffer + "\n\n");
|
|
1799
|
+
yield* parsed.events;
|
|
1800
|
+
} finally {
|
|
1801
|
+
reader.releaseLock();
|
|
1665
1802
|
}
|
|
1666
1803
|
}
|
|
1804
|
+
|
|
1805
|
+
// src/utils/request-id.ts
|
|
1806
|
+
function extractRequestIdFromMessage(message) {
|
|
1807
|
+
const match = message.match(/request ID ([a-z0-9-]{8,})/i);
|
|
1808
|
+
return match?.[1];
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/providers/openai-codex.ts
|
|
1812
|
+
var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
1667
1813
|
function outputTextKey(itemId, contentIndex) {
|
|
1668
1814
|
return `${itemId ?? ""}:${contentIndex ?? 0}`;
|
|
1669
1815
|
}
|
|
@@ -1676,7 +1822,8 @@ function streamOpenAICodex(options) {
|
|
|
1676
1822
|
async function* runStream3(options) {
|
|
1677
1823
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1678
1824
|
const url = `${baseUrl}/codex/responses`;
|
|
1679
|
-
const
|
|
1825
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1826
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1680
1827
|
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
1681
1828
|
const body = {
|
|
1682
1829
|
model: options.model,
|
|
@@ -1728,7 +1875,7 @@ async function* runStream3(options) {
|
|
|
1728
1875
|
const text = await response.text().catch(() => "");
|
|
1729
1876
|
const parsed = parseCodexErrorBody(text);
|
|
1730
1877
|
const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
|
|
1731
|
-
const requestId = parsed.requestId ?? response.headers
|
|
1878
|
+
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
1732
1879
|
const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
|
|
1733
1880
|
if (usageLimit) throw usageLimit;
|
|
1734
1881
|
let hint;
|
|
@@ -1772,7 +1919,7 @@ async function* runStream3(options) {
|
|
|
1772
1919
|
const nested = event.error ?? void 0;
|
|
1773
1920
|
const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
|
|
1774
1921
|
const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
|
|
1775
|
-
const requestId =
|
|
1922
|
+
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
1776
1923
|
const usageLimit = codexUsageLimitError(
|
|
1777
1924
|
nested ?? event,
|
|
1778
1925
|
void 0,
|
|
@@ -1787,7 +1934,7 @@ async function* runStream3(options) {
|
|
|
1787
1934
|
if (type === "response.failed") {
|
|
1788
1935
|
const nested = event.error;
|
|
1789
1936
|
const message = nested?.message ?? "Codex response failed.";
|
|
1790
|
-
const requestId =
|
|
1937
|
+
const requestId = extractRequestIdFromMessage(message) ?? event.request_id;
|
|
1791
1938
|
throw new ProviderError("openai", message, {
|
|
1792
1939
|
...requestId != null ? { requestId } : {}
|
|
1793
1940
|
});
|
|
@@ -1906,7 +2053,7 @@ async function* runStream3(options) {
|
|
|
1906
2053
|
const id = `${callId}|${itemId}`;
|
|
1907
2054
|
const tc = toolCalls.get(id);
|
|
1908
2055
|
if (tc) {
|
|
1909
|
-
const args =
|
|
2056
|
+
const args = parseToolArguments(tc.argsJson);
|
|
1910
2057
|
yield {
|
|
1911
2058
|
type: "toolcall_done",
|
|
1912
2059
|
id: tc.id,
|
|
@@ -1930,7 +2077,7 @@ async function* runStream3(options) {
|
|
|
1930
2077
|
contentParts.push({ type: "text", text: textAccum });
|
|
1931
2078
|
}
|
|
1932
2079
|
for (const [, tc] of toolCalls) {
|
|
1933
|
-
const args =
|
|
2080
|
+
const args = parseToolArguments(tc.argsJson);
|
|
1934
2081
|
const toolCall = {
|
|
1935
2082
|
type: "tool_call",
|
|
1936
2083
|
id: tc.id,
|
|
@@ -1953,33 +2100,13 @@ async function* runStream3(options) {
|
|
|
1953
2100
|
return streamResponse;
|
|
1954
2101
|
}
|
|
1955
2102
|
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
|
-
}
|
|
2103
|
+
for await (const event of readSseStream(body)) {
|
|
2104
|
+
const data = event.data.trim();
|
|
2105
|
+
if (!data || data === "[DONE]") continue;
|
|
2106
|
+
try {
|
|
2107
|
+
yield JSON.parse(data);
|
|
2108
|
+
} catch {
|
|
1980
2109
|
}
|
|
1981
|
-
} finally {
|
|
1982
|
-
reader.releaseLock();
|
|
1983
2110
|
}
|
|
1984
2111
|
}
|
|
1985
2112
|
function remapCodexId(id, idMap) {
|
|
@@ -1990,10 +2117,6 @@ function remapCodexId(id, idMap) {
|
|
|
1990
2117
|
idMap.set(id, mapped);
|
|
1991
2118
|
return mapped;
|
|
1992
2119
|
}
|
|
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
2120
|
function toCodexInput(messages, options) {
|
|
1998
2121
|
let system;
|
|
1999
2122
|
const input = [];
|
|
@@ -2050,7 +2173,7 @@ function toCodexInput(messages, options) {
|
|
|
2050
2173
|
const toolImages = [];
|
|
2051
2174
|
for (const result of msg.content) {
|
|
2052
2175
|
const [callId] = result.toolCallId.includes("|") ? result.toolCallId.split("|", 2) : [result.toolCallId];
|
|
2053
|
-
const text =
|
|
2176
|
+
const text = toolResultText(result.content);
|
|
2054
2177
|
input.push({
|
|
2055
2178
|
type: "function_call_output",
|
|
2056
2179
|
call_id: remapCodexId(callId, idMap),
|
|
@@ -2085,14 +2208,10 @@ function toCodexTools(tools) {
|
|
|
2085
2208
|
type: "function",
|
|
2086
2209
|
name: tool.name,
|
|
2087
2210
|
description: tool.description,
|
|
2088
|
-
parameters:
|
|
2211
|
+
parameters: resolveToolSchema(tool),
|
|
2089
2212
|
strict: null
|
|
2090
2213
|
}));
|
|
2091
2214
|
}
|
|
2092
|
-
function extractCodexRequestId(message) {
|
|
2093
|
-
const match = message.match(/request ID ([a-z0-9-]{8,})/i);
|
|
2094
|
-
return match?.[1];
|
|
2095
|
-
}
|
|
2096
2215
|
function parseCodexErrorBody(text) {
|
|
2097
2216
|
if (!text) return {};
|
|
2098
2217
|
try {
|
|
@@ -2100,7 +2219,7 @@ function parseCodexErrorBody(text) {
|
|
|
2100
2219
|
const error = parsed.error;
|
|
2101
2220
|
const detail = parsed.detail;
|
|
2102
2221
|
const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
|
|
2103
|
-
const requestId = parsed.request_id ?? error?.request_id ?? (message ?
|
|
2222
|
+
const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
|
|
2104
2223
|
const errorObj = error ?? parsed;
|
|
2105
2224
|
return {
|
|
2106
2225
|
...message ? { message } : {},
|
|
@@ -2150,12 +2269,6 @@ var CODE_ASSIST_SUPPORTED_MODELS = /* @__PURE__ */ new Set([
|
|
|
2150
2269
|
"gemma-4-31b-it",
|
|
2151
2270
|
"gemma-4-26b-a4b-it"
|
|
2152
2271
|
]);
|
|
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
2272
|
function getGoogleProject(options) {
|
|
2160
2273
|
const env = getEnvironment();
|
|
2161
2274
|
return options.projectId ?? env?.GOOGLE_CLOUD_PROJECT ?? env?.GOOGLE_CLOUD_PROJECT_ID;
|
|
@@ -2177,6 +2290,20 @@ ${formatUnsupportedModelMessage(model)}`;
|
|
|
2177
2290
|
}
|
|
2178
2291
|
return `Gemini API error (${status}): ${body}`;
|
|
2179
2292
|
}
|
|
2293
|
+
function parseRetryDelaySeconds(body) {
|
|
2294
|
+
const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
|
|
2295
|
+
if (!match) return void 0;
|
|
2296
|
+
const seconds = Number(match[1]);
|
|
2297
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2298
|
+
}
|
|
2299
|
+
function parseGeminiQuota(status, body) {
|
|
2300
|
+
if (status !== 429) return null;
|
|
2301
|
+
const lower = body.toLowerCase();
|
|
2302
|
+
if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
|
|
2303
|
+
const retryDelaySeconds = parseRetryDelaySeconds(body);
|
|
2304
|
+
const exhausted = retryDelaySeconds === void 0;
|
|
2305
|
+
return { exhausted, retryDelaySeconds };
|
|
2306
|
+
}
|
|
2180
2307
|
function toSystemAndContents(messages) {
|
|
2181
2308
|
let systemText = "";
|
|
2182
2309
|
const contents = [];
|
|
@@ -2255,7 +2382,7 @@ function toGeminiTools(tools) {
|
|
|
2255
2382
|
functionDeclarations: tools.map((tool) => ({
|
|
2256
2383
|
name: tool.name,
|
|
2257
2384
|
description: tool.description,
|
|
2258
|
-
parameters: sanitizeSchema(
|
|
2385
|
+
parameters: sanitizeSchema(resolveToolSchema(tool))
|
|
2259
2386
|
}))
|
|
2260
2387
|
}
|
|
2261
2388
|
];
|
|
@@ -2266,7 +2393,7 @@ function sanitizeSchema(schema) {
|
|
|
2266
2393
|
return clone;
|
|
2267
2394
|
}
|
|
2268
2395
|
function stripUnsupportedSchemaFields(value) {
|
|
2269
|
-
if (!
|
|
2396
|
+
if (!isJsonObject(value)) {
|
|
2270
2397
|
if (Array.isArray(value)) {
|
|
2271
2398
|
for (const item of value) stripUnsupportedSchemaFields(item);
|
|
2272
2399
|
}
|
|
@@ -2275,7 +2402,7 @@ function stripUnsupportedSchemaFields(value) {
|
|
|
2275
2402
|
delete value.$schema;
|
|
2276
2403
|
delete value.additionalProperties;
|
|
2277
2404
|
for (const item of Object.values(value)) {
|
|
2278
|
-
if (
|
|
2405
|
+
if (isJsonObject(item) || Array.isArray(item)) {
|
|
2279
2406
|
stripUnsupportedSchemaFields(item);
|
|
2280
2407
|
}
|
|
2281
2408
|
}
|
|
@@ -2328,7 +2455,8 @@ function toThinkingConfig(model, level) {
|
|
|
2328
2455
|
};
|
|
2329
2456
|
}
|
|
2330
2457
|
function buildGenerateRequest(options) {
|
|
2331
|
-
const
|
|
2458
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
2459
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
2332
2460
|
const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
|
|
2333
2461
|
const tools = toGeminiTools(options.tools);
|
|
2334
2462
|
const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
|
|
@@ -2390,54 +2518,11 @@ function normalizeGeminiStopReason(reason) {
|
|
|
2390
2518
|
return "end_turn";
|
|
2391
2519
|
}
|
|
2392
2520
|
}
|
|
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
2521
|
async function* streamSse(response) {
|
|
2417
2522
|
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();
|
|
2523
|
+
for await (const event of readSseStream(response.body)) {
|
|
2524
|
+
if (event.data === "[DONE]") continue;
|
|
2525
|
+
yield JSON.parse(event.data);
|
|
2441
2526
|
}
|
|
2442
2527
|
}
|
|
2443
2528
|
function candidatesFromResponse(response) {
|
|
@@ -2460,7 +2545,7 @@ function readFunctionCallPart(part) {
|
|
|
2460
2545
|
return {
|
|
2461
2546
|
...part.functionCall.id ? { id: part.functionCall.id } : {},
|
|
2462
2547
|
name: part.functionCall.name,
|
|
2463
|
-
args:
|
|
2548
|
+
args: isJsonObject(part.functionCall.args) ? part.functionCall.args : {}
|
|
2464
2549
|
};
|
|
2465
2550
|
}
|
|
2466
2551
|
function makeToolCallId(index, providerId) {
|
|
@@ -2499,8 +2584,17 @@ async function fetchCodeAssist(plan, options) {
|
|
|
2499
2584
|
});
|
|
2500
2585
|
if (!response.ok) {
|
|
2501
2586
|
const text = await response.text().catch(() => "");
|
|
2502
|
-
|
|
2503
|
-
|
|
2587
|
+
const quota = parseGeminiQuota(response.status, text);
|
|
2588
|
+
let message = formatErrorMessage(response.status, text, options.model);
|
|
2589
|
+
let resetsAt;
|
|
2590
|
+
if (quota?.exhausted) {
|
|
2591
|
+
message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
|
|
2592
|
+
} else if (quota?.retryDelaySeconds !== void 0) {
|
|
2593
|
+
resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
|
|
2594
|
+
}
|
|
2595
|
+
throw new ProviderError("gemini", message, {
|
|
2596
|
+
statusCode: response.status,
|
|
2597
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2504
2598
|
});
|
|
2505
2599
|
}
|
|
2506
2600
|
return response;
|
|
@@ -2892,6 +2986,7 @@ function registerPalsuProvider(config) {
|
|
|
2892
2986
|
StreamResult,
|
|
2893
2987
|
formatError,
|
|
2894
2988
|
formatErrorForDisplay,
|
|
2989
|
+
isHardBillingMessage,
|
|
2895
2990
|
isUsageLimitError,
|
|
2896
2991
|
palsuAssistantMessage,
|
|
2897
2992
|
palsuText,
|