@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
@@ -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";
@@ -23,13 +21,16 @@ const Usage = Schema.Struct({
23
21
  input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
24
22
  output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
25
23
  });
26
- const OpenAIImageResponse = Schema.Struct({
27
- data: Schema.Array(Schema.Struct({
28
- b64_json: Schema.optional(Schema.String),
29
- url: Schema.optional(Schema.String),
30
- revised_prompt: Schema.optional(Schema.String),
31
- })),
24
+ /** What the provider actually rendered; it can differ from the request when `auto` or a default applied. */
25
+ const Settings = {
32
26
  output_format: Schema.optional(Schema.String),
27
+ size: Schema.optional(Schema.String),
28
+ quality: Schema.optional(Schema.String),
29
+ background: Schema.optional(Schema.String),
30
+ };
31
+ const OpenAIImageResponse = Schema.Struct({
32
+ data: Schema.Array(Schema.Struct({ b64_json: Schema.String })),
33
+ ...Settings,
33
34
  usage: Schema.optional(Usage),
34
35
  });
35
36
  // ---------------------------------------------------------------------------
@@ -40,16 +41,18 @@ const StreamEvent = Schema.Union([
40
41
  type: Schema.Literals(["image_generation.partial_image", "image_edit.partial_image"]),
41
42
  b64_json: Schema.String,
42
43
  partial_image_index: Schema.Number,
44
+ ...Settings,
43
45
  output_format: Schema.String,
44
46
  }),
45
47
  Schema.Struct({
46
48
  type: Schema.Literals(["image_generation.completed", "image_edit.completed"]),
47
49
  b64_json: Schema.String,
50
+ ...Settings,
48
51
  output_format: Schema.String,
49
52
  usage: Schema.optional(Usage),
50
53
  }),
51
54
  ]);
52
- const decodeEvent = MediaProtocol.decodeFrame(ADAPTER, NAME, StreamEvent);
55
+ const decodeEvent = route.decodeFrame(StreamEvent);
53
56
  const decodeDocument = Schema.decodeUnknownEffect(Schema.fromJsonString(OpenAIImageResponse));
54
57
  // ---------------------------------------------------------------------------
55
58
  // 5. Request body construction
@@ -65,25 +68,13 @@ const nativeOptions = (options) => {
65
68
  const streamOptions = (request) => {
66
69
  if (request.mode !== "stream")
67
70
  return Effect.succeed(undefined);
68
- 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
- }));
75
71
  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
- }));
72
+ return Effect.fail(route.unsupported("media.n", `${route.name} streams one image; use Image.generate for n=${request.n}`));
82
73
  return Effect.succeed({ stream: true, partial_images: request.providerOptions?.partialImages ?? 2 });
83
74
  };
84
75
  const isEdit = (request) => (request.images?.length ?? 0) > 0;
85
76
  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 })));
77
+ 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
78
  const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
88
79
  const images = request.images ?? [];
89
80
  const mask = request.mask;
@@ -93,17 +84,11 @@ const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
93
84
  // Owned bytes go through multipart edits; remote URLs and file IDs use the JSON edits body instead.
94
85
  if (images.length > 0 && images.every(isInline) && (mask === undefined || isInline(mask))) {
95
86
  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));
87
+ MediaInput.appendFields(form, { model: request.model.id, prompt: request.prompt }, { overlay: fields, reserved: RESERVED_FORM_FIELDS });
88
+ const uploads = yield* Effect.forEach(images, (image) => MediaInput.inlineBytes(route.id, image));
104
89
  uploads.forEach((data, index) => form.append("image[]", MediaInput.blob(data, images[index].mediaType), `image-${index}`));
105
90
  if (mask !== undefined)
106
- form.append("mask", MediaInput.blob(yield* MediaInput.inlineBytes(ADAPTER, mask), mask.mediaType), "mask");
91
+ form.append("mask", MediaInput.blob(yield* MediaInput.inlineBytes(route.id, mask), mask.mediaType), "mask");
107
92
  return MediaProtocol.multipart(form);
108
93
  }
109
94
  const references = yield* Effect.forEach(images, reference);
