@prestyj/ai 4.3.164 → 4.3.201

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,33 +34,180 @@ __export(index_exports, {
34
34
  EventStream: () => EventStream,
35
35
  ProviderError: () => ProviderError,
36
36
  StreamResult: () => StreamResult,
37
+ formatError: () => formatError,
38
+ formatErrorForDisplay: () => formatErrorForDisplay,
37
39
  palsuAssistantMessage: () => palsuAssistantMessage,
38
40
  palsuText: () => palsuText,
39
41
  palsuThinking: () => palsuThinking,
40
42
  palsuToolCall: () => palsuToolCall,
41
43
  providerRegistry: () => providerRegistry,
42
44
  registerPalsuProvider: () => registerPalsuProvider,
45
+ setProviderDiagnostic: () => setProviderDiagnostic,
43
46
  stream: () => stream
44
47
  });
45
48
  module.exports = __toCommonJS(index_exports);
46
49
 
47
50
  // src/errors.ts
48
51
  var EZCoderAIError = class extends Error {
52
+ source;
53
+ requestId;
54
+ hint;
49
55
  constructor(message, options) {
50
- super(message, options);
56
+ super(message, { cause: options?.cause });
51
57
  this.name = "EZCoderAIError";
58
+ this.source = options?.source ?? "ezcoder";
59
+ this.requestId = options?.requestId;
60
+ this.hint = options?.hint;
52
61
  }
53
62
  };
54
63
  var ProviderError = class extends EZCoderAIError {
55
64
  provider;
56
65
  statusCode;
57
66
  constructor(provider, message, options) {
58
- super(`[${provider}] ${message}`, { cause: options?.cause });
67
+ super(message, {
68
+ source: "provider",
69
+ requestId: options?.requestId,
70
+ hint: options?.hint,
71
+ cause: options?.cause
72
+ });
59
73
  this.name = "ProviderError";
60
74
  this.provider = provider;
61
75
  this.statusCode = options?.statusCode;
62
76
  }
63
77
  };
