@opencode/ai 2.0.14 → 2.0.15

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 (86) hide show
  1. package/README.md +115 -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 +69 -0
  9. package/dist/generation.js +84 -0
  10. package/dist/image-client.d.ts +6 -4
  11. package/dist/image-client.js +11 -11
  12. package/dist/image.d.ts +1040 -62
  13. package/dist/image.js +80 -52
  14. package/dist/index.d.ts +5 -2
  15. package/dist/index.js +3 -1
  16. package/dist/llm.d.ts +9 -1
  17. package/dist/media.d.ts +212 -0
  18. package/dist/media.js +228 -0
  19. package/dist/promise.d.ts +554 -0
  20. package/dist/promise.js +44 -0
  21. package/dist/protocols/anthropic-messages.js +7 -17
  22. package/dist/protocols/bedrock-converse.d.ts +4 -4
  23. package/dist/protocols/bedrock-converse.js +1 -6
  24. package/dist/protocols/gemini.d.ts +21 -0
  25. package/dist/protocols/gemini.js +42 -6
  26. package/dist/protocols/google-images.d.ts +9 -21
  27. package/dist/protocols/google-images.js +169 -132
  28. package/dist/protocols/meta-images.d.ts +7 -12
  29. package/dist/protocols/meta-images.js +92 -66
  30. package/dist/protocols/mistral-chat.js +7 -6
  31. package/dist/protocols/open-responses.d.ts +11 -3
  32. package/dist/protocols/open-responses.js +23 -12
  33. package/dist/protocols/openai-chat.d.ts +34 -1
  34. package/dist/protocols/openai-chat.js +100 -31
  35. package/dist/protocols/openai-images.d.ts +9 -20
  36. package/dist/protocols/openai-images.js +112 -142
  37. package/dist/protocols/shared.d.ts +17 -17
  38. package/dist/protocols/shared.js +32 -35
  39. package/dist/protocols/utils/bedrock-media.d.ts +2 -3
  40. package/dist/protocols/utils/bedrock-media.js +4 -4
  41. package/dist/protocols/utils/media-input.d.ts +10 -0
  42. package/dist/protocols/utils/media-input.js +17 -0
  43. package/dist/protocols/utils/responses-compaction.js +6 -5
  44. package/dist/protocols/utils/tool-stream.d.ts +27 -3
  45. package/dist/protocols/xai-images.d.ts +9 -15
  46. package/dist/protocols/xai-images.js +85 -83
  47. package/dist/protocols/zai-images.d.ts +9 -13
  48. package/dist/protocols/zai-images.js +59 -57
  49. package/dist/providers/amazon-bedrock.d.ts +2 -2
  50. package/dist/providers/cerebras.js +6 -1
  51. package/dist/providers/deepinfra.js +6 -1
  52. package/dist/providers/google-vertex.d.ts +7 -0
  53. package/dist/providers/google.d.ts +7 -0
  54. package/dist/providers/index.d.ts +1 -0
  55. package/dist/providers/index.js +1 -0
  56. package/dist/providers/openrouter.d.ts +19 -0
  57. package/dist/providers/openrouter.js +13 -1
  58. package/dist/providers/vercel-ai-gateway.d.ts +41 -0
  59. package/dist/providers/vercel-ai-gateway.js +85 -0
  60. package/dist/route/client.d.ts +9 -1
  61. package/dist/route/endpoint.d.ts +10 -10
  62. package/dist/route/executor-service.d.ts +12 -0
  63. package/dist/route/executor-service.js +3 -0
  64. package/dist/route/executor.d.ts +4 -9
  65. package/dist/route/executor.js +3 -3
  66. package/dist/route/index.d.ts +2 -0
  67. package/dist/route/index.js +2 -0
  68. package/dist/route/media-protocol.d.ts +45 -0
  69. package/dist/route/media-protocol.js +40 -0
  70. package/dist/route/media.d.ts +46 -0
  71. package/dist/route/media.js +64 -0
  72. package/dist/schema/errors.d.ts +13 -3
  73. package/dist/schema/errors.js +7 -0
  74. package/dist/schema/events.d.ts +563 -40
  75. package/dist/schema/events.js +35 -2
  76. package/dist/schema/messages.d.ts +98 -8
  77. package/dist/schema/messages.js +8 -6
  78. package/dist/schema/options.d.ts +2 -0
  79. package/dist/schema/options.js +3 -0
  80. package/dist/testing.d.ts +72 -8
  81. package/dist/utils/media-type.d.ts +6 -0
  82. package/dist/utils/media-type.js +49 -0
  83. package/dist/utils/sanitize.js +3 -1
  84. package/package.json +7 -3
  85. package/dist/protocols/utils/image-input.d.ts +0 -21
  86. package/dist/protocols/utils/image-input.js +0 -20
