import { runWeb } from "../../worker/web-errors";
export { Sandbox } from "../../worker/sandbox";
import { browserRace } from "./browser-races";
import runtime, {
PersonalAgent as FixturePersonalAgent,
Conversation as FixtureConversation,
} from "./think-worker";
import type { Env } from "../../worker/personal-agent";
import { createWebTools } from "../../worker/web-tools";
import { searchWeb } from "../../worker/web-search";
import { searchResponse } from "./search-response";
import { createFetchTools } from "@cloudflare/think/tools/fetch";
export class PersonalAgent extends FixturePersonalAgent {}
const originalFetch = globalThis.fetch;
const calls: string[] = [];
let bodyAborts = 0;
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = new URL(
typeof input === "string"
? input
: input instanceof URL
? input.href
: input.url,
);
if (!url.hostname.endsWith(".fixture.example.com"))
return originalFetch(input, init);
calls.push(url.href);
const type =
url.pathname === "/binary"
? "application/pdf"
: url.pathname === "/plain" || url.pathname === "/large"
? "text/plain"
: "text/html";
if (url.pathname === "/redirect")
return new Response(null, {
status: 302,
headers: { location: "https://other.fixture.example.com/html" },
});
if (url.pathname === "/private")
return new Response(null, {
status: 302,
headers: { location: "http://127.0.0.1/secret" },
});
if (url.pathname === "/loop")
return new Response(null, { status: 302, headers: { location: url.href } });
if (url.pathname === "/error")
return new Response("private-error-marker", { status: 429 });
if (url.pathname === "/slow")
return new Response(
new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("
Slow "));
const abort = () => {
bodyAborts++;
controller.error(new DOMException("Aborted", "AbortError"));
};
init?.signal?.addEventListener("abort", abort, { once: true });
if (init?.signal?.aborted) abort();
},
}),
{ headers: { "content-type": "text/html" } },
);
const html =
"Fish & Chips 🐟Research title
A < B & C. Café 🐟
Another paragraph.
";
return new Response(
url.pathname === "/large"
? "🐟".repeat(200_000)
: url.pathname === "/plain"
? "Plain evidence"
: html,
{ headers: { "content-type": type } },
);
}) as typeof fetch;
const searches: {
model: string;
input: Record;
options: unknown;
}[] = [];
let searchAborts = 0,
searchBodyCancels = 0;
const fixtureAI = {
async run(model: string, input: Record, options: AiOptions) {
searches.push({
model,
input,
options: { ...options, signal: !!options.signal },
});
const query = input.input;
if (query === "slow" || query === "timeout")
return new Promise((resolve, reject) => {
const timer = setTimeout(() => resolve(new Response("late")), 120_000);
const abort = () => {
clearTimeout(timer);
searchAborts++;
reject(options.signal?.reason);
};
options.signal?.addEventListener("abort", abort, { once: true });
if (options.signal?.aborted) abort();
});
if (query === "slow-body")
return new Response(
new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('{"status":'));
},
cancel() {
searchBodyCancels++;
},
}),
);
if (typeof query === "string" && /^http-\d+$/.test(query))
return new Response("private-error-marker", {
status: Number(query.slice(5)),
});
if (query === "throws") throw new Error("private-error-marker");
if (query === "malformed")
return new Response("not JSON private-error-marker");
if (query === "oversized") return new Response("x".repeat(256_001));
const body: any = searchResponse(
"https://source.fixture.example.com/html",
query === "long" ? "🐟".repeat(200) : "Fixture & research",
query === "long"
? "🐟".repeat(4_000)
: "Generated search summary, not page text.",
);
if (query === "empty")
body.output = [
{
type: "web_search_call",
status: "completed",
action: { type: "search", sources: [] },
},
];
if (query === "unexpected") body.output = [];
if (query === "incomplete") body.status = "incomplete";
if (query === "failed-search") body.output[0].status = "failed";
if (query === "searching-only") body.output[0].status = "searching";
if (query === "mixed-search" || query === "mixed-empty") {
if (query === "mixed-empty") {
body.output[0].action.sources = [];
body.output.pop();
}
body.output.splice(1, 0, {
type: "web_search_call",
status: "searching",
action: {
type: "search",
sources: [
{ type: "url", url: "https://unfinished.fixture.example.com/" },
],
},
});
}
if (query === "missing-sources") {
delete body.output[0].action.sources;
body.output.pop();
}
if (query === "refusal")
body.output[1].content = [
{ type: "refusal", refusal: "private-error-marker" },
];
if (query === "normal") {
body.output[1].content[0].annotations.push(
{
type: "url_citation",
url: "https://source.fixture.example.com/html#duplicate",
title: "Duplicate",
},
...[
"http://127.0.0.1/secret",
"javascript:alert(1)",
"https://user:secret@example.com",
"/relative",
].map((url) => ({ type: "url_citation", url, title: "Unsafe" })),
);
}
if (query === "many")
body.output[0].action.sources.push(
...Array.from({ length: 8 }, (_, i) => ({
type: "url",
url: `https://source.fixture.example.com/${i}`,
})),
);
if (query === "unsafe-only") {
body.output.pop();
body.output[0].action.sources = [
{ type: "url", url: "http://127.0.0.1/secret" },
];
}
return Response.json(body);
},
} as unknown as Ai;
export class Conversation extends FixtureConversation {
getTools() {
return {
...super.getTools(),
...createWebTools(
{
fetch() {
throw new Error("Search must not acquire a browser");
},
},
fixtureAI,
(promise) => this.ctx.waitUntil(promise),
this.browserOptions(),
),
};
}
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
) {
const url = new URL(request.url);
if (url.pathname === "/__fixture/browser-race")
return Response.json(
await browserRace(url.searchParams.get("scenario") ?? "success"),
);
if (url.pathname === "/__fixture/web-status")
return Response.json({
calls,
bodyAborts,
searches,
searchAborts,
searchBodyCancels,
});
if (url.pathname === "/__fixture/search-pre-abort") {
const controller = new AbortController();
controller.abort();
return Response.json(
await runWeb(searchWeb(fixtureAI, "normal", 5), controller.signal),
);
}
if (url.pathname === "/__fixture/native-fetch-timeout") {
const native = createFetchTools({
allowlist: ["https://**"],
timeoutMs: 100,
response: "text",
}).fetch_url;
const result = await native.execute!(
{ url: "https://source.fixture.example.com/slow" },
{ toolCallId: "timeout", messages: [], context: undefined },
);
return Response.json({ result, bodyAborts });
}
return runtime.fetch(request, env, ctx);
},
};