@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.
Files changed (128) hide show
  1. package/dist/experimental/evaluation.js +1 -1
  2. package/dist/generation.d.ts +1 -7
  3. package/dist/generation.js +1 -11
  4. package/dist/image.d.ts +2 -2
  5. package/dist/image.js +2 -2
  6. package/dist/llm.js +1 -1
  7. package/dist/media-model.d.ts +0 -2
  8. package/dist/media-model.js +0 -2
  9. package/dist/protocols/alibaba-messages.d.ts +1 -1
  10. package/dist/protocols/anthropic-messages.d.ts +34 -34
  11. package/dist/protocols/assemblyai-transcription.d.ts +2 -1
  12. package/dist/protocols/assemblyai-transcription.js +10 -14
  13. package/dist/protocols/bfl-images.js +15 -31
  14. package/dist/protocols/cartesia-speech.d.ts +2 -2
  15. package/dist/protocols/cartesia-speech.js +10 -16
  16. package/dist/protocols/deepgram-speech.d.ts +3 -3
  17. package/dist/protocols/deepgram-speech.js +7 -11
  18. package/dist/protocols/deepgram-transcription.d.ts +2 -1
  19. package/dist/protocols/deepgram-transcription.js +9 -13
  20. package/dist/protocols/elevenlabs-speech.d.ts +3 -3
  21. package/dist/protocols/elevenlabs-speech.js +9 -13
  22. package/dist/protocols/fal-images.d.ts +2 -1
  23. package/dist/protocols/fal-images.js +16 -29
  24. package/dist/protocols/fal-video.d.ts +2 -2
  25. package/dist/protocols/fal-video.js +9 -24
  26. package/dist/protocols/gemini.d.ts +13 -13
  27. package/dist/protocols/google-images.d.ts +3 -3
  28. package/dist/protocols/google-images.js +11 -21
  29. package/dist/protocols/google-speech.js +7 -13
  30. package/dist/protocols/google-transcription.d.ts +2 -1
  31. package/dist/protocols/google-transcription.js +8 -20
  32. package/dist/protocols/google-video.d.ts +2 -2
  33. package/dist/protocols/google-video.js +14 -30
  34. package/dist/protocols/meta-images.d.ts +3 -4
  35. package/dist/protocols/meta-images.js +12 -21
  36. package/dist/protocols/meta-messages.d.ts +5 -5
  37. package/dist/protocols/meta-responses.js +5 -2
  38. package/dist/protocols/mistral-chat.js +6 -3
  39. package/dist/protocols/openai-images.d.ts +4 -5
  40. package/dist/protocols/openai-images.js +24 -55
  41. package/dist/protocols/openai-responses.js +8 -2
  42. package/dist/protocols/openai-speech.js +7 -11
  43. package/dist/protocols/openai-transcription.js +21 -29
  44. package/dist/protocols/replicate-images.js +11 -17
  45. package/dist/protocols/runway-video.d.ts +3 -3
  46. package/dist/protocols/runway-video.js +10 -16
  47. package/dist/protocols/shared.d.ts +5 -17
  48. package/dist/protocols/shared.js +3 -39
  49. package/dist/protocols/stability-images.d.ts +2 -1
  50. package/dist/protocols/stability-images.js +24 -36
  51. package/dist/protocols/utils/fal-queue.d.ts +1 -3
  52. package/dist/protocols/utils/fal-queue.js +4 -6
  53. package/dist/protocols/utils/media-input.d.ts +15 -1
  54. package/dist/protocols/utils/media-input.js +22 -0
  55. package/dist/protocols/utils/speech-stream.d.ts +3 -3
  56. package/dist/protocols/utils/speech-stream.js +1 -4
  57. package/dist/protocols/utils/tool-schema.js +19 -6
  58. package/dist/protocols/xai-images.d.ts +4 -4
  59. package/dist/protocols/xai-images.js +12 -28
  60. package/dist/protocols/xai-video.js +12 -18
  61. package/dist/protocols/zai-images.d.ts +2 -2
  62. package/dist/protocols/zai-images.js +8 -11
  63. package/dist/protocols/zai-messages.d.ts +1 -1
  64. package/dist/providers/alibaba.d.ts +1 -1
  65. package/dist/providers/anthropic-compatible.d.ts +5 -5
  66. package/dist/providers/anthropic.d.ts +5 -5
  67. package/dist/providers/assemblyai.d.ts +1 -1
  68. package/dist/providers/assemblyai.js +5 -10
  69. package/dist/providers/azure.js +2 -2
  70. package/dist/providers/black-forest-labs.d.ts +1 -1
  71. package/dist/providers/black-forest-labs.js +5 -10
  72. package/dist/providers/cartesia.d.ts +1 -1
  73. package/dist/providers/cartesia.js +5 -10
  74. package/dist/providers/cloudflare-ai-gateway.d.ts +10 -10
  75. package/dist/providers/deepgram.d.ts +1 -1
  76. package/dist/providers/deepgram.js +6 -12
  77. package/dist/providers/elevenlabs.d.ts +1 -1
  78. package/dist/providers/elevenlabs.js +5 -10
  79. package/dist/providers/fal.d.ts +1 -1
  80. package/dist/providers/fal.js +5 -12
  81. package/dist/providers/google-vertex-messages.d.ts +5 -5
  82. package/dist/providers/google-vertex.d.ts +3 -3
  83. package/dist/providers/google.d.ts +3 -3
  84. package/dist/providers/google.js +7 -12
  85. package/dist/providers/meta.d.ts +8 -8
  86. package/dist/providers/meta.js +4 -8
  87. package/dist/providers/minimax.d.ts +5 -5
  88. package/dist/providers/moonshot.d.ts +5 -5
  89. package/dist/providers/openai.d.ts +7 -8
  90. package/dist/providers/openai.js +10 -11
  91. package/dist/providers/opencode-zen.js +1 -1
  92. package/dist/providers/openrouter.d.ts +6 -7
  93. package/dist/providers/openrouter.js +1 -1
  94. package/dist/providers/replicate.d.ts +1 -1
  95. package/dist/providers/replicate.js +5 -10
  96. package/dist/providers/runway.d.ts +1 -1
  97. package/dist/providers/runway.js +5 -10
  98. package/dist/providers/stability.d.ts +1 -1
  99. package/dist/providers/stability.js +6 -11
  100. package/dist/providers/typesafe-ai.js +1 -1
  101. package/dist/providers/vercel-ai-gateway.js +1 -1
  102. package/dist/providers/xai.js +5 -10
  103. package/dist/providers/zai-coding-plan.d.ts +1 -1
  104. package/dist/providers/zai.js +4 -8
  105. package/dist/route/client.d.ts +0 -1
  106. package/dist/route/client.js +1 -6
  107. package/dist/route/endpoint.d.ts +1 -0
  108. package/dist/route/endpoint.js +2 -2
  109. package/dist/route/framing.d.ts +10 -1
  110. package/dist/route/framing.js +45 -5
  111. package/dist/route/media-protocol.d.ts +42 -35
  112. package/dist/route/media-protocol.js +67 -62
  113. package/dist/route/media.d.ts +6 -2
  114. package/dist/route/media.js +40 -29
  115. package/dist/schema/options.d.ts +6 -3
  116. package/dist/schema/options.js +7 -3
  117. package/dist/speech.d.ts +2 -2
  118. package/dist/speech.js +2 -2
  119. package/dist/transcription.js +2 -2
  120. package/dist/utils/json.d.ts +4 -0
  121. package/dist/utils/json.js +4 -0
  122. package/dist/video.d.ts +2 -2
  123. package/dist/video.js +2 -2
  124. package/package.json +3 -3
  125. package/dist/protocols/utils/meta-image.d.ts +0 -2
  126. package/dist/protocols/utils/meta-image.js +0 -13
  127. package/dist/protocols/utils/openai-image.d.ts +0 -5
  128. 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, ProviderID, mergeJsonRecords } from "../schema/index.js";
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 ADAPTER = "cartesia-speech";
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 = MediaProtocol.decodeFrame(ADAPTER, NAME, SseEvent);
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* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} supports the pcm, wav, and mp3 formats, not "${format}"`);
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* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} streams and timestamps only raw PCM; request format "pcm" instead of "${format}"`);
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(`${NAME} requires a voice id; pass it as \`voice\``);
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: `${NAME} stream failed${event.title === undefined ? "" : ` (${event.title})`}: ${event.message ?? "unknown error"}`,
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* MediaProtocol.incomplete(ADAPTER);
94
+ return yield* route.incomplete();
97
95
  const format = yield* outputFormat(context.request);