@@ -3,7 +3,8 @@ import { Route } from "../route/client.js";
3
3
  import { Endpoint } from "../route/endpoint.js";
4
4
  import { Protocol } from "../route/protocol.js";
5
5
  import { AuthOptions } from "../route/auth-options.js";
6
- import { ProviderID } from "../schema/index.js";
6
+ import { HttpOptions, ProviderID } from "../schema/index.js";
7
+ import { SystemOne } from "../experimental/system-one.js";
7
8
  import { OpenAIChat } from "../protocols/openai-chat.js";
8
9
  import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js";
9
10
  import { isRecord } from "../protocols/shared.js";
@@ -98,13 +99,24 @@ const configuredRoute = (input) => {
98
99
  };
99
100
  export const configure = (input = {}) => {
100
101
  const route = configuredRoute(input);
102
+ const evaluation = (modelID) => SystemOne.model({
103
+ id: modelID,
104
+ provider: id,
105
+ providerMetadataKey: "openrouter",
106
+ auth: AuthOptions.bearer(input, "OPENROUTER_API_KEY"),
107
+ baseURL: input.baseURL ?? baseURL,
108
+ headers: input.headers,
109
+ http: input.http === undefined ? undefined : HttpOptions.make(input.http),
110
+ });
101
111
  return {
102
112
  id,
103
113
  model: (modelID) => route.model({ id: modelID, compatibility: { supportsPromptCacheKey: true } }),
114
+ experimental: { evaluation },
104
115
  configure,
105
116
  };
106
117
  };
107
118
  export const provider = configure();
