@opencode-ai/ai 0.0.0-next-15825 → 0.0.0-next-15830

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @opencode-ai/ai
2
2
 
3
- Schema-first LLM core for opencode. One typed request, response, event, and tool language; provider quirks live in adapters, not in calling code.
3
+ Schema-first AI primitives for opencode. Provider quirks live in adapters, not in calling code.
4
4
 
5
5
  ```ts
6
6
  import { Effect } from "effect"
@@ -24,6 +24,45 @@ const program = Effect.gen(function* () {
24
24
 
25
25
  Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
26
26
 
27
+ ## Image generation
28
+
29
+ Use `Image.generate` with an image model for direct asset generation:
30
+
31
+ ```ts
32
+ import { Image } from "@opencode-ai/ai"
33
+ import { OpenAI } from "@opencode-ai/ai/providers"
34
+
35
+ const program = Effect.gen(function* () {
36
+ const response = yield* Image.generate({
37
+ model: OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).image("gpt-image-2"),
38
+ prompt: "A robot tending a rooftop garden",
39
+ count: 2,
40
+ size: { width: 1024, height: 1024 },
41
+ providerOptions: { openai: { quality: "high", outputFormat: "webp" } },
42
+ })
43
+
44
+ return response.images // GeneratedImage[] with owned bytes or a provider URL
45
+ })
46
+ ```
47
+
48
+ Conversational image generation remains part of the LLM interaction. OpenAI Responses exposes it through its hosted image tool:
49
+
50
+ ```ts
51
+ const program = Effect.gen(function* () {
52
+ const response = yield* LLM.generate(
53
+ LLM.request({
54
+ model: OpenAI.configure({ apiKey }).responses("gpt-5"),
55
+ prompt: "Design a solarpunk rooftop garden, then show me.",
56
+ tools: [OpenAI.imageGeneration({ quality: "high" })],
57
+ }),
58
+ )
59
+
60
+ return response.message
61
+ })
62
+ ```
63
+
64
+ The hosted result is represented as a provider-executed tool call and tool result. Its image is a `file` content item with a data URI, so retaining `response.message` preserves the generated image for continuation.
65
+
27
66
  ## Public API
28
67
 
29
68
  - **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
@@ -32,6 +71,8 @@ Run `LLMClient.stream(request)` instead of `generate` when you want incremental
32
71
  - **`Model.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
33
72
  - **`LLMClient.prepare(request)`** — compile a request through protocol body construction, validation, and HTTP preparation without sending. Useful for inspection and testing.
34
73
  - **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
74
+ - **`Image.generate({...})`** — generate images through a provider-neutral image request and response model.
75
+ - **`ImageClient`** — Effect service and layer for image execution, parallel to `LLMClient`.
35
76
 
36
77
  ## Caching
37
78
 
