1
>
/*---------------------------------------------------------------------------------------------
claudeFileEditObserver.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk';
7
>
import { Disposable, IReference } from '../../../../base/common/lifecycle.js';
8
>
import { IInstantiationService } from '../../../instantiation/common/instantiation.js';
9
>
import { ILogService } from '../../../log/common/log.js';
10
>
import { ISessionDatabase } from '../../common/sessionDataService.js';
11
>
import { FileEditTracker } from '../shared/fileEditTracker.js';
12
>
import type { ClaudeMapperState } from './claudeMapSessionEvents.js';
13
>
import { getClaudeToolPath, isClaudeFileEditTool } from './claudeToolDisplay.js';
14
>
15
>
/**
16
>
* Phase 8 — file-edit observation off the SDK message stream.
17
>
*
18
>
* Owns the {@link FileEditTracker}, the in-flight `tool_use_id → path`
19
>
* map, and the dbRef whose lifetime gates persistence writes. Snapshots
20
>
* before-content when an assistant `tool_use` block arrives, snapshots
21
>
* after-content when the matching synthetic `tool_result` arrives, and
22
>
* stages a {@link ToolResultFileEditContent} on the session's
23
>
* {@link ClaudeMapperState} so the synchronous mapper can attach it to
24
>
* the `ChatToolCallComplete` action.
25
>
*
26
>
* Hooks (`Options.hooks.PreToolUse` / `Options.hooks.PostToolUse`) are
27
>
* deliberately NOT used: they are user-bypassable via settings, whereas
28
>
* the SDK message stream is the canonical, non-bypassable signal that
29
>
* a tool will run. Mirrors the production extension's dispatch-time
30
>
* observation (extensions/copilot/.../claudeMessageDispatch.ts:200) —
31
>
* see the comment there about `bypassPermissions` and internal SDK
32
>
* paths that skip `canUseTool`.
33
>
*
34
>
* Best-effort: the SDK proceeds to run the tool concurrently after
35
>
* yielding the `tool_use` block, so {@link observeAssistant}'s before-
36
>
* snapshot races against tool execution. Tool execution involves disk
37
>
* I/O before the write, which gives enough microtask headroom in
38
>
* practice; same guarantee the production extension relies on with
39
>
* `stream.externalEdit`.
40
>
*/
41
>
export class ClaudeFileEditObserver extends Disposable {
42
>
43
>
private readonly _editTracker: FileEditTracker;
44
>
45
>
/**
46
>
* Maps SDK `tool_use_id` → file path + raw tool input + model
47
>
* captured when the SDK yields the assistant `tool_use` block in
48
>
* {@link observeAssistant}. Consumed (and removed) by
49
>
* {@link observeUser} when the matching `tool_result` arrives.
50
>
* The raw input is forwarded to
51
>
* {@link FileEditTracker.takeCompletedEdit} so it can extract the
52
>
* AI-written text chunks for the edit-survival reporter. The
53
>
* model is read off the assistant message body and is naturally
54
>
* per-subagent: when a subagent emits the `tool_use`, its model
55
>
* (not the parent's) is what we record.
56
>
*/
57
>
private readonly _editToolPaths = new Map<string, { readonly filePath: string; readonly toolName: string; readonly toolInput: unknown; readonly modelId: string | undefined }>();
58
>
59
>
constructor(
60
sessionUri: string,
61
dbRef: IReference<ISessionDatabase>,