@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 +18 -0
- package/dist/protocols/xai-images.d.ts +25 -0
- package/dist/protocols/xai-images.js +93 -0
- package/dist/providers/xai.d.ts +5 -0
- package/dist/providers/xai.js +11 -1
- package/package.json +3 -3
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
|
+
};
|
package/dist/providers/xai.d.ts
CHANGED
|
@@ -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>;
|
package/dist/providers/xai.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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"
|