@opencode/ai 0.0.0-dev-20120 → 0.0.0-dev-20123
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/dist/experimental/evaluation.js +1 -1
- package/dist/generation.d.ts +1 -7
- package/dist/generation.js +1 -11
- package/dist/image.d.ts +2 -2
- package/dist/image.js +2 -2
- package/dist/llm.js +1 -1
- package/dist/media-model.d.ts +0 -2
- package/dist/media-model.js +0 -2
- package/dist/protocols/alibaba-messages.d.ts +1 -1
- package/dist/protocols/anthropic-messages.d.ts +34 -34
- package/dist/protocols/assemblyai-transcription.d.ts +2 -1
- package/dist/protocols/assemblyai-transcription.js +10 -14
- package/dist/protocols/bfl-images.js +15 -31
- package/dist/protocols/cartesia-speech.d.ts +2 -2
- package/dist/protocols/cartesia-speech.js +10 -16
- package/dist/protocols/deepgram-speech.d.ts +3 -3
- package/dist/protocols/deepgram-speech.js +7 -11
- package/dist/protocols/deepgram-transcription.d.ts +2 -1
- package/dist/protocols/deepgram-transcription.js +9 -13
- package/dist/protocols/elevenlabs-speech.d.ts +3 -3
- package/dist/protocols/elevenlabs-speech.js +9 -13
- package/dist/protocols/fal-images.d.ts +2 -1
- package/dist/protocols/fal-images.js +16 -29
- package/dist/protocols/fal-video.d.ts +2 -2
- package/dist/protocols/fal-video.js +9 -24
- package/dist/protocols/gemini.d.ts +13 -13
- package/dist/protocols/google-images.d.ts +3 -3
- package/dist/protocols/google-images.js +11 -21
- package/dist/protocols/google-speech.js +7 -13
- package/dist/protocols/google-transcription.d.ts +2 -1
- package/dist/protocols/google-transcription.js +8 -20
- package/dist/protocols/google-video.d.ts +2 -2
- package/dist/protocols/google-video.js +14 -30
- package/dist/protocols/meta-images.d.ts +3 -4
- package/dist/protocols/meta-images.js +12 -21
- package/dist/protocols/meta-messages.d.ts +5 -5
- package/dist/protocols/meta-responses.js +5 -2
- package/dist/protocols/mistral-chat.js +6 -3
- package/dist/protocols/openai-images.d.ts +4 -5
- package/dist/protocols/openai-images.js +24 -55
- package/dist/protocols/openai-responses.js +8 -2
- package/dist/protocols/openai-speech.js +7 -11
- package/dist/protocols/openai-transcription.js +21 -29
- package/dist/protocols/replicate-images.js +11 -17
- package/dist/protocols/runway-video.d.ts +3 -3
- package/dist/protocols/runway-video.js +10 -16
- package/dist/protocols/shared.d.ts +5 -17
- package/dist/protocols/shared.js +3 -39
- package/dist/protocols/stability-images.d.ts +2 -1
- package/dist/protocols/stability-images.js +24 -36
- package/dist/protocols/utils/fal-queue.d.ts +1 -3
- package/dist/protocols/utils/fal-queue.js +4 -6
- package/dist/protocols/utils/media-input.d.ts +15 -1
- package/dist/protocols/utils/media-input.js +22 -0
- package/dist/protocols/utils/speech-stream.d.ts +3 -3
- package/dist/protocols/utils/speech-stream.js +1 -4
- package/dist/protocols/utils/tool-schema.js +19 -6
- package/dist/protocols/xai-images.d.ts +4 -4
- package/dist/protocols/xai-images.js +12 -28
- package/dist/protocols/xai-video.js +12 -18
- package/dist/protocols/zai-images.d.ts +2 -2
- package/dist/protocols/zai-images.js +8 -11
- package/dist/protocols/zai-messages.d.ts +1 -1
- package/dist/providers/alibaba.d.ts +1 -1
- package/dist/providers/anthropic-compatible.d.ts +5 -5
- package/dist/providers/anthropic.d.ts +5 -5
- package/dist/providers/assemblyai.d.ts +1 -1
- package/dist/providers/assemblyai.js +5 -10
- package/dist/providers/azure.js +2 -2
- package/dist/providers/black-forest-labs.d.ts +1 -1
- package/dist/providers/black-forest-labs.js +5 -10
- package/dist/providers/cartesia.d.ts +1 -1
- package/dist/providers/cartesia.js +5 -10
- package/dist/providers/cloudflare-ai-gateway.d.ts +10 -10
- package/dist/providers/deepgram.d.ts +1 -1
- package/dist/providers/deepgram.js +6 -12
- package/dist/providers/elevenlabs.d.ts +1 -1
- package/dist/providers/elevenlabs.js +5 -10
- package/dist/providers/fal.d.ts +1 -1
- package/dist/providers/fal.js +5 -12
- package/dist/providers/google-vertex-messages.d.ts +5 -5
- package/dist/providers/google-vertex.d.ts +3 -3
- package/dist/providers/google.d.ts +3 -3
- package/dist/providers/google.js +7 -12
- package/dist/providers/meta.d.ts +8 -8
- package/dist/providers/meta.js +4 -8
- package/dist/providers/minimax.d.ts +5 -5
- package/dist/providers/moonshot.d.ts +5 -5
- package/dist/providers/openai.d.ts +7 -8
- package/dist/providers/openai.js +10 -11
- package/dist/providers/opencode-zen.js +1 -1
- package/dist/providers/openrouter.d.ts +6 -7
- package/dist/providers/openrouter.js +1 -1
- package/dist/providers/replicate.d.ts +1 -1
- package/dist/providers/replicate.js +5 -10
- package/dist/providers/runway.d.ts +1 -1
- package/dist/providers/runway.js +5 -10
- package/dist/providers/stability.d.ts +1 -1
- package/dist/providers/stability.js +6 -11
- package/dist/providers/typesafe-ai.js +1 -1
- package/dist/providers/vercel-ai-gateway.js +1 -1
- package/dist/providers/xai.js +5 -10
- package/dist/providers/zai-coding-plan.d.ts +1 -1
- package/dist/providers/zai.js +4 -8
- package/dist/route/client.d.ts +0 -1
- package/dist/route/client.js +1 -6
- package/dist/route/endpoint.d.ts +1 -0
- package/dist/route/endpoint.js +2 -2
- package/dist/route/framing.d.ts +10 -1
- package/dist/route/framing.js +45 -5
- package/dist/route/media-protocol.d.ts +42 -35
- package/dist/route/media-protocol.js +67 -62
- package/dist/route/media.d.ts +6 -2
- package/dist/route/media.js +40 -29
- package/dist/schema/options.d.ts +6 -3
- package/dist/schema/options.js +7 -3
- package/dist/speech.d.ts +2 -2
- package/dist/speech.js +2 -2
- package/dist/transcription.js +2 -2
- package/dist/utils/json.d.ts +4 -0
- package/dist/utils/json.js +4 -0
- package/dist/video.d.ts +2 -2
- package/dist/video.js +2 -2
- package/package.json +3 -3
- 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
|
@@ -3,13 +3,11 @@ import { classifyProviderFailure } from "../provider-error.js";
|
|
|
3
3
|
import { Framing } from "../route/framing.js";
|
|
4
4
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
5
|
import { MediaRoute } from "../route/media.js";
|
|
6
|
-
import { AIError,
|
|
6
|
+
import { AIError, mergeJsonRecords } from "../schema/index.js";
|
|
7
7
|
import { SpeechModel } from "../speech.js";
|
|
8
8
|
import { ProviderShared, optionalNull } from "./shared.js";
|
|
9
9
|
import { SpeechStream } from "./utils/speech-stream.js";
|
|
10
|
-
const
|
|
11
|
-
const NAME = "Cartesia";
|
|
12
|
-
const PROVIDER = ProviderID.make("cartesia");
|
|
10
|
+
const route = MediaProtocol.identity({ id: "cartesia-speech", name: "Cartesia", provider: "cartesia" });
|
|
13
11
|
export const DEFAULT_BASE_URL = "https://api.cartesia.ai";
|
|
14
12
|
export const API_VERSION = "2026-08-14";
|
|
15
13
|
export const BYTES_PATH = "/tts/bytes";
|
|
@@ -33,7 +31,7 @@ const SseEvent = Schema.Struct({
|
|
|
33
31
|
message: Schema.optional(Schema.String),
|
|
34
32
|
error_code: optionalNull(Schema.String),
|
|
35
33
|
});
|
|
36
|
-
const decodeEvent =
|
|
34
|
+
const decodeEvent = route.decodeFrame(SseEvent);
|
|
37
35
|
// ---------------------------------------------------------------------------
|
|
38
36
|
// 5. Request body construction
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
@@ -45,9 +43,9 @@ const outputFormat = Effect.fn("CartesiaSpeech.outputFormat")(function* (request
|
|
|
45
43
|
const format = request.format ?? (sse ? "pcm" : "mp3");
|
|
46
44
|
const container = CONTAINERS[format];
|
|
47
45
|
if (container === undefined)
|
|
48
|
-
return yield*
|
|
46
|
+
return yield* route.unsupported("media.format", `${route.name} supports the pcm, wav, and mp3 formats, not "${format}"`);
|
|
49
47
|
if (sse && container !== "raw")
|
|
50
|
-
return yield*
|
|
48
|
+
return yield* route.unsupported("media.format", `${route.name} streams and timestamps only raw PCM; request format "pcm" instead of "${format}"`);
|
|
51
49
|
const sampleRate = request.providerOptions?.sampleRate ?? DEFAULT_SAMPLE_RATE;
|
|
52
50
|
if (container === "mp3")
|
|
53
51
|
return { container, sample_rate: sampleRate, bit_rate: request.providerOptions?.bitRate ?? DEFAULT_BIT_RATE };
|
|
@@ -56,7 +54,7 @@ const outputFormat = Effect.fn("CartesiaSpeech.outputFormat")(function* (request
|
|
|
56
54
|
const fromRequest = Effect.fn("CartesiaSpeech.fromRequest")(function* (request) {
|
|
57
55
|
const voice = SpeechStream.voiceID(request.voice);
|
|
58
56
|
if (voice === undefined)
|
|
59
|
-
return yield* ProviderShared.invalidRequest(`${
|
|
57
|
+
return yield* ProviderShared.invalidRequest(`${route.name} requires a voice id; pass it as \`voice\``);
|
|
60
58
|
const { sampleRate: _sampleRate, bitRate: _bitRate, encoding: _encoding, ...native } = request.providerOptions ?? {};
|
|
61
59
|
return MediaProtocol.json(mergeJsonRecords({
|
|
62
60
|
model_id: request.model.id,
|
|
@@ -84,7 +82,7 @@ const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state, frame) {
|
|
|
84
82
|
if (event.type === "error")
|
|
85
83
|
return yield* new AIError({
|
|
86
84
|
reason: classifyProviderFailure({
|
|
87
|
-
message: `${
|
|
85
|
+
message: `${route.name} stream failed${event.title === undefined ? "" : ` (${event.title})`}: ${event.message ?? "unknown error"}`,
|
|
88
86
|
status: event.status_code,
|
|
89
87
|
rawBody: frame,
|
|
90
88
|
}),
|
|
@@ -93,18 +91,16 @@ const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state, frame) {
|
|
|
93
91
|
});
|
|
94
92
|
const finish = Effect.fn("CartesiaSpeech.finish")(function* (state, context) {
|
|
95
93
|
if (usesSse(context.request) && !state.done)
|
|
96
|
-
return yield*
|
|
94
|
+
return yield* route.incomplete();
|
|
97
95
|
const format = yield* outputFormat(context.request);
|
|
98
|
-
return yield* SpeechStream.finish(
|
|
96
|
+
return yield* SpeechStream.finish(route, state, format.container === "raw"
|
|
99
97
|
? SpeechStream.pcm(format.encoding, format.sample_rate)
|
|
100
98
|
: SpeechStream.container(format.container, format.sample_rate));
|
|
101
99
|
});
|
|
102
100
|
// ---------------------------------------------------------------------------
|
|
103
101
|
// 7. Protocol and route
|
|
104
102
|
// ---------------------------------------------------------------------------
|
|
105
|
-
export const protocol = MediaProtocol.stream({
|
|
106
|
-
id: ADAPTER,
|
|
107
|
-
name: NAME,
|
|
103
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
108
104
|
unsupported: ["instructions"],
|
|
109
105
|
body: { from: fromRequest },
|
|
110
106
|
frames: (bytes, context) => (usesSse(context.request) ? Framing.sse.frame(bytes) : bytes),
|
|
@@ -113,8 +109,6 @@ export const protocol = MediaProtocol.stream({
|
|
|
113
109
|
finish,
|
|
114
110
|
});
|
|
115
111
|
export const model = (input) => SpeechModel.fromRoute({
|
|
116
|
-
id: ADAPTER,
|
|
117
|
-
provider: PROVIDER,
|
|
118
112
|
protocol,
|
|
119
113
|
baseURL: DEFAULT_BASE_URL,
|
|
120
114
|
headers: { "Cartesia-Version": API_VERSION },
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
2
|
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type OpenString } from "../schema/index.js";
|
|
3
4
|
import { SpeechModel, type SpeechRequestFor } from "../speech.js";
|
|
4
5
|
import { SpeechStream } from "./utils/speech-stream.js";
|
|
5
6
|
export declare const DEFAULT_BASE_URL = "https://api.deepgram.com";
|
|
6
7
|
export declare const PATH = "/v1/speak";
|
|
7
|
-
export type
|
|
8
|
-
export type DeepgramEncoding = DeepgramSpeechString<"linear16" | "mulaw" | "alaw" | "mp3" | "opus" | "flac" | "aac">;
|
|
8
|
+
export type DeepgramEncoding = OpenString<"linear16" | "mulaw" | "alaw" | "mp3" | "opus" | "flac" | "aac">;
|
|
9
9
|
export type DeepgramSpeechOptions = {
|
|
10
10
|
readonly encoding?: DeepgramEncoding;
|
|
11
|
-
readonly container?:
|
|
11
|
+
readonly container?: OpenString<"wav" | "ogg" | "none">;
|
|
12
12
|
readonly sampleRate?: number;
|
|
13
13
|
readonly bitRate?: number;
|
|
14
14
|
readonly mip_opt_out?: boolean;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
3
|
import { MediaRoute } from "../route/media.js";
|
|
4
|
-
import {
|
|
4
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
5
5
|
import { SpeechModel } from "../speech.js";
|
|
6
6
|
import { MediaInput } from "./utils/media-input.js";
|
|
7
7
|
import { SpeechStream } from "./utils/speech-stream.js";
|
|
8
|
-
const
|
|
9
|
-
const NAME = "Deepgram";
|
|
10
|
-
const PROVIDER = ProviderID.make("deepgram");
|
|
8
|
+
const route = MediaProtocol.identity({ id: "deepgram-speech", name: "Deepgram", provider: "deepgram" });
|
|
11
9
|
export const DEFAULT_BASE_URL = "https://api.deepgram.com";
|
|
12
10
|
export const PATH = "/v1/speak";
|
|
13
11
|
// ---------------------------------------------------------------------------
|
|
@@ -30,7 +28,7 @@ const audioFormat = (request) => {
|
|
|
30
28
|
};
|
|
31
29
|
const queryParameters = (request) => {
|
|
32
30
|
const { encoding: _encoding, container: _container, sampleRate, bitRate, ...native } = request.providerOptions ?? {};
|
|
33
|
-
return MediaInput.query(
|
|
31
|
+
return MediaInput.query(route.id, {
|
|
34
32
|
...native,
|
|
35
33
|
model: request.model.id,
|
|
36
34
|
...audioFormat(request),
|
|
@@ -43,7 +41,7 @@ const fromRequest = Effect.fn("DeepgramSpeech.fromRequest")(function* (request)
|
|
|
43
41
|
if (request.format !== undefined &&
|
|
44
42
|
FORMATS[request.format] === undefined &&
|
|
45
43
|
request.providerOptions?.encoding === undefined)
|
|
46
|
-
return yield*
|
|
44
|
+
return yield* route.unsupported("media.format", `${route.name} has no encoding for format "${request.format}"; pass providerOptions.encoding`);
|
|
47
45
|
return MediaProtocol.json(mergeJsonRecords({ text: request.text }, request.http?.body) ?? {}, yield* queryParameters(request));
|
|
48
46
|
});
|
|
49
47
|
// ---------------------------------------------------------------------------
|
|
@@ -61,7 +59,7 @@ const finish = (state, context) => {
|
|
|
61
59
|
const encoding = HEADERLESS_ENCODINGS[format.encoding ?? ""];
|
|
62
60
|
const requestID = headers["dg-request-id"];
|
|
63
61
|
const modelName = headers["dg-model-name"];
|
|
64
|
-
return SpeechStream.finish(
|
|
62
|
+
return SpeechStream.finish(route, state, {
|
|
65
63
|
...(format.container === "none" && encoding !== undefined
|
|
66
64
|
? SpeechStream.pcm(encoding, SpeechStream.sampleRate(mediaType), mediaType)
|
|
67
65
|
: // Deepgram's default encoding is MP3; WAV is a container around any encoding.
|
|
@@ -75,9 +73,7 @@ const finish = (state, context) => {
|
|
|
75
73
|
// ---------------------------------------------------------------------------
|
|
76
74
|
// 7. Protocol and route
|
|
77
75
|
// ---------------------------------------------------------------------------
|
|
78
|
-
export const protocol = MediaProtocol.stream({
|
|
79
|
-
id: ADAPTER,
|
|
80
|
-
name: NAME,
|
|
76
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
81
77
|
unsupported: ["voice", "language", "instructions", "timestamps"],
|
|
82
78
|
body: { from: fromRequest },
|
|
83
79
|
frames: (bytes) => bytes,
|
|
@@ -85,7 +81,7 @@ export const protocol = MediaProtocol.stream({
|
|
|
85
81
|
step: (state, frame) => Effect.succeed(SpeechStream.delta(state, frame)),
|
|
86
82
|
finish,
|
|
87
83
|
});
|
|
88
|
-
export const model = (input) => SpeechModel.fromRoute({
|
|
84
|
+
export const model = (input) => SpeechModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
|
|
89
85
|
export const DeepgramSpeech = {
|
|
90
86
|
protocol,
|
|
91
87
|
model,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
2
|
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type OpenString } from "../schema/index.js";
|
|
3
4
|
import { TranscriptionModel, TranscriptionResponse, type TranscriptionRequestFor } from "../transcription.js";
|
|
4
5
|
export declare const DEFAULT_BASE_URL = "https://api.deepgram.com";
|
|
5
6
|
export declare const PATH = "/v1/listen";
|
|
@@ -10,7 +11,7 @@ export type DeepgramTranscriptionOptions = {
|
|
|
10
11
|
readonly utterances?: boolean;
|
|
11
12
|
readonly detect_language?: boolean | ReadonlyArray<string>;
|
|
12
13
|
readonly keyterm?: ReadonlyArray<string>;
|
|
13
|
-
readonly diarize_model?: "latest" | "v1" | "v2"
|
|
14
|
+
readonly diarize_model?: OpenString<"latest" | "v1" | "v2">;
|
|
14
15
|
readonly filler_words?: boolean;
|
|
15
16
|
readonly numerals?: boolean;
|
|
16
17
|
readonly mip_opt_out?: boolean;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
3
|
import { MediaRoute } from "../route/media.js";
|
|
4
|
-
import {
|
|
4
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
5
5
|
import { TranscriptionModel, TranscriptionResponse } from "../transcription.js";
|
|
6
6
|
import { ProviderShared } from "./shared.js";
|
|
7
7
|
import { MediaInput } from "./utils/media-input.js";
|
|
8
|
-
const
|
|
9
|
-
const NAME = "Deepgram";
|
|
10
|
-
const PROVIDER = ProviderID.make("deepgram");
|
|
8
|
+
const route = MediaProtocol.identity({ id: "deepgram-transcription", name: "Deepgram", provider: "deepgram" });
|
|
11
9
|
export const DEFAULT_BASE_URL = "https://api.deepgram.com";
|
|
12
10
|
export const PATH = "/v1/listen";
|
|
13
11
|
// ---------------------------------------------------------------------------
|
|
@@ -40,7 +38,7 @@ const ListenResponse = Schema.Struct({
|
|
|
40
38
|
// ---------------------------------------------------------------------------
|
|
41
39
|
// 5. Request body construction
|
|
42
40
|
// ---------------------------------------------------------------------------
|
|
43
|
-
const query = (request) => MediaInput.query(
|
|
41
|
+
const query = (request) => MediaInput.query(route.id, mergeJsonRecords({
|
|
44
42
|
model: request.model.id,
|
|
45
43
|
smart_format: true,
|
|
46
44
|
language: request.language,
|
|
@@ -55,14 +53,14 @@ const fromRequest = Effect.fn("DeepgramTranscription.fromRequest")(function* (re
|
|
|
55
53
|
if (url !== undefined)
|
|
56
54
|
return MediaProtocol.json(mergeJsonRecords({ url }, request.http?.body) ?? {}, yield* query(request));
|
|
57
55
|
if (request.http?.body !== undefined)
|
|
58
|
-
return yield* ProviderShared.invalidRequest(`${
|
|
59
|
-
const audio = yield* MediaInput.inlineBytes(
|
|
56
|
+
return yield* ProviderShared.invalidRequest(`${route.name} sends inline audio as the raw body, so http.body cannot apply`);
|
|
57
|
+
const audio = yield* MediaInput.inlineBytes(route.id, request.audio);
|
|
60
58
|
return MediaProtocol.binary(audio, request.audio.mediaType, yield* query(request));
|
|
61
59
|
});
|
|
62
60
|
// ---------------------------------------------------------------------------
|
|
63
61
|
// 6. Response decoding
|
|
64
62
|
// ---------------------------------------------------------------------------
|
|
65
|
-
const decodeListen =
|
|
63
|
+
const decodeListen = route.decodeJson(ListenResponse);
|
|
66
64
|
const speaker = (value) => (value === undefined ? undefined : String(value));
|
|
67
65
|
const wordText = (word) => word.punctuated_word ?? word.word;
|
|
68
66
|
// Utterances split on pauses, not speakers: the v2 diarizer labels a whole utterance with one speaker even when its
|
|
@@ -79,7 +77,7 @@ const decodeResponse = Effect.fn("DeepgramTranscription.decodeResponse")(functio
|
|
|
79
77
|
const channel = output.value.results.channels[0];
|
|
80
78
|
const alternative = channel?.alternatives?.[0];
|
|
81
79
|
if (alternative === undefined)
|
|
82
|
-
return yield* output.invalid(`${
|
|
80
|
+
return yield* output.invalid(`${route.name} returned no transcript`);
|
|
83
81
|
const duration = output.value.metadata?.duration;
|
|
84
82
|
const requestID = output.value.metadata?.request_id;
|
|
85
83
|
return new TranscriptionResponse({
|
|
@@ -115,14 +113,12 @@ const decodeResponse = Effect.fn("DeepgramTranscription.decodeResponse")(functio
|
|
|
115
113
|
// ---------------------------------------------------------------------------
|
|
116
114
|
// 7. Protocol and route
|
|
117
115
|
// ---------------------------------------------------------------------------
|
|
118
|
-
export const protocol = MediaProtocol.inline({
|
|
119
|
-
id: ADAPTER,
|
|
120
|
-
name: NAME,
|
|
116
|
+
export const protocol = MediaProtocol.inline(route, {
|
|
121
117
|
unsupported: ["prompt", "speakers"],
|
|
122
118
|
body: { from: fromRequest },
|
|
123
119
|
response: { decode: decodeResponse },
|
|
124
120
|
});
|
|
125
|
-
export const model = (input) => TranscriptionModel.fromRoute({
|
|
121
|
+
export const model = (input) => TranscriptionModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
|
|
126
122
|
export const DeepgramTranscription = {
|
|
127
123
|
protocol,
|
|
128
124
|
model,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
2
|
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type OpenString } from "../schema/index.js";
|
|
3
4
|
import { SpeechModel, type SpeechRequestFor } from "../speech.js";
|
|
4
5
|
import { SpeechStream } from "./utils/speech-stream.js";
|
|
5
6
|
export declare const DEFAULT_BASE_URL = "https://api.elevenlabs.io";
|
|
6
7
|
export declare const PATH = "/v1/text-to-speech";
|
|
7
|
-
export type
|
|
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">;
|
|
8
|
+
export type ElevenLabsOutputFormat = OpenString<"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
9
|
export type ElevenLabsSpeechOptions = {
|
|
10
10
|
readonly outputFormat?: ElevenLabsOutputFormat;
|
|
11
11
|
readonly voice_settings?: {
|
|
@@ -15,7 +15,7 @@ export type ElevenLabsSpeechOptions = {
|
|
|
15
15
|
readonly use_speaker_boost?: boolean;
|
|
16
16
|
};
|
|
17
17
|
readonly seed?: number;
|
|
18
|
-
readonly apply_text_normalization?:
|
|
18
|
+
readonly apply_text_normalization?: OpenString<"auto" | "on" | "off">;
|
|
19
19
|
} & Record<string, unknown>;
|
|
20
20
|
export type Request = SpeechRequestFor<ElevenLabsSpeechOptions>;
|
|
21
21
|
export declare const protocol: MediaProtocol.Streamed<Request, {
|
|
@@ -2,13 +2,11 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import { Framing } from "../route/framing.js";
|
|
3
3
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
4
|
import { MediaRoute } from "../route/media.js";
|
|
5
|
-
import {
|
|
5
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
6
6
|
import { SpeechModel } from "../speech.js";
|
|
7
7
|
import { ProviderShared, optionalNull } from "./shared.js";
|
|
8
8
|
import { SpeechStream } from "./utils/speech-stream.js";
|
|
9
|
-
const
|
|
10
|
-
const NAME = "ElevenLabs";
|
|
11
|
-
const PROVIDER = ProviderID.make("elevenlabs");
|
|
9
|
+
const route = MediaProtocol.identity({ id: "elevenlabs-speech", name: "ElevenLabs", provider: "elevenlabs" });
|
|
12
10
|
export const DEFAULT_BASE_URL = "https://api.elevenlabs.io";
|
|
13
11
|
export const PATH = "/v1/text-to-speech";
|
|
14
12
|
// ---------------------------------------------------------------------------
|
|
@@ -23,7 +21,7 @@ const TimestampedAudio = Schema.Struct({
|
|
|
23
21
|
audio_base64: Schema.Uint8ArrayFromBase64,
|
|
24
22
|
alignment: optionalNull(Alignment),
|
|
25
23
|
});
|
|
26
|
-
const decodeRecord =
|
|
24
|
+
const decodeRecord = route.decodeFrame(TimestampedAudio);
|
|
27
25
|
// ---------------------------------------------------------------------------
|
|
28
26
|
// 5. Request body construction
|
|
29
27
|
// ---------------------------------------------------------------------------
|
|
@@ -37,14 +35,14 @@ const OUTPUT_FORMATS = {
|
|
|
37
35
|
const outputFormat = Effect.fn("ElevenLabsSpeech.outputFormat")(function* (request) {
|
|
38
36
|
const format = request.providerOptions?.outputFormat ?? OUTPUT_FORMATS[request.format ?? "mp3"];
|
|
39
37
|
if (format === undefined)
|
|
40
|
-
return yield*
|
|
38
|
+
return yield* route.unsupported("media.format", `${route.name} has no default output format for "${request.format}"; pass providerOptions.outputFormat`);
|
|
41
39
|
if (request.mode === "stream" && format.startsWith("wav_"))
|
|
42
|
-
return yield*
|
|
40
|
+
return yield* route.unsupported("media.format", `${route.name} streams mp3, pcm, opus, ulaw, and alaw but not "${format}"; use generate for WAV`);
|
|
43
41
|
return format;
|
|
44
42
|
});
|
|
45
43
|
const fromRequest = Effect.fn("ElevenLabsSpeech.fromRequest")(function* (request) {
|
|
46
44
|
if (request.voice === undefined)
|
|
47
|
-
return yield* ProviderShared.invalidRequest(`${
|
|
45
|
+
return yield* ProviderShared.invalidRequest(`${route.name} requires a voice id; pass it as \`voice\``);
|
|
48
46
|
const { outputFormat: _outputFormat, ...native } = request.providerOptions ?? {};
|
|
49
47
|
return MediaProtocol.json(mergeJsonRecords({
|
|
50
48
|
text: request.text,
|
|
@@ -84,7 +82,7 @@ const describeOutput = (format) => {
|
|
|
84
82
|
};
|
|
85
83
|
const finish = Effect.fn("ElevenLabsSpeech.finish")(function* (state, context) {
|
|
86
84
|
const requestID = context.http.headers["request-id"];
|
|
87
|
-
return yield* SpeechStream.finish(
|
|
85
|
+
return yield* SpeechStream.finish(route, state, {
|
|
88
86
|
...describeOutput(yield* outputFormat(context.request)),
|
|
89
87
|
// `character-cost` is billed credits, not a character count (3 for 20 characters on `eleven_flash_v2_5`).
|
|
90
88
|
usage: SpeechStream.headerUsage("credits", context.http.headers["character-cost"]),
|
|
@@ -94,9 +92,7 @@ const finish = Effect.fn("ElevenLabsSpeech.finish")(function* (state, context) {
|
|
|
94
92
|
// ---------------------------------------------------------------------------
|
|
95
93
|
// 7. Protocol and route
|
|
96
94
|
// ---------------------------------------------------------------------------
|
|
97
|
-
export const protocol = MediaProtocol.stream({
|
|
98
|
-
id: ADAPTER,
|
|
99
|
-
name: NAME,
|
|
95
|
+
export const protocol = MediaProtocol.stream(route, {
|
|
100
96
|
unsupported: ["instructions"],
|
|
101
97
|
body: { from: fromRequest },
|
|
102
98
|
frames: (bytes, context) => {
|
|
@@ -108,7 +104,7 @@ export const protocol = MediaProtocol.stream({
|
|
|
108
104
|
step: SpeechStream.step(onRecord),
|
|
109
105
|
finish,
|
|
110
106
|
});
|
|
111
|
-
export const model = (input) => SpeechModel.fromRoute({
|
|
107
|
+
export const model = (input) => SpeechModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: ({ request }) => path(request) }, input);
|
|
112
108
|
export const ElevenLabsSpeech = {
|
|
113
109
|
protocol,
|
|
114
110
|
model,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
2
2
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
3
|
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { type OpenString } from "../schema/index.js";
|
|
4
5
|
export type FalImageOptions = {
|
|
5
|
-
readonly image_size?: "square_hd" | "square" | "portrait_4_3" | "portrait_16_9" | "landscape_4_3" | "landscape_16_9"
|
|
6
|
+
readonly image_size?: OpenString<"square_hd" | "square" | "portrait_4_3" | "portrait_16_9" | "landscape_4_3" | "landscape_16_9">;
|
|
6
7
|
readonly enable_safety_checker?: boolean;
|
|
7
8
|
} & Record<string, unknown>;
|
|
8
9
|
export type Request = ImageRequestFor<FalImageOptions>;
|
|
@@ -3,13 +3,11 @@ import { ImageModel, ImageResponse } from "../image.js";
|
|
|
3
3
|
import { Media } from "../media.js";
|
|
4
4
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
5
|
import { MediaRoute } from "../route/media.js";
|
|
6
|
-
import {
|
|
6
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
7
7
|
import { ProviderShared, optionalNull } from "./shared.js";
|
|
8
8
|
import { FalQueue } from "./utils/fal-queue.js";
|
|
9
9
|
import { MediaInput } from "./utils/media-input.js";
|
|
10
|
-
const
|
|
11
|
-
const NAME = "fal Images";
|
|
12
|
-
const PROVIDER = ProviderID.make("fal");
|
|
10
|
+
const route = MediaProtocol.identity({ id: "fal-images", name: "fal Images", provider: "fal" });
|
|
13
11
|
// ---------------------------------------------------------------------------
|
|
14
12
|
// 2. Response schema
|
|
15
13
|
// ---------------------------------------------------------------------------
|
|
@@ -33,30 +31,24 @@ const sizing = (model) => {
|
|
|
33
31
|
return "image_size";
|
|
34
32
|
return undefined;
|
|
35
33
|
};
|
|
36
|
-
const unsupported = (model, field, message) => ProviderShared.unsupportedOperation({
|
|
37
|
-
operation: `media.${field}`,
|
|
38
|
-
provider: PROVIDER,
|
|
39
|
-
route: ADAPTER,
|
|
40
|
-
message: `${model} ${message}`,
|
|
41
|
-
});
|
|
42
34
|
const validate = (request) => {
|
|
43
35
|
const id = request.model.id;
|
|
44
36
|
const field = sizing(id);
|
|
45
37
|
if (request.size !== undefined && request.aspectRatio !== undefined)
|
|
46
|
-
return Effect.fail(ProviderShared.invalidRequest(`${
|
|
38
|
+
return Effect.fail(ProviderShared.invalidRequest(`${route.name} accepts either size or aspectRatio, not both`));
|
|
47
39
|
if (request.size !== undefined && field === "aspect_ratio")
|
|
48
|
-
return Effect.fail(unsupported(
|
|
40
|
+
return Effect.fail(route.unsupported("media.size", `${id} sizes by aspectRatio`));
|
|
49
41
|
if (request.aspectRatio !== undefined && field === "image_size")
|
|
50
|
-
return Effect.fail(unsupported(
|
|
42
|
+
return Effect.fail(route.unsupported("media.aspectRatio", `${id} sizes by size (image_size)`));
|
|
51
43
|
if ((request.images?.length ?? 0) > 1 && !isEdit(id))
|
|
52
|
-
return Effect.fail(unsupported(
|
|
44
|
+
return Effect.fail(route.unsupported("media.images", `${id} takes one image_url; use an /edit endpoint for several images`));
|
|
53
45
|
return Effect.void;
|
|
54
46
|
};
|
|
55
47
|
// `/edit` endpoints take an `image_urls` list; image-to-image, fill, and Ultra take one `image_url` (beside `mask_url`).
|
|
56
48
|
const isEdit = (model) => model.endsWith("/edit");
|
|
57
49
|
const fromRequest = Effect.fn("FalImages.fromRequest")(function* (request) {
|
|
58
50
|
yield* validate(request);
|
|
59
|
-
const images = yield* Effect.forEach(request.images ?? [], (image) => FalQueue.mediaUrl(image,
|
|
51
|
+
const images = yield* Effect.forEach(request.images ?? [], (image) => FalQueue.mediaUrl(image, route.name));
|
|
60
52
|
const edit = isEdit(request.model.id);
|
|
61
53
|
return MediaProtocol.json(mergeJsonRecords({
|
|
62
54
|
prompt: request.prompt,
|
|
@@ -67,18 +59,18 @@ const fromRequest = Effect.fn("FalImages.fromRequest")(function* (request) {
|
|
|
67
59
|
output_format: request.format,
|
|
68
60
|
image_urls: edit && images.length > 0 ? images : undefined,
|
|
69
61
|
image_url: edit ? undefined : images[0],
|
|
70
|
-
mask_url: request.mask === undefined ? undefined : yield* FalQueue.mediaUrl(request.mask,
|
|
62
|
+
mask_url: request.mask === undefined ? undefined : yield* FalQueue.mediaUrl(request.mask, route.name),
|
|
71
63
|
}, request.providerOptions, request.http?.body) ?? {});
|
|
72
64
|
});
|
|
73
65
|
// ---------------------------------------------------------------------------
|
|
74
66
|
// 6. Response decoding
|
|
75
67
|
// ---------------------------------------------------------------------------
|
|
76
|
-
const decodeQueueResult =
|
|
68
|
+
const decodeQueueResult = route.decodeJson(QueueResult);
|
|
77
69
|
const decodeResult = Effect.fn("FalImages.decodeResult")(function* (response, context) {
|
|
78
70
|
const output = yield* decodeQueueResult(response);
|
|
79
71
|
const { images, seed, has_nsfw_concepts, ...rest } = output.value;
|
|
80
72
|
if (images.length === 0)
|
|
81
|
-
return yield* output.invalid(`${
|
|
73
|
+
return yield* output.invalid(`${route.name} returned no images`);
|
|
82
74
|
// With the safety checker on, flagged images come back blacked out rather than omitted.
|
|
83
75
|
const flagged = (has_nsfw_concepts ?? []).flatMap((value, index) => (value ? [index] : []));
|
|
84
76
|
return new ImageResponse({
|
|
@@ -88,26 +80,21 @@ const decodeResult = Effect.fn("FalImages.decodeResult")(function* (response, co
|
|
|
88
80
|
})),
|
|
89
81
|
notices: flagged.length === 0
|
|
90
82
|
? undefined
|
|
91
|
-
: flagged.map((index) => ({
|
|
83
|
+
: flagged.map((index) => ({
|
|
84
|
+
type: "moderated",
|
|
85
|
+
message: `${route.name} flagged image ${index} as NSFW`,
|
|
86
|
+
})),
|
|
92
87
|
providerMetadata: { fal: { requestId: context.token.requestID, seed: seed ?? undefined, ...rest } },
|
|
93
88
|
});
|
|
94
89
|
});
|
|
95
90
|
// ---------------------------------------------------------------------------
|
|
96
91
|
// 7. Protocol and route
|
|
97
92
|
// ---------------------------------------------------------------------------
|
|
98
|
-
export const protocol = FalQueue.protocol({
|
|
99
|
-
id: ADAPTER,
|
|
100
|
-
name: NAME,
|
|
93
|
+
export const protocol = FalQueue.protocol(route, {
|
|
101
94
|
from: fromRequest,
|
|
102
95
|
decodeResult,
|
|
103
96
|
});
|
|
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);
|
|
97
|
+
export const model = (input) => ImageModel.fromRoute({ protocol, baseURL: FalQueue.DEFAULT_BASE_URL, path: ({ request }) => `/${request.model.id}` }, input);
|
|
111
98
|
export const FalImages = {
|
|
112
99
|
protocol,
|
|
113
100
|
model,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
2
|
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type OpenString } from "../schema/index.js";
|
|
3
4
|
import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
|
|
4
|
-
export type FalVideoString<Known extends string> = Known | (string & {});
|
|
5
5
|
/**
|
|
6
6
|
* Provider-native input. fal video endpoints are model-specific: `duration` is a string enum whose values differ per
|
|
7
7
|
* model (`"8s"` for Veo, `"5"` for Kling), and last-frame fields are named per model (`end_image_url`,
|
|
8
8
|
* `last_frame_url`, `tail_image_url`), so those pass through here instead of lowering from common fields.
|
|
9
9
|
*/
|
|
10
10
|
export type FalVideoOptions = {
|
|
11
|
-
readonly duration?:
|
|
11
|
+
readonly duration?: OpenString<"4s" | "6s" | "8s" | "5" | "10">;
|
|
12
12
|
} & Record<string, unknown>;
|
|
13
13
|
export type Request = VideoRequestFor<FalVideoOptions>;
|
|
14
14
|
export declare const protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
@@ -2,13 +2,11 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import { Media } from "../media.js";
|
|
3
3
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
4
|
import { MediaRoute } from "../route/media.js";
|
|
5
|
-
import {
|
|
5
|
+
import { mergeJsonRecords } from "../schema/index.js";
|
|
6
6
|
import { VideoModel, VideoResponse } from "../video.js";
|
|
7
|
-
import {
|
|
7
|
+
import { optionalNull } from "./shared.js";
|
|
8
8
|
import { FalQueue } from "./utils/fal-queue.js";
|
|
9
|
-
const
|
|
10
|
-
const NAME = "fal Video";
|
|
11
|
-
const PROVIDER = ProviderID.make("fal");
|
|
9
|
+
const route = MediaProtocol.identity({ id: "fal-video", name: "fal Video", provider: "fal" });
|
|
12
10
|
// ---------------------------------------------------------------------------
|
|
13
11
|
// 2. Response schema
|
|
14
12
|
// ---------------------------------------------------------------------------
|
|
@@ -26,14 +24,9 @@ const QueueResult = Schema.StructWithRest(Schema.Struct({
|
|
|
26
24
|
// ---------------------------------------------------------------------------
|
|
27
25
|
const fromRequest = Effect.fn("FalVideo.fromRequest")(function* (request) {
|
|
28
26
|
if (request.frames?.last !== undefined)
|
|
29
|
-
return yield*
|
|
30
|
-
|
|
31
|
-
|
|
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);
|
|
27
|
+
return yield* route.unsupported("video.frames.last", `${route.name} names the last frame per model; pass it through providerOptions (e.g. end_image_url) instead of frames.last`);
|
|
28
|
+
const imageUrl = request.frames?.first === undefined ? undefined : yield* FalQueue.mediaUrl(request.frames.first, route.name);
|
|
29
|
+
const videoUrl = request.video === undefined ? undefined : yield* FalQueue.mediaUrl(request.video, route.name);
|
|
37
30
|
return MediaProtocol.json(mergeJsonRecords({
|
|
38
31
|
prompt: request.prompt,
|
|
39
32
|
negative_prompt: request.negativePrompt,
|
|
@@ -48,7 +41,7 @@ const fromRequest = Effect.fn("FalVideo.fromRequest")(function* (request) {
|
|
|
48
41
|
// ---------------------------------------------------------------------------
|
|
49
42
|
// 6. Response decoding
|
|
50
43
|
// ---------------------------------------------------------------------------
|
|
51
|
-
const decodeQueueResult =
|
|
44
|
+
const decodeQueueResult = route.decodeJson(QueueResult);
|
|
52
45
|
const decodeResult = Effect.fn("FalVideo.decodeResult")(function* (response, context) {
|
|
53
46
|
const output = yield* decodeQueueResult(response);
|
|
54
47
|
const { video, seed, ...rest } = output.value;
|
|
@@ -68,20 +61,12 @@ const decodeResult = Effect.fn("FalVideo.decodeResult")(function* (response, con
|
|
|
68
61
|
// ---------------------------------------------------------------------------
|
|
69
62
|
// 7. Protocol and route
|
|
70
63
|
// ---------------------------------------------------------------------------
|
|
71
|
-
export const protocol = FalQueue.protocol({
|
|
72
|
-
id: ADAPTER,
|
|
73
|
-
name: NAME,
|
|
64
|
+
export const protocol = FalQueue.protocol(route, {
|
|
74
65
|
unsupported: ["n", "durationSeconds", "references"],
|
|
75
66
|
from: fromRequest,
|
|
76
67
|
decodeResult,
|
|
77
68
|
});
|
|
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);
|
|
69
|
+
export const model = (input) => VideoModel.fromRoute({ protocol, baseURL: FalQueue.DEFAULT_BASE_URL, path: ({ request }) => `/${request.model.id}` }, input);
|
|
85
70
|
export const FalVideo = {
|
|
86
71
|
protocol,
|
|
87
72
|
model,
|