// What an answer shows while it is being written: the step it is on, worded from the call it makes, // and the model's latest note to itself, its words beside its tool calls or, where the provider hands // them back, its thinking. Pure, so the wording is tested. /** How much of a note the line shows: its end, where the model is now. */ const THOUGHT_MAX = 280 export interface Working { step: string thought: string | null /** The company add_company is reading in, while it waits. */ company?: string } /** A tool call as the working line says it. */ export function stepOf(call: { name: string; input: Record }): string { const text = (key: string): string => (typeof call.input[key] === 'string' ? (call.input[key] as string).trim() : '') switch (call.name) { case 'search_prism': { const where = [text('ticker'), text('form'), call.input['fiscal_year'], text('fiscal_period'), text('section')] .filter((part) => part !== '' && part !== undefined && part !== null) .join(' ') return `Searching “${text('query')}”${where ? ` · ${where}` : ''}` } case 'fetch_page': { let host = text('url') try { host = new URL(host).host } catch { // Said as written. } return `Reading ${host}` } case 'search_web': return `Searching the web for “${text('query')}”` case 'calculate': return 'Calculating' case 'add_company': { const [from, to] = [call.input['from'], call.input['to']] return `Reading ${text('ticker')} filings, ${from === to ? from : `${from}–${to}`}` } case 'activate_skill': return `Loading skill ${text('name')}` case 'read_skill_file': return `Reading ${text('skill')}/${text('path')}` default: return `Calling ${call.name}` } } /** The model's note on a round that calls tools: its words, else its thinking where the payload has it. */ export function thoughtOf(text: string, raw: unknown[] | null): string | null { const said = text.trim() || thinkingIn(raw) if (!said) return null const flat = said.replace(/\s+/g, ' ') return flat.length > THOUGHT_MAX ? `…${flat.slice(-THOUGHT_MAX)}` : flat } /** Anthropic's thinking blocks, the only payload a plugin is handed that carries thinking as words. */ function thinkingIn(raw: unknown[] | null): string { return (raw ?? []) .map((block) => block && typeof block === 'object' && (block as { type?: unknown }).type === 'thinking' ? String((block as { thinking?: unknown }).thinking ?? '') : '', ) .filter(Boolean) .join(' ') .trim() }