export { Sandbox } from "../../worker/sandbox"; import runtime from "../../worker/index"; import { Conversation as BaseConversation } from "./think-worker"; import { getAgentByName } from "agents"; import { PersonalAgent as BasePersonalAgent } from "./think-worker"; import { Conversation as RuntimeConversation } from "../../worker/conversation"; import { MockLanguageModelV3 } from "ai/test"; import { tool, type UIMessage } from "ai"; import { z } from "zod"; import type { Env } from "../../worker/personal-agent"; export class PersonalAgent extends BasePersonalAgent { fixturePreviousModel() { this .sql`UPDATE flarebot_model_settings SET configuration = ${JSON.stringify({ provider: "openrouter", model: "openai/gpt-5-mini", })} WHERE singleton = 1`; this.fixtureEnableOpenRouter(); } async seedChat(id: string, count: number) { const conversation = await this.subAgent(RuntimeConversation, id); await (conversation as unknown as Conversation).seedHistory(count); } } const rich = `## Research result\n\nThis is **clear** and *readable* with \`inline code\`.\n\n- First item\n- Second item\n\n> Quoted evidence\n\n\`\`\`javascript\nconst answer = "";\nconsole.log(answer);\n\`\`\`\n\n| Topic | Result |\n| --- | --- |\n| Native | Working |\n\n[Documentation](https://developers.cloudflare.com/agents/)\n\n\n\n[unsafe](javascript:alert(1)) ![remote](https://evil.invalid/pixel) [data](data:text/html,bad) [relative](//evil.invalid/)\n`; type Chunk = Awaited< ReturnType >["stream"] extends ReadableStream ? C : never; export class Conversation extends BaseConversation { protected createModel() { return new MockLanguageModelV3({ doStream: async ({ prompt, abortSignal }) => { const user = prompt.findLast( (message) => message.role === "user" && message.content.some( (part) => part.type === "text" && part.text.startsWith("chat:"), ), ); const text = user?.role === "user" ? user.content .filter((part) => part.type === "text") .map((part) => part.text) .join("") .slice(5) : "default"; const continuing = prompt.some( (message) => message.role === "user" && message.content.some( (part) => part.type === "text" && part.text === "Continue your previous response from exactly where it left off. Do not repeat any of it.", ), ); if (text === "error") throw new Error("Fixture unavailable"); if (text === "pending") await new Promise((resolve) => setTimeout(resolve, 2500)); const resource = text.startsWith("resource:") ? (JSON.parse(text.slice("resource:".length)) as { toolName: string; input: unknown; }) : undefined; const calls = (text === "rich" || resource) && prompt.at(-1)?.role !== "tool"; const tokens = text === "restart" && continuing ? ["Recovered remaining response."] : resource ? ["Resource saved."] : text === "rich" ? [rich] : [ "Starting ", ...Array.from({ length: 20 }, (_, i) => `${text}-${i} `), "finished.", ]; return { stream: new ReadableStream({ async start(controller) { const emit = (value: Chunk) => controller.enqueue(value); emit({ type: "stream-start", warnings: [] }); if (calls) { for (const call of resource ? [resource] : [false, true].map((fail) => ({ toolName: "fixtureSources", input: { fail }, }))) emit({ type: "tool-call", toolCallId: crypto.randomUUID(), toolName: call.toolName, input: JSON.stringify(call.input), }); } else { emit({ type: "text-start", id: "text" }); for (const token of tokens) { if (abortSignal?.aborted) { controller.close(); return; } emit({ type: "text-delta", id: "text", delta: token }); if (text !== "rich" && text !== "fast") await new Promise((resolve) => { const timer = setTimeout( resolve, text === "restart" ? 1500 : 150, ); abortSignal?.addEventListener( "abort", () => { clearTimeout(timer); resolve(); }, { once: true }, ); }); } emit({ type: "text-end", id: "text" }); } emit({ type: "finish", finishReason: { unified: calls ? "tool-calls" : "stop", raw: undefined, }, usage: { inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0, }, outputTokens: { total: 1, text: 1, reasoning: 0 }, }, }); controller.close(); }, }), }; }, }); } protected getToolActivityDescriptors() { return { ...super.getToolActivityDescriptors(), fixtureSources: { kind: "web" as const, label: "Read evidence", outcome: (output: unknown) => (output as { ok: boolean }).ok ? ("succeeded" as const) : ("failed" as const), outputSummary: (output: unknown) => (output as { ok: boolean }).ok ? "Evidence read" : "Page could not be read", }, }; } getTools() { return { ...super.getTools(), fixtureSources: tool({ description: "Fixture source evidence", inputSchema: z.object({ fail: z.boolean() }), execute: async ({ fail }) => { await new Promise((resolve) => setTimeout(resolve, 1500)); return fail ? { ok: false, message: "Safe tool failure ", } : { ok: true, sources: [ { id: "fixture-source", title: "Agents documentation", requestedUrl: "https://developers.cloudflare.com/agents/", finalUrl: "https://developers.cloudflare.com/agents/", fetchedAt: new Date().toISOString(), sourceKind: "page", content: "Native evidence", truncated: false, }, ], }; }, }), }; } async seedHistory(count: number) { const messages: UIMessage[] = Array.from({ length: count }, (_, index) => ({ id: `seed-${index}`, role: index % 2 ? "assistant" : "user", parts: [ { type: "text", text: `Saved message ${index}\n\n${"Long conversation content. ".repeat(12)}`, }, ], })); await this.saveMessages(messages); } } export default { async fetch(request, env, ctx) { if (new URL(request.url).pathname === "/__chat/previous-model") { const owner = await getAgentByName( env.PersonalAgent as unknown as DurableObjectNamespace, "personal", ); await owner.fixturePreviousModel(); return Response.json({ ok: true }); } if (new URL(request.url).pathname === "/__chat/seed") { const { id, count } = (await request.json()) as { id: string; count: number; }; const owner = await getAgentByName( env.PersonalAgent as unknown as DurableObjectNamespace, "personal", ); await owner.seedChat(id, count); return Response.json({ ok: true }); } return runtime.fetch(request, env, ctx); }, } satisfies ExportedHandler;