Files
superwork/vscode/src/cc/claudeProgress.ts
T

87 lines
2.5 KiB
TypeScript

/**
* 把 `claude -p --output-format stream-json --verbose` 吐出的每行 NDJSON
* 压缩成人能看懂的进度短语,供长任务(如提交代码)实时展示。
*
* 只关心 assistant 消息里的 `tool_use` 和 `text` 块,其它行(system / user /
* result / thinking 块 / 坏 JSON)一律返回空数组,调用方无需再做判断。
*/
import * as path from 'node:path'
const LABEL_MAX_CHARS = 60
interface ContentBlock {
type?: unknown
text?: unknown
name?: unknown
input?: unknown
}
function truncate(text: string): string {
if (text.length <= LABEL_MAX_CHARS)
return text
return `${text.slice(0, LABEL_MAX_CHARS)}…`
}
function asString(value: unknown): string {
return typeof value === 'string' ? value : ''
}
/** 一次工具调用写成人看得懂的短语:「读取 routes.py」「搜索 get_user」。 */
export function describeToolUse(name: string, input: Record<string, unknown>): string {
switch (name) {
case 'Bash':
return `执行命令:${truncate(asString(input.description) || asString(input.command))}`
case 'Read':
return `读取 ${path.basename(asString(input.file_path))}`
case 'Edit':
case 'Write':
case 'MultiEdit':
return `修改 ${path.basename(asString(input.file_path))}`
case 'Grep':
case 'Glob':
return `搜索 ${truncate(asString(input.pattern))}`
default:
return `调用 ${name}`
}
}
function describeBlock(block: ContentBlock): string | null {
if (block.type === 'tool_use' && typeof block.name === 'string') {
const input = block.input && typeof block.input === 'object'
? block.input as Record<string, unknown>
: {}
return describeToolUse(block.name, input)
}
if (block.type === 'text' && typeof block.text === 'string') {
const firstLine = block.text.split('\n').map(l => l.trim()).find(l => l.length > 0)
return firstLine ? truncate(firstLine) : null
}
return null
}
export function summarizeClaudeStreamLine(line: string): string[] {
let parsed: unknown
try {
parsed = JSON.parse(line)
}
catch {
return []
}
if (!parsed || typeof parsed !== 'object')
return []
const obj = parsed as { type?: unknown, message?: { content?: unknown } }
if (obj.type !== 'assistant' || !Array.isArray(obj.message?.content))
return []
const labels: string[] = []
for (const block of obj.message.content as ContentBlock[]) {
if (!block || typeof block !== 'object')
continue
const label = describeBlock(block)
if (label)
labels.push(label)
}
return labels
}