@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
@@ -10,14 +10,14 @@ export type ProviderOptionsInput = OptionsInput;
10
10
  declare const Options: Schema.Struct<{
11
11
  readonly cachedContent: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.String>, never>>;
12
12
  readonly safetySettings: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.$Array<Schema.Struct<{
13
- readonly category: Schema.declare<(string & {}) | "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY", (string & {}) | "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">;
14
- readonly threshold: Schema.declare<(string & {}) | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF", (string & {}) | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">;
13
+ readonly category: Schema.declare<import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">, import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">>;
14
+ readonly threshold: Schema.declare<import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">, import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">>;
15
15
  }>>>, never>>;
16
- readonly serviceTier: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<(string & {}) | "standard" | "flex" | "priority", (string & {}) | "standard" | "flex" | "priority">>, never>>;
16
+ readonly serviceTier: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<import("../schema/options.js").OpenString<"standard" | "flex" | "priority">, import("../schema/options.js").OpenString<"standard" | "flex" | "priority">>>, never>>;
17
17
  readonly thinkingConfig: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.Struct<{
18
18
  readonly thinkingBudget: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.Number>, never>>;
19
19
  readonly includeThoughts: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.Boolean>, never>>;
20
- readonly thinkingLevel: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<(string & {}) | "minimal" | "low" | "medium" | "high", (string & {}) | "minimal" | "low" | "medium" | "high">>, never>>;
20
+ readonly thinkingLevel: Schema.optionalKey<Schema.middlewareDecoding<Schema.UndefinedOr<Schema.declare<import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">, import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">>>, never>>;
21
21
  }>>, never>>;
22
22
  }>;
