@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
@@ -6,7 +6,7 @@ import { OpenResponses } from "./open-responses.js";
6
6
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
7
7
  import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js";
8
8
  import { ToolSchemaProjection } from "./utils/tool-schema.js";
9
- import { MetaImage } from "./utils/meta-image.js";
9
+ import { detectMediaType } from "../utils/media-type.js";
10
10
  const ADAPTER = "meta-responses";
11
11
  const NAME = "Meta Responses";
12
12
  export const WebSearch = Schema.Struct({
@@ -101,7 +101,10 @@ const HOSTED_TOOLS = {
101
101
  if (!item.result)
102
102
  return yield* ProviderShared.eventError(ADAPTER, "Meta returned an image without data", ProviderShared.encodeJson(raw));
103
103
  const data = yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Meta returned invalid image base64", ProviderShared.encodeJson(raw), cause)));
104
- const mime = MetaImage.mediaType(data, item.output_format);
104
+ // Responses image items can omit output_format, including when PNG/JPEG was requested.
105
+ const mime = item.output_format === undefined
106
+ ? (detectMediaType(data) ?? "application/octet-stream")
107
+ : `image/${item.output_format}`;
105
108
  return {
106
109
  type: "content",
107
110
  value: [{ type: "file", uri: `data:${mime};base64,${item.result}`, mime }],
@@ -10,6 +10,7 @@ import { classifyProviderFailure } from "../provider-error.js";
10
10
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
11
11
  import { Lifecycle } from "./utils/lifecycle.js";
12
12
  import { MistralToolID } from "./utils/mistral-tool-id.js";
13
+ import { ToolSchemaProjection } from "./utils/tool-schema.js";
13
14
  import { ToolStream } from "./utils/tool-stream.js";
14
15
  const ADAPTER = "mistral-chat";
15
16
  const DONE = "[DONE]";
@@ -272,9 +273,9 @@ const lowerMessages = Effect.fn("MistralChat.lowerMessages")(function* (request)
272
273
  }
273
274
  return messages;
274
275
  });
275
- const lowerTool = (tool) => ({
276
+ const lowerTool = (tool, inputSchema) => ({
276
277
  type: "function",
277
- function: { name: tool.name, description: tool.description, parameters: tool.inputSchema, strict: false },
278
+ function: { name: tool.name, description: tool.description, parameters: inputSchema, strict: false },
278
279
  });
279
280
  export const fromRequest = Effect.fn("MistralChat.fromRequest")(function* (request) {
280
281
  const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(MistralOptions))(request.providerOptions ?? {});
@@ -295,7 +296,9 @@ export const fromRequest = Effect.fn("MistralChat.fromRequest")(function* (reque
295
296
  return {
296
297
  model: request.model.id,
297
298
  messages: yield* lowerMessages(flattened.request),
298
- tools: flattened.tools.length > 0 ? flattened.tools.map(lowerTool) : undefined,
299
+ tools: flattened.tools.length > 0
300
+ ? flattened.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model)))
301
+ : undefined,
299
302
  tool_choice: toolChoice,
300
303
  stream: true,
301
304
  max_tokens: request.generation?.maxTokens,
@@ -2,16 +2,15 @@ import { ImageModel, type ImageRequestFor } from "../image.js";
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 { type MediaUsage } from "../schema/index.js";
5
+ import { type MediaUsage, type OpenString } from "../schema/index.js";
6
6
  export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
7
7
  export declare const PATH = "/images/generations";
8
8
  export declare const EDIT_PATH = "/images/edits";
9
- export type OpenAIImageString<Known extends string> = Known | (string & {});
10
9
  /** Provider-native options. Common fields (`n`, `size`, `format`, `images`, `mask`) live on the request. */
11
10
  export type OpenAIImageOptions = {
12
- readonly quality?: OpenAIImageString<"auto" | "low" | "medium" | "high" | "standard" | "hd">;
13
- readonly background?: OpenAIImageString<"auto" | "opaque" | "transparent">;
14
- readonly moderation?: OpenAIImageString<"auto" | "low">;
11
+ readonly quality?: OpenString<"auto" | "low" | "medium" | "high" | "standard" | "hd">;
12
+ readonly background?: OpenString<"auto" | "opaque" | "transparent">;
13
+ readonly moderation?: OpenString<"auto" | "low">;
15
14
  readonly outputCompression?: number;
16
15
  /** Previews sent before the final image when streaming (default 2); ignored by `Image.generate`. */
17
16
  readonly partialImages?: number;
@@ -4,12 +4,10 @@ import { Media } from "../media.js";
4
4
  import { Framing } from "../route/framing.js";
5
5
  import { MediaProtocol } from "../route/media-protocol.js";
6
6
  import { MediaRoute } from "../route/media.js";
7
- import { ProviderID, mergeJsonRecords } from "../schema/index.js";
7
+ import { mergeJsonRecords } from "../schema/index.js";
8
8
  import { ProviderShared } from "./shared.js";
9
9
  import { MediaInput } from "./utils/media-input.js";
10
- const ADAPTER = "openai-images";
11
- const NAME = "OpenAI Images";
12
- const PROVIDER = ProviderID.make("openai");
10
+ const route = MediaProtocol.identity({ id: "openai-images", name: "OpenAI Images", provider: "openai" });
13
11
  export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
14
12
  export const PATH = "/images/generations";
15
13
  export const EDIT_PATH = "/images/edits";
@@ -49,7 +47,7 @@ const StreamEvent = Schema.Union([
49
47
  usage: Schema.optional(Usage),
50
48
  }),
51
49
  ]);
52
- const decodeEvent = MediaProtocol.decodeFrame(ADAPTER, NAME, StreamEvent);
50
+ const decodeEvent = route.decodeFrame(StreamEvent);
53
51
  const decodeDocument = Schema.decodeUnknownEffect(Schema.fromJsonString(OpenAIImageResponse));
54
52
  // ---------------------------------------------------------------------------
55
53
  // 5. Request body construction
@@ -66,24 +64,14 @@ const streamOptions = (request) => {
66
64
  if (request.mode !== "stream")
67
65
  return Effect.succeed(undefined);
68
66
  if (request.model.id.startsWith("dall-e"))
69
- return Effect.fail(ProviderShared.unsupportedOperation({
70
- operation: "media.stream",
71
- provider: PROVIDER,
72
- route: ADAPTER,
73
- message: `${request.model.id} does not stream; use Image.generate or a GPT image model`,
74
- }));
67
+ return Effect.fail(route.unsupported("media.stream", `${request.model.id} does not stream; use Image.generate or a GPT image model`));
75
68
  if (request.n !== undefined && request.n > 1)
76
- return Effect.fail(ProviderShared.unsupportedOperation({
77
- operation: "media.n",
78
- provider: PROVIDER,
79
- route: ADAPTER,
80
- message: `${NAME} streams one image; use Image.generate for n=${request.n}`,
81
- }));
69
+ return Effect.fail(route.unsupported("media.n", `${route.name} streams one image; use Image.generate for n=${request.n}`));
82
70
  return Effect.succeed({ stream: true, partial_images: request.providerOptions?.partialImages ?? 2 });
83
71
  };
84
72
  const isEdit = (request) => (request.images?.length ?? 0) > 0;
85
73
  const isInline = (asset) => asset.source.type === "bytes" || asset.source.type === "base64";
86
- const reference = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((item) => (item.type === "ref" ? { file_id: item.value } : { image_url: item.value })));
74
+ const reference = (asset) => ProviderShared.mediaReference(asset, route.provider, route.name).pipe(Effect.map((item) => (item.type === "ref" ? { file_id: item.value } : { image_url: item.value })));
87
75
  const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
88
76
  const images = request.images ?? [];
89
77
  const mask = request.mask;
@@ -93,17 +81,11 @@ const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
93
81
  // Owned bytes go through multipart edits; remote URLs and file IDs use the JSON edits body instead.
94
82
  if (images.length > 0 && images.every(isInline) && (mask === undefined || isInline(mask))) {
95
83
  const form = new FormData();
96
- form.append("model", request.model.id);
97
- form.append("prompt", request.prompt);
98
- Object.entries(fields ?? {}).forEach(([key, value]) => {
99
- if (RESERVED_FORM_FIELDS.has(key))
100
- return;
101
- form.append(key, typeof value === "string" ? value : ProviderShared.encodeJson(value));
102
- });
103
- const uploads = yield* Effect.forEach(images, (image) => MediaInput.inlineBytes(ADAPTER, image));
84
+ MediaInput.appendFields(form, { model: request.model.id, prompt: request.prompt }, { overlay: fields, reserved: RESERVED_FORM_FIELDS });
85
+ const uploads = yield* Effect.forEach(images, (image) => MediaInput.inlineBytes(route.id, image));
104
86
  uploads.forEach((data, index) => form.append("image[]", MediaInput.blob(data, images[index].mediaType), `image-${index}`));
105
87
  if (mask !== undefined)
106
- form.append("mask", MediaInput.blob(yield* MediaInput.inlineBytes(ADAPTER, mask), mask.mediaType), "mask");
88
+ form.append("mask", MediaInput.blob(yield* MediaInput.inlineBytes(route.id, mask), mask.mediaType), "mask");
107
89
  return MediaProtocol.multipart(form);
108
90
  }
109
91
  const references = yield* Effect.forEach(images, reference);
@@ -133,45 +115,40 @@ const usage = (value) => value === undefined
133
115
  total: value.total_tokens,
134
116
  details: { openai: value },
135
117
  };
136
- const eventImage = (frame, label, data, format) => MediaInput.decodedAsset((message, cause) => MediaProtocol.frameError(ADAPTER, message, frame, cause), label, data, `image/${format}`, { info: { format } });
118
+ const eventImage = (frame, label, data, format) => MediaInput.decodedAsset((message, cause) => route.frameError(message, frame, cause), label, data, `image/${format}`, {
119
+ info: { format },
120
+ });
137
121
  const onEvent = Effect.fn("OpenAIImages.onEvent")(function* (state, frame) {
138
122
  const event = yield* decodeEvent(frame);
139
123
  const format = event.output_format;
140
124
  if ("partial_image_index" in event) {
141
- const image = yield* eventImage(frame, `${NAME} partial image`, event.b64_json, format);
125
+ const image = yield* eventImage(frame, `${route.name} partial image`, event.b64_json, format);
142
126
  return [state, [ImagePartialEvent.make({ index: event.partial_image_index, image })]];
143
127
  }
144
- const image = yield* eventImage(frame, `${NAME} result ${state.completed}`, event.b64_json, format);
128
+ const image = yield* eventImage(frame, `${route.name} result ${state.completed}`, event.b64_json, format);
145
129
  return [
146
130
  { ...state, completed: state.completed + 1, format, usage: usage(event.usage) },
147
131
  [ImageOutputEvent.make({ index: state.completed, image })],
148
132
  ];
149
133
  });
150
134
  const onDocument = Effect.fn("OpenAIImages.onDocument")(function* (frame) {
151
- const invalid = (message, cause) => MediaProtocol.frameError(ADAPTER, message, frame.document, cause);
152
- const decoded = yield* decodeDocument(frame.document).pipe(Effect.mapError((cause) => invalid(`${NAME} returned an invalid response`, cause)));
135
+ const invalid = (message, cause) => route.frameError(message, frame.document, cause);
136
+ const decoded = yield* decodeDocument(frame.document).pipe(Effect.mapError((cause) => invalid(`${route.name} returned an invalid response`, cause)));
153
137
  const format = decoded.output_format ?? frame.requested ?? "png";
154
138
  const mediaType = `image/${format}`;
155
- const images = yield* Effect.forEach(decoded.data, (item, index) => {
156
- const providerMetadata = item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } };
157
- if (item.b64_json)
158
- return MediaInput.decodedAsset(invalid, `${NAME} result ${index}`, item.b64_json, mediaType, {
159
- info: { format },
160
- providerMetadata,
161
- });
162
- if (item.url)
163
- return Effect.succeed(Media.url(item.url, { mediaType, info: { format }, providerMetadata }));
164
- return Effect.fail(invalid(`${NAME} result ${index} has neither image data nor a URL`));
165
- });
139
+ const images = yield* Effect.forEach(decoded.data, (item, index) => MediaInput.imageOutput(invalid, `${route.name} result ${index}`, item, mediaType, {
140
+ info: { format },
141
+ providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
142
+ }));
166
143
  if (images.length === 0)
167
- return yield* invalid(`${NAME} returned no images`);
144
+ return yield* invalid(`${route.name} returned no images`);
168
145
  const state = { completed: images.length, format, usage: usage(decoded.usage) };
169
146
  return [state, images.map((image, index) => ImageOutputEvent.make({ index, image }))];
170
147
  });
