@prestyj/ai 4.3.239 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +354 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.js +353 -39
- 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,
|
|
@@ -77,6 +78,12 @@ var EZCoderAIError = class extends Error {
|
|
|
77
78
|
this.hint = options?.hint;
|
|
78
79
|
}
|
|
79
80
|
};
|
|
81
|
+
var VideoUnsupportedError = class extends EZCoderAIError {
|
|
82
|
+
constructor() {
|
|
83
|
+
super("This model can't analyze video.", { source: "capability" });
|
|
84
|
+
this.name = "VideoUnsupportedError";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
80
87
|
var ProviderError = class extends EZCoderAIError {
|
|
81
88
|
provider;
|
|
82
89
|
statusCode;
|
|
@@ -117,6 +124,10 @@ function isUsageLimitError(err) {
|
|
|
117
124
|
if (!(err instanceof Error)) return false;
|
|
118
125
|
return /usage limit reached/i.test(err.message);
|
|
119
126
|
}
|
|
127
|
+
function isHardBillingMessage(message) {
|
|
128
|
+
const lower = message.toLowerCase();
|
|
129
|
+
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");
|
|
130
|
+
}
|
|
120
131
|
function formatResetTime(resetsAt) {
|
|
121
132
|
const when = new Date(resetsAt * 1e3);
|
|
122
133
|
const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
@@ -188,6 +199,14 @@ function finaliseBySource(source, message, requestId, hint) {
|
|
|
188
199
|
guidance: hint ?? providerGuidance(void 0, message, void 0),
|
|
189
200
|
...requestId ? { requestId } : {}
|
|
190
201
|
};
|
|
202
|
+
case "capability":
|
|
203
|
+
return {
|
|
204
|
+
headline: message,
|
|
205
|
+
source,
|
|
206
|
+
message: "",
|
|
207
|
+
guidance: hint ?? "Only Kimi, Gemini, MiniMax, and MiMo-V2.5 can analyze video. Switch with /model.",
|
|
208
|
+
...requestId ? { requestId } : {}
|
|
209
|
+
};
|
|
191
210
|
case "ezcoder":
|
|
192
211
|
return {
|
|
193
212
|
headline: "ezcoder hit an unexpected error.",
|
|
@@ -462,8 +481,8 @@ function toAnthropicAssistantPart(part, idMap) {
|
|
|
462
481
|
if (part.type === "raw") return part.data;
|
|
463
482
|
return null;
|
|
464
483
|
}
|
|
465
|
-
function toAnthropicAssistantContent(content,
|
|
466
|
-
if (!
|
|
484
|
+
function toAnthropicAssistantContent(content, preserveThinking, idMap) {
|
|
485
|
+
if (!preserveThinking) {
|
|
467
486
|
return content.filter((part) => {
|
|
468
487
|
if (part.type === "thinking" || isRawThinking(part)) return false;
|
|
469
488
|
if (part.type === "text" && !part.text) return false;
|
|
@@ -482,6 +501,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
|
|
|
482
501
|
}
|
|
483
502
|
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
484
503
|
var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
504
|
+
var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
|
|
485
505
|
function stripImages(content, placeholder) {
|
|
486
506
|
const out = [];
|
|
487
507
|
let lastWasPlaceholder = false;
|
|
@@ -492,10 +512,33 @@ function stripImages(content, placeholder) {
|
|
|
492
512
|
continue;
|
|
493
513
|
}
|
|
494
514
|
out.push(block);
|
|
495
|
-
lastWasPlaceholder = block.text === placeholder;
|
|
515
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
496
516
|
}
|
|
497
517
|
return out;
|
|
498
518
|
}
|
|
519
|
+
function stripVideos(content, placeholder) {
|
|
520
|
+
const out = [];
|
|
521
|
+
let lastWasPlaceholder = false;
|
|
522
|
+
for (const block of content) {
|
|
523
|
+
if (block.type === "video") {
|
|
524
|
+
if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
|
|
525
|
+
lastWasPlaceholder = true;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
out.push(block);
|
|
529
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
530
|
+
}
|
|
531
|
+
return out;
|
|
532
|
+
}
|
|
533
|
+
function downgradeUnsupportedVideos(messages, supportsVideo) {
|
|
534
|
+
if (supportsVideo === true) return messages;
|
|
535
|
+
return messages.map((msg) => {
|
|
536
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
537
|
+
return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
|
|
538
|
+
}
|
|
539
|
+
return msg;
|
|
540
|
+
});
|
|
541
|
+
}
|
|
499
542
|
function downgradeUnsupportedImages(messages, supportsImages) {
|
|
500
543
|
if (supportsImages !== false) return messages;
|
|
501
544
|
return messages.map((msg) => {
|
|
@@ -524,6 +567,10 @@ function toolResultImages(content) {
|
|
|
524
567
|
if (typeof content === "string") return [];
|
|
525
568
|
return content.filter((b) => b.type === "image");
|
|
526
569
|
}
|
|
570
|
+
function toolResultVideos(content) {
|
|
571
|
+
if (typeof content === "string") return [];
|
|
572
|
+
return content.filter((b) => b.type === "video");
|
|
573
|
+
}
|
|
527
574
|
function toAnthropicCacheControl(retention, baseUrl) {
|
|
528
575
|
const resolved = retention ?? "short";
|
|
529
576
|
if (resolved === "none") return void 0;
|
|
@@ -534,6 +581,12 @@ function toAnthropicToolResultContent(content) {
|
|
|
534
581
|
if (typeof content === "string") return content;
|
|
535
582
|
return content.map((block) => {
|
|
536
583
|
if (block.type === "text") return { type: "text", text: block.text };
|
|
584
|
+
if (block.type === "video") {
|
|
585
|
+
return {
|
|
586
|
+
type: "video",
|
|
587
|
+
source: { type: "base64", media_type: block.mediaType, data: block.data }
|
|
588
|
+
};
|
|
589
|
+
}
|
|
537
590
|
return {
|
|
538
591
|
type: "image",
|
|
539
592
|
source: {
|
|
@@ -556,8 +609,8 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
556
609
|
let systemText;
|
|
557
610
|
const out = [];
|
|
558
611
|
const idMap = /* @__PURE__ */ new Map();
|
|
559
|
-
const
|
|
560
|
-
(last, m, i) => m.role === "
|
|
612
|
+
const trajectoryStartIdx = messages.reduce(
|
|
613
|
+
(last, m, i) => m.role === "user" ? i : last,
|
|
561
614
|
-1
|
|
562
615
|
);
|
|
563
616
|
let msgIdx = -1;
|
|
@@ -572,6 +625,16 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
572
625
|
role: "user",
|
|
573
626
|
content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
|
|
574
627
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
628
|
+
if (part.type === "video") {
|
|
629
|
+
return {
|
|
630
|
+
type: "video",
|
|
631
|
+
source: {
|
|
632
|
+
type: "base64",
|
|
633
|
+
media_type: part.mediaType,
|
|
634
|
+
data: part.data
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
575
638
|
return {
|
|
576
639
|
type: "image",
|
|
577
640
|
source: {
|
|
@@ -585,7 +648,7 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
585
648
|
continue;
|
|
586
649
|
}
|
|
587
650
|
if (msg.role === "assistant") {
|
|
588
|
-
const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx
|
|
651
|
+
const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx > trajectoryStartIdx, idMap);
|
|
589
652
|
if (Array.isArray(content) && content.length === 0) continue;
|
|
590
653
|
out.push({ role: "assistant", content });
|
|
591
654
|
continue;
|
|
@@ -593,6 +656,8 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
593
656
|
if (msg.role === "tool") {
|
|
594
657
|
out.push({
|
|
595
658
|
role: "user",
|
|
659
|
+
// Cast covers the video block (used by the Anthropic-compatible MiniMax
|
|
660
|
+
// API), which isn't in the first-party Anthropic tool_result types.
|
|
596
661
|
content: msg.content.map((result) => ({
|
|
597
662
|
type: "tool_result",
|
|
598
663
|
tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
|
|
@@ -732,6 +797,13 @@ function toOpenAIMessages(messages, options) {
|
|
|
732
797
|
content: msg.content.map(
|
|
733
798
|
(part) => {
|
|
734
799
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
800
|
+
if (part.type === "video") {
|
|
801
|
+
const videoUrl = part.fileId ? { url: `ms://${part.fileId}`, id: part.fileId } : { url: `data:${part.mediaType};base64,${part.data}` };
|
|
802
|
+
return {
|
|
803
|
+
type: "video_url",
|
|
804
|
+
video_url: videoUrl
|
|
805
|
+
};
|
|
806
|
+
}
|
|
735
807
|
return {
|
|
736
808
|
type: "image_url",
|
|
737
809
|
image_url: {
|
|
@@ -774,29 +846,56 @@ function toOpenAIMessages(messages, options) {
|
|
|
774
846
|
continue;
|
|
775
847
|
}
|
|
776
848
|
if (msg.role === "tool") {
|
|
777
|
-
const
|
|
849
|
+
const isMoonshot = options?.provider === "moonshot";
|
|
850
|
+
const followUpMediaBlocks = [];
|
|
851
|
+
let followUpHasVideo = false;
|
|
778
852
|
for (const result of msg.content) {
|
|
779
853
|
const text = toolResultText(result.content);
|
|
780
854
|
const images = toolResultImages(result.content);
|
|
855
|
+
const videos = toolResultVideos(result.content);
|
|
781
856
|
const hasText = text.length > 0;
|
|
857
|
+
if (isMoonshot && videos.length > 0) {
|
|
858
|
+
const parts = [];
|
|
859
|
+
if (hasText) parts.push({ type: "text", text });
|
|
860
|
+
const videoParts = videos.map((v) => {
|
|
861
|
+
const videoUrl = v.fileId ? { url: `ms://${v.fileId}`, id: v.fileId } : { url: `data:${v.mediaType};base64,${v.data}` };
|
|
862
|
+
return { type: "video_url", video_url: videoUrl };
|
|
863
|
+
});
|
|
864
|
+
out.push({
|
|
865
|
+
role: "tool",
|
|
866
|
+
tool_call_id: remapToolCallId(result.toolCallId, idMap),
|
|
867
|
+
content: [...parts, ...videoParts]
|
|
868
|
+
});
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
782
871
|
out.push({
|
|
783
872
|
role: "tool",
|
|
784
873
|
tool_call_id: remapToolCallId(result.toolCallId, idMap),
|
|
785
|
-
content: hasText ? text : "(see attached
|
|
874
|
+
content: hasText ? text : "(see attached media)"
|
|
786
875
|
});
|
|
787
876
|
if (images.length > 0 && options?.supportsImages !== false) {
|
|
788
877
|
for (const img of images) {
|
|
789
|
-
|
|
878
|
+
followUpMediaBlocks.push({
|
|
790
879
|
type: "image_url",
|
|
791
880
|
image_url: { url: `data:${img.mediaType};base64,${img.data}` }
|
|
792
881
|
});
|
|
793
882
|
}
|
|
794
883
|
}
|
|
884
|
+
if (!isMoonshot && videos.length > 0) {
|
|
885
|
+
for (const v of videos) {
|
|
886
|
+
followUpMediaBlocks.push({
|
|
887
|
+
type: "video_url",
|
|
888
|
+
video_url: { url: `data:${v.mediaType};base64,${v.data}` }
|
|
889
|
+
});
|
|
890
|
+
followUpHasVideo = true;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
795
893
|
}
|
|
796
|
-
if (
|
|
894
|
+
if (followUpMediaBlocks.length > 0) {
|
|
895
|
+
const label = followUpHasVideo ? "Attached media from tool result:" : "Attached image(s) from tool result:";
|
|
797
896
|
out.push({
|
|
798
897
|
role: "user",
|
|
799
|
-
content: [{ type: "text", text:
|
|
898
|
+
content: [{ type: "text", text: label }, ...followUpMediaBlocks]
|
|
800
899
|
});
|
|
801
900
|
}
|
|
802
901
|
}
|
|
@@ -897,7 +996,8 @@ async function* runStream(options) {
|
|
|
897
996
|
const useStreaming = options.streaming !== false;
|
|
898
997
|
const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
|
|
899
998
|
const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
|
|
900
|
-
const
|
|
999
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1000
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
901
1001
|
const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
|
|
902
1002
|
const system = isOAuth ? [
|
|
903
1003
|
{
|
|
@@ -1103,11 +1203,18 @@ async function* runStream(options) {
|
|
|
1103
1203
|
args: tc.args
|
|
1104
1204
|
};
|
|
1105
1205
|
} else if (accum.type === "server_tool_use") {
|
|
1206
|
+
let input = accum.input;
|
|
1207
|
+
if (accum.argsJson) {
|
|
1208
|
+
try {
|
|
1209
|
+
input = JSON.parse(accum.argsJson);
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1106
1213
|
const stc = {
|
|
1107
1214
|
type: "server_tool_call",
|
|
1108
1215
|
id: accum.toolId,
|
|
1109
1216
|
name: accum.toolName,
|
|
1110
|
-
input
|
|
1217
|
+
input
|
|
1111
1218
|
};
|
|
1112
1219
|
contentParts.push(stc);
|
|
1113
1220
|
yield {
|
|
@@ -1315,6 +1422,14 @@ function toError(err) {
|
|
|
1315
1422
|
});
|
|
1316
1423
|
}
|
|
1317
1424
|
}
|
|
1425
|
+
if (isHardBillingMessage(message)) {
|
|
1426
|
+
const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
1427
|
+
return new ProviderError("anthropic", usageMessage, {
|
|
1428
|
+
statusCode: err.status,
|
|
1429
|
+
...requestId ? { requestId } : {},
|
|
1430
|
+
cause: err
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1318
1433
|
return new ProviderError("anthropic", message, {
|
|
1319
1434
|
statusCode: err.status,
|
|
1320
1435
|
...requestId ? { requestId } : {},
|
|
@@ -1347,6 +1462,75 @@ function fnv1aHash(value) {
|
|
|
1347
1462
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1348
1463
|
}
|
|
1349
1464
|
|
|
1465
|
+
// src/utils/diag.ts
|
|
1466
|
+
var _diagFn = null;
|
|
1467
|
+
function setProviderDiagnostic(fn) {
|
|
1468
|
+
_diagFn = fn;
|
|
1469
|
+
}
|
|
1470
|
+
function providerDiag(phase, data) {
|
|
1471
|
+
_diagFn?.(phase, data);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/providers/moonshot-video.ts
|
|
1475
|
+
async function uploadMoonshotVideos(client, messages, signal) {
|
|
1476
|
+
for (const msg of messages) {
|
|
1477
|
+
if (typeof msg.content === "string") continue;
|
|
1478
|
+
for (const part of msg.content) {
|
|
1479
|
+
if (part.type === "video") {
|
|
1480
|
+
await ensureUploaded(client, part, signal);
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
if (part.type === "tool_result" && Array.isArray(part.content)) {
|
|
1484
|
+
for (const inner of part.content) {
|
|
1485
|
+
if (inner.type === "video") {
|
|
1486
|
+
await ensureUploaded(client, inner, signal);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
async function ensureUploaded(client, video, signal) {
|
|
1494
|
+
if (video.fileId) {
|
|
1495
|
+
providerDiag("moonshot_video_cached", { fileId: video.fileId });
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (!video.data) {
|
|
1499
|
+
providerDiag("moonshot_video_skipped_no_data", {});
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
providerDiag("moonshot_video_upload_start", {
|
|
1503
|
+
mediaType: video.mediaType,
|
|
1504
|
+
bytes: Math.floor(video.data.length * 3 / 4)
|
|
1505
|
+
});
|
|
1506
|
+
video.fileId = await uploadOne(client, video, signal);
|
|
1507
|
+
providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
|
|
1508
|
+
}
|
|
1509
|
+
async function uploadOne(client, video, signal) {
|
|
1510
|
+
const bytes = Buffer.from(video.data, "base64");
|
|
1511
|
+
const mediaType = video.mediaType || "video/mp4";
|
|
1512
|
+
const filename = `upload.${extForMime(mediaType)}`;
|
|
1513
|
+
const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
|
|
1514
|
+
const uploaded = await client.files.create(
|
|
1515
|
+
{ file, purpose: "video" },
|
|
1516
|
+
signal ? { signal } : void 0
|
|
1517
|
+
);
|
|
1518
|
+
return uploaded.id;
|
|
1519
|
+
}
|
|
1520
|
+
var MIME_TO_EXT = {
|
|
1521
|
+
"video/mp4": "mp4",
|
|
1522
|
+
"video/mpeg": "mpeg",
|
|
1523
|
+
"video/quicktime": "mov",
|
|
1524
|
+
"video/webm": "webm",
|
|
1525
|
+
"video/x-matroska": "mkv",
|
|
1526
|
+
"video/x-msvideo": "avi",
|
|
1527
|
+
"video/x-flv": "flv",
|
|
1528
|
+
"video/3gpp": "3gp"
|
|
1529
|
+
};
|
|
1530
|
+
function extForMime(mediaType) {
|
|
1531
|
+
return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1350
1534
|
// src/utils/env.ts
|
|
1351
1535
|
function getEnvironment() {
|
|
1352
1536
|
return globalThis.process?.env;
|
|
@@ -1376,7 +1560,8 @@ function createClient2(options) {
|
|
|
1376
1560
|
return new import_openai.default({
|
|
1377
1561
|
apiKey: options.apiKey,
|
|
1378
1562
|
...options.baseUrl ? { baseURL: options.baseUrl } : {},
|
|
1379
|
-
...options.fetch ? { fetch: options.fetch } : {}
|
|
1563
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
1564
|
+
...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
|
|
1380
1565
|
});
|
|
1381
1566
|
}
|
|
1382
1567
|
function streamOpenAI(options) {
|
|
@@ -1387,7 +1572,15 @@ async function* runStream2(options) {
|
|
|
1387
1572
|
const useStreaming = options.streaming !== false;
|
|
1388
1573
|
const client = createClient2(options);
|
|
1389
1574
|
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
|
|
1390
|
-
const
|
|
1575
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1576
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1577
|
+
if (options.provider === "moonshot") {
|
|
1578
|
+
try {
|
|
1579
|
+
await uploadMoonshotVideos(client, downgradedMessages, options.signal);
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
throw toError2(err, providerName);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1391
1584
|
const messages = toOpenAIMessages(downgradedMessages, {
|
|
1392
1585
|
provider: options.provider,
|
|
1393
1586
|
thinking: !!options.thinking,
|
|
@@ -1625,6 +1818,16 @@ function completionToResponse(completion) {
|
|
|
1625
1818
|
usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
|
|
1626
1819
|
};
|
|
1627
1820
|
}
|
|
1821
|
+
function classifyOpenAICompatLimit(args) {
|
|
1822
|
+
const { status, code, type, message } = args;
|
|
1823
|
+
const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
|
|
1824
|
+
const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
|
|
1825
|
+
if (isHard) return "hard";
|
|
1826
|
+
if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
|
|
1827
|
+
return "transient";
|
|
1828
|
+
}
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1628
1831
|
function toError2(err, provider = "openai") {
|
|
1629
1832
|
if (err instanceof import_openai.default.APIError) {
|
|
1630
1833
|
const body = err.error;
|
|
@@ -1636,6 +1839,35 @@ function toError2(err, provider = "openai") {
|
|
|
1636
1839
|
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.";
|
|
1637
1840
|
}
|
|
1638
1841
|
const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
|
|
1842
|
+
const code = typeof err.code === "string" ? err.code : void 0;
|
|
1843
|
+
const type = typeof err.type === "string" ? err.type : void 0;
|
|
1844
|
+
const limit = classifyOpenAICompatLimit({
|
|
1845
|
+
status: err.status,
|
|
1846
|
+
code,
|
|
1847
|
+
type,
|
|
1848
|
+
message: cleanMessage
|
|
1849
|
+
});
|
|
1850
|
+
if (limit === "hard") {
|
|
1851
|
+
const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
|
|
1852
|
+
return new ProviderError(provider, message, {
|
|
1853
|
+
statusCode: err.status,
|
|
1854
|
+
...requestId ? { requestId } : {},
|
|
1855
|
+
...hint ? { hint } : {},
|
|
1856
|
+
cause: err
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
if (limit === "transient") {
|
|
1860
|
+
const retryAfterRaw = readHeader(err.headers, "retry-after");
|
|
1861
|
+
const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
|
|
1862
|
+
const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
|
|
1863
|
+
return new ProviderError(provider, cleanMessage, {
|
|
1864
|
+
statusCode: err.status,
|
|
1865
|
+
...requestId ? { requestId } : {},
|
|
1866
|
+
...hint ? { hint } : {},
|
|
1867
|
+
...resetsAt ? { resetsAt } : {},
|
|
1868
|
+
cause: err
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1639
1871
|
return new ProviderError(provider, cleanMessage, {
|
|
1640
1872
|
statusCode: err.status,
|
|
1641
1873
|
...requestId ? { requestId } : {},
|
|
@@ -1652,15 +1884,6 @@ function toError2(err, provider = "openai") {
|
|
|
1652
1884
|
// src/providers/openai-codex.ts
|
|
1653
1885
|
var import_node_os = __toESM(require("os"), 1);
|
|
1654
1886
|
|
|
1655
|
-
// src/utils/diag.ts
|
|
1656
|
-
var _diagFn = null;
|
|
1657
|
-
function setProviderDiagnostic(fn) {
|
|
1658
|
-
_diagFn = fn;
|
|
1659
|
-
}
|
|
1660
|
-
function providerDiag(phase, data) {
|
|
1661
|
-
_diagFn?.(phase, data);
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
1887
|
// src/utils/sse.ts
|
|
1665
1888
|
function parseSseBuffer(buffer) {
|
|
1666
1889
|
const events = [];
|
|
@@ -1726,7 +1949,8 @@ function streamOpenAICodex(options) {
|
|
|
1726
1949
|
async function* runStream3(options) {
|
|
1727
1950
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1728
1951
|
const url = `${baseUrl}/codex/responses`;
|
|
1729
|
-
const
|
|
1952
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1953
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1730
1954
|
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
1731
1955
|
const body = {
|
|
1732
1956
|
model: options.model,
|
|
@@ -1803,6 +2027,7 @@ async function* runStream3(options) {
|
|
|
1803
2027
|
const contentParts = [];
|
|
1804
2028
|
let textAccum = "";
|
|
1805
2029
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
2030
|
+
const orderedItems = [];
|
|
1806
2031
|
const outputItemTypes = /* @__PURE__ */ new Map();
|
|
1807
2032
|
const outputTextByPart = /* @__PURE__ */ new Map();
|
|
1808
2033
|
const pendingOutputTextByPart = /* @__PURE__ */ new Map();
|
|
@@ -1950,12 +2175,26 @@ async function* runStream3(options) {
|
|
|
1950
2175
|
}
|
|
1951
2176
|
if (type === "response.output_item.done") {
|
|
1952
2177
|
const item = event.item;
|
|
2178
|
+
if (item?.type === "reasoning") {
|
|
2179
|
+
const encrypted = item.encrypted_content;
|
|
2180
|
+
const reasoningId = item.id;
|
|
2181
|
+
if (encrypted && reasoningId) {
|
|
2182
|
+
orderedItems.push({
|
|
2183
|
+
kind: "reasoning",
|
|
2184
|
+
part: {
|
|
2185
|
+
type: "raw",
|
|
2186
|
+
data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] }
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
1953
2191
|
if (item?.type === "function_call") {
|
|
1954
2192
|
const callId = item.call_id;
|
|
1955
2193
|
const itemId = item.id;
|
|
1956
2194
|
const id = `${callId}|${itemId}`;
|
|
1957
2195
|
const tc = toolCalls.get(id);
|
|
1958
2196
|
if (tc) {
|
|
2197
|
+
orderedItems.push({ kind: "tool", id });
|
|
1959
2198
|
const args = parseToolArguments(tc.argsJson);
|
|
1960
2199
|
yield {
|
|
1961
2200
|
type: "toolcall_done",
|
|
@@ -1976,19 +2215,41 @@ async function* runStream3(options) {
|
|
|
1976
2215
|
}
|
|
1977
2216
|
}
|
|
1978
2217
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2218
|
+
const seenTool = /* @__PURE__ */ new Set();
|
|
2219
|
+
let textInserted = false;
|
|
2220
|
+
for (const entry of orderedItems) {
|
|
2221
|
+
if (entry.kind === "reasoning") {
|
|
2222
|
+
contentParts.push(entry.part);
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
if (textAccum && !textInserted) {
|
|
2226
|
+
contentParts.push({ type: "text", text: textAccum });
|
|
2227
|
+
textInserted = true;
|
|
2228
|
+
}
|
|
2229
|
+
const tc = toolCalls.get(entry.id);
|
|
2230
|
+
if (!tc || seenTool.has(entry.id)) continue;
|
|
2231
|
+
seenTool.add(entry.id);
|
|
1984
2232
|
const toolCall = {
|
|
1985
2233
|
type: "tool_call",
|
|
1986
2234
|
id: tc.id,
|
|
1987
2235
|
name: tc.name,
|
|
1988
|
-
args
|
|
2236
|
+
args: parseToolArguments(tc.argsJson)
|
|
1989
2237
|
};
|
|
1990
2238
|
contentParts.push(toolCall);
|
|
1991
2239
|
}
|
|
2240
|
+
if (textAccum && !textInserted) {
|
|
2241
|
+
contentParts.push({ type: "text", text: textAccum });
|
|
2242
|
+
}
|
|
2243
|
+
for (const [id, tc] of toolCalls) {
|
|
2244
|
+
if (seenTool.has(id)) continue;
|
|
2245
|
+
seenTool.add(id);
|
|
2246
|
+
contentParts.push({
|
|
2247
|
+
type: "tool_call",
|
|
2248
|
+
id: tc.id,
|
|
2249
|
+
name: tc.name,
|
|
2250
|
+
args: parseToolArguments(tc.argsJson)
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
1992
2253
|
const hasToolCalls = contentParts.some((p) => p.type === "tool_call");
|
|
1993
2254
|
const stopReason = hasToolCalls ? "tool_use" : "end_turn";
|
|
1994
2255
|
const streamResponse = {
|
|
@@ -2020,6 +2281,9 @@ function remapCodexId(id, idMap) {
|
|
|
2020
2281
|
idMap.set(id, mapped);
|
|
2021
2282
|
return mapped;
|
|
2022
2283
|
}
|
|
2284
|
+
function isEncryptedReasoning(data) {
|
|
2285
|
+
return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
|
|
2286
|
+
}
|
|
2023
2287
|
function toCodexInput(messages, options) {
|
|
2024
2288
|
let system;
|
|
2025
2289
|
const input = [];
|
|
@@ -2052,7 +2316,9 @@ function toCodexInput(messages, options) {
|
|
|
2052
2316
|
continue;
|
|
2053
2317
|
}
|
|
2054
2318
|
for (const part of msg.content) {
|
|
2055
|
-
if (part.type === "
|
|
2319
|
+
if (part.type === "raw" && isEncryptedReasoning(part.data)) {
|
|
2320
|
+
input.push(part.data);
|
|
2321
|
+
} else if (part.type === "text") {
|
|
2056
2322
|
input.push({
|
|
2057
2323
|
type: "message",
|
|
2058
2324
|
role: "assistant",
|
|
@@ -2193,6 +2459,20 @@ ${formatUnsupportedModelMessage(model)}`;
|
|
|
2193
2459
|
}
|
|
2194
2460
|
return `Gemini API error (${status}): ${body}`;
|
|
2195
2461
|
}
|
|
2462
|
+
function parseRetryDelaySeconds(body) {
|
|
2463
|
+
const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
|
|
2464
|
+
if (!match) return void 0;
|
|
2465
|
+
const seconds = Number(match[1]);
|
|
2466
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2467
|
+
}
|
|
2468
|
+
function parseGeminiQuota(status, body) {
|
|
2469
|
+
if (status !== 429) return null;
|
|
2470
|
+
const lower = body.toLowerCase();
|
|
2471
|
+
if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
|
|
2472
|
+
const retryDelaySeconds = parseRetryDelaySeconds(body);
|
|
2473
|
+
const exhausted = retryDelaySeconds === void 0;
|
|
2474
|
+
return { exhausted, retryDelaySeconds };
|
|
2475
|
+
}
|
|
2196
2476
|
function toSystemAndContents(messages) {
|
|
2197
2477
|
let systemText = "";
|
|
2198
2478
|
const contents = [];
|
|
@@ -2252,6 +2532,13 @@ ${msg.content}` : msg.content;
|
|
|
2252
2532
|
}
|
|
2253
2533
|
}
|
|
2254
2534
|
});
|
|
2535
|
+
if (typeof result.content !== "string") {
|
|
2536
|
+
for (const block of result.content) {
|
|
2537
|
+
if (block.type === "video") {
|
|
2538
|
+
parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2255
2542
|
}
|
|
2256
2543
|
if (parts.length > 0) contents.push({ role: "user", parts });
|
|
2257
2544
|
}
|
|
@@ -2344,7 +2631,8 @@ function toThinkingConfig(model, level) {
|
|
|
2344
2631
|
};
|
|
2345
2632
|
}
|
|
2346
2633
|
function buildGenerateRequest(options) {
|
|
2347
|
-
const
|
|
2634
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
2635
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
2348
2636
|
const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
|
|
2349
2637
|
const tools = toGeminiTools(options.tools);
|
|
2350
2638
|
const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
|
|
@@ -2472,8 +2760,17 @@ async function fetchCodeAssist(plan, options) {
|
|
|
2472
2760
|
});
|
|
2473
2761
|
if (!response.ok) {
|
|
2474
2762
|
const text = await response.text().catch(() => "");
|
|
2475
|
-
|
|
2476
|
-
|
|
2763
|
+
const quota = parseGeminiQuota(response.status, text);
|
|
2764
|
+
let message = formatErrorMessage(response.status, text, options.model);
|
|
2765
|
+
let resetsAt;
|
|
2766
|
+
if (quota?.exhausted) {
|
|
2767
|
+
message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
|
|
2768
|
+
} else if (quota?.retryDelaySeconds !== void 0) {
|
|
2769
|
+
resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
|
|
2770
|
+
}
|
|
2771
|
+
throw new ProviderError("gemini", message, {
|
|
2772
|
+
statusCode: response.status,
|
|
2773
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2477
2774
|
});
|
|
2478
2775
|
}
|
|
2479
2776
|
return response;
|
|
@@ -2639,6 +2936,7 @@ var providerRegistry = new ProviderRegistryImpl();
|
|
|
2639
2936
|
|
|
2640
2937
|
// src/stream.ts
|
|
2641
2938
|
var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
|
2939
|
+
var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
|
|
2642
2940
|
providerRegistry.register("anthropic", {
|
|
2643
2941
|
stream: (options) => streamAnthropic(options)
|
|
2644
2942
|
});
|
|
@@ -2667,10 +2965,11 @@ providerRegistry.register("glm", {
|
|
|
2667
2965
|
})
|
|
2668
2966
|
});
|
|
2669
2967
|
providerRegistry.register("moonshot", {
|
|
2670
|
-
stream: (options) =>
|
|
2671
|
-
|
|
2672
|
-
baseUrl: options.
|
|
2673
|
-
|
|
2968
|
+
stream: (options) => {
|
|
2969
|
+
const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
|
|
2970
|
+
const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
|
|
2971
|
+
return streamOpenAI({ ...options, baseUrl, defaultHeaders });
|
|
2972
|
+
}
|
|
2674
2973
|
});
|
|
2675
2974
|
providerRegistry.register("deepseek", {
|
|
2676
2975
|
stream: (options) => streamOpenAI({
|
|
@@ -2703,8 +3002,23 @@ function stream(options) {
|
|
|
2703
3002
|
`Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
|
|
2704
3003
|
);
|
|
2705
3004
|
}
|
|
3005
|
+
if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
|
|
3006
|
+
throw new VideoUnsupportedError();
|
|
3007
|
+
}
|
|
2706
3008
|
return entry.stream(options);
|
|
2707
3009
|
}
|
|
3010
|
+
function messagesContainVideo(messages) {
|
|
3011
|
+
for (const msg of messages) {
|
|
3012
|
+
if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
|
|
3013
|
+
for (const part of msg.content) {
|
|
3014
|
+
if (part.type === "video") return true;
|
|
3015
|
+
if (part.type === "tool_result" && Array.isArray(part.content)) {
|
|
3016
|
+
if (part.content.some((block) => block.type === "video")) return true;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
return false;
|
|
3021
|
+
}
|
|
2708
3022
|
|
|
2709
3023
|
// src/providers/palsu.ts
|
|
2710
3024
|
function palsuText(text) {
|
|
@@ -2865,6 +3179,7 @@ function registerPalsuProvider(config) {
|
|
|
2865
3179
|
StreamResult,
|
|
2866
3180
|
formatError,
|
|
2867
3181
|
formatErrorForDisplay,
|
|
3182
|
+
isHardBillingMessage,
|
|
2868
3183
|
isUsageLimitError,
|
|
2869
3184
|
palsuAssistantMessage,
|
|
2870
3185
|
palsuText,
|