@opencode-ai/ai 0.0.0-next-15903 → 0.0.0-next-15906

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,6 +63,24 @@ yield *
63
63
  })
64
64
  ```
65
65
 
66
+ xAI image models use the same request API with xAI-native controls:
67
+
68
+ ```ts
69
+ yield *
70
+ Image.generate({
71
+ model: XAI.configure({ apiKey }).image("any-model-id"),
72
+ prompt,
73
+ options: {
74
+ n: 2,
75
+ aspectRatio: "16:9",
76
+ resolution: "1k",
77
+ responseFormat: "b64_json",
78
+ future_option: true,
79
+ },
80
+ http,
81
+ })
82
+ ```
83
+
66
84
  Conversational image generation remains part of the LLM interaction. OpenAI Responses exposes it through its hosted image tool:
67
85
 
68
86
  ```ts
@@ -0,0 +1,25 @@
1
+ import { ImageModel } from "../image";
2
+ import { type Definition as AuthDefinition } from "../route/auth";
3
+ import { type HttpOptions } from "../schema";
4
+ export declare const DEFAULT_BASE_URL = "https://api.x.ai/v1";
5
+ export declare const PATH = "/images/generations";
6
+ export type XAIImageString<Known extends string> = Known | (string & {});
7
+ export type XAIImageOptions = {
8
+ readonly n?: number;
9
+ readonly aspectRatio?: XAIImageString<"1:1" | "3:4" | "4:3" | "9:16" | "16:9" | "2:3" | "3:2" | "9:19.5" | "19.5:9" | "9:20" | "20:9" | "1:2" | "2:1" | "auto">;
10
+ readonly aspect_ratio?: XAIImageString<"1:1" | "3:4" | "4:3" | "9:16" | "16:9" | "2:3" | "3:2" | "9:19.5" | "19.5:9" | "9:20" | "20:9" | "1:2" | "2:1" | "auto">;
11
+ readonly resolution?: XAIImageString<"1k" | "2k">;
12
+ readonly responseFormat?: XAIImageString<"url" | "b64_json">;
13
+ readonly response_format?: XAIImageString<"url" | "b64_json">;
14
+ } & Record<string, unknown>;
15
+ export interface ModelInput {
16
+ readonly id: string;
17
+ readonly auth: AuthDefinition;
18
+ readonly baseURL?: string;
19
+ readonly headers?: Record<string, string>;
20
+ readonly http?: HttpOptions;
21
+ }
22
+ export declare const model: (input: ModelInput) => ImageModel<XAIImageOptions>;
23
+ export declare const XAIImages: {
24
+ readonly model: (input: ModelInput) => ImageModel<XAIImageOptions>;
25
+ };
@@ -0,0 +1,93 @@
1
+ import { Effect, Encoding, Schema } from "effect";
2
+ import { Headers, HttpClientRequest } from "effect/unstable/http";
3
+ import { GeneratedImage, ImageModel, ImageResponse } from "../image";
4
+ import { Auth } from "../route/auth";
5
+ import { InvalidProviderOutputReason, LLMError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema";
6
+ import { ProviderShared, optionalNull } from "./shared";
7
+ const ADAPTER = "xai-images";
8
+ export const DEFAULT_BASE_URL = "https://api.x.ai/v1";
9
+ export const PATH = "/images/generations";
10
+ const XAIImageResponse = Schema.Struct({
11
+ data: Schema.Array(Schema.Struct({
12
+ b64_json: optionalNull(Schema.String),
13
+ url: optionalNull(Schema.String),
14
+ revised_prompt: optionalNull(Schema.String),
15
+ mime_type: optionalNull(Schema.String),
16
+ })),
17
+ usage: Schema.optional(Schema.Unknown),
18
+ });
19
+ const nativeOptions = (options) => {
20
+ if (!options)
21
+ return undefined;
22
+ const { aspectRatio, responseFormat, ...native } = options;
23
+ return {
24
+ aspect_ratio: aspectRatio,
25
+ response_format: responseFormat,
26
+ ...native,
27
+ };
28
+ };
29
+ const invalidOutput = (message) => new LLMError({
30
+ module: ADAPTER,
31
+ method: "generate",
32
+ reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
33
+ });
34
+ const applyQuery = (url, query) => {
35
+ if (!query)
36
+ return url;
37
+ const next = new URL(url);
38
+ Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
39
+ return next.toString();
40
+ };
41
+ export const model = (input) => {
42
+ const route = {
43
+ id: ADAPTER,
44
+ generate: Effect.fn("XAIImages.generate")(function* (request, execute) {
45
+ const http = mergeHttpOptions(request.model.http, request.http);
46
+ const requestBody = mergeJsonRecords({ model: request.model.id, prompt: request.prompt }, nativeOptions(request.options), http?.body);
47
+ const text = ProviderShared.encodeJson(requestBody);
48
+ const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${PATH}`, http?.query);
49
+ const headers = yield* Auth.toEffect(input.auth)({
50
+ request,
51
+ method: "POST",
52
+ url,
53
+ body: text,
54
+ headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
55
+ });
56
+ const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
57
+ const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the xAI Images response")));
58
+ const decoded = yield* Schema.decodeUnknownEffect(XAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("xAI Images returned an invalid response")));
59
+ const images = yield* Effect.forEach(decoded.data, (item, index) => {
60
+ const mediaType = item.mime_type ?? "application/octet-stream";
61
+ if (item.b64_json)
62
+ return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() => invalidOutput(`xAI Images result ${index} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
63
+ mediaType,
64
+ data,
65
+ providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
66
+ ? undefined
67
+ : { xai: { revisedPrompt: item.revised_prompt } },
68
+ })));
69
+ if (item.url)
70
+ return Effect.succeed(new GeneratedImage({
71
+ mediaType,
72
+ data: item.url,
73
+ providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
74
+ ? undefined
75
+ : { xai: { revisedPrompt: item.revised_prompt } },
76
+ }));
77
+ return Effect.fail(invalidOutput(`xAI Images result ${index} has neither image data nor a URL`));
78
+ });
79
+ if (images.length === 0)
80
+ return yield* invalidOutput("xAI Images returned no images");
81
+ const usage = ProviderShared.isRecord(decoded.usage) ? decoded.usage : undefined;
82
+ return new ImageResponse({
83
+ images,
84
+ usage: usage === undefined ? undefined : new Usage({ providerMetadata: { xai: usage } }),
85
+ providerMetadata: usage === undefined ? undefined : { xai: { usage } },
86
+ });
87
+ }),
88
+ };
89
+ return ImageModel.make({ id: input.id, provider: "xai", route, http: input.http });
90
+ };
91
+ export const XAIImages = {
92
+ model,
93
+ };
@@ -5,6 +5,7 @@ export declare const id: string & import("effect/Brand").Brand<"LLM.ProviderID">
5
5
  export type ModelOptions = RouteDefaultsInput & ProviderAuthOption<"optional"> & {
6
6
  readonly baseURL?: string;
7
7
  };
