@opencode/ai 2.0.14 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +399 -56
  2. package/dist/experimental/evaluation-client.d.ts +1 -1
  3. package/dist/experimental/evaluation-client.js +39 -3
  4. package/dist/experimental/evaluation.d.ts +4 -4
  5. package/dist/experimental/evaluation.js +2 -2
  6. package/dist/experimental/system-one.d.ts +3 -3
  7. package/dist/experimental/system-one.js +40 -51
  8. package/dist/generation.d.ts +83 -0
  9. package/dist/generation.js +113 -0
  10. package/dist/image-client.d.ts +16 -7
  11. package/dist/image-client.js +29 -13
  12. package/dist/image.d.ts +1410 -81
  13. package/dist/image.js +97 -67
  14. package/dist/index.d.ts +17 -2
  15. package/dist/index.js +12 -1
  16. package/dist/llm.d.ts +9 -1
  17. package/dist/media-model.d.ts +44 -0
  18. package/dist/media-model.js +49 -0
  19. package/dist/media.d.ts +213 -0
  20. package/dist/media.js +227 -0
  21. package/dist/promise.d.ts +974 -0
  22. package/dist/promise.js +81 -0
  23. package/dist/protocols/alibaba-chat.d.ts +12 -0
  24. package/dist/protocols/alibaba-responses.d.ts +2 -2
  25. package/dist/protocols/anthropic-messages.js +8 -19
  26. package/dist/protocols/assemblyai-transcription.d.ts +40 -0
  27. package/dist/protocols/assemblyai-transcription.js +138 -0
  28. package/dist/protocols/bedrock-converse.d.ts +4 -4
  29. package/dist/protocols/bedrock-converse.js +6 -17
  30. package/dist/protocols/bfl-images.d.ts +32 -0
  31. package/dist/protocols/bfl-images.js +153 -0
  32. package/dist/protocols/cartesia-speech.d.ts +127 -0
  33. package/dist/protocols/cartesia-speech.js +126 -0
  34. package/dist/protocols/deepgram-speech.d.ts +119 -0
  35. package/dist/protocols/deepgram-speech.js +92 -0
  36. package/dist/protocols/deepgram-transcription.d.ts +25 -0
  37. package/dist/protocols/deepgram-transcription.js +129 -0
  38. package/dist/protocols/elevenlabs-speech.d.ts +122 -0
  39. package/dist/protocols/elevenlabs-speech.js +115 -0
  40. package/dist/protocols/fal-images.d.ts +24 -0
  41. package/dist/protocols/fal-images.js +114 -0
  42. package/dist/protocols/fal-video.d.ts +29 -0
  43. package/dist/protocols/fal-video.js +88 -0
  44. package/dist/protocols/gemini.d.ts +30 -9
  45. package/dist/protocols/gemini.js +45 -35
  46. package/dist/protocols/google-images.d.ts +9 -21
  47. package/dist/protocols/google-images.js +158 -133
  48. package/dist/protocols/google-speech.d.ts +130 -0
  49. package/dist/protocols/google-speech.js +84 -0
  50. package/dist/protocols/google-transcription.d.ts +173 -0
  51. package/dist/protocols/google-transcription.js +138 -0
  52. package/dist/protocols/google-video.d.ts +26 -0
  53. package/dist/protocols/google-video.js +158 -0
  54. package/dist/protocols/meta-images.d.ts +7 -12
  55. package/dist/protocols/meta-images.js +85 -66
  56. package/dist/protocols/meta-responses.d.ts +4 -4
  57. package/dist/protocols/meta-responses.js +1 -1
  58. package/dist/protocols/mistral-chat.js +7 -6
  59. package/dist/protocols/open-responses.d.ts +17 -9
  60. package/dist/protocols/open-responses.js +24 -14
  61. package/dist/protocols/openai-chat.d.ts +118 -1
  62. package/dist/protocols/openai-chat.js +125 -44
  63. package/dist/protocols/openai-compatible-chat.d.ts +12 -0
  64. package/dist/protocols/openai-compatible-responses.d.ts +2 -2
  65. package/dist/protocols/openai-images.d.ts +128 -18
  66. package/dist/protocols/openai-images.js +177 -154
  67. package/dist/protocols/openai-responses.d.ts +15 -15
  68. package/dist/protocols/openai-responses.js +5 -6
  69. package/dist/protocols/openai-speech.d.ts +116 -0
  70. package/dist/protocols/openai-speech.js +98 -0
  71. package/dist/protocols/openai-transcription.d.ts +207 -0
  72. package/dist/protocols/openai-transcription.js +190 -0
  73. package/dist/protocols/replicate-images.d.ts +28 -0
  74. package/dist/protocols/replicate-images.js +133 -0
  75. package/dist/protocols/runway-video.d.ts +38 -0
  76. package/dist/protocols/runway-video.js +146 -0
  77. package/dist/protocols/shared.d.ts +27 -17
  78. package/dist/protocols/shared.js +52 -35
  79. package/dist/protocols/stability-images.d.ts +38 -0
  80. package/dist/protocols/stability-images.js +148 -0
  81. package/dist/protocols/utils/bedrock-media.d.ts +2 -3
  82. package/dist/protocols/utils/bedrock-media.js +4 -4
  83. package/dist/protocols/utils/fal-queue.d.ts +28 -0
  84. package/dist/protocols/utils/fal-queue.js +69 -0
  85. package/dist/protocols/utils/gemini-generate-content.d.ts +65 -0
  86. package/dist/protocols/utils/gemini-generate-content.js +65 -0
  87. package/dist/protocols/utils/gemini-json-schema.d.ts +3 -0
  88. package/dist/protocols/utils/gemini-json-schema.js +76 -0
  89. package/dist/protocols/utils/media-input.d.ts +18 -0
  90. package/dist/protocols/utils/media-input.js +35 -0
  91. package/dist/protocols/utils/responses-compaction.js +6 -5
  92. package/dist/protocols/utils/speech-stream.d.ts +49 -0
  93. package/dist/protocols/utils/speech-stream.js +67 -0
  94. package/dist/protocols/utils/tool-schema.d.ts +2 -2
  95. package/dist/protocols/utils/tool-schema.js +40 -17
  96. package/dist/protocols/utils/tool-stream.d.ts +27 -3
  97. package/dist/protocols/xai-images.d.ts +9 -15
  98. package/dist/protocols/xai-images.js +75 -84
  99. package/dist/protocols/xai-responses.d.ts +2 -2
  100. package/dist/protocols/xai-video.d.ts +34 -0
  101. package/dist/protocols/xai-video.js +147 -0
  102. package/dist/protocols/zai-chat.d.ts +13 -1
  103. package/dist/protocols/zai-images.d.ts +9 -13
  104. package/dist/protocols/zai-images.js +59 -57
  105. package/dist/provider-error.js +3 -0
  106. package/dist/providers/alibaba.d.ts +14 -2
  107. package/dist/providers/amazon-bedrock-mantle.d.ts +14 -2
  108. package/dist/providers/amazon-bedrock.d.ts +2 -2
  109. package/dist/providers/assemblyai.d.ts +25 -0
  110. package/dist/providers/assemblyai.js +29 -0
  111. package/dist/providers/azure.d.ts +18 -6
  112. package/dist/providers/baseten.d.ts +24 -0
  113. package/dist/providers/black-forest-labs.d.ts +25 -0
  114. package/dist/providers/black-forest-labs.js +28 -0
  115. package/dist/providers/cartesia.d.ts +24 -0
  116. package/dist/providers/cartesia.js +22 -0
  117. package/dist/providers/cerebras.d.ts +24 -0
  118. package/dist/providers/cerebras.js +6 -1
  119. package/dist/providers/cloudflare-ai-gateway.d.ts +30 -6
  120. package/dist/providers/cloudflare-workers-ai.d.ts +24 -0
  121. package/dist/providers/deepgram.d.ts +29 -0
  122. package/dist/providers/deepgram.js +31 -0
  123. package/dist/providers/deepinfra.d.ts +24 -0
  124. package/dist/providers/deepinfra.js +6 -1
  125. package/dist/providers/deepseek.d.ts +24 -0
  126. package/dist/providers/elevenlabs.d.ts +24 -0
  127. package/dist/providers/elevenlabs.js +28 -0
  128. package/dist/providers/fal.d.ts +29 -0
  129. package/dist/providers/fal.js +33 -0
  130. package/dist/providers/fireworks.d.ts +24 -0
  131. package/dist/providers/google-vertex-chat.d.ts +12 -0
  132. package/dist/providers/google-vertex-responses.d.ts +2 -2
  133. package/dist/providers/google-vertex.d.ts +10 -3
  134. package/dist/providers/google.d.ts +25 -3
  135. package/dist/providers/google.js +11 -2
  136. package/dist/providers/groq.d.ts +24 -0
  137. package/dist/providers/index.d.ts +10 -0
  138. package/dist/providers/index.js +10 -0
  139. package/dist/providers/meta.d.ts +14 -2
  140. package/dist/providers/minimax.d.ts +14 -2
  141. package/dist/providers/moonshot.d.ts +14 -2
  142. package/dist/providers/moonshot.js +3 -3
  143. package/dist/providers/openai-compatible-responses.d.ts +2 -2
  144. package/dist/providers/openai-compatible.d.ts +12 -0
  145. package/dist/providers/openai.d.ts +25 -3
  146. package/dist/providers/openai.js +10 -1
  147. package/dist/providers/openrouter.d.ts +67 -0
  148. package/dist/providers/openrouter.js +13 -1
  149. package/dist/providers/replicate.d.ts +25 -0
  150. package/dist/providers/replicate.js +22 -0
  151. package/dist/providers/runway.d.ts +24 -0
  152. package/dist/providers/runway.js +22 -0
  153. package/dist/providers/stability.d.ts +28 -0
  154. package/dist/providers/stability.js +23 -0
  155. package/dist/providers/togetherai.d.ts +24 -0
  156. package/dist/providers/vercel-ai-gateway.d.ts +41 -0
  157. package/dist/providers/vercel-ai-gateway.js +85 -0
  158. package/dist/providers/xai.d.ts +17 -0
  159. package/dist/providers/xai.js +5 -2
  160. package/dist/providers/zai-coding-plan.d.ts +15 -3
  161. package/dist/providers/zai.d.ts +13 -1
  162. package/dist/route/auth.d.ts +4 -1
  163. package/dist/route/auth.js +6 -0
  164. package/dist/route/client.d.ts +9 -1
  165. package/dist/route/endpoint.d.ts +10 -10
  166. package/dist/route/executor-service.d.ts +12 -0
  167. package/dist/route/executor-service.js +3 -0
  168. package/dist/route/executor.d.ts +4 -9
  169. package/dist/route/executor.js +3 -3
  170. package/dist/route/framing.d.ts +5 -1
  171. package/dist/route/framing.js +9 -0
  172. package/dist/route/index.d.ts +2 -0
  173. package/dist/route/index.js +2 -0
  174. package/dist/route/media-protocol.d.ts +158 -0
  175. package/dist/route/media-protocol.js +96 -0
  176. package/dist/route/media.d.ts +97 -0
  177. package/dist/route/media.js +236 -0
  178. package/dist/schema/errors.d.ts +13 -3
  179. package/dist/schema/errors.js +7 -0
  180. package/dist/schema/events.d.ts +557 -40
  181. package/dist/schema/events.js +35 -2
  182. package/dist/schema/messages.d.ts +95 -8
  183. package/dist/schema/messages.js +8 -6
  184. package/dist/schema/options.d.ts +6 -3
  185. package/dist/schema/options.js +6 -2
  186. package/dist/speech-client.d.ts +21 -0
  187. package/dist/speech-client.js +25 -0
  188. package/dist/speech.d.ts +1150 -0
  189. package/dist/speech.js +119 -0
  190. package/dist/testing.d.ts +72 -8
  191. package/dist/transcription-client.d.ts +28 -0
  192. package/dist/transcription-client.js +44 -0
  193. package/dist/transcription.d.ts +1504 -0
  194. package/dist/transcription.js +133 -0
  195. package/dist/utils/bytes.d.ts +1 -0
  196. package/dist/utils/bytes.js +10 -0
  197. package/dist/utils/media-type.d.ts +7 -0
  198. package/dist/utils/media-type.js +70 -0
  199. package/dist/utils/sanitize.js +3 -1
  200. package/dist/video-client.d.ts +28 -0
  201. package/dist/video-client.js +40 -0
  202. package/dist/video.d.ts +1359 -0
  203. package/dist/video.js +119 -0
  204. package/package.json +7 -3
  205. package/dist/protocols/utils/gemini-tool-schema.d.ts +0 -2
  206. package/dist/protocols/utils/gemini-tool-schema.js +0 -103
  207. package/dist/protocols/utils/image-input.d.ts +0 -21
  208. package/dist/protocols/utils/image-input.js +0 -20
