@posthog/agent 2.3.46 → 2.3.62

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/adapters/claude/conversion/tool-use-to-acp.ts","../../../../src/utils/acp-content.ts","../../../../src/adapters/claude/mcp/tool-metadata.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n PlanEntry,\n ToolCall,\n ToolCallContent,\n ToolCallLocation,\n ToolCallUpdate,\n ToolKind,\n} from \"@agentclientprotocol/sdk\";\nimport type {\n ToolResultBlockParam,\n ToolUseBlock,\n WebSearchToolResultBlockParam,\n} from \"@anthropic-ai/sdk/resources\";\nimport type {\n BetaBashCodeExecutionToolResultBlockParam,\n BetaCodeExecutionToolResultBlockParam,\n BetaRequestMCPToolResultBlockParam,\n BetaTextEditorCodeExecutionToolResultBlockParam,\n BetaToolSearchToolResultBlockParam,\n BetaWebFetchToolResultBlockParam,\n BetaWebSearchToolResultBlockParam,\n} from \"@anthropic-ai/sdk/resources/beta.mjs\";\n\nconst SYSTEM_REMINDER_REGEX =\n /\\s*<system-reminder>[\\s\\S]*?<\\/system-reminder>/g;\n\nfunction stripSystemReminders(value: string): string {\n return value.replace(SYSTEM_REMINDER_REGEX, \"\");\n}\n\nimport { resourceLink, text, toolContent } from \"../../../utils/acp-content\";\nimport { getMcpToolMetadata } from \"../mcp/tool-metadata\";\n\ntype ToolInfo = Pick<ToolCall, \"title\" | \"kind\" | \"content\" | \"locations\">;\n\n/**\n * Convert an absolute file path to a project-relative path for display.\n * Returns the original path if it's outside the project directory or if no cwd is provided.\n */\nexport function toDisplayPath(filePath: string, cwd?: string): string {\n if (!cwd) return filePath;\n const resolvedCwd = path.resolve(cwd);\n const resolvedFile = path.resolve(filePath);\n if (\n resolvedFile.startsWith(resolvedCwd + path.sep) ||\n resolvedFile === resolvedCwd\n ) {\n return path.relative(resolvedCwd, resolvedFile);\n }\n return filePath;\n}\n\nexport function toolInfoFromToolUse(\n toolUse: Pick<ToolUseBlock, \"name\" | \"input\">,\n options?: {\n supportsTerminalOutput?: boolean;\n toolUseId?: string;\n cachedFileContent?: Record<string, string>;\n cwd?: string;\n },\n): ToolInfo {\n const name = toolUse.name;\n const input = toolUse.input as Record<string, unknown> | undefined;\n\n switch (name) {\n case \"Task\":\n case \"Agent\":\n return {\n title: input?.description ? String(input.description) : name,\n kind: \"think\",\n content: input?.prompt\n ? toolContent().text(String(input.prompt)).build()\n : [],\n };\n\n case \"NotebookRead\":\n return {\n title: input?.notebook_path\n ? `Read Notebook ${String(input.notebook_path)}`\n : \"Read Notebook\",\n kind: \"read\",\n content: [],\n locations: input?.notebook_path\n ? [{ path: String(input.notebook_path) }]\n : [],\n };\n\n case \"NotebookEdit\":\n return {\n title: input?.notebook_path\n ? `Edit Notebook ${String(input.notebook_path)}`\n : \"Edit Notebook\",\n kind: \"edit\",\n content: input?.new_source\n ? toolContent().text(String(input.new_source)).build()\n : [],\n locations: input?.notebook_path\n ? [{ path: String(input.notebook_path) }]\n : [],\n };\n\n case \"Bash\":\n if (options?.supportsTerminalOutput && options?.toolUseId) {\n return {\n title: input?.description\n ? String(input.description)\n : \"Execute command\",\n kind: \"execute\",\n content: [{ type: \"terminal\", terminalId: options.toolUseId }],\n };\n }\n return {\n title: input?.description\n ? String(input.description)\n : \"Execute command\",\n kind: \"execute\",\n content: input?.command\n ? toolContent().text(String(input.command)).build()\n : [],\n };\n\n case \"BashOutput\":\n return {\n title: \"Tail Logs\",\n kind: \"execute\",\n content: [],\n };\n\n case \"KillShell\":\n return {\n title: \"Kill Process\",\n kind: \"execute\",\n content: [],\n };\n\n case \"Read\": {\n let limit = \"\";\n const inputLimit = input?.limit as number | undefined;\n const inputOffset = (input?.offset as number | undefined) ?? 1;\n if (inputLimit) {\n limit = ` (${inputOffset} - ${inputOffset + inputLimit - 1})`;\n } else if (inputOffset > 1) {\n limit = ` (from line ${inputOffset})`;\n }\n const displayPath = input?.file_path\n ? toDisplayPath(String(input.file_path), options?.cwd)\n : \"File\";\n return {\n title: `Read ${displayPath}${limit}`,\n kind: \"read\",\n locations: input?.file_path\n ? [\n {\n path: String(input.file_path),\n line: inputOffset,\n },\n ]\n : [],\n content: [],\n };\n }\n\n case \"LS\":\n return {\n title: `List the ${input?.path ? `\\`${String(input.path)}\\`` : \"current\"} directory's contents`,\n kind: \"search\",\n content: [],\n locations: [],\n };\n\n case \"Edit\": {\n const filePath = input?.file_path ? String(input.file_path) : undefined;\n const displayPath = filePath\n ? toDisplayPath(filePath, options?.cwd)\n : undefined;\n let oldText: string | null = input?.old_string\n ? String(input.old_string)\n : null;\n let newText: string = input?.new_string ? String(input.new_string) : \"\";\n\n // try to display a rich diff by first checking if file content is cached\n // and valid (old_text exists in the content), then fall back to reading\n // file from disk, then fall back to fragemented snippet diff\n if (filePath && oldText !== null) {\n const fileContent = resolveFileContent(\n filePath,\n oldText,\n options?.cachedFileContent,\n );\n if (fileContent) {\n const newContent = input?.replace_all\n ? fileContent.replaceAll(oldText, newText)\n : fileContent.replace(oldText, newText);\n oldText = fileContent;\n newText = newContent;\n }\n }\n\n return {\n title: displayPath ? `Edit \\`${displayPath}\\`` : \"Edit\",\n kind: \"edit\",\n content:\n input && filePath\n ? [\n {\n type: \"diff\",\n path: filePath,\n oldText,\n newText,\n },\n ]\n : [],\n locations: filePath ? [{ path: filePath }] : [],\n };\n }\n\n case \"Write\": {\n let contentResult: ToolCallContent[] = [];\n const writeFilePath = input?.file_path\n ? String(input.file_path)\n : undefined;\n const writeDisplayPath = writeFilePath\n ? toDisplayPath(writeFilePath, options?.cwd)\n : undefined;\n const contentStr = input?.content ? String(input.content) : undefined;\n if (writeFilePath) {\n const oldContent =\n options?.cachedFileContent &&\n writeFilePath in options.cachedFileContent\n ? options.cachedFileContent[writeFilePath]\n : null;\n contentResult = toolContent()\n .diff(writeFilePath, oldContent, contentStr ?? \"\")\n .build();\n } else if (contentStr) {\n contentResult = toolContent().text(contentStr).build();\n }\n return {\n title: writeDisplayPath ? `Write ${writeDisplayPath}` : \"Write\",\n kind: \"edit\",\n content: contentResult,\n locations: writeFilePath ? [{ path: writeFilePath }] : [],\n };\n }\n\n case \"Glob\": {\n let label = \"Find\";\n const pathStr = input?.path ? String(input.path) : undefined;\n if (pathStr) {\n label += ` \"${pathStr}\"`;\n }\n if (input?.pattern) {\n label += ` \"${String(input.pattern)}\"`;\n }\n return {\n title: label,\n kind: \"search\",\n content: [],\n locations: pathStr ? [{ path: pathStr }] : [],\n };\n }\n\n case \"Grep\": {\n let label = \"grep\";\n\n if (input?.[\"-i\"]) {\n label += \" -i\";\n }\n if (input?.[\"-n\"]) {\n label += \" -n\";\n }\n\n if (input?.[\"-A\"] !== undefined) {\n label += ` -A ${input[\"-A\"]}`;\n }\n if (input?.[\"-B\"] !== undefined) {\n label += ` -B ${input[\"-B\"]}`;\n }\n if (input?.[\"-C\"] !== undefined) {\n label += ` -C ${input[\"-C\"]}`;\n }\n\n if (input?.output_mode) {\n switch (input.output_mode) {\n case \"files_with_matches\":\n label += \" -l\";\n break;\n case \"count\":\n label += \" -c\";\n break;\n default:\n break;\n }\n }\n\n if (input?.head_limit !== undefined) {\n label += ` | head -${input.head_limit}`;\n }\n\n if (input?.glob) {\n label += ` --include=\"${String(input.glob)}\"`;\n }\n\n if (input?.type) {\n label += ` --type=${String(input.type)}`;\n }\n\n if (input?.multiline) {\n label += \" -P\";\n }\n\n if (input?.pattern) {\n label += ` \"${String(input.pattern)}\"`;\n }\n\n if (input?.path) {\n label += ` ${String(input.path)}`;\n }\n\n return {\n title: label,\n kind: \"search\",\n content: [],\n };\n }\n\n case \"WebFetch\":\n return {\n title: \"Fetch\",\n kind: \"fetch\",\n content: input?.url\n ? [\n {\n type: \"content\",\n content: resourceLink(String(input.url), String(input.url), {\n description: input?.prompt ? String(input.prompt) : undefined,\n }),\n },\n ]\n : [],\n };\n\n case \"WebSearch\": {\n let label = `\"${input?.query ? String(input.query) : \"\"}\"`;\n const allowedDomains = input?.allowed_domains as string[] | undefined;\n const blockedDomains = input?.blocked_domains as string[] | undefined;\n\n if (allowedDomains && allowedDomains.length > 0) {\n label += ` (allowed: ${allowedDomains.join(\", \")})`;\n }\n\n if (blockedDomains && blockedDomains.length > 0) {\n label += ` (blocked: ${blockedDomains.join(\", \")})`;\n }\n\n return {\n title: label,\n kind: \"fetch\",\n content: [],\n };\n }\n\n case \"TodoWrite\":\n return {\n title: Array.isArray(input?.todos)\n ? `Update TODOs: ${input.todos.map((todo: { content?: string }) => todo.content).join(\", \")}`\n : \"Update TODOs\",\n kind: \"think\",\n content: [],\n };\n\n case \"ExitPlanMode\":\n return {\n title: \"Ready to code?\",\n kind: \"switch_mode\",\n content: input?.plan\n ? toolContent().text(String(input.plan)).build()\n : [],\n };\n\n case \"AskUserQuestion\": {\n const questions = input?.questions as\n | Array<{ question?: string }>\n | undefined;\n return {\n title: questions?.[0]?.question || \"Question\",\n kind: \"other\" as ToolKind,\n content: questions\n ? toolContent()\n .text(JSON.stringify(questions, null, 2))\n .build()\n : [],\n };\n }\n\n case \"Other\": {\n let output: string;\n try {\n output = JSON.stringify(input, null, 2);\n } catch {\n output = typeof input === \"string\" ? input : \"{}\";\n }\n return {\n title: name || \"Unknown Tool\",\n kind: \"other\",\n content: toolContent().text(`\\`\\`\\`json\\n${output}\\`\\`\\``).build(),\n };\n }\n\n default: {\n if (name?.startsWith(\"mcp__\")) {\n return mcpToolInfo(name, input);\n }\n return {\n title: name || \"Unknown Tool\",\n kind: \"other\",\n content: [],\n };\n }\n }\n}\n\nfunction mcpToolInfo(\n name: string,\n _input: Record<string, unknown> | undefined,\n): ToolInfo {\n const metadata = getMcpToolMetadata(name);\n // Fallback: parse tool name from mcp__<server>__<tool> prefix\n const title =\n metadata?.name ?? (name.split(\"__\").slice(2).join(\"__\") || name);\n\n return {\n title,\n kind: \"other\",\n content: [],\n };\n}\n\ninterface StructuredPatchHunk {\n oldStart: number;\n oldLines: number;\n newStart: number;\n newLines: number;\n lines: string[];\n}\n\ninterface StructuredPatch {\n oldFileName: string;\n newFileName: string;\n hunks: StructuredPatchHunk[];\n}\n\nexport function toolUpdateFromEditToolResponse(\n toolResponse: unknown,\n): { content: ToolCallContent[]; locations: ToolCallLocation[] } | null {\n if (!toolResponse || typeof toolResponse !== \"object\") return null;\n const response = toolResponse as Record<string, unknown>;\n\n const patches = response.structuredPatch as StructuredPatch[] | undefined;\n if (!Array.isArray(patches) || patches.length === 0) return null;\n\n const content: ToolCallContent[] = [];\n const locations: ToolCallLocation[] = [];\n\n for (const patch of patches) {\n if (!patch.hunks || patch.hunks.length === 0) continue;\n\n const filePath = patch.newFileName || patch.oldFileName;\n\n const oldLines: string[] = [];\n const newLines: string[] = [];\n for (const hunk of patch.hunks) {\n for (const line of hunk.lines) {\n if (line.startsWith(\"-\")) {\n oldLines.push(line.slice(1));\n } else if (line.startsWith(\"+\")) {\n newLines.push(line.slice(1));\n } else if (line.startsWith(\" \")) {\n oldLines.push(line.slice(1));\n newLines.push(line.slice(1));\n }\n }\n }\n\n content.push({\n type: \"diff\",\n path: filePath,\n oldText: oldLines.join(\"\\n\"),\n newText: newLines.join(\"\\n\"),\n });\n\n const firstHunk = patch.hunks[0];\n locations.push({\n path: filePath,\n line: firstHunk.newStart,\n });\n }\n\n if (content.length === 0) return null;\n return { content, locations };\n}\n\nexport function toolUpdateFromToolResult(\n toolResult:\n | ToolResultBlockParam\n | BetaWebSearchToolResultBlockParam\n | BetaWebFetchToolResultBlockParam\n | WebSearchToolResultBlockParam\n | BetaCodeExecutionToolResultBlockParam\n | BetaBashCodeExecutionToolResultBlockParam\n | BetaTextEditorCodeExecutionToolResultBlockParam\n | BetaRequestMCPToolResultBlockParam\n | BetaToolSearchToolResultBlockParam,\n toolUse: Pick<ToolUseBlock, \"name\" | \"input\"> | undefined,\n options?: {\n supportsTerminalOutput?: boolean;\n toolUseId?: string;\n cachedFileContent?: Record<string, string>;\n },\n): Pick<ToolCallUpdate, \"title\" | \"content\" | \"locations\" | \"_meta\"> {\n if (\n \"is_error\" in toolResult &&\n toolResult.is_error &&\n toolResult.content &&\n (toolResult.content as unknown[]).length > 0\n ) {\n return toAcpContentUpdate(toolResult.content, true);\n }\n\n switch (toolUse?.name) {\n case \"Read\":\n if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {\n return {\n content: toolResult.content.map((item) => {\n const itemObj = item as {\n type?: string;\n text?: string;\n source?: { data?: string; media_type?: string };\n };\n if (itemObj.type === \"text\") {\n return {\n type: \"content\" as const,\n content: text(\n markdownEscape(stripSystemReminders(itemObj.text ?? \"\")),\n ),\n };\n }\n if (itemObj.type === \"image\" && itemObj.source) {\n return {\n type: \"content\" as const,\n content: {\n type: \"image\" as const,\n data: itemObj.source.data ?? \"\",\n mimeType: itemObj.source.media_type ?? \"image/png\",\n },\n };\n }\n return {\n type: \"content\" as const,\n content: item as { type: \"text\"; text: string },\n };\n }),\n };\n } else if (\n typeof toolResult.content === \"string\" &&\n toolResult.content.length > 0\n ) {\n return {\n content: toolContent()\n .text(markdownEscape(stripSystemReminders(toolResult.content)))\n .build(),\n };\n }\n return {};\n\n case \"Bash\": {\n const result = toolResult.content;\n const terminalId =\n \"tool_use_id\" in toolResult ? String(toolResult.tool_use_id) : \"\";\n const isError = \"is_error\" in toolResult && toolResult.is_error;\n\n let output = \"\";\n let exitCode = isError ? 1 : 0;\n\n if (\n result &&\n typeof result === \"object\" &&\n \"type\" in result &&\n (result as { type: string }).type === \"bash_code_execution_result\"\n ) {\n const bashResult = result as {\n stdout?: string;\n stderr?: string;\n return_code: number;\n };\n output = [bashResult.stdout, bashResult.stderr]\n .filter(Boolean)\n .join(\"\\n\");\n exitCode = bashResult.return_code;\n } else if (typeof result === \"string\") {\n output = result;\n } else if (\n Array.isArray(result) &&\n result.length > 0 &&\n \"text\" in result[0] &&\n typeof result[0].text === \"string\"\n ) {\n output = result.map((c: { text?: string }) => c.text ?? \"\").join(\"\\n\");\n }\n\n if (options?.supportsTerminalOutput) {\n return {\n content: [{ type: \"terminal\" as const, terminalId }],\n _meta: {\n terminal_info: {\n terminal_id: terminalId,\n },\n terminal_output: {\n terminal_id: terminalId,\n data: output,\n },\n terminal_exit: {\n terminal_id: terminalId,\n exit_code: exitCode,\n signal: null,\n },\n },\n };\n }\n if (output.trim()) {\n return {\n content: toolContent()\n .text(`\\`\\`\\`console\\n${output.trimEnd()}\\n\\`\\`\\``)\n .build(),\n };\n }\n return {};\n }\n case \"Edit\":\n case \"Write\":\n return {};\n\n case \"ExitPlanMode\": {\n return { title: \"Exited Plan Mode\" };\n }\n case \"AskUserQuestion\": {\n const content = toolResult.content;\n if (Array.isArray(content) && content.length > 0) {\n const firstItem = content[0];\n if (\n typeof firstItem === \"object\" &&\n firstItem !== null &&\n \"text\" in firstItem\n ) {\n return {\n title: \"Answer received\",\n content: toolContent().text(String(firstItem.text)).build(),\n };\n }\n }\n return { title: \"Question answered\" };\n }\n case \"WebFetch\": {\n const input = toolUse?.input as Record<string, unknown> | undefined;\n const url = input?.url ? String(input.url) : \"\";\n const prompt = input?.prompt ? String(input.prompt) : undefined;\n\n const resultContent = toAcpContentUpdate(\n toolResult.content,\n \"is_error\" in toolResult ? toolResult.is_error : false,\n );\n\n const content: ToolCallContent[] = [];\n if (url) {\n content.push({\n type: \"content\",\n content: resourceLink(url, url, {\n description: prompt,\n }),\n });\n }\n if (resultContent.content) {\n content.push(...resultContent.content);\n }\n\n return { content };\n }\n default: {\n return toAcpContentUpdate(\n toolResult.content,\n \"is_error\" in toolResult ? toolResult.is_error : false,\n );\n }\n }\n}\n\nfunction itemToText(item: unknown): string | null {\n if (!item || typeof item !== \"object\") return null;\n const obj = item as Record<string, unknown>;\n // Standard text block\n if (obj.type === \"text\" && typeof obj.text === \"string\") {\n return stripSystemReminders(obj.text);\n }\n // Any other structured object — serialize it\n try {\n return JSON.stringify(obj, null, 2);\n } catch {\n return null;\n }\n}\n\nfunction toAcpContentUpdate(\n content: unknown,\n isError: boolean = false,\n): Pick<ToolCallUpdate, \"content\"> {\n if (Array.isArray(content) && content.length > 0) {\n const texts: string[] = [];\n for (const item of content) {\n const t = itemToText(item);\n if (t) texts.push(t);\n }\n if (texts.length > 0) {\n const combined = texts.join(\"\\n\");\n return {\n content: toolContent()\n .text(isError ? `\\`\\`\\`\\n${combined}\\n\\`\\`\\`` : combined)\n .build(),\n };\n }\n } else if (typeof content === \"string\" && content.length > 0) {\n return {\n content: toolContent()\n .text(isError ? `\\`\\`\\`\\n${content}\\n\\`\\`\\`` : content)\n .build(),\n };\n } else if (content && typeof content === \"object\") {\n try {\n const json = JSON.stringify(content, null, 2);\n if (json && json !== \"{}\") {\n return {\n content: toolContent().text(json).build(),\n };\n }\n } catch {\n // ignore serialization errors\n }\n }\n return {};\n}\n\nexport type ClaudePlanEntry = {\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n activeForm: string;\n};\n\nexport function planEntries(input: { todos: ClaudePlanEntry[] }): PlanEntry[] {\n return input.todos.map((input) => ({\n content: input.content,\n status: input.status,\n priority: \"medium\",\n }));\n}\n\n/**\n * attempt to resolve full file contents for diff generation\n *\n * 1) check file content cache exists, and is valid (old_text in content)\n * 2) if missing or invalid, read file from disk\n * 3) if both fail, return null, we'll fall back to fragmented snippet diff\n */\nfunction resolveFileContent(\n filePath: string,\n oldText: string,\n cachedFileContent?: Record<string, string>,\n): string | null {\n if (cachedFileContent && filePath in cachedFileContent) {\n const cached = cachedFileContent[filePath];\n if (cached.includes(oldText)) {\n return cached;\n }\n }\n\n try {\n const content = fs.readFileSync(filePath, \"utf-8\");\n if (content.includes(oldText)) {\n return content;\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction markdownEscape(text: string): string {\n let escapedText = \"```\";\n for (const [m] of text.matchAll(/^```+/gm)) {\n while (m.length >= escapedText.length) {\n escapedText += \"`\";\n }\n }\n return `${escapedText}\\n${text}${text.endsWith(\"\\n\") ? \"\" : \"\\n\"}${escapedText}`;\n}\n","import type { ContentBlock, ToolCallContent } from \"@agentclientprotocol/sdk\";\n\nexport function text(value: string): ContentBlock {\n return { type: \"text\", text: value };\n}\n\nexport function image(\n data: string,\n mimeType: string,\n uri?: string,\n): ContentBlock {\n return { type: \"image\", data, mimeType, uri };\n}\n\nexport function resourceLink(\n uri: string,\n name: string,\n options?: {\n mimeType?: string;\n title?: string;\n description?: string;\n size?: number | null;\n },\n): ContentBlock {\n return {\n type: \"resource_link\",\n uri,\n name,\n ...options,\n };\n}\n\nclass ToolContentBuilder {\n private items: ToolCallContent[] = [];\n\n text(value: string): this {\n this.items.push({ type: \"content\", content: text(value) });\n return this;\n }\n\n image(data: string, mimeType: string, uri?: string): this {\n this.items.push({ type: \"content\", content: image(data, mimeType, uri) });\n return this;\n }\n\n diff(path: string, oldText: string | null, newText: string): this {\n this.items.push({ type: \"diff\", path, oldText, newText });\n return this;\n }\n\n build(): ToolCallContent[] {\n return this.items;\n }\n}\n\nexport function toolContent(): ToolContentBuilder {\n return new ToolContentBuilder();\n}\n","import type { McpServerStatus, Query } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Logger } from \"../../../utils/logger\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n name: string;\n description?: string;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nconst PENDING_RETRY_INTERVAL_MS = 1_000;\nconst PENDING_MAX_RETRIES = 10;\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function fetchMcpToolMetadata(\n q: Query,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n let retries = 0;\n\n while (retries <= PENDING_MAX_RETRIES) {\n let statuses: McpServerStatus[];\n try {\n statuses = await q.mcpServerStatus();\n } catch (error) {\n logger.error(\"Failed to fetch MCP server status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n const pendingServers = statuses.filter((s) => s.status === \"pending\");\n\n for (const server of statuses) {\n if (server.status !== \"connected\" || !server.tools) {\n continue;\n }\n\n let readOnlyCount = 0;\n for (const tool of server.tools) {\n const toolKey = buildToolKey(server.name, tool.name);\n const readOnly = tool.annotations?.readOnly === true;\n mcpToolMetadataCache.set(toolKey, {\n readOnly,\n name: tool.name,\n description: tool.description,\n });\n if (readOnly) readOnlyCount++;\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName: server.name,\n toolCount: server.tools.length,\n readOnlyCount,\n });\n }\n\n if (pendingServers.length === 0) {\n return;\n }\n\n retries++;\n if (retries > PENDING_MAX_RETRIES) {\n logger.warn(\"Gave up waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n });\n return;\n }\n\n logger.info(\"Waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n retry: retries,\n });\n await delay(PENDING_RETRY_INTERVAL_MS);\n }\n}\n\nexport function getMcpToolMetadata(\n toolName: string,\n): McpToolMetadata | undefined {\n return mcpToolMetadataCache.get(toolName);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n"],"mappings":"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","names":["path","input","text"]}
1
+ {"version":3,"sources":["../../../../src/adapters/claude/conversion/tool-use-to-acp.ts","../../../../src/utils/acp-content.ts","../../../../src/adapters/claude/mcp/tool-metadata.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n PlanEntry,\n ToolCall,\n ToolCallContent,\n ToolCallLocation,\n ToolCallUpdate,\n ToolKind,\n} from \"@agentclientprotocol/sdk\";\nimport type {\n ToolResultBlockParam,\n ToolUseBlock,\n WebSearchToolResultBlockParam,\n} from \"@anthropic-ai/sdk/resources\";\nimport type {\n BetaBashCodeExecutionToolResultBlockParam,\n BetaCodeExecutionToolResultBlockParam,\n BetaRequestMCPToolResultBlockParam,\n BetaTextEditorCodeExecutionToolResultBlockParam,\n BetaToolSearchToolResultBlockParam,\n BetaWebFetchToolResultBlockParam,\n BetaWebSearchToolResultBlockParam,\n} from \"@anthropic-ai/sdk/resources/beta.mjs\";\n\nconst SYSTEM_REMINDER_REGEX =\n /\\s*<system-reminder>[\\s\\S]*?<\\/system-reminder>/g;\n\nfunction stripSystemReminders(value: string): string {\n return value.replace(SYSTEM_REMINDER_REGEX, \"\");\n}\n\nimport { resourceLink, text, toolContent } from \"../../../utils/acp-content\";\nimport { getMcpToolMetadata } from \"../mcp/tool-metadata\";\n\ntype ToolInfo = Pick<ToolCall, \"title\" | \"kind\" | \"content\" | \"locations\">;\n\n/**\n * Convert an absolute file path to a project-relative path for display.\n * Returns the original path if it's outside the project directory or if no cwd is provided.\n */\nexport function toDisplayPath(filePath: string, cwd?: string): string {\n if (!cwd) return filePath;\n const resolvedCwd = path.resolve(cwd);\n const resolvedFile = path.resolve(filePath);\n if (\n resolvedFile.startsWith(resolvedCwd + path.sep) ||\n resolvedFile === resolvedCwd\n ) {\n return path.relative(resolvedCwd, resolvedFile);\n }\n return filePath;\n}\n\nexport function toolInfoFromToolUse(\n toolUse: Pick<ToolUseBlock, \"name\" | \"input\">,\n options?: {\n supportsTerminalOutput?: boolean;\n toolUseId?: string;\n cachedFileContent?: Record<string, string>;\n cwd?: string;\n },\n): ToolInfo {\n const name = toolUse.name;\n const input = toolUse.input as Record<string, unknown> | undefined;\n\n switch (name) {\n case \"Task\":\n case \"Agent\":\n return {\n title: input?.description ? String(input.description) : name,\n kind: \"think\",\n content: input?.prompt\n ? toolContent().text(String(input.prompt)).build()\n : [],\n };\n\n case \"NotebookRead\":\n return {\n title: input?.notebook_path\n ? `Read Notebook ${String(input.notebook_path)}`\n : \"Read Notebook\",\n kind: \"read\",\n content: [],\n locations: input?.notebook_path\n ? [{ path: String(input.notebook_path) }]\n : [],\n };\n\n case \"NotebookEdit\":\n return {\n title: input?.notebook_path\n ? `Edit Notebook ${String(input.notebook_path)}`\n : \"Edit Notebook\",\n kind: \"edit\",\n content: input?.new_source\n ? toolContent().text(String(input.new_source)).build()\n : [],\n locations: input?.notebook_path\n ? [{ path: String(input.notebook_path) }]\n : [],\n };\n\n case \"Bash\":\n if (options?.supportsTerminalOutput && options?.toolUseId) {\n return {\n title: input?.description\n ? String(input.description)\n : \"Execute command\",\n kind: \"execute\",\n content: [{ type: \"terminal\", terminalId: options.toolUseId }],\n };\n }\n return {\n title: input?.description\n ? String(input.description)\n : \"Execute command\",\n kind: \"execute\",\n content: input?.command\n ? toolContent().text(String(input.command)).build()\n : [],\n };\n\n case \"BashOutput\":\n return {\n title: \"Tail Logs\",\n kind: \"execute\",\n content: [],\n };\n\n case \"KillShell\":\n return {\n title: \"Kill Process\",\n kind: \"execute\",\n content: [],\n };\n\n case \"Read\": {\n let limit = \"\";\n const inputLimit = input?.limit as number | undefined;\n const inputOffset = (input?.offset as number | undefined) ?? 1;\n if (inputLimit) {\n limit = ` (${inputOffset} - ${inputOffset + inputLimit - 1})`;\n } else if (inputOffset > 1) {\n limit = ` (from line ${inputOffset})`;\n }\n const displayPath = input?.file_path\n ? toDisplayPath(String(input.file_path), options?.cwd)\n : \"File\";\n return {\n title: `Read ${displayPath}${limit}`,\n kind: \"read\",\n locations: input?.file_path\n ? [\n {\n path: String(input.file_path),\n line: inputOffset,\n },\n ]\n : [],\n content: [],\n };\n }\n\n case \"LS\":\n return {\n title: `List the ${input?.path ? `\\`${String(input.path)}\\`` : \"current\"} directory's contents`,\n kind: \"search\",\n content: [],\n locations: [],\n };\n\n case \"Edit\": {\n const filePath = input?.file_path ? String(input.file_path) : undefined;\n const displayPath = filePath\n ? toDisplayPath(filePath, options?.cwd)\n : undefined;\n let oldText: string | null = input?.old_string\n ? String(input.old_string)\n : null;\n let newText: string = input?.new_string ? String(input.new_string) : \"\";\n\n // try to display a rich diff by first checking if file content is cached\n // and valid (old_text exists in the content), then fall back to reading\n // file from disk, then fall back to fragemented snippet diff\n if (filePath && oldText !== null) {\n const fileContent = resolveFileContent(\n filePath,\n oldText,\n options?.cachedFileContent,\n );\n if (fileContent) {\n const newContent = input?.replace_all\n ? fileContent.replaceAll(oldText, newText)\n : fileContent.replace(oldText, newText);\n oldText = fileContent;\n newText = newContent;\n }\n }\n\n return {\n title: displayPath ? `Edit \\`${displayPath}\\`` : \"Edit\",\n kind: \"edit\",\n content:\n input && filePath\n ? [\n {\n type: \"diff\",\n path: filePath,\n oldText,\n newText,\n },\n ]\n : [],\n locations: filePath ? [{ path: filePath }] : [],\n };\n }\n\n case \"Write\": {\n let contentResult: ToolCallContent[] = [];\n const writeFilePath = input?.file_path\n ? String(input.file_path)\n : undefined;\n const writeDisplayPath = writeFilePath\n ? toDisplayPath(writeFilePath, options?.cwd)\n : undefined;\n const contentStr = input?.content ? String(input.content) : undefined;\n if (writeFilePath) {\n const oldContent =\n options?.cachedFileContent &&\n writeFilePath in options.cachedFileContent\n ? options.cachedFileContent[writeFilePath]\n : null;\n contentResult = toolContent()\n .diff(writeFilePath, oldContent, contentStr ?? \"\")\n .build();\n } else if (contentStr) {\n contentResult = toolContent().text(contentStr).build();\n }\n return {\n title: writeDisplayPath ? `Write ${writeDisplayPath}` : \"Write\",\n kind: \"edit\",\n content: contentResult,\n locations: writeFilePath ? [{ path: writeFilePath }] : [],\n };\n }\n\n case \"Glob\": {\n let label = \"Find\";\n const pathStr = input?.path ? String(input.path) : undefined;\n if (pathStr) {\n label += ` \"${pathStr}\"`;\n }\n if (input?.pattern) {\n label += ` \"${String(input.pattern)}\"`;\n }\n return {\n title: label,\n kind: \"search\",\n content: [],\n locations: pathStr ? [{ path: pathStr }] : [],\n };\n }\n\n case \"Grep\": {\n let label = \"grep\";\n\n if (input?.[\"-i\"]) {\n label += \" -i\";\n }\n if (input?.[\"-n\"]) {\n label += \" -n\";\n }\n\n if (input?.[\"-A\"] !== undefined) {\n label += ` -A ${input[\"-A\"]}`;\n }\n if (input?.[\"-B\"] !== undefined) {\n label += ` -B ${input[\"-B\"]}`;\n }\n if (input?.[\"-C\"] !== undefined) {\n label += ` -C ${input[\"-C\"]}`;\n }\n\n if (input?.output_mode) {\n switch (input.output_mode) {\n case \"files_with_matches\":\n label += \" -l\";\n break;\n case \"count\":\n label += \" -c\";\n break;\n default:\n break;\n }\n }\n\n if (input?.head_limit !== undefined) {\n label += ` | head -${input.head_limit}`;\n }\n\n if (input?.glob) {\n label += ` --include=\"${String(input.glob)}\"`;\n }\n\n if (input?.type) {\n label += ` --type=${String(input.type)}`;\n }\n\n if (input?.multiline) {\n label += \" -P\";\n }\n\n if (input?.pattern) {\n label += ` \"${String(input.pattern)}\"`;\n }\n\n if (input?.path) {\n label += ` ${String(input.path)}`;\n }\n\n return {\n title: label,\n kind: \"search\",\n content: [],\n };\n }\n\n case \"WebFetch\":\n return {\n title: \"Fetch\",\n kind: \"fetch\",\n content: input?.url\n ? [\n {\n type: \"content\",\n content: resourceLink(String(input.url), String(input.url), {\n description: input?.prompt ? String(input.prompt) : undefined,\n }),\n },\n ]\n : [],\n };\n\n case \"WebSearch\": {\n let label = `\"${input?.query ? String(input.query) : \"\"}\"`;\n const allowedDomains = input?.allowed_domains as string[] | undefined;\n const blockedDomains = input?.blocked_domains as string[] | undefined;\n\n if (allowedDomains && allowedDomains.length > 0) {\n label += ` (allowed: ${allowedDomains.join(\", \")})`;\n }\n\n if (blockedDomains && blockedDomains.length > 0) {\n label += ` (blocked: ${blockedDomains.join(\", \")})`;\n }\n\n return {\n title: label,\n kind: \"fetch\",\n content: [],\n };\n }\n\n case \"TodoWrite\":\n return {\n title: Array.isArray(input?.todos)\n ? `Update TODOs: ${input.todos.map((todo: { content?: string }) => todo.content).join(\", \")}`\n : \"Update TODOs\",\n kind: \"think\",\n content: [],\n };\n\n case \"ExitPlanMode\":\n return {\n title: \"Ready to code?\",\n kind: \"switch_mode\",\n content: input?.plan\n ? toolContent().text(String(input.plan)).build()\n : [],\n };\n\n case \"AskUserQuestion\": {\n const questions = input?.questions as\n | Array<{ question?: string }>\n | undefined;\n return {\n title: questions?.[0]?.question || \"Question\",\n kind: \"other\" as ToolKind,\n content: questions\n ? toolContent()\n .text(JSON.stringify(questions, null, 2))\n .build()\n : [],\n };\n }\n\n case \"Other\": {\n let output: string;\n try {\n output = JSON.stringify(input, null, 2);\n } catch {\n output = typeof input === \"string\" ? input : \"{}\";\n }\n return {\n title: name || \"Unknown Tool\",\n kind: \"other\",\n content: toolContent().text(`\\`\\`\\`json\\n${output}\\`\\`\\``).build(),\n };\n }\n\n default: {\n if (name?.startsWith(\"mcp__\")) {\n return mcpToolInfo(name, input);\n }\n return {\n title: name || \"Unknown Tool\",\n kind: \"other\",\n content: [],\n };\n }\n }\n}\n\nfunction mcpToolInfo(\n name: string,\n _input: Record<string, unknown> | undefined,\n): ToolInfo {\n const metadata = getMcpToolMetadata(name);\n // Fallback: parse tool name from mcp__<server>__<tool> prefix\n const title =\n metadata?.name ?? (name.split(\"__\").slice(2).join(\"__\") || name);\n\n return {\n title,\n kind: \"other\",\n content: [],\n };\n}\n\ninterface StructuredPatchHunk {\n oldStart: number;\n oldLines: number;\n newStart: number;\n newLines: number;\n lines: string[];\n}\n\ninterface StructuredPatch {\n oldFileName: string;\n newFileName: string;\n hunks: StructuredPatchHunk[];\n}\n\nexport function toolUpdateFromEditToolResponse(\n toolResponse: unknown,\n): { content: ToolCallContent[]; locations: ToolCallLocation[] } | null {\n if (!toolResponse || typeof toolResponse !== \"object\") return null;\n const response = toolResponse as Record<string, unknown>;\n\n const patches = response.structuredPatch as StructuredPatch[] | undefined;\n if (!Array.isArray(patches) || patches.length === 0) return null;\n\n const content: ToolCallContent[] = [];\n const locations: ToolCallLocation[] = [];\n\n for (const patch of patches) {\n if (!patch.hunks || patch.hunks.length === 0) continue;\n\n const filePath = patch.newFileName || patch.oldFileName;\n\n const oldLines: string[] = [];\n const newLines: string[] = [];\n for (const hunk of patch.hunks) {\n for (const line of hunk.lines) {\n if (line.startsWith(\"-\")) {\n oldLines.push(line.slice(1));\n } else if (line.startsWith(\"+\")) {\n newLines.push(line.slice(1));\n } else if (line.startsWith(\" \")) {\n oldLines.push(line.slice(1));\n newLines.push(line.slice(1));\n }\n }\n }\n\n content.push({\n type: \"diff\",\n path: filePath,\n oldText: oldLines.join(\"\\n\"),\n newText: newLines.join(\"\\n\"),\n });\n\n const firstHunk = patch.hunks[0];\n locations.push({\n path: filePath,\n line: firstHunk.newStart,\n });\n }\n\n if (content.length === 0) return null;\n return { content, locations };\n}\n\nexport function toolUpdateFromToolResult(\n toolResult:\n | ToolResultBlockParam\n | BetaWebSearchToolResultBlockParam\n | BetaWebFetchToolResultBlockParam\n | WebSearchToolResultBlockParam\n | BetaCodeExecutionToolResultBlockParam\n | BetaBashCodeExecutionToolResultBlockParam\n | BetaTextEditorCodeExecutionToolResultBlockParam\n | BetaRequestMCPToolResultBlockParam\n | BetaToolSearchToolResultBlockParam,\n toolUse: Pick<ToolUseBlock, \"name\" | \"input\"> | undefined,\n options?: {\n supportsTerminalOutput?: boolean;\n toolUseId?: string;\n cachedFileContent?: Record<string, string>;\n },\n): Pick<ToolCallUpdate, \"title\" | \"content\" | \"locations\" | \"_meta\"> {\n if (\n \"is_error\" in toolResult &&\n toolResult.is_error &&\n toolResult.content &&\n (toolResult.content as unknown[]).length > 0\n ) {\n return toAcpContentUpdate(toolResult.content, true);\n }\n\n switch (toolUse?.name) {\n case \"Read\":\n if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {\n return {\n content: toolResult.content.map((item) => {\n const itemObj = item as {\n type?: string;\n text?: string;\n source?: { data?: string; media_type?: string };\n };\n if (itemObj.type === \"text\") {\n return {\n type: \"content\" as const,\n content: text(\n markdownEscape(stripSystemReminders(itemObj.text ?? \"\")),\n ),\n };\n }\n if (itemObj.type === \"image\" && itemObj.source) {\n return {\n type: \"content\" as const,\n content: {\n type: \"image\" as const,\n data: itemObj.source.data ?? \"\",\n mimeType: itemObj.source.media_type ?? \"image/png\",\n },\n };\n }\n return {\n type: \"content\" as const,\n content: item as { type: \"text\"; text: string },\n };\n }),\n };\n } else if (\n typeof toolResult.content === \"string\" &&\n toolResult.content.length > 0\n ) {\n return {\n content: toolContent()\n .text(markdownEscape(stripSystemReminders(toolResult.content)))\n .build(),\n };\n }\n return {};\n\n case \"Bash\": {\n const result = toolResult.content;\n const terminalId =\n \"tool_use_id\" in toolResult ? String(toolResult.tool_use_id) : \"\";\n const isError = \"is_error\" in toolResult && toolResult.is_error;\n\n let output = \"\";\n let exitCode = isError ? 1 : 0;\n\n if (\n result &&\n typeof result === \"object\" &&\n \"type\" in result &&\n (result as { type: string }).type === \"bash_code_execution_result\"\n ) {\n const bashResult = result as {\n stdout?: string;\n stderr?: string;\n return_code: number;\n };\n output = [bashResult.stdout, bashResult.stderr]\n .filter(Boolean)\n .join(\"\\n\");\n exitCode = bashResult.return_code;\n } else if (typeof result === \"string\") {\n output = result;\n } else if (\n Array.isArray(result) &&\n result.length > 0 &&\n \"text\" in result[0] &&\n typeof result[0].text === \"string\"\n ) {\n output = result.map((c: { text?: string }) => c.text ?? \"\").join(\"\\n\");\n }\n\n if (options?.supportsTerminalOutput) {\n return {\n content: [{ type: \"terminal\" as const, terminalId }],\n _meta: {\n terminal_info: {\n terminal_id: terminalId,\n },\n terminal_output: {\n terminal_id: terminalId,\n data: output,\n },\n terminal_exit: {\n terminal_id: terminalId,\n exit_code: exitCode,\n signal: null,\n },\n },\n };\n }\n if (output.trim()) {\n return {\n content: toolContent()\n .text(`\\`\\`\\`console\\n${output.trimEnd()}\\n\\`\\`\\``)\n .build(),\n };\n }\n return {};\n }\n case \"Edit\":\n case \"Write\":\n return {};\n\n case \"ExitPlanMode\": {\n return { title: \"Exited Plan Mode\" };\n }\n case \"AskUserQuestion\": {\n const content = toolResult.content;\n if (Array.isArray(content) && content.length > 0) {\n const firstItem = content[0];\n if (\n typeof firstItem === \"object\" &&\n firstItem !== null &&\n \"text\" in firstItem\n ) {\n return {\n title: \"Answer received\",\n content: toolContent().text(String(firstItem.text)).build(),\n };\n }\n }\n return { title: \"Question answered\" };\n }\n case \"WebFetch\": {\n const input = toolUse?.input as Record<string, unknown> | undefined;\n const url = input?.url ? String(input.url) : \"\";\n const prompt = input?.prompt ? String(input.prompt) : undefined;\n\n const resultContent = toAcpContentUpdate(\n toolResult.content,\n \"is_error\" in toolResult ? toolResult.is_error : false,\n );\n\n const content: ToolCallContent[] = [];\n if (url) {\n content.push({\n type: \"content\",\n content: resourceLink(url, url, {\n description: prompt,\n }),\n });\n }\n if (resultContent.content) {\n content.push(...resultContent.content);\n }\n\n return { content };\n }\n default: {\n return toAcpContentUpdate(\n toolResult.content,\n \"is_error\" in toolResult ? toolResult.is_error : false,\n );\n }\n }\n}\n\nfunction itemToText(item: unknown): string | null {\n if (!item || typeof item !== \"object\") return null;\n const obj = item as Record<string, unknown>;\n // Standard text block\n if (obj.type === \"text\" && typeof obj.text === \"string\") {\n return stripSystemReminders(obj.text);\n }\n // Any other structured object — serialize it\n try {\n return JSON.stringify(obj, null, 2);\n } catch {\n return null;\n }\n}\n\nfunction toAcpContentUpdate(\n content: unknown,\n isError: boolean = false,\n): Pick<ToolCallUpdate, \"content\"> {\n if (Array.isArray(content) && content.length > 0) {\n const texts: string[] = [];\n for (const item of content) {\n const t = itemToText(item);\n if (t) texts.push(t);\n }\n if (texts.length > 0) {\n const combined = texts.join(\"\\n\");\n return {\n content: toolContent()\n .text(isError ? `\\`\\`\\`\\n${combined}\\n\\`\\`\\`` : combined)\n .build(),\n };\n }\n } else if (typeof content === \"string\" && content.length > 0) {\n return {\n content: toolContent()\n .text(isError ? `\\`\\`\\`\\n${content}\\n\\`\\`\\`` : content)\n .build(),\n };\n } else if (content && typeof content === \"object\") {\n try {\n const json = JSON.stringify(content, null, 2);\n if (json && json !== \"{}\") {\n return {\n content: toolContent().text(json).build(),\n };\n }\n } catch {\n // ignore serialization errors\n }\n }\n return {};\n}\n\nexport type ClaudePlanEntry = {\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n activeForm: string;\n};\n\nexport function planEntries(input: { todos: ClaudePlanEntry[] }): PlanEntry[] {\n return input.todos.map((input) => ({\n content: input.content,\n status: input.status,\n priority: \"medium\",\n }));\n}\n\n/**\n * attempt to resolve full file contents for diff generation\n *\n * 1) check file content cache exists, and is valid (old_text in content)\n * 2) if missing or invalid, read file from disk\n * 3) if both fail, return null, we'll fall back to fragmented snippet diff\n */\nfunction resolveFileContent(\n filePath: string,\n oldText: string,\n cachedFileContent?: Record<string, string>,\n): string | null {\n if (cachedFileContent && filePath in cachedFileContent) {\n const cached = cachedFileContent[filePath];\n if (cached.includes(oldText)) {\n return cached;\n }\n }\n\n try {\n const content = fs.readFileSync(filePath, \"utf-8\");\n if (content.includes(oldText)) {\n return content;\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction markdownEscape(text: string): string {\n let escapedText = \"```\";\n for (const [m] of text.matchAll(/^```+/gm)) {\n while (m.length >= escapedText.length) {\n escapedText += \"`\";\n }\n }\n return `${escapedText}\\n${text}${text.endsWith(\"\\n\") ? \"\" : \"\\n\"}${escapedText}`;\n}\n","import type { ContentBlock, ToolCallContent } from \"@agentclientprotocol/sdk\";\n\nexport function text(value: string): ContentBlock {\n return { type: \"text\", text: value };\n}\n\nexport function image(\n data: string,\n mimeType: string,\n uri?: string,\n): ContentBlock {\n return { type: \"image\", data, mimeType, uri };\n}\n\nexport function resourceLink(\n uri: string,\n name: string,\n options?: {\n mimeType?: string;\n title?: string;\n description?: string;\n size?: number | null;\n },\n): ContentBlock {\n return {\n type: \"resource_link\",\n uri,\n name,\n ...options,\n };\n}\n\nclass ToolContentBuilder {\n private items: ToolCallContent[] = [];\n\n text(value: string): this {\n this.items.push({ type: \"content\", content: text(value) });\n return this;\n }\n\n image(data: string, mimeType: string, uri?: string): this {\n this.items.push({ type: \"content\", content: image(data, mimeType, uri) });\n return this;\n }\n\n diff(path: string, oldText: string | null, newText: string): this {\n this.items.push({ type: \"diff\", path, oldText, newText });\n return this;\n }\n\n build(): ToolCallContent[] {\n return this.items;\n }\n}\n\nexport function toolContent(): ToolContentBuilder {\n return new ToolContentBuilder();\n}\n","import type { McpServerStatus, Query } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Logger } from \"../../../utils/logger\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n name: string;\n description?: string;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nconst PENDING_RETRY_INTERVAL_MS = 1_000;\nconst PENDING_MAX_RETRIES = 10;\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function fetchMcpToolMetadata(\n q: Query,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n let retries = 0;\n\n while (retries <= PENDING_MAX_RETRIES) {\n let statuses: McpServerStatus[];\n try {\n statuses = await q.mcpServerStatus();\n } catch (error) {\n logger.error(\"Failed to fetch MCP server status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n const pendingServers = statuses.filter((s) => s.status === \"pending\");\n\n for (const server of statuses) {\n if (server.status !== \"connected\" || !server.tools) {\n continue;\n }\n\n let readOnlyCount = 0;\n for (const tool of server.tools) {\n const toolKey = buildToolKey(server.name, tool.name);\n const readOnly = tool.annotations?.readOnly === true;\n\n mcpToolMetadataCache.set(toolKey, {\n readOnly,\n name: tool.name,\n description: tool.description,\n });\n if (readOnly) readOnlyCount++;\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName: server.name,\n toolCount: server.tools.length,\n readOnlyCount,\n });\n }\n\n if (pendingServers.length === 0) {\n return;\n }\n\n retries++;\n if (retries > PENDING_MAX_RETRIES) {\n logger.warn(\"Gave up waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n });\n return;\n }\n\n logger.info(\"Waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n retry: retries,\n });\n await delay(PENDING_RETRY_INTERVAL_MS);\n }\n}\n\nexport function getMcpToolMetadata(\n toolName: string,\n): McpToolMetadata | undefined {\n return mcpToolMetadataCache.get(toolName);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function getConnectedMcpServerNames(): string[] {\n const names = new Set<string>();\n for (const key of mcpToolMetadataCache.keys()) {\n const parts = key.split(\"__\");\n if (parts.length >= 3) names.add(parts[1]);\n }\n return [...names];\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n"],"mappings":"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","names":["path","input","text"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/common.ts","../../../src/execution-mode.ts","../../../src/adapters/claude/mcp/tool-metadata.ts","../../../src/adapters/claude/tools.ts"],"sourcesContent":["import type { Logger } from \"./logger\";\n\n/**\n * Races an operation against a timeout.\n * Returns success with the value if the operation completes in time,\n * or timeout if the operation takes longer than the specified duration.\n */\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n): Promise<{ result: \"success\"; value: T } | { result: \"timeout\" }> {\n const timeoutPromise = new Promise<{ result: \"timeout\" }>((resolve) =>\n setTimeout(() => resolve({ result: \"timeout\" }), timeoutMs),\n );\n const operationPromise = operation.then((value) => ({\n result: \"success\" as const,\n value,\n }));\n return Promise.race([operationPromise, timeoutPromise]);\n}\n\nexport const IS_ROOT =\n typeof process !== \"undefined\" &&\n (process.geteuid?.() ?? process.getuid?.()) === 0;\n\nexport function unreachable(value: never, logger: Logger): void {\n let valueAsString: string;\n try {\n valueAsString = JSON.stringify(value);\n } catch {\n valueAsString = String(value);\n }\n logger.error(`Unexpected case: ${valueAsString}`);\n}\n","import { IS_ROOT } from \"./utils/common\";\n\nexport interface ModeInfo {\n id: CodeExecutionMode;\n name: string;\n description: string;\n}\n\n// Helper constant that can easily be toggled for env/feature flag/etc\nconst ALLOW_BYPASS = !IS_ROOT;\n\nconst availableModes: ModeInfo[] = [\n {\n id: \"default\",\n name: \"Default\",\n description: \"Standard behavior, prompts for dangerous operations\",\n },\n {\n id: \"acceptEdits\",\n name: \"Accept Edits\",\n description: \"Auto-accept file edit operations\",\n },\n {\n id: \"plan\",\n name: \"Plan Mode\",\n description: \"Planning mode, no actual tool execution\",\n },\n // {\n // id: \"dontAsk\",\n // name: \"Don't Ask\",\n // description: \"Don't prompt for permissions, deny if not pre-approved\",\n // },\n];\n\nif (ALLOW_BYPASS) {\n availableModes.push({\n id: \"bypassPermissions\",\n name: \"Bypass Permissions\",\n description: \"Bypass all permission checks\",\n });\n}\n\n// Expose execution mode IDs in type-safe order for type checks\nexport const CODE_EXECUTION_MODES = [\n \"default\",\n \"acceptEdits\",\n \"plan\",\n // \"dontAsk\",\n \"bypassPermissions\",\n] as const;\n\nexport type CodeExecutionMode = (typeof CODE_EXECUTION_MODES)[number];\n\nexport function getAvailableModes(): ModeInfo[] {\n // When IS_ROOT, do not allow bypassPermissions\n return IS_ROOT\n ? availableModes.filter((m) => m.id !== \"bypassPermissions\")\n : availableModes;\n}\n","import type { McpServerStatus, Query } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Logger } from \"../../../utils/logger\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n name: string;\n description?: string;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nconst PENDING_RETRY_INTERVAL_MS = 1_000;\nconst PENDING_MAX_RETRIES = 10;\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function fetchMcpToolMetadata(\n q: Query,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n let retries = 0;\n\n while (retries <= PENDING_MAX_RETRIES) {\n let statuses: McpServerStatus[];\n try {\n statuses = await q.mcpServerStatus();\n } catch (error) {\n logger.error(\"Failed to fetch MCP server status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n const pendingServers = statuses.filter((s) => s.status === \"pending\");\n\n for (const server of statuses) {\n if (server.status !== \"connected\" || !server.tools) {\n continue;\n }\n\n let readOnlyCount = 0;\n for (const tool of server.tools) {\n const toolKey = buildToolKey(server.name, tool.name);\n const readOnly = tool.annotations?.readOnly === true;\n mcpToolMetadataCache.set(toolKey, {\n readOnly,\n name: tool.name,\n description: tool.description,\n });\n if (readOnly) readOnlyCount++;\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName: server.name,\n toolCount: server.tools.length,\n readOnlyCount,\n });\n }\n\n if (pendingServers.length === 0) {\n return;\n }\n\n retries++;\n if (retries > PENDING_MAX_RETRIES) {\n logger.warn(\"Gave up waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n });\n return;\n }\n\n logger.info(\"Waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n retry: retries,\n });\n await delay(PENDING_RETRY_INTERVAL_MS);\n }\n}\n\nexport function getMcpToolMetadata(\n toolName: string,\n): McpToolMetadata | undefined {\n return mcpToolMetadataCache.get(toolName);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n","export {\n CODE_EXECUTION_MODES,\n type CodeExecutionMode,\n getAvailableModes,\n type ModeInfo,\n} from \"../../execution-mode\";\n\nimport type { CodeExecutionMode } from \"../../execution-mode\";\nimport { isMcpToolReadOnly } from \"./mcp/tool-metadata\";\n\nexport const READ_TOOLS: Set<string> = new Set([\"Read\", \"NotebookRead\"]);\n\nexport const WRITE_TOOLS: Set<string> = new Set([\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n]);\n\nexport const BASH_TOOLS: Set<string> = new Set([\n \"Bash\",\n \"BashOutput\",\n \"KillShell\",\n]);\n\nexport const SEARCH_TOOLS: Set<string> = new Set([\"Glob\", \"Grep\", \"LS\"]);\n\nexport const WEB_TOOLS: Set<string> = new Set([\"WebSearch\", \"WebFetch\"]);\n\nexport const AGENT_TOOLS: Set<string> = new Set([\n \"Task\",\n \"Agent\",\n \"TodoWrite\",\n \"Skill\",\n]);\n\nconst BASE_ALLOWED_TOOLS = [\n ...READ_TOOLS,\n ...SEARCH_TOOLS,\n ...WEB_TOOLS,\n ...AGENT_TOOLS,\n];\n\nconst AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {\n default: new Set(BASE_ALLOWED_TOOLS),\n acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),\n plan: new Set(BASE_ALLOWED_TOOLS),\n // dontAsk: new Set(BASE_ALLOWED_TOOLS),\n};\n\nexport function isToolAllowedForMode(\n toolName: string,\n mode: CodeExecutionMode,\n): boolean {\n if (mode === \"bypassPermissions\") {\n return true;\n }\n if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {\n return true;\n }\n if (isMcpToolReadOnly(toolName)) {\n return true;\n }\n return false;\n}\n"],"mappings":";AAqBO,IAAM,UACX,OAAO,YAAY,gBAClB,QAAQ,UAAU,KAAK,QAAQ,SAAS,OAAO;;;ACdlD,IAAM,eAAe,CAAC;AAEtB,IAAM,iBAA6B;AAAA,EACjC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAMF;AAEA,IAAI,cAAc;AAChB,iBAAe,KAAK;AAAA,IAClB,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf,CAAC;AACH;AAGO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAIO,SAAS,oBAAgC;AAE9C,SAAO,UACH,eAAe,OAAO,CAAC,MAAM,EAAE,OAAO,mBAAmB,IACzD;AACN;;;ACjDA,IAAM,uBAAqD,oBAAI,IAAI;AAkF5D,SAAS,kBAAkB,UAA2B;AAC3D,QAAM,WAAW,qBAAqB,IAAI,QAAQ;AAClD,SAAO,UAAU,aAAa;AAChC;;;ACpFO,IAAM,aAA0B,oBAAI,IAAI,CAAC,QAAQ,cAAc,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,aAA0B,oBAAI,IAAI;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,eAA4B,oBAAI,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAEhE,IAAM,YAAyB,oBAAI,IAAI,CAAC,aAAa,UAAU,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,qBAAqB;AAAA,EACzB,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,IAAM,qBAAkD;AAAA,EACtD,SAAS,IAAI,IAAI,kBAAkB;AAAA,EACnC,aAAa,oBAAI,IAAI,CAAC,GAAG,oBAAoB,GAAG,WAAW,CAAC;AAAA,EAC5D,MAAM,IAAI,IAAI,kBAAkB;AAAA;AAElC;AAEO,SAAS,qBACd,UACA,MACS;AACT,MAAI,SAAS,qBAAqB;AAChC,WAAO;AAAA,EACT;AACA,MAAI,mBAAmB,IAAI,GAAG,IAAI,QAAQ,MAAM,MAAM;AACpD,WAAO;AAAA,EACT;AACA,MAAI,kBAAkB,QAAQ,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/utils/common.ts","../../../src/execution-mode.ts","../../../src/adapters/claude/mcp/tool-metadata.ts","../../../src/adapters/claude/tools.ts"],"sourcesContent":["import type { Logger } from \"./logger\";\n\n/**\n * Races an operation against a timeout.\n * Returns success with the value if the operation completes in time,\n * or timeout if the operation takes longer than the specified duration.\n */\nexport async function withTimeout<T>(\n operation: Promise<T>,\n timeoutMs: number,\n): Promise<{ result: \"success\"; value: T } | { result: \"timeout\" }> {\n const timeoutPromise = new Promise<{ result: \"timeout\" }>((resolve) =>\n setTimeout(() => resolve({ result: \"timeout\" }), timeoutMs),\n );\n const operationPromise = operation.then((value) => ({\n result: \"success\" as const,\n value,\n }));\n return Promise.race([operationPromise, timeoutPromise]);\n}\n\nexport const IS_ROOT =\n typeof process !== \"undefined\" &&\n (process.geteuid?.() ?? process.getuid?.()) === 0;\n\nexport function unreachable(value: never, logger: Logger): void {\n let valueAsString: string;\n try {\n valueAsString = JSON.stringify(value);\n } catch {\n valueAsString = String(value);\n }\n logger.error(`Unexpected case: ${valueAsString}`);\n}\n","import { IS_ROOT } from \"./utils/common\";\n\nexport interface ModeInfo {\n id: CodeExecutionMode;\n name: string;\n description: string;\n}\n\n// Helper constant that can easily be toggled for env/feature flag/etc\nconst ALLOW_BYPASS = !IS_ROOT;\n\nconst availableModes: ModeInfo[] = [\n {\n id: \"default\",\n name: \"Default\",\n description: \"Standard behavior, prompts for dangerous operations\",\n },\n {\n id: \"acceptEdits\",\n name: \"Accept Edits\",\n description: \"Auto-accept file edit operations\",\n },\n {\n id: \"plan\",\n name: \"Plan Mode\",\n description: \"Planning mode, no actual tool execution\",\n },\n // {\n // id: \"dontAsk\",\n // name: \"Don't Ask\",\n // description: \"Don't prompt for permissions, deny if not pre-approved\",\n // },\n];\n\nif (ALLOW_BYPASS) {\n availableModes.push({\n id: \"bypassPermissions\",\n name: \"Bypass Permissions\",\n description: \"Bypass all permission checks\",\n });\n}\n\n// Expose execution mode IDs in type-safe order for type checks\nexport const CODE_EXECUTION_MODES = [\n \"default\",\n \"acceptEdits\",\n \"plan\",\n // \"dontAsk\",\n \"bypassPermissions\",\n] as const;\n\nexport type CodeExecutionMode = (typeof CODE_EXECUTION_MODES)[number];\n\nexport function getAvailableModes(): ModeInfo[] {\n // When IS_ROOT, do not allow bypassPermissions\n return IS_ROOT\n ? availableModes.filter((m) => m.id !== \"bypassPermissions\")\n : availableModes;\n}\n","import type { McpServerStatus, Query } from \"@anthropic-ai/claude-agent-sdk\";\nimport { Logger } from \"../../../utils/logger\";\n\nexport interface McpToolMetadata {\n readOnly: boolean;\n name: string;\n description?: string;\n}\n\nconst mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();\n\nconst PENDING_RETRY_INTERVAL_MS = 1_000;\nconst PENDING_MAX_RETRIES = 10;\n\nfunction buildToolKey(serverName: string, toolName: string): string {\n return `mcp__${serverName}__${toolName}`;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function fetchMcpToolMetadata(\n q: Query,\n logger: Logger = new Logger({ debug: false, prefix: \"[McpToolMetadata]\" }),\n): Promise<void> {\n let retries = 0;\n\n while (retries <= PENDING_MAX_RETRIES) {\n let statuses: McpServerStatus[];\n try {\n statuses = await q.mcpServerStatus();\n } catch (error) {\n logger.error(\"Failed to fetch MCP server status\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n\n const pendingServers = statuses.filter((s) => s.status === \"pending\");\n\n for (const server of statuses) {\n if (server.status !== \"connected\" || !server.tools) {\n continue;\n }\n\n let readOnlyCount = 0;\n for (const tool of server.tools) {\n const toolKey = buildToolKey(server.name, tool.name);\n const readOnly = tool.annotations?.readOnly === true;\n\n mcpToolMetadataCache.set(toolKey, {\n readOnly,\n name: tool.name,\n description: tool.description,\n });\n if (readOnly) readOnlyCount++;\n }\n\n logger.info(\"Fetched MCP tool metadata\", {\n serverName: server.name,\n toolCount: server.tools.length,\n readOnlyCount,\n });\n }\n\n if (pendingServers.length === 0) {\n return;\n }\n\n retries++;\n if (retries > PENDING_MAX_RETRIES) {\n logger.warn(\"Gave up waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n });\n return;\n }\n\n logger.info(\"Waiting for pending MCP servers\", {\n pendingServers: pendingServers.map((s) => s.name),\n retry: retries,\n });\n await delay(PENDING_RETRY_INTERVAL_MS);\n }\n}\n\nexport function getMcpToolMetadata(\n toolName: string,\n): McpToolMetadata | undefined {\n return mcpToolMetadataCache.get(toolName);\n}\n\nexport function isMcpToolReadOnly(toolName: string): boolean {\n const metadata = mcpToolMetadataCache.get(toolName);\n return metadata?.readOnly === true;\n}\n\nexport function getConnectedMcpServerNames(): string[] {\n const names = new Set<string>();\n for (const key of mcpToolMetadataCache.keys()) {\n const parts = key.split(\"__\");\n if (parts.length >= 3) names.add(parts[1]);\n }\n return [...names];\n}\n\nexport function clearMcpToolMetadataCache(): void {\n mcpToolMetadataCache.clear();\n}\n","export {\n CODE_EXECUTION_MODES,\n type CodeExecutionMode,\n getAvailableModes,\n type ModeInfo,\n} from \"../../execution-mode\";\n\nimport type { CodeExecutionMode } from \"../../execution-mode\";\nimport { isMcpToolReadOnly } from \"./mcp/tool-metadata\";\n\nexport const READ_TOOLS: Set<string> = new Set([\"Read\", \"NotebookRead\"]);\n\nexport const WRITE_TOOLS: Set<string> = new Set([\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n]);\n\nexport const BASH_TOOLS: Set<string> = new Set([\n \"Bash\",\n \"BashOutput\",\n \"KillShell\",\n]);\n\nexport const SEARCH_TOOLS: Set<string> = new Set([\"Glob\", \"Grep\", \"LS\"]);\n\nexport const WEB_TOOLS: Set<string> = new Set([\"WebSearch\", \"WebFetch\"]);\n\nexport const AGENT_TOOLS: Set<string> = new Set([\n \"Task\",\n \"Agent\",\n \"TodoWrite\",\n \"Skill\",\n]);\n\nconst BASE_ALLOWED_TOOLS = [\n ...READ_TOOLS,\n ...SEARCH_TOOLS,\n ...WEB_TOOLS,\n ...AGENT_TOOLS,\n];\n\nconst AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {\n default: new Set(BASE_ALLOWED_TOOLS),\n acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),\n plan: new Set(BASE_ALLOWED_TOOLS),\n // dontAsk: new Set(BASE_ALLOWED_TOOLS),\n};\n\nexport function isToolAllowedForMode(\n toolName: string,\n mode: CodeExecutionMode,\n): boolean {\n if (mode === \"bypassPermissions\") {\n return true;\n }\n if (AUTO_ALLOWED_TOOLS[mode]?.has(toolName) === true) {\n return true;\n }\n if (isMcpToolReadOnly(toolName)) {\n return true;\n }\n return false;\n}\n"],"mappings":";AAqBO,IAAM,UACX,OAAO,YAAY,gBAClB,QAAQ,UAAU,KAAK,QAAQ,SAAS,OAAO;;;ACdlD,IAAM,eAAe,CAAC;AAEtB,IAAM,iBAA6B;AAAA,EACjC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAMF;AAEA,IAAI,cAAc;AAChB,iBAAe,KAAK;AAAA,IAClB,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf,CAAC;AACH;AAGO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAIO,SAAS,oBAAgC;AAE9C,SAAO,UACH,eAAe,OAAO,CAAC,MAAM,EAAE,OAAO,mBAAmB,IACzD;AACN;;;ACjDA,IAAM,uBAAqD,oBAAI,IAAI;AAmF5D,SAAS,kBAAkB,UAA2B;AAC3D,QAAM,WAAW,qBAAqB,IAAI,QAAQ;AAClD,SAAO,UAAU,aAAa;AAChC;;;ACrFO,IAAM,aAA0B,oBAAI,IAAI,CAAC,QAAQ,cAAc,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,aAA0B,oBAAI,IAAI;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,eAA4B,oBAAI,IAAI,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAEhE,IAAM,YAAyB,oBAAI,IAAI,CAAC,aAAa,UAAU,CAAC;AAEhE,IAAM,cAA2B,oBAAI,IAAI;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,qBAAqB;AAAA,EACzB,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,IAAM,qBAAkD;AAAA,EACtD,SAAS,IAAI,IAAI,kBAAkB;AAAA,EACnC,aAAa,oBAAI,IAAI,CAAC,GAAG,oBAAoB,GAAG,WAAW,CAAC;AAAA,EAC5D,MAAM,IAAI,IAAI,kBAAkB;AAAA;AAElC;AAEO,SAAS,qBACd,UACA,MACS;AACT,MAAI,SAAS,qBAAqB;AAChC,WAAO;AAAA,EACT;AACA,MAAI,mBAAmB,IAAI,GAAG,IAAI,QAAQ,MAAM,MAAM;AACpD,WAAO;AAAA,EACT;AACA,MAAI,kBAAkB,QAAQ,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
package/dist/agent.js CHANGED
@@ -281,7 +281,7 @@ import { v7 as uuidv7 } from "uuid";
281
281
  // package.json
