@opencode/ai 2.0.14 → 2.0.16
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 +399 -56
- package/dist/experimental/evaluation-client.d.ts +1 -1
- package/dist/experimental/evaluation-client.js +39 -3
- package/dist/experimental/evaluation.d.ts +4 -4
- package/dist/experimental/evaluation.js +2 -2
- package/dist/experimental/system-one.d.ts +3 -3
- package/dist/experimental/system-one.js +40 -51
- package/dist/generation.d.ts +83 -0
- package/dist/generation.js +113 -0
- package/dist/image-client.d.ts +16 -7
- package/dist/image-client.js +29 -13
- package/dist/image.d.ts +1410 -81
- package/dist/image.js +97 -67
- package/dist/index.d.ts +17 -2
- package/dist/index.js +12 -1
- package/dist/llm.d.ts +9 -1
- package/dist/media-model.d.ts +44 -0
- package/dist/media-model.js +49 -0
- package/dist/media.d.ts +213 -0
- package/dist/media.js +227 -0
- package/dist/promise.d.ts +974 -0
- package/dist/promise.js +81 -0
- package/dist/protocols/alibaba-chat.d.ts +12 -0
- package/dist/protocols/alibaba-responses.d.ts +2 -2
- package/dist/protocols/anthropic-messages.js +8 -19
- package/dist/protocols/assemblyai-transcription.d.ts +40 -0
- package/dist/protocols/assemblyai-transcription.js +138 -0
- package/dist/protocols/bedrock-converse.d.ts +4 -4
- package/dist/protocols/bedrock-converse.js +6 -17
- package/dist/protocols/bfl-images.d.ts +32 -0
- package/dist/protocols/bfl-images.js +153 -0
- package/dist/protocols/cartesia-speech.d.ts +127 -0
- package/dist/protocols/cartesia-speech.js +126 -0
- package/dist/protocols/deepgram-speech.d.ts +119 -0
- package/dist/protocols/deepgram-speech.js +92 -0
- package/dist/protocols/deepgram-transcription.d.ts +25 -0
- package/dist/protocols/deepgram-transcription.js +129 -0
- package/dist/protocols/elevenlabs-speech.d.ts +122 -0
- package/dist/protocols/elevenlabs-speech.js +115 -0
- package/dist/protocols/fal-images.d.ts +24 -0
- package/dist/protocols/fal-images.js +114 -0
- package/dist/protocols/fal-video.d.ts +29 -0
- package/dist/protocols/fal-video.js +88 -0
- package/dist/protocols/gemini.d.ts +30 -9
- package/dist/protocols/gemini.js +45 -35
- package/dist/protocols/google-images.d.ts +9 -21
- package/dist/protocols/google-images.js +158 -133
- package/dist/protocols/google-speech.d.ts +130 -0
- package/dist/protocols/google-speech.js +84 -0
- package/dist/protocols/google-transcription.d.ts +173 -0
- package/dist/protocols/google-transcription.js +138 -0
- package/dist/protocols/google-video.d.ts +26 -0
- package/dist/protocols/google-video.js +158 -0
- package/dist/protocols/meta-images.d.ts +7 -12
- package/dist/protocols/meta-images.js +85 -66
- package/dist/protocols/meta-responses.d.ts +4 -4
- package/dist/protocols/meta-responses.js +1 -1
- package/dist/protocols/mistral-chat.js +7 -6
- package/dist/protocols/open-responses.d.ts +17 -9
- package/dist/protocols/open-responses.js +24 -14
- package/dist/protocols/openai-chat.d.ts +118 -1
- package/dist/protocols/openai-chat.js +125 -44
- package/dist/protocols/openai-compatible-chat.d.ts +12 -0
- package/dist/protocols/openai-compatible-responses.d.ts +2 -2
- package/dist/protocols/openai-images.d.ts +128 -18
- package/dist/protocols/openai-images.js +177 -154
- package/dist/protocols/openai-responses.d.ts +15 -15
- package/dist/protocols/openai-responses.js +5 -6
- package/dist/protocols/openai-speech.d.ts +116 -0
- package/dist/protocols/openai-speech.js +98 -0
- package/dist/protocols/openai-transcription.d.ts +207 -0
- package/dist/protocols/openai-transcription.js +190 -0
- package/dist/protocols/replicate-images.d.ts +28 -0
- package/dist/protocols/replicate-images.js +133 -0
- package/dist/protocols/runway-video.d.ts +38 -0
- package/dist/protocols/runway-video.js +146 -0
- package/dist/protocols/shared.d.ts +27 -17
- package/dist/protocols/shared.js +52 -35
- package/dist/protocols/stability-images.d.ts +38 -0
- package/dist/protocols/stability-images.js +148 -0
- package/dist/protocols/utils/bedrock-media.d.ts +2 -3
- package/dist/protocols/utils/bedrock-media.js +4 -4
- package/dist/protocols/utils/fal-queue.d.ts +28 -0
- package/dist/protocols/utils/fal-queue.js +69 -0
- package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
- package/dist/protocols/utils/gemini-generate-content.js +65 -0
- package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
- package/dist/protocols/utils/gemini-json-schema.js +76 -0
- package/dist/protocols/utils/media-input.d.ts +18 -0
- package/dist/protocols/utils/media-input.js +35 -0
- package/dist/protocols/utils/responses-compaction.js +6 -5
- package/dist/protocols/utils/speech-stream.d.ts +49 -0
- package/dist/protocols/utils/speech-stream.js +67 -0
- package/dist/protocols/utils/tool-schema.d.ts +2 -2
- package/dist/protocols/utils/tool-schema.js +40 -17
- package/dist/protocols/utils/tool-stream.d.ts +27 -3
- package/dist/protocols/xai-images.d.ts +9 -15
- package/dist/protocols/xai-images.js +75 -84
- package/dist/protocols/xai-responses.d.ts +2 -2
- package/dist/protocols/xai-video.d.ts +34 -0
- package/dist/protocols/xai-video.js +147 -0
- package/dist/protocols/zai-chat.d.ts +13 -1
- package/dist/protocols/zai-images.d.ts +9 -13
- package/dist/protocols/zai-images.js +59 -57
- package/dist/provider-error.js +3 -0
- package/dist/providers/alibaba.d.ts +14 -2
- package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
- package/dist/providers/amazon-bedrock.d.ts +2 -2
- package/dist/providers/assemblyai.d.ts +25 -0
- package/dist/providers/assemblyai.js +29 -0
- package/dist/providers/azure.d.ts +18 -6
- package/dist/providers/baseten.d.ts +24 -0
- package/dist/providers/black-forest-labs.d.ts +25 -0
- package/dist/providers/black-forest-labs.js +28 -0
- package/dist/providers/cartesia.d.ts +24 -0
- package/dist/providers/cartesia.js +22 -0
- package/dist/providers/cerebras.d.ts +24 -0
- package/dist/providers/cerebras.js +6 -1
- package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
- package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
- package/dist/providers/deepgram.d.ts +29 -0
- package/dist/providers/deepgram.js +31 -0
- package/dist/providers/deepinfra.d.ts +24 -0
- package/dist/providers/deepinfra.js +6 -1
- package/dist/providers/deepseek.d.ts +24 -0
- package/dist/providers/elevenlabs.d.ts +24 -0
- package/dist/providers/elevenlabs.js +28 -0
- package/dist/providers/fal.d.ts +29 -0
- package/dist/providers/fal.js +33 -0
- package/dist/providers/fireworks.d.ts +24 -0
- package/dist/providers/google-vertex-chat.d.ts +12 -0
- package/dist/providers/google-vertex-responses.d.ts +2 -2
- package/dist/providers/google-vertex.d.ts +10 -3
- package/dist/providers/google.d.ts +25 -3
- package/dist/providers/google.js +11 -2
- package/dist/providers/groq.d.ts +24 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +10 -0
- package/dist/providers/meta.d.ts +14 -2
- package/dist/providers/minimax.d.ts +14 -2
- package/dist/providers/moonshot.d.ts +14 -2
- package/dist/providers/moonshot.js +3 -3
- package/dist/providers/openai-compatible-responses.d.ts +2 -2
- package/dist/providers/openai-compatible.d.ts +12 -0
- package/dist/providers/openai.d.ts +25 -3
- package/dist/providers/openai.js +10 -1
- package/dist/providers/openrouter.d.ts +67 -0
- package/dist/providers/openrouter.js +13 -1
- package/dist/providers/replicate.d.ts +25 -0
- package/dist/providers/replicate.js +22 -0
- package/dist/providers/runway.d.ts +24 -0
- package/dist/providers/runway.js +22 -0
- package/dist/providers/stability.d.ts +28 -0
- package/dist/providers/stability.js +23 -0
- package/dist/providers/togetherai.d.ts +24 -0
- package/dist/providers/vercel-ai-gateway.d.ts +41 -0
- package/dist/providers/vercel-ai-gateway.js +85 -0
- package/dist/providers/xai.d.ts +17 -0
- package/dist/providers/xai.js +5 -2
- package/dist/providers/zai-coding-plan.d.ts +15 -3
- package/dist/providers/zai.d.ts +13 -1
- package/dist/route/auth.d.ts +4 -1
- package/dist/route/auth.js +6 -0
- package/dist/route/client.d.ts +9 -1
- package/dist/route/endpoint.d.ts +10 -10
- package/dist/route/executor-service.d.ts +12 -0
- package/dist/route/executor-service.js +3 -0
- package/dist/route/executor.d.ts +4 -9
- package/dist/route/executor.js +3 -3
- package/dist/route/framing.d.ts +5 -1
- package/dist/route/framing.js +9 -0
- package/dist/route/index.d.ts +2 -0
- package/dist/route/index.js +2 -0
- package/dist/route/media-protocol.d.ts +158 -0
- package/dist/route/media-protocol.js +96 -0
- package/dist/route/media.d.ts +97 -0
- package/dist/route/media.js +236 -0
- package/dist/schema/errors.d.ts +13 -3
- package/dist/schema/errors.js +7 -0
- package/dist/schema/events.d.ts +557 -40
- package/dist/schema/events.js +35 -2
- package/dist/schema/messages.d.ts +95 -8
- package/dist/schema/messages.js +8 -6
- package/dist/schema/options.d.ts +6 -3
- package/dist/schema/options.js +6 -2
- package/dist/speech-client.d.ts +21 -0
- package/dist/speech-client.js +25 -0
- package/dist/speech.d.ts +1150 -0
- package/dist/speech.js +119 -0
- package/dist/testing.d.ts +72 -8
- package/dist/transcription-client.d.ts +28 -0
- package/dist/transcription-client.js +44 -0
- package/dist/transcription.d.ts +1504 -0
- package/dist/transcription.js +133 -0
- package/dist/utils/bytes.d.ts +1 -0
- package/dist/utils/bytes.js +10 -0
- package/dist/utils/media-type.d.ts +7 -0
- package/dist/utils/media-type.js +70 -0
- package/dist/utils/sanitize.js +3 -1
- package/dist/video-client.d.ts +28 -0
- package/dist/video-client.js +40 -0
- package/dist/video.d.ts +1359 -0
- package/dist/video.js +119 -0
- package/package.json +7 -3
- package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
- package/dist/protocols/utils/gemini-tool-schema.js +0 -103
- package/dist/protocols/utils/image-input.d.ts +0 -21
- package/dist/protocols/utils/image-input.js +0 -20
|
@@ -1,32 +1,142 @@
|
|
|
1
|
-
import { ImageModel, type
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ImageModel, type ImageRequestFor } from "../image.js";
|
|
2
|
+
import { Media } from "../media.js";
|
|
3
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
|
+
import { MediaRoute } from "../route/media.js";
|
|
5
|
+
import { type MediaUsage } from "../schema/index.js";
|
|
4
6
|
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
5
7
|
export declare const PATH = "/images/generations";
|
|
6
8
|
export declare const EDIT_PATH = "/images/edits";
|
|
7
9
|
export type OpenAIImageString<Known extends string> = Known | (string & {});
|
|
10
|
+
/** Provider-native options. Common fields (`n`, `size`, `format`, `images`, `mask`) live on the request. */
|
|
8
11
|
export type OpenAIImageOptions = {
|
|
9
|
-
readonly mask?: ImageInput;
|
|
10
|
-
readonly n?: number;
|
|
11
|
-
readonly size?: OpenAIImageString<"auto" | "256x256" | "512x512" | "1024x1024" | "1536x1024" | "1024x1536" | "1792x1024" | "1024x1792">;
|
|
12
12
|
readonly quality?: OpenAIImageString<"auto" | "low" | "medium" | "high" | "standard" | "hd">;
|
|
13
13
|
readonly background?: OpenAIImageString<"auto" | "opaque" | "transparent">;
|
|
14
14
|
readonly moderation?: OpenAIImageString<"auto" | "low">;
|
|
15
|
-
readonly outputFormat?: OpenAIImageString<"png" | "jpeg" | "webp">;
|
|
16
15
|
readonly outputCompression?: number;
|
|
16
|
+
/** Previews sent before the final image when streaming (default 2); ignored by `Image.generate`. */
|
|
17
|
+
readonly partialImages?: number;
|
|
17
18
|
} & Record<string, unknown>;
|
|
18
|
-
export type
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export type Request = ImageRequestFor<OpenAIImageOptions>;
|
|
20
|
+
/** `generate` reads the whole JSON response as one frame, with the requested format for responses that omit it. */
|
|
21
|
+
type Frame = string | {
|
|
22
|
+
readonly document: string;
|
|
23
|
+
readonly requested: string | undefined;
|
|
21
24
|
};
|
|
22
|
-
|
|
23
|
-
readonly
|
|
24
|
-
readonly
|
|
25
|
-
readonly
|
|
26
|
-
readonly headers?: Record<string, string>;
|
|
27
|
-
readonly http?: HttpOptions;
|
|
25
|
+
interface State {
|
|
26
|
+
readonly completed: number;
|
|
27
|
+
readonly format?: string;
|
|
28
|
+
readonly usage?: MediaUsage;
|
|
28
29
|
}
|
|
29
|
-
export declare const
|
|
30
|
+
export declare const protocol: MediaProtocol.Streamed<Request, {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly type: "generation-queued";
|
|
33
|
+
readonly position?: number | undefined;
|
|
34
|
+
} | {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly type: "generation-progress";
|
|
37
|
+
readonly progress?: number | undefined;
|
|
38
|
+
} | {
|
|
39
|
+
readonly type: "image-partial";
|
|
40
|
+
readonly index: number;
|
|
41
|
+
readonly image: Media.Asset;
|
|
42
|
+
} | {
|
|
43
|
+
readonly type: "image";
|
|
44
|
+
readonly index: number;
|
|
45
|
+
readonly image: Media.Asset;
|
|
46
|
+
} | {
|
|
47
|
+
readonly type: "finish";
|
|
48
|
+
readonly providerMetadata?: {
|
|
49
|
+
readonly [x: string]: {
|
|
50
|
+
readonly [x: string]: unknown;
|
|
51
|
+
};
|
|
52
|
+
} | undefined;
|
|
53
|
+
readonly usage?: {
|
|
54
|
+
readonly type: "tokens";
|
|
55
|
+
readonly input?: number | undefined;
|
|
56
|
+
readonly output?: number | undefined;
|
|
57
|
+
readonly total?: number | undefined;
|
|
58
|
+
readonly details?: {
|
|
59
|
+
readonly [x: string]: unknown;
|
|
60
|
+
} | undefined;
|
|
61
|
+
} | {
|
|
62
|
+
readonly type: "seconds";
|
|
63
|
+
readonly seconds: number;
|
|
64
|
+
} | {
|
|
65
|
+
readonly type: "characters";
|
|
66
|
+
readonly characters: number;
|
|
67
|
+
} | {
|
|
68
|
+
readonly type: "credits";
|
|
69
|
+
readonly credits: number;
|
|
70
|
+
} | {
|
|
71
|
+
readonly type: "compute";
|
|
72
|
+
readonly seconds: number;
|
|
73
|
+
} | undefined;
|
|
74
|
+
readonly notices?: readonly {
|
|
75
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
76
|
+
readonly message: string;
|
|
77
|
+
readonly providerMetadata?: {
|
|
78
|
+
readonly [x: string]: {
|
|
79
|
+
readonly [x: string]: unknown;
|
|
80
|
+
};
|
|
81
|
+
} | undefined;
|
|
82
|
+
}[] | undefined;
|
|
83
|
+
}, Frame, State>;
|
|
84
|
+
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<OpenAIImageOptions>;
|
|
30
85
|
export declare const OpenAIImages: {
|
|
31
|
-
readonly
|
|
86
|
+
readonly protocol: MediaProtocol.Streamed<Request, {
|
|
87
|
+
readonly id: string;
|
|
88
|
+
readonly type: "generation-queued";
|
|
89
|
+
readonly position?: number | undefined;
|
|
90
|
+
} | {
|
|
91
|
+
readonly id: string;
|
|
92
|
+
readonly type: "generation-progress";
|
|
93
|
+
readonly progress?: number | undefined;
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: "image-partial";
|
|
96
|
+
readonly index: number;
|
|
97
|
+
readonly image: Media.Asset;
|
|
98
|
+
} | {
|
|
99
|
+
readonly type: "image";
|
|
100
|
+
readonly index: number;
|
|
101
|
+
readonly image: Media.Asset;
|
|
102
|
+
} | {
|
|
103
|
+
readonly type: "finish";
|
|
104
|
+
readonly providerMetadata?: {
|
|
105
|
+
readonly [x: string]: {
|
|
106
|
+
readonly [x: string]: unknown;
|
|
107
|
+
};
|
|
108
|
+
} | undefined;
|
|
109
|
+
readonly usage?: {
|
|
110
|
+
readonly type: "tokens";
|
|
111
|
+
readonly input?: number | undefined;
|
|
112
|
+
readonly output?: number | undefined;
|
|
113
|
+
readonly total?: number | undefined;
|
|
114
|
+
readonly details?: {
|
|
115
|
+
readonly [x: string]: unknown;
|
|
116
|
+
} | undefined;
|
|
117
|
+
} | {
|
|
118
|
+
readonly type: "seconds";
|
|
119
|
+
readonly seconds: number;
|
|
120
|
+
} | {
|
|
121
|
+
readonly type: "characters";
|
|
122
|
+
readonly characters: number;
|
|
123
|
+
} | {
|
|
124
|
+
readonly type: "credits";
|
|
125
|
+
readonly credits: number;
|
|
126
|
+
} | {
|
|
127
|
+
readonly type: "compute";
|
|
128
|
+
readonly seconds: number;
|
|
129
|
+
} | undefined;
|
|
130
|
+
readonly notices?: readonly {
|
|
131
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
132
|
+
readonly message: string;
|
|
133
|
+
readonly providerMetadata?: {
|
|
134
|
+
readonly [x: string]: {
|
|
135
|
+
readonly [x: string]: unknown;
|
|
136
|
+
};
|
|
137
|
+
} | undefined;
|
|
138
|
+
}[] | undefined;
|
|
139
|
+
}, Frame, State>;
|
|
140
|
+
readonly model: (input: MediaRoute.ModelInput) => ImageModel<OpenAIImageOptions>;
|
|
32
141
|
};
|
|
142
|
+
export {};
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
import { Effect,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { ImageFinishEvent, ImageModel, ImageOutputEvent, ImagePartialEvent, } from "../image.js";
|
|
3
|
+
import { Media } from "../media.js";
|
|
4
|
+
import { Framing } from "../route/framing.js";
|
|
5
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
6
|
+
import { MediaRoute } from "../route/media.js";
|
|
7
|
+
import { ProviderID, mergeJsonRecords } from "../schema/index.js";
|
|
6
8
|
import { ProviderShared } from "./shared.js";
|
|
7
|
-
import {
|
|
8
|
-
import { OpenAIImage } from "./utils/openai-image.js";
|
|
9
|
+
import { MediaInput } from "./utils/media-input.js";
|
|
9
10
|
const ADAPTER = "openai-images";
|
|
11
|
+
const NAME = "OpenAI Images";
|
|
12
|
+
const PROVIDER = ProviderID.make("openai");
|
|
10
13
|
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
11
14
|
export const PATH = "/images/generations";
|
|
12
15
|
export const EDIT_PATH = "/images/edits";
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// 2. Response schema
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const Usage = Schema.Struct({
|
|
20
|
+
input_tokens: Schema.optional(Schema.Number),
|
|
21
|
+
output_tokens: Schema.optional(Schema.Number),
|
|
22
|
+
total_tokens: Schema.optional(Schema.Number),
|
|
23
|
+
input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
24
|
+
output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
25
|
+
});
|
|
13
26
|
const OpenAIImageResponse = Schema.Struct({
|
|
14
27
|
data: Schema.Array(Schema.Struct({
|
|
15
28
|
b64_json: Schema.optional(Schema.String),
|
|
@@ -17,167 +30,177 @@ const OpenAIImageResponse = Schema.Struct({
|
|
|
17
30
|
revised_prompt: Schema.optional(Schema.String),
|
|
18
31
|
})),
|
|
19
32
|
output_format: Schema.optional(Schema.String),
|
|
20
|
-
usage: Schema.optional(
|
|
21
|
-
input_tokens: Schema.optional(Schema.Number),
|
|
22
|
-
output_tokens: Schema.optional(Schema.Number),
|
|
23
|
-
total_tokens: Schema.optional(Schema.Number),
|
|
24
|
-
input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
25
|
-
output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
26
|
-
})),
|
|
33
|
+
usage: Schema.optional(Usage),
|
|
27
34
|
});
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// 3. Streaming event schema
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
const StreamEvent = Schema.Union([
|
|
39
|
+
Schema.Struct({
|
|
40
|
+
type: Schema.Literals(["image_generation.partial_image", "image_edit.partial_image"]),
|
|
41
|
+
b64_json: Schema.String,
|
|
42
|
+
partial_image_index: Schema.Number,
|
|
43
|
+
output_format: Schema.String,
|
|
44
|
+
}),
|
|
45
|
+
Schema.Struct({
|
|
46
|
+
type: Schema.Literals(["image_generation.completed", "image_edit.completed"]),
|
|
47
|
+
b64_json: Schema.String,
|
|
48
|
+
output_format: Schema.String,
|
|
49
|
+
usage: Schema.optional(Usage),
|
|
50
|
+
}),
|
|
51
|
+
]);
|
|
52
|
+
const decodeEvent = MediaProtocol.decodeFrame(ADAPTER, NAME, StreamEvent);
|
|
53
|
+
const decodeDocument = Schema.decodeUnknownEffect(Schema.fromJsonString(OpenAIImageResponse));
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// 5. Request body construction
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
/** Multipart field names the route owns; `http.body` overlays cannot smuggle replacements for them. */
|
|
58
|
+
const RESERVED_FORM_FIELDS = new Set(["model", "prompt", "image", "image[]", "images", "mask"]);
|
|
28
59
|
const nativeOptions = (options) => {
|
|
29
60
|
if (!options)
|
|
30
61
|
return undefined;
|
|
31
|
-
const {
|
|
32
|
-
return {
|
|
33
|
-
output_format: outputFormat,
|
|
34
|
-
output_compression: outputCompression,
|
|
35
|
-
...native,
|
|
36
|
-
};
|
|
62
|
+
const { outputCompression, partialImages: _, ...native } = options;
|
|
63
|
+
return { output_compression: outputCompression, ...native };
|
|
37
64
|
};
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
const streamOptions = (request) => {
|
|
66
|
+
if (request.mode !== "stream")
|
|
67
|
+
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
|
+
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
|
+
}));
|
|
82
|
+
return Effect.succeed({ stream: true, partial_images: request.providerOptions?.partialImages ?? 2 });
|
|
44
83
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (multipartMask !== undefined)
|
|
88
|
-
form.append("mask", imageBlob(multipartMask.data, multipartMask.mediaType), "mask");
|
|
89
|
-
const headers = yield* Auth.toEffect(input.auth)({
|
|
90
|
-
request,
|
|
91
|
-
method: "POST",
|
|
92
|
-
url,
|
|
93
|
-
body: "[multipart/form-data]",
|
|
94
|
-
headers: Headers.remove(Headers.fromInput({ ...input.headers, ...http?.headers }), "content-type"),
|
|
95
|
-
});
|
|
96
|
-
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyFormData(form)));
|
|
97
|
-
return yield* parseResponse(response, request.options, http?.body);
|
|
98
|
-
}
|
|
99
|
-
const references = sourceImages.map((image) => {
|
|
100
|
-
if (image.type === "bytes")
|
|
101
|
-
return { image_url: ImageInputs.dataUrl(image) };
|
|
102
|
-
if (image.type === "url")
|
|
103
|
-
return { image_url: image.url };
|
|
104
|
-
if (image.type === "file-id")
|
|
105
|
-
return { file_id: image.id };
|
|
106
|
-
return undefined;
|
|
107
|
-
});
|
|
108
|
-
if (references.some((image) => image === undefined))
|
|
109
|
-
return yield* ImageInputs.invalid("OpenAI Images accepts image URLs, data URLs, bytes, and file IDs");
|
|
110
|
-
const maskReference = mask === undefined
|
|
111
|
-
? undefined
|
|
112
|
-
: mask.type === "bytes"
|
|
113
|
-
? { image_url: ImageInputs.dataUrl(mask) }
|
|
114
|
-
: mask.type === "url"
|
|
115
|
-
? { image_url: mask.url }
|
|
116
|
-
: mask.type === "file-id"
|
|
117
|
-
? { file_id: mask.id }
|
|
118
|
-
: undefined;
|
|
119
|
-
if (mask !== undefined && maskReference === undefined)
|
|
120
|
-
return yield* ImageInputs.invalid("OpenAI Images accepts masks as URLs, data URLs, bytes, or file IDs");
|
|
121
|
-
const requestBody = mergeJsonRecords({
|
|
122
|
-
model: request.model.id,
|
|
123
|
-
prompt: request.prompt,
|
|
124
|
-
images: references.length === 0 ? undefined : references,
|
|
125
|
-
mask: maskReference,
|
|
126
|
-
}, nativeOptions(request.options), http?.body);
|
|
127
|
-
const text = ProviderShared.encodeJson(requestBody);
|
|
128
|
-
const headers = yield* Auth.toEffect(input.auth)({
|
|
129
|
-
request,
|
|
130
|
-
method: "POST",
|
|
131
|
-
url,
|
|
132
|
-
body: text,
|
|
133
|
-
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
|
|
134
|
-
});
|
|
135
|
-
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
|
|
136
|
-
return yield* parseResponse(response, request.options, http?.body);
|
|
137
|
-
}),
|
|
138
|
-
};
|
|
139
|
-
return ImageModel.make({ id: input.id, provider: "openai", route, http: input.http });
|
|
84
|
+
const isEdit = (request) => (request.images?.length ?? 0) > 0;
|
|
85
|
+
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 })));
|
|
87
|
+
const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
88
|
+
const images = request.images ?? [];
|
|
89
|
+
const mask = request.mask;
|
|
90
|
+
if (mask !== undefined && images.length === 0)
|
|
91
|
+
return yield* ProviderShared.invalidRequest("An OpenAI image mask requires at least one input image");
|
|
92
|
+
const fields = mergeJsonRecords({ n: request.n, size: request.size, output_format: request.format, ...(yield* streamOptions(request)) }, nativeOptions(request.providerOptions), request.http?.body);
|
|
93
|
+
// Owned bytes go through multipart edits; remote URLs and file IDs use the JSON edits body instead.
|
|
94
|
+
if (images.length > 0 && images.every(isInline) && (mask === undefined || isInline(mask))) {
|
|
95
|
+
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));
|
|
104
|
+
uploads.forEach((data, index) => form.append("image[]", MediaInput.blob(data, images[index].mediaType), `image-${index}`));
|
|
105
|
+
if (mask !== undefined)
|
|
106
|
+
form.append("mask", MediaInput.blob(yield* MediaInput.inlineBytes(ADAPTER, mask), mask.mediaType), "mask");
|
|
107
|
+
return MediaProtocol.multipart(form);
|
|
108
|
+
}
|
|
109
|
+
const references = yield* Effect.forEach(images, reference);
|
|
110
|
+
const maskReference = mask === undefined ? undefined : yield* reference(mask);
|
|
111
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
112
|
+
model: request.model.id,
|
|
113
|
+
prompt: request.prompt,
|
|
114
|
+
images: references.length === 0 ? undefined : references,
|
|
115
|
+
mask: maskReference,
|
|
116
|
+
}, fields) ?? {});
|
|
117
|
+
});
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// 6. Stream parsing
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
const requestedFormat = (body) => {
|
|
122
|
+
if (body.type === "binary")
|
|
123
|
+
return undefined;
|
|
124
|
+
const value = body.type === "json" ? body.value.output_format : body.value.get("output_format");
|
|
125
|
+
return typeof value === "string" ? value : undefined;
|
|
140
126
|
};
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
127
|
+
const usage = (value) => value === undefined
|
|
128
|
+
? undefined
|
|
129
|
+
: {
|
|
130
|
+
type: "tokens",
|
|
131
|
+
input: value.input_tokens,
|
|
132
|
+
output: value.output_tokens,
|
|
133
|
+
total: value.total_tokens,
|
|
134
|
+
details: { openai: value },
|
|
135
|
+
};
|
|
136
|
+
const eventImage = (frame, label, data, format) => MediaInput.decodedAsset((message, cause) => MediaProtocol.frameError(ADAPTER, message, frame, cause), label, data, `image/${format}`, { info: { format } });
|
|
137
|
+
const onEvent = Effect.fn("OpenAIImages.onEvent")(function* (state, frame) {
|
|
138
|
+
const event = yield* decodeEvent(frame);
|
|
139
|
+
const format = event.output_format;
|
|
140
|
+
if ("partial_image_index" in event) {
|
|
141
|
+
const image = yield* eventImage(frame, `${NAME} partial image`, event.b64_json, format);
|
|
142
|
+
return [state, [ImagePartialEvent.make({ index: event.partial_image_index, image })]];
|
|
143
|
+
}
|
|
144
|
+
const image = yield* eventImage(frame, `${NAME} result ${state.completed}`, event.b64_json, format);
|
|
145
|
+
return [
|
|
146
|
+
{ ...state, completed: state.completed + 1, format, usage: usage(event.usage) },
|
|
147
|
+
[ImageOutputEvent.make({ index: state.completed, image })],
|
|
148
|
+
];
|
|
149
|
+
});
|
|
150
|
+
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)));
|
|
153
|
+
const format = decoded.output_format ?? frame.requested ?? "png";
|
|
154
|
+
const mediaType = `image/${format}`;
|
|
146
155
|
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
|
156
|
+
const providerMetadata = item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } };
|
|
147
157
|
if (item.b64_json)
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
})));
|
|
158
|
+
return MediaInput.decodedAsset(invalid, `${NAME} result ${index}`, item.b64_json, mediaType, {
|
|
159
|
+
info: { format },
|
|
160
|
+
providerMetadata,
|
|
161
|
+
});
|
|
153
162
|
if (item.url)
|
|
154
|
-
return Effect.succeed(
|
|
155
|
-
|
|
156
|
-
data: item.url,
|
|
157
|
-
providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
|
|
158
|
-
}));
|
|
159
|
-
return Effect.fail(output.invalid(`OpenAI Images result ${index} has neither image data nor a 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`));
|
|
160
165
|
});
|
|
161
166
|
if (images.length === 0)
|
|
162
|
-
return yield*
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
usage: decoded.usage === undefined
|
|
166
|
-
? undefined
|
|
167
|
-
: new Usage({
|
|
168
|
-
inputTokens: decoded.usage.input_tokens,
|
|
169
|
-
outputTokens: decoded.usage.output_tokens,
|
|
170
|
-
totalTokens: decoded.usage.total_tokens,
|
|
171
|
-
providerMetadata: { openai: decoded.usage },
|
|
172
|
-
}),
|
|
173
|
-
providerMetadata: { openai: { outputFormat: format } },
|
|
174
|
-
});
|
|
167
|
+
return yield* invalid(`${NAME} returned no images`);
|
|
168
|
+
const state = { completed: images.length, format, usage: usage(decoded.usage) };
|
|
169
|
+
return [state, images.map((image, index) => ImageOutputEvent.make({ index, image }))];
|
|
175
170
|
});
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
const step = (state, frame) => (typeof frame === "string" ? onEvent(state, frame) : onDocument(frame));
|
|
172
|
+
const finish = (state) => {
|
|
173
|
+
if (state.completed === 0)
|
|
174
|
+
return Effect.fail(MediaProtocol.incomplete(ADAPTER));
|
|
175
|
+
return Effect.succeed([
|
|
176
|
+
ImageFinishEvent.make({ usage: state.usage, providerMetadata: { openai: { outputFormat: state.format } } }),
|
|
177
|
+
]);
|
|
180
178
|
};
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// 7. Protocol and route
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
export const protocol = MediaProtocol.stream({
|
|
183
|
+
id: ADAPTER,
|
|
184
|
+
name: NAME,
|
|
185
|
+
unsupported: ["aspectRatio", "seed"],
|
|
186
|
+
body: { from: fromRequest },
|
|
187
|
+
frames: (bytes, context) => context.request.mode === "stream"
|
|
188
|
+
? Framing.sse.frame(bytes)
|
|
189
|
+
: Framing.document
|
|
190
|
+
.frame(bytes)
|
|
191
|
+
.pipe(Stream.map((document) => ({ document, requested: requestedFormat(context.body) }))),
|
|
192
|
+
initial: () => ({ completed: 0 }),
|
|
193
|
+
step,
|
|
194
|
+
finish,
|
|
195
|
+
});
|
|
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);
|
|
181
203
|
export const OpenAIImages = {
|
|
204
|
+
protocol,
|
|
182
205
|
model,
|
|
183
206
|
};
|