@@ -182,7 +223,7 @@ Adding a new model or deployment is usually 5-15 lines using `Route.make({ proto
182
223
 
183
224
  ## Effect
184
225
 
185
- This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for runtime dispatch and import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
226
+ This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for LLM dispatch and `ImageClient.layer` for image dispatch, then import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
186
227
 
187
228
  ## See also
188
229
 
@@ -0,0 +1,19 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { RequestExecutor } from "./route/executor";
3
+ import type { ImageRequest, ImageResponse } from "./image";
4
+ import type { LLMError } from "./schema";
5
+ export type Execute = RequestExecutor.Interface["execute"];
6
+ export interface Interface {
7
+ readonly generate: (request: ImageRequest) => Effect.Effect<ImageResponse, LLMError>;
8
+ }
9
+ declare const Service_base: Context.ServiceClass<Service, "@opencode/ImageClient", Interface>;
10
+ export declare class Service extends Service_base {
11
+ }
12
+ export declare const generate: (request: ImageRequest) => Effect.Effect<ImageResponse, LLMError>;
13
+ export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
14
+ export declare const ImageClient: {
15
+ readonly Service: typeof Service;
16
+ readonly layer: Layer.Layer<Service, never, RequestExecutor.Service>;
17
+ readonly generate: (request: ImageRequest) => Effect.Effect<ImageResponse, LLMError>;
18
+ };
19
+ export {};
@@ -0,0 +1,19 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { RequestExecutor } from "./route/executor";
3
+ export class Service extends Context.Service()("@opencode/ImageClient") {
4
+ }
5
+ export const generate = (request) => Effect.gen(function* () {
6
+ const client = yield* Service;
7
+ return yield* client.generate(request);
8
+ });
9
+ export const layer = Layer.effect(Service, Effect.gen(function* () {
10
+ const executor = yield* RequestExecutor.Service;
11
+ return Service.of({
12
+ generate: (request) => request.model.route.generate(request, executor.execute),
13
+ });
14
+ }));
15
+ export const ImageClient = {
16
+ Service,
17
+ layer,
18
+ generate,
19
+ };
@@ -0,0 +1,78 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { HttpOptions, LLMError, ModelID, ProviderID, Usage } from "./schema";
3
+ import { type Execute as ImageExecute } from "./image-client";
4
+ export interface ImageRoute {
5
+ readonly id: string;
6
+ readonly generate: (request: ImageRequest, execute: ImageExecute) => Effect.Effect<ImageResponse, LLMError>;
7
+ }
8
+ export declare class ImageModel {
9
+ readonly id: ModelID;
10
+ readonly provider: ProviderID;
11
+ readonly route: ImageRoute;
12
+ readonly defaults?: ImageModelDefaults;
13
+ constructor(input: ImageModel.Input);
14
+ static make(input: ImageModel.MakeInput): ImageModel;
15
+ }
16
+ export declare namespace ImageModel {
17
+ interface Input {
18
+ readonly id: ModelID;
19
+ readonly provider: ProviderID;
20
+ readonly route: ImageRoute;
21
+ readonly defaults?: ImageModelDefaults;
22
+ }
23
+ interface MakeInput extends Omit<Input, "id" | "provider"> {
24
+ readonly id: string | ModelID;
25
+ readonly provider: string | ProviderID;
26
+ }
27
+ }
28
+ export interface ImageModelDefaults {
29
+ readonly providerOptions?: Record<string, Record<string, unknown>>;
30
+ readonly http?: HttpOptions;
31
+ }
32
+ export declare const ImageModelSchema: Schema.declare<ImageModel, ImageModel>;
33
+ export declare const ImageSize: Schema.Struct<{
34
+ readonly width: Schema.Int;
35
+ readonly height: Schema.Int;
36
+ }>;
37
+ export type ImageSize = Schema.Schema.Type<typeof ImageSize>;
38
+ declare const ImageRequest_base: Schema.Class<ImageRequest, Schema.Struct<{
39
+ readonly model: Schema.declare<ImageModel, ImageModel>;
40
+ readonly prompt: Schema.String;
41
+ readonly count: Schema.optional<Schema.Int>;
42
+ readonly size: Schema.optional<Schema.Struct<{
43
+ readonly width: Schema.Int;
44
+ readonly height: Schema.Int;
45
+ }>>;
46
+ readonly aspectRatio: Schema.optional<Schema.String>;
47
+ readonly seed: Schema.optional<Schema.Number>;
48
+ readonly providerOptions: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
49
+ readonly http: Schema.optional<typeof HttpOptions>;
50
+ readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
51
+ }>, {}>;
52
+ export declare class ImageRequest extends ImageRequest_base {
53
+ }
54
+ export type ImageRequestInput = Omit<ConstructorParameters<typeof ImageRequest>[0], "http"> & {
55
+ readonly http?: HttpOptions.Input;
56
+ };
57
+ declare const GeneratedImage_base: Schema.Class<GeneratedImage, Schema.Struct<{
58
+ readonly mediaType: Schema.String;
59
+ readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
60
+ readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
61
+ }>, {}>;
62
+ export declare class GeneratedImage extends GeneratedImage_base {
63
+ }
64
+ declare const ImageResponse_base: Schema.Class<ImageResponse, Schema.Struct<{
65
+ readonly images: Schema.$Array<typeof GeneratedImage>;
66
+ readonly usage: Schema.optional<typeof Usage>;
67
+ readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
68
+ }>, {}>;
69
+ export declare class ImageResponse extends ImageResponse_base {
70
+ get image(): GeneratedImage;
71
+ }
72
+ export declare const request: (input: ImageRequest | ImageRequestInput) => ImageRequest;
73
+ export declare const generate: (input: ImageRequest | ImageRequestInput) => Effect.Effect<ImageResponse, LLMError, never>;
74
+ export declare const Image: {
75
+ readonly request: (input: ImageRequest | ImageRequestInput) => ImageRequest;
76
+ readonly generate: (input: ImageRequest | ImageRequestInput) => Effect.Effect<ImageResponse, LLMError, never>;
77
+ };
78
+ export {};
package/dist/image.js ADDED
@@ -0,0 +1,77 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { HttpOptions, InvalidRequestReason, LLMError, ModelID, ProviderID, ProviderMetadata, Usage } from "./schema";
3
+ import { ImageClient } from "./image-client";
4
+ export class ImageModel {
5
+ id;
6
+ provider;
7
+ route;
8
+ defaults;
9
+ constructor(input) {
10
+ this.id = input.id;
11
+ this.provider = input.provider;
12
+ this.route = input.route;
13
+ this.defaults = input.defaults;
14
+ }
15
+ static make(input) {
16
+ return new ImageModel({
17
+ id: ModelID.make(input.id),
18
+ provider: ProviderID.make(input.provider),
19
+ route: input.route,
20
+ defaults: input.defaults,
21
+ });
22
+ }
23
+ }
24
+ export const ImageModelSchema = Schema.declare((value) => value instanceof ImageModel, {
25
+ expected: "Image.Model",
26
+ });
27
+ export const ImageSize = Schema.Struct({
28
+ width: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
29
+ height: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
30
+ }).annotate({ identifier: "Image.Size" });
31
+ export class ImageRequest extends Schema.Class("Image.Request")({
32
+ model: ImageModelSchema,
33
+ prompt: Schema.String,
34
+ count: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))),
35
+ size: Schema.optional(ImageSize),
36
+ aspectRatio: Schema.optional(Schema.String),
37
+ seed: Schema.optional(Schema.Number),
38
+ providerOptions: Schema.optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown))),
39
+ http: Schema.optional(HttpOptions),
40
+ metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
41
+ }) {
42
+ }
43
+ export class GeneratedImage extends Schema.Class("Image.Generated")({
44
+ mediaType: Schema.String,
45
+ data: Schema.Union([Schema.String, Schema.Uint8Array]),
46
+ providerMetadata: Schema.optional(ProviderMetadata),
47
+ }) {
48
+ }
49
+ export class ImageResponse extends Schema.Class("Image.Response")({
50
+ images: Schema.Array(GeneratedImage),
51
+ usage: Schema.optional(Usage),
52
+ providerMetadata: Schema.optional(ProviderMetadata),
53
+ }) {
54
+ get image() {
55
+ return this.images[0];
56
+ }
57
+ }
58
+ export const request = (input) => {
59
+ if (input instanceof ImageRequest)
60
+ return input;
61
+ return new ImageRequest({
62
+ ...input,
63
+ http: input.http === undefined ? undefined : HttpOptions.make(input.http),
64
+ });
65
+ };
66
+ export const generate = (input) => Effect.try({
67
+ try: () => request(input),
68
+ catch: (error) => new LLMError({
69
+ module: "Image",
70
+ method: "generate",
71
+ reason: new InvalidRequestReason({ message: error instanceof Error ? error.message : String(error) }),
72
+ }),
73
+ }).pipe(Effect.flatMap(ImageClient.generate));
74
+ export const Image = {
75
+ request,
76
+ generate,
77
+ };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export { LLMClient } from "./route/client";
2
+ export { ImageClient } from "./image-client";
2
3
  export { Auth } from "./route/auth";
3
4
  export { Provider } from "./provider";
4
5
  export { ProviderPackage } from "./provider-package";
5
6
  export { isContextOverflow, isContextOverflowFailure } from "./provider-error";
6
7
  export type { RouteModelInput, RouteRoutedModelInput, Interface as LLMClientShape, Service as LLMClientService, } from "./route/client";
7
8
  export * from "./schema";
9
+ export { GeneratedImage, ImageModel, ImageRequest, ImageResponse, ImageSize } from "./image";
10
+ export type { ImageModelDefaults, ImageRequestInput, ImageRoute } from "./image";
11
+ export { Image } from "./image";
8
12
  export { Tool, ToolFailure, toDefinitions } from "./tool";
9
13
  export { ToolRuntime } from "./tool-runtime";
10
14
  export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime";
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  export { LLMClient } from "./route/client";
2
+ export { ImageClient } from "./image-client";
2
3
  export { Auth } from "./route/auth";
3
4
  export { Provider } from "./provider";
4
5
  export { ProviderPackage } from "./provider-package";
5
6
  export { isContextOverflow, isContextOverflowFailure } from "./provider-error";
6
7
  export * from "./schema";
8
+ export { GeneratedImage, ImageModel, ImageRequest, ImageResponse, ImageSize } from "./image";
9
+ export { Image } from "./image";
7
10
  export { Tool, ToolFailure, toDefinitions } from "./tool";
8
11
  export { ToolRuntime } from "./tool-runtime";
9
12
  export * as LLM from "./llm";
@@ -2,6 +2,7 @@ export * as AnthropicMessages from "./anthropic-messages";
2
2
  export * as BedrockConverse from "./bedrock-converse";
3
3
  export * as Gemini from "./gemini";
4
4
  export * as OpenAIChat from "./openai-chat";
5
+ export * as OpenAIImages from "./openai-images";
5
6
  export * as OpenAICompatibleChat from "./openai-compatible-chat";
6
7
  export * as OpenAICompatibleResponses from "./openai-compatible-responses";
7
8
  export * as OpenAIResponses from "./openai-responses";
@@ -2,6 +2,7 @@ export * as AnthropicMessages from "./anthropic-messages";
2
2
  export * as BedrockConverse from "./bedrock-converse";
3
3
  export * as Gemini from "./gemini";
4
4
  export * as OpenAIChat from "./openai-chat";
5
+ export * as OpenAIImages from "./openai-images";
5
6
  export * as OpenAICompatibleChat from "./openai-compatible-chat";
6
7
  export * as OpenAICompatibleResponses from "./openai-compatible-responses";
7
8
  export * as OpenAIResponses from "./openai-responses";
@@ -60,7 +60,7 @@ export declare const route: Route<{
60
60
  readonly effort?: string | undefined;
61
61
  readonly summary?: "auto" | undefined;
62
62
  } | undefined;
63
- readonly tools?: readonly {
63
+ readonly tools?: readonly ({
64
64
  readonly type: "function";
65
65
  readonly name: string;
66
66
  readonly description: string;
@@ -68,11 +68,23 @@ export declare const route: Route<{
68
68
  readonly [x: string]: unknown;
69
69
  };
70
70
  readonly strict?: boolean | undefined;
71
- }[] | undefined;
71
+ } | {
72
+ readonly type: "image_generation";
73
+ readonly size?: string | undefined;
74
+ readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
75
+ readonly background?: "auto" | "opaque" | "transparent" | undefined;
76
+ readonly output_format?: "png" | "jpeg" | "webp" | undefined;
77
+ readonly output_compression?: number | undefined;
78
+ readonly action?: "generate" | "auto" | "edit" | undefined;
79
+ readonly input_fidelity?: "low" | "high" | undefined;
80
+ readonly partial_images?: number | undefined;
81
+ })[] | undefined;
72
82
  readonly temperature?: number | undefined;
73
83
  readonly tool_choice?: "required" | "none" | "auto" | import("effect/Schema").Struct.ReadonlySide<{
74
84
  readonly type: import("effect/Schema").tag<"function">;
75
85
  readonly name: import("effect/Schema").String;
86
+ }, "Type"> | import("effect/Schema").Struct.ReadonlySide<{
87
+ readonly type: import("effect/Schema").tag<"image_generation">;
76
88
  }, "Type"> | undefined;
77
89
  readonly top_p?: number | undefined;
78
90
  readonly store?: boolean | undefined;
@@ -0,0 +1,36 @@
1
+ import { Schema } from "effect";
2
+ import { ImageModel, type ImageModelDefaults } from "../image";
3
+ import { type Definition as AuthDefinition } from "../route/auth";
4
+ export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
5
+ export declare const PATH = "/images/generations";
6
+ export interface OpenAIImageOptions {
7
+ readonly quality?: "auto" | "low" | "medium" | "high";
8
+ readonly background?: "auto" | "opaque" | "transparent";
9
+ readonly moderation?: "auto" | "low";
10
+ readonly outputFormat?: "png" | "jpeg" | "webp";
11
+ readonly outputCompression?: number;
12
+ }
13
+ declare const OpenAIImageBody: Schema.Struct<{
14
+ readonly model: Schema.String;
15
+ readonly prompt: Schema.String;
16
+ readonly n: Schema.optional<Schema.Int>;
17
+ readonly size: Schema.optional<Schema.String>;
18
+ readonly quality: Schema.optional<Schema.Literals<readonly ["auto", "low", "medium", "high"]>>;
19
+ readonly background: Schema.optional<Schema.Literals<readonly ["auto", "opaque", "transparent"]>>;
20
+ readonly moderation: Schema.optional<Schema.Literals<readonly ["auto", "low"]>>;
21
+ readonly output_format: Schema.optional<Schema.Literals<readonly ["png", "jpeg", "webp"]>>;
22
+ readonly output_compression: Schema.optional<Schema.Int>;
23
+ }>;
24
+ export type OpenAIImageBody = Schema.Schema.Type<typeof OpenAIImageBody>;
25
+ export interface ModelInput {
26
+ readonly id: string;
27
+ readonly auth: AuthDefinition;
28
+ readonly baseURL?: string;
29
+ readonly headers?: Record<string, string>;
30
+ readonly defaults?: ImageModelDefaults;
31
+ }
32
+ export declare const model: (input: ModelInput) => ImageModel;
33
+ export declare const OpenAIImages: {
34
+ readonly model: (input: ModelInput) => ImageModel;
35
+ };
36
+ export {};
@@ -0,0 +1,145 @@
1
+ import { Effect, Encoding, Schema } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { ImageModel, GeneratedImage, ImageResponse, } from "../image";
4
+ import { Auth } from "../route/auth";
5
+ import { InvalidProviderOutputReason, LLMError, Usage, mergeHttpOptions, mergeJsonRecords } from "../schema";
6
+ import { ProviderShared } from "./shared";
7
+ import { OpenAIImage } from "./utils/openai-image";
8
+ const ADAPTER = "openai-images";
9
+ export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
10
+ export const PATH = "/images/generations";
11
+ const OpenAIImageBody = Schema.Struct({
12
+ model: Schema.String,
13
+ prompt: Schema.String,
14
+ n: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))),
15
+ size: Schema.optional(Schema.String),
16
+ quality: Schema.optional(Schema.Literals(["auto", "low", "medium", "high"])),
17
+ background: Schema.optional(Schema.Literals(["auto", "opaque", "transparent"])),
18
+ moderation: Schema.optional(Schema.Literals(["auto", "low"])),
19
+ output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
20
+ output_compression: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))),
21
+ });
22
+ const OpenAIImageResponse = Schema.Struct({
23
+ data: Schema.Array(Schema.Struct({
24
+ b64_json: Schema.optional(Schema.String),
25
+ url: Schema.optional(Schema.String),
26
+ revised_prompt: Schema.optional(Schema.String),
27
+ })),
28
+ output_format: Schema.optional(Schema.String),
29
+ usage: Schema.optional(Schema.Struct({
30
+ input_tokens: Schema.optional(Schema.Number),
31
+ output_tokens: Schema.optional(Schema.Number),
32
+ total_tokens: Schema.optional(Schema.Number),
33
+ input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
34
+ output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
35
+ })),
36
+ });
37
+ const providerOptions = (request) => ({
38
+ ...request.model.defaults?.providerOptions?.openai,
39
+ ...request.providerOptions?.openai,
40
+ });
41
+ const body = (request) => {
42
+ const options = providerOptions(request);
43
+ return {
44
+ model: request.model.id,
45
+ prompt: request.prompt,
46
+ n: request.count,
47
+ size: request.size === undefined ? undefined : `${request.size.width}x${request.size.height}`,
48
+ quality: options.quality,
49
+ background: options.background,
50
+ moderation: options.moderation,
51
+ output_format: options.outputFormat,
52
+ output_compression: options.outputCompression,
53
+ };
54
+ };
55
+ const invalidOutput = (message) => new LLMError({
56
+ module: ADAPTER,
57
+ method: "generate",
58
+ reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
59
+ });
60
+ const applyQuery = (url, query) => {
61
+ if (!query)
62
+ return url;
63
+ const next = new URL(url);
64
+ Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
65
+ return next.toString();
66
+ };
67
+ const PROTOCOL_BODY_FIELDS = new Set([
68
+ "model",
69
+ "prompt",
70
+ "n",
71
+ "size",
72
+ "quality",
73
+ "background",
74
+ "moderation",
75
+ "output_format",
76
+ "output_compression",
77
+ ]);
78
+ const bodyWithOverlay = Effect.fn("OpenAIImages.bodyWithOverlay")(function* (imageBody, overlay) {
79
+ if (!overlay)
80
+ return imageBody;
81
+ const reserved = Object.keys(overlay).filter((key) => PROTOCOL_BODY_FIELDS.has(key));
82
+ if (reserved.length > 0)
83
+ return yield* ProviderShared.invalidRequest(`http.body cannot overlay protocol-owned field(s): ${reserved.join(", ")}`);
84
+ return mergeJsonRecords(imageBody, overlay) ?? imageBody;
85
+ });
86
+ export const model = (input) => {
87
+ const route = {
88
+ id: ADAPTER,
89
+ generate: Effect.fn("OpenAIImages.generate")(function* (request, execute) {
90
+ if (request.aspectRatio !== undefined)
91
+ return yield* ProviderShared.invalidRequest("OpenAI Images does not support the common aspectRatio option");
92
+ if (request.seed !== undefined)
93
+ return yield* ProviderShared.invalidRequest("OpenAI Images does not support the common seed option");
94
+ const requestBody = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenAIImageBody))(body(request));
95
+ const http = mergeHttpOptions(request.model.defaults?.http, request.http);
96
+ const overlaidBody = yield* bodyWithOverlay(requestBody, http?.body);
97
+ const text = ProviderShared.encodeJson(overlaidBody);
98
+ const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${PATH}`, http?.query);
99
+ const headers = yield* Auth.toEffect(input.auth)({
100
+ request,
101
+ method: "POST",
102
+ url,
103
+ body: text,
104
+ headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
105
+ });
106
+ const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
107
+ const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the OpenAI Images response")));
108
+ const decoded = yield* Schema.decodeUnknownEffect(OpenAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("OpenAI Images returned an invalid response")));
109
+ const format = decoded.output_format ?? providerOptions(request).outputFormat ?? "png";
110
+ const images = yield* Effect.forEach(decoded.data, (item, index) => {
111
+ if (item.b64_json)
112
+ return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() => invalidOutput(`OpenAI Images result ${index} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
113
+ mediaType: `image/${format}`,
114
+ data,
115
+ providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
116
+ })));
117
+ if (item.url)
118
+ return Effect.succeed(new GeneratedImage({
119
+ mediaType: `image/${format}`,
120
+ data: item.url,
121
+ providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
122
+ }));
123
+ return Effect.fail(invalidOutput(`OpenAI Images result ${index} has neither image data nor a URL`));
124
+ });
125
+ if (images.length === 0)
126
+ return yield* invalidOutput("OpenAI Images returned no images");
127
+ return new ImageResponse({
128
+ images,
129
+ usage: decoded.usage === undefined
130
+ ? undefined
131
+ : new Usage({
132
+ inputTokens: decoded.usage.input_tokens,
133
+ outputTokens: decoded.usage.output_tokens,
134
+ totalTokens: decoded.usage.total_tokens,
135
+ providerMetadata: { openai: decoded.usage },
136
+ }),
137
+ providerMetadata: { openai: { outputFormat: format } },
138
+ });
139
+ }),
140
+ };
141
+ return ImageModel.make({ id: input.id, provider: "openai", route, defaults: input.defaults });
142
+ };
143
+ export const OpenAIImages = {
144
+ model,
145
+ };