@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/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
3
  import OpenAI from 'openai';
4
4
 
5
- type Provider = "anthropic" | "xiaomi" | "openai" | "gemini" | "glm" | "moonshot" | "minimax" | "deepseek" | "openrouter" | "sakana" | "palsu";
5
+ type Provider = "anthropic" | "xiaomi" | "openai" | "gemini" | "glm" | "moonshot" | "minimax" | "deepseek" | "openrouter" | "sakana" | "xai" | "palsu";
6
6
  type ThinkingLevel = "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
7
7
  type CacheRetention = "none" | "short" | "long";
8
8
  interface TextContent {
@@ -444,6 +444,27 @@ declare function formatErrorForDisplay(err: unknown): string;
444
444
  */
445
445
  declare function classifyProviderError(message: string): string;
446
446
 
447
+ declare const REDACTED = "[REDACTED]";
448
+ interface RedactionOptions {
449
+ /** Exact secret values to remove in addition to high-confidence formats. */
450
+ secrets?: Iterable<string>;
451
+ /** Maximum recursive object depth before a stable truncation marker is emitted. */
452
+ maxDepth?: number;
453
+ /** Maximum total array/object entries cloned before truncation markers are emitted. */
454
+ maxEntries?: number;
455
+ /** Maximum retained string length after sanitization. */
456
+ maxStringLength?: number;
457
+ }
458
+ /** Collect sufficiently distinctive secrets from security-sensitive environment variables. */
459
+ declare function environmentSecrets(env: Record<string, string | undefined>): string[];
460
+ /** Redact credentials from arbitrary text without mutating its source. */
461
+ declare function redactText(text: string, options?: RedactionOptions): string;
462
+ /**
463
+ * Recursively clone and sanitize transport/persistence payloads.
464
+ * Cycles, excessive depth, and excessive collection sizes become stable markers.
465
+ */
466
+ declare function redactValue<T>(value: T, options?: RedactionOptions): T;
467
+
447
468
  /**
448
469
  * Provider-level diagnostic hook. Mirrors the pattern used by gg-agent's
449
470
  * setStreamDiagnostic — the host app wires a callback (typically writing to
@@ -454,6 +475,12 @@ type ProviderDiagnosticFn = (phase: string, data?: Record<string, unknown>) => v
454
475
  /** Register a diagnostic callback for provider-level tracing. */
455
476
  declare function setProviderDiagnostic(fn: ProviderDiagnosticFn | null): void;
456
477
 
478
+ /**
479
+ * Cap historical images before provider dispatch, removing the oldest first.
480
+ * The persisted/live conversation is never mutated; only modified messages and
481
+ * tool results are cloned for the outgoing request.
482
+ */
483
+ declare function clampProviderContextImages(messages: Message[], provider: Provider, supportsImages: boolean | undefined): Message[];
457
484
  declare function toAnthropicMessages(messages: Message[], cacheControl?: {
458
485
  type: "ephemeral";
459
486
  ttl?: "1h";
@@ -558,4 +585,4 @@ interface PalsuProviderConfig {
558
585
  */
559
586
  declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
560
587
 
561
- export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, classifyProviderError, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, prewarmAnthropicCache, providerRegistry, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
588
+ export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, REDACTED as REDACTION_MARKER, type RawContent, type RedactionOptions, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, clampProviderContextImages, classifyProviderError, environmentSecrets, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, prewarmAnthropicCache, providerRegistry, redactText, redactValue, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
3
  import OpenAI from 'openai';
4
4
 
5
- type Provider = "anthropic" | "xiaomi" | "openai" | "gemini" | "glm" | "moonshot" | "minimax" | "deepseek" | "openrouter" | "sakana" | "palsu";
5
+ type Provider = "anthropic" | "xiaomi" | "openai" | "gemini" | "glm" | "moonshot" | "minimax" | "deepseek" | "openrouter" | "sakana" | "xai" | "palsu";
6
6
  type ThinkingLevel = "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
7
7
  type CacheRetention = "none" | "short" | "long";
8
8
  interface TextContent {
@@ -444,6 +444,27 @@ declare function formatErrorForDisplay(err: unknown): string;
444
444
  */
445
445
  declare function classifyProviderError(message: string): string;
446
446
 
447
+ declare const REDACTED = "[REDACTED]";
448
+ interface RedactionOptions {
449
+ /** Exact secret values to remove in addition to high-confidence formats. */
450
+ secrets?: Iterable<string>;
451
+ /** Maximum recursive object depth before a stable truncation marker is emitted. */
452
+ maxDepth?: number;
453
+ /** Maximum total array/object entries cloned before truncation markers are emitted. */
454
+ maxEntries?: number;
455
+ /** Maximum retained string length after sanitization. */
456
+ maxStringLength?: number;
457
+ }
458
+ /** Collect sufficiently distinctive secrets from security-sensitive environment variables. */
459
+ declare function environmentSecrets(env: Record<string, string | undefined>): string[];
460
+ /** Redact credentials from arbitrary text without mutating its source. */
461
+ declare function redactText(text: string, options?: RedactionOptions): string;
462
+ /**
463
+ * Recursively clone and sanitize transport/persistence payloads.
464
+ * Cycles, excessive depth, and excessive collection sizes become stable markers.
465
+ */
466
+ declare function redactValue<T>(value: T, options?: RedactionOptions): T;
467
+
447
468
  /**
448
469
  * Provider-level diagnostic hook. Mirrors the pattern used by gg-agent's
449
470
  * setStreamDiagnostic — the host app wires a callback (typically writing to
@@ -454,6 +475,12 @@ type ProviderDiagnosticFn = (phase: string, data?: Record<string, unknown>) => v
454
475
  /** Register a diagnostic callback for provider-level tracing. */
455
476
  declare function setProviderDiagnostic(fn: ProviderDiagnosticFn | null): void;
456
477
 
478
+ /**
479
+ * Cap historical images before provider dispatch, removing the oldest first.
480
+ * The persisted/live conversation is never mutated; only modified messages and
481
+ * tool results are cloned for the outgoing request.
482
+ */
483
+ declare function clampProviderContextImages(messages: Message[], provider: Provider, supportsImages: boolean | undefined): Message[];
457
484
  declare function toAnthropicMessages(messages: Message[], cacheControl?: {
458
485
  type: "ephemeral";
459
486
  ttl?: "1h";
@@ -558,4 +585,4 @@ interface PalsuProviderConfig {
558
585
  */
559
586
  declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
560
587
 
561
- export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, classifyProviderError, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, prewarmAnthropicCache, providerRegistry, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
588
+ export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, EZCoderAIError, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderDiagnosticFn, type ProviderEntry, ProviderError, type ProviderStreamFn, REDACTED as REDACTION_MARKER, type RawContent, type RedactionOptions, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, type VideoContent, clampProviderContextImages, classifyProviderError, environmentSecrets, formatError, formatErrorForDisplay, isHardBillingMessage, isUsageLimitError, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, prewarmAnthropicCache, providerRegistry, redactText, redactValue, registerPalsuProvider, setProviderDiagnostic, stream, toAnthropicMessages, toOpenAIMessages };
package/dist/index.js CHANGED
@@ -58,12 +58,14 @@ var PROVIDER_DISPLAY = {
58
58
  deepseek: "DeepSeek",
59
59
  openrouter: "OpenRouter",
60
60
  sakana: "Sakana",
61
+ xai: "xAI (Grok)",
61
62
  xiaomi: "Xiaomi (MiMo)",
62
63
  minimax: "MiniMax"
63
64
  };
64
65
  var PROVIDER_STATUS_URL = {
65
66
  openai: "status.openai.com",
66
- anthropic: "status.anthropic.com"
67
+ anthropic: "status.anthropic.com",
68
+ xai: "status.x.ai"
67
69
  };
68
70
  function providerDisplayName(provider) {
69
71
  return PROVIDER_DISPLAY[provider] ?? provider;
@@ -102,13 +104,20 @@ function isRawJsonErrorEcho(message) {
102
104
  return false;
103
105
  }
104
106
  }
107
+ function isRawHtmlErrorEcho(message) {
108
+ const withoutStatus = message.trimStart().replace(/^\d{3}\s+/, "").trimStart();
109
+ return /^<!doctype\s+html(?:\s|>)/i.test(withoutStatus) || /^<html(?:\s|>)/i.test(withoutStatus);
110
+ }
111
+ function providerHtmlErrorMessage(statusCode) {
112
+ 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.";
113
+ }
105
114
  function emptyProviderErrorMessage(statusCode) {
106
115
  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.";
107
116
  }
108
117
  function formatError(err) {
109
118
  if (err instanceof ProviderError) {
110
119
  const name = providerDisplayName(err.provider);
111
- const cleanMessage = cleanProviderMessage(err.message);
120
+ const cleanMessage = cleanProviderMessage(err.message, err.statusCode);
112
121
  if (isMythosAccessError(cleanMessage)) {
113
122
  return {
114
123
  headline: "Claude Mythos 5 is invitation-only.",
@@ -203,8 +212,9 @@ function formatErrorForDisplay(err) {
203
212
  lines.push(` \u2192 ${f.guidance}`);
204
213
  return lines.join("\n");
205
214
  }
206
- function cleanProviderMessage(message) {
207
- return message.replace(/^\[[^\]]+\]\s*/, "").trim();
215
+ function cleanProviderMessage(message, statusCode) {
216
+ const clean = message.replace(/^\[[^\]]+\]\s*/, "").trim();
217
+ return isRawHtmlErrorEcho(clean) ? providerHtmlErrorMessage(statusCode) : clean;
208
218
  }
209
219
  function inferSource(err) {
210
220
  const msg = err.message.toLowerCase();
@@ -239,6 +249,9 @@ function providerGuidance(provider, message, statusCode) {
239
249
  if (statusCode === 503 || lower.includes("service unavailable")) {
240
250
  return `${name} is temporarily unavailable. Retry shortly \u2014 not a EZ Coder issue.`;
241
251
  }
252
+ if (statusCode === 507 || lower.includes("exceeded request buffer limit while retrying upstream")) {
253
+ return `${name}'s proxy could not retry this large request. EZ Coder already retried automatically \u2014 compact the conversation, then retry.`;
254
+ }
242
255
  if (statusCode === 500 || lower.includes("server_error") || lower.includes("500") && lower.includes("internal server error")) {
243
256
  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.`;
244
257
  }
@@ -565,6 +578,66 @@ function toAnthropicAssistantContent(content, preserveThinking, idMap) {
565
578
  return true;
566
579
  }).map((part) => toAnthropicAssistantPart(part, idMap)).filter((b) => b !== null);
567
580
  }
581
+ var PROVIDER_IMAGE_LIMIT_PLACEHOLDER = "[image omitted: provider image limit]";
582
+ var PROVIDER_IMAGE_BUDGETS = {
583
+ anthropic: 90,
584
+ minimax: 90,
585
+ openai: 200,
586
+ gemini: 200,
587
+ openrouter: 90
588
+ };
589
+ function countContextImages(messages) {
590
+ let count = 0;
591
+ for (const message of messages) {
592
+ if (message.role === "user" && Array.isArray(message.content)) {
593
+ count += message.content.filter((part) => part.type === "image").length;
594
+ } else if (message.role === "tool") {
595
+ for (const result of message.content) {
596
+ if (Array.isArray(result.content)) {
597
+ count += result.content.filter((part) => part.type === "image").length;
598
+ }
599
+ }
600
+ }
601
+ }
602
+ return count;
603
+ }
604
+ function clampProviderContextImages(messages, provider, supportsImages) {
605
+ if (supportsImages === false) return messages;
606
+ const budget = PROVIDER_IMAGE_BUDGETS[provider] ?? 5;
607
+ let remainingToRemove = countContextImages(messages) - budget;
608
+ if (remainingToRemove <= 0) return messages;
609
+ return messages.map((message) => {
610
+ if (message.role === "user" && Array.isArray(message.content)) {
611
+ const content = message.content.filter((part) => {
612
+ if (part.type !== "image" || remainingToRemove <= 0) return true;
613
+ remainingToRemove--;
614
+ return false;
615
+ });
616
+ return {
617
+ ...message,
618
+ content: content.length > 0 ? content : [{ type: "text", text: PROVIDER_IMAGE_LIMIT_PLACEHOLDER }]
619
+ };
620
+ }
621
+ if (message.role === "tool") {
622
+ return {
623
+ ...message,
624
+ content: message.content.map((result) => {
625
+ if (!Array.isArray(result.content)) return result;
626
+ const content = result.content.filter((part) => {
627
+ if (part.type !== "image" || remainingToRemove <= 0) return true;
628
+ remainingToRemove--;
629
+ return false;
630
+ });
631
+ return {
632
+ ...result,
633
+ content: content.length > 0 ? content : [{ type: "text", text: PROVIDER_IMAGE_LIMIT_PLACEHOLDER }]
634
+ };
635
+ })
636
+ };
637
+ }
638
+ return message;
639
+ });
640
+ }
568
641
  var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
569
642
  var NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
570
643
  var NON_VIDEO_USER_PLACEHOLDER = "(video omitted: model does not support video)";
@@ -1582,7 +1655,8 @@ function toError(err) {
1582
1655
  const bodyMessage = typeof nestedError?.message === "string" && nestedError.message.trim() ? nestedError.message.trim() : typeof errorBody?.message === "string" && errorBody.message.trim() ? errorBody.message.trim() : void 0;
1583
1656
  const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
1584
1657
  const fallbackMessage = isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message;
1585
- const message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? fallbackMessage;
1658
+ const messageCandidate = bodyMessage ?? err.message;
1659
+ const message = isRawHtmlErrorEcho(messageCandidate) ? providerHtmlErrorMessage(err.status) : bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? fallbackMessage;
1586
1660
  if (err.status === 429) {
1587
1661
  const limit = readUnifiedRateLimit(err.headers);
1588
1662
  const farOff = limit.resetsAt != null && limit.resetsAt * 1e3 - Date.now() > 6e4;
@@ -1764,7 +1838,11 @@ async function* runStream2(options) {
1764
1838
  const providerName = options.provider ?? "openai";
1765
1839
  const useStreaming = options.streaming !== false;
1766
1840
  const client = createClient2(options);
1767
- const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
1841
+ const isKimiK3 = options.provider === "moonshot" && options.model === "kimi-k3";
1842
+ const isManagedKimiK3 = isKimiK3 && options.baseUrl?.replace(/\/+$/, "").endsWith("/coding/v1") === true;
1843
+ const isKimiK27 = options.provider === "moonshot" && options.model.startsWith("kimi-k2.7-code");
1844
+ const hasFixedKimiSampling = isKimiK3 || isKimiK27;
1845
+ const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" && !isKimiK3 && !isKimiK27 || options.provider === "xiaomi";
1768
1846
  const downgradedImages = downgradeUnsupportedImages(options.messages, options.supportsImages);
1769
1847
  const downgradedMessages = downgradeUnsupportedVideos(downgradedImages, options.supportsVideo);
1770
1848
  if (options.provider === "moonshot") {
@@ -1776,7 +1854,9 @@ async function* runStream2(options) {
1776
1854
  }
1777
1855
  const messages = toOpenAIMessages(downgradedMessages, {
1778
1856
  provider: options.provider,
1779
- thinking: !!options.thinking,
1857
+ // K3 and K2.7 preserve reasoning even when the user hides thinking in the
1858
+ // UI; keep assistant tool-call history wire-valid in that display mode.
1859
+ thinking: isKimiK3 || isKimiK27 || !!options.thinking,
1780
1860
  supportsImages: options.supportsImages
1781
1861
  });
1782
1862
  const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
@@ -1786,10 +1866,10 @@ async function* runStream2(options) {
1786
1866
  messages,
1787
1867
  stream: useStreaming,
1788
1868
  ...options.maxTokens ? { max_completion_tokens: options.maxTokens } : {},
1789
- ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
1790
- ...options.topP != null ? { top_p: options.topP } : {},
1869
+ ...effectiveTemp != null && !options.thinking && !hasFixedKimiSampling ? { temperature: effectiveTemp } : {},
1870
+ ...options.topP != null && !hasFixedKimiSampling ? { top_p: options.topP } : {},
1791
1871
  ...options.stop ? { stop: options.stop } : {},
1792
- ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
1872
+ ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
1793
1873
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
1794
1874
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
1795
1875
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
@@ -1799,13 +1879,21 @@ async function* runStream2(options) {
1799
1879
  paramsAny.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1800
1880
  if (options.provider === "openai" && options.model.startsWith("gpt-5.6")) {
1801
1881
  paramsAny.prompt_cache_options = { mode: "implicit", ttl: "30m" };
1802
- } else if ((options.cacheRetention ?? "short") === "long") {
1882
+ } else if (!isKimiK3 && (options.cacheRetention ?? "short") === "long") {
1803
1883
  paramsAny.prompt_cache_retention = "24h";
1804
1884
  }
1805
1885
  }
1806
1886
  if (options.provider === "openai" && options.serviceTier) {
1807
1887
  params.service_tier = options.serviceTier;
1808
1888
  }
1889
+ if (isKimiK3) {
1890
+ const paramsAny = params;
1891
+ if (isManagedKimiK3) {
1892
+ paramsAny.thinking = { type: "enabled", effort: "max", keep: "all" };
1893
+ } else {
1894
+ paramsAny.reasoning_effort = "max";
1895
+ }
1896
+ }
1809
1897
  if (usesThinkingParam) {
1810
1898
  if (options.thinking) {
1811
1899
  params.thinking = { type: "enabled" };
@@ -2050,7 +2138,8 @@ function toError2(err, provider = "openai") {
2050
2138
  const body = err.error;
2051
2139
  const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
2052
2140
  const modelName = typeof body?.model === "string" ? body.model : "";
2053
- const cleanMessage = bodyMessage ?? (isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message);
2141
+ const messageCandidate = bodyMessage ?? err.message;
2142
+ const cleanMessage = isRawHtmlErrorEcho(messageCandidate) ? providerHtmlErrorMessage(err.status) : bodyMessage ? bodyMessage : isRawJsonErrorEcho(err.message) ? emptyProviderErrorMessage(err.status) : err.message;
2054
2143
  let hint;
2055
2144
  if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
2056
2145
  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.";
@@ -2100,6 +2189,7 @@ function toError2(err, provider = "openai") {
2100
2189
 
2101
2190
  // src/providers/openai-codex.ts
2102
2191
  import os from "os";
2192
+ import * as zstd from "@bokuweb/zstd-wasm";
2103
2193
 
2104
2194
  // src/utils/sse.ts
2105
2195
  function parseSseBuffer(buffer) {
@@ -2155,6 +2245,50 @@ function extractRequestIdFromMessage(message) {
2155
2245
  // src/providers/openai-codex.ts
2156
2246
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
2157
2247
  var CODEX_CLIENT_VERSION = "0.144.1";
2248
+ var CODEX_REQUEST_COMPRESSION_MIN_BYTES = 16 * 1024;
2249
+ var zstdInitPromise;
2250
+ async function encodeCodexRequest(body) {
2251
+ const json = JSON.stringify(body);
2252
+ const raw = new TextEncoder().encode(json);
2253
+ if (raw.byteLength < CODEX_REQUEST_COMPRESSION_MIN_BYTES) {
2254
+ return {
2255
+ body: json,
2256
+ compressed: false,
2257
+ rawBytes: raw.byteLength,
2258
+ encodedBytes: raw.byteLength
2259
+ };
2260
+ }
2261
+ try {
2262
+ zstdInitPromise ??= zstd.init();
2263
+ await zstdInitPromise;
2264
+ const compressed = Uint8Array.from(zstd.compress(raw));
2265
+ if (compressed.byteLength >= raw.byteLength) {
2266
+ return {
2267
+ body: json,
2268
+ compressed: false,
2269
+ rawBytes: raw.byteLength,
2270
+ encodedBytes: raw.byteLength
2271
+ };
2272
+ }
2273
+ return {
2274
+ body: compressed,
2275
+ compressed: true,
2276
+ rawBytes: raw.byteLength,
2277
+ encodedBytes: compressed.byteLength
2278
+ };
2279
+ } catch (error) {
2280
+ providerDiag("codex_request_compression_failed", {
2281
+ error: error instanceof Error ? error.message : String(error),
2282
+ rawBytes: raw.byteLength
2283
+ });
2284
+ return {
2285
+ body: json,
2286
+ compressed: false,
2287
+ rawBytes: raw.byteLength,
2288
+ encodedBytes: raw.byteLength
2289
+ };
2290
+ }
2291
+ }
2158
2292
  function usesResponsesLite(model) {
2159
2293
  return model.startsWith("gpt-5.6-");
2160
2294
  }
@@ -2164,6 +2298,24 @@ function outputTextKey(itemId, contentIndex) {
2164
2298
  function isVisibleOutputItem(itemType) {
2165
2299
  return itemType === "message";
2166
2300
  }
2301
+ function toCodexToolChoice(choice, tools) {
2302
+ const resolved = choice ?? "auto";
2303
+ if (typeof resolved === "object") {
2304
+ throw new EZCoderAIError(
2305
+ `OpenAI Codex does not support selecting the named tool \`${resolved.name}\`; use auto, none, or required.`,
2306
+ { source: "capability" }
2307
+ );
2308
+ }
2309
+ if (resolved === "required" && !tools?.length) {
2310
+ throw new EZCoderAIError(
2311
+ "OpenAI Codex cannot require a tool call when no tools are configured.",
2312
+ {
2313
+ source: "capability"
2314
+ }
2315
+ );
2316
+ }
2317
+ return resolved;
2318
+ }
2167
2319
  function streamOpenAICodex(options) {
2168
2320
  return new StreamResult(runStream3(options), options.signal);
2169
2321
  }
@@ -2180,7 +2332,7 @@ async function* runStream3(options) {
2180
2332
  stream: true,
2181
2333
  instructions: system,
2182
2334
  input,
2183
- tool_choice: "auto",
2335
+ tool_choice: toCodexToolChoice(options.toolChoice, options.tools),
2184
2336
  parallel_tool_calls: !responsesLite,
2185
2337
  include: ["reasoning.encrypted_content"]
2186
2338
  };
@@ -2213,18 +2365,26 @@ async function* runStream3(options) {
2213
2365
  headers["chatgpt-account-id"] = options.accountId;
2214
2366
  }
2215
2367
  if (options.transportSessionId) {
2216
- headers["session_id"] = options.transportSessionId;
2217
- headers["x-client-request-id"] = options.transportSessionId;
2218
- }
2368
+ const transportSessionId = normalizePromptCacheKey(options.transportSessionId);
2369
+ headers["session_id"] = transportSessionId;
2370
+ headers["x-client-request-id"] = transportSessionId;
2371
+ }
2372
+ const encodedRequest = await encodeCodexRequest(body);
2373
+ if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
2374
+ providerDiag("codex_request_body", {
2375
+ rawBytes: encodedRequest.rawBytes,
2376
+ encodedBytes: encodedRequest.encodedBytes,
2377
+ compressed: encodedRequest.compressed
2378
+ });
2219
2379
  const response = await fetch(url, {
2220
2380
  method: "POST",
2221
2381
  headers,
2222
- body: JSON.stringify(body),
2382
+ body: encodedRequest.body,
2223
2383
  signal: options.signal
2224
2384
  });
2225
2385
  if (!response.ok) {
2226
2386
  const text = await response.text().catch(() => "");
2227
- const parsed = parseCodexErrorBody(text);
2387
+ const parsed = parseCodexErrorBody(text, response.status);
2228
2388
  const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
2229
2389
  const requestId = parsed.requestId ?? readHeader(response.headers, "x-request-id", "openai-request-id", "x-oai-request-id");
2230
2390
  const usageLimit = codexUsageLimitError(parsed.errorObj, response.status, requestId);
@@ -2618,13 +2778,14 @@ function toCodexTools(tools) {
2618
2778
  strict: null
2619
2779
  }));
2620
2780
  }
2621
- function parseCodexErrorBody(text) {
2781
+ function parseCodexErrorBody(text, statusCode) {
2622
2782
  if (!text) return {};
2623
2783
  try {
2624
2784
  const parsed = JSON.parse(text);
2625
2785
  const error = parsed.error;
2626
2786
  const detail = parsed.detail;
2627
- const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
2787
+ const rawMessage = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
2788
+ const message = rawMessage && isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(statusCode) : rawMessage;
2628
2789
  const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractRequestIdFromMessage(message) : void 0);
2629
2790
  const errorObj = error ?? parsed;
2630
2791
  return {
@@ -2633,8 +2794,12 @@ function parseCodexErrorBody(text) {
2633
2794
  ...errorObj ? { errorObj } : {}
2634
2795
  };
2635
2796
  } catch {
2636
- const trimmed = text.trim().slice(0, 240);
2637
- return trimmed ? { message: trimmed } : {};
2797
+ const trimmed = text.trim();
2798
+ if (isRawHtmlErrorEcho(trimmed)) {
2799
+ return { message: providerHtmlErrorMessage(statusCode) };
2800
+ }
2801
+ const bounded = trimmed.slice(0, 240);
2802
+ return bounded ? { message: bounded } : {};
2638
2803
  }
2639
2804
  }
2640
2805
  var CODEX_USAGE_LIMIT_CODE = /usage_limit_reached|usage_not_included/i;
@@ -3253,6 +3418,18 @@ providerRegistry.register("sakana", {
3253
3418
  baseUrl: options.baseUrl ?? "https://api.sakana.ai/v1"
3254
3419
  })
3255
3420
  });
3421
+ providerRegistry.register("xai", {
3422
+ // xAI's public API (console.x.ai key) is OpenAI-compatible — ride the Chat
3423
+ // Completions transport like Moonshot/DeepSeek. Grok reasoning models take
3424
+ // top-level `reasoning_effort` (low/medium/high), which the shared thinking
3425
+ // path already sends. xAI's OAuth path exists but only via the Grok CLI's
3426
+ // private Responses proxy (cli-chat-proxy.grok.com) with reverse-engineered
3427
+ // attribution headers and account-tier gating — intentionally not wired.
3428
+ stream: (options) => streamOpenAI({
3429
+ ...options,
3430
+ baseUrl: options.baseUrl ?? "https://api.x.ai/v1"
3431
+ })
3432
+ });
3256
3433
  providerRegistry.register("minimax", {
3257
3434
  stream: (options) => streamAnthropic({
3258
3435
  ...options,
@@ -3275,7 +3452,12 @@ function stream(options) {
3275
3452
  if (options.supportsVideo !== true && messagesContainVideo(options.messages)) {
3276
3453
  throw new VideoUnsupportedError();
3277
3454
  }
3278
- return entry.stream(options);
3455
+ const messages = clampProviderContextImages(
3456
+ options.messages,
3457
+ options.provider,
3458
+ options.supportsImages
3459
+ );
3460
+ return entry.stream(messages === options.messages ? options : { ...options, messages });
3279
3461
  }
3280
3462
  function messagesContainVideo(messages) {
3281
3463
  for (const msg of messages) {
@@ -3380,6 +3562,125 @@ Original: ${message}`;
3380
3562
  return message;
3381
3563
  }
3382
3564
 
3565
+ // src/redaction.ts
3566
+ var REDACTED = "[REDACTED]";
3567
+ var TRUNCATED = "[TRUNCATED]";
3568
+ var CIRCULAR = "[CIRCULAR]";
3569
+ var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
3570
+ 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;
3571
+ function escaped(value) {
3572
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3573
+ }
3574
+ function normalizedSecrets(secrets) {
3575
+ if (!secrets) return [];
3576
+ return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
3577
+ (a, b) => b.length - a.length
3578
+ );
3579
+ }
3580
+ function environmentSecrets(env) {
3581
+ const values = /* @__PURE__ */ new Set();
3582
+ for (const [name, value] of Object.entries(env)) {
3583
+ if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
3584
+ values.add(value);
3585
+ }
3586
+ return [...values].sort((a, b) => b.length - a.length);
3587
+ }
3588
+ function redactText(text, options = {}) {
3589
+ let result = text;
3590
+ result = result.replace(
3591
+ /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
3592
+ REDACTED
3593
+ );
3594
+ result = result.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi, `$1${REDACTED}@`);
3595
+ result = result.replace(
3596
+ /\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
3597
+ `$1${REDACTED}`
3598
+ );
3599
+ result = result.replace(/\b(bearer|basic)\s+[A-Za-z0-9+/_.=-]{8,}/gi, `$1 ${REDACTED}`);
3600
+ result = result.replace(/\b(cookie|set-cookie)(\s*[:=]\s*)[^\r\n]+/gi, `$1$2${REDACTED}`);
3601
+ result = result.replace(
3602
+ /\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
3603
+ REDACTED
3604
+ );
3605
+ result = result.replace(
3606
+ /\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
3607
+ REDACTED
3608
+ );
3609
+ result = result.replace(
3610
+ SENSITIVE_ASSIGNMENT,
3611
+ (_match, name, separator) => `${name}${separator}${REDACTED}`
3612
+ );
3613
+ for (const secret of normalizedSecrets(options.secrets)) {
3614
+ result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
3615
+ }
3616
+ const maxStringLength = options.maxStringLength ?? 1e6;
3617
+ if (result.length > maxStringLength) {
3618
+ result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
3619
+ }
3620
+ return result;
3621
+ }
3622
+ function isBinary(value) {
3623
+ return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
3624
+ }
3625
+ function isMediaObject(value) {
3626
+ return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
3627
+ }
3628
+ function redactValue(value, options = {}) {
3629
+ const maxDepth = options.maxDepth ?? 20;
3630
+ const maxEntries = options.maxEntries ?? 1e4;
3631
+ const seen = /* @__PURE__ */ new WeakSet();
3632
+ let entries = 0;
3633
+ const visit = (current, depth, sensitive = false) => {
3634
+ if (typeof current === "string") {
3635
+ if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
3636
+ return redactText(current, options);
3637
+ }
3638
+ if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
3639
+ return current;
3640
+ }
3641
+ if (typeof current !== "object") return current;
3642
+ if (isBinary(current)) return current;
3643
+ if (current instanceof Date) return new Date(current.getTime());
3644
+ if (depth >= maxDepth) return TRUNCATED;
3645
+ if (seen.has(current)) return CIRCULAR;
3646
+ seen.add(current);
3647
+ if (current instanceof Error) {
3648
+ const error = {
3649
+ name: current.name,
3650
+ message: visit(current.message, depth + 1),
3651
+ stack: visit(current.stack, depth + 1)
3652
+ };
3653
+ for (const [key, child] of Object.entries(current)) {
3654
+ error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
3655
+ }
3656
+ return error;
3657
+ }
3658
+ if (Array.isArray(current)) {
3659
+ const clone2 = [];
3660
+ for (const child of current) {
3661
+ if (++entries > maxEntries) {
3662
+ clone2.push(TRUNCATED);
3663
+ break;
3664
+ }
3665
+ clone2.push(visit(child, depth + 1));
3666
+ }
3667
+ return clone2;
3668
+ }
3669
+ const record = current;
3670
+ if (isMediaObject(record)) return { ...record };
3671
+ const clone = {};
3672
+ for (const [key, child] of Object.entries(record)) {
3673
+ if (++entries > maxEntries) {
3674
+ clone[TRUNCATED] = true;
3675
+ break;
3676
+ }
3677
+ clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
3678
+ }
3679
+ return clone;
3680
+ };
3681
+ return visit(value, 0);
3682
+ }
3683
+
3383
3684
  // src/providers/palsu.ts
3384
3685
  function palsuText(text) {
3385
3686
  return { role: "assistant", content: text ? [{ type: "text", text }] : [] };
@@ -3535,8 +3836,11 @@ export {
3535
3836
  EZCoderAIError,
3536
3837
  EventStream,
3537
3838
  ProviderError,
3839
+ REDACTED as REDACTION_MARKER,
3538
3840
  StreamResult,
3841
+ clampProviderContextImages,
3539
3842
  classifyProviderError,
3843
+ environmentSecrets,
3540
3844
  formatError,
3541
3845
  formatErrorForDisplay,
3542
3846
  isHardBillingMessage,
@@ -3547,6 +3851,8 @@ export {
3547
3851
  palsuToolCall,
3548
3852
  prewarmAnthropicCache,
3549
3853
  providerRegistry,
3854
+ redactText,
3855
+ redactValue,
3550
3856
  registerPalsuProvider,
3551
3857
  setProviderDiagnostic,
3552
3858
  stream,