@opencode/ai 2.0.15 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +286 -2
  2. package/dist/generation.d.ts +36 -22
  3. package/dist/generation.js +53 -24
  4. package/dist/image-client.d.ts +14 -7
  5. package/dist/image-client.js +24 -8
  6. package/dist/image.d.ts +398 -47
  7. package/dist/image.js +47 -45
  8. package/dist/index.d.ts +13 -1
  9. package/dist/index.js +9 -0
  10. package/dist/media-model.d.ts +44 -0
  11. package/dist/media-model.js +49 -0
  12. package/dist/media.d.ts +10 -9
  13. package/dist/media.js +9 -10
  14. package/dist/promise.d.ts +428 -8
  15. package/dist/promise.js +40 -3
  16. package/dist/protocols/alibaba-chat.d.ts +12 -0
  17. package/dist/protocols/alibaba-responses.d.ts +2 -2
  18. package/dist/protocols/anthropic-messages.js +1 -2
  19. package/dist/protocols/assemblyai-transcription.d.ts +40 -0
  20. package/dist/protocols/assemblyai-transcription.js +138 -0
  21. package/dist/protocols/bedrock-converse.js +5 -11
  22. package/dist/protocols/bfl-images.d.ts +32 -0
  23. package/dist/protocols/bfl-images.js +153 -0
  24. package/dist/protocols/cartesia-speech.d.ts +127 -0
  25. package/dist/protocols/cartesia-speech.js +126 -0
  26. package/dist/protocols/deepgram-speech.d.ts +119 -0
  27. package/dist/protocols/deepgram-speech.js +92 -0
  28. package/dist/protocols/deepgram-transcription.d.ts +25 -0
  29. package/dist/protocols/deepgram-transcription.js +129 -0
  30. package/dist/protocols/elevenlabs-speech.d.ts +122 -0
  31. package/dist/protocols/elevenlabs-speech.js +115 -0
  32. package/dist/protocols/fal-images.d.ts +24 -0
  33. package/dist/protocols/fal-images.js +114 -0
  34. package/dist/protocols/fal-video.d.ts +29 -0
  35. package/dist/protocols/fal-video.js +88 -0
  36. package/dist/protocols/gemini.d.ts +9 -9
  37. package/dist/protocols/gemini.js +8 -34
  38. package/dist/protocols/google-images.js +2 -14
  39. package/dist/protocols/google-speech.d.ts +130 -0
  40. package/dist/protocols/google-speech.js +84 -0
  41. package/dist/protocols/google-transcription.d.ts +173 -0
  42. package/dist/protocols/google-transcription.js +138 -0
  43. package/dist/protocols/google-video.d.ts +26 -0
  44. package/dist/protocols/google-video.js +158 -0
  45. package/dist/protocols/meta-images.js +2 -9
  46. package/dist/protocols/meta-responses.d.ts +4 -4
  47. package/dist/protocols/meta-responses.js +1 -1
  48. package/dist/protocols/open-responses.d.ts +6 -6
  49. package/dist/protocols/open-responses.js +1 -2
  50. package/dist/protocols/openai-chat.d.ts +84 -0
  51. package/dist/protocols/openai-chat.js +26 -14
  52. package/dist/protocols/openai-compatible-chat.d.ts +12 -0
  53. package/dist/protocols/openai-compatible-responses.d.ts +2 -2
  54. package/dist/protocols/openai-images.d.ts +124 -3
  55. package/dist/protocols/openai-images.js +107 -54
  56. package/dist/protocols/openai-responses.d.ts +15 -15
  57. package/dist/protocols/openai-responses.js +5 -6
  58. package/dist/protocols/openai-speech.d.ts +116 -0
  59. package/dist/protocols/openai-speech.js +98 -0
  60. package/dist/protocols/openai-transcription.d.ts +207 -0
  61. package/dist/protocols/openai-transcription.js +190 -0
  62. package/dist/protocols/replicate-images.d.ts +28 -0
  63. package/dist/protocols/replicate-images.js +133 -0
  64. package/dist/protocols/runway-video.d.ts +38 -0
  65. package/dist/protocols/runway-video.js +146 -0
  66. package/dist/protocols/shared.d.ts +13 -3
  67. package/dist/protocols/shared.js +23 -3
  68. package/dist/protocols/stability-images.d.ts +38 -0
  69. package/dist/protocols/stability-images.js +148 -0
  70. package/dist/protocols/utils/fal-queue.d.ts +28 -0
  71. package/dist/protocols/utils/fal-queue.js +69 -0
  72. package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
  73. package/dist/protocols/utils/gemini-generate-content.js +65 -0
  74. package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
  75. package/dist/protocols/utils/gemini-json-schema.js +76 -0
  76. package/dist/protocols/utils/media-input.d.ts +8 -0
  77. package/dist/protocols/utils/media-input.js +18 -0
  78. package/dist/protocols/utils/speech-stream.d.ts +49 -0
  79. package/dist/protocols/utils/speech-stream.js +67 -0
  80. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  81. package/dist/protocols/utils/tool-schema.js +40 -17
  82. package/dist/protocols/xai-images.js +1 -12
  83. package/dist/protocols/xai-responses.d.ts +2 -2
  84. package/dist/protocols/xai-video.d.ts +34 -0
  85. package/dist/protocols/xai-video.js +147 -0
  86. package/dist/protocols/zai-chat.d.ts +13 -1
  87. package/dist/provider-error.js +3 -0
  88. package/dist/providers/alibaba.d.ts +14 -2
  89. package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
  90. package/dist/providers/assemblyai.d.ts +25 -0
  91. package/dist/providers/assemblyai.js +29 -0
  92. package/dist/providers/azure.d.ts +18 -6
  93. package/dist/providers/baseten.d.ts +24 -0
  94. package/dist/providers/black-forest-labs.d.ts +25 -0
  95. package/dist/providers/black-forest-labs.js +28 -0
  96. package/dist/providers/cartesia.d.ts +24 -0
  97. package/dist/providers/cartesia.js +22 -0
  98. package/dist/providers/cerebras.d.ts +24 -0
  99. package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
  100. package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
  101. package/dist/providers/deepgram.d.ts +29 -0
  102. package/dist/providers/deepgram.js +31 -0
  103. package/dist/providers/deepinfra.d.ts +24 -0
  104. package/dist/providers/deepseek.d.ts +24 -0
  105. package/dist/providers/elevenlabs.d.ts +24 -0
  106. package/dist/providers/elevenlabs.js +28 -0
  107. package/dist/providers/fal.d.ts +29 -0
  108. package/dist/providers/fal.js +33 -0
  109. package/dist/providers/fireworks.d.ts +24 -0
  110. package/dist/providers/google-vertex-chat.d.ts +12 -0
  111. package/dist/providers/google-vertex-responses.d.ts +2 -2
  112. package/dist/providers/google-vertex.d.ts +3 -3
  113. package/dist/providers/google.d.ts +18 -3
  114. package/dist/providers/google.js +11 -2
  115. package/dist/providers/groq.d.ts +24 -0
  116. package/dist/providers/index.d.ts +9 -0
  117. package/dist/providers/index.js +9 -0
  118. package/dist/providers/meta.d.ts +14 -2
  119. package/dist/providers/minimax.d.ts +14 -2
  120. package/dist/providers/moonshot.d.ts +14 -2
  121. package/dist/providers/moonshot.js +3 -3
  122. package/dist/providers/openai-compatible-responses.d.ts +2 -2
  123. package/dist/providers/openai-compatible.d.ts +12 -0
  124. package/dist/providers/openai.d.ts +25 -3
  125. package/dist/providers/openai.js +10 -1
  126. package/dist/providers/openrouter.d.ts +48 -0
  127. package/dist/providers/replicate.d.ts +25 -0
  128. package/dist/providers/replicate.js +22 -0
  129. package/dist/providers/runway.d.ts +24 -0
  130. package/dist/providers/runway.js +22 -0
  131. package/dist/providers/stability.d.ts +28 -0
  132. package/dist/providers/stability.js +23 -0
  133. package/dist/providers/togetherai.d.ts +24 -0
  134. package/dist/providers/xai.d.ts +17 -0
  135. package/dist/providers/xai.js +5 -2
  136. package/dist/providers/zai-coding-plan.d.ts +15 -3
  137. package/dist/providers/zai.d.ts +13 -1
  138. package/dist/route/auth.d.ts +4 -1
  139. package/dist/route/auth.js +6 -0
  140. package/dist/route/framing.d.ts +5 -1
  141. package/dist/route/framing.js +9 -0
  142. package/dist/route/media-protocol.d.ts +116 -3
  143. package/dist/route/media-protocol.js +60 -4
  144. package/dist/route/media.d.ts +58 -7
  145. package/dist/route/media.js +201 -29
  146. package/dist/schema/events.d.ts +0 -6
  147. package/dist/schema/messages.d.ts +0 -3
  148. package/dist/schema/options.d.ts +4 -3
  149. package/dist/schema/options.js +3 -2
  150. package/dist/speech-client.d.ts +21 -0
  151. package/dist/speech-client.js +25 -0
  152. package/dist/speech.d.ts +1150 -0
  153. package/dist/speech.js +119 -0
  154. package/dist/transcription-client.d.ts +28 -0
  155. package/dist/transcription-client.js +44 -0
  156. package/dist/transcription.d.ts +1504 -0
  157. package/dist/transcription.js +133 -0
  158. package/dist/utils/bytes.d.ts +1 -0
  159. package/dist/utils/bytes.js +10 -0
  160. package/dist/utils/media-type.d.ts +1 -0
  161. package/dist/utils/media-type.js +22 -1
  162. package/dist/video-client.d.ts +28 -0
  163. package/dist/video-client.js +40 -0
  164. package/dist/video.d.ts +1359 -0
  165. package/dist/video.js +119 -0
  166. package/package.json +3 -3
  167. package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
  168. package/dist/protocols/utils/gemini-tool-schema.js +0 -103
