| |
| |
| |
| |
| |
| |
|
|
| import { E_TOOL_USE_SUMMARY_GENERATION_FAILED } from '../../constants/errorIds.js' |
| import { toError } from '../../utils/errors.js' |
| import { logError } from '../../utils/log.js' |
| import { jsonStringify } from '../../utils/slowOperations.js' |
| import { asSystemPrompt } from '../../utils/systemPromptType.js' |
| import { queryHaiku } from '../api/claude.js' |
|
|
| const TOOL_USE_SUMMARY_SYSTEM_PROMPT = `Write a short summary label describing what these tool calls accomplished. It appears as a single-line row in a mobile app and truncates around 30 characters, so think git-commit-subject, not sentence. |
| |
| Keep the verb in past tense and the most distinctive noun. Drop articles, connectors, and long location context first. |
| |
| Examples: |
| - Searched in auth/ |
| - Fixed NPE in UserService |
| - Created signup endpoint |
| - Read config.json |
| - Ran failing tests` |
|
|
| type ToolInfo = { |
| name: string |
| input: unknown |
| output: unknown |
| } |
|
|
| export type GenerateToolUseSummaryParams = { |
| tools: ToolInfo[] |
| signal: AbortSignal |
| isNonInteractiveSession: boolean |
| lastAssistantText?: string |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function generateToolUseSummary({ |
| tools, |
| signal, |
| isNonInteractiveSession, |
| lastAssistantText, |
| }: GenerateToolUseSummaryParams): Promise<string | null> { |
| if (tools.length === 0) { |
| return null |
| } |
|
|
| try { |
| |
| const toolSummaries = tools |
| .map(tool => { |
| const inputStr = truncateJson(tool.input, 300) |
| const outputStr = truncateJson(tool.output, 300) |
| return `Tool: ${tool.name}\nInput: ${inputStr}\nOutput: ${outputStr}` |
| }) |
| .join('\n\n') |
|
|
| const contextPrefix = lastAssistantText |
| ? `User's intent (from assistant's last message): ${lastAssistantText.slice(0, 200)}\n\n` |
| : '' |
|
|
| const response = await queryHaiku({ |
| systemPrompt: asSystemPrompt([TOOL_USE_SUMMARY_SYSTEM_PROMPT]), |
| userPrompt: `${contextPrefix}Tools completed:\n\n${toolSummaries}\n\nLabel:`, |
| signal, |
| options: { |
| querySource: 'tool_use_summary_generation', |
| enablePromptCaching: true, |
| agents: [], |
| isNonInteractiveSession, |
| hasAppendSystemPrompt: false, |
| mcpTools: [], |
| }, |
| }) |
|
|
| const summary = response.message.content |
| .filter(block => block.type === 'text') |
| .map(block => (block.type === 'text' ? block.text : '')) |
| .join('') |
| .trim() |
|
|
| return summary || null |
| } catch (error) { |
| |
| const err = toError(error) |
| err.cause = { errorId: E_TOOL_USE_SUMMARY_GENERATION_FAILED } |
| logError(err) |
| return null |
| } |
| } |
|
|
| |
| |
| |
| function truncateJson(value: unknown, maxLength: number): string { |
| try { |
| const str = jsonStringify(value) |
| if (str.length <= maxLength) { |
| return str |
| } |
| return str.slice(0, maxLength - 3) + '...' |
| } catch { |
| return '[unable to serialize]' |
| } |
| } |
|
|