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:
2026-07-13 16:52:22 +07:00
parent 7f436e2792
commit b1d368d960
28 changed files with 939 additions and 149 deletions

View File

@@ -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");
});
});