@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 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
- return message.replace(/^\[[^\]]+\]\s*/, "").trim();
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 message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? fallbackMessage;
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 usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
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
- thinking: !!options.thinking,
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 cleanMessage = bodyMessage ?? (isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message);
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: "auto",
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
- headers["session_id"] = options.transportSessionId;
2272
- headers["x-client-request-id"] = options.transportSessionId;
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: JSON.stringify(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 message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
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().slice(0, 240);
2692
- return trimmed ? { message: trimmed } : {};
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
- return entry.stream(options);
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,