@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
@@ -1,14 +1,20 @@
1
- import { Effect, Encoding, Schema } from "effect";
2
- import { Headers, HttpClientRequest } from "effect/unstable/http";
3
- import { GeneratedImage, ImageModel, ImageResponse } from "../image.js";
4
- import { Auth } from "../route/auth.js";
5
- import { Usage, mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
1
+ import { Effect, Schema } from "effect";
2
+ import { ImageModel, ImageResponse } from "../image.js";
3
+ import { Media } from "../media.js";
4
+ import { MediaProtocol } from "../route/media-protocol.js";
5
+ import { MediaRoute } from "../route/media.js";
6
+ import { ProviderID, mergeJsonRecords } from "../schema/index.js";
6
7
  import { ProviderShared, optionalNull } from "./shared.js";
7
- import { ImageInputs } from "./utils/image-input.js";
8
+ import { MediaInput } from "./utils/media-input.js";
8
9
  const ADAPTER = "xai-images";
10
+ const NAME = "xAI Images";
11
+ const PROVIDER = ProviderID.make("xai");
9
12
  export const DEFAULT_BASE_URL = "https://api.x.ai/v1";
10
13
  export const PATH = "/images/generations";
11
14
  export const EDIT_PATH = "/images/edits";
15
+ // ---------------------------------------------------------------------------
16
+ // 2. Response schema
17
+ // ---------------------------------------------------------------------------
12
18
  const XAIImageResponse = Schema.Struct({
13
19
  data: Schema.Array(Schema.Struct({
14
20
  b64_json: optionalNull(Schema.String),
@@ -18,89 +24,74 @@ const XAIImageResponse = Schema.Struct({
18
24
  })),
19
25
  usage: Schema.optional(Schema.Unknown),
20
26
  });
27
+ // ---------------------------------------------------------------------------
28
+ // 5. Request body construction
29
+ // ---------------------------------------------------------------------------
21
30
  const nativeOptions = (options) => {
22
31
  if (!options)
23
32
  return undefined;
24
- const { aspectRatio, responseFormat, ...native } = options;
25
- return {
26
- aspect_ratio: aspectRatio,
27
- response_format: responseFormat,
28
- ...native,
29
- };
33
+ const { responseFormat, ...native } = options;
34
+ return { response_format: responseFormat, ...native };
30
35
  };
31
- const applyQuery = (url, query) => {
32
- if (!query)
33
- return url;
34
- const next = new URL(url);
35
- Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
36
- return next.toString();
37
- };
38
- export const model = (input) => {
39
- const route = {
40
- id: ADAPTER,
41
- generate: Effect.fn("XAIImages.generate")(function* (request, execute) {
42
- const http = mergeHttpOptions(request.model.http, request.http);
43
- const imageReferences = (request.images ?? []).map((image) => {
44
- if (image.type === "bytes")
45
- return { url: ImageInputs.dataUrl(image), type: "image_url" };
46
- if (image.type === "url")
47
- return { url: image.url, type: "image_url" };
48
- if (image.type === "file-id")
49
- return { file_id: image.id };
50
- return undefined;
51
- });
52
- if (imageReferences.some((image) => image === undefined))
53
- return yield* ImageInputs.invalid("xAI Images accepts image URLs, data URLs, bytes, and file IDs");
54
- const requestBody = mergeJsonRecords({
55
- model: request.model.id,
56
- prompt: request.prompt,
57
- image: imageReferences.length === 1 ? imageReferences[0] : undefined,
58
- images: imageReferences.length > 1 ? imageReferences : undefined,
59
- }, nativeOptions(request.options), http?.body);
60
- const text = ProviderShared.encodeJson(requestBody);
61
- const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${imageReferences.length === 0 ? PATH : EDIT_PATH}`, http?.query);
62
- const headers = yield* Auth.toEffect(input.auth)({
63
- request,
64
- method: "POST",
65
- url,
66
- body: text,
67
- headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
68
- });
69
- const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
70
- const output = yield* ProviderShared.imageResponse(ADAPTER, "xAI Images", response);
71
- const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(XAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("xAI Images returned an invalid response", cause)));
72
- const images = yield* Effect.forEach(decoded.data, (item, index) => {
73
- const mediaType = item.mime_type ?? "application/octet-stream";
74
- if (item.b64_json)
75
- return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError((cause) => output.invalid(`xAI Images result ${index} contains invalid base64 data`, cause)), Effect.map((data) => new GeneratedImage({
76
- mediaType,
77
- data,
78
- providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
79
- ? undefined
80
- : { xai: { revisedPrompt: item.revised_prompt } },
81
- })));
82
- if (item.url)
83
- return Effect.succeed(new GeneratedImage({
84
- mediaType,
85
- data: item.url,
86
- providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
87
- ? undefined
88
- : { xai: { revisedPrompt: item.revised_prompt } },
89
- }));
90
- return Effect.fail(output.invalid(`xAI Images result ${index} has neither image data nor a URL`));
91
- });
92
- if (images.length === 0)
93
- return yield* output.invalid("xAI Images returned no images");
94
- const usage = ProviderShared.isRecord(decoded.usage) ? decoded.usage : undefined;
95
- return new ImageResponse({
96
- images,
97
- usage: usage === undefined ? undefined : new Usage({ providerMetadata: { xai: usage } }),
98
- providerMetadata: usage === undefined ? undefined : { xai: { usage } },
36
+ const isEdit = (request) => (request.images?.length ?? 0) > 0;
37
+ const reference = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((item) => item.type === "ref" ? { file_id: item.value } : { url: item.value, type: "image_url" }));
38
+ const fromRequest = Effect.fn("XAIImages.fromRequest")(function* (request) {
39
+ const references = yield* Effect.forEach(request.images ?? [], reference);
40
+ return MediaProtocol.json(mergeJsonRecords({
41
+ model: request.model.id,
42
+ prompt: request.prompt,
43
+ // xAI takes one edit source as `image` and several as `images`.
44
+ image: references.length === 1 ? references[0] : undefined,
45
+ images: references.length > 1 ? references : undefined,
46
+ n: request.n,
47
+ aspect_ratio: request.aspectRatio,
48
+ }, nativeOptions(request.providerOptions), request.http?.body) ?? {});
49
+ });
50
+ // ---------------------------------------------------------------------------
51
+ // 6. Response decoding
52
+ // ---------------------------------------------------------------------------
53
+ const decodeResponse = Effect.fn("XAIImages.decodeResponse")(function* (response) {
54
+ const output = yield* MediaProtocol.decodeJson(ADAPTER, NAME, XAIImageResponse)(response);
55
+ const decoded = output.value;
56
+ const images = yield* Effect.forEach(decoded.data, (item, index) => {
57
+ const providerMetadata = item.revised_prompt === undefined || item.revised_prompt === null
58
+ ? undefined
59
+ : { xai: { revisedPrompt: item.revised_prompt } };
60
+ if (item.b64_json)
61
+ return MediaInput.decodedAsset(output.invalid, `${NAME} result ${index}`, item.b64_json, item.mime_type ?? undefined, {
62
+ providerMetadata,
99
63
  });
100
- }),
101
- };
102
- return ImageModel.make({ id: input.id, provider: "xai", route, http: input.http });
103
- };
64
+ if (item.url)
65
+ return Effect.succeed(Media.url(item.url, { mediaType: item.mime_type ?? undefined, providerMetadata }));
66
+ return Effect.fail(output.invalid(`${NAME} result ${index} has neither image data nor a URL`));
67
+ });
68
+ if (images.length === 0)
69
+ return yield* output.invalid(`${NAME} returned no images`);
70
+ const usage = ProviderShared.isRecord(decoded.usage) ? decoded.usage : undefined;
71
+ // xAI reports image counts rather than tokens, seconds, or credits; the raw record stays in provider metadata.
72
+ return new ImageResponse({
73
+ images,
74
+ providerMetadata: usage === undefined ? undefined : { xai: { usage } },
75
+ });
76
+ });
77
+ // ---------------------------------------------------------------------------
78
+ // 7. Protocol and route
79
+ // ---------------------------------------------------------------------------
80
+ export const protocol = MediaProtocol.inline({
81
+ id: ADAPTER,
82
+ name: NAME,
83
+ unsupported: ["mask", "size", "seed", "format"],
84
+ body: { from: fromRequest },
85
+ response: { decode: decodeResponse },
86
+ });
87
+ export const model = (input) => ImageModel.fromRoute({
88
+ id: ADAPTER,
89
+ provider: PROVIDER,
90
+ protocol,
91
+ baseURL: DEFAULT_BASE_URL,
92
+ path: ({ request }) => (isEdit(request) ? EDIT_PATH : PATH),
93
+ }, input);
104
94
  export const XAIImages = {
95
+ protocol,
105
96
  model,
106
97
  };
@@ -67,8 +67,8 @@ export declare const protocol: Protocol<{
67
67
  } | {
68
68
  readonly type: "input_file";
69
69
  readonly filename: string;
70
- readonly detail?: string | undefined;
71
70
  readonly file_data?: string | undefined;
71
+ readonly detail?: string | undefined;
72
72
  readonly file_url?: string | undefined;
73
73
  } | {
74
74
  readonly type: "input_text";
@@ -104,8 +104,8 @@ export declare const protocol: Protocol<{
104
104
  } | {
105
105
  readonly type: "input_file";
106
106
  readonly filename: string;
107
- readonly detail?: string | undefined;
108
107
  readonly file_data?: string | undefined;
108
+ readonly detail?: string | undefined;
109
109
  readonly file_url?: string | undefined;
110
110
  } | {
111
111
  readonly type: "input_text";
@@ -0,0 +1,34 @@
1
+ import { Schema } from "effect";
2
+ import { MediaProtocol } from "../route/media-protocol.js";
3
+ import { MediaRoute } from "../route/media.js";
4
+ import { VideoModel, VideoResponse, type VideoRequestFor } from "../video.js";
5
+ export declare const DEFAULT_BASE_URL = "https://api.x.ai/v1";
6
+ export declare const PATH = "/videos/generations";
7
+ export declare const EDIT_PATH = "/videos/edits";
8
+ export declare const EXTEND_PATH = "/videos/extensions";
9
+ export declare const STATUS_PATH = "/videos";
10
+ /**
11
+ * Provider-native options. Common fields (`frames`, `references`, `video`, `durationSeconds`, `aspectRatio`,
12
+ * `resolution`, `audio`) live on the request. `mode` selects the endpoint a `video` input is sent to.
13
+ */
14
+ export type XAIVideoOptions = {
15
+ readonly mode?: "edit" | "extend";
16
+ readonly reference_audios?: ReadonlyArray<{
17
+ readonly voice_id: string;
18
+ }>;
19
+ } & Record<string, unknown>;
20
+ export type Request = VideoRequestFor<XAIVideoOptions>;
21
+ export declare const Token: Schema.Struct<{
22
+ readonly requestID: Schema.String;
23
+ }>;
24
+ export type Token = Schema.Schema.Type<typeof Token>;
25
+ export declare const protocol: MediaProtocol.Queued<Request, VideoResponse, {
26
+ readonly requestID: string;
27
+ }>;
28
+ export declare const model: (input: MediaRoute.ModelInput) => VideoModel<XAIVideoOptions>;
29
+ export declare const XAIVideo: {
30
+ readonly protocol: MediaProtocol.Queued<Request, VideoResponse, {
31
+ readonly requestID: string;
32
+ }>;
33
+ readonly model: (input: MediaRoute.ModelInput) => VideoModel<XAIVideoOptions>;
34
+ };
@@ -0,0 +1,147 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { Media } from "../media.js";
3
+ import { MediaProtocol } from "../route/media-protocol.js";
4
+ import { MediaRoute } from "../route/media.js";
5
+ import { ProviderID, mergeJsonRecords } from "../schema/index.js";
6
+ import { VideoModel, VideoResponse } from "../video.js";
7
+ import { ProviderShared, optionalNull } from "./shared.js";
8
+ const ADAPTER = "xai-video";
9
+ const NAME = "xAI Video";
10
+ const PROVIDER = ProviderID.make("xai");
11
+ export const DEFAULT_BASE_URL = "https://api.x.ai/v1";
12
+ export const PATH = "/videos/generations";
13
+ export const EDIT_PATH = "/videos/edits";
14
+ export const EXTEND_PATH = "/videos/extensions";
15
+ export const STATUS_PATH = "/videos";
16
+ // ---------------------------------------------------------------------------
17
+ // 2. Token and response schemas
18
+ // ---------------------------------------------------------------------------
19
+ export const Token = Schema.Struct({ requestID: Schema.String });
20
+ const StartResponse = Schema.Struct({ request_id: Schema.String });
21
+ const VideoStatus = Schema.Struct({
22
+ status: Schema.String,
23
+ progress: optionalNull(Schema.Number),
24
+ video: optionalNull(Schema.Struct({
25
+ url: optionalNull(Schema.String),
26
+ duration: optionalNull(Schema.Number),
27
+ respect_moderation: optionalNull(Schema.Boolean),
28
+ })),
29
+ error: optionalNull(Schema.Struct({
30
+ code: optionalNull(Schema.String),
31
+ message: optionalNull(Schema.String),
32
+ })),
33
+ model: optionalNull(Schema.String),
34
+ });
35
+ const STATUS = {
36
+ pending: "running",
37
+ done: "completed",
38
+ failed: "failed",
39
+ expired: "expired",
40
+ };
41
+ // ---------------------------------------------------------------------------
42
+ // 5. Request body construction
43
+ // ---------------------------------------------------------------------------
44
+ const mediaInput = (asset) => ProviderShared.mediaReference(asset, PROVIDER, NAME).pipe(Effect.map((reference) => (reference.type === "ref" ? { file_id: reference.value } : { url: reference.value })));
45
+ const nativeOptions = (options) => {
46
+ if (!options)
47
+ return undefined;
48
+ const { mode: _mode, ...native } = options;
49
+ return native;
50
+ };
51
+ const fromRequest = Effect.fn("XAIVideo.fromRequest")(function* (request) {
52
+ const image = request.frames?.first === undefined ? undefined : yield* mediaInput(request.frames.first);
53
+ const lastFrame = request.frames?.last === undefined ? undefined : yield* mediaInput(request.frames.last);
54
+ const video = request.video === undefined ? undefined : yield* mediaInput(request.video);
55
+ const references = yield* Effect.forEach(request.references ?? [], mediaInput);
56
+ return MediaProtocol.json(mergeJsonRecords({
57
+ model: request.model.id,
58
+ prompt: request.prompt,
59
+ image,
60
+ last_frame: lastFrame,
61
+ reference_images: references.length === 0 ? undefined : references,
62
+ video,
63
+ duration: request.durationSeconds,
64
+ aspect_ratio: request.aspectRatio,
65
+ resolution: request.resolution,
66
+ generate_audio: request.audio,
67
+ }, nativeOptions(request.providerOptions), request.http?.body) ?? {});
68
+ });
69
+ // ---------------------------------------------------------------------------
70
+ // 6. Response decoding
71
+ // ---------------------------------------------------------------------------
72
+ const decodeStart = MediaProtocol.decodeStarted(ADAPTER, NAME, StartResponse, (value) => ({
73
+ token: { requestID: value.request_id },
74
+ snapshot: { id: value.request_id, status: "running" },
75
+ }));
76
+ // `progress` is undocumented but observed live as a 0..100 percentage (recorded cassette: 1 → 10 → 37 → 100).
77
+ const fraction = (progress) => progress !== undefined && progress !== null && progress >= 0 && progress <= 100 ? progress / 100 : undefined;
78
+ const decodeVideoStatus = MediaProtocol.decodeJson(ADAPTER, NAME, VideoStatus);
79
+ const decodeStatus = Effect.fn("XAIVideo.decodeStatus")(function* (response, context) {
80
+ const output = yield* decodeVideoStatus(response);
81
+ const status = yield* MediaProtocol.status(STATUS, output.value.status, output);
82
+ return { id: context.token.requestID, status, progress: fraction(output.value.progress) };
83
+ });
84
+ const decodeResult = Effect.fn("XAIVideo.decodeResult")(function* (response, context) {
85
+ const output = yield* decodeVideoStatus(response);
86
+ const decoded = output.value;
87
+ const status = yield* MediaProtocol.status(STATUS, decoded.status, output);
88
+ if (status === "running")
89
+ return yield* output.invalid(`${NAME} request ${context.token.requestID} has not finished`);
90
+ if (status === "failed") {
91
+ const code = decoded.error?.code ?? undefined;
92
+ const message = decoded.error?.message ?? undefined;
93
+ return yield* output.ended("failed", `${NAME} generation failed${code === undefined ? "" : ` (${code})`}${message === undefined ? "" : `: ${message}`}`);
94
+ }
95
+ if (status !== "completed")
96
+ return yield* output.ended("expired", `${NAME} request ${context.token.requestID} expired`);
97
+ // `respect_moderation: false` marks a filtered result; a URL may still be present, so report it as a notice.
98
+ const notices = decoded.video?.respect_moderation === false
99
+ ? [{ type: "moderated", message: `${NAME} flagged the generated video for moderation` }]
100
+ : undefined;
101
+ const url = decoded.video?.url ?? undefined;
102
+ if (url === undefined && notices !== undefined)
103
+ return yield* output.contentPolicy(`${NAME} withheld the video for moderation`);
104
+ if (url === undefined)
105
+ return yield* output.invalid(`${NAME} completed without a video URL`);
106
+ const duration = decoded.video?.duration ?? undefined;
107
+ return new VideoResponse({
108
+ videos: [
109
+ Media.url(url, {
110
+ mediaType: "video/mp4",
111
+ info: duration === undefined ? undefined : { durationSeconds: duration },
112
+ }),
113
+ ],
114
+ notices,
115
+ providerMetadata: { xai: { requestId: context.token.requestID, model: decoded.model ?? undefined } },
116
+ });
117
+ });
118
+ // ---------------------------------------------------------------------------
119
+ // 7. Protocol and route
120
+ // ---------------------------------------------------------------------------
121
+ const statusPath = (token) => `${STATUS_PATH}/${token.requestID}`;
122
+ export const protocol = MediaProtocol.queued({
123
+ id: ADAPTER,
124
+ name: NAME,
125
+ token: Token,
126
+ unsupported: ["n", "seed", "negativePrompt"],
127
+ start: { body: { from: fromRequest }, decode: decodeStart },
128
+ status: { path: statusPath, decode: decodeStatus },
129
+ result: { path: statusPath, decode: decodeResult },
130
+ });
131
+ // A source video goes to `/videos/edits` unless `providerOptions.mode` asks for an extension.
132
+ const startPath = (request) => {
133
+ if (request.video === undefined)
134
+ return PATH;
135
+ return request.providerOptions?.mode === "extend" ? EXTEND_PATH : EDIT_PATH;
136
+ };
137
+ export const model = (input) => VideoModel.fromRoute({
138
+ id: ADAPTER,
139
+ provider: PROVIDER,
140
+ protocol,
141
+ baseURL: DEFAULT_BASE_URL,
142
+ path: ({ request }) => startPath(request),
143
+ }, input);
144
+ export const XAIVideo = {
145
+ protocol,
146
+ model,
147
+ };
@@ -39,6 +39,12 @@ export declare const protocol: Protocol<{
39
39
  readonly image_url: {
40
40
  readonly url: string;
41
41
  };
42
+ } | {
43
+ readonly type: "file";
44
+ readonly file: {
45
+ readonly filename: string;
46
+ readonly file_data: string;
47
+ };
42
48
  })[];
43
49
  } | {
44
50
  readonly role: "user";
@@ -54,6 +60,12 @@ export declare const protocol: Protocol<{
54
60
  readonly image_url: {
55
61
  readonly url: string;
56
62
  };
63
+ } | {
64
+ readonly type: "file";
65
+ readonly file: {
66
+ readonly filename: string;
67
+ readonly file_data: string;
68
+ };
57
69
  })[];
58
70
  } | {
59
71
  readonly [x: string]: unknown;
@@ -129,8 +141,8 @@ export declare const protocol: Protocol<{
129
141
  readonly response_format?: {
130
142
  readonly type: string;
131
143
  } | undefined;
132
- readonly do_sample?: boolean | undefined;
133
144
  readonly request_id?: string | undefined;
145
+ readonly do_sample?: boolean | undefined;
134
146
  }, string, "[DONE]" | {
135
147
  readonly [x: string]: unknown;
136
148
  readonly error?: {
@@ -1,22 +1,18 @@
1
- import { ImageModel } from "../image.js";
2
- import { type Definition as AuthDefinition } from "../route/auth.js";
3
- import { type HttpOptions } from "../schema/index.js";
1
+ import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js";
2
+ import { MediaProtocol } from "../route/media-protocol.js";
3
+ import { MediaRoute } from "../route/media.js";
4
4
  export declare const DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
5
5
  export declare const PATH = "/images/generations";
6
6
  export type ZAIImageString<Known extends string> = Known | (string & {});
7
+ /** Provider-native options. The common `size` field lives on the request. */
7
8
  export type ZAIImageOptions = {
8
- readonly size?: ZAIImageString<"1024x1024" | "768x1344" | "864x1152" | "1344x768" | "1152x864" | "1440x720" | "720x1440">;
9
9
  readonly quality?: ZAIImageString<"hd" | "standard">;
10
10
  readonly userID?: string;
11
11
  } & Record<string, unknown>;
12
- export interface ModelInput {
13
- readonly id: string;
14
- readonly auth: AuthDefinition;
15
- readonly baseURL?: string;
16
- readonly headers?: Record<string, string>;
17
- readonly http?: HttpOptions;
18
- }
19
- export declare const model: (input: ModelInput) => ImageModel<ZAIImageOptions>;
12
+ export type Request = ImageRequestFor<ZAIImageOptions>;
13
+ export declare const protocol: MediaProtocol.Inline<Request, ImageResponse>;
14
+ export declare const model: (input: MediaRoute.ModelInput) => ImageModel<ZAIImageOptions>;
20
15
  export declare const ZAIImages: {
21
- readonly model: (input: ModelInput) => ImageModel<ZAIImageOptions>;
16
+ readonly protocol: MediaProtocol.Inline<Request, ImageResponse>;
17
+ readonly model: (input: MediaRoute.ModelInput) => ImageModel<ZAIImageOptions>;
22
18
  };
@@ -1,13 +1,17 @@
1
1
  import { Effect, Schema } from "effect";
2
- import { Headers, HttpClientRequest } from "effect/unstable/http";
3
- import { GeneratedImage, ImageModel, ImageResponse } from "../image.js";
4
- import { Auth } from "../route/auth.js";
5
- import { mergeHttpOptions, mergeJsonRecords } from "../schema/index.js";
6
- import { ProviderShared } from "./shared.js";
7
- import { ImageInputs } from "./utils/image-input.js";
2
+ import { ImageModel, ImageResponse } from "../image.js";
3
+ import { Media } from "../media.js";
4
+ import { MediaProtocol } from "../route/media-protocol.js";
5
+ import { MediaRoute } from "../route/media.js";
6
+ import { ProviderID, mergeJsonRecords } from "../schema/index.js";
8
7
  const ADAPTER = "zai-images";
8
+ const NAME = "Z.ai Images";
9
+ const PROVIDER = ProviderID.make("zai");
9
10
  export const DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
10
11
  export const PATH = "/images/generations";
12
+ // ---------------------------------------------------------------------------
13
+ // 2. Response schema
14
+ // ---------------------------------------------------------------------------
11
15
  const ZAIImageResponse = Schema.Struct({
12
16
  created: Schema.optional(Schema.Int),
13
17
  id: Schema.optional(Schema.String),
@@ -18,62 +22,60 @@ const ZAIImageResponse = Schema.Struct({
18
22
  level: Schema.optional(Schema.Number),
19
23
  }))),
20
24
  });
25
+ // ---------------------------------------------------------------------------
26
+ // 5. Request body construction
27
+ // ---------------------------------------------------------------------------
21
28
  const nativeOptions = (options) => {
22
29
  if (!options)
23
30
  return undefined;
24
31
  const { userID, ...native } = options;
25
- return {
26
- user_id: userID,
27
- ...native,
28
- };
29
- };
30
- const applyQuery = (url, query) => {
31
- if (!query)
32
- return url;
33
- const next = new URL(url);
34
- Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
35
- return next.toString();
36
- };
37
- export const model = (input) => {
38
- const route = {
39
- id: ADAPTER,
40
- generate: Effect.fn("ZAIImages.generate")(function* (request, execute) {
41
- if ((request.images?.length ?? 0) > 0)
42
- return yield* ImageInputs.invalid("Z.ai hosted image generation does not support image inputs");
43
- const http = mergeHttpOptions(request.model.http, request.http);
44
- const requestBody = mergeJsonRecords({ model: request.model.id, prompt: request.prompt }, nativeOptions(request.options), http?.body);
45
- const text = ProviderShared.encodeJson(requestBody);
46
- const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${PATH}`, http?.query);
47
- const headers = yield* Auth.toEffect(input.auth)({
48
- request,
49
- method: "POST",
50
- url,
51
- body: text,
52
- headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
53
- });
54
- const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
55
- const output = yield* ProviderShared.imageResponse(ADAPTER, "Z.ai Images", response);
56
- const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ZAIImageResponse))(output.body).pipe(Effect.mapError((cause) => output.invalid("Z.ai Images returned an invalid response", cause)));
57
- if (decoded.data.length === 0)
58
- return yield* output.invalid("Z.ai Images returned no images");
59
- return new ImageResponse({
60
- images: decoded.data.map((item) => new GeneratedImage({
61
- mediaType: "application/octet-stream",
62
- data: item.url,
63
- })),
64
- providerMetadata: {
65
- zai: {
66
- created: decoded.created,
67
- id: decoded.id,
68
- requestID: decoded.request_id,
69
- contentFilter: decoded.content_filter,
70
- },
71
- },
72
- });
73
- }),
74
- };
75
- return ImageModel.make({ id: input.id, provider: "zai", route, http: input.http });
32
+ return { user_id: userID, ...native };
76
33
  };
