@prestyj/ai 5.6.0 → 5.8.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/README.md +1 -1
- package/dist/index.cjs +334 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +329 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
|
|
|
43
43
|
| `anthropic` | Claude Opus 4.8, Sonnet 5, Haiku 4.5 | Extended thinking, prompt caching, server-side compaction |
|
|
44
44
|
| `openai` | GPT-4.1, o3, o4-mini | Supports OAuth (codex endpoint) and API keys |
|
|
45
45
|
| `glm` | GLM-5.1, GLM-4.7 | Z.AI platform, OpenAI-compatible |
|
|
46
|
-
| `moonshot` | Kimi K2.7 | Moonshot platform, OpenAI-compatible |
|
|
46
|
+
| `moonshot` | Kimi K3, Kimi K2.7 Code | Moonshot platform, OpenAI-compatible |
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
package/dist/index.cjs
CHANGED
|
@@ -33,8 +33,11 @@ __export(index_exports, {
|
|
|
33
33
|
EZCoderAIError: () => EZCoderAIError,
|
|
34
34
|
EventStream: () => EventStream,
|
|
35
35
|
ProviderError: () => ProviderError,
|
|
36
|
+
REDACTION_MARKER: () => REDACTED,
|
|
36
37
|
StreamResult: () => StreamResult,
|
|
38
|
+
clampProviderContextImages: () => clampProviderContextImages,
|
|
37
39
|
classifyProviderError: () => classifyProviderError,
|
|
40
|
+
environmentSecrets: () => environmentSecrets,
|
|
38
41
|
formatError: () => formatError,
|
|
39
42
|
formatErrorForDisplay: () => formatErrorForDisplay,
|
|
40
43
|
isHardBillingMessage: () => isHardBillingMessage,
|
|
@@ -45,6 +48,8 @@ __export(index_exports, {
|
|
|
45
48
|
palsuToolCall: () => palsuToolCall,
|
|
46
49
|
prewarmAnthropicCache: () => prewarmAnthropicCache,
|
|
47
50
|
providerRegistry: () => providerRegistry,
|
|
51
|
+
redactText: () => redactText,
|
|
52
|
+
redactValue: () => redactValue,
|
|
48
53
|
registerPalsuProvider: () => registerPalsuProvider,
|
|
49
54
|
setProviderDiagnostic: () => setProviderDiagnostic,
|
|
50
55
|
stream: () => stream,
|
|
@@ -113,12 +118,14 @@ var PROVIDER_DISPLAY = {
|
|
|
113
118
|
deepseek: "DeepSeek",
|
|
114
119
|
openrouter: "OpenRouter",
|
|
115
120
|
sakana: "Sakana",
|
|
121
|
+
xai: "xAI (Grok)",
|
|
116
122
|
xiaomi: "Xiaomi (MiMo)",
|
|
117
123
|
minimax: "MiniMax"
|
|
118
124
|
};
|
|
119
125
|
var PROVIDER_STATUS_URL = {
|
|
120
126
|
openai: "status.openai.com",
|
|
121
|
-
anthropic: "status.anthropic.com"
|
|
127
|
+
anthropic: "status.anthropic.com",
|
|
128
|
+
xai: "status.x.ai"
|
|
122
129
|
};
|
|
123
130
|
function providerDisplayName(provider) {
|
|
124
131
|
return PROVIDER_DISPLAY[provider] ?? provider;
|
|
@@ -157,13 +164,20 @@ function isRawJsonErrorEcho(message) {
|
|
|
157
164
|
return false;
|
|
158
165
|
}
|
|
159
166
|
}
|
|
167
|
+
function isRawHtmlErrorEcho(message) {
|
|
168
|
+
const withoutStatus = message.trimStart().replace(/^\d{3}\s+/, "").trimStart();
|
|
169
|
+
return /^<!doctype\s+html(?:\s|>)/i.test(withoutStatus) || /^<html(?:\s|>)/i.test(withoutStatus);
|
|
170
|
+
}
|
|
171
|
+
function providerHtmlErrorMessage(statusCode) {
|
|
172
|
+
return statusCode ? `The provider returned an HTML error page (HTTP ${statusCode}) instead of an API response.` : "The provider returned an HTML error page instead of an API response.";
|
|
173
|
+
}
|
|
160
174
|
function emptyProviderErrorMessage(statusCode) {
|
|
161
175
|
return statusCode ? `The provider returned an empty error response (HTTP ${statusCode}), with no further detail.` : "The provider returned an empty error response, with no further detail.";
|
|
162
176
|
}
|
|
163
177
|
function formatError(err) {
|
|
164
178
|
if (err instanceof ProviderError) {
|
|
165
179
|
const name = providerDisplayName(err.provider);
|
|
166
|
-
const cleanMessage = cleanProviderMessage(err.message);
|
|
180
|
+
const cleanMessage = cleanProviderMessage(err.message, err.statusCode);
|
|
167
181
|
if (isMythosAccessError(cleanMessage)) {
|
|
168
182
|
return {
|
|
169
183
|
headline: "Claude Mythos 5 is invitation-only.",
|
|
@@ -258,8 +272,9 @@ function formatErrorForDisplay(err) {
|
|
|
258
272
|
lines.push(` \u2192 ${f.guidance}`);
|
|
259
273
|
return lines.join("\n");
|
|
260
274
|
}
|
|
261
|
-
function cleanProviderMessage(message) {
|
|
262
|
-
|
|
275
|
+
function cleanProviderMessage(message, statusCode) {
|
|
276
|
+
const clean = message.replace(/^\[[^\]]+\]\s*/, "").trim();
|
|
277
|
+
return isRawHtmlErrorEcho(clean) ? providerHtmlErrorMessage(statusCode) : clean;
|
|
263
278
|
}
|
|
264
279
|
function inferSource(err) {
|
|
265
280
|
const msg = err.message.toLowerCase();
|
|
@@ -294,6 +309,9 @@ function providerGuidance(provider, message, statusCode) {
|
|
|
294
309
|
if (statusCode === 503 || lower.includes("service unavailable")) {
|
|
295
310
|
return `${name} is temporarily unavailable. Retry shortly \u2014 not a EZ Coder issue.`;
|
|
296
311
|
}
|
|
312
|
+
if (statusCode === 507 || lower.includes("exceeded request buffer limit while retrying upstream")) {
|
|
313
|
+
return `${name}'s proxy could not retry this large request. EZ Coder already retried automatically \u2014 compact the conversation, then retry.`;
|
|
314
|
+
}
|
|
297
315
|
if (statusCode === 500 || lower.includes("server_error") || lower.includes("500") && lower.includes("internal server error")) {
|
|
298
316
|
return status ? `This is an error from ${name}, not EZ Coder. Retry \u2014 if it keeps happening, check ${status}.` : `This is an error from ${name}, not EZ Coder. Retry \u2014 if it keeps happening, try a different model via the model selector.`;
|
|
299
317
|
}
|
|
@@ -620,6 +638,66 @@ function toAnthropicAssistantContent(content, preserveThinking, idMap) {
|
|
|
620
638
|
return true;
|
|
621
639
|
}).map((part) => toAnthropicAssistantPart(part, idMap)).filter((b) => b !== null);
|
|
622
640
|
}
|
|
641
|
+
var PROVIDER_IMAGE_LIMIT_PLACEHOLDER = "[image omitted: provider image limit]";
|
|
642
|
+
var PROVIDER_IMAGE_BUDGETS = {
|
|
643
|
+
anthropic: 90,
|
|
644
|
+
minimax: 90,
|
|
645
|
+
openai: 200,
|
|
646
|
+
gemini: 200,
|
|
647
|
+
openrouter: 90
|
|
648
|
+
};
|
|
649
|
+
function countContextImages(messages) {
|
|
650
|
+
let count = 0;
|
|
651
|
+
for (const message of messages) {
|
|
652
|
+
if (message.role === "user" && Array.isArray(message.content)) {
|
|
653
|
+
count += message.content.filter((part) => part.type === "image").length;
|
|
654
|
+
} else if (message.role === "tool") {
|
|
655
|
+
for (const result of message.content) {
|
|
656
|
+
if (Array.isArray(result.content)) {
|
|
657
|
+
count += result.content.filter((part) => part.type === "image").length;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return count;
|
|
663
|
+
}
|
|
664
|
+
function clampProviderContextImages(messages, provider, supportsImages) {
|
|
665
|
+
if (supportsImages === false) return messages;
|
|
666
|
+
const budget = PROVIDER_IMAGE_BUDGETS[provider] ?? 5;
|
|
667
|
+
let remainingToRemove = countContextImages(messages) - budget;
|
|
668
|
+
if (remainingToRemove <= 0) return messages;
|
|
669
|
+
return messages.map((message) => {
|
|
670
|
+
if (message.role === "user" && Array.isArray(message.content)) {
|
|
671
|
+
const content = message.content.filter((part) => {
|
|
672
|
+
if (part.type !== "image" || remainingToRemove <= 0) return true;
|
|
673
|
+
remainingToRemove--;
|
|
674
|
+
return false;
|
|
675
|
+
});
|
|
676
|
+
return {
|
|
677
|
+
...message,
|
|
678
|
+
content: content.length > 0 ? content : [{ type: "text", text: PROVIDER_IMAGE_LIMIT_PLACEHOLDER }]
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (message.role === "tool") {
|
|
682
|
+
return {
|
|
683
|
+
...message,
|
|
684
|
+
content: message.content.map((result) => {
|
|
685
|
+
if (!Array.isArray(result.content)) return result;
|
|
686
|
+
const content = result.content.filter((part) => {
|
|
687
|
+
if (part.type !== "image" || remainingToRemove <= 0) return true;
|
|
688
|
+
remainingToRemove--;
|
|
689
|
+
return false;
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
...result,
|
|
693
|
+
content: content.length > 0 ? content : [{ type: "text", text: PROVIDER_IMAGE_LIMIT_PLACEHOLDER }]
|
|
694
|
+
};
|
|
695
|
+
})
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
return message;
|
|
699
|
+
});
|
|
700
|
+
}
|
|
623
701
|
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
624
702
|
var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
625
703
|
var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
|
|
@@ -1637,7 +1715,8 @@ function toError(err) {
|
|
|
1637
1715
|
const bodyMessage = typeof nestedError?.message === "string" && nestedError.message.trim() ? nestedError.message.trim() : typeof errorBody?.message === "string" && errorBody.message.trim() ? errorBody.message.trim() : void 0;
|
|
1638
1716
|
const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
|
|
1639
1717
|
const fallbackMessage = isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message;
|
|
1640
|
-
const
|
|
1718
|
+
const messageCandidate = bodyMessage ?? err.message;
|
|
1719
|
+
const message = isRawHtmlErrorEcho(messageCandidate) ? providerHtmlErrorMessage(err.status) : bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? fallbackMessage;
|
|
1641
1720
|
if (err.status === 429) {
|
|
1642
1721
|
const limit = readUnifiedRateLimit(err.headers);
|
|
1643
1722
|
const farOff = limit.resetsAt != null && limit.resetsAt * 1e3 - Date.now() > 6e4;
|
|
@@ -1819,7 +1898,11 @@ async function* runStream2(options) {
|
|
|
1819
1898
|
const providerName = options.provider ?? "openai";
|
|
1820
1899
|
const useStreaming = options.streaming !== false;
|
|
1821
1900
|
const client = createClient2(options);
|
|
1822
|
-
const
|
|
1901
|
+
const isKimiK3 = options.provider === "moonshot" && options.model === "kimi-k3";
|
|
1902
|
+
const isManagedKimiK3 = isKimiK3 && options.baseUrl?.replace(/\/+$/, "").endsWith("/coding/v1") === true;
|
|
1903
|
+
const isKimiK27 = options.provider === "moonshot" && options.model.startsWith("kimi-k2.7-code");
|
|
1904
|
+
const hasFixedKimiSampling = isKimiK3 || isKimiK27;
|
|
1905
|
+
const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" && !isKimiK3 && !isKimiK27 || options.provider === "xiaomi";
|
|
1823
1906
|
const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
|
|
1824
1907
|
const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
|
|
1825
1908
|
if (options.provider === "moonshot") {
|
|
@@ -1831,7 +1914,9 @@ async function* runStream2(options) {
|
|
|
1831
1914
|
}
|
|
1832
1915
|
const messages = toOpenAIMessages(downgradedMessages, {
|
|
1833
1916
|
provider: options.provider,
|
|
1834
|
-
|
|
1917
|
+
// K3 and K2.7 preserve reasoning even when the user hides thinking in the
|
|
1918
|
+
// UI; keep assistant tool-call history wire-valid in that display mode.
|
|
1919
|
+
thinking: isKimiK3 || isKimiK27 || !!options.thinking,
|
|
1835
1920
|
supportsImages: options.supportsImages
|
|
1836
1921
|
});
|
|
1837
1922
|
const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
|
|
@@ -1841,10 +1926,10 @@ async function* runStream2(options) {
|
|
|
1841
1926
|
messages,
|
|
1842
1927
|
stream: useStreaming,
|
|
1843
1928
|
...options.maxTokens ? { max_completion_tokens: options.maxTokens } : {},
|
|
1844
|
-
...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
|
|
1845
|
-
...options.topP != null ? { top_p: options.topP } : {},
|
|
1929
|
+
...effectiveTemp != null && !options.thinking && !hasFixedKimiSampling ? { temperature: effectiveTemp } : {},
|
|
1930
|
+
...options.topP != null && !hasFixedKimiSampling ? { top_p: options.topP } : {},
|
|
1846
1931
|
...options.stop ? { stop: options.stop } : {},
|
|
1847
|
-
...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
|
|
1932
|
+
...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
|
|
1848
1933
|
...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
|
|
1849
1934
|
...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
|
|
1850
1935
|
...useStreaming ? { stream_options: { include_usage: true } } : {}
|
|
@@ -1854,13 +1939,21 @@ async function* runStream2(options) {
|
|
|
1854
1939
|
paramsAny.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
|
|
1855
1940
|
if (options.provider === "openai" && options.model.startsWith("gpt-5.6")) {
|
|
1856
1941
|
paramsAny.prompt_cache_options = { mode: "implicit", ttl: "30m" };
|
|
1857
|
-
} else if ((options.cacheRetention ?? "short") === "long") {
|
|
1942
|
+
} else if (!isKimiK3 && (options.cacheRetention ?? "short") === "long") {
|
|
1858
1943
|
paramsAny.prompt_cache_retention = "24h";
|
|
1859
1944
|
}
|
|
1860
1945
|
}
|
|
1861
1946
|
if (options.provider === "openai" && options.serviceTier) {
|
|
1862
1947
|
params.service_tier = options.serviceTier;
|
|
1863
1948
|
}
|
|
1949
|
+
if (isKimiK3) {
|
|
1950
|
+
const paramsAny = params;
|
|
1951
|
+
if (isManagedKimiK3) {
|
|
1952
|
+
paramsAny.thinking = { type: "enabled", effort: "max", keep: "all" };
|
|
1953
|
+
} else {
|
|
1954
|
+
paramsAny.reasoning_effort = "max";
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1864
1957
|
if (usesThinkingParam) {
|
|
1865
1958
|
if (options.thinking) {
|
|
1866
1959
|
params.thinking = { type: "enabled" };
|
|
@@ -2105,7 +2198,8 @@ function toError2(err, provider = "openai") {
|
|
|
2105
2198
|
const body = err.error;
|
|
2106
2199
|
const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
|
|
2107
2200
|
const modelName = typeof body?.model === "string" ? body.model : "";
|
|
2108
|
-
const
|
|
2201
|
+
const messageCandidate = bodyMessage ?? err.message;
|
|
2202
|
+
const cleanMessage = isRawHtmlErrorEcho(messageCandidate) ? providerHtmlErrorMessage(err.status) : bodyMessage ? bodyMessage : isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message;
|
|
2109
2203
|
let hint;
|
|
2110
2204
|
if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
|
|
2111
2205
|
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.";
|
|
@@ -2155,6 +2249,7 @@ function toError2(err, provider = "openai") {
|
|
|
2155
2249
|
|
|
2156
2250
|
// src/providers/openai-codex.ts
|
|
2157
2251
|
var import_node_os = __toESM(require("os"), 1);
|
|
2252
|
+
var zstd = __toESM(require("@bokuweb/zstd-wasm"), 1);
|
|
2158
2253
|
|
|
2159
2254
|
// src/utils/sse.ts
|
|
2160
2255
|
function parseSseBuffer(buffer) {
|
|
@@ -2210,6 +2305,50 @@ function extractRequestIdFromMessage(message) {
|
|
|
2210
2305
|
// src/providers/openai-codex.ts
|
|
2211
2306
|
var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
2212
2307
|
var CODEX_CLIENT_VERSION = "0.144.1";
|
|
2308
|
+
var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
|
|
2309
|
+
var zstdInitPromise;
|
|
2310
|
+
async function encodeCodexRequest(body) {
|
|
2311
|
+
const json = JSON.stringify(body);
|
|
2312
|
+
const raw = new TextEncoder().encode(json);
|
|
2313
|
+
if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
|
|
2314
|
+
return {
|
|
2315
|
+
body: json,
|
|
2316
|
+
compressed: false,
|
|
2317
|
+
rawBytes: raw.byteLength,
|
|
2318
|
+
encodedBytes: raw.byteLength
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
try {
|
|
2322
|
+
zstdInitPromise ??= zstd.init();
|
|
2323
|
+
await zstdInitPromise;
|
|
2324
|
+
const compressed = Uint8Array.from(zstd.compress(raw));
|
|
2325
|
+
if (compressed.byteLength >= raw.byteLength) {
|
|
2326
|
+
return {
|
|
2327
|
+
body: json,
|
|
2328
|
+
compressed: false,
|
|
2329
|
+
rawBytes: raw.byteLength,
|
|
2330
|
+
encodedBytes: raw.byteLength
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
return {
|
|
2334
|
+
body: compressed,
|
|
2335
|
+
compressed: true,
|
|
2336
|
+
rawBytes: raw.byteLength,
|
|
2337
|
+
encodedBytes: compressed.byteLength
|
|
2338
|
+
};
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
providerDiag("codex_request_compression_failed", {
|
|
2341
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2342
|
+
rawBytes: raw.byteLength
|
|
2343
|
+
});
|
|
2344
|
+
return {
|
|
2345
|
+
body: json,
|
|
2346
|
+
compressed: false,
|
|
2347
|
+
rawBytes: raw.byteLength,
|
|
2348
|
+
encodedBytes: raw.byteLength
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2213
2352
|
function usesResponsesLite(model) {
|
|
2214
2353
|
return model.startsWith("gpt-5.6-");
|
|
2215
2354
|
}
|
|
@@ -2219,6 +2358,24 @@ function outputTextKey(itemId, contentIndex) {
|
|
|
2219
2358
|
function isVisibleOutputItem(itemType) {
|
|
2220
2359
|
return itemType === "message";
|
|
2221
2360
|
}
|
|
2361
|
+
function toCodexToolChoice(choice, tools) {
|
|
2362
|
+
const resolved = choice ?? "auto";
|
|
2363
|
+
if (typeof resolved === "object") {
|
|
2364
|
+
throw new EZCoderAIError(
|
|
2365
|
+
`OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
|
|
2366
|
+
{ source: "capability" }
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
if (resolved === "required" && !tools?.length) {
|
|
2370
|
+
throw new EZCoderAIError(
|
|
2371
|
+
"OpenAI Codex cannot require a tool call when no tools are configured.",
|
|
2372
|
+
{
|
|
2373
|
+
source: "capability"
|
|
2374
|
+
}
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return resolved;
|
|
2378
|
+
}
|
|
2222
2379
|
function streamOpenAICodex(options) {
|
|
2223
2380
|
return new StreamResult(runStream3(options), options.signal);
|
|
2224
2381
|
}
|
|
@@ -2235,7 +2392,7 @@ async function* runStream3(options) {
|
|
|
2235
2392
|
stream: true,
|
|
2236
2393
|
instructions: system,
|
|
2237
2394
|
input,
|
|
2238
|
-
tool_choice:
|
|
2395
|
+
tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
|
|
2239
2396
|
parallel_tool_calls: !responsesLite,
|
|
2240
2397
|
include: ["reasoning.encrypted_content"]
|
|
2241
2398
|
};
|
|
@@ -2268,18 +2425,26 @@ async function* runStream3(options) {
|
|
|
2268
2425
|
headers["chatgpt-account-id"] = options.accountId;
|
|
2269
2426
|
}
|
|
2270
2427
|
if (options.transportSessionId) {
|
|
2271
|
-
|
|
2272
|
-
headers["
|
|
2273
|
-
|
|
2428
|
+
const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
|
|
2429
|
+
headers["session_id"] = transportSessionId;
|
|
2430
|
+
headers["x-client-request-id"] = transportSessionId;
|
|
2431
|
+
}
|
|
2432
|
+
const encodedRequest = await encodeCodexRequest(body);
|
|
2433
|
+
if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
|
|
2434
|
+
providerDiag("codex_request_body", {
|
|
2435
|
+
rawBytes: encodedRequest.rawBytes,
|
|
2436
|
+
encodedBytes: encodedRequest.encodedBytes,
|
|
2437
|
+
compressed: encodedRequest.compressed
|
|
2438
|
+
});
|
|
2274
2439
|
const response = await fetch(url, {
|
|
2275
2440
|
method: "POST",
|
|
2276
2441
|
headers,
|
|
2277
|
-
body:
|
|
2442
|
+
body: encodedRequest.body,
|
|
2278
2443
|
signal: options.signal
|
|
2279
2444
|
});
|
|
2280
2445
|
if (!response.ok) {
|
|
2281
2446
|
const text = await response.text().catch(() => "");
|
|
2282
|
-
const parsed = parseCodexErrorBody(text);
|
|
2447
|
+
const parsed = parseCodexErrorBody(text, response.status);
|
|
2283
2448
|
const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
|
|
2284
2449
|
const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
|
|
2285
2450
|
const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
|
|
@@ -2673,13 +2838,14 @@ function toCodexTools(tools) {
|
|
|
2673
2838
|
strict: null
|
|
2674
2839
|
}));
|
|
2675
2840
|
}
|
|
2676
|
-
function parseCodexErrorBody(text) {
|
|
2841
|
+
function parseCodexErrorBody(text, statusCode) {
|
|
2677
2842
|
if (!text) return {};
|
|
2678
2843
|
try {
|
|
2679
2844
|
const parsed = JSON.parse(text);
|
|
2680
2845
|
const error = parsed.error;
|
|
2681
2846
|
const detail = parsed.detail;
|
|
2682
|
-
const
|
|
2847
|
+
const rawMessage = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
|
|
2848
|
+
const message = rawMessage && isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(statusCode) : rawMessage;
|
|
2683
2849
|
const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
|
|
2684
2850
|
const errorObj = error ?? parsed;
|
|
2685
2851
|
return {
|
|
@@ -2688,8 +2854,12 @@ function parseCodexErrorBody(text) {
|
|
|
2688
2854
|
...errorObj ? { errorObj } : {}
|
|
2689
2855
|
};
|
|
2690
2856
|
} catch {
|
|
2691
|
-
const trimmed = text.trim()
|
|
2692
|
-
|
|
2857
|
+
const trimmed = text.trim();
|
|
2858
|
+
if (isRawHtmlErrorEcho(trimmed)) {
|
|
2859
|
+
return { message: providerHtmlErrorMessage(statusCode) };
|
|
2860
|
+
}
|
|
2861
|
+
const bounded = trimmed.slice(0, 240);
|
|
2862
|
+
return bounded ? { message: bounded } : {};
|
|
2693
2863
|
}
|
|
2694
2864
|
}
|
|
2695
2865
|
var CODEX_USAGE_LIMIT_CODE = /usage_limit_reached|usage_not_included/i;
|
|
@@ -3308,6 +3478,18 @@ providerRegistry.register("sakana", {
|
|
|
3308
3478
|
baseUrl: options.baseUrl ?? "https://api.sakana.ai/v1"
|
|
3309
3479
|
})
|
|
3310
3480
|
});
|
|
3481
|
+
providerRegistry.register("xai", {
|
|
3482
|
+
// xAI's public API (console.x.ai key) is OpenAI-compatible — ride the Chat
|
|
3483
|
+
// Completions transport like Moonshot/DeepSeek. Grok reasoning models take
|
|
3484
|
+
// top-level `reasoning_effort` (low/medium/high), which the shared thinking
|
|
3485
|
+
// path already sends. xAI's OAuth path exists but only via the Grok CLI's
|
|
3486
|
+
// private Responses proxy (cli-chat-proxy.grok.com) with reverse-engineered
|
|
3487
|
+
// attribution headers and account-tier gating — intentionally not wired.
|
|
3488
|
+
stream: (options) => streamOpenAI({
|
|
3489
|
+
...options,
|
|
3490
|
+
baseUrl: options.baseUrl ?? "https://api.x.ai/v1"
|
|
3491
|
+
})
|
|
3492
|
+
});
|
|
3311
3493
|
providerRegistry.register("minimax", {
|
|
3312
3494
|
stream: (options) => streamAnthropic({
|
|
3313
3495
|
...options,
|
|
@@ -3330,7 +3512,12 @@ function stream(options) {
|
|
|
3330
3512
|
if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
|
|
3331
3513
|
throw new VideoUnsupportedError();
|
|
3332
3514
|
}
|
|
3333
|
-
|
|
3515
|
+
const messages = clampProviderContextImages(
|
|
3516
|
+
options.messages,
|
|
3517
|
+
options.provider,
|
|
3518
|
+
options.supportsImages
|
|
3519
|
+
);
|
|
3520
|
+
return entry.stream(messages === options.messages ? options : { ...options, messages });
|
|
3334
3521
|
}
|
|
3335
3522
|
function messagesContainVideo(messages) {
|
|
3336
3523
|
for (const msg of messages) {
|
|
@@ -3435,6 +3622,125 @@ Original: ${message}`;
|
|
|
3435
3622
|
return message;
|
|
3436
3623
|
}
|
|
3437
3624
|
|
|
3625
|
+
// src/redaction.ts
|
|
3626
|
+
var REDACTED = "[REDACTED]";
|
|
3627
|
+
var TRUNCATED = "[TRUNCATED]";
|
|
3628
|
+
var CIRCULAR = "[CIRCULAR]";
|
|
3629
|
+
var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
|
|
3630
|
+
var SENSITIVE_ASSIGNMENT = /\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret))\b(\s*[=:]\s*)(["']?)([^\s,"';}]+)\3/gi;
|
|
3631
|
+
function escaped(value) {
|
|
3632
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3633
|
+
}
|
|
3634
|
+
function normalizedSecrets(secrets) {
|
|
3635
|
+
if (!secrets) return [];
|
|
3636
|
+
return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
|
|
3637
|
+
(a, b) => b.length - a.length
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
function environmentSecrets(env) {
|
|
3641
|
+
const values = /* @__PURE__ */ new Set();
|
|
3642
|
+
for (const [name, value] of Object.entries(env)) {
|
|
3643
|
+
if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
|
|
3644
|
+
values.add(value);
|
|
3645
|
+
}
|
|
3646
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
3647
|
+
}
|
|
3648
|
+
function redactText(text, options = {}) {
|
|
3649
|
+
let result = text;
|
|
3650
|
+
result = result.replace(
|
|
3651
|
+
/-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
|
|
3652
|
+
REDACTED
|
|
3653
|
+
);
|
|
3654
|
+
result = result.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, `$1${REDACTED}@`);
|
|
3655
|
+
result = result.replace(
|
|
3656
|
+
/\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
|
|
3657
|
+
`$1${REDACTED}`
|
|
3658
|
+
);
|
|
3659
|
+
result = result.replace(/\b(bearer|basic)\s+[A-Za-z0-9+/_.=-]{8,}/gi, `$1 ${REDACTED}`);
|
|
3660
|
+
result = result.replace(/\b(cookie|set-cookie)(\s*[:=]\s*)[^\r\n]+/gi, `$1$2${REDACTED}`);
|
|
3661
|
+
result = result.replace(
|
|
3662
|
+
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
|
|
3663
|
+
REDACTED
|
|
3664
|
+
);
|
|
3665
|
+
result = result.replace(
|
|
3666
|
+
/\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
|
|
3667
|
+
REDACTED
|
|
3668
|
+
);
|
|
3669
|
+
result = result.replace(
|
|
3670
|
+
SENSITIVE_ASSIGNMENT,
|
|
3671
|
+
(_match, name, separator) => `${name}${separator}${REDACTED}`
|
|
3672
|
+
);
|
|
3673
|
+
for (const secret of normalizedSecrets(options.secrets)) {
|
|
3674
|
+
result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
|
|
3675
|
+
}
|
|
3676
|
+
const maxStringLength = options.maxStringLength ?? 1e6;
|
|
3677
|
+
if (result.length > maxStringLength) {
|
|
3678
|
+
result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
|
|
3679
|
+
}
|
|
3680
|
+
return result;
|
|
3681
|
+
}
|
|
3682
|
+
function isBinary(value) {
|
|
3683
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
|
|
3684
|
+
}
|
|
3685
|
+
function isMediaObject(value) {
|
|
3686
|
+
return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
|
|
3687
|
+
}
|
|
3688
|
+
function redactValue(value, options = {}) {
|
|
3689
|
+
const maxDepth = options.maxDepth ?? 20;
|
|
3690
|
+
const maxEntries = options.maxEntries ?? 1e4;
|
|
3691
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
3692
|
+
let entries = 0;
|
|
3693
|
+
const visit = (current, depth, sensitive = false) => {
|
|
3694
|
+
if (typeof current === "string") {
|
|
3695
|
+
if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
|
|
3696
|
+
return redactText(current, options);
|
|
3697
|
+
}
|
|
3698
|
+
if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
|
|
3699
|
+
return current;
|
|
3700
|
+
}
|
|
3701
|
+
if (typeof current !== "object") return current;
|
|
3702
|
+
if (isBinary(current)) return current;
|
|
3703
|
+
if (current instanceof Date) return new Date(current.getTime());
|
|
3704
|
+
if (depth >= maxDepth) return TRUNCATED;
|
|
3705
|
+
if (seen.has(current)) return CIRCULAR;
|
|
3706
|
+
seen.add(current);
|
|
3707
|
+
if (current instanceof Error) {
|
|
3708
|
+
const error = {
|
|
3709
|
+
name: current.name,
|
|
3710
|
+
message: visit(current.message, depth + 1),
|
|
3711
|
+
stack: visit(current.stack, depth + 1)
|
|
3712
|
+
};
|
|
3713
|
+
for (const [key, child] of Object.entries(current)) {
|
|
3714
|
+
error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
3715
|
+
}
|
|
3716
|
+
return error;
|
|
3717
|
+
}
|
|
3718
|
+
if (Array.isArray(current)) {
|
|
3719
|
+
const clone2 = [];
|
|
3720
|
+
for (const child of current) {
|
|
3721
|
+
if (++entries > maxEntries) {
|
|
3722
|
+
clone2.push(TRUNCATED);
|
|
3723
|
+
break;
|
|
3724
|
+
}
|
|
3725
|
+
clone2.push(visit(child, depth + 1));
|
|
3726
|
+
}
|
|
3727
|
+
return clone2;
|
|
3728
|
+
}
|
|
3729
|
+
const record = current;
|
|
3730
|
+
if (isMediaObject(record)) return { ...record };
|
|
3731
|
+
const clone = {};
|
|
3732
|
+
for (const [key, child] of Object.entries(record)) {
|
|
3733
|
+
if (++entries > maxEntries) {
|
|
3734
|
+
clone[TRUNCATED] = true;
|
|
3735
|
+
break;
|
|
3736
|
+
}
|
|
3737
|
+
clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
3738
|
+
}
|
|
3739
|
+
return clone;
|
|
3740
|
+
};
|
|
3741
|
+
return visit(value, 0);
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3438
3744
|
// src/providers/palsu.ts
|
|
3439
3745
|
function palsuText(text) {
|
|
3440
3746
|
return { role: "assistant", content: text ? [{ type: "text", text }] : [] };
|
|
@@ -3591,8 +3897,11 @@ function registerPalsuProvider(config) {
|
|
|
3591
3897
|
EZCoderAIError,
|
|
3592
3898
|
EventStream,
|
|
3593
3899
|
ProviderError,
|
|
3900
|
+
REDACTION_MARKER,
|
|
3594
3901
|
StreamResult,
|
|
3902
|
+
clampProviderContextImages,
|
|
3595
3903
|
classifyProviderError,
|
|
3904
|
+
environmentSecrets,
|
|
3596
3905
|
formatError,
|
|
3597
3906
|
formatErrorForDisplay,
|
|
3598
3907
|
isHardBillingMessage,
|
|
@@ -3603,6 +3912,8 @@ function registerPalsuProvider(config) {
|
|
|
3603
3912
|
palsuToolCall,
|
|
3604
3913
|
prewarmAnthropicCache,
|
|
3605
3914
|
providerRegistry,
|
|
3915
|
+
redactText,
|
|
3916
|
+
redactValue,
|
|
3606
3917
|
registerPalsuProvider,
|
|
3607
3918
|
setProviderDiagnostic,
|
|
3608
3919
|
stream,
|