171
148
  const step = (state, frame) => (typeof frame === "string" ? onEvent(state, frame) : onDocument(frame));
172
149
  const finish = (state) => {
173
150
  if (state.completed === 0)
174
- return Effect.fail(MediaProtocol.incomplete(ADAPTER));
151
+ return Effect.fail(route.incomplete());
175
152
  return Effect.succeed([
176
153
  ImageFinishEvent.make({ usage: state.usage, providerMetadata: { openai: { outputFormat: state.format } } }),
177
154
  ]);
@@ -179,9 +156,7 @@ const finish = (state) => {
179
156
  // ---------------------------------------------------------------------------
180
157
  // 7. Protocol and route
181
158
  // ---------------------------------------------------------------------------
182
- export const protocol = MediaProtocol.stream({
183
- id: ADAPTER,
184
- name: NAME,
159
+ export const protocol = MediaProtocol.stream(route, {
185
160
  unsupported: ["aspectRatio", "seed"],
186
161
  body: { from: fromRequest },
187
162
  frames: (bytes, context) => context.request.mode === "stream"
@@ -193,13 +168,7 @@ export const protocol = MediaProtocol.stream({
193
168
  step,
194
169
  finish,
195
170
  });
196
- export const model = (input) => ImageModel.fromRoute({
197
- id: ADAPTER,
198
- provider: PROVIDER,
199
- protocol,
200
- baseURL: DEFAULT_BASE_URL,
201
- path: ({ request }) => (isEdit(request) ? EDIT_PATH : PATH),
202
- }, input);
171
+ export const model = (input) => ImageModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: ({ request }) => (isEdit(request) ? EDIT_PATH : PATH) }, input);
203
172
  export const OpenAIImages = {
204
173
  protocol,
205
174
  model,
@@ -10,7 +10,6 @@ import { resolveEffortUpdates } from "../effort-updates.js";
10
10
  import { OpenResponses } from "./open-responses.js";
11
11
  import { OpenResponsesOptions } from "./utils/open-responses-options.js";
12
12
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
13
- import { OpenAIImage } from "./utils/openai-image.js";
14
13
  import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js";
15
14
  import { ToolSchemaProjection } from "./utils/tool-schema.js";
16
15
  import { OpenResponsesChannel } from "./open-responses-channel.js";
@@ -35,7 +34,14 @@ const OpenAIResponsesImageGenerationTool = Schema.Struct({
35
34
  output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
36
35
  partial_images: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
37
36
  quality: Schema.optional(Schema.Literals(["auto", "low", "medium", "high"])),
38
- size: Schema.optional(OpenAIImage.Size),
37
+ size: Schema.optional(Schema.String.check(Schema.makeFilter((value) => {
38
+ if (value === "auto")
39
+ return undefined;
40
+ const match = /^(\d+)x(\d+)$/.exec(value);
41
+ if (!match)
42
+ return "image size must be `auto` or `{width}x{height}`";
43
+ return Number(match[1]) > 0 && Number(match[2]) > 0 ? undefined : "image dimensions must be positive integers";
44
+ }))),
39
45
  });
40
46
  const OpenAIResponsesHostedToolItem = Schema.Union([
41
47
  Schema.StructWithRest(Schema.Struct({
@@ -2,12 +2,10 @@ 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 { SpeechStream } from "./utils/speech-stream.js";
8
- const ADAPTER = "openai-speech";
9
- const NAME = "OpenAI Speech";
10
- const PROVIDER = ProviderID.make("openai");
8
+ const route = MediaProtocol.identity({ id: "openai-speech", name: "OpenAI Speech", provider: "openai" });
11
9
  export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
12
10
  export const PATH = "/audio/speech";
13
11
  /** `pcm` is raw 24 kHz, 16-bit signed little-endian mono samples without a header. */
@@ -26,7 +24,7 @@ const SpeechStreamEvent = Schema.Union([
26
24
  })),
27
25
  }),
28
26
  ]);
29
- const decodeEvent = MediaProtocol.decodeFrame(ADAPTER, NAME, SpeechStreamEvent);
27
+ const decodeEvent = route.decodeFrame(SpeechStreamEvent);
30
28
  // ---------------------------------------------------------------------------
31
29
  // 5. Request body construction
32
30
  // ---------------------------------------------------------------------------
@@ -71,9 +69,9 @@ const onEvent = Effect.fn("OpenAISpeech.onEvent")(function* (state, frame) {
71
69
  });
72
70
  const finish = (state, context) => {
73
71
  if (isSse(context.body) && !state.done)
74
- return Effect.fail(MediaProtocol.incomplete(ADAPTER));
72
+ return Effect.fail(route.incomplete());
75
73
  const format = context.request.format ?? "mp3";
76
- return SpeechStream.finish(ADAPTER, state, {
74
+ return SpeechStream.finish(route, state, {
77
75
  ...(format === "pcm" ? SpeechStream.pcm("pcm_s16le", PCM_SAMPLE_RATE) : SpeechStream.container(format)),
78
76
  usage: state.usage,
79
77
  });
@@ -81,9 +79,7 @@ const finish = (state, context) => {
81
79
  // ---------------------------------------------------------------------------
82
80
  // 7. Protocol and route
83
81
  // ---------------------------------------------------------------------------
84
- export const protocol = MediaProtocol.stream({
85
- id: ADAPTER,
86
- name: NAME,
82
+ export const protocol = MediaProtocol.stream(route, {
87
83
  unsupported: ["language", "timestamps"],
88
84
  body: { from: fromRequest },
89
85
  frames: (bytes, context) => (isSse(context.body) ? Framing.sse.frame(bytes) : bytes),
@@ -91,7 +87,7 @@ export const protocol = MediaProtocol.stream({
91
87
  step: SpeechStream.step(onEvent),
92
88
  finish,
93
89
  });
94
- export const model = (input) => SpeechModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
90
+ export const model = (input) => SpeechModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
95
91
  export const OpenAISpeech = {
96
92
  protocol,
97
93
  model,
@@ -2,14 +2,12 @@ import { Effect, Schema, Stream } 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 { TranscriptionFinishEvent, TranscriptionModel, TranscriptionSegmentEvent, TranscriptionTextDeltaEvent, } from "../transcription.js";
7
7
  import { mediaTypeExtension } from "../utils/media-type.js";
8
8
  import { ProviderShared } from "./shared.js";
9
9
  import { MediaInput } from "./utils/media-input.js";
10
- const ADAPTER = "openai-transcription";
11
- const NAME = "OpenAI Transcription";
12
- const PROVIDER = ProviderID.make("openai");
10
+ const route = MediaProtocol.identity({ id: "openai-transcription", name: "OpenAI Transcription", provider: "openai" });
13
11
  export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
14
12
  export const PATH = "/audio/transcriptions";
15
13
  // ---------------------------------------------------------------------------
@@ -45,8 +43,8 @@ const Event = Schema.Union([
45
43
  Schema.Struct({ type: Schema.Literal("transcript.text.done"), ...transcriptFields }),
46
44
  ]);
47
45
  const Transcript = Schema.Struct(transcriptFields);
48
- const decodeEvent = MediaProtocol.decodeFrame(ADAPTER, NAME, Event);
49
- const decodeTranscript = MediaProtocol.decodeFrame(ADAPTER, NAME, Transcript);
46
+ const decodeEvent = route.decodeFrame(Event);
47
+ const decodeTranscript = route.decodeFrame(Transcript);
50
48
  const TRANSCRIBE = { stream: true, timestamps: [], diarize: false, languageField: "language" };
51
49
  const capabilities = (model) => {
52
50
  if (model.startsWith("whisper"))
@@ -58,20 +56,19 @@ const capabilities = (model) => {
58
56
  return { ...TRANSCRIBE, languageField: "languages" };
59
57
  return TRANSCRIBE;
60
58
  };
61
- const unsupported = (operation, message) => Effect.fail(ProviderShared.unsupportedOperation({ operation, provider: PROVIDER, route: ADAPTER, message }));
62
59
  const validate = (request, model) => {
63
60
  const id = request.model.id;
64
61
  if (request.mode === "stream" && !model.stream)
65
- return unsupported("media.stream", `${id} does not stream; use Transcription.generate`);
62
+ return Effect.fail(route.unsupported("media.stream", `${id} does not stream; use Transcription.generate`));
66
63
  if (request.diarize === true && !model.diarize)
67
- return unsupported("media.diarize", `${id} does not diarize; use gpt-4o-transcribe-diarize`);
64
+ return Effect.fail(route.unsupported("media.diarize", `${id} does not diarize; use gpt-4o-transcribe-diarize`));
68
65
  if (request.prompt !== undefined && model.diarize)
69
- return unsupported("media.prompt", `${id} does not accept a prompt`);
66
+ return Effect.fail(route.unsupported("media.prompt", `${id} does not accept a prompt`));
70
67
  if (request.timestamps === undefined ||
71
68
  request.timestamps === "none" ||
72
69
  model.timestamps.includes(request.timestamps))
73
70
  return Effect.void;
74
- return unsupported("media.timestamps", `${id} does not return ${request.timestamps} timestamps`);
71
+ return Effect.fail(route.unsupported("media.timestamps", `${id} does not return ${request.timestamps} timestamps`));
75
72
  };
76
73
  const RESERVED_FORM_FIELDS = new Set([
77
74
  "file",
@@ -82,26 +79,22 @@ const RESERVED_FORM_FIELDS = new Set([
82
79
  "timestamp_granularities",
83
80
  "stream",
84
81
  ]);
85
- const appendField = (form, key, value) => {
86
- if (Array.isArray(value))
87
- return value.forEach((item) => form.append(`${key}[]`, String(item)));
88
- form.append(key, typeof value === "object" && value !== null ? ProviderShared.encodeJson(value) : String(value));
89
- };
90
82
  const fromRequest = Effect.fn("OpenAITranscription.fromRequest")(function* (request) {
91
83
  const model = capabilities(request.model.id);
92
84
  yield* validate(request, model);
93
85
  // The API detects the audio format from the upload's filename extension.
94
86
  const extension = mediaTypeExtension(request.audio.mediaType);
95
87
  if (extension === undefined)
96
- return yield* ProviderShared.invalidRequest(`${NAME} cannot name a ${request.audio.mediaType} upload; send mp3, mp4, m4a, wav, webm, ogg, or flac audio`);
97
- const audio = yield* MediaInput.inlineBytes(ADAPTER, request.audio);
88
+ return yield* ProviderShared.invalidRequest(`${route.name} cannot name a ${request.audio.mediaType} upload; send mp3, mp4, m4a, wav, webm, ogg, or flac audio`);
89
+ const audio = yield* MediaInput.inlineBytes(route.id, request.audio);
98
90
  const responseFormat = model.diarize
99
91
  ? "diarized_json"
100
92
  : request.timestamps === undefined || request.timestamps === "none"
101
93
  ? undefined
102
94
  : "verbose_json";
103
- const native = Object.entries(mergeJsonRecords(request.providerOptions, request.http?.body) ?? {}).filter(([key]) => !RESERVED_FORM_FIELDS.has(key));
104
- const fields = mergeJsonRecords({
95
+ const form = new FormData();
96
+ form.append("file", MediaInput.blob(audio, request.audio.mediaType), `audio.${extension}`);
97
+ MediaInput.appendFields(form, {
105
98
  model: request.model.id,
106
99
  language: model.languageField === "language" ? request.language : undefined,
107
100
  languages: model.languageField === "languages" && request.language !== undefined ? [request.language] : undefined,
@@ -111,10 +104,11 @@ const fromRequest = Effect.fn("OpenAITranscription.fromRequest")(function* (requ
111
104
  // Diarizing audio longer than 30 seconds requires a chunking strategy.
112
105
  chunking_strategy: model.diarize ? "auto" : undefined,
113
106
  stream: request.mode === "stream" ? true : undefined,
114
- }, Object.fromEntries(native));
115
- const form = new FormData();
116
- form.append("file", MediaInput.blob(audio, request.audio.mediaType), `audio.${extension}`);
117
- Object.entries(fields ?? {}).forEach(([key, value]) => appendField(form, key, value));
107
+ }, {
108
+ overlay: mergeJsonRecords(request.providerOptions, request.http?.body),
109
+ reserved: RESERVED_FORM_FIELDS,
110
+ repeatArrays: true,
111
+ });
118
112
  return MediaProtocol.multipart(form);
119
113
  });
120
114
  // ---------------------------------------------------------------------------
@@ -155,7 +149,7 @@ const usage = (value) => {
155
149
  const finish = (state) => {
156
150
  const transcript = state.transcript;
157
151
  if (transcript === undefined)
158
- return Effect.fail(MediaProtocol.incomplete(ADAPTER));
152
+ return Effect.fail(route.incomplete());
159
153
  const segments = transcript.segments?.map(segment) ?? state.segments;
160
154
  return Effect.succeed([
161
155
  TranscriptionFinishEvent.make({
@@ -171,9 +165,7 @@ const finish = (state) => {
171
165
  // ---------------------------------------------------------------------------
172
166
  // 7. Protocol and route
173
167
  // ---------------------------------------------------------------------------
174
- export const protocol = MediaProtocol.stream({
175
- id: ADAPTER,
176
- name: NAME,
168
+ export const protocol = MediaProtocol.stream(route, {
177
169
  unsupported: ["speakers"],
178
170
  body: { from: fromRequest },
179
171
  frames: (bytes, context) => context.request.mode === "stream"
@@ -183,7 +175,7 @@ export const protocol = MediaProtocol.stream({
183
175
  step,
184
176
  finish,
185
177
  });
186
- export const model = (input) => TranscriptionModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
178
+ export const model = (input) => TranscriptionModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
187
179
  export const OpenAITranscription = {
188
180
  protocol,
189
181
  model,
@@ -3,11 +3,9 @@ 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
- const ADAPTER = "replicate-images";
9
- const NAME = "Replicate";
10
- const PROVIDER = ProviderID.make("replicate");
8
+ const route = MediaProtocol.identity({ id: "replicate-images", name: "Replicate", provider: "replicate" });
11
9
  export const DEFAULT_BASE_URL = "https://api.replicate.com";
12
10
  const OUTPUT_RETENTION = Duration.hours(1);
13
11
  const MAX_DATA_URL_BYTES = 256 * 1024;
@@ -50,8 +48,8 @@ const inlineSize = (source) => {
50
48
  };
51
49
  const fileInput = (asset) => {
52
50
  if (inlineSize(asset.source) > MAX_DATA_URL_BYTES)
53
- return Effect.fail(ProviderShared.invalidRequest(`${NAME} data URL inputs are limited to 256 KB; pass a larger file by https URL`));
54
- return ProviderShared.mediaReference(asset, undefined, NAME).pipe(Effect.map((reference) => reference.value));
51
+ return Effect.fail(ProviderShared.invalidRequest(`${route.name} data URL inputs are limited to 256 KB; pass a larger file by https URL`));
52
+ return ProviderShared.mediaReference(asset, undefined, route.name).pipe(Effect.map((reference) => reference.value));
55
53
  };
56
54
  const inputValue = (value) => {
57
55
  if (value instanceof Media.Asset)
@@ -69,7 +67,7 @@ const fromRequest = Effect.fn("ReplicateImages.fromRequest")(function* (request)
69
67
  // ---------------------------------------------------------------------------
70
68
  // 6. Response decoding
71
69
  // ---------------------------------------------------------------------------
72
- const decodePrediction = MediaProtocol.decodeJson(ADAPTER, NAME, Prediction);
70
+ const decodePrediction = route.decodeJson(Prediction);
73
71
  const decodeStart = Effect.fn("ReplicateImages.decodeStart")(function* (response) {
74
72
  const output = yield* decodePrediction(response);
75
73
  const prediction = output.value;
@@ -88,16 +86,16 @@ const decodeResult = Effect.fn("ReplicateImages.decodeResult")(function* (respon
88
86
  const prediction = output.value;
89
87
  const status = yield* MediaProtocol.status(STATUS, prediction.status, output);
90
88
  if (status === "failed" || status === "cancelled" || status === "expired")
91
- return yield* output.ended(status, `${NAME} prediction ${context.token.id} ${prediction.status}${typeof prediction.error === "string" ? `: ${prediction.error}` : ""}`);
89
+ return yield* output.ended(status, `${route.name} prediction ${context.token.id} ${prediction.status}${typeof prediction.error === "string" ? `: ${prediction.error}` : ""}`);
92
90
  if (status !== "completed")
93
- return yield* output.invalid(`${NAME} prediction ${context.token.id} has not finished`);
91
+ return yield* output.invalid(`${route.name} prediction ${context.token.id} has not finished`);
94
92
  if (prediction.data_removed === true)
95
- return yield* output.ended("expired", `${NAME} removed the output of prediction ${context.token.id}`);
93
+ return yield* output.ended("expired", `${route.name} removed the output of prediction ${context.token.id}`);
96
94
  if (!isOutput(prediction.output))
97
- return yield* output.invalid(`${NAME} prediction ${context.token.id} returned output that is not image URLs`);
95
+ return yield* output.invalid(`${route.name} prediction ${context.token.id} returned output that is not image URLs`);
98
96
  const urls = typeof prediction.output === "string" ? [prediction.output] : prediction.output;
99
97
  if (urls.length === 0)
100
- return yield* output.invalid(`${NAME} prediction ${context.token.id} returned no images`);
98
+ return yield* output.invalid(`${route.name} prediction ${context.token.id} returned no images`);
101
99
  const predictTime = prediction.metrics?.predict_time ?? undefined;
102
100
  const completedAt = prediction.completed_at ?? undefined;
103
101
  const expiresAt = completedAt === undefined ? undefined : Date.parse(completedAt) + Duration.toMillis(OUTPUT_RETENTION);
@@ -110,9 +108,7 @@ const decodeResult = Effect.fn("ReplicateImages.decodeResult")(function* (respon
110
108
  // ---------------------------------------------------------------------------
111
109
  // 7. Protocol and route
112
110
  // ---------------------------------------------------------------------------
113
- export const protocol = MediaProtocol.queued({
114
- id: ADAPTER,
115
- name: NAME,
111
+ export const protocol = MediaProtocol.queued(route, {
116
112
  token: Token,
117
113
  unsupported: ["images", "mask", "n", "size", "aspectRatio", "seed", "format"],
118
114
  start: { body: { from: fromRequest }, decode: decodeStart },
@@ -121,8 +117,6 @@ export const protocol = MediaProtocol.queued({
121
117
  cancel: { method: "POST", path: (token) => token.cancelURL },
122
118
  });
123
119
  export const model = (input) => ImageModel.fromRoute({
124
- id: ADAPTER,
125
- provider: PROVIDER,
126
120
  protocol,
127
121
  baseURL: DEFAULT_BASE_URL,
128
122
  path: ({ request }) => isOfficial(request.model.id) ? `/v1/models/${request.model.id}/predictions` : "/v1/predictions",
@@ -1,6 +1,7 @@
1
1
  import { Schema } from "effect";
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
  import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
5
6
  export declare const DEFAULT_BASE_URL = "https://api.dev.runwayml.com/v1";
6
7
  /** Every Runway request must pin the API version. */
@@ -9,7 +10,6 @@ export declare const TEXT_TO_VIDEO_PATH = "/text_to_video";
9
10
  export declare const IMAGE_TO_VIDEO_PATH = "/image_to_video";
10
11
  export declare const VIDEO_TO_VIDEO_PATH = "/video_to_video";
11
12
  export declare const TASKS_PATH = "/tasks";
12
- export type RunwayVideoString<Known extends string> = Known | (string & {});
13
13
  /**
14
14
  * Provider-native options. Common fields lower to Runway's names: `aspectRatio` → `ratio` (Runway expects pixel
15
15
  * ratios such as `1280:720` for most models), `durationSeconds` → `duration`, `audio`, `negativePrompt`,
@@ -17,9 +17,9 @@ export type RunwayVideoString<Known extends string> = Known | (string & {});
17
17
  */
18
18
  export type RunwayVideoOptions = {
19
19
  readonly contentModeration?: {
20
- readonly publicFigureThreshold?: RunwayVideoString<"auto" | "low">;
20
+ readonly publicFigureThreshold?: OpenString<"auto" | "low">;
21
21
  };
22
- readonly outputFormat?: RunwayVideoString<"mp4" | "prores" | "png_sequence">;
22
+ readonly outputFormat?: OpenString<"mp4" | "prores" | "png_sequence">;
23
23
  } & Record<string, unknown>;
24
24
  export type Request = VideoRequestFor<RunwayVideoOptions>;
25
25
  export declare const Token: Schema.Struct<{
@@ -2,12 +2,10 @@ import { Duration, 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
7
  import { ProviderShared, optionalArray, optionalNull } from "./shared.js";
8
- const ADAPTER = "runway-video";
9
- const NAME = "Runway";
10
- const PROVIDER = ProviderID.make("runway");
8
+ const route = MediaProtocol.identity({ id: "runway-video", name: "Runway", provider: "runway" });
11
9
  export const DEFAULT_BASE_URL = "https://api.dev.runwayml.com/v1";
12
10
  /** Every Runway request must pin the API version. */
13
11
  export const API_VERSION = "2024-11-06";
@@ -44,7 +42,7 @@ const STATUS = {
44
42
  // 5. Request body construction
45
43
  // ---------------------------------------------------------------------------
46
44
  // Runway accepts HTTPS URLs, `runway://` upload URIs, and data URIs, all as one string.
47
- const mediaUri = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((reference) => reference.value));
45
+ const mediaUri = (asset) => ProviderShared.mediaReference(asset, route.provider, route.name).pipe(Effect.map((reference) => reference.value));
48
46
  const fromRequest = Effect.fn("RunwayVideo.fromRequest")(function* (request) {
49
47
  const first = request.frames?.first === undefined ? undefined : yield* mediaUri(request.frames.first);
50
48
  const last = request.frames?.last === undefined ? undefined : yield* mediaUri(request.frames.last);
@@ -71,11 +69,11 @@ const fromRequest = Effect.fn("RunwayVideo.fromRequest")(function* (request) {
71
69
  // ---------------------------------------------------------------------------
72
70
  // 6. Response decoding
73
71
  // ---------------------------------------------------------------------------
74
- const decodeStart = MediaProtocol.decodeStarted(ADAPTER, NAME, StartResponse, (value) => ({
72
+ const decodeStart = route.decodeStarted(StartResponse, (value) => ({
75
73
  token: { taskID: value.id },
76
74
  snapshot: { id: value.id, status: "queued" },
77
75
  }));
78
- const decodeTask = MediaProtocol.decodeJson(ADAPTER, NAME, Task);
76
+ const decodeTask = route.decodeJson(Task);
79
77
  const decodeStatus = Effect.fn("RunwayVideo.decodeStatus")(function* (response, context) {
80
78
  const output = yield* decodeTask(response);
81
79
  const status = yield* MediaProtocol.status(STATUS, output.value.status, output);
@@ -87,19 +85,19 @@ const decodeResult = Effect.fn("RunwayVideo.decodeResult")(function* (response,
87
85
  const status = yield* MediaProtocol.status(STATUS, task.status, output);
88
86
  if (status === "failed") {
89
87
  const code = task.failureCode ?? undefined;
90
- const message = `${NAME} task failed${code === undefined ? "" : ` (${code})`}${task.failure ? `: ${task.failure}` : ""}`;
88
+ const message = `${route.name} task failed${code === undefined ? "" : ` (${code})`}${task.failure ? `: ${task.failure}` : ""}`;
91
89
  // Runway failure codes are dotted paths; every moderation outcome carries a SAFETY segment.
92
90
  if (code !== undefined && /(^|\.)SAFETY(\.|$)/.test(code))
93
91
  return yield* output.contentPolicy(message);
94
92
  return yield* output.ended("failed", message);
95
93
  }
96
94
  if (status === "cancelled")
97
- return yield* output.ended("cancelled", `${NAME} task ${context.token.taskID} was cancelled`);
95
+ return yield* output.ended("cancelled", `${route.name} task ${context.token.taskID} was cancelled`);
98
96
  if (status !== "completed")
99
- return yield* output.invalid(`${NAME} task ${context.token.taskID} has not finished`);
97
+ return yield* output.invalid(`${route.name} task ${context.token.taskID} has not finished`);
100
98
  const urls = task.output ?? [];
101
99
  if (urls.length === 0)
102
- return yield* output.invalid(`${NAME} task succeeded without any output`);
100
+ return yield* output.invalid(`${route.name} task succeeded without any output`);
103
101
  return new VideoResponse({
104
102
  videos: yield* Effect.forEach(urls, (url) => MediaProtocol.expiringUrl(url, OUTPUT_RETENTION, { mediaType: "video/mp4" })),
105
103
  usage: task.cost === undefined ? undefined : { type: "credits", credits: task.cost.credits },
@@ -115,9 +113,7 @@ const decodeResult = Effect.fn("RunwayVideo.decodeResult")(function* (response,
115
113
  // 7. Protocol and route
116
114
  // ---------------------------------------------------------------------------
117
115
  const taskPath = (token) => `${TASKS_PATH}/${token.taskID}`;
118
- export const protocol = MediaProtocol.queued({
119
- id: ADAPTER,
120
- name: NAME,
116
+ export const protocol = MediaProtocol.queued(route, {
121
117
  token: Token,
122
118
  unsupported: ["n"],
123
119
  start: { body: { from: fromRequest }, decode: decodeStart },
@@ -133,8 +129,6 @@ const startPath = (request) => {
133
129
  return TEXT_TO_VIDEO_PATH;
134
130
  };
135
131
  export const model = (input) => VideoModel.fromRoute({
136
- id: ADAPTER,
137
- provider: PROVIDER,
138
132
  protocol,
139
133
  baseURL: DEFAULT_BASE_URL,
140
134
  headers: { "X-Runway-Version": API_VERSION },