282
282
  var package_default = {
283
283
  name: "@posthog/agent",
284
- version: "2.3.46",
284
+ version: "2.3.62",
285
285
  repository: "https://github.com/PostHog/code",
286
286
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
287
287
  exports: {
@@ -865,6 +865,14 @@ function isMcpToolReadOnly(toolName) {
865
865
  const metadata = mcpToolMetadataCache.get(toolName);
866
866
  return metadata?.readOnly === true;
867
867
  }
868
+ function getConnectedMcpServerNames() {
869
+ const names = /* @__PURE__ */ new Set();
870
+ for (const key of mcpToolMetadataCache.keys()) {
871
+ const parts = key.split("__");
872
+ if (parts.length >= 3) names.add(parts[1]);
873
+ }
874
+ return [...names];
875
+ }
868
876
 
869
877
  // src/adapters/claude/conversion/tool-use-to-acp.ts
870
878
  var SYSTEM_REMINDER_REGEX = /\s*<system-reminder>[\s\S]*?<\/system-reminder>/g;
@@ -1621,7 +1629,10 @@ function handleToolResultChunk(chunk, ctx) {
1621
1629
  toolCallId: chunk.tool_use_id,
1622
1630
  sessionUpdate: "tool_call_update",
1623
1631
  status: chunk.is_error ? "failed" : "completed",
1624
- rawOutput: chunk.content,
1632
+ rawOutput: ctx.mcpToolUseResult ? { ...ctx.mcpToolUseResult, isError: chunk.is_error ?? false } : {
1633
+ content: Array.isArray(chunk.content) ? chunk.content : typeof chunk.content === "string" ? [{ type: "text", text: chunk.content }] : [],
1634
+ isError: chunk.is_error ?? false
1635
+ },
1625
1636
  ...toolUpdate
1626
1637
  });
1627
1638
  return updates;
@@ -1675,7 +1686,7 @@ function processContentChunk(chunk, role, ctx) {
1675
1686
  return [];
1676
1687
  }
1677
1688
  }
