113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
/**
|
|
* Tool Use Summary Generator
|
|
*
|
|
* Generates human-readable summaries of completed tool batches using Haiku.
|
|
* Used by the SDK to provide high-level progress updates to clients.
|
|
*/
|
|
|
|
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
|
|
}
|
|
|
|
/**
|
|
* Generates a human-readable summary of completed tools.
|
|
*
|
|
* @param params - Parameters including tools executed and their results
|
|
* @returns A brief summary string, or null if generation fails
|
|
*/
|
|
export async function generateToolUseSummary({
|
|
tools,
|
|
signal,
|
|
isNonInteractiveSession,
|
|
lastAssistantText,
|
|
}: GenerateToolUseSummaryParams): Promise<string | null> {
|
|
if (tools.length === 0) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
// Build a concise representation of what tools did
|
|
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) {
|
|
// Log but don't fail - summaries are non-critical
|
|
const err = toError(error)
|
|
err.cause = { errorId: E_TOOL_USE_SUMMARY_GENERATION_FAILED }
|
|
logError(err)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Truncates a JSON value to a maximum length for the prompt.
|
|
*/
|
|
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]'
|
|
}
|
|
}
|