@opencode/ai 2.0.16 → 2.0.17

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 (185) hide show
  1. package/README.md +187 -129
  2. package/dist/ai-client.d.ts +8 -0
  3. package/dist/ai-client.js +12 -0
  4. package/dist/experimental/evaluation-client.d.ts +3 -3
  5. package/dist/experimental/evaluation-client.js +1 -1
  6. package/dist/experimental/evaluation.js +1 -1
  7. package/dist/generation.d.ts +6 -10
  8. package/dist/generation.js +17 -20
  9. package/dist/image-client.d.ts +59 -24
  10. package/dist/image-client.js +16 -40
  11. package/dist/image.d.ts +15 -27
  12. package/dist/image.js +4 -15
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.js +1 -0
  15. package/dist/llm.d.ts +7 -5
  16. package/dist/llm.js +10 -4
  17. package/dist/media-client.d.ts +30 -0
  18. package/dist/media-client.js +51 -0
  19. package/dist/media-model.d.ts +9 -10
  20. package/dist/media-model.js +10 -12
  21. package/dist/media.js +2 -2
  22. package/dist/promise.d.ts +68 -30
  23. package/dist/promise.js +52 -31
  24. package/dist/protocols/alibaba-chat.js +4 -1
  25. package/dist/protocols/alibaba-messages.d.ts +1 -1
  26. package/dist/protocols/alibaba-messages.js +6 -4
  27. package/dist/protocols/anthropic-messages.d.ts +34 -34
  28. package/dist/protocols/anthropic-messages.js +14 -8
  29. package/dist/protocols/assemblyai-transcription.d.ts +2 -1
  30. package/dist/protocols/assemblyai-transcription.js +14 -16
  31. package/dist/protocols/bedrock-converse.d.ts +7 -0
  32. package/dist/protocols/bedrock-converse.js +37 -11
  33. package/dist/protocols/bfl-images.d.ts +7 -1
  34. package/dist/protocols/bfl-images.js +36 -36
  35. package/dist/protocols/cartesia-speech.d.ts +18 -2
  36. package/dist/protocols/cartesia-speech.js +10 -16
  37. package/dist/protocols/deepgram-speech.d.ts +19 -3
  38. package/dist/protocols/deepgram-speech.js +11 -12
  39. package/dist/protocols/deepgram-transcription.d.ts +2 -1
  40. package/dist/protocols/deepgram-transcription.js +9 -13
  41. package/dist/protocols/elevenlabs-speech.d.ts +19 -3
  42. package/dist/protocols/elevenlabs-speech.js +9 -13
  43. package/dist/protocols/fal-images.d.ts +2 -1
  44. package/dist/protocols/fal-images.js +30 -40
  45. package/dist/protocols/fal-video.d.ts +2 -2
  46. package/dist/protocols/fal-video.js +9 -24
  47. package/dist/protocols/gemini.d.ts +13 -13
  48. package/dist/protocols/gemini.js +16 -7
  49. package/dist/protocols/google-images.d.ts +3 -3
  50. package/dist/protocols/google-images.js +11 -21
  51. package/dist/protocols/google-speech.d.ts +16 -0
  52. package/dist/protocols/google-speech.js +20 -16
  53. package/dist/protocols/google-transcription.d.ts +2 -1
  54. package/dist/protocols/google-transcription.js +8 -20
  55. package/dist/protocols/google-video.d.ts +2 -2
  56. package/dist/protocols/google-video.js +14 -30
  57. package/dist/protocols/meta-images.d.ts +3 -4
  58. package/dist/protocols/meta-images.js +12 -21
  59. package/dist/protocols/meta-messages.d.ts +5 -5
  60. package/dist/protocols/meta-responses.js +6 -4
  61. package/dist/protocols/open-responses.d.ts +4 -3
  62. package/dist/protocols/open-responses.js +4 -8
  63. package/dist/protocols/openai-chat.js +3 -4
  64. package/dist/protocols/openai-images.d.ts +7 -5
  65. package/dist/protocols/openai-images.js +65 -66
  66. package/dist/protocols/openai-responses.d.ts +10 -10
  67. package/dist/protocols/openai-responses.js +31 -30
  68. package/dist/protocols/openai-speech.d.ts +21 -1
  69. package/dist/protocols/openai-speech.js +11 -12
  70. package/dist/protocols/openai-transcription.d.ts +4 -0
  71. package/dist/protocols/openai-transcription.js +44 -32
  72. package/dist/protocols/replicate-images.js +11 -17
  73. package/dist/protocols/runway-video.d.ts +3 -3
  74. package/dist/protocols/runway-video.js +11 -17
  75. package/dist/protocols/shared.d.ts +11 -17
  76. package/dist/protocols/shared.js +11 -40
  77. package/dist/protocols/stability-images.d.ts +2 -1
  78. package/dist/protocols/stability-images.js +24 -36
  79. package/dist/protocols/utils/fal-queue.d.ts +1 -3
  80. package/dist/protocols/utils/fal-queue.js +4 -6
  81. package/dist/protocols/utils/media-input.d.ts +15 -1
  82. package/dist/protocols/utils/media-input.js +22 -0
  83. package/dist/protocols/utils/responses-checkpoint.js +3 -7
  84. package/dist/protocols/utils/responses-compaction.d.ts +3 -1
  85. package/dist/protocols/utils/responses-compaction.js +16 -3
  86. package/dist/protocols/utils/speech-stream.d.ts +3 -3
  87. package/dist/protocols/utils/speech-stream.js +1 -4
  88. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  89. package/dist/protocols/utils/tool-schema.js +29 -9
  90. package/dist/protocols/xai-images.d.ts +4 -4
  91. package/dist/protocols/xai-images.js +14 -29
  92. package/dist/protocols/xai-responses.js +1 -1
  93. package/dist/protocols/xai-video.js +12 -18
  94. package/dist/protocols/zai-images.d.ts +2 -2
  95. package/dist/protocols/zai-images.js +11 -14
  96. package/dist/protocols/zai-messages.d.ts +1 -1
  97. package/dist/provider-error.js +7 -1
  98. package/dist/providers/alibaba.d.ts +1 -1
  99. package/dist/providers/amazon-bedrock.d.ts +2 -0
  100. package/dist/providers/amazon-bedrock.js +1 -0
  101. package/dist/providers/anthropic-compatible.d.ts +5 -5
  102. package/dist/providers/anthropic.d.ts +5 -5
  103. package/dist/providers/assemblyai.d.ts +1 -1
  104. package/dist/providers/assemblyai.js +5 -10
  105. package/dist/providers/azure.d.ts +4 -4
  106. package/dist/providers/azure.js +2 -2
  107. package/dist/providers/black-forest-labs.d.ts +1 -1
  108. package/dist/providers/black-forest-labs.js +5 -10
  109. package/dist/providers/cartesia.d.ts +1 -1
  110. package/dist/providers/cartesia.js +5 -10
  111. package/dist/providers/cloudflare-ai-gateway.d.ts +14 -14
  112. package/dist/providers/deepgram.d.ts +1 -1
  113. package/dist/providers/deepgram.js +6 -12
  114. package/dist/providers/elevenlabs.d.ts +1 -1
  115. package/dist/providers/elevenlabs.js +5 -10
  116. package/dist/providers/fal.d.ts +1 -1
  117. package/dist/providers/fal.js +5 -12
  118. package/dist/providers/google-vertex-messages.d.ts +5 -5
  119. package/dist/providers/google-vertex.d.ts +3 -3
  120. package/dist/providers/google.d.ts +3 -3
  121. package/dist/providers/google.js +7 -12
  122. package/dist/providers/meta.d.ts +8 -8
  123. package/dist/providers/meta.js +4 -8
  124. package/dist/providers/minimax.d.ts +5 -5
  125. package/dist/providers/moonshot.d.ts +5 -5
  126. package/dist/providers/openai-options.d.ts +3 -9
  127. package/dist/providers/openai-options.js +4 -7
  128. package/dist/providers/openai.d.ts +9 -10
  129. package/dist/providers/openai.js +11 -12
  130. package/dist/providers/opencode-zen.js +1 -1
  131. package/dist/providers/openrouter.d.ts +6 -7
  132. package/dist/providers/openrouter.js +10 -5
  133. package/dist/providers/replicate.d.ts +1 -1
  134. package/dist/providers/replicate.js +5 -10
  135. package/dist/providers/runway.d.ts +1 -1
  136. package/dist/providers/runway.js +5 -10
  137. package/dist/providers/stability.d.ts +1 -1
  138. package/dist/providers/stability.js +6 -11
  139. package/dist/providers/typesafe-ai.js +1 -1
  140. package/dist/providers/vercel-ai-gateway.js +1 -1
  141. package/dist/providers/xai.js +5 -10
  142. package/dist/providers/zai-coding-plan.d.ts +1 -1
  143. package/dist/providers/zai.js +4 -8
  144. package/dist/route/auth.d.ts +1 -1
  145. package/dist/route/auth.js +14 -13
  146. package/dist/route/client.d.ts +9 -7
  147. package/dist/route/client.js +6 -8
  148. package/dist/route/endpoint.d.ts +1 -0
  149. package/dist/route/endpoint.js +2 -2
  150. package/dist/route/executor-service.d.ts +4 -2
  151. package/dist/route/executor-service.js +2 -1
  152. package/dist/route/executor.d.ts +3 -1
  153. package/dist/route/executor.js +7 -0
  154. package/dist/route/framing.d.ts +10 -1
  155. package/dist/route/framing.js +45 -5
  156. package/dist/route/index.d.ts +1 -1
  157. package/dist/route/media-protocol.d.ts +49 -35
  158. package/dist/route/media-protocol.js +77 -65
  159. package/dist/route/media.d.ts +10 -16
  160. package/dist/route/media.js +46 -62
  161. package/dist/route/protocol.d.ts +3 -1
  162. package/dist/schema/options.d.ts +6 -3
  163. package/dist/schema/options.js +7 -3
  164. package/dist/speech-client.d.ts +62 -17
  165. package/dist/speech-client.js +17 -21
  166. package/dist/speech.d.ts +178 -21
  167. package/dist/speech.js +13 -8
  168. package/dist/testing.d.ts +2 -2
  169. package/dist/transcription-client.d.ts +78 -24
  170. package/dist/transcription-client.js +9 -40
  171. package/dist/transcription.d.ts +15 -27
  172. package/dist/transcription.js +4 -10
  173. package/dist/utils/json.d.ts +4 -0
  174. package/dist/utils/json.js +4 -0
  175. package/dist/utils/media-type.d.ts +2 -1
  176. package/dist/utils/media-type.js +3 -1
  177. package/dist/video-client.d.ts +55 -24
  178. package/dist/video-client.js +16 -36
  179. package/dist/video.d.ts +18 -26
  180. package/dist/video.js +15 -16
  181. package/package.json +3 -3
  182. package/dist/protocols/utils/meta-image.d.ts +0 -2
  183. package/dist/protocols/utils/meta-image.js +0 -13
  184. package/dist/protocols/utils/openai-image.d.ts +0 -5
  185. package/dist/protocols/utils/openai-image.js +0 -18
