@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.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,23 @@ 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
+ }
724
+ function uniqueToolsByName(tools) {
725
+ const seen = /* @__PURE__ */ new Set();
726
+ const unique = [];
727
+ for (const tool of tools) {
728
+ if (!tool.name) {
729
+ unique.push(tool);
730
+ continue;
731
+ }
732
+ if (seen.has(tool.name)) continue;
733
+ seen.add(tool.name);
734
+ unique.push(tool);
735
+ }
736
+ return unique;
737
+ }
561
738
  function createClient(options) {
562
739
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
563
740
  return new Anthropic({
@@ -570,7 +747,10 @@ function createClient(options) {
570
747
  maxRetries: 2,
571
748
  ...isOAuth ? {
572
749
  defaultHeaders: {
573
- "user-agent": "claude-cli/2.1.75",
750
+ // Anthropic's OAuth edge validates the claude-cli version. Callers
751
+ // (ezcoder) resolve the live version at runtime; the literal here
752
+ // is the offline fallback for direct gg-ai consumers.
753
+ "user-agent": options.userAgent ?? "claude-cli/2.1.75 (external, cli)",
574
754
  "x-app": "cli"
575
755
  }
576
756
  } : {}
@@ -584,6 +764,7 @@ async function* runStream(options) {
584
764
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
585
765
  const useStreaming = options.streaming !== false;
586
766
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
767
+ const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
587
768
  const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
588
769
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
589
770
  const system = isOAuth ? [
@@ -614,13 +795,28 @@ async function* runStream(options) {
614
795
  ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
615
796
  ...options.topP != null ? { top_p: options.topP } : {},
616
797
  ...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
- } : {},
798
+ ...options.tools?.length || options.serverTools?.length || options.webSearch ? (() => {
799
+ const reservedServerNames = /* @__PURE__ */ new Set();
800
+ if (options.webSearch) reservedServerNames.add("web_search");
801
+ for (const t of options.serverTools ?? []) {
802
+ const name = t.name;
803
+ if (name) reservedServerNames.add(name);
804
+ }
805
+ const clientTools = options.tools?.length ? toAnthropicTools(
806
+ uniqueToolsByName(options.tools.filter((t) => !reservedServerNames.has(t.name))),
807
+ {
808
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
809
+ ...supportsFirstPartyToolExtras ? { enableFineGrainedToolStreaming: true } : {}
810
+ }
811
+ ) : [];
812
+ return {
813
+ tools: uniqueToolsByName([
814
+ ...clientTools,
815
+ ...options.serverTools ?? [],
816
+ ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
817
+ ])
818
+ };
819
+ })() : {},
624
820
  ...options.toolChoice && options.tools?.length ? { tool_choice: toAnthropicToolChoice(options.toolChoice) } : {},
625
821
  ...(() => {
626
822
  const contextEdits = [
@@ -697,6 +893,7 @@ async function* runStream(options) {
697
893
  if (block.type === "tool_use") {
698
894
  accum.toolId = block.id;
699
895
  accum.toolName = block.name;
896
+ accum.input = block.input;
700
897
  } else if (block.type === "server_tool_use") {
701
898
  accum.toolId = block.id;
702
899
  accum.toolName = block.name;
@@ -705,7 +902,11 @@ async function* runStream(options) {
705
902
  accum.raw = block;
706
903
  }
707
904
  blocks.set(idx, accum);
708
- yield keepalive;
905
+ if (block.type === "thinking") {
906
+ yield { type: "thinking_delta", text: "" };
907
+ } else {
908
+ yield keepalive;
909
+ }
709
910
  break;
710
911
  }
711
912
  case "content_block_delta": {
@@ -748,10 +949,13 @@ async function* runStream(options) {
748
949
  });
749
950
  yield keepalive;
750
951
  } else if (accum.type === "tool_use") {
751
- let args = {};
752
- try {
753
- args = JSON.parse(accum.argsJson);
754
- } catch {
952
+ let args = isJsonObject(accum.input) ? accum.input : {};
953
+ if (accum.argsJson) {
954
+ try {
955
+ const parsed = JSON.parse(accum.argsJson);
956
+ args = isJsonObject(parsed) ? parsed : {};
957
+ } catch {
958
+ }
755
959
  }
756
960
  const tc = {
757
961
  type: "tool_call",
@@ -949,8 +1153,15 @@ function messageToResponse(message) {
949
1153
  }
950
1154
  function toError(err) {
951
1155
  if (err instanceof Anthropic.APIError) {
952
- return new ProviderError("anthropic", err.message, {
1156
+ const errorBody = err.error;
1157
+ const nestedError = errorBody?.error;
1158
+ 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;
1159
+ const bodyMessage = typeof nestedError?.message === "string" ? nestedError.message : typeof errorBody?.message === "string" ? errorBody.message : void 0;
1160
+ const bodyType = typeof nestedError?.type === "string" ? nestedError.type : typeof errorBody?.type === "string" ? errorBody.type : typeof err.type === "string" ? err.type : void 0;
1161
+ const message = bodyType && bodyMessage ? `${bodyType}: ${bodyMessage}` : bodyMessage ?? err.message;
1162
+ return new ProviderError("anthropic", message, {
953
1163
  statusCode: err.status,
1164
+ ...requestId ? { requestId } : {},
954
1165
  cause: err
955
1166
  });
956
1167
  }
@@ -962,6 +1173,38 @@ function toError(err) {
962
1173
 
963
1174
  // src/providers/openai.ts
964
1175
  import OpenAI from "openai";
1176
+
1177
+ // src/providers/prompt-cache-key.ts
1178
+ var MAX_PROMPT_CACHE_KEY_LENGTH = 64;
1179
+ function normalizePromptCacheKey(key) {
1180
+ if (key.length <= MAX_PROMPT_CACHE_KEY_LENGTH) return key;
1181
+ const hash = fnv1aHash(key);
1182
+ const prefixLength = MAX_PROMPT_CACHE_KEY_LENGTH - hash.length - 1;
1183
+ return `${key.slice(0, prefixLength)}:${hash}`;
1184
+ }
1185
+ function fnv1aHash(value) {
1186
+ let hash = 2166136261;
1187
+ for (let index = 0; index < value.length; index++) {
1188
+ hash ^= value.charCodeAt(index);
1189
+ hash = Math.imul(hash, 16777619);
1190
+ }
1191
+ return (hash >>> 0).toString(16).padStart(8, "0");
1192
+ }
1193
+
1194
+ // src/providers/openai.ts
1195
+ function isJsonObject2(value) {
1196
+ return value != null && typeof value === "object" && !Array.isArray(value);
1197
+ }
1198
+ function parseToolArguments(argsJson) {
1199
+ if (!argsJson) return {};
1200
+ try {
1201
+ const parsed = JSON.parse(argsJson);
1202
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1203
+ return isJsonObject2(unwrapped) ? unwrapped : {};
1204
+ } catch {
1205
+ return {};
1206
+ }
1207
+ }
965
1208
  function createClient2(options) {
966
1209
  return new OpenAI({
967
1210
  apiKey: options.apiKey,
@@ -993,19 +1236,22 @@ async function* runStream2(options) {
993
1236
  ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
994
1237
  ...options.topP != null ? { top_p: options.topP } : {},
995
1238
  ...options.stop ? { stop: options.stop } : {},
996
- ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
1239
+ ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
997
1240
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
998
1241
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
999
1242
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
1000
1243
  };
1001
1244
  if (options.provider === "openai" || options.provider === "moonshot") {
1002
1245
  const paramsAny = params;
1003
- paramsAny.prompt_cache_key = "ezcoder";
1246
+ paramsAny.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1004
1247
  const retention = options.cacheRetention ?? "short";
1005
1248
  if (retention === "long") {
1006
1249
  paramsAny.prompt_cache_retention = "24h";
1007
1250
  }
1008
1251
  }
1252
+ if (options.provider === "openai" && options.serviceTier) {
1253
+ params.service_tier = options.serviceTier;
1254
+ }
1009
1255
  if (usesThinkingParam) {
1010
1256
  if (options.thinking) {
1011
1257
  params.thinking = { type: "enabled" };
@@ -1063,6 +1309,9 @@ async function* runStream2(options) {
1063
1309
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1064
1310
  cacheRead = usageAny.cached_tokens;
1065
1311
  }
1312
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1313
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1314
+ }
1066
1315
  inputTokens = chunk.usage.prompt_tokens - cacheRead;
1067
1316
  }
1068
1317
  if (!choice) continue;
@@ -1113,11 +1362,7 @@ async function* runStream2(options) {
1113
1362
  contentParts.push({ type: "text", text: textAccum });
1114
1363
  }
1115
1364
  for (const [, tc] of toolCallAccum) {
1116
- let args = {};
1117
- try {
1118
- args = JSON.parse(tc.argsJson);
1119
- } catch {
1120
- }
1365
+ const args = parseToolArguments(tc.argsJson);
1121
1366
  const toolCall = {
1122
1367
  type: "tool_call",
1123
1368
  id: tc.id,
@@ -1170,11 +1415,7 @@ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
1170
1415
  argsJson
1171
1416
  };
1172
1417
  }
1173
- let args = {};
1174
- try {
1175
- args = JSON.parse(argsJson);
1176
- } catch {
1177
- }
1418
+ const args = parseToolArguments(argsJson);
1178
1419
  yield {
1179
1420
  type: "toolcall_done",
1180
1421
  id: tc.id,
@@ -1202,11 +1443,7 @@ function completionToResponse(completion) {
1202
1443
  const toolCalls = msg.tool_calls;
1203
1444
  if (toolCalls) {
1204
1445
  for (const tc of toolCalls) {
1205
- let args = {};
1206
- try {
1207
- args = JSON.parse(tc.function?.arguments ?? "{}");
1208
- } catch {
1209
- }
1446
+ const args = parseToolArguments(tc.function?.arguments ?? "");
1210
1447
  const toolCall = {
1211
1448
  type: "tool_call",
1212
1449
  id: tc.id,
@@ -1228,6 +1465,9 @@ function completionToResponse(completion) {
1228
1465
  if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1229
1466
  cacheRead = usageAny.cached_tokens;
1230
1467
  }
1468
+ if (!cacheRead && typeof usageAny.prompt_cache_hit_tokens === "number" && usageAny.prompt_cache_hit_tokens > 0) {
1469
+ cacheRead = usageAny.prompt_cache_hit_tokens;
1470
+ }
1231
1471
  inputTokens = completion.usage.prompt_tokens - cacheRead;
1232
1472
  }
1233
1473
  const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
@@ -1242,19 +1482,19 @@ function completionToResponse(completion) {
1242
1482
  }
1243
1483
  function toError2(err, provider = "openai") {
1244
1484
  if (err instanceof OpenAI.APIError) {
1245
- let msg = err.message;
1246
1485
  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, {
1486
+ const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
1487
+ const modelName = typeof body?.model === "string" ? body.model : "";
1488
+ const cleanMessage = bodyMessage ?? err.message;
1489
+ let hint;
1490
+ if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
1491
+ 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.";
1492
+ }
1493
+ const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1494
+ return new ProviderError(provider, cleanMessage, {
1257
1495
  statusCode: err.status,
1496
+ ...requestId ? { requestId } : {},
1497
+ ...hint ? { hint } : {},
1258
1498
  cause: err
1259
1499
  });
1260
1500
  }
@@ -1266,7 +1506,34 @@ function toError2(err, provider = "openai") {
1266
1506
 
1267
1507
  // src/providers/openai-codex.ts
1268
1508
  import os from "os";
1509
+
1510
+ // src/utils/diag.ts
1511
+ var _diagFn = null;
1512
+ function setProviderDiagnostic(fn) {
1513
+ _diagFn = fn;
1514
+ }
1515
+ function providerDiag(phase, data) {
1516
+ _diagFn?.(phase, data);
1517
+ }
1518
+
1519
+ // src/providers/openai-codex.ts
1269
1520
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
1521
+ function isJsonObject3(value) {
1522
+ return value != null && typeof value === "object" && !Array.isArray(value);
1523
+ }
1524
+ function parseToolArguments2(argsJson) {
1525
+ if (!argsJson) return {};
1526
+ try {
1527
+ const parsed = JSON.parse(argsJson);
1528
+ const unwrapped = typeof parsed === "string" ? JSON.parse(parsed) : parsed;
1529
+ return isJsonObject3(unwrapped) ? unwrapped : {};
1530
+ } catch {
1531
+ return {};
1532
+ }
1533
+ }
1534
+ function outputTextKey(itemId, contentIndex) {
1535
+ return `${itemId ?? ""}:${contentIndex ?? 0}`;
1536
+ }
1270
1537
  function streamOpenAICodex(options) {
1271
1538
  return new StreamResult(runStream3(options));
1272
1539
  }
@@ -1288,15 +1555,17 @@ async function* runStream3(options) {
1288
1555
  if (options.tools?.length) {
1289
1556
  body.tools = toCodexTools(options.tools);
1290
1557
  }
1558
+ body.prompt_cache_key = normalizePromptCacheKey(options.promptCacheKey ?? "ezcoder");
1559
+ if (options.cacheRetention === "long") {
1560
+ body.prompt_cache_retention = "24h";
1561
+ }
1291
1562
  if (options.temperature != null && !options.thinking) {
1292
1563
  body.temperature = options.temperature;
1293
1564
  }
1294
- if (options.thinking) {
1295
- body.reasoning = {
1296
- effort: options.thinking,
1297
- summary: "auto"
1298
- };
1299
- }
1565
+ body.reasoning = {
1566
+ effort: options.thinking ?? "none",
1567
+ summary: "auto"
1568
+ };
1300
1569
  const headers = {
1301
1570
  "Content-Type": "application/json",
1302
1571
  Accept: "text/event-stream",
@@ -1308,6 +1577,11 @@ async function* runStream3(options) {
1308
1577
  if (options.accountId) {
1309
1578
  headers["chatgpt-account-id"] = options.accountId;
1310
1579
  }
1580
+ const cacheScopeId = body.prompt_cache_key;
1581
+ if (cacheScopeId) {
1582
+ headers["session_id"] = cacheScopeId;
1583
+ headers["x-client-request-id"] = cacheScopeId;
1584
+ }
1311
1585
  const response = await fetch(url, {
1312
1586
  method: "POST",
1313
1587
  headers,
@@ -1316,19 +1590,23 @@ async function* runStream3(options) {
1316
1590
  });
1317
1591
  if (!response.ok) {
1318
1592
  const text = await response.text().catch(() => "");
1319
- let message = `Codex API error (${response.status}): ${text}`;
1593
+ const parsed = parseCodexErrorBody(text);
1594
+ const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1595
+ 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;
1596
+ let hint;
1320
1597
  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.`;
1598
+ if (options.model === "gpt-5.5-pro") {
1599
+ hint = "Use gpt-5.5 instead. OpenAI's Codex model catalog does not list gpt-5.5-pro.";
1600
+ } else {
1601
+ 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.";
1602
+ }
1603
+ } else if (response.status === 404 && text.includes("does not exist")) {
1604
+ 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
1605
  }
1330
1606
  throw new ProviderError("openai", message, {
1331
- statusCode: response.status
1607
+ statusCode: response.status,
1608
+ ...requestId ? { requestId } : {},
1609
+ ...hint ? { hint } : {}
1332
1610
  });
1333
1611
  }
1334
1612
  if (!response.body) {
@@ -1337,27 +1615,84 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1337
1615
  const contentParts = [];
1338
1616
  let textAccum = "";
1339
1617
  const toolCalls = /* @__PURE__ */ new Map();
1618
+ const outputItemTypes = /* @__PURE__ */ new Map();
1619
+ const outputTextByPart = /* @__PURE__ */ new Map();
1340
1620
  let inputTokens = 0;
1341
1621
  let outputTokens = 0;
1622
+ let cacheRead = 0;
1623
+ const diagStart = Date.now();
1624
+ const diagSeen = /* @__PURE__ */ new Set();
1342
1625
  for await (const event of parseSSE(response.body)) {
1343
1626
  const type = event.type;
1344
1627
  if (!type) continue;
1628
+ if (!diagSeen.has(type)) {
1629
+ diagSeen.add(type);
1630
+ providerDiag("codex_event_first", { type, sinceStartMs: Date.now() - diagStart });
1631
+ }
1345
1632
  if (type === "error") {
1346
- const msg = event.message || JSON.stringify(event);
1347
- throw new ProviderError("openai", `Codex error: ${msg}`);
1633
+ const nested = event.error ?? void 0;
1634
+ const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1635
+ const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1636
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1637
+ throw new ProviderError("openai", message, {
1638
+ ...requestId != null ? { requestId } : {},
1639
+ ...code === "server_error" ? { statusCode: 500 } : {}
1640
+ });
1348
1641
  }
1349
1642
  if (type === "response.failed") {
1350
- const msg = event.error?.message || "Codex response failed";
1351
- throw new ProviderError("openai", msg);
1643
+ const nested = event.error;
1644
+ const message = nested?.message ?? "Codex response failed.";
1645
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1646
+ throw new ProviderError("openai", message, {
1647
+ ...requestId != null ? { requestId } : {}
1648
+ });
1352
1649
  }
1353
1650
  if (type === "response.output_text.delta") {
1354
1651
  const delta = event.delta;
1355
- textAccum += delta;
1356
- yield { type: "text_delta", text: delta };
1652
+ const itemId = event.item_id;
1653
+ const contentIndex = event.content_index;
1654
+ const key = outputTextKey(itemId, contentIndex);
1655
+ outputTextByPart.set(key, `${outputTextByPart.get(key) ?? ""}${delta}`);
1656
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1657
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1658
+ } else {
1659
+ textAccum += delta;
1660
+ yield { type: "text_delta", text: delta };
1661
+ }
1662
+ }
1663
+ if (type === "response.output_text.done") {
1664
+ const fullText = event.text;
1665
+ if (fullText) {
1666
+ const itemId = event.item_id;
1667
+ const contentIndex = event.content_index;
1668
+ const key = outputTextKey(itemId, contentIndex);
1669
+ const streamedText = outputTextByPart.get(key) ?? "";
1670
+ const missingText = streamedText ? fullText.slice(streamedText.length) : fullText;
1671
+ outputTextByPart.set(key, fullText);
1672
+ if (missingText && fullText.startsWith(streamedText)) {
1673
+ if (itemId && outputItemTypes.get(itemId) === "reasoning") {
1674
+ if (options.thinking) yield { type: "thinking_delta", text: missingText };
1675
+ } else {
1676
+ textAccum += missingText;
1677
+ yield { type: "text_delta", text: missingText };
1678
+ }
1679
+ }
1680
+ }
1357
1681
  }
1358
- if (type === "response.reasoning_summary_text.delta") {
1682
+ if (type === "response.reasoning_summary_text.delta" || type === "response.reasoning_summary.delta" || type === "response.reasoning_text.delta" || type === "response.reasoning.delta") {
1359
1683
  const delta = event.delta;
1360
- yield { type: "thinking_delta", text: delta };
1684
+ if (options.thinking) yield { type: "thinking_delta", text: delta };
1685
+ }
1686
+ if (type === "response.output_item.added") {
1687
+ const item = event.item;
1688
+ const itemId = item?.id;
1689
+ const itemType = item?.type;
1690
+ if (itemId && itemType) {
1691
+ outputItemTypes.set(itemId, itemType);
1692
+ }
1693
+ if (itemType === "reasoning" && options.thinking) {
1694
+ yield { type: "thinking_delta", text: "" };
1695
+ }
1361
1696
  }
1362
1697
  if (type === "response.output_item.added") {
1363
1698
  const item = event.item;
@@ -1403,11 +1738,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1403
1738
  const id = `${callId}|${itemId}`;
1404
1739
  const tc = toolCalls.get(id);
1405
1740
  if (tc) {
1406
- let args = {};
1407
- try {
1408
- args = JSON.parse(tc.argsJson);
1409
- } catch {
1410
- }
1741
+ const args = parseToolArguments2(tc.argsJson);
1411
1742
  yield {
1412
1743
  type: "toolcall_done",
1413
1744
  id: tc.id,
@@ -1421,7 +1752,8 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1421
1752
  const resp = event.response;
1422
1753
  const usage = resp?.usage;
1423
1754
  if (usage) {
1424
- inputTokens = usage.input_tokens ?? 0;
1755
+ cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
1756
+ inputTokens = (usage.input_tokens ?? 0) - cacheRead;
1425
1757
  outputTokens = usage.output_tokens ?? 0;
1426
1758
  }
1427
1759
  }
@@ -1430,11 +1762,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1430
1762
  contentParts.push({ type: "text", text: textAccum });
1431
1763
  }
1432
1764
  for (const [, tc] of toolCalls) {
1433
- let args = {};
1434
- try {
1435
- args = JSON.parse(tc.argsJson);
1436
- } catch {
1437
- }
1765
+ const args = parseToolArguments2(tc.argsJson);
1438
1766
  const toolCall = {
1439
1767
  type: "tool_call",
1440
1768
  id: tc.id,
@@ -1451,7 +1779,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1451
1779
  content: contentParts.length > 0 ? contentParts : textAccum || ""
1452
1780
  },
1453
1781
  stopReason,
1454
- usage: { inputTokens, outputTokens }
1782
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1455
1783
  };
1456
1784
  yield { type: "done", stopReason };
1457
1785
  return streamResponse;
@@ -1593,6 +1921,24 @@ function toCodexTools(tools) {
1593
1921
  strict: null
1594
1922
  }));
1595
1923
  }
1924
+ function extractCodexRequestId(message) {
1925
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1926
+ return match?.[1];
1927
+ }
1928
+ function parseCodexErrorBody(text) {
1929
+ if (!text) return {};
1930
+ try {
1931
+ const parsed = JSON.parse(text);
1932
+ const error = parsed.error;
1933
+ const detail = parsed.detail;
1934
+ const message = error?.message ?? parsed.message ?? (typeof detail === "string" ? detail : void 0);
1935
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
1936
+ return { ...message ? { message } : {}, ...requestId ? { requestId } : {} };
1937
+ } catch {
1938
+ const trimmed = text.trim().slice(0, 240);
1939
+ return trimmed ? { message: trimmed } : {};
1940
+ }
1941
+ }
1596
1942
 
1597
1943
  // src/provider-registry.ts
1598
1944
  var ProviderRegistryImpl = class {
@@ -1852,12 +2198,15 @@ export {
1852
2198
  EventStream,
1853
2199
  ProviderError,
1854
2200
  StreamResult,
2201
+ formatError,
2202
+ formatErrorForDisplay,
1855
2203
  palsuAssistantMessage,
1856
2204
  palsuText,
1857
2205
  palsuThinking,
1858
2206
  palsuToolCall,
1859
2207
  providerRegistry,
1860
2208
  registerPalsuProvider,
2209
+ setProviderDiagnostic,
1861
2210
  stream
1862
2211
  };
1863
2212
  //# sourceMappingURL=index.js.map