@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.js CHANGED
@@ -1,20 +1,164 @@
1
1
  // src/errors.ts
2
2
  var EZCoderAIError = class extends Error {
3
+ source;
4
+ requestId;
5
+ hint;
3
6
  constructor(message, options) {
4
- super(message, options);
7
+ super(message, { cause: options?.cause });
5
8
  this.name = "EZCoderAIError";
9
+ this.source = options?.source ?? "ezcoder";
10
+ this.requestId = options?.requestId;
11
+ this.hint = options?.hint;
6
12
  }
7
13
  };
8
14
  var ProviderError = class extends EZCoderAIError {
9
15
  provider;
10
16
  statusCode;
11
17
  constructor(provider, message, options) {
12
- super(`[${provider}] ${message}`, { cause: options?.cause });
18
+ super(message, {
19
+ source: "provider",
20
+ requestId: options?.requestId,
21
+ hint: options?.hint,
22
+ cause: options?.cause
23
+ });
13
24
  this.name = "ProviderError";
14
25
  this.provider = provider;
15
26
  this.statusCode = options?.statusCode;
16
27
  }
17
28
  };
29
+ var PROVIDER_DISPLAY = {
30
+ openai: "OpenAI",
31
+ anthropic: "Anthropic",
32
+ glm: "Z.AI (GLM)",
33
+ moonshot: "Moonshot",
34
+ deepseek: "DeepSeek",
35
+ openrouter: "OpenRouter",
36
+ xiaomi: "Xiaomi (MiMo)",
37
+ minimax: "MiniMax"
38
+ };
39
+ var PROVIDER_STATUS_URL = {
40
+ openai: "status.openai.com",
41
+ anthropic: "status.anthropic.com"
42
+ };
43
+ function providerDisplayName(provider) {
44
+ return PROVIDER_DISPLAY[provider] ?? provider;
45
+ }
46
+ function formatError(err) {
47
+ if (err instanceof ProviderError) {
48
+ const name = providerDisplayName(err.provider);
49
+ const cleanMessage = cleanProviderMessage(err.message);
50
+ return {
51
+ headline: `${name} returned an error.`,
52
+ source: "provider",
53
+ message: cleanMessage,
54
+ provider: err.provider,
55
+ statusCode: err.statusCode,
56
+ requestId: err.requestId,
57
+ guidance: err.hint ?? providerGuidance(err.provider, cleanMessage, err.statusCode)
58
+ };
59
+ }
60
+ if (err instanceof EZCoderAIError) {
61
+ return finaliseBySource(err.source, err.message, err.requestId, err.hint);
62
+ }
63
+ if (err instanceof Error) {
64
+ const source = inferSource(err);
65
+ return finaliseBySource(source, err.message, void 0, void 0);
66
+ }
67
+ return finaliseBySource("ezcoder", String(err), void 0, void 0);
68
+ }
69
+ function finaliseBySource(source, message, requestId, hint) {
70
+ switch (source) {
71
+ case "network":
72
+ return {
73
+ headline: "Network error \u2014 couldn't reach the provider.",
74
+ source,
75
+ message,
76
+ guidance: hint ?? "Check your internet connection. Not a ezcoder issue \u2014 retry shortly.",
77
+ ...requestId ? { requestId } : {}
78
+ };
79
+ case "auth":
80
+ return {
81
+ headline: "Authentication issue.",
82
+ source,
83
+ message,
84
+ guidance: hint ?? "Run `ezcoder login` to refresh your credentials.",
85
+ ...requestId ? { requestId } : {}
86
+ };
87
+ case "provider":
88
+ return {
89
+ headline: "Provider returned an error.",
90
+ source,
91
+ message,
92
+ guidance: hint ?? providerGuidance(void 0, message, void 0),
93
+ ...requestId ? { requestId } : {}
94
+ };
95
+ case "ezcoder":
96
+ return {
97
+ headline: "ezcoder hit an unexpected error.",
98
+ source,
99
+ message,
100
+ guidance: hint ?? "This looks like a ezcoder bug \u2014 please report it to the developer (see /help).",
101
+ ...requestId ? { requestId } : {}
102
+ };
103
+ }
104
+ }
105
+ function formatErrorForDisplay(err) {
106
+ const f = formatError(err);
107
+ const lines = [f.headline];
108
+ if (f.message && f.message !== f.headline) lines.push(` ${f.message}`);
109
+ lines.push(` \u2192 ${f.guidance}`);
110
+ return lines.join("\n");
111
+ }
112
+ function cleanProviderMessage(message) {
113
+ return message.replace(/^\[[^\]]+\]\s*/, "").trim();
114
+ }
115
+ function inferSource(err) {
116
+ const msg = err.message.toLowerCase();
117
+ const code = err.code ?? "";
118
+ if (code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "ECONNRESET" || msg.includes("fetch failed") || msg.includes("network request failed")) {
119
+ return "network";
120
+ }
121
+ if (msg.includes("not logged in") || msg.includes("token exchange failed") || msg.includes("token refresh failed") || msg.includes("invalid_grant")) {
122
+ return "auth";
123
+ }
124
+ return "ezcoder";
125
+ }
126
+ function providerGuidance(provider, message, statusCode) {
127
+ const name = provider ? providerDisplayName(provider) : "the provider";
128
+ const status = provider ? PROVIDER_STATUS_URL[provider] : void 0;
129
+ const lower = message.toLowerCase();
130
+ if (statusCode === 401 || lower.includes("unauthorized") || lower.includes("invalid api key")) {
131
+ return `Authentication failed with ${name}. Run \`ezcoder login\` to refresh your credentials.`;
132
+ }
133
+ if (lower.includes("overloaded") || lower.includes("engine_overloaded")) {
134
+ return `${name}'s servers are overloaded right now. Retry in a moment \u2014 not a ezcoder issue.`;
135
+ }
136
+ if (lower.includes("insufficient balance") || lower.includes("quota exceeded") || lower.includes("recharge") || lower.includes("no resource package")) {
137
+ return `Your ${name} account has a billing or quota issue \u2014 check your balance. Not a ezcoder issue.`;
138
+ }
139
+ if (statusCode === 429 || lower.includes("rate limit") || lower.includes("too many requests")) {
140
+ return `${name} rate limit hit. Wait a moment then retry \u2014 not a ezcoder issue.`;
141
+ }
142
+ if (statusCode === 502 || lower.includes("bad gateway")) {
143
+ return `${name} returned a bad gateway. Retry \u2014 this is on their side, not ezcoder.`;
144
+ }
145
+ if (statusCode === 503 || lower.includes("service unavailable")) {
146
+ return `${name} is temporarily unavailable. Retry shortly \u2014 not a ezcoder issue.`;
147
+ }
148
+ if (statusCode === 500 || lower.includes("server_error") || lower.includes("500") && lower.includes("internal server error")) {
149
+ 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.`;
150
+ }
151
+ if (lower.includes("timeout") || lower.includes("timed out")) {
152
+ return `Request to ${name} timed out. Their servers may be slow \u2014 retry. Not a ezcoder issue.`;
153
+ }
154
+ if (lower.includes("does not recognize the requested model") || lower.includes("model") && (lower.includes("not exist") || lower.includes("not found") || lower.includes("no access"))) {
155
+ return `${name} doesn't recognise this model on your account. Use /model to switch, or check your subscription tier.`;
156
+ }
157
+ if (lower.includes("context_length_exceeded") || lower.includes("prompt is too long")) {
158
+ return `Context window for this ${name} model is full. Run /compact to shrink history, or start a new session.`;
159
+ }
160
+ 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.`;
161
+ }
18
162
 
19
163
  // src/providers/anthropic.ts
20
164
  import Anthropic from "@anthropic-ai/sdk";
@@ -246,9 +390,18 @@ function toAnthropicToolResultContent(content) {
246
390
  };
247
391
  });
248
392
  }
393
+ function remapAnthropicToolCallId(id, idMap) {
394
+ if (/^[a-zA-Z0-9_-]+$/.test(id)) return id;
395
+ const existing = idMap.get(id);
396
+ if (existing) return existing;
397
+ const mapped = id.replace(/[^a-zA-Z0-9_-]/g, "_");
398
+ idMap.set(id, mapped);
399
+ return mapped;
400
+ }
249
401
  function toAnthropicMessages(messages, cacheControl) {
250
402
  let systemText;
251
403
  const out = [];
404
+ const idMap = /* @__PURE__ */ new Map();
252
405
  for (const msg of messages) {
253
406
  if (msg.role === "system") {
254
407
  systemText = msg.content;
@@ -283,7 +436,7 @@ function toAnthropicMessages(messages, cacheControl) {
283
436
  if (part.type === "tool_call")
284
437
  return {
285
438
  type: "tool_use",
286
- id: part.id,
439
+ id: remapAnthropicToolCallId(part.id, idMap),
287
440
  name: part.name,
288
441
  input: part.args
289
442
  };
@@ -308,7 +461,7 @@ function toAnthropicMessages(messages, cacheControl) {
308
461
  role: "user",
309
462
  content: msg.content.map((result) => ({
310
463
  type: "tool_result",
311
- tool_use_id: result.toolCallId,
464
+ tool_use_id: remapAnthropicToolCallId(result.toolCallId, idMap),
312
465
  content: toAnthropicToolResultContent(result.content),
313
466
  is_error: result.isError
314
467
  }))
@@ -364,12 +517,19 @@ function toAnthropicMessages(messages, cacheControl) {
364
517
  }
365
518
  return { system, messages: out };
366
519
  }
367
- function toAnthropicTools(tools) {
368
- return tools.map((tool) => ({
369
- name: tool.name,
370
- description: tool.description,
371
- input_schema: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters)
372
- }));
520
+ function toAnthropicTools(tools, options) {
521
+ return tools.map((tool, index) => {
522
+ const anthropicTool = {
523
+ name: tool.name,
524
+ description: tool.description,
525
+ input_schema: tool.rawInputSchema ?? zodToJsonSchema(tool.parameters),
526
+ ...options?.enableFineGrainedToolStreaming ? { eager_input_streaming: true } : {}
527
+ };
528
+ if (options?.cacheControl && index === tools.length - 1) {
529
+ anthropicTool.cache_control = options.cacheControl;
530
+ }
531
+ return anthropicTool;
532
+ });
373
533
  }
374
534
  function toAnthropicToolChoice(choice) {
375
535
  if (choice === "auto") return { type: "auto" };
@@ -382,8 +542,8 @@ function supportsAdaptiveThinking(model) {
382
542
  }
383
543
  function toAnthropicThinking(level, maxTokens, model) {
384
544
  if (supportsAdaptiveThinking(model)) {
385
- let effort = level;
386
- if (level === "max" && !model.includes("opus")) {
545
+ let effort = level === "xhigh" ? "max" : level;
546
+ if (effort === "max" && !model.includes("opus")) {
387
547
  effort = "high";
388
548
  }
389
549
  return {
@@ -392,7 +552,7 @@ function toAnthropicThinking(level, maxTokens, model) {
392
552
  outputConfig: { effort }
393
553
  };
394
554
  }
395
- const effectiveLevel = level === "max" ? "high" : level;
555
+ const effectiveLevel = level === "xhigh" ? "high" : level;
396
556
  const budgetMap = {
397
557
  low: Math.max(1024, Math.floor(maxTokens * 0.25)),
398
558
  medium: Math.max(2048, Math.floor(maxTokens * 0.5)),
@@ -525,8 +685,8 @@ function toOpenAIToolChoice(choice) {
525
685
  if (choice === "required") return "required";
526
686
  return { type: "function", function: { name: choice.name } };
527
687
  }
528
- function toOpenAIReasoningEffort(level) {
529
- return level === "max" ? "high" : level;
688
+ function toOpenAIReasoningEffort(level, _model) {
689
+ return level;
530
690
  }
531
691
  function normalizeAnthropicStopReason(reason) {
532
692
  switch (reason) {
@@ -558,6 +718,9 @@ function normalizeOpenAIStopReason(reason) {
558
718
  }
559
719
 
560
720
  // src/providers/anthropic.ts
721
+ function isJsonObject(value) {
722
+ return value != null && typeof value === "object" && !Array.isArray(value);
723
+ }
561
724
  function createClient(options) {
562
725
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
563
726
  return new Anthropic({
@@ -570,7 +733,10 @@ function createClient(options) {
570
733
  maxRetries: 2,
571
734
  ...isOAuth ? {
572
735
  defaultHeaders: {
573
- "user-agent": "claude-cli/2.1.75",
736
+ // Anthropic's OAuth edge validates the claude-cli version. Callers
737
+ // (ezcoder) resolve the live version at runtime; the literal here
738
+ // is the offline fallback for direct gg-ai consumers.
739
+ "user-agent": options.userAgent ?? "claude-cli/2.1.75 (external, cli)",
574
740
  "x-app": "cli"
575
741
  }
576
742
  } : {}
@@ -584,6 +750,7 @@ async function* runStream(options) {
584
750
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
585
751
  const useStreaming = options.streaming !== false;
586
752
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
753
+ const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
587
754
  const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
588
755
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
589
756
  const system = isOAuth ? [
@@ -614,13 +781,28 @@ async function* runStream(options) {
614
781
  ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
615
782
  ...options.topP != null ? { top_p: options.topP } : {},
616
783
  ...options.stop ? { stop_sequences: options.stop } : {},
617
- ...options.tools?.length || options.serverTools?.length || options.webSearch ? {
618
- tools: [
619
- ...options.tools?.length ? toAnthropicTools(options.tools) : [],
620
- ...options.serverTools ?? [],
621
- ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
622
- ]
623
- } : {},
784
+ ...options.tools?.length || options.serverTools?.length || options.webSearch ? (() => {
785
+ const reservedServerNames = /* @__PURE__ */ new Set();
786
+ if (options.webSearch) reservedServerNames.add("web_search");
787
+ for (const t of options.serverTools ?? []) {
788
+ const name = t.name;
789
+ if (name) reservedServerNames.add(name);
790
+ }
791
+ const clientTools = options.tools?.length ? toAnthropicTools(
792
+ options.tools.filter((t) => !reservedServerNames.has(t.name)),
793
+ {
794
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
795
+ ...supportsFirstPartyToolExtras ? { enableFineGrainedToolStreaming: true } : {}
796
+ }
797
+ ) : [];
798
+ return {
799
+ tools: [
800
+ ...clientTools,
801
+ ...options.serverTools ?? [],
802
+ ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
803
+ ]
804
+ };
805
+ })() : {},
624
806
  ...options.toolChoice && options.tools?.length ? { tool_choice: toAnthropicToolChoice(options.toolChoice) } : {},
625
807
  ...(() => {
626
808
  const contextEdits = [
@@ -697,6 +879,7 @@ async function* runStream(options) {
697
879
  if (block.type === "tool_use") {
698
880
  accum.toolId = block.id;
699
881
  accum.toolName = block.name;
882
+ accum.input = block.input;
700
883
  } else if (block.type === "server_tool_use") {
701
884
  accum.toolId = block.id;
702
885
  accum.toolName = block.name;
@@ -705,7 +888,11 @@ async function* runStream(options) {
705
888
  accum.raw = block;
706
889
  }
707
890
  blocks.set(idx, accum);
708
- yield keepalive;
891
+ if (block.type === "thinking") {
892
+ yield { type: "thinking_delta", text: "" };
893
+ } else {
894
+ yield keepalive;
895
+ }
709
896
  break;
710
897
  }
711
898
  case "content_block_delta": {
@@ -748,10 +935,13 @@ async function* runStream(options) {
748
935
  });
749
936
  yield keepalive;
750
937
  } else if (accum.type === "tool_use") {
751
- let args = {};
752
- try {
753
- args = JSON.parse(accum.argsJson);
754
- } catch {
938
+ let args = isJsonObject(accum.input) ? accum.input : {};
939
+ if (accum.argsJson) {
940
+ try {
941
+ const parsed = JSON.parse(accum.argsJson);
942
+ args = isJsonObject(parsed) ? parsed : {};
943
+ } catch {
944
+ }
755
945
  }
756
946
  const tc = {
757
947
  type: "tool_call",
@@ -949,8 +1139,15 @@ function messageToResponse(message) {
949
1139
  }
950
1140
  function toError(err) {
951
1141
  if (err instanceof Anthropic.APIError) {
952
- return new ProviderError("anthropic", err.message, {
1142
+ const errorBody = err.error;
1143
+ const nestedError = errorBody?.error;
1144
+ 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;
1145
+ const bodyMessage = typeof nestedError?.message === "string" ? nestedError.message : typeof errorBody?.message === "string" ? errorBody.message : void 0;
1146
+ const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
1147
+ const message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? err.message;
1148
+ return new ProviderError("anthropic", message, {
953
1149
  statusCode: err.status,
1150
+ ...requestId ? { requestId } : {},
954
1151
  cause: err
955
1152
  });
956
1153
  }
@@ -962,6 +1159,38 @@ function toError(err) {
962
1159
 
963
1160
  // src/providers/openai.ts
964
1161
  import OpenAI from "openai";
1162
+
1163
+ // src/providers/prompt-cache-key.ts
1164
+ var MAX_PROMPT_CACHE_KEY_LENGTH = 64;
1165
+ function normalizePromptCacheKey(key) {
1166
+ if (key.length <= MAX_PROMPT_CACHE_KEY_LENGTH) return key;
1167
+ const hash = fnv1aHash(key);
1168
+ const prefixLength = MAX_PROMPT_CACHE_KEY_LENGTH - hash.length - 1;
1169
+ return `${key.slice(0, prefixLength)}:${hash}`;
1170
+ }
1171
+ function fnv1aHash(value) {
1172
+ let hash = 2166136261;
1173
+ for (let index = 0; index < value.length; index++) {
1174
+ hash ^= value.charCodeAt(index);
1175
+ hash = Math.imul(hash, 16777619);
1176
+ }
1177
+ return (hash >>> 0).toString(16).padStart(8, "0");
1178
+ }
1179
+
1180
+ // src/providers/openai.ts
1181
+ function isJsonObject2(value) {
1182
+ return value != null && typeof value === "object" && !Array.isArray(value);
1183
+ }
1184
+ function parseToolArguments(argsJson) {
1185
+ if (!argsJson) return {};
1186
+ try {
1187
+ const parsed = JSON.parse(argsJson);
1188
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1189
+ return isJsonObject2(unwrapped) ? unwrapped : {};
1190
+ } catch {
1191
+ return {};
1192
+ }
1193
+ }
965
1194
  function createClient2(options) {
966
1195
  return new OpenAI({
967
1196
  apiKey: options.apiKey,
@@ -993,19 +1222,22 @@ async function* runStream2(options) {
993
1222
  ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
994
1223
  ...options.topP != null ? { top_p: options.topP } : {},
995
1224
  ...options.stop ? { stop: options.stop } : {},
996
- ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
1225
+ ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
997
1226
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
998
1227
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
999
1228
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
1000
1229
  };
1001
1230
  if (options.provider === "openai" || options.provider === "moonshot") {
1002
1231
  const paramsAny = params;
1003
- paramsAny.prompt_cache_key = "ezcoder";
1232
+ paramsAny.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1004
1233
  const retention = options.cacheRetention ?? "short";
1005
1234
  if (retention === "long") {
1006
1235
  paramsAny.prompt_cache_retention = "24h";
1007
1236
  }
1008
1237
  }
1238
+ if (options.provider === "openai" && options.serviceTier) {
1239
+ params.service_tier = options.serviceTier;
1240
+ }
1009
1241
  if (usesThinkingParam) {
1010
1242
  if (options.thinking) {
1011
1243
  params.thinking = { type: "enabled" };
@@ -1063,6 +1295,9 @@ async function* runStream2(options) {
1063
1295
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1064
1296
  cacheRead = usageAny.cached_tokens;
1065
1297
  }
1298
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1299
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1300
+ }
1066
1301
  inputTokens = chunk.usage.prompt_tokens - cacheRead;
1067
1302
  }
1068
1303
  if (!choice) continue;
@@ -1113,11 +1348,7 @@ async function* runStream2(options) {
1113
1348
  contentParts.push({ type: "text", text: textAccum });
1114
1349
  }
1115
1350
  for (const [, tc] of toolCallAccum) {
1116
- let args = {};
1117
- try {
1118
- args = JSON.parse(tc.argsJson);
1119
- } catch {
1120
- }
1351
+ const args = parseToolArguments(tc.argsJson);
1121
1352
  const toolCall = {
1122
1353
  type: "tool_call",
1123
1354
  id: tc.id,
@@ -1170,11 +1401,7 @@ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
1170
1401
  argsJson
1171
1402
  };
1172
1403
  }
1173
- let args = {};
1174
- try {
1175
- args = JSON.parse(argsJson);
1176
- } catch {
1177
- }
1404
+ const args = parseToolArguments(argsJson);
1178
1405
  yield {
1179
1406
  type: "toolcall_done",
1180
1407
  id: tc.id,
@@ -1202,11 +1429,7 @@ function completionToResponse(completion) {
1202
1429
  const toolCalls = msg.tool_calls;
1203
1430
  if (toolCalls) {
1204
1431
  for (const tc of toolCalls) {
1205
- let args = {};
1206
- try {
1207
- args = JSON.parse(tc.function?.arguments ?? "{}");
1208
- } catch {
1209
- }
1432
+ const args = parseToolArguments(tc.function?.arguments ?? "");
1210
1433
  const toolCall = {
1211
1434
  type: "tool_call",
1212
1435
  id: tc.id,
@@ -1228,6 +1451,9 @@ function completionToResponse(completion) {
1228
1451
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1229
1452
  cacheRead = usageAny.cached_tokens;
1230
1453
  }
1454
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1455
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1456
+ }
1231
1457
  inputTokens = completion.usage.prompt_tokens - cacheRead;
1232
1458
  }
1233
1459
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
@@ -1242,19 +1468,19 @@ function completionToResponse(completion) {
1242
1468
  }
1243
1469
  function toError2(err, provider = "openai") {
1244
1470
  if (err instanceof OpenAI.APIError) {
1245
- let msg = err.message;
1246
1471
  const body = err.error;
1247
- if (body) {
1248
- const modelName = body.model || "";
1249
- const _code = body.code || "";
1250
- const message = body.message || "";
1251
- if (modelName === "codex-mini-latest" || message.includes("codex-mini-latest")) {
1252
- 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.`;
1253
- }
1254
- msg += ` | body: ${JSON.stringify(body)}`;
1255
- }
1256
- return new ProviderError(provider, msg, {
1472
+ const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
1473
+ const modelName = typeof body?.model === "string" ? body.model : "";
1474
+ const cleanMessage = bodyMessage ?? err.message;
1475
+ let hint;
1476
+ if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
1477
+ 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.";
1478
+ }
1479
+ const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1480
+ return new ProviderError(provider, cleanMessage, {
1257
1481
  statusCode: err.status,
1482
+ ...requestId ? { requestId } : {},
1483
+ ...hint ? { hint } : {},
1258
1484
  cause: err
1259
1485
  });
1260
1486
  }
