@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.
- package/README.md +24 -12
- package/dist/cache-policy.js +7 -0
- package/dist/experimental/evaluation.d.ts +7 -0
- package/dist/experimental/evaluation.js +2 -0
- package/dist/experimental/system-one.js +10 -7
- package/dist/generation.d.ts +1 -1
- package/dist/generation.js +23 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/llm.d.ts +1 -1
- package/dist/promise.d.ts +3 -3
- package/dist/promise.js +4 -3
- package/dist/protocols/alibaba-chat.d.ts +8 -2
- package/dist/protocols/alibaba-responses.d.ts +14 -14
- package/dist/protocols/anthropic-messages.js +2 -15
- package/dist/protocols/bedrock-converse.d.ts +1 -1
- package/dist/protocols/bedrock-converse.js +38 -12
- package/dist/protocols/deepgram-speech.js +9 -7
- package/dist/protocols/deepgram-transcription.js +4 -10
- package/dist/protocols/elevenlabs-transcription.d.ts +28 -0
- package/dist/protocols/elevenlabs-transcription.js +137 -0
- package/dist/protocols/gemini.js +1 -10
- package/dist/protocols/google-speech.js +9 -2
- package/dist/protocols/google-transcription.js +4 -0
- package/dist/protocols/google-video.js +11 -2
- package/dist/protocols/meta-messages.d.ts +5 -1
- package/dist/protocols/meta-messages.js +11 -4
- package/dist/protocols/meta-responses.d.ts +16 -16
- package/dist/protocols/mistral-chat.d.ts +6 -0
- package/dist/protocols/mistral-chat.js +8 -5
- package/dist/protocols/open-responses.d.ts +31 -31
- package/dist/protocols/openai-chat.d.ts +53 -10
- package/dist/protocols/openai-chat.js +21 -8
- package/dist/protocols/openai-compatible-chat.d.ts +7 -2
- package/dist/protocols/openai-compatible-responses.d.ts +2 -2
- package/dist/protocols/openai-responses.d.ts +22 -22
- package/dist/protocols/openai-speech.js +6 -1
- package/dist/protocols/openai-transcription.js +1 -1
- package/dist/protocols/runway-video.js +2 -1
- package/dist/protocols/utils/claude-model.d.ts +8 -0
- package/dist/protocols/utils/claude-model.js +15 -0
- package/dist/protocols/utils/gemini-generate-content.d.ts +6 -0
- package/dist/protocols/utils/gemini-generate-content.js +33 -0
- package/dist/protocols/utils/media-input.d.ts +4 -3
- package/dist/protocols/utils/media-input.js +5 -4
- package/dist/protocols/utils/speaker-turns.d.ts +3 -0
- package/dist/protocols/utils/speaker-turns.js +9 -0
- package/dist/protocols/utils/speech-stream.d.ts +1 -0
- package/dist/protocols/utils/speech-stream.js +1 -0
- package/dist/protocols/utils/tool-stream.d.ts +4 -3
- package/dist/protocols/utils/tool-stream.js +1 -1
- package/dist/protocols/xai-responses.d.ts +14 -14
- package/dist/protocols/xai-video.js +7 -1
- package/dist/protocols/zai-chat.d.ts +8 -2
- package/dist/provider-error.d.ts +6 -0
- package/dist/provider-error.js +65 -2
- package/dist/providers/alibaba.d.ts +9 -4
- package/dist/providers/amazon-bedrock-mantle.d.ts +9 -4
- package/dist/providers/anthropic-compatible.js +3 -2
- package/dist/providers/azure.d.ts +11 -6
- package/dist/providers/baseten.d.ts +14 -4
- package/dist/providers/cerebras.d.ts +14 -4
- package/dist/providers/cloudflare-ai-gateway.d.ts +18 -8
- package/dist/providers/cloudflare-workers-ai.d.ts +14 -4
- package/dist/providers/deepinfra.d.ts +14 -4
- package/dist/providers/deepseek.d.ts +14 -4
- package/dist/providers/elevenlabs.d.ts +5 -0
- package/dist/providers/elevenlabs.js +4 -0
- package/dist/providers/fireworks.d.ts +14 -4
- package/dist/providers/google-vertex-chat.d.ts +7 -2
- package/dist/providers/google-vertex-responses.d.ts +2 -2
- package/dist/providers/groq.d.ts +15 -4
- package/dist/providers/meta.d.ts +14 -5
- package/dist/providers/minimax.d.ts +9 -4
- package/dist/providers/moonshot.d.ts +9 -4
- package/dist/providers/openai-compatible-responses.d.ts +2 -2
- package/dist/providers/openai-compatible.d.ts +7 -2
- package/dist/providers/openai.d.ts +9 -4
- package/dist/providers/openrouter.d.ts +27 -6
- package/dist/providers/togetherai.d.ts +14 -4
- package/dist/providers/xai.d.ts +7 -2
- package/dist/providers/xai.js +3 -3
- package/dist/providers/zai-coding-plan.d.ts +9 -4
- package/dist/providers/zai.d.ts +7 -2
- package/dist/route/client.d.ts +1 -1
- package/dist/route/executor.js +12 -8
- package/dist/route/media-protocol.d.ts +13 -3
- package/dist/route/media-protocol.js +16 -6
- package/dist/route/media.js +18 -4
- package/dist/schema/events.d.ts +30 -30
- package/dist/testing.d.ts +8 -8
- package/dist/transcription.d.ts +1 -1
- package/dist/transcription.js +1 -1
- 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
|
|
25
|
-
* `repeatArrays`. `overlay` keys in `reserved` are dropped so `http.body` cannot replace
|
|
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?:
|
|
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
|
|
47
|
-
* `repeatArrays`. `overlay` keys in `reserved` are dropped so `http.body` cannot replace
|
|
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}[]
|
|
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,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;
|
package/dist/provider-error.d.ts
CHANGED
|
@@ -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;
|
package/dist/provider-error.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
} | {
|