@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
package/dist/media.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
export * as Media from "./media.js";
|
|
2
|
+
import { Effect, Encoding, FileSystem, Schema, SchemaGetter } from "effect";
|
|
3
|
+
import { HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
import { ProviderID } from "./schema/ids.js";
|
|
5
|
+
import { AIError, HttpContext, InvalidProviderOutputError, InvalidRequestError } from "./schema/errors.js";
|
|
6
|
+
import { ProviderMetadata } from "./schema/options.js";
|
|
7
|
+
import { Service } from "./route/executor-service.js";
|
|
8
|
+
import { detectMediaType, extensionMediaType } from "./utils/media-type.js";
|
|
9
|
+
export { detectMediaType } from "./utils/media-type.js";
|
|
10
|
+
const OCTET_STREAM = "application/octet-stream";
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Source — the serializable wire/persistence form of a media asset
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const BytesSource = Schema.Struct({
|
|
15
|
+
type: Schema.Literal("bytes"),
|
|
16
|
+
data: Schema.Uint8Array,
|
|
17
|
+
mediaType: Schema.String,
|
|
18
|
+
});
|
|
19
|
+
const Base64Source = Schema.Struct({
|
|
20
|
+
type: Schema.Literal("base64"),
|
|
21
|
+
data: Schema.String,
|
|
22
|
+
mediaType: Schema.String,
|
|
23
|
+
});
|
|
24
|
+
const UrlSource = Schema.Struct({
|
|
25
|
+
type: Schema.Literal("url"),
|
|
26
|
+
url: Schema.String,
|
|
27
|
+
mediaType: Schema.optional(Schema.String),
|
|
28
|
+
/** Epoch milliseconds after which the provider no longer serves the URL. */
|
|
29
|
+
expiresAt: Schema.optional(Schema.Number),
|
|
30
|
+
});
|
|
31
|
+
/** A provider-side handle: OpenAI `file_id`, Gemini file URI, `gs://`, `runway://`, or a prior generation id. */
|
|
32
|
+
const RefSource = Schema.Struct({
|
|
33
|
+
type: Schema.Literal("ref"),
|
|
34
|
+
provider: ProviderID,
|
|
35
|
+
id: Schema.String,
|
|
36
|
+
mediaType: Schema.optional(Schema.String),
|
|
37
|
+
});
|
|
38
|
+
export const Source = Schema.Union([BytesSource, Base64Source, UrlSource, RefSource])
|
|
39
|
+
.pipe(Schema.toTaggedUnion("type"))
|
|
40
|
+
.annotate({ identifier: "Media.Source" });
|
|
41
|
+
export const AspectRatio = Schema.declare((value) => typeof value === "string" && /^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/.test(value), { title: "Media.AspectRatio" });
|
|
42
|
+
export const Kind = Schema.Literals(["image", "video", "audio", "document", "other"]);
|
|
43
|
+
export const kindOf = (mediaType) => {
|
|
44
|
+
const lower = mediaType.toLowerCase();
|
|
45
|
+
if (lower.startsWith("image/"))
|
|
46
|
+
return "image";
|
|
47
|
+
if (lower.startsWith("video/"))
|
|
48
|
+
return "video";
|
|
49
|
+
if (lower.startsWith("audio/"))
|
|
50
|
+
return "audio";
|
|
51
|
+
if (lower === "application/pdf" || lower.startsWith("text/"))
|
|
52
|
+
return "document";
|
|
53
|
+
return "other";
|
|
54
|
+
};
|
|
55
|
+
/** Container-independent facts about the payload; raw PCM audio relies on these because it has no header. */
|
|
56
|
+
export const Info = Schema.Struct({
|
|
57
|
+
width: Schema.optional(Schema.Number),
|
|
58
|
+
height: Schema.optional(Schema.Number),
|
|
59
|
+
durationSeconds: Schema.optional(Schema.Number),
|
|
60
|
+
sampleRate: Schema.optional(Schema.Number),
|
|
61
|
+
channels: Schema.optional(Schema.Number),
|
|
62
|
+
encoding: Schema.optional(Schema.String),
|
|
63
|
+
format: Schema.optional(Schema.String),
|
|
64
|
+
}).annotate({ identifier: "Media.Info" });
|
|
65
|
+
/** A provider-side partial result such as stripped audio or a moderated sample; never a silent drop. */
|
|
66
|
+
export const Notice = Schema.Struct({
|
|
67
|
+
type: Schema.Literals(["moderated", "filtered", "other"]),
|
|
68
|
+
message: Schema.String,
|
|
69
|
+
providerMetadata: Schema.optional(ProviderMetadata),
|
|
70
|
+
}).annotate({ identifier: "Media.Notice" });
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Asset
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const invalid = (message, cause) => new AIError({ reason: new InvalidRequestError({ message, cause }) });
|
|
75
|
+
export class Asset {
|
|
76
|
+
source;
|
|
77
|
+
/** Derived from the source: declared type, sniffed magic bytes, then `application/octet-stream`. */
|
|
78
|
+
mediaType;
|
|
79
|
+
kind;
|
|
80
|
+
info;
|
|
81
|
+
/** Epoch milliseconds after which a `url` source stops resolving. */
|
|
82
|
+
expiresAt;
|
|
83
|
+
providerMetadata;
|
|
84
|
+
/** Transient download credentials for `url` sources; see `Asset.Input.headers`. */
|
|
85
|
+
headers;
|
|
86
|
+
// Derived payload forms are cached on the instance because every protocol lowering re-reads the same payload. The
|
|
87
|
+
// cache is check-then-set (concurrent first reads of a `url` source may both download) and is never observable
|
|
88
|
+
// through `source`, so round-tripping through `Media.from(asset.source)` stays lossless.
|
|
89
|
+
#bytes;
|
|
90
|
+
#base64;
|
|
91
|
+
constructor(input) {
|
|
92
|
+
this.source = input.source;
|
|
93
|
+
this.mediaType =
|
|
94
|
+
input.source.mediaType ??
|
|
95
|
+
(input.source.type === "bytes" ? detectMediaType(input.source.data) : undefined) ??
|
|
96
|
+
OCTET_STREAM;
|
|
97
|
+
this.kind = kindOf(this.mediaType);
|
|
98
|
+
this.info = input.info;
|
|
99
|
+
this.expiresAt = input.source.type === "url" ? input.source.expiresAt : undefined;
|
|
100
|
+
this.providerMetadata = input.providerMetadata;
|
|
101
|
+
this.headers = input.source.type === "url" ? input.headers : undefined;
|
|
102
|
+
}
|
|
103
|
+
/** Inline payload without effects, for protocols that embed base64 or data URLs directly. */
|
|
104
|
+
inline() {
|
|
105
|
+
const source = this.source;
|
|
106
|
+
if (source.type !== "bytes" && source.type !== "base64")
|
|
107
|
+
return undefined;
|
|
108
|
+
const base64 = source.type === "base64" ? source.data : (this.#base64 ??= Encoding.encodeBase64(source.data));
|
|
109
|
+
const mime = this.mediaType.toLowerCase();
|
|
110
|
+
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` };
|
|
111
|
+
}
|
|
112
|
+
/** Decoded payload; downloads `url` sources through the request executor and caches the result. */
|
|
113
|
+
bytes() {
|
|
114
|
+
return Effect.suspend(() => {
|
|
115
|
+
const source = this.source;
|
|
116
|
+
if (source.type === "bytes")
|
|
117
|
+
return Effect.succeed(source.data);
|
|
118
|
+
if (this.#bytes !== undefined)
|
|
119
|
+
return Effect.succeed(this.#bytes);
|
|
120
|
+
if (source.type === "ref")
|
|
121
|
+
return Effect.fail(invalid(`Cannot materialize provider ref ${source.provider}:${source.id}`));
|
|
122
|
+
const decoded = source.type === "base64"
|
|
123
|
+
? Effect.fromResult(Encoding.decodeBase64(source.data)).pipe(Effect.mapError((cause) => invalid(`Media asset contains invalid base64 data`, cause)))
|
|
124
|
+
: download(source, this.headers);
|
|
125
|
+
return decoded.pipe(Effect.tap((data) => Effect.sync(() => (this.#bytes = data))));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
base64() {
|
|
129
|
+
return Effect.suspend(() => {
|
|
130
|
+
const source = this.source;
|
|
131
|
+
if (source.type === "base64")
|
|
132
|
+
return Effect.succeed(source.data);
|
|
133
|
+
if (this.#base64 !== undefined)
|
|
134
|
+
return Effect.succeed(this.#base64);
|
|
135
|
+
return this.bytes().pipe(Effect.map((data) => (this.#base64 = Encoding.encodeBase64(data))));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
dataUrl() {
|
|
139
|
+
return this.base64().pipe(Effect.map((data) => `data:${this.mediaType};base64,${data}`));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The `AssetEncoded` JSON form with `bytes` sources as base64, matching `Schema.toCodecJson(AssetSchema)`, so a
|
|
143
|
+
* plain `JSON.stringify` of messages or events stays lossless and decodes back through the JSON codec.
|
|
144
|
+
*/
|
|
145
|
+
toJSON() {
|
|
146
|
+
const source = this.source;
|
|
147
|
+
return {
|
|
148
|
+
source: source.type === "bytes" ? { ...source, data: Encoding.encodeBase64(source.data) } : source,
|
|
149
|
+
info: this.info,
|
|
150
|
+
providerMetadata: this.providerMetadata,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Pull `url` sources into owned bytes before the URL expires. Inline sources return themselves. */
|
|
154
|
+
materialize() {
|
|
155
|
+
if (this.source.type === "bytes" || this.source.type === "base64")
|
|
156
|
+
return Effect.succeed(this);
|
|
157
|
+
return this.bytes().pipe(Effect.map((data) => bytes(data, this.source.mediaType, { info: this.info, providerMetadata: this.providerMetadata })));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** JSON form of an asset: the serializable `Source` plus caller-supplied metadata. `bytes` sources encode as base64. */
|
|
161
|
+
export const AssetEncoded = Schema.Struct({
|
|
162
|
+
source: Source,
|
|
163
|
+
info: Schema.optional(Info),
|
|
164
|
+
providerMetadata: Schema.optional(ProviderMetadata),
|
|
165
|
+
}).annotate({ identifier: "Media.AssetEncoded" });
|
|
166
|
+
const encodeAsset = (asset) => ({
|
|
167
|
+
source: asset.source,
|
|
168
|
+
info: asset.info,
|
|
169
|
+
providerMetadata: asset.providerMetadata,
|
|
170
|
+
});
|
|
171
|
+
const AssetInstance = Schema.declare((value) => value instanceof Asset, {
|
|
172
|
+
expected: "Media.Asset",
|
|
173
|
+
});
|
|
174
|
+
/** `Asset` in the type domain and `AssetEncoded` on the wire, so messages and events holding assets serialize. */
|
|
175
|
+
export const AssetSchema = AssetEncoded.pipe(Schema.decodeTo(AssetInstance, {
|
|
176
|
+
decode: SchemaGetter.transform((encoded) => new Asset(encoded)),
|
|
177
|
+
encode: SchemaGetter.transform(encodeAsset),
|
|
178
|
+
}));
|
|
179
|
+
const download = Effect.fn("Media.download")(function* (source, headers) {
|
|
180
|
+
const executor = yield* Service;
|
|
181
|
+
const response = yield* executor.execute(HttpClientRequest.get(source.url).pipe(HttpClientRequest.setHeaders(headers ?? {})));
|
|
182
|
+
const buffer = yield* response.arrayBuffer.pipe(Effect.mapError((cause) => new AIError({
|
|
183
|
+
reason: new InvalidProviderOutputError({
|
|
184
|
+
message: `Failed to read media from ${source.url}`,
|
|
185
|
+
http: new HttpContext({ url: response.request.url, status: response.status, headers: response.headers }),
|
|
186
|
+
cause,
|
|
187
|
+
}),
|
|
188
|
+
})));
|
|
189
|
+
return new Uint8Array(buffer);
|
|
190
|
+
});
|
|
191
|
+
export const from = (source, options) => new Asset({ ...options, source });
|
|
192
|
+
export const bytes = (data, mediaType, options) => from({ type: "bytes", data, mediaType: mediaType ?? detectMediaType(data) ?? OCTET_STREAM }, options);
|
|
193
|
+
export const base64 = (data, mediaType, options) => from({ type: "base64", data, mediaType }, options);
|
|
194
|
+
export const url = (value, options) => {
|
|
195
|
+
const { mediaType, expiresAt, ...rest } = options ?? {};
|
|
196
|
+
return from({ type: "url", url: value, mediaType, expiresAt }, rest);
|
|
197
|
+
};
|
|
198
|
+
export const ref = (provider, id, mediaType, options) => from({ type: "ref", provider: ProviderID.make(provider), id, mediaType }, options);
|
|
199
|
+
const DATA_URL = /^data:([^;,]+)(?:;[^,]*)*;base64,(.*)$/s;
|
|
200
|
+
/** Parse a `data:<mime>;base64,<data>` URL, or `undefined` when the value is not a base64 data URL. */
|
|
201
|
+
export const parseDataUrl = (value, options) => {
|
|
202
|
+
const match = DATA_URL.exec(value);
|
|
203
|
+
return match === null ? undefined : base64(match[2], match[1], options);
|
|
204
|
+
};
|
|
205
|
+
/** Parse a `data:<mime>;base64,<data>` URL. Malformed input throws a typed `AIError` because constructors are sync. */
|
|
206
|
+
export const fromDataUrl = (dataUrl, options) => {
|
|
207
|
+
const asset = parseDataUrl(dataUrl, options);
|
|
208
|
+
if (asset === undefined)
|
|
209
|
+
throw invalid("Media data URLs must contain a MIME type and base64 data");
|
|
210
|
+
return asset;
|
|
211
|
+
};
|
|
212
|
+
/** Read a file through `FileSystem` and sniff its media type from magic bytes, then the extension. */
|
|
213
|
+
export const file = (path, options) => Effect.gen(function* () {
|
|
214
|
+
const fs = yield* FileSystem.FileSystem;
|
|
215
|
+
const data = yield* fs
|
|
216
|
+
.readFile(path)
|
|
217
|
+
.pipe(Effect.mapError((cause) => invalid(`Failed to read media file ${path}`, cause)));
|
|
218
|
+
return bytes(data, detectMediaType(data) ?? extensionMediaType(path), options);
|
|
219
|
+
});
|
|
220
|
+
/** Materialize an asset and write its bytes through `FileSystem`. */
|
|
221
|
+
export const write = (asset, path) => Effect.gen(function* () {
|
|
222
|
+
const fs = yield* FileSystem.FileSystem;
|
|
223
|
+
const data = yield* asset.bytes();
|
|
224
|
+
yield* fs
|
|
225
|
+
.writeFile(path, data)
|
|
226
|
+
.pipe(Effect.mapError((cause) => invalid(`Failed to write media file ${path}`, cause)));
|
|
227
|
+
});
|