feat: xAI image generate/edit, API key import, and per-provider timeouts
- Add dedicated xAI image adapter with generate + edit (multi-image) via /v1/images/generations and /v1/images/edits, plus aspect_ratio/resolution UI - Support importing existing API keys and exposing connection api-key routes - Add global/per-provider connect timeout overrides from settings - Keep unrelated provider UX improvements on this branch; no Grok quota tracking
This commit is contained in:
@@ -543,4 +543,146 @@ describe("handleImageGenerationCore", () => {
|
||||
expect(result.success).toBe(true);
|
||||
expect(onRequestSuccess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("generates image with xAI Imagine API", async () => {
|
||||
global.fetch.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
created: 1234567890,
|
||||
data: [{ url: "https://example.com/xai-gen.png" }],
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
);
|
||||
|
||||
const result = await handleImageGenerationCore({
|
||||
body: {
|
||||
prompt: "Mountain landscape at sunrise",
|
||||
n: 2,
|
||||
aspect_ratio: "16:9",
|
||||
resolution: "2k",
|
||||
response_format: "url",
|
||||
},
|
||||
modelInfo: { provider: "xai", model: "grok-imagine-image-quality" },
|
||||
credentials: { apiKey: "xai-key" },
|
||||
log: null,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://api.x.ai/v1/images/generations",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: expect.objectContaining({
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer xai-key",
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const reqBody = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(reqBody).toEqual({
|
||||
model: "grok-imagine-image-quality",
|
||||
prompt: "Mountain landscape at sunrise",
|
||||
n: 2,
|
||||
response_format: "url",
|
||||
aspect_ratio: "16:9",
|
||||
resolution: "2k",
|
||||
});
|
||||
});
|
||||
|
||||
it("maps OpenAI size to aspect_ratio for xAI generation", async () => {
|
||||
global.fetch.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({ created: 1, data: [{ url: "https://example.com/xai.png" }] }),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
);
|
||||
|
||||
await handleImageGenerationCore({
|
||||
body: { prompt: "city", size: "1792x1024" },
|
||||
modelInfo: { provider: "xai", model: "grok-imagine-image-quality" },
|
||||
credentials: { accessToken: "oauth-token" },
|
||||
log: null,
|
||||
});
|
||||
|
||||
const reqBody = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(reqBody.aspect_ratio).toBe("16:9");
|
||||
expect(reqBody.size).toBeUndefined();
|
||||
expect(global.fetch.mock.calls[0][1].headers.Authorization).toBe("Bearer oauth-token");
|
||||
});
|
||||
|
||||
it("edits a single image via xAI /images/edits", async () => {
|
||||
global.fetch.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({ created: 1, data: [{ url: "https://example.com/xai-edit.png" }] }),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
);
|
||||
|
||||
const result = await handleImageGenerationCore({
|
||||
body: {
|
||||
prompt: "Render this as a pencil sketch",
|
||||
image: "https://docs.x.ai/assets/api-examples/images/style-realistic.png",
|
||||
},
|
||||
modelInfo: { provider: "xai", model: "grok-imagine-image-quality" },
|
||||
credentials: { apiKey: "xai-key" },
|
||||
log: null,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://api.x.ai/v1/images/edits",
|
||||
expect.objectContaining({ method: "POST" })
|
||||
);
|
||||
|
||||
const reqBody = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(reqBody.image).toEqual({
|
||||
type: "image_url",
|
||||
url: "https://docs.x.ai/assets/api-examples/images/style-realistic.png",
|
||||
});
|
||||
expect(reqBody.images).toBeUndefined();
|
||||
});
|
||||
|
||||
it("supports multi-image edit for xAI (up to 3 refs)", async () => {
|
||||
global.fetch.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({ created: 1, data: [{ b64_json: "abc" }] }),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
)
|
||||
);
|
||||
|
||||
const result = await handleImageGenerationCore({
|
||||
body: {
|
||||
prompt: "Show all subjects sitting together on the grass",
|
||||
images: [
|
||||
"https://docs.x.ai/assets/api-examples/images/image-merge/woman.jpg",
|
||||
{ url: "https://docs.x.ai/assets/api-examples/images/image-merge/man.jpg" },
|
||||
"rawbase64payload",
|
||||
"https://example.com/extra-ignored.jpg",
|
||||
],
|
||||
aspect_ratio: "3:2",
|
||||
response_format: "b64_json",
|
||||
},
|
||||
modelInfo: { provider: "xai", model: "grok-imagine-image-quality" },
|
||||
credentials: { apiKey: "xai-key" },
|
||||
log: null,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://api.x.ai/v1/images/edits",
|
||||
expect.any(Object)
|
||||
);
|
||||
|
||||
const reqBody = JSON.parse(global.fetch.mock.calls[0][1].body);
|
||||
expect(reqBody.images).toEqual([
|
||||
{ type: "image_url", url: "https://docs.x.ai/assets/api-examples/images/image-merge/woman.jpg" },
|
||||
{ type: "image_url", url: "https://docs.x.ai/assets/api-examples/images/image-merge/man.jpg" },
|
||||
{ type: "image_url", url: "data:image/png;base64,rawbase64payload" },
|
||||
]);
|
||||
expect(reqBody.image).toBeUndefined();
|
||||
expect(reqBody.aspect_ratio).toBe("3:2");
|
||||
expect(reqBody.response_format).toBe("b64_json");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user