78
+ var PROVIDER_DISPLAY = {
79
+ openai: "OpenAI",
80
+ anthropic: "Anthropic",
81
+ glm: "Z.AI (GLM)",
82
+ moonshot: "Moonshot",
83
+ deepseek: "DeepSeek",
84
+ openrouter: "OpenRouter",
85
+ xiaomi: "Xiaomi (MiMo)",
86
+ minimax: "MiniMax"
87
+ };
88
+ var PROVIDER_STATUS_URL = {
89
+ openai: "status.openai.com",
90
+ anthropic: "status.anthropic.com"
91
+ };
92
+ function providerDisplayName(provider) {
93
+ return PROVIDER_DISPLAY[provider] ?? provider;
94
+ }
95
+ function formatError(err) {
96
+ if (err instanceof ProviderError) {
97
+ const name = providerDisplayName(err.provider);
98
+ const cleanMessage = cleanProviderMessage(err.message);
99
+ return {
100
+ headline: `${name} returned an error.`,
101
+ source: "provider",
102
+ message: cleanMessage,
103
+ provider: err.provider,
104
+ statusCode: err.statusCode,
105
+ requestId: err.requestId,
106
+ guidance: err.hint ?? providerGuidance(err.provider, cleanMessage, err.statusCode)
107
+ };
108
+ }
109
+ if (err instanceof EZCoderAIError) {
110
+ return finaliseBySource(err.source, err.message, err.requestId, err.hint);
111
+ }
112
+ if (err instanceof Error) {
113
+ const source = inferSource(err);
114
+ return finaliseBySource(source, err.message, void 0, void 0);
115
+ }
116
+ return finaliseBySource("ezcoder", String(err), void 0, void 0);
117
+ }
118
+ function finaliseBySource(source, message, requestId, hint) {
119
+ switch (source) {
120
+ case "network":
121
+ return {
122
+ headline: "Network error \u2014 couldn't reach the provider.",
123
+ source,
124
+ message,
125
+ guidance: hint ?? "Check your internet connection. Not a ezcoder issue \u2014 retry shortly.",
126
+ ...requestId ? { requestId } : {}
127
+ };
128
+ case "auth":
129
+ return {
130
+ headline: "Authentication issue.",
131
+ source,
132
+ message,
133
+ guidance: hint ?? "Run `ezcoder login` to refresh your credentials.",
134
+ ...requestId ? { requestId } : {}
135
+ };
136
+ case "provider":
137
+ return {
138
+ headline: "Provider returned an error.",
139
+ source,
140
+ message,
141
+ guidance: hint ?? providerGuidance(void 0, message, void 0),
142
+ ...requestId ? { requestId } : {}
143
+ };
144
+ case "ezcoder":
145
+ return {
146
+ headline: "ezcoder hit an unexpected error.",
147
+ source,
148
+ message,
149
+ guidance: hint ?? "This looks like a ezcoder bug \u2014 please report it to the developer (see /help).",
150
+ ...requestId ? { requestId } : {}
151
+ };
152
+ }
153
+ }
154
+ function formatErrorForDisplay(err) {
155
+ const f = formatError(err);
156
+ const lines = [f.headline];
157
+ if (f.message && f.message !== f.headline) lines.push(` ${f.message}`);
158
+ lines.push(` \u2192 ${f.guidance}`);
159
+ return lines.join("\n");
160
+ }
161
+ function cleanProviderMessage(message) {
162
+ return message.replace(/^\[[^\]]+\]\s*/, "").trim();
163
+ }
164
+ function inferSource(err) {
165
+ const msg = err.message.toLowerCase();
166
+ const code = err.code ?? "";
167
+ if (code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "ECONNRESET" || msg.includes("fetch failed") || msg.includes("network request failed")) {
168
+ return "network";
169
+ }
170
+ if (msg.includes("not logged in") || msg.includes("token exchange failed") || msg.includes("token refresh failed") || msg.includes("invalid_grant")) {
171
+ return "auth";
172
+ }
173
+ return "ezcoder";
174
+ }
175
+ function providerGuidance(provider, message, statusCode) {
176
+ const name = provider ? providerDisplayName(provider) : "the provider";
177
+ const status = provider ? PROVIDER_STATUS_URL[provider] : void 0;
178
+ const lower = message.toLowerCase();
179
+ if (statusCode === 401 || lower.includes("unauthorized") || lower.includes("invalid api key")) {
180
+ return `Authentication failed with ${name}. Run \`ezcoder login\` to refresh your credentials.`;
181
+ }
182
+ if (lower.includes("overloaded") || lower.includes("engine_overloaded")) {
183
+ return `${name}'s servers are overloaded right now. Retry in a moment \u2014 not a ezcoder issue.`;
184
+ }
185
+ if (lower.includes("insufficient balance") || lower.includes("quota exceeded") || lower.includes("recharge") || lower.includes("no resource package")) {
186
+ return `Your ${name} account has a billing or quota issue \u2014 check your balance. Not a ezcoder issue.`;
187
+ }
188
+ if (statusCode === 429 || lower.includes("rate limit") || lower.includes("too many requests")) {
189
+ return `${name} rate limit hit. Wait a moment then retry \u2014 not a ezcoder issue.`;
190
+ }
191
+ if (statusCode === 502 || lower.includes("bad gateway")) {
192
+ return `${name} returned a bad gateway. Retry \u2014 this is on their side, not ezcoder.`;
193
+ }
194
+ if (statusCode === 503 || lower.includes("service unavailable")) {
195
+ return `${name} is temporarily unavailable. Retry shortly \u2014 not a ezcoder issue.`;
196
+ }
197
+ if (statusCode === 500 || lower.includes("server_error") || lower.includes("500") && lower.includes("internal server error")) {
198
+ return status ? `This is an error from ${name}, not ezcoder. Retry \u2014 if it keeps happening, check ${status}.` : `This is an error from ${name}, not ezcoder. Retry \u2014 if it keeps happening, try a different model with /model.`;
199
+ }
200
+ if (lower.includes("timeout") || lower.includes("timed out")) {
201
+ return `Request to ${name} timed out. Their servers may be slow \u2014 retry. Not a ezcoder issue.`;
202
+ }
203
+ if (lower.includes("does not recognize the requested model") || lower.includes("model") && (lower.includes("not exist") || lower.includes("not found") || lower.includes("no access"))) {
204
+ return `${name} doesn't recognise this model on your account. Use /model to switch, or check your subscription tier.`;
205
+ }
206
+ if (lower.includes("context_length_exceeded") || lower.includes("prompt is too long")) {
207
+ return `Context window for this ${name} model is full. Run /compact to shrink history, or start a new session.`;
208
+ }
209
+ return status ? `This is an error from ${name}, not ezcoder. Retry \u2014 if it persists, check ${status}.` : `This is an error from ${name}, not ezcoder. Retry \u2014 if it persists, try a different model with /model.`;
210
+ }
64
211
 
65
212
  // src/providers/anthropic.ts
66
213
  var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
@@ -292,9 +439,18 @@ function toAnthropicToolResultContent(content) {
292
439
  };
293
440
  });
294
441
  }