@@ -133,55 +118,75 @@ const usage = (value) => value === undefined
133
118
  total: value.total_tokens,
134
119
  details: { openai: value },
135
120
  };
136
- const eventImage = (frame, label, data, format) => MediaInput.decodedAsset((message, cause) => MediaProtocol.frameError(ADAPTER, message, frame, cause), label, data, `image/${format}`, { info: { format } });
121
+ /** `size` echoes the rendered `WIDTHxHEIGHT`; `auto` or any other value leaves the dimensions unknown. */
122
+ const info = (format, size) => {
123
+ const match = size?.match(/^(\d+)x(\d+)$/);
124
+ return match ? { format, width: Number(match[1]), height: Number(match[2]) } : { format };
125
+ };
126
+ const eventImage = (frame, label, data, format, size) => MediaInput.decodedAsset((message, cause) => route.frameError(message, frame, cause), label, data, `image/${format}`, {
127
+ info: info(format, size),
128
+ });
137
129
  const onEvent = Effect.fn("OpenAIImages.onEvent")(function* (state, frame) {
138
130
  const event = yield* decodeEvent(frame);
139
131
  const format = event.output_format;
140
132
  if ("partial_image_index" in event) {
141
- const image = yield* eventImage(frame, `${NAME} partial image`, event.b64_json, format);
133
+ const image = yield* eventImage(frame, `${route.name} partial image`, event.b64_json, format, event.size);
142
134
  return [state, [ImagePartialEvent.make({ index: event.partial_image_index, image })]];
143
135
  }
144
- const image = yield* eventImage(frame, `${NAME} result ${state.completed}`, event.b64_json, format);
136
+ const image = yield* eventImage(frame, `${route.name} result ${state.completed}`, event.b64_json, format, event.size);
145
137
  return [
146
- { ...state, completed: state.completed + 1, format, usage: usage(event.usage) },
138
+ {
139
+ completed: state.completed + 1,
140
+ format,
141
+ size: event.size,
142
+ quality: event.quality,
143
+ background: event.background,
144
+ usage: usage(event.usage),
145
+ },
147
146
  [ImageOutputEvent.make({ index: state.completed, image })],
148
147
  ];
149
148
  });
150
149
  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)));
150
+ const invalid = (message, cause) => route.frameError(message, frame.document, cause);
151
+ const decoded = yield* decodeDocument(frame.document).pipe(Effect.mapError((cause) => invalid(`${route.name} returned an invalid response`, cause)));
153
152
  const format = decoded.output_format ?? frame.requested ?? "png";
154
- 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
- });
153
+ const images = yield* Effect.forEach(decoded.data, (item, index) => MediaInput.decodedAsset(invalid, `${route.name} result ${index}`, item.b64_json, `image/${format}`, {
154
+ info: info(format, decoded.size),
155
+ }));
166
156
  if (images.length === 0)
167
- return yield* invalid(`${NAME} returned no images`);
168
- const state = { completed: images.length, format, usage: usage(decoded.usage) };
157
+ return yield* invalid(`${route.name} returned no images`);
158
+ const state = {
159
+ completed: images.length,
160
+ format,
161
+ size: decoded.size,
162
+ quality: decoded.quality,
163
+ background: decoded.background,
164
+ usage: usage(decoded.usage),
165
+ };
169
166
  return [state, images.map((image, index) => ImageOutputEvent.make({ index, image }))];
170
167
  });
171
168
  const step = (state, frame) => (typeof frame === "string" ? onEvent(state, frame) : onDocument(frame));
172
169
  const finish = (state) => {
173
170
  if (state.completed === 0)
174
- return Effect.fail(MediaProtocol.incomplete(ADAPTER));
171
+ return Effect.fail(route.incomplete());
175
172
  return Effect.succeed([
176
- ImageFinishEvent.make({ usage: state.usage, providerMetadata: { openai: { outputFormat: state.format } } }),
173
+ ImageFinishEvent.make({
174
+ usage: state.usage,
175
+ providerMetadata: {
176
+ openai: {
177
+ outputFormat: state.format,
178
+ size: state.size,
179
+ quality: state.quality,
180
+ background: state.background,
181
+ },
182
+ },
183
+ }),
177
184
  ]);
178
185
  };