34
+ const fromRequest = Effect.fn("ZAIImages.fromRequest")(function* (request) {
35
+ return MediaProtocol.json(mergeJsonRecords({ model: request.model.id, prompt: request.prompt, size: request.size }, nativeOptions(request.providerOptions), request.http?.body) ?? {});
36
+ });
37
+ // ---------------------------------------------------------------------------
38
+ // 6. Response decoding
39
+ // ---------------------------------------------------------------------------
40
+ const decodeResponse = Effect.fn("ZAIImages.decodeResponse")(function* (response) {
41
+ const output = yield* MediaProtocol.decodeJson(ADAPTER, NAME, ZAIImageResponse)(response);
42
+ const decoded = output.value;
43
+ if (decoded.data.length === 0)
44
+ return yield* output.invalid(`${NAME} returned no images`);
45
+ const filters = decoded.content_filter ?? [];
46
+ return new ImageResponse({
47
+ // Z.ai returns only URLs and no content type; the media type resolves when the asset is materialized.
48
+ images: decoded.data.map((item) => Media.url(item.url)),
49
+ // Z.ai reports applied content filters alongside a successful result; surface them instead of dropping them.
50
+ notices: filters.length === 0
51
+ ? undefined
52
+ : filters.map((filter) => ({
53
+ type: "moderated",
54
+ message: `${NAME} applied a content filter${filter.role === undefined ? "" : ` for ${filter.role}`}${filter.level === undefined ? "" : ` at level ${filter.level}`}`,
55
+ providerMetadata: { zai: filter },
56
+ })),
57
+ providerMetadata: {
58
+ zai: {
59
+ created: decoded.created,
60
+ id: decoded.id,
61
+ requestID: decoded.request_id,
62
+ contentFilter: decoded.content_filter,
63
+ },
64
+ },
65
+ });
66
+ });
67
+ // ---------------------------------------------------------------------------
68
+ // 7. Protocol and route
69
+ // ---------------------------------------------------------------------------
70
+ export const protocol = MediaProtocol.inline({
71
+ id: ADAPTER,
72
+ name: NAME,
73
+ unsupported: ["images", "mask", "n", "aspectRatio", "seed", "format"],
74
+ body: { from: fromRequest },
75
+ response: { decode: decodeResponse },
76
+ });
77
+ export const model = (input) => ImageModel.fromRoute({ id: ADAPTER, provider: PROVIDER, protocol, baseURL: DEFAULT_BASE_URL, path: PATH }, input);
77
78
  export const ZAIImages = {
79
+ protocol,
78
80
  model,
79
81
  };