1678
- function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput, cwd) {
1689
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput, cwd, mcpToolUseResult) {
1679
1690
  if (typeof content === "string") {
1680
1691
  const update = {
1681
1692
  sessionUpdate: messageUpdateType(role),
@@ -1699,7 +1710,8 @@ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentC
1699
1710
  parentToolCallId,
1700
1711
  registerHooks,
1701
1712
  supportsTerminalOutput,
1702
- cwd
1713
+ cwd,
1714
+ mcpToolUseResult
1703
1715
  };
1704
1716
  const output = [];
1705
1717
  for (const chunk of content) {
@@ -1958,6 +1970,7 @@ async function handleUserAssistantMessage(message, context) {
1958
1970
  const content = message.message.content;
1959
1971
  const contentToProcess = message.type === "assistant" ? filterMessageContent(content) : content;
1960
1972
  const parentToolCallId = "parent_tool_use_id" in message ? message.parent_tool_use_id ?? void 0 : void 0;
1973
+ const mcpToolUseResult = message.type === "user" && message.tool_use_result != null ? message.tool_use_result : void 0;
1961
1974
  for (const notification of toAcpNotifications(
1962
1975
  contentToProcess,
1963
1976
  message.message.role,
@@ -1969,7 +1982,8 @@ async function handleUserAssistantMessage(message, context) {
1969
1982
  parentToolCallId,
1970
1983
  context.registerHooks,
1971
1984
  context.supportsTerminalOutput,
1972
- session.cwd
1985
+ session.cwd,
1986
+ mcpToolUseResult
1973
1987
  )) {
1974
1988
  await client.sessionUpdate(notification);
1975
1989
  session.notificationHistory.push(notification);
@@ -3809,11 +3823,17 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3809
3823
  * Both populate caches used later — neither is needed to return configOptions.
3810
3824
  */
3811
3825
  deferBackgroundFetches(q) {
3826
+ this.logger.info("Starting background fetches (commands + MCP metadata)");
3812
3827
  Promise.all([
3813
3828
  new Promise((resolve3) => setTimeout(resolve3, 10)).then(
3814
3829
  () => this.sendAvailableCommandsUpdate()
3815
3830
  ),
3816
- fetchMcpToolMetadata(q, this.logger)
3831
+ fetchMcpToolMetadata(q, this.logger).then(() => {
3832
+ const serverNames = getConnectedMcpServerNames();
3833
+ if (serverNames.length > 0) {
3834
+ this.options?.onMcpServersReady?.(serverNames);
3835
+ }
3836
+ })
3817
3837
  ]).catch(
3818
3838
  (err) => this.logger.error("Background fetch failed", { error: err })
3819
3839
  );
@@ -4495,7 +4515,7 @@ var SessionLogWriter = class _SessionLogWriter {
4495
4515
  taskId: context.taskId,
4496
4516
  runId: context.runId
4497
4517
  });
4498
- this.sessions.set(sessionId, { context });
4518
+ this.sessions.set(sessionId, { context, currentTurnMessages: [] });
4499
4519
  this.lastFlushAttemptTime.set(sessionId, Date.now());
4500
4520
  if (this.localCachePath) {
4501
4521
  const sessionDir = path6.join(
@@ -4542,6 +4562,7 @@ var SessionLogWriter = class _SessionLogWriter {
4542
4562
  const nonChunkAgentText = this.extractAgentMessageText(message);
4543
4563
  if (nonChunkAgentText) {
4544
4564
  session.lastAgentMessage = nonChunkAgentText;
4565
+ session.currentTurnMessages.push(nonChunkAgentText);
4545
4566
  }
4546
4567
  const entry = {
4547
4568
  type: "notification",
@@ -4638,6 +4659,7 @@ var SessionLogWriter = class _SessionLogWriter {
4638
4659
  const { text: text2, firstTimestamp } = session.chunkBuffer;
4639
4660
  session.chunkBuffer = void 0;
4640
4661
  session.lastAgentMessage = text2;
4662
+ session.currentTurnMessages.push(text2);
4641
4663
  const entry = {
4642
4664
  type: "notification",
4643
4665
  timestamp: firstTimestamp,
@@ -4663,6 +4685,17 @@ var SessionLogWriter = class _SessionLogWriter {
4663
4685
  getLastAgentMessage(sessionId) {
4664
4686
  return this.sessions.get(sessionId)?.lastAgentMessage;
4665
4687
  }
4688
+ getFullAgentResponse(sessionId) {
4689
+ const session = this.sessions.get(sessionId);
4690
+ if (!session || session.currentTurnMessages.length === 0) return void 0;
4691
+ return session.currentTurnMessages.join("\n\n");
4692
+ }
4693
+ resetTurnMessages(sessionId) {
4694
+ const session = this.sessions.get(sessionId);
4695
+ if (session) {
4696
+ session.currentTurnMessages = [];
4697
+ }
4698
+ }
4666
4699
  extractAgentMessageText(message) {
4667
4700
  if (message.method !== "session/update") {
4668
4701
  return null;