119
+ export const experimental = provider.experimental;
108
120
  export const model = (modelID, { apiKey, baseURL, body, headers, ...providerOptions }) => configure({
109
121
  apiKey,
110
122
  baseURL,
@@ -0,0 +1,41 @@
1
+ import { EvaluationModel } from "../experimental/evaluation.js";
2
+ import { type ProviderAuthOption } from "../route/auth-options.js";
3
+ import { HttpOptions, ModelID } from "../schema/index.js";
4
+ export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
5
+ export interface EvaluationOptions {
6
+ readonly [key: string]: unknown;
7
+ readonly gateway?: Readonly<{
8
+ readonly [key: string]: unknown;
9
+ readonly zeroDataRetention?: boolean;
10
+ readonly only?: ReadonlyArray<string>;
11
+ }>;
12
+ }
13
+ export type Options = ProviderAuthOption<"optional"> & {
14
+ readonly baseURL?: string;
15
+ readonly headers?: Record<string, string>;
16
+ readonly http?: HttpOptions.Input;
17
+ };
18
+ export declare const configure: (input?: Options) => {
19
+ id: string & import("effect/Brand").Brand<"AI.ProviderID">;
20
+ experimental: {
21
+ evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
22
+ };
23
+ configure: (input?: Options) => /*elided*/ any;
24
+ };
25
+ export declare const provider: {
26
+ id: string & import("effect/Brand").Brand<"AI.ProviderID">;
27
+ experimental: {
28
+ evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
29
+ };
30
+ configure: (input?: Options) => {
31
+ id: string & import("effect/Brand").Brand<"AI.ProviderID">;
32
+ experimental: {
33
+ evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
34
+ };
35
+ configure: /*elided*/ any;
36
+ };
37
+ };
38
+ export declare const experimental: {
39
+ evaluation: (modelID: string | ModelID) => EvaluationModel<EvaluationOptions>;
40
+ };
41
+ export * as VercelAIGateway from "./vercel-ai-gateway.js";
@@ -0,0 +1,85 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { EvaluationAnswer, EvaluationInput, EvaluationModel, EvaluationQuestion, EvaluationResponse, EvaluationRounding, } from "../experimental/evaluation.js";
4
+ import { Auth } from "../route/auth.js";
5
+ import { AuthOptions } from "../route/auth-options.js";
6
+ import { AIError, HttpContext, HttpOptions, InvalidProviderOutputError, InvalidRequestError, ModelID, ProviderID, ProviderMetadata, Usage, } from "../schema/index.js";
7
+ export const id = ProviderID.make("vercel-ai-gateway");
8
+ const baseURL = "https://ai-gateway.vercel.sh/v1";
9
+ const Request = Schema.StructWithRest(Schema.Struct({
10
+ model: Schema.String,
11
+ state: EvaluationInput,
12
+ questions: Schema.Record(Schema.String, EvaluationQuestion),
13
+ providerOptions: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
14
+ }), [Schema.Record(Schema.String, Schema.Any)]);
15
+ const Response = Schema.Struct({
16
+ model: Schema.optional(Schema.String),
17
+ answers: Schema.Record(Schema.String, EvaluationAnswer),
18
+ usage: Schema.optional(Schema.Struct({
19
+ inputTokens: Schema.optional(Schema.Number),
20
+ outputTokens: Schema.optional(Schema.Number),
21
+ })),
22
+ rounding: Schema.optional(EvaluationRounding),
23
+ providerMetadata: Schema.optional(ProviderMetadata),
24
+ });
25
+ export const configure = (input = {}) => {
26
+ const evaluation = (modelID) => EvaluationModel.make({
27
+ id: modelID,
28
+ provider: id,
29
+ http: input.http === undefined ? undefined : HttpOptions.make(input.http),
30
+ route: {
31
+ id: "vercel-evaluation",
32
+ evaluate: (req, send) => Effect.gen(function* () {
33
+ const url = new URL(`${(input.baseURL ?? baseURL).replace(/\/$/, "")}/evaluate`);
34
+ Object.entries(req.http?.query ?? {}).forEach(([key, value]) => url.searchParams.set(key, value));
35
+ const body = yield* Schema.encodeUnknownEffect(Schema.fromJsonString(Request))({
36
+ ...req.http?.body,
37
+ model: req.model.id,
38
+ state: req.state,
39
+ questions: req.questions,
40
+ providerOptions: req.options,
41
+ }).pipe(Effect.mapError((cause) => new AIError({ reason: new InvalidRequestError({ message: cause.message, cause }) })));
42
+ const headers = yield* Auth.toEffect(AuthOptions.bearer(input, ["AI_GATEWAY_API_KEY", "VERCEL_OIDC_TOKEN"]))({
43
+ request: req,
44
+ method: "POST",
45
+ url: url.toString(),
46
+ body,
47
+ headers: Headers.fromInput({ ...input.headers, ...req.http?.headers }),
48
+ });
49
+ const res = yield* send(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(body, "application/json")));
50
+ const http = new HttpContext({ url: res.request.url, status: res.status, headers: res.headers });
51
+ const fail = (message, cause, body) => new AIError({
52
+ reason: new InvalidProviderOutputError({
53
+ route: "vercel-evaluation",
54
+ message,
55
+ body,
56
+ http,
57
+ cause,
58
+ }),
59
+ });
60
+ const text = yield* res.text.pipe(Effect.mapError((cause) => fail("Failed to read the Vercel AI Gateway evaluation response", cause)));
61
+ const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Response))(text).pipe(Effect.mapError((cause) => fail("Vercel AI Gateway returned an invalid evaluation response", cause, text)));
62
+ return new EvaluationResponse({
63
+ model: ModelID.make(data.model ?? req.model.id),
64
+ answers: data.answers,
65
+ usage: data.usage
66
+ ? new Usage({
67
+ inputTokens: data.usage.inputTokens,
68
+ outputTokens: data.usage.outputTokens,
69
+ totalTokens: data.usage.inputTokens === undefined && data.usage.outputTokens === undefined
70
+ ? undefined
71
+ : (data.usage.inputTokens ?? 0) + (data.usage.outputTokens ?? 0),
72
+ providerMetadata: { gateway: data.usage },
73
+ })
74
+ : undefined,
75
+ rounding: data.rounding,
76
+ providerMetadata: data.providerMetadata,
77
+ });
78
+ }),
79
+ },
80
+ });
81
+ return { id, experimental: { evaluation }, configure };
82
+ };
83
+ export const provider = configure();
84
+ export const experimental = provider.experimental;
85
+ export * as VercelAIGateway from "./vercel-ai-gateway.js";
@@ -381,6 +381,14 @@ export declare const streamRequest: (request: LLMRequest, options?: StreamOption
381
381
  };
