@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":"workspace.js","names":[],"sources":["../../src/tools/workspace.ts"],"sourcesContent":["import type { Workspace, FileInfo } from \"agents/experimental/workspace\";\nimport { tool } from \"ai\";\nimport { z } from \"zod\";\n\n// ── Operations interfaces ─────────────────────────────────────────\n// Abstractions over file I/O so the same tools can work against\n// Workspace, a local filesystem, or anything else.\n\nexport interface ReadOperations {\n readFile(path: string): Promise<string | null>;\n stat(path: string): Promise<FileInfo | null> | FileInfo | null;\n}\n\nexport interface WriteOperations {\n writeFile(path: string, content: string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> | void;\n}\n\nexport interface EditOperations {\n readFile(path: string): Promise<string | null>;\n writeFile(path: string, content: string): Promise<void>;\n}\n\nexport interface ListOperations {\n readDir(\n dir: string,\n opts?: { limit?: number; offset?: number }\n ): Promise<FileInfo[]> | FileInfo[];\n}\n\nexport interface FindOperations {\n glob(pattern: string): Promise<FileInfo[]> | FileInfo[];\n}\n\nexport interface DeleteOperations {\n rm(\n path: string,\n opts?: { recursive?: boolean; force?: boolean }\n ): Promise<void>;\n}\n\nexport interface GrepOperations {\n glob(pattern: string): Promise<FileInfo[]> | FileInfo[];\n readFile(path: string): Promise<string | null>;\n}\n\n// ── Workspace-backed operation factories ──────────────────────────\n\nfunction workspaceReadOps(ws: Workspace): ReadOperations {\n return {\n readFile: (path) => ws.readFile(path),\n stat: (path) => ws.stat(path)\n };\n}\n\nfunction workspaceWriteOps(ws: Workspace): WriteOperations {\n return {\n writeFile: (path, content) => ws.writeFile(path, content),\n mkdir: (path, opts) => ws.mkdir(path, opts)\n };\n}\n\nfunction workspaceEditOps(ws: Workspace): EditOperations {\n return {\n readFile: (path) => ws.readFile(path),\n writeFile: (path, content) => ws.writeFile(path, content)\n };\n}\n\nfunction workspaceListOps(ws: Workspace): ListOperations {\n return {\n readDir: (dir, opts) => ws.readDir(dir, opts)\n };\n}\n\nfunction workspaceFindOps(ws: Workspace): FindOperations {\n return {\n glob: (pattern) => ws.glob(pattern)\n };\n}\n\nfunction workspaceDeleteOps(ws: Workspace): DeleteOperations {\n return {\n rm: (path, opts) => ws.rm(path, opts)\n };\n}\n\nfunction workspaceGrepOps(ws: Workspace): GrepOperations {\n return {\n glob: (pattern) => ws.glob(pattern),\n readFile: (path) => ws.readFile(path)\n };\n}\n\n/**\n * Create a complete set of AI SDK tools backed by a Workspace instance.\n *\n * ```ts\n * import { Workspace } from \"agents/experimental/workspace\";\n * import { createWorkspaceTools } from \"@cloudflare/think\";\n *\n * class MyAgent extends Agent<Env> {\n * workspace = new Workspace(this);\n *\n * async onChatMessage() {\n * const tools = createWorkspaceTools(this.workspace);\n * const result = streamText({ model, tools, messages });\n * return result.toUIMessageStreamResponse();\n * }\n * }\n * ```\n */\nexport function createWorkspaceTools(workspace: Workspace) {\n return {\n read: createReadTool({ ops: workspaceReadOps(workspace) }),\n write: createWriteTool({ ops: workspaceWriteOps(workspace) }),\n edit: createEditTool({ ops: workspaceEditOps(workspace) }),\n list: createListTool({ ops: workspaceListOps(workspace) }),\n find: createFindTool({ ops: workspaceFindOps(workspace) }),\n grep: createGrepTool({ ops: workspaceGrepOps(workspace) }),\n delete: createDeleteTool({ ops: workspaceDeleteOps(workspace) })\n };\n}\n\n// ── Read ────────────────────────────────────────────────────────────\n\nconst MAX_LINES = 2000;\nconst MAX_LINE_LENGTH = 2000;\n\nexport interface ReadToolOptions {\n ops: ReadOperations;\n}\n\nexport function createReadTool(options: ReadToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"Read the contents of a file. Returns the file content with line numbers. \" +\n \"Use offset and limit for large files. Returns null if the file does not exist.\",\n inputSchema: z.object({\n path: z.string().describe(\"Absolute path to the file\"),\n offset: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe(\"1-indexed line number to start reading from\"),\n limit: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe(\"Number of lines to read\")\n }),\n execute: async ({ path, offset, limit }) => {\n const stat = await ops.stat(path);\n if (!stat) {\n return { error: `File not found: ${path}` };\n }\n if (stat.type === \"directory\") {\n return { error: `${path} is a directory, not a file` };\n }\n\n const content = await ops.readFile(path);\n if (content === null) {\n return { error: `Could not read file: ${path}` };\n }\n\n const allLines = content.split(\"\\n\");\n const totalLines = allLines.length;\n\n // Apply offset/limit\n const startLine = offset ? offset - 1 : 0;\n const endLine = limit ? startLine + limit : allLines.length;\n const lines = allLines.slice(startLine, endLine);\n\n // Format with line numbers, truncate long lines\n const numbered = lines.map((line, i) => {\n const lineNum = startLine + i + 1;\n const truncated =\n line.length > MAX_LINE_LENGTH\n ? line.slice(0, MAX_LINE_LENGTH) + \"... (truncated)\"\n : line;\n return `${lineNum}\\t${truncated}`;\n });\n\n // Truncate if too many lines\n let output: string;\n if (numbered.length > MAX_LINES) {\n output =\n numbered.slice(0, MAX_LINES).join(\"\\n\") +\n `\\n... (${numbered.length - MAX_LINES} more lines truncated)`;\n } else {\n output = numbered.join(\"\\n\");\n }\n\n const result: Record<string, unknown> = {\n path,\n content: output,\n totalLines\n };\n\n if (offset || limit) {\n result.fromLine = startLine + 1;\n result.toLine = Math.min(endLine, totalLines);\n }\n\n return result;\n }\n });\n}\n\n// ── Write ───────────────────────────────────────────────────────────\n\nexport interface WriteToolOptions {\n ops: WriteOperations;\n}\n\nexport function createWriteTool(options: WriteToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"Write content to a file. Creates the file if it does not exist, \" +\n \"overwrites if it does. Parent directories are created automatically.\",\n inputSchema: z.object({\n path: z.string().describe(\"Absolute path to the file\"),\n content: z.string().describe(\"Content to write to the file\")\n }),\n execute: async ({ path, content }) => {\n // Ensure parent directory exists\n const parent = path.replace(/\\/[^/]+$/, \"\");\n if (parent && parent !== \"/\") {\n await ops.mkdir(parent, { recursive: true });\n }\n\n await ops.writeFile(path, content);\n\n const lines = content.split(\"\\n\").length;\n return {\n path,\n bytesWritten: new TextEncoder().encode(content).byteLength,\n lines\n };\n }\n });\n}\n\n// ── Edit ────────────────────────────────────────────────────────────\n\nexport interface EditToolOptions {\n ops: EditOperations;\n}\n\nexport function createEditTool(options: EditToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"Make a targeted edit to a file by replacing an exact string match. \" +\n \"Provide the old_string to find and new_string to replace it with. \" +\n \"The old_string must match exactly (including whitespace and indentation). \" +\n \"Use an empty old_string with new_string to create a new file.\",\n inputSchema: z.object({\n path: z.string().describe(\"Absolute path to the file\"),\n old_string: z\n .string()\n .describe(\n \"Exact text to find and replace. Empty string to create a new file.\"\n ),\n new_string: z.string().describe(\"Replacement text\")\n }),\n execute: async ({ path, old_string, new_string }) => {\n // Create new file\n if (old_string === \"\") {\n const existing = await ops.readFile(path);\n if (existing !== null) {\n return {\n error:\n \"File already exists. Provide old_string to edit, or use the write tool to overwrite.\"\n };\n }\n await ops.writeFile(path, new_string);\n return {\n path,\n created: true,\n lines: new_string.split(\"\\n\").length\n };\n }\n\n // Edit existing file\n const content = await ops.readFile(path);\n if (content === null) {\n return { error: `File not found: ${path}` };\n }\n\n // Count occurrences\n const occurrences = countOccurrences(content, old_string);\n if (occurrences === 0) {\n // Try fuzzy match — normalize whitespace and look again\n const fuzzyResult = fuzzyReplace(content, old_string, new_string);\n if (fuzzyResult === \"ambiguous\") {\n return {\n error:\n \"old_string matches multiple locations after whitespace normalization. \" +\n \"Include more surrounding context to make the match unique.\"\n };\n }\n if (fuzzyResult !== null) {\n await ops.writeFile(path, fuzzyResult);\n return {\n path,\n replaced: true,\n fuzzyMatch: true,\n lines: fuzzyResult.split(\"\\n\").length\n };\n }\n\n return {\n error:\n \"old_string not found in file. Make sure it matches exactly, \" +\n \"including whitespace and indentation. Read the file first to verify.\"\n };\n }\n\n if (occurrences > 1) {\n return {\n error:\n `old_string appears ${occurrences} times in the file. ` +\n \"Include more surrounding context to make the match unique.\"\n };\n }\n\n const newContent = content.replace(old_string, new_string);\n await ops.writeFile(path, newContent);\n\n return {\n path,\n replaced: true,\n lines: newContent.split(\"\\n\").length\n };\n }\n });\n}\n\nfunction countOccurrences(text: string, search: string): number {\n let count = 0;\n let pos = 0;\n while (true) {\n const idx = text.indexOf(search, pos);\n if (idx === -1) break;\n count++;\n pos = idx + 1;\n }\n return count;\n}\n\n/**\n * Fuzzy replacement: normalize whitespace in both the file content\n * and the search string, find the match, then replace the corresponding\n * region in the original content.\n */\nfunction fuzzyReplace(\n content: string,\n oldStr: string,\n newStr: string\n): string | \"ambiguous\" | null {\n const normalizedContent = normalizeWhitespace(content);\n const normalizedSearch = normalizeWhitespace(oldStr);\n\n if (!normalizedSearch) return null;\n\n const idx = normalizedContent.indexOf(normalizedSearch);\n if (idx === -1) return null;\n\n // Check for multiple fuzzy matches\n const secondIdx = normalizedContent.indexOf(\n normalizedSearch,\n idx + normalizedSearch.length\n );\n if (secondIdx !== -1) return \"ambiguous\";\n\n // Map the normalized index back to the original content.\n // Walk both strings in parallel to find the original start/end.\n const originalStart = mapToOriginal(content, idx);\n const originalEnd = mapToOriginal(content, idx + normalizedSearch.length);\n\n return content.slice(0, originalStart) + newStr + content.slice(originalEnd);\n}\n\nfunction normalizeWhitespace(s: string): string {\n return s.replace(/[ \\t]+/g, \" \").replace(/\\r\\n/g, \"\\n\");\n}\n\n/**\n * Map a position in the normalized string back to the original string.\n * Walks both strings char-by-char, skipping extra whitespace in the original.\n */\nfunction mapToOriginal(original: string, normalizedPos: number): number {\n let ni = 0;\n let oi = 0;\n\n while (ni < normalizedPos && oi < original.length) {\n const oc = original[oi];\n if (oc === \"\\r\" && original[oi + 1] === \"\\n\") {\n // \\r\\n in original maps to \\n in normalized\n oi += 2;\n ni += 1;\n } else if (oc === \" \" || oc === \"\\t\") {\n // Consume a run of spaces/tabs in original → single space in normalized\n oi++;\n while (\n oi < original.length &&\n (original[oi] === \" \" || original[oi] === \"\\t\")\n ) {\n oi++;\n }\n ni++;\n } else {\n oi++;\n ni++;\n }\n }\n\n return oi;\n}\n\n// ── List ────────────────────────────────────────────────────────────\n\nexport interface ListToolOptions {\n ops: ListOperations;\n}\n\nexport function createListTool(options: ListToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"List files and directories in a given path. \" +\n \"Returns names, types, and sizes for each entry.\",\n inputSchema: z.object({\n path: z\n .string()\n .default(\"/\")\n .describe(\"Absolute path to the directory to list\"),\n limit: z\n .number()\n .int()\n .min(1)\n .max(1000)\n .optional()\n .describe(\"Maximum number of entries to return (default: 200)\"),\n offset: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\"Number of entries to skip (for pagination)\")\n }),\n execute: async ({ path, limit, offset }) => {\n const maxEntries = limit ?? 200;\n const entries = await ops.readDir(path, {\n limit: maxEntries,\n offset: offset ?? 0\n });\n\n const formatted = entries.map((entry) => {\n const suffix = entry.type === \"directory\" ? \"/\" : \"\";\n const sizeStr =\n entry.type === \"file\" ? ` (${formatSize(entry.size)})` : \"\";\n return `${entry.name}${suffix}${sizeStr}`;\n });\n\n return {\n path,\n count: entries.length,\n entries: formatted\n };\n }\n });\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ── Find ────────────────────────────────────────────────────────────\n\nexport interface FindToolOptions {\n ops: FindOperations;\n}\n\nexport function createFindTool(options: FindToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"Find files matching a glob pattern. \" +\n \"Supports standard glob syntax: * matches any file, ** matches directories recursively, \" +\n \"? matches a single character. Returns matching file paths with types and sizes.\",\n inputSchema: z.object({\n pattern: z\n .string()\n .describe(\n 'Glob pattern to match (e.g. \"**/*.ts\", \"src/**/*.test.ts\", \"*.md\")'\n )\n }),\n execute: async ({ pattern }) => {\n const matches = await ops.glob(pattern);\n\n const MAX_RESULTS = 200;\n const truncated = matches.length > MAX_RESULTS;\n const results = matches.slice(0, MAX_RESULTS);\n\n const formatted = results.map((entry) => {\n const suffix = entry.type === \"directory\" ? \"/\" : \"\";\n return `${entry.path}${suffix}`;\n });\n\n const result: Record<string, unknown> = {\n pattern,\n count: matches.length,\n files: formatted\n };\n\n if (truncated) {\n result.truncated = true;\n result.showing = MAX_RESULTS;\n }\n\n return result;\n }\n });\n}\n\n// ── Grep ────────────────────────────────────────────────────────────\n\nconst MAX_MATCHES = 200;\nconst MAX_FILE_SIZE = 1_048_576; // 1 MB — skip files larger than this in grep\n\nexport interface GrepToolOptions {\n ops: GrepOperations;\n}\n\nexport function createGrepTool(options: GrepToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"Search file contents using a regular expression or fixed string. \" +\n \"Returns matching lines with file paths and line numbers. \" +\n \"Searches all files matching the include glob, or all files if not specified.\",\n inputSchema: z.object({\n query: z.string().describe(\"Search pattern (regex or fixed string)\"),\n include: z\n .string()\n .optional()\n .describe(\n 'Glob pattern to filter files (e.g. \"**/*.ts\"). Defaults to \"**/*\"'\n ),\n fixedString: z\n .boolean()\n .optional()\n .describe(\"If true, treat query as a literal string instead of regex\"),\n caseSensitive: z\n .boolean()\n .optional()\n .describe(\"If true, search is case-sensitive (default: false)\"),\n contextLines: z\n .number()\n .int()\n .min(0)\n .max(10)\n .optional()\n .describe(\"Number of context lines around each match (default: 0)\")\n }),\n execute: async ({\n query,\n include,\n fixedString,\n caseSensitive,\n contextLines\n }) => {\n const pattern = include ?? \"**/*\";\n const allFiles = await ops.glob(pattern);\n const files = allFiles.filter((f: { type: string }) => f.type === \"file\");\n\n let regex: RegExp;\n try {\n const escaped = fixedString ? escapeRegex(query) : query;\n regex = new RegExp(escaped, caseSensitive ? \"g\" : \"gi\");\n } catch {\n return { error: `Invalid regex: ${query}` };\n }\n\n const ctx = contextLines ?? 0;\n const matches: Array<{\n file: string;\n line: number;\n text: string;\n context?: string[];\n }> = [];\n let totalMatches = 0;\n let filesSearched = 0;\n let filesWithMatches = 0;\n\n let filesSkipped = 0;\n\n for (const file of files) {\n if (totalMatches >= MAX_MATCHES) break;\n\n // Skip files larger than 1 MB to avoid memory blowup\n if (file.size > MAX_FILE_SIZE) {\n filesSkipped++;\n continue;\n }\n\n const content = await ops.readFile(file.path);\n if (content === null) continue;\n filesSearched++;\n\n const lines = content.split(\"\\n\");\n let fileHasMatch = false;\n\n for (let i = 0; i < lines.length; i++) {\n if (totalMatches >= MAX_MATCHES) break;\n\n regex.lastIndex = 0;\n if (regex.test(lines[i])) {\n if (!fileHasMatch) {\n fileHasMatch = true;\n filesWithMatches++;\n }\n totalMatches++;\n\n const match: {\n file: string;\n line: number;\n text: string;\n context?: string[];\n } = {\n file: file.path,\n line: i + 1,\n text: lines[i]\n };\n\n if (ctx > 0) {\n const start = Math.max(0, i - ctx);\n const end = Math.min(lines.length, i + ctx + 1);\n match.context = lines.slice(start, end).map((l, j) => {\n const lineNum = start + j + 1;\n const marker = lineNum === i + 1 ? \">\" : \" \";\n return `${marker} ${lineNum}\\t${l}`;\n });\n }\n\n matches.push(match);\n }\n }\n }\n\n const result: Record<string, unknown> = {\n query,\n filesSearched,\n filesWithMatches,\n totalMatches,\n matches: matches.map((m) => {\n if (m.context) {\n return {\n file: m.file,\n line: m.line,\n context: m.context.join(\"\\n\")\n };\n }\n return `${m.file}:${m.line}: ${m.text}`;\n })\n };\n\n if (totalMatches >= MAX_MATCHES) {\n result.truncated = true;\n }\n if (filesSkipped > 0) {\n result.filesSkipped = filesSkipped;\n result.note = `${filesSkipped} file(s) skipped (larger than 1 MB)`;\n }\n\n return result;\n }\n });\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// ── Delete ──────────────────────────────────────────────────────────\n\nexport interface DeleteToolOptions {\n ops: DeleteOperations;\n}\n\nexport function createDeleteTool(options: DeleteToolOptions) {\n const { ops } = options;\n\n return tool({\n description:\n \"Delete a file or directory. \" +\n \"Set recursive to true to remove non-empty directories.\",\n inputSchema: z.object({\n path: z.string().describe(\"Absolute path to the file or directory\"),\n recursive: z\n .boolean()\n .optional()\n .describe(\"If true, remove directories and their contents recursively\")\n }),\n execute: async ({ path, recursive }) => {\n await ops.rm(path, { recursive, force: true });\n return { deleted: path };\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ChatTransport, UIMessage, UIMessageChunk } from "ai";
|
|
2
|
+
|
|
3
|
+
//#region src/transport.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Minimal interface for the agent connection object.
|
|
6
|
+
* Satisfied by the return value of `useAgent()` from `agents/react`.
|
|
7
|
+
*/
|
|
8
|
+
interface AgentSocket {
|
|
9
|
+
addEventListener(
|
|
10
|
+
type: "message",
|
|
11
|
+
handler: (event: MessageEvent) => void,
|
|
12
|
+
options?: {
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
): void;
|
|
16
|
+
removeEventListener(
|
|
17
|
+
type: "message",
|
|
18
|
+
handler: (event: MessageEvent) => void
|
|
19
|
+
): void;
|
|
20
|
+
call(method: string, args?: unknown[]): Promise<unknown>;
|
|
21
|
+
send(data: string): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Options for constructing an AgentChatTransport.
|
|
25
|
+
*/
|
|
26
|
+
interface AgentChatTransportOptions {
|
|
27
|
+
/**
|
|
28
|
+
* The server-side RPC method to call when sending a message.
|
|
29
|
+
* Receives `[text, requestId]` as arguments.
|
|
30
|
+
* @default "sendMessage"
|
|
31
|
+
*/
|
|
32
|
+
sendMethod?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Timeout in milliseconds for reconnectToStream to wait for a
|
|
35
|
+
* stream-resuming response before giving up.
|
|
36
|
+
* @default 500
|
|
37
|
+
*/
|
|
38
|
+
resumeTimeout?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* ChatTransport implementation for Agent WebSocket connections.
|
|
42
|
+
*
|
|
43
|
+
* Speaks the wire protocol used by Think's `chat()` method
|
|
44
|
+
* and ChunkRelay on the server:
|
|
45
|
+
* - `stream-start` → new stream with requestId
|
|
46
|
+
* - `stream-event` → UIMessageChunk payload
|
|
47
|
+
* - `stream-done` → stream complete
|
|
48
|
+
* - `stream-resuming` → replay after reconnect
|
|
49
|
+
* - `cancel` → client→server abort
|
|
50
|
+
*/
|
|
51
|
+
declare class AgentChatTransport implements ChatTransport<UIMessage> {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(agent: AgentSocket, options?: AgentChatTransportOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Detach from the current stream. Call this before switching agents
|
|
56
|
+
* or cleaning up to ensure the stream controller is closed.
|
|
57
|
+
*/
|
|
58
|
+
detach(): void;
|
|
59
|
+
sendMessages({
|
|
60
|
+
messages,
|
|
61
|
+
abortSignal
|
|
62
|
+
}: Parameters<ChatTransport<UIMessage>["sendMessages"]>[0]): Promise<
|
|
63
|
+
ReadableStream<UIMessageChunk>
|
|
64
|
+
>;
|
|
65
|
+
reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null>;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { AgentChatTransport, AgentChatTransportOptions, AgentSocket };
|
|
69
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldGet2, r as _assertClassBrand, t as _classPrivateFieldSet2 } from "./classPrivateFieldSet2-COLddhya.js";
|
|
2
|
+
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-CdQXQy1O.js";
|
|
3
|
+
//#region src/transport.ts
|
|
4
|
+
/**
|
|
5
|
+
* Extract the text content from a UIMessage's parts.
|
|
6
|
+
*/
|
|
7
|
+
function getMessageText(msg) {
|
|
8
|
+
return msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
9
|
+
}
|
|
10
|
+
var _agent = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
var _activeRequestIds = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
var _currentFinish = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
var _sendMethod = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
var _resumeTimeout = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
var _AgentChatTransport_brand = /* @__PURE__ */ new WeakSet();
|
|
16
|
+
/**
|
|
17
|
+
* ChatTransport implementation for Agent WebSocket connections.
|
|
18
|
+
*
|
|
19
|
+
* Speaks the wire protocol used by Think's `chat()` method
|
|
20
|
+
* and ChunkRelay on the server:
|
|
21
|
+
* - `stream-start` → new stream with requestId
|
|
22
|
+
* - `stream-event` → UIMessageChunk payload
|
|
23
|
+
* - `stream-done` → stream complete
|
|
24
|
+
* - `stream-resuming` → replay after reconnect
|
|
25
|
+
* - `cancel` → client→server abort
|
|
26
|
+
*/
|
|
27
|
+
var AgentChatTransport = class {
|
|
28
|
+
constructor(agent, options) {
|
|
29
|
+
_classPrivateMethodInitSpec(this, _AgentChatTransport_brand);
|
|
30
|
+
_classPrivateFieldInitSpec(this, _agent, void 0);
|
|
31
|
+
_classPrivateFieldInitSpec(this, _activeRequestIds, /* @__PURE__ */ new Set());
|
|
32
|
+
_classPrivateFieldInitSpec(this, _currentFinish, null);
|
|
33
|
+
_classPrivateFieldInitSpec(this, _sendMethod, void 0);
|
|
34
|
+
_classPrivateFieldInitSpec(this, _resumeTimeout, void 0);
|
|
35
|
+
_classPrivateFieldSet2(_agent, this, agent);
|
|
36
|
+
_classPrivateFieldSet2(_sendMethod, this, options?.sendMethod ?? "sendMessage");
|
|
37
|
+
_classPrivateFieldSet2(_resumeTimeout, this, options?.resumeTimeout ?? 500);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Detach from the current stream. Call this before switching agents
|
|
41
|
+
* or cleaning up to ensure the stream controller is closed.
|
|
42
|
+
*/
|
|
43
|
+
detach() {
|
|
44
|
+
_classPrivateFieldGet2(_currentFinish, this)?.call(this);
|
|
45
|
+
_classPrivateFieldSet2(_currentFinish, this, null);
|
|
46
|
+
}
|
|
47
|
+
async sendMessages({ messages, abortSignal }) {
|
|
48
|
+
const lastMessage = messages[messages.length - 1];
|
|
49
|
+
const text = getMessageText(lastMessage);
|
|
50
|
+
const requestId = crypto.randomUUID().slice(0, 8);
|
|
51
|
+
let completed = false;
|
|
52
|
+
const abortController = new AbortController();
|
|
53
|
+
let streamController;
|
|
54
|
+
const finish = (action) => {
|
|
55
|
+
if (completed) return;
|
|
56
|
+
completed = true;
|
|
57
|
+
_classPrivateFieldSet2(_currentFinish, this, null);
|
|
58
|
+
try {
|
|
59
|
+
action();
|
|
60
|
+
} catch {}
|
|
61
|
+
_classPrivateFieldGet2(_activeRequestIds, this).delete(requestId);
|
|
62
|
+
abortController.abort();
|
|
63
|
+
};
|
|
64
|
+
_classPrivateFieldSet2(_currentFinish, this, () => finish(() => streamController.close()));
|
|
65
|
+
const onAbort = () => {
|
|
66
|
+
if (completed) return;
|
|
67
|
+
try {
|
|
68
|
+
_classPrivateFieldGet2(_agent, this).send(JSON.stringify({
|
|
69
|
+
type: "cancel",
|
|
70
|
+
requestId
|
|
71
|
+
}));
|
|
72
|
+
} catch {}
|
|
73
|
+
finish(() => streamController.error(Object.assign(/* @__PURE__ */ new Error("Aborted"), { name: "AbortError" })));
|
|
74
|
+
};
|
|
75
|
+
const stream = new ReadableStream({
|
|
76
|
+
start(controller) {
|
|
77
|
+
streamController = controller;
|
|
78
|
+
},
|
|
79
|
+
cancel() {
|
|
80
|
+
onAbort();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
_classPrivateFieldGet2(_agent, this).addEventListener("message", (event) => {
|
|
84
|
+
if (typeof event.data !== "string") return;
|
|
85
|
+
try {
|
|
86
|
+
const msg = JSON.parse(event.data);
|
|
87
|
+
if (msg.requestId !== requestId) return;
|
|
88
|
+
if (msg.type === "stream-event") {
|
|
89
|
+
const chunk = JSON.parse(msg.event);
|
|
90
|
+
streamController.enqueue(chunk);
|
|
91
|
+
} else if (msg.type === "stream-done") finish(() => streamController.close());
|
|
92
|
+
} catch {}
|
|
93
|
+
}, { signal: abortController.signal });
|
|
94
|
+
if (abortSignal) {
|
|
95
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
96
|
+
if (abortSignal.aborted) onAbort();
|
|
97
|
+
}
|
|
98
|
+
_classPrivateFieldGet2(_activeRequestIds, this).add(requestId);
|
|
99
|
+
_classPrivateFieldGet2(_agent, this).call(_classPrivateFieldGet2(_sendMethod, this), [text, requestId]).catch((error) => {
|
|
100
|
+
finish(() => streamController.error(error));
|
|
101
|
+
});
|
|
102
|
+
return stream;
|
|
103
|
+
}
|
|
104
|
+
async reconnectToStream() {
|
|
105
|
+
const resumeTimeout = _classPrivateFieldGet2(_resumeTimeout, this);
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
let resolved = false;
|
|
108
|
+
let timeout;
|
|
109
|
+
const done = (value) => {
|
|
110
|
+
if (resolved) return;
|
|
111
|
+
resolved = true;
|
|
112
|
+
if (timeout) clearTimeout(timeout);
|
|
113
|
+
_classPrivateFieldGet2(_agent, this).removeEventListener("message", handler);
|
|
114
|
+
resolve(value);
|
|
115
|
+
};
|
|
116
|
+
const handler = (event) => {
|
|
117
|
+
if (typeof event.data !== "string") return;
|
|
118
|
+
try {
|
|
119
|
+
const msg = JSON.parse(event.data);
|
|
120
|
+
if (msg.type === "stream-resuming") done(_assertClassBrand(_AgentChatTransport_brand, this, _createResumeStream).call(this, msg.requestId));
|
|
121
|
+
} catch {}
|
|
122
|
+
};
|
|
123
|
+
_classPrivateFieldGet2(_agent, this).addEventListener("message", handler);
|
|
124
|
+
try {
|
|
125
|
+
_classPrivateFieldGet2(_agent, this).send(JSON.stringify({ type: "resume-request" }));
|
|
126
|
+
} catch {}
|
|
127
|
+
timeout = setTimeout(() => done(null), resumeTimeout);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function _createResumeStream(requestId) {
|
|
132
|
+
const abortController = new AbortController();
|
|
133
|
+
let completed = false;
|
|
134
|
+
const finish = (action) => {
|
|
135
|
+
if (completed) return;
|
|
136
|
+
completed = true;
|
|
137
|
+
try {
|
|
138
|
+
action();
|
|
139
|
+
} catch {}
|
|
140
|
+
_classPrivateFieldGet2(_activeRequestIds, this).delete(requestId);
|
|
141
|
+
abortController.abort();
|
|
142
|
+
};
|
|
143
|
+
_classPrivateFieldGet2(_activeRequestIds, this).add(requestId);
|
|
144
|
+
return new ReadableStream({
|
|
145
|
+
start: (controller) => {
|
|
146
|
+
_classPrivateFieldGet2(_agent, this).addEventListener("message", (event) => {
|
|
147
|
+
if (typeof event.data !== "string") return;
|
|
148
|
+
try {
|
|
149
|
+
const msg = JSON.parse(event.data);
|
|
150
|
+
if (msg.requestId !== requestId) return;
|
|
151
|
+
if (msg.type === "stream-event") {
|
|
152
|
+
const chunk = JSON.parse(msg.event);
|
|
153
|
+
controller.enqueue(chunk);
|
|
154
|
+
} else if (msg.type === "stream-done") finish(() => controller.close());
|
|
155
|
+
} catch {}
|
|
156
|
+
}, { signal: abortController.signal });
|
|
157
|
+
},
|
|
158
|
+
cancel() {
|
|
159
|
+
finish(() => {});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { AgentChatTransport };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","names":[],"sources":["../src/transport.ts"],"sourcesContent":["/**\n * AgentChatTransport — bridges the AI SDK's useChat hook with an Agent\n * WebSocket connection that speaks Think's streaming protocol.\n *\n * Features:\n * - Request ID correlation: each request gets a unique ID, only matching\n * WS messages are processed\n * - Cancel: sends { type: \"cancel\", requestId } to stop server-side streaming\n * - Completion guard: close/error/abort are idempotent\n * - Signal-based cleanup: uses AbortController signal on addEventListener\n * - Stream resumption: reconnectToStream sends resume-request, server replays\n * buffered chunks via ChunkRelay\n *\n * @example\n * ```tsx\n * import { AgentChatTransport } from \"@cloudflare/think/transport\";\n * import { useAgent } from \"agents/react\";\n * import { useChat } from \"@ai-sdk/react\";\n *\n * const agent = useAgent({ agent: \"MyAssistant\" });\n * const transport = useMemo(() => new AgentChatTransport(agent), [agent]);\n * const { messages, sendMessage, status } = useChat({ transport });\n * ```\n */\n\nimport type { UIMessage, UIMessageChunk, ChatTransport } from \"ai\";\n\n/**\n * Minimal interface for the agent connection object.\n * Satisfied by the return value of `useAgent()` from `agents/react`.\n */\nexport interface AgentSocket {\n addEventListener(\n type: \"message\",\n handler: (event: MessageEvent) => void,\n options?: { signal?: AbortSignal }\n ): void;\n removeEventListener(\n type: \"message\",\n handler: (event: MessageEvent) => void\n ): void;\n call(method: string, args?: unknown[]): Promise<unknown>;\n send(data: string): void;\n}\n\n/**\n * Options for constructing an AgentChatTransport.\n */\nexport interface AgentChatTransportOptions {\n /**\n * The server-side RPC method to call when sending a message.\n * Receives `[text, requestId]` as arguments.\n * @default \"sendMessage\"\n */\n sendMethod?: string;\n\n /**\n * Timeout in milliseconds for reconnectToStream to wait for a\n * stream-resuming response before giving up.\n * @default 500\n */\n resumeTimeout?: number;\n}\n\n/**\n * Extract the text content from a UIMessage's parts.\n */\nfunction getMessageText(msg: UIMessage): string {\n return msg.parts\n .filter((p): p is { type: \"text\"; text: string } => p.type === \"text\")\n .map((p) => p.text)\n .join(\"\");\n}\n\n/**\n * ChatTransport implementation for Agent WebSocket connections.\n *\n * Speaks the wire protocol used by Think's `chat()` method\n * and ChunkRelay on the server:\n * - `stream-start` → new stream with requestId\n * - `stream-event` → UIMessageChunk payload\n * - `stream-done` → stream complete\n * - `stream-resuming` → replay after reconnect\n * - `cancel` → client→server abort\n */\nexport class AgentChatTransport implements ChatTransport<UIMessage> {\n #agent: AgentSocket;\n #activeRequestIds = new Set<string>();\n #currentFinish: (() => void) | null = null;\n #sendMethod: string;\n #resumeTimeout: number;\n\n constructor(agent: AgentSocket, options?: AgentChatTransportOptions) {\n this.#agent = agent;\n this.#sendMethod = options?.sendMethod ?? \"sendMessage\";\n this.#resumeTimeout = options?.resumeTimeout ?? 500;\n }\n\n /**\n * Detach from the current stream. Call this before switching agents\n * or cleaning up to ensure the stream controller is closed.\n */\n detach() {\n this.#currentFinish?.();\n this.#currentFinish = null;\n }\n\n async sendMessages({\n messages,\n abortSignal\n }: Parameters<ChatTransport<UIMessage>[\"sendMessages\"]>[0]): Promise<\n ReadableStream<UIMessageChunk>\n > {\n const lastMessage = messages[messages.length - 1];\n const text = getMessageText(lastMessage);\n const requestId = crypto.randomUUID().slice(0, 8);\n\n let completed = false;\n const abortController = new AbortController();\n let streamController!: ReadableStreamDefaultController<UIMessageChunk>;\n\n const finish = (action: () => void) => {\n if (completed) return;\n completed = true;\n this.#currentFinish = null;\n try {\n action();\n } catch {\n /* stream may already be closed */\n }\n this.#activeRequestIds.delete(requestId);\n abortController.abort();\n };\n\n this.#currentFinish = () => finish(() => streamController.close());\n\n const onAbort = () => {\n if (completed) return;\n try {\n this.#agent.send(JSON.stringify({ type: \"cancel\", requestId }));\n } catch {\n /* ignore send failures */\n }\n finish(() =>\n streamController.error(\n Object.assign(new Error(\"Aborted\"), { name: \"AbortError\" })\n )\n );\n };\n\n const stream = new ReadableStream<UIMessageChunk>({\n start(controller) {\n streamController = controller;\n },\n cancel() {\n onAbort();\n }\n });\n\n this.#agent.addEventListener(\n \"message\",\n (event: MessageEvent) => {\n if (typeof event.data !== \"string\") return;\n try {\n const msg = JSON.parse(event.data);\n if (msg.requestId !== requestId) return;\n if (msg.type === \"stream-event\") {\n const chunk: UIMessageChunk = JSON.parse(msg.event);\n streamController.enqueue(chunk);\n } else if (msg.type === \"stream-done\") {\n finish(() => streamController.close());\n }\n } catch {\n /* ignore parse errors */\n }\n },\n { signal: abortController.signal }\n );\n\n if (abortSignal) {\n abortSignal.addEventListener(\"abort\", onAbort, { once: true });\n if (abortSignal.aborted) onAbort();\n }\n\n this.#activeRequestIds.add(requestId);\n\n this.#agent\n .call(this.#sendMethod, [text, requestId])\n .catch((error: Error) => {\n finish(() => streamController.error(error));\n });\n\n return stream;\n }\n\n async reconnectToStream(): Promise<ReadableStream<UIMessageChunk> | null> {\n const resumeTimeout = this.#resumeTimeout;\n\n return new Promise<ReadableStream<UIMessageChunk> | null>((resolve) => {\n let resolved = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const done = (value: ReadableStream<UIMessageChunk> | null) => {\n if (resolved) return;\n resolved = true;\n if (timeout) clearTimeout(timeout);\n this.#agent.removeEventListener(\"message\", handler);\n resolve(value);\n };\n\n const handler = (event: MessageEvent) => {\n if (typeof event.data !== \"string\") return;\n try {\n const msg = JSON.parse(event.data);\n if (msg.type === \"stream-resuming\") {\n done(this.#createResumeStream(msg.requestId));\n }\n } catch {\n /* ignore */\n }\n };\n\n this.#agent.addEventListener(\"message\", handler);\n\n try {\n this.#agent.send(JSON.stringify({ type: \"resume-request\" }));\n } catch {\n /* WebSocket may not be open yet */\n }\n\n timeout = setTimeout(() => done(null), resumeTimeout);\n });\n }\n\n #createResumeStream(requestId: string): ReadableStream<UIMessageChunk> {\n const abortController = new AbortController();\n let completed = false;\n\n const finish = (action: () => void) => {\n if (completed) return;\n completed = true;\n try {\n action();\n } catch {\n /* stream may already be closed */\n }\n this.#activeRequestIds.delete(requestId);\n abortController.abort();\n };\n\n this.#activeRequestIds.add(requestId);\n\n return new ReadableStream<UIMessageChunk>({\n start: (controller) => {\n this.#agent.addEventListener(\n \"message\",\n (event: MessageEvent) => {\n if (typeof event.data !== \"string\") return;\n try {\n const msg = JSON.parse(event.data);\n if (msg.requestId !== requestId) return;\n if (msg.type === \"stream-event\") {\n const chunk: UIMessageChunk = JSON.parse(msg.event);\n controller.enqueue(chunk);\n } else if (msg.type === \"stream-done\") {\n finish(() => controller.close());\n }\n } catch {\n /* ignore */\n }\n },\n { signal: abortController.signal }\n );\n },\n cancel() {\n finish(() => {});\n }\n });\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,87 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/think",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
3
|
+
"description": "Opinionated assistant agent with session management, workspace tools, and extensions",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"cloudflare",
|
|
6
|
+
"agents",
|
|
7
|
+
"ai",
|
|
8
|
+
"assistant",
|
|
9
|
+
"tools"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"version": "0.0.2",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"directory": "packages/think",
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/cloudflare/agents.git"
|
|
18
|
+
},
|
|
19
|
+
"author": "Cloudflare Inc.",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/cloudflare/agents/issues"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@cloudflare/shell": "^0.0.2",
|
|
25
|
+
"ai": "^6.0.116",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@cloudflare/codemode": "^0.2.1",
|
|
30
|
+
"@cloudflare/shell": "^0.0.2",
|
|
31
|
+
"agents": "^0.7.7",
|
|
32
|
+
"ai": "^6.0.0",
|
|
33
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
8
34
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"@cloudflare/codemode": {
|
|
37
|
+
"optional": true
|
|
38
|
+
},
|
|
39
|
+
"@cloudflare/shell": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/think.d.ts",
|
|
46
|
+
"import": "./dist/think.js"
|
|
47
|
+
},
|
|
48
|
+
"./session": {
|
|
49
|
+
"types": "./dist/session/index.d.ts",
|
|
50
|
+
"import": "./dist/session/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./extensions": {
|
|
53
|
+
"types": "./dist/extensions/index.d.ts",
|
|
54
|
+
"import": "./dist/extensions/index.js"
|
|
55
|
+
},
|
|
56
|
+
"./tools/workspace": {
|
|
57
|
+
"types": "./dist/tools/workspace.d.ts",
|
|
58
|
+
"import": "./dist/tools/workspace.js"
|
|
59
|
+
},
|
|
60
|
+
"./message-builder": {
|
|
61
|
+
"types": "./dist/message-builder.d.ts",
|
|
62
|
+
"import": "./dist/message-builder.js"
|
|
63
|
+
},
|
|
64
|
+
"./transport": {
|
|
65
|
+
"types": "./dist/transport.d.ts",
|
|
66
|
+
"import": "./dist/transport.js"
|
|
67
|
+
},
|
|
68
|
+
"./tools/execute": {
|
|
69
|
+
"types": "./dist/tools/execute.d.ts",
|
|
70
|
+
"import": "./dist/tools/execute.js"
|
|
71
|
+
},
|
|
72
|
+
"./tools/extensions": {
|
|
73
|
+
"types": "./dist/tools/extensions.d.ts",
|
|
74
|
+
"import": "./dist/tools/extensions.js"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"files": [
|
|
78
|
+
"dist",
|
|
79
|
+
"README.md"
|
|
80
|
+
],
|
|
81
|
+
"scripts": {
|
|
82
|
+
"build": "tsx ./scripts/build.ts",
|
|
83
|
+
"test": "vitest --run -c src/tests/vitest.config.ts",
|
|
84
|
+
"test:workers": "vitest --run -c src/tests/vitest.config.ts",
|
|
85
|
+
"test:e2e": "vitest --run -c src/e2e-tests/vitest.config.ts"
|
|
86
|
+
}
|
|
13
87
|
}
|