feat: редактирование фото через xAI Grok Imagine API (/v1/images/edits) вместо Codex CLI
EDIT_ENGINE=grok по умолчанию, XAI_API_KEY/XAI_BASE_URL/XAI_IMAGE_MODEL/XAI_TIMEOUT_MS; Codex остаётся для подсказок и как fallback (EDIT_ENGINE=codex).
This commit is contained in:
60
test/grok-edit.test.js
Normal file
60
test/grok-edit.test.js
Normal file
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
|
||||
const { test, describe } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { XaiImageClient } = require("../src/core/xai/client");
|
||||
const { buildGrokEditPrompt } = require("../src/core/engines/grok-edit-engine");
|
||||
const { loadConfig } = require("../src/config");
|
||||
|
||||
describe("xAI Grok image edit", () => {
|
||||
test("sends JSON to /images/edits and decodes b64_json", async () => {
|
||||
let captured;
|
||||
const fetchImpl = async (url, opts) => {
|
||||
captured = { url, opts, body: JSON.parse(opts.body) };
|
||||
return new Response(JSON.stringify({ data: [{ b64_json: Buffer.from("JPEGDATA").toString("base64"), mime_type: "image/jpeg" }] }), { status: 200 });
|
||||
};
|
||||
const c = new XaiImageClient({ apiKey: "k", baseUrl: "https://api.x.ai/v1/", fetchImpl });
|
||||
const r = await c.edit({ prompt: "p", images: [{ buffer: Buffer.from("a"), mimetype: "image/png" }] });
|
||||
assert.equal(captured.url, "https://api.x.ai/v1/images/edits");
|
||||
assert.equal(captured.opts.headers.Authorization, "Bearer k");
|
||||
assert.equal(captured.body.model, "grok-imagine-image-2.0");
|
||||
assert.equal(captured.body.response_format, "b64_json");
|
||||
assert.equal(captured.body.image.type, "image_url");
|
||||
assert.ok(captured.body.image.url.startsWith("data:image/png;base64,"));
|
||||
assert.equal(r.buffer.toString(), "JPEGDATA");
|
||||
});
|
||||
|
||||
test("uses images[] for multiple inputs and surfaces API errors", async () => {
|
||||
let body;
|
||||
const fetchImpl = async (_u, o) => {
|
||||
body = JSON.parse(o.body);
|
||||
return new Response(JSON.stringify({ error: "bad" }), { status: 400 });
|
||||
};
|
||||
const c = new XaiImageClient({ apiKey: "k", fetchImpl });
|
||||
const img = { buffer: Buffer.from("a"), mimetype: "image/jpeg" };
|
||||
await assert.rejects(c.edit({ prompt: "p", images: [img, img] }), /Grok API 400: bad/);
|
||||
assert.equal(body.images.length, 2);
|
||||
assert.equal(body.image, undefined);
|
||||
});
|
||||
|
||||
test("fails clearly without API key", async () => {
|
||||
const c = new XaiImageClient({ apiKey: "" });
|
||||
await assert.rejects(c.edit({ prompt: "p", images: [{ buffer: Buffer.from("a") }] }), /XAI_API_KEY/);
|
||||
});
|
||||
|
||||
test("prompt mentions reference region", () => {
|
||||
const t = buildGrokEditPrompt({ prompt: "add hat", hasReference: true, region: { x: 1, y: 2, w: 3, h: 4 } });
|
||||
assert.match(t, /add hat/);
|
||||
assert.match(t, /x=1, y=2, width=3, height=4/);
|
||||
});
|
||||
|
||||
test("config defaults and key is not in sanitized output", () => {
|
||||
const cfg = loadConfig({ XAI_API_KEY: "secret" });
|
||||
assert.equal(cfg.EDIT_ENGINE, "grok");
|
||||
assert.equal(cfg.XAI_IMAGE_MODEL, "grok-imagine-image-2.0");
|
||||
const s = cfg.getSanitized();
|
||||
assert.equal(s.XAI_API_KEY, undefined);
|
||||
assert.equal(s.XAI_API_KEY_SET, true);
|
||||
assert.throws(() => loadConfig({ EDIT_ENGINE: "foo" }));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user