8
+ export type { XAIImageOptions } from "../protocols/xai-images";
8
9
  export declare const routes: (import("../route").Route<{
9
10
  readonly messages: readonly (import("effect/Schema").Struct.ReadonlySide<{
10
11
  readonly role: import("effect/Schema").Literal<"system">;
@@ -162,6 +163,7 @@ export declare const configure: (input?: ModelOptions) => {
162
163
  model: (modelID: string | ModelID) => import("..").Model;
163
164
  responses: (modelID: string | ModelID) => import("..").Model;
164
165
  chat: (modelID: string | ModelID) => import("..").Model;
166
+ image: (modelID: string | ModelID) => import("..").ImageModel<import("./xai").XAIImageOptions>;
165
167
  configure: (input?: ModelOptions) => /*elided*/ any;
166
168
  };
167
169
  export declare const provider: {
@@ -169,14 +171,17 @@ export declare const provider: {
169
171
  model: (modelID: string | ModelID) => import("..").Model;
170
172
  responses: (modelID: string | ModelID) => import("..").Model;
171
173
  chat: (modelID: string | ModelID) => import("..").Model;
174
+ image: (modelID: string | ModelID) => import("..").ImageModel<import("./xai").XAIImageOptions>;
172
175
  configure: (input?: ModelOptions) => {
173
176
  id: string & import("effect/Brand").Brand<"LLM.ProviderID">;
174
177
  model: (modelID: string | ModelID) => import("..").Model;
175
178
  responses: (modelID: string | ModelID) => import("..").Model;
176
179
  chat: (modelID: string | ModelID) => import("..").Model;
180
+ image: (modelID: string | ModelID) => import("..").ImageModel<import("./xai").XAIImageOptions>;
177
181
  configure: /*elided*/ any;
178
182
  };
179
183
  };
180
184
  export declare const model: (modelID: string | ModelID) => import("..").Model;
181
185
  export declare const responses: (modelID: string | ModelID) => import("..").Model;
182
186
  export declare const chat: (modelID: string | ModelID) => import("..").Model;
187
+ export declare const image: (modelID: string | ModelID) => import("..").ImageModel<import("./xai").XAIImageOptions>;
@@ -1,8 +1,9 @@
1
1
  import { AuthOptions } from "../route/auth-options";
2
- import { ProviderID } from "../schema";
2
+ import { HttpOptions, ProviderID } from "../schema";
3
3
  import * as OpenAICompatibleProfiles from "./openai-compatible-profile";
4
4
  import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat";
5
5
  import * as OpenAIResponses from "../protocols/openai-responses";
6
+ import { XAIImages } from "../protocols/xai-images";
6
7
  export const id = ProviderID.make("xai");
7
8
  export const routes = [OpenAIResponses.route, OpenAICompatibleChat.route];
8
9
  const auth = (options) => AuthOptions.bearer(options, "XAI_API_KEY");
@@ -29,11 +30,19 @@ export const configure = (input = {}) => {
29
30
  const chatRoute = configuredChatRoute(input);
30
31
  const responses = (modelID) => responsesRoute.model({ id: modelID });
31
32
  const chat = (modelID) => chatRoute.model({ id: modelID });
33
+ const image = (modelID) => XAIImages.model({
34
+ id: modelID,
35
+ auth: auth(input),
36
+ baseURL: input.baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL,
37
+ headers: input.headers,
38
+ http: input.http === undefined ? undefined : HttpOptions.make(input.http),
39
+ });
32
40
  return {
33
41
  id,
34
42
  model: responses,
35
43
  responses,
36
44
  chat,
45
+ image,
37
46
  configure,
38
47
  };
39
48
  };
@@ -41,3 +50,4 @@ export const provider = configure();
41
50
  export const model = provider.model;
42
51
  export const responses = provider.responses;
43
52
  export const chat = provider.chat;
53
+ export const image = provider.image;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "0.0.0-next-15903",
3
+ "version": "0.0.0-next-15906",
4
4
  "name": "@opencode-ai/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "@clack/prompts": "1.0.0-alpha.1",
28
28
  "@effect/platform-node": "4.0.0-beta.98",
29
- "@opencode-ai/http-recorder": "0.0.0-next-15903",
29
+ "@opencode-ai/http-recorder": "0.0.0-next-15906",
30
30
  "@tsconfig/bun": "1.0.9",
31
31
  "@types/bun": "1.3.13",
32
32
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@smithy/eventstream-codec": "4.2.14",
37
37
  "@smithy/util-utf8": "4.2.2",
38
- "@opencode-ai/schema": "0.0.0-next-15903",
38
+ "@opencode-ai/schema": "0.0.0-next-15906",
39
39
  "aws4fetch": "1.0.20",
40
40
  "effect": "4.0.0-beta.98",
41
41
  "google-auth-library": "10.5.0"