ask.tsx
1 import { last } from 'lodash-es' 2 import { Command } from '../commands.js' 3 import { getSystemPrompt } from '../constants/prompts.js' 4 import { getContext } from '../context.js' 5 import { getTotalCost } from '../cost-tracker.js' 6 import { Message, query } from '../query.js' 7 import { CanUseToolFn } from '../hooks/useCanUseTool.js' 8 import { Tool } from '../Tool.js' 9 import { getSlowAndCapableModel } from '../utils/model.js' 10 import { setCwd } from './state.js' 11 import { getMessagesPath, overwriteLog } from './log.js' 12 import { createUserMessage } from './messages.js' 13 14 type Props = { 15 commands: Command[] 16 dangerouslySkipPermissions?: boolean 17 hasPermissionsToUseTool: CanUseToolFn 18 messageLogName: string 19 prompt: string 20 cwd: string 21 tools: Tool[] 22 verbose?: boolean 23 } 24 25 // Sends a single prompt to the Claude API and returns the response. 26 // Assumes that claude is being used non-interactively -- will not 27 // ask the user for permissions or further input. 28 export async function ask({ 29 commands, 30 dangerouslySkipPermissions, 31 hasPermissionsToUseTool, 32 messageLogName, 33 prompt, 34 cwd, 35 tools, 36 verbose = false, 37 }: Props): Promise<{ 38 resultText: string 39 totalCost: number 40 messageHistoryFile: string 41 }> { 42 await setCwd(cwd) 43 const message = createUserMessage(prompt) 44 const messages: Message[] = [message] 45 46 const [systemPrompt, context, model] = await Promise.all([ 47 getSystemPrompt(), 48 getContext(), 49 getSlowAndCapableModel(), 50 ]) 51 52 for await (const m of query( 53 messages, 54 systemPrompt, 55 context, 56 hasPermissionsToUseTool, 57 { 58 options: { 59 commands, 60 tools, 61 verbose, 62 dangerouslySkipPermissions, 63 slowAndCapableModel: model, 64 forkNumber: 0, 65 messageLogName: 'unused', 66 maxThinkingTokens: 0, 67 }, 68 abortController: new AbortController(), 69 messageId: undefined, 70 readFileTimestamps: {}, 71 }, 72 )) { 73 messages.push(m) 74 } 75 76 const result = last(messages) 77 if (!result || result.type !== 'assistant') { 78 throw new Error('Expected content to be an assistant message') 79 } 80 if (result.message.content[0]?.type !== 'text') { 81 throw new Error( 82 `Expected first content item to be text, but got ${JSON.stringify( 83 result.message.content[0], 84 null, 85 2, 86 )}`, 87 ) 88 } 89 90 // Write log that can be retrieved with `claude log` 91 const messageHistoryFile = getMessagesPath(messageLogName, 0, 0) 92 overwriteLog(messageHistoryFile, messages) 93 94 return { 95 resultText: result.message.content[0].text, 96 totalCost: getTotalCost(), 97 messageHistoryFile, 98 } 99 }