442
+ function remapAnthropicToolCallId(id, idMap) {
443
+ if (/^[a-zA-Z0-9_-]+$/.test(id)) return id;
444
+ const existing = idMap.get(id);
445
+ if (existing) return existing;
446
+ const mapped = id.replace(/[^a-zA-Z0-9_-]/g, "_");
447
+ idMap.set(id, mapped);
448
+ return mapped;
449
+ }
295
450
  function toAnthropicMessages(messages, cacheControl) {
296
451
  let systemText;
297
452
  const out = [];
453
+ const idMap = /* @__PURE__ */ new Map();
298
454
  for (const msg of messages) {
299
455
  if (msg.role === "system") {
300
456
  systemText = msg.content;
@@ -329,7 +485,7 @@ function toAnthropicMessages(messages, cacheControl) {
329
485
  if (part.type === "tool_call")
330
486
  return {
331
487
  type: "tool_use",
332
- id: part.id,
488
+ id: remapAnthropicToolCallId(part.id, idMap),
333
489
  name: part.name,
334
490
  input: part.args
335
491
  };
@@ -354,7 +510,7 @@ function toAnthropicMessages(messages, cacheControl) {
354
510
  role: "user",
355
511
  content: msg.content.map((result) => ({
356
512
  type: "tool_result",
357
- tool_use_id: result.toolCallId,
513
+ tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
358
514
  content: toAnthropicToolResultContent(result.content),
359
515
  is_error: result.isError
360
516
  }))
@@ -410,12 +566,19 @@ function toAnthropicMessages(messages, cacheControl) {
410
566
  }
411
567
  return { system, messages: out };
412
568
  }
413
- function toAnthropicTools(tools) {
414
- return tools.map((tool) => ({
415
- name: tool.name,
416
- description: tool.description,
417
- input_schema: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters)
418
- }));
569
+ function toAnthropicTools(tools, options) {
570
+ return tools.map((tool, index) => {
571
+ const anthropicTool = {
572
+ name: tool.name,
573
+ description: tool.description,
574
+ input_schema: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
575
+ ...options?.enableFineGrainedToolStreaming ? { eager_input_streaming: true } : {}
576
+ };
577
+ if (options?.cacheControl && index === tools.length - 1) {
578
+ anthropicTool.cache_control = options.cacheControl;
579
+ }
580
+ return anthropicTool;
581
+ });
419
582
  }
420
583
  function toAnthropicToolChoice(choice) {
421
584
  if (choice === "auto") return { type: "auto" };
@@ -428,8 +591,8 @@ function supportsAdaptiveThinking(model) {
428
591
  }
429
592
  function toAnthropicThinking(level, maxTokens, model) {
430
593
  if (supportsAdaptiveThinking(model)) {
431
- let effort = level;
432
- if (level === "max" && !model.includes("opus")) {
594
+ let effort = level === "xhigh" ? "max" : level;
595
+ if (effort === "max" && !model.includes("opus")) {
433
596
  effort = "high";
434
597
  }
435
598
  return {
@@ -438,7 +601,7 @@ function toAnthropicThinking(level, maxTokens, model) {
438
601
  outputConfig: { effort }
439
602
  };
440
603
  }
441
- const effectiveLevel = level === "max" ? "high" : level;
604
+ const effectiveLevel = level === "xhigh" ? "high" : level;
442
605
  const budgetMap = {
443
606
  low: Math.max(1024, Math.floor(maxTokens * 0.25)),
444
607
  medium: Math.max(2048, Math.floor(maxTokens * 0.5)),
@@ -571,8 +734,8 @@ function toOpenAIToolChoice(choice) {
571
734
  if (choice === "required") return "required";
572
735
  return { type: "function", function: { name: choice.name } };
573
736
  }
574
- function toOpenAIReasoningEffort(level) {
575
- return level === "max" ? "high" : level;
737
+ function toOpenAIReasoningEffort(level, _model) {
738
+ return level;
576
739
  }
577
740
  function normalizeAnthropicStopReason(reason) {
578
741
  switch (reason) {
@@ -604,6 +767,23 @@ function normalizeOpenAIStopReason(reason) {
604
767
  }
605
768
 
606
769
  // src/providers/anthropic.ts
770
+ function isJsonObject(value) {
771
+ return value != null && typeof value === "object" && !Array.isArray(value);
772
+ }
773
+ function uniqueToolsByName(tools) {
774
+ const seen = /* @__PURE__ */ new Set();
775
+ const unique = [];
776
+ for (const tool of tools) {
777
+ if (!tool.name) {
778
+ unique.push(tool);
779
+ continue;
780
+ }
781
+ if (seen.has(tool.name)) continue;
782
+ seen.add(tool.name);
783
+ unique.push(tool);
784
+ }
785
+ return unique;
786
+ }
607
787
  function createClient(options) {
608
788
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
609
789
  return new import_sdk.default({
@@ -616,7 +796,10 @@ function createClient(options) {
616
796
  maxRetries: 2,
617
797
  ...isOAuth ? {
618
798
  defaultHeaders: {
619
- "user-agent": "claude-cli/2.1.75",
799
+ // Anthropic's OAuth edge validates the claude-cli version. Callers
800
+ // (ezcoder) resolve the live version at runtime; the literal here
801
+ // is the offline fallback for direct gg-ai consumers.
802
+ "user-agent": options.userAgent ?? "claude-cli/2.1.75 (external, cli)",
620
803
  "x-app": "cli"
621
804
  }
622
805
  } : {}
@@ -630,6 +813,7 @@ async function* runStream(options) {
630
813
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
631
814
  const useStreaming = options.streaming !== false;
632
815
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
816
+ const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
633
817
  const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
634
818
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
635
819
  const system = isOAuth ? [
@@ -660,13 +844,28 @@ async function* runStream(options) {
660
844
  ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
661
845
  ...options.topP != null ? { top_p: options.topP } : {},
662
846
  ...options.stop ? { stop_sequences: options.stop } : {},
663
- ...options.tools?.length || options.serverTools?.length || options.webSearch ? {
664
- tools: [
665
- ...options.tools?.length ? toAnthropicTools(options.tools) : [],
666
- ...options.serverTools ?? [],
667
- ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
668
- ]
669
- } : {},
847
+ ...options.tools?.length || options.serverTools?.length || options.webSearch ? (() => {
848
+ const reservedServerNames = /* @__PURE__ */ new Set();
849
+ if (options.webSearch) reservedServerNames.add("web_search");
850
+ for (const t of options.serverTools ?? []) {
851
+ const name = t.name;
852
+ if (name) reservedServerNames.add(name);
853
+ }
854
+ const clientTools = options.tools?.length ? toAnthropicTools(
855
+ uniqueToolsByName(options.tools.filter((t) => !reservedServerNames.has(t.name))),
856
+ {
857
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
858
+ ...supportsFirstPartyToolExtras ? { enableFineGrainedToolStreaming: true } : {}
859
+ }
860
+ ) : [];
861
+ return {
862
+ tools: uniqueToolsByName([
863
+ ...clientTools,
864
+ ...options.serverTools ?? [],
865
+ ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
866
+ ])
867
+ };
868
+ })() : {},
670
869
  ...options.toolChoice && options.tools?.length ? { tool_choice: toAnthropicToolChoice(options.toolChoice) } : {},
671
870
  ...(() => {
672
871
  const contextEdits = [
@@ -743,6 +942,7 @@ async function* runStream(options) {
743
942
  if (block.type === "tool_use") {
744
943
  accum.toolId = block.id;
745
944
  accum.toolName = block.name;
945
+ accum.input = block.input;
746
946
  } else if (block.type === "server_tool_use") {
747
947
  accum.toolId = block.id;
748
948
  accum.toolName = block.name;
@@ -751,7 +951,11 @@ async function* runStream(options) {
751
951
  accum.raw = block;
752
952
  }
753
953
  blocks.set(idx, accum);
754
- yield keepalive;
954
+ if (block.type === "thinking") {
955
+ yield { type: "thinking_delta", text: "" };
956
+ } else {
957
+ yield keepalive;
958
+ }
755
959
  break;
756
960
  }
757
961
  case "content_block_delta": {
@@ -794,10 +998,13 @@ async function* runStream(options) {
794
998
  });
795
999
  yield keepalive;
796
1000
  } else if (accum.type === "tool_use") {
797
- let args = {};
798
- try {
799
- args = JSON.parse(accum.argsJson);
800
- } catch {
1001
+ let args = isJsonObject(accum.input) ? accum.input : {};
1002
+ if (accum.argsJson) {
1003
+ try {
1004
+ const parsed = JSON.parse(accum.argsJson);
1005
+ args = isJsonObject(parsed) ? parsed : {};
1006
+ } catch {
1007
+ }
801
1008
  }
802
1009
  const tc = {
803
1010
  type: "tool_call",
@@ -995,8 +1202,15 @@ function messageToResponse(message) {
995
1202
  }
996
1203
  function toError(err) {
997
1204
  if (err instanceof import_sdk.default.APIError) {
998
- return new ProviderError("anthropic", err.message, {
1205
+ const errorBody = err.error;
1206
+ const nestedError = errorBody?.error;
1207
+ const requestId = err.requestID ?? err.request_id ?? (typeof errorBody?.request_id === "string" ? errorBody.request_id : void 0) ?? (typeof nestedError?.request_id === "string" ? nestedError.request_id : void 0) ?? void 0;
1208
+ const bodyMessage = typeof nestedError?.message === "string" ? nestedError.message : typeof errorBody?.message === "string" ? errorBody.message : void 0;
1209
+ const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
1210
+ const message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? err.message;
1211
+ return new ProviderError("anthropic", message, {
999
1212
  statusCode: err.status,
1213
+ ...requestId ? { requestId } : {},
1000
1214
  cause: err
1001
1215
  });
1002
1216
  }
@@ -1008,6 +1222,38 @@ function toError(err) {
1008
1222
 
1009
1223
  // src/providers/openai.ts
1010
1224
  var import_openai = __toESM(require("openai"), 1);
1225
+
1226
+ // src/providers/prompt-cache-key.ts
1227
+ var MAX_PROMPT_CACHE_KEY_LENGTH = 64;
1228
+ function normalizePromptCacheKey(key) {
1229
+ if (key.length <= MAX_PROMPT_CACHE_KEY_LENGTH) return key;
1230
+ const hash = fnv1aHash(key);
1231
+ const prefixLength = MAX_PROMPT_CACHE_KEY_LENGTH - hash.length - 1;
1232
+ return `${key.slice(0, prefixLength)}:${hash}`;
1233
+ }
1234
+ function fnv1aHash(value) {
1235
+ let hash = 2166136261;
1236
+ for (let index = 0; index < value.length; index++) {
1237
+ hash ^= value.charCodeAt(index);
1238
+ hash = Math.imul(hash, 16777619);
1239
+ }
1240
+ return (hash >>> 0).toString(16).padStart(8, "0");
1241
+ }
1242
+
1243
+ // src/providers/openai.ts
1244
+ function isJsonObject2(value) {
1245
+ return value != null && typeof value === "object" && !Array.isArray(value);
1246
+ }
1247
+ function parseToolArguments(argsJson) {
1248
+ if (!argsJson) return {};
1249
+ try {
1250
+ const parsed = JSON.parse(argsJson);
1251
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1252
+ return isJsonObject2(unwrapped) ? unwrapped : {};
1253
+ } catch {
1254
+ return {};
1255
+ }
1256
+ }
1011
1257
  function createClient2(options) {
1012
1258
  return new import_openai.default({
1013
1259
  apiKey: options.apiKey,
@@ -1039,19 +1285,22 @@ async function* runStream2(options) {
1039
1285
  ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
1040
1286
  ...options.topP != null ? { top_p: options.topP } : {},
1041
1287
  ...options.stop ? { stop: options.stop } : {},
1042
- ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
1288
+ ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
1043
1289
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
1044
1290
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
1045
1291
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
1046
1292
  };
1047
1293
  if (options.provider === "openai" || options.provider === "moonshot") {
1048
1294
  const paramsAny = params;
1049
- paramsAny.prompt_cache_key = "ezcoder";
1295
+ paramsAny.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1050
1296
  const retention = options.cacheRetention ?? "short";
1051
1297
  if (retention === "long") {
1052
1298
  paramsAny.prompt_cache_retention = "24h";
1053
1299
  }
1054
1300
  }
1301
+ if (options.provider === "openai" && options.serviceTier) {
1302
+ params.service_tier = options.serviceTier;
1303
+ }
1055
1304
  if (usesThinkingParam) {
1056
1305
  if (options.thinking) {
1057
1306
  params.thinking = { type: "enabled" };
@@ -1109,6 +1358,9 @@ async function* runStream2(options) {
1109
1358
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1110
1359
  cacheRead = usageAny.cached_tokens;
1111
1360
  }
1361
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1362
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1363
+ }
1112
1364
  inputTokens = chunk.usage.prompt_tokens - cacheRead;
1113
1365
  }
1114
1366
  if (!choice) continue;
@@ -1159,11 +1411,7 @@ async function* runStream2(options) {
1159
1411
  contentParts.push({ type: "text", text: textAccum });
1160
1412
  }
1161
1413
  for (const [, tc] of toolCallAccum) {
1162
- let args = {};
1163
- try {
1164
- args = JSON.parse(tc.argsJson);
1165
- } catch {
1166
- }
1414
+ const args = parseToolArguments(tc.argsJson);
1167
1415
  const toolCall = {
1168
1416
  type: "tool_call",
1169
1417
  id: tc.id,
@@ -1216,11 +1464,7 @@ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
1216
1464
  argsJson
1217
1465
  };
1218
1466
  }
1219
- let args = {};
1220
- try {
1221
- args = JSON.parse(argsJson);
1222
- } catch {
1223
- }
1467
+ const args = parseToolArguments(argsJson);
1224
1468
  yield {
1225
1469
  type: "toolcall_done",
1226
1470
  id: tc.id,
@@ -1248,11 +1492,7 @@ function completionToResponse(completion) {
1248
1492
  const toolCalls = msg.tool_calls;
1249
1493
  if (toolCalls) {
1250
1494
  for (const tc of toolCalls) {
1251
- let args = {};
1252
- try {
1253
- args = JSON.parse(tc.function?.arguments ?? "{}");
1254
- } catch {
1255
- }
1495
+ const args = parseToolArguments(tc.function?.arguments ?? "");
1256
1496
  const toolCall = {
1257
1497
  type: "tool_call",
1258
1498
  id: tc.id,
@@ -1274,6 +1514,9 @@ function completionToResponse(completion) {
1274
1514
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1275
1515
  cacheRead = usageAny.cached_tokens;
1276
1516
  }
1517
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1518
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1519
+ }
1277
1520
  inputTokens = completion.usage.prompt_tokens - cacheRead;
1278
1521
  }
1279
1522
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
@@ -1288,19 +1531,19 @@ function completionToResponse(completion) {
1288
1531
  }
1289
1532
  function toError2(err, provider = "openai") {
1290
1533
  if (err instanceof import_openai.default.APIError) {
1291
- let msg = err.message;
1292
1534
  const body = err.error;
1293
- if (body) {
1294
- const modelName = body.model || "";
1295
- const _code = body.code || "";
1296
- const message = body.message || "";
1297
- if (modelName === "codex-mini-latest" || message.includes("codex-mini-latest")) {
1298
- msg = `codex-mini-latest requires an OpenAI Pro or Max subscription. You currently have access to GPT-5.4 and GPT-5.4 Mini with your account.`;
1299
- }
1300
- msg += ` | body: ${JSON.stringify(body)}`;
1301
- }
1302
- return new ProviderError(provider, msg, {
1535
+ const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
1536
+ const modelName = typeof body?.model === "string" ? body.model : "";
1537
+ const cleanMessage = bodyMessage ?? err.message;
1538
+ let hint;
1539
+ if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
1540
+ 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.";
1541
+ }
1542
+ const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1543
+ return new ProviderError(provider, cleanMessage, {
1303
1544
  statusCode: err.status,
1545
+ ...requestId ? { requestId } : {},
1546
+ ...hint ? { hint } : {},
1304
1547
  cause: err
1305
1548
  });
1306
1549
  }
@@ -1312,7 +1555,34 @@ function toError2(err, provider = "openai") {
1312
1555
 
1313
1556
  // src/providers/openai-codex.ts
1314
1557
  var import_node_os = __toESM(require("os"), 1);
1558
+
1559
+ // src/utils/diag.ts
1560
+ var _diagFn = null;
1561
+ function setProviderDiagnostic(fn) {
1562
+ _diagFn = fn;
1563
+ }
1564
+ function providerDiag(phase, data) {
1565
+ _diagFn?.(phase, data);
1566
+ }
1567
+
1568
+ // src/providers/openai-codex.ts
1315
1569
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1570
+ function isJsonObject3(value) {
1571
+ return value != null && typeof value === "object" && !Array.isArray(value);
1572
+ }
1573
+ function parseToolArguments2(argsJson) {
1574
+ if (!argsJson) return {};
1575
+ try {
1576
+ const parsed = JSON.parse(argsJson);
1577
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1578
+ return isJsonObject3(unwrapped) ? unwrapped : {};
1579
+ } catch {
1580
+ return {};
1581
+ }
1582
+ }
1583
+ function outputTextKey(itemId, contentIndex) {
1584
+ return `${itemId ?? ""}:${contentIndex ?? 0}`;
1585
+ }
1316
1586
  function streamOpenAICodex(options) {
1317
1587
  return new StreamResult(runStream3(options));
1318
1588
  }
@@ -1334,15 +1604,17 @@ async function* runStream3(options) {
1334
1604
  if (options.tools?.length) {
1335
1605
  body.tools = toCodexTools(options.tools);
1336
1606
  }
1607
+ body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1608
+ if (options.cacheRetention === "long") {
1609
+ body.prompt_cache_retention = "24h";
1610
+ }
1337
1611
  if (options.temperature != null && !options.thinking) {
1338
1612
  body.temperature = options.temperature;
1339
1613
  }
1340
- if (options.thinking) {
1341
- body.reasoning = {
1342
- effort: options.thinking,
1343
- summary: "auto"
1344
- };
1345
- }
1614
+ body.reasoning = {
1615
+ effort: options.thinking ?? "none",
1616
+ summary: "auto"
1617
+ };
1346
1618
  const headers = {
1347
1619
  "Content-Type": "application/json",
1348
1620
  Accept: "text/event-stream",
@@ -1354,6 +1626,11 @@ async function* runStream3(options) {
1354
1626
  if (options.accountId) {
1355
1627
  headers["chatgpt-account-id"] = options.accountId;
1356
1628
  }
1629
+ const cacheScopeId = body.prompt_cache_key;
1630
+ if (cacheScopeId) {
1631
+ headers["session_id"] = cacheScopeId;
1632
+ headers["x-client-request-id"] = cacheScopeId;
1633
+ }
1357
1634
  const response = await fetch(url, {
1358
1635
  method: "POST",
1359
1636
  headers,
@@ -1362,19 +1639,23 @@ async function* runStream3(options) {
1362
1639
  });
1363
1640
  if (!response.ok) {
1364
1641
  const text = await response.text().catch(() => "");
1365
- let message = `Codex API error (${response.status}): ${text}`;
1642
+ const parsed = parseCodexErrorBody(text);
1643
+ const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1644
+ const requestId = parsed.requestId ?? response.headers.get("x-request-id") ?? response.headers.get("openai-request-id") ?? response.headers.get("x-oai-request-id") ?? void 0;
1645
+ let hint;
1366
1646
  if (response.status === 400 && text.includes("not supported")) {
1367
- message += `
1368
-
1369
- Hint: Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription. The "codex-spark" variants require ChatGPT Pro. Ensure your account has an active subscription at https://chatgpt.com/settings`;
1370
- }
1371
- if (response.status === 404 && text.includes("does not exist")) {
1372
- message += `
1373
-
1374
- Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GPT-5.4 and GPT-5.4 Mini work with any active ChatGPT plan.`;
1647
+ if (options.model === "gpt-5.5-pro") {
1648
+ hint = "Use gpt-5.5 instead. OpenAI's Codex model catalog does not list gpt-5.5-pro.";
1649
+ } else {
1650
+ hint = "This model is not available through Codex for the authenticated account. Run /model and choose a model listed for OpenAI Codex, or check your Codex model picker/usage limits.";
1651
+ }
1652
+ } else if (response.status === 404 && text.includes("does not exist")) {
1653
+ hint = "This model is not in the current OpenAI Codex catalog for this account. Try gpt-5.5, gpt-5.4, gpt-5.4-mini, or gpt-5.3-codex.";
1375
1654
  }
1376
1655
  throw new ProviderError("openai", message, {
1377
- statusCode: response.status
1656
+ statusCode: response.status,
1657
+ ...requestId ? { requestId } : {},
1658
+ ...hint ? { hint } : {}
1378
1659
  });
1379
1660
  }
1380
1661
  if (!response.body) {
@@ -1383,27 +1664,84 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1383
1664
  const contentParts = [];
1384
1665
  let textAccum = "";
1385
1666
  const toolCalls = /* @__PURE__ */ new Map();
1667
+ const outputItemTypes = /* @__PURE__ */ new Map();
1668
+ const outputTextByPart = /* @__PURE__ */ new Map();
1386
1669
  let inputTokens = 0;
1387
1670
  let outputTokens = 0;
1671
+ let cacheRead = 0;
1672
+ const diagStart = Date.now();
1673
+ const diagSeen = /* @__PURE__ */ new Set();
1388
1674
  for await (const event of parseSSE(response.body)) {
1389
1675
  const type = event.type;
1390
1676
  if (!type) continue;
1677
+ if (!diagSeen.has(type)) {
1678
+ diagSeen.add(type);
1679
+ providerDiag("codex_event_first", { type, sinceStartMs: Date.now() - diagStart });
1680
+ }
1391
1681
  if (type === "error") {
1392
- const msg = event.message || JSON.stringify(event);
1393
- throw new ProviderError("openai", `Codex error: ${msg}`);
1682
+ const nested = event.error ?? void 0;
1683
+ const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1684
+ const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1685
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1686
+ throw new ProviderError("openai", message, {
1687
+ ...requestId != null ? { requestId } : {},
1688
+ ...code === "server_error" ? { statusCode: 500 } : {}
1689
+ });
1394
1690
  }
1395
1691
  if (type === "response.failed") {
1396
- const msg = event.error?.message || "Codex response failed";
1397
- throw new ProviderError("openai", msg);
1692
+ const nested = event.error;
1693
+ const message = nested?.message ?? "Codex response failed.";
1694
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1695
+ throw new ProviderError("openai", message, {
1696
+ ...requestId != null ? { requestId } : {}
1697
+ });
1398
1698
  }
1399
1699
  if (type === "response.output_text.delta") {
1400
1700
  const delta = event.delta;
1401
- textAccum += delta;
1402
- yield { type: "text_delta", text: delta };
1701
+ const itemId = event.item_id;
1702
+ const contentIndex = event.content_index;
1703
+ const key = outputTextKey(itemId, contentIndex);
1704
+ outputTextByPart.set(key, `${outputTextByPart.get(key) ?? ""}${delta}`);
1705
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1706
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1707
+ } else {
1708
+ textAccum += delta;
1709
+ yield { type: "text_delta", text: delta };
1710
+ }
1711
+ }
1712
+ if (type === "response.output_text.done") {
1713
+ const fullText = event.text;
1714
+ if (fullText) {
1715
+ const itemId = event.item_id;
1716
+ const contentIndex = event.content_index;
1717
+ const key = outputTextKey(itemId, contentIndex);
1718
+ const streamedText = outputTextByPart.get(key) ?? "";
1719
+ const missingText = streamedText ? fullText.slice(streamedText.length) : fullText;
1720
+ outputTextByPart.set(key, fullText);
1721
+ if (missingText && fullText.startsWith(streamedText)) {
1722
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1723
+ if (options.thinking) yield { type: "thinking_delta", text: missingText };
1724
+ } else {
1725
+ textAccum += missingText;
1726
+ yield { type: "text_delta", text: missingText };
1727
+ }
1728
+ }
1729
+ }
1403
1730
  }
1404
- if (type === "response.reasoning_summary_text.delta") {
1731
+ if (type === "response.reasoning_summary_text.delta" || type === "response.reasoning_summary.delta" || type === "response.reasoning_text.delta" || type === "response.reasoning.delta") {
1405
1732
  const delta = event.delta;
1406
- yield { type: "thinking_delta", text: delta };
1733
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1734
+ }
1735
+ if (type === "response.output_item.added") {
1736
+ const item = event.item;
1737
+ const itemId = item?.id;
1738
+ const itemType = item?.type;
1739
+ if (itemId && itemType) {
1740
+ outputItemTypes.set(itemId, itemType);
1741
+ }
1742
+ if (itemType === "reasoning" && options.thinking) {
1743
+ yield { type: "thinking_delta", text: "" };
1744
+ }
1407
1745
  }
1408
1746
  if (type === "response.output_item.added") {
1409
1747
  const item = event.item;
@@ -1449,11 +1787,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1449
1787
  const id = `${callId}|${itemId}`;
1450
1788
  const tc = toolCalls.get(id);
1451
1789
  if (tc) {
1452
- let args = {};
1453
- try {
1454
- args = JSON.parse(tc.argsJson);
1455
- } catch {
1456
- }
1790
+ const args = parseToolArguments2(tc.argsJson);
1457
1791
  yield {
1458
1792
  type: "toolcall_done",
1459
1793
  id: tc.id,
@@ -1467,7 +1801,8 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1467
1801
  const resp = event.response;
1468
1802
  const usage = resp?.usage;
1469
1803
  if (usage) {
1470
- inputTokens = usage.input_tokens ?? 0;
1804
+ cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
1805
+ inputTokens = (usage.input_tokens ?? 0) - cacheRead;
1471
1806
  outputTokens = usage.output_tokens ?? 0;
1472
1807
  }
1473
1808
  }
@@ -1476,11 +1811,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1476
1811
  contentParts.push({ type: "text", text: textAccum });
1477
1812
  }
1478
1813
  for (const [, tc] of toolCalls) {
1479
- let args = {};
1480
- try {
1481
- args = JSON.parse(tc.argsJson);
1482
- } catch {
1483
- }
1814
+ const args = parseToolArguments2(tc.argsJson);
1484
1815
  const toolCall = {
1485
1816
  type: "tool_call",
1486
1817
  id: tc.id,
@@ -1497,7 +1828,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1497
1828
  content: contentParts.length > 0 ? contentParts : textAccum || ""
1498
1829
  },
1499
1830
  stopReason,
1500
- usage: { inputTokens, outputTokens }
1831
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1501
1832
  };
1502
1833
  yield { type: "done", stopReason };
1503
1834
  return streamResponse;
@@ -1639,6 +1970,24 @@ function toCodexTools(tools) {
1639
1970
  strict: null
1640
1971
  }));
1641
1972
  }
1973
+ function extractCodexRequestId(message) {
1974
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1975
+ return match?.[1];
1976
+ }
1977
+ function parseCodexErrorBody(text) {
1978
+ if (!text) return {};
1979
+ try {
1980
+ const parsed = JSON.parse(text);
1981
+ const error = parsed.error;
1982
+ const detail = parsed.detail;
1983
+ const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
1984
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
1985
+ return { ...message ? { message } : {}, ...requestId ? { requestId } : {} };
1986
+ } catch {
1987
+ const trimmed = text.trim().slice(0, 240);
1988
+ return trimmed ? { message: trimmed } : {};
1989
+ }
1990
+ }
1642
1991
 
1643
1992
  // src/provider-registry.ts
1644
1993
  var ProviderRegistryImpl = class {
@@ -1899,12 +2248,15 @@ function registerPalsuProvider(config) {
1899
2248
  EventStream,
1900
2249
  ProviderError,
1901
2250
  StreamResult,
2251
+ formatError,
2252
+ formatErrorForDisplay,
1902
2253
  palsuAssistantMessage,
1903
2254
  palsuText,
1904
2255
  palsuThinking,
1905
2256
  palsuToolCall,
1906
2257
  providerRegistry,
1907
2258
  registerPalsuProvider,
2259
+ setProviderDiagnostic,
1908
2260
  stream
1909
2261
  });
1910
2262
  //# sourceMappingURL=index.cjs.map