@@ -1266,7 +1492,34 @@ function toError2(err, provider = "openai") {
1266
1492
 
1267
1493
  // src/providers/openai-codex.ts
1268
1494
  import os from "os";
1495
+
1496
+ // src/utils/diag.ts
1497
+ var _diagFn = null;
1498
+ function setProviderDiagnostic(fn) {
1499
+ _diagFn = fn;
1500
+ }
1501
+ function providerDiag(phase, data) {
1502
+ _diagFn?.(phase, data);
1503
+ }
1504
+
1505
+ // src/providers/openai-codex.ts
1269
1506
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1507
+ function isJsonObject3(value) {
1508
+ return value != null && typeof value === "object" && !Array.isArray(value);
1509
+ }
1510
+ function parseToolArguments2(argsJson) {
1511
+ if (!argsJson) return {};
1512
+ try {
1513
+ const parsed = JSON.parse(argsJson);
1514
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1515
+ return isJsonObject3(unwrapped) ? unwrapped : {};
1516
+ } catch {
1517
+ return {};
1518
+ }
1519
+ }
1520
+ function outputTextKey(itemId, contentIndex) {
1521
+ return `${itemId ?? ""}:${contentIndex ?? 0}`;
1522
+ }
1270
1523
  function streamOpenAICodex(options) {
1271
1524
  return new StreamResult(runStream3(options));
1272
1525
  }
@@ -1288,15 +1541,17 @@ async function* runStream3(options) {
1288
1541
  if (options.tools?.length) {
1289
1542
  body.tools = toCodexTools(options.tools);
1290
1543
  }
1544
+ body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1545
+ if (options.cacheRetention === "long") {
1546
+ body.prompt_cache_retention = "24h";
1547
+ }
1291
1548
  if (options.temperature != null && !options.thinking) {
1292
1549
  body.temperature = options.temperature;
1293
1550
  }
1294
- if (options.thinking) {
1295
- body.reasoning = {
1296
- effort: options.thinking,
1297
- summary: "auto"
1298
- };
1299
- }
1551
+ body.reasoning = {
1552
+ effort: options.thinking ?? "none",
1553
+ summary: "auto"
1554
+ };
1300
1555
  const headers = {
1301
1556
  "Content-Type": "application/json",
1302
1557
  Accept: "text/event-stream",
@@ -1308,6 +1563,11 @@ async function* runStream3(options) {
1308
1563
  if (options.accountId) {
1309
1564
  headers["chatgpt-account-id"] = options.accountId;
1310
1565
  }
1566
+ const cacheScopeId = body.prompt_cache_key;
1567
+ if (cacheScopeId) {
1568
+ headers["session_id"] = cacheScopeId;
1569
+ headers["x-client-request-id"] = cacheScopeId;
1570
+ }
1311
1571
  const response = await fetch(url, {
1312
1572
  method: "POST",
1313
1573
  headers,
@@ -1316,19 +1576,23 @@ async function* runStream3(options) {
1316
1576
  });
1317
1577
  if (!response.ok) {
1318
1578
  const text = await response.text().catch(() => "");
1319
- let message = `Codex API error (${response.status}): ${text}`;
1579
+ const parsed = parseCodexErrorBody(text);
1580
+ const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1581
+ 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;
1582
+ let hint;
1320
1583
  if (response.status === 400 && text.includes("not supported")) {
1321
- message += `
1322
-
1323
- 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`;
1324
- }
1325
- if (response.status === 404 && text.includes("does not exist")) {
1326
- message += `
1327
-
1328
- 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.`;
1584
+ if (options.model === "gpt-5.5-pro") {
1585
+ hint = "Use gpt-5.5 instead. OpenAI's Codex model catalog does not list gpt-5.5-pro.";
1586
+ } else {
1587
+ 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.";
1588
+ }
1589
+ } else if (response.status === 404 && text.includes("does not exist")) {
1590
+ 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.";
1329
1591
  }
1330
1592
  throw new ProviderError("openai", message, {
1331
- statusCode: response.status
1593
+ statusCode: response.status,
1594
+ ...requestId ? { requestId } : {},
1595
+ ...hint ? { hint } : {}
1332
1596
  });
1333
1597
  }
1334
1598
  if (!response.body) {
@@ -1337,27 +1601,84 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1337
1601
  const contentParts = [];
1338
1602
  let textAccum = "";
1339
1603
  const toolCalls = /* @__PURE__ */ new Map();
1604
+ const outputItemTypes = /* @__PURE__ */ new Map();
1605
+ const outputTextByPart = /* @__PURE__ */ new Map();
1340
1606
  let inputTokens = 0;
1341
1607
  let outputTokens = 0;
1608
+ let cacheRead = 0;
1609
+ const diagStart = Date.now();
1610
+ const diagSeen = /* @__PURE__ */ new Set();
1342
1611
  for await (const event of parseSSE(response.body)) {
1343
1612
  const type = event.type;
1344
1613
  if (!type) continue;
1614
+ if (!diagSeen.has(type)) {
1615
+ diagSeen.add(type);
1616
+ providerDiag("codex_event_first", { type, sinceStartMs: Date.now() - diagStart });
1617
+ }
1345
1618
  if (type === "error") {
1346
- const msg = event.message || JSON.stringify(event);
1347
- throw new ProviderError("openai", `Codex error: ${msg}`);
1619
+ const nested = event.error ?? void 0;
1620
+ const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1621
+ const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1622
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1623
+ throw new ProviderError("openai", message, {
1624
+ ...requestId != null ? { requestId } : {},
1625
+ ...code === "server_error" ? { statusCode: 500 } : {}
1626
+ });
1348
1627
  }
1349
1628
  if (type === "response.failed") {
1350
- const msg = event.error?.message || "Codex response failed";
1351
- throw new ProviderError("openai", msg);
1629
+ const nested = event.error;
1630
+ const message = nested?.message ?? "Codex response failed.";
1631
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1632
+ throw new ProviderError("openai", message, {
1633
+ ...requestId != null ? { requestId } : {}
1634
+ });
1352
1635
  }
1353
1636
  if (type === "response.output_text.delta") {
1354
1637
  const delta = event.delta;
1355
- textAccum += delta;
1356
- yield { type: "text_delta", text: delta };
1638
+ const itemId = event.item_id;
1639
+ const contentIndex = event.content_index;
1640
+ const key = outputTextKey(itemId, contentIndex);
1641
+ outputTextByPart.set(key, `${outputTextByPart.get(key) ?? ""}${delta}`);
1642
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1643
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1644
+ } else {
1645
+ textAccum += delta;
1646
+ yield { type: "text_delta", text: delta };
1647
+ }
1357
1648
  }
1358
- if (type === "response.reasoning_summary_text.delta") {
1649
+ if (type === "response.output_text.done") {
1650
+ const fullText = event.text;
1651
+ if (fullText) {
1652
+ const itemId = event.item_id;
1653
+ const contentIndex = event.content_index;
1654
+ const key = outputTextKey(itemId, contentIndex);
1655
+ const streamedText = outputTextByPart.get(key) ?? "";
1656
+ const missingText = streamedText ? fullText.slice(streamedText.length) : fullText;
1657
+ outputTextByPart.set(key, fullText);
1658
+ if (missingText && fullText.startsWith(streamedText)) {
1659
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1660
+ if (options.thinking) yield { type: "thinking_delta", text: missingText };
1661
+ } else {
1662
+ textAccum += missingText;
1663
+ yield { type: "text_delta", text: missingText };
1664
+ }
1665
+ }
1666
+ }
1667
+ }
1668
+ if (type === "response.reasoning_summary_text.delta" || type === "response.reasoning_summary.delta" || type === "response.reasoning_text.delta" || type === "response.reasoning.delta") {
1359
1669
  const delta = event.delta;
1360
- yield { type: "thinking_delta", text: delta };
1670
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1671
+ }
1672
+ if (type === "response.output_item.added") {
1673
+ const item = event.item;
1674
+ const itemId = item?.id;
1675
+ const itemType = item?.type;
1676
+ if (itemId && itemType) {
1677
+ outputItemTypes.set(itemId, itemType);
1678
+ }
1679
+ if (itemType === "reasoning" && options.thinking) {
1680
+ yield { type: "thinking_delta", text: "" };
1681
+ }
1361
1682
  }
1362
1683
  if (type === "response.output_item.added") {
1363
1684
  const item = event.item;
@@ -1403,11 +1724,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1403
1724
  const id = `${callId}|${itemId}`;
1404
1725
  const tc = toolCalls.get(id);
1405
1726
  if (tc) {
1406
- let args = {};
1407
- try {
1408
- args = JSON.parse(tc.argsJson);
1409
- } catch {
1410
- }
1727
+ const args = parseToolArguments2(tc.argsJson);
1411
1728
  yield {
1412
1729
  type: "toolcall_done",
1413
1730
  id: tc.id,
@@ -1421,7 +1738,8 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1421
1738
  const resp = event.response;
1422
1739
  const usage = resp?.usage;
1423
1740
  if (usage) {
1424
- inputTokens = usage.input_tokens ?? 0;
1741
+ cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
1742
+ inputTokens = (usage.input_tokens ?? 0) - cacheRead;
1425
1743
  outputTokens = usage.output_tokens ?? 0;
1426
1744
  }
1427
1745
  }
@@ -1430,11 +1748,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1430
1748
  contentParts.push({ type: "text", text: textAccum });
1431
1749
  }
1432
1750
  for (const [, tc] of toolCalls) {
1433
- let args = {};
1434
- try {
1435
- args = JSON.parse(tc.argsJson);
1436
- } catch {
1437
- }
1751
+ const args = parseToolArguments2(tc.argsJson);
1438
1752
  const toolCall = {
1439
1753
  type: "tool_call",
1440
1754
  id: tc.id,
@@ -1451,7 +1765,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1451
1765
  content: contentParts.length > 0 ? contentParts : textAccum || ""
1452
1766
  },
1453
1767
  stopReason,
1454
- usage: { inputTokens, outputTokens }
1768
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1455
1769
  };
1456
1770
  yield { type: "done", stopReason };
1457
1771
  return streamResponse;
@@ -1593,6 +1907,24 @@ function toCodexTools(tools) {
1593
1907
  strict: null
1594
1908
  }));
1595
1909
  }
1910
+ function extractCodexRequestId(message) {
1911
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1912
+ return match?.[1];
1913
+ }
1914
+ function parseCodexErrorBody(text) {
1915
+ if (!text) return {};
1916
+ try {
1917
+ const parsed = JSON.parse(text);
1918
+ const error = parsed.error;
1919
+ const detail = parsed.detail;
1920
+ const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
1921
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
1922
+ return { ...message ? { message } : {}, ...requestId ? { requestId } : {} };
1923
+ } catch {
1924
+ const trimmed = text.trim().slice(0, 240);
1925
+ return trimmed ? { message: trimmed } : {};
1926
+ }
1927
+ }
1596
1928
 
1597
1929
  // src/provider-registry.ts
1598
1930
  var ProviderRegistryImpl = class {
@@ -1852,12 +2184,15 @@ export {
1852
2184
  EventStream,
1853
2185
  ProviderError,
1854
2186
  StreamResult,
2187
+ formatError,
2188
+ formatErrorForDisplay,
1855
2189
  palsuAssistantMessage,
1856
2190
  palsuText,
1857
2191
  palsuThinking,
1858
2192
  palsuToolCall,
1859
2193
  providerRegistry,
1860
2194
  registerPalsuProvider,
2195
+ setProviderDiagnostic,
1861
2196
  stream
1862
2197
  };
1863
2198
  //# sourceMappingURL=index.js.map