File size: 2,870 Bytes
7a1ad33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import * as Diff from 'diff';
import type { EditorType } from '../utils/editor.js';
import {
  isModifiableDeclarativeTool,
  modifyWithEditor,
  type ModifyContext,
} from '../tools/modifiable-tool.js';
import type { ToolConfirmationPayload } from '../tools/tools.js';
import type { WaitingToolCall } from './types.js';

export interface ModificationResult {
  updatedParams: Record<string, unknown>;
  updatedDiff?: string;
}

export class ToolModificationHandler {
  /**
   * Handles the "Modify with Editor" flow where an external editor is launched
   * to modify the tool's parameters.
   */
  async handleModifyWithEditor(
    toolCall: WaitingToolCall,
    editorType: EditorType,
    signal: AbortSignal,
  ): Promise<ModificationResult | undefined> {
    if (!isModifiableDeclarativeTool(toolCall.tool)) {
      return undefined;
    }

    const confirmationDetails = toolCall.confirmationDetails;
    const modifyContext = toolCall.tool.getModifyContext(signal);

    const contentOverrides =
      confirmationDetails.type === 'edit'
        ? {
            currentContent: confirmationDetails.originalContent,
            proposedContent: confirmationDetails.newContent,
          }
        : undefined;

    const { updatedParams, updatedDiff } = await modifyWithEditor<
      typeof toolCall.request.args
    >(
      toolCall.request.args,
      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
      modifyContext as ModifyContext<typeof toolCall.request.args>,
      editorType,
      signal,
      contentOverrides,
    );

    return {
      updatedParams,
      updatedDiff,
    };
  }

  /**
   * Applies user-provided inline content updates (e.g. from the chat UI).
   */
  async applyInlineModify(
    toolCall: WaitingToolCall,
    payload: ToolConfirmationPayload,
    signal: AbortSignal,
  ): Promise<ModificationResult | undefined> {
    if (
      toolCall.confirmationDetails.type !== 'edit' ||
      !('newContent' in payload) ||
      !isModifiableDeclarativeTool(toolCall.tool)
    ) {
      return undefined;
    }

    // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
    const modifyContext = toolCall.tool.getModifyContext(
      signal,
    ) as ModifyContext<typeof toolCall.request.args>;
    const currentContent = await modifyContext.getCurrentContent(
      toolCall.request.args,
    );

    const updatedParams = modifyContext.createUpdatedParams(
      currentContent,
      payload.newContent,
      toolCall.request.args,
    );

    const updatedDiff = Diff.createPatch(
      modifyContext.getFilePath(toolCall.request.args),
      currentContent,
      payload.newContent,
      'Current',
      'Proposed',
    );

    return {
      updatedParams,
      updatedDiff,
    };
  }
}