@@ -0,0 +1,158 @@
1
+ import { Duration, Effect, Schema, type Stream } from "effect";
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";
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. */
10
+ export type Body = {
11
+ readonly type: "json";
12
+ readonly value: Record<string, unknown>;
13
+ readonly query?: Query;
14
+ } | {
15
+ readonly type: "multipart";
16
+ readonly value: FormData;
17
+ } | {
18
+ readonly type: "binary";
19
+ readonly value: Uint8Array;
20
+ readonly contentType: string;
21
+ readonly query?: Query;
22
+ };
23
+ export declare const json: (value: Record<string, unknown>, query?: Query) => Body;
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>;
29
+ export interface DecodeContext<Request> {
30
+ readonly request: Request;
31
+ readonly body: Body;
32
+ }
33
+ /** One request, one response. JSON or multipart in; JSON or raw bytes out. */
34
+ export interface Inline<Request, Response> {
35
+ readonly kind: "inline";
36
+ readonly id: string;
37
+ readonly name: string;
38
+ /** Common request fields this protocol cannot lower; the route rejects them before `body.from` runs. */
39
+ readonly unsupported?: ReadonlyArray<keyof Request & string>;
40
+ readonly body: {
41
+ readonly from: (request: Request) => Effect.Effect<Body, AIError>;
42
+ };
43
+ readonly response: {
44
+ readonly decode: (response: HttpClientResponse.HttpClientResponse, context: DecodeContext<Request>) => Effect.Effect<Response, AIError>;
45
+ };
46
+ }
47
+ export declare const inline: <Request, Response>(input: Omit<Inline<Request, Response>, "kind">) => Inline<Request, Response>;
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
+ */
131
+ export declare const text: (route: string, name: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
132
+ body: string;
133
+ http: HttpContext;
134
+ invalid: (message: string, cause?: unknown) => AIError;
135
+ ended: (status: Exclude<Status, "queued" | "running" | "completed">, message: string) => AIError;
136
+ contentPolicy: (message: string) => AIError;
137
+ }, AIError, never>;
138
+ export type Output = Effect.Success<ReturnType<typeof text>>;
139
+ /** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
140
+ export declare const decodeJson: <A>(route: string, name: string, schema: Schema.Codec<A, unknown>) => (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
141
+ value: A;
142
+ body: string;
143
+ http: HttpContext;
144
+ invalid: (message: string, cause?: unknown) => AIError;
145
+ ended: (status: Exclude<Status, "queued" | "running" | "completed">, message: string) => AIError;
146
+ contentPolicy: (message: string) => AIError;
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>;
158
+ export * as MediaProtocol from "./media-protocol.js";
@@ -0,0 +1,96 @@
1
+ import { Clock, Duration, Effect, Schema } from "effect";
2
+ import { HttpClientResponse } from "effect/unstable/http";
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
+ });
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
+ });
17
+ export const inline = (input) => ({
18
+ kind: "inline",
19
+ ...input,
20
+ });
21
+ export const queued = (input) => ({
22
+ kind: "queued",
23
+ ...input,
24
+ });
25
+ export const stream = (input) => ({
26
+ kind: "stream",
27
+ ...input,
28
+ });
29
+ // ---------------------------------------------------------------------------
30
+ // Response helpers
31
+ // ---------------------------------------------------------------------------
32
+ const context = (response) => new HttpContext({ url: response.request.url, status: response.status, headers: response.headers });
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
+ */
38
+ export const text = Effect.fn("MediaProtocol.text")(function* (route, name, response) {
39
+ const http = context(response);
40
+ const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
41
+ reason: new InvalidProviderOutputError({
42
+ route,
43
+ message: `Failed to read the ${name} response`,
44
+ http,
45
+ cause,
46
+ }),
47
+ })));
48
+ return {
49
+ body,
50
+ http,
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 }) }),
58
+ };
59
+ });
60
+ /** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
61
+ export const decodeJson = (route, name, schema) => {
62
+ const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(schema));
63
+ return Effect.fn("MediaProtocol.decodeJson")(function* (response) {
64
+ const output = yield* text(route, name, response);
65
+ const value = yield* decode(output.body).pipe(Effect.mapError((cause) => output.invalid(`${name} returned an invalid response`, cause)));
66
+ return { ...output, value };
67
+ });
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) })));
96
+ export * as MediaProtocol from "./media-protocol.js";
@@ -0,0 +1,97 @@
1
+ import { Effect, Stream } from "effect";
2
+ import { Auth } from "./auth.js";
3
+ import { Endpoint } from "./endpoint.js";
4
+ import { type Interface } from "./executor-service.js";
5
+ import { MediaProtocol } from "./media-protocol.js";
6
+ import { Generation, type AwaitOptions, type Observation } from "../generation.js";
7
+ import { AIError, HttpOptions, ProviderID } from "../schema/index.js";
8
+ export type Execute = Interface["execute"];
9
+ /** The minimum a media request must carry for the route to build a transport request. */
10
+ export interface MediaRequest {
11
+ readonly model: {
12
+ readonly id: string;
13
+ readonly provider: ProviderID;
14
+ readonly http?: HttpOptions;
15
+ };
16
+ readonly http?: HttpOptions;
17
+ }
18
+ /** Deployment inputs every media model factory accepts; provider facades fill these from `configure(...)`. */
19
+ export interface ModelInput {
20
+ readonly id: string;
21
+ readonly auth: Auth.Definition;
22
+ readonly baseURL?: string;
23
+ readonly headers?: Record<string, string>;
24
+ readonly http?: HttpOptions;
25
+ }
26
+ /** One request, one response. */
27
+ export interface Route<Request extends MediaRequest, Response> {
28
+ readonly kind: "inline";
29
+ readonly id: string;
30
+ readonly provider: ProviderID;
31
+ readonly protocol: string;
32
+ readonly generate: (request: Request, execute: Execute) => Effect.Effect<Response, AIError>;
33
+ }
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> {
55
+ readonly id: string;
56
+ readonly provider: string | ProviderID;
57
+ readonly endpoint: Endpoint.Definition<MediaProtocol.Body, Request>;
58
+ readonly auth: Auth.Definition;
59
+ /** Deployment headers applied before transport authentication. */
60
+ readonly headers?: Record<string, string>;
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
+ }
72
+ /**
73
+ * Compose an inline media protocol with an endpoint and auth into a runnable route. The route owns the transport
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`.
83
+ */
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
+ };
97
+ export * as MediaRoute from "./media.js";
@@ -0,0 +1,236 @@
1
+ import { Effect, Schema, Stream } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { Auth } from "./auth.js";
4
+ import { Endpoint } from "./endpoint.js";
5
+ import { Service as RequestExecutorService } from "./executor-service.js";
6
+ import { RequestExecutor } from "./executor.js";
7
+ import { MediaProtocol } from "./media-protocol.js";
8
+ import { Generation, resultEvents, } from "../generation.js";
9
+ import { ProviderShared } from "../protocols/shared.js";
10
+ import { AIError, AIErrorReason, HttpOptions, InvalidRequestError, ProviderID, mergeHttpOptions, } from "../schema/index.js";
11
+ import { sanitizeSurrogates } from "../utils/sanitize.js";
12
+ /**
13
+ * Compose an inline media protocol with an endpoint and auth into a runnable route. The route owns the transport
14
+ * plumbing every media protocol would otherwise duplicate: option merging, surrogate sanitizing, unsupported-field
15
+ * rejection, URL and query rendering, auth headers, JSON, multipart, or binary encoding, and handing responses back
16
+ * to the protocol.
17
+ */
18
+ export const inline = (input) => {
19
+ const transport = makeTransport(input);
20
+ return {
21
+ kind: "inline",
22
+ id: input.id,
23
+ provider: transport.provider,
24
+ protocol: input.protocol.id,
25
+ generate: Effect.fn(`MediaRoute.generate`)(function* (request, execute) {
26
+ const submitted = yield* transport.submit(request, { unsupported: input.protocol.unsupported, from: input.protocol.body.from }, execute);
27
+ return yield* input.protocol.response.decode(submitted.response, submitted.context);
28
+ }),
29
+ };
30
+ };
31
+ /**
32
+ * Compose a queued media protocol the same way, adding `start`/`resume` handles whose polls reuse the route's auth,
33
+ * deployment headers, and (for `start`) the request's `http` overlay. The token is decoded once at the boundary and
34
+ * closed over by the resulting `Generation.Route`.
35
+ */
36
+ export const queued = (input) => {
37
+ const transport = makeTransport(input);
38
+ const protocol = input.protocol;
39
+ const decodeToken = Schema.decodeUnknownEffect(protocol.token);
40
+ // A protocol producing a token its own codec rejects is a programmer defect, not a provider error.
41
+ const encodeToken = Schema.encodeSync(protocol.token);
42
+ const generationRoute = (token, http, execute) => {
43
+ const materialize = (asset) => asset.materialize().pipe(Effect.provideService(RequestExecutorService, { execute }));
44
+ const poll = (operation) => transport
45
+ .call("GET", operation.path(token), http, execute)
46
+ .pipe(Effect.flatMap((sent) => operation.decode(sent.response, { token, auth: sent.auth, materialize })));
47
+ const cancel = protocol.cancel;
48
+ const route = {
49
+ status: poll(protocol.status),
50
+ result: poll(protocol.result),
51
+ cancel: cancel === undefined
52
+ ? undefined
53
+ : transport.call(cancel.method, cancel.path(token), http, execute).pipe(Effect.asVoid),
54
+ };
55
+ return route;
56
+ };
57
+ const start = Effect.fn("MediaRoute.start")(function* (request, execute) {
58
+ const submitted = yield* transport.submit(request, { unsupported: protocol.unsupported, prepare: protocol.start.prepare, from: protocol.start.body.from }, execute);
59
+ const started = yield* protocol.start.decode(submitted.response, submitted.context);
60
+ const route = generationRoute(started.token, submitted.context.request.http, execute);
61
+ return new Generation(route, encodeToken(started.token), started.snapshot);
62
+ });
63
+ const resume = Effect.fn("MediaRoute.resume")(function* (model, raw, execute) {
64
+ const token = yield* decodeToken(raw).pipe(Effect.mapError((cause) => new AIError({
65
+ reason: new InvalidRequestError({
66
+ message: `${input.id} cannot resume a generation from this token`,
67
+ cause,
68
+ }),
69
+ })));
70
+ const route = generationRoute(token, transport.http(model), execute);
71
+ return new Generation(route, encodeToken(token), yield* route.status);
72
+ });
73
+ return { kind: "queued", id: input.id, provider: transport.provider, protocol: protocol.id, start, resume };
74
+ };
75
+ /** Compose a streaming media protocol; `generate` runs the same stream in `generate` mode and folds it with `collect`. */
76
+ export const stream = (input) => {
77
+ const transport = makeTransport(input);
78
+ const protocol = input.protocol;
79
+ const events = (request, execute, mode) => Stream.unwrap(Effect.gen(function* () {
80
+ const submitted = yield* transport.submit({ ...request, mode }, { unsupported: protocol.unsupported, from: protocol.body.from }, execute);
81
+ const http = RequestExecutor.responseHttp(submitted.response);
82
+ return Stream.suspend(() => {
83
+ // Parser state is local to one response, exactly like `Route.make`'s LLM stream loop.
84
+ let state = protocol.initial();
85
+ return protocol.frames(RequestExecutor.responseStream(submitted.response), submitted.context).pipe(Stream.mapEffect((frame) => protocol.step(state, frame).pipe(Effect.map(([next, output]) => {
86
+ state = next;
87
+ return output;
88
+ }))), Stream.flattenIterable, Stream.concat(Stream.suspend(() => Stream.fromIterableEffect(protocol.finish(state, { ...submitted.context, http })))), Stream.mapError((error) => error.reason.http !== undefined
89
+ ? error
90
+ : new AIError({
91
+ reason: AIErrorReason.make({
92
+ ...error.reason,
93
+ message: error.reason.message,
94
+ cause: error.reason.cause,
95
+ http,
96
+ }),
97
+ })));
98
+ });
99
+ }));
100
+ return {
101
+ kind: "stream",
102
+ id: input.id,
103
+ provider: transport.provider,
104
+ protocol: protocol.id,
105
+ stream: (request, execute) => events(request, execute, "stream"),
106
+ generate: (request, execute) => events(request, execute, "generate").pipe(Stream.runCollect, Effect.flatMap(input.collect)),
107
+ };
108
+ };
109
+ export const dispatch = (input) => {
110
+ const notQueued = (route, operation) => ProviderShared.unsupportedOperation({
111
+ operation: `${input.modality}.${operation}`,
112
+ provider: route.provider,
113
+ route: route.id,
114
+ message: `${route.provider}/${route.id} is not a queued route; use generate or stream`,
115
+ });
116
+ const start = (route, request) => {
117
+ if (route.kind !== "queued")
118
+ return Effect.fail(notQueued(route, "start"));
119
+ return route.start(request, input.execute);
120
+ };
121
+ return {
122
+ start,
123
+ resume: (route, model, token) => {
124
+ if (route.kind !== "queued")
125
+ return Effect.fail(notQueued(route, "resume"));
126
+ return route.resume(model, token, input.execute);
127
+ },
128
+ generate: (route, request, options) => {
129
+ if (route.kind !== "queued")
130
+ return route.generate(request, input.execute);
131
+ return start(route, request).pipe(Effect.flatMap((generation) => generation.await(options)));
132
+ },
133
+ stream: (route, request, options) => {
134
+ if (route.kind === "stream")
135
+ return route.stream(request, input.execute);
136
+ if (route.kind === "queued")
137
+ return Stream.unwrap(start(route, request).pipe(Effect.map((generation) => resultEvents(generation, input.responseEvents, options))));
138
+ return Stream.fromIterableEffect(Effect.map(route.generate(request, input.execute), input.responseEvents));
139
+ },
140
+ };
141
+ };
142
+ // ---------------------------------------------------------------------------
143
+ // Transport plumbing shared by every kind
144
+ // ---------------------------------------------------------------------------
145
+ const makeTransport = (input) => {
146
+ const provider = ProviderID.make(input.provider);
147
+ const routeHttp = input.headers === undefined ? undefined : new HttpOptions({ headers: input.headers });
148
+ const authorize = Auth.toEffect(input.auth);
149
+ const baseURL = (path) => new URL(`${ProviderShared.trimBaseUrl(input.endpoint.baseURL ?? "")}${path}`);
150
+ /** `auth` is only what `Auth` added, never deployment headers. */
151
+ const send = Effect.fn("MediaRoute.send")(function* (call, execute) {
152
+ const encoded = encode(call.body, call.headers);
153
+ const url = call.url.toString();
154
+ const headers = yield* authorize({
155
+ request: call.request,
156
+ method: call.method,
157
+ url,
158
+ body: encoded.text,
159
+ headers: encoded.headers,
160
+ });
161
+ const response = yield* execute(encoded.apply(HttpClientRequest.make(call.method)(url).pipe(HttpClientRequest.setHeaders(headers))));
162
+ return { response, auth: Object.fromEntries(Object.entries(headers).filter(([key]) => !(key in call.headers))) };
163
+ });
164
+ return {
165
+ provider,
166
+ /** Route and model overlays; `start` additionally merges the request's own `http`. */
167
+ http: (model) => mergeHttpOptions(routeHttp, model.http),
168
+ /** POST the protocol body to the route endpoint. */
169
+ submit: Effect.fn("MediaRoute.submit")(function* (request, protocol, execute) {
170
+ yield* rejectUnsupported(input.id, provider, request, protocol.unsupported);
171
+ const http = mergeHttpOptions(routeHttp, request.model.http, request.http);
172
+ const headers = Headers.fromInput(http?.headers);
173
+ const prepared = protocol.prepare === undefined
174
+ ? request
175
+ : yield* protocol.prepare(request, (path, body) => send({ method: "POST", url: baseURL(path), headers, request, body }, execute).pipe(Effect.map((sent) => sent.response)));
176
+ // Sanitize after merging so model-level overlays are covered; the model value is restored, not sanitized.
177
+ const resolved = { ...sanitizeSurrogates({ ...prepared, http }), model: request.model };
178
+ const body = yield* protocol.from(resolved);
179
+ const url = withQuery(withQuery(Endpoint.render(input.endpoint, { request: resolved, body }), body.type === "multipart" ? undefined : body.query), http?.query);
180
+ const sent = yield* send({ method: "POST", url, headers, request: resolved, body }, execute);
181
+ return { response: sent.response, context: { request: resolved, body } };
182
+ }),
183
+ /** Bodiless follow-up call (status, result, cancel) with the same auth and headers as `submit`. */
184
+ call: (method, path, http, execute) => {
185
+ // Provider-issued absolute URLs (fal `status_url`) are used as-is; everything else resolves against the base.
186
+ const url = withQuery(/^https?:\/\//.test(path) ? new URL(path) : baseURL(path), http?.query);
187
+ for (const [key, value] of Object.entries(input.endpoint.query ?? {}))
188
+ url.searchParams.set(key, value);
189
+ return send({ method, url, headers: Headers.fromInput(http?.headers), request: { http } }, execute);
190
+ },
191
+ };
192
+ };
193
+ const withQuery = (url, query) => {
194
+ for (const [key, value] of Object.entries(query ?? {})) {
195
+ url.searchParams.delete(key);
196
+ for (const item of typeof value === "string" ? [value] : value)
197
+ url.searchParams.append(key, item);
198
+ }
199
+ return url;
200
+ };
201
+ const encode = (body, headers) => {
202
+ if (body === undefined)
203
+ return { text: "", headers, apply: (request) => request };
204
+ if (body.type === "json") {
205
+ const text = ProviderShared.encodeJson(body.value);
206
+ return { text, headers, apply: HttpClientRequest.bodyText(text, "application/json") };
207
+ }
208
+ if (body.type === "binary")
209
+ return {
210
+ text: `[${body.contentType}]`,
211
+ headers,
212
+ apply: HttpClientRequest.bodyUint8Array(body.value, body.contentType),
213
+ };
214
+ return {
215
+ text: "[multipart/form-data]",
216
+ // The HTTP client sets the multipart boundary; a caller-supplied content-type would corrupt it.
217
+ headers: Headers.remove(headers, "content-type"),
218
+ apply: HttpClientRequest.bodyFormData(body.value),
219
+ };
220
+ };
221
+ /** Common fields are never silently dropped: a present field the protocol declared unsupported fails typed. */
222
+ const rejectUnsupported = (route, provider, request, unsupported) => {
223
+ const present = (unsupported ?? []).filter((field) => {
224
+ const value = request[field];
225
+ return Array.isArray(value) ? value.length > 0 : value !== undefined;
226
+ });
227
+ if (present.length === 0)
228
+ return Effect.void;
229
+ return Effect.fail(ProviderShared.unsupportedOperation({
230
+ operation: `media.${present[0]}`,
231
+ provider,
232
+ route,
233
+ message: `${provider}/${route} does not support ${present.join(", ")}`,
234
+ }));
235
+ };
236
+ export * as MediaRoute from "./media.js";
@@ -147,13 +147,23 @@ declare const UnknownProviderError_base: Schema.Class<UnknownProviderError, Sche
147
147
  }>, import("effect/Cause").YieldableError>;