@@ -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.pending(context.token.operation);
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 {
@@ -5,8 +5,7 @@ import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.j
5
5
  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
- import { ToolSchemaProjection } from "./utils/tool-schema.js";
9
- import { MetaImage } from "./utils/meta-image.js";
8
+ import { detectMediaType } from "../utils/media-type.js";
10
9
  const ADAPTER = "meta-responses";
11
10
  const NAME = "Meta Responses";
12
11
  export const WebSearch = Schema.Struct({
@@ -82,7 +81,7 @@ const fromRequest = Effect.fn("MetaResponses.fromRequest")(function* (request) {
82
81
  ? undefined
83
82
  : yield* Effect.forEach(projected.tools, (tool) => Effect.gen(function* () {
84
83
  if (tool.native === undefined)
85
- return yield* OpenResponses.lowerTool(NAME, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model));
84
+ return yield* OpenResponses.lowerTool(NAME, tool);
86
85
  return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(NativeTool))(tool.native.meta);
87
86
  })),
88
87
  tool_choice: OpenResponses.allowedToolChoice(request) ??
@@ -101,7 +100,10 @@ const HOSTED_TOOLS = {
101
100
  if (!item.result)
102
101
  return yield* ProviderShared.eventError(ADAPTER, "Meta returned an image without data", ProviderShared.encodeJson(raw));
103
102
  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);
103
+ // Responses image items can omit output_format, including when PNG/JPEG was requested.
104
+ const mime = item.output_format === undefined
105
+ ? (detectMediaType(data) ?? "application/octet-stream")
106
+ : `image/${item.output_format}`;
105
107
  return {
106
108
  type: "content",
107
109
  value: [{ type: "file", uri: `data:${mime};base64,${item.result}`, mime }],
@@ -716,9 +716,7 @@ interface ReasoningStreamItem {
716
716
  readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>;
717
717
  readonly deltaIndexes: ReadonlySet<number>;
718
718
  }
719
- export declare const lowerTool: (protocolName: string, tool: ToolDefinition, inputSchema: {
720
- readonly [x: string]: unknown;
721
- }) => Effect.Effect<{
719
+ export declare const lowerTool: (protocolName: string, tool: ToolDefinition) => Effect.Effect<{
722
720
  type: "function";
723
721
  name: string;
724
722
  description: string;
@@ -727,6 +725,9 @@ export declare const lowerTool: (protocolName: string, tool: ToolDefinition, inp
727
725
  };
728
726
  strict: boolean;
729
727
  }, AIError, never>;
728
+ export declare const lowerTools: (tools: ReadonlyArray<ToolDefinition>, adapter: ProviderAdapter) => Effect.Effect<{
729
+ readonly type: string;
730
+ }[], AIError, never>;
730
731
  export declare const lowerToolChoice: (protocolName: string, toolChoice: NonNullable<LLMRequest["toolChoice"]>) => Effect.Effect<"required" | "auto" | "none" | {
731
732
  type: "function";
732
733
  name: string;
@@ -7,7 +7,6 @@ import { classifyProviderFailure } from "../provider-error.js";
7
7
  import { effortUpdate } from "../effort-updates.js";
8
8
  import { OpenResponsesOptions } from "./utils/open-responses-options.js";
9
9
  import { Lifecycle } from "./utils/lifecycle.js";
10
- import { ToolSchemaProjection } from "./utils/tool-schema.js";
11
10
  import { ToolStream } from "./utils/tool-stream.js";
12
11
  const ADAPTER = "open-responses";
13
12
  const NAME = "Open Responses";
@@ -281,18 +280,19 @@ const BASE_ADAPTER = { id: ADAPTER, name: NAME };
281
280
  // =============================================================================
282
281
  // Request Lowering
283
282
  // =============================================================================
284
- export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool, inputSchema) {
283
+ export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool) {
285
284
  if (tool.native !== undefined)
286
285
  return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`);
287
286
  return {
288
287
  type: "function",
289
288
  name: tool.name,
290
289
  description: tool.description,
291
- parameters: inputSchema,
290
+ parameters: tool.inputSchema,
292
291
  // The common tool definition does not currently express Responses strict-schema policy.
293
292
  strict: false,
294
293
  };
295
294
  });
295
+ export const lowerTools = (tools, adapter) => Effect.forEach(tools, (tool) => tool.native !== undefined && adapter.nativeTool ? adapter.nativeTool(tool.native) : lowerTool(adapter.name, tool));
296
296
  export const lowerToolChoice = (protocolName, toolChoice) => ProviderShared.matchToolChoice(protocolName, toolChoice, {
297
297
  auto: () => "auto",
298
298
  none: () => "none",
@@ -606,11 +606,7 @@ export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAd
606
606
  return {
607
607
  ...(yield* lowerConversation(projected.request, adapter)),
608
608
  ...lowerGeneration(request),
609
- tools: projected.tools.length === 0
610
- ? undefined
611
- : yield* Effect.forEach(projected.tools, (tool) => tool.native !== undefined && adapter.nativeTool
612
- ? adapter.nativeTool(tool.native)
613
- : lowerTool(adapter.name, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model))),
609
+ tools: projected.tools.length === 0 ? undefined : yield* lowerTools(projected.tools, adapter),
614
610
  tool_choice: allowedToolChoice(request) ??
615
611
  (request.toolChoice ? yield* lowerToolChoice(adapter.name, request.toolChoice) : undefined),
616
612
  };
@@ -11,7 +11,6 @@ import { classifyProviderFailure } from "../provider-error.js";
11
11
  import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
12
12
  import { OpenAIOptions } from "./utils/openai-options.js";
13
13
  import { Lifecycle } from "./utils/lifecycle.js";
14
- import { ToolSchemaProjection } from "./utils/tool-schema.js";
15
14
  import { ToolStream } from "./utils/tool-stream.js";
16
15
  const ADAPTER = "openai-chat";
17
16
  const RESERVED_REASONING_FIELDS = new Set(["role", "content", "refusal", "tool_calls"]);
@@ -209,12 +208,12 @@ export const OpenAIChatEvent = Schema.StructWithRest(Schema.Struct({
209
208
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
210
209
  const DONE = "[DONE]";
211
210
  const OpenAIChatStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(OpenAIChatEvent)]);
212
- const lowerTool = (tool, inputSchema, options, supportsStrictMode) => ({
211
+ const lowerTool = (tool, options, supportsStrictMode) => ({
213
212
  type: "function",
214
213
  function: {
215
214
  name: tool.name,
216
215
  description: tool.description,
217
- parameters: inputSchema,
216
+ parameters: tool.inputSchema,
218
217
  ...(supportsStrictMode ? { strict: false } : {}),
219
218
  },
220
219
  cache_control: options.cacheControl?.(tool.cache),
@@ -649,7 +648,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
649
648
  ? hasHistory
650
649
  ? []
651
650
  : undefined
652
- : flattened.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model), options, supportsStrictMode)),
651
+ : flattened.tools.map((tool) => lowerTool(tool, options, supportsStrictMode)),
653
652
  tool_choice: hasActiveTools && request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
654
653
  stream: true,
655
654
  ...(supportsUsageInStreaming ? { stream_options: { include_usage: true } } : {}),
@@ -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;
@@ -25,6 +24,9 @@ type Frame = string | {
25
24
  interface State {
26
25
  readonly completed: number;
27
26
  readonly format?: string;
27
+ readonly size?: string;
28
+ readonly quality?: string;
29
+ readonly background?: string;
28
30
  readonly usage?: MediaUsage;
29
31
  }
30
32
  export declare const protocol: MediaProtocol.Streamed<Request, {