@opencode/ai 2.0.15 → 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 +286 -2
- package/dist/generation.d.ts +36 -22
- package/dist/generation.js +53 -24
- package/dist/image-client.d.ts +14 -7
- package/dist/image-client.js +24 -8
- package/dist/image.d.ts +398 -47
- package/dist/image.js +47 -45
- package/dist/index.d.ts +13 -1
- package/dist/index.js +9 -0
- package/dist/media-model.d.ts +44 -0
- package/dist/media-model.js +49 -0
- package/dist/media.d.ts +10 -9
- package/dist/media.js +9 -10
- package/dist/promise.d.ts +428 -8
- package/dist/promise.js +40 -3
- package/dist/protocols/alibaba-chat.d.ts +12 -0
- package/dist/protocols/alibaba-responses.d.ts +2 -2
- package/dist/protocols/anthropic-messages.js +1 -2
- package/dist/protocols/assemblyai-transcription.d.ts +40 -0
- package/dist/protocols/assemblyai-transcription.js +138 -0
- package/dist/protocols/bedrock-converse.js +5 -11
- 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 +9 -9
- package/dist/protocols/gemini.js +8 -34
- package/dist/protocols/google-images.js +2 -14
- 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.js +2 -9
- package/dist/protocols/meta-responses.d.ts +4 -4
- package/dist/protocols/meta-responses.js +1 -1
- package/dist/protocols/open-responses.d.ts +6 -6
- package/dist/protocols/open-responses.js +1 -2
- package/dist/protocols/openai-chat.d.ts +84 -0
- package/dist/protocols/openai-chat.js +26 -14
- 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 +124 -3
- package/dist/protocols/openai-images.js +107 -54
- 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 +13 -3
- package/dist/protocols/shared.js +23 -3
- 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/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 +8 -0
- package/dist/protocols/utils/media-input.js +18 -0
- 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/xai-images.js +1 -12
- 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/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/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/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/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 +3 -3
- package/dist/providers/google.d.ts +18 -3
- package/dist/providers/google.js +11 -2
- package/dist/providers/groq.d.ts +24 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +9 -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 +48 -0
- 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/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/framing.d.ts +5 -1
- package/dist/route/framing.js +9 -0
- package/dist/route/media-protocol.d.ts +116 -3
- package/dist/route/media-protocol.js +60 -4
- package/dist/route/media.d.ts +58 -7
- package/dist/route/media.js +201 -29
- package/dist/schema/events.d.ts +0 -6
- package/dist/schema/messages.d.ts +0 -3
- package/dist/schema/options.d.ts +4 -3
- package/dist/schema/options.js +3 -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/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 +1 -0
- package/dist/utils/media-type.js +22 -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 +3 -3
- package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
- package/dist/protocols/utils/gemini-tool-schema.js +0 -103
|
@@ -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,34 +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
|
};
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return Effect.
|
|
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 });
|
|
62
83
|
};
|
|
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 })));
|
|
63
87
|
const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
64
88
|
const images = request.images ?? [];
|
|
65
89
|
const mask = request.mask;
|
|
66
90
|
if (mask !== undefined && images.length === 0)
|
|
67
91
|
return yield* ProviderShared.invalidRequest("An OpenAI image mask requires at least one input image");
|
|
68
|
-
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);
|
|
69
93
|
// Owned bytes go through multipart edits; remote URLs and file IDs use the JSON edits body instead.
|
|
70
94
|
if (images.length > 0 && images.every(isInline) && (mask === undefined || isInline(mask))) {
|
|
71
95
|
const form = new FormData();
|
|
@@ -77,9 +101,9 @@ const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
|
77
101
|
form.append(key, typeof value === "string" ? value : ProviderShared.encodeJson(value));
|
|
78
102
|
});
|
|
79
103
|
const uploads = yield* Effect.forEach(images, (image) => MediaInput.inlineBytes(ADAPTER, image));
|
|
80
|
-
uploads.forEach((data, index) => form.append("image[]", blob(data, images[index].mediaType), `image-${index}`));
|
|
104
|
+
uploads.forEach((data, index) => form.append("image[]", MediaInput.blob(data, images[index].mediaType), `image-${index}`));
|
|
81
105
|
if (mask !== undefined)
|
|
82
|
-
form.append("mask", blob(yield* MediaInput.inlineBytes(ADAPTER, mask), mask.mediaType), "mask");
|
|
106
|
+
form.append("mask", MediaInput.blob(yield* MediaInput.inlineBytes(ADAPTER, mask), mask.mediaType), "mask");
|
|
83
107
|
return MediaProtocol.multipart(form);
|
|
84
108
|
}
|
|
85
109
|
const references = yield* Effect.forEach(images, reference);
|
|
@@ -92,53 +116,82 @@ const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
|
92
116
|
}, fields) ?? {});
|
|
93
117
|
});
|
|
94
118
|
// ---------------------------------------------------------------------------
|
|
95
|
-
// 6.
|
|
119
|
+
// 6. Stream parsing
|
|
96
120
|
// ---------------------------------------------------------------------------
|
|
97
121
|
const requestedFormat = (body) => {
|
|
122
|
+
if (body.type === "binary")
|
|
123
|
+
return undefined;
|
|
98
124
|
const value = body.type === "json" ? body.value.output_format : body.value.get("output_format");
|
|
99
125
|
return typeof value === "string" ? value : undefined;
|
|
100
126
|
};
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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";
|
|
105
154
|
const mediaType = `image/${format}`;
|
|
106
155
|
const images = yield* Effect.forEach(decoded.data, (item, index) => {
|
|
107
156
|
const providerMetadata = item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } };
|
|
108
157
|
if (item.b64_json)
|
|
109
|
-
return MediaInput.decodedAsset(
|
|
158
|
+
return MediaInput.decodedAsset(invalid, `${NAME} result ${index}`, item.b64_json, mediaType, {
|
|
110
159
|
info: { format },
|
|
111
160
|
providerMetadata,
|
|
112
161
|
});
|
|
113
162
|
if (item.url)
|
|
114
163
|
return Effect.succeed(Media.url(item.url, { mediaType, info: { format }, providerMetadata }));
|
|
115
|
-
return Effect.fail(
|
|
164
|
+
return Effect.fail(invalid(`${NAME} result ${index} has neither image data nor a URL`));
|
|
116
165
|
});
|
|
117
166
|
if (images.length === 0)
|
|
118
|
-
return yield*
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
usage: decoded.usage === undefined
|
|
122
|
-
? undefined
|
|
123
|
-
: {
|
|
124
|
-
type: "tokens",
|
|
125
|
-
input: decoded.usage.input_tokens,
|
|
126
|
-
output: decoded.usage.output_tokens,
|
|
127
|
-
total: decoded.usage.total_tokens,
|
|
128
|
-
details: { openai: decoded.usage },
|
|
129
|
-
},
|
|
130
|
-
providerMetadata: { openai: { outputFormat: format } },
|
|
131
|
-
});
|
|
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 }))];
|
|
132
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
|
+
};
|
|
133
179
|
// ---------------------------------------------------------------------------
|
|
134
180
|
// 7. Protocol and route
|
|
135
181
|
// ---------------------------------------------------------------------------
|
|
136
|
-
export const protocol = MediaProtocol.
|
|
182
|
+
export const protocol = MediaProtocol.stream({
|
|
137
183
|
id: ADAPTER,
|
|
138
184
|
name: NAME,
|
|
139
185
|
unsupported: ["aspectRatio", "seed"],
|
|
140
186
|
body: { from: fromRequest },
|
|
141
|
-
|
|
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,
|
|
142
195
|
});
|
|
143
196
|
export const model = (input) => ImageModel.fromRoute({
|
|
144
197
|
id: ADAPTER,
|
|
@@ -291,8 +291,8 @@ export declare const protocol: Protocol<{
|
|
|
291
291
|
} | {
|
|
292
292
|
readonly type: "input_file";
|
|
293
293
|
readonly filename: string;
|
|
294
|
-
readonly detail?: string | undefined;
|
|
295
294
|
readonly file_data?: string | undefined;
|
|
295
|
+
readonly detail?: string | undefined;
|
|
296
296
|
readonly file_url?: string | undefined;
|
|
297
297
|
} | {
|
|
298
298
|
readonly type: "input_text";
|
|
@@ -328,8 +328,8 @@ export declare const protocol: Protocol<{
|
|
|
328
328
|
} | {
|
|
329
329
|
readonly type: "input_file";
|
|
330
330
|
readonly filename: string;
|
|
331
|
-
readonly detail?: string | undefined;
|
|
332
331
|
readonly file_data?: string | undefined;
|
|
332
|
+
readonly detail?: string | undefined;
|
|
333
333
|
readonly file_url?: string | undefined;
|
|
334
334
|
} | {
|
|
335
335
|
readonly type: "input_text";
|
|
@@ -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;
|
|
@@ -574,8 +574,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
|
|
|
574
574
|
} | {
|
|
575
575
|
readonly type: "input_file";
|
|
576
576
|
readonly filename: string;
|
|
577
|
-
readonly detail?: string | undefined;
|
|
578
577
|
readonly file_data?: string | undefined;
|
|
578
|
+
readonly detail?: string | undefined;
|
|
579
579
|
readonly file_url?: string | undefined;
|
|
580
580
|
} | {
|
|
581
581
|
readonly type: "input_text";
|
|
@@ -611,8 +611,8 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
|
|
|
611
611
|
} | {
|
|
612
612
|
readonly type: "input_file";
|
|
613
613
|
readonly filename: string;
|
|
614
|
-
readonly detail?: string | undefined;
|
|
615
614
|
readonly file_data?: string | undefined;
|
|
615
|
+
readonly detail?: string | undefined;
|
|
616
616
|
readonly file_url?: string | undefined;
|
|
617
617
|
} | {
|
|
618
618
|
readonly type: "input_text";
|
|
@@ -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;
|
|
@@ -802,8 +802,8 @@ export declare const channelTransport: (options: import("./open-responses-channe
|
|
|
802
802
|
} | {
|
|
803
803
|
readonly type: "input_file";
|
|
804
804
|
readonly filename: string;
|
|
805
|
-
readonly detail?: string | undefined;
|
|
806
805
|
readonly file_data?: string | undefined;
|
|
806
|
+
readonly detail?: string | undefined;
|
|
807
807
|
readonly file_url?: string | undefined;
|
|
808
808
|
} | {
|
|
809
809
|
readonly type: "input_text";
|
|
@@ -839,8 +839,8 @@ export declare const channelTransport: (options: import("./open-responses-channe
|
|
|
839
839
|
} | {
|
|
840
840
|
readonly type: "input_file";
|
|
841
841
|
readonly filename: string;
|
|
842
|
-
readonly detail?: string | undefined;
|
|
843
842
|
readonly file_data?: string | undefined;
|
|
843
|
+
readonly detail?: string | undefined;
|
|
844
844
|
readonly file_url?: string | undefined;
|
|
845
845
|
} | {
|
|
846
846
|
readonly type: "input_text";
|
|
@@ -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;
|
|
@@ -1030,8 +1030,8 @@ export declare const transport: import("../route/transport/index.js").Transport<
|
|
|
1030
1030
|
} | {
|
|
1031
1031
|
readonly type: "input_file";
|
|
1032
1032
|
readonly filename: string;
|
|
1033
|
-
readonly detail?: string | undefined;
|
|
1034
1033
|
readonly file_data?: string | undefined;
|
|
1034
|
+
readonly detail?: string | undefined;
|
|
1035
1035
|
readonly file_url?: string | undefined;
|
|
1036
1036
|
} | {
|
|
1037
1037
|
readonly type: "input_text";
|
|
@@ -1067,8 +1067,8 @@ export declare const transport: import("../route/transport/index.js").Transport<
|
|
|
1067
1067
|
} | {
|
|
1068
1068
|
readonly type: "input_file";
|
|
1069
1069
|
readonly filename: string;
|
|
1070
|
-
readonly detail?: string | undefined;
|
|
1071
1070
|
readonly file_data?: string | undefined;
|
|
1071
|
+
readonly detail?: string | undefined;
|
|
1072
1072
|
readonly file_url?: string | undefined;
|
|
1073
1073
|
} | {
|
|
1074
1074
|
readonly type: "input_text";
|
|
@@ -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;
|
|
@@ -1258,8 +1258,8 @@ export declare const route: Route<{
|
|
|
1258
1258
|
} | {
|
|
1259
1259
|
readonly type: "input_file";
|
|
1260
1260
|
readonly filename: string;
|
|
1261
|
-
readonly detail?: string | undefined;
|
|
1262
1261
|
readonly file_data?: string | undefined;
|
|
1262
|
+
readonly detail?: string | undefined;
|
|
1263
1263
|
readonly file_url?: string | undefined;
|
|
1264
1264
|
} | {
|
|
1265
1265
|
readonly type: "input_text";
|
|
@@ -1295,8 +1295,8 @@ export declare const route: Route<{
|
|
|
1295
1295
|
} | {
|
|
1296
1296
|
readonly type: "input_file";
|
|
1297
1297
|
readonly filename: string;
|
|
1298
|
-
readonly detail?: string | undefined;
|
|
1299
1298
|
readonly file_data?: string | undefined;
|
|
1299
|
+
readonly detail?: string | undefined;
|
|
1300
1300
|
readonly file_url?: string | undefined;
|
|
1301
1301
|
} | {
|
|
1302
1302
|
readonly type: "input_text";
|
|
@@ -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;
|
|
@@ -5,7 +5,7 @@ import { Auth } from "../route/auth.js";
|
|
|
5
5
|
import { Endpoint } from "../route/endpoint.js";
|
|
6
6
|
import { Protocol } from "../route/protocol.js";
|
|
7
7
|
import { HttpTransport } from "../route/transport/index.js";
|
|
8
|
-
import { LLMRequest, mergeJsonRecords } from "../schema/index.js";
|
|
8
|
+
import { LLMRequest, mergeJsonRecords, } from "../schema/index.js";
|
|
9
9
|
import { resolveEffortUpdates } from "../effort-updates.js";
|
|
10
10
|
import { OpenResponses } from "./open-responses.js";
|
|
11
11
|
import { OpenResponsesOptions } from "./utils/open-responses-options.js";
|
|
@@ -137,16 +137,16 @@ const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool, inputS
|
|
|
137
137
|
});
|
|
138
138
|
// Native namespaces hold only function tools, so deeper levels flatten into
|
|
139
139
|
// the leaf names the same way non-native protocols flatten the whole tree.
|
|
140
|
-
const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool,
|
|
140
|
+
const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool, model) {
|
|
141
141
|
if (tool.type === "tool")
|
|
142
|
-
return yield* lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema,
|
|
142
|
+
return yield* lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, model));
|
|
143
143
|
// OpenAI requires a namespace description; fall back to a generic one so a
|
|
144
144
|
// missing description never blocks the request.
|
|
145
145
|
return {
|
|
146
146
|
type: "namespace",
|
|
147
147
|
name: tool.name,
|
|
148
148
|
description: tool.description ?? `Tools in the ${tool.name} namespace.`,
|
|
149
|
-
tools: yield* Effect.forEach(ProviderShared.flattenTools(tool.tools), (leaf) => OpenResponses.lowerTool(NAME, leaf, ToolSchemaProjection.modelCompatibility(leaf.inputSchema,
|
|
149
|
+
tools: yield* Effect.forEach(ProviderShared.flattenTools(tool.tools), (leaf) => OpenResponses.lowerTool(NAME, leaf, ToolSchemaProjection.modelCompatibility(leaf.inputSchema, model))),
|
|
150
150
|
};
|
|
151
151
|
});
|
|
152
152
|
const lowerToolChoice = (toolChoice, tools) => ProviderShared.matchToolChoice(NAME, toolChoice, {
|
|
@@ -162,14 +162,13 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request)
|
|
|
162
162
|
const management = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Schema.UndefinedOr(ContextManagement)))(request.providerOptions?.contextManagement);
|
|
163
163
|
const options = OpenResponsesOptions.resolve(request);
|
|
164
164
|
const updates = resolveEffortUpdates(request, options.reasoningEffort);
|
|
165
|
-
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
|
|
166
165
|
return yield* decodeBody({
|
|
167
166
|
...(yield* OpenResponses.lowerConversation(updates.request, adapter)),
|
|
168
167
|
...OpenResponses.lowerGeneration(request, { ...options, reasoningEffort: updates.effort }),
|
|
169
168
|
context_management: management?.map((edit) => ({ type: edit.type, compact_threshold: edit.compactThreshold })),
|
|
170
169
|
tools: request.tools.length === 0
|
|
171
170
|
? undefined
|
|
172
|
-
: yield* Effect.forEach(request.tools, (tool) => lowerToolEntry(tool,
|
|
171
|
+
: yield* Effect.forEach(request.tools, (tool) => lowerToolEntry(tool, request.model)),
|
|
173
172
|
tool_choice: request.tools.length === 0
|
|
174
173
|
? undefined
|
|
175
174
|
: (OpenResponses.allowedToolChoice(request) ??
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
2
|
+
import { MediaRoute } from "../route/media.js";
|
|
3
|
+
import { type MediaUsage } from "../schema/index.js";
|
|
4
|
+
import { SpeechModel, type SpeechRequestFor } from "../speech.js";
|
|
5
|
+
import { SpeechStream } from "./utils/speech-stream.js";
|
|
6
|
+
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
7
|
+
export declare const PATH = "/audio/speech";
|
|
8
|
+
export type OpenAISpeechOptions = Record<string, unknown>;
|
|
9
|
+
export type Request = SpeechRequestFor<OpenAISpeechOptions>;
|
|
10
|
+
interface State extends SpeechStream.Audio {
|
|
11
|
+
readonly done: boolean;
|
|
12
|
+
readonly usage?: MediaUsage;
|
|
13
|
+
}
|
|
14
|
+
export declare const protocol: MediaProtocol.Streamed<Request, {
|
|
15
|
+
readonly type: "audio-delta";
|
|
16
|
+
readonly chunk: Uint8Array<ArrayBufferLike>;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "timestamps";
|
|
19
|
+
readonly items: readonly {
|
|
20
|
+
readonly text: string;
|
|
21
|
+
readonly startSeconds: number;
|
|
22
|
+
readonly endSeconds: number;
|
|
23
|
+
}[];
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: "finish";
|
|
26
|
+
readonly audio: import("../media.js").Asset;
|
|
27
|
+
readonly providerMetadata?: {
|
|
28
|
+
readonly [x: string]: {
|
|
29
|
+
readonly [x: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
} | undefined;
|
|
32
|
+
readonly usage?: {
|
|
33
|
+
readonly type: "tokens";
|
|
34
|
+
readonly input?: number | undefined;
|
|
35
|
+
readonly output?: number | undefined;
|
|
36
|
+
readonly total?: number | undefined;
|
|
37
|
+
readonly details?: {
|
|
38
|
+
readonly [x: string]: unknown;
|
|
39
|
+
} | undefined;
|
|
40
|
+
} | {
|
|
41
|
+
readonly type: "seconds";
|
|
42
|
+
readonly seconds: number;
|
|
43
|
+
} | {
|
|
44
|
+
readonly type: "characters";
|
|
45
|
+
readonly characters: number;
|
|
46
|
+
} | {
|
|
47
|
+
readonly type: "credits";
|
|
48
|
+
readonly credits: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly type: "compute";
|
|
51
|
+
readonly seconds: number;
|
|
52
|
+
} | undefined;
|
|
53
|
+
readonly notices?: readonly {
|
|
54
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
55
|
+
readonly message: string;
|
|
56
|
+
readonly providerMetadata?: {
|
|
57
|
+
readonly [x: string]: {
|
|
58
|
+
readonly [x: string]: unknown;
|
|
59
|
+
};
|
|
60
|
+
} | undefined;
|
|
61
|
+
}[] | undefined;
|
|
62
|
+
}, string | Uint8Array<ArrayBufferLike>, State>;
|
|
63
|
+
export declare const model: (input: MediaRoute.ModelInput) => SpeechModel<OpenAISpeechOptions>;
|
|
64
|
+
export declare const OpenAISpeech: {
|
|
65
|
+
readonly protocol: MediaProtocol.Streamed<Request, {
|
|
66
|
+
readonly type: "audio-delta";
|
|
67
|
+
readonly chunk: Uint8Array<ArrayBufferLike>;
|
|
68
|
+
} | {
|
|
69
|
+
readonly type: "timestamps";
|
|
70
|
+
readonly items: readonly {
|
|
71
|
+
readonly text: string;
|
|
72
|
+
readonly startSeconds: number;
|
|
73
|
+
readonly endSeconds: number;
|
|
74
|
+
}[];
|
|
75
|
+
} | {
|
|
76
|
+
readonly type: "finish";
|
|
77
|
+
readonly audio: import("../media.js").Asset;
|
|
78
|
+
readonly providerMetadata?: {
|
|
79
|
+
readonly [x: string]: {
|
|
80
|
+
readonly [x: string]: unknown;
|
|
81
|
+
};
|
|
82
|
+
} | undefined;
|
|
83
|
+
readonly usage?: {
|
|
84
|
+
readonly type: "tokens";
|
|
85
|
+
readonly input?: number | undefined;
|
|
86
|
+
readonly output?: number | undefined;
|
|
87
|
+
readonly total?: number | undefined;
|
|
88
|
+
readonly details?: {
|
|
89
|
+
readonly [x: string]: unknown;
|
|
90
|
+
} | undefined;
|
|
91
|
+
} | {
|
|
92
|
+
readonly type: "seconds";
|
|
93
|
+
readonly seconds: number;
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: "characters";
|
|
96
|
+
readonly characters: number;
|
|
97
|
+
} | {
|
|
98
|
+
readonly type: "credits";
|
|
99
|
+
readonly credits: number;
|
|
100
|
+
} | {
|
|
101
|
+
readonly type: "compute";
|
|
102
|
+
readonly seconds: number;
|
|
103
|
+
} | undefined;
|
|
104
|
+
readonly notices?: readonly {
|
|
105
|
+
readonly type: "other" | "moderated" | "filtered";
|
|
106
|
+
readonly message: string;
|
|
107
|
+
readonly providerMetadata?: {
|
|
108
|
+
readonly [x: string]: {
|
|
109
|
+
readonly [x: string]: unknown;
|
|
110
|
+
};
|
|
111
|
+
} | undefined;
|
|
112
|
+
}[] | undefined;
|
|
113
|
+
}, string | Uint8Array<ArrayBufferLike>, State>;
|
|
114
|
+
readonly model: (input: MediaRoute.ModelInput) => SpeechModel<OpenAISpeechOptions>;
|
|
115
|
+
};
|
|
116
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Framing } from "../route/framing.js";
|
|
3
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
4
|
+
import { MediaRoute } from "../route/media.js";
|
|
5
|
+
import { ProviderID, mergeJsonRecords } from "../schema/index.js";
|
|
6
|
+
import { SpeechModel } from "../speech.js";
|
|
7
|
+
import { SpeechStream } from "./utils/speech-stream.js";
|
|
8
|
+
const ADAPTER = "openai-speech";
|
|
9
|
+
const NAME = "OpenAI Speech";
|
|
10
|
+
const PROVIDER = ProviderID.make("openai");
|
|
11
|
+
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
12
|
+
export const PATH = "/audio/speech";
|
|
13
|
+
/** `pcm` is raw 24 kHz, 16-bit signed little-endian mono samples without a header. */
|
|
14
|
+
const PCM_SAMPLE_RATE = 24000;
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// 3. Streaming event schema
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const SpeechStreamEvent = Schema.Union([
|
|
19
|
+
Schema.Struct({ type: Schema.Literal("speech.audio.delta"), audio: Schema.Uint8ArrayFromBase64 }),
|
|
20
|
+
Schema.Struct({
|
|
21
|
+
type: Schema.Literal("speech.audio.done"),
|
|
22
|
+
usage: Schema.optional(Schema.Struct({
|
|
23
|
+
input_tokens: Schema.optional(Schema.Number),
|
|
24
|
+
output_tokens: Schema.optional(Schema.Number),
|
|
25
|
+
total_tokens: Schema.optional(Schema.Number),
|
|
26
|
+
})),
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
const decodeEvent = MediaProtocol.decodeFrame(ADAPTER, NAME, SpeechStreamEvent);
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// 5. Request body construction
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// `sse` is not supported for `tts-1` or `tts-1-hd`; those models stream the raw audio body instead.
|
|
34
|
+
const supportsSse = (model) => !/^tts-1(-hd)?(-|$)/.test(model);
|
|
35
|
+
const fromRequest = Effect.fn("OpenAISpeech.fromRequest")(function* (request) {
|
|
36
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
37
|
+
model: request.model.id,
|
|
38
|
+
input: request.text,
|
|
39
|
+
voice: request.voice,
|
|
40
|
+
instructions: request.instructions,
|
|
41
|
+
response_format: request.format,
|
|
42
|
+
speed: request.speed,
|
|
43
|
+
stream_format: request.mode === "stream" && supportsSse(request.model.id) ? "sse" : undefined,
|
|
44
|
+
}, request.providerOptions, request.http?.body) ?? {});
|
|
45
|
+
});
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// 6. Stream parsing
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
const isSse = (body) => body.type === "json" && body.value.stream_format === "sse";
|
|
50
|
+
const onEvent = Effect.fn("OpenAISpeech.onEvent")(function* (state, frame) {
|
|
51
|
+
const event = yield* decodeEvent(frame);
|
|
52
|
+
if (event.type === "speech.audio.delta")
|
|
53
|
+
return SpeechStream.delta(state, event.audio);
|
|
54
|
+
const usage = event.usage;
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
...state,
|
|
58
|
+
done: true,
|
|
59
|
+
usage: usage === undefined
|
|
60
|
+
? undefined
|
|
61
|
+
: {
|
|
62
|
+
type: "tokens",
|
|
63
|
+
input: usage.input_tokens,
|
|
64
|
+
output: usage.output_tokens,
|
|
65
|
+
total: usage.total_tokens,
|
|
66
|
+
details: { openai: usage },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
[],
|
|
70
|
+
];
|
|
71
|
+
});
|
|
72
|
+
const finish = (state, context) => {
|
|
73
|
+
if (isSse(context.body) && !state.done)
|
|
74
|
+
return Effect.fail(MediaProtocol.incomplete(ADAPTER));
|
|
75
|
+
const format = context.request.format ?? "mp3";
|
|
76
|
+
return SpeechStream.finish(ADAPTER, state, {
|
|
77
|
+
...(format === "pcm" ? SpeechStream.pcm("pcm_s16le", PCM_SAMPLE_RATE) : SpeechStream.container(format)),
|
|
78
|
+
usage: state.usage,
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// 7. Protocol and route
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
export const protocol = MediaProtocol.stream({
|
|
85
|
+
id: ADAPTER,
|
|
86
|
+
name: NAME,
|
|
87
|
+
unsupported: ["language", "timestamps"],
|
|
88
|
+
body: { from: fromRequest },
|
|
89
|
+
frames: (bytes, context) => (isSse(context.body) ? Framing.sse.frame(bytes) : bytes),
|
|
90
|
+
initial: () => ({ chunks: [], done: false }),
|
|
91
|
+
step: SpeechStream.step(onEvent),
|
|
92
|
+
finish,
|
|
93
|
+
});
|
|
94
|
+
export const model = (input) => SpeechModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
|
|
95
|
+
export const OpenAISpeech = {
|
|
96
|
+
protocol,
|
|
97
|
+
model,
|
|
98
|
+
};
|