import { Agent, getAgentByName } from "agents"; import * as Effect from "effect/Effect"; import { loadCustomerConfig, loadCustomerSecrets, type CustomerConfigBindings, } from "../../configuration/customer"; import { WebDeadline } from "../../worker/browser-session"; import { DEFAULT_MODEL, MODEL_CATALOG } from "../../worker/model-settings"; import { operationCall, operationResult } from "../../worker/operation-result"; import { PersonalMemory } from "../../worker/personal-memory"; import { PersonalModels } from "../../worker/personal-models"; import { browserRace } from "./browser-races"; interface Env extends CustomerConfigBindings { FAILURES: DurableObjectNamespace; } export class FailureFixture extends Agent { private readonly models = new PersonalModels( this.sql.bind(this), this.ctx.storage, () => loadCustomerConfig(this.env).effectiveInstallation, () => loadCustomerSecrets(this.env).sessionSecret, ); private readonly memory = new PersonalMemory(this.sql.bind(this)); onStart() { this.models.initialize(); this.memory.initialize(); this.models.updateModelSettings({ provider: "anthropic", model: MODEL_CATALOG.anthropic[0], }); this .sql`INSERT OR REPLACE INTO flarebot_provider_credentials VALUES ('anthropic', 'broken-envelope')`; if (!this.memory.listMemories().length) this.memory.addMemory("Prefers coffee"); } model() { return operationResult(this.models.readModelConfiguration()); } defaultModel() { return operationResult(this.models.readModelConfiguration(DEFAULT_MODEL)); } editMemory(version: number) { return operationResult( Effect.sync(() => { const fact = this.memory.listMemories()[0]; return this.memory.updateMemory(fact.id, "Prefers tea", version); }), ); } unexpected() { return operationResult( Effect.die( Object.assign(new Error("PRIVATE-RPC-FAILURE"), { code: "credential_unreadable", _tag: "AgentFailure", }), ), ); } } async function deadlines() { const deadline = new WebDeadline(10_000); const listeners = new Set(); const add = deadline.signal.addEventListener.bind(deadline.signal); const remove = deadline.signal.removeEventListener.bind(deadline.signal); deadline.signal.addEventListener = (type, listener, options) => { if (type === "abort") listeners.add(listener); add(type, listener, options); }; deadline.signal.removeEventListener = (type, listener, options) => { if (type === "abort") listeners.delete(listener); remove(type, listener, options); }; try { for (let i = 0; i < 25; i++) await Effect.runPromise(deadline.run(() => Promise.resolve(i))); const success = listeners.size; await Effect.runPromiseExit( deadline.run(() => Promise.reject(new Error("rejected"))), ); const rejected = listeners.size; await Effect.runPromiseExit( deadline.run(() => { throw new Error("thrown"); }), ); const thrown = listeners.size; const started = Promise.withResolvers(); const caller = new AbortController(); const pending = Effect.runPromiseExit( deadline.run(() => { started.resolve(); return new Promise(() => {}); }), { signal: caller.signal }, ); await started.promise; caller.abort(); await pending; return { success, rejected, thrown, interrupted: listeners.size }; } finally { deadline.dispose(); } } export default { async fetch(request: Request, env: Env) { const url = new URL(request.url); if (url.pathname === "/deadlines") return Response.json(await deadlines()); if (url.pathname === "/browser-race") return Response.json( await browserRace(url.searchParams.get("scenario")!), ); const parent = await getAgentByName(env.FAILURES, "failure-fixture"); const failures = []; for (const call of [ () => parent.model(), () => parent.editMemory(999), () => parent.unexpected(), ]) { try { await Effect.runPromise(operationCall(call)); failures.push(null); } catch (error) { failures.push(error instanceof Error ? error.message : "unexpected"); } } return Response.json({ raw: await parent.model(), failures, model: await Effect.runPromise( operationCall(() => parent.defaultModel()), ), memory: await Effect.runPromise( operationCall(() => parent.editMemory(1)), ), }); }, };