diff --git a/README.md b/README.md index 392f82d..1ebd92b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ It also exposes provider and tool surfaces for live model routing: - `src/scenarios.js` is the shared deterministic fixture engine used by dry commands, tools, providers, hooks, channel delivery, and tests. - `kitchen_sink_image_job` returns a deterministic image job, waits 10 seconds - in real runtime execution, then returns a bundled SVG image payload. + in real runtime execution, then returns the bundled `kitchen_sink_office.png` + image payload. - `kitchen-sink-image` is a registered image generation provider with aliases `kitchen`, `kitchen-sink`, and `openclaw-kitchen-sink`. - `kitchen-sink-media` describes images with deterministic fixture text. diff --git a/scripts/check-kitchen-runtime.mjs b/scripts/check-kitchen-runtime.mjs index 0166479..d526735 100644 --- a/scripts/check-kitchen-runtime.mjs +++ b/scripts/check-kitchen-runtime.mjs @@ -108,9 +108,16 @@ assert.equal(imageResult.route, "provider:image"); assert.equal(imageResult.job.status, "completed"); assert.equal(imageResult.job.pluginId, "openclaw-kitchen-sink-fixture"); assert.equal(imageResult.image.metadata.pluginId, "openclaw-kitchen-sink-fixture"); -assert.equal(imageResult.image.mimeType, "image/svg+xml"); -assert.ok(imageResult.image.buffer.toString("utf8").includes("Kitchen Sink Fixture")); -assert.ok(imageResult.image.dataUrl.startsWith("data:image/svg+xml;base64,")); +assert.equal(imageResult.image.metadata.assetName, "kitchen_sink_office.png"); +assert.equal(imageResult.image.metadata.width, 1024); +assert.equal(imageResult.image.metadata.height, 1024); +assert.equal(imageResult.image.mimeType, "image/png"); +assert.equal(imageResult.image.fileName, `${imageResult.job.id}.png`); +assert.deepEqual( + [...imageResult.image.buffer.subarray(0, 8)], + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], +); +assert.ok(imageResult.image.dataUrl.startsWith("data:image/png;base64,")); const scenarioResult = await runKitchenScenario(fastRuntime, { scenario: "web.fetch", diff --git a/src/assets/kitchen_sink_office.png b/src/assets/kitchen_sink_office.png new file mode 100644 index 0000000..e954035 Binary files /dev/null and b/src/assets/kitchen_sink_office.png differ diff --git a/src/scenarios.js b/src/scenarios.js index ddd6e8d..cd558bf 100644 --- a/src/scenarios.js +++ b/src/scenarios.js @@ -1,3 +1,5 @@ +import { readFileSync } from "node:fs"; + export const PLUGIN_ID = "openclaw-kitchen-sink-fixture"; export const IMAGE_PROVIDER_ID = "kitchen-sink-image"; export const MEDIA_PROVIDER_ID = "kitchen-sink-media"; @@ -10,6 +12,10 @@ export const DEFAULT_IMAGE_MODEL = "kitchen-sink-image-v1"; export const DEFAULT_MEDIA_MODEL = "kitchen-sink-vision-v1"; export const DEFAULT_TEXT_MODEL = "kitchen-sink-text-v1"; export const DEFAULT_IMAGE_DELAY_MS = 10_000; +const KITCHEN_SINK_OFFICE_IMAGE_FILE = "kitchen_sink_office.png"; +const KITCHEN_SINK_OFFICE_IMAGE = readFileSync( + new URL(`./assets/${KITCHEN_SINK_OFFICE_IMAGE_FILE}`, import.meta.url), +); export function createKitchenScenarioRuntime(options = {}) { const runtime = { @@ -91,16 +97,18 @@ export async function runKitchenScenario(runtime, request = {}) { } export function createKitchenSinkImageAsset({ prompt, jobId, scenario = "image.generate" }) { - const svg = renderKitchenSinkSvg({ prompt, jobId }); - const buffer = Buffer.from(svg, "utf8"); + const buffer = Buffer.from(KITCHEN_SINK_OFFICE_IMAGE); return { buffer, - mimeType: "image/svg+xml", - fileName: `${jobId}.svg`, - dataUrl: `data:image/svg+xml;base64,${buffer.toString("base64")}`, - revisedPrompt: `Kitchen sink fixture image for: ${prompt}`, + mimeType: "image/png", + fileName: `${jobId}.png`, + dataUrl: `data:image/png;base64,${buffer.toString("base64")}`, + revisedPrompt: `Kitchen sink office fixture image for: ${prompt}`, metadata: { kitchenSink: true, + assetName: KITCHEN_SINK_OFFICE_IMAGE_FILE, + width: 1024, + height: 1024, pluginId: PLUGIN_ID, scenarioId: scenario, jobId, @@ -314,7 +322,7 @@ export function kitchenImageDescription(prompt, count) { return [ `Kitchen Sink media fixture described ${count || 1} image${count === 1 ? "" : "s"}.`, `Prompt: ${normalizePrompt(prompt, "describe kitchen sink image")}.`, - "Visible content: a deterministic sink basin, faucet, job id label, and OpenClaw fixture badge.", + "Visible content: the bundled kitchen_sink_office PNG: an office lobby scene with a lobster-costumed figure holding a real sink.", ].join(" "); } @@ -413,25 +421,6 @@ function createKitchenJob(kind, prompt, date, delayMs, scenarioId, route) { }; } -function renderKitchenSinkSvg({ prompt, jobId }) { - const safePrompt = escapeXml(prompt).slice(0, 180); - const safeJobId = escapeXml(jobId); - return ` - - - - - - - - - - Kitchen Sink Fixture - ${safeJobId} - ${safePrompt} -`; -} - function createAssistantMessageEventStream() { const queue = []; const waiters = []; @@ -645,11 +634,3 @@ function stableHash(input) { } return (hash >>> 0).toString(16).padStart(8, "0"); } - -function escapeXml(value) { - return String(value) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """); -}