import { tool as aiTool, type Tool, type ToolSet } from 'ai'; import { tool as claudeTool, type SdkMcpToolDefinition } from '@anthropic-ai/claude-agent-sdk'; import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import type { KtxRuntimeToolDescriptor, KtxRuntimeToolOutput, KtxRuntimeToolSet } from './runtime-port.js'; function isRuntimeOutput(value: unknown): value is KtxRuntimeToolOutput { return Boolean( value && typeof value === 'object' && 'markdown' in value && typeof (value as { markdown?: unknown }).markdown === 'string', ); } export function normalizeKtxRuntimeToolOutput(value: unknown): KtxRuntimeToolOutput { if (isRuntimeOutput(value)) { return 'structured' in value ? { markdown: value.markdown, structured: value.structured } : { markdown: value.markdown }; } if (typeof value === 'string') { return { markdown: value }; } return { markdown: `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``, structured: value, }; } function assertObjectSchema(name: string, schema: z.ZodType): asserts schema is z.ZodObject { if (!(schema instanceof z.ZodObject)) { throw new Error(`KTX runtime tool "${name}" must use z.object input schema for claude-code`); } } export function createAiSdkToolSet(tools: KtxRuntimeToolSet = {}): ToolSet { return Object.fromEntries( Object.entries(tools).map(([name, descriptor]) => [ name, aiTool({ description: descriptor.description, inputSchema: descriptor.inputSchema, execute: async (input) => descriptor.execute(input), toModelOutput: ({ output }) => { const normalized = normalizeKtxRuntimeToolOutput(output); return { type: 'text', value: normalized.markdown }; }, }), ]), ); } export function createClaudeSdkTools(tools: KtxRuntimeToolSet = {}): Array> { return Object.values(tools).map((descriptor) => { assertObjectSchema(descriptor.name, descriptor.inputSchema); return claudeTool( descriptor.name, descriptor.description, descriptor.inputSchema.shape, async (input): Promise => { const normalized = normalizeKtxRuntimeToolOutput(await descriptor.execute(input)); return { content: [{ type: 'text', text: normalized.markdown }] }; }, ); }); } export function mcpToolIds(tools: KtxRuntimeToolSet = {}): string[] { return Object.keys(tools).map((name) => `mcp__ktx__${name}`); } export function createRuntimeToolDescriptorFromAiTool(name: string, aiSdkTool: Tool): KtxRuntimeToolDescriptor { return { name, description: aiSdkTool.description ?? '', inputSchema: aiSdkTool.inputSchema as KtxRuntimeToolDescriptor['inputSchema'], execute: async (input) => { if (typeof aiSdkTool.execute !== 'function') { throw new Error(`KTX runtime tool "${name}" has no execute function`); } return normalizeKtxRuntimeToolOutput( await aiSdkTool.execute(input as never, { toolCallId: `runtime-${name}` } as never), ); }, }; }