@opencode/ai 2.0.18 → 2.0.20

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.
Files changed (94) hide show
  1. package/README.md +24 -12
  2. package/dist/cache-policy.js +7 -0
  3. package/dist/experimental/evaluation.d.ts +7 -0
  4. package/dist/experimental/evaluation.js +2 -0
  5. package/dist/experimental/system-one.js +10 -7
  6. package/dist/generation.d.ts +1 -1
  7. package/dist/generation.js +23 -18
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/llm.d.ts +1 -1
  11. package/dist/promise.d.ts +3 -3
  12. package/dist/promise.js +4 -3
  13. package/dist/protocols/alibaba-chat.d.ts +8 -2
  14. package/dist/protocols/alibaba-responses.d.ts +14 -14
  15. package/dist/protocols/anthropic-messages.js +2 -15
  16. package/dist/protocols/bedrock-converse.d.ts +1 -1
  17. package/dist/protocols/bedrock-converse.js +38 -12
  18. package/dist/protocols/deepgram-speech.js +9 -7
  19. package/dist/protocols/deepgram-transcription.js +4 -10
  20. package/dist/protocols/elevenlabs-transcription.d.ts +28 -0
  21. package/dist/protocols/elevenlabs-transcription.js +137 -0
  22. package/dist/protocols/gemini.js +1 -10
  23. package/dist/protocols/google-speech.js +9 -2
  24. package/dist/protocols/google-transcription.js +4 -0
  25. package/dist/protocols/google-video.js +11 -2
  26. package/dist/protocols/meta-messages.d.ts +5 -1
  27. package/dist/protocols/meta-messages.js +11 -4
  28. package/dist/protocols/meta-responses.d.ts +16 -16
  29. package/dist/protocols/mistral-chat.d.ts +6 -0
  30. package/dist/protocols/mistral-chat.js +8 -5
  31. package/dist/protocols/open-responses.d.ts +31 -31
  32. package/dist/protocols/openai-chat.d.ts +53 -10
  33. package/dist/protocols/openai-chat.js +21 -8
  34. package/dist/protocols/openai-compatible-chat.d.ts +7 -2
  35. package/dist/protocols/openai-compatible-responses.d.ts +2 -2
  36. package/dist/protocols/openai-responses.d.ts +22 -22
  37. package/dist/protocols/openai-speech.js +6 -1
  38. package/dist/protocols/openai-transcription.js +1 -1
  39. package/dist/protocols/runway-video.js +2 -1
  40. package/dist/protocols/utils/claude-model.d.ts +8 -0
  41. package/dist/protocols/utils/claude-model.js +15 -0
  42. package/dist/protocols/utils/gemini-generate-content.d.ts +6 -0
  43. package/dist/protocols/utils/gemini-generate-content.js +33 -0
  44. package/dist/protocols/utils/media-input.d.ts +4 -3
  45. package/dist/protocols/utils/media-input.js +5 -4
  46. package/dist/protocols/utils/speaker-turns.d.ts +3 -0
  47. package/dist/protocols/utils/speaker-turns.js +9 -0
  48. package/dist/protocols/utils/speech-stream.d.ts +1 -0
  49. package/dist/protocols/utils/speech-stream.js +1 -0
  50. package/dist/protocols/utils/tool-stream.d.ts +4 -3
  51. package/dist/protocols/utils/tool-stream.js +1 -1
  52. package/dist/protocols/xai-responses.d.ts +14 -14
  53. package/dist/protocols/xai-video.js +7 -1
  54. package/dist/protocols/zai-chat.d.ts +8 -2
  55. package/dist/provider-error.d.ts +6 -0
  56. package/dist/provider-error.js +65 -2
  57. package/dist/providers/alibaba.d.ts +9 -4
  58. package/dist/providers/amazon-bedrock-mantle.d.ts +9 -4
  59. package/dist/providers/anthropic-compatible.js +3 -2
  60. package/dist/providers/azure.d.ts +11 -6
  61. package/dist/providers/baseten.d.ts +14 -4
  62. package/dist/providers/cerebras.d.ts +14 -4
  63. package/dist/providers/cloudflare-ai-gateway.d.ts +18 -8
  64. package/dist/providers/cloudflare-workers-ai.d.ts +14 -4
  65. package/dist/providers/deepinfra.d.ts +14 -4
  66. package/dist/providers/deepseek.d.ts +14 -4
  67. package/dist/providers/elevenlabs.d.ts +5 -0
  68. package/dist/providers/elevenlabs.js +4 -0
  69. package/dist/providers/fireworks.d.ts +14 -4
  70. package/dist/providers/google-vertex-chat.d.ts +7 -2
  71. package/dist/providers/google-vertex-responses.d.ts +2 -2
  72. package/dist/providers/groq.d.ts +15 -4
  73. package/dist/providers/meta.d.ts +14 -5
  74. package/dist/providers/minimax.d.ts +9 -4
  75. package/dist/providers/moonshot.d.ts +9 -4
  76. package/dist/providers/openai-compatible-responses.d.ts +2 -2
  77. package/dist/providers/openai-compatible.d.ts +7 -2
  78. package/dist/providers/openai.d.ts +9 -4
  79. package/dist/providers/openrouter.d.ts +27 -6
  80. package/dist/providers/togetherai.d.ts +14 -4
  81. package/dist/providers/xai.d.ts +7 -2
  82. package/dist/providers/xai.js +3 -3
  83. package/dist/providers/zai-coding-plan.d.ts +9 -4
  84. package/dist/providers/zai.d.ts +7 -2
  85. package/dist/route/client.d.ts +1 -1
  86. package/dist/route/executor.js +12 -8
  87. package/dist/route/media-protocol.d.ts +13 -3
  88. package/dist/route/media-protocol.js +16 -6
  89. package/dist/route/media.js +18 -4
  90. package/dist/schema/events.d.ts +30 -30
  91. package/dist/testing.d.ts +8 -8
  92. package/dist/transcription.d.ts +1 -1
  93. package/dist/transcription.js +1 -1
  94. package/package.json +3 -3