23
23
  declare const GeminiBody: Schema.Struct<{
@@ -63,8 +63,8 @@ declare const GeminiBody: Schema.Struct<{
63
63
  }>>;
64
64
  labels: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
65
65
  safetySettings: Schema.optional<Schema.$Array<Schema.Struct<{
66
- readonly category: Schema.declare<(string & {}) | "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY", (string & {}) | "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">;
67
- readonly threshold: Schema.declare<(string & {}) | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF", (string & {}) | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">;
66
+ readonly category: Schema.declare<import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">, import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">>;
67
+ readonly threshold: Schema.declare<import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">, import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">>;
68
68
  }>>>;
69
69
  serviceTier: Schema.optional<Schema.String>;
70
70
  systemInstruction: Schema.optional<Schema.Struct<{
@@ -97,7 +97,7 @@ declare const GeminiBody: Schema.Struct<{
97
97
  readonly thinkingConfig: Schema.optional<Schema.Struct<{
98
98
  readonly thinkingBudget: Schema.optional<Schema.Number>;
99
99
  readonly includeThoughts: Schema.optional<Schema.Boolean>;
100
- readonly thinkingLevel: Schema.optional<Schema.declare<(string & {}) | "minimal" | "low" | "medium" | "high", (string & {}) | "minimal" | "low" | "medium" | "high">>;
100
+ readonly thinkingLevel: Schema.optional<Schema.declare<import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">, import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high">>>;
101
101
  }>>;
102
102
  }>>;
103
103
  }>;
@@ -164,8 +164,8 @@ export declare const protocol: Protocol<{
164
164
  } | undefined;
165
165
  readonly cachedContent?: string | undefined;
166
166
  readonly safetySettings?: readonly {
167
- readonly category: (string & {}) | "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY";
168
- readonly threshold: (string & {}) | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF";
167
+ readonly category: import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">;
168
+ readonly threshold: import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">;
169
169
  }[] | undefined;
170
170
  readonly labels?: {
171
171
  readonly [x: string]: string;
@@ -186,7 +186,7 @@ export declare const protocol: Protocol<{
186
186
  readonly thinkingConfig?: {
187
187
  readonly thinkingBudget?: number | undefined;
188
188
  readonly includeThoughts?: boolean | undefined;
189
- readonly thinkingLevel?: (string & {}) | "minimal" | "low" | "medium" | "high" | undefined;
189
+ readonly thinkingLevel?: import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high"> | undefined;
190
190
  } | undefined;
191
191
  readonly maxOutputTokens?: number | undefined;
192
192
  } | undefined;
@@ -278,8 +278,8 @@ export declare const route: Route<{
278
278
  } | undefined;
279
279
  readonly cachedContent?: string | undefined;
280
280
  readonly safetySettings?: readonly {
281
- readonly category: (string & {}) | "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY";
282
- readonly threshold: (string & {}) | "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF";
281
+ readonly category: import("../schema/options.js").OpenString<"HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY">;
282
+ readonly threshold: import("../schema/options.js").OpenString<"HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF">;
283
283
  }[] | undefined;
284
284
  readonly labels?: {
285
285
  readonly [x: string]: string;
@@ -300,7 +300,7 @@ export declare const route: Route<{
300
300
  readonly thinkingConfig?: {
301
301
  readonly thinkingBudget?: number | undefined;
302
302
  readonly includeThoughts?: boolean | undefined;
303
- readonly thinkingLevel?: (string & {}) | "minimal" | "low" | "medium" | "high" | undefined;
303
+ readonly thinkingLevel?: import("../schema/options.js").OpenString<"minimal" | "low" | "medium" | "high"> | undefined;
304
304
  } | undefined;
305
305
  readonly maxOutputTokens?: number | undefined;
306
306
  } | undefined;
@@ -1,12 +1,12 @@
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 declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
5
- export type GoogleImageString<Known extends string> = Known | (string & {});
6
6
  /** Provider-native options. Common fields (`aspectRatio`, `seed`, `images`) live on the request. */
7
7
  export type GoogleImageOptions = {
8
- readonly imageSize?: GoogleImageString<"1K" | "2K" | "4K">;
9
- readonly thinkingLevel?: GoogleImageString<"MINIMAL" | "LOW" | "MEDIUM" | "HIGH">;
8
+ readonly imageSize?: OpenString<"1K" | "2K" | "4K">;
9
+ readonly thinkingLevel?: OpenString<"MINIMAL" | "LOW" | "MEDIUM" | "HIGH">;
10
10
  readonly includeThoughts?: boolean;
11
11
  } & Record<string, unknown>;
12
12
  export type Request = ImageRequestFor<GoogleImageOptions>;
@@ -2,13 +2,11 @@ import { Effect, Schema } from "effect";
2
2
  import { ImageModel, ImageResponse } from "../image.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 { ProviderShared } from "./shared.js";
7
7
  import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
8
8
  import { MediaInput } from "./utils/media-input.js";
9
- const ADAPTER = "google-images";
10
- const NAME = "Google Images";
11
- const PROVIDER = ProviderID.make("google");
9
+ const route = MediaProtocol.identity({ id: "google-images", name: "Google Images", provider: "google" });
12
10
  export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
13
11
  // ---------------------------------------------------------------------------
14
12
  // 2. Response schema
@@ -63,13 +61,8 @@ const generationConfig = (request) => {
63
61
  };
64
62
  const fromRequest = Effect.fn("GoogleImages.fromRequest")(function* (request) {
65
63
  if (request.n !== undefined && request.n > 1)
66
- return yield* ProviderShared.unsupportedOperation({
67
- operation: "image.n",
68
- provider: PROVIDER,
69
- route: ADAPTER,
70
- message: `${NAME} generates one image per request; call it once per image instead of n=${request.n}`,
71
- });
72
- const parts = yield* Effect.forEach(request.images ?? [], (image) => GeminiGenerateContent.mediaPart(NAME, image));
64
+ return yield* route.unsupported("image.n", `${route.name} generates one image per request; call it once per image instead of n=${request.n}`);
65
+ const parts = yield* Effect.forEach(request.images ?? [], (image) => GeminiGenerateContent.mediaPart(route.name, image));
73
66
  return MediaProtocol.json(mergeJsonRecords({
74
67
  contents: [{ role: "user", parts: [{ text: request.prompt }, ...parts] }],
75
68
  generationConfig: generationConfig(request),
@@ -78,8 +71,9 @@ const fromRequest = Effect.fn("GoogleImages.fromRequest")(function* (request) {
78
71
  // ---------------------------------------------------------------------------
79
72
  // 6. Response decoding
80
73
  // ---------------------------------------------------------------------------
74
+ const decodeDocument = route.decodeJson(GoogleImageResponse);
81
75
  const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (response) {
82
- const output = yield* MediaProtocol.decodeJson(ADAPTER, NAME, GoogleImageResponse)(response);
76
+ const output = yield* decodeDocument(response);
83
77
  const decoded = output.value;
84
78
  const candidates = decoded.candidates ?? [];
85
79
  const candidateMetadata = candidates.map((candidate, candidateIndex) => ({
@@ -110,7 +104,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
110
104
  thoughtSignature: part.thoughtSignature,
111
105
  },
112
106
  ]));
113
- const images = yield* Effect.forEach(encoded, (item) => MediaInput.decodedAsset(output.invalid, `${NAME} candidate ${item.candidateIndex} part ${item.partIndex}`, item.inlineData.data, item.inlineData.mimeType, {
107
+ const images = yield* Effect.forEach(encoded, (item) => MediaInput.decodedAsset(output.invalid, `${route.name} candidate ${item.candidateIndex} part ${item.partIndex}`, item.inlineData.data, item.inlineData.mimeType, {
114
108
  providerMetadata: {
115
109
  google: {
116
110
  candidateIndex: item.candidate.index ?? item.candidateIndex,
@@ -125,7 +119,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
125
119
  }));
126
120
  if (images.length === 0) {
127
121
  const finishReasons = candidates.flatMap((candidate) => candidate.finishReason === undefined ? [] : [candidate.finishReason]);
128
- return yield* output.invalid(`${NAME} returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect body for prompt feedback and candidate details`);
122
+ return yield* output.invalid(`${route.name} returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect body for prompt feedback and candidate details`);
129
123
  }
130
124
  // Candidates that stopped for a safety or policy reason are partial results, not a silent drop.
131
125
  const notices = [
@@ -134,7 +128,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
134
128
  : [
135
129
  {
136
130
  type: "filtered",
137
- message: `${NAME} reported prompt feedback`,
131
+ message: `${route.name} reported prompt feedback`,
138
132
  providerMetadata: { google: { promptFeedback: decoded.promptFeedback } },
139
133
  },
140
134
  ]),
@@ -143,7 +137,7 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
143
137
  : [
144
138
  {
145
139
  type: "filtered",
146
- message: `${NAME} candidate ${candidate.index ?? index} finished with ${candidate.finishReason}${candidate.finishMessage === undefined ? "" : `: ${candidate.finishMessage}`}`,
140
+ message: `${route.name} candidate ${candidate.index ?? index} finished with ${candidate.finishReason}${candidate.finishMessage === undefined ? "" : `: ${candidate.finishMessage}`}`,
147
141
  providerMetadata: {
148
142
  google: {
149
143
  candidateIndex: candidate.index ?? index,
@@ -186,16 +180,12 @@ const decodeResponse = Effect.fn("GoogleImages.decodeResponse")(function* (respo
186
180
  // ---------------------------------------------------------------------------
187
181
  // 7. Protocol and route
188
182
  // ---------------------------------------------------------------------------
189
- export const protocol = MediaProtocol.inline({
190
- id: ADAPTER,
191
- name: NAME,
183
+ export const protocol = MediaProtocol.inline(route, {
192
184
  unsupported: ["mask", "size", "format"],
193
185
  body: { from: fromRequest },
194
186
  response: { decode: decodeResponse },
195
187
  });
196
188
  export const model = (input) => ImageModel.fromRoute({
197
- id: ADAPTER,
198
- provider: PROVIDER,
199
189
  protocol,
200
190
  baseURL: DEFAULT_BASE_URL,
201
191
  path: ({ request }) => `/models/${request.model.id}:generateContent`,
@@ -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 { SpeechModel } from "../speech.js";
6
6
  import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
7
7
  import { SpeechStream } from "./utils/speech-stream.js";
8
- const ADAPTER = "google-speech";
9
- const NAME = "Google Speech";
10
- const PROVIDER = ProviderID.make("google");
8
+ const route = MediaProtocol.identity({ id: "google-speech", name: "Google Speech", provider: "google" });
11
9
  export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
12
10
  const DEFAULT_SAMPLE_RATE = 24000;
13
11
  // ---------------------------------------------------------------------------
@@ -17,13 +15,13 @@ const GenerateContentChunk = GeminiGenerateContent.chunk(Schema.Struct({
17
15
  text: Schema.optional(Schema.String),
18
16
  inlineData: Schema.optional(Schema.Struct({ mimeType: Schema.String, data: Schema.Uint8ArrayFromBase64 })),
19
17
  }));
20
- const decodeChunk = MediaProtocol.decodeFrame(ADAPTER, NAME, GenerateContentChunk);
18
+ const decodeChunk = route.decodeFrame(GenerateContentChunk);
21
19
  // ---------------------------------------------------------------------------
22
20
  // 5. Request body construction
23
21
  // ---------------------------------------------------------------------------
24
22
  const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
25
23
  if (request.format !== undefined && request.format !== "pcm")
26
- return yield* SpeechStream.unsupportedFormat(PROVIDER, ADAPTER, `${NAME} only returns raw PCM; request format "pcm" or omit it, then wrap the samples yourself`);
24
+ return yield* route.unsupported("media.format", `${route.name} only returns raw PCM; request format "pcm" or omit it, then wrap the samples yourself`);
27
25
  const voiceName = SpeechStream.voiceID(request.voice);
28
26
  return MediaProtocol.json(mergeJsonRecords({
29
27
  contents: [{ role: "user", parts: [{ text: request.text }] }],
@@ -41,7 +39,7 @@ const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
41
39
  // ---------------------------------------------------------------------------
42
40
  const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
43
41
  const chunk = yield* decodeChunk(frame);
44
- const blocked = GeminiGenerateContent.blocked(NAME, chunk, frame);
42
+ const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
45
43
  if (blocked !== undefined)
46
44
  return yield* blocked;
47
45
  const audio = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.inlineData === undefined ? [] : [part.inlineData]);
@@ -50,7 +48,7 @@ const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
50
48
  });
51
49
  const finish = (state) => {
52
50
  const sampleRate = SpeechStream.sampleRate(state.mimeType) ?? DEFAULT_SAMPLE_RATE;
53
- return SpeechStream.finish(ADAPTER, state, {
51
+ return SpeechStream.finish(route, state, {
54
52
  ...SpeechStream.pcm("pcm_s16le", sampleRate, state.mimeType ?? `audio/L16;codec=pcm;rate=${sampleRate}`),
55
53
  usage: GeminiGenerateContent.usage(state.usage),
56
54
  providerMetadata: GeminiGenerateContent.providerMetadata(state),
@@ -60,9 +58,7 @@ const finish = (state) => {
60
58
  // ---------------------------------------------------------------------------
61
59
  // 7. Protocol and route
62
60
  // ---------------------------------------------------------------------------
63
- export const protocol = MediaProtocol.stream({
64
- id: ADAPTER,
65
- name: NAME,
61
+ export const protocol = MediaProtocol.stream(route, {
66
62
  unsupported: ["instructions", "speed", "timestamps"],
67
63
  body: { from: fromRequest },
68
64
  frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
@@ -71,8 +67,6 @@ export const protocol = MediaProtocol.stream({
71
67
  finish,
72
68
  });
73
69
  export const model = (input) => SpeechModel.fromRoute({
74
- id: ADAPTER,
75
- provider: PROVIDER,
76
70
  protocol,
77
71
  baseURL: DEFAULT_BASE_URL,
78
72
  // Only `gemini-3.1-flash-tts-preview` and later stream; earlier TTS models reject `streamGenerateContent`.
@@ -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, type TranscriptionRequestFor, type TranscriptionSegment, type TranscriptionWord } from "../transcription.js";
4
5
  import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
5
6
  export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
@@ -9,7 +10,7 @@ export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.c
9
10
  */
10
11
  export type GoogleTranscriptionOptions = {
11
12
  readonly audioTranscriptionConfig?: {
12
- readonly mode?: "VERBATIM" | "SMART" | (string & {});
13
+ readonly mode?: OpenString<"VERBATIM" | "SMART">;
13
14
  readonly customVocabulary?: ReadonlyArray<string>;
14
15
  readonly languageCodes?: ReadonlyArray<string>;
15
16
  };
@@ -1,13 +1,10 @@
1
1
  import { Effect, Schema, SchemaGetter } 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 { TranscriptionFinishEvent, TranscriptionModel, TranscriptionSegmentEvent, TranscriptionTextDeltaEvent, } from "../transcription.js";
6
- import { ProviderShared } from "./shared.js";
7
6
  import { GeminiGenerateContent } from "./utils/gemini-generate-content.js";
8
- const ADAPTER = "google-transcription";
9
- const NAME = "Google Transcription";
10
- const PROVIDER = ProviderID.make("google");
7
+ const route = MediaProtocol.identity({ id: "google-transcription", name: "Google Transcription", provider: "google" });
11
8
  export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
12
9
  // ---------------------------------------------------------------------------
13
10
  // 3. Streaming event schema
@@ -26,21 +23,16 @@ const AudioTranscription = Schema.Struct({
26
23
  speakerLabel: Schema.optional(Schema.String),
27
24
  }))),
28
25
  });
29
- const decodeChunk = MediaProtocol.decodeFrame(ADAPTER, NAME, GeminiGenerateContent.chunk(Schema.Struct({ audioTranscription: Schema.optional(AudioTranscription) })));
26
+ const decodeChunk = route.decodeFrame(GeminiGenerateContent.chunk(Schema.Struct({ audioTranscription: Schema.optional(AudioTranscription) })));
30
27
  // ---------------------------------------------------------------------------
31
28
  // 5. Request body construction
32
29
  // ---------------------------------------------------------------------------
33
30
  const fromRequest = Effect.fn("GoogleTranscription.fromRequest")(function* (request) {
34
31
  // General Gemini models ignore `audioTranscriptionConfig` and answer the audio conversationally.
35
32
  if (!request.model.id.includes("transcribe"))
36
- return yield* ProviderShared.unsupportedOperation({
37
- operation: "transcription.model",
38
- provider: PROVIDER,
39
- route: ADAPTER,
40
- message: `${request.model.id} is not a transcription model; use a transcribe model such as gemini-3.5-transcribe`,
41
- });
33
+ return yield* route.unsupported("transcription.model", `${request.model.id} is not a transcription model; use a transcribe model such as gemini-3.5-transcribe`);
42
34
  return MediaProtocol.json(mergeJsonRecords({
43
- contents: [{ role: "user", parts: [yield* GeminiGenerateContent.mediaPart(ADAPTER, request.audio)] }],
35
+ contents: [{ role: "user", parts: [yield* GeminiGenerateContent.mediaPart(route.id, request.audio)] }],
44
36
  generationConfig: mergeJsonRecords({
45
37
  audioTranscriptionConfig: {
46
38
  languageCodes: request.language === undefined ? undefined : [request.language],
@@ -80,7 +72,7 @@ const turn = (part) => {
80
72
  };
81
73
  const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
82
74
  const chunk = yield* decodeChunk(frame);
83
- const blocked = GeminiGenerateContent.blocked(NAME, chunk, frame);
75
+ const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
84
76
  if (blocked !== undefined)
85
77
  return yield* blocked;
86
78
  const turns = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.audioTranscription === undefined ? [] : [turn(part.audioTranscription)]);
@@ -101,7 +93,7 @@ const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
101
93
  });
102
94
  const finish = (state) => {
103
95
  if (state.finishReason === undefined)
104
- return Effect.fail(MediaProtocol.incomplete(ADAPTER));
96
+ return Effect.fail(route.incomplete());
105
97
  return Effect.succeed([
106
98
  TranscriptionFinishEvent.make({
107
99
  text: state.text,
@@ -115,9 +107,7 @@ const finish = (state) => {
115
107
  // ---------------------------------------------------------------------------
116
108
  // 7. Protocol and route
117
109
  // ---------------------------------------------------------------------------
118
- export const protocol = MediaProtocol.stream({
119
- id: ADAPTER,
120
- name: NAME,
110
+ export const protocol = MediaProtocol.stream(route, {
121
111
  unsupported: ["prompt", "speakers"],
122
112
  body: { from: fromRequest },
123
113
  frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
@@ -126,8 +116,6 @@ export const protocol = MediaProtocol.stream({
126
116
  finish,
127
117
  });
128
118
  export const model = (input) => TranscriptionModel.fromRoute({
129
- id: ADAPTER,
130
- provider: PROVIDER,
131
119
  protocol,
132
120
  baseURL: DEFAULT_BASE_URL,
133
121
  path: ({ request }) => GeminiGenerateContent.path(request.model.id, request.mode),
@@ -1,12 +1,12 @@
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://generativelanguage.googleapis.com/v1beta";
6
- export type GoogleVideoString<Known extends string> = Known | (string & {});
7
7
  /** Provider-native `parameters`. Common fields (`aspectRatio`, `resolution`, `durationSeconds`, `seed`) live on the request. */
8
8
  export type GoogleVideoOptions = {
9
- readonly personGeneration?: GoogleVideoString<"allow_all" | "allow_adult" | "dont_allow">;
9
+ readonly personGeneration?: OpenString<"allow_all" | "allow_adult" | "dont_allow">;
10
10
  } & Record<string, unknown>;
11
11
  export type Request = VideoRequestFor<GoogleVideoOptions>;
12
12
  /** The long-running operation name, e.g. `models/veo-3.1-generate-preview/operations/abc123`. */
@@ -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 } from "./shared.js";
8
- const ADAPTER = "google-video";
9
- const NAME = "Google Veo";
10
- const PROVIDER = ProviderID.make("google");
8
+ const route = MediaProtocol.identity({ id: "google-video", name: "Google Veo", provider: "google" });
11
9
  export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
12
10
  /** Veo keeps generated files for two days; the asset carries that deadline so callers materialize in time. */
13
11
  const FILE_RETENTION = Duration.days(2);
@@ -38,24 +36,14 @@ const Operation = Schema.Struct({
38
36
  // 5. Request body construction
39
37
  // ---------------------------------------------------------------------------
40
38
  // Veo takes inline media only; a prior Veo output is `Media.url` with transient auth, so materialize it first.
41
- const inlineMedia = (asset) => ProviderShared.requireInlineMedia(NAME, asset).pipe(Effect.map((inline) => ({ inlineData: { mimeType: inline.mime, data: inline.base64 } })));
39
+ const inlineMedia = (asset) => ProviderShared.requireInlineMedia(route.name, asset).pipe(Effect.map((inline) => ({ inlineData: { mimeType: inline.mime, data: inline.base64 } })));
42
40
  const fromRequest = Effect.fn("GoogleVideo.fromRequest")(function* (request) {
43
41
  if (request.n !== undefined && request.n > 1)
44
- return yield* ProviderShared.unsupportedOperation({
45
- operation: "video.n",
46
- provider: PROVIDER,
47
- route: ADAPTER,
48
- message: `${NAME} generates one video per request; call it once per video instead of n=${request.n}`,
49
- });
42
+ return yield* route.unsupported("video.n", `${route.name} generates one video per request; call it once per video instead of n=${request.n}`);
50
43
  if (request.audio === false)
51
- return yield* ProviderShared.unsupportedOperation({
52
- operation: "video.audio",
53
- provider: PROVIDER,
54
- route: ADAPTER,
55
- message: `${NAME} always generates audio; audio: false cannot be honored`,
56
- });
44
+ return yield* route.unsupported("video.audio", `${route.name} always generates audio; audio: false cannot be honored`);
57
45
  if (request.frames?.last !== undefined && request.frames.first === undefined)
58
- return yield* ProviderShared.invalidRequest(`${NAME} requires frames.first when frames.last is set`);
46
+ return yield* ProviderShared.invalidRequest(`${route.name} requires frames.first when frames.last is set`);
59
47
  const image = request.frames?.first === undefined ? undefined : yield* inlineMedia(request.frames.first);
60
48
  const lastFrame = request.frames?.last === undefined ? undefined : yield* inlineMedia(request.frames.last);
61
49
  const video = request.video === undefined ? undefined : yield* inlineMedia(request.video);
@@ -82,7 +70,7 @@ const fromRequest = Effect.fn("GoogleVideo.fromRequest")(function* (request) {
82
70
  // ---------------------------------------------------------------------------
83
71
  // 6. Response decoding
84
72
  // ---------------------------------------------------------------------------
85
- const decodeStart = MediaProtocol.decodeStarted(ADAPTER, NAME, StartResponse, (value) => ({
73
+ const decodeStart = route.decodeStarted(StartResponse, (value) => ({
86
74
  token: { operation: value.name },
87
75
  snapshot: { id: value.name, status: "running" },
88
76
  }));
@@ -92,7 +80,7 @@ const statusOf = (operation) => {
92
80
  return "running";
93
81
  return operation.error === undefined ? "completed" : "failed";
94
82
  };
95
- const decodeOperation = MediaProtocol.decodeJson(ADAPTER, NAME, Operation);
83
+ const decodeOperation = route.decodeJson(Operation);
96
84
  const decodeStatus = Effect.fn("GoogleVideo.decodeStatus")(function* (response, context) {
97
85
  const output = yield* decodeOperation(response);
98
86
  return { id: context.token.operation, status: statusOf(output.value) };
@@ -102,9 +90,9 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
102
90
  const operation = output.value;
103
91
  const status = statusOf(operation);
104
92
  if (status === "running")
105
- return yield* output.invalid(`${NAME} operation ${context.token.operation} has not finished`);
93
+ return yield* output.invalid(`${route.name} operation ${context.token.operation} has not finished`);
106
94
  if (status === "failed")
107
- return yield* output.ended("failed", `${NAME} operation failed${operation.error?.message === undefined ? "" : `: ${operation.error.message}`}`);
95
+ return yield* output.ended("failed", `${route.name} operation failed${operation.error?.message === undefined ? "" : `: ${operation.error.message}`}`);
108
96
  const generated = operation.response?.generateVideoResponse;
109
97
  // Downloads require the same API key as the poll; the asset carries it transiently and follows the redirect.
110
98
  const videos = yield* Effect.forEach((generated?.generatedSamples ?? []).flatMap((sample) => sample.video?.uri === undefined ? [] : [{ uri: sample.video.uri, mimeType: sample.video.mimeType }]), (video) => MediaProtocol.expiringUrl(video.uri, FILE_RETENTION, {
@@ -114,13 +102,13 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
114
102
  const reasons = generated?.raiMediaFilteredReasons ?? [];
115
103
  const notices = reasons.map((reason) => ({
116
104
  type: "filtered",
117
- message: `${NAME} filtered media: ${reason}`,
105
+ message: `${route.name} filtered media: ${reason}`,
118
106
  providerMetadata: { google: { raiMediaFilteredReason: reason } },
119
107
  }));
120
108
  if (videos.length === 0 && (reasons.length > 0 || (generated?.raiMediaFilteredCount ?? 0) > 0))
121
- return yield* output.contentPolicy(`${NAME} filtered every video${reasons.length === 0 ? "" : `: ${reasons.join("; ")}`}`);
109
+ return yield* output.contentPolicy(`${route.name} filtered every video${reasons.length === 0 ? "" : `: ${reasons.join("; ")}`}`);
122
110
  if (videos.length === 0)
123
- return yield* output.invalid(`${NAME} operation completed without any video`);
111
+ return yield* output.invalid(`${route.name} operation completed without any video`);
124
112
  return new VideoResponse({
125
113
  videos,
126
114
  notices: notices.length === 0 ? undefined : notices,
@@ -137,17 +125,13 @@ const decodeResult = Effect.fn("GoogleVideo.decodeResult")(function* (response,
137
125
  // 7. Protocol and route
138
126
  // ---------------------------------------------------------------------------
139
127
  const operationPath = (token) => `/${token.operation}`;
140
- export const protocol = MediaProtocol.queued({
141
- id: ADAPTER,
142
- name: NAME,
128
+ export const protocol = MediaProtocol.queued(route, {
143
129
  token: Token,
144
130
  start: { body: { from: fromRequest }, decode: decodeStart },
145
131
  status: { path: operationPath, decode: decodeStatus },
146
132
  result: { path: operationPath, decode: decodeResult },
147
133
  });
148
134
  export const model = (input) => VideoModel.fromRoute({
149
- id: ADAPTER,
150
- provider: PROVIDER,
151
135
  protocol,
152
136
  baseURL: DEFAULT_BASE_URL,
153
137
  path: ({ request }) => `/models/${request.model.id}:predictLongRunning`,
@@ -1,7 +1,8 @@
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
- type OpenString<Known extends string> = Known | (string & {});
4
+ import { type OpenString } from "../schema/index.js";
5
+ export declare const DEFAULT_BASE_URL = "https://api.meta.ai/v1";
5
6
  /** Provider-native options. Common fields (`n`, `size`, `format`, `images`) live on the request. */
6
7
  export type ImageOptions = {
7
8
  readonly responseFormat?: OpenString<"b64_json" | "url">;
@@ -15,7 +16,5 @@ export type ImageOptions = {
15
16
  };
16
17
  export type Request = ImageRequestFor<ImageOptions>;
17
18
  export declare const protocol: MediaProtocol.Inline<Request, ImageResponse>;
18
- export declare const model: (input: MediaRoute.ModelInput & {
19
- readonly baseURL: string;
20
- }) => ImageModel<ImageOptions>;
19
+ export declare const model: (input: MediaRoute.ModelInput) => ImageModel<ImageOptions>;
21
20
  export * as MetaImages from "./meta-images.js";
@@ -3,12 +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 { JsonObject, ProviderShared, optionalNull } from "./shared.js";
8
8
  import { MediaInput } from "./utils/media-input.js";
9
- const ADAPTER = "meta-images";
10
- const NAME = "Meta Images";
11
- const PROVIDER = ProviderID.make("meta");
9
+ const route = MediaProtocol.identity({ id: "meta-images", name: "Meta Images", provider: "meta" });
10
+ export const DEFAULT_BASE_URL = "https://api.meta.ai/v1";
12
11
  // ---------------------------------------------------------------------------
13
12
  // 2. Request body and response schemas
14
13
  // ---------------------------------------------------------------------------
@@ -38,7 +37,7 @@ const Response = Schema.Struct({
38
37
  // ---------------------------------------------------------------------------
39
38
  const isEdit = (request) => (request.images?.length ?? 0) > 0;
40
39
  // Meta has no file handles: refs are rejected even when they name this provider.
41
- const reference = (asset) => ProviderShared.mediaReference(asset, undefined, NAME).pipe(Effect.map((item) => ({ image_url: item.value })));
40
+ const reference = (asset) => ProviderShared.mediaReference(asset, undefined, route.name).pipe(Effect.map((item) => ({ image_url: item.value })));
42
41
  const fromRequest = Effect.fn("MetaImages.fromRequest")(function* (request) {
43
42
  const images = yield* Effect.forEach(request.images ?? [], reference);
44
43
  const { responseFormat, reasoningStrength, toolEnablement, ...native } = request.providerOptions ?? {};
@@ -58,23 +57,18 @@ const fromRequest = Effect.fn("MetaImages.fromRequest")(function* (request) {
58
57
  // ---------------------------------------------------------------------------
59
58
  // 6. Response decoding
60
59
  // ---------------------------------------------------------------------------
60
+ const decodeDocument = route.decodeJson(Response);
61
61
  const decodeResponse = Effect.fn("MetaImages.decodeResponse")(function* (response, context) {
62
- const output = yield* MediaProtocol.decodeJson(ADAPTER, NAME, Response)(response);
62
+ const output = yield* decodeDocument(response);
63
63
  const decoded = output.value;
64
64
  const requested = context.body.type === "json" ? context.body.value.output_format : undefined;
65
65
  const format = decoded.output_format ?? (typeof requested === "string" ? requested : "webp");
66
66
  const mediaType = `image/${format}`;
67
- const images = yield* Effect.forEach(decoded.data, (item, index) => {
68
- if (item.b64_json)
69
- return MediaInput.decodedAsset(output.invalid, `${NAME} result ${index}`, item.b64_json, mediaType, {
70
- info: { format },
71
- });
72
- if (item.url)
73
- return Effect.succeed(Media.url(item.url, { mediaType, info: { format } }));
74
- return Effect.fail(output.invalid(`${NAME} result ${index} has neither image data nor a URL`));
75
- });
67
+ const images = yield* Effect.forEach(decoded.data, (item, index) => MediaInput.imageOutput(output.invalid, `${route.name} result ${index}`, item, mediaType, {
68
+ info: { format },
69
+ }));
76
70
  if (images.length === 0)
77
- return yield* output.invalid(`${NAME} returned no images`);
71
+ return yield* output.invalid(`${route.name} returned no images`);
78
72
  return new ImageResponse({
79
73
  images,
80
74
  usage: decoded.usage === undefined
@@ -92,17 +86,14 @@ const decodeResponse = Effect.fn("MetaImages.decodeResponse")(function* (respons
92
86
  // ---------------------------------------------------------------------------
93
87
  // 7. Protocol and route
94
88
  // ---------------------------------------------------------------------------
95
- export const protocol = MediaProtocol.inline({
96
- id: ADAPTER,
97
- name: NAME,
89
+ export const protocol = MediaProtocol.inline(route, {
98
90
  unsupported: ["mask", "aspectRatio", "seed"],
99
91
  body: { from: fromRequest },
100
92
  response: { decode: decodeResponse },
101
93
  });
102
94
  export const model = (input) => ImageModel.fromRoute({
103
- id: ADAPTER,
104
- provider: PROVIDER,
105
95
  protocol,
96
+ baseURL: DEFAULT_BASE_URL,
106
97
  path: ({ request }) => `/images/${isEdit(request) ? "edits" : "generations"}`,
107
98
  }, input);
108
99
  export * as MetaImages from "./meta-images.js";
@@ -221,20 +221,20 @@ export declare const protocol: Protocol<{
221
221
  readonly thinking?: {
222
222
  readonly type: "enabled";
223
223
  readonly budget_tokens: number;
224
- readonly display?: (string & {}) | "summarized" | "omitted" | undefined;
224
+ readonly display?: import("../schema/options.js").OpenString<"summarized" | "omitted"> | undefined;
225
225
  readonly block_binding?: {
226
- readonly prefix_mismatch_behavior?: (string & {}) | "error" | "drop_block" | undefined;
226
+ readonly prefix_mismatch_behavior?: import("../schema/options.js").OpenString<"error" | "drop_block"> | undefined;
227
227
  } | undefined;
228
228
  } | {
229
229
  readonly type: "adaptive";
230
- readonly display?: (string & {}) | "summarized" | "omitted" | undefined;
230
+ readonly display?: import("../schema/options.js").OpenString<"summarized" | "omitted"> | undefined;
231
231
  readonly block_binding?: {
232
- readonly prefix_mismatch_behavior?: (string & {}) | "error" | "drop_block" | undefined;
232
+ readonly prefix_mismatch_behavior?: import("../schema/options.js").OpenString<"error" | "drop_block"> | undefined;
233
233
  } | undefined;
234
234
  } | {
235
235
  readonly type: "disabled";
236
236
  } | undefined;
237
- readonly service_tier?: "auto" | (string & {}) | "standard_only" | undefined;
237
+ readonly service_tier?: import("../schema/options.js").OpenString<"auto" | "standard_only"> | undefined;
238
238
  readonly container?: string | {
239
239
  readonly id?: string | null | undefined;
240
240
  readonly skills?: readonly {