@opencode/ai 0.0.0-dev-20035 → 0.0.0-dev-20039
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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AuthOptions } from "../route/auth-options.js";
|
|
2
|
+
import { HttpOptions, ProviderID } from "../schema/index.js";
|
|
3
|
+
import { DEFAULT_BASE_URL, RunwayVideo } from "../protocols/runway-video.js";
|
|
4
|
+
export const id = ProviderID.make("runway");
|
|
5
|
+
const baseURL = DEFAULT_BASE_URL;
|
|
6
|
+
const auth = (options) => AuthOptions.bearer(options, "RUNWAYML_API_SECRET");
|
|
7
|
+
export const configure = (input = {}) => {
|
|
8
|
+
const video = (modelID) => RunwayVideo.model({
|
|
9
|
+
id: modelID,
|
|
10
|
+
auth: auth(input),
|
|
11
|
+
baseURL: input.baseURL ?? baseURL,
|
|
12
|
+
headers: input.headers,
|
|
13
|
+
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
id,
|
|
17
|
+
video,
|
|
18
|
+
configure,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export const provider = configure();
|
|
22
|
+
export const video = provider.video;
|
package/dist/providers/xai.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type Settings = ProviderPackage.Settings & XAIProviderOptionsInput & {
|
|
|
16
16
|
readonly baseURL?: string;
|
|
17
17
|
};
|
|
18
18
|
export type { XAIImageOptions } from "../protocols/xai-images.js";
|
|
19
|
+
export type { XAIVideoOptions } from "../protocols/xai-video.js";
|
|
19
20
|
declare const responsesRoute: Route<unknown, import("../protocols/open-responses-channel.js").Prepared, {
|
|
20
21
|
endpoint: import("../route/client.js").CompactOperation;
|
|
21
22
|
}>;
|
|
@@ -130,6 +131,7 @@ export declare const configure: (input?: LanguageModelOptions) => {
|
|
|
130
131
|
}>;
|
|
131
132
|
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
132
133
|
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
|
|
134
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/xai-video.js").XAIVideoOptions>;
|
|
133
135
|
configure: (input?: LanguageModelOptions) => /*elided*/ any;
|
|
134
136
|
};
|
|
135
137
|
export declare const provider: {
|
|
@@ -142,6 +144,7 @@ export declare const provider: {
|
|
|
142
144
|
}>;
|
|
143
145
|
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
144
146
|
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
|
|
147
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/xai-video.js").XAIVideoOptions>;
|
|
145
148
|
configure: (input?: LanguageModelOptions) => {
|
|
146
149
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
|
147
150
|
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput, {
|
|
@@ -152,6 +155,7 @@ export declare const provider: {
|
|
|
152
155
|
}>;
|
|
153
156
|
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
154
157
|
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
|
|
158
|
+
video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/xai-video.js").XAIVideoOptions>;
|
|
155
159
|
configure: /*elided*/ any;
|
|
156
160
|
};
|
|
157
161
|
};
|
|
@@ -161,3 +165,4 @@ export declare const responses: (modelID: string | ModelID) => import("../schema
|
|
|
161
165
|
}>;
|
|
162
166
|
export declare const chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput, import("../route/client.js").CompactionOperations | undefined>;
|
|
163
167
|
export declare const image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
|
|
168
|
+
export declare const video: (modelID: string | ModelID) => import("../video.js").VideoModel<import("../protocols/xai-video.js").XAIVideoOptions>;
|
package/dist/providers/xai.js
CHANGED
|
@@ -6,6 +6,7 @@ import { OpenAIChat } from "../protocols/openai-chat.js";
|
|
|
6
6
|
import { OpenResponsesChannel } from "../protocols/open-responses-channel.js";
|
|
7
7
|
import { XAIResponses } from "../protocols/xai-responses.js";
|
|
8
8
|
import { XAIImages } from "../protocols/xai-images.js";
|
|
9
|
+
import { XAIVideo } from "../protocols/xai-video.js";
|
|
9
10
|
export const id = ProviderID.make("xai");
|
|
10
11
|
const baseURL = "https://api.x.ai/v1";
|
|
11
12
|
const RESPONSES_WEBSOCKET_ROTATE_AFTER_MS = 24 * 60 * 1000;
|
|
@@ -59,7 +60,7 @@ export const configure = (input = {}) => {
|
|
|
59
60
|
const chatRoute = configuredChatRoute(input);
|
|
60
61
|
const responses = (modelID) => responsesRoute.model({ id: modelID });
|
|
61
62
|
const chat = (modelID) => chatRoute.model({ id: modelID });
|
|
62
|
-
const
|
|
63
|
+
const media = (modelID) => ({
|
|
63
64
|
id: modelID,
|
|
64
65
|
auth: auth(input),
|
|
65
66
|
baseURL: input.baseURL ?? baseURL,
|
|
@@ -71,7 +72,8 @@ export const configure = (input = {}) => {
|
|
|
71
72
|
model: responses,
|
|
72
73
|
responses,
|
|
73
74
|
chat,
|
|
74
|
-
image,
|
|
75
|
+
image: (modelID) => XAIImages.model(media(modelID)),
|
|
76
|
+
video: (modelID) => XAIVideo.model(media(modelID)),
|
|
75
77
|
configure,
|
|
76
78
|
};
|
|
77
79
|
};
|
|
@@ -86,3 +88,4 @@ export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOpti
|
|
|
86
88
|
export const responses = provider.responses;
|
|
87
89
|
export const chat = provider.chat;
|
|
88
90
|
export const image = provider.image;
|
|
91
|
+
export const video = provider.video;
|
|
@@ -290,8 +290,8 @@ export declare const routes: (Route<{
|
|
|
290
290
|
readonly response_format?: {
|
|
291
291
|
readonly type: string;
|
|
292
292
|
} | undefined;
|
|
293
|
-
readonly do_sample?: boolean | undefined;
|
|
294
293
|
readonly request_id?: string | undefined;
|
|
294
|
+
readonly do_sample?: boolean | undefined;
|
|
295
295
|
}, import("../route/transport/http.js").HttpPrepared<string>, import("../route/client.js").CompactionOperations | undefined> | Route<{
|
|
296
296
|
readonly max_tokens: number;
|
|
297
297
|
readonly model: string;
|
package/dist/providers/zai.d.ts
CHANGED
|
@@ -120,8 +120,8 @@ export declare const routes: Route<{
|
|
|
120
120
|
readonly response_format?: {
|
|
121
121
|
readonly type: string;
|
|
122
122
|
} | undefined;
|
|
123
|
-
readonly do_sample?: boolean | undefined;
|
|
124
123
|
readonly request_id?: string | undefined;
|
|
124
|
+
readonly do_sample?: boolean | undefined;
|
|
125
125
|
}, import("../route/transport/http.js").HttpPrepared<string>, import("../route/client.js").CompactionOperations | undefined>[];
|
|
126
126
|
export declare const configure: (input?: Config) => {
|
|
127
127
|
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
|
package/dist/route/auth.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface AuthInput {
|
|
|
13
13
|
readonly request: {
|
|
14
14
|
readonly http?: HttpOptions;
|
|
15
15
|
};
|
|
16
|
-
readonly method: "POST" | "GET";
|
|
16
|
+
readonly method: "POST" | "GET" | "PUT" | "DELETE";
|
|
17
17
|
readonly url: string;
|
|
18
18
|
readonly body: string;
|
|
19
19
|
readonly headers: Headers.Headers;
|
|
@@ -47,5 +47,8 @@ export declare function header(name: string): (source: Secret | Credential) => D
|
|
|
47
47
|
export declare function header(name: string, source: Secret | Credential): Definition;
|
|
48
48
|
export declare function bearerHeader(name: string): (source: Secret | Credential) => Definition;
|
|
49
49
|
export declare function bearerHeader(name: string, source: Secret | Credential): Definition;
|
|
50
|
+
/** `Authorization: <scheme> <secret>` for providers whose scheme is not `Bearer`, such as fal's `Key`. */
|
|
51
|
+
export declare function scheme(name: string): (source: Secret | Credential) => Definition;
|
|
52
|
+
export declare function scheme(name: string, source: Secret | Credential): Definition;
|
|
50
53
|
export declare const toEffect: (input: Definition) => (authInput: AuthInput) => Effect.Effect<Headers.Headers, AIError>;
|
|
51
54
|
export * as Auth from "./auth.js";
|
package/dist/route/auth.js
CHANGED
|
@@ -73,6 +73,12 @@ export function bearerHeader(name, source) {
|
|
|
73
73
|
return render;
|
|
74
74
|
return render(source);
|
|
75
75
|
}
|
|
76
|
+
export function scheme(name, source) {
|
|
77
|
+
const render = (input) => fromCredential(credentialInput(input), (secret) => ({ authorization: `${name} ${secret}` }));
|
|
78
|
+
if (source === undefined)
|
|
79
|
+
return render;
|
|
80
|
+
return render(source);
|
|
81
|
+
}
|
|
76
82
|
const toAIError = (error) => {
|
|
77
83
|
if (error instanceof MissingCredentialError || error instanceof Config.ConfigError) {
|
|
78
84
|
return new AIError({
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
1
|
+
import { Duration, Effect, Schema } from "effect";
|
|
2
2
|
import { HttpClientResponse } from "effect/unstable/http";
|
|
3
|
+
import type { Snapshot, Status } from "../generation.js";
|
|
4
|
+
import { Media } from "../media.js";
|
|
5
|
+
import type { AuthInput } from "./auth.js";
|
|
3
6
|
import { AIError, HttpContext } from "../schema/index.js";
|
|
4
7
|
export type Body = {
|
|
5
8
|
readonly type: "json";
|
|
@@ -29,17 +32,80 @@ export interface Inline<Request, Response> {
|
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
export declare const inline: <Request, Response>(input: Omit<Inline<Request, Response>, "kind">) => Inline<Request, Response>;
|
|
32
|
-
/**
|
|
35
|
+
/** What `start` learned from the submission response: the route-owned handle plus the first observation. */
|
|
36
|
+
export interface Started<Token> {
|
|
37
|
+
readonly token: Token;
|
|
38
|
+
readonly snapshot: Snapshot;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A follow-up call's inputs: the decoded token and the auth headers the route sent, so a protocol can attach them
|
|
42
|
+
* to output URLs that require the same credentials to download (Veo).
|
|
43
|
+
*/
|
|
44
|
+
export interface PollContext<Token> {
|
|
45
|
+
readonly token: Token;
|
|
46
|
+
readonly auth: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Submit, then poll. `start` posts the body to the route endpoint; `status`, `result`, and `cancel` are follow-up
|
|
50
|
+
* calls addressed by the token. Paths are relative to the route base URL unless the provider hands back absolute
|
|
51
|
+
* URLs (fal `status_url`), in which case they are used verbatim. `result` is always its own GET: providers that
|
|
52
|
+
* return the output inside the status body (Veo, xAI, Runway) point `result.path` at the status path and decode the
|
|
53
|
+
* same document, so `Generation.await` and `Video.resume(...).await()` behave identically everywhere.
|
|
54
|
+
*/
|
|
55
|
+
export interface Queued<Request, Response, Token> {
|
|
56
|
+
readonly kind: "queued";
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
/** Common request fields this protocol cannot lower; the route rejects them before `start.body.from` runs. */
|
|
60
|
+
readonly unsupported?: ReadonlyArray<keyof Request & string>;
|
|
61
|
+
/** Serializable handle. `Generation.token` carries the encoded form so it can be persisted and resumed elsewhere. */
|
|
62
|
+
readonly token: Schema.Codec<Token, unknown>;
|
|
63
|
+
readonly start: {
|
|
64
|
+
readonly body: {
|
|
65
|
+
readonly from: (request: Request) => Effect.Effect<Body, AIError>;
|
|
66
|
+
};
|
|
67
|
+
readonly decode: (response: HttpClientResponse.HttpClientResponse, context: DecodeContext<Request>) => Effect.Effect<Started<Token>, AIError>;
|
|
68
|
+
};
|
|
69
|
+
readonly status: {
|
|
70
|
+
readonly path: (token: Token) => string;
|
|
71
|
+
readonly decode: (response: HttpClientResponse.HttpClientResponse, context: PollContext<Token>) => Effect.Effect<Snapshot, AIError>;
|
|
72
|
+
};
|
|
73
|
+
readonly result: {
|
|
74
|
+
readonly path: (token: Token) => string;
|
|
75
|
+
readonly decode: (response: HttpClientResponse.HttpClientResponse, context: PollContext<Token>) => Effect.Effect<Response, AIError>;
|
|
76
|
+
};
|
|
77
|
+
readonly cancel?: {
|
|
78
|
+
readonly method: AuthInput["method"];
|
|
79
|
+
readonly path: (token: Token) => string;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export declare const queued: <Request, Response, Token>(input: Omit<Queued<Request, Response, Token>, "kind">) => Queued<Request, Response, Token>;
|
|
83
|
+
/**
|
|
84
|
+
* Read a text body while retaining the original payload and HTTP context on every downstream error. `invalid` is a
|
|
85
|
+
* malformed provider document; `ended` is a generation that reached a terminal status without output (`failed` is
|
|
86
|
+
* provider-side, `cancelled`/`expired` mean the result will never exist); `contentPolicy` is a moderated result.
|
|
87
|
+
*/
|
|
33
88
|
export declare const text: (route: string, name: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
|
|
34
89
|
body: string;
|
|
35
90
|
http: HttpContext;
|
|
36
91
|
invalid: (message: string, cause?: unknown) => AIError;
|
|
92
|
+
ended: (status: Exclude<Status, "queued" | "running" | "completed">, message: string) => AIError;
|
|
93
|
+
contentPolicy: (message: string) => AIError;
|
|
37
94
|
}, AIError, never>;
|
|
95
|
+
export type Output = Effect.Success<ReturnType<typeof text>>;
|
|
38
96
|
/** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
|
|
39
97
|
export declare const decodeJson: <A>(route: string, name: string, schema: Schema.Codec<A, unknown>) => (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
|
|
40
98
|
value: A;
|
|
41
99
|
body: string;
|
|
42
100
|
http: HttpContext;
|
|
43
101
|
invalid: (message: string, cause?: unknown) => AIError;
|
|
102
|
+
ended: (status: Exclude<Status, "queued" | "running" | "completed">, message: string) => AIError;
|
|
103
|
+
contentPolicy: (message: string) => AIError;
|
|
44
104
|
}, AIError, never>;
|
|
105
|
+
/** Decode a submission response into the token and first snapshot. */
|
|
106
|
+
export declare const decodeStarted: <A, Token>(route: string, name: string, schema: Schema.Codec<A, unknown>, started: (value: A) => Started<Token>) => (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<Started<Token>, AIError, never>;
|
|
107
|
+
/** Map a provider status string through the protocol's table; unknown values are an invalid provider document. */
|
|
108
|
+
export declare const status: <Table extends Record<string, Status>>(table: Table, raw: string, output: Output) => Effect.Effect<Status, AIError>;
|
|
109
|
+
/** A `url` asset whose provider-declared retention window starts now. */
|
|
110
|
+
export declare const expiringUrl: (url: string, retention: Duration.Duration, options?: Parameters<typeof Media.url>[1]) => Effect.Effect<Media.Asset, never, never>;
|
|
45
111
|
export * as MediaProtocol from "./media-protocol.js";
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect";
|
|
1
|
+
import { Clock, Duration, Effect, Schema } from "effect";
|
|
2
2
|
import { HttpClientResponse } from "effect/unstable/http";
|
|
3
|
-
import {
|
|
3
|
+
import { Media } from "../media.js";
|
|
4
|
+
import { AIError, ContentPolicyError, HttpContext, InvalidProviderOutputError, InvalidRequestError, ProviderInternalError, } from "../schema/index.js";
|
|
4
5
|
export const json = (value) => ({ type: "json", value });
|
|
5
6
|
export const multipart = (value) => ({ type: "multipart", value });
|
|
6
7
|
export const inline = (input) => ({
|
|
7
8
|
kind: "inline",
|
|
8
9
|
...input,
|
|
9
10
|
});
|
|
11
|
+
export const queued = (input) => ({
|
|
12
|
+
kind: "queued",
|
|
13
|
+
...input,
|
|
14
|
+
});
|
|
10
15
|
// ---------------------------------------------------------------------------
|
|
11
16
|
// Response helpers
|
|
12
17
|
// ---------------------------------------------------------------------------
|
|
13
18
|
const context = (response) => new HttpContext({ url: response.request.url, status: response.status, headers: response.headers });
|
|
14
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Read a text body while retaining the original payload and HTTP context on every downstream error. `invalid` is a
|
|
21
|
+
* malformed provider document; `ended` is a generation that reached a terminal status without output (`failed` is
|
|
22
|
+
* provider-side, `cancelled`/`expired` mean the result will never exist); `contentPolicy` is a moderated result.
|
|
23
|
+
*/
|
|
15
24
|
export const text = Effect.fn("MediaProtocol.text")(function* (route, name, response) {
|
|
16
25
|
const http = context(response);
|
|
17
26
|
const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
|
|
@@ -26,6 +35,12 @@ export const text = Effect.fn("MediaProtocol.text")(function* (route, name, resp
|
|
|
26
35
|
body,
|
|
27
36
|
http,
|
|
28
37
|
invalid: (message, cause) => new AIError({ reason: new InvalidProviderOutputError({ route, message, body, http, cause }) }),
|
|
38
|
+
ended: (status, message) => new AIError({
|
|
39
|
+
reason: status === "failed"
|
|
40
|
+
? new ProviderInternalError({ message, body, http })
|
|
41
|
+
: new InvalidRequestError({ message, body, http }),
|
|
42
|
+
}),
|
|
43
|
+
contentPolicy: (message) => new AIError({ reason: new ContentPolicyError({ message, body, http }) }),
|
|
29
44
|
};
|
|
30
45
|
});
|
|
31
46
|
/** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
|
|
@@ -37,4 +52,18 @@ export const decodeJson = (route, name, schema) => {
|
|
|
37
52
|
return { ...output, value };
|
|
38
53
|
});
|
|
39
54
|
};
|
|
55
|
+
/** Decode a submission response into the token and first snapshot. */
|
|
56
|
+
export const decodeStarted = (route, name, schema, started) => {
|
|
57
|
+
const decode = decodeJson(route, name, schema);
|
|
58
|
+
return (response) => decode(response).pipe(Effect.map((output) => started(output.value)));
|
|
59
|
+
};
|
|
60
|
+
/** Map a provider status string through the protocol's table; unknown values are an invalid provider document. */
|
|
61
|
+
export const status = (table, raw, output) => {
|
|
62
|
+
const normalized = table[raw];
|
|
63
|
+
if (normalized === undefined)
|
|
64
|
+
return Effect.fail(output.invalid(`Unknown generation status "${raw}"`));
|
|
65
|
+
return Effect.succeed(normalized);
|
|
66
|
+
};
|
|
67
|
+
/** A `url` asset whose provider-declared retention window starts now. */
|
|
68
|
+
export const expiringUrl = (url, retention, options) => Clock.currentTimeMillis.pipe(Effect.map((now) => Media.url(url, { ...options, expiresAt: now + Duration.toMillis(retention) })));
|
|
40
69
|
export * as MediaProtocol from "./media-protocol.js";
|
package/dist/route/media.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Auth } from "./auth.js";
|
|
|
3
3
|
import { Endpoint } from "./endpoint.js";
|
|
4
4
|
import type { Interface } from "./executor-service.js";
|
|
5
5
|
import { MediaProtocol } from "./media-protocol.js";
|
|
6
|
+
import { Generation } from "../generation.js";
|
|
6
7
|
import { AIError, HttpOptions, ProviderID } from "../schema/index.js";
|
|
7
8
|
export type Execute = Interface["execute"];
|
|
8
9
|
/** The minimum a media request must carry for the route to build a transport request. */
|
|
@@ -22,25 +23,47 @@ export interface ModelInput {
|
|
|
22
23
|
readonly headers?: Record<string, string>;
|
|
23
24
|
readonly http?: HttpOptions;
|
|
24
25
|
}
|
|
26
|
+
/** One request, one response. */
|
|
25
27
|
export interface Route<Request extends MediaRequest, Response> {
|
|
26
28
|
readonly id: string;
|
|
27
29
|
readonly provider: ProviderID;
|
|
28
30
|
readonly protocol: string;
|
|
29
31
|
readonly generate: (request: Request, execute: Execute) => Effect.Effect<Response, AIError>;
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
/** Submit, then poll through the returned `Generation`. */
|
|
34
|
+
export interface QueuedRoute<Request extends MediaRequest, Response> {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly provider: ProviderID;
|
|
37
|
+
readonly protocol: string;
|
|
38
|
+
readonly start: (request: Request, execute: Execute) => Effect.Effect<Generation<Response>, AIError>;
|
|
39
|
+
/** Rebuild a handle from a persisted `Generation.token`; fails typed when the token is not this route's. */
|
|
40
|
+
readonly resume: (model: MediaRequest["model"], token: unknown, execute: Execute) => Effect.Effect<Generation<Response>, AIError>;
|
|
41
|
+
}
|
|
42
|
+
export interface Composition<Request extends MediaRequest> {
|
|
32
43
|
readonly id: string;
|
|
33
44
|
readonly provider: string | ProviderID;
|
|
34
|
-
readonly protocol: MediaProtocol.Inline<Request, Response>;
|
|
35
45
|
readonly endpoint: Endpoint.Definition<MediaProtocol.Body, Request>;
|
|
36
46
|
readonly auth: Auth.Definition;
|
|
37
47
|
/** Deployment headers applied before transport authentication. */
|
|
38
48
|
readonly headers?: Record<string, string>;
|
|
39
49
|
}
|
|
50
|
+
export interface InlineInput<Request extends MediaRequest, Response> extends Composition<Request> {
|
|
51
|
+
readonly protocol: MediaProtocol.Inline<Request, Response>;
|
|
52
|
+
}
|
|
53
|
+
export interface QueuedInput<Request extends MediaRequest, Response, Token> extends Composition<Request> {
|
|
54
|
+
readonly protocol: MediaProtocol.Queued<Request, Response, Token>;
|
|
55
|
+
}
|
|
40
56
|
/**
|
|
41
57
|
* Compose an inline media protocol with an endpoint and auth into a runnable route. The route owns the transport
|
|
42
|
-
* plumbing every media protocol would otherwise duplicate: option merging, surrogate sanitizing, unsupported-field
|
|
43
|
-
* rendering, auth headers, JSON vs multipart encoding, and handing
|
|
58
|
+
* plumbing every media protocol would otherwise duplicate: option merging, surrogate sanitizing, unsupported-field
|
|
59
|
+
* rejection, URL and query rendering, auth headers, JSON vs multipart encoding, and handing responses back to the
|
|
60
|
+
* protocol.
|
|
61
|
+
*/
|
|
62
|
+
export declare const inline: <Request extends MediaRequest, Response>(input: InlineInput<Request, Response>) => Route<Request, Response>;
|
|
63
|
+
/**
|
|
64
|
+
* Compose a queued media protocol the same way, adding `start`/`resume` handles whose polls reuse the route's auth,
|
|
65
|
+
* deployment headers, and (for `start`) the request's `http` overlay. The token is decoded once at the boundary and
|
|
66
|
+
* closed over by the resulting `Generation.Route`.
|
|
44
67
|
*/
|
|
45
|
-
export declare const
|
|
68
|
+
export declare const queued: <Request extends MediaRequest, Response, Token>(input: QueuedInput<Request, Response, Token>) => QueuedRoute<Request, Response>;
|
|
46
69
|
export * as MediaRoute from "./media.js";
|
package/dist/route/media.js
CHANGED
|
@@ -1,33 +1,97 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
2
|
import { Headers, HttpClientRequest } from "effect/unstable/http";
|
|
3
3
|
import { Auth } from "./auth.js";
|
|
4
4
|
import { Endpoint } from "./endpoint.js";
|
|
5
5
|
import { MediaProtocol } from "./media-protocol.js";
|
|
6
|
+
import { Generation } from "../generation.js";
|
|
6
7
|
import { ProviderShared } from "../protocols/shared.js";
|
|
7
|
-
import { AIError, HttpOptions, ProviderID, mergeHttpOptions } from "../schema/index.js";
|
|
8
|
+
import { AIError, HttpOptions, InvalidRequestError, ProviderID, mergeHttpOptions } from "../schema/index.js";
|
|
8
9
|
import { sanitizeSurrogates } from "../utils/sanitize.js";
|
|
9
10
|
/**
|
|
10
11
|
* Compose an inline media protocol with an endpoint and auth into a runnable route. The route owns the transport
|
|
11
|
-
* plumbing every media protocol would otherwise duplicate: option merging, surrogate sanitizing, unsupported-field
|
|
12
|
-
* rendering, auth headers, JSON vs multipart encoding, and handing
|
|
12
|
+
* plumbing every media protocol would otherwise duplicate: option merging, surrogate sanitizing, unsupported-field
|
|
13
|
+
* rejection, URL and query rendering, auth headers, JSON vs multipart encoding, and handing responses back to the
|
|
14
|
+
* protocol.
|
|
13
15
|
*/
|
|
14
|
-
export const
|
|
16
|
+
export const inline = (input) => {
|
|
17
|
+
const transport = makeTransport(input);
|
|
18
|
+
return {
|
|
19
|
+
id: input.id,
|
|
20
|
+
provider: transport.provider,
|
|
21
|
+
protocol: input.protocol.id,
|
|
22
|
+
generate: Effect.fn(`MediaRoute.generate`)(function* (request, execute) {
|
|
23
|
+
const submitted = yield* transport.submit(request, { unsupported: input.protocol.unsupported, from: input.protocol.body.from }, execute);
|
|
24
|
+
return yield* input.protocol.response.decode(submitted.response, submitted.context);
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Compose a queued media protocol the same way, adding `start`/`resume` handles whose polls reuse the route's auth,
|
|
30
|
+
* deployment headers, and (for `start`) the request's `http` overlay. The token is decoded once at the boundary and
|
|
31
|
+
* closed over by the resulting `Generation.Route`.
|
|
32
|
+
*/
|
|
33
|
+
export const queued = (input) => {
|
|
34
|
+
const transport = makeTransport(input);
|
|
35
|
+
const protocol = input.protocol;
|
|
36
|
+
const decodeToken = Schema.decodeUnknownEffect(protocol.token);
|
|
37
|
+
// A protocol producing a token its own codec rejects is a programmer defect, not a provider error.
|
|
38
|
+
const encodeToken = Schema.encodeSync(protocol.token);
|
|
39
|
+
const generationRoute = (token, http, execute) => {
|
|
40
|
+
const poll = (operation) => transport
|
|
41
|
+
.call("GET", operation.path(token), http, execute)
|
|
42
|
+
.pipe(Effect.flatMap((sent) => operation.decode(sent.response, { token, auth: sent.auth })));
|
|
43
|
+
const cancel = protocol.cancel;
|
|
44
|
+
const route = {
|
|
45
|
+
status: poll(protocol.status),
|
|
46
|
+
result: poll(protocol.result),
|
|
47
|
+
cancel: cancel === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: transport.call(cancel.method, cancel.path(token), http, execute).pipe(Effect.asVoid),
|
|
50
|
+
};
|
|
51
|
+
return route;
|
|
52
|
+
};
|
|
53
|
+
const start = Effect.fn("MediaRoute.start")(function* (request, execute) {
|
|
54
|
+
const submitted = yield* transport.submit(request, { unsupported: protocol.unsupported, from: protocol.start.body.from }, execute);
|
|
55
|
+
const started = yield* protocol.start.decode(submitted.response, submitted.context);
|
|
56
|
+
const route = generationRoute(started.token, submitted.context.request.http, execute);
|
|
57
|
+
return new Generation(route, encodeToken(started.token), started.snapshot);
|
|
58
|
+
});
|
|
59
|
+
const resume = Effect.fn("MediaRoute.resume")(function* (model, raw, execute) {
|
|
60
|
+
const token = yield* decodeToken(raw).pipe(Effect.mapError((cause) => new AIError({
|
|
61
|
+
reason: new InvalidRequestError({
|
|
62
|
+
message: `${input.id} cannot resume a generation from this token`,
|
|
63
|
+
cause,
|
|
64
|
+
}),
|
|
65
|
+
})));
|
|
66
|
+
const route = generationRoute(token, transport.http(model), execute);
|
|
67
|
+
return new Generation(route, encodeToken(token), yield* route.status);
|
|
68
|
+
});
|
|
69
|
+
return { id: input.id, provider: transport.provider, protocol: protocol.id, start, resume };
|
|
70
|
+
};
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Transport plumbing shared by both kinds
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const makeTransport = (input) => {
|
|
15
75
|
const provider = ProviderID.make(input.provider);
|
|
16
76
|
const routeHttp = input.headers === undefined ? undefined : new HttpOptions({ headers: input.headers });
|
|
17
77
|
const authorize = Auth.toEffect(input.auth);
|
|
78
|
+
const withQuery = (url, http) => {
|
|
79
|
+
for (const [key, value] of Object.entries(http?.query ?? {}))
|
|
80
|
+
url.searchParams.set(key, value);
|
|
81
|
+
return url;
|
|
82
|
+
};
|
|
18
83
|
return {
|
|
19
|
-
id: input.id,
|
|
20
84
|
provider,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
85
|
+
/** Route and model overlays; `start` additionally merges the request's own `http`. */
|
|
86
|
+
http: (model) => mergeHttpOptions(routeHttp, model.http),
|
|
87
|
+
/** POST the protocol body to the route endpoint. */
|
|
88
|
+
submit: Effect.fn("MediaRoute.submit")(function* (request, protocol, execute) {
|
|
89
|
+
yield* rejectUnsupported(input.id, provider, request, protocol.unsupported);
|
|
24
90
|
const http = mergeHttpOptions(routeHttp, request.model.http, request.http);
|
|
25
91
|
// Sanitize after merging so model-level overlays are covered; the model value is restored, not sanitized.
|
|
26
92
|
const resolved = { ...sanitizeSurrogates({ ...request, http }), model: request.model };
|
|
27
|
-
const body = yield*
|
|
28
|
-
const url = Endpoint.render(input.endpoint, { request: resolved, body });
|
|
29
|
-
for (const [key, value] of Object.entries(http?.query ?? {}))
|
|
30
|
-
url.searchParams.set(key, value);
|
|
93
|
+
const body = yield* protocol.from(resolved);
|
|
94
|
+
const url = withQuery(Endpoint.render(input.endpoint, { request: resolved, body }), http);
|
|
31
95
|
const encoded = body.type === "json" ? ProviderShared.encodeJson(body.value) : "[multipart/form-data]";
|
|
32
96
|
const baseHeaders = Headers.fromInput(http?.headers);
|
|
33
97
|
const headers = yield* authorize({
|
|
@@ -42,7 +106,24 @@ export const make = (input) => {
|
|
|
42
106
|
? HttpClientRequest.bodyText(encoded, "application/json")
|
|
43
107
|
: HttpClientRequest.bodyFormData(body.value));
|
|
44
108
|
const response = yield* execute(transport);
|
|
45
|
-
return
|
|
109
|
+
return { response, context: { request: resolved, body } };
|
|
110
|
+
}),
|
|
111
|
+
/**
|
|
112
|
+
* Bodiless follow-up call (status, result, cancel) with the same auth and headers as `submit`. `auth` is only
|
|
113
|
+
* what `Auth` added, so protocols can hand download credentials to output assets without deployment headers.
|
|
114
|
+
*/
|
|
115
|
+
call: Effect.fn("MediaRoute.call")(function* (method, path, http, execute) {
|
|
116
|
+
// Provider-issued absolute URLs (fal `status_url`) are used as-is; everything else resolves against the base.
|
|
117
|
+
const url = withQuery(/^https?:\/\//.test(path)
|
|
118
|
+
? new URL(path)
|
|
119
|
+
: new URL(`${ProviderShared.trimBaseUrl(input.endpoint.baseURL ?? "")}${path}`), http);
|
|
120
|
+
for (const [key, value] of Object.entries(input.endpoint.query ?? {}))
|
|
121
|
+
url.searchParams.set(key, value);
|
|
122
|
+
const base = Headers.fromInput(http?.headers);
|
|
123
|
+
const headers = yield* authorize({ request: { http }, method, url: url.toString(), body: "", headers: base });
|
|
124
|
+
const response = yield* execute(HttpClientRequest.make(method)(url.toString()).pipe(HttpClientRequest.setHeaders(headers)));
|
|
125
|
+
const auth = Object.fromEntries(Object.entries(headers).filter(([key]) => !(key in base)));
|
|
126
|
+
return { response, auth };
|
|
46
127
|
}),
|
|
47
128
|
};
|
|
48
129
|
};
|
package/dist/schema/events.d.ts
CHANGED
|
@@ -365,7 +365,6 @@ export declare const MediaEvent: Schema.Struct<{
|
|
|
365
365
|
readonly url: Schema.String;
|
|
366
366
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
367
367
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
368
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
369
368
|
}>, Schema.Struct<{
|
|
370
369
|
readonly type: Schema.Literal<"ref">;
|
|
371
370
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -578,7 +577,6 @@ declare const llmEventTagged: Schema.toTaggedUnion<"type", readonly [Schema.refi
|
|
|
578
577
|
readonly url: Schema.String;
|
|
579
578
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
580
579
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
581
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
582
580
|
}>, Schema.Struct<{
|
|
583
581
|
readonly type: Schema.Literal<"ref">;
|
|
584
582
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -820,7 +818,6 @@ export declare const LLMEvent: Schema.Union<readonly [Schema.refine<{
|
|
|
820
818
|
readonly url: Schema.String;
|
|
821
819
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
822
820
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
823
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
824
821
|
}>, Schema.Struct<{
|
|
825
822
|
readonly type: Schema.Literal<"ref">;
|
|
826
823
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -1064,7 +1061,6 @@ export declare const LLMEvent: Schema.Union<readonly [Schema.refine<{
|
|
|
1064
1061
|
readonly url: Schema.String;
|
|
1065
1062
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
1066
1063
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
1067
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
1068
1064
|
}>, Schema.Struct<{
|
|
1069
1065
|
readonly type: Schema.Literal<"ref">;
|
|
1070
1066
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -4805,7 +4801,6 @@ declare const LLMResponse_base: Schema.Class<LLMResponse, Schema.Struct<{
|
|
|
4805
4801
|
readonly url: Schema.String;
|
|
4806
4802
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
4807
4803
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
4808
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
4809
4804
|
}>, Schema.Struct<{
|
|
4810
4805
|
readonly type: Schema.Literal<"ref">;
|
|
4811
4806
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -5049,7 +5044,6 @@ declare const LLMResponse_base: Schema.Class<LLMResponse, Schema.Struct<{
|
|
|
5049
5044
|
readonly url: Schema.String;
|
|
5050
5045
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
5051
5046
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
5052
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
5053
5047
|
}>, Schema.Struct<{
|
|
5054
5048
|
readonly type: Schema.Literal<"ref">;
|
|
5055
5049
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -44,7 +44,6 @@ export declare const MediaPart: Schema.Struct<{
|
|
|
44
44
|
readonly url: Schema.String;
|
|
45
45
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
46
46
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
47
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
48
47
|
}>, Schema.Struct<{
|
|
49
48
|
readonly type: Schema.Literal<"ref">;
|
|
50
49
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -305,7 +304,6 @@ export declare const ContentPart: Schema.toTaggedUnion<"type", readonly [Schema.
|
|
|
305
304
|
readonly url: Schema.String;
|
|
306
305
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
307
306
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
308
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
309
307
|
}>, Schema.Struct<{
|
|
310
308
|
readonly type: Schema.Literal<"ref">;
|
|
311
309
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -450,7 +448,6 @@ declare const Message_base: Schema.Class<Message, Schema.Struct<{
|
|
|
450
448
|
readonly url: Schema.String;
|
|
451
449
|
readonly mediaType: Schema.optional<Schema.String>;
|
|
452
450
|
readonly expiresAt: Schema.optional<Schema.Number>;
|
|
453
|
-
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
454
451
|
}>, Schema.Struct<{
|
|
455
452
|
readonly type: Schema.Literal<"ref">;
|
|
456
453
|
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Stream } from "effect";
|
|
2
|
+
import type { AwaitOptions, Generation } from "./generation.js";
|
|
3
|
+
import { RequestExecutor } from "./route/executor.js";
|
|
4
|
+
import type { AIError } from "./schema/index.js";
|
|
5
|
+
import { type VideoEvent, type VideoModel, type VideoOptions, type VideoRequestFor, type VideoResponse } from "./video.js";
|
|
6
|
+
export interface Interface {
|
|
7
|
+
readonly start: <Options extends VideoOptions>(request: VideoRequestFor<Options>) => Effect.Effect<Generation<VideoResponse>, AIError>;
|
|
8
|
+
readonly resume: <Options extends VideoOptions>(model: VideoModel<Options>, token: unknown) => Effect.Effect<Generation<VideoResponse>, AIError>;
|
|
9
|
+
readonly generate: <Options extends VideoOptions>(request: VideoRequestFor<Options>, options?: AwaitOptions) => Effect.Effect<VideoResponse, AIError>;
|
|
10
|
+
readonly stream: <Options extends VideoOptions>(request: VideoRequestFor<Options>, options?: AwaitOptions) => Stream.Stream<VideoEvent, AIError>;
|
|
11
|
+
}
|
|
12
|
+
declare const Service_base: Context.ServiceClass<Service, "@opencode/VideoClient", Interface>;
|
|
13
|
+
export declare class Service extends Service_base {
|
|
14
|
+
}
|
|
15
|
+
export declare const start: <Options extends VideoOptions>(request: VideoRequestFor<Options>) => Effect.Effect<Generation<VideoResponse>, AIError, Service>;
|
|
16
|
+
export declare const resume: <Options extends VideoOptions>(model: VideoModel<Options>, token: unknown) => Effect.Effect<Generation<VideoResponse>, AIError, Service>;
|
|
17
|
+
export declare const generate: <Options extends VideoOptions>(request: VideoRequestFor<Options>, options?: AwaitOptions) => Effect.Effect<VideoResponse, AIError, Service>;
|
|
18
|
+
export declare const stream: <Options extends VideoOptions>(request: VideoRequestFor<Options>, options?: AwaitOptions) => Stream.Stream<VideoEvent, AIError, Service>;
|
|
19
|
+
export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
|
|
20
|
+
export declare const VideoClient: {
|
|
21
|
+
readonly Service: typeof Service;
|
|
22
|
+
readonly layer: Layer.Layer<Service, never, RequestExecutor.Service>;
|
|
23
|
+
readonly start: <Options extends VideoOptions>(request: VideoRequestFor<Options>) => Effect.Effect<Generation<VideoResponse>, AIError, Service>;
|
|
24
|
+
readonly resume: <Options extends VideoOptions>(model: VideoModel<Options>, token: unknown) => Effect.Effect<Generation<VideoResponse>, AIError, Service>;
|
|
25
|
+
readonly generate: <Options extends VideoOptions>(request: VideoRequestFor<Options>, options?: AwaitOptions) => Effect.Effect<VideoResponse, AIError, Service>;
|
|
26
|
+
readonly stream: <Options extends VideoOptions>(request: VideoRequestFor<Options>, options?: AwaitOptions) => Stream.Stream<VideoEvent, AIError, Service>;
|
|
27
|
+
};
|
|
28
|
+
export {};
|