@cloudflare/think 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -0
- package/dist/classPrivateFieldSet2-COLddhya.js +27 -0
- package/dist/classPrivateMethodInitSpec-CdQXQy1O.js +7 -0
- package/dist/extensions/index.d.ts +20 -0
- package/dist/extensions/index.js +62 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/index-BlcvIdWK.d.ts +171 -0
- package/dist/index-C4OTSwUW.d.ts +193 -0
- package/dist/manager-DIV0gQf3.js +214 -0
- package/dist/manager-DIV0gQf3.js.map +1 -0
- package/dist/message-builder.d.ts +51 -0
- package/dist/message-builder.js +217 -0
- package/dist/message-builder.js.map +1 -0
- package/dist/session/index.d.ts +22 -0
- package/dist/session/index.js +2 -0
- package/dist/session-C6ZU_1zM.js +507 -0
- package/dist/session-C6ZU_1zM.js.map +1 -0
- package/dist/think.d.ts +315 -0
- package/dist/think.js +701 -0
- package/dist/think.js.map +1 -0
- package/dist/tools/execute.d.ts +105 -0
- package/dist/tools/execute.js +64 -0
- package/dist/tools/execute.js.map +1 -0
- package/dist/tools/extensions.d.ts +67 -0
- package/dist/tools/extensions.js +85 -0
- package/dist/tools/extensions.js.map +1 -0
- package/dist/tools/workspace.d.ts +303 -0
- package/dist/tools/workspace.js +398 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/transport.d.ts +69 -0
- package/dist/transport.js +166 -0
- package/dist/transport.js.map +1 -0
- package/package.json +83 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"think.js","names":["agentContext"],"sources":["../src/sanitize.ts","../src/think.ts"],"sourcesContent":["/**\n * Message sanitization and row-size enforcement utilities.\n *\n * Shared by Think to ensure persistence\n * hygiene: stripping ephemeral provider metadata and compacting\n * oversized messages before writing to SQLite.\n */\n\nimport type { ProviderMetadata, ReasoningUIPart, UIMessage } from \"ai\";\n\n/** Shared encoder for UTF-8 byte length measurement */\nconst textEncoder = new TextEncoder();\n\n/** Maximum serialized message size before compaction (bytes). 1.8MB with headroom below SQLite's 2MB limit. */\nconst ROW_MAX_BYTES = 1_800_000;\n\n/** Measure UTF-8 byte length of a string. */\nfunction byteLength(s: string): number {\n return textEncoder.encode(s).byteLength;\n}\n\n/**\n * Sanitize a message for persistence by removing ephemeral provider-specific\n * data that should not be stored or sent back in subsequent requests.\n *\n * 1. Strips OpenAI ephemeral fields (itemId, reasoningEncryptedContent)\n * 2. Filters truly empty reasoning parts (no text, no remaining providerMetadata)\n */\nexport function sanitizeMessage(message: UIMessage): UIMessage {\n // Strip OpenAI-specific ephemeral data from all parts\n const strippedParts = message.parts.map((part) => {\n let sanitizedPart = part;\n\n if (\n \"providerMetadata\" in sanitizedPart &&\n sanitizedPart.providerMetadata &&\n typeof sanitizedPart.providerMetadata === \"object\" &&\n \"openai\" in sanitizedPart.providerMetadata\n ) {\n sanitizedPart = stripOpenAIMetadata(sanitizedPart, \"providerMetadata\");\n }\n\n if (\n \"callProviderMetadata\" in sanitizedPart &&\n sanitizedPart.callProviderMetadata &&\n typeof sanitizedPart.callProviderMetadata === \"object\" &&\n \"openai\" in sanitizedPart.callProviderMetadata\n ) {\n sanitizedPart = stripOpenAIMetadata(\n sanitizedPart,\n \"callProviderMetadata\"\n );\n }\n\n return sanitizedPart;\n }) as UIMessage[\"parts\"];\n\n // Filter out reasoning parts that are truly empty\n const sanitizedParts = strippedParts.filter((part) => {\n if (part.type === \"reasoning\") {\n const reasoningPart = part as ReasoningUIPart;\n if (!reasoningPart.text || reasoningPart.text.trim() === \"\") {\n if (\n \"providerMetadata\" in reasoningPart &&\n reasoningPart.providerMetadata &&\n typeof reasoningPart.providerMetadata === \"object\" &&\n Object.keys(reasoningPart.providerMetadata).length > 0\n ) {\n return true;\n }\n return false;\n }\n }\n return true;\n });\n\n return { ...message, parts: sanitizedParts };\n}\n\n/**\n * Strip OpenAI-specific ephemeral fields from a metadata object.\n */\nfunction stripOpenAIMetadata<T extends UIMessage[\"parts\"][number]>(\n part: T,\n metadataKey: \"providerMetadata\" | \"callProviderMetadata\"\n): T {\n const metadata = (part as Record<string, unknown>)[metadataKey] as {\n openai?: Record<string, unknown>;\n [key: string]: unknown;\n };\n\n if (!metadata?.openai) return part;\n\n const {\n itemId: _itemId,\n reasoningEncryptedContent: _rec,\n ...restOpenai\n } = metadata.openai;\n\n const hasOtherOpenaiFields = Object.keys(restOpenai).length > 0;\n const { openai: _openai, ...restMetadata } = metadata;\n\n let newMetadata: ProviderMetadata | undefined;\n if (hasOtherOpenaiFields) {\n newMetadata = { ...restMetadata, openai: restOpenai } as ProviderMetadata;\n } else if (Object.keys(restMetadata).length > 0) {\n newMetadata = restMetadata as ProviderMetadata;\n }\n\n const { [metadataKey]: _oldMeta, ...restPart } = part as Record<\n string,\n unknown\n >;\n\n if (newMetadata) {\n return { ...restPart, [metadataKey]: newMetadata } as T;\n }\n return restPart as T;\n}\n\n/**\n * Enforce SQLite row size limits by compacting tool outputs and text parts\n * when a serialized message exceeds the safety threshold (1.8MB).\n *\n * Compaction strategy:\n * 1. Compact tool outputs over 1KB (replace with summary)\n * 2. If still too big, truncate text parts from oldest to newest\n */\nexport function enforceRowSizeLimit(message: UIMessage): UIMessage {\n let json = JSON.stringify(message);\n let size = byteLength(json);\n if (size <= ROW_MAX_BYTES) return message;\n\n if (message.role !== \"assistant\") {\n return truncateTextParts(message);\n }\n\n // Pass 1: compact tool outputs\n const compactedParts = message.parts.map((part) => {\n if (\n \"output\" in part &&\n \"toolCallId\" in part &&\n \"state\" in part &&\n part.state === \"output-available\"\n ) {\n const outputJson = JSON.stringify((part as { output: unknown }).output);\n if (outputJson.length > 1000) {\n return {\n ...part,\n output:\n \"This tool output was too large to persist in storage \" +\n `(${outputJson.length} bytes). ` +\n \"If the user asks about this data, suggest re-running the tool. \" +\n `Preview: ${outputJson.slice(0, 500)}...`\n };\n }\n }\n return part;\n }) as UIMessage[\"parts\"];\n\n let result: UIMessage = { ...message, parts: compactedParts };\n\n json = JSON.stringify(result);\n size = byteLength(json);\n if (size <= ROW_MAX_BYTES) return result;\n\n // Pass 2: truncate text parts\n return truncateTextParts(result);\n}\n\n/**\n * Truncate text parts to fit within the row size limit.\n */\nfunction truncateTextParts(message: UIMessage): UIMessage {\n const parts = [...message.parts];\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n if (part.type === \"text\" && \"text\" in part) {\n const text = (part as { text: string }).text;\n if (text.length > 1000) {\n parts[i] = {\n ...part,\n text:\n `[Text truncated for storage (${text.length} chars). ` +\n `First 500 chars: ${text.slice(0, 500)}...]`\n } as UIMessage[\"parts\"][number];\n\n const candidate = { ...message, parts };\n if (byteLength(JSON.stringify(candidate)) <= ROW_MAX_BYTES) {\n break;\n }\n }\n }\n }\n\n return { ...message, parts };\n}\n","/**\n * Think — a unified Agent base class for chat sessions.\n *\n * Works as both a **top-level agent** (speaking the `cf_agent_chat_*`\n * WebSocket protocol to browser clients) and a **sub-agent** (called\n * via `chat()` over RPC from a parent agent).\n *\n * Each instance gets its own SQLite storage and runs the full chat\n * lifecycle:\n * store user message → assemble context → call LLM → stream events → persist response\n *\n * Uses SessionManager for message persistence, giving you branching and\n * compaction support for free.\n *\n * Override points:\n * - getModel() — return the LanguageModel to use\n * - getSystemPrompt() — return the system prompt\n * - getTools() — return the ToolSet for the agentic loop\n * - getMaxSteps() — max tool-call rounds per turn (default: 10)\n * - assembleContext() — customize context assembly from this.messages\n * - onChatMessage() — full control over inference (override the agentic loop)\n * - onChatError() — customize error handling\n *\n * Production features:\n * - WebSocket chat protocol (compatible with useAgentChat / useChat)\n * - Multi-session management (create, switch, list, delete, rename)\n * - Sub-agent RPC streaming via StreamCallback\n * - Abort/cancel support via AbortSignal\n * - Error handling with partial message persistence\n * - Message sanitization (strips OpenAI ephemeral metadata)\n * - Row size enforcement (compacts large tool outputs)\n * - Configurable storage bounds (maxPersistedMessages)\n * - Incremental persistence (skips unchanged messages)\n * - Richer input (accepts UIMessage or string)\n *\n * @experimental Requires the `\"experimental\"` compatibility flag.\n *\n * @example\n * ```typescript\n * import { Think } from \"@cloudflare/think\";\n * import { createWorkersAI } from \"workers-ai-provider\";\n * import { createWorkspaceTools } from \"@cloudflare/think/tools/workspace\";\n * import { Workspace } from \"agents/experimental/workspace\";\n *\n * export class ChatSession extends Think<Env> {\n * workspace = new Workspace(this);\n *\n * getModel() {\n * return createWorkersAI({ binding: this.env.AI })(\"@cf/meta/llama-3.3-70b-instruct-fp8-fast\");\n * }\n *\n * getTools() {\n * return createWorkspaceTools(this.workspace);\n * }\n * }\n * ```\n */\n\nimport type { LanguageModel, ModelMessage, ToolSet, UIMessage } from \"ai\";\nimport {\n convertToModelMessages,\n pruneMessages,\n stepCountIs,\n streamText\n} from \"ai\";\nimport {\n Agent,\n __DO_NOT_USE_WILL_BREAK__agentContext as agentContext\n} from \"agents\";\nimport type { Connection, WSMessage } from \"agents\";\nimport type { Workspace } from \"agents/experimental/workspace\";\nimport { withFibers } from \"agents/experimental/forever\";\nimport type { FiberMethods } from \"agents/experimental/forever\";\nimport { SessionManager } from \"./session/index\";\nimport type { Session } from \"./session/index\";\nimport { applyChunkToParts } from \"./message-builder\";\nimport type { StreamChunkData } from \"./message-builder\";\nimport { sanitizeMessage, enforceRowSizeLimit } from \"./sanitize\";\n\nexport type { Session } from \"./session/index\";\nexport type {\n FiberState,\n FiberRecoveryContext,\n FiberContext,\n FiberCompleteContext,\n FiberMethods\n} from \"agents/experimental/forever\";\n\n// ── Fiber base class ──────────────────────────────────────────────────\n// Think extends withFibers(Agent) so fiber methods (spawnFiber, etc.)\n// are always available on the prototype. The `fibers` flag controls\n// whether interrupted fibers are recovered on start.\n//\n// The type cast preserves Agent's generic constructor while adding\n// FiberMethods to the instance type, avoiding unsafe interface merging.\ntype ThinkBaseConstructor = {\n new <\n Env extends Cloudflare.Env = Cloudflare.Env,\n State = unknown,\n Props extends Record<string, unknown> = Record<string, unknown>\n >(\n ctx: DurableObjectState,\n env: Env\n ): Agent<Env, State, Props> & FiberMethods;\n};\n\nconst ThinkBase = withFibers(Agent) as unknown as ThinkBaseConstructor;\n\n// ── Wire protocol constants ────────────────────────────────────────\n// These string values are wire-compatible with @cloudflare/ai-chat's\n// MessageType enum. Defined locally to avoid a circular dependency.\nconst MSG_CHAT_MESSAGES = \"cf_agent_chat_messages\";\nconst MSG_CHAT_REQUEST = \"cf_agent_use_chat_request\";\nconst MSG_CHAT_RESPONSE = \"cf_agent_use_chat_response\";\nconst MSG_CHAT_CLEAR = \"cf_agent_chat_clear\";\nconst MSG_CHAT_CANCEL = \"cf_agent_chat_request_cancel\";\n\n/**\n * Callback interface for streaming chat events from a Think.\n *\n * Designed to work across the sub-agent RPC boundary — implement as\n * an RpcTarget in the parent agent and pass to `chat()`.\n *\n * Methods may return a Promise for async RPC callbacks.\n */\nexport interface StreamCallback {\n /** Called for each UIMessageChunk event during streaming. */\n onEvent(json: string): void | Promise<void>;\n /** Called when the stream completes successfully (not called on abort). */\n onDone(): void | Promise<void>;\n /** Called when an error occurs during streaming. */\n onError?(error: string): void | Promise<void>;\n}\n\n/**\n * Minimal interface for the result of `onChatMessage()`.\n * Must provide a `toUIMessageStream()` method that returns an\n * async-iterable stream of UI message chunks.\n *\n * The AI SDK's `streamText()` result satisfies this interface.\n */\nexport interface StreamableResult {\n toUIMessageStream(): AsyncIterable<unknown>;\n}\n\n/**\n * Options for a chat turn (sub-agent RPC entry point).\n */\nexport interface ChatOptions {\n /** AbortSignal — fires when the caller wants to cancel the turn. */\n signal?: AbortSignal;\n /** Extra tools to merge with getTools() for this turn only. */\n tools?: ToolSet;\n}\n\n/**\n * Options passed to the onChatMessage handler.\n */\nexport interface ChatMessageOptions {\n /** AbortSignal for cancelling the request */\n signal?: AbortSignal;\n /** Extra tools to merge with getTools() for this turn only. */\n tools?: ToolSet;\n}\n\n/**\n * A unified Agent base class for chat sessions.\n *\n * Works as both a top-level agent (WebSocket chat protocol) and a\n * sub-agent (RPC streaming via `chat()`).\n *\n * @experimental Requires the `\"experimental\"` compatibility flag.\n */\nexport class Think<\n Env extends Cloudflare.Env = Cloudflare.Env,\n Config = Record<string, unknown>\n> extends (ThinkBase as ThinkBaseConstructor)<Env> {\n /** Session manager — persistence layer with branching and compaction. */\n sessions!: SessionManager;\n\n /** In-memory messages for the current conversation. Authoritative after load. */\n messages: UIMessage[] = [];\n\n /**\n * Enable durable fiber recovery on start. Set to `true` to\n * automatically recover interrupted fibers when the DO restarts.\n *\n * Fiber methods (`spawnFiber()`, `stashFiber()`, etc.) are always\n * available — this flag only controls automatic recovery.\n *\n * @experimental\n */\n fibers = false;\n\n /**\n * Maximum number of messages to keep in storage per session.\n * When exceeded, oldest messages are deleted after each persist.\n * Set to `undefined` (default) for no limit.\n *\n * This controls storage only — it does not affect what's sent to the LLM.\n * Use `pruneMessages()` in `assembleContext()` to control LLM context.\n */\n maxPersistedMessages: number | undefined = undefined;\n\n /**\n * Cache of last-persisted JSON for each message ID.\n * Used for incremental persistence: skip SQL writes for unchanged messages.\n * @internal\n */\n private _persistedMessageCache: Map<string, string> = new Map();\n\n private _sessionId: string | null = null;\n private _abortControllers = new Map<string, AbortController>();\n private _clearGeneration = 0;\n\n // ── Dynamic config ──────────────────────────────────────────────\n\n #configTableReady = false;\n #configCache: Config | null = null;\n\n private _ensureConfigTable(): void {\n if (this.#configTableReady) return;\n this.sql`\n CREATE TABLE IF NOT EXISTS _think_config (\n key TEXT PRIMARY KEY, value TEXT NOT NULL\n )\n `;\n this.#configTableReady = true;\n }\n\n /**\n * Persist a typed configuration object.\n * Stored in SQLite so it survives restarts and hibernation.\n */\n configure(config: Config): void {\n this._ensureConfigTable();\n const json = JSON.stringify(config);\n this.sql`\n INSERT OR REPLACE INTO _think_config (key, value) VALUES ('config', ${json})\n `;\n this.#configCache = config;\n }\n\n /**\n * Read the persisted configuration, or null if never configured.\n */\n getConfig(): Config | null {\n if (this.#configCache) return this.#configCache;\n this._ensureConfigTable();\n const rows = this.sql<{ value: string }>`\n SELECT value FROM _think_config WHERE key = 'config'\n `;\n if (rows.length > 0) {\n this.#configCache = JSON.parse(rows[0].value) as Config;\n return this.#configCache;\n }\n return null;\n }\n\n onStart() {\n this.sessions = new SessionManager(this, {\n exec: (query, ...values) => {\n this.ctx.storage.sql.exec(query, ...values);\n }\n });\n const existing = this.sessions.list();\n if (existing.length > 0) {\n this._sessionId = existing[0].id;\n this.messages = this.sessions.getHistory(this._sessionId);\n this._rebuildPersistenceCache();\n }\n this._setupProtocolHandlers();\n\n if (this.fibers) {\n void this.checkFibers();\n }\n }\n\n // ── Override points ──────────────────────────────────────────────\n\n /**\n * Return the language model to use for inference.\n * Must be overridden by subclasses that rely on the default\n * `onChatMessage` implementation (the agentic loop).\n */\n getModel(): LanguageModel {\n throw new Error(\n \"Override getModel() to return a LanguageModel, or override onChatMessage() for full control.\"\n );\n }\n\n /**\n * Return the system prompt for the assistant.\n * Override to customize instructions.\n */\n getSystemPrompt(): string {\n return \"You are a helpful assistant.\";\n }\n\n /**\n * Return the tools available to the assistant.\n * Override to provide workspace tools, custom tools, etc.\n */\n getTools(): ToolSet {\n return {};\n }\n\n /**\n * Return the maximum number of tool-call steps per turn.\n */\n getMaxSteps(): number {\n return 10;\n }\n\n /**\n * Return the workspace instance for this session, or null if none.\n *\n * Override in subclasses that create a Workspace. Used by\n * HostBridgeLoopback to provide workspace access to extension Workers.\n */\n getWorkspace(): Workspace | null {\n return null;\n }\n\n // ── Workspace proxy methods (called by HostBridgeLoopback via RPC) ──\n\n async _hostReadFile(path: string): Promise<string | null> {\n const ws = this.getWorkspace();\n if (!ws) throw new Error(\"No workspace available on this agent\");\n return ws.readFile(path);\n }\n\n async _hostWriteFile(path: string, content: string): Promise<void> {\n const ws = this.getWorkspace();\n if (!ws) throw new Error(\"No workspace available on this agent\");\n await ws.writeFile(path, content);\n }\n\n async _hostDeleteFile(path: string): Promise<boolean> {\n const ws = this.getWorkspace();\n if (!ws) throw new Error(\"No workspace available on this agent\");\n return ws.deleteFile(path);\n }\n\n _hostListFiles(\n dir: string\n ): Array<{ name: string; type: string; size: number; path: string }> {\n const ws = this.getWorkspace();\n if (!ws) throw new Error(\"No workspace available on this agent\");\n return ws.readDir(dir);\n }\n\n /**\n * Assemble the model messages from the current conversation history.\n * Override to customize context assembly (e.g. inject memory,\n * project context, or apply compaction).\n */\n async assembleContext(): Promise<ModelMessage[]> {\n return pruneMessages({\n messages: await convertToModelMessages(this.messages),\n toolCalls: \"before-last-2-messages\"\n });\n }\n\n /**\n * Handle a chat turn and return the streaming result.\n *\n * The default implementation runs the agentic loop:\n * 1. Assemble context from `this.messages`\n * 2. Call `streamText` with the model, system prompt, tools, and step limit\n *\n * Override for full control over inference (e.g. different models per turn,\n * RAG pipelines, routing to specialized sub-agents, etc.).\n *\n * When this is called, `this.messages` already contains the user's\n * latest message persisted to the current session.\n *\n * @returns A result with `toUIMessageStream()` — AI SDK's `streamText()`\n * return value satisfies this interface.\n */\n async onChatMessage(options?: ChatMessageOptions): Promise<StreamableResult> {\n const baseTools = this.getTools();\n const tools = options?.tools\n ? { ...baseTools, ...options.tools }\n : baseTools;\n return streamText({\n model: this.getModel(),\n system: this.getSystemPrompt(),\n messages: await this.assembleContext(),\n tools,\n stopWhen: stepCountIs(this.getMaxSteps()),\n abortSignal: options?.signal\n });\n }\n\n /**\n * Handle an error that occurred during a chat turn.\n * Override to customize error handling (e.g. logging, metrics).\n *\n * @param error The error that occurred\n * @returns The error (or a wrapped version) to propagate\n */\n onChatError(error: unknown): unknown {\n return error;\n }\n\n // ── Sub-agent RPC entry point ───────────────────────────────────\n\n /**\n * Run a chat turn: persist the user message, run the agentic loop,\n * stream UIMessageChunk events via callback, and persist the\n * assistant's response.\n *\n * On error or abort, the partial assistant message is still persisted\n * so the user doesn't lose context.\n *\n * @param userMessage The user's message (string or UIMessage for multi-modal)\n * @param callback Streaming callback (typically an RpcTarget from the parent)\n * @param options Optional chat options (e.g. AbortSignal)\n */\n async chat(\n userMessage: string | UIMessage,\n callback: StreamCallback,\n options?: ChatOptions\n ): Promise<void> {\n // Ensure a session exists\n if (!this._sessionId) {\n const session = this.sessions.create(\"default\");\n this._sessionId = session.id;\n }\n\n // Persist user message\n const userMsg: UIMessage =\n typeof userMessage === \"string\"\n ? {\n id: crypto.randomUUID(),\n role: \"user\",\n parts: [{ type: \"text\", text: userMessage }]\n }\n : userMessage;\n\n this.sessions.append(this._sessionId, userMsg);\n this.messages = this.sessions.getHistory(this._sessionId);\n\n // Build assistant message from stream chunks\n const assistantMsg: UIMessage = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n parts: []\n };\n\n try {\n // Run the agentic loop (or custom override)\n const result = await this.onChatMessage({\n signal: options?.signal,\n tools: options?.tools\n });\n\n // Stream UIMessageChunk events via callback\n let aborted = false;\n for await (const chunk of result.toUIMessageStream()) {\n if (options?.signal?.aborted) {\n aborted = true;\n break;\n }\n applyChunkToParts(\n assistantMsg.parts,\n chunk as unknown as StreamChunkData\n );\n await callback.onEvent(JSON.stringify(chunk));\n }\n\n // Persist assistant message (sanitized + size-enforced)\n this._persistAssistantMessage(assistantMsg);\n\n // Only signal completion if not aborted\n if (!aborted) {\n await callback.onDone();\n }\n } catch (error) {\n // Persist partial assistant message so context isn't lost\n if (assistantMsg.parts.length > 0) {\n this._persistAssistantMessage(assistantMsg);\n }\n\n const wrapped = this.onChatError(error);\n const errorMessage =\n wrapped instanceof Error ? wrapped.message : String(wrapped);\n\n if (callback.onError) {\n await callback.onError(errorMessage);\n } else {\n // Re-throw if no error callback — caller must handle it\n throw wrapped;\n }\n }\n }\n\n // ── Session management ─────────────────────────────────────────\n\n getSessions(): Session[] {\n return this.sessions.list();\n }\n\n createSession(name: string): Session {\n const session = this.sessions.create(name);\n this._sessionId = session.id;\n this.messages = [];\n this._broadcastMessages();\n return session;\n }\n\n switchSession(sessionId: string): UIMessage[] {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n this._sessionId = sessionId;\n this.messages = this.sessions.getHistory(sessionId);\n this._broadcastMessages();\n return this.messages;\n }\n\n deleteSession(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n this.sessions.delete(sessionId);\n if (this._sessionId === sessionId) {\n this._sessionId = null;\n this.messages = [];\n this._broadcastMessages();\n }\n }\n\n renameSession(sessionId: string, name: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) {\n throw new Error(`Session not found: ${sessionId}`);\n }\n this.sessions.rename(sessionId, name);\n }\n\n getCurrentSessionId(): string | null {\n return this._sessionId;\n }\n\n // ── Message access ───────────────────────────────────────────────\n\n /**\n * Get the current session info, or null if no session exists yet.\n */\n getSession(): Session | null {\n if (!this._sessionId) return null;\n return this.sessions.get(this._sessionId);\n }\n\n /**\n * Get the conversation history as UIMessage[].\n */\n getHistory(): UIMessage[] {\n if (!this._sessionId) return [];\n return this.sessions.getHistory(this._sessionId);\n }\n\n /**\n * Get the total message count for this session.\n */\n getMessageCount(): number {\n if (!this._sessionId) return 0;\n return this.sessions.getMessageCount(this._sessionId);\n }\n\n /**\n * Clear all messages from this session (preserves the session itself).\n */\n clearMessages(): void {\n if (!this._sessionId) return;\n this.sessions.clearMessages(this._sessionId);\n this.messages = [];\n this._persistedMessageCache.clear();\n }\n\n // ── WebSocket protocol ──────────────────────────────────────────\n\n /**\n * Wrap onMessage and onRequest to intercept the chat protocol.\n * Unrecognized messages are forwarded to the user's handlers.\n * @internal\n */\n private _setupProtocolHandlers() {\n const _onMessage = this.onMessage.bind(this);\n this.onMessage = async (connection: Connection, message: WSMessage) => {\n if (typeof message === \"string\") {\n try {\n const data = JSON.parse(message) as Record<string, unknown>;\n if (await this._handleProtocol(connection, data)) return;\n } catch {\n // Not JSON — fall through to user handler\n }\n }\n return _onMessage(connection, message);\n };\n\n const _onRequest = this.onRequest.bind(this);\n this.onRequest = async (request: Request) => {\n const url = new URL(request.url);\n if (\n url.pathname === \"/get-messages\" ||\n url.pathname.endsWith(\"/get-messages\")\n ) {\n const sessionId = url.searchParams.get(\"sessionId\");\n if (sessionId) {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return Response.json(\n { error: \"Session not found\" },\n { status: 404 }\n );\n }\n return Response.json(this.sessions.getHistory(sessionId));\n }\n return Response.json(this.messages);\n }\n return _onRequest(request);\n };\n }\n\n /**\n * Route an incoming WebSocket message to the appropriate handler.\n * Returns true if the message was handled by the protocol.\n * @internal\n */\n private async _handleProtocol(\n connection: Connection,\n data: Record<string, unknown>\n ): Promise<boolean> {\n const type = data.type as string;\n\n if (type === MSG_CHAT_REQUEST) {\n const init = data.init as { method?: string; body?: string } | undefined;\n if (init?.method === \"POST\") {\n await this._handleChatRequest(connection, data);\n return true;\n }\n }\n\n if (type === MSG_CHAT_CLEAR) {\n this._handleClear();\n return true;\n }\n\n if (type === MSG_CHAT_CANCEL) {\n this._handleCancel(data.id as string);\n return true;\n }\n\n return false;\n }\n\n /**\n * Handle CF_AGENT_USE_CHAT_REQUEST:\n * 1. Parse incoming messages\n * 2. Ensure a session exists\n * 3. Persist user messages to session\n * 4. Call onChatMessage\n * 5. Stream response back to clients\n * 6. Persist assistant message to session\n * @internal\n */\n private async _handleChatRequest(\n connection: Connection,\n data: Record<string, unknown>\n ) {\n const init = data.init as { body?: string };\n if (!init?.body) return;\n\n let parsed: { messages?: UIMessage[] };\n try {\n parsed = JSON.parse(init.body) as { messages?: UIMessage[] };\n } catch {\n return;\n }\n\n const incomingMessages = parsed.messages;\n if (!Array.isArray(incomingMessages)) return;\n\n // Ensure a session exists\n if (!this._sessionId) {\n const session = this.sessions.create(\"New Chat\");\n this._sessionId = session.id;\n }\n\n // Persist incoming messages to session (idempotent via INSERT OR IGNORE)\n this.sessions.appendAll(this._sessionId, incomingMessages);\n\n // Reload from session (authoritative)\n this.messages = this.sessions.getHistory(this._sessionId);\n\n // Broadcast updated messages to other connections\n this._broadcastMessages([connection.id]);\n\n // Set up abort controller\n const requestId = data.id as string;\n const abortController = new AbortController();\n this._abortControllers.set(requestId, abortController);\n\n try {\n await this.keepAliveWhile(async () => {\n const result = await agentContext.run(\n { agent: this, connection, request: undefined, email: undefined },\n () =>\n this.onChatMessage({\n signal: abortController.signal\n })\n );\n\n if (result) {\n await this._streamResult(requestId, result, abortController.signal);\n } else {\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: \"No response was generated.\",\n done: true\n });\n }\n });\n } catch (error) {\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: error instanceof Error ? error.message : \"Error\",\n done: true,\n error: true\n });\n } finally {\n this._abortControllers.delete(requestId);\n }\n }\n\n /**\n * Handle CF_AGENT_CHAT_CLEAR: abort streams, clear current session messages.\n * @internal\n */\n private _handleClear() {\n for (const controller of this._abortControllers.values()) {\n controller.abort();\n }\n this._abortControllers.clear();\n\n if (this._sessionId) {\n this.sessions.clearMessages(this._sessionId);\n }\n\n this.messages = [];\n this._persistedMessageCache.clear();\n this._clearGeneration++;\n this._broadcast({ type: MSG_CHAT_CLEAR });\n }\n\n /**\n * Handle CF_AGENT_CHAT_REQUEST_CANCEL: abort a specific request.\n * @internal\n */\n private _handleCancel(requestId: string) {\n const controller = this._abortControllers.get(requestId);\n if (controller) {\n controller.abort();\n }\n }\n\n /**\n * Iterate a StreamableResult, broadcast chunks to clients,\n * build a UIMessage, and persist it to the session.\n * @internal\n */\n private async _streamResult(\n requestId: string,\n result: StreamableResult,\n abortSignal?: AbortSignal\n ) {\n const clearGen = this._clearGeneration;\n\n const message: UIMessage = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n parts: []\n };\n\n let doneSent = false;\n\n try {\n for await (const chunk of result.toUIMessageStream()) {\n if (abortSignal?.aborted) break;\n\n const data = chunk as StreamChunkData;\n\n // Build UIMessage from stream events\n const handled = applyChunkToParts(message.parts, data);\n\n if (!handled) {\n // Handle metadata events that applyChunkToParts doesn't cover\n switch (data.type) {\n case \"start\": {\n if (data.messageId != null) {\n message.id = data.messageId;\n }\n if (data.messageMetadata != null) {\n message.metadata = message.metadata\n ? { ...message.metadata, ...data.messageMetadata }\n : data.messageMetadata;\n }\n break;\n }\n case \"finish\":\n case \"message-metadata\": {\n if (data.messageMetadata != null) {\n message.metadata = message.metadata\n ? { ...message.metadata, ...data.messageMetadata }\n : data.messageMetadata;\n }\n break;\n }\n case \"error\": {\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: data.errorText ?? JSON.stringify(data),\n done: false,\n error: true\n });\n continue;\n }\n }\n }\n\n // Broadcast chunk to clients\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: JSON.stringify(chunk),\n done: false\n });\n }\n\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: \"\",\n done: true\n });\n doneSent = true;\n } catch (error) {\n if (!doneSent) {\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: error instanceof Error ? error.message : \"Stream error\",\n done: true,\n error: true\n });\n doneSent = true;\n }\n } finally {\n if (!doneSent) {\n this._broadcast({\n type: MSG_CHAT_RESPONSE,\n id: requestId,\n body: \"\",\n done: true\n });\n }\n }\n\n // Persist the assistant message to the session (sanitized + size-enforced).\n // Skip if a clear happened during this stream (clearGeneration changed).\n // Wrapped in try-catch: the stream done message was already sent above,\n // so a persistence error must not propagate to the outer catch (which\n // would broadcast a second done message).\n if (\n message.parts.length > 0 &&\n this._sessionId &&\n this._clearGeneration === clearGen\n ) {\n try {\n this._persistAssistantMessage(message);\n this._broadcastMessages();\n } catch (e) {\n console.error(\"Failed to persist assistant message:\", e);\n }\n }\n }\n\n // ── Persistence internals ────────────────────────────────────────\n\n /**\n * Persist an assistant message with sanitization, size enforcement,\n * and incremental persistence.\n * @internal\n */\n private _persistAssistantMessage(msg: UIMessage): void {\n if (!this._sessionId) return;\n\n const sanitized = sanitizeMessage(msg);\n const safe = enforceRowSizeLimit(sanitized);\n const json = JSON.stringify(safe);\n\n // Skip SQL write if unchanged (incremental persistence)\n if (this._persistedMessageCache.get(safe.id) !== json) {\n this.sessions.upsert(this._sessionId, safe);\n this._persistedMessageCache.set(safe.id, json);\n }\n\n // Enforce storage bounds\n if (this.maxPersistedMessages != null) {\n this._enforceMaxPersistedMessages();\n }\n\n this.messages = this.sessions.getHistory(this._sessionId);\n }\n\n /**\n * Rebuild the persistence cache from current messages.\n * Called on startup to enable incremental persistence.\n * @internal\n */\n private _rebuildPersistenceCache(): void {\n this._persistedMessageCache.clear();\n for (const msg of this.messages) {\n this._persistedMessageCache.set(msg.id, JSON.stringify(msg));\n }\n }\n\n /**\n * Delete oldest messages on the current branch when count exceeds\n * maxPersistedMessages. Uses path-based count (not total across all\n * branches) and individual deletes to preserve branch structure.\n * @internal\n */\n private _enforceMaxPersistedMessages(): void {\n if (this.maxPersistedMessages == null || !this._sessionId) return;\n\n // Use current branch history, not total message count across all branches\n const history = this.sessions.getHistory(this._sessionId);\n if (history.length <= this.maxPersistedMessages) return;\n\n const excess = history.length - this.maxPersistedMessages;\n const toRemove = history.slice(0, excess);\n\n // Delete individual messages — preserves branch structure\n this.sessions.deleteMessages(toRemove.map((m) => m.id));\n for (const msg of toRemove) {\n this._persistedMessageCache.delete(msg.id);\n }\n }\n\n /**\n * Broadcast a JSON message to all connected clients.\n * @internal\n */\n private _broadcast(message: Record<string, unknown>, exclude?: string[]) {\n this.broadcast(JSON.stringify(message), exclude);\n }\n\n /**\n * Broadcast the current message list to all connected clients.\n * @internal\n */\n private _broadcastMessages(exclude?: string[]) {\n this._broadcast(\n { type: MSG_CHAT_MESSAGES, messages: this.messages },\n exclude\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as ai from "ai";
|
|
2
|
+
import { ToolSet } from "ai";
|
|
3
|
+
import { Executor } from "@cloudflare/codemode";
|
|
4
|
+
|
|
5
|
+
//#region src/tools/execute.d.ts
|
|
6
|
+
interface CreateExecuteToolOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The tools available inside the sandboxed code.
|
|
9
|
+
* These are exposed as `codemode.toolName(args)` in the sandbox.
|
|
10
|
+
*
|
|
11
|
+
* Typically this is the workspace tools from `createWorkspaceTools()`,
|
|
12
|
+
* but can include any AI SDK tools with `execute` functions.
|
|
13
|
+
*/
|
|
14
|
+
tools: ToolSet;
|
|
15
|
+
/**
|
|
16
|
+
* The executor that runs the generated code.
|
|
17
|
+
*
|
|
18
|
+
* Use `DynamicWorkerExecutor` for Cloudflare Workers (requires a
|
|
19
|
+
* `worker_loaders` binding in wrangler.jsonc), or implement the
|
|
20
|
+
* `Executor` interface for other runtimes.
|
|
21
|
+
*
|
|
22
|
+
* If not provided, you must provide a `loader` instead.
|
|
23
|
+
*/
|
|
24
|
+
executor?: Executor;
|
|
25
|
+
/**
|
|
26
|
+
* WorkerLoader binding for creating a `DynamicWorkerExecutor`.
|
|
27
|
+
* This is a convenience alternative to passing a full `executor`.
|
|
28
|
+
*
|
|
29
|
+
* Requires `"worker_loaders": [{ "binding": "LOADER" }]` in wrangler.jsonc.
|
|
30
|
+
*/
|
|
31
|
+
loader?: WorkerLoader;
|
|
32
|
+
/**
|
|
33
|
+
* Timeout in milliseconds for code execution. Defaults to 30000 (30s).
|
|
34
|
+
* Only used when `loader` is provided (ignored if `executor` is given).
|
|
35
|
+
*/
|
|
36
|
+
timeout?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Controls outbound network access from sandboxed code.
|
|
39
|
+
* - `null` (default): fetch() and connect() throw — sandbox is fully isolated.
|
|
40
|
+
* - `undefined`: inherits parent Worker's network access.
|
|
41
|
+
* - A `Fetcher`: all outbound requests route through this handler.
|
|
42
|
+
*
|
|
43
|
+
* Only used when `loader` is provided (ignored if `executor` is given).
|
|
44
|
+
*/
|
|
45
|
+
globalOutbound?: Fetcher | null;
|
|
46
|
+
/**
|
|
47
|
+
* Custom tool description. Use `{{types}}` as a placeholder for the
|
|
48
|
+
* auto-generated TypeScript type definitions of the available tools.
|
|
49
|
+
*/
|
|
50
|
+
description?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a code execution tool that lets the LLM write and run JavaScript
|
|
54
|
+
* with access to your tools in a sandboxed environment.
|
|
55
|
+
*
|
|
56
|
+
* The LLM sees typed `codemode.*` functions and writes code that calls them.
|
|
57
|
+
* Code runs in an isolated Worker via `DynamicWorkerExecutor` — external
|
|
58
|
+
* network access is blocked by default.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { createWorkspaceTools, createExecuteTool } from "@cloudflare/think";
|
|
63
|
+
*
|
|
64
|
+
* getTools() {
|
|
65
|
+
* const workspaceTools = createWorkspaceTools(this.workspace);
|
|
66
|
+
* return {
|
|
67
|
+
* ...workspaceTools,
|
|
68
|
+
* execute: createExecuteTool({
|
|
69
|
+
* tools: workspaceTools,
|
|
70
|
+
* loader: this.env.LOADER,
|
|
71
|
+
* }),
|
|
72
|
+
* };
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example Using a custom executor
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
|
79
|
+
*
|
|
80
|
+
* const executor = new DynamicWorkerExecutor({
|
|
81
|
+
* loader: this.env.LOADER,
|
|
82
|
+
* timeout: 60000,
|
|
83
|
+
* globalOutbound: this.env.OUTBOUND,
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* getTools() {
|
|
87
|
+
* return {
|
|
88
|
+
* execute: createExecuteTool({ tools: myTools, executor }),
|
|
89
|
+
* };
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function createExecuteTool(options: CreateExecuteToolOptions): ai.Tool<
|
|
94
|
+
{
|
|
95
|
+
code: string;
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
code: string;
|
|
99
|
+
result: unknown;
|
|
100
|
+
logs?: string[];
|
|
101
|
+
}
|
|
102
|
+
>;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { CreateExecuteToolOptions, createExecuteTool };
|
|
105
|
+
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createCodeTool } from "@cloudflare/codemode/ai";
|
|
2
|
+
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
|
3
|
+
//#region src/tools/execute.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a code execution tool that lets the LLM write and run JavaScript
|
|
6
|
+
* with access to your tools in a sandboxed environment.
|
|
7
|
+
*
|
|
8
|
+
* The LLM sees typed `codemode.*` functions and writes code that calls them.
|
|
9
|
+
* Code runs in an isolated Worker via `DynamicWorkerExecutor` — external
|
|
10
|
+
* network access is blocked by default.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createWorkspaceTools, createExecuteTool } from "@cloudflare/think";
|
|
15
|
+
*
|
|
16
|
+
* getTools() {
|
|
17
|
+
* const workspaceTools = createWorkspaceTools(this.workspace);
|
|
18
|
+
* return {
|
|
19
|
+
* ...workspaceTools,
|
|
20
|
+
* execute: createExecuteTool({
|
|
21
|
+
* tools: workspaceTools,
|
|
22
|
+
* loader: this.env.LOADER,
|
|
23
|
+
* }),
|
|
24
|
+
* };
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example Using a custom executor
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
|
31
|
+
*
|
|
32
|
+
* const executor = new DynamicWorkerExecutor({
|
|
33
|
+
* loader: this.env.LOADER,
|
|
34
|
+
* timeout: 60000,
|
|
35
|
+
* globalOutbound: this.env.OUTBOUND,
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* getTools() {
|
|
39
|
+
* return {
|
|
40
|
+
* execute: createExecuteTool({ tools: myTools, executor }),
|
|
41
|
+
* };
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function createExecuteTool(options) {
|
|
46
|
+
const { tools, description } = options;
|
|
47
|
+
let executor;
|
|
48
|
+
if (options.executor) executor = options.executor;
|
|
49
|
+
else if (options.loader) executor = new DynamicWorkerExecutor({
|
|
50
|
+
loader: options.loader,
|
|
51
|
+
timeout: options.timeout,
|
|
52
|
+
globalOutbound: options.globalOutbound
|
|
53
|
+
});
|
|
54
|
+
else throw new Error("createExecuteTool requires either an `executor` or a `loader` (WorkerLoader binding).");
|
|
55
|
+
return createCodeTool({
|
|
56
|
+
tools,
|
|
57
|
+
executor,
|
|
58
|
+
description
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { createExecuteTool };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.js","names":[],"sources":["../../src/tools/execute.ts"],"sourcesContent":["import type { ToolSet } from \"ai\";\nimport { createCodeTool } from \"@cloudflare/codemode/ai\";\nimport { DynamicWorkerExecutor } from \"@cloudflare/codemode\";\nimport type { Executor } from \"@cloudflare/codemode\";\n\nexport interface CreateExecuteToolOptions {\n /**\n * The tools available inside the sandboxed code.\n * These are exposed as `codemode.toolName(args)` in the sandbox.\n *\n * Typically this is the workspace tools from `createWorkspaceTools()`,\n * but can include any AI SDK tools with `execute` functions.\n */\n tools: ToolSet;\n\n /**\n * The executor that runs the generated code.\n *\n * Use `DynamicWorkerExecutor` for Cloudflare Workers (requires a\n * `worker_loaders` binding in wrangler.jsonc), or implement the\n * `Executor` interface for other runtimes.\n *\n * If not provided, you must provide a `loader` instead.\n */\n executor?: Executor;\n\n /**\n * WorkerLoader binding for creating a `DynamicWorkerExecutor`.\n * This is a convenience alternative to passing a full `executor`.\n *\n * Requires `\"worker_loaders\": [{ \"binding\": \"LOADER\" }]` in wrangler.jsonc.\n */\n loader?: WorkerLoader;\n\n /**\n * Timeout in milliseconds for code execution. Defaults to 30000 (30s).\n * Only used when `loader` is provided (ignored if `executor` is given).\n */\n timeout?: number;\n\n /**\n * Controls outbound network access from sandboxed code.\n * - `null` (default): fetch() and connect() throw — sandbox is fully isolated.\n * - `undefined`: inherits parent Worker's network access.\n * - A `Fetcher`: all outbound requests route through this handler.\n *\n * Only used when `loader` is provided (ignored if `executor` is given).\n */\n globalOutbound?: Fetcher | null;\n\n /**\n * Custom tool description. Use `{{types}}` as a placeholder for the\n * auto-generated TypeScript type definitions of the available tools.\n */\n description?: string;\n}\n\n/**\n * Create a code execution tool that lets the LLM write and run JavaScript\n * with access to your tools in a sandboxed environment.\n *\n * The LLM sees typed `codemode.*` functions and writes code that calls them.\n * Code runs in an isolated Worker via `DynamicWorkerExecutor` — external\n * network access is blocked by default.\n *\n * @example\n * ```ts\n * import { createWorkspaceTools, createExecuteTool } from \"@cloudflare/think\";\n *\n * getTools() {\n * const workspaceTools = createWorkspaceTools(this.workspace);\n * return {\n * ...workspaceTools,\n * execute: createExecuteTool({\n * tools: workspaceTools,\n * loader: this.env.LOADER,\n * }),\n * };\n * }\n * ```\n *\n * @example Using a custom executor\n * ```ts\n * import { DynamicWorkerExecutor } from \"@cloudflare/codemode\";\n *\n * const executor = new DynamicWorkerExecutor({\n * loader: this.env.LOADER,\n * timeout: 60000,\n * globalOutbound: this.env.OUTBOUND,\n * });\n *\n * getTools() {\n * return {\n * execute: createExecuteTool({ tools: myTools, executor }),\n * };\n * }\n * ```\n */\nexport function createExecuteTool(options: CreateExecuteToolOptions) {\n const { tools, description } = options;\n\n let executor: Executor;\n if (options.executor) {\n executor = options.executor;\n } else if (options.loader) {\n executor = new DynamicWorkerExecutor({\n loader: options.loader,\n timeout: options.timeout,\n globalOutbound: options.globalOutbound\n });\n } else {\n throw new Error(\n \"createExecuteTool requires either an `executor` or a `loader` (WorkerLoader binding).\"\n );\n }\n\n return createCodeTool({ tools, executor, description });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,SAAgB,kBAAkB,SAAmC;CACnE,MAAM,EAAE,OAAO,gBAAgB;CAE/B,IAAI;AACJ,KAAI,QAAQ,SACV,YAAW,QAAQ;UACV,QAAQ,OACjB,YAAW,IAAI,sBAAsB;EACnC,QAAQ,QAAQ;EAChB,SAAS,QAAQ;EACjB,gBAAgB,QAAQ;EACzB,CAAC;KAEF,OAAM,IAAI,MACR,wFACD;AAGH,QAAO,eAAe;EAAE;EAAO;EAAU;EAAa,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
r as ExtensionManager,
|
|
3
|
+
s as ExtensionPermissions
|
|
4
|
+
} from "../index-BlcvIdWK.js";
|
|
5
|
+
import * as ai from "ai";
|
|
6
|
+
|
|
7
|
+
//#region src/tools/extensions.d.ts
|
|
8
|
+
interface ExtensionToolsOptions {
|
|
9
|
+
manager: ExtensionManager;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create AI SDK tools for managing extensions at runtime.
|
|
13
|
+
*
|
|
14
|
+
* These tools let the LLM load and list extensions dynamically.
|
|
15
|
+
* Loaded extensions expose their own tools on the next inference
|
|
16
|
+
* turn. Unloading is a client-side action (via @callable RPC).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const extensions = new ExtensionManager({ loader: this.env.LOADER, workspace: this.workspace });
|
|
21
|
+
* const extensionTools = createExtensionTools({ manager: extensions });
|
|
22
|
+
*
|
|
23
|
+
* getTools() {
|
|
24
|
+
* return {
|
|
25
|
+
* ...createWorkspaceTools(this.workspace),
|
|
26
|
+
* ...extensionTools,
|
|
27
|
+
* ...extensions.getTools(), // tools from loaded extensions
|
|
28
|
+
* };
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function createExtensionTools(options: ExtensionToolsOptions): {
|
|
33
|
+
load_extension: ai.Tool<
|
|
34
|
+
{
|
|
35
|
+
name: string;
|
|
36
|
+
version: string;
|
|
37
|
+
source: string;
|
|
38
|
+
description?: string | undefined;
|
|
39
|
+
workspace_access?: "none" | "read" | "read-write" | undefined;
|
|
40
|
+
network?: string[] | undefined;
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
loaded: boolean;
|
|
44
|
+
name: string;
|
|
45
|
+
prefix: string;
|
|
46
|
+
version: string;
|
|
47
|
+
tools: string[];
|
|
48
|
+
message: string;
|
|
49
|
+
}
|
|
50
|
+
>;
|
|
51
|
+
list_extensions: ai.Tool<
|
|
52
|
+
Record<string, never>,
|
|
53
|
+
{
|
|
54
|
+
count: number;
|
|
55
|
+
extensions: {
|
|
56
|
+
name: string;
|
|
57
|
+
version: string;
|
|
58
|
+
description: string | undefined;
|
|
59
|
+
tools: string[];
|
|
60
|
+
permissions: ExtensionPermissions;
|
|
61
|
+
}[];
|
|
62
|
+
}
|
|
63
|
+
>;
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { ExtensionToolsOptions, createExtensionTools };
|
|
67
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { n as sanitizeName } from "../manager-DIV0gQf3.js";
|
|
2
|
+
import { tool } from "ai";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/tools/extensions.ts
|
|
5
|
+
/**
|
|
6
|
+
* Create AI SDK tools for managing extensions at runtime.
|
|
7
|
+
*
|
|
8
|
+
* These tools let the LLM load and list extensions dynamically.
|
|
9
|
+
* Loaded extensions expose their own tools on the next inference
|
|
10
|
+
* turn. Unloading is a client-side action (via @callable RPC).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const extensions = new ExtensionManager({ loader: this.env.LOADER, workspace: this.workspace });
|
|
15
|
+
* const extensionTools = createExtensionTools({ manager: extensions });
|
|
16
|
+
*
|
|
17
|
+
* getTools() {
|
|
18
|
+
* return {
|
|
19
|
+
* ...createWorkspaceTools(this.workspace),
|
|
20
|
+
* ...extensionTools,
|
|
21
|
+
* ...extensions.getTools(), // tools from loaded extensions
|
|
22
|
+
* };
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function createExtensionTools(options) {
|
|
27
|
+
const { manager } = options;
|
|
28
|
+
return {
|
|
29
|
+
load_extension: tool({
|
|
30
|
+
description: "Load an extension from JavaScript source code. The source is a JS object expression defining tools. Each tool has: description, parameters (JSON Schema properties), optional required array, and an async execute function. The execute function receives (args, host) where host provides workspace access (host.readFile, host.writeFile, host.listFiles). IMPORTANT: Use only lowercase letters, numbers, and underscores in the extension name. Tool names are prefixed: name 'math' with tool 'add' becomes 'math_add'. New tools become available on the next message turn — call them by their full prefixed name.",
|
|
31
|
+
inputSchema: z.object({
|
|
32
|
+
name: z.string().describe("Unique name for the extension"),
|
|
33
|
+
version: z.string().describe("Semver version (e.g. '1.0.0')"),
|
|
34
|
+
description: z.string().optional().describe("Human-readable description"),
|
|
35
|
+
source: z.string().describe("JavaScript object expression defining tools. Example:\n{\n greet: {\n description: \"Greet someone\",\n parameters: { name: { type: \"string\" } },\n required: [\"name\"],\n execute: async (args) => \"Hello, \" + args.name\n }\n}"),
|
|
36
|
+
workspace_access: z.enum([
|
|
37
|
+
"none",
|
|
38
|
+
"read",
|
|
39
|
+
"read-write"
|
|
40
|
+
]).optional().describe("Workspace access level for the extension (default: none)"),
|
|
41
|
+
network: z.array(z.string()).optional().describe("Network hosts the extension needs access to (default: none)")
|
|
42
|
+
}),
|
|
43
|
+
execute: async ({ name, version, description, source, workspace_access, network }) => {
|
|
44
|
+
const info = await manager.load({
|
|
45
|
+
name,
|
|
46
|
+
version,
|
|
47
|
+
description,
|
|
48
|
+
permissions: {
|
|
49
|
+
workspace: workspace_access ?? "none",
|
|
50
|
+
network
|
|
51
|
+
}
|
|
52
|
+
}, source);
|
|
53
|
+
return {
|
|
54
|
+
loaded: true,
|
|
55
|
+
name: info.name,
|
|
56
|
+
prefix: sanitizeName(name),
|
|
57
|
+
version: info.version,
|
|
58
|
+
tools: info.tools,
|
|
59
|
+
message: `Extension "${name}" loaded. On the NEXT message turn, call these tools by their full name: ${info.tools.join(", ")}.`
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}),
|
|
63
|
+
list_extensions: tool({
|
|
64
|
+
description: "List all currently loaded extensions and their tools.",
|
|
65
|
+
inputSchema: z.object({}),
|
|
66
|
+
execute: async () => {
|
|
67
|
+
const extensions = manager.list();
|
|
68
|
+
return {
|
|
69
|
+
count: extensions.length,
|
|
70
|
+
extensions: extensions.map((ext) => ({
|
|
71
|
+
name: ext.name,
|
|
72
|
+
version: ext.version,
|
|
73
|
+
description: ext.description,
|
|
74
|
+
tools: ext.tools,
|
|
75
|
+
permissions: ext.permissions
|
|
76
|
+
}))
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { createExtensionTools };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","names":[],"sources":["../../src/tools/extensions.ts"],"sourcesContent":["import { tool } from \"ai\";\nimport { z } from \"zod\";\nimport type { ExtensionManager } from \"../extensions/manager\";\nimport { sanitizeName } from \"../extensions/manager\";\n\nexport interface ExtensionToolsOptions {\n manager: ExtensionManager;\n}\n\n/**\n * Create AI SDK tools for managing extensions at runtime.\n *\n * These tools let the LLM load and list extensions dynamically.\n * Loaded extensions expose their own tools on the next inference\n * turn. Unloading is a client-side action (via @callable RPC).\n *\n * @example\n * ```ts\n * const extensions = new ExtensionManager({ loader: this.env.LOADER, workspace: this.workspace });\n * const extensionTools = createExtensionTools({ manager: extensions });\n *\n * getTools() {\n * return {\n * ...createWorkspaceTools(this.workspace),\n * ...extensionTools,\n * ...extensions.getTools(), // tools from loaded extensions\n * };\n * }\n * ```\n */\nexport function createExtensionTools(options: ExtensionToolsOptions) {\n const { manager } = options;\n\n return {\n load_extension: tool({\n description:\n \"Load an extension from JavaScript source code. \" +\n \"The source is a JS object expression defining tools. \" +\n \"Each tool has: description, parameters (JSON Schema properties), \" +\n \"optional required array, and an async execute function. \" +\n \"The execute function receives (args, host) where host provides \" +\n \"workspace access (host.readFile, host.writeFile, host.listFiles). \" +\n \"IMPORTANT: Use only lowercase letters, numbers, and underscores in the extension name. \" +\n \"Tool names are prefixed: name 'math' with tool 'add' becomes 'math_add'. \" +\n \"New tools become available on the next message turn — call them by their full prefixed name.\",\n inputSchema: z.object({\n name: z.string().describe(\"Unique name for the extension\"),\n version: z.string().describe(\"Semver version (e.g. '1.0.0')\"),\n description: z\n .string()\n .optional()\n .describe(\"Human-readable description\"),\n source: z\n .string()\n .describe(\n \"JavaScript object expression defining tools. Example:\\n\" +\n \"{\\n\" +\n ' greet: {\\n description: \"Greet someone\",\\n' +\n ' parameters: { name: { type: \"string\" } },\\n' +\n ' required: [\"name\"],\\n' +\n ' execute: async (args) => \"Hello, \" + args.name\\n }\\n}'\n ),\n workspace_access: z\n .enum([\"none\", \"read\", \"read-write\"])\n .optional()\n .describe(\"Workspace access level for the extension (default: none)\"),\n network: z\n .array(z.string())\n .optional()\n .describe(\n \"Network hosts the extension needs access to (default: none)\"\n )\n }),\n execute: async ({\n name,\n version,\n description,\n source,\n workspace_access,\n network\n }) => {\n const info = await manager.load(\n {\n name,\n version,\n description,\n permissions: {\n workspace: workspace_access ?? \"none\",\n network\n }\n },\n source\n );\n return {\n loaded: true,\n name: info.name,\n prefix: sanitizeName(name),\n version: info.version,\n tools: info.tools,\n message: `Extension \"${name}\" loaded. On the NEXT message turn, call these tools by their full name: ${info.tools.join(\", \")}.`\n };\n }\n }),\n\n list_extensions: tool({\n description: \"List all currently loaded extensions and their tools.\",\n inputSchema: z.object({}),\n execute: async () => {\n const extensions = manager.list();\n return {\n count: extensions.length,\n extensions: extensions.map((ext) => ({\n name: ext.name,\n version: ext.version,\n description: ext.description,\n tools: ext.tools,\n permissions: ext.permissions\n }))\n };\n }\n })\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,qBAAqB,SAAgC;CACnE,MAAM,EAAE,YAAY;AAEpB,QAAO;EACL,gBAAgB,KAAK;GACnB,aACE;GASF,aAAa,EAAE,OAAO;IACpB,MAAM,EAAE,QAAQ,CAAC,SAAS,gCAAgC;IAC1D,SAAS,EAAE,QAAQ,CAAC,SAAS,gCAAgC;IAC7D,aAAa,EACV,QAAQ,CACR,UAAU,CACV,SAAS,6BAA6B;IACzC,QAAQ,EACL,QAAQ,CACR,SACC,sPAMD;IACH,kBAAkB,EACf,KAAK;KAAC;KAAQ;KAAQ;KAAa,CAAC,CACpC,UAAU,CACV,SAAS,2DAA2D;IACvE,SAAS,EACN,MAAM,EAAE,QAAQ,CAAC,CACjB,UAAU,CACV,SACC,8DACD;IACJ,CAAC;GACF,SAAS,OAAO,EACd,MACA,SACA,aACA,QACA,kBACA,cACI;IACJ,MAAM,OAAO,MAAM,QAAQ,KACzB;KACE;KACA;KACA;KACA,aAAa;MACX,WAAW,oBAAoB;MAC/B;MACD;KACF,EACD,OACD;AACD,WAAO;KACL,QAAQ;KACR,MAAM,KAAK;KACX,QAAQ,aAAa,KAAK;KAC1B,SAAS,KAAK;KACd,OAAO,KAAK;KACZ,SAAS,cAAc,KAAK,2EAA2E,KAAK,MAAM,KAAK,KAAK,CAAC;KAC9H;;GAEJ,CAAC;EAEF,iBAAiB,KAAK;GACpB,aAAa;GACb,aAAa,EAAE,OAAO,EAAE,CAAC;GACzB,SAAS,YAAY;IACnB,MAAM,aAAa,QAAQ,MAAM;AACjC,WAAO;KACL,OAAO,WAAW;KAClB,YAAY,WAAW,KAAK,SAAS;MACnC,MAAM,IAAI;MACV,SAAS,IAAI;MACb,aAAa,IAAI;MACjB,OAAO,IAAI;MACX,aAAa,IAAI;MAClB,EAAE;KACJ;;GAEJ,CAAC;EACH"}
|