@@ -40,6 +40,39 @@ export const blocked = (name, chunk, frame) => {
40
40
  }),
41
41
  });
42
42
  };
43
+ const CONTENT_FILTER_REASONS = new Set([
44
+ "IMAGE_SAFETY",
45
+ "RECITATION",
46
+ "SAFETY",
47
+ "BLOCKLIST",
48
+ "PROHIBITED_CONTENT",
49
+ "SPII",
50
+ "MODEL_ARMOR",
51
+ "IMAGE_PROHIBITED_CONTENT",
52
+ "IMAGE_RECITATION",
53
+ "LANGUAGE",
54
+ ]);
55
+ /** Finish reasons for which Gemini stops output on safety or policy grounds. */
56
+ export const contentFiltered = (finishReason) => finishReason !== undefined && CONTENT_FILTER_REASONS.has(finishReason);
57
+ /** Callers check that the response produced no output: a policy stop after output is a partial result instead. */
58
+ export const withheld = (name, chunk, frame) => {
59
+ const finishReason = chunk.candidates?.[0]?.finishReason;
60
+ if (!contentFiltered(finishReason))
61
+ return undefined;
62
+ return new AIError({
63
+ reason: new ContentPolicyError({ message: `${name} withheld its output (${finishReason})`, body: frame }),
64
+ });
65
+ };
66
+ /** Any finish reason other than `STOP` means the output may be cut short, so it is surfaced rather than dropped. */
67
+ export const notices = (name, state) => state.finishReason === undefined || state.finishReason === "STOP"
68
+ ? undefined
69
+ : [
70
+ {
71
+ type: contentFiltered(state.finishReason) ? "filtered" : "other",
72
+ message: `${name} finished with ${state.finishReason}`,
73
+ providerMetadata: { google: { finishReason: state.finishReason } },
74
+ },
75
+ ];
43
76
  export const usage = (usage) => usage === undefined
44
77
  ? undefined
45
78
  : {
@@ -21,12 +21,13 @@ export declare const imageOutput: (invalid: (message: string, cause?: unknown) =
21
21
  readonly url?: string | null;
22
22
  }, mediaType: string | undefined, options?: Media.AssetOptions) => Effect.Effect<Media.Asset, AIError, never>;
23
23
  /**
24
- * Append multipart text fields: strings as-is, other values as JSON, or arrays as repeated `key[]` parts with
25
- * `repeatArrays`. `overlay` keys in `reserved` are dropped so `http.body` cannot replace route-owned fields.
24
+ * Append multipart text fields: strings as-is, other values as JSON, or scalar arrays as one part per item with
25
+ * `repeatArrays`, named `key[]` or `key`. `overlay` keys in `reserved` are dropped so `http.body` cannot replace
26
+ * route-owned fields.
26
27
  */
27
28
  export declare const appendFields: (form: FormData, fields: Record<string, unknown>, options: {
28
29
  readonly overlay?: Record<string, unknown>;
29
30
  readonly reserved: ReadonlySet<string>;
30
- readonly repeatArrays?: true;
31
+ readonly repeatArrays?: "key[]" | "key";
31
32
  }) => void;