98
- return yield* SpeechStream.finish(ADAPTER, state, format.container === "raw"
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 DeepgramSpeechString<Known extends string> = Known | (string & {});
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?: DeepgramSpeechString<"wav" | "ogg" | "none">;
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 { ProviderID, mergeJsonRecords } from "../schema/index.js";
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 ADAPTER = "deepgram-speech";
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(ADAPTER, {
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* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} has no encoding for format "${request.format}"; pass providerOptions.encoding`);
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(ADAPTER, state, {
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({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
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" | (string & {});
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 { ProviderID, mergeJsonRecords } from "../schema/index.js";
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 ADAPTER = "deepgram-transcription";
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(ADAPTER, mergeJsonRecords({
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(`${NAME} sends inline audio as the raw body, so http.body cannot apply`);
59
- const audio = yield* MediaInput.inlineBytes(ADAPTER, request.audio);
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 = MediaProtocol.decodeJson(ADAPTER, NAME, ListenResponse);
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(`${NAME} returned no transcript`);
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({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
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 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">;
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?: ElevenLabsSpeechString<"auto" | "on" | "off">;
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 { ProviderID, mergeJsonRecords } from "../schema/index.js";
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 ADAPTER = "elevenlabs-speech";
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 = MediaProtocol.decodeFrame(ADAPTER, NAME, TimestampedAudio);
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* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} has no default output format for "${request.format}"; pass providerOptions.outputFormat`);
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* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} streams mp3, pcm, opus, ulaw, and alaw but not "${format}"; use generate for WAV`);
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(`${NAME} requires a voice id; pass it as \`voice\``);
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(ADAPTER, state, {
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({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: ({ request }) => path(request) }, input);
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" | (string & {});
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 { ProviderID, mergeJsonRecords } from "../schema/index.js";
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 ADAPTER = "fal-images";
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(`${NAME} accepts either size or aspectRatio, not both`));
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(id, "size", "sizes by aspectRatio"));
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(id, "aspectRatio", "sizes by size (image_size)"));
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(id, "images", "takes one image_url; use an /edit endpoint for several images"));
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, NAME));
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, NAME),
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 = MediaProtocol.decodeJson(ADAPTER, NAME, QueueResult);
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(`${NAME} returned no images`);
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) => ({ type: "moderated", message: `${NAME} flagged image ${index} as NSFW` })),
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?: FalVideoString<"4s" | "6s" | "8s" | "5" | "10">;
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 { ProviderID, mergeJsonRecords } from "../schema/index.js";
5
+ import { mergeJsonRecords } from "../schema/index.js";
6
6
  import { VideoModel, VideoResponse } from "../video.js";
7
- import { ProviderShared, optionalNull } from "./shared.js";
7
+ import { optionalNull } from "./shared.js";
8
8
  import { FalQueue } from "./utils/fal-queue.js";
9
- const ADAPTER = "fal-video";
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* 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);
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 = MediaProtocol.decodeJson(ADAPTER, NAME, QueueResult);
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,