@prestyj/ai 4.3.161 → 4.3.200

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,9 @@ 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
+ }
607
773
  function createClient(options) {
608
774
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
609
775
  return new import_sdk.default({
@@ -616,7 +782,10 @@ function createClient(options) {
616
782
  maxRetries: 2,
617
783
  ...isOAuth ? {
618
784
  defaultHeaders: {
619
- "user-agent": "claude-cli/2.1.75",
785
+ // Anthropic's OAuth edge validates the claude-cli version. Callers
786
+ // (ezcoder) resolve the live version at runtime; the literal here
787
+ // is the offline fallback for direct gg-ai consumers.
788
+ "user-agent": options.userAgent ?? "claude-cli/2.1.75 (external, cli)",
620
789
  "x-app": "cli"
621
790
  }
622
791
  } : {}
@@ -630,6 +799,7 @@ async function* runStream(options) {
630
799
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
631
800
  const useStreaming = options.streaming !== false;
632
801
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
802
+ const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
633
803
  const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
634
804
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
635
805
  const system = isOAuth ? [
@@ -660,13 +830,28 @@ async function* runStream(options) {
660
830
  ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
661
831
  ...options.topP != null ? { top_p: options.topP } : {},
662
832
  ...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
- } : {},
833
+ ...options.tools?.length || options.serverTools?.length || options.webSearch ? (() => {
834
+ const reservedServerNames = /* @__PURE__ */ new Set();
835
+ if (options.webSearch) reservedServerNames.add("web_search");
836
+ for (const t of options.serverTools ?? []) {
837
+ const name = t.name;
838
+ if (name) reservedServerNames.add(name);
839
+ }
840
+ const clientTools = options.tools?.length ? toAnthropicTools(
841
+ options.tools.filter((t) => !reservedServerNames.has(t.name)),
842
+ {
843
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
844
+ ...supportsFirstPartyToolExtras ? { enableFineGrainedToolStreaming: true } : {}
845
+ }
846
+ ) : [];
847
+ return {
848
+ tools: [
849
+ ...clientTools,
850
+ ...options.serverTools ?? [],
851
+ ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
852
+ ]
853
+ };
854
+ })() : {},
670
855
  ...options.toolChoice && options.tools?.length ? { tool_choice: toAnthropicToolChoice(options.toolChoice) } : {},
671
856
  ...(() => {
672
857
  const contextEdits = [
@@ -743,6 +928,7 @@ async function* runStream(options) {
743
928
  if (block.type === "tool_use") {
744
929
  accum.toolId = block.id;
745
930
  accum.toolName = block.name;
931
+ accum.input = block.input;
746
932
  } else if (block.type === "server_tool_use") {
747
933
  accum.toolId = block.id;
748
934
  accum.toolName = block.name;
@@ -751,7 +937,11 @@ async function* runStream(options) {
751
937
  accum.raw = block;
752
938
  }
753
939
  blocks.set(idx, accum);
754
- yield keepalive;
940
+ if (block.type === "thinking") {
941
+ yield { type: "thinking_delta", text: "" };
942
+ } else {
943
+ yield keepalive;
944
+ }
755
945
  break;
756
946
  }
757
947
  case "content_block_delta": {
@@ -794,10 +984,13 @@ async function* runStream(options) {
794
984
  });
795
985
  yield keepalive;
796
986
  } else if (accum.type === "tool_use") {
797
- let args = {};
798
- try {
799
- args = JSON.parse(accum.argsJson);
800
- } catch {
987
+ let args = isJsonObject(accum.input) ? accum.input : {};
988
+ if (accum.argsJson) {
989
+ try {
990
+ const parsed = JSON.parse(accum.argsJson);
991
+ args = isJsonObject(parsed) ? parsed : {};
992
+ } catch {
993
+ }
801
994
  }
802
995
  const tc = {
803
996
  type: "tool_call",
@@ -995,8 +1188,15 @@ function messageToResponse(message) {
995
1188
  }
996
1189
  function toError(err) {
997
1190
  if (err instanceof import_sdk.default.APIError) {
998
- return new ProviderError("anthropic", err.message, {
1191
+ const errorBody = err.error;
1192
+ const nestedError = errorBody?.error;
1193
+ 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;
1194
+ const bodyMessage = typeof nestedError?.message === "string" ? nestedError.message : typeof errorBody?.message === "string" ? errorBody.message : void 0;
1195
+ const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
1196
+ const message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? err.message;
1197
+ return new ProviderError("anthropic", message, {
999
1198
  statusCode: err.status,
1199
+ ...requestId ? { requestId } : {},
1000
1200
  cause: err
1001
1201
  });
1002
1202
  }
@@ -1008,6 +1208,38 @@ function toError(err) {
1008
1208
 
1009
1209
  // src/providers/openai.ts
1010
1210
  var import_openai = __toESM(require("openai"), 1);
1211
+
1212
+ // src/providers/prompt-cache-key.ts
1213
+ var MAX_PROMPT_CACHE_KEY_LENGTH = 64;
1214
+ function normalizePromptCacheKey(key) {
1215
+ if (key.length <= MAX_PROMPT_CACHE_KEY_LENGTH) return key;
1216
+ const hash = fnv1aHash(key);
1217
+ const prefixLength = MAX_PROMPT_CACHE_KEY_LENGTH - hash.length - 1;
1218
+ return `${key.slice(0, prefixLength)}:${hash}`;
1219
+ }
1220
+ function fnv1aHash(value) {
1221
+ let hash = 2166136261;
1222
+ for (let index = 0; index < value.length; index++) {
1223
+ hash ^= value.charCodeAt(index);
1224
+ hash = Math.imul(hash, 16777619);
1225
+ }
1226
+ return (hash >>> 0).toString(16).padStart(8, "0");
1227
+ }
1228
+
1229
+ // src/providers/openai.ts
1230
+ function isJsonObject2(value) {
1231
+ return value != null && typeof value === "object" && !Array.isArray(value);
1232
+ }
1233
+ function parseToolArguments(argsJson) {
1234
+ if (!argsJson) return {};
1235
+ try {
1236
+ const parsed = JSON.parse(argsJson);
1237
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1238
+ return isJsonObject2(unwrapped) ? unwrapped : {};
1239
+ } catch {
1240
+ return {};
1241
+ }
1242
+ }
1011
1243
  function createClient2(options) {
1012
1244
  return new import_openai.default({
1013
1245
  apiKey: options.apiKey,
@@ -1039,19 +1271,22 @@ async function* runStream2(options) {
1039
1271
  ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
1040
1272
  ...options.topP != null ? { top_p: options.topP } : {},
1041
1273
  ...options.stop ? { stop: options.stop } : {},
1042
- ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
1274
+ ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
1043
1275
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
1044
1276
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
1045
1277
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
1046
1278
  };
1047
1279
  if (options.provider === "openai" || options.provider === "moonshot") {
1048
1280
  const paramsAny = params;
1049
- paramsAny.prompt_cache_key = "ezcoder";
1281
+ paramsAny.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1050
1282
  const retention = options.cacheRetention ?? "short";
1051
1283
  if (retention === "long") {
1052
1284
  paramsAny.prompt_cache_retention = "24h";
1053
1285
  }
1054
1286
  }
1287
+ if (options.provider === "openai" && options.serviceTier) {
1288
+ params.service_tier = options.serviceTier;
1289
+ }
1055
1290
  if (usesThinkingParam) {
1056
1291
  if (options.thinking) {
1057
1292
  params.thinking = { type: "enabled" };
@@ -1109,6 +1344,9 @@ async function* runStream2(options) {
1109
1344
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1110
1345
  cacheRead = usageAny.cached_tokens;
1111
1346
  }
1347
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1348
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1349
+ }
1112
1350
  inputTokens = chunk.usage.prompt_tokens - cacheRead;
1113
1351
  }
1114
1352
  if (!choice) continue;
@@ -1159,11 +1397,7 @@ async function* runStream2(options) {
1159
1397
  contentParts.push({ type: "text", text: textAccum });
1160
1398
  }
1161
1399
  for (const [, tc] of toolCallAccum) {
1162
- let args = {};
1163
- try {
1164
- args = JSON.parse(tc.argsJson);
1165
- } catch {
1166
- }
1400
+ const args = parseToolArguments(tc.argsJson);
1167
1401
  const toolCall = {
1168
1402
  type: "tool_call",
1169
1403
  id: tc.id,
@@ -1216,11 +1450,7 @@ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
1216
1450
  argsJson
1217
1451
  };
1218
1452
  }
1219
- let args = {};
1220
- try {
1221
- args = JSON.parse(argsJson);
1222
- } catch {
1223
- }
1453
+ const args = parseToolArguments(argsJson);
1224
1454
  yield {
1225
1455
  type: "toolcall_done",
1226
1456
  id: tc.id,
@@ -1248,11 +1478,7 @@ function completionToResponse(completion) {
1248
1478
  const toolCalls = msg.tool_calls;
1249
1479
  if (toolCalls) {
1250
1480
  for (const tc of toolCalls) {
1251
- let args = {};
1252
- try {
1253
- args = JSON.parse(tc.function?.arguments ?? "{}");
1254
- } catch {
1255
- }
1481
+ const args = parseToolArguments(tc.function?.arguments ?? "");
1256
1482
  const toolCall = {
1257
1483
  type: "tool_call",
1258
1484
  id: tc.id,
@@ -1274,6 +1500,9 @@ function completionToResponse(completion) {
1274
1500
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1275
1501
  cacheRead = usageAny.cached_tokens;
1276
1502
  }
1503
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1504
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1505
+ }
1277
1506
  inputTokens = completion.usage.prompt_tokens - cacheRead;
1278
1507
  }
1279
1508
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
@@ -1288,19 +1517,19 @@ function completionToResponse(completion) {
1288
1517
  }
1289
1518
  function toError2(err, provider = "openai") {
1290
1519
  if (err instanceof import_openai.default.APIError) {
1291
- let msg = err.message;
1292
1520
  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, {
1521
+ const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
1522
+ const modelName = typeof body?.model === "string" ? body.model : "";
1523
+ const cleanMessage = bodyMessage ?? err.message;
1524
+ let hint;
1525
+ if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
1526
+ 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.";
1527
+ }
1528
+ const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1529
+ return new ProviderError(provider, cleanMessage, {
1303
1530
  statusCode: err.status,
1531
+ ...requestId ? { requestId } : {},
1532
+ ...hint ? { hint } : {},
1304
1533
  cause: err
1305
1534
  });
1306
1535
  }
@@ -1312,7 +1541,34 @@ function toError2(err, provider = "openai") {
1312
1541
 
1313
1542
  // src/providers/openai-codex.ts
1314
1543
  var import_node_os = __toESM(require("os"), 1);
1544
+
1545
+ // src/utils/diag.ts
1546
+ var _diagFn = null;
1547
+ function setProviderDiagnostic(fn) {
1548
+ _diagFn = fn;
1549
+ }
1550
+ function providerDiag(phase, data) {
1551
+ _diagFn?.(phase, data);
1552
+ }
1553
+
1554
+ // src/providers/openai-codex.ts
1315
1555
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1556
+ function isJsonObject3(value) {
1557
+ return value != null && typeof value === "object" && !Array.isArray(value);
1558
+ }
1559
+ function parseToolArguments2(argsJson) {
1560
+ if (!argsJson) return {};
1561
+ try {
1562
+ const parsed = JSON.parse(argsJson);
1563
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1564
+ return isJsonObject3(unwrapped) ? unwrapped : {};
1565
+ } catch {
1566
+ return {};
1567
+ }
1568
+ }
1569
+ function outputTextKey(itemId, contentIndex) {
1570
+ return `${itemId ?? ""}:${contentIndex ?? 0}`;
1571
+ }
1316
1572
  function streamOpenAICodex(options) {
1317
1573
  return new StreamResult(runStream3(options));
1318
1574
  }
@@ -1334,15 +1590,17 @@ async function* runStream3(options) {
1334
1590
  if (options.tools?.length) {
1335
1591
  body.tools = toCodexTools(options.tools);
1336
1592
  }
1593
+ body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1594
+ if (options.cacheRetention === "long") {
1595
+ body.prompt_cache_retention = "24h";
1596
+ }
1337
1597
  if (options.temperature != null && !options.thinking) {
1338
1598
  body.temperature = options.temperature;
1339
1599
  }
1340
- if (options.thinking) {
1341
- body.reasoning = {
1342
- effort: options.thinking,
1343
- summary: "auto"
1344
- };
1345
- }
1600
+ body.reasoning = {
1601
+ effort: options.thinking ?? "none",
1602
+ summary: "auto"
1603
+ };
1346
1604
  const headers = {
1347
1605
  "Content-Type": "application/json",
1348
1606
  Accept: "text/event-stream",
@@ -1354,6 +1612,11 @@ async function* runStream3(options) {
1354
1612
  if (options.accountId) {
1355
1613
  headers["chatgpt-account-id"] = options.accountId;
1356
1614
  }
1615
+ const cacheScopeId = body.prompt_cache_key;
1616
+ if (cacheScopeId) {
1617
+ headers["session_id"] = cacheScopeId;
1618
+ headers["x-client-request-id"] = cacheScopeId;
1619
+ }
1357
1620
  const response = await fetch(url, {
1358
1621
  method: "POST",
1359
1622
  headers,
@@ -1362,19 +1625,23 @@ async function* runStream3(options) {
1362
1625
  });
1363
1626
  if (!response.ok) {
1364
1627
  const text = await response.text().catch(() => "");
1365
- let message = `Codex API error (${response.status}): ${text}`;
1628
+ const parsed = parseCodexErrorBody(text);
1629
+ const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1630
+ 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;
1631
+ let hint;
1366
1632
  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.`;
1633
+ if (options.model === "gpt-5.5-pro") {
1634
+ hint = "Use gpt-5.5 instead. OpenAI's Codex model catalog does not list gpt-5.5-pro.";
1635
+ } else {
1636
+ 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.";
1637
+ }
1638
+ } else if (response.status === 404 && text.includes("does not exist")) {
1639
+ 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
1640
  }
1376
1641
  throw new ProviderError("openai", message, {
1377
- statusCode: response.status
1642
+ statusCode: response.status,
1643
+ ...requestId ? { requestId } : {},
1644
+ ...hint ? { hint } : {}
1378
1645
  });
1379
1646
  }
1380
1647
  if (!response.body) {
@@ -1383,27 +1650,84 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1383
1650
  const contentParts = [];
1384
1651
  let textAccum = "";
1385
1652
  const toolCalls = /* @__PURE__ */ new Map();
1653
+ const outputItemTypes = /* @__PURE__ */ new Map();
1654
+ const outputTextByPart = /* @__PURE__ */ new Map();
1386
1655
  let inputTokens = 0;
1387
1656
  let outputTokens = 0;
1657
+ let cacheRead = 0;
1658
+ const diagStart = Date.now();
1659
+ const diagSeen = /* @__PURE__ */ new Set();
1388
1660
  for await (const event of parseSSE(response.body)) {
1389
1661
  const type = event.type;
1390
1662
  if (!type) continue;
1663
+ if (!diagSeen.has(type)) {
1664
+ diagSeen.add(type);
1665
+ providerDiag("codex_event_first", { type, sinceStartMs: Date.now() - diagStart });
1666
+ }
1391
1667
  if (type === "error") {
1392
- const msg = event.message || JSON.stringify(event);
1393
- throw new ProviderError("openai", `Codex error: ${msg}`);
1668
+ const nested = event.error ?? void 0;
1669
+ const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1670
+ const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1671
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1672
+ throw new ProviderError("openai", message, {
1673
+ ...requestId != null ? { requestId } : {},
1674
+ ...code === "server_error" ? { statusCode: 500 } : {}
1675
+ });
1394
1676
  }
1395
1677
  if (type === "response.failed") {
1396
- const msg = event.error?.message || "Codex response failed";
1397
- throw new ProviderError("openai", msg);
1678
+ const nested = event.error;
1679
+ const message = nested?.message ?? "Codex response failed.";
1680
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1681
+ throw new ProviderError("openai", message, {
1682
+ ...requestId != null ? { requestId } : {}
1683
+ });
1398
1684
  }
1399
1685
  if (type === "response.output_text.delta") {
1400
1686
  const delta = event.delta;
1401
- textAccum += delta;
1402
- yield { type: "text_delta", text: delta };
1687
+ const itemId = event.item_id;
1688
+ const contentIndex = event.content_index;
1689
+ const key = outputTextKey(itemId, contentIndex);
1690
+ outputTextByPart.set(key, `${outputTextByPart.get(key) ?? ""}${delta}`);
1691
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1692
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1693
+ } else {
1694
+ textAccum += delta;
1695
+ yield { type: "text_delta", text: delta };
1696
+ }
1403
1697
  }
1404
- if (type === "response.reasoning_summary_text.delta") {
1698
+ if (type === "response.output_text.done") {
1699
+ const fullText = event.text;
1700
+ if (fullText) {
1701
+ const itemId = event.item_id;
1702
+ const contentIndex = event.content_index;
1703
+ const key = outputTextKey(itemId, contentIndex);
1704
+ const streamedText = outputTextByPart.get(key) ?? "";
1705
+ const missingText = streamedText ? fullText.slice(streamedText.length) : fullText;
1706
+ outputTextByPart.set(key, fullText);
1707
+ if (missingText && fullText.startsWith(streamedText)) {
1708
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1709
+ if (options.thinking) yield { type: "thinking_delta", text: missingText };
1710
+ } else {
1711
+ textAccum += missingText;
1712
+ yield { type: "text_delta", text: missingText };
1713
+ }
1714
+ }
1715
+ }
1716
+ }
1717
+ if (type === "response.reasoning_summary_text.delta" || type === "response.reasoning_summary.delta" || type === "response.reasoning_text.delta" || type === "response.reasoning.delta") {
1405
1718
  const delta = event.delta;
1406
- yield { type: "thinking_delta", text: delta };
1719
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1720
+ }
1721
+ if (type === "response.output_item.added") {
1722
+ const item = event.item;
1723
+ const itemId = item?.id;
1724
+ const itemType = item?.type;
1725
+ if (itemId && itemType) {
1726
+ outputItemTypes.set(itemId, itemType);
1727
+ }
1728
+ if (itemType === "reasoning" && options.thinking) {
1729
+ yield { type: "thinking_delta", text: "" };
1730
+ }
1407
1731
  }
1408
1732
  if (type === "response.output_item.added") {
1409
1733
  const item = event.item;
@@ -1449,11 +1773,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1449
1773
  const id = `${callId}|${itemId}`;
1450
1774
  const tc = toolCalls.get(id);
1451
1775
  if (tc) {
1452
- let args = {};
1453
- try {
1454
- args = JSON.parse(tc.argsJson);
1455
- } catch {
1456
- }
1776
+ const args = parseToolArguments2(tc.argsJson);
1457
1777
  yield {
1458
1778
  type: "toolcall_done",
1459
1779
  id: tc.id,
@@ -1467,7 +1787,8 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1467
1787
  const resp = event.response;
1468
1788
  const usage = resp?.usage;
1469
1789
  if (usage) {
1470
- inputTokens = usage.input_tokens ?? 0;
1790
+ cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
1791
+ inputTokens = (usage.input_tokens ?? 0) - cacheRead;
1471
1792
  outputTokens = usage.output_tokens ?? 0;
1472
1793
  }
1473
1794
  }
@@ -1476,11 +1797,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1476
1797
  contentParts.push({ type: "text", text: textAccum });
1477
1798
  }
1478
1799
  for (const [, tc] of toolCalls) {
1479
- let args = {};
1480
- try {
1481
- args = JSON.parse(tc.argsJson);
1482
- } catch {
1483
- }
1800
+ const args = parseToolArguments2(tc.argsJson);
1484
1801
  const toolCall = {
1485
1802
  type: "tool_call",
1486
1803
  id: tc.id,
@@ -1497,7 +1814,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1497
1814
  content: contentParts.length > 0 ? contentParts : textAccum || ""
1498
1815
  },
1499
1816
  stopReason,
1500
- usage: { inputTokens, outputTokens }
1817
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1501
1818
  };
1502
1819
  yield { type: "done", stopReason };
1503
1820
  return streamResponse;
@@ -1639,6 +1956,24 @@ function toCodexTools(tools) {
1639
1956
  strict: null
1640
1957
  }));
1641
1958
  }
1959
+ function extractCodexRequestId(message) {
1960
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1961
+ return match?.[1];
1962
+ }
1963
+ function parseCodexErrorBody(text) {
1964
+ if (!text) return {};
1965
+ try {
1966
+ const parsed = JSON.parse(text);
1967
+ const error = parsed.error;
1968
+ const detail = parsed.detail;
1969
+ const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
1970
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
1971
+ return { ...message ? { message } : {}, ...requestId ? { requestId } : {} };
1972
+ } catch {
1973
+ const trimmed = text.trim().slice(0, 240);
1974
+ return trimmed ? { message: trimmed } : {};
1975
+ }
1976
+ }
1642
1977
 
1643
1978
  // src/provider-registry.ts
1644
1979
  var ProviderRegistryImpl = class {
@@ -1899,12 +2234,15 @@ function registerPalsuProvider(config) {
1899
2234
  EventStream,
1900
2235
  ProviderError,
1901
2236
  StreamResult,
2237
+ formatError,
2238
+ formatErrorForDisplay,
1902
2239
  palsuAssistantMessage,
1903
2240
  palsuText,
1904
2241
  palsuThinking,
1905
2242
  palsuToolCall,
1906
2243
  providerRegistry,
1907
2244
  registerPalsuProvider,
2245
+ setProviderDiagnostic,
1908
2246
  stream
1909
2247
  });
1910
2248
  //# sourceMappingURL=index.cjs.map