148
148
  export declare class UnknownProviderError extends UnknownProviderError_base {
149
149
  }
150
- export declare const AIErrorReason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestError, typeof UnsupportedOperationError, typeof NoRouteError, typeof AuthenticationError, typeof RateLimitError, typeof QuotaExceededError, typeof ContentPolicyError, typeof ProviderInternalError, typeof TransportError, typeof InvalidProviderOutputError, typeof UnknownProviderError]>;
150
+ declare const TimeoutError_base: Schema.Class<TimeoutError, Schema.TaggedStruct<"Timeout", {
151
+ readonly timeoutMs: Schema.optional<Schema.Number>;
152
+ readonly message: Schema.String;
153
+ readonly body: Schema.optional<Schema.String>;
154
+ readonly http: Schema.optional<typeof HttpContext>;
155
+ readonly cause: Schema.optional<Schema.Defect>;
156
+ }>, import("effect/Cause").YieldableError>;
157
+ /** A caller-supplied deadline elapsed, such as `Generation.await` polling past its `Poll.timeout`. */
158
+ export declare class TimeoutError extends TimeoutError_base {
159
+ }
160
+ export declare const AIErrorReason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestError, typeof UnsupportedOperationError, typeof NoRouteError, typeof AuthenticationError, typeof RateLimitError, typeof QuotaExceededError, typeof ContentPolicyError, typeof ProviderInternalError, typeof TransportError, typeof InvalidProviderOutputError, typeof UnknownProviderError, typeof TimeoutError]>;
151
161
  export type AIErrorReason = Schema.Schema.Type<typeof AIErrorReason>;
