import assert from "node:assert/strict";
import { test } from "node:test";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { chromium } from "playwright";
import { unstable_startWorker } from "wrangler";
// Opt-in real inference; kept separate from fixtures that replace credentials.
test(
"live vision reads an attachment before answering a natural question",
{
skip: process.env.FLAREBOT_ATTACHMENTS_LIVE !== "1",
timeout: 120000,
},
async () => {
const directory = await mkdtemp(join(tmpdir(), "flarebot-vision-live-"));
let worker, browser;
try {
browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 400, height: 300 },
});
await page.setContent(
'
',
);
const png = await page.screenshot();
await browser.close();
browser = undefined;
const entry = join(directory, "worker.ts");
await writeFile(
entry,
`
import {DurableObject} from 'cloudflare:workers';
import {Workspace} from ${JSON.stringify(resolve("node_modules/@cloudflare/shell/dist/index.js"))};
import {streamText, stepCountIs} from ${JSON.stringify(resolve("node_modules/ai/dist/index.js"))};
import {ConversationAttachments} from ${JSON.stringify(resolve("worker/attachments.ts"))};
import {createConfiguredModel} from ${JSON.stringify(resolve("worker/model-provider.ts"))};
import {requestedAttachmentReader} from ${JSON.stringify(resolve("shared/attachments.ts"))};
export class Store extends DurableObject {
async fetch(request) {
const files = new ConversationAttachments((strings,...values)=>this.ctx.storage.sql.exec(strings.join('?'),...values).toArray(), new Workspace({sql:this.ctx.storage.sql}), this.env.AI);
const file = await files.upload('sample.png', new Uint8Array(await request.arrayBuffer()));
const message = {id:'question', role:'user', parts:[{type:'text',text:"What's this?"}], metadata:{attachments:[file]}};
const configuration = {provider:'workers-ai', model:new URL(request.url).searchParams.get('model')};
const prepared = files.prepare([message], configuration);
const result = streamText({model:createConfiguredModel(this.env.AI, configuration), system:prepared.instructions, messages:[{role:'user',content:"What's this?"}], tools:prepared.tools, stopWhen:stepCountIs(3), maxOutputTokens:300,
prepareStep: ({stepNumber}) => stepNumber===0 && requestedAttachmentReader([message]) ? {toolChoice:{type:'tool',toolName:'read_attachment'}} : {toolChoice:'auto'},
});
const text = await result.text;
const steps = await result.steps;
return Response.json({text, tools:steps.flatMap(s=>s.toolResults.map(r=>r.toolName))});
}
}
export default {fetch(request,env){return env.Store.getByName(crypto.randomUUID()).fetch(request)}};
`,
);
const config = join(directory, "wrangler.json");
await writeFile(
config,
JSON.stringify({
name: "flarebot-vision-live",
main: entry,
compatibility_date: "2026-09-04",
compatibility_flags: ["nodejs_compat"],
ai: { binding: "AI", remote: true },
durable_objects: {
bindings: [{ name: "Store", class_name: "Store" }],
},
migrations: [{ tag: "v1", new_sqlite_classes: ["Store"] }],
}),
);
worker = await unstable_startWorker({
config,
dev: { inspector: false, server: { hostname: "127.0.0.1", port: 0 } },
});
await worker.ready;
for (const model of [
"@cf/meta/llama-4-scout-17b-16e-instruct",
"@cf/qwen/qwen3.8-27b",
]) {
const response = await worker.fetch(
`http://localhost/?model=${encodeURIComponent(model)}`,
{ method: "POST", body: png },
);
const result = await response.json();
console.log(model, result);
assert.ok(result.tools.includes("read_attachment"));
assert.match(result.text, /red/i);
assert.match(result.text, /square/i);
assert.match(result.text, /orchid\s*47/i);
}
} finally {
await browser?.close();
await worker?.dispose();
await rm(directory, { recursive: true, force: true });
}
},
);