179
186
  // ---------------------------------------------------------------------------
180
187
  // 7. Protocol and route
181
188
  // ---------------------------------------------------------------------------
182
- export const protocol = MediaProtocol.stream({
183
- id: ADAPTER,
184
- name: NAME,
189
+ export const protocol = MediaProtocol.stream(route, {
185
190
  unsupported: ["aspectRatio", "seed"],
186
191
  body: { from: fromRequest },
187
192
  frames: (bytes, context) => context.request.mode === "stream"
@@ -193,13 +198,7 @@ export const protocol = MediaProtocol.stream({
193
198
  step,
194
199
  finish,
195
200
  });
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);
201
+ export const model = (input) => ImageModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: ({ request }) => (isEdit(request) ? EDIT_PATH : PATH) }, input);
203
202
  export const OpenAIImages = {
204
203
  protocol,
205
204
  model,
@@ -364,8 +364,8 @@ export declare const protocol: Protocol<{
364
364
  readonly type: "image_generation_call";
365
365
  readonly status?: string | undefined;
366
366
  readonly result?: string | null | undefined;
367
- readonly revised_prompt?: string | null | undefined;
368
367
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
368
+ readonly revised_prompt?: string | null | undefined;
369
369
  })[];
370
370
  readonly model: string;
371
371
  readonly stream: true;
@@ -390,9 +390,9 @@ export declare const protocol: Protocol<{
390
390
  readonly size?: string | undefined;
391
391
  readonly action?: "generate" | "auto" | "edit" | undefined;
392
392
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
393
- readonly partial_images?: number | undefined;
394
393
  readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
395
394
  readonly background?: "auto" | "opaque" | "transparent" | undefined;
395
+ readonly partial_images?: number | undefined;
396
396
  readonly output_compression?: number | undefined;
397
397
  readonly input_fidelity?: "low" | "high" | undefined;
398
398
  } | {
@@ -647,8 +647,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
647
647
  readonly type: "image_generation_call";
648
648
  readonly status?: string | undefined;
649
649
  readonly result?: string | null | undefined;
650
- readonly revised_prompt?: string | null | undefined;
651
650
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
651
+ readonly revised_prompt?: string | null | undefined;
652
652
  })[];
653
653
  readonly model: string;
654
654
  readonly stream: true;
@@ -673,9 +673,9 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
673
673
  readonly size?: string | undefined;
674
674
  readonly action?: "generate" | "auto" | "edit" | undefined;
675
675
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
676
- readonly partial_images?: number | undefined;
677
676
  readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
678
677
  readonly background?: "auto" | "opaque" | "transparent" | undefined;
678
+ readonly partial_images?: number | undefined;
679
679
  readonly output_compression?: number | undefined;
680
680
  readonly input_fidelity?: "low" | "high" | undefined;