152
162
  declare const AIError_base: Schema.Class<AIError, Schema.TaggedStruct<"AI.Error", {
153
- readonly reason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestError, typeof UnsupportedOperationError, typeof NoRouteError, typeof AuthenticationError, typeof RateLimitError, typeof QuotaExceededError, typeof ContentPolicyError, typeof ProviderInternalError, typeof TransportError, typeof InvalidProviderOutputError, typeof UnknownProviderError]>;
163
+ readonly reason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestError, typeof UnsupportedOperationError, typeof NoRouteError, typeof AuthenticationError, typeof RateLimitError, typeof QuotaExceededError, typeof ContentPolicyError, typeof ProviderInternalError, typeof TransportError, typeof InvalidProviderOutputError, typeof UnknownProviderError, typeof TimeoutError]>;
154
164
  }>, import("effect/Cause").YieldableError>;
155
165
  export declare class AIError extends AIError_base {
156
- readonly cause: InvalidRequestError | UnsupportedOperationError | NoRouteError | AuthenticationError | RateLimitError | QuotaExceededError | ContentPolicyError | ProviderInternalError | TransportError | InvalidProviderOutputError | UnknownProviderError;
166
+ readonly cause: InvalidRequestError | UnsupportedOperationError | NoRouteError | AuthenticationError | RateLimitError | QuotaExceededError | ContentPolicyError | ProviderInternalError | TransportError | InvalidProviderOutputError | UnknownProviderError | TimeoutError;
157
167
  get message(): string;
158
168
  }
159
169
  /**
@@ -97,6 +97,12 @@ export class InvalidProviderOutputError extends Schema.TaggedError("AI.Error.Inv
97
97
  }
98
98
  export class UnknownProviderError extends Schema.TaggedError("AI.Error.UnknownProvider")("UnknownProvider", ReasonFields) {
99
99
  }
100
+ /** A caller-supplied deadline elapsed, such as `Generation.await` polling past its `Poll.timeout`. */
101
+ export class TimeoutError extends Schema.TaggedError("AI.Error.Timeout")("Timeout", {
102
+ ...ReasonFields,
103
+ timeoutMs: Schema.optional(Schema.Number),
104
+ }) {
105
+ }
100
106
  export const AIErrorReason = Schema.Union([
101
107
  InvalidRequestError,
102
108
  UnsupportedOperationError,
@@ -109,6 +115,7 @@ export const AIErrorReason = Schema.Union([
109
115
  TransportError,
110
116
  InvalidProviderOutputError,
111
117
  UnknownProviderError,
118
+ TimeoutError,
112
119
  ]).pipe(Schema.toTaggedUnion("_tag"));
113
120
  export class AIError extends Schema.TaggedError()("AI.Error", {
114
121
  reason: AIErrorReason,