32
33
  export * as MediaInput from "./media-input.js";
@@ -43,14 +43,15 @@ export const imageOutput = (invalid, label, item, mediaType, options) => {
43
43
  return Effect.fail(invalid(`${label} has neither image data nor a URL`));
44
44
  };
45
45
  /**
46
- * Append multipart text fields: strings as-is, other values as JSON, or arrays as repeated `key[]` parts with
47
- * `repeatArrays`. `overlay` keys in `reserved` are dropped so `http.body` cannot replace route-owned fields.
46
+ * Append multipart text fields: strings as-is, other values as JSON, or scalar arrays as one part per item with
47
+ * `repeatArrays`, named `key[]` or `key`. `overlay` keys in `reserved` are dropped so `http.body` cannot replace
48
+ * route-owned fields.
48
49
  */
49
50
  export const appendFields = (form, fields, options) => {
50
51
  const overlay = Object.entries(options.overlay ?? {}).filter(([key]) => !options.reserved.has(key));
51
52
  Object.entries(mergeJsonRecords(fields, Object.fromEntries(overlay)) ?? {}).forEach(([key, value]) => {
52
- if (Array.isArray(value) && options.repeatArrays)
53
- return value.forEach((item) => form.append(`${key}[]`, String(item)));
53
+ if (Array.isArray(value) && value.every(isScalar) && options.repeatArrays !== undefined)
54
+ return value.forEach((item) => form.append(options.repeatArrays === "key[]" ? `${key}[]` : key, String(item)));
54
55
  form.append(key, typeof value === "string" ? value : encodeJson(value));
55
56
  });
56
57
  };
@@ -0,0 +1,3 @@
1
+ /** Split an ordered token list into runs of consecutive tokens with the same speaker. */
2
+ export declare const group: <Item>(items: ReadonlyArray<Item>, speaker: (item: Item) => unknown) => Item[][];
3
+ export * as SpeakerTurns from "./speaker-turns.js";
@@ -0,0 +1,9 @@
1
+ /** Split an ordered token list into runs of consecutive tokens with the same speaker. */
2
+ export const group = (items, speaker) => items.reduce((turns, item) => {
3
+ const last = turns.at(-1);
4
+ if (last === undefined || speaker(last[0]) !== speaker(item))
5
+ return [...turns, [item]];
6
+ last.push(item);
7
+ return turns;
8
+ }, []);
9
+ export * as SpeakerTurns from "./speaker-turns.js";
@@ -42,6 +42,7 @@ export declare const finish: (route: MediaProtocol.Identity, state: Audio, outpu
42
42
  readonly mediaType: string | undefined;
43
43
  readonly info?: Media.Info;
44
44
  readonly usage?: MediaUsage;
45
+ readonly notices?: ReadonlyArray<Media.Notice>;
45
46
  readonly providerMetadata?: ProviderMetadata;
46
47
  readonly detail?: string;
47
48
  }) => Effect.Effect<ReadonlyArray<SpeechEvent>, AIError>;
@@ -51,6 +51,7 @@ export const finish = (route, state, output) => {
51
51
  SpeechFinishEvent.make({
52
52
  audio: Media.bytes(concatBytes(state.chunks), output.mediaType, { info: output.info }),
53
53
  usage: output.usage,
54
+ notices: output.notices,
54
55
  providerMetadata: output.providerMetadata,
55
56
  }),
56
57
  ]);
@@ -52,6 +52,7 @@ export declare const appendOrStart: <K extends StreamKey>(route: string, tools:
52
52
  readonly id?: string;
53
53
  readonly name?: string;
54
54
  readonly text: string;
55
+ readonly providerMetadata?: ProviderMetadata;
55
56
  }, missingToolMessage: string) => AppendOutcome<K> | AIError;
