@kenkaiiii/gg-ai 4.3.165 → 4.3.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -157,6 +157,10 @@ interface StreamOptions {
157
157
  signal?: AbortSignal;
158
158
  /** Prompt cache retention preference. Providers map this to their supported values. Default: "short". */
159
159
  cacheRetention?: CacheRetention;
160
+ /** Stable per-session cache routing key for providers that support it (OpenAI, Moonshot). */
161
+ promptCacheKey?: string;
162
+ /** OpenAI service tier for latency-sensitive requests. Only sent to first-party OpenAI API calls. */
163
+ serviceTier?: "auto" | "default" | "flex" | "priority";
160
164
  /** OpenAI ChatGPT account ID (from OAuth JWT) for codex endpoint */
161
165
  accountId?: string;
162
166
  /** Enable provider-native web search. Each provider uses its own format:
@@ -292,17 +296,74 @@ declare class ProviderRegistryImpl {
292
296
  /** Global provider registry. Import this to register custom providers. */
293
297
  declare const providerRegistry: ProviderRegistryImpl;
294
298
 
299
+ /**
300
+ * Error model for gg-ai and downstream consumers.
301
+ *
302
+ * Every error users see should answer one question: "is this me or them?"
303
+ * That answer drives whether they retry, switch model, log in, or report a
304
+ * ggcoder bug. The `FormattedError` shape captures it in plain English:
305
+ *
306
+ * ✗ OpenAI returned an error.
307
+ * An error occurred while processing your request...
308
+ * → This is an OpenAI issue, not ggcoder. Retry — if it persists, check status.openai.com.
309
+ *
310
+ * ✗ ggcoder hit an unexpected error.
311
+ * Cannot read property 'foo' of undefined
312
+ * → This is a ggcoder bug — please report it.
313
+ */
314
+ type ErrorSource = "provider" | "ggcoder" | "network" | "auth";
315
+ interface FormattedError {
316
+ /** Plain-English headline, e.g. "OpenAI returned an error." */
317
+ headline: string;
318
+ /** Machine-readable classification. */
319
+ source: ErrorSource;
320
+ /** Detailed message body from the underlying error (no JSON, no tag prefix). */
321
+ message: string;
322
+ /** Action line — tells the user whether to retry, switch model, log in, or report a bug. */
323
+ guidance: string;
324
+ /** Provider name when source === "provider". */
325
+ provider?: string;
326
+ /** HTTP status code if known. */
327
+ statusCode?: number;
328
+ /** Provider request ID, kept for telemetry / debug — not shown by default. */
329
+ requestId?: string;
330
+ }
295
331
  declare class GGAIError extends Error {
296
- constructor(message: string, options?: ErrorOptions);
332
+ readonly source: ErrorSource;
333
+ readonly requestId?: string;
334
+ readonly hint?: string;
335
+ constructor(message: string, options?: {
336
+ source?: ErrorSource;
337
+ requestId?: string;
338
+ hint?: string;
339
+ cause?: unknown;
340
+ });
297
341
  }
298
342
  declare class ProviderError extends GGAIError {
299
343
  readonly provider: string;
300
344
  readonly statusCode?: number;
301
345
  constructor(provider: string, message: string, options?: {
302
346
  statusCode?: number;
347
+ requestId?: string;
348
+ hint?: string;
303
349
  cause?: unknown;
304
350
  });
305
351
  }
352
+ /**
353
+ * Normalise any thrown value into a structured display object. Always returns
354
+ * a non-empty `headline` and `guidance` so the UI never has to second-guess
355
+ * what to show the user.
356
+ */
357
+ declare function formatError(err: unknown): FormattedError;
358
+ /**
359
+ * Render a FormattedError as a multi-line string for terminal display.
360
+ *
361
+ * Format:
362
+ * <headline>
363
+ * <message>
364
+ * → <guidance>
365
+ */
366
+ declare function formatErrorForDisplay(err: unknown): string;
306
367
 
307
368
  interface PalsuProviderState {
308
369
  callCount: number;
@@ -373,4 +434,4 @@ interface PalsuProviderConfig {
373
434
  */
374
435
  declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
375
436
 
376
- export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, type ErrorEvent, EventStream, GGAIError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
437
+ export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, GGAIError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, formatError, formatErrorForDisplay, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
package/dist/index.d.ts CHANGED
@@ -157,6 +157,10 @@ interface StreamOptions {
157
157
  signal?: AbortSignal;
158
158
  /** Prompt cache retention preference. Providers map this to their supported values. Default: "short". */
159
159
  cacheRetention?: CacheRetention;
160
+ /** Stable per-session cache routing key for providers that support it (OpenAI, Moonshot). */
161
+ promptCacheKey?: string;
162
+ /** OpenAI service tier for latency-sensitive requests. Only sent to first-party OpenAI API calls. */
163
+ serviceTier?: "auto" | "default" | "flex" | "priority";
160
164
  /** OpenAI ChatGPT account ID (from OAuth JWT) for codex endpoint */
161
165
  accountId?: string;
162
166
  /** Enable provider-native web search. Each provider uses its own format:
@@ -292,17 +296,74 @@ declare class ProviderRegistryImpl {
292
296
  /** Global provider registry. Import this to register custom providers. */
293
297
  declare const providerRegistry: ProviderRegistryImpl;
294
298
 
299
+ /**
300
+ * Error model for gg-ai and downstream consumers.
301
+ *
302
+ * Every error users see should answer one question: "is this me or them?"
303
+ * That answer drives whether they retry, switch model, log in, or report a
304
+ * ggcoder bug. The `FormattedError` shape captures it in plain English:
305
+ *
306
+ * ✗ OpenAI returned an error.
307
+ * An error occurred while processing your request...
308
+ * → This is an OpenAI issue, not ggcoder. Retry — if it persists, check status.openai.com.
309
+ *
310
+ * ✗ ggcoder hit an unexpected error.
311
+ * Cannot read property 'foo' of undefined
312
+ * → This is a ggcoder bug — please report it.
313
+ */
314
+ type ErrorSource = "provider" | "ggcoder" | "network" | "auth";
315
+ interface FormattedError {
316
+ /** Plain-English headline, e.g. "OpenAI returned an error." */
317
+ headline: string;
318
+ /** Machine-readable classification. */
319
+ source: ErrorSource;
320
+ /** Detailed message body from the underlying error (no JSON, no tag prefix). */
321
+ message: string;
322
+ /** Action line — tells the user whether to retry, switch model, log in, or report a bug. */
323
+ guidance: string;
324
+ /** Provider name when source === "provider". */
325
+ provider?: string;
326
+ /** HTTP status code if known. */
327
+ statusCode?: number;
328
+ /** Provider request ID, kept for telemetry / debug — not shown by default. */
329
+ requestId?: string;
330
+ }
295
331
  declare class GGAIError extends Error {
296
- constructor(message: string, options?: ErrorOptions);
332
+ readonly source: ErrorSource;
333
+ readonly requestId?: string;
334
+ readonly hint?: string;
335
+ constructor(message: string, options?: {
336
+ source?: ErrorSource;
337
+ requestId?: string;
338
+ hint?: string;
339
+ cause?: unknown;
340
+ });
297
341
  }
298
342
  declare class ProviderError extends GGAIError {
299
343
  readonly provider: string;
300
344
  readonly statusCode?: number;
301
345
  constructor(provider: string, message: string, options?: {
302
346
  statusCode?: number;
347
+ requestId?: string;
348
+ hint?: string;
303
349
  cause?: unknown;
304
350
  });
305
351
  }
352
+ /**
353
+ * Normalise any thrown value into a structured display object. Always returns
354
+ * a non-empty `headline` and `guidance` so the UI never has to second-guess
355
+ * what to show the user.
356
+ */
357
+ declare function formatError(err: unknown): FormattedError;
358
+ /**
359
+ * Render a FormattedError as a multi-line string for terminal display.
360
+ *
361
+ * Format:
362
+ * <headline>
363
+ * <message>
364
+ * → <guidance>
365
+ */
366
+ declare function formatErrorForDisplay(err: unknown): string;
306
367
 
307
368
  interface PalsuProviderState {
308
369
  callCount: number;
@@ -373,4 +434,4 @@ interface PalsuProviderConfig {
373
434
  */
374
435
  declare function registerPalsuProvider(config?: PalsuProviderConfig): PalsuProviderHandle;
375
436
 
376
- export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, type ErrorEvent, EventStream, GGAIError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
437
+ export { type AssistantMessage, type CacheRetention, type ContentPart, type DoneEvent, type ErrorEvent, type ErrorSource, EventStream, type FormattedError, GGAIError, type ImageContent, type Message, type PalsuModelConfig, type PalsuModelHandle, type PalsuProviderConfig, type PalsuProviderHandle, type PalsuProviderState, type PalsuResponse, type PalsuResponseFactory, type Provider, type ProviderEntry, ProviderError, type ProviderStreamFn, type RawContent, type ServerToolCall, type ServerToolCallEvent, type ServerToolDefinition, type ServerToolResult, type ServerToolResultEvent, type StopReason, type StreamEvent, type StreamOptions, type StreamResponse, StreamResult, type SystemMessage, type TextContent, type TextDeltaEvent, type ThinkingContent, type ThinkingDeltaEvent, type ThinkingLevel, type Tool, type ToolCall, type ToolCallDeltaEvent, type ToolCallDoneEvent, type ToolChoice, type ToolResult, type ToolResultContent, type ToolResultMessage, type Usage, type UserMessage, formatError, formatErrorForDisplay, palsuAssistantMessage, palsuText, palsuThinking, palsuToolCall, providerRegistry, registerPalsuProvider, stream };
package/dist/index.js CHANGED
@@ -1,20 +1,164 @@
1
1
  // src/errors.ts
2
2
  var GGAIError = 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 = "GGAIError";
9
+ this.source = options?.source ?? "ggcoder";
10
+ this.requestId = options?.requestId;
11
+ this.hint = options?.hint;
6
12
  }
7
13
  };
8
14
  var ProviderError = class extends GGAIError {
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 GGAIError) {
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("ggcoder", 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 ggcoder 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 `ggcoder 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 "ggcoder":
96
+ return {
97
+ headline: "ggcoder hit an unexpected error.",
98
+ source,
99
+ message,
100
+ guidance: hint ?? "This looks like a ggcoder 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 "ggcoder";
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 \`ggcoder 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 ggcoder 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 ggcoder 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 ggcoder 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 ggcoder.`;
144
+ }
145
+ if (statusCode === 503 || lower.includes("service unavailable")) {
146
+ return `${name} is temporarily unavailable. Retry shortly \u2014 not a ggcoder 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 ggcoder. Retry \u2014 if it keeps happening, check ${status}.` : `This is an error from ${name}, not ggcoder. 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 ggcoder 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 ggcoder. Retry \u2014 if it persists, check ${status}.` : `This is an error from ${name}, not ggcoder. 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" };
@@ -588,6 +748,7 @@ async function* runStream(options) {
588
748
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
589
749
  const useStreaming = options.streaming !== false;
590
750
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
751
+ const supportsFirstPartyToolExtras = !options.baseUrl || options.baseUrl.includes("api.anthropic.com");
591
752
  const downgradedMessages = downgradeUnsupportedImages(options.messages, options.supportsImages);
592
753
  const { system: rawSystem, messages } = toAnthropicMessages(downgradedMessages, cacheControl);
593
754
  const system = isOAuth ? [
@@ -620,7 +781,10 @@ async function* runStream(options) {
620
781
  ...options.stop ? { stop_sequences: options.stop } : {},
621
782
  ...options.tools?.length || options.serverTools?.length || options.webSearch ? {
622
783
  tools: [
623
- ...options.tools?.length ? toAnthropicTools(options.tools) : [],
784
+ ...options.tools?.length ? toAnthropicTools(options.tools, {
785
+ ...supportsFirstPartyToolExtras && cacheControl ? { cacheControl } : {},
786
+ ...supportsFirstPartyToolExtras ? { enableFineGrainedToolStreaming: true } : {}
787
+ }) : [],
624
788
  ...options.serverTools ?? [],
625
789
  ...options.webSearch ? [{ type: "web_search_20250305", name: "web_search" }] : []
626
790
  ]
@@ -953,8 +1117,10 @@ function messageToResponse(message) {
953
1117
  }
954
1118
  function toError(err) {
955
1119
  if (err instanceof Anthropic.APIError) {
1120
+ const requestId = err.request_id ?? err.error?.request_id;
956
1121
  return new ProviderError("anthropic", err.message, {
957
1122
  statusCode: err.status,
1123
+ ...requestId ? { requestId } : {},
958
1124
  cause: err
959
1125
  });
960
1126
  }
@@ -1004,12 +1170,15 @@ async function* runStream2(options) {
1004
1170
  };
1005
1171
  if (options.provider === "openai" || options.provider === "moonshot") {
1006
1172
  const paramsAny = params;
1007
- paramsAny.prompt_cache_key = "ggcoder";
1173
+ paramsAny.prompt_cache_key = options.promptCacheKey ?? "ggcoder";
1008
1174
  const retention = options.cacheRetention ?? "short";
1009
1175
  if (retention === "long") {
1010
1176
  paramsAny.prompt_cache_retention = "24h";
1011
1177
  }
1012
1178
  }
1179
+ if (options.provider === "openai" && options.serviceTier) {
1180
+ params.service_tier = options.serviceTier;
1181
+ }
1013
1182
  if (usesThinkingParam) {
1014
1183
  if (options.thinking) {
1015
1184
  params.thinking = { type: "enabled" };
@@ -1246,19 +1415,19 @@ function completionToResponse(completion) {
1246
1415
  }
1247
1416
  function toError2(err, provider = "openai") {
1248
1417
  if (err instanceof OpenAI.APIError) {
1249
- let msg = err.message;
1250
1418
  const body = err.error;
1251
- if (body) {
1252
- const modelName = body.model || "";
1253
- const _code = body.code || "";
1254
- const message = body.message || "";
1255
- if (modelName === "codex-mini-latest" || message.includes("codex-mini-latest")) {
1256
- 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.`;
1257
- }
1258
- msg += ` | body: ${JSON.stringify(body)}`;
1259
- }
1260
- return new ProviderError(provider, msg, {
1419
+ const bodyMessage = typeof body?.message === "string" && body.message.trim() ? body.message.trim() : void 0;
1420
+ const modelName = typeof body?.model === "string" ? body.model : "";
1421
+ const cleanMessage = bodyMessage ?? err.message;
1422
+ let hint;
1423
+ if (modelName === "codex-mini-latest" || cleanMessage.includes("codex-mini-latest")) {
1424
+ 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.";
1425
+ }
1426
+ const requestId = err.request_id ?? (typeof body?.request_id === "string" ? body.request_id : void 0);
1427
+ return new ProviderError(provider, cleanMessage, {
1261
1428
  statusCode: err.status,
1429
+ ...requestId ? { requestId } : {},
1430
+ ...hint ? { hint } : {},
1262
1431
  cause: err
1263
1432
  });
1264
1433
  }
@@ -1292,6 +1461,9 @@ async function* runStream3(options) {
1292
1461
  if (options.tools?.length) {
1293
1462
  body.tools = toCodexTools(options.tools);
1294
1463
  }
1464
+ if (options.promptCacheKey) {
1465
+ body.prompt_cache_key = options.promptCacheKey;
1466
+ }
1295
1467
  if (options.temperature != null && !options.thinking) {
1296
1468
  body.temperature = options.temperature;
1297
1469
  }
@@ -1320,19 +1492,19 @@ async function* runStream3(options) {
1320
1492
  });
1321
1493
  if (!response.ok) {
1322
1494
  const text = await response.text().catch(() => "");
1323
- let message = `Codex API error (${response.status}): ${text}`;
1495
+ const parsed = parseCodexErrorBody(text);
1496
+ const message = parsed.message ?? `Codex API returned HTTP ${response.status}.`;
1497
+ const requestId = parsed.requestId ?? response.headers.get("x-request-id") ?? response.headers.get("openai-request-id") ?? void 0;
1498
+ let hint;
1324
1499
  if (response.status === 400 && text.includes("not supported")) {
1325
- message += `
1326
-
1327
- 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`;
1328
- }
1329
- if (response.status === 404 && text.includes("does not exist")) {
1330
- message += `
1331
-
1332
- 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.`;
1500
+ hint = 'Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription. The "codex-spark" variants require ChatGPT Pro. Check your subscription at https://chatgpt.com/settings.';
1501
+ } else if (response.status === 404 && text.includes("does not exist")) {
1502
+ 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.";
1333
1503
  }
1334
1504
  throw new ProviderError("openai", message, {
1335
- statusCode: response.status
1505
+ statusCode: response.status,
1506
+ ...requestId ? { requestId } : {},
1507
+ ...hint ? { hint } : {}
1336
1508
  });
1337
1509
  }
1338
1510
  if (!response.body) {
@@ -1343,16 +1515,27 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1343
1515
  const toolCalls = /* @__PURE__ */ new Map();
1344
1516
  let inputTokens = 0;
1345
1517
  let outputTokens = 0;
1518
+ let cacheRead = 0;
1346
1519
  for await (const event of parseSSE(response.body)) {
1347
1520
  const type = event.type;
1348
1521
  if (!type) continue;
1349
1522
  if (type === "error") {
1350
- const msg = event.message || JSON.stringify(event);
1351
- throw new ProviderError("openai", `Codex error: ${msg}`);
1523
+ const nested = event.error ?? void 0;
1524
+ const message = nested?.message ?? event.message ?? "Codex stream emitted an error chunk without a message.";
1525
+ const code = nested?.code ?? nested?.type ?? event.code ?? "server_error";
1526
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1527
+ throw new ProviderError("openai", message, {
1528
+ ...requestId != null ? { requestId } : {},
1529
+ ...code === "server_error" ? { statusCode: 500 } : {}
1530
+ });
1352
1531
  }
1353
1532
  if (type === "response.failed") {
1354
- const msg = event.error?.message || "Codex response failed";
1355
- throw new ProviderError("openai", msg);
1533
+ const nested = event.error;
1534
+ const message = nested?.message ?? "Codex response failed.";
1535
+ const requestId = extractCodexRequestId(message) ?? event.request_id;
1536
+ throw new ProviderError("openai", message, {
1537
+ ...requestId != null ? { requestId } : {}
1538
+ });
1356
1539
  }
1357
1540
  if (type === "response.output_text.delta") {
1358
1541
  const delta = event.delta;
@@ -1425,7 +1608,8 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1425
1608
  const resp = event.response;
1426
1609
  const usage = resp?.usage;
1427
1610
  if (usage) {
1428
- inputTokens = usage.input_tokens ?? 0;
1611
+ cacheRead = usage.input_tokens_details?.cached_tokens ?? 0;
1612
+ inputTokens = (usage.input_tokens ?? 0) - cacheRead;
1429
1613
  outputTokens = usage.output_tokens ?? 0;
1430
1614
  }
1431
1615
  }
@@ -1455,7 +1639,7 @@ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GP
1455
1639
  content: contentParts.length > 0 ? contentParts : textAccum || ""
1456
1640
  },
1457
1641
  stopReason,
1458
- usage: { inputTokens, outputTokens }
1642
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1459
1643
  };
1460
1644
  yield { type: "done", stopReason };
1461
1645
  return streamResponse;
@@ -1597,6 +1781,23 @@ function toCodexTools(tools) {
1597
1781
  strict: null
1598
1782
  }));
1599
1783
  }
1784
+ function extractCodexRequestId(message) {
1785
+ const match = message.match(/request ID ([a-z0-9-]{8,})/i);
1786
+ return match?.[1];
1787
+ }
1788
+ function parseCodexErrorBody(text) {
1789
+ if (!text) return {};
1790
+ try {
1791
+ const parsed = JSON.parse(text);
1792
+ const error = parsed.error;
1793
+ const message = error?.message ?? parsed.message;
1794
+ const requestId = parsed.request_id ?? error?.request_id ?? (message ? extractCodexRequestId(message) : void 0);
1795
+ return { ...message ? { message } : {}, ...requestId ? { requestId } : {} };
1796
+ } catch {
1797
+ const trimmed = text.trim().slice(0, 240);
1798
+ return trimmed ? { message: trimmed } : {};
1799
+ }
1800
+ }
1600
1801
 
1601
1802
  // src/provider-registry.ts
1602
1803
  var ProviderRegistryImpl = class {
@@ -1856,6 +2057,8 @@ export {
1856
2057
  GGAIError,
1857
2058
  ProviderError,
1858
2059
  StreamResult,
2060
+ formatError,
2061
+ formatErrorForDisplay,
1859
2062
  palsuAssistantMessage,
1860
2063
  palsuText,
1861
2064
  palsuThinking,