@opencode/ai 2.0.14 → 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.
- package/README.md +399 -56
- package/dist/experimental/evaluation-client.d.ts +1 -1
- package/dist/experimental/evaluation-client.js +39 -3
- package/dist/experimental/evaluation.d.ts +4 -4
- package/dist/experimental/evaluation.js +2 -2
- package/dist/experimental/system-one.d.ts +3 -3
- package/dist/experimental/system-one.js +40 -51
- package/dist/generation.d.ts +83 -0
- package/dist/generation.js +113 -0
- package/dist/image-client.d.ts +16 -7
- package/dist/image-client.js +29 -13
- package/dist/image.d.ts +1410 -81
- package/dist/image.js +97 -67
- package/dist/index.d.ts +17 -2
- package/dist/index.js +12 -1
- package/dist/llm.d.ts +9 -1
- package/dist/media-model.d.ts +44 -0
- package/dist/media-model.js +49 -0
- package/dist/media.d.ts +213 -0
- package/dist/media.js +227 -0
- package/dist/promise.d.ts +974 -0
- package/dist/promise.js +81 -0
- package/dist/protocols/alibaba-chat.d.ts +12 -0
- package/dist/protocols/alibaba-responses.d.ts +2 -2
- package/dist/protocols/anthropic-messages.js +8 -19
- package/dist/protocols/assemblyai-transcription.d.ts +40 -0
- package/dist/protocols/assemblyai-transcription.js +138 -0
- package/dist/protocols/bedrock-converse.d.ts +4 -4
- package/dist/protocols/bedrock-converse.js +6 -17
- package/dist/protocols/bfl-images.d.ts +32 -0
- package/dist/protocols/bfl-images.js +153 -0
- package/dist/protocols/cartesia-speech.d.ts +127 -0
- package/dist/protocols/cartesia-speech.js +126 -0
- package/dist/protocols/deepgram-speech.d.ts +119 -0
- package/dist/protocols/deepgram-speech.js +92 -0
- package/dist/protocols/deepgram-transcription.d.ts +25 -0
- package/dist/protocols/deepgram-transcription.js +129 -0
- package/dist/protocols/elevenlabs-speech.d.ts +122 -0
- package/dist/protocols/elevenlabs-speech.js +115 -0
- package/dist/protocols/fal-images.d.ts +24 -0
- package/dist/protocols/fal-images.js +114 -0
- package/dist/protocols/fal-video.d.ts +29 -0
- package/dist/protocols/fal-video.js +88 -0
- package/dist/protocols/gemini.d.ts +30 -9
- package/dist/protocols/gemini.js +45 -35
- package/dist/protocols/google-images.d.ts +9 -21
- package/dist/protocols/google-images.js +158 -133
- package/dist/protocols/google-speech.d.ts +130 -0
- package/dist/protocols/google-speech.js +84 -0
- package/dist/protocols/google-transcription.d.ts +173 -0
- package/dist/protocols/google-transcription.js +138 -0
- package/dist/protocols/google-video.d.ts +26 -0
- package/dist/protocols/google-video.js +158 -0
- package/dist/protocols/meta-images.d.ts +7 -12
- package/dist/protocols/meta-images.js +85 -66
- package/dist/protocols/meta-responses.d.ts +4 -4
- package/dist/protocols/meta-responses.js +1 -1
- package/dist/protocols/mistral-chat.js +7 -6
- package/dist/protocols/open-responses.d.ts +17 -9
- package/dist/protocols/open-responses.js +24 -14
- package/dist/protocols/openai-chat.d.ts +118 -1
- package/dist/protocols/openai-chat.js +125 -44
- 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 +128 -18
- package/dist/protocols/openai-images.js +177 -154
- package/dist/protocols/openai-responses.d.ts +15 -15
- package/dist/protocols/openai-responses.js +5 -6
- package/dist/protocols/openai-speech.d.ts +116 -0
- package/dist/protocols/openai-speech.js +98 -0
- package/dist/protocols/openai-transcription.d.ts +207 -0
- package/dist/protocols/openai-transcription.js +190 -0
- package/dist/protocols/replicate-images.d.ts +28 -0
- package/dist/protocols/replicate-images.js +133 -0
- package/dist/protocols/runway-video.d.ts +38 -0
- package/dist/protocols/runway-video.js +146 -0
- package/dist/protocols/shared.d.ts +27 -17
- package/dist/protocols/shared.js +52 -35
- package/dist/protocols/stability-images.d.ts +38 -0
- package/dist/protocols/stability-images.js +148 -0
- package/dist/protocols/utils/bedrock-media.d.ts +2 -3
- package/dist/protocols/utils/bedrock-media.js +4 -4
- package/dist/protocols/utils/fal-queue.d.ts +28 -0
- package/dist/protocols/utils/fal-queue.js +69 -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 +18 -0
- package/dist/protocols/utils/media-input.js +35 -0
- package/dist/protocols/utils/responses-compaction.js +6 -5
- package/dist/protocols/utils/speech-stream.d.ts +49 -0
- package/dist/protocols/utils/speech-stream.js +67 -0
- package/dist/protocols/utils/tool-schema.d.ts +2 -2
- package/dist/protocols/utils/tool-schema.js +40 -17
- package/dist/protocols/utils/tool-stream.d.ts +27 -3
- package/dist/protocols/xai-images.d.ts +9 -15
- package/dist/protocols/xai-images.js +75 -84
- package/dist/protocols/xai-responses.d.ts +2 -2
- package/dist/protocols/xai-video.d.ts +34 -0
- package/dist/protocols/xai-video.js +147 -0
- package/dist/protocols/zai-chat.d.ts +13 -1
- package/dist/protocols/zai-images.d.ts +9 -13
- package/dist/protocols/zai-images.js +59 -57
- package/dist/provider-error.js +3 -0
- package/dist/providers/alibaba.d.ts +14 -2
- package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
- package/dist/providers/amazon-bedrock.d.ts +2 -2
- package/dist/providers/assemblyai.d.ts +25 -0
- package/dist/providers/assemblyai.js +29 -0
- package/dist/providers/azure.d.ts +18 -6
- 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 +28 -0
- package/dist/providers/cartesia.d.ts +24 -0
- package/dist/providers/cartesia.js +22 -0
- package/dist/providers/cerebras.d.ts +24 -0
- package/dist/providers/cerebras.js +6 -1
- package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
- package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
- package/dist/providers/deepgram.d.ts +29 -0
- package/dist/providers/deepgram.js +31 -0
- package/dist/providers/deepinfra.d.ts +24 -0
- package/dist/providers/deepinfra.js +6 -1
- package/dist/providers/deepseek.d.ts +24 -0
- package/dist/providers/elevenlabs.d.ts +24 -0
- package/dist/providers/elevenlabs.js +28 -0
- package/dist/providers/fal.d.ts +29 -0
- package/dist/providers/fal.js +33 -0
- package/dist/providers/fireworks.d.ts +24 -0
- package/dist/providers/google-vertex-chat.d.ts +12 -0
- package/dist/providers/google-vertex-responses.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts +10 -3
- package/dist/providers/google.d.ts +25 -3
- package/dist/providers/google.js +11 -2
- package/dist/providers/groq.d.ts +24 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +10 -0
- package/dist/providers/meta.d.ts +14 -2
- package/dist/providers/minimax.d.ts +14 -2
- package/dist/providers/moonshot.d.ts +14 -2
- 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.d.ts +25 -3
- package/dist/providers/openai.js +10 -1
- package/dist/providers/openrouter.d.ts +67 -0
- package/dist/providers/openrouter.js +13 -1
- package/dist/providers/replicate.d.ts +25 -0
- package/dist/providers/replicate.js +22 -0
- package/dist/providers/runway.d.ts +24 -0
- package/dist/providers/runway.js +22 -0
- package/dist/providers/stability.d.ts +28 -0
- package/dist/providers/stability.js +23 -0
- package/dist/providers/togetherai.d.ts +24 -0
- package/dist/providers/vercel-ai-gateway.d.ts +41 -0
- package/dist/providers/vercel-ai-gateway.js +85 -0
- package/dist/providers/xai.d.ts +17 -0
- package/dist/providers/xai.js +5 -2
- package/dist/providers/zai-coding-plan.d.ts +15 -3
- package/dist/providers/zai.d.ts +13 -1
- package/dist/route/auth.d.ts +4 -1
- package/dist/route/auth.js +6 -0
- package/dist/route/client.d.ts +9 -1
- package/dist/route/endpoint.d.ts +10 -10
- package/dist/route/executor-service.d.ts +12 -0
- package/dist/route/executor-service.js +3 -0
- package/dist/route/executor.d.ts +4 -9
- package/dist/route/executor.js +3 -3
- package/dist/route/framing.d.ts +5 -1
- package/dist/route/framing.js +9 -0
- package/dist/route/index.d.ts +2 -0
- package/dist/route/index.js +2 -0
- package/dist/route/media-protocol.d.ts +158 -0
- package/dist/route/media-protocol.js +96 -0
- package/dist/route/media.d.ts +97 -0
- package/dist/route/media.js +236 -0
- package/dist/schema/errors.d.ts +13 -3
- package/dist/schema/errors.js +7 -0
- package/dist/schema/events.d.ts +557 -40
- package/dist/schema/events.js +35 -2
- package/dist/schema/messages.d.ts +95 -8
- package/dist/schema/messages.js +8 -6
- package/dist/schema/options.d.ts +6 -3
- package/dist/schema/options.js +6 -2
- package/dist/speech-client.d.ts +21 -0
- package/dist/speech-client.js +25 -0
- package/dist/speech.d.ts +1150 -0
- package/dist/speech.js +119 -0
- package/dist/testing.d.ts +72 -8
- package/dist/transcription-client.d.ts +28 -0
- package/dist/transcription-client.js +44 -0
- package/dist/transcription.d.ts +1504 -0
- package/dist/transcription.js +133 -0
- package/dist/utils/bytes.d.ts +1 -0
- package/dist/utils/bytes.js +10 -0
- package/dist/utils/media-type.d.ts +7 -0
- package/dist/utils/media-type.js +70 -0
- package/dist/utils/sanitize.js +3 -1
- package/dist/video-client.d.ts +28 -0
- package/dist/video-client.js +40 -0
- package/dist/video.d.ts +1359 -0
- package/dist/video.js +119 -0
- package/package.json +7 -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/image-input.d.ts +0 -21
- package/dist/protocols/utils/image-input.js +0 -20
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Effect, Schema } 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 { TranscriptionModel, TranscriptionResponse } from "../transcription.js";
|
|
6
|
+
import { ProviderShared } from "./shared.js";
|
|
7
|
+
import { MediaInput } from "./utils/media-input.js";
|
|
8
|
+
const ADAPTER = "deepgram-transcription";
|
|
9
|
+
const NAME = "Deepgram";
|
|
10
|
+
const PROVIDER = ProviderID.make("deepgram");
|
|
11
|
+
export const DEFAULT_BASE_URL = "https://api.deepgram.com";
|
|
12
|
+
export const PATH = "/v1/listen";
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// 2. Response schema
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const Word = Schema.Struct({
|
|
17
|
+
word: Schema.String,
|
|
18
|
+
start: Schema.Number,
|
|
19
|
+
end: Schema.Number,
|
|
20
|
+
confidence: Schema.optional(Schema.Number),
|
|
21
|
+
speaker: Schema.optional(Schema.Number),
|
|
22
|
+
punctuated_word: Schema.optional(Schema.String),
|
|
23
|
+
});
|
|
24
|
+
const ListenResponse = Schema.Struct({
|
|
25
|
+
metadata: Schema.optional(Schema.Struct({ request_id: Schema.optional(Schema.String), duration: Schema.optional(Schema.Number) })),
|
|
26
|
+
results: Schema.Struct({
|
|
27
|
+
channels: Schema.Array(Schema.Struct({
|
|
28
|
+
alternatives: Schema.optional(Schema.Array(Schema.Struct({ transcript: Schema.String, words: Schema.optional(Schema.Array(Word)) }))),
|
|
29
|
+
detected_language: Schema.optional(Schema.String),
|
|
30
|
+
})),
|
|
31
|
+
utterances: Schema.optional(Schema.Array(Schema.Struct({
|
|
32
|
+
start: Schema.Number,
|
|
33
|
+
end: Schema.Number,
|
|
34
|
+
transcript: Schema.String,
|
|
35
|
+
speaker: Schema.optional(Schema.Number),
|
|
36
|
+
words: Schema.optional(Schema.Array(Word)),
|
|
37
|
+
}))),
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// 5. Request body construction
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
const query = (request) => MediaInput.query(ADAPTER, mergeJsonRecords({
|
|
44
|
+
model: request.model.id,
|
|
45
|
+
smart_format: true,
|
|
46
|
+
language: request.language,
|
|
47
|
+
// Deepgram assumes English unless asked to detect, unlike the other routes' auto-detection.
|
|
48
|
+
detect_language: request.language === undefined ? true : undefined,
|
|
49
|
+
// `diarize=true` is deprecated in favor of choosing a diarization model.
|
|
50
|
+
diarize_model: request.diarize === true ? "latest" : undefined,
|
|
51
|
+
utterances: request.diarize === true || request.timestamps === "segment" ? true : undefined,
|
|
52
|
+
}, request.providerOptions) ?? {});
|
|
53
|
+
const fromRequest = Effect.fn("DeepgramTranscription.fromRequest")(function* (request) {
|
|
54
|
+
const url = ProviderShared.mediaUrl(request.audio);
|
|
55
|
+
if (url !== undefined)
|
|
56
|
+
return MediaProtocol.json(mergeJsonRecords({ url }, request.http?.body) ?? {}, yield* query(request));
|
|
57
|
+
if (request.http?.body !== undefined)
|
|
58
|
+
return yield* ProviderShared.invalidRequest(`${NAME} sends inline audio as the raw body, so http.body cannot apply`);
|
|
59
|
+
const audio = yield* MediaInput.inlineBytes(ADAPTER, request.audio);
|
|
60
|
+
return MediaProtocol.binary(audio, request.audio.mediaType, yield* query(request));
|
|
61
|
+
});
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// 6. Response decoding
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
const decodeListen = MediaProtocol.decodeJson(ADAPTER, NAME, ListenResponse);
|
|
66
|
+
const speaker = (value) => (value === undefined ? undefined : String(value));
|
|
67
|
+
const wordText = (word) => word.punctuated_word ?? word.word;
|
|
68
|
+
// Utterances split on pauses, not speakers: the v2 diarizer labels a whole utterance with one speaker even when its
|
|
69
|
+
// words change speaker, so segments split each utterance at speaker changes.
|
|
70
|
+
const speakerTurns = (words) => words.reduce((turns, word) => {
|
|
71
|
+
const last = turns.at(-1);
|
|
72
|
+
if (last === undefined || last[0].speaker !== word.speaker)
|
|
73
|
+
return [...turns, [word]];
|
|
74
|
+
last.push(word);
|
|
75
|
+
return turns;
|
|
76
|
+
}, []);
|
|
77
|
+
const decodeResponse = Effect.fn("DeepgramTranscription.decodeResponse")(function* (response) {
|
|
78
|
+
const output = yield* decodeListen(response);
|
|
79
|
+
const channel = output.value.results.channels[0];
|
|
80
|
+
const alternative = channel?.alternatives?.[0];
|
|
81
|
+
if (alternative === undefined)
|
|
82
|
+
return yield* output.invalid(`${NAME} returned no transcript`);
|
|
83
|
+
const duration = output.value.metadata?.duration;
|
|
84
|
+
const requestID = output.value.metadata?.request_id;
|
|
85
|
+
return new TranscriptionResponse({
|
|
86
|
+
text: alternative.transcript,
|
|
87
|
+
segments: output.value.results.utterances?.flatMap((utterance) => utterance.words === undefined || utterance.words.length === 0
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
text: utterance.transcript,
|
|
91
|
+
startSeconds: utterance.start,
|
|
92
|
+
endSeconds: utterance.end,
|
|
93
|
+
speaker: speaker(utterance.speaker),
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
: speakerTurns(utterance.words).map((turn) => ({
|
|
97
|
+
text: turn.map(wordText).join(" "),
|
|
98
|
+
startSeconds: turn[0].start,
|
|
99
|
+
endSeconds: turn[turn.length - 1].end,
|
|
100
|
+
speaker: speaker(turn[0].speaker),
|
|
101
|
+
}))),
|
|
102
|
+
words: alternative.words?.map((word) => ({
|
|
103
|
+
text: wordText(word),
|
|
104
|
+
startSeconds: word.start,
|
|
105
|
+
endSeconds: word.end,
|
|
106
|
+
speaker: speaker(word.speaker),
|
|
107
|
+
confidence: word.confidence,
|
|
108
|
+
})),
|
|
109
|
+
language: channel?.detected_language?.toLowerCase(),
|
|
110
|
+
durationSeconds: duration,
|
|
111
|
+
usage: duration === undefined ? undefined : { type: "seconds", seconds: duration },
|
|
112
|
+
providerMetadata: requestID === undefined ? undefined : { deepgram: { requestId: requestID } },
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// 7. Protocol and route
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
export const protocol = MediaProtocol.inline({
|
|
119
|
+
id: ADAPTER,
|
|
120
|
+
name: NAME,
|
|
121
|
+
unsupported: ["prompt", "speakers"],
|
|
122
|
+
body: { from: fromRequest },
|
|
123
|
+
response: { decode: decodeResponse },
|
|
124
|
+
});
|
|
125
|
+
export const model = (input) => TranscriptionModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
|
|
126
|
+
export const DeepgramTranscription = {
|
|
127
|
+
protocol,
|
|
128
|
+
model,
|
|
129
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
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 { SpeechStream } from "./utils/speech-stream.js";
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://api.elevenlabs.io";
|
|
6
|
+
export declare const PATH = "/v1/text-to-speech";
|
|
7
|
+
export type ElevenLabsSpeechString<Known extends string> = Known | (string & {});
|
|
8
|
+
export type ElevenLabsOutputFormat = ElevenLabsSpeechString<"mp3_22050_32" | "mp3_24000_48" | "mp3_44100_32" | "mp3_44100_64" | "mp3_44100_96" | "mp3_44100_128" | "mp3_44100_192" | "pcm_8000" | "pcm_16000" | "pcm_22050" | "pcm_24000" | "pcm_32000" | "pcm_44100" | "pcm_48000" | "wav_8000" | "wav_16000" | "wav_22050" | "wav_24000" | "wav_32000" | "wav_44100" | "wav_48000" | "ulaw_8000" | "alaw_8000" | "opus_48000_32" | "opus_48000_64" | "opus_48000_96" | "opus_48000_128" | "opus_48000_192">;
|
|
9
|
+
export type ElevenLabsSpeechOptions = {
|
|
10
|
+
readonly outputFormat?: ElevenLabsOutputFormat;
|
|
11
|
+
readonly voice_settings?: {
|
|
12
|
+
readonly stability?: number;
|
|
13
|
+
readonly similarity_boost?: number;
|
|
14
|
+
readonly style?: number;
|
|
15
|
+
readonly use_speaker_boost?: boolean;
|
|
16
|
+
};
|
|
17
|
+
readonly seed?: number;
|
|
18
|
+
readonly apply_text_normalization?: ElevenLabsSpeechString<"auto" | "on" | "off">;
|
|
19
|
+
} & Record<string, unknown>;
|
|
20
|
+
export type Request = SpeechRequestFor<ElevenLabsSpeechOptions>;
|
|
21
|
+
export declare const protocol: MediaProtocol.Streamed<Request, {
|
|
22
|
+
readonly type: "audio-delta";
|
|
23
|
+
readonly chunk: Uint8Array<ArrayBufferLike>;
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: "timestamps";
|
|
26
|
+
readonly items: readonly {
|
|
27
|
+
readonly text: string;
|
|
28
|
+
readonly startSeconds: number;
|
|
29
|
+
readonly endSeconds: number;
|
|
30
|
+
}[];
|
|
31
|
+
} | {
|
|
32
|
+
readonly type: "finish";
|
|
33
|
+
readonly audio: import("../media.js").Asset;
|
|
34
|
+
readonly providerMetadata?: {
|
|
35
|
+
readonly [x: string]: {
|
|
36
|
+
readonly [x: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
} | undefined;
|
|
39
|
+
readonly usage?: {
|
|
40
|
+
readonly type: "tokens";
|
|
41
|
+
readonly input?: number | undefined;
|
|
42
|
+
readonly output?: number | undefined;
|
|
43
|
+
readonly total?: number | undefined;
|
|
44
|
+
readonly details?: {
|
|
45
|
+
readonly [x: string]: unknown;
|
|
46
|
+
} | undefined;
|
|
47
|
+
} | {
|
|
48
|
+
readonly type: "seconds";
|
|
49
|
+
readonly seconds: number;
|
|
50
|
+
} | {
|
|
51
|
+
readonly type: "characters";
|
|
52
|
+
readonly characters: number;
|
|
53
|
+
} | {
|
|
54
|
+
readonly type: "credits";
|
|
55
|
+
readonly credits: number;
|
|
56
|
+
} | {
|
|
57
|
+
readonly type: "compute";
|
|
58
|
+
readonly seconds: number;
|
|
59
|
+
} | undefined;
|
|
60
|
+
readonly notices?: readonly {
|
|
61
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
62
|
+
readonly message: string;
|
|
63
|
+
readonly providerMetadata?: {
|
|
64
|
+
readonly [x: string]: {
|
|
65
|
+
readonly [x: string]: unknown;
|
|
66
|
+
};
|
|
67
|
+
} | undefined;
|
|
68
|
+
}[] | undefined;
|
|
69
|
+
}, string | Uint8Array<ArrayBufferLike>, SpeechStream.Audio>;
|
|
70
|
+
export declare const model: (input: MediaRoute.ModelInput) => SpeechModel<ElevenLabsSpeechOptions>;
|
|
71
|
+
export declare const ElevenLabsSpeech: {
|
|
72
|
+
readonly protocol: MediaProtocol.Streamed<Request, {
|
|
73
|
+
readonly type: "audio-delta";
|
|
74
|
+
readonly chunk: Uint8Array<ArrayBufferLike>;
|
|
75
|
+
} | {
|
|
76
|
+
readonly type: "timestamps";
|
|
77
|
+
readonly items: readonly {
|
|
78
|
+
readonly text: string;
|
|
79
|
+
readonly startSeconds: number;
|
|
80
|
+
readonly endSeconds: number;
|
|
81
|
+
}[];
|
|
82
|
+
} | {
|
|
83
|
+
readonly type: "finish";
|
|
84
|
+
readonly audio: import("../media.js").Asset;
|
|
85
|
+
readonly providerMetadata?: {
|
|
86
|
+
readonly [x: string]: {
|
|
87
|
+
readonly [x: string]: unknown;
|
|
88
|
+
};
|
|
89
|
+
} | undefined;
|
|
90
|
+
readonly usage?: {
|
|
91
|
+
readonly type: "tokens";
|
|
92
|
+
readonly input?: number | undefined;
|
|
93
|
+
readonly output?: number | undefined;
|
|
94
|
+
readonly total?: number | undefined;
|
|
95
|
+
readonly details?: {
|
|
96
|
+
readonly [x: string]: unknown;
|
|
97
|
+
} | undefined;
|
|
98
|
+
} | {
|
|
99
|
+
readonly type: "seconds";
|
|
100
|
+
readonly seconds: number;
|
|
101
|
+
} | {
|
|
102
|
+
readonly type: "characters";
|
|
103
|
+
readonly characters: number;
|
|
104
|
+
} | {
|
|
105
|
+
readonly type: "credits";
|
|
106
|
+
readonly credits: number;
|
|
107
|
+
} | {
|
|
108
|
+
readonly type: "compute";
|
|
109
|
+
readonly seconds: number;
|
|
110
|
+
} | undefined;
|
|
111
|
+
readonly notices?: readonly {
|
|
112
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
113
|
+
readonly message: string;
|
|
114
|
+
readonly providerMetadata?: {
|
|
115
|
+
readonly [x: string]: {
|
|
116
|
+
readonly [x: string]: unknown;
|
|
117
|
+
};
|
|
118
|
+
} | undefined;
|
|
119
|
+
}[] | undefined;
|
|
120
|
+
}, string | Uint8Array<ArrayBufferLike>, SpeechStream.Audio>;
|
|
121
|
+
readonly model: (input: MediaRoute.ModelInput) => SpeechModel<ElevenLabsSpeechOptions>;
|
|
122
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Framing } from "../route/framing.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 { SpeechModel } from "../speech.js";
|
|
7
|
+
import { ProviderShared, optionalNull } from "./shared.js";
|
|
8
|
+
import { SpeechStream } from "./utils/speech-stream.js";
|
|
9
|
+
const ADAPTER = "elevenlabs-speech";
|
|
10
|
+
const NAME = "ElevenLabs";
|
|
11
|
+
const PROVIDER = ProviderID.make("elevenlabs");
|
|
12
|
+
export const DEFAULT_BASE_URL = "https://api.elevenlabs.io";
|
|
13
|
+
export const PATH = "/v1/text-to-speech";
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// 3. Streaming event schema
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const Alignment = Schema.Struct({
|
|
18
|
+
characters: Schema.Array(Schema.String),
|
|
19
|
+
character_start_times_seconds: Schema.Array(Schema.Number),
|
|
20
|
+
character_end_times_seconds: Schema.Array(Schema.Number),
|
|
21
|
+
});
|
|
22
|
+
const TimestampedAudio = Schema.Struct({
|
|
23
|
+
audio_base64: Schema.Uint8ArrayFromBase64,
|
|
24
|
+
alignment: optionalNull(Alignment),
|
|
25
|
+
});
|
|
26
|
+
const decodeRecord = MediaProtocol.decodeFrame(ADAPTER, NAME, TimestampedAudio);
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// 5. Request body construction
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const OUTPUT_FORMATS = {
|
|
31
|
+
mp3: "mp3_44100_128",
|
|
32
|
+
pcm: "pcm_24000",
|
|
33
|
+
wav: "wav_24000",
|
|
34
|
+
opus: "opus_48000_64",
|
|
35
|
+
};
|
|
36
|
+
/** WAV is served only by the non-streaming endpoints. */
|
|
37
|
+
const outputFormat = Effect.fn("ElevenLabsSpeech.outputFormat")(function* (request) {
|
|
38
|
+
const format = request.providerOptions?.outputFormat ?? OUTPUT_FORMATS[request.format ?? "mp3"];
|
|
39
|
+
if (format === undefined)
|
|
40
|
+
return yield* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} has no default output format for "${request.format}"; pass providerOptions.outputFormat`);
|
|
41
|
+
if (request.mode === "stream" && format.startsWith("wav_"))
|
|
42
|
+
return yield* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} streams mp3, pcm, opus, ulaw, and alaw but not "${format}"; use generate for WAV`);
|
|
43
|
+
return format;
|
|
44
|
+
});
|
|
45
|
+
const fromRequest = Effect.fn("ElevenLabsSpeech.fromRequest")(function* (request) {
|
|
46
|
+
if (request.voice === undefined)
|
|
47
|
+
return yield* ProviderShared.invalidRequest(`${NAME} requires a voice id; pass it as \`voice\``);
|
|
48
|
+
const { outputFormat: _outputFormat, ...native } = request.providerOptions ?? {};
|
|
49
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
50
|
+
text: request.text,
|
|
51
|
+
model_id: request.model.id,
|
|
52
|
+
language_code: request.language,
|
|
53
|
+
voice_settings: request.speed === undefined ? undefined : { speed: request.speed },
|
|
54
|
+
}, native, request.http?.body) ?? {}, { output_format: yield* outputFormat(request) });
|
|
55
|
+
});
|
|
56
|
+
const path = (request) => `${PATH}/${encodeURIComponent(SpeechStream.voiceID(request.voice) ?? "")}${request.mode === "stream" ? "/stream" : ""}${request.timestamps === true ? "/with-timestamps" : ""}`;
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// 6. Stream parsing
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
const onRecord = Effect.fn("ElevenLabsSpeech.onRecord")(function* (state, frame) {
|
|
61
|
+
const record = yield* decodeRecord(frame);
|
|
62
|
+
const [next, events] = SpeechStream.delta(state, record.audio_base64);
|
|
63
|
+
const alignment = record.alignment;
|
|
64
|
+
if (!alignment)
|
|
65
|
+
return [next, events];
|
|
66
|
+
return [
|
|
67
|
+
next,
|
|
68
|
+
[
|
|
69
|
+
...events,
|
|
70
|
+
...SpeechStream.timestamps(alignment.characters, alignment.character_start_times_seconds, alignment.character_end_times_seconds),
|
|
71
|
+
],
|
|
72
|
+
];
|
|
73
|
+
});
|
|
74
|
+
const PCM_CODECS = {
|
|
75
|
+
pcm: "pcm_s16le",
|
|
76
|
+
ulaw: "pcm_mulaw",
|
|
77
|
+
alaw: "pcm_alaw",
|
|
78
|
+
};
|
|
79
|
+
const describeOutput = (format) => {
|
|
80
|
+
const [codec = format, rate] = format.split("_");
|
|
81
|
+
const sampleRate = rate === undefined ? undefined : Number(rate);
|
|
82
|
+
const encoding = PCM_CODECS[codec];
|
|
83
|
+
return encoding === undefined ? SpeechStream.container(codec, sampleRate) : SpeechStream.pcm(encoding, sampleRate);
|
|
84
|
+
};
|
|
85
|
+
const finish = Effect.fn("ElevenLabsSpeech.finish")(function* (state, context) {
|
|
86
|
+
const requestID = context.http.headers["request-id"];
|
|
87
|
+
return yield* SpeechStream.finish(ADAPTER, state, {
|
|
88
|
+
...describeOutput(yield* outputFormat(context.request)),
|
|
89
|
+
// `character-cost` is billed credits, not a character count (3 for 20 characters on `eleven_flash_v2_5`).
|
|
90
|
+
usage: SpeechStream.headerUsage("credits", context.http.headers["character-cost"]),
|
|
91
|
+
providerMetadata: requestID === undefined ? undefined : { elevenlabs: { requestId: requestID } },
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// 7. Protocol and route
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
export const protocol = MediaProtocol.stream({
|
|
98
|
+
id: ADAPTER,
|
|
99
|
+
name: NAME,
|
|
100
|
+
unsupported: ["instructions"],
|
|
101
|
+
body: { from: fromRequest },
|
|
102
|
+
frames: (bytes, context) => {
|
|
103
|
+
if (context.request.timestamps !== true)
|
|
104
|
+
return bytes;
|
|
105
|
+
return context.request.mode === "stream" ? Framing.lines.frame(bytes) : Framing.document.frame(bytes);
|
|
106
|
+
},
|
|
107
|
+
initial: () => ({ chunks: [] }),
|
|
108
|
+
step: SpeechStream.step(onRecord),
|
|
109
|
+
finish,
|
|
110
|
+
});
|
|
111
|
+
export const model = (input) => SpeechModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: ({ request }) => path(request) }, input);
|
|
112
|
+
export const ElevenLabsSpeech = {
|
|
113
|
+
protocol,
|
|
114
|
+
model,
|
|
115
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
export type FalImageOptions = {
|
|
5
|
+
readonly image_size?: "square_hd" | "square" | "portrait_4_3" | "portrait_16_9" | "landscape_4_3" | "landscape_16_9" | (string & {});
|
|
6
|
+
readonly enable_safety_checker?: boolean;
|
|
7
|
+
} & Record<string, unknown>;
|
|
8
|
+
export type Request = ImageRequestFor<FalImageOptions>;
|
|
9
|
+
export declare const protocol: MediaProtocol.Queued<Request, ImageResponse, {
|
|
10
|
+
readonly requestID: string;
|
|
11
|
+
readonly statusURL: string;
|
|
12
|
+
readonly responseURL: string;
|
|
13
|
+
readonly cancelURL: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<FalImageOptions>;
|
|
16
|
+
export declare const FalImages: {
|
|
17
|
+
readonly protocol: MediaProtocol.Queued<Request, ImageResponse, {
|
|
18
|
+
readonly requestID: string;
|
|
19
|
+
readonly statusURL: string;
|
|
20
|
+
readonly responseURL: string;
|
|
21
|
+
readonly cancelURL: string;
|
|
22
|
+
}>;
|
|
23
|
+
readonly model: (input: MediaRoute.ModelInput) => ImageModel<FalImageOptions>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { ImageModel, ImageResponse } from "../image.js";
|
|
3
|
+
import { Media } from "../media.js";
|
|
4
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
|
+
import { MediaRoute } from "../route/media.js";
|
|
6
|
+
import { ProviderID, mergeJsonRecords } from "../schema/index.js";
|
|
7
|
+
import { ProviderShared, optionalNull } from "./shared.js";
|
|
8
|
+
import { FalQueue } from "./utils/fal-queue.js";
|
|
9
|
+
import { MediaInput } from "./utils/media-input.js";
|
|
10
|
+
const ADAPTER = "fal-images";
|
|
11
|
+
const NAME = "fal Images";
|
|
12
|
+
const PROVIDER = ProviderID.make("fal");
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// 2. Response schema
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const QueueResult = Schema.StructWithRest(Schema.Struct({
|
|
17
|
+
images: Schema.Array(Schema.Struct({
|
|
18
|
+
url: Schema.String,
|
|
19
|
+
width: optionalNull(Schema.Number),
|
|
20
|
+
height: optionalNull(Schema.Number),
|
|
21
|
+
content_type: optionalNull(Schema.String),
|
|
22
|
+
})),
|
|
23
|
+
seed: optionalNull(Schema.Number),
|
|
24
|
+
has_nsfw_concepts: optionalNull(Schema.Array(Schema.Boolean)),
|
|
25
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// 5. Request body construction
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
const sizing = (model) => {
|
|
30
|
+
if (/^fal-ai\/(nano-banana|flux-pro\/v1\.1-ultra)/.test(model))
|
|
31
|
+
return "aspect_ratio";
|
|
32
|
+
if (model.startsWith("fal-ai/flux"))
|
|
33
|
+
return "image_size";
|
|
34
|
+
return undefined;
|
|
35
|
+
};
|
|
36
|
+
const unsupported = (model, field, message) => ProviderShared.unsupportedOperation({
|
|
37
|
+
operation: `media.${field}`,
|
|
38
|
+
provider: PROVIDER,
|
|
39
|
+
route: ADAPTER,
|
|
40
|
+
message: `${model} ${message}`,
|
|
41
|
+
});
|
|
42
|
+
const validate = (request) => {
|
|
43
|
+
const id = request.model.id;
|
|
44
|
+
const field = sizing(id);
|
|
45
|
+
if (request.size !== undefined && request.aspectRatio !== undefined)
|
|
46
|
+
return Effect.fail(ProviderShared.invalidRequest(`${NAME} accepts either size or aspectRatio, not both`));
|
|
47
|
+
if (request.size !== undefined && field === "aspect_ratio")
|
|
48
|
+
return Effect.fail(unsupported(id, "size", "sizes by aspectRatio"));
|
|
49
|
+
if (request.aspectRatio !== undefined && field === "image_size")
|
|
50
|
+
return Effect.fail(unsupported(id, "aspectRatio", "sizes by size (image_size)"));
|
|
51
|
+
if ((request.images?.length ?? 0) > 1 && !isEdit(id))
|
|
52
|
+
return Effect.fail(unsupported(id, "images", "takes one image_url; use an /edit endpoint for several images"));
|
|
53
|
+
return Effect.void;
|
|
54
|
+
};
|
|
55
|
+
// `/edit` endpoints take an `image_urls` list; image-to-image, fill, and Ultra take one `image_url` (beside `mask_url`).
|
|
56
|
+
const isEdit = (model) => model.endsWith("/edit");
|
|
57
|
+
const fromRequest = Effect.fn("FalImages.fromRequest")(function* (request) {
|
|
58
|
+
yield* validate(request);
|
|
59
|
+
const images = yield* Effect.forEach(request.images ?? [], (image) => FalQueue.mediaUrl(image, NAME));
|
|
60
|
+
const edit = isEdit(request.model.id);
|
|
61
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
62
|
+
prompt: request.prompt,
|
|
63
|
+
num_images: request.n,
|
|
64
|
+
seed: request.seed,
|
|
65
|
+
image_size: request.size === undefined ? undefined : MediaInput.dimensions(request.size),
|
|
66
|
+
aspect_ratio: request.aspectRatio,
|
|
67
|
+
output_format: request.format,
|
|
68
|
+
image_urls: edit && images.length > 0 ? images : undefined,
|
|
69
|
+
image_url: edit ? undefined : images[0],
|
|
70
|
+
mask_url: request.mask === undefined ? undefined : yield* FalQueue.mediaUrl(request.mask, NAME),
|
|
71
|
+
}, request.providerOptions, request.http?.body) ?? {});
|
|
72
|
+
});
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// 6. Response decoding
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
const decodeQueueResult = MediaProtocol.decodeJson(ADAPTER, NAME, QueueResult);
|
|
77
|
+
const decodeResult = Effect.fn("FalImages.decodeResult")(function* (response, context) {
|
|
78
|
+
const output = yield* decodeQueueResult(response);
|
|
79
|
+
const { images, seed, has_nsfw_concepts, ...rest } = output.value;
|
|
80
|
+
if (images.length === 0)
|
|
81
|
+
return yield* output.invalid(`${NAME} returned no images`);
|
|
82
|
+
// With the safety checker on, flagged images come back blacked out rather than omitted.
|
|
83
|
+
const flagged = (has_nsfw_concepts ?? []).flatMap((value, index) => (value ? [index] : []));
|
|
84
|
+
return new ImageResponse({
|
|
85
|
+
images: images.map((image) => Media.url(image.url, {
|
|
86
|
+
mediaType: image.content_type ?? undefined,
|
|
87
|
+
info: { width: image.width ?? undefined, height: image.height ?? undefined },
|
|
88
|
+
})),
|
|
89
|
+
notices: flagged.length === 0
|
|
90
|
+
? undefined
|
|
91
|
+
: flagged.map((index) => ({ type: "moderated", message: `${NAME} flagged image ${index} as NSFW` })),
|
|
92
|
+
providerMetadata: { fal: { requestId: context.token.requestID, seed: seed ?? undefined, ...rest } },
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// 7. Protocol and route
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
export const protocol = FalQueue.protocol({
|
|
99
|
+
id: ADAPTER,
|
|
100
|
+
name: NAME,
|
|
101
|
+
from: fromRequest,
|
|
102
|
+
decodeResult,
|
|
103
|
+
});
|
|
104
|
+
export const model = (input) => ImageModel.fromRoute({
|
|
105
|
+
id: ADAPTER,
|
|
106
|
+
provider: PROVIDER,
|
|
107
|
+
protocol,
|
|
108
|
+
baseURL: FalQueue.DEFAULT_BASE_URL,
|
|
109
|
+
path: ({ request }) => `/${request.model.id}`,
|
|
110
|
+
}, input);
|
|
111
|
+
export const FalImages = {
|
|
112
|
+
protocol,
|
|
113
|
+
model,
|
|
114
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
|
+
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
|
|
4
|
+
export type FalVideoString<Known extends string> = Known | (string & {});
|
|
5
|
+
/**
|
|
6
|
+
* Provider-native input. fal video endpoints are model-specific: `duration` is a string enum whose values differ per
|
|
7
|
+
* model (`"8s"` for Veo, `"5"` for Kling), and last-frame fields are named per model (`end_image_url`,
|
|
8
|
+
* `last_frame_url`, `tail_image_url`), so those pass through here instead of lowering from common fields.
|
|
9
|
+
*/
|
|
10
|
+
export type FalVideoOptions = {
|
|
11
|
+
readonly duration?: FalVideoString<"4s" | "6s" | "8s" | "5" | "10">;
|
|
12
|
+
} & Record<string, unknown>;
|
|
13
|
+
export type Request = VideoRequestFor<FalVideoOptions>;
|
|
14
|
+
export declare const protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
15
|
+
readonly requestID: string;
|
|
16
|
+
readonly statusURL: string;
|
|
17
|
+
readonly responseURL: string;
|
|
18
|
+
readonly cancelURL: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const model: (input: MediaRoute.ModelInput) => VideoModel<FalVideoOptions>;
|
|
21
|
+
export declare const FalVideo: {
|
|
22
|
+
readonly protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
23
|
+
readonly requestID: string;
|
|
24
|
+
readonly statusURL: string;
|
|
25
|
+
readonly responseURL: string;
|
|
26
|
+
readonly cancelURL: string;
|
|
27
|
+
}>;
|
|
28
|
+
readonly model: (input: MediaRoute.ModelInput) => VideoModel<FalVideoOptions>;
|
|
29
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { 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, optionalNull } from "./shared.js";
|
|
8
|
+
import { FalQueue } from "./utils/fal-queue.js";
|
|
9
|
+
const ADAPTER = "fal-video";
|
|
10
|
+
const NAME = "fal Video";
|
|
11
|
+
const PROVIDER = ProviderID.make("fal");
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// 2. Response schema
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const QueueResult = Schema.StructWithRest(Schema.Struct({
|
|
16
|
+
video: Schema.Struct({
|
|
17
|
+
url: Schema.String,
|
|
18
|
+
content_type: optionalNull(Schema.String),
|
|
19
|
+
file_name: optionalNull(Schema.String),
|
|
20
|
+
file_size: optionalNull(Schema.Number),
|
|
21
|
+
}),
|
|
22
|
+
seed: optionalNull(Schema.Number),
|
|
23
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// 5. Request body construction
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
const fromRequest = Effect.fn("FalVideo.fromRequest")(function* (request) {
|
|
28
|
+
if (request.frames?.last !== undefined)
|
|
29
|
+
return yield* ProviderShared.unsupportedOperation({
|
|
30
|
+
operation: "video.frames.last",
|
|
31
|
+
provider: PROVIDER,
|
|
32
|
+
route: ADAPTER,
|
|
33
|
+
message: `${NAME} names the last frame per model; pass it through providerOptions (e.g. end_image_url) instead of frames.last`,
|
|
34
|
+
});
|
|
35
|
+
const imageUrl = request.frames?.first === undefined ? undefined : yield* FalQueue.mediaUrl(request.frames.first, NAME);
|
|
36
|
+
const videoUrl = request.video === undefined ? undefined : yield* FalQueue.mediaUrl(request.video, NAME);
|
|
37
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
38
|
+
prompt: request.prompt,
|
|
39
|
+
negative_prompt: request.negativePrompt,
|
|
40
|
+
seed: request.seed,
|
|
41
|
+
aspect_ratio: request.aspectRatio,
|
|
42
|
+
resolution: request.resolution,
|
|
43
|
+
generate_audio: request.audio,
|
|
44
|
+
image_url: imageUrl,
|
|
45
|
+
video_url: videoUrl,
|
|
46
|
+
}, request.providerOptions, request.http?.body) ?? {});
|
|
47
|
+
});
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// 6. Response decoding
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
const decodeQueueResult = MediaProtocol.decodeJson(ADAPTER, NAME, QueueResult);
|
|
52
|
+
const decodeResult = Effect.fn("FalVideo.decodeResult")(function* (response, context) {
|
|
53
|
+
const output = yield* decodeQueueResult(response);
|
|
54
|
+
const { video, seed, ...rest } = output.value;
|
|
55
|
+
return new VideoResponse({
|
|
56
|
+
videos: [Media.url(video.url, { mediaType: video.content_type ?? "video/mp4" })],
|
|
57
|
+
providerMetadata: {
|
|
58
|
+
fal: {
|
|
59
|
+
requestId: context.token.requestID,
|
|
60
|
+
seed: seed ?? undefined,
|
|
61
|
+
fileName: video.file_name ?? undefined,
|
|
62
|
+
fileSize: video.file_size ?? undefined,
|
|
63
|
+
...rest,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// 7. Protocol and route
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export const protocol = FalQueue.protocol({
|
|
72
|
+
id: ADAPTER,
|
|
73
|
+
name: NAME,
|
|
74
|
+
unsupported: ["n", "durationSeconds", "references"],
|
|
75
|
+
from: fromRequest,
|
|
76
|
+
decodeResult,
|
|
77
|
+
});
|
|
78
|
+
export const model = (input) => VideoModel.fromRoute({
|
|
79
|
+
id: ADAPTER,
|
|
80
|
+
provider: PROVIDER,
|
|
81
|
+
protocol,
|
|
82
|
+
baseURL: FalQueue.DEFAULT_BASE_URL,
|
|
83
|
+
path: ({ request }) => `/${request.model.id}`,
|
|
84
|
+
}, input);
|
|
85
|
+
export const FalVideo = {
|
|
86
|
+
protocol,
|
|
87
|
+
model,
|
|
88
|
+
};
|