/ src / tools / ArchitectTool / ArchitectTool.tsx
ArchitectTool.tsx
  1  import type { TextBlock } from '@anthropic-ai/sdk/resources/index.mjs'
  2  import { Box } from 'ink'
  3  import * as React from 'react'
  4  import { z } from 'zod'
  5  import type { Tool } from '../../Tool.js'
  6  import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage.js'
  7  import { HighlightedCode } from '../../components/HighlightedCode.js'
  8  import { getContext } from '../../context.js'
  9  import { Message, query } from '../../query.js'
 10  import { lastX } from '../../utils/generators.js'
 11  import { createUserMessage } from '../../utils/messages.js'
 12  import { BashTool } from '../BashTool/BashTool.js'
 13  import { FileReadTool } from '../FileReadTool/FileReadTool.js'
 14  import { FileWriteTool } from '../FileWriteTool/FileWriteTool.js'
 15  import { GlobTool } from '../GlobTool/GlobTool.js'
 16  import { GrepTool } from '../GrepTool/GrepTool.js'
 17  import { LSTool } from '../lsTool/lsTool.js'
 18  import { ARCHITECT_SYSTEM_PROMPT, DESCRIPTION } from './prompt.js'
 19  
 20  const FS_EXPLORATION_TOOLS: Tool[] = [
 21    BashTool,
 22    LSTool,
 23    FileReadTool,
 24    FileWriteTool,
 25    GlobTool,
 26    GrepTool,
 27  ]
 28  
 29  const inputSchema = z.strictObject({
 30    prompt: z
 31      .string()
 32      .describe('The technical request or coding task to analyze'),
 33    context: z
 34      .string()
 35      .describe('Optional context from previous conversation or system state')
 36      .optional(),
 37  })
 38  
 39  export const ArchitectTool = {
 40    name: 'Architect',
 41    async description() {
 42      return DESCRIPTION
 43    },
 44    inputSchema,
 45    isReadOnly() {
 46      return true
 47    },
 48    userFacingName() {
 49      return 'Architect'
 50    },
 51    async isEnabled() {
 52      return false
 53    },
 54    needsPermissions() {
 55      return false
 56    },
 57    async *call({ prompt, context }, toolUseContext, canUseTool) {
 58      const content = context
 59        ? `<context>${context}</context>\n\n${prompt}`
 60        : prompt
 61  
 62      const userMessage = createUserMessage(content)
 63  
 64      const messages: Message[] = [userMessage]
 65  
 66      // We only allow the file exploration tools to be used in the architect tool
 67      const allowedTools = (toolUseContext.options.tools ?? []).filter(_ =>
 68        FS_EXPLORATION_TOOLS.map(_ => _.name).includes(_.name),
 69      )
 70  
 71      const lastResponse = await lastX(
 72        query(
 73          messages,
 74          [ARCHITECT_SYSTEM_PROMPT],
 75          await getContext(),
 76          canUseTool,
 77          {
 78            ...toolUseContext,
 79            options: { ...toolUseContext.options, tools: allowedTools },
 80          },
 81        ),
 82      )
 83  
 84      if (lastResponse.type !== 'assistant') {
 85        throw new Error('Invalid response from Claude API')
 86      }
 87  
 88      const data = lastResponse.message.content.filter(_ => _.type === 'text')
 89      yield {
 90        type: 'result',
 91        data,
 92        resultForAssistant: this.renderResultForAssistant(data),
 93      }
 94    },
 95    async prompt() {
 96      return DESCRIPTION
 97    },
 98    renderResultForAssistant(data) {
 99      return data
100    },
101    renderToolUseMessage(input) {
102      return Object.entries(input)
103        .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
104        .join(', ')
105    },
106    renderToolResultMessage(content) {
107      return (
108        <Box flexDirection="column" gap={1}>
109          <HighlightedCode
110            code={content.map(_ => _.text).join('\n')}
111            language="markdown"
112          />
113        </Box>
114      )
115    },
116    renderToolUseRejectedMessage() {
117      return <FallbackToolUseRejectedMessage />
118    },
119  } satisfies Tool<typeof inputSchema, TextBlock[]>