@opencode/ai 2.0.15 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +286 -2
  2. package/dist/generation.d.ts +36 -22
  3. package/dist/generation.js +53 -24
  4. package/dist/image-client.d.ts +14 -7
  5. package/dist/image-client.js +24 -8
  6. package/dist/image.d.ts +398 -47
  7. package/dist/image.js +47 -45
  8. package/dist/index.d.ts +13 -1
  9. package/dist/index.js +9 -0
  10. package/dist/media-model.d.ts +44 -0
  11. package/dist/media-model.js +49 -0
  12. package/dist/media.d.ts +10 -9
  13. package/dist/media.js +9 -10
  14. package/dist/promise.d.ts +428 -8
  15. package/dist/promise.js +40 -3
  16. package/dist/protocols/alibaba-chat.d.ts +12 -0
  17. package/dist/protocols/alibaba-responses.d.ts +2 -2
  18. package/dist/protocols/anthropic-messages.js +1 -2
  19. package/dist/protocols/assemblyai-transcription.d.ts +40 -0
  20. package/dist/protocols/assemblyai-transcription.js +138 -0
  21. package/dist/protocols/bedrock-converse.js +5 -11
  22. package/dist/protocols/bfl-images.d.ts +32 -0
  23. package/dist/protocols/bfl-images.js +153 -0
  24. package/dist/protocols/cartesia-speech.d.ts +127 -0
  25. package/dist/protocols/cartesia-speech.js +126 -0
  26. package/dist/protocols/deepgram-speech.d.ts +119 -0
  27. package/dist/protocols/deepgram-speech.js +92 -0
  28. package/dist/protocols/deepgram-transcription.d.ts +25 -0
  29. package/dist/protocols/deepgram-transcription.js +129 -0
  30. package/dist/protocols/elevenlabs-speech.d.ts +122 -0
  31. package/dist/protocols/elevenlabs-speech.js +115 -0
  32. package/dist/protocols/fal-images.d.ts +24 -0
  33. package/dist/protocols/fal-images.js +114 -0
  34. package/dist/protocols/fal-video.d.ts +29 -0
  35. package/dist/protocols/fal-video.js +88 -0
  36. package/dist/protocols/gemini.d.ts +9 -9
  37. package/dist/protocols/gemini.js +8 -34
  38. package/dist/protocols/google-images.js +2 -14
  39. package/dist/protocols/google-speech.d.ts +130 -0
  40. package/dist/protocols/google-speech.js +84 -0
  41. package/dist/protocols/google-transcription.d.ts +173 -0
  42. package/dist/protocols/google-transcription.js +138 -0
  43. package/dist/protocols/google-video.d.ts +26 -0
  44. package/dist/protocols/google-video.js +158 -0
  45. package/dist/protocols/meta-images.js +2 -9
  46. package/dist/protocols/meta-responses.d.ts +4 -4
  47. package/dist/protocols/meta-responses.js +1 -1
  48. package/dist/protocols/open-responses.d.ts +6 -6
  49. package/dist/protocols/open-responses.js +1 -2
  50. package/dist/protocols/openai-chat.d.ts +84 -0
  51. package/dist/protocols/openai-chat.js +26 -14
  52. package/dist/protocols/openai-compatible-chat.d.ts +12 -0
  53. package/dist/protocols/openai-compatible-responses.d.ts +2 -2
  54. package/dist/protocols/openai-images.d.ts +124 -3
  55. package/dist/protocols/openai-images.js +107 -54
  56. package/dist/protocols/openai-responses.d.ts +15 -15
  57. package/dist/protocols/openai-responses.js +5 -6
  58. package/dist/protocols/openai-speech.d.ts +116 -0
  59. package/dist/protocols/openai-speech.js +98 -0
  60. package/dist/protocols/openai-transcription.d.ts +207 -0
  61. package/dist/protocols/openai-transcription.js +190 -0
  62. package/dist/protocols/replicate-images.d.ts +28 -0
  63. package/dist/protocols/replicate-images.js +133 -0
  64. package/dist/protocols/runway-video.d.ts +38 -0
  65. package/dist/protocols/runway-video.js +146 -0
  66. package/dist/protocols/shared.d.ts +13 -3
  67. package/dist/protocols/shared.js +23 -3
  68. package/dist/protocols/stability-images.d.ts +38 -0
  69. package/dist/protocols/stability-images.js +148 -0
  70. package/dist/protocols/utils/fal-queue.d.ts +28 -0
  71. package/dist/protocols/utils/fal-queue.js +69 -0
  72. package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
  73. package/dist/protocols/utils/gemini-generate-content.js +65 -0
  74. package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
  75. package/dist/protocols/utils/gemini-json-schema.js +76 -0
  76. package/dist/protocols/utils/media-input.d.ts +8 -0
  77. package/dist/protocols/utils/media-input.js +18 -0
  78. package/dist/protocols/utils/speech-stream.d.ts +49 -0
  79. package/dist/protocols/utils/speech-stream.js +67 -0
  80. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  81. package/dist/protocols/utils/tool-schema.js +40 -17
  82. package/dist/protocols/xai-images.js +1 -12
  83. package/dist/protocols/xai-responses.d.ts +2 -2
  84. package/dist/protocols/xai-video.d.ts +34 -0
  85. package/dist/protocols/xai-video.js +147 -0
  86. package/dist/protocols/zai-chat.d.ts +13 -1
  87. package/dist/provider-error.js +3 -0
  88. package/dist/providers/alibaba.d.ts +14 -2
  89. package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
  90. package/dist/providers/assemblyai.d.ts +25 -0
  91. package/dist/providers/assemblyai.js +29 -0
  92. package/dist/providers/azure.d.ts +18 -6
  93. package/dist/providers/baseten.d.ts +24 -0
  94. package/dist/providers/black-forest-labs.d.ts +25 -0
  95. package/dist/providers/black-forest-labs.js +28 -0
  96. package/dist/providers/cartesia.d.ts +24 -0
  97. package/dist/providers/cartesia.js +22 -0
  98. package/dist/providers/cerebras.d.ts +24 -0
  99. package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
  100. package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
  101. package/dist/providers/deepgram.d.ts +29 -0
  102. package/dist/providers/deepgram.js +31 -0
  103. package/dist/providers/deepinfra.d.ts +24 -0
  104. package/dist/providers/deepseek.d.ts +24 -0
  105. package/dist/providers/elevenlabs.d.ts +24 -0
  106. package/dist/providers/elevenlabs.js +28 -0
  107. package/dist/providers/fal.d.ts +29 -0
  108. package/dist/providers/fal.js +33 -0
  109. package/dist/providers/fireworks.d.ts +24 -0
  110. package/dist/providers/google-vertex-chat.d.ts +12 -0
  111. package/dist/providers/google-vertex-responses.d.ts +2 -2
  112. package/dist/providers/google-vertex.d.ts +3 -3
  113. package/dist/providers/google.d.ts +18 -3
  114. package/dist/providers/google.js +11 -2
  115. package/dist/providers/groq.d.ts +24 -0
  116. package/dist/providers/index.d.ts +9 -0
  117. package/dist/providers/index.js +9 -0
  118. package/dist/providers/meta.d.ts +14 -2
  119. package/dist/providers/minimax.d.ts +14 -2
  120. package/dist/providers/moonshot.d.ts +14 -2
  121. package/dist/providers/moonshot.js +3 -3
  122. package/dist/providers/openai-compatible-responses.d.ts +2 -2
  123. package/dist/providers/openai-compatible.d.ts +12 -0
  124. package/dist/providers/openai.d.ts +25 -3
  125. package/dist/providers/openai.js +10 -1
  126. package/dist/providers/openrouter.d.ts +48 -0
  127. package/dist/providers/replicate.d.ts +25 -0
  128. package/dist/providers/replicate.js +22 -0
  129. package/dist/providers/runway.d.ts +24 -0
  130. package/dist/providers/runway.js +22 -0
  131. package/dist/providers/stability.d.ts +28 -0
  132. package/dist/providers/stability.js +23 -0
  133. package/dist/providers/togetherai.d.ts +24 -0
  134. package/dist/providers/xai.d.ts +17 -0
  135. package/dist/providers/xai.js +5 -2
  136. package/dist/providers/zai-coding-plan.d.ts +15 -3
  137. package/dist/providers/zai.d.ts +13 -1
  138. package/dist/route/auth.d.ts +4 -1
  139. package/dist/route/auth.js +6 -0
  140. package/dist/route/framing.d.ts +5 -1
  141. package/dist/route/framing.js +9 -0
  142. package/dist/route/media-protocol.d.ts +116 -3
  143. package/dist/route/media-protocol.js +60 -4
  144. package/dist/route/media.d.ts +58 -7
  145. package/dist/route/media.js +201 -29
  146. package/dist/schema/events.d.ts +0 -6
  147. package/dist/schema/messages.d.ts +0 -3
  148. package/dist/schema/options.d.ts +4 -3
  149. package/dist/schema/options.js +3 -2
  150. package/dist/speech-client.d.ts +21 -0
  151. package/dist/speech-client.js +25 -0
  152. package/dist/speech.d.ts +1150 -0
  153. package/dist/speech.js +119 -0
  154. package/dist/transcription-client.d.ts +28 -0
  155. package/dist/transcription-client.js +44 -0
  156. package/dist/transcription.d.ts +1504 -0
  157. package/dist/transcription.js +133 -0
  158. package/dist/utils/bytes.d.ts +1 -0
  159. package/dist/utils/bytes.js +10 -0
  160. package/dist/utils/media-type.d.ts +1 -0
  161. package/dist/utils/media-type.js +22 -1
  162. package/dist/video-client.d.ts +28 -0
  163. package/dist/video-client.js +40 -0
  164. package/dist/video.d.ts +1359 -0
  165. package/dist/video.js +119 -0
  166. package/package.json +3 -3
  167. package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
  168. package/dist/protocols/utils/gemini-tool-schema.js +0 -103
