| 1 | import type {MappingState, StreamEvent, WorkspaceLine} from './types.js'; |
| 2 | |
| 3 | export function createMappingState(): MappingState { |
| 4 | return {assistantStreaming: false}; |
| 5 | } |
| 6 | |
| 7 | export function appendUserLine(lines: WorkspaceLine[], text: string): {lines: WorkspaceLine[]; state: MappingState} { |
| 8 | return {lines: [...lines, {kind: 'user', text}], state: createMappingState()}; |
| 9 | } |
| 10 | |
| 11 | export function applyStreamEvent(lines: WorkspaceLine[], state: MappingState, event: StreamEvent): {lines: WorkspaceLine[]; state: MappingState} { |
| 12 | switch (event.type) { |
| 13 | case 'turn': |
| 14 | case 'status': |
| 15 | return {lines, state}; |
| 16 | case 'token': |
| 17 | return appendAssistantToken(lines, state, event.delta ?? ''); |
| 18 | case 'tool_start': |
| 19 | return append(lines, state, {kind: 'tool', status: 'running', text: `tool ${event.tool?.name ?? 'unknown'} started`}); |
| 20 | case 'tool_progress': |
| 21 | return append(lines, state, { |
| 22 | kind: 'tool', |
| 23 | status: 'running', |
| 24 | text: compactJoin([`tool ${event.tool?.name ?? 'unknown'}`, event.progress?.stage, event.progress?.message]), |
| 25 | }); |
| 26 | case 'tool_result': { |
| 27 | const result = event.tool_result ?? {}; |
| 28 | const ok = result.ok !== false; |
| 29 | const name = result.name ?? 'unknown'; |
| 30 | const detail = ok ? '' : cleanToolError(result.content); |
| 31 | return append(lines, state, {kind: 'tool', status: ok ? 'done' : 'error', text: detail ? `tool ${name} error: ${detail}` : `tool ${name} ${ok ? 'done' : 'error'}`}); |
| 32 | } |
| 33 | case 'terminal': |
| 34 | return append(lines, state, {kind: 'terminal', text: compactJoin([event.stream ? `[${event.stream}]` : '', event.line])}); |
| 35 | case 'session': |
| 36 | return {lines, state}; |
| 37 | case 'done': |
| 38 | return {lines, state: createMappingState()}; |
| 39 | case 'error': |
| 40 | return append(lines, state, {kind: 'error', text: event.message ?? 'Unknown error'}); |
| 41 | default: |
| 42 | return {lines, state}; |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | function append(lines: WorkspaceLine[], state: MappingState, line: WorkspaceLine): {lines: WorkspaceLine[]; state: MappingState} { |
| 47 | return {lines: [...lines, line], state: {...state, assistantStreaming: false}}; |
| 48 | } |
| 49 | |
| 50 | function appendAssistantToken(lines: WorkspaceLine[], state: MappingState, delta: string): {lines: WorkspaceLine[]; state: MappingState} { |
| 51 | if (!delta) return {lines, state: {...state, assistantStreaming: true}}; |
| 52 | const next = [...lines]; |
| 53 | const last = next[next.length - 1]; |
| 54 | if (state.assistantStreaming && last?.kind === 'assistant') { |
| 55 | next[next.length - 1] = {...last, text: `${last.text}${delta}`}; |
| 56 | } else { |
| 57 | next.push({kind: 'assistant', text: delta}); |
| 58 | } |
| 59 | return {lines: next, state: {assistantStreaming: true}}; |
| 60 | } |
| 61 | |
| 62 | |
| 63 | function compactJoin(parts: Array<string | undefined | null>): string { |
| 64 | return parts.map((part) => String(part ?? '').trim()).filter(Boolean).join(': '); |
| 65 | } |
| 66 | |
| 67 | |
| 68 | function cleanToolError(content: string | undefined): string { |
| 69 | return String(content ?? '').replace(/\s+/g, ' ').trim(); |
| 70 | } |
| 71 |