@opencode/ai 0.0.0-dev-20035 → 0.0.0-dev-20038
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 +80 -0
- package/dist/generation.d.ts +23 -13
- package/dist/generation.js +42 -24
- package/dist/image-client.js +1 -1
- package/dist/image.d.ts +7 -36
- package/dist/image.js +8 -38
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -0
- package/dist/media-model.d.ts +42 -0
- package/dist/media-model.js +39 -0
- package/dist/media.d.ts +10 -9
- package/dist/media.js +9 -10
- package/dist/promise.d.ts +126 -3
- package/dist/promise.js +18 -1
- package/dist/protocols/fal-video.d.ts +39 -0
- package/dist/protocols/fal-video.js +139 -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/openai-images.js +1 -12
- package/dist/protocols/runway-video.d.ts +38 -0
- package/dist/protocols/runway-video.js +146 -0
- package/dist/protocols/shared.d.ts +10 -0
- package/dist/protocols/shared.js +17 -0
- package/dist/protocols/xai-images.js +1 -12
- package/dist/protocols/xai-video.d.ts +34 -0
- package/dist/protocols/xai-video.js +147 -0
- package/dist/protocols/zai-chat.d.ts +1 -1
- package/dist/providers/fal.d.ts +24 -0
- package/dist/providers/fal.js +29 -0
- package/dist/providers/google.d.ts +5 -0
- package/dist/providers/google.js +5 -2
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/runway.d.ts +24 -0
- package/dist/providers/runway.js +22 -0
- package/dist/providers/xai.d.ts +5 -0
- package/dist/providers/xai.js +5 -2
- package/dist/providers/zai-coding-plan.d.ts +1 -1
- package/dist/providers/zai.d.ts +1 -1
- package/dist/route/auth.d.ts +4 -1
- package/dist/route/auth.js +6 -0
- package/dist/route/media-protocol.d.ts +68 -2
- package/dist/route/media-protocol.js +32 -3
- package/dist/route/media.d.ts +28 -5
- package/dist/route/media.js +95 -14
- package/dist/schema/events.d.ts +0 -6
- package/dist/schema/messages.d.ts +0 -3
- package/dist/video-client.d.ts +28 -0
- package/dist/video-client.js +43 -0
- package/dist/video.d.ts +1373 -0
- package/dist/video.js +131 -0
- package/package.json +3 -3
|
@@ -48,18 +48,7 @@ const blob = (data, mediaType) => {
|
|
|
48
48
|
new Uint8Array(buffer).set(data);
|
|
49
49
|
return new Blob([buffer], { type: mediaType });
|
|
50
50
|
};
|
|
51
|
-
const reference = (asset) => {
|
|
52
|
-
const inline = asset.inline();
|
|
53
|
-
if (inline)
|
|
54
|
-
return Effect.succeed({ image_url: inline.dataUrl });
|
|
55
|
-
const url = ProviderShared.mediaUrl(asset);
|
|
56
|
-
if (url)
|
|
57
|
-
return Effect.succeed({ image_url: url });
|
|
58
|
-
const id = MediaInput.refID(asset, PROVIDER);
|
|
59
|
-
if (id)
|
|
60
|
-
return Effect.succeed({ file_id: id });
|
|
61
|
-
return Effect.fail(ProviderShared.invalidRequest("OpenAI Images accepts image URLs, data URLs, bytes, and OpenAI file IDs"));
|
|
62
|
-
};
|
|
51
|
+
const reference = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((item) => (item.type === "ref" ? { file_id: item.value } : { image_url: item.value })));
|
|
63
52
|
const fromRequest = Effect.fn("OpenAIImages.fromRequest")(function* (request) {
|
|
64
53
|
const images = request.images ?? [];
|
|
65
54
|
const mask = request.mask;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://api.dev.runwayml.com/v1";
|
|
6
|
+
/** Every Runway request must pin the API version. */
|
|
7
|
+
export declare const API_VERSION = "2024-11-06";
|
|
8
|
+
export declare const TEXT_TO_VIDEO_PATH = "/text_to_video";
|
|
9
|
+
export declare const IMAGE_TO_VIDEO_PATH = "/image_to_video";
|
|
10
|
+
export declare const VIDEO_TO_VIDEO_PATH = "/video_to_video";
|
|
11
|
+
export declare const TASKS_PATH = "/tasks";
|
|
12
|
+
export type RunwayVideoString<Known extends string> = Known | (string & {});
|
|
13
|
+
/**
|
|
14
|
+
* Provider-native options. Common fields lower to Runway's names: `aspectRatio` → `ratio` (Runway expects pixel
|
|
15
|
+
* ratios such as `1280:720` for most models), `durationSeconds` → `duration`, `audio`, `negativePrompt`,
|
|
16
|
+
* `resolution`, `references`, and `frames` → `promptImage`.
|
|
17
|
+
*/
|
|
18
|
+
export type RunwayVideoOptions = {
|
|
19
|
+
readonly contentModeration?: {
|
|
20
|
+
readonly publicFigureThreshold?: RunwayVideoString<"auto" | "low">;
|
|
21
|
+
};
|
|
22
|
+
readonly outputFormat?: RunwayVideoString<"mp4" | "prores" | "png_sequence">;
|
|
23
|
+
} & Record<string, unknown>;
|
|
24
|
+
export type Request = VideoRequestFor<RunwayVideoOptions>;
|
|
25
|
+
export declare const Token: Schema.Struct<{
|
|
26
|
+
readonly taskID: Schema.String;
|
|
27
|
+
}>;
|
|
28
|
+
export type Token = Schema.Schema.Type<typeof Token>;
|
|
29
|
+
export declare const protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
30
|
+
readonly taskID: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const model: (input: MediaRoute.ModelInput) => VideoModel<RunwayVideoOptions>;
|
|
33
|
+
export declare const RunwayVideo: {
|
|
34
|
+
readonly protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
35
|
+
readonly taskID: string;
|
|
36
|
+
}>;
|
|
37
|
+
readonly model: (input: MediaRoute.ModelInput) => VideoModel<RunwayVideoOptions>;
|
|
38
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Duration, Effect, Schema } from "effect";
|
|
2
|
+
import { Media } from "../media.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 { VideoModel, VideoResponse } from "../video.js";
|
|
7
|
+
import { ProviderShared, optionalArray, optionalNull } from "./shared.js";
|
|
8
|
+
const ADAPTER = "runway-video";
|
|
9
|
+
const NAME = "Runway";
|
|
10
|
+
const PROVIDER = ProviderID.make("runway");
|
|
11
|
+
export const DEFAULT_BASE_URL = "https://api.dev.runwayml.com/v1";
|
|
12
|
+
/** Every Runway request must pin the API version. */
|
|
13
|
+
export const API_VERSION = "2024-11-06";
|
|
14
|
+
export const TEXT_TO_VIDEO_PATH = "/text_to_video";
|
|
15
|
+
export const IMAGE_TO_VIDEO_PATH = "/image_to_video";
|
|
16
|
+
export const VIDEO_TO_VIDEO_PATH = "/video_to_video";
|
|
17
|
+
export const TASKS_PATH = "/tasks";
|
|
18
|
+
/** Output URLs are valid for 24–48 hours; the asset carries the conservative bound. */
|
|
19
|
+
const OUTPUT_RETENTION = Duration.hours(24);
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// 2. Token and response schemas
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export const Token = Schema.Struct({ taskID: Schema.String });
|
|
24
|
+
const Cost = Schema.Struct({ credits: Schema.Number });
|
|
25
|
+
const StartResponse = Schema.Struct({ id: Schema.String });
|
|
26
|
+
const Task = Schema.Struct({
|
|
27
|
+
status: Schema.String,
|
|
28
|
+
progress: optionalNull(Schema.Number),
|
|
29
|
+
output: optionalArray(Schema.String),
|
|
30
|
+
failure: optionalNull(Schema.String),
|
|
31
|
+
failureCode: optionalNull(Schema.String),
|
|
32
|
+
cost: Schema.optional(Cost),
|
|
33
|
+
estimatedCost: Schema.optional(Cost),
|
|
34
|
+
});
|
|
35
|
+
const STATUS = {
|
|
36
|
+
PENDING: "queued",
|
|
37
|
+
THROTTLED: "queued",
|
|
38
|
+
RUNNING: "running",
|
|
39
|
+
SUCCEEDED: "completed",
|
|
40
|
+
FAILED: "failed",
|
|
41
|
+
CANCELLED: "cancelled",
|
|
42
|
+
};
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// 5. Request body construction
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Runway accepts HTTPS URLs, `runway://` upload URIs, and data URIs, all as one string.
|
|
47
|
+
const mediaUri = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((reference) => reference.value));
|
|
48
|
+
const fromRequest = Effect.fn("RunwayVideo.fromRequest")(function* (request) {
|
|
49
|
+
const first = request.frames?.first === undefined ? undefined : yield* mediaUri(request.frames.first);
|
|
50
|
+
const last = request.frames?.last === undefined ? undefined : yield* mediaUri(request.frames.last);
|
|
51
|
+
const promptImage = [
|
|
52
|
+
...(first === undefined ? [] : [{ uri: first, position: "first" }]),
|
|
53
|
+
...(last === undefined ? [] : [{ uri: last, position: "last" }]),
|
|
54
|
+
];
|
|
55
|
+
const videoUri = request.video === undefined ? undefined : yield* mediaUri(request.video);
|
|
56
|
+
const references = yield* Effect.forEach(request.references ?? [], (asset) => mediaUri(asset).pipe(Effect.map((uri) => ({ uri }))));
|
|
57
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
58
|
+
model: request.model.id,
|
|
59
|
+
promptText: request.prompt,
|
|
60
|
+
promptImage: promptImage.length === 0 ? undefined : promptImage,
|
|
61
|
+
videoUri,
|
|
62
|
+
references: references.length === 0 ? undefined : references,
|
|
63
|
+
ratio: request.aspectRatio,
|
|
64
|
+
duration: request.durationSeconds,
|
|
65
|
+
resolution: request.resolution,
|
|
66
|
+
audio: request.audio,
|
|
67
|
+
negativePrompt: request.negativePrompt,
|
|
68
|
+
seed: request.seed,
|
|
69
|
+
}, request.providerOptions, request.http?.body) ?? {});
|
|
70
|
+
});
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// 6. Response decoding
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const decodeStart = MediaProtocol.decodeStarted(ADAPTER, NAME, StartResponse, (value) => ({
|
|
75
|
+
token: { taskID: value.id },
|
|
76
|
+
snapshot: { id: value.id, status: "queued" },
|
|
77
|
+
}));
|
|
78
|
+
const decodeTask = MediaProtocol.decodeJson(ADAPTER, NAME, Task);
|
|
79
|
+
const decodeStatus = Effect.fn("RunwayVideo.decodeStatus")(function* (response, context) {
|
|
80
|
+
const output = yield* decodeTask(response);
|
|
81
|
+
const status = yield* MediaProtocol.status(STATUS, output.value.status, output);
|
|
82
|
+
return { id: context.token.taskID, status, progress: output.value.progress ?? undefined };
|
|
83
|
+
});
|
|
84
|
+
const decodeResult = Effect.fn("RunwayVideo.decodeResult")(function* (response, context) {
|
|
85
|
+
const output = yield* decodeTask(response);
|
|
86
|
+
const task = output.value;
|
|
87
|
+
const status = yield* MediaProtocol.status(STATUS, task.status, output);
|
|
88
|
+
if (status === "failed") {
|
|
89
|
+
const code = task.failureCode ?? undefined;
|
|
90
|
+
const message = `${NAME} task failed${code === undefined ? "" : ` (${code})`}${task.failure ? `: ${task.failure}` : ""}`;
|
|
91
|
+
// Runway failure codes are dotted paths; every moderation outcome carries a SAFETY segment.
|
|
92
|
+
if (code !== undefined && /(^|\.)SAFETY(\.|$)/.test(code))
|
|
93
|
+
return yield* output.contentPolicy(message);
|
|
94
|
+
return yield* output.ended("failed", message);
|
|
95
|
+
}
|
|
96
|
+
if (status === "cancelled")
|
|
97
|
+
return yield* output.ended("cancelled", `${NAME} task ${context.token.taskID} was cancelled`);
|
|
98
|
+
if (status !== "completed")
|
|
99
|
+
return yield* output.invalid(`${NAME} task ${context.token.taskID} has not finished`);
|
|
100
|
+
const urls = task.output ?? [];
|
|
101
|
+
if (urls.length === 0)
|
|
102
|
+
return yield* output.invalid(`${NAME} task succeeded without any output`);
|
|
103
|
+
return new VideoResponse({
|
|
104
|
+
videos: yield* Effect.forEach(urls, (url) => MediaProtocol.expiringUrl(url, OUTPUT_RETENTION, { mediaType: "video/mp4" })),
|
|
105
|
+
usage: task.cost === undefined ? undefined : { type: "credits", credits: task.cost.credits },
|
|
106
|
+
providerMetadata: {
|
|
107
|
+
runway: {
|
|
108
|
+
taskId: context.token.taskID,
|
|
109
|
+
estimatedCredits: task.estimatedCost?.credits,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// 7. Protocol and route
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
const taskPath = (token) => `${TASKS_PATH}/${token.taskID}`;
|
|
118
|
+
export const protocol = MediaProtocol.queued({
|
|
119
|
+
id: ADAPTER,
|
|
120
|
+
name: NAME,
|
|
121
|
+
token: Token,
|
|
122
|
+
unsupported: ["n"],
|
|
123
|
+
start: { body: { from: fromRequest }, decode: decodeStart },
|
|
124
|
+
status: { path: taskPath, decode: decodeStatus },
|
|
125
|
+
result: { path: taskPath, decode: decodeResult },
|
|
126
|
+
cancel: { method: "DELETE", path: taskPath },
|
|
127
|
+
});
|
|
128
|
+
const startPath = (request) => {
|
|
129
|
+
if (request.video !== undefined)
|
|
130
|
+
return VIDEO_TO_VIDEO_PATH;
|
|
131
|
+
if (request.frames?.first !== undefined || request.frames?.last !== undefined)
|
|
132
|
+
return IMAGE_TO_VIDEO_PATH;
|
|
133
|
+
return TEXT_TO_VIDEO_PATH;
|
|
134
|
+
};
|
|
135
|
+
export const model = (input) => VideoModel.fromRoute({
|
|
136
|
+
id: ADAPTER,
|
|
137
|
+
provider: PROVIDER,
|
|
138
|
+
protocol,
|
|
139
|
+
baseURL: DEFAULT_BASE_URL,
|
|
140
|
+
headers: { "X-Runway-Version": API_VERSION },
|
|
141
|
+
path: ({ request }) => startPath(request),
|
|
142
|
+
}, input);
|
|
143
|
+
export const RunwayVideo = {
|
|
144
|
+
protocol,
|
|
145
|
+
model,
|
|
146
|
+
};
|
|
@@ -116,6 +116,16 @@ export declare const requireInlineMedia: (route: string, asset: Media.Asset) =>
|
|
|
116
116
|
export declare const inlineRequired: (route: string, asset: Media.Asset) => AIError;
|
|
117
117
|
/** The remote URL of a `url` asset, for protocols that accept `http(s)` references natively. */
|
|
118
118
|
export declare const mediaUrl: (asset: Media.Asset) => string | undefined;
|
|
119
|
+
export type MediaReference = {
|
|
120
|
+
readonly type: "dataUrl" | "url" | "ref";
|
|
121
|
+
readonly value: string;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* The one string a provider can address an asset by: inline payloads as a data URL, `url` sources as their URL, and
|
|
125
|
+
* this provider's own `ref` as its id. Other providers' refs are never forwarded and fail typed; omit `provider` for
|
|
126
|
+
* APIs with no file handles at all.
|
|
127
|
+
*/
|
|
128
|
+
export declare const mediaReference: (asset: Media.Asset, provider: ProviderID | undefined, label: string) => Effect.Effect<MediaReference, AIError>;
|
|
119
129
|
/**
|
|
120
130
|
* Lift a tool-result file into a `MediaPart`. Tool files carry either a data URL, an `http(s)` URL, or raw base64 in
|
|
121
131
|
* `uri`; the declared `mime` wins over any data-URL prefix so tool authors control the type the model sees.
|
package/dist/protocols/shared.js
CHANGED
|
@@ -134,6 +134,23 @@ export const requireInlineMedia = (route, asset) => {
|
|
|
134
134
|
export const inlineRequired = (route, asset) => invalidRequest(`${route} requires inline media (bytes or base64); ${asset.source.type} sources must be materialized first`);
|
|
135
135
|
/** The remote URL of a `url` asset, for protocols that accept `http(s)` references natively. */
|
|
136
136
|
export const mediaUrl = (asset) => (asset.source.type === "url" ? asset.source.url : undefined);
|
|
137
|
+
/**
|
|
138
|
+
* The one string a provider can address an asset by: inline payloads as a data URL, `url` sources as their URL, and
|
|
139
|
+
* this provider's own `ref` as its id. Other providers' refs are never forwarded and fail typed; omit `provider` for
|
|
140
|
+
* APIs with no file handles at all.
|
|
141
|
+
*/
|
|
142
|
+
export const mediaReference = (asset, provider, label) => {
|
|
143
|
+
const inline = asset.inline();
|
|
144
|
+
if (inline)
|
|
145
|
+
return Effect.succeed({ type: "dataUrl", value: inline.dataUrl });
|
|
146
|
+
const url = mediaUrl(asset);
|
|
147
|
+
if (url)
|
|
148
|
+
return Effect.succeed({ type: "url", value: url });
|
|
149
|
+
if (provider !== undefined && asset.source.type === "ref" && asset.source.provider === provider)
|
|
150
|
+
return Effect.succeed({ type: "ref", value: asset.source.id });
|
|
151
|
+
const accepted = provider === undefined ? "" : `, and ${provider} references`;
|
|
152
|
+
return Effect.fail(invalidRequest(`${label} accepts inline bytes, data URLs, http(s) URLs${accepted}`));
|
|
153
|
+
};
|
|
137
154
|
/**
|
|
138
155
|
* Lift a tool-result file into a `MediaPart`. Tool files carry either a data URL, an `http(s)` URL, or raw base64 in
|
|
139
156
|
* `uri`; the declared `mime` wins over any data-URL prefix so tool authors control the type the model sees.
|
|
@@ -34,18 +34,7 @@ const nativeOptions = (options) => {
|
|
|
34
34
|
return { response_format: responseFormat, ...native };
|
|
35
35
|
};
|
|
36
36
|
const isEdit = (request) => (request.images?.length ?? 0) > 0;
|
|
37
|
-
const reference = (asset) => {
|
|
38
|
-
const inline = asset.inline();
|
|
39
|
-
if (inline)
|
|
40
|
-
return Effect.succeed({ url: inline.dataUrl, type: "image_url" });
|
|
41
|
-
const url = ProviderShared.mediaUrl(asset);
|
|
42
|
-
if (url)
|
|
43
|
-
return Effect.succeed({ url, type: "image_url" });
|
|
44
|
-
const id = MediaInput.refID(asset, PROVIDER);
|
|
45
|
-
if (id)
|
|
46
|
-
return Effect.succeed({ file_id: id });
|
|
47
|
-
return Effect.fail(ProviderShared.invalidRequest(`${NAME} accepts image URLs, data URLs, bytes, and xAI file IDs`));
|
|
48
|
-
};
|
|
37
|
+
const reference = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((item) => item.type === "ref" ? { file_id: item.value } : { url: item.value, type: "image_url" }));
|
|
49
38
|
const fromRequest = Effect.fn("XAIImages.fromRequest")(function* (request) {
|
|
50
39
|
const references = yield* Effect.forEach(request.images ?? [], reference);
|
|
51
40
|
return MediaProtocol.json(mergeJsonRecords({
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { MediaProtocol } from "../route/media-protocol.js";
|
|
3
|
+
import { MediaRoute } from "../route/media.js";
|
|
4
|
+
import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
6
|
+
export declare const PATH = "/videos/generations";
|
|
7
|
+
export declare const EDIT_PATH = "/videos/edits";
|
|
8
|
+
export declare const EXTEND_PATH = "/videos/extensions";
|
|
9
|
+
export declare const STATUS_PATH = "/videos";
|
|
10
|
+
/**
|
|
11
|
+
* Provider-native options. Common fields (`frames`, `references`, `video`, `durationSeconds`, `aspectRatio`,
|
|
12
|
+
* `resolution`, `audio`) live on the request. `mode` selects the endpoint a `video` input is sent to.
|
|
13
|
+
*/
|
|
14
|
+
export type XAIVideoOptions = {
|
|
15
|
+
readonly mode?: "edit" | "extend";
|
|
16
|
+
readonly reference_audios?: ReadonlyArray<{
|
|
17
|
+
readonly voice_id: string;
|
|
18
|
+
}>;
|
|
19
|
+
} & Record<string, unknown>;
|
|
20
|
+
export type Request = VideoRequestFor<XAIVideoOptions>;
|
|
21
|
+
export declare const Token: Schema.Struct<{
|
|
22
|
+
readonly requestID: Schema.String;
|
|
23
|
+
}>;
|
|
24
|
+
export type Token = Schema.Schema.Type<typeof Token>;
|
|
25
|
+
export declare const protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
26
|
+
readonly requestID: string;
|
|
27
|
+
}>;
|
|
28
|
+
export declare const model: (input: MediaRoute.ModelInput) => VideoModel<XAIVideoOptions>;
|
|
29
|
+
export declare const XAIVideo: {
|
|
30
|
+
readonly protocol: MediaProtocol.Queued<Request, VideoResponse, {
|
|
31
|
+
readonly requestID: string;
|
|
32
|
+
}>;
|
|
33
|
+
readonly model: (input: MediaRoute.ModelInput) => VideoModel<XAIVideoOptions>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { Media } from "../media.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 { VideoModel, VideoResponse } from "../video.js";
|
|
7
|
+
import { ProviderShared, optionalNull } from "./shared.js";
|
|
8
|
+
const ADAPTER = "xai-video";
|
|
9
|
+
const NAME = "xAI Video";
|
|
10
|
+
const PROVIDER = ProviderID.make("xai");
|
|
11
|
+
export const DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
12
|
+
export const PATH = "/videos/generations";
|
|
13
|
+
export const EDIT_PATH = "/videos/edits";
|
|
14
|
+
export const EXTEND_PATH = "/videos/extensions";
|
|
15
|
+
export const STATUS_PATH = "/videos";
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// 2. Token and response schemas
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
export const Token = Schema.Struct({ requestID: Schema.String });
|
|
20
|
+
const StartResponse = Schema.Struct({ request_id: Schema.String });
|
|
21
|
+
const VideoStatus = Schema.Struct({
|
|
22
|
+
status: Schema.String,
|
|
23
|
+
progress: optionalNull(Schema.Number),
|
|
24
|
+
video: optionalNull(Schema.Struct({
|
|
25
|
+
url: optionalNull(Schema.String),
|
|
26
|
+
duration: optionalNull(Schema.Number),
|
|
27
|
+
respect_moderation: optionalNull(Schema.Boolean),
|
|
28
|
+
})),
|
|
29
|
+
error: optionalNull(Schema.Struct({
|
|
30
|
+
code: optionalNull(Schema.String),
|
|
31
|
+
message: optionalNull(Schema.String),
|
|
32
|
+
})),
|
|
33
|
+
model: optionalNull(Schema.String),
|
|
34
|
+
});
|
|
35
|
+
const STATUS = {
|
|
36
|
+
pending: "running",
|
|
37
|
+
done: "completed",
|
|
38
|
+
failed: "failed",
|
|
39
|
+
expired: "expired",
|
|
40
|
+
};
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// 5. Request body construction
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
const mediaInput = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((reference) => (reference.type === "ref" ? { file_id: reference.value } : { url: reference.value })));
|
|
45
|
+
const nativeOptions = (options) => {
|
|
46
|
+
if (!options)
|
|
47
|
+
return undefined;
|
|
48
|
+
const { mode: _mode, ...native } = options;
|
|
49
|
+
return native;
|
|
50
|
+
};
|
|
51
|
+
const fromRequest = Effect.fn("XAIVideo.fromRequest")(function* (request) {
|
|
52
|
+
const image = request.frames?.first === undefined ? undefined : yield* mediaInput(request.frames.first);
|
|
53
|
+
const lastFrame = request.frames?.last === undefined ? undefined : yield* mediaInput(request.frames.last);
|
|
54
|
+
const video = request.video === undefined ? undefined : yield* mediaInput(request.video);
|
|
55
|
+
const references = yield* Effect.forEach(request.references ?? [], mediaInput);
|
|
56
|
+
return MediaProtocol.json(mergeJsonRecords({
|
|
57
|
+
model: request.model.id,
|
|
58
|
+
prompt: request.prompt,
|
|
59
|
+
image,
|
|
60
|
+
last_frame: lastFrame,
|
|
61
|
+
reference_images: references.length === 0 ? undefined : references,
|
|
62
|
+
video,
|
|
63
|
+
duration: request.durationSeconds,
|
|
64
|
+
aspect_ratio: request.aspectRatio,
|
|
65
|
+
resolution: request.resolution,
|
|
66
|
+
generate_audio: request.audio,
|
|
67
|
+
}, nativeOptions(request.providerOptions), request.http?.body) ?? {});
|
|
68
|
+
});
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// 6. Response decoding
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
const decodeStart = MediaProtocol.decodeStarted(ADAPTER, NAME, StartResponse, (value) => ({
|
|
73
|
+
token: { requestID: value.request_id },
|
|
74
|
+
snapshot: { id: value.request_id, status: "running" },
|
|
75
|
+
}));
|
|
76
|
+
// `progress` is undocumented but observed live as a 0..100 percentage (recorded cassette: 1 → 10 → 37 → 100).
|
|
77
|
+
const fraction = (progress) => progress !== undefined && progress !== null && progress >= 0 && progress <= 100 ? progress / 100 : undefined;
|
|
78
|
+
const decodeVideoStatus = MediaProtocol.decodeJson(ADAPTER, NAME, VideoStatus);
|
|
79
|
+
const decodeStatus = Effect.fn("XAIVideo.decodeStatus")(function* (response, context) {
|
|
80
|
+
const output = yield* decodeVideoStatus(response);
|
|
81
|
+
const status = yield* MediaProtocol.status(STATUS, output.value.status, output);
|
|
82
|
+
return { id: context.token.requestID, status, progress: fraction(output.value.progress) };
|
|
83
|
+
});
|
|
84
|
+
const decodeResult = Effect.fn("XAIVideo.decodeResult")(function* (response, context) {
|
|
85
|
+
const output = yield* decodeVideoStatus(response);
|
|
86
|
+
const decoded = output.value;
|
|
87
|
+
const status = yield* MediaProtocol.status(STATUS, decoded.status, output);
|
|
88
|
+
if (status === "running")
|
|
89
|
+
return yield* output.invalid(`${NAME} request ${context.token.requestID} has not finished`);
|
|
90
|
+
if (status === "failed") {
|
|
91
|
+
const code = decoded.error?.code ?? undefined;
|
|
92
|
+
const message = decoded.error?.message ?? undefined;
|
|
93
|
+
return yield* output.ended("failed", `${NAME} generation failed${code === undefined ? "" : ` (${code})`}${message === undefined ? "" : `: ${message}`}`);
|
|
94
|
+
}
|
|
95
|
+
if (status !== "completed")
|
|
96
|
+
return yield* output.ended("expired", `${NAME} request ${context.token.requestID} expired`);
|
|
97
|
+
// `respect_moderation: false` marks a filtered result; a URL may still be present, so report it as a notice.
|
|
98
|
+
const notices = decoded.video?.respect_moderation === false
|
|
99
|
+
? [{ type: "moderated", message: `${NAME} flagged the generated video for moderation` }]
|
|
100
|
+
: undefined;
|
|
101
|
+
const url = decoded.video?.url ?? undefined;
|
|
102
|
+
if (url === undefined && notices !== undefined)
|
|
103
|
+
return yield* output.contentPolicy(`${NAME} withheld the video for moderation`);
|
|
104
|
+
if (url === undefined)
|
|
105
|
+
return yield* output.invalid(`${NAME} completed without a video URL`);
|
|
106
|
+
const duration = decoded.video?.duration ?? undefined;
|
|
107
|
+
return new VideoResponse({
|
|
108
|
+
videos: [
|
|
109
|
+
Media.url(url, {
|
|
110
|
+
mediaType: "video/mp4",
|
|
111
|
+
info: duration === undefined ? undefined : { durationSeconds: duration },
|
|
112
|
+
}),
|
|
113
|
+
],
|
|
114
|
+
notices,
|
|
115
|
+
providerMetadata: { xai: { requestId: context.token.requestID, model: decoded.model ?? undefined } },
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// 7. Protocol and route
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
const statusPath = (token) => `${STATUS_PATH}/${token.requestID}`;
|
|
122
|
+
export const protocol = MediaProtocol.queued({
|
|
123
|
+
id: ADAPTER,
|
|
124
|
+
name: NAME,
|
|
125
|
+
token: Token,
|
|
126
|
+
unsupported: ["n", "seed", "negativePrompt"],
|
|
127
|
+
start: { body: { from: fromRequest }, decode: decodeStart },
|
|
128
|
+
status: { path: statusPath, decode: decodeStatus },
|
|
129
|
+
result: { path: statusPath, decode: decodeResult },
|
|
130
|
+
});
|
|
131
|
+
// A source video goes to `/videos/edits` unless `providerOptions.mode` asks for an extension.
|
|
132
|
+
const startPath = (request) => {
|
|
133
|
+
if (request.video === undefined)
|
|
134
|
+
return PATH;
|
|
135
|
+
return request.providerOptions?.mode === "extend" ? EXTEND_PATH : EDIT_PATH;
|
|
136
|
+
};
|
|
137
|
+
export const model = (input) => VideoModel.fromRoute({
|
|
138
|
+
id: ADAPTER,
|
|
139
|
+
provider: PROVIDER,
|
|
140
|
+
protocol,
|
|
141
|
+
baseURL: DEFAULT_BASE_URL,
|
|
142
|
+
path: ({ request }) => startPath(request),
|
|
143
|
+
}, input);
|
|
144
|
+
export const XAIVideo = {
|
|
145
|
+
protocol,
|
|
146
|
+
model,
|
|
147
|
+
};
|
|
@@ -129,8 +129,8 @@ export declare const protocol: Protocol<{
|
|
|
129
129
|
readonly response_format?: {
|
|
130
130
|
readonly type: string;
|
|
131
131
|
} | undefined;
|
|
132
|
-
readonly do_sample?: boolean | undefined;
|
|
133
132
|
readonly request_id?: string | undefined;
|
|
133
|
+
readonly do_sample?: boolean | undefined;
|
|
134
134
|
}, string, "[DONE]" | {
|
|
135
135
|
readonly [x: string]: unknown;
|
|
136
136
|
readonly error?: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ProviderAuthOption } from "../route/auth-options.js";
|
|
2
|
+
import { HttpOptions, type ModelID } from "../schema/index.js";
|
|
3
|
+
export type { FalVideoOptions } from "../protocols/fal-video.js";
|
|
4
|
+
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
5
|
+
export type Config = ProviderAuthOption<"optional"> & {
|
|
6
|
+
readonly baseURL?: string;
|
|
7
|
+
readonly headers?: Record<string, string>;
|
|
8
|
+
readonly http?: HttpOptions.Input;
|
|
9
|
+
};
|
|
10
|
+
export declare const configure: (input?: Config) => {
|
|
11
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
12
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/fal-video.js").FalVideoOptions>;
|
|
13
|
+
configure: (input?: Config) => /*elided*/ any;
|
|
14
|
+
};
|
|
15
|
+
export declare const provider: {
|
|
16
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
17
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/fal-video.js").FalVideoOptions>;
|
|
18
|
+
configure: (input?: Config) => {
|
|
19
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
20
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/fal-video.js").FalVideoOptions>;
|
|
21
|
+
configure: /*elided*/ any;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare const video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/fal-video.js").FalVideoOptions>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Auth } from "../route/auth.js";
|
|
2
|
+
import { HttpOptions, ProviderID } from "../schema/index.js";
|
|
3
|
+
import { DEFAULT_BASE_URL, FalVideo } from "../protocols/fal-video.js";
|
|
4
|
+
export const id = ProviderID.make("fal");
|
|
5
|
+
const baseURL = DEFAULT_BASE_URL;
|
|
6
|
+
// fal authenticates with `Authorization: Key <FAL_KEY>` rather than a bearer token.
|
|
7
|
+
const auth = (options) => {
|
|
8
|
+
if ("auth" in options && options.auth)
|
|
9
|
+
return options.auth;
|
|
10
|
+
return Auth.optional("apiKey" in options ? options.apiKey : undefined, "apiKey")
|
|
11
|
+
.orElse(Auth.config("FAL_KEY"))
|
|
12
|
+
.pipe(Auth.scheme("Key"));
|
|
13
|
+
};
|
|
14
|
+
export const configure = (input = {}) => {
|
|
15
|
+
const video = (modelID) => FalVideo.model({
|
|
16
|
+
id: modelID,
|
|
17
|
+
auth: auth(input),
|
|
18
|
+
baseURL: input.baseURL ?? baseURL,
|
|
19
|
+
headers: input.headers,
|
|
20
|
+
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
video,
|
|
25
|
+
configure,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const provider = configure();
|
|
29
|
+
export const video = provider.video;
|
|
@@ -4,6 +4,7 @@ import type { ProviderPackage } from "../provider-package.js";
|
|
|
4
4
|
import { type ModelID } from "../schema/index.js";
|
|
5
5
|
import { Gemini } from "../protocols/gemini.js";
|
|
6
6
|
export type { GoogleImageOptions } from "../protocols/google-images.js";
|
|
7
|
+
export type { GoogleVideoOptions } from "../protocols/google-video.js";
|
|
7
8
|
export type GeminiOptionsInput = Gemini.OptionsInput;
|
|
8
9
|
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput;
|
|
9
10
|
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
@@ -104,18 +105,22 @@ export declare const configure: (input?: Config) => {
|
|
|
104
105
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
105
106
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.OptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
106
107
|
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
|
|
108
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/google-video.js").GoogleVideoOptions>;
|
|
107
109
|
configure: (input?: Config) => /*elided*/ any;
|
|
108
110
|
};
|
|
109
111
|
export declare const provider: {
|
|
110
112
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
111
113
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.OptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
112
114
|
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
|
|
115
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/google-video.js").GoogleVideoOptions>;
|
|
113
116
|
configure: (input?: Config) => {
|
|
114
117
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
115
118
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.OptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
116
119
|
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
|
|
120
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/google-video.js").GoogleVideoOptions>;
|
|
117
121
|
configure: /*elided*/ any;
|
|
118
122
|
};
|
|
119
123
|
};
|
|
120
124
|
export declare const model: ProviderPackage.Definition<Settings, Gemini.ProviderOptionsInput>["model"];
|
|
121
125
|
export declare const image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
|
|
126
|
+
export declare const video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/google-video.js").GoogleVideoOptions>;
|
package/dist/providers/google.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Auth } from "../route/auth.js";
|
|
|
2
2
|
import { HttpOptions, ProviderID, mergeHttpOptions } from "../schema/index.js";
|
|
3
3
|
import { Gemini } from "../protocols/gemini.js";
|
|
4
4
|
import { GoogleImages } from "../protocols/google-images.js";
|
|
5
|
+
import { GoogleVideo } from "../protocols/google-video.js";
|
|
5
6
|
export const id = ProviderID.make("google");
|
|
6
7
|
export const routes = [Gemini.route];
|
|
7
8
|
const auth = (options) => {
|
|
@@ -17,7 +18,7 @@ const configuredRoute = (input) => {
|
|
|
17
18
|
};
|
|
18
19
|
export const configure = (input = {}) => {
|
|
19
20
|
const route = configuredRoute(input);
|
|
20
|
-
const
|
|
21
|
+
const media = (modelID) => ({
|
|
21
22
|
id: modelID,
|
|
22
23
|
auth: auth(input),
|
|
23
24
|
baseURL: input.baseURL,
|
|
@@ -27,7 +28,8 @@ export const configure = (input = {}) => {
|
|
|
27
28
|
return {
|
|
28
29
|
id,
|
|
29
30
|
model: (modelID) => route.model({ id: modelID }),
|
|
30
|
-
image,
|
|
31
|
+
image: (modelID) => GoogleImages.model(media(modelID)),
|
|
32
|
+
video: (modelID) => GoogleVideo.model(media(modelID)),
|
|
31
33
|
configure,
|
|
32
34
|
};
|
|
33
35
|
};
|
|
@@ -40,3 +42,4 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
|
|
|
40
42
|
providerOptions,
|
|
41
43
|
}).model(modelID);
|
|
42
44
|
export const image = provider.image;
|
|
45
|
+
export const video = provider.video;
|
|
@@ -10,6 +10,7 @@ export * as CloudflareAIGateway from "./cloudflare-ai-gateway.js";
|
|
|
10
10
|
export * as CloudflareWorkersAI from "./cloudflare-workers-ai.js";
|
|
11
11
|
export * as DeepInfra from "./deepinfra.js";
|
|
12
12
|
export * as DeepSeek from "./deepseek.js";
|
|
13
|
+
export * as Fal from "./fal.js";
|
|
13
14
|
export * as Fireworks from "./fireworks.js";
|
|
14
15
|
export * as Google from "./google.js";
|
|
15
16
|
export * as GoogleVertex from "./google-vertex.js";
|
|
@@ -26,6 +27,7 @@ export * as OpenAICompatible from "./openai-compatible.js";
|
|
|
26
27
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
|
|
27
28
|
export * as OpenCodeZen from "./opencode-zen.js";
|
|
28
29
|
export * as OpenRouter from "./openrouter.js";
|
|
30
|
+
export * as Runway from "./runway.js";
|
|
29
31
|
export * as TogetherAI from "./togetherai.js";
|
|
30
32
|
export * as TypeSafeAI from "./typesafe-ai.js";
|
|
31
33
|
export * as VercelAIGateway from "./vercel-ai-gateway.js";
|
package/dist/providers/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * as CloudflareAIGateway from "./cloudflare-ai-gateway.js";
|
|
|
10
10
|
export * as CloudflareWorkersAI from "./cloudflare-workers-ai.js";
|
|
11
11
|
export * as DeepInfra from "./deepinfra.js";
|
|
12
12
|
export * as DeepSeek from "./deepseek.js";
|
|
13
|
+
export * as Fal from "./fal.js";
|
|
13
14
|
export * as Fireworks from "./fireworks.js";
|
|
14
15
|
export * as Google from "./google.js";
|
|
15
16
|
export * as GoogleVertex from "./google-vertex.js";
|
|
@@ -26,6 +27,7 @@ export * as OpenAICompatible from "./openai-compatible.js";
|
|
|
26
27
|
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
|
|
27
28
|
export * as OpenCodeZen from "./opencode-zen.js";
|
|
28
29
|
export * as OpenRouter from "./openrouter.js";
|
|
30
|
+
export * as Runway from "./runway.js";
|
|
29
31
|
export * as TogetherAI from "./togetherai.js";
|
|
30
32
|
export * as TypeSafeAI from "./typesafe-ai.js";
|
|
31
33
|
export * as VercelAIGateway from "./vercel-ai-gateway.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ProviderAuthOption } from "../route/auth-options.js";
|
|
2
|
+
import { HttpOptions, type ModelID } from "../schema/index.js";
|
|
3
|
+
export type { RunwayVideoOptions } from "../protocols/runway-video.js";
|
|
4
|
+
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
5
|
+
export type Config = ProviderAuthOption<"optional"> & {
|
|
6
|
+
readonly baseURL?: string;
|
|
7
|
+
readonly headers?: Record<string, string>;
|
|
8
|
+
readonly http?: HttpOptions.Input;
|
|
9
|
+
};
|
|
10
|
+
export declare const configure: (input?: Config) => {
|
|
11
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
12
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/runway-video.js").RunwayVideoOptions>;
|
|
13
|
+
configure: (input?: Config) => /*elided*/ any;
|
|
14
|
+
};
|
|
15
|
+
export declare const provider: {
|
|
16
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
17
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/runway-video.js").RunwayVideoOptions>;
|
|
18
|
+
configure: (input?: Config) => {
|
|
19
|
+
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
20
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/runway-video.js").RunwayVideoOptions>;
|
|
21
|
+
configure: /*elided*/ any;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export declare const video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/runway-video.js").RunwayVideoOptions>;
|