@prestyj/ai 4.3.238 → 4.4.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.js
CHANGED
|
@@ -25,6 +25,12 @@ var EZCoderAIError = class extends Error {
|
|
|
25
25
|
this.hint = options?.hint;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
+
var VideoUnsupportedError = class extends EZCoderAIError {
|
|
29
|
+
constructor() {
|
|
30
|
+
super("This model can't analyze video.", { source: "capability" });
|
|
31
|
+
this.name = "VideoUnsupportedError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
28
34
|
var ProviderError = class extends EZCoderAIError {
|
|
29
35
|
provider;
|
|
30
36
|
statusCode;
|
|
@@ -65,6 +71,10 @@ function isUsageLimitError(err) {
|
|
|
65
71
|
if (!(err instanceof Error)) return false;
|
|
66
72
|
return /usage limit reached/i.test(err.message);
|
|
67
73
|
}
|
|
74
|
+
function isHardBillingMessage(message) {
|
|
75
|
+
const lower = message.toLowerCase();
|
|
76
|
+
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");
|
|
77
|
+
}
|
|
68
78
|
function formatResetTime(resetsAt) {
|
|
69
79
|
const when = new Date(resetsAt * 1e3);
|
|
70
80
|
const sameDay = when.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
@@ -136,6 +146,14 @@ function finaliseBySource(source, message, requestId, hint) {
|
|
|
136
146
|
guidance: hint ?? providerGuidance(void 0, message, void 0),
|
|
137
147
|
...requestId ? { requestId } : {}
|
|
138
148
|
};
|
|
149
|
+
case "capability":
|
|
150
|
+
return {
|
|
151
|
+
headline: message,
|
|
152
|
+
source,
|
|
153
|
+
message: "",
|
|
154
|
+
guidance: hint ?? "Only Kimi, Gemini, MiniMax, and MiMo-V2.5 can analyze video. Switch with /model.",
|
|
155
|
+
...requestId ? { requestId } : {}
|
|
156
|
+
};
|
|
139
157
|
case "ezcoder":
|
|
140
158
|
return {
|
|
141
159
|
headline: "ezcoder hit an unexpected error.",
|
|
@@ -410,8 +428,8 @@ function toAnthropicAssistantPart(part, idMap) {
|
|
|
410
428
|
if (part.type === "raw") return part.data;
|
|
411
429
|
return null;
|
|
412
430
|
}
|
|
413
|
-
function toAnthropicAssistantContent(content,
|
|
414
|
-
if (!
|
|
431
|
+
function toAnthropicAssistantContent(content, preserveThinking, idMap) {
|
|
432
|
+
if (!preserveThinking) {
|
|
415
433
|
return content.filter((part) => {
|
|
416
434
|
if (part.type === "thinking" || isRawThinking(part)) return false;
|
|
417
435
|
if (part.type === "text" && !part.text) return false;
|
|
@@ -430,6 +448,7 @@ function toAnthropicAssistantContent(content, isLatest, idMap) {
|
|
|
430
448
|
}
|
|
431
449
|
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
432
450
|
var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
451
|
+
var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
|
|
433
452
|
function stripImages(content, placeholder) {
|
|
434
453
|
const out = [];
|
|
435
454
|
let lastWasPlaceholder = false;
|
|
@@ -440,10 +459,33 @@ function stripImages(content, placeholder) {
|
|
|
440
459
|
continue;
|
|
441
460
|
}
|
|
442
461
|
out.push(block);
|
|
443
|
-
lastWasPlaceholder = block.text === placeholder;
|
|
462
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
444
463
|
}
|
|
445
464
|
return out;
|
|
446
465
|
}
|
|
466
|
+
function stripVideos(content, placeholder) {
|
|
467
|
+
const out = [];
|
|
468
|
+
let lastWasPlaceholder = false;
|
|
469
|
+
for (const block of content) {
|
|
470
|
+
if (block.type === "video") {
|
|
471
|
+
if (!lastWasPlaceholder) out.push({ type: "text", text: placeholder });
|
|
472
|
+
lastWasPlaceholder = true;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
out.push(block);
|
|
476
|
+
lastWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
function downgradeUnsupportedVideos(messages, supportsVideo) {
|
|
481
|
+
if (supportsVideo === true) return messages;
|
|
482
|
+
return messages.map((msg) => {
|
|
483
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
484
|
+
return { ...msg, content: stripVideos(msg.content, NON_VIDEO_USER_PLACEHOLDER) };
|
|
485
|
+
}
|
|
486
|
+
return msg;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
447
489
|
function downgradeUnsupportedImages(messages, supportsImages) {
|
|
448
490
|
if (supportsImages !== false) return messages;
|
|
449
491
|
return messages.map((msg) => {
|
|
@@ -472,6 +514,10 @@ function toolResultImages(content) {
|
|
|
472
514
|
if (typeof content === "string") return [];
|
|
473
515
|
return content.filter((b) => b.type === "image");
|
|
474
516
|
}
|
|
517
|
+
function toolResultVideos(content) {
|
|
518
|
+
if (typeof content === "string") return [];
|
|
519
|
+
return content.filter((b) => b.type === "video");
|
|
520
|
+
}
|
|
475
521
|
function toAnthropicCacheControl(retention, baseUrl) {
|
|
476
522
|
const resolved = retention ?? "short";
|
|
477
523
|
if (resolved === "none") return void 0;
|
|
@@ -482,6 +528,12 @@ function toAnthropicToolResultContent(content) {
|
|
|
482
528
|
if (typeof content === "string") return content;
|
|
483
529
|
return content.map((block) => {
|
|
484
530
|
if (block.type === "text") return { type: "text", text: block.text };
|
|
531
|
+
if (block.type === "video") {
|
|
532
|
+
return {
|
|
533
|
+
type: "video",
|
|
534
|
+
source: { type: "base64", media_type: block.mediaType, data: block.data }
|
|
535
|
+
};
|
|
536
|
+
}
|
|
485
537
|
return {
|
|
486
538
|
type: "image",
|
|
487
539
|
source: {
|
|
@@ -504,8 +556,8 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
504
556
|
let systemText;
|
|
505
557
|
const out = [];
|
|
506
558
|
const idMap = /* @__PURE__ */ new Map();
|
|
507
|
-
const
|
|
508
|
-
(last, m, i) => m.role === "
|
|
559
|
+
const trajectoryStartIdx = messages.reduce(
|
|
560
|
+
(last, m, i) => m.role === "user" ? i : last,
|
|
509
561
|
-1
|
|
510
562
|
);
|
|
511
563
|
let msgIdx = -1;
|
|
@@ -520,6 +572,16 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
520
572
|
role: "user",
|
|
521
573
|
content: typeof msg.content === "string" ? msg.content : msg.content.map((part) => {
|
|
522
574
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
575
|
+
if (part.type === "video") {
|
|
576
|
+
return {
|
|
577
|
+
type: "video",
|
|
578
|
+
source: {
|
|
579
|
+
type: "base64",
|
|
580
|
+
media_type: part.mediaType,
|
|
581
|
+
data: part.data
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
523
585
|
return {
|
|
524
586
|
type: "image",
|
|
525
587
|
source: {
|
|
@@ -533,7 +595,7 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
533
595
|
continue;
|
|
534
596
|
}
|
|
535
597
|
if (msg.role === "assistant") {
|
|
536
|
-
const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx
|
|
598
|
+
const content = typeof msg.content === "string" ? msg.content : toAnthropicAssistantContent(msg.content, msgIdx > trajectoryStartIdx, idMap);
|
|
537
599
|
if (Array.isArray(content) && content.length === 0) continue;
|
|
538
600
|
out.push({ role: "assistant", content });
|
|
539
601
|
continue;
|
|
@@ -541,6 +603,8 @@ function toAnthropicMessages(messages, cacheControl) {
|
|
|
541
603
|
if (msg.role === "tool") {
|
|
542
604
|
out.push({
|
|
543
605
|
role: "user",
|
|
606
|
+
// Cast covers the video block (used by the Anthropic-compatible MiniMax
|
|
607
|
+
// API), which isn't in the first-party Anthropic tool_result types.
|
|
544
608
|
content: msg.content.map((result) => ({
|
|
545
609
|
type: "tool_result",
|
|
546
610
|
tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
|
|
@@ -680,6 +744,13 @@ function toOpenAIMessages(messages, options) {
|
|
|
680
744
|
content: msg.content.map(
|
|
681
745
|
(part) => {
|
|
682
746
|
if (part.type === "text") return { type: "text", text: part.text };
|
|
747
|
+
if (part.type === "video") {
|
|
748
|
+
const videoUrl = part.fileId ? { url: `ms://${part.fileId}`, id: part.fileId } : { url: `data:${part.mediaType};base64,${part.data}` };
|
|
749
|
+
return {
|
|
750
|
+
type: "video_url",
|
|
751
|
+
video_url: videoUrl
|
|
752
|
+
};
|
|
753
|
+
}
|
|
683
754
|
return {
|
|
684
755
|
type: "image_url",
|
|
685
756
|
image_url: {
|
|
@@ -722,29 +793,56 @@ function toOpenAIMessages(messages, options) {
|
|
|
722
793
|
continue;
|
|
723
794
|
}
|
|
724
795
|
if (msg.role === "tool") {
|
|
725
|
-
const
|
|
796
|
+
const isMoonshot = options?.provider === "moonshot";
|
|
797
|
+
const followUpMediaBlocks = [];
|
|
798
|
+
let followUpHasVideo = false;
|
|
726
799
|
for (const result of msg.content) {
|
|
727
800
|
const text = toolResultText(result.content);
|
|
728
801
|
const images = toolResultImages(result.content);
|
|
802
|
+
const videos = toolResultVideos(result.content);
|
|
729
803
|
const hasText = text.length > 0;
|
|
804
|
+
if (isMoonshot && videos.length > 0) {
|
|
805
|
+
const parts = [];
|
|
806
|
+
if (hasText) parts.push({ type: "text", text });
|
|
807
|
+
const videoParts = videos.map((v) => {
|
|
808
|
+
const videoUrl = v.fileId ? { url: `ms://${v.fileId}`, id: v.fileId } : { url: `data:${v.mediaType};base64,${v.data}` };
|
|
809
|
+
return { type: "video_url", video_url: videoUrl };
|
|
810
|
+
});
|
|
811
|
+
out.push({
|
|
812
|
+
role: "tool",
|
|
813
|
+
tool_call_id: remapToolCallId(result.toolCallId, idMap),
|
|
814
|
+
content: [...parts, ...videoParts]
|
|
815
|
+
});
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
730
818
|
out.push({
|
|
731
819
|
role: "tool",
|
|
732
820
|
tool_call_id: remapToolCallId(result.toolCallId, idMap),
|
|
733
|
-
content: hasText ? text : "(see attached
|
|
821
|
+
content: hasText ? text : "(see attached media)"
|
|
734
822
|
});
|
|
735
823
|
if (images.length > 0 && options?.supportsImages !== false) {
|
|
736
824
|
for (const img of images) {
|
|
737
|
-
|
|
825
|
+
followUpMediaBlocks.push({
|
|
738
826
|
type: "image_url",
|
|
739
827
|
image_url: { url: `data:${img.mediaType};base64,${img.data}` }
|
|
740
828
|
});
|
|
741
829
|
}
|
|
742
830
|
}
|
|
831
|
+
if (!isMoonshot && videos.length > 0) {
|
|
832
|
+
for (const v of videos) {
|
|
833
|
+
followUpMediaBlocks.push({
|
|
834
|
+
type: "video_url",
|
|
835
|
+
video_url: { url: `data:${v.mediaType};base64,${v.data}` }
|
|
836
|
+
});
|
|
837
|
+
followUpHasVideo = true;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
743
840
|
}
|
|
744
|
-
if (
|
|
841
|
+
if (followUpMediaBlocks.length > 0) {
|
|
842
|
+
const label = followUpHasVideo ? "Attached media from tool result:" : "Attached image(s) from tool result:";
|
|
745
843
|
out.push({
|
|
746
844
|
role: "user",
|
|
747
|
-
content: [{ type: "text", text:
|
|
845
|
+
content: [{ type: "text", text: label }, ...followUpMediaBlocks]
|
|
748
846
|
});
|
|
749
847
|
}
|
|
750
848
|
}
|
|
@@ -845,7 +943,8 @@ async function* runStream(options) {
|
|
|
845
943
|
const useStreaming = options.streaming !== false;
|
|
846
944
|
const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
|
|
847
945
|
const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
|
|
848
|
-
const
|
|
946
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
947
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
849
948
|
const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
|
|
850
949
|
const system = isOAuth ? [
|
|
851
950
|
{
|
|
@@ -1051,11 +1150,18 @@ async function* runStream(options) {
|
|
|
1051
1150
|
args: tc.args
|
|
1052
1151
|
};
|
|
1053
1152
|
} else if (accum.type === "server_tool_use") {
|
|
1153
|
+
let input = accum.input;
|
|
1154
|
+
if (accum.argsJson) {
|
|
1155
|
+
try {
|
|
1156
|
+
input = JSON.parse(accum.argsJson);
|
|
1157
|
+
} catch {
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1054
1160
|
const stc = {
|
|
1055
1161
|
type: "server_tool_call",
|
|
1056
1162
|
id: accum.toolId,
|
|
1057
1163
|
name: accum.toolName,
|
|
1058
|
-
input
|
|
1164
|
+
input
|
|
1059
1165
|
};
|
|
1060
1166
|
contentParts.push(stc);
|
|
1061
1167
|
yield {
|
|
@@ -1263,6 +1369,14 @@ function toError(err) {
|
|
|
1263
1369
|
});
|
|
1264
1370
|
}
|
|
1265
1371
|
}
|
|
1372
|
+
if (isHardBillingMessage(message)) {
|
|
1373
|
+
const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
1374
|
+
return new ProviderError("anthropic", usageMessage, {
|
|
1375
|
+
statusCode: err.status,
|
|
1376
|
+
...requestId ? { requestId } : {},
|
|
1377
|
+
cause: err
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1266
1380
|
return new ProviderError("anthropic", message, {
|
|
1267
1381
|
statusCode: err.status,
|
|
1268
1382
|
...requestId ? { requestId } : {},
|
|
@@ -1295,6 +1409,75 @@ function fnv1aHash(value) {
|
|
|
1295
1409
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
1296
1410
|
}
|
|
1297
1411
|
|
|
1412
|
+
// src/utils/diag.ts
|
|
1413
|
+
var _diagFn = null;
|
|
1414
|
+
function setProviderDiagnostic(fn) {
|
|
1415
|
+
_diagFn = fn;
|
|
1416
|
+
}
|
|
1417
|
+
function providerDiag(phase, data) {
|
|
1418
|
+
_diagFn?.(phase, data);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/providers/moonshot-video.ts
|
|
1422
|
+
async function uploadMoonshotVideos(client, messages, signal) {
|
|
1423
|
+
for (const msg of messages) {
|
|
1424
|
+
if (typeof msg.content === "string") continue;
|
|
1425
|
+
for (const part of msg.content) {
|
|
1426
|
+
if (part.type === "video") {
|
|
1427
|
+
await ensureUploaded(client, part, signal);
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
if (part.type === "tool_result" && Array.isArray(part.content)) {
|
|
1431
|
+
for (const inner of part.content) {
|
|
1432
|
+
if (inner.type === "video") {
|
|
1433
|
+
await ensureUploaded(client, inner, signal);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
async function ensureUploaded(client, video, signal) {
|
|
1441
|
+
if (video.fileId) {
|
|
1442
|
+
providerDiag("moonshot_video_cached", { fileId: video.fileId });
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (!video.data) {
|
|
1446
|
+
providerDiag("moonshot_video_skipped_no_data", {});
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
providerDiag("moonshot_video_upload_start", {
|
|
1450
|
+
mediaType: video.mediaType,
|
|
1451
|
+
bytes: Math.floor(video.data.length * 3 / 4)
|
|
1452
|
+
});
|
|
1453
|
+
video.fileId = await uploadOne(client, video, signal);
|
|
1454
|
+
providerDiag("moonshot_video_upload_done", { fileId: video.fileId });
|
|
1455
|
+
}
|
|
1456
|
+
async function uploadOne(client, video, signal) {
|
|
1457
|
+
const bytes = Buffer.from(video.data, "base64");
|
|
1458
|
+
const mediaType = video.mediaType || "video/mp4";
|
|
1459
|
+
const filename = `upload.${extForMime(mediaType)}`;
|
|
1460
|
+
const file = new File([new Uint8Array(bytes)], filename, { type: mediaType });
|
|
1461
|
+
const uploaded = await client.files.create(
|
|
1462
|
+
{ file, purpose: "video" },
|
|
1463
|
+
signal ? { signal } : void 0
|
|
1464
|
+
);
|
|
1465
|
+
return uploaded.id;
|
|
1466
|
+
}
|
|
1467
|
+
var MIME_TO_EXT = {
|
|
1468
|
+
"video/mp4": "mp4",
|
|
1469
|
+
"video/mpeg": "mpeg",
|
|
1470
|
+
"video/quicktime": "mov",
|
|
1471
|
+
"video/webm": "webm",
|
|
1472
|
+
"video/x-matroska": "mkv",
|
|
1473
|
+
"video/x-msvideo": "avi",
|
|
1474
|
+
"video/x-flv": "flv",
|
|
1475
|
+
"video/3gpp": "3gp"
|
|
1476
|
+
};
|
|
1477
|
+
function extForMime(mediaType) {
|
|
1478
|
+
return MIME_TO_EXT[mediaType.toLowerCase()] ?? "mp4";
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1298
1481
|
// src/utils/env.ts
|
|
1299
1482
|
function getEnvironment() {
|
|
1300
1483
|
return globalThis.process?.env;
|
|
@@ -1324,7 +1507,8 @@ function createClient2(options) {
|
|
|
1324
1507
|
return new OpenAI({
|
|
1325
1508
|
apiKey: options.apiKey,
|
|
1326
1509
|
...options.baseUrl ? { baseURL: options.baseUrl } : {},
|
|
1327
|
-
...options.fetch ? { fetch: options.fetch } : {}
|
|
1510
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
1511
|
+
...options.defaultHeaders ? { defaultHeaders: options.defaultHeaders } : {}
|
|
1328
1512
|
});
|
|
1329
1513
|
}
|
|
1330
1514
|
function streamOpenAI(options) {
|
|
@@ -1335,7 +1519,15 @@ async function* runStream2(options) {
|
|
|
1335
1519
|
const useStreaming = options.streaming !== false;
|
|
1336
1520
|
const client = createClient2(options);
|
|
1337
1521
|
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
|
|
1338
|
-
const
|
|
1522
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1523
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1524
|
+
if (options.provider === "moonshot") {
|
|
1525
|
+
try {
|
|
1526
|
+
await uploadMoonshotVideos(client, downgradedMessages, options.signal);
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
throw toError2(err, providerName);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1339
1531
|
const messages = toOpenAIMessages(downgradedMessages, {
|
|
1340
1532
|
provider: options.provider,
|
|
1341
1533
|
thinking: !!options.thinking,
|
|
@@ -1573,6 +1765,16 @@ function completionToResponse(completion) {
|
|
|
1573
1765
|
usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
|
|
1574
1766
|
};
|
|
1575
1767
|
}
|
|
1768
|
+
function classifyOpenAICompatLimit(args) {
|
|
1769
|
+
const { status, code, type, message } = args;
|
|
1770
|
+
const codeType = `${code ?? ""} ${type ?? ""}`.toLowerCase();
|
|
1771
|
+
const isHard = status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message);
|
|
1772
|
+
if (isHard) return "hard";
|
|
1773
|
+
if (status === 429 || codeType.includes("rate_limit_exceeded") || codeType.includes("too_many_requests")) {
|
|
1774
|
+
return "transient";
|
|
1775
|
+
}
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1576
1778
|
function toError2(err, provider = "openai") {
|
|
1577
1779
|
if (err instanceof OpenAI.APIError) {
|
|
1578
1780
|
const body = err.error;
|
|
@@ -1584,6 +1786,35 @@ function toError2(err, provider = "openai") {
|
|
|
1584
1786
|
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.";
|
|
1585
1787
|
}
|
|
1586
1788
|
const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
|
|
1789
|
+
const code = typeof err.code === "string" ? err.code : void 0;
|
|
1790
|
+
const type = typeof err.type === "string" ? err.type : void 0;
|
|
1791
|
+
const limit = classifyOpenAICompatLimit({
|
|
1792
|
+
status: err.status,
|
|
1793
|
+
code,
|
|
1794
|
+
type,
|
|
1795
|
+
message: cleanMessage
|
|
1796
|
+
});
|
|
1797
|
+
if (limit === "hard") {
|
|
1798
|
+
const message = /usage limit reached/i.test(cleanMessage) ? cleanMessage : `usage limit reached: ${cleanMessage}`;
|
|
1799
|
+
return new ProviderError(provider, message, {
|
|
1800
|
+
statusCode: err.status,
|
|
1801
|
+
...requestId ? { requestId } : {},
|
|
1802
|
+
...hint ? { hint } : {},
|
|
1803
|
+
cause: err
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
if (limit === "transient") {
|
|
1807
|
+
const retryAfterRaw = readHeader(err.headers, "retry-after");
|
|
1808
|
+
const retryAfterSec = retryAfterRaw != null ? Number(retryAfterRaw) : Number.NaN;
|
|
1809
|
+
const resetsAt = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.floor(Date.now() / 1e3) + retryAfterSec : void 0;
|
|
1810
|
+
return new ProviderError(provider, cleanMessage, {
|
|
1811
|
+
statusCode: err.status,
|
|
1812
|
+
...requestId ? { requestId } : {},
|
|
1813
|
+
...hint ? { hint } : {},
|
|
1814
|
+
...resetsAt ? { resetsAt } : {},
|
|
1815
|
+
cause: err
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1587
1818
|
return new ProviderError(provider, cleanMessage, {
|
|
1588
1819
|
statusCode: err.status,
|
|
1589
1820
|
...requestId ? { requestId } : {},
|
|
@@ -1600,15 +1831,6 @@ function toError2(err, provider = "openai") {
|
|
|
1600
1831
|
// src/providers/openai-codex.ts
|
|
1601
1832
|
import os from "os";
|
|
1602
1833
|
|
|
1603
|
-
// src/utils/diag.ts
|
|
1604
|
-
var _diagFn = null;
|
|
1605
|
-
function setProviderDiagnostic(fn) {
|
|
1606
|
-
_diagFn = fn;
|
|
1607
|
-
}
|
|
1608
|
-
function providerDiag(phase, data) {
|
|
1609
|
-
_diagFn?.(phase, data);
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
1834
|
// src/utils/sse.ts
|
|
1613
1835
|
function parseSseBuffer(buffer) {
|
|
1614
1836
|
const events = [];
|
|
@@ -1674,7 +1896,8 @@ function streamOpenAICodex(options) {
|
|
|
1674
1896
|
async function* runStream3(options) {
|
|
1675
1897
|
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1676
1898
|
const url = `${baseUrl}/codex/responses`;
|
|
1677
|
-
const
|
|
1899
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1900
|
+
const downgraded = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1678
1901
|
const { system, input } = toCodexInput(downgraded, { supportsImages: options.supportsImages });
|
|
1679
1902
|
const body = {
|
|
1680
1903
|
model: options.model,
|
|
@@ -1751,6 +1974,7 @@ async function* runStream3(options) {
|
|
|
1751
1974
|
const contentParts = [];
|
|
1752
1975
|
let textAccum = "";
|
|
1753
1976
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
1977
|
+
const orderedItems = [];
|
|
1754
1978
|
const outputItemTypes = /* @__PURE__ */ new Map();
|
|
1755
1979
|
const outputTextByPart = /* @__PURE__ */ new Map();
|
|
1756
1980
|
const pendingOutputTextByPart = /* @__PURE__ */ new Map();
|
|
@@ -1898,12 +2122,26 @@ async function* runStream3(options) {
|
|
|
1898
2122
|
}
|
|
1899
2123
|
if (type === "response.output_item.done") {
|
|
1900
2124
|
const item = event.item;
|
|
2125
|
+
if (item?.type === "reasoning") {
|
|
2126
|
+
const encrypted = item.encrypted_content;
|
|
2127
|
+
const reasoningId = item.id;
|
|
2128
|
+
if (encrypted && reasoningId) {
|
|
2129
|
+
orderedItems.push({
|
|
2130
|
+
kind: "reasoning",
|
|
2131
|
+
part: {
|
|
2132
|
+
type: "raw",
|
|
2133
|
+
data: { ...item, summary: Array.isArray(item.summary) ? item.summary : [] }
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
1901
2138
|
if (item?.type === "function_call") {
|
|
1902
2139
|
const callId = item.call_id;
|
|
1903
2140
|
const itemId = item.id;
|
|
1904
2141
|
const id = `${callId}|${itemId}`;
|
|
1905
2142
|
const tc = toolCalls.get(id);
|
|
1906
2143
|
if (tc) {
|
|
2144
|
+
orderedItems.push({ kind: "tool", id });
|
|
1907
2145
|
const args = parseToolArguments(tc.argsJson);
|
|
1908
2146
|
yield {
|
|
1909
2147
|
type: "toolcall_done",
|
|
@@ -1924,19 +2162,41 @@ async function* runStream3(options) {
|
|
|
1924
2162
|
}
|
|
1925
2163
|
}
|
|
1926
2164
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2165
|
+
const seenTool = /* @__PURE__ */ new Set();
|
|
2166
|
+
let textInserted = false;
|
|
2167
|
+
for (const entry of orderedItems) {
|
|
2168
|
+
if (entry.kind === "reasoning") {
|
|
2169
|
+
contentParts.push(entry.part);
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (textAccum && !textInserted) {
|
|
2173
|
+
contentParts.push({ type: "text", text: textAccum });
|
|
2174
|
+
textInserted = true;
|
|
2175
|
+
}
|
|
2176
|
+
const tc = toolCalls.get(entry.id);
|
|
2177
|
+
if (!tc || seenTool.has(entry.id)) continue;
|
|
2178
|
+
seenTool.add(entry.id);
|
|
1932
2179
|
const toolCall = {
|
|
1933
2180
|
type: "tool_call",
|
|
1934
2181
|
id: tc.id,
|
|
1935
2182
|
name: tc.name,
|
|
1936
|
-
args
|
|
2183
|
+
args: parseToolArguments(tc.argsJson)
|
|
1937
2184
|
};
|
|
1938
2185
|
contentParts.push(toolCall);
|
|
1939
2186
|
}
|
|
2187
|
+
if (textAccum && !textInserted) {
|
|
2188
|
+
contentParts.push({ type: "text", text: textAccum });
|
|
2189
|
+
}
|
|
2190
|
+
for (const [id, tc] of toolCalls) {
|
|
2191
|
+
if (seenTool.has(id)) continue;
|
|
2192
|
+
seenTool.add(id);
|
|
2193
|
+
contentParts.push({
|
|
2194
|
+
type: "tool_call",
|
|
2195
|
+
id: tc.id,
|
|
2196
|
+
name: tc.name,
|
|
2197
|
+
args: parseToolArguments(tc.argsJson)
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
1940
2200
|
const hasToolCalls = contentParts.some((p) => p.type === "tool_call");
|
|
1941
2201
|
const stopReason = hasToolCalls ? "tool_use" : "end_turn";
|
|
1942
2202
|
const streamResponse = {
|
|
@@ -1968,6 +2228,9 @@ function remapCodexId(id, idMap) {
|
|
|
1968
2228
|
idMap.set(id, mapped);
|
|
1969
2229
|
return mapped;
|
|
1970
2230
|
}
|
|
2231
|
+
function isEncryptedReasoning(data) {
|
|
2232
|
+
return data.type === "reasoning" && typeof data.id === "string" && typeof data.encrypted_content === "string";
|
|
2233
|
+
}
|
|
1971
2234
|
function toCodexInput(messages, options) {
|
|
1972
2235
|
let system;
|
|
1973
2236
|
const input = [];
|
|
@@ -2000,7 +2263,9 @@ function toCodexInput(messages, options) {
|
|
|
2000
2263
|
continue;
|
|
2001
2264
|
}
|
|
2002
2265
|
for (const part of msg.content) {
|
|
2003
|
-
if (part.type === "
|
|
2266
|
+
if (part.type === "raw" && isEncryptedReasoning(part.data)) {
|
|
2267
|
+
input.push(part.data);
|
|
2268
|
+
} else if (part.type === "text") {
|
|
2004
2269
|
input.push({
|
|
2005
2270
|
type: "message",
|
|
2006
2271
|
role: "assistant",
|
|
@@ -2141,6 +2406,20 @@ ${formatUnsupportedModelMessage(model)}`;
|
|
|
2141
2406
|
}
|
|
2142
2407
|
return `Gemini API error (${status}): ${body}`;
|
|
2143
2408
|
}
|
|
2409
|
+
function parseRetryDelaySeconds(body) {
|
|
2410
|
+
const match = body.match(/"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/);
|
|
2411
|
+
if (!match) return void 0;
|
|
2412
|
+
const seconds = Number(match[1]);
|
|
2413
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
2414
|
+
}
|
|
2415
|
+
function parseGeminiQuota(status, body) {
|
|
2416
|
+
if (status !== 429) return null;
|
|
2417
|
+
const lower = body.toLowerCase();
|
|
2418
|
+
if (!lower.includes("resource_exhausted") && !lower.includes("quota")) return null;
|
|
2419
|
+
const retryDelaySeconds = parseRetryDelaySeconds(body);
|
|
2420
|
+
const exhausted = retryDelaySeconds === void 0;
|
|
2421
|
+
return { exhausted, retryDelaySeconds };
|
|
2422
|
+
}
|
|
2144
2423
|
function toSystemAndContents(messages) {
|
|
2145
2424
|
let systemText = "";
|
|
2146
2425
|
const contents = [];
|
|
@@ -2200,6 +2479,13 @@ ${msg.content}` : msg.content;
|
|
|
2200
2479
|
}
|
|
2201
2480
|
}
|
|
2202
2481
|
});
|
|
2482
|
+
if (typeof result.content !== "string") {
|
|
2483
|
+
for (const block of result.content) {
|
|
2484
|
+
if (block.type === "video") {
|
|
2485
|
+
parts.push({ inlineData: { mimeType: block.mediaType, data: block.data } });
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2203
2489
|
}
|
|
2204
2490
|
if (parts.length > 0) contents.push({ role: "user", parts });
|
|
2205
2491
|
}
|
|
@@ -2292,7 +2578,8 @@ function toThinkingConfig(model, level) {
|
|
|
2292
2578
|
};
|
|
2293
2579
|
}
|
|
2294
2580
|
function buildGenerateRequest(options) {
|
|
2295
|
-
const
|
|
2581
|
+
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
2582
|
+
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
2296
2583
|
const { systemInstruction, contents } = toSystemAndContents(downgradedMessages);
|
|
2297
2584
|
const tools = toGeminiTools(options.tools);
|
|
2298
2585
|
const toolConfig = toGeminiToolConfig(options.toolChoice, options.tools);
|
|
@@ -2420,8 +2707,17 @@ async function fetchCodeAssist(plan, options) {
|
|
|
2420
2707
|
});
|
|
2421
2708
|
if (!response.ok) {
|
|
2422
2709
|
const text = await response.text().catch(() => "");
|
|
2423
|
-
|
|
2424
|
-
|
|
2710
|
+
const quota = parseGeminiQuota(response.status, text);
|
|
2711
|
+
let message = formatErrorMessage(response.status, text, options.model);
|
|
2712
|
+
let resetsAt;
|
|
2713
|
+
if (quota?.exhausted) {
|
|
2714
|
+
message = `Gemini quota exhausted \u2014 usage limit reached. ${message}`;
|
|
2715
|
+
} else if (quota?.retryDelaySeconds !== void 0) {
|
|
2716
|
+
resetsAt = Math.floor(Date.now() / 1e3) + Math.ceil(quota.retryDelaySeconds);
|
|
2717
|
+
}
|
|
2718
|
+
throw new ProviderError("gemini", message, {
|
|
2719
|
+
statusCode: response.status,
|
|
2720
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2425
2721
|
});
|
|
2426
2722
|
}
|
|
2427
2723
|
return response;
|
|
@@ -2587,6 +2883,7 @@ var providerRegistry = new ProviderRegistryImpl();
|
|
|
2587
2883
|
|
|
2588
2884
|
// src/stream.ts
|
|
2589
2885
|
var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
|
2886
|
+
var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
|
|
2590
2887
|
providerRegistry.register("anthropic", {
|
|
2591
2888
|
stream: (options) => streamAnthropic(options)
|
|
2592
2889
|
});
|
|
@@ -2615,10 +2912,11 @@ providerRegistry.register("glm", {
|
|
|
2615
2912
|
})
|
|
2616
2913
|
});
|
|
2617
2914
|
providerRegistry.register("moonshot", {
|
|
2618
|
-
stream: (options) =>
|
|
2619
|
-
|
|
2620
|
-
baseUrl: options.
|
|
2621
|
-
|
|
2915
|
+
stream: (options) => {
|
|
2916
|
+
const baseUrl = options.baseUrl ?? "https://api.moonshot.ai/v1";
|
|
2917
|
+
const defaultHeaders = baseUrl.includes("api.kimi.com") ? { "User-Agent": KIMI_CODE_USER_AGENT, ...options.defaultHeaders } : options.defaultHeaders;
|
|
2918
|
+
return streamOpenAI({ ...options, baseUrl, defaultHeaders });
|
|
2919
|
+
}
|
|
2622
2920
|
});
|
|
2623
2921
|
providerRegistry.register("deepseek", {
|
|
2624
2922
|
stream: (options) => streamOpenAI({
|
|
@@ -2651,8 +2949,23 @@ function stream(options) {
|
|
|
2651
2949
|
`Unknown provider: "${options.provider}". Registered: ${providerRegistry.list().join(", ")}`
|
|
2652
2950
|
);
|
|
2653
2951
|
}
|
|
2952
|
+
if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
|
|
2953
|
+
throw new VideoUnsupportedError();
|
|
2954
|
+
}
|
|
2654
2955
|
return entry.stream(options);
|
|
2655
2956
|
}
|
|
2957
|
+
function messagesContainVideo(messages) {
|
|
2958
|
+
for (const msg of messages) {
|
|
2959
|
+
if (typeof msg.content === "string" || !Array.isArray(msg.content)) continue;
|
|
2960
|
+
for (const part of msg.content) {
|
|
2961
|
+
if (part.type === "video") return true;
|
|
2962
|
+
if (part.type === "tool_result" && Array.isArray(part.content)) {
|
|
2963
|
+
if (part.content.some((block) => block.type === "video")) return true;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
return false;
|
|
2968
|
+
}
|
|
2656
2969
|
|
|
2657
2970
|
// src/providers/palsu.ts
|
|
2658
2971
|
function palsuText(text) {
|
|
@@ -2812,6 +3125,7 @@ export {
|
|
|
2812
3125
|
StreamResult,
|
|
2813
3126
|
formatError,
|
|
2814
3127
|
formatErrorForDisplay,
|
|
3128
|
+
isHardBillingMessage,
|
|
2815
3129
|
isUsageLimitError,
|
|
2816
3130
|
palsuAssistantMessage,
|
|
2817
3131
|
palsuText,
|