| 1 | import type { Item } from "./useController"; |
| 2 | import { fileIdentity } from "./filePaths"; |
| 3 | |
| 4 | type ToolItem = Extract<Item, { kind: "tool" }>; |
| 5 | export type TurnFileOperation = "written" | "modified"; |
| 6 | export interface TurnFileView { |
| 7 | path: string; |
| 8 | toolCallId: string; |
| 9 | operation: TurnFileOperation; |
| 10 | } |
| 11 | |
| 12 | const MUTATION_PATHS: Record<string, { field: string; operation: TurnFileOperation }> = { |
| 13 | write_file: { field: "path", operation: "written" }, |
| 14 | edit_file: { field: "path", operation: "modified" }, |
| 15 | multi_edit: { field: "path", operation: "modified" }, |
| 16 | notebook_edit: { field: "path", operation: "modified" }, |
| 17 | delete_range: { field: "path", operation: "modified" }, |
| 18 | delete_symbol: { field: "path", operation: "modified" }, |
| 19 | move_file: { field: "destination_path", operation: "written" }, |
| 20 | }; |
| 21 | |
| 22 | export { fileIdentity }; |
| 23 | /** Derive file facts only from successful native mutation tools. */ |
| 24 | export function deriveTurnFiles(calls: readonly ToolItem[]): TurnFileView[] { |
| 25 | const files = new Map<string, TurnFileView>(); |
| 26 | for (const call of calls) { |
| 27 | const definition = MUTATION_PATHS[call.name]; |
| 28 | if (!definition || call.status !== "done" || call.error || call.readOnly) continue; |
| 29 | if (/\bno changes made\b/i.test(call.output ?? "")) continue; |
| 30 | let args: Record<string, unknown>; |
| 31 | try { |
| 32 | const parsed: unknown = JSON.parse(call.args || "{}"); |
| 33 | if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue; |
| 34 | args = parsed as Record<string, unknown>; |
| 35 | } catch { continue; } |
| 36 | const path = args[definition.field]; |
| 37 | if (typeof path !== "string" || !path.trim()) continue; |
| 38 | const key = fileIdentity(path); |
| 39 | if (!key) continue; |
| 40 | const existing = files.get(key); |
| 41 | const value = { path: path.trim(), toolCallId: call.id, operation: definition.operation }; |
| 42 | if (existing) files.set(key, { ...value, path: existing.path }); |
| 43 | else files.set(key, value); |
| 44 | } |
| 45 | return [...files.values()]; |
| 46 | } |
| 47 |