681
681
  } | {
@@ -875,8 +875,8 @@ export declare const channelTransport: (options: import("./open-responses-channe
875
875
  readonly type: "image_generation_call";
876
876
  readonly status?: string | undefined;
877
877
  readonly result?: string | null | undefined;
878
- readonly revised_prompt?: string | null | undefined;
879
878
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
879
+ readonly revised_prompt?: string | null | undefined;
880
880
  })[];
881
881
  readonly model: string;
882
882
  readonly stream: true;
@@ -901,9 +901,9 @@ export declare const channelTransport: (options: import("./open-responses-channe
901
901
  readonly size?: string | undefined;
902
902
  readonly action?: "generate" | "auto" | "edit" | undefined;
903
903
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
904
- readonly partial_images?: number | undefined;
905
904
  readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
906
905
  readonly background?: "auto" | "opaque" | "transparent" | undefined;
906
+ readonly partial_images?: number | undefined;
907
907
  readonly output_compression?: number | undefined;
908
908
  readonly input_fidelity?: "low" | "high" | undefined;
909
909
  } | {
@@ -1103,8 +1103,8 @@ export declare const transport: import("../route/transport/index.js").Transport<
1103
1103
  readonly type: "image_generation_call";
1104
1104
  readonly status?: string | undefined;
1105
1105
  readonly result?: string | null | undefined;
1106
- readonly revised_prompt?: string | null | undefined;
1107
1106
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
1107
+ readonly revised_prompt?: string | null | undefined;
1108
1108
  })[];
1109
1109
  readonly model: string;
1110
1110
  readonly stream: true;
@@ -1129,9 +1129,9 @@ export declare const transport: import("../route/transport/index.js").Transport<
1129
1129
  readonly size?: string | undefined;
1130
1130
  readonly action?: "generate" | "auto" | "edit" | undefined;
1131
1131
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
1132
- readonly partial_images?: number | undefined;
1133
1132
  readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
1134
1133
  readonly background?: "auto" | "opaque" | "transparent" | undefined;
1134
+ readonly partial_images?: number | undefined;
1135
1135
  readonly output_compression?: number | undefined;
1136
1136
  readonly input_fidelity?: "low" | "high" | undefined;
1137
1137
  } | {
@@ -1331,8 +1331,8 @@ export declare const route: Route<{
1331
1331
  readonly type: "image_generation_call";
1332
1332
  readonly status?: string | undefined;
1333
1333
  readonly result?: string | null | undefined;
1334
- readonly revised_prompt?: string | null | undefined;
1335
1334
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
1335
+ readonly revised_prompt?: string | null | undefined;
1336
1336
  })[];
1337
1337
  readonly model: string;
1338
1338
  readonly stream: true;
