/** * HostBridgeLoopback — a WorkerEntrypoint that provides controlled workspace * access to extension Workers loaded via WorkerLoader. * * This is a loopback: the extension worker's `env.host` binding points here, * and each method call resolves the parent agent via `ctx.exports`, then * delegates to the agent's workspace proxy methods (`_hostReadFile`, etc.). * * Props carry serializable identifiers (agent class name, agent ID, and * permissions) so the binding survives across requests and hibernation. * * Users must re-export this class from their worker entry point: * * ```typescript * export { HostBridgeLoopback } from "@cloudflare/think/extensions"; * ``` * * @experimental Requires the `"experimental"` compatibility flag. */ import { WorkerEntrypoint } from "cloudflare:workers"; import type { ExtensionPermissions } from "./types"; export type HostBridgeLoopbackProps = { agentClassName: string; agentId: string; permissions: ExtensionPermissions; }; export class HostBridgeLoopback extends WorkerEntrypoint< Record, HostBridgeLoopbackProps > { private _permissions = this.ctx.props.permissions; private _getAgent() { const { agentClassName, agentId } = this.ctx.props; // @ts-expect-error — experimental: ctx.exports on WorkerEntrypoint const ns = this.ctx.exports[agentClassName] as DurableObjectNamespace; return ns.get(ns.idFromString(agentId)); } #requirePermission(level: "read" | "read-write"): void { const ws = this._permissions.workspace ?? "none"; if (ws === "none") { throw new Error("Extension error: no workspace permission declared"); } if (level === "read-write" && ws !== "read-write") { throw new Error( "Extension error: workspace write permission required, but only read granted" ); } } async readFile(path: string): Promise { this.#requirePermission("read"); return ( this._getAgent() as unknown as { _hostReadFile(path: string): Promise; } )._hostReadFile(path); } async writeFile(path: string, content: string): Promise { this.#requirePermission("read-write"); return ( this._getAgent() as unknown as { _hostWriteFile(path: string, content: string): Promise; } )._hostWriteFile(path, content); } async deleteFile(path: string): Promise { this.#requirePermission("read-write"); return ( this._getAgent() as unknown as { _hostDeleteFile(path: string): Promise; } )._hostDeleteFile(path); } async listFiles( dir: string ): Promise< Array<{ name: string; type: string; size: number; path: string }> > { this.#requirePermission("read"); return ( this._getAgent() as unknown as { _hostListFiles( dir: string ): Promise< Array<{ name: string; type: string; size: number; path: string }> >; } )._hostListFiles(dir); } }