@@ -0,0 +1,173 @@
1
+ import { MediaProtocol } from "../route/media-protocol.js";
2
+ import { MediaRoute } from "../route/media.js";
3
+ import { TranscriptionModel, type TranscriptionRequestFor, type TranscriptionSegment, type TranscriptionWord } from "../transcription.js";
4
+ import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
5
+ export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
6
+ /**
7
+ * Merged into `generationConfig`. The API rejects `customVocabulary` and `mode: "SMART"` alongside diarization or word
8
+ * timestamps.
9
+ */
10
+ export type GoogleTranscriptionOptions = {
11
+ readonly audioTranscriptionConfig?: {
12
+ readonly mode?: "VERBATIM" | "SMART" | (string & {});
13
+ readonly customVocabulary?: ReadonlyArray<string>;
14
+ readonly languageCodes?: ReadonlyArray<string>;
15
+ };
16
+ } & Record<string, unknown>;
17
+ export type Request = TranscriptionRequestFor<GoogleTranscriptionOptions>;
18
+ interface State extends GeminiGenerateContent.Metadata {
19
+ readonly text: string;
20
+ readonly segments: Array<TranscriptionSegment>;
21
+ readonly words: Array<TranscriptionWord>;
22
+ }
23
+ export declare const protocol: MediaProtocol.Streamed<Request, {
24
+ readonly id: string;
25
+ readonly type: "generation-queued";
26
+ readonly position?: number | undefined;
27
+ } | {
28
+ readonly id: string;
29
+ readonly type: "generation-progress";
30
+ readonly progress?: number | undefined;
31
+ } | {
32
+ readonly type: "text-delta";
33
+ readonly delta: string;
34
+ } | {
35
+ readonly type: "segment";
36
+ readonly segment: {
37
+ readonly text: string;
38
+ readonly startSeconds: number;
39
+ readonly endSeconds: number;
40
+ readonly speaker?: string | undefined;
41
+ };
42
+ } | {
43
+ readonly type: "finish";
44
+ readonly text: string;
45
+ readonly durationSeconds?: number | undefined;
46
+ readonly providerMetadata?: {
47
+ readonly [x: string]: {
48
+ readonly [x: string]: unknown;
49
+ };
50
+ } | undefined;
51
+ readonly usage?: {
52
+ readonly type: "tokens";
53
+ readonly input?: number | undefined;
54
+ readonly output?: number | undefined;
55
+ readonly total?: number | undefined;
56
+ readonly details?: {
57
+ readonly [x: string]: unknown;
58
+ } | undefined;
59
+ } | {
60
+ readonly type: "seconds";
61
+ readonly seconds: number;
62
+ } | {
63
+ readonly type: "characters";
64
+ readonly characters: number;
65
+ } | {
66
+ readonly type: "credits";
67
+ readonly credits: number;
68
+ } | {
69
+ readonly type: "compute";
70
+ readonly seconds: number;
71
+ } | undefined;
72
+ readonly notices?: readonly {
73
+ readonly type: "other" | "moderated" | "filtered";
74
+ readonly message: string;
75
+ readonly providerMetadata?: {
76
+ readonly [x: string]: {
77
+ readonly [x: string]: unknown;
78
+ };
79
+ } | undefined;
80
+ }[] | undefined;
81
+ readonly language?: string | undefined;
82
+ readonly segments?: readonly {
83
+ readonly text: string;
84
+ readonly startSeconds: number;
85
+ readonly endSeconds: number;
86
+ readonly speaker?: string | undefined;
87
+ }[] | undefined;
88
+ readonly words?: readonly {
89
+ readonly text: string;
90
+ readonly startSeconds: number;
91
+ readonly endSeconds: number;
92
+ readonly speaker?: string | undefined;
93
+ readonly confidence?: number | undefined;
94
+ }[] | undefined;
95
+ }, string, State>;
96
+ export declare const model: (input: MediaRoute.ModelInput) => TranscriptionModel<GoogleTranscriptionOptions>;
97
+ export declare const GoogleTranscription: {
98
+ readonly protocol: MediaProtocol.Streamed<Request, {
99
+ readonly id: string;
100
+ readonly type: "generation-queued";
101
+ readonly position?: number | undefined;
102
+ } | {
103
+ readonly id: string;
104
+ readonly type: "generation-progress";
105
+ readonly progress?: number | undefined;
106
+ } | {
107
+ readonly type: "text-delta";
108
+ readonly delta: string;
109
+ } | {
110
+ readonly type: "segment";
111
+ readonly segment: {
112
+ readonly text: string;
113
+ readonly startSeconds: number;
114
+ readonly endSeconds: number;
115
+ readonly speaker?: string | undefined;
116
+ };
117
+ } | {
118
+ readonly type: "finish";
119
+ readonly text: string;
120
+ readonly durationSeconds?: number | undefined;
121
+ readonly providerMetadata?: {
122
+ readonly [x: string]: {
123
+ readonly [x: string]: unknown;
124
+ };
125
+ } | undefined;
126
+ readonly usage?: {
127
+ readonly type: "tokens";
128
+ readonly input?: number | undefined;
129
+ readonly output?: number | undefined;
130
+ readonly total?: number | undefined;
131
+ readonly details?: {
132
+ readonly [x: string]: unknown;
133
+ } | undefined;
134
+ } | {
135
+ readonly type: "seconds";
136
+ readonly seconds: number;
137
+ } | {
138
+ readonly type: "characters";
139
+ readonly characters: number;
140
+ } | {
141
+ readonly type: "credits";
142
+ readonly credits: number;
143
+ } | {
144
+ readonly type: "compute";
145
+ readonly seconds: number;
146
+ } | undefined;
147
+ readonly notices?: readonly {
148
+ readonly type: "other" | "moderated" | "filtered";
149
+ readonly message: string;
150
+ readonly providerMetadata?: {
151
+ readonly [x: string]: {
152
+ readonly [x: string]: unknown;
153
+ };
154
+ } | undefined;
155
+ }[] | undefined;
156
+ readonly language?: string | undefined;
157
+ readonly segments?: readonly {
158
+ readonly text: string;
159
+ readonly startSeconds: number;
160
+ readonly endSeconds: number;
161
+ readonly speaker?: string | undefined;
162
+ }[] | undefined;
163
+ readonly words?: readonly {
164
+ readonly text: string;
165
+ readonly startSeconds: number;
166
+ readonly endSeconds: number;
167
+ readonly speaker?: string | undefined;
168
+ readonly confidence?: number | undefined;
169
+ }[] | undefined;
170
+ }, string, State>;
171
+ readonly model: (input: MediaRoute.ModelInput) => TranscriptionModel<GoogleTranscriptionOptions>;
172
+ };
173
+ export {};
@@ -0,0 +1,138 @@
1
+ import { Effect, Schema, SchemaGetter } from "effect";
2
+ import { MediaProtocol } from "../route/media-protocol.js";
3
+ import { MediaRoute } from "../route/media.js";
4
+ import { ProviderID, mergeJsonRecords } from "../schema/index.js";
5
+ import { TranscriptionFinishEvent, TranscriptionModel, TranscriptionSegmentEvent, TranscriptionTextDeltaEvent, } from "../transcription.js";
6
+ import { ProviderShared } from "./shared.js";
7
+ import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
8
+ const ADAPTER = "google-transcription";
9
+ const NAME = "Google Transcription";
10
+ const PROVIDER = ProviderID.make("google");
11
+ export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
12
+ // ---------------------------------------------------------------------------
13
+ // 3. Streaming event schema
14
+ // ---------------------------------------------------------------------------
15
+ const Seconds = Schema.String.check(Schema.isPattern(/^\d+(\.\d+)?s$/)).pipe(Schema.decodeTo(Schema.Number, {
16
+ decode: SchemaGetter.transform((value) => Number.parseFloat(value)),
17
+ encode: SchemaGetter.transform((value) => `${value}s`),
18
+ }));
19
+ const AudioTranscription = Schema.Struct({
20
+ text: Schema.String,
21
+ speakerLabel: Schema.optional(Schema.String),
22
+ words: Schema.optional(Schema.Array(Schema.Struct({
23
+ word: Schema.String,
24
+ startOffset: Seconds,
25
+ endOffset: Seconds,
26
+ speakerLabel: Schema.optional(Schema.String),
27
+ }))),
28
+ });
29
+ const decodeChunk = MediaProtocol.decodeFrame(ADAPTER, NAME, GeminiGenerateContent.chunk(Schema.Struct({ audioTranscription: Schema.optional(AudioTranscription) })));
30
+ // ---------------------------------------------------------------------------
31
+ // 5. Request body construction
32
+ // ---------------------------------------------------------------------------
33
+ const fromRequest = Effect.fn("GoogleTranscription.fromRequest")(function* (request) {
34
+ // General Gemini models ignore `audioTranscriptionConfig` and answer the audio conversationally.
35
+ if (!request.model.id.includes("transcribe"))
36
+ return yield* ProviderShared.unsupportedOperation({
37
+ operation: "transcription.model",
38
+ provider: PROVIDER,
39
+ route: ADAPTER,
40
+ message: `${request.model.id} is not a transcription model; use a transcribe model such as gemini-3.5-transcribe`,
41
+ });
42
+ return MediaProtocol.json(mergeJsonRecords({
43
+ contents: [{ role: "user", parts: [yield* GeminiGenerateContent.mediaPart(ADAPTER, request.audio)] }],
44
+ generationConfig: mergeJsonRecords({
45
+ audioTranscriptionConfig: {
46
+ languageCodes: request.language === undefined ? undefined : [request.language],
47
+ // Parts carry no offsets of their own, so segment times come from word offsets.
48
+ wordTimestamp: request.diarize === true || request.timestamps === "word" || request.timestamps === "segment"
49
+ ? true
50
+ : undefined,
51
+ diarization: request.diarize === true ? true : undefined,
52
+ },
53
+ }, request.providerOptions),
54
+ }, request.http?.body) ?? {});
55
+ });
56
+ // ---------------------------------------------------------------------------
57
+ // 6. Stream parsing
58
+ // ---------------------------------------------------------------------------
59
+ const turn = (part) => {
60
+ const words = (part.words ?? []).map((word) => ({
61
+ text: word.word,
62
+ startSeconds: word.startOffset,
63
+ endSeconds: word.endOffset,
64
+ speaker: word.speakerLabel ?? part.speakerLabel,
65
+ }));
66
+ const first = words[0];
67
+ const last = words.at(-1);
68
+ return {
69
+ text: part.text,
70
+ words,
71
+ segment: first === undefined || last === undefined
72
+ ? undefined
73
+ : {
74
+ text: part.text,
75
+ startSeconds: first.startSeconds,
76
+ endSeconds: last.endSeconds,
77
+ speaker: part.speakerLabel,
78
+ },
79
+ };
80
+ };
81
+ const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
82
+ const chunk = yield* decodeChunk(frame);
83
+ const blocked = GeminiGenerateContent.blocked(NAME, chunk, frame);
84
+ if (blocked !== undefined)
85
+ return yield* blocked;
86
+ const turns = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.audioTranscription === undefined ? [] : [turn(part.audioTranscription)]);
87
+ const segments = turns.flatMap((item) => (item.segment === undefined ? [] : [item.segment]));
88
+ state.words.push(...turns.flatMap((item) => item.words));
89
+ state.segments.push(...segments);
90
+ // Each part is one whole speaker turn without surrounding whitespace, so turns join with a space.
91
+ const text = turns
92
+ .map((item) => item.text)
93
+ .filter((item) => item.length > 0)
94
+ .join(" ");
95
+ const delta = text.length === 0 || state.text.length === 0 ? text : ` ${text}`;
96
+ const events = [
97
+ ...(delta.length === 0 ? [] : [TranscriptionTextDeltaEvent.make({ delta })]),
98
+ ...segments.map((segment) => TranscriptionSegmentEvent.make({ segment })),
99
+ ];
100
+ return [{ ...GeminiGenerateContent.track(state, chunk), text: state.text + delta }, events];
101
+ });
102
+ const finish = (state) => {
103
+ if (state.finishReason === undefined)
104
+ return Effect.fail(MediaProtocol.incomplete(ADAPTER));
105
+ return Effect.succeed([
106
+ TranscriptionFinishEvent.make({
107
+ text: state.text,
108
+ segments: state.segments.length === 0 ? undefined : state.segments,
109
+ words: state.words.length === 0 ? undefined : state.words,
110
+ usage: GeminiGenerateContent.usage(state.usage),
111
+ providerMetadata: GeminiGenerateContent.providerMetadata(state),
112
+ }),
113
+ ]);
114
+ };
115
+ // ---------------------------------------------------------------------------
116
+ // 7. Protocol and route
117
+ // ---------------------------------------------------------------------------
118
+ export const protocol = MediaProtocol.stream({
119
+ id: ADAPTER,
120
+ name: NAME,
121
+ unsupported: ["prompt", "speakers"],
122
+ body: { from: fromRequest },
123
+ frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
124
+ initial: () => ({ text: "", segments: [], words: [] }),
125
+ step,
126
+ finish,
127
+ });
128
+ export const model = (input) => TranscriptionModel.fromRoute({
129
+ id: ADAPTER,
130
+ provider: PROVIDER,
131
+ protocol,
132
+ baseURL: DEFAULT_BASE_URL,
133
+ path: ({ request }) => GeminiGenerateContent.path(request.model.id, request.mode),
134
+ }, input);
135
+ export const GoogleTranscription = {
136
+ protocol,
137
+ model,
138
+ };
@@ -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 { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
5
+ export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
6
+ export type GoogleVideoString<Known extends string> = Known | (string & {});
7
+ /** Provider-native `parameters`. Common fields (`aspectRatio`, `resolution`, `durationSeconds`, `seed`) live on the request. */
8
+ export type GoogleVideoOptions = {
9
+ readonly personGeneration?: GoogleVideoString<"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
+ };
@@ -0,0 +1,158 @@
1
+ import { Duration, Effect, Schema } from "effect";
2
+ import { Media } from "../media.js";
3
+ import { MediaProtocol } from "../route/media-protocol.js";
4
+ import { MediaRoute } from "../route/media.js";
5
+ import { ProviderID, mergeJsonRecords } from "../schema/index.js";
6
+ import { VideoModel, VideoResponse } from "../video.js";
7
+ import { ProviderShared, optionalArray } from "./shared.js";
8
+ const ADAPTER = "google-video";
9
+ const NAME = "Google Veo";
10
+ const PROVIDER = ProviderID.make("google");
11
+ export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
12
+ /** Veo keeps generated files for two days; the asset carries that deadline so callers materialize in time. */
13
+ const FILE_RETENTION = Duration.days(2);
14
+ // ---------------------------------------------------------------------------
15
+ // 2. Token and response schemas
16
+ // ---------------------------------------------------------------------------
17
+ /** The long-running operation name, e.g. `models/veo-3.1-generate-preview/operations/abc123`. */
18
+ export const Token = Schema.Struct({ operation: Schema.String });
19
+ const StartResponse = Schema.Struct({ name: Schema.String });
20
+ const Operation = Schema.Struct({
21
+ done: Schema.optional(Schema.Boolean),
22
+ error: Schema.optional(Schema.Struct({ message: Schema.optional(Schema.String) })),
23
+ response: Schema.optional(Schema.Struct({
24
+ generateVideoResponse: Schema.optional(Schema.Struct({
25
+ generatedSamples: optionalArray(Schema.Struct({
26
+ video: Schema.optional(Schema.Struct({
27
+ uri: Schema.optional(Schema.String),
28
+ mimeType: Schema.optional(Schema.String),
29
+ })),
30
+ })),
31
+ raiMediaFilteredCount: Schema.optional(Schema.Number),
32
+ raiMediaFilteredReasons: optionalArray(Schema.String),
33
+ })),
34
+ })),
35
+ metadata: Schema.optional(Schema.Unknown),
36
+ });
37
+ // ---------------------------------------------------------------------------
38
+ // 5. Request body construction
39
+ // ---------------------------------------------------------------------------
40
+ // Veo takes inline media only; a prior Veo output is `Media.url` with transient auth, so materialize it first.
41
+ const inlineMedia = (asset) => ProviderShared.requireInlineMedia(NAME, asset).pipe(Effect.map((inline) => ({ inlineData: { mimeType: inline.mime, data: inline.base64 } })));
42
+ const fromRequest = Effect.fn("GoogleVideo.fromRequest")(function* (request) {
43
+ if (request.n !== undefined && request.n > 1)
44
+ return yield* ProviderShared.unsupportedOperation({
45
+ operation: "video.n",
46
+ provider: PROVIDER,
47
+ route: ADAPTER,
48
+ message: `${NAME} generates one video per request; call it once per video instead of n=${request.n}`,
49
+ });
50
+ if (request.audio === false)
51
+ return yield* ProviderShared.unsupportedOperation({
52
+ operation: "video.audio",
53
+ provider: PROVIDER,
54
+ route: ADAPTER,
55
+ message: `${NAME} always generates audio; audio: false cannot be honored`,
56
+ });
57
+ if (request.frames?.last !== undefined && request.frames.first === undefined)
58
+ return yield* ProviderShared.invalidRequest(`${NAME} requires frames.first when frames.last is set`);
59
+ const image = request.frames?.first === undefined ? undefined : yield* inlineMedia(request.frames.first);
60
+ const lastFrame = request.frames?.last === undefined ? undefined : yield* inlineMedia(request.frames.last);
61
+ const video = request.video === undefined ? undefined : yield* inlineMedia(request.video);
62
+ const referenceImages = yield* Effect.forEach(request.references ?? [], (asset) => inlineMedia(asset).pipe(Effect.map((image) => ({ image, referenceType: "asset" }))));
63
+ return MediaProtocol.json(mergeJsonRecords({
64
+ instances: [
65
+ {
66
+ prompt: request.prompt,
67
+ image,
68
+ lastFrame,
69
+ referenceImages: referenceImages.length === 0 ? undefined : referenceImages,
70
+ video,
71
+ },
72
+ ],
73
+ parameters: mergeJsonRecords({
74
+ aspectRatio: request.aspectRatio,
75
+ resolution: request.resolution,
76
+ durationSeconds: request.durationSeconds,
77
+ negativePrompt: request.negativePrompt,
78
+ seed: request.seed,
79
+ }, request.providerOptions),
80
+ }, request.http?.body) ?? {});
81
+ });
82
+ // ---------------------------------------------------------------------------
83
+ // 6. Response decoding
84
+ // ---------------------------------------------------------------------------
85
+ const decodeStart = MediaProtocol.decodeStarted(ADAPTER, NAME, StartResponse, (value) => ({
86
+ token: { operation: value.name },
87
+ snapshot: { id: value.name, status: "running" },
88
+ }));
89
+ // Operations carry no status string: not done is running, done with `error` failed, otherwise completed.
90
+ const statusOf = (operation) => {
91
+ if (operation.done !== true)
92
+ return "running";
93
+ return operation.error === undefined ? "completed" : "failed";
94
+ };
95
+ const decodeOperation = MediaProtocol.decodeJson(ADAPTER, NAME, Operation);
96
+ const decodeStatus = Effect.fn("GoogleVideo.decodeStatus")(function* (response, context) {
97
+ const output = yield* decodeOperation(response);
98
+ return { id: context.token.operation, status: statusOf(output.value) };
99
+ });
100
+ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response, context) {
101
+ const output = yield* decodeOperation(response);
102
+ const operation = output.value;
103
+ const status = statusOf(operation);
104
+ if (status === "running")
105
+ return yield* output.invalid(`${NAME} operation ${context.token.operation} has not finished`);
106
+ if (status === "failed")
107
+ return yield* output.ended("failed", `${NAME} operation failed${operation.error?.message === undefined ? "" : `: ${operation.error.message}`}`);
108
+ const generated = operation.response?.generateVideoResponse;
109
+ // Downloads require the same API key as the poll; the asset carries it transiently and follows the redirect.
110
+ 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, {
111
+ mediaType: video.mimeType ?? "video/mp4",
112
+ headers: context.auth,
113
+ }));
114
+ const reasons = generated?.raiMediaFilteredReasons ?? [];
115
+ const notices = reasons.map((reason) => ({
116
+ type: "filtered",
117
+ message: `${NAME} filtered media: ${reason}`,
118
+ providerMetadata: { google: { raiMediaFilteredReason: reason } },
119
+ }));
120
+ if (videos.length === 0 && (reasons.length > 0 || (generated?.raiMediaFilteredCount ?? 0) > 0))
121
+ return yield* output.contentPolicy(`${NAME} filtered every video${reasons.length === 0 ? "" : `: ${reasons.join("; ")}`}`);
122
+ if (videos.length === 0)
123
+ return yield* output.invalid(`${NAME} operation completed without any video`);
124
+ return new VideoResponse({
125
+ videos,
126
+ notices: notices.length === 0 ? undefined : notices,
127
+ providerMetadata: {
128
+ google: {
129
+ operation: context.token.operation,
130
+ raiMediaFilteredCount: generated?.raiMediaFilteredCount,
131
+ metadata: operation.metadata,
132
+ },
133
+ },
134
+ });
135
+ });
136
+ // ---------------------------------------------------------------------------
137
+ // 7. Protocol and route
138
+ // ---------------------------------------------------------------------------
139
+ const operationPath = (token) => `/${token.operation}`;
140
+ export const protocol = MediaProtocol.queued({
141
+ id: ADAPTER,
142
+ name: NAME,
143
+ token: Token,
144
+ start: { body: { from: fromRequest }, decode: decodeStart },
145
+ status: { path: operationPath, decode: decodeStatus },
146
+ result: { path: operationPath, decode: decodeResult },
147
+ });
148
+ export const model = (input) => VideoModel.fromRoute({
149
+ id: ADAPTER,
150
+ provider: PROVIDER,
151
+ protocol,
152
+ baseURL: DEFAULT_BASE_URL,
153
+ path: ({ request }) => `/models/${request.model.id}:predictLongRunning`,
154
+ }, input);
155
+ export const GoogleVideo = {
156
+ protocol,
157
+ model,
158
+ };
@@ -37,15 +37,8 @@ const Response = Schema.Struct({
37
37
  // 5. Request body construction
38
38
  // ---------------------------------------------------------------------------
39
39
  const isEdit = (request) => (request.images?.length ?? 0) > 0;
40
- const reference = (asset) => {
41
- const inline = asset.inline();
42
- if (inline)
43
- return Effect.succeed({ image_url: inline.dataUrl });
44
- const url = ProviderShared.mediaUrl(asset);
45
- if (url)
46
- return Effect.succeed({ image_url: url });
47
- return Effect.fail(ProviderShared.invalidRequest(`${NAME} accepts image bytes and URLs`));
48
- };
40
+ // Meta has no file handles: refs are rejected even when they name this provider.
41
+ const reference = (asset) => ProviderShared.mediaReference(asset, undefined, NAME).pipe(Effect.map((item) => ({ image_url: item.value })));
49
42
  const fromRequest = Effect.fn("MetaImages.fromRequest")(function* (request) {
50
43
  const images = yield* Effect.forEach(request.images ?? [], reference);
51
44
  const { responseFormat, reasoningStrength, toolEnablement, ...native } = request.providerOptions ?? {};
@@ -92,8 +92,8 @@ export declare const protocol: Protocol<{
92
92
  } | {
93
93
  readonly type: "input_file";
94
94
  readonly filename: string;
95
- readonly detail?: string | undefined;
96
95
  readonly file_data?: string | undefined;
96
+ readonly detail?: string | undefined;
97
97
  readonly file_url?: string | undefined;
98
98
  } | {
99
99
  readonly type: "input_text";
@@ -129,8 +129,8 @@ export declare const protocol: Protocol<{
129
129
  } | {
130
130
  readonly type: "input_file";
131
131
  readonly filename: string;
132
- readonly detail?: string | undefined;
133
132
  readonly file_data?: string | undefined;
133
+ readonly detail?: string | undefined;
134
134
  readonly file_url?: string | undefined;
135
135
  } | {
136
136
  readonly type: "input_text";
@@ -338,8 +338,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
338
338
  } | {
339
339
  readonly type: "input_file";
340
340
  readonly filename: string;
341
- readonly detail?: string | undefined;
342
341
  readonly file_data?: string | undefined;
342
+ readonly detail?: string | undefined;
343
343
  readonly file_url?: string | undefined;
344
344
  } | {
345
345
  readonly type: "input_text";
@@ -375,8 +375,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
375
375
  } | {
376
376
  readonly type: "input_file";
377
377
  readonly filename: string;
378
- readonly detail?: string | undefined;
379
378
  readonly file_data?: string | undefined;
379
+ readonly detail?: string | undefined;
380
380
  readonly file_url?: string | undefined;
381
381
  } | {
382
382
  readonly type: "input_text";
@@ -82,7 +82,7 @@ const fromRequest = Effect.fn("MetaResponses.fromRequest")(function* (request) {
82
82
  ? undefined
83
83
  : yield* Effect.forEach(projected.tools, (tool) => Effect.gen(function* () {
84
84
  if (tool.native === undefined)
85
- return yield* OpenResponses.lowerTool(NAME, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model.compatibility?.toolSchema));
85
+ return yield* OpenResponses.lowerTool(NAME, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model));
86
86
  return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(NativeTool))(tool.native.meta);
87
87
  })),
88
88
  tool_choice: OpenResponses.allowedToolChoice(request) ??
@@ -895,8 +895,8 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
895
895
  } | {
896
896
  readonly type: "input_file";
897
897
  readonly filename: string;
898
- readonly detail?: string | undefined;
899
898
  readonly file_data?: string | undefined;
899
+ readonly detail?: string | undefined;
900
900
  readonly file_url?: string | undefined;
901
901
  } | {
902
902
  readonly type: "input_text";
@@ -932,8 +932,8 @@ export declare const fromRequest: (request: LLMRequest<import("../schema/options
932
932
  } | {
933
933
  readonly type: "input_file";
934
934
  readonly filename: string;
935
- readonly detail?: string | undefined;
936
935
  readonly file_data?: string | undefined;
936
+ readonly detail?: string | undefined;
937
937
  readonly file_url?: string | undefined;
938
938
  } | {
939
939
  readonly type: "input_text";
@@ -1292,8 +1292,8 @@ export declare const protocol: Protocol<{
1292
1292
  } | {
1293
1293
  readonly type: "input_file";
1294
1294
  readonly filename: string;
1295
- readonly detail?: string | undefined;
1296
1295
  readonly file_data?: string | undefined;
1296
+ readonly detail?: string | undefined;
1297
1297
  readonly file_url?: string | undefined;
1298
1298
  } | {
1299
1299
  readonly type: "input_text";
@@ -1329,8 +1329,8 @@ export declare const protocol: Protocol<{
1329
1329
  } | {
1330
1330
  readonly type: "input_file";
1331
1331
  readonly filename: string;
1332
- readonly detail?: string | undefined;
1333
1332
  readonly file_data?: string | undefined;
1333
+ readonly detail?: string | undefined;
1334
1334
  readonly file_url?: string | undefined;
1335
1335
  } | {
1336
1336
  readonly type: "input_text";
@@ -1513,8 +1513,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
1513
1513
  } | {
1514
1514
  readonly type: "input_file";
1515
1515
  readonly filename: string;
1516
- readonly detail?: string | undefined;
1517
1516
  readonly file_data?: string | undefined;
1517
+ readonly detail?: string | undefined;
1518
1518
  readonly file_url?: string | undefined;
1519
1519
  } | {
1520
1520
  readonly type: "input_text";
@@ -1550,8 +1550,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
1550
1550
  } | {
1551
1551
  readonly type: "input_file";
1552
1552
  readonly filename: string;
1553
- readonly detail?: string | undefined;
1554
1553
  readonly file_data?: string | undefined;
1554
+ readonly detail?: string | undefined;
1555
1555
  readonly file_url?: string | undefined;
1556
1556
  } | {
1557
1557
  readonly type: "input_text";
@@ -603,7 +603,6 @@ export const allowedToolChoice = (request) => {
603
603
  };
604
604
  export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAdapter")(function* (request, adapter) {
605
605
  const projected = ProviderShared.flattenToolRequest(request);
606
- const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
607
606
  return {
608
607
  ...(yield* lowerConversation(projected.request, adapter)),
609
608
  ...lowerGeneration(request),
@@ -611,7 +610,7 @@ export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAd
611
610
  ? undefined
612
611
  : yield* Effect.forEach(projected.tools, (tool) => tool.native !== undefined && adapter.nativeTool
613
612
  ? adapter.nativeTool(tool.native)
614
- : lowerTool(adapter.name, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility))),
613
+ : lowerTool(adapter.name, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model))),
615
614
  tool_choice: allowedToolChoice(request) ??
616
615
  (request.toolChoice ? yield* lowerToolChoice(adapter.name, request.toolChoice) : undefined),
617
616
  };