@@ -86,8 +86,8 @@ export declare const routes: (Route<{
86
86
  } | {
87
87
  readonly type: "input_file";
88
88
  readonly filename: string;
89
- readonly detail?: string | undefined;
90
89
  readonly file_data?: string | undefined;
90
+ readonly detail?: string | undefined;
91
91
  readonly file_url?: string | undefined;
92
92
  } | {
93
93
  readonly type: "input_text";
@@ -123,8 +123,8 @@ export declare const routes: (Route<{
123
123
  } | {
124
124
  readonly type: "input_file";
125
125
  readonly filename: string;
126
- readonly detail?: string | undefined;
127
126
  readonly file_data?: string | undefined;
127
+ readonly detail?: string | undefined;
128
128
  readonly file_url?: string | undefined;
129
129
  } | {
130
130
  readonly type: "input_text";
@@ -200,6 +200,12 @@ export declare const routes: (Route<{
200
200
  readonly image_url: {
201
201
  readonly url: string;
202
202
  };
203
+ } | {
204
+ readonly type: "file";
205
+ readonly file: {
206
+ readonly filename: string;
207
+ readonly file_data: string;
208
+ };
203
209
  })[];
204
210
  } | {
205
211
  readonly role: "user";
@@ -215,6 +221,12 @@ export declare const routes: (Route<{
215
221
  readonly image_url: {
216
222
  readonly url: string;
217
223
  };
224
+ } | {
225
+ readonly type: "file";
226
+ readonly file: {
227
+ readonly filename: string;
228
+ readonly file_data: string;
229
+ };
218
230
  })[];
219
231
  } | {
220
232
  readonly [x: string]: unknown;
@@ -290,8 +302,8 @@ export declare const routes: (Route<{
290
302
  readonly response_format?: {
291
303
  readonly type: string;
292
304
  } | undefined;
293
- readonly do_sample?: boolean | undefined;
294
305
  readonly request_id?: string | undefined;
306
+ readonly do_sample?: boolean | undefined;
295
307
  }, import("../route/transport/http.js").HttpPrepared<string>, import("../route/client.js").CompactionOperations | undefined> | Route<{
296
308
  readonly max_tokens: number;
297
309
  readonly model: string;
@@ -30,6 +30,12 @@ export declare const routes: Route<{
30
30
  readonly image_url: {
31
31
  readonly url: string;
32
32
  };
33
+ } | {
34
+ readonly type: "file";
35
+ readonly file: {
36
+ readonly filename: string;
37
+ readonly file_data: string;
38
+ };
33
39
  })[];
34
40
  } | {
35
41
  readonly role: "user";
@@ -45,6 +51,12 @@ export declare const routes: Route<{
45
51
  readonly image_url: {
46
52
  readonly url: string;
47
53
  };
54
+ } | {
55
+ readonly type: "file";
56
+ readonly file: {
57
+ readonly filename: string;
58
+ readonly file_data: string;
59
+ };
48
60
  })[];
49
61
  } | {
50
62
  readonly [x: string]: unknown;
@@ -120,8 +132,8 @@ export declare const routes: Route<{
120
132
  readonly response_format?: {
121
133
  readonly type: string;
122
134
  } | undefined;
123
- readonly do_sample?: boolean | undefined;
124
135
  readonly request_id?: string | undefined;
136
+ readonly do_sample?: boolean | undefined;
125
137
  }, import("../route/transport/http.js").HttpPrepared<string>, import("../route/client.js").CompactionOperations | undefined>[];
126
138
  export declare const configure: (input?: Config) => {
127
139
  id: string & import("effect/Brand").Brand<"AI.ProviderID">;
@@ -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";
@@ -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,4 +1,4 @@
1
- import type { Stream } from "effect";
1
+ import { Stream } from "effect";
2
2
  import type { AIError } from "../schema/index.js";
3
3
  /**
4
4
  * Decode a streaming HTTP response body into provider-protocol frames.
@@ -10,6 +10,8 @@ import type { AIError } from "../schema/index.js";
10
10
  * `[DONE]`; protocols that use it as a terminal select `sseWithDone`.
11
11
  * - AWS event stream — length-prefixed binary frames with CRC checksums.
12
12
  * Each emitted frame is one parsed binary event record.
13
+ * - Media streams — newline-delimited JSON (`lines`) or the whole body as one
14
+ * frame (`document`); chunked binary bodies need no framing.
13
15
  *
14
16
  * The frame type is opaque to this layer; the protocol's event schema decodes
15
17
  * each frame before its state machine handles it.
@@ -26,4 +28,6 @@ export declare const sse: Definition<string>;
26
28
  export declare const sseWithDone: Definition<string>;
27
29
  /** SSE framing restricted to protocol-recognized event names. */
28
30
  export declare const sseEvents: (events: ReadonlySet<string>) => Definition<string>;
31
+ export declare const lines: Definition<string>;
32
+ export declare const document: Definition<string>;
29
33
  export * as Framing from "./framing.js";
@@ -1,3 +1,4 @@
1
+ import { Stream } from "effect";
1
2
  import * as ProviderShared from "../protocols/shared.js";
2
3
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
3
4
  export const sse = { id: "sse", frame: ProviderShared.sseFraming };
@@ -11,4 +12,12 @@ export const sseEvents = (events) => ({
11
12
  id: "sse",
12
13
  frame: (bytes) => ProviderShared.sseFraming(bytes, events),
13
14
  });
15
+ export const lines = {
16
+ id: "lines",
17
+ frame: (bytes) => bytes.pipe(Stream.decodeText(), Stream.splitLines, Stream.filter((line) => line.trim().length > 0)),
18
+ };
19
+ export const document = {
20
+ id: "document",
21
+ frame: (bytes) => Stream.fromEffect(Stream.mkString(bytes.pipe(Stream.decodeText()))),
22
+ };
14
23
  export * as Framing from "./framing.js";
@@ -1,15 +1,31 @@
1
- import { Effect, Schema } from "effect";
1
+ import { Duration, Effect, Schema, type Stream } 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";
7
+ /** Array values become repeated parameters (`keyterm=a&keyterm=b`). */
8
+ export type Query = Readonly<Record<string, string | ReadonlyArray<string>>>;
9
+ /** `query` is appended to the endpoint URL before the route and caller `http.query` overlays. */
4
10
  export type Body = {
5
11
  readonly type: "json";
6
12
  readonly value: Record<string, unknown>;
13
+ readonly query?: Query;
7
14
  } | {
8
15
  readonly type: "multipart";
9
16
  readonly value: FormData;
17
+ } | {
18
+ readonly type: "binary";
19
+ readonly value: Uint8Array;
20
+ readonly contentType: string;
21
+ readonly query?: Query;
10
22
  };
11
- export declare const json: (value: Record<string, unknown>) => Body;
23
+ export declare const json: (value: Record<string, unknown>, query?: Query) => Body;
12
24
  export declare const multipart: (value: FormData) => Body;
25
+ export declare const binary: (value: Uint8Array, contentType: string, query?: Query) => Body;
26
+ export type Send = (path: string, body: Body) => Effect.Effect<HttpClientResponse.HttpClientResponse, AIError>;
27
+ /** Runs after unsupported-field rejection and before `body.from`, for providers that need an upload first. */
28
+ export type Prepare<Request> = (request: Request, send: Send) => Effect.Effect<Request, AIError>;
13
29
  export interface DecodeContext<Request> {
14
30
  readonly request: Request;
15
31
  readonly body: Body;
@@ -29,17 +45,114 @@ export interface Inline<Request, Response> {
29
45
  };
30
46
  }
31
47
  export declare const inline: <Request, Response>(input: Omit<Inline<Request, Response>, "kind">) => Inline<Request, Response>;
32
- /** Read a text body while retaining the original payload and HTTP context on every downstream error. */
48
+ /** What `start` learned from the submission response: the route-owned handle plus the first observation. */
49
+ export interface Started<Token> {
50
+ readonly token: Token;
51
+ readonly snapshot: Snapshot;
52
+ }
53
+ /**
54
+ * A follow-up call's inputs: the decoded token and the auth headers the route sent, so a protocol can attach them
55
+ * to output URLs that require the same credentials to download (Veo). `materialize` downloads an output through the
56
+ * route's executor, for URLs that expire too soon to hand back (BFL).
57
+ */
58
+ export interface PollContext<Token> {
59
+ readonly token: Token;
60
+ readonly auth: Record<string, string>;
61
+ readonly materialize: (asset: Media.Asset) => Effect.Effect<Media.Asset, AIError>;
62
+ }
63
+ /**
64
+ * Submit, then poll. `start` posts the body to the route endpoint; `status`, `result`, and `cancel` are follow-up
65
+ * calls addressed by the token. Paths are relative to the route base URL unless the provider hands back absolute
66
+ * URLs (fal `status_url`), in which case they are used verbatim. `result` is always its own GET: providers that
67
+ * return the output inside the status body (Veo, xAI, Runway) point `result.path` at the status path and decode the
68
+ * same document, so `Generation.await` and `Video.resume(...).await()` behave identically everywhere.
69
+ */
70
+ export interface Queued<Request, Response, Token> {
71
+ readonly kind: "queued";
72
+ readonly id: string;
73
+ readonly name: string;
74
+ /** Common request fields this protocol cannot lower; the route rejects them before `start.body.from` runs. */
75
+ readonly unsupported?: ReadonlyArray<keyof Request & string>;
76
+ /** Serializable handle. `Generation.token` carries the encoded form so it can be persisted and resumed elsewhere. */
77
+ readonly token: Schema.Codec<Token, unknown>;
78
+ readonly start: {
79
+ readonly prepare?: Prepare<Request>;
80
+ readonly body: {
81
+ readonly from: (request: Request) => Effect.Effect<Body, AIError>;
82
+ };
83
+ readonly decode: (response: HttpClientResponse.HttpClientResponse, context: DecodeContext<Request>) => Effect.Effect<Started<Token>, AIError>;
84
+ };
85
+ readonly status: {
86
+ readonly path: (token: Token) => string;
87
+ readonly decode: (response: HttpClientResponse.HttpClientResponse, context: PollContext<Token>) => Effect.Effect<Snapshot, AIError>;
88
+ };
89
+ readonly result: {
90
+ readonly path: (token: Token) => string;
91
+ readonly decode: (response: HttpClientResponse.HttpClientResponse, context: PollContext<Token>) => Effect.Effect<Response, AIError>;
92
+ };
93
+ readonly cancel?: {
94
+ readonly method: AuthInput["method"];
95
+ readonly path: (token: Token) => string;
96
+ };
97
+ }
98
+ export declare const queued: <Request, Response, Token>(input: Omit<Queued<Request, Response, Token>, "kind">) => Queued<Request, Response, Token>;
99
+ export type Mode = "generate" | "stream";
100
+ export type Addressed<Request> = Request & {
101
+ readonly mode: Mode;
102
+ };
103
+ export interface ResponseContext<Request> extends DecodeContext<Addressed<Request>> {
104
+ readonly http: HttpContext;
105
+ }
106
+ /**
107
+ * One request whose body is parsed incrementally, like LLM protocols: `frames` → `step`* → `finish`. `generate` and
108
+ * `stream` share this state machine; `request.mode` lets a protocol pick a different body, path, or framing.
109
+ */
110
+ export interface Streamed<Request, Event, Frame, State> {
111
+ readonly kind: "stream";
112
+ readonly id: string;
113
+ readonly name: string;
114
+ /** Common request fields this protocol cannot lower; the route rejects them before `body.from` runs. */
115
+ readonly unsupported?: ReadonlyArray<keyof Request & string>;
116
+ readonly body: {
117
+ readonly from: (request: Addressed<Request>) => Effect.Effect<Body, AIError>;
118
+ };
119
+ readonly frames: (bytes: Stream.Stream<Uint8Array, AIError>, context: DecodeContext<Addressed<Request>>) => Stream.Stream<Frame, AIError>;
120
+ readonly initial: () => State;
121
+ readonly step: (state: State, frame: Frame) => Effect.Effect<readonly [State, ReadonlyArray<Event>], AIError>;
122
+ /** Emit exactly one terminal event, or fail when the provider stopped before completing. */
123
+ readonly finish: (state: State, context: ResponseContext<Request>) => Effect.Effect<ReadonlyArray<Event>, AIError>;
124
+ }
125
+ export declare const stream: <Request, Event, Frame, State>(input: Omit<Streamed<Request, Event, Frame, State>, "kind">) => Streamed<Request, Event, Frame, State>;
126
+ /**
127
+ * Read a text body while retaining the original payload and HTTP context on every downstream error. `invalid` is a
128
+ * malformed provider document; `ended` is a generation that reached a terminal status without output (`failed` is
129
+ * provider-side, `cancelled`/`expired` mean the result will never exist); `contentPolicy` is a moderated result.
130
+ */
33
131
  export declare const text: (route: string, name: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
34
132
  body: string;
35
133
  http: HttpContext;
36
134
  invalid: (message: string, cause?: unknown) => AIError;
135
+ ended: (status: Exclude<Status, "queued" | "running" | "completed">, message: string) => AIError;
136
+ contentPolicy: (message: string) => AIError;
37
137
  }, AIError, never>;
138
+ export type Output = Effect.Success<ReturnType<typeof text>>;
38
139
  /** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
39
140
  export declare const decodeJson: <A>(route: string, name: string, schema: Schema.Codec<A, unknown>) => (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
40
141
  value: A;
41
142
  body: string;
42
143
  http: HttpContext;
43
144
  invalid: (message: string, cause?: unknown) => AIError;
145
+ ended: (status: Exclude<Status, "queued" | "running" | "completed">, message: string) => AIError;
146
+ contentPolicy: (message: string) => AIError;
44
147
  }, AIError, never>;
148
+ /** Decode a submission response into the token and first snapshot. */
149
+ 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>;
150
+ /** Map a provider status string through the protocol's table; unknown values are an invalid provider document. */
151
+ export declare const status: <Table extends Record<string, Status>>(table: Table, raw: string, output: Output) => Effect.Effect<Status, AIError>;
152
+ export declare const frameError: (route: string, message: string, body?: string, cause?: unknown) => AIError;
153
+ export declare const incomplete: (route: string) => AIError;
154
+ /** Schema-decode one JSON stream frame. Decode failures keep the frame as `reason.body`. */
155
+ export declare const decodeFrame: <A>(route: string, name: string, schema: Schema.Codec<A, unknown>) => (frame: string) => Effect.Effect<A, AIError, never>;
156
+ /** A `url` asset whose provider-declared retention window starts now. */
157
+ export declare const expiringUrl: (url: string, retention: Duration.Duration, options?: Parameters<typeof Media.url>[1]) => Effect.Effect<Media.Asset, never, never>;
45
158
  export * as MediaProtocol from "./media-protocol.js";
@@ -1,17 +1,40 @@
1
- import { Effect, Schema } from "effect";
1
+ import { Clock, Duration, Effect, Schema } from "effect";
2
2
  import { HttpClientResponse } from "effect/unstable/http";
3
- import { AIError, HttpContext, InvalidProviderOutputError } from "../schema/index.js";
4
- export const json = (value) => ({ type: "json", value });
3
+ import { Media } from "../media.js";
4
+ import { AIError, ContentPolicyError, HttpContext, InvalidProviderOutputError, InvalidRequestError, ProviderInternalError, } from "../schema/index.js";
5
+ export const json = (value, query) => ({
6
+ type: "json",
7
+ value,
8
+ query,
9
+ });
5
10
  export const multipart = (value) => ({ type: "multipart", value });
11
+ export const binary = (value, contentType, query) => ({
12
+ type: "binary",
13
+ value,
14
+ contentType,
15
+ query,
16
+ });
6
17
  export const inline = (input) => ({
7
18
  kind: "inline",
8
19
  ...input,
9
20
  });
21
+ export const queued = (input) => ({
22
+ kind: "queued",
23
+ ...input,
24
+ });
25
+ export const stream = (input) => ({
26
+ kind: "stream",
27
+ ...input,
28
+ });
10
29
  // ---------------------------------------------------------------------------
11
30
  // Response helpers
12
31
  // ---------------------------------------------------------------------------
13
32
  const context = (response) => new HttpContext({ url: response.request.url, status: response.status, headers: response.headers });
14
- /** Read a text body while retaining the original payload and HTTP context on every downstream error. */
33
+ /**
34
+ * Read a text body while retaining the original payload and HTTP context on every downstream error. `invalid` is a
35
+ * malformed provider document; `ended` is a generation that reached a terminal status without output (`failed` is
36
+ * provider-side, `cancelled`/`expired` mean the result will never exist); `contentPolicy` is a moderated result.
37
+ */
15
38
  export const text = Effect.fn("MediaProtocol.text")(function* (route, name, response) {
16
39
  const http = context(response);
17
40
  const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
@@ -26,6 +49,12 @@ export const text = Effect.fn("MediaProtocol.text")(function* (route, name, resp
26
49
  body,
27
50
  http,
28
51
  invalid: (message, cause) => new AIError({ reason: new InvalidProviderOutputError({ route, message, body, http, cause }) }),
52
+ ended: (status, message) => new AIError({
53
+ reason: status === "failed"
54
+ ? new ProviderInternalError({ message, body, http })
55
+ : new InvalidRequestError({ message, body, http }),
56
+ }),
57
+ contentPolicy: (message) => new AIError({ reason: new ContentPolicyError({ message, body, http }) }),
29
58
  };
30
59
  });
31
60
  /** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
@@ -37,4 +66,31 @@ export const decodeJson = (route, name, schema) => {
37
66
  return { ...output, value };
38
67
  });
39
68
  };
69
+ /** Decode a submission response into the token and first snapshot. */
70
+ export const decodeStarted = (route, name, schema, started) => {
71
+ const decode = decodeJson(route, name, schema);
72
+ return (response) => decode(response).pipe(Effect.map((output) => started(output.value)));
73
+ };
74
+ /** Map a provider status string through the protocol's table; unknown values are an invalid provider document. */
75
+ export const status = (table, raw, output) => {
76
+ const normalized = table[raw];
77
+ if (normalized === undefined)
78
+ return Effect.fail(output.invalid(`Unknown generation status "${raw}"`));
79
+ return Effect.succeed(normalized);
80
+ };
81
+ export const frameError = (route, message, body, cause) => new AIError({ reason: new InvalidProviderOutputError({ route, message, body, cause }) });
82
+ export const incomplete = (route) => new AIError({
83
+ reason: new InvalidProviderOutputError({
84
+ route,
85
+ message: "The provider response ended unexpectedly.",
86
+ classification: "incomplete-stream",
87
+ }),
88
+ });
89
+ /** Schema-decode one JSON stream frame. Decode failures keep the frame as `reason.body`. */
90
+ export const decodeFrame = (route, name, schema) => {
91
+ const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(schema));
92
+ return (frame) => decode(frame).pipe(Effect.mapError((cause) => frameError(route, `${name} sent an invalid stream event`, frame, cause)));
93
+ };
94
+ /** A `url` asset whose provider-declared retention window starts now. */
95
+ export const expiringUrl = (url, retention, options) => Clock.currentTimeMillis.pipe(Effect.map((now) => Media.url(url, { ...options, expiresAt: now + Duration.toMillis(retention) })));
40
96
  export * as MediaProtocol from "./media-protocol.js";
@@ -1,8 +1,9 @@
1
- import { Effect } from "effect";
1
+ import { Effect, Stream } from "effect";
2
2
  import { Auth } from "./auth.js";
3
3
  import { Endpoint } from "./endpoint.js";
4
- import type { Interface } from "./executor-service.js";
4
+ import { type Interface } from "./executor-service.js";
5
5
  import { MediaProtocol } from "./media-protocol.js";
6
+ import { Generation, type AwaitOptions, type Observation } 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,75 @@ 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> {
28
+ readonly kind: "inline";
26
29
  readonly id: string;
27
30
  readonly provider: ProviderID;
28
31
  readonly protocol: string;
29
32
  readonly generate: (request: Request, execute: Execute) => Effect.Effect<Response, AIError>;
30
33
  }
31
- export interface MakeInput<Request extends MediaRequest, Response> {
34
+ /** Submit, then poll through the returned `Generation`. */
35
+ export interface QueuedRoute<Request extends MediaRequest, Response> {
36
+ readonly kind: "queued";
37
+ readonly id: string;
38
+ readonly provider: ProviderID;
39
+ readonly protocol: string;
40
+ readonly start: (request: Request, execute: Execute) => Effect.Effect<Generation<Response>, AIError>;
41
+ /** Rebuild a handle from a persisted `Generation.token`; fails typed when the token is not this route's. */
42
+ readonly resume: (model: MediaRequest["model"], token: unknown, execute: Execute) => Effect.Effect<Generation<Response>, AIError>;
43
+ }
44
+ /** One request whose response parses into events; `generate` runs the same stream and collects it. */
45
+ export interface StreamRoute<Request extends MediaRequest, Event, Response> {
46
+ readonly kind: "stream";
47
+ readonly id: string;
48
+ readonly provider: ProviderID;
49
+ readonly protocol: string;
50
+ readonly stream: (request: Request, execute: Execute) => Stream.Stream<Event, AIError>;
51
+ readonly generate: (request: Request, execute: Execute) => Effect.Effect<Response, AIError>;
52
+ }
53
+ export type AnyRoute<Request extends MediaRequest, Event, Response> = Route<Request, Response> | StreamRoute<Request, Event, Response> | QueuedRoute<Request, Response>;
54
+ export interface Composition<Request extends MediaRequest> {
32
55
  readonly id: string;
33
56
  readonly provider: string | ProviderID;
34
- readonly protocol: MediaProtocol.Inline<Request, Response>;
35
57
  readonly endpoint: Endpoint.Definition<MediaProtocol.Body, Request>;
36
58
  readonly auth: Auth.Definition;
37
59
  /** Deployment headers applied before transport authentication. */
38
60
  readonly headers?: Record<string, string>;
39
61
  }
62
+ export interface InlineInput<Request extends MediaRequest, Response> extends Composition<Request> {
63
+ readonly protocol: MediaProtocol.Inline<Request, Response>;
64
+ }
65
+ export interface QueuedInput<Request extends MediaRequest, Response, Token> extends Composition<Request> {
66
+ readonly protocol: MediaProtocol.Queued<Request, Response, Token>;
67
+ }
68
+ export interface StreamInput<Request extends MediaRequest, Event, Response, Frame, State> extends Composition<MediaProtocol.Addressed<Request>> {
69
+ readonly protocol: MediaProtocol.Streamed<Request, Event, Frame, State>;
70
+ readonly collect: (events: ReadonlyArray<Event>) => Effect.Effect<Response, AIError>;
71
+ }
40
72
  /**
41
73
  * 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 rejection, URL and query
43
- * rendering, auth headers, JSON vs multipart encoding, and handing the response back to the protocol for decoding.
74
+ * plumbing every media protocol would otherwise duplicate: option merging, surrogate sanitizing, unsupported-field
75
+ * rejection, URL and query rendering, auth headers, JSON, multipart, or binary encoding, and handing responses back
76
+ * to the protocol.
77
+ */
78
+ export declare const inline: <Request extends MediaRequest, Response>(input: InlineInput<Request, Response>) => Route<Request, Response>;
79
+ /**
80
+ * Compose a queued media protocol the same way, adding `start`/`resume` handles whose polls reuse the route's auth,
81
+ * deployment headers, and (for `start`) the request's `http` overlay. The token is decoded once at the boundary and
82
+ * closed over by the resulting `Generation.Route`.
44
83
  */
45
- export declare const make: <Request extends MediaRequest, Response>(input: MakeInput<Request, Response>) => Route<Request, Response>;
84
+ export declare const queued: <Request extends MediaRequest, Response, Token>(input: QueuedInput<Request, Response, Token>) => QueuedRoute<Request, Response>;
85
+ /** Compose a streaming media protocol; `generate` runs the same stream in `generate` mode and folds it with `collect`. */
86
+ export declare const stream: <Request extends MediaRequest, Event, Response, Frame, State>(input: StreamInput<Request, Event, Response, Frame, State>) => StreamRoute<Request, Event, Response>;
87
+ export declare const dispatch: <Event, Response>(input: {
88
+ readonly modality: string;
89
+ readonly execute: Execute;
90
+ readonly responseEvents: (response: Response) => ReadonlyArray<Event>;
91
+ }) => {
92
+ start: <Request extends MediaRequest>(route: AnyRoute<Request, Event, Response>, request: Request) => Effect.Effect<Generation<Response>, AIError, never>;
93
+ resume: <Request extends MediaRequest>(route: AnyRoute<Request, Event, Response>, model: MediaRequest["model"], token: unknown) => Effect.Effect<Generation<Response>, AIError, never>;
94
+ generate: <Request extends MediaRequest>(route: AnyRoute<Request, Event, Response>, request: Request, options?: AwaitOptions) => Effect.Effect<Response, AIError, never>;
95
+ stream: <Request extends MediaRequest>(route: AnyRoute<Request, Event, Response>, request: Request, options?: AwaitOptions) => Stream.Stream<Event | Observation, AIError>;
96
+ };
46
97
  export * as MediaRoute from "./media.js";