@opencode/ai 0.0.0-dev-20061 → 0.0.0-dev-20065
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 +56 -1
- package/dist/image-client.d.ts +14 -7
- package/dist/image-client.js +24 -8
- package/dist/image.d.ts +392 -12
- package/dist/image.js +41 -9
- package/dist/media-model.d.ts +3 -1
- package/dist/media-model.js +10 -0
- package/dist/promise.d.ts +33 -5
- package/dist/promise.js +4 -2
- package/dist/protocols/bfl-images.d.ts +32 -0
- package/dist/protocols/bfl-images.js +153 -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 +0 -10
- package/dist/protocols/fal-video.js +8 -59
- package/dist/protocols/openai-images.d.ts +124 -3
- package/dist/protocols/openai-images.js +102 -35
- package/dist/protocols/openai-responses.d.ts +5 -5
- package/dist/protocols/replicate-images.d.ts +28 -0
- package/dist/protocols/replicate-images.js +133 -0
- package/dist/protocols/stability-images.d.ts +38 -0
- package/dist/protocols/stability-images.js +148 -0
- package/dist/protocols/utils/fal-queue.d.ts +28 -0
- package/dist/protocols/utils/fal-queue.js +69 -0
- package/dist/protocols/utils/media-input.d.ts +4 -0
- package/dist/protocols/utils/media-input.js +4 -0
- package/dist/provider-error.js +3 -0
- package/dist/providers/azure.d.ts +2 -2
- package/dist/providers/black-forest-labs.d.ts +25 -0
- package/dist/providers/black-forest-labs.js +28 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts +2 -2
- package/dist/providers/fal.d.ts +5 -0
- package/dist/providers/fal.js +8 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/replicate.d.ts +25 -0
- package/dist/providers/replicate.js +22 -0
- package/dist/providers/stability.d.ts +28 -0
- package/dist/providers/stability.js +23 -0
- package/dist/route/media-protocol.d.ts +3 -1
- package/dist/route/media.d.ts +13 -2
- package/dist/route/media.js +37 -2
- package/dist/transcription-client.d.ts +1 -1
- package/dist/transcription-client.js +10 -35
- package/dist/transcription.d.ts +2 -2
- package/dist/transcription.js +2 -9
- package/package.json +3 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { ImageModel,
|
|
1
|
+
import { ImageModel, type ImageRequestFor } from "../image.js";
|
|
2
|
+
import { Media } from "../media.js";
|
|
2
3
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
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";
|
|
@@ -11,11 +13,130 @@ export type OpenAIImageOptions = {
|
|
|
11
13
|
readonly background?: OpenAIImageString<"auto" | "opaque" | "transparent">;
|
|
12
14
|
readonly moderation?: OpenAIImageString<"auto" | "low">;
|
|
13
15
|
readonly outputCompression?: number;
|
|
16
|
+
/** Previews sent before the final image when streaming (default 2); ignored by `Image.generate`. */
|
|
17
|
+
readonly partialImages?: number;
|
|
14
18
|
} & Record<string, unknown>;
|
|
15
19
|
export type Request = ImageRequestFor<OpenAIImageOptions>;
|
|
16
|
-
|
|
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;
|
|
24
|
+
};
|
|
25
|
+
interface State {
|
|
26
|
+
readonly completed: number;
|
|
27
|
+
readonly format?: string;
|
|
28
|
+
readonly usage?: MediaUsage;
|
|
29
|
+
}
|
|
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>;
|
|
17
84
|
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<OpenAIImageOptions>;
|
|
18
85
|
export declare const OpenAIImages: {
|
|
19
|
-
readonly protocol: MediaProtocol.
|
|
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>;
|
|
20
140
|
readonly model: (input: MediaRoute.ModelInput) => ImageModel<OpenAIImageOptions>;
|
|
21
141
|
};
|
|
142
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
2
|
-
import { ImageModel,
|
|
1
|
+
import { Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { ImageFinishEvent, ImageModel, ImageOutputEvent, ImagePartialEvent, } from "../image.js";
|
|
3
3
|
import { Media } from "../media.js";
|
|
4
|
+
import { Framing } from "../route/framing.js";
|
|
4
5
|
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
6
|
import { MediaRoute } from "../route/media.js";
|
|
6
7
|
import { ProviderID, mergeJsonRecords } from "../schema/index.js";
|
|
@@ -15,6 +16,13 @@ export const EDIT_PATH = "/images/edits";
|
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// 2. Response schema
|
|
17
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
|
+
});
|
|
18
26
|
const OpenAIImageResponse = Schema.Struct({
|
|
19
27
|
data: Schema.Array(Schema.Struct({
|
|
20
28
|
b64_json: Schema.optional(Schema.String),
|
|
@@ -22,15 +30,28 @@ const OpenAIImageResponse = Schema.Struct({
|
|
|
22
30
|
revised_prompt: Schema.optional(Schema.String),
|
|
23
31
|
})),
|
|
24
32
|
output_format: Schema.optional(Schema.String),
|
|
25
|
-
usage: Schema.optional(
|
|
26
|
-
input_tokens: Schema.optional(Schema.Number),
|
|
27
|
-
output_tokens: Schema.optional(Schema.Number),
|
|
28
|
-
total_tokens: Schema.optional(Schema.Number),
|
|
29
|
-
input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
30
|
-
output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
31
|
-
})),
|
|
33
|
+
usage: Schema.optional(Usage),
|
|
32
34
|
});
|
|
33
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
|
+
// ---------------------------------------------------------------------------
|
|
34
55
|
// 5. Request body construction
|
|
35
56
|
// ---------------------------------------------------------------------------
|
|
36
57
|
/** Multipart field names the route owns; `http.body` overlays cannot smuggle replacements for them. */
|
|
@@ -38,18 +59,37 @@ const RESERVED_FORM_FIELDS = new Set(["model", "prompt", "image", "image[]", "im
|
|
|
38
59
|
const nativeOptions = (options) => {
|
|
39
60
|
if (!options)
|
|
40
61
|
return undefined;
|
|
41
|
-
const { outputCompression, ...native } = options;
|
|
62
|
+
const { outputCompression, partialImages: _, ...native } = options;
|
|
42
63
|
return { output_compression: outputCompression, ...native };
|
|
43
64
|
};
|
|
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 });
|
|
83
|
+
};
|
|
44
84
|
const isEdit = (request) => (request.images?.length ?? 0) > 0;
|
|
45
|
-
const isInline = (asset) => asset.
|
|
85
|
+
const isInline = (asset) => asset.source.type === "bytes" || asset.source.type === "base64";
|
|
46
86
|
const reference = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((item) => (item.type === "ref" ? { file_id: item.value } : { image_url: item.value })));
|
|
47
87
|
const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
48
88
|
const images = request.images ?? [];
|
|
49
89
|
const mask = request.mask;
|
|
50
90
|
if (mask !== undefined && images.length === 0)
|
|
51
91
|
return yield* ProviderShared.invalidRequest("An OpenAI image mask requires at least one input image");
|
|
52
|
-
const fields = mergeJsonRecords({ n: request.n, size: request.size, output_format: request.format }, nativeOptions(request.providerOptions), request.http?.body);
|
|
92
|
+
const fields = mergeJsonRecords({ n: request.n, size: request.size, output_format: request.format, ...(yield* streamOptions(request)) }, nativeOptions(request.providerOptions), request.http?.body);
|
|
53
93
|
// Owned bytes go through multipart edits; remote URLs and file IDs use the JSON edits body instead.
|
|
54
94
|
if (images.length > 0 && images.every(isInline) && (mask === undefined || isInline(mask))) {
|
|
55
95
|
const form = new FormData();
|
|
@@ -76,7 +116,7 @@ const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
|
76
116
|
}, fields) ?? {});
|
|
77
117
|
});
|
|
78
118
|
// ---------------------------------------------------------------------------
|
|
79
|
-
// 6.
|
|
119
|
+
// 6. Stream parsing
|
|
80
120
|
// ---------------------------------------------------------------------------
|
|
81
121
|
const requestedFormat = (body) => {
|
|
82
122
|
if (body.type === "binary")
|
|
@@ -84,47 +124,74 @@ const requestedFormat = (body) => {
|
|
|
84
124
|
const value = body.type === "json" ? body.value.output_format : body.value.get("output_format");
|
|
85
125
|
return typeof value === "string" ? value : undefined;
|
|
86
126
|
};
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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";
|
|
91
154
|
const mediaType = `image/${format}`;
|
|
92
155
|
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
|
93
156
|
const providerMetadata = item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } };
|
|
94
157
|
if (item.b64_json)
|
|
95
|
-
return MediaInput.decodedAsset(
|
|
158
|
+
return MediaInput.decodedAsset(invalid, `${NAME} result ${index}`, item.b64_json, mediaType, {
|
|
96
159
|
info: { format },
|
|
97
160
|
providerMetadata,
|
|
98
161
|
});
|
|
99
162
|
if (item.url)
|
|
100
163
|
return Effect.succeed(Media.url(item.url, { mediaType, info: { format }, providerMetadata }));
|
|
101
|
-
return Effect.fail(
|
|
164
|
+
return Effect.fail(invalid(`${NAME} result ${index} has neither image data nor a URL`));
|
|
102
165
|
});
|
|
103
166
|
if (images.length === 0)
|
|
104
|
-
return yield*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
usage: decoded.usage === undefined
|
|
108
|
-
? undefined
|
|
109
|
-
: {
|
|
110
|
-
type: "tokens",
|
|
111
|
-
input: decoded.usage.input_tokens,
|
|
112
|
-
output: decoded.usage.output_tokens,
|
|
113
|
-
total: decoded.usage.total_tokens,
|
|
114
|
-
details: { openai: decoded.usage },
|
|
115
|
-
},
|
|
116
|
-
providerMetadata: { openai: { outputFormat: format } },
|
|
117
|
-
});
|
|
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 }))];
|
|
118
170
|
});
|
|
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
|
+
]);
|
|
178
|
+
};
|
|
119
179
|
// ---------------------------------------------------------------------------
|
|
120
180
|
// 7. Protocol and route
|
|
121
181
|
// ---------------------------------------------------------------------------
|
|
122
|
-
export const protocol = MediaProtocol.
|
|
182
|
+
export const protocol = MediaProtocol.stream({
|
|
123
183
|
id: ADAPTER,
|
|
124
184
|
name: NAME,
|
|
125
185
|
unsupported: ["aspectRatio", "seed"],
|
|
126
186
|
body: { from: fromRequest },
|
|
127
|
-
|
|
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,
|
|
128
195
|
});
|
|
129
196
|
export const model = (input) => ImageModel.fromRoute({
|
|
130
197
|
id: ADAPTER,
|
|
@@ -390,11 +390,11 @@ 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;
|
|
393
394
|
readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
|
|
394
395
|
readonly background?: "auto" | "opaque" | "transparent" | undefined;
|
|
395
396
|
readonly output_compression?: number | undefined;
|
|
396
397
|
readonly input_fidelity?: "low" | "high" | undefined;
|
|
397
|
-
readonly partial_images?: number | undefined;
|
|
398
398
|
} | {
|
|
399
399
|
readonly type: "namespace";
|
|
400
400
|
readonly name: string;
|
|
@@ -673,11 +673,11 @@ 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;
|
|
676
677
|
readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
|
|
677
678
|
readonly background?: "auto" | "opaque" | "transparent" | undefined;
|
|
678
679
|
readonly output_compression?: number | undefined;
|
|
679
680
|
readonly input_fidelity?: "low" | "high" | undefined;
|
|
680
|
-
readonly partial_images?: number | undefined;
|
|
681
681
|
} | {
|
|
682
682
|
readonly type: "namespace";
|
|
683
683
|
readonly name: string;
|
|
@@ -901,11 +901,11 @@ 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;
|
|
904
905
|
readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
|
|
905
906
|
readonly background?: "auto" | "opaque" | "transparent" | undefined;
|
|
906
907
|
readonly output_compression?: number | undefined;
|
|
907
908
|
readonly input_fidelity?: "low" | "high" | undefined;
|
|
908
|
-
readonly partial_images?: number | undefined;
|
|
909
909
|
} | {
|
|
910
910
|
readonly type: "namespace";
|
|
911
911
|
readonly name: string;
|
|
@@ -1129,11 +1129,11 @@ 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;
|
|
1132
1133
|
readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
|
|
1133
1134
|
readonly background?: "auto" | "opaque" | "transparent" | undefined;
|
|
1134
1135
|
readonly output_compression?: number | undefined;
|
|
1135
1136
|
readonly input_fidelity?: "low" | "high" | undefined;
|
|
1136
|
-
readonly partial_images?: number | undefined;
|
|
1137
1137
|
} | {
|
|
1138
1138
|
readonly type: "namespace";
|
|
1139
1139
|
readonly name: string;
|
|
@@ -1357,11 +1357,11 @@ 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;
|
|
1360
1361
|
readonly quality?: "auto" | "low" | "medium" | "high" | undefined;
|
|
1361
1362
|
readonly background?: "auto" | "opaque" | "transparent" | undefined;
|
|
1362
1363
|
readonly output_compression?: number | undefined;
|
|
1363
1364
|
readonly input_fidelity?: "low" | "high" | undefined;
|
|
1364
|
-
readonly partial_images?: number | undefined;
|
|
1365
1365
|
} | {
|
|
1366
1366
|
readonly type: "namespace";
|
|
1367
1367
|
readonly name: string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
3
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
|
+
import { MediaRoute } from "../route/media.js";
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://api.replicate.com";
|
|
6
|
+
/** Model-defined `input` fields under the model's own names; `Media.Asset` values lower to URLs or data URLs. */
|
|
7
|
+
export type ReplicateImageOptions = Record<string, unknown>;
|
|
8
|
+
export type Request = ImageRequestFor<ReplicateImageOptions>;
|
|
9
|
+
export declare const Token: Schema.Struct<{
|
|
10
|
+
readonly id: Schema.String;
|
|
11
|
+
readonly getURL: Schema.String;
|
|
12
|
+
readonly cancelURL: Schema.String;
|
|
13
|
+
}>;
|
|
14
|
+
export type Token = Schema.Schema.Type<typeof Token>;
|
|
15
|
+
export declare const protocol: MediaProtocol.Queued<Request, ImageResponse, {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly getURL: string;
|
|
18
|
+
readonly cancelURL: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<ReplicateImageOptions>;
|
|
21
|
+
export declare const ReplicateImages: {
|
|
22
|
+
readonly protocol: MediaProtocol.Queued<Request, ImageResponse, {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly getURL: string;
|
|
25
|
+
readonly cancelURL: string;
|
|
26
|
+
}>;
|
|
27
|
+
readonly model: (input: MediaRoute.ModelInput) => ImageModel<ReplicateImageOptions>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Duration, Effect, Schema } from "effect";
|
|
2
|
+
import { ImageModel, ImageResponse } from "../image.js";
|
|
3
|
+
import { Media } from "../media.js";
|
|
4
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
5
|
+
import { MediaRoute } from "../route/media.js";
|
|
6
|
+
import { ProviderID, mergeJsonRecords } from "../schema/index.js";
|
|
7
|
+
import { ProviderShared, optionalNull } from "./shared.js";
|
|
8
|
+
const ADAPTER = "replicate-images";
|
|
9
|
+
const NAME = "Replicate";
|
|
10
|
+
const PROVIDER = ProviderID.make("replicate");
|
|
11
|
+
export const DEFAULT_BASE_URL = "https://api.replicate.com";
|
|
12
|
+
const OUTPUT_RETENTION = Duration.hours(1);
|
|
13
|
+
const MAX_DATA_URL_BYTES = 256 * 1024;
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// 2. Token and response schemas
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const Token = Schema.Struct({ id: Schema.String, getURL: Schema.String, cancelURL: Schema.String });
|
|
18
|
+
const Prediction = Schema.Struct({
|
|
19
|
+
id: Schema.String,
|
|
20
|
+
status: Schema.String,
|
|
21
|
+
output: optionalNull(Schema.Unknown),
|
|
22
|
+
error: optionalNull(Schema.Unknown),
|
|
23
|
+
data_removed: optionalNull(Schema.Boolean),
|
|
24
|
+
completed_at: optionalNull(Schema.String),
|
|
25
|
+
metrics: optionalNull(Schema.Struct({ predict_time: optionalNull(Schema.Number) })),
|
|
26
|
+
urls: Schema.Struct({ get: Schema.String, cancel: Schema.String }),
|
|
27
|
+
});
|
|
28
|
+
const Output = Schema.Union([Schema.String, Schema.Array(Schema.String)]);
|
|
29
|
+
const isOutput = Schema.is(Output);
|
|
30
|
+
const STATUS = {
|
|
31
|
+
starting: "queued",
|
|
32
|
+
processing: "running",
|
|
33
|
+
succeeded: "completed",
|
|
34
|
+
failed: "failed",
|
|
35
|
+
canceled: "cancelled",
|
|
36
|
+
// The prediction hit its `Cancel-After` deadline before it started running.
|
|
37
|
+
aborted: "expired",
|
|
38
|
+
};
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// 5. Request body construction
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Official models run by `owner/name`; anything else (`owner/name:version` or a bare version id) is a pinned version.
|
|
43
|
+
const isOfficial = (model) => /^[^/:]+\/[^/:]+$/.test(model);
|
|
44
|
+
const inlineSize = (source) => {
|
|
45
|
+
if (source.type === "bytes")
|
|
46
|
+
return source.data.byteLength;
|
|
47
|
+
if (source.type === "base64")
|
|
48
|
+
return source.data.length * 0.75;
|
|
49
|
+
return 0;
|
|
50
|
+
};
|
|
51
|
+
const fileInput = (asset) => {
|
|
52
|
+
if (inlineSize(asset.source) > MAX_DATA_URL_BYTES)
|
|
53
|
+
return Effect.fail(ProviderShared.invalidRequest(`${NAME} data URL inputs are limited to 256 KB; pass a larger file by https URL`));
|
|
54
|
+
return ProviderShared.mediaReference(asset, undefined, NAME).pipe(Effect.map((reference) => reference.value));
|
|
55
|
+
};
|
|
56
|
+
const inputValue = (value) => {
|
|
57
|
+
if (value instanceof Media.Asset)
|
|
58
|
+
return fileInput(value);
|
|
59
|
+
if (Array.isArray(value) && value.some((item) => item instanceof Media.Asset))
|
|
60
|
+
return Effect.forEach(value, (item) => (item instanceof Media.Asset ? fileInput(item) : Effect.succeed(item)));
|
|
61
|
+
return Effect.succeed(value);
|
|
62
|
+
};
|
|
63
|
+
const fromRequest = Effect.fn("ReplicateImages.fromRequest")(function* (request) {
|
|
64
|
+
const native = yield* Effect.forEach(Object.entries(request.providerOptions ?? {}), ([key, value]) => inputValue(value).pipe(Effect.map((lowered) => [key, lowered])));
|
|
65
|
+
const input = mergeJsonRecords({ prompt: request.prompt }, Object.fromEntries(native));
|
|
66
|
+
const model = request.model.id;
|
|
67
|
+
return MediaProtocol.json(mergeJsonRecords(isOfficial(model) ? { input } : { version: model, input }, request.http?.body) ?? {});
|
|
68
|
+
});
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// 6. Response decoding
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
const decodePrediction = MediaProtocol.decodeJson(ADAPTER, NAME, Prediction);
|
|
73
|
+
const decodeStart = Effect.fn("ReplicateImages.decodeStart")(function* (response) {
|
|
74
|
+
const output = yield* decodePrediction(response);
|
|
75
|
+
const prediction = output.value;
|
|
76
|
+
return {
|
|
77
|
+
token: { id: prediction.id, getURL: prediction.urls.get, cancelURL: prediction.urls.cancel },
|
|
78
|
+
// `Prefer: wait` can return an already-finished prediction, so `await` skips straight to the result.
|
|
79
|
+
snapshot: { id: prediction.id, status: yield* MediaProtocol.status(STATUS, prediction.status, output) },
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
const decodeStatus = Effect.fn("ReplicateImages.decodeStatus")(function* (response, context) {
|
|
83
|
+
const output = yield* decodePrediction(response);
|
|
84
|
+
return { id: context.token.id, status: yield* MediaProtocol.status(STATUS, output.value.status, output) };
|
|
85
|
+
});
|
|
86
|
+
const decodeResult = Effect.fn("ReplicateImages.decodeResult")(function* (response, context) {
|
|
87
|
+
const output = yield* decodePrediction(response);
|
|
88
|
+
const prediction = output.value;
|
|
89
|
+
const status = yield* MediaProtocol.status(STATUS, prediction.status, output);
|
|
90
|
+
if (status === "failed" || status === "cancelled" || status === "expired")
|
|
91
|
+
return yield* output.ended(status, `${NAME} prediction ${context.token.id} ${prediction.status}${typeof prediction.error === "string" ? `: ${prediction.error}` : ""}`);
|
|
92
|
+
if (status !== "completed")
|
|
93
|
+
return yield* output.invalid(`${NAME} prediction ${context.token.id} has not finished`);
|
|
94
|
+
if (prediction.data_removed === true)
|
|
95
|
+
return yield* output.ended("expired", `${NAME} removed the output of prediction ${context.token.id}`);
|
|
96
|
+
if (!isOutput(prediction.output))
|
|
97
|
+
return yield* output.invalid(`${NAME} prediction ${context.token.id} returned output that is not image URLs`);
|
|
98
|
+
const urls = typeof prediction.output === "string" ? [prediction.output] : prediction.output;
|
|
99
|
+
if (urls.length === 0)
|
|
100
|
+
return yield* output.invalid(`${NAME} prediction ${context.token.id} returned no images`);
|
|
101
|
+
const predictTime = prediction.metrics?.predict_time ?? undefined;
|
|
102
|
+
const completedAt = prediction.completed_at ?? undefined;
|
|
103
|
+
const expiresAt = completedAt === undefined ? undefined : Date.parse(completedAt) + Duration.toMillis(OUTPUT_RETENTION);
|
|
104
|
+
return new ImageResponse({
|
|
105
|
+
images: urls.map((url) => Media.url(url, { expiresAt })),
|
|
106
|
+
usage: predictTime === undefined ? undefined : { type: "compute", seconds: predictTime },
|
|
107
|
+
providerMetadata: { replicate: { predictionId: prediction.id } },
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// 7. Protocol and route
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
export const protocol = MediaProtocol.queued({
|
|
114
|
+
id: ADAPTER,
|
|
115
|
+
name: NAME,
|
|
116
|
+
token: Token,
|
|
117
|
+
unsupported: ["images", "mask", "n", "size", "aspectRatio", "seed", "format"],
|
|
118
|
+
start: { body: { from: fromRequest }, decode: decodeStart },
|
|
119
|
+
status: { path: (token) => token.getURL, decode: decodeStatus },
|
|
120
|
+
result: { path: (token) => token.getURL, decode: decodeResult },
|
|
121
|
+
cancel: { method: "POST", path: (token) => token.cancelURL },
|
|
122
|
+
});
|
|
123
|
+
export const model = (input) => ImageModel.fromRoute({
|
|
124
|
+
id: ADAPTER,
|
|
125
|
+
provider: PROVIDER,
|
|
126
|
+
protocol,
|
|
127
|
+
baseURL: DEFAULT_BASE_URL,
|
|
128
|
+
path: ({ request }) => isOfficial(request.model.id) ? `/v1/models/${request.model.id}/predictions` : "/v1/predictions",
|
|
129
|
+
}, input);
|
|
130
|
+
export const ReplicateImages = {
|
|
131
|
+
protocol,
|
|
132
|
+
model,
|
|
133
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
|
|
3
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
|
+
import { MediaRoute } from "../route/media.js";
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://api.stability.ai";
|
|
6
|
+
export type StabilityStylePreset = "enhance" | "anime" | "photographic" | "digital-art" | "comic-book" | "fantasy-art" | "line-art" | "analog-film" | "neon-punk" | "isometric" | "low-poly" | "origami" | "modeling-compound" | "cinematic" | "3d-model" | "pixel-art" | "tile-texture" | (string & {});
|
|
7
|
+
export type StabilityImageOptions = {
|
|
8
|
+
readonly negative_prompt?: string;
|
|
9
|
+
readonly style_preset?: StabilityStylePreset;
|
|
10
|
+
readonly strength?: number;
|
|
11
|
+
readonly cfg_scale?: number;
|
|
12
|
+
} & Record<string, unknown>;
|
|
13
|
+
/** Creative upscale options; the one `images` source is the image to upscale. */
|
|
14
|
+
export type StabilityUpscaleOptions = {
|
|
15
|
+
readonly negative_prompt?: string;
|
|
16
|
+
readonly style_preset?: StabilityStylePreset;
|
|
17
|
+
readonly creativity?: number;
|
|
18
|
+
} & Record<string, unknown>;
|
|
19
|
+
export type Request = ImageRequestFor<StabilityImageOptions>;
|
|
20
|
+
export type UpscaleRequest = ImageRequestFor<StabilityUpscaleOptions>;
|
|
21
|
+
export declare const Token: Schema.Struct<{
|
|
22
|
+
readonly id: Schema.String;
|
|
23
|
+
}>;
|
|
24
|
+
export type Token = Schema.Schema.Type<typeof Token>;
|
|
25
|
+
export declare const protocol: MediaProtocol.Inline<Request, ImageResponse>;
|
|
26
|
+
export declare const upscaleProtocol: MediaProtocol.Queued<UpscaleRequest, ImageResponse, {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const model: (input: MediaRoute.ModelInput) => ImageModel<StabilityImageOptions>;
|
|
30
|
+
export declare const upscaleModel: (input: Omit<MediaRoute.ModelInput, "id">) => ImageModel<StabilityUpscaleOptions>;
|
|
31
|
+
export declare const StabilityImages: {
|
|
32
|
+
readonly protocol: MediaProtocol.Inline<Request, ImageResponse>;
|
|
33
|
+
readonly upscaleProtocol: MediaProtocol.Queued<UpscaleRequest, ImageResponse, {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
}>;
|
|
36
|
+
readonly model: (input: MediaRoute.ModelInput) => ImageModel<StabilityImageOptions>;
|
|
37
|
+
readonly upscaleModel: (input: Omit<MediaRoute.ModelInput, "id">) => ImageModel<StabilityUpscaleOptions>;
|
|
38
|
+
};
|