56
57
  /**
57
58
  * Append argument text to a started tool. Returns `undefined` when no tool is
@@ -235,8 +236,8 @@ export declare const finish: <K extends StreamKey>(route: string, tools: State<K
235
236
  } | {
236
237
  readonly id: string;
237
238
  readonly type: "tool-error";
238
- readonly name: string;
239
239
  readonly message: string;
240
+ readonly name: string;
240
241
  readonly error?: unknown;
241
242
  readonly providerMetadata?: {
242
243
  readonly [x: string]: {
@@ -458,8 +459,8 @@ export declare const finishWithInput: <K extends StreamKey>(route: string, tools
458
459
  } | {
459
460
  readonly id: string;
460
461
  readonly type: "tool-error";
461
- readonly name: string;
462
462
  readonly message: string;
463
+ readonly name: string;
463
464
  readonly error?: unknown;
464
465
  readonly providerMetadata?: {
465
466
  readonly [x: string]: {
@@ -678,8 +679,8 @@ export declare const finishAll: <K extends StreamKey>(route: string, tools: Stat
678
679
  } | {
679
680
  readonly id: string;
680
681
  readonly type: "tool-error";
681
- readonly name: string;
682
682
  readonly message: string;
683
+ readonly name: string;
683
684
  readonly error?: unknown;
684
685
  readonly providerMetadata?: {
685
686
  readonly [x: string]: {
@@ -87,7 +87,7 @@ export const appendOrStart = (route, tools, key, delta, missingToolMessage) => {
87
87
  namespace: current?.namespace,
88
88
  input: `${current?.input ?? ""}${delta.text}`,
89
89
  providerExecuted: current?.providerExecuted,
90
- providerMetadata: current?.providerMetadata,
90
+ providerMetadata: current?.providerMetadata ?? delta.providerMetadata,
91
91
  };
92
92
  if (current && delta.text.length === 0 && current.id === id && current.name === name)
93
93
  return { tools, tool: current, events: [] };
@@ -67,8 +67,8 @@ export declare const protocol: Protocol<{
67
67
  } | {
68
68
  readonly type: "input_file";
69
69
  readonly filename: string;
70
- readonly file_data?: string | undefined;
71
70
  readonly detail?: string | undefined;
71
+ readonly file_data?: string | undefined;
72
72
  readonly file_url?: string | undefined;
73
73
  } | {
74
74
  readonly type: "input_text";
@@ -104,8 +104,8 @@ export declare const protocol: Protocol<{
104
104
  } | {
105
105
  readonly type: "input_file";
106
106
  readonly filename: string;
107
- readonly file_data?: string | undefined;
108
107
  readonly detail?: string | undefined;
108
+ readonly file_data?: string | undefined;
109
109
  readonly file_url?: string | undefined;
110
110
  } | {
111
111
  readonly type: "input_text";
@@ -184,21 +184,10 @@ export declare const protocol: Protocol<{
184
184
  readonly [x: string]: unknown;
185
185
  readonly type: string;
186
186
  readonly status?: unknown;
187
- readonly code?: unknown;
188
187
  readonly message?: unknown;
188
+ readonly code?: unknown;
189
189
  readonly text?: string | undefined;
190
190
  readonly error?: unknown;
191
- readonly item?: {
192
- readonly [x: string]: unknown;
193
- readonly type: string;
194
- readonly id?: string | undefined;
195
- readonly name?: string | undefined;
196
- readonly namespace?: string | undefined;
197
- readonly arguments?: string | undefined;
198
- readonly encrypted_content?: string | null | undefined;
199
- readonly call_id?: string | undefined;
200
- } | null | undefined;
201
- readonly delta?: string | undefined;
202
191
  readonly response?: {
203
192
  readonly [x: string]: unknown;
204
193
  readonly id?: string | undefined;
@@ -230,6 +219,17 @@ export declare const protocol: Protocol<{
230
219
  readonly reason?: string | undefined;
231
220
  } | null | undefined;
232
221
  } | undefined;
222
+ readonly item?: {
223
+ readonly [x: string]: unknown;
224
+ readonly type: string;
225
+ readonly id?: string | undefined;
226
+ readonly name?: string | undefined;
227
+ readonly namespace?: string | undefined;
228
+ readonly arguments?: string | undefined;
229
+ readonly encrypted_content?: string | null | undefined;
230
+ readonly call_id?: string | undefined;
231
+ } | null | undefined;
232
+ readonly delta?: string | undefined;
233
233
  readonly arguments?: string | undefined;
234
234
  readonly item_id?: string | undefined;
235
235
  readonly output_index?: number | undefined;
@@ -36,6 +36,12 @@ const STATUS = {
36
36
  failed: "failed",
37
37
  expired: "expired",
38
38
  };
39
+ // Documented video error codes; `service_unavailable`, `internal_error`, and unknown codes are provider-side.
40
+ const FAILURE = {
41
+ invalid_argument: "InvalidRequest",
42
+ failed_precondition: "InvalidRequest",
43
+ permission_denied: "Authentication",
44
+ };
39
45
  // ---------------------------------------------------------------------------
40
46
  // 5. Request body construction
41
47
  // ---------------------------------------------------------------------------
@@ -88,7 +94,7 @@ const decodeResult = Effect.fn("XAIVideo.decodeResult")(function* (response, con
88
94
  if (status === "failed") {
89
95
  const code = decoded.error?.code ?? undefined;
90
96
  const message = decoded.error?.message ?? undefined;
91
- return yield* output.ended("failed", `${route.name} generation failed${code === undefined ? "" : ` (${code})`}${message === undefined ? "" : `: ${message}`}`);
97
+ return yield* output.ended("failed", `${route.name} generation failed${code === undefined ? "" : ` (${code})`}${message === undefined ? "" : `: ${message}`}`, MediaProtocol.failure(FAILURE, code));
92
98
  }
93
99
  if (status !== "completed")
94
100
  return yield* output.ended("expired", `${route.name} request ${context.token.requestID} expired`);
@@ -79,12 +79,17 @@ export declare const protocol: Protocol<{
79
79
  readonly ttl?: string | undefined;
80
80
  } | undefined;
81
81
  readonly tool_calls?: readonly {
82
- readonly id: string;
83
- readonly type: "function";
84
82
  readonly function: {
85
83
  readonly name: string;
86
84
  readonly arguments: string;
87
85
  };
86
+ readonly id: string;
87
+ readonly type: "function";
88
+ readonly extra_content?: {
89
+ readonly google: {
90
+ readonly thought_signature: string;
91
+ };
92
+ } | undefined;
88
93
  }[] | undefined;
89
94
  readonly reasoning_details?: unknown;
90
95
  } | {
@@ -185,6 +190,7 @@ export declare const protocol: Protocol<{
185
190
  } | null | undefined;
186
191
  readonly id?: string | null | undefined;
187
192
  readonly index?: number | null | undefined;
193
+ readonly extra_content?: unknown;
188
194
  }[] | null | undefined;
189
195
  readonly reasoning_details?: unknown;
190
196
  } | null | undefined;
@@ -2,6 +2,12 @@ import { AIError, type HttpContext, type HttpRateLimitDetails } from "./schema/i
2
2
  export declare const isContextOverflow: (message: string) => boolean;
3
3
  export declare const isPayloadTooLarge: (message: string) => boolean;
4
4
  export declare const isContextOverflowFailure: (failure: unknown) => boolean;
5
+ /**
6
+ * Whether a failed call may succeed when sent again: rate limits, provider-side failures, transport failures that did
7
+ * not deliver an accepted write, and unrecognized failures. Callers decide which calls are safe to repeat.
8
+ */
9
+ export declare const isRetryable: (error: AIError) => boolean;
10
+ export declare const providerErrorMessage: (body: string) => string | undefined;
5
11
  export interface ProviderFailure {
6
12
  readonly message: string;
7
13
  readonly status?: number | undefined;
@@ -1,4 +1,4 @@
1
- import { Option, Schema } from "effect";
1
+ import { Option, Schema, SchemaGetter } from "effect";
2
2
  import { AuthenticationError, ContentPolicyError, InvalidRequestError, AIError, ProviderErrorEvent, ProviderInternalError, QuotaExceededError, RateLimitError, UnknownProviderError, } from "./schema/index.js";
3
3
  const patterns = [
4
4
  /prompt is too long/i,
@@ -37,6 +37,46 @@ export const isPayloadTooLarge = (message) => payloadPatterns.some((pattern) =>
37
37
  export const isContextOverflowFailure = (failure) => failure instanceof AIError
38
38
  ? failure.reason._tag === "InvalidRequest" && failure.reason.classification === "context-overflow"
39
39
  : Schema.is(ProviderErrorEvent)(failure) && failure.classification === "context-overflow";
40
+ /**
41
+ * Whether a failed call may succeed when sent again: rate limits, provider-side failures, transport failures that did
42
+ * not deliver an accepted write, and unrecognized failures. Callers decide which calls are safe to repeat.
43
+ */
44
+ export const isRetryable = (error) => {
45
+ const override = error.reason.http?.headers["x-should-retry"];
46
+ if (override === "true")
47
+ return true;
48
+ if (override === "false")
49
+ return false;
50
+ switch (error.reason._tag) {
51
+ case "RateLimit":
52
+ case "ProviderInternal":
53
+ return true;
54
+ // A WebSocket acknowledgment marks delivery accepted before model output may exist.
55
+ // Read failures can still recover; the caller chooses retry versus continuation from durable output.
56
+ case "Transport":
57
+ return (error.reason.delivery !== "rejected" &&
58
+ (error.reason.delivery !== "accepted" || error.reason.operation === "read"));
59
+ case "InvalidProviderOutput":
60
+ return error.reason.classification === "incomplete-stream";
61
+ // Unrecognized failures retry: classification records affirmative
62
+ // deterministic evidence, and transient failures are exactly the ones
63
+ // that arrive in shapes no classifier anticipates.
64
+ case "UnknownProvider":
65
+ return true;
66
+ case "Authentication":
67
+ case "QuotaExceeded":
68
+ case "ContentPolicy":
69
+ case "InvalidRequest":
70
+ case "UnsupportedOperation":
71
+ case "NoRoute":
72
+ case "Timeout":
73
+ return false;
74
+ default: {
75
+ const exhaustive = error.reason;
76
+ return exhaustive;
77
+ }
78
+ }
79
+ };
40
80
  const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
41
81
  // OpenCode Zen reports account caps as typed 429/402 errors that are not throttles.
42
82
  const QUOTA_CODES = new Set([
@@ -47,7 +87,8 @@ const QUOTA_CODES = new Set([
47
87
  "freeusagelimiterror",
48
88
  "creditlimitexceeded",
49
89
  ]);
50
- const AUTH_CODES = new Set(["authentication_error", "permission_error"]);
90
+ // Google reports an invalid API key as HTTP 400 INVALID_ARGUMENT with this `details[].reason`.
91
+ const AUTH_CODES = new Set(["authentication_error", "permission_error", "api_key_invalid"]);
51
92
  const SERVER_CODES = new Set([
52
93
  "api_error",
53
94
  "internal_error",
@@ -89,6 +130,24 @@ const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded|budget exceede
89
130
  // under `invalid_request_error`.
90
131
  const CONTENT_POLICY_TEXT = /violating our usage policy|blocked by content filtering policy|content[-_\s]?policy|rejected as a result of our safety system/i;
91
132
  const SERVER_ERROR_TEXT = /\b(?:try again|(?:please |you can )?retry (?:the |this |your )?request|try (?:the |this |your )?request again|(?:currently |temporarily )?at capacity|overloaded|temporarily unavailable|service[-_\s]?unavailable|(?:server|internal)[-_\s]?error|server (?:is )?busy|provider returned (?:an )?error|resource[-_\s]?exhausted|upstream (?:connect|connection|request)|request buffer limit while retrying upstream)\b/i;
133
+ const Message = Schema.String.check(Schema.isPattern(/\S/));
134
+ const messageAt = (fields, message) => Schema.Struct(fields).pipe(Schema.decodeTo(Schema.String, {
135
+ decode: SchemaGetter.transform(message),
136
+ encode: SchemaGetter.forbidden(() => "Provider error messages are decode-only"),
137
+ }));
138
+ // Common error body layouts that carry a human-readable message, in priority order.
139
+ // Provider-specific layouts belong in their protocol.
140
+ const decodeMessage = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Union([
141
+ messageAt({ error: Schema.Struct({ message: Message }) }, (body) => body.error.message),
142
+ messageAt({ error: Message }, (body) => body.error),
143
+ messageAt({ message: Message }, (body) => body.message),
144
+ // AWS services
145
+ messageAt({ Message: Message }, (body) => body.Message),
146
+ // RFC 9457 problem details
147
+ messageAt({ detail: Message }, (body) => body.detail),
148
+ messageAt({ errors: Schema.NonEmptyArray(Schema.Struct({ message: Message })) }, (body) => body.errors[0].message),
149
+ ])));
150
+ export const providerErrorMessage = (body) => Option.getOrUndefined(decodeMessage(body));
92
151
  // Classification records affirmative evidence about a failure. Deterministic
93
152
  // failures need positive identification (a 4xx status, quota/auth/policy
94
153
  // signals); anything unrecognized stays UnknownProvider, which the session
@@ -171,6 +230,10 @@ function providerCodes(value) {
171
230
  error?.type,
172
231
  error?.status,
173
232
  error?.error_type,
233
+ // Google `google.rpc.ErrorInfo` details carry the specific reason.
234
+ ...(Array.isArray(error?.details)
235
+ ? error.details.map((detail) => (isRecord(detail) ? detail.reason : undefined))
236
+ : []),
174
237
  inner?.code,
175
238
  metadata?.error_type,
176
239
  responseError?.code,
@@ -79,12 +79,17 @@ export declare const routes: (Route<{
79
79
  readonly ttl?: string | undefined;
80
80
  } | undefined;
81
81
  readonly tool_calls?: readonly {
82
- readonly id: string;
83
- readonly type: "function";
84
82
  readonly function: {
85
83
  readonly name: string;
86
84
  readonly arguments: string;
87
85
  };
86
+ readonly id: string;
87
+ readonly type: "function";
88
+ readonly extra_content?: {
89
+ readonly google: {
90
+ readonly thought_signature: string;
91
+ };
92
+ } | undefined;
88
93
  }[] | undefined;
89
94
  readonly reasoning_details?: unknown;
90
95
  } | {
@@ -482,8 +487,8 @@ export declare const routes: (Route<{
482
487
  } | {
483
488
  readonly type: "input_file";
484
489
  readonly filename: string;
485
- readonly file_data?: string | undefined;
486
490
  readonly detail?: string | undefined;
491
+ readonly file_data?: string | undefined;
487
492
  readonly file_url?: string | undefined;
488
493
  } | {
489
494
  readonly type: "input_text";
@@ -519,8 +524,8 @@ export declare const routes: (Route<{
519
524
  } | {
520
525
  readonly type: "input_file";
521
526
  readonly filename: string;
522
- readonly file_data?: string | undefined;
523
527
  readonly detail?: string | undefined;
528
+ readonly file_data?: string | undefined;
524
529
  readonly file_url?: string | undefined;
525
530
  } | {
526
531
  readonly type: "input_text";
@@ -82,12 +82,17 @@ export declare const routes: (Route<{
82
82
  readonly ttl?: string | undefined;
83
83
  } | undefined;
84
84
  readonly tool_calls?: readonly {
85
- readonly id: string;
86
- readonly type: "function";
87
85
  readonly function: {
88
86
  readonly name: string;
89
87
  readonly arguments: string;
90
88
  };
89
+ readonly id: string;
90
+ readonly type: "function";
91
+ readonly extra_content?: {
92
+ readonly google: {
93
+ readonly thought_signature: string;
94
+ };
95
+ } | undefined;
91
96
  }[] | undefined;
92
97
  readonly reasoning_details?: unknown;
93
98
  } | {
@@ -203,8 +208,8 @@ export declare const routes: (Route<{
203
208
  } | {
204
209
  readonly type: "input_file";
205
210
  readonly filename: string;
206
- readonly file_data?: string | undefined;
207
211
  readonly detail?: string | undefined;
212
+ readonly file_data?: string | undefined;
208
213
  readonly file_url?: string | undefined;
209
214
  } | {
210
215
  readonly type: "input_text";
@@ -240,8 +245,8 @@ export declare const routes: (Route<{
240
245
  } | {
241
246
  readonly type: "input_file";
242
247
  readonly filename: string;
243
- readonly file_data?: string | undefined;
244
248
  readonly detail?: string | undefined;
249
+ readonly file_data?: string | undefined;
245
250
  readonly file_url?: string | undefined;
246
251
  } | {
247
252
  readonly type: "input_text";
@@ -2,7 +2,8 @@ import { AnthropicMessages } from "../protocols/anthropic-messages.js";
2
2
  import { Auth } from "../route/auth.js";
3
3
  import { ProviderConfigurationError, ProviderID } from "../schema/index.js";
4
4
  export const id = ProviderID.make("anthropic-compatible");
5
- export const routes = [AnthropicMessages.route];
5
+ const compatibleRoute = AnthropicMessages.route.with({ id: "anthropic-compatible-messages", provider: id });
6
+ export const routes = [compatibleRoute];
6
7
  const auth = (input) => {
7
8
  if ("auth" in input && input.auth)
8
9
  return input.auth;
@@ -16,7 +17,7 @@ export const configure = (input) => {
16
17
  message: "Anthropic-compatible providers require a baseURL",
17
18
  });
18
19
  const { provider: _, baseURL, apiKey: _apiKey, auth: _auth, ...rest } = input;
19
- const route = AnthropicMessages.route.with({
20
+ const route = (provider === "anthropic" ? AnthropicMessages.route : compatibleRoute).with({
20
21
  ...rest,
21
22
  provider,
22
23
  endpoint: { baseURL },
@@ -93,8 +93,8 @@ declare const responsesRoute: Route<{
93
93
  } | {
94
94
  readonly type: "input_file";
95
95
  readonly filename: string;
96
- readonly file_data?: string | undefined;
97
96
  readonly detail?: string | undefined;
97
+ readonly file_data?: string | undefined;
98
98
  readonly file_url?: string | undefined;
99
99
  } | {
100
100
  readonly type: "input_text";
@@ -130,8 +130,8 @@ declare const responsesRoute: Route<{
130
130
  } | {
131
131
  readonly type: "input_file";
132
132
  readonly filename: string;
133
- readonly file_data?: string | undefined;
134
133
  readonly detail?: string | undefined;
134
+ readonly file_data?: string | undefined;
135
135
  readonly file_url?: string | undefined;
136
136
  } | {
137
137
  readonly type: "input_text";
@@ -307,12 +307,17 @@ export declare const routes: (Route<{
307
307
  readonly ttl?: string | undefined;
308
308
  } | undefined;
309
309
  readonly tool_calls?: readonly {
310
- readonly id: string;
311
- readonly type: "function";
312
310
  readonly function: {
313
311
  readonly name: string;
314
312
  readonly arguments: string;
315
313
  };
314
+ readonly id: string;
315
+ readonly type: "function";
316
+ readonly extra_content?: {
317
+ readonly google: {
318
+ readonly thought_signature: string;
319
+ };
320
+ } | undefined;
316
321
  }[] | undefined;
317
322
  readonly reasoning_details?: unknown;
318
323
  } | {
@@ -433,8 +438,8 @@ export declare const routes: (Route<{
433
438
  } | {
434
439
  readonly type: "input_file";
435
440
  readonly filename: string;
436
- readonly file_data?: string | undefined;
437
441
  readonly detail?: string | undefined;
442
+ readonly file_data?: string | undefined;
438
443
  readonly file_url?: string | undefined;
439
444
  } | {
440
445
  readonly type: "input_text";
@@ -470,8 +475,8 @@ export declare const routes: (Route<{
470
475
  } | {
471
476
  readonly type: "input_file";
472
477
  readonly filename: string;
473
- readonly file_data?: string | undefined;
474
478
  readonly detail?: string | undefined;
479
+ readonly file_data?: string | undefined;
475
480
  readonly file_url?: string | undefined;
476
481
  } | {
477
482
  readonly type: "input_text";
@@ -68,12 +68,17 @@ export declare const route: Route<{
68
68
  readonly ttl?: string | undefined;
69
69
  } | undefined;
70
70
  readonly tool_calls?: readonly {
71
- readonly id: string;
72
- readonly type: "function";
73
71
  readonly function: {
74
72
  readonly name: string;
75
73
  readonly arguments: string;
76
74
  };
75
+ readonly id: string;
76
+ readonly type: "function";
77
+ readonly extra_content?: {
78
+ readonly google: {
79
+ readonly thought_signature: string;
80
+ };
81
+ } | undefined;
77
82
  }[] | undefined;
78
83
  readonly reasoning_details?: unknown;
79
84
  } | {
@@ -179,12 +184,17 @@ export declare const routes: Route<{
179
184
  readonly ttl?: string | undefined;
180
185
  } | undefined;
181
186
  readonly tool_calls?: readonly {
182
- readonly id: string;
183
- readonly type: "function";
184
187
  readonly function: {
185
188
  readonly name: string;
186
189
  readonly arguments: string;
187
190
  };
191
+ readonly id: string;
192
+ readonly type: "function";
193
+ readonly extra_content?: {
194
+ readonly google: {
195
+ readonly thought_signature: string;
196
+ };
197
+ } | undefined;
188
198
  }[] | undefined;
189
199
  readonly reasoning_details?: unknown;
190
200
  } | {
@@ -68,12 +68,17 @@ export declare const route: Route<{
68
68
  readonly ttl?: string | undefined;
69
69
  } | undefined;
70
70
  readonly tool_calls?: readonly {
71
- readonly id: string;
72
- readonly type: "function";
73
71
  readonly function: {
74
72
  readonly name: string;
75
73
  readonly arguments: string;
76
74
  };
75
+ readonly id: string;
76
+ readonly type: "function";
77
+ readonly extra_content?: {
78
+ readonly google: {
79
+ readonly thought_signature: string;
80
+ };
81
+ } | undefined;
77
82
  }[] | undefined;
78
83
  readonly reasoning_details?: unknown;
79
84
  } | {
@@ -179,12 +184,17 @@ export declare const routes: Route<{
179
184
  readonly ttl?: string | undefined;
180
185
  } | undefined;
181
186
  readonly tool_calls?: readonly {
182
- readonly id: string;
183
- readonly type: "function";
184
187
  readonly function: {
185
188
  readonly name: string;
186
189
  readonly arguments: string;
187
190
  };
191
+ readonly id: string;
192
+ readonly type: "function";
193
+ readonly extra_content?: {
194
+ readonly google: {
195
+ readonly thought_signature: string;
196
+ };
197
+ } | undefined;
188
198
  }[] | undefined;
189
199
  readonly reasoning_details?: unknown;
190
200
  } | {