/ src / utils / ask.tsx
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  }