(r: unknown)
| 169 | * all top-level string fields — crude but better than indexing model-chatter. |
| 170 | * Empty for unknown shapes: under-count > phantom. */ |
| 171 | export function toolResultSearchText(r: unknown): string { |
| 172 | if (!r || typeof r !== 'object') return typeof r === 'string' ? r : '' |
| 173 | const o = r as Record<string, unknown> |
| 174 | // Known shapes first (common tools). |
| 175 | if (typeof o.stdout === 'string') { |
| 176 | const err = typeof o.stderr === 'string' ? o.stderr : '' |
| 177 | return o.stdout + (err ? '\n' + err : '') |
| 178 | } |
| 179 | if ( |
| 180 | o.file && |
| 181 | typeof o.file === 'object' && |
| 182 | typeof (o.file as { content?: unknown }).content === 'string' |
| 183 | ) { |
| 184 | return (o.file as { content: string }).content |
| 185 | } |
| 186 | // Known output-field names only. A blind walk would index metadata |
| 187 | // the UI doesn't show (rawOutputPath, backgroundTaskId, filePath, |
| 188 | // durationMs-as-string). Allowlist the fields tools actually render. |
| 189 | // Tools not matching any shape index empty — add them here as found. |
| 190 | const parts: string[] = [] |
| 191 | for (const k of ['content', 'output', 'result', 'text', 'message']) { |
| 192 | const v = o[k] |
| 193 | if (typeof v === 'string') parts.push(v) |
| 194 | } |
| 195 | for (const k of ['filenames', 'lines', 'results']) { |
| 196 | const v = o[k] |
| 197 | if (Array.isArray(v) && v.every(x => typeof x === 'string')) { |
| 198 | parts.push((v as string[]).join('\n')) |
| 199 | } |
| 200 | } |
| 201 | return parts.join('\n') |
| 202 | } |
| 203 |
no test coverage detected