382
382
  } | undefined;
383
383
  readonly namespace?: string | undefined;
384
+ } | {
385
+ readonly type: "media";
386
+ readonly media: import("../media.js").Asset;
387
+ readonly providerMetadata?: {
388
+ readonly [x: string]: {
389
+ readonly [x: string]: unknown;
390
+ };
391
+ } | undefined;
384
392
  } | {
385
393
  readonly type: "step-finish";
386
394
  readonly reason: {
@@ -409,12 +417,12 @@ export declare const streamRequest: (request: LLMRequest, options?: StreamOption
409
417
  } | {
410
418
  readonly type: "provider-error";
411
419
  readonly message: string;
420
+ readonly classification?: "context-overflow" | "payload-too-large" | undefined;
412
421
  readonly providerMetadata?: {
413
422
  readonly [x: string]: {
414
423
  readonly [x: string]: unknown;
415
424
  };
416
425
  } | undefined;
417
- readonly classification?: "context-overflow" | "payload-too-large" | undefined;
418
426
  }, AIError, Service>;
419
427
  export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
420
428
  export declare const Route: {
@@ -1,9 +1,9 @@
1
1
  import type { LLMRequest } from "../schema/index.js";
2
- export interface EndpointInput<Body> {
3
- readonly request: LLMRequest;
2
+ export interface EndpointInput<Body, Request = LLMRequest> {
3
+ readonly request: Request;
4
4
  readonly body: Body;
5
5
  }
6
- export type EndpointPart<Body> = string | ((input: EndpointInput<Body>) => string);
6
+ export type EndpointPart<Body, Request = LLMRequest> = string | ((input: EndpointInput<Body, Request>) => string);
7
7
  /**
8
8
  * Declarative URL construction for one route.
9
9
  *
@@ -13,16 +13,16 @@ export type EndpointPart<Body> = string | ((input: EndpointInput<Body>) => strin
13
13
  *
14
14
  * `path` may be a string or a function of `EndpointInput`, for routes whose
15
15
  * URL embeds the model id, region, or another body field (e.g. Bedrock,
16
- * Gemini).
16
+ * Gemini). Media routes reuse the same shape with their own request type.
17
17
  */
18
- export interface Definition<Body> {
18
+ export interface Definition<Body, Request = LLMRequest> {
19
19
  readonly baseURL?: string;
20
- readonly path: EndpointPart<Body>;
20
+ readonly path: EndpointPart<Body, Request>;
21
21
  readonly query?: Record<string, string>;
22
22
  }
23
- export type EndpointPatch<Body> = Partial<Definition<Body>>;
23
+ export type EndpointPatch<Body, Request = LLMRequest> = Partial<Definition<Body, Request>>;
24
24
  /** Construct an `Endpoint` from a path string or path function. */
25
- export declare const path: <Body>(value: EndpointPart<Body>, options?: Omit<Definition<Body>, "path">) => Definition<Body>;
26
- export declare const merge: <Body>(base: Definition<Body>, patch: EndpointPatch<Body>) => Definition<Body>;
27
- export declare const render: <Body>(endpoint: Definition<Body>, input: EndpointInput<Body>) => URL;
25
+ export declare const path: <Body, Request = LLMRequest>(value: EndpointPart<Body, Request>, options?: Omit<Definition<Body, Request>, "path">) => Definition<Body, Request>;
26
+ export declare const merge: <Body, Request = LLMRequest>(base: Definition<Body, Request>, patch: EndpointPatch<Body, Request>) => Definition<Body, Request>;
27
+ export declare const render: <Body, Request = LLMRequest>(endpoint: Definition<Body, Request>, input: EndpointInput<Body, Request>) => URL;
28
28
  export * as Endpoint from "./endpoint.js";
@@ -0,0 +1,12 @@
1
+ import { Context, type Effect } from "effect";
2
+ import type { HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
3
+ import type { AIError } from "../schema/errors.js";
4
+ export interface Interface {
5
+ readonly execute: (request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Effect.Effect<HttpClientResponse.HttpClientResponse, AIError>;
6
+ }
7
+ export type HttpHandler = (request: HttpClientRequest.HttpClientRequest) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
8
+ export type HttpMiddleware = (request: HttpClientRequest.HttpClientRequest, handler: HttpHandler) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
9
+ declare const Service_base: Context.ServiceClass<Service, "@opencode/AI/RequestExecutor", Interface>;
10
+ export declare class Service extends Service_base {
11
+ }
12
+ export {};
@@ -0,0 +1,3 @@
1
+ import { Context } from "effect";
2
+ export class Service extends Context.Service()("@opencode/AI/RequestExecutor") {
3
+ }
@@ -1,14 +1,9 @@
1
- import { Context, Effect, Layer, Stream } from "effect";
1
+ import { Layer, Stream } from "effect";
2
2
  import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
3
3
  import { HttpContext, AIError } from "../schema/index.js";
4
- export interface Interface {
5
- readonly execute: (request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Effect.Effect<HttpClientResponse.HttpClientResponse, AIError>;
6
- }
7
- export type HttpHandler = (request: HttpClientRequest.HttpClientRequest) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
8
- export type HttpMiddleware = (request: HttpClientRequest.HttpClientRequest, handler: HttpHandler) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
9
- declare const Service_base: Context.ServiceClass<Service, "@opencode/AI/RequestExecutor", Interface>;
10
- export declare class Service extends Service_base {
11
- }
4
+ import { Service, type HttpMiddleware, type Interface } from "./executor-service.js";
5
+ export { Service } from "./executor-service.js";
6
+ export type { HttpHandler, HttpMiddleware, Interface } from "./executor-service.js";
12
7
  export declare const responseHttp: (response: HttpClientResponse.HttpClientResponse) => HttpContext;
13
8
  /** Preserve HTTP diagnostics for executor and externally captured failures alike. */
14
9
  export declare const httpFailure: (input: {
@@ -1,9 +1,9 @@
1
- import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect";
1
+ import { Cause, Effect, Layer, Option, Schema, Stream } from "effect";
2
2
  import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
3
3
  import { HttpContext, HttpRateLimitDetails, AIError, TransportError } from "../schema/index.js";
4
4
  import { classifyProviderFailure } from "../provider-error.js";
5
- export class Service extends Context.Service()("@opencode/AI/RequestExecutor") {
6
- }
5
+ import { Service } from "./executor-service.js";
6
+ export { Service } from "./executor-service.js";
7
7
  const headerDetails = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, String(value)]));
8
8
  const normalizedHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
9
9
  const retryAfterMs = (headers) => {
@@ -4,6 +4,8 @@ export * from "./executor.js";
4
4
  export { Auth } from "./auth.js";
5
5
  export { AuthOptions } from "./auth-options.js";
6
6
  export { Endpoint } from "./endpoint.js";
7
+ export { MediaRoute } from "./media.js";
8
+ export { MediaProtocol } from "./media-protocol.js";
7
9
  export { Framing } from "./framing.js";
8
10
  export { Protocol } from "./protocol.js";
9
11
  export { HttpTransport, WebSocketTransport } from "./transport/index.js";
@@ -3,6 +3,8 @@ export * from "./executor.js";
3
3
  export { Auth } from "./auth.js";
4
4
  export { AuthOptions } from "./auth-options.js";
5
5
  export { Endpoint } from "./endpoint.js";
6
+ export { MediaRoute } from "./media.js";
7
+ export { MediaProtocol } from "./media-protocol.js";
6
8
  export { Framing } from "./framing.js";
7
9
  export { Protocol } from "./protocol.js";
8
10
  export { HttpTransport, WebSocketTransport } from "./transport/index.js";
@@ -0,0 +1,45 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { HttpClientResponse } from "effect/unstable/http";
3
+ import { AIError, HttpContext } from "../schema/index.js";
4
+ export type Body = {
5
+ readonly type: "json";
6
+ readonly value: Record<string, unknown>;
7
+ } | {
8
+ readonly type: "multipart";
9
+ readonly value: FormData;
10
+ };
11
+ export declare const json: (value: Record<string, unknown>) => Body;
12
+ export declare const multipart: (value: FormData) => Body;
13
+ export interface DecodeContext<Request> {
14
+ readonly request: Request;
15
+ readonly body: Body;
16
+ }
17
+ /** One request, one response. JSON or multipart in; JSON or raw bytes out. */
18
+ export interface Inline<Request, Response> {
19
+ readonly kind: "inline";
20
+ readonly id: string;
21
+ readonly name: string;
22
+ /** Common request fields this protocol cannot lower; the route rejects them before `body.from` runs. */
23
+ readonly unsupported?: ReadonlyArray<keyof Request & string>;
24
+ readonly body: {
25
+ readonly from: (request: Request) => Effect.Effect<Body, AIError>;
26
+ };
27
+ readonly response: {
28
+ readonly decode: (response: HttpClientResponse.HttpClientResponse, context: DecodeContext<Request>) => Effect.Effect<Response, AIError>;
29
+ };
30
+ }
31
+ 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. */
33
+ export declare const text: (route: string, name: string, response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
34
+ body: string;
35
+ http: HttpContext;
36
+ invalid: (message: string, cause?: unknown) => AIError;
37
+ }, AIError, never>;
38
+ /** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
39
+ export declare const decodeJson: <A>(route: string, name: string, schema: Schema.Codec<A, unknown>) => (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<{
40
+ value: A;
41
+ body: string;
42
+ http: HttpContext;
43
+ invalid: (message: string, cause?: unknown) => AIError;
44
+ }, AIError, never>;
45
+ export * as MediaProtocol from "./media-protocol.js";
@@ -0,0 +1,40 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { HttpClientResponse } from "effect/unstable/http";
3
+ import { AIError, HttpContext, InvalidProviderOutputError } from "../schema/index.js";
4
+ export const json = (value) => ({ type: "json", value });
5
+ export const multipart = (value) => ({ type: "multipart", value });
6
+ export const inline = (input) => ({
7
+ kind: "inline",
8
+ ...input,
9
+ });
10
+ // ---------------------------------------------------------------------------
11
+ // Response helpers
12
+ // ---------------------------------------------------------------------------
13
+ 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. */
15
+ export const text = Effect.fn("MediaProtocol.text")(function* (route, name, response) {
16
+ const http = context(response);
17
+ const body = yield* response.text.pipe(Effect.mapError((cause) => new AIError({
18
+ reason: new InvalidProviderOutputError({
19
+ route,
20
+ message: `Failed to read the ${name} response`,
21
+ http,
22
+ cause,
23
+ }),
24
+ })));
25
+ return {
26
+ body,
27
+ http,
28
+ invalid: (message, cause) => new AIError({ reason: new InvalidProviderOutputError({ route, message, body, http, cause }) }),
29
+ };
30
+ });
31
+ /** Read and Schema-decode a JSON body. Decode failures keep the raw body as `reason.body`. */
32
+ export const decodeJson = (route, name, schema) => {
33
+ const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(schema));
34
+ return Effect.fn("MediaProtocol.decodeJson")(function* (response) {
35
+ const output = yield* text(route, name, response);
36
+ const value = yield* decode(output.body).pipe(Effect.mapError((cause) => output.invalid(`${name} returned an invalid response`, cause)));
37
+ return { ...output, value };
38
+ });
39
+ };
40
+ export * as MediaProtocol from "./media-protocol.js";
@@ -0,0 +1,46 @@
1
+ import { Effect } 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 { AIError, HttpOptions, ProviderID } from "../schema/index.js";
7
+ export type Execute = Interface["execute"];
8
+ /** The minimum a media request must carry for the route to build a transport request. */
9
+ export interface MediaRequest {
10
+ readonly model: {
11
+ readonly id: string;
12
+ readonly provider: ProviderID;
13
+ readonly http?: HttpOptions;
14
+ };
15
+ readonly http?: HttpOptions;
16
+ }
17
+ /** Deployment inputs every media model factory accepts; provider facades fill these from `configure(...)`. */
18
+ export interface ModelInput {
19
+ readonly id: string;
20
+ readonly auth: Auth.Definition;
21
+ readonly baseURL?: string;
22
+ readonly headers?: Record<string, string>;
23
+ readonly http?: HttpOptions;
24
+ }
25
+ export interface Route<Request extends MediaRequest, Response> {
26
+ readonly id: string;
27
+ readonly provider: ProviderID;
28
+ readonly protocol: string;
29
+ readonly generate: (request: Request, execute: Execute) => Effect.Effect<Response, AIError>;
30
+ }
31
+ export interface MakeInput<Request extends MediaRequest, Response> {
32
+ readonly id: string;
33
+ readonly provider: string | ProviderID;
34
+ readonly protocol: MediaProtocol.Inline<Request, Response>;
35
+ readonly endpoint: Endpoint.Definition<MediaProtocol.Body, Request>;
36
+ readonly auth: Auth.Definition;
37
+ /** Deployment headers applied before transport authentication. */
38
+ readonly headers?: Record<string, string>;
39
+ }
40
+ /**
41
+ * 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.
44
+ */
45
+ export declare const make: <Request extends MediaRequest, Response>(input: MakeInput<Request, Response>) => Route<Request, Response>;
46
+ export * as MediaRoute from "./media.js";
@@ -0,0 +1,64 @@
1
+ import { Effect } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { Auth } from "./auth.js";
4
+ import { Endpoint } from "./endpoint.js";
5
+ import { MediaProtocol } from "./media-protocol.js";
6
+ import { ProviderShared } from "../protocols/shared.js";
7
+ import { AIError, HttpOptions, ProviderID, mergeHttpOptions } from "../schema/index.js";
8
+ import { sanitizeSurrogates } from "../utils/sanitize.js";
9
+ /**
10
+ * 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 rejection, URL and query
12
+ * rendering, auth headers, JSON vs multipart encoding, and handing the response back to the protocol for decoding.
13
+ */
14
+ export const make = (input) => {
15
+ const provider = ProviderID.make(input.provider);
16
+ const routeHttp = input.headers === undefined ? undefined : new HttpOptions({ headers: input.headers });
17
+ const authorize = Auth.toEffect(input.auth);
18
+ return {
19
+ id: input.id,
20
+ provider,
21
+ protocol: input.protocol.id,
22
+ generate: Effect.fn(`MediaRoute.generate`)(function* (request, execute) {
23
+ yield* rejectUnsupported(input.id, provider, request, input.protocol.unsupported);
24
+ const http = mergeHttpOptions(routeHttp, request.model.http, request.http);
25
+ // Sanitize after merging so model-level overlays are covered; the model value is restored, not sanitized.
26
+ const resolved = { ...sanitizeSurrogates({ ...request, http }), model: request.model };
27
+ const body = yield* input.protocol.body.from(resolved);
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);
31
+ const encoded = body.type === "json" ? ProviderShared.encodeJson(body.value) : "[multipart/form-data]";
32
+ const baseHeaders = Headers.fromInput(http?.headers);
33
+ const headers = yield* authorize({
34
+ request: resolved,
35
+ method: "POST",
36
+ url: url.toString(),
37
+ body: encoded,
38
+ // The HTTP client sets the multipart boundary; a caller-supplied content-type would corrupt it.
39
+ headers: body.type === "multipart" ? Headers.remove(baseHeaders, "content-type") : baseHeaders,
40
+ });
41
+ const transport = HttpClientRequest.post(url.toString()).pipe(HttpClientRequest.setHeaders(headers), body.type === "json"
42
+ ? HttpClientRequest.bodyText(encoded, "application/json")
43
+ : HttpClientRequest.bodyFormData(body.value));
44
+ const response = yield* execute(transport);
45
+ return yield* input.protocol.response.decode(response, { request: resolved, body });
46
+ }),
47
+ };
48
+ };
49
+ /** Common fields are never silently dropped: a present field the protocol declared unsupported fails typed. */
50
+ const rejectUnsupported = (route, provider, request, unsupported) => {
51
+ const present = (unsupported ?? []).filter((field) => {
52
+ const value = request[field];
53
+ return Array.isArray(value) ? value.length > 0 : value !== undefined;
54
+ });
55
+ if (present.length === 0)
56
+ return Effect.void;
57
+ return Effect.fail(ProviderShared.unsupportedOperation({
58
+ operation: `media.${present[0]}`,
59
+ provider,
60
+ route,
61
+ message: `${provider}/${route} does not support ${present.join(", ")}`,
62
+ }));
63
+ };
64
+ 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,