@@ -65,6 +65,7 @@ const INVALID_REQUEST_CODES = new Set(["invalid_prompt", "invalid_request_error"
65
65
  // emits `image_content_policy_violation` as the native code.
66
66
  const CONTENT_POLICY_CODES = new Set([
67
67
  "content_filter",
68
+ "content_moderation",
68
69
  "responsibleaipolicyviolation",
69
70
  "content_policy_violation",
70
71
  "image_content_policy_violation",
@@ -157,6 +158,8 @@ function providerCodes(value) {
157
158
  const exception = isRecord(decoded.exception) ? decoded.exception : undefined;
158
159
  return [
159
160
  decoded.code,
161
+ // Stability's `{ id, name, errors }` bodies carry the code in `name`.
162
+ Array.isArray(decoded.errors) ? decoded.name : undefined,
160
163
  decoded.error_type,
161
164
  error?.code,
162
165
  error?.type,
@@ -39,6 +39,12 @@ export declare const routes: (Route<{
39
39
  readonly image_url: {
40
40
  readonly url: string;
41
41
  };
42
+ } | {
43
+ readonly type: "file";
44
+ readonly file: {
45
+ readonly filename: string;
46
+ readonly file_data: string;
47
+ };
42
48
  })[];
43
49
  } | {
44
50
  readonly role: "user";
@@ -54,6 +60,12 @@ export declare const routes: (Route<{
54
60
  readonly image_url: {
55
61
  readonly url: string;
56
62
  };
63
+ } | {
64
+ readonly type: "file";
65
+ readonly file: {
66
+ readonly filename: string;
67
+ readonly file_data: string;
68
+ };
57
69
  })[];
58
70
  } | {
59
71
  readonly [x: string]: unknown;
@@ -470,8 +482,8 @@ export declare const routes: (Route<{
470
482
  } | {
471
483
  readonly type: "input_file";
472
484
  readonly filename: string;
473
- readonly detail?: string | undefined;
474
485
  readonly file_data?: string | undefined;
486
+ readonly detail?: string | undefined;
475
487
  readonly file_url?: string | undefined;
476
488
  } | {
477
489
  readonly type: "input_text";
@@ -507,8 +519,8 @@ export declare const routes: (Route<{
507
519
  } | {
508
520
  readonly type: "input_file";
509
521
  readonly filename: string;
510
- readonly detail?: string | undefined;
511
522
  readonly file_data?: string | undefined;
523
+ readonly detail?: string | undefined;
512
524
  readonly file_url?: string | undefined;
513
525
  } | {
514
526
  readonly type: "input_text";