@opencode/ai 2.0.15 → 2.0.17
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 +441 -99
- package/dist/ai-client.d.ts +8 -0
- package/dist/ai-client.js +12 -0
- package/dist/experimental/evaluation-client.d.ts +3 -3
- package/dist/experimental/evaluation-client.js +1 -1
- package/dist/experimental/evaluation.js +1 -1
- package/dist/generation.d.ts +39 -29
- package/dist/generation.js +62 -36
- package/dist/image-client.d.ts +59 -17
- package/dist/image-client.js +16 -24
- package/dist/image.d.ts +394 -55
- package/dist/image.js +48 -57
- package/dist/index.d.ts +15 -2
- package/dist/index.js +10 -0
- package/dist/llm.d.ts +7 -5
- package/dist/llm.js +10 -4
- package/dist/media-client.d.ts +30 -0
- package/dist/media-client.js +51 -0
- package/dist/media-model.d.ts +43 -0
- package/dist/media-model.js +47 -0
- package/dist/media.d.ts +10 -9
- package/dist/media.js +11 -12
- package/dist/promise.d.ts +473 -15
- package/dist/promise.js +71 -13
- package/dist/protocols/alibaba-chat.d.ts +12 -0
- package/dist/protocols/alibaba-chat.js +4 -1
- package/dist/protocols/alibaba-messages.d.ts +1 -1
- package/dist/protocols/alibaba-messages.js +6 -4
- package/dist/protocols/alibaba-responses.d.ts +2 -2
- package/dist/protocols/anthropic-messages.d.ts +34 -34
- package/dist/protocols/anthropic-messages.js +14 -9
- package/dist/protocols/assemblyai-transcription.d.ts +41 -0
- package/dist/protocols/assemblyai-transcription.js +136 -0
- package/dist/protocols/bedrock-converse.d.ts +7 -0
- package/dist/protocols/bedrock-converse.js +39 -19
- package/dist/protocols/bfl-images.d.ts +38 -0
- package/dist/protocols/bfl-images.js +153 -0
- package/dist/protocols/cartesia-speech.d.ts +143 -0
- package/dist/protocols/cartesia-speech.js +120 -0
- package/dist/protocols/deepgram-speech.d.ts +135 -0
- package/dist/protocols/deepgram-speech.js +91 -0
- package/dist/protocols/deepgram-transcription.d.ts +26 -0
- package/dist/protocols/deepgram-transcription.js +125 -0
- package/dist/protocols/elevenlabs-speech.d.ts +138 -0
- package/dist/protocols/elevenlabs-speech.js +111 -0
- package/dist/protocols/fal-images.d.ts +25 -0
- package/dist/protocols/fal-images.js +104 -0
- package/dist/protocols/fal-video.d.ts +29 -0
- package/dist/protocols/fal-video.js +73 -0
- package/dist/protocols/gemini.d.ts +22 -22
- package/dist/protocols/gemini.js +19 -36
- package/dist/protocols/google-images.d.ts +3 -3
- package/dist/protocols/google-images.js +12 -34
- package/dist/protocols/google-speech.d.ts +146 -0
- package/dist/protocols/google-speech.js +88 -0
- package/dist/protocols/google-transcription.d.ts +174 -0
- package/dist/protocols/google-transcription.js +126 -0
- package/dist/protocols/google-video.d.ts +26 -0
- package/dist/protocols/google-video.js +142 -0
- package/dist/protocols/meta-images.d.ts +3 -4
- package/dist/protocols/meta-images.js +13 -29
- package/dist/protocols/meta-messages.d.ts +5 -5
- package/dist/protocols/meta-responses.d.ts +4 -4
- package/dist/protocols/meta-responses.js +6 -4
- package/dist/protocols/open-responses.d.ts +10 -9
- package/dist/protocols/open-responses.js +4 -9
- package/dist/protocols/openai-chat.d.ts +84 -0
- package/dist/protocols/openai-chat.js +28 -17
- package/dist/protocols/openai-compatible-chat.d.ts +12 -0
- package/dist/protocols/openai-compatible-responses.d.ts +2 -2
- package/dist/protocols/openai-images.d.ts +130 -7
- package/dist/protocols/openai-images.js +143 -91
- package/dist/protocols/openai-responses.d.ts +20 -20
- package/dist/protocols/openai-responses.js +31 -31
- package/dist/protocols/openai-speech.d.ts +136 -0
- package/dist/protocols/openai-speech.js +97 -0
- package/dist/protocols/openai-transcription.d.ts +211 -0
- package/dist/protocols/openai-transcription.js +202 -0
- package/dist/protocols/replicate-images.d.ts +28 -0
- package/dist/protocols/replicate-images.js +127 -0
- package/dist/protocols/runway-video.d.ts +38 -0
- package/dist/protocols/runway-video.js +140 -0
- package/dist/protocols/shared.d.ts +21 -17
- package/dist/protocols/shared.js +27 -36
- package/dist/protocols/stability-images.d.ts +39 -0
- package/dist/protocols/stability-images.js +136 -0
- package/dist/protocols/utils/fal-queue.d.ts +26 -0
- package/dist/protocols/utils/fal-queue.js +67 -0
- package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
- package/dist/protocols/utils/gemini-generate-content.js +65 -0
- package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
- package/dist/protocols/utils/gemini-json-schema.js +76 -0
- package/dist/protocols/utils/media-input.d.ts +23 -1
- package/dist/protocols/utils/media-input.js +40 -0
- package/dist/protocols/utils/responses-checkpoint.js +3 -7
- package/dist/protocols/utils/responses-compaction.d.ts +3 -1
- package/dist/protocols/utils/responses-compaction.js +16 -3
- package/dist/protocols/utils/speech-stream.d.ts +49 -0
- package/dist/protocols/utils/speech-stream.js +64 -0
- package/dist/protocols/utils/tool-schema.d.ts +2 -2
- package/dist/protocols/utils/tool-schema.js +62 -19
- package/dist/protocols/xai-images.d.ts +4 -4
- package/dist/protocols/xai-images.js +14 -40
- package/dist/protocols/xai-responses.d.ts +2 -2
- package/dist/protocols/xai-responses.js +1 -1
- package/dist/protocols/xai-video.d.ts +34 -0
- package/dist/protocols/xai-video.js +141 -0
- package/dist/protocols/zai-chat.d.ts +13 -1
- package/dist/protocols/zai-images.d.ts +2 -2
- package/dist/protocols/zai-images.js +11 -14
- package/dist/protocols/zai-messages.d.ts +1 -1
- package/dist/provider-error.js +10 -1
- package/dist/providers/alibaba.d.ts +15 -3
- package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
- package/dist/providers/amazon-bedrock.d.ts +2 -0
- package/dist/providers/amazon-bedrock.js +1 -0
- package/dist/providers/anthropic-compatible.d.ts +5 -5
- package/dist/providers/anthropic.d.ts +5 -5
- package/dist/providers/assemblyai.d.ts +25 -0
- package/dist/providers/assemblyai.js +24 -0
- package/dist/providers/azure.d.ts +20 -8
- package/dist/providers/azure.js +2 -2
- package/dist/providers/baseten.d.ts +24 -0
- package/dist/providers/black-forest-labs.d.ts +25 -0
- package/dist/providers/black-forest-labs.js +23 -0
- package/dist/providers/cartesia.d.ts +24 -0
- package/dist/providers/cartesia.js +17 -0
- package/dist/providers/cerebras.d.ts +24 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts +42 -18
- package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
- package/dist/providers/deepgram.d.ts +29 -0
- package/dist/providers/deepgram.js +25 -0
- package/dist/providers/deepinfra.d.ts +24 -0
- package/dist/providers/deepseek.d.ts +24 -0
- package/dist/providers/elevenlabs.d.ts +24 -0
- package/dist/providers/elevenlabs.js +23 -0
- package/dist/providers/fal.d.ts +29 -0
- package/dist/providers/fal.js +26 -0
- package/dist/providers/fireworks.d.ts +24 -0
- package/dist/providers/google-vertex-chat.d.ts +12 -0
- package/dist/providers/google-vertex-messages.d.ts +5 -5
- package/dist/providers/google-vertex-responses.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts +6 -6
- package/dist/providers/google.d.ts +21 -6
- package/dist/providers/google.js +13 -9
- package/dist/providers/groq.d.ts +24 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/meta.d.ts +22 -10
- package/dist/providers/meta.js +4 -8
- package/dist/providers/minimax.d.ts +19 -7
- package/dist/providers/moonshot.d.ts +19 -7
- package/dist/providers/moonshot.js +3 -3
- package/dist/providers/openai-compatible-responses.d.ts +2 -2
- package/dist/providers/openai-compatible.d.ts +12 -0
- package/dist/providers/openai-options.d.ts +3 -9
- package/dist/providers/openai-options.js +4 -7
- package/dist/providers/openai.d.ts +33 -12
- package/dist/providers/openai.js +17 -9
- package/dist/providers/opencode-zen.js +1 -1
- package/dist/providers/openrouter.d.ts +54 -7
- package/dist/providers/openrouter.js +10 -5
- package/dist/providers/replicate.d.ts +25 -0
- package/dist/providers/replicate.js +17 -0
- package/dist/providers/runway.d.ts +24 -0
- package/dist/providers/runway.js +17 -0
- package/dist/providers/stability.d.ts +28 -0
- package/dist/providers/stability.js +18 -0
- package/dist/providers/togetherai.d.ts +24 -0
- package/dist/providers/typesafe-ai.js +1 -1
- package/dist/providers/vercel-ai-gateway.js +1 -1
- package/dist/providers/xai.d.ts +17 -0
- package/dist/providers/xai.js +7 -9
- package/dist/providers/zai-coding-plan.d.ts +16 -4
- package/dist/providers/zai.d.ts +13 -1
- package/dist/providers/zai.js +4 -8
- package/dist/route/auth.d.ts +5 -2
- package/dist/route/auth.js +20 -13
- package/dist/route/client.d.ts +9 -7
- package/dist/route/client.js +6 -8
- package/dist/route/endpoint.d.ts +1 -0
- package/dist/route/endpoint.js +2 -2
- package/dist/route/executor-service.d.ts +4 -2
- package/dist/route/executor-service.js +2 -1
- package/dist/route/executor.d.ts +3 -1
- package/dist/route/executor.js +7 -0
- package/dist/route/framing.d.ts +15 -2
- package/dist/route/framing.js +53 -4
- package/dist/route/index.d.ts +1 -1
- package/dist/route/media-protocol.d.ts +145 -18
- package/dist/route/media-protocol.js +96 -28
- package/dist/route/media.d.ts +54 -9
- package/dist/route/media.js +194 -38
- package/dist/route/protocol.d.ts +3 -1
- package/dist/schema/events.d.ts +0 -6
- package/dist/schema/messages.d.ts +0 -3
- package/dist/schema/options.d.ts +10 -6
- package/dist/schema/options.js +10 -5
- package/dist/speech-client.d.ts +66 -0
- package/dist/speech-client.js +21 -0
- package/dist/speech.d.ts +1307 -0
- package/dist/speech.js +124 -0
- package/dist/testing.d.ts +2 -2
- package/dist/transcription-client.d.ts +82 -0
- package/dist/transcription-client.js +13 -0
- package/dist/transcription.d.ts +1492 -0
- package/dist/transcription.js +127 -0
- package/dist/utils/bytes.d.ts +1 -0
- package/dist/utils/bytes.js +10 -0
- package/dist/utils/json.d.ts +4 -0
- package/dist/utils/json.js +4 -0
- package/dist/utils/media-type.d.ts +3 -1
- package/dist/utils/media-type.js +25 -2
- package/dist/video-client.d.ts +59 -0
- package/dist/video-client.js +20 -0
- package/dist/video.d.ts +1351 -0
- package/dist/video.js +118 -0
- package/package.json +3 -3
- package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
- package/dist/protocols/utils/gemini-tool-schema.js +0 -103
- package/dist/protocols/utils/meta-image.d.ts +0 -2
- package/dist/protocols/utils/meta-image.js +0 -13
- package/dist/protocols/utils/openai-image.d.ts +0 -5
- package/dist/protocols/utils/openai-image.js +0 -18
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
|
+
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { SpeechModel, type SpeechRequestFor } from "../speech.js";
|
|
4
|
+
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
5
|
+
import { SpeechStream } from "./utils/speech-stream.js";
|
|
6
|
+
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
7
|
+
/** Style is directed in the text itself, and `speechConfig.multiSpeakerVoiceConfig` excludes `voice`. */
|
|
8
|
+
export type GoogleSpeechOptions = {
|
|
9
|
+
readonly temperature?: number;
|
|
10
|
+
readonly seed?: number;
|
|
11
|
+
readonly speechConfig?: {
|
|
12
|
+
readonly multiSpeakerVoiceConfig?: {
|
|
13
|
+
readonly speakerVoiceConfigs: ReadonlyArray<{
|
|
14
|
+
readonly speaker: string;
|
|
15
|
+
readonly voiceConfig: {
|
|
16
|
+
readonly prebuiltVoiceConfig: {
|
|
17
|
+
readonly voiceName: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
} & Record<string, unknown>;
|
|
24
|
+
export type Request = SpeechRequestFor<GoogleSpeechOptions>;
|
|
25
|
+
interface State extends SpeechStream.Audio, GeminiGenerateContent.Metadata {
|
|
26
|
+
readonly mimeType?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const protocol: MediaProtocol.Streamed<Request, {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly type: "generation-queued";
|
|
31
|
+
readonly position?: number | undefined;
|
|
32
|
+
} | {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly type: "generation-progress";
|
|
35
|
+
readonly progress?: number | undefined;
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "audio-delta";
|
|
38
|
+
readonly chunk: Uint8Array<ArrayBufferLike>;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: "timestamps";
|
|
41
|
+
readonly items: readonly {
|
|
42
|
+
readonly text: string;
|
|
43
|
+
readonly startSeconds: number;
|
|
44
|
+
readonly endSeconds: number;
|
|
45
|
+
}[];
|
|
46
|
+
} | {
|
|
47
|
+
readonly type: "finish";
|
|
48
|
+
readonly audio: import("../media.js").Asset;
|
|
49
|
+
readonly providerMetadata?: {
|
|
50
|
+
readonly [x: string]: {
|
|
51
|
+
readonly [x: string]: unknown;
|
|
52
|
+
};
|
|
53
|
+
} | undefined;
|
|
54
|
+
readonly usage?: {
|
|
55
|
+
readonly type: "tokens";
|
|
56
|
+
readonly input?: number | undefined;
|
|
57
|
+
readonly output?: number | undefined;
|
|
58
|
+
readonly total?: number | undefined;
|
|
59
|
+
readonly details?: {
|
|
60
|
+
readonly [x: string]: unknown;
|
|
61
|
+
} | undefined;
|
|
62
|
+
} | {
|
|
63
|
+
readonly type: "seconds";
|
|
64
|
+
readonly seconds: number;
|
|
65
|
+
} | {
|
|
66
|
+
readonly type: "characters";
|
|
67
|
+
readonly characters: number;
|
|
68
|
+
} | {
|
|
69
|
+
readonly type: "credits";
|
|
70
|
+
readonly credits: number;
|
|
71
|
+
} | {
|
|
72
|
+
readonly type: "compute";
|
|
73
|
+
readonly seconds: number;
|
|
74
|
+
} | undefined;
|
|
75
|
+
readonly notices?: readonly {
|
|
76
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
77
|
+
readonly message: string;
|
|
78
|
+
readonly providerMetadata?: {
|
|
79
|
+
readonly [x: string]: {
|
|
80
|
+
readonly [x: string]: unknown;
|
|
81
|
+
};
|
|
82
|
+
} | undefined;
|
|
83
|
+
}[] | undefined;
|
|
84
|
+
}, string, State>;
|
|
85
|
+
export declare const model: (input: MediaRoute.ModelInput) => SpeechModel<GoogleSpeechOptions>;
|
|
86
|
+
export declare const GoogleSpeech: {
|
|
87
|
+
readonly protocol: MediaProtocol.Streamed<Request, {
|
|
88
|
+
readonly id: string;
|
|
89
|
+
readonly type: "generation-queued";
|
|
90
|
+
readonly position?: number | undefined;
|
|
91
|
+
} | {
|
|
92
|
+
readonly id: string;
|
|
93
|
+
readonly type: "generation-progress";
|
|
94
|
+
readonly progress?: number | undefined;
|
|
95
|
+
} | {
|
|
96
|
+
readonly type: "audio-delta";
|
|
97
|
+
readonly chunk: Uint8Array<ArrayBufferLike>;
|
|
98
|
+
} | {
|
|
99
|
+
readonly type: "timestamps";
|
|
100
|
+
readonly items: readonly {
|
|
101
|
+
readonly text: string;
|
|
102
|
+
readonly startSeconds: number;
|
|
103
|
+
readonly endSeconds: number;
|
|
104
|
+
}[];
|
|
105
|
+
} | {
|
|
106
|
+
readonly type: "finish";
|
|
107
|
+
readonly audio: import("../media.js").Asset;
|
|
108
|
+
readonly providerMetadata?: {
|
|
109
|
+
readonly [x: string]: {
|
|
110
|
+
readonly [x: string]: unknown;
|
|
111
|
+
};
|
|
112
|
+
} | undefined;
|
|
113
|
+
readonly usage?: {
|
|
114
|
+
readonly type: "tokens";
|
|
115
|
+
readonly input?: number | undefined;
|
|
116
|
+
readonly output?: number | undefined;
|
|
117
|
+
readonly total?: number | undefined;
|
|
118
|
+
readonly details?: {
|
|
119
|
+
readonly [x: string]: unknown;
|
|
120
|
+
} | undefined;
|
|
121
|
+
} | {
|
|
122
|
+
readonly type: "seconds";
|
|
123
|
+
readonly seconds: number;
|
|
124
|
+
} | {
|
|
125
|
+
readonly type: "characters";
|
|
126
|
+
readonly characters: number;
|
|
127
|
+
} | {
|
|
128
|
+
readonly type: "credits";
|
|
129
|
+
readonly credits: number;
|
|
130
|
+
} | {
|
|
131
|
+
readonly type: "compute";
|
|
132
|
+
readonly seconds: number;
|
|
133
|
+
} | undefined;
|
|
134
|
+
readonly notices?: readonly {
|
|
135
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
136
|
+
readonly message: string;
|
|
137
|
+
readonly providerMetadata?: {
|
|
138
|
+
readonly [x: string]: {
|
|
139
|
+
readonly [x: string]: unknown;
|
|
140
|
+
};
|
|
141
|
+
} | undefined;
|
|
142
|
+
}[] | undefined;
|
|
143
|
+
}, string, State>;
|
|
144
|
+
readonly model: (input: MediaRoute.ModelInput) => SpeechModel<GoogleSpeechOptions>;
|
|
145
|
+
};
|
|
146
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
5
|
+
import { SpeechModel } from "../speech.js";
|
|
6
|
+
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
7
|
+
import { SpeechStream } from "./utils/speech-stream.js";
|
|
8
|
+
const route = MediaProtocol.identity({ id: "google-speech", name: "Google Speech", provider: "google" });
|
|
9
|
+
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
10
|
+
const DEFAULT_SAMPLE_RATE = 24000;
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// 3. Streaming event schema
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const GenerateContentChunk = GeminiGenerateContent.chunk(Schema.Struct({
|
|
15
|
+
text: Schema.optional(Schema.String),
|
|
16
|
+
inlineData: Schema.optional(Schema.Struct({ mimeType: Schema.String, data: Schema.Uint8ArrayFromBase64 })),
|
|
17
|
+
}));
|
|
18
|
+
const decodeChunk = route.decodeFrame(GenerateContentChunk);
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// 5. Request body construction
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
|
|
23
|
+
// Not in `unsupported`: that list would also reject `timestamps: false`, which asks for nothing.
|
|
24
|
+
if (request.timestamps === true)
|
|
25
|
+
return yield* route.unsupported("media.timestamps", `${route.name} does not return timestamps`);
|
|
26
|
+
if (request.format === "pcm" && request.mode === "generate" && /^gemini-3\.8-.*-tts(?:-|$)/.test(request.model.id))
|
|
27
|
+
return yield* route.unsupported("media.format", `${route.name} returns WAV by default for Gemini 3.8 TTS unary requests; omit the format to accept it`);
|
|
28
|
+
if (request.format !== undefined && request.format !== "pcm")
|
|
29
|
+
return yield* route.unsupported("media.format", `${route.name} only accepts raw PCM as an explicit format; omit it to accept the provider's default output`);
|
|
30
|
+
const voiceName = SpeechStream.voiceID(request.voice);
|
|
31
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
32
|
+
contents: [{ role: "user", parts: [{ text: request.text }] }],
|
|
33
|
+
generationConfig: mergeJsonRecords({
|
|
34
|
+
responseModalities: ["AUDIO"],
|
|
35
|
+
speechConfig: {
|
|
36
|
+
voiceConfig: voiceName === undefined ? undefined : { prebuiltVoiceConfig: { voiceName } },
|
|
37
|
+
languageCode: request.language,
|
|
38
|
+
},
|
|
39
|
+
}, request.providerOptions),
|
|
40
|
+
}, request.http?.body) ?? {});
|
|
41
|
+
});
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// 6. Stream parsing
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
|
|
46
|
+
const chunk = yield* decodeChunk(frame);
|
|
47
|
+
const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
|
|
48
|
+
if (blocked !== undefined)
|
|
49
|
+
return yield* blocked;
|
|
50
|
+
const audio = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.inlineData === undefined ? [] : [part.inlineData]);
|
|
51
|
+
const next = { ...GeminiGenerateContent.track(state, chunk), mimeType: state.mimeType ?? audio[0]?.mimeType };
|
|
52
|
+
return [next, audio.flatMap((part) => SpeechStream.delta(next, part.data)[1])];
|
|
53
|
+
});
|
|
54
|
+
const finish = (state, context) => {
|
|
55
|
+
const sampleRate = SpeechStream.sampleRate(state.mimeType) ?? DEFAULT_SAMPLE_RATE;
|
|
56
|
+
const output = state.mimeType?.split(";")[0]?.toLowerCase() === "audio/wav"
|
|
57
|
+
? SpeechStream.container("wav", sampleRate)
|
|
58
|
+
: SpeechStream.pcm("pcm_s16le", sampleRate, state.mimeType ?? `audio/L16;codec=pcm;rate=${sampleRate}`);
|
|
59
|
+
if (context.request.format === "pcm" && output.info.format !== "pcm")
|
|
60
|
+
return Effect.fail(route.frameError(`Google Speech returned ${output.info.format} instead of the requested raw PCM`));
|
|
61
|
+
return SpeechStream.finish(route, state, {
|
|
62
|
+
...output,
|
|
63
|
+
usage: GeminiGenerateContent.usage(state.usage),
|
|
64
|
+
providerMetadata: GeminiGenerateContent.providerMetadata(state),
|
|
65
|
+
detail: state.finishReason === undefined ? undefined : `finish reason: ${state.finishReason}`,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// 7. Protocol and route
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
72
|
+
unsupported: ["instructions", "speed"],
|
|
73
|
+
body: { from: fromRequest },
|
|
74
|
+
frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
|
|
75
|
+
initial: () => ({ chunks: [] }),
|
|
76
|
+
step,
|
|
77
|
+
finish,
|
|
78
|
+
});
|
|
79
|
+
export const model = (input) => SpeechModel.fromRoute({
|
|
80
|
+
protocol,
|
|
81
|
+
baseURL: DEFAULT_BASE_URL,
|
|
82
|
+
// Only `gemini-3.1-flash-tts-preview` and later stream; earlier TTS models reject `streamGenerateContent`.
|
|
83
|
+
path: ({ request }) => GeminiGenerateContent.path(request.model.id, request.mode),
|
|
84
|
+
}, input);
|
|
85
|
+
export const GoogleSpeech = {
|
|
86
|
+
protocol,
|
|
87
|
+
model,
|
|
88
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
|
+
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type OpenString } from "../schema/index.js";
|
|
4
|
+
import { TranscriptionModel, type TranscriptionRequestFor, type TranscriptionSegment, type TranscriptionWord } from "../transcription.js";
|
|
5
|
+
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
6
|
+
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
7
|
+
/**
|
|
8
|
+
* Merged into `generationConfig`. The API rejects `customVocabulary` and `mode: "SMART"` alongside diarization or word
|
|
9
|
+
* timestamps.
|
|
10
|
+
*/
|
|
11
|
+
export type GoogleTranscriptionOptions = {
|
|
12
|
+
readonly audioTranscriptionConfig?: {
|
|
13
|
+
readonly mode?: OpenString<"VERBATIM" | "SMART">;
|
|
14
|
+
readonly customVocabulary?: ReadonlyArray<string>;
|
|
15
|
+
readonly languageCodes?: ReadonlyArray<string>;
|
|
16
|
+
};
|
|
17
|
+
} & Record<string, unknown>;
|
|
18
|
+
export type Request = TranscriptionRequestFor<GoogleTranscriptionOptions>;
|
|
19
|
+
interface State extends GeminiGenerateContent.Metadata {
|
|
20
|
+
readonly text: string;
|
|
21
|
+
readonly segments: Array<TranscriptionSegment>;
|
|
22
|
+
readonly words: Array<TranscriptionWord>;
|
|
23
|
+
}
|
|
24
|
+
export declare const protocol: MediaProtocol.Streamed<Request, {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly type: "generation-queued";
|
|
27
|
+
readonly position?: number | undefined;
|
|
28
|
+
} | {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly type: "generation-progress";
|
|
31
|
+
readonly progress?: number | undefined;
|
|
32
|
+
} | {
|
|
33
|
+
readonly type: "text-delta";
|
|
34
|
+
readonly delta: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly type: "segment";
|
|
37
|
+
readonly segment: {
|
|
38
|
+
readonly text: string;
|
|
39
|
+
readonly startSeconds: number;
|
|
40
|
+
readonly endSeconds: number;
|
|
41
|
+
readonly speaker?: string | undefined;
|
|
42
|
+
};
|
|
43
|
+
} | {
|
|
44
|
+
readonly type: "finish";
|
|
45
|
+
readonly text: string;
|
|
46
|
+
readonly durationSeconds?: number | undefined;
|
|
47
|
+
readonly providerMetadata?: {
|
|
48
|
+
readonly [x: string]: {
|
|
49
|
+
readonly [x: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
} | undefined;
|
|
52
|
+
readonly usage?: {
|
|
53
|
+
readonly type: "tokens";
|
|
54
|
+
readonly input?: number | undefined;
|
|
55
|
+
readonly output?: number | undefined;
|
|
56
|
+
readonly total?: number | undefined;
|
|
57
|
+
readonly details?: {
|
|
58
|
+
readonly [x: string]: unknown;
|
|
59
|
+
} | undefined;
|
|
60
|
+
} | {
|
|
61
|
+
readonly type: "seconds";
|
|
62
|
+
readonly seconds: number;
|
|
63
|
+
} | {
|
|
64
|
+
readonly type: "characters";
|
|
65
|
+
readonly characters: number;
|
|
66
|
+
} | {
|
|
67
|
+
readonly type: "credits";
|
|
68
|
+
readonly credits: number;
|
|
69
|
+
} | {
|
|
70
|
+
readonly type: "compute";
|
|
71
|
+
readonly seconds: number;
|
|
72
|
+
} | undefined;
|
|
73
|
+
readonly notices?: readonly {
|
|
74
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
75
|
+
readonly message: string;
|
|
76
|
+
readonly providerMetadata?: {
|
|
77
|
+
readonly [x: string]: {
|
|
78
|
+
readonly [x: string]: unknown;
|
|
79
|
+
};
|
|
80
|
+
} | undefined;
|
|
81
|
+
}[] | undefined;
|
|
82
|
+
readonly language?: string | undefined;
|
|
83
|
+
readonly segments?: readonly {
|
|
84
|
+
readonly text: string;
|
|
85
|
+
readonly startSeconds: number;
|
|
86
|
+
readonly endSeconds: number;
|
|
87
|
+
readonly speaker?: string | undefined;
|
|
88
|
+
}[] | undefined;
|
|
89
|
+
readonly words?: readonly {
|
|
90
|
+
readonly text: string;
|
|
91
|
+
readonly startSeconds: number;
|
|
92
|
+
readonly endSeconds: number;
|
|
93
|
+
readonly speaker?: string | undefined;
|
|
94
|
+
readonly confidence?: number | undefined;
|
|
95
|
+
}[] | undefined;
|
|
96
|
+
}, string, State>;
|
|
97
|
+
export declare const model: (input: MediaRoute.ModelInput) => TranscriptionModel<GoogleTranscriptionOptions>;
|
|
98
|
+
export declare const GoogleTranscription: {
|
|
99
|
+
readonly protocol: MediaProtocol.Streamed<Request, {
|
|
100
|
+
readonly id: string;
|
|
101
|
+
readonly type: "generation-queued";
|
|
102
|
+
readonly position?: number | undefined;
|
|
103
|
+
} | {
|
|
104
|
+
readonly id: string;
|
|
105
|
+
readonly type: "generation-progress";
|
|
106
|
+
readonly progress?: number | undefined;
|
|
107
|
+
} | {
|
|
108
|
+
readonly type: "text-delta";
|
|
109
|
+
readonly delta: string;
|
|
110
|
+
} | {
|
|
111
|
+
readonly type: "segment";
|
|
112
|
+
readonly segment: {
|
|
113
|
+
readonly text: string;
|
|
114
|
+
readonly startSeconds: number;
|
|
115
|
+
readonly endSeconds: number;
|
|
116
|
+
readonly speaker?: string | undefined;
|
|
117
|
+
};
|
|
118
|
+
} | {
|
|
119
|
+
readonly type: "finish";
|
|
120
|
+
readonly text: string;
|
|
121
|
+
readonly durationSeconds?: number | undefined;
|
|
122
|
+
readonly providerMetadata?: {
|
|
123
|
+
readonly [x: string]: {
|
|
124
|
+
readonly [x: string]: unknown;
|
|
125
|
+
};
|
|
126
|
+
} | undefined;
|
|
127
|
+
readonly usage?: {
|
|
128
|
+
readonly type: "tokens";
|
|
129
|
+
readonly input?: number | undefined;
|
|
130
|
+
readonly output?: number | undefined;
|
|
131
|
+
readonly total?: number | undefined;
|
|
132
|
+
readonly details?: {
|
|
133
|
+
readonly [x: string]: unknown;
|
|
134
|
+
} | undefined;
|
|
135
|
+
} | {
|
|
136
|
+
readonly type: "seconds";
|
|
137
|
+
readonly seconds: number;
|
|
138
|
+
} | {
|
|
139
|
+
readonly type: "characters";
|
|
140
|
+
readonly characters: number;
|
|
141
|
+
} | {
|
|
142
|
+
readonly type: "credits";
|
|
143
|
+
readonly credits: number;
|
|
144
|
+
} | {
|
|
145
|
+
readonly type: "compute";
|
|
146
|
+
readonly seconds: number;
|
|
147
|
+
} | undefined;
|
|
148
|
+
readonly notices?: readonly {
|
|
149
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
150
|
+
readonly message: string;
|
|
151
|
+
readonly providerMetadata?: {
|
|
152
|
+
readonly [x: string]: {
|
|
153
|
+
readonly [x: string]: unknown;
|
|
154
|
+
};
|
|
155
|
+
} | undefined;
|
|
156
|
+
}[] | undefined;
|
|
157
|
+
readonly language?: string | undefined;
|
|
158
|
+
readonly segments?: readonly {
|
|
159
|
+
readonly text: string;
|
|
160
|
+
readonly startSeconds: number;
|
|
161
|
+
readonly endSeconds: number;
|
|
162
|
+
readonly speaker?: string | undefined;
|
|
163
|
+
}[] | undefined;
|
|
164
|
+
readonly words?: readonly {
|
|
165
|
+
readonly text: string;
|
|
166
|
+
readonly startSeconds: number;
|
|
167
|
+
readonly endSeconds: number;
|
|
168
|
+
readonly speaker?: string | undefined;
|
|
169
|
+
readonly confidence?: number | undefined;
|
|
170
|
+
}[] | undefined;
|
|
171
|
+
}, string, State>;
|
|
172
|
+
readonly model: (input: MediaRoute.ModelInput) => TranscriptionModel<GoogleTranscriptionOptions>;
|
|
173
|
+
};
|
|
174
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Effect, Schema, SchemaGetter } from "effect";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
5
|
+
import { TranscriptionFinishEvent, TranscriptionModel, TranscriptionSegmentEvent, TranscriptionTextDeltaEvent, } from "../transcription.js";
|
|
6
|
+
import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
|
|
7
|
+
const route = MediaProtocol.identity({ id: "google-transcription", name: "Google Transcription", provider: "google" });
|
|
8
|
+
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// 3. Streaming event schema
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const Seconds = Schema.String.check(Schema.isPattern(/^\d+(\.\d+)?s$/)).pipe(Schema.decodeTo(Schema.Number, {
|
|
13
|
+
decode: SchemaGetter.transform((value) => Number.parseFloat(value)),
|
|
14
|
+
encode: SchemaGetter.transform((value) => `${value}s`),
|
|
15
|
+
}));
|
|
16
|
+
const AudioTranscription = Schema.Struct({
|
|
17
|
+
text: Schema.String,
|
|
18
|
+
speakerLabel: Schema.optional(Schema.String),
|
|
19
|
+
words: Schema.optional(Schema.Array(Schema.Struct({
|
|
20
|
+
word: Schema.String,
|
|
21
|
+
startOffset: Seconds,
|
|
22
|
+
endOffset: Seconds,
|
|
23
|
+
speakerLabel: Schema.optional(Schema.String),
|
|
24
|
+
}))),
|
|
25
|
+
});
|
|
26
|
+
const decodeChunk = route.decodeFrame(GeminiGenerateContent.chunk(Schema.Struct({ audioTranscription: Schema.optional(AudioTranscription) })));
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// 5. Request body construction
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const fromRequest = Effect.fn("GoogleTranscription.fromRequest")(function* (request) {
|
|
31
|
+
// General Gemini models ignore `audioTranscriptionConfig` and answer the audio conversationally.
|
|
32
|
+
if (!request.model.id.includes("transcribe"))
|
|
33
|
+
return yield* route.unsupported("transcription.model", `${request.model.id} is not a transcription model; use a transcribe model such as gemini-3.5-transcribe`);
|
|
34
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
35
|
+
contents: [{ role: "user", parts: [yield* GeminiGenerateContent.mediaPart(route.id, request.audio)] }],
|
|
36
|
+
generationConfig: mergeJsonRecords({
|
|
37
|
+
audioTranscriptionConfig: {
|
|
38
|
+
languageCodes: request.language === undefined ? undefined : [request.language],
|
|
39
|
+
// Parts carry no offsets of their own, so segment times come from word offsets.
|
|
40
|
+
wordTimestamp: request.diarize === true || request.timestamps === "word" || request.timestamps === "segment"
|
|
41
|
+
? true
|
|
42
|
+
: undefined,
|
|
43
|
+
diarization: request.diarize === true ? true : undefined,
|
|
44
|
+
},
|
|
45
|
+
}, request.providerOptions),
|
|
46
|
+
}, request.http?.body) ?? {});
|
|
47
|
+
});
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// 6. Stream parsing
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
const turn = (part) => {
|
|
52
|
+
const words = (part.words ?? []).map((word) => ({
|
|
53
|
+
text: word.word,
|
|
54
|
+
startSeconds: word.startOffset,
|
|
55
|
+
endSeconds: word.endOffset,
|
|
56
|
+
speaker: word.speakerLabel ?? part.speakerLabel,
|
|
57
|
+
}));
|
|
58
|
+
const first = words[0];
|
|
59
|
+
const last = words.at(-1);
|
|
60
|
+
return {
|
|
61
|
+
text: part.text,
|
|
62
|
+
words,
|
|
63
|
+
segment: first === undefined || last === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: {
|
|
66
|
+
text: part.text,
|
|
67
|
+
startSeconds: first.startSeconds,
|
|
68
|
+
endSeconds: last.endSeconds,
|
|
69
|
+
speaker: part.speakerLabel,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
|
|
74
|
+
const chunk = yield* decodeChunk(frame);
|
|
75
|
+
const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
|
|
76
|
+
if (blocked !== undefined)
|
|
77
|
+
return yield* blocked;
|
|
78
|
+
const turns = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.audioTranscription === undefined ? [] : [turn(part.audioTranscription)]);
|
|
79
|
+
const segments = turns.flatMap((item) => (item.segment === undefined ? [] : [item.segment]));
|
|
80
|
+
state.words.push(...turns.flatMap((item) => item.words));
|
|
81
|
+
state.segments.push(...segments);
|
|
82
|
+
// Each part is one whole speaker turn without surrounding whitespace, so turns join with a space.
|
|
83
|
+
const text = turns
|
|
84
|
+
.map((item) => item.text)
|
|
85
|
+
.filter((item) => item.length > 0)
|
|
86
|
+
.join(" ");
|
|
87
|
+
const delta = text.length === 0 || state.text.length === 0 ? text : ` ${text}`;
|
|
88
|
+
const events = [
|
|
89
|
+
...(delta.length === 0 ? [] : [TranscriptionTextDeltaEvent.make({ delta })]),
|
|
90
|
+
...segments.map((segment) => TranscriptionSegmentEvent.make({ segment })),
|
|
91
|
+
];
|
|
92
|
+
return [{ ...GeminiGenerateContent.track(state, chunk), text: state.text + delta }, events];
|
|
93
|
+
});
|
|
94
|
+
const finish = (state) => {
|
|
95
|
+
if (state.finishReason === undefined)
|
|
96
|
+
return Effect.fail(route.incomplete());
|
|
97
|
+
return Effect.succeed([
|
|
98
|
+
TranscriptionFinishEvent.make({
|
|
99
|
+
text: state.text,
|
|
100
|
+
segments: state.segments.length === 0 ? undefined : state.segments,
|
|
101
|
+
words: state.words.length === 0 ? undefined : state.words,
|
|
102
|
+
usage: GeminiGenerateContent.usage(state.usage),
|
|
103
|
+
providerMetadata: GeminiGenerateContent.providerMetadata(state),
|
|
104
|
+
}),
|
|
105
|
+
]);
|
|
106
|
+
};
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// 7. Protocol and route
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
111
|
+
unsupported: ["prompt", "speakers"],
|
|
112
|
+
body: { from: fromRequest },
|
|
113
|
+
frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
|
|
114
|
+
initial: () => ({ text: "", segments: [], words: [] }),
|
|
115
|
+
step,
|
|
116
|
+
finish,
|
|
117
|
+
});
|
|
118
|
+
export const model = (input) => TranscriptionModel.fromRoute({
|
|
119
|
+
protocol,
|
|
120
|
+
baseURL: DEFAULT_BASE_URL,
|
|
121
|
+
path: ({ request }) => GeminiGenerateContent.path(request.model.id, request.mode),
|
|
122
|
+
}, input);
|
|
123
|
+
export const GoogleTranscription = {
|
|
124
|
+
protocol,
|
|
125
|
+
model,
|
|
126
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { type OpenString } from "../schema/index.js";
|
|
5
|
+
import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
|
|
6
|
+
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
7
|
+
/** Provider-native `parameters`. Common fields (`aspectRatio`, `resolution`, `durationSeconds`, `seed`) live on the request. */
|
|
8
|
+
export type GoogleVideoOptions = {
|
|
9
|
+
readonly personGeneration?: OpenString<"allow_all" | "allow_adult" | "dont_allow">;
|
|
10
|
+
} & Record<string, unknown>;
|
|
11
|
+
export type Request = VideoRequestFor<GoogleVideoOptions>;
|
|
12
|
+
/** The long-running operation name, e.g. `models/veo-3.1-generate-preview/operations/abc123`. */
|
|
13
|
+
export declare const Token: Schema.Struct<{
|
|
14
|
+
readonly operation: Schema.String;
|
|
15
|
+
}>;
|
|
16
|
+
export type Token = Schema.Schema.Type<typeof Token>;
|
|
17
|
+
export declare const protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
18
|
+
readonly operation: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const model: (input: MediaRoute.ModelInput) => VideoModel<GoogleVideoOptions>;
|
|
21
|
+
export declare const GoogleVideo: {
|
|
22
|
+
readonly protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
23
|
+
readonly operation: string;
|
|
24
|
+
}>;
|
|
25
|
+
readonly model: (input: MediaRoute.ModelInput) => VideoModel<GoogleVideoOptions>;
|
|
26
|
+
};
|