@opencode/ai 2.0.17 → 2.0.19
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 +1 -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/promise.d.ts +1 -1
- package/dist/promise.js +4 -3
- package/dist/protocols/alibaba-chat.d.ts +8 -2
- 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/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-speech.js +6 -1
- package/dist/protocols/openai-transcription.js +1 -1
- package/dist/protocols/runway-video.js +2 -1
- 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 +1 -0
- package/dist/protocols/utils/tool-stream.js +1 -1
- package/dist/protocols/xai-video.js +7 -1
- package/dist/protocols/zai-chat.d.ts +8 -2
- package/dist/provider-error.d.ts +5 -0
- package/dist/provider-error.js +46 -1
- package/dist/providers/alibaba.d.ts +7 -2
- package/dist/providers/amazon-bedrock-mantle.d.ts +7 -2
- package/dist/providers/anthropic-compatible.js +3 -2
- package/dist/providers/azure.d.ts +7 -2
- package/dist/providers/baseten.d.ts +14 -4
- package/dist/providers/cerebras.d.ts +14 -4
- package/dist/providers/cloudflare-ai-gateway.d.ts +14 -4
- 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/groq.d.ts +15 -4
- package/dist/providers/meta.d.ts +7 -2
- package/dist/providers/minimax.d.ts +7 -2
- package/dist/providers/moonshot.d.ts +7 -2
- package/dist/providers/openai-compatible.d.ts +7 -2
- package/dist/providers/openai.d.ts +7 -2
- 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 +7 -2
- package/dist/providers/zai.d.ts +7 -2
- 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/transcription.d.ts +1 -1
- package/dist/transcription.js +1 -1
- package/package.json +3 -3
|
@@ -49,9 +49,15 @@ const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
|
|
|
49
49
|
return yield* blocked;
|
|
50
50
|
const audio = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.inlineData === undefined ? [] : [part.inlineData]);
|
|
51
51
|
const next = { ...GeminiGenerateContent.track(state, chunk), mimeType: state.mimeType ?? audio[0]?.mimeType };
|
|
52
|
-
|
|
52
|
+
const events = audio.flatMap((part) => SpeechStream.delta(next, part.data)[1]);
|
|
53
|
+
const withheld = next.chunks.length === 0 ? GeminiGenerateContent.withheld(route.name, chunk, frame) : undefined;
|
|
54
|
+
if (withheld !== undefined)
|
|
55
|
+
return yield* withheld;
|
|
56
|
+
return [next, events];
|
|
53
57
|
});
|
|
54
58
|
const finish = (state, context) => {
|
|
59
|
+
if (state.finishReason === undefined)
|
|
60
|
+
return Effect.fail(route.incomplete());
|
|
55
61
|
const sampleRate = SpeechStream.sampleRate(state.mimeType) ?? DEFAULT_SAMPLE_RATE;
|
|
56
62
|
const output = state.mimeType?.split(";")[0]?.toLowerCase() === "audio/wav"
|
|
57
63
|
? SpeechStream.container("wav", sampleRate)
|
|
@@ -61,8 +67,9 @@ const finish = (state, context) => {
|
|
|
61
67
|
return SpeechStream.finish(route, state, {
|
|
62
68
|
...output,
|
|
63
69
|
usage: GeminiGenerateContent.usage(state.usage),
|
|
70
|
+
notices: GeminiGenerateContent.notices(route.name, state),
|
|
64
71
|
providerMetadata: GeminiGenerateContent.providerMetadata(state),
|
|
65
|
-
detail:
|
|
72
|
+
detail: `finish reason: ${state.finishReason}`,
|
|
66
73
|
});
|
|
67
74
|
};
|
|
68
75
|
// ---------------------------------------------------------------------------
|
|
@@ -85,6 +85,9 @@ const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
|
|
|
85
85
|
.filter((item) => item.length > 0)
|
|
86
86
|
.join(" ");
|
|
87
87
|
const delta = text.length === 0 || state.text.length === 0 ? text : ` ${text}`;
|
|
88
|
+
const withheld = state.text.length + delta.length === 0 ? GeminiGenerateContent.withheld(route.name, chunk, frame) : undefined;
|
|
89
|
+
if (withheld !== undefined)
|
|
90
|
+
return yield* withheld;
|
|
88
91
|
const events = [
|
|
89
92
|
...(delta.length === 0 ? [] : [TranscriptionTextDeltaEvent.make({ delta })]),
|
|
90
93
|
...segments.map((segment) => TranscriptionSegmentEvent.make({ segment })),
|
|
@@ -100,6 +103,7 @@ const finish = (state) => {
|
|
|
100
103
|
segments: state.segments.length === 0 ? undefined : state.segments,
|
|
101
104
|
words: state.words.length === 0 ? undefined : state.words,
|
|
102
105
|
usage: GeminiGenerateContent.usage(state.usage),
|
|
106
|
+
notices: GeminiGenerateContent.notices(route.name, state),
|
|
103
107
|
providerMetadata: GeminiGenerateContent.providerMetadata(state),
|
|
104
108
|
}),
|
|
105
109
|
]);
|
|
@@ -17,7 +17,7 @@ export const Token = Schema.Struct({ operation: Schema.String });
|
|
|
17
17
|
const StartResponse = Schema.Struct({ name: Schema.String });
|
|
18
18
|
const Operation = Schema.Struct({
|
|
19
19
|
done: Schema.optional(Schema.Boolean),
|
|
20
|
-
error: Schema.optional(Schema.Struct({ message: Schema.optional(Schema.String) })),
|
|
20
|
+
error: Schema.optional(Schema.Struct({ code: Schema.optional(Schema.Number), message: Schema.optional(Schema.String) })),
|
|
21
21
|
response: Schema.optional(Schema.Struct({
|
|
22
22
|
generateVideoResponse: Schema.optional(Schema.Struct({
|
|
23
23
|
generatedSamples: optionalArray(Schema.Struct({
|
|
@@ -32,6 +32,15 @@ const Operation = Schema.Struct({
|
|
|
32
32
|
})),
|
|
33
33
|
metadata: Schema.optional(Schema.Unknown),
|
|
34
34
|
});
|
|
35
|
+
// Operation errors are `google.rpc.Status`; unlisted codes (INTERNAL, UNAVAILABLE, ...) are provider-side.
|
|
36
|
+
const FAILURE = {
|
|
37
|
+
3: "InvalidRequest", // INVALID_ARGUMENT
|
|
38
|
+
7: "Authentication", // PERMISSION_DENIED
|
|
39
|
+
8: "RateLimit", // RESOURCE_EXHAUSTED
|
|
40
|
+
9: "InvalidRequest", // FAILED_PRECONDITION
|
|
41
|
+
11: "InvalidRequest", // OUT_OF_RANGE
|
|
42
|
+
16: "Authentication", // UNAUTHENTICATED
|
|
43
|
+
};
|
|
35
44
|
// ---------------------------------------------------------------------------
|
|
36
45
|
// 5. Request body construction
|
|
37
46
|
// ---------------------------------------------------------------------------
|
|
@@ -92,7 +101,7 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
|
|
|
92
101
|
if (status === "running")
|
|
93
102
|
return yield* output.pending(context.token.operation);
|
|
94
103
|
if (status === "failed")
|
|
95
|
-
return yield* output.ended("failed", `${route.name} operation failed${operation.error?.message === undefined ? "" : `: ${operation.error.message}`}
|
|
104
|
+
return yield* output.ended("failed", `${route.name} operation failed${operation.error?.message === undefined ? "" : `: ${operation.error.message}`}`, MediaProtocol.failure(FAILURE, operation.error?.code));
|
|
96
105
|
const generated = operation.response?.generateVideoResponse;
|
|
97
106
|
// Downloads require the same API key as the poll; the asset carries it transiently and follows the redirect.
|
|
98
107
|
const videos = yield* Effect.forEach((generated?.generatedSamples ?? []).flatMap((sample) => sample.video?.uri === undefined ? [] : [{ uri: sample.video.uri, mimeType: sample.video.mimeType }]), (video) => MediaProtocol.expiringUrl(video.uri, FILE_RETENTION, {
|
|
@@ -12,6 +12,11 @@ declare const OpenAIChatCacheControl: Schema.Struct<{
|
|
|
12
12
|
readonly type: Schema.Literal<"ephemeral">;
|
|
13
13
|
readonly ttl: Schema.optional<Schema.String>;
|
|
14
14
|
}>;
|
|
15
|
+
declare const ExtraContent: Schema.Struct<{
|
|
16
|
+
readonly google: Schema.Struct<{
|
|
17
|
+
readonly thought_signature: Schema.String;
|
|
18
|
+
}>;
|
|
19
|
+
}>;
|
|
15
20
|
declare const ReasoningDetail: Schema.Union<readonly [Schema.StructWithRest<Schema.Struct<{
|
|
16
21
|
readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
17
22
|
readonly format: Schema.optional<Schema.String>;
|
|
@@ -97,6 +102,11 @@ export declare const bodyFields: {
|
|
|
97
102
|
readonly name: Schema.String;
|
|
98
103
|
readonly arguments: Schema.String;
|
|
99
104
|
}>;
|
|
105
|
+
readonly extra_content: Schema.optional<Schema.Struct<{
|
|
106
|
+
readonly google: Schema.Struct<{
|
|
107
|
+
readonly thought_signature: Schema.String;
|
|
108
|
+
}>;
|
|
109
|
+
}>>;
|
|
100
110
|
}>>>;
|
|
101
111
|
readonly reasoning_content: Schema.optional<Schema.String>;
|
|
102
112
|
readonly reasoning: Schema.optional<Schema.String>;
|
|
@@ -205,6 +215,11 @@ declare const OpenAIChatBody: Schema.Struct<{
|
|
|
205
215
|
readonly name: Schema.String;
|
|
206
216
|
readonly arguments: Schema.String;
|
|
207
217
|
}>;
|
|
218
|
+
readonly extra_content: Schema.optional<Schema.Struct<{
|
|
219
|
+
readonly google: Schema.Struct<{
|
|
220
|
+
readonly thought_signature: Schema.String;
|
|
221
|
+
}>;
|
|
222
|
+
}>>;
|
|
208
223
|
}>>>;
|
|
209
224
|
readonly reasoning_content: Schema.optional<Schema.String>;
|
|
210
225
|
readonly reasoning: Schema.optional<Schema.String>;
|
|
@@ -276,6 +291,7 @@ export declare const OpenAIChatEvent: Schema.StructWithRest<Schema.Struct<{
|
|
|
276
291
|
readonly name: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
277
292
|
readonly arguments: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
278
293
|
}>>>;
|
|
294
|
+
readonly extra_content: Schema.optional<Schema.NullOr<Schema.Unknown>>;
|
|
279
295
|
}>>>>;
|
|
280
296
|
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
|
|
281
297
|
readonly finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
@@ -323,6 +339,7 @@ interface PendingToolDelta {
|
|
|
323
339
|
readonly id?: string;
|
|
324
340
|
readonly name?: string;
|
|
325
341
|
readonly input: string;
|
|
342
|
+
readonly extraContent?: Schema.Schema.Type<typeof ExtraContent>;
|
|
326
343
|
}
|
|
327
344
|
export interface ParserState {
|
|
328
345
|
readonly providerMetadataKey: string;
|
|
@@ -418,12 +435,17 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
|
|
|
418
435
|
readonly ttl?: string | undefined;
|
|
419
436
|
} | undefined;
|
|
420
437
|
readonly tool_calls?: readonly {
|
|
421
|
-
readonly id: string;
|
|
422
|
-
readonly type: "function";
|
|
423
438
|
readonly function: {
|
|
424
439
|
readonly name: string;
|
|
425
440
|
readonly arguments: string;
|
|
426
441
|
};
|
|
442
|
+
readonly id: string;
|
|
443
|
+
readonly type: "function";
|
|
444
|
+
readonly extra_content?: {
|
|
445
|
+
readonly google: {
|
|
446
|
+
readonly thought_signature: string;
|
|
447
|
+
};
|
|
448
|
+
} | undefined;
|
|
427
449
|
}[] | undefined;
|
|
428
450
|
readonly reasoning_details?: unknown;
|
|
429
451
|
} | {
|
|
@@ -527,12 +549,17 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
|
|
|
527
549
|
readonly ttl?: string | undefined;
|
|
528
550
|
} | undefined;
|
|
529
551
|
readonly tool_calls?: readonly {
|
|
530
|
-
readonly id: string;
|
|
531
|
-
readonly type: "function";
|
|
532
552
|
readonly function: {
|
|
533
553
|
readonly name: string;
|
|
534
554
|
readonly arguments: string;
|
|
535
555
|
};
|
|
556
|
+
readonly id: string;
|
|
557
|
+
readonly type: "function";
|
|
558
|
+
readonly extra_content?: {
|
|
559
|
+
readonly google: {
|
|
560
|
+
readonly thought_signature: string;
|
|
561
|
+
};
|
|
562
|
+
} | undefined;
|
|
536
563
|
}[] | undefined;
|
|
537
564
|
readonly reasoning_details?: unknown;
|
|
538
565
|
} | {
|
|
@@ -629,12 +656,17 @@ export declare const protocol: Protocol<{
|
|
|
629
656
|
readonly ttl?: string | undefined;
|
|
630
657
|
} | undefined;
|
|
631
658
|
readonly tool_calls?: readonly {
|
|
632
|
-
readonly id: string;
|
|
633
|
-
readonly type: "function";
|
|
634
659
|
readonly function: {
|
|
635
660
|
readonly name: string;
|
|
636
661
|
readonly arguments: string;
|
|
637
662
|
};
|
|
663
|
+
readonly id: string;
|
|
664
|
+
readonly type: "function";
|
|
665
|
+
readonly extra_content?: {
|
|
666
|
+
readonly google: {
|
|
667
|
+
readonly thought_signature: string;
|
|
668
|
+
};
|
|
669
|
+
} | undefined;
|
|
638
670
|
}[] | undefined;
|
|
639
671
|
readonly reasoning_details?: unknown;
|
|
640
672
|
} | {
|
|
@@ -725,6 +757,7 @@ export declare const protocol: Protocol<{
|
|
|
725
757
|
} | null | undefined;
|
|
726
758
|
readonly id?: string | null | undefined;
|
|
727
759
|
readonly index?: number | null | undefined;
|
|
760
|
+
readonly extra_content?: unknown;
|
|
728
761
|
}[] | null | undefined;
|
|
729
762
|
readonly reasoning_details?: unknown;
|
|
730
763
|
} | null | undefined;
|
|
@@ -808,12 +841,17 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
|
|
|
808
841
|
readonly ttl?: string | undefined;
|
|
809
842
|
} | undefined;
|
|
810
843
|
readonly tool_calls?: readonly {
|
|
811
|
-
readonly id: string;
|
|
812
|
-
readonly type: "function";
|
|
813
844
|
readonly function: {
|
|
814
845
|
readonly name: string;
|
|
815
846
|
readonly arguments: string;
|
|
816
847
|
};
|
|
848
|
+
readonly id: string;
|
|
849
|
+
readonly type: "function";
|
|
850
|
+
readonly extra_content?: {
|
|
851
|
+
readonly google: {
|
|
852
|
+
readonly thought_signature: string;
|
|
853
|
+
};
|
|
854
|
+
} | undefined;
|
|
817
855
|
}[] | undefined;
|
|
818
856
|
readonly reasoning_details?: unknown;
|
|
819
857
|
} | {
|
|
@@ -919,12 +957,17 @@ export declare const route: Route<{
|
|
|
919
957
|
readonly ttl?: string | undefined;
|
|
920
958
|
} | undefined;
|
|
921
959
|
readonly tool_calls?: readonly {
|
|
922
|
-
readonly id: string;
|
|
923
|
-
readonly type: "function";
|
|
924
960
|
readonly function: {
|
|
925
961
|
readonly name: string;
|
|
926
962
|
readonly arguments: string;
|
|
927
963
|
};
|
|
964
|
+
readonly id: string;
|
|
965
|
+
readonly type: "function";
|
|
966
|
+
readonly extra_content?: {
|
|
967
|
+
readonly google: {
|
|
968
|
+
readonly thought_signature: string;
|
|
969
|
+
};
|
|
970
|
+
} | undefined;
|
|
928
971
|
}[] | undefined;
|
|
929
972
|
readonly reasoning_details?: unknown;
|
|
930
973
|
} | {
|
|
@@ -41,6 +41,13 @@ const OpenAIChatTool = Schema.Struct({
|
|
|
41
41
|
}),
|
|
42
42
|
cache_control: Schema.optional(OpenAIChatCacheControl),
|
|
43
43
|
});
|
|
44
|
+
// Gemini's OpenAI-compatible surface carries thought signatures in tool call
|
|
45
|
+
// `extra_content` and rejects replayed parallel calls without them:
|
|
46
|
+
// https://ai.google.dev/gemini-api/docs/thinking#signatures
|
|
47
|
+
const ExtraContent = Schema.Struct({
|
|
48
|
+
google: Schema.Struct({ thought_signature: Schema.String }),
|
|
49
|
+
});
|
|
50
|
+
const decodeExtraContent = (value) => Option.getOrUndefined(Schema.decodeUnknownOption(ExtraContent)(value));
|
|
44
51
|
const OpenAIChatAssistantToolCall = Schema.Struct({
|
|
45
52
|
id: Schema.String,
|
|
46
53
|
type: Schema.tag("function"),
|
|
@@ -48,6 +55,7 @@ const OpenAIChatAssistantToolCall = Schema.Struct({
|
|
|
48
55
|
name: Schema.String,
|
|
49
56
|
arguments: Schema.String,
|
|
50
57
|
}),
|
|
58
|
+
extra_content: Schema.optional(ExtraContent),
|
|
51
59
|
});
|
|
52
60
|
// `reasoning_details` carries two dialects. OpenRouter's `reasoning.*` entries
|
|
53
61
|
// must be replayed unmodified (`index` included), so they keep every field they
|
|
@@ -75,11 +83,6 @@ const ReasoningDetail = Schema.Union([
|
|
|
75
83
|
]);
|
|
76
84
|
const decodeReasoningDetail = Schema.decodeUnknownOption(ReasoningDetail);
|
|
77
85
|
const knownReasoningDetails = (details) => details.flatMap((detail) => Option.toArray(decodeReasoningDetail(detail)));
|
|
78
|
-
// Intentionally omit Gemini's provider-specific `extra_content.google.thought_signature`
|
|
79
|
-
// extension until direct Google OpenAI-compatible routing is supported here:
|
|
80
|
-
// https://github.com/vercel/ai/issues/11590
|
|
81
|
-
// https://github.com/vercel/ai/pull/11745
|
|
82
|
-
// https://ai.google.dev/gemini-api/docs/thought-signatures#openai
|
|
83
86
|
const OpenAIChatUserContent = Schema.Union([
|
|
84
87
|
Schema.Struct({
|
|
85
88
|
type: Schema.Literal("text"),
|
|
@@ -180,6 +183,7 @@ const OpenAIChatToolCallDelta = Schema.Struct({
|
|
|
180
183
|
index: optionalNull(Schema.Number),
|
|
181
184
|
id: optionalNull(Schema.String),
|
|
182
185
|
function: optionalNull(OpenAIChatToolCallDeltaFunction),
|
|
186
|
+
extra_content: optionalNull(Schema.Unknown),
|
|
183
187
|
});
|
|
184
188
|
const OpenAIChatDelta = Schema.StructWithRest(Schema.Struct({
|
|
185
189
|
content: optionalNull(Schema.String),
|
|
@@ -231,6 +235,7 @@ const lowerToolCall = (part, options) => ({
|
|
|
231
235
|
name: part.name,
|
|
232
236
|
arguments: ProviderShared.encodeJson(part.input === undefined ? {} : part.input),
|
|
233
237
|
},
|
|
238
|
+
extra_content: decodeExtraContent(part.providerMetadata?.[options.providerMetadataKey]?.extraContent),
|
|
234
239
|
});
|
|
235
240
|
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part) {
|
|
236
241
|
// Chat Completions accepts PDFs, and no other documents, as inline `file` parts; file URLs are not supported.
|
|
@@ -566,7 +571,9 @@ const detectSupportsStore = (provider, baseURL) => {
|
|
|
566
571
|
const isVercelAiGateway = p === "vercel-ai-gateway" || url.includes("ai-gateway.vercel.sh") || url.includes("vercel.sh");
|
|
567
572
|
const isAntLing = p === "ant-ling" || url.includes("api.ant-ling.com");
|
|
568
573
|
const isOpencode = p === "opencode" || url.includes("opencode.ai");
|
|
569
|
-
const
|
|
574
|
+
const isGemini = url.includes("generativelanguage.googleapis.com");
|
|
575
|
+
const isNonStandard = isGemini ||
|
|
576
|
+
isNvidia ||
|
|
570
577
|
isCerebras ||
|
|
571
578
|
isXai ||
|
|
572
579
|
isTogether ||
|
|
@@ -908,12 +915,13 @@ const step = (state, event) => Effect.gen(function* () {
|
|
|
908
915
|
const id = current?.id ?? pending?.id ?? (tool.id || undefined);
|
|
909
916
|
const name = current?.name ?? pending?.name ?? (tool.function?.name || undefined);
|
|
910
917
|
const text = `${pending?.input ?? ""}${tool.function?.arguments ?? ""}`;
|
|
918
|
+
const extraContent = pending?.extraContent ?? decodeExtraContent(tool.extra_content);
|
|
911
919
|
latestToolIndex = index;
|
|
912
920
|
nextToolIndex = Math.max(nextToolIndex, index + 1);
|
|
913
921
|
if (!current && (!id || !name)) {
|
|
914
922
|
pendingTools = {
|
|
915
923
|
...pendingTools,
|
|
916
|
-
[index]: { id: id || undefined, name: name || undefined, input: text },
|
|
924
|
+
[index]: { id: id || undefined, name: name || undefined, input: text, extraContent },
|
|
917
925
|
};
|
|
918
926
|
continue;
|
|
919
927
|
}
|
|
@@ -921,7 +929,12 @@ const step = (state, event) => Effect.gen(function* () {
|
|
|
921
929
|
pendingTools = { ...pendingTools };
|
|
922
930
|
delete pendingTools[index];
|
|
923
931
|
}
|
|
924
|
-
const result = ToolStream.appendOrStart(ADAPTER, tools, index, {
|
|
932
|
+
const result = ToolStream.appendOrStart(ADAPTER, tools, index, {
|
|
933
|
+
id: id || undefined,
|
|
934
|
+
name: name || undefined,
|
|
935
|
+
text,
|
|
936
|
+
providerMetadata: extraContent && { [state.providerMetadataKey]: { extraContent } },
|
|
937
|
+
}, "OpenAI Chat tool call delta is missing id or name");
|
|
925
938
|
if (ToolStream.isError(result))
|
|
926
939
|
return yield* new AIError({
|
|
927
940
|
reason: AIErrorReason.make({
|
|
@@ -63,12 +63,17 @@ export declare const route: Route<{
|
|
|
63
63
|
readonly ttl?: string | undefined;
|
|
64
64
|
} | undefined;
|
|
65
65
|
readonly tool_calls?: readonly {
|
|
66
|
-
readonly id: string;
|
|
67
|
-
readonly type: "function";
|
|
68
66
|
readonly function: {
|
|
69
67
|
readonly name: string;
|
|
70
68
|
readonly arguments: string;
|
|
71
69
|
};
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly type: "function";
|
|
72
|
+
readonly extra_content?: {
|
|
73
|
+
readonly google: {
|
|
74
|
+
readonly thought_signature: string;
|
|
75
|
+
};
|
|
76
|
+
} | undefined;
|
|
72
77
|
}[] | undefined;
|
|
73
78
|
readonly reasoning_details?: unknown;
|
|
74
79
|
} | {
|
|
@@ -30,10 +30,13 @@ const decodeEvent = route.decodeFrame(SpeechStreamEvent);
|
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
// `sse` is not supported for `tts-1` or `tts-1-hd`; those models stream the raw audio body instead.
|
|
32
32
|
const supportsSse = (model) => !/^tts-1(-hd)?(-|$)/.test(model);
|
|
33
|
+
const FORMATS = new Set(["mp3", "opus", "aac", "flac", "wav", "pcm"]);
|
|
33
34
|
const fromRequest = Effect.fn("OpenAISpeech.fromRequest")(function* (request) {
|
|
34
35
|
// Not in `unsupported`: that list would also reject `timestamps: false`, which asks for nothing.
|
|
35
36
|
if (request.timestamps === true)
|
|
36
37
|
return yield* route.unsupported("media.timestamps", `${route.name} does not return timestamps`);
|
|
38
|
+
if (request.format !== undefined && !FORMATS.has(request.format))
|
|
39
|
+
return yield* route.unsupported("media.format", `${route.name} supports the mp3, opus, aac, flac, wav, and pcm formats, not "${request.format}"`);
|
|
37
40
|
return MediaProtocol.json(mergeJsonRecords({
|
|
38
41
|
model: request.model.id,
|
|
39
42
|
input: request.text,
|
|
@@ -73,7 +76,9 @@ const onEvent = Effect.fn("OpenAISpeech.onEvent")(function* (state, frame) {
|
|
|
73
76
|
const finish = (state, context) => {
|
|
74
77
|
if (isSse(context.body) && !state.done)
|
|
75
78
|
return Effect.fail(route.incomplete());
|
|
76
|
-
|
|
79
|
+
// The sent body reflects `providerOptions` and `http.body` overrides of `format`.
|
|
80
|
+
const sent = context.body.type === "json" ? context.body.value.response_format : undefined;
|
|
81
|
+
const format = typeof sent === "string" ? sent : "mp3";
|
|
77
82
|
return SpeechStream.finish(route, state, {
|
|
78
83
|
...(format === "pcm" ? SpeechStream.pcm("pcm_s16le", PCM_SAMPLE_RATE) : SpeechStream.container(format)),
|
|
79
84
|
usage: state.usage,
|
|
@@ -118,7 +118,7 @@ const fromRequest = Effect.fn("OpenAITranscription.fromRequest")(function* (requ
|
|
|
118
118
|
}, {
|
|
119
119
|
overlay: mergeJsonRecords(request.providerOptions, request.http?.body),
|
|
120
120
|
reserved: RESERVED_FORM_FIELDS,
|
|
121
|
-
repeatArrays:
|
|
121
|
+
repeatArrays: "key[]",
|
|
122
122
|
});
|
|
123
123
|
return MediaProtocol.multipart(form);
|
|
124
124
|
});
|
|
@@ -89,7 +89,8 @@ const decodeResult = Effect.fn("RunwayVideo.decodeResult")(function* (response,
|
|
|
89
89
|
// Runway failure codes are dotted paths; every moderation outcome carries a SAFETY segment.
|
|
90
90
|
if (code !== undefined && /(^|\.)SAFETY(\.|$)/.test(code))
|
|
91
91
|
return yield* output.contentPolicy(message);
|
|
92
|
-
|
|
92
|
+
// ASSET.INVALID rejects the caller's input media; Runway documents it as not retryable.
|
|
93
|
+
return yield* output.ended("failed", message, code !== undefined && /^ASSET\.INVALID(\.|$)/.test(code) ? "InvalidRequest" : "ProviderInternal");
|
|
93
94
|
}
|
|
94
95
|
if (status === "cancelled")
|
|
95
96
|
return yield* output.ended("cancelled", `${route.name} task ${context.token.taskID} was cancelled`);
|
|
@@ -47,6 +47,12 @@ export interface Metadata {
|
|
|
47
47
|
}
|
|
48
48
|
export declare const track: <State extends Metadata>(state: State, chunk: Chunk) => State;
|
|
49
49
|
export declare const blocked: (name: string, chunk: Chunk, frame: string) => AIError | undefined;
|
|
50
|
+
/** Finish reasons for which Gemini stops output on safety or policy grounds. */
|
|
51
|
+
export declare const contentFiltered: (finishReason: string | undefined) => boolean;
|
|
52
|
+
/** Callers check that the response produced no output: a policy stop after output is a partial result instead. */
|
|
53
|
+
export declare const withheld: (name: string, chunk: Chunk, frame: string) => AIError | undefined;
|
|
54
|
+
/** Any finish reason other than `STOP` means the output may be cut short, so it is surfaced rather than dropped. */
|
|
55
|
+
export declare const notices: (name: string, state: Metadata) => ReadonlyArray<Media.Notice> | undefined;
|
|
50
56
|
export declare const usage: (usage: UsageMetadata | undefined) => MediaUsage | undefined;
|
|
51
57
|
export declare const providerMetadata: (state: Metadata) => ProviderMetadata;
|
|
52
58
|
export declare const path: (model: string, mode: MediaProtocol.Mode) => string;
|
|
@@ -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
|
|
@@ -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: [] };
|
|
@@ -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,11 @@ 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;
|
|
5
10
|
export interface ProviderFailure {
|
|
6
11
|
readonly message: string;
|
|
7
12
|
readonly status?: number | undefined;
|