@@ -1357,9 +1357,9 @@ export declare const route: Route<{
1357
1357
  readonly size?: string | undefined;
1358
1358
  readonly action?: "generate" | "auto" | "edit" | undefined;
1359
1359
  readonly output_format?: "png" | "jpeg" | "webp" | undefined;
1360
- readonly partial_images?: number | undefined;
1361
1360
  readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
1362
1361
  readonly background?: "auto" | "opaque" | "transparent" | undefined;
1362
+ readonly partial_images?: number | undefined;
1363
1363
  readonly output_compression?: number | undefined;
1364
1364
  readonly input_fidelity?: "low" | "high" | undefined;
1365
1365
  } | {
@@ -5,14 +5,12 @@ import { Auth } from "../route/auth.js";
5
5
  import { Endpoint } from "../route/endpoint.js";
6
6
  import { Protocol } from "../route/protocol.js";
7
7
  import { HttpTransport } from "../route/transport/index.js";
8
- import { LLMRequest, mergeJsonRecords, } from "../schema/index.js";
8
+ import { LLMRequest } from "../schema/index.js";
9
9
  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
- import { ToolSchemaProjection } from "./utils/tool-schema.js";
16
14
  import { OpenResponsesChannel } from "./open-responses-channel.js";
17
15
  import { ResponsesCompaction } from "./utils/responses-compaction.js";
18
16
  import { ResponsesCheckpoint } from "./utils/responses-checkpoint.js";
@@ -35,7 +33,14 @@ const OpenAIResponsesImageGenerationTool = Schema.Struct({
35
33
  output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
36
34
  partial_images: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
37
35
  quality: Schema.optional(Schema.Literals(["auto", "low", "medium", "high"])),
38
- size: Schema.optional(OpenAIImage.Size),
36
+ size: Schema.optional(Schema.String.check(Schema.makeFilter((value) => {
37
+ if (value === "auto")
38
+ return undefined;
39
+ const match = /^(\d+)x(\d+)$/.exec(value);
40
+ if (!match)
41
+ return "image size must be `auto` or `{width}x{height}`";
42
+ return Number(match[1]) > 0 && Number(match[2]) > 0 ? undefined : "image dimensions must be positive integers";
43
+ }))),
39
44
  });
40
45
  const OpenAIResponsesHostedToolItem = Schema.Union([
41
46
  Schema.StructWithRest(Schema.Struct({
@@ -100,23 +105,23 @@ export const CompactionTrigger = Schema.Struct({ type: Schema.Literal("compactio
100
105
  const CheckpointBody = Schema.Struct({
101
106
  ...OpenAIResponsesBody.fields,
102
107
  input: Schema.Array(Schema.Union([OpenAIResponsesInputItem, CompactionTrigger])),
103
- store: Schema.Literal(false),
104
- prompt_cache_retention: optionalNull(Schema.String),
105
- prompt_cache_options: optionalNull(Schema.Struct({ mode: Schema.optional(Schema.String), ttl: Schema.optional(Schema.String) })),
106
108
  });
107
109
  const adapter = {
108
110
  id: ADAPTER,
109
111
  name: NAME,
110
112
  restoreHostedToolItem: (item) => (Schema.is(OpenAIResponsesHostedToolItem)(item) ? item : undefined),
111
113
  };
112
- // Only GPT-6 Astra accepts `configuration_update`, and never alongside automatic `context_management` compaction.
114
+ // GPT-6 Astra, Sol, and Luna accept `configuration_update` only in standard mode (not `reasoning.mode: "pro"` or
115
+ // `-pro` slugs), and never alongside automatic `context_management` compaction.
113
116
  const supportsEffortUpdates = (request) => {
114
117
  if (request.providerOptions?.contextManagement !== undefined)
115
118
  return false;
119
+ if (Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(request.http?.body?.reasoning))
120
+ return false;
116
121
  const override = request.model.compatibility?.supportsEffortUpdates;
117
122
  if (override !== undefined)
118
123
  return override;
119
- return /(?:^|\/)gpt-6-astra$/i.test(request.model.id);
124
+ return /(?:^|\/)gpt-6-(?:astra|sol|luna)$/i.test(request.model.id);
120
125
  };
121
126
  const nativeImageToolInput = (tool) => {
122
127
  const native = tool.native?.openai;
@@ -126,29 +131,30 @@ const nativeImageTool = (tool) => {
126
131
  const native = nativeImageToolInput(tool);
127
132
  return Schema.is(OpenAIResponsesImageGenerationTool)(native) ? native : undefined;
128
133
  };
129
- const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool, inputSchema) {
134
+ const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool) {
130
135
  const native = nativeImageToolInput(tool);
131
136
  if (native !== undefined) {
132
137
  if (Schema.is(OpenAIResponsesImageGenerationTool)(native))
133
138
  return native;
134
139
  return yield* ProviderShared.invalidRequest("OpenAI Responses image generation tool options are invalid");
135
140
  }
136
- return yield* OpenResponses.lowerTool(NAME, tool, inputSchema);
141
+ return yield* OpenResponses.lowerTool(NAME, tool);
137
142
  });
138
143
  // Native namespaces hold only function tools, so deeper levels flatten into
139
144
  // the leaf names the same way non-native protocols flatten the whole tree.
140
- const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool, model) {
145
+ const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool) {
141
146
  if (tool.type === "tool")
142
- return yield* lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, model));
147
+ return yield* lowerTool(tool);
143
148
  // OpenAI requires a namespace description; fall back to a generic one so a
144
149
  // missing description never blocks the request.
145
150
  return {
146
151
  type: "namespace",
147
152
  name: tool.name,
148
153
  description: tool.description ?? `Tools in the ${tool.name} namespace.`,
149
- tools: yield* Effect.forEach(ProviderShared.flattenTools(tool.tools), (leaf) => OpenResponses.lowerTool(NAME, leaf, ToolSchemaProjection.modelCompatibility(leaf.inputSchema, model))),
154
+ tools: yield* Effect.forEach(ProviderShared.flattenTools(tool.tools), (leaf) => OpenResponses.lowerTool(NAME, leaf)),
150
155
  };
151
156
  });
157
+ const lowerTools = (request) => Effect.forEach(request.tools, lowerToolEntry);
152
158
  const lowerToolChoice = (toolChoice, tools) => ProviderShared.matchToolChoice(NAME, toolChoice, {
153
159
  auto: () => "auto",
154
160
  none: () => "none",
@@ -166,9 +172,7 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request)
166
172
  ...(yield* OpenResponses.lowerConversation(updates.request, adapter)),
167
173
  ...OpenResponses.lowerGeneration(request, { ...options, reasoningEffort: updates.effort }),
168
174
  context_management: management?.map((edit) => ({ type: edit.type, compact_threshold: edit.compactThreshold })),
169
- tools: request.tools.length === 0
170
- ? undefined
171
- : yield* Effect.forEach(request.tools, (tool) => lowerToolEntry(tool, request.model)),
175
+ tools: request.tools.length === 0 ? undefined : yield* lowerTools(request),
172
176
  tool_choice: request.tools.length === 0
173
177
  ? undefined
174
178
  : (OpenResponses.allowedToolChoice(request) ??
@@ -178,25 +182,19 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request)
178
182
  const checkpointBody = {
179
183
  schema: CheckpointBody,
180
184
  from: Effect.fn("OpenAIResponses.checkpointBody")(function* (request) {
181
- const native = yield* fromRequest(LLMRequest.update(request, { toolChoice: undefined }));
182
185
  const overlay = request.http?.body;
183
186
  // Complete history is required for stateless replay and SSE recovery. Raw input overrides bypass that contract.
184
187
  if (overlay?.input !== undefined ||
185
188
  overlay?.previous_response_id !== undefined ||
186
189
  overlay?.conversation !== undefined)
187
190
  return yield* ProviderShared.invalidRequest("Trigger compaction requires complete canonical history, not an input or continuation override");
188
- return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(CheckpointBody))({
189
- ...mergeJsonRecords(native, overlay),
191
+ if (overlay?.stream !== undefined && overlay.stream !== true)
192
+ return yield* ProviderShared.invalidRequest("Trigger compaction requires a streamed response");
193
+ const native = yield* fromRequest(request);
194
+ return {
195
+ ...native,
190
196
  input: [...native.input, { type: "compaction_trigger" }],
191
- stream: true,
192
- store: false,
193
- parallel_tool_calls: true,
194
- tool_choice: undefined,
195
- context_management: undefined,
196
- text: undefined,
197
- max_output_tokens: undefined,
198
- max_tool_calls: undefined,
199
- });
197
+ };
200
198
  }),
201
199
  };
202
200
  const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item) {
@@ -267,7 +265,10 @@ export const transport = channelTransport({
267
265
  headers: (headers) => Headers.set(headers, "openai-beta", headers["openai-beta"] ?? WEBSOCKET_PROTOCOL_HEADER),
268
266
  });
269
267
  export const route = Route.make({
270
- compact: { endpoint: ResponsesCompaction.make(adapter), trigger: ResponsesCheckpoint.make(checkpointBody) },
268
+ compact: {
269
+ endpoint: ResponsesCompaction.make(adapter, lowerTools),
270
+ trigger: ResponsesCheckpoint.make(checkpointBody),
271
+ },
271
272
  id: ADAPTER,
272
273
  provider: "openai",
273
274
  providerMetadataKey: "openai",
@@ -5,13 +5,25 @@ import { SpeechModel, type SpeechRequestFor } from "../speech.js";
5
5
  import { SpeechStream } from "./utils/speech-stream.js";
6
6
  export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
7
7
  export declare const PATH = "/audio/speech";
8
- export type OpenAISpeechOptions = Record<string, unknown>;
8
+ /** `voice`, `instructions`, `speed`, and `format` are common request fields; other native body fields pass through. */
9
+ export type OpenAISpeechOptions = {
10
+ /** Defaults to `"sse"` in `stream` mode on models that support it; the merged value selects the response framing. */
11
+ readonly stream_format?: "sse" | "audio";
12
+ } & Record<string, unknown>;
9
13
  export type Request = SpeechRequestFor<OpenAISpeechOptions>;
10
14
  interface State extends SpeechStream.Audio {
11
15
  readonly done: boolean;
12
16
  readonly usage?: MediaUsage;
13
17
  }
14
18
  export declare const protocol: MediaProtocol.Streamed<Request, {
19
+ readonly id: string;
20
+ readonly type: "generation-queued";
21
+ readonly position?: number | undefined;
22
+ } | {
23
+ readonly id: string;
24
+ readonly type: "generation-progress";
25
+ readonly progress?: number | undefined;
26
+ } | {
15
27
  readonly type: "audio-delta";
16
28
  readonly chunk: Uint8Array<ArrayBufferLike>;
17
29
  } | {
@@ -63,6 +75,14 @@ export declare const protocol: MediaProtocol.Streamed<Request, {
63
75
  export declare const model: (input: MediaRoute.ModelInput) => SpeechModel<OpenAISpeechOptions>;
64
76
  export declare const OpenAISpeech: {
65
77
  readonly protocol: MediaProtocol.Streamed<Request, {
78
+ readonly id: string;
79
+ readonly type: "generation-queued";
80
+ readonly position?: number | undefined;
81
+ } | {
82
+ readonly id: string;
83
+ readonly type: "generation-progress";
84
+ readonly progress?: number | undefined;
85
+ } | {
66
86
  readonly type: "audio-delta";
67
87
  readonly chunk: Uint8Array<ArrayBufferLike>;
68
88
  } | {
@@ -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,13 +24,16 @@ 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
  // ---------------------------------------------------------------------------
33
31
  // `sse` is not supported for `tts-1` or `tts-1-hd`; those models stream the raw audio body instead.
34
32
  const supportsSse = (model) => !/^tts-1(-hd)?(-|$)/.test(model);
35
33
  const fromRequest = Effect.fn("OpenAISpeech.fromRequest")(function* (request) {
34
+ // Not in `unsupported`: that list would also reject `timestamps: false`, which asks for nothing.
35
+ if (request.timestamps === true)
36
+ return yield* route.unsupported("media.timestamps", `${route.name} does not return timestamps`);
36
37
  return MediaProtocol.json(mergeJsonRecords({
37
38
  model: request.model.id,
38
39
  input: request.text,
@@ -71,9 +72,9 @@ const onEvent = Effect.fn("OpenAISpeech.onEvent")(function* (state, frame) {
71
72
  });
72
73
  const finish = (state, context) => {
73
74
  if (isSse(context.body) && !state.done)
74
- return Effect.fail(MediaProtocol.incomplete(ADAPTER));
75
+ return Effect.fail(route.incomplete());
75
76
  const format = context.request.format ?? "mp3";
76
- return SpeechStream.finish(ADAPTER, state, {
77
+ return SpeechStream.finish(route, state, {
77
78
  ...(format === "pcm" ? SpeechStream.pcm("pcm_s16le", PCM_SAMPLE_RATE) : SpeechStream.container(format)),
78
79
  usage: state.usage,
79
80
  });
@@ -81,17 +82,15 @@ const finish = (state, context) => {
81
82
  // ---------------------------------------------------------------------------
82
83
  // 7. Protocol and route
83
84
  // ---------------------------------------------------------------------------
84
- export const protocol = MediaProtocol.stream({
85
- id: ADAPTER,
86
- name: NAME,
87
- unsupported: ["language", "timestamps"],
85
+ export const protocol = MediaProtocol.stream(route, {
86
+ unsupported: ["language"],
88
87
  body: { from: fromRequest },
89
88
  frames: (bytes, context) => (isSse(context.body) ? Framing.sse.frame(bytes) : bytes),
90
89
  initial: () => ({ chunks: [], done: false }),
91
90
  step: SpeechStream.step(onEvent),
92
91
  finish,
93
92
  });
94
- export const model = (input) => SpeechModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
93
+ export const model = (input) => SpeechModel.fromRoute({ protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
95
94
  export const OpenAISpeech = {
96
95
  protocol,
97
96
  model,
@@ -41,6 +41,10 @@ declare const Transcript: Schema.Struct<{
41
41
  readonly input_tokens: Schema.optional<Schema.Number>;
42
42
  readonly output_tokens: Schema.optional<Schema.Number>;
43
43
  readonly total_tokens: Schema.optional<Schema.Number>;
44
+ readonly input_token_details: Schema.optional<Schema.Struct<{
45
+ readonly audio_tokens: Schema.optional<Schema.Number>;
46
+ readonly text_tokens: Schema.optional<Schema.Number>;
47
+ }>>;
44
48
  }>, Schema.Struct<{
45
49
  readonly type: Schema.Literal<"duration">;
46
50
  readonly seconds: Schema.Number;