@jupyter-ai/acp-client 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/lib/diff-view.d.ts +3 -1
- package/lib/diff-view.js +18 -6
- package/lib/tool-calls.js +32 -9
- package/package.json +1 -1
- package/src/diff-view.tsx +34 -7
- package/src/tool-calls.tsx +42 -9
- package/style/base.css +4 -1
package/README.md
CHANGED
|
@@ -28,13 +28,13 @@ package also provides a default ACP client implementation as `JaiAcpClient`.
|
|
|
28
28
|
the persona name, the persona avatar, and the `executable` starting the ACP
|
|
29
29
|
agent server.
|
|
30
30
|
|
|
31
|
-
For example, the `@Claude
|
|
31
|
+
For example, the `@Claude` persona is defined in `claude.py` using less than
|
|
32
32
|
20 lines of code:
|
|
33
33
|
|
|
34
34
|
```py
|
|
35
35
|
class ClaudeAcpPersona(BaseAcpPersona):
|
|
36
36
|
def __init__(self, *args, **kwargs):
|
|
37
|
-
executable = ["claude-
|
|
37
|
+
executable = ["claude-agent-acp"]
|
|
38
38
|
super().__init__(*args, executable=executable, **kwargs)
|
|
39
39
|
|
|
40
40
|
@property
|
|
@@ -44,19 +44,24 @@ class ClaudeAcpPersona(BaseAcpPersona):
|
|
|
44
44
|
))
|
|
45
45
|
|
|
46
46
|
return PersonaDefaults(
|
|
47
|
-
name="Claude
|
|
47
|
+
name="Claude",
|
|
48
48
|
description="Claude Code as an ACP agent persona.",
|
|
49
49
|
avatar_path=avatar_path,
|
|
50
50
|
system_prompt="unused"
|
|
51
51
|
)
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Currently, this package provides
|
|
54
|
+
Currently, this package provides 4 personas:
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
- `@Claude`
|
|
57
|
+
- requires `claude-agent-acp`, installed via `npm install -g @zed-industries/claude-agent-acp`
|
|
58
|
+
- optional env variable `CLAUDE_CODE_EXECUTABLE` points to your custom-installed Claude executable location. By default, claude-agent-acp uses Claude packaged in `@zed-industries/claude-agent-acp`.
|
|
59
|
+
- `@Gemini`
|
|
60
|
+
- requires `gemini` CLI (>= 0.34.0), installed via https://geminicli.com/
|
|
61
|
+
- `@Kiro`
|
|
62
|
+
- requires `kiro-cli` (>= 1.25.0, < 2), installed via https://kiro.dev
|
|
63
|
+
- `@Mistral-Vibe`
|
|
64
|
+
- requires `vibe-acp`, installed via `uv tool install mistral-vibe` or `pip install mistral-vibe`
|
|
60
65
|
|
|
61
66
|
## Dependencies
|
|
62
67
|
|
|
@@ -68,8 +73,10 @@ Currently, this package provides 2 personas:
|
|
|
68
73
|
|
|
69
74
|
**Optional**
|
|
70
75
|
|
|
71
|
-
- `claude-
|
|
76
|
+
- `claude-agent-acp` (enables `@Claude`)
|
|
77
|
+
- `gemini` (enables `@Gemini`)
|
|
72
78
|
- `kiro-cli` (enables `@Kiro`)
|
|
79
|
+
- `mistral-vibe` (enables `@Mistral-Vibe` via the `vibe-acp` command)
|
|
73
80
|
|
|
74
81
|
## Install
|
|
75
82
|
|
package/lib/diff-view.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { IToolCallDiff } from '@jupyter/chat';
|
|
|
2
2
|
/**
|
|
3
3
|
* Renders one or more file diffs.
|
|
4
4
|
*/
|
|
5
|
-
export declare function DiffView({ diffs, onOpenFile }: {
|
|
5
|
+
export declare function DiffView({ diffs, onOpenFile, toDisplayPath, pendingPermission }: {
|
|
6
6
|
diffs: IToolCallDiff[];
|
|
7
7
|
onOpenFile?: (path: string) => void;
|
|
8
|
+
toDisplayPath?: (path: string) => string;
|
|
9
|
+
pendingPermission?: boolean;
|
|
8
10
|
}): JSX.Element;
|
package/lib/diff-view.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
2
3
|
import { structuredPatch } from 'diff';
|
|
4
|
+
import clsx from 'clsx';
|
|
3
5
|
/** Maximum number of diff lines shown before truncation. */
|
|
4
6
|
const MAX_DIFF_LINES = 20;
|
|
5
7
|
/**
|
|
6
8
|
* Renders a single file diff block with filename header, line-level
|
|
7
9
|
* highlighting, and click-to-expand truncation.
|
|
8
10
|
*/
|
|
9
|
-
function DiffBlock({ diff, onOpenFile }) {
|
|
10
|
-
var _a
|
|
11
|
+
function DiffBlock({ diff, onOpenFile, toDisplayPath, pendingPermission }) {
|
|
12
|
+
var _a;
|
|
11
13
|
const patch = structuredPatch(diff.path, diff.path, (_a = diff.old_text) !== null && _a !== void 0 ? _a : '', diff.new_text, undefined, undefined, { context: Infinity });
|
|
12
|
-
const
|
|
14
|
+
const displayPath = toDisplayPath
|
|
15
|
+
? toDisplayPath(diff.path)
|
|
16
|
+
: PathExt.basename(diff.path);
|
|
17
|
+
// toDisplayPath makes paths inside the server root relative. A leading '/'
|
|
18
|
+
// means the file is outside it and cannot be opened via the Contents API.
|
|
19
|
+
const isOutsideRoot = displayPath.startsWith('/');
|
|
20
|
+
const isClickable = !!onOpenFile &&
|
|
21
|
+
!isOutsideRoot &&
|
|
22
|
+
!(pendingPermission && diff.old_text === undefined);
|
|
13
23
|
const [expanded, setExpanded] = React.useState(false);
|
|
14
24
|
// Flatten hunks into renderable lines
|
|
15
25
|
const allLines = [];
|
|
@@ -37,7 +47,9 @@ function DiffBlock({ diff, onOpenFile }) {
|
|
|
37
47
|
const visible = canTruncate && !expanded ? allLines.slice(0, MAX_DIFF_LINES) : allLines;
|
|
38
48
|
const hiddenCount = allLines.length - MAX_DIFF_LINES;
|
|
39
49
|
return (React.createElement("div", { className: "jp-jupyter-ai-acp-client-diff-block" },
|
|
40
|
-
React.createElement("div", { className:
|
|
50
|
+
React.createElement("div", { className: clsx('jp-jupyter-ai-acp-client-diff-header', {
|
|
51
|
+
'jp-jupyter-ai-acp-client-diff-header-clickable': isClickable
|
|
52
|
+
}), onClick: isClickable ? () => onOpenFile(diff.path) : undefined, title: diff.path }, displayPath),
|
|
41
53
|
React.createElement("div", { className: "jp-jupyter-ai-acp-client-diff-content" },
|
|
42
54
|
visible.map((line) => (React.createElement("div", { key: line.key, className: `jp-jupyter-ai-acp-client-diff-line ${line.cls}` },
|
|
43
55
|
React.createElement("span", { className: "jp-jupyter-ai-acp-client-diff-line-text" },
|
|
@@ -53,6 +65,6 @@ function DiffBlock({ diff, onOpenFile }) {
|
|
|
53
65
|
/**
|
|
54
66
|
* Renders one or more file diffs.
|
|
55
67
|
*/
|
|
56
|
-
export function DiffView({ diffs, onOpenFile }) {
|
|
57
|
-
return (React.createElement("div", { className: "jp-jupyter-ai-acp-client-diff-container" }, diffs.map((d, i) => (React.createElement(DiffBlock, { key: i, diff: d, onOpenFile: onOpenFile })))));
|
|
68
|
+
export function DiffView({ diffs, onOpenFile, toDisplayPath, pendingPermission }) {
|
|
69
|
+
return (React.createElement("div", { className: "jp-jupyter-ai-acp-client-diff-container" }, diffs.map((d, i) => (React.createElement(DiffBlock, { key: i, diff: d, onOpenFile: onOpenFile, toDisplayPath: toDisplayPath, pendingPermission: pendingPermission })))));
|
|
58
70
|
}
|
package/lib/tool-calls.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
|
|
2
3
|
import { submitPermissionDecision } from './request';
|
|
3
4
|
import clsx from 'clsx';
|
|
4
5
|
import { DiffView } from './diff-view';
|
|
6
|
+
/**
|
|
7
|
+
* Convert an absolute filesystem path to a server-relative path.
|
|
8
|
+
* Returns the path unchanged if the server root is not set or the path
|
|
9
|
+
* is outside it.
|
|
10
|
+
*/
|
|
11
|
+
function toServerRelativePath(absolutePath) {
|
|
12
|
+
const rootUri = PageConfig.getOption('rootUri');
|
|
13
|
+
const serverRoot = rootUri
|
|
14
|
+
? new URL(rootUri).pathname
|
|
15
|
+
: PageConfig.getOption('serverRoot');
|
|
16
|
+
if (!serverRoot) {
|
|
17
|
+
return absolutePath;
|
|
18
|
+
}
|
|
19
|
+
const relativePath = PathExt.relative(serverRoot, absolutePath);
|
|
20
|
+
// Path is outside server root — keep absolute
|
|
21
|
+
if (relativePath.startsWith('..')) {
|
|
22
|
+
return absolutePath;
|
|
23
|
+
}
|
|
24
|
+
return relativePath;
|
|
25
|
+
}
|
|
5
26
|
/**
|
|
6
27
|
* Preamble component that renders tool call status lines above message body.
|
|
7
28
|
* Returns null if the message has no tool calls.
|
|
@@ -14,7 +35,7 @@ export function ToolCallsComponent(props) {
|
|
|
14
35
|
}
|
|
15
36
|
const onOpenFile = (path) => {
|
|
16
37
|
var _a;
|
|
17
|
-
(_a = model.documentManager) === null || _a === void 0 ? void 0 : _a.openOrReveal(path);
|
|
38
|
+
(_a = model.documentManager) === null || _a === void 0 ? void 0 : _a.openOrReveal(toServerRelativePath(path));
|
|
18
39
|
};
|
|
19
40
|
return (React.createElement("div", { className: "jp-jupyter-ai-acp-client-tool-calls" }, ((_d = (_c = message.metadata) === null || _c === void 0 ? void 0 : _c.tool_calls) !== null && _d !== void 0 ? _d : []).map((tc) => (React.createElement(ToolCallLine, { key: tc.tool_call_id, toolCall: tc, onOpenFile: onOpenFile })))));
|
|
20
41
|
}
|
|
@@ -51,7 +72,7 @@ function formatToolInput(input) {
|
|
|
51
72
|
}
|
|
52
73
|
/**
|
|
53
74
|
* Compute the pre-permission detail text for a tool call, or null if nothing
|
|
54
|
-
* to show beyond the title.
|
|
75
|
+
* to show beyond the title.
|
|
55
76
|
*/
|
|
56
77
|
function buildPermissionDetail(toolCall) {
|
|
57
78
|
const { kind, title, locations, raw_input } = toolCall;
|
|
@@ -72,8 +93,10 @@ function buildPermissionDetail(toolCall) {
|
|
|
72
93
|
if ((kind === 'delete' || kind === 'move' || kind === 'read') &&
|
|
73
94
|
(locations === null || locations === void 0 ? void 0 : locations.length)) {
|
|
74
95
|
return kind === 'move' && locations.length >= 2
|
|
75
|
-
? locations[0] +
|
|
76
|
-
|
|
96
|
+
? toServerRelativePath(locations[0]) +
|
|
97
|
+
' \u2192 ' +
|
|
98
|
+
toServerRelativePath(locations[1])
|
|
99
|
+
: locations.map(toServerRelativePath).join('\n');
|
|
77
100
|
}
|
|
78
101
|
// Generic fallback for unknown/MCP kinds with raw_input.
|
|
79
102
|
if (raw_input !== null &&
|
|
@@ -105,7 +128,7 @@ function buildPermissionDetail(toolCall) {
|
|
|
105
128
|
}
|
|
106
129
|
return null;
|
|
107
130
|
}
|
|
108
|
-
/** Tool kinds where expanded view shows
|
|
131
|
+
/** Tool kinds where expanded view shows file path(s) from locations. */
|
|
109
132
|
const FILE_KINDS = new Set(['read', 'edit', 'delete', 'move']);
|
|
110
133
|
/** Tool kinds where expanded view shows raw_output (stdout, search results, etc.). */
|
|
111
134
|
const OUTPUT_KINDS = new Set(['search', 'execute', 'think', 'fetch']);
|
|
@@ -113,7 +136,7 @@ const OUTPUT_KINDS = new Set(['search', 'execute', 'think', 'fetch']);
|
|
|
113
136
|
* Build the expandable details content for a tool call.
|
|
114
137
|
* Returns lines of metadata to display, or empty array if nothing to show.
|
|
115
138
|
*
|
|
116
|
-
* File operations show
|
|
139
|
+
* File operations show server-relative paths; output operations show raw_output;
|
|
117
140
|
* switch_mode/other/None show nothing (clean title only).
|
|
118
141
|
*/
|
|
119
142
|
function buildDetailsLines(toolCall) {
|
|
@@ -122,7 +145,7 @@ function buildDetailsLines(toolCall) {
|
|
|
122
145
|
const kind = toolCall.kind;
|
|
123
146
|
if (kind && FILE_KINDS.has(kind) && ((_a = toolCall.locations) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
124
147
|
for (const loc of toolCall.locations) {
|
|
125
|
-
lines.push(loc);
|
|
148
|
+
lines.push(toServerRelativePath(loc));
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
151
|
else if (kind && OUTPUT_KINDS.has(kind) && toolCall.raw_output) {
|
|
@@ -172,7 +195,7 @@ function ToolCallLine({ toolCall, onOpenFile }) {
|
|
|
172
195
|
React.createElement("span", { className: "jp-jupyter-ai-acp-client-tool-call-icon" }, icon),
|
|
173
196
|
' ',
|
|
174
197
|
React.createElement("em", null, displayTitle)),
|
|
175
|
-
React.createElement(DiffView, { diffs: toolCall.diffs, onOpenFile: onOpenFile })),
|
|
198
|
+
React.createElement(DiffView, { diffs: toolCall.diffs, onOpenFile: toolCall.kind === 'edit' ? onOpenFile : undefined, toDisplayPath: toServerRelativePath, pendingPermission: true })),
|
|
176
199
|
React.createElement(PermissionButtons, { toolCall: toolCall })));
|
|
177
200
|
}
|
|
178
201
|
// Pending permission without diffs: show kind-specific detail if available
|
|
@@ -199,7 +222,7 @@ function ToolCallLine({ toolCall, onOpenFile }) {
|
|
|
199
222
|
' ',
|
|
200
223
|
displayTitle,
|
|
201
224
|
React.createElement(PermissionLabel, { toolCall: toolCall })),
|
|
202
|
-
hasDiffs ? (React.createElement(DiffView, { diffs: toolCall.diffs, onOpenFile: onOpenFile })) : (React.createElement("div", { className: "jp-jupyter-ai-acp-client-tool-call-detail" }, detailsLines.join('\n')))));
|
|
225
|
+
hasDiffs ? (React.createElement(DiffView, { diffs: toolCall.diffs, onOpenFile: onOpenFile, toDisplayPath: toServerRelativePath })) : (React.createElement("div", { className: "jp-jupyter-ai-acp-client-tool-call-detail" }, detailsLines.join('\n')))));
|
|
203
226
|
}
|
|
204
227
|
// In-progress — italic
|
|
205
228
|
if (isInProgress) {
|
package/package.json
CHANGED
package/src/diff-view.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { IToolCallDiff } from '@jupyter/chat';
|
|
3
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
3
4
|
import { structuredPatch } from 'diff';
|
|
5
|
+
import clsx from 'clsx';
|
|
4
6
|
|
|
5
7
|
/** Maximum number of diff lines shown before truncation. */
|
|
6
8
|
const MAX_DIFF_LINES = 20;
|
|
@@ -19,10 +21,14 @@ interface IDiffLineInfo {
|
|
|
19
21
|
*/
|
|
20
22
|
function DiffBlock({
|
|
21
23
|
diff,
|
|
22
|
-
onOpenFile
|
|
24
|
+
onOpenFile,
|
|
25
|
+
toDisplayPath,
|
|
26
|
+
pendingPermission
|
|
23
27
|
}: {
|
|
24
28
|
diff: IToolCallDiff;
|
|
25
29
|
onOpenFile?: (path: string) => void;
|
|
30
|
+
toDisplayPath?: (path: string) => string;
|
|
31
|
+
pendingPermission?: boolean;
|
|
26
32
|
}): JSX.Element {
|
|
27
33
|
const patch = structuredPatch(
|
|
28
34
|
diff.path,
|
|
@@ -33,7 +39,16 @@ function DiffBlock({
|
|
|
33
39
|
undefined,
|
|
34
40
|
{ context: Infinity }
|
|
35
41
|
);
|
|
36
|
-
const
|
|
42
|
+
const displayPath = toDisplayPath
|
|
43
|
+
? toDisplayPath(diff.path)
|
|
44
|
+
: PathExt.basename(diff.path);
|
|
45
|
+
// toDisplayPath makes paths inside the server root relative. A leading '/'
|
|
46
|
+
// means the file is outside it and cannot be opened via the Contents API.
|
|
47
|
+
const isOutsideRoot = displayPath.startsWith('/');
|
|
48
|
+
const isClickable =
|
|
49
|
+
!!onOpenFile &&
|
|
50
|
+
!isOutsideRoot &&
|
|
51
|
+
!(pendingPermission && diff.old_text === undefined);
|
|
37
52
|
const [expanded, setExpanded] = React.useState(false);
|
|
38
53
|
|
|
39
54
|
// Flatten hunks into renderable lines
|
|
@@ -67,11 +82,13 @@ function DiffBlock({
|
|
|
67
82
|
return (
|
|
68
83
|
<div className="jp-jupyter-ai-acp-client-diff-block">
|
|
69
84
|
<div
|
|
70
|
-
className=
|
|
71
|
-
|
|
85
|
+
className={clsx('jp-jupyter-ai-acp-client-diff-header', {
|
|
86
|
+
'jp-jupyter-ai-acp-client-diff-header-clickable': isClickable
|
|
87
|
+
})}
|
|
88
|
+
onClick={isClickable ? () => onOpenFile!(diff.path) : undefined}
|
|
72
89
|
title={diff.path}
|
|
73
90
|
>
|
|
74
|
-
{
|
|
91
|
+
{displayPath}
|
|
75
92
|
</div>
|
|
76
93
|
<div className="jp-jupyter-ai-acp-client-diff-content">
|
|
77
94
|
{visible.map((line: IDiffLineInfo) => (
|
|
@@ -110,15 +127,25 @@ function DiffBlock({
|
|
|
110
127
|
*/
|
|
111
128
|
export function DiffView({
|
|
112
129
|
diffs,
|
|
113
|
-
onOpenFile
|
|
130
|
+
onOpenFile,
|
|
131
|
+
toDisplayPath,
|
|
132
|
+
pendingPermission
|
|
114
133
|
}: {
|
|
115
134
|
diffs: IToolCallDiff[];
|
|
116
135
|
onOpenFile?: (path: string) => void;
|
|
136
|
+
toDisplayPath?: (path: string) => string;
|
|
137
|
+
pendingPermission?: boolean;
|
|
117
138
|
}): JSX.Element {
|
|
118
139
|
return (
|
|
119
140
|
<div className="jp-jupyter-ai-acp-client-diff-container">
|
|
120
141
|
{diffs.map((d, i) => (
|
|
121
|
-
<DiffBlock
|
|
142
|
+
<DiffBlock
|
|
143
|
+
key={i}
|
|
144
|
+
diff={d}
|
|
145
|
+
onOpenFile={onOpenFile}
|
|
146
|
+
toDisplayPath={toDisplayPath}
|
|
147
|
+
pendingPermission={pendingPermission}
|
|
148
|
+
/>
|
|
122
149
|
))}
|
|
123
150
|
</div>
|
|
124
151
|
);
|
package/src/tool-calls.tsx
CHANGED
|
@@ -4,10 +4,32 @@ import {
|
|
|
4
4
|
IPermissionOption,
|
|
5
5
|
MessagePreambleProps
|
|
6
6
|
} from '@jupyter/chat';
|
|
7
|
+
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
|
|
7
8
|
import { submitPermissionDecision } from './request';
|
|
8
9
|
import clsx from 'clsx';
|
|
9
10
|
import { DiffView } from './diff-view';
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Convert an absolute filesystem path to a server-relative path.
|
|
14
|
+
* Returns the path unchanged if the server root is not set or the path
|
|
15
|
+
* is outside it.
|
|
16
|
+
*/
|
|
17
|
+
function toServerRelativePath(absolutePath: string): string {
|
|
18
|
+
const rootUri = PageConfig.getOption('rootUri');
|
|
19
|
+
const serverRoot = rootUri
|
|
20
|
+
? new URL(rootUri).pathname
|
|
21
|
+
: PageConfig.getOption('serverRoot');
|
|
22
|
+
if (!serverRoot) {
|
|
23
|
+
return absolutePath;
|
|
24
|
+
}
|
|
25
|
+
const relativePath = PathExt.relative(serverRoot, absolutePath);
|
|
26
|
+
// Path is outside server root — keep absolute
|
|
27
|
+
if (relativePath.startsWith('..')) {
|
|
28
|
+
return absolutePath;
|
|
29
|
+
}
|
|
30
|
+
return relativePath;
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
/**
|
|
12
34
|
* Preamble component that renders tool call status lines above message body.
|
|
13
35
|
* Returns null if the message has no tool calls.
|
|
@@ -21,7 +43,7 @@ export function ToolCallsComponent(
|
|
|
21
43
|
}
|
|
22
44
|
|
|
23
45
|
const onOpenFile = (path: string) => {
|
|
24
|
-
model.documentManager?.openOrReveal(path);
|
|
46
|
+
model.documentManager?.openOrReveal(toServerRelativePath(path));
|
|
25
47
|
};
|
|
26
48
|
|
|
27
49
|
return (
|
|
@@ -75,7 +97,7 @@ function formatToolInput(input: unknown): string {
|
|
|
75
97
|
|
|
76
98
|
/**
|
|
77
99
|
* Compute the pre-permission detail text for a tool call, or null if nothing
|
|
78
|
-
* to show beyond the title.
|
|
100
|
+
* to show beyond the title.
|
|
79
101
|
*/
|
|
80
102
|
function buildPermissionDetail(toolCall: IToolCall): string | null {
|
|
81
103
|
const { kind, title, locations, raw_input } = toolCall;
|
|
@@ -105,8 +127,10 @@ function buildPermissionDetail(toolCall: IToolCall): string | null {
|
|
|
105
127
|
locations?.length
|
|
106
128
|
) {
|
|
107
129
|
return kind === 'move' && locations.length >= 2
|
|
108
|
-
? locations[0] +
|
|
109
|
-
|
|
130
|
+
? toServerRelativePath(locations[0]) +
|
|
131
|
+
' \u2192 ' +
|
|
132
|
+
toServerRelativePath(locations[1])
|
|
133
|
+
: locations.map(toServerRelativePath).join('\n');
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
// Generic fallback for unknown/MCP kinds with raw_input.
|
|
@@ -151,7 +175,7 @@ function buildPermissionDetail(toolCall: IToolCall): string | null {
|
|
|
151
175
|
return null;
|
|
152
176
|
}
|
|
153
177
|
|
|
154
|
-
/** Tool kinds where expanded view shows
|
|
178
|
+
/** Tool kinds where expanded view shows file path(s) from locations. */
|
|
155
179
|
const FILE_KINDS = new Set(['read', 'edit', 'delete', 'move']);
|
|
156
180
|
|
|
157
181
|
/** Tool kinds where expanded view shows raw_output (stdout, search results, etc.). */
|
|
@@ -161,7 +185,7 @@ const OUTPUT_KINDS = new Set(['search', 'execute', 'think', 'fetch']);
|
|
|
161
185
|
* Build the expandable details content for a tool call.
|
|
162
186
|
* Returns lines of metadata to display, or empty array if nothing to show.
|
|
163
187
|
*
|
|
164
|
-
* File operations show
|
|
188
|
+
* File operations show server-relative paths; output operations show raw_output;
|
|
165
189
|
* switch_mode/other/None show nothing (clean title only).
|
|
166
190
|
*/
|
|
167
191
|
function buildDetailsLines(toolCall: IToolCall): string[] {
|
|
@@ -170,7 +194,7 @@ function buildDetailsLines(toolCall: IToolCall): string[] {
|
|
|
170
194
|
|
|
171
195
|
if (kind && FILE_KINDS.has(kind) && toolCall.locations?.length) {
|
|
172
196
|
for (const loc of toolCall.locations) {
|
|
173
|
-
lines.push(loc);
|
|
197
|
+
lines.push(toServerRelativePath(loc));
|
|
174
198
|
}
|
|
175
199
|
} else if (kind && OUTPUT_KINDS.has(kind) && toolCall.raw_output) {
|
|
176
200
|
lines.push(formatOutput(toolCall.raw_output));
|
|
@@ -240,7 +264,12 @@ function ToolCallLine({
|
|
|
240
264
|
</span>{' '}
|
|
241
265
|
<em>{displayTitle}</em>
|
|
242
266
|
</summary>
|
|
243
|
-
<DiffView
|
|
267
|
+
<DiffView
|
|
268
|
+
diffs={toolCall.diffs!}
|
|
269
|
+
onOpenFile={toolCall.kind === 'edit' ? onOpenFile : undefined}
|
|
270
|
+
toDisplayPath={toServerRelativePath}
|
|
271
|
+
pendingPermission
|
|
272
|
+
/>
|
|
244
273
|
</details>
|
|
245
274
|
<PermissionButtons toolCall={toolCall} />
|
|
246
275
|
</div>
|
|
@@ -286,7 +315,11 @@ function ToolCallLine({
|
|
|
286
315
|
<PermissionLabel toolCall={toolCall} />
|
|
287
316
|
</summary>
|
|
288
317
|
{hasDiffs ? (
|
|
289
|
-
<DiffView
|
|
318
|
+
<DiffView
|
|
319
|
+
diffs={toolCall.diffs!}
|
|
320
|
+
onOpenFile={onOpenFile}
|
|
321
|
+
toDisplayPath={toServerRelativePath}
|
|
322
|
+
/>
|
|
290
323
|
) : (
|
|
291
324
|
<div className="jp-jupyter-ai-acp-client-tool-call-detail">
|
|
292
325
|
{detailsLines.join('\n')}
|
package/style/base.css
CHANGED
|
@@ -145,10 +145,13 @@ details[open].jp-jupyter-ai-acp-client-tool-call summary::after,
|
|
|
145
145
|
font-size: var(--jp-ui-font-size0);
|
|
146
146
|
font-family: var(--jp-code-font-family);
|
|
147
147
|
color: var(--jp-ui-font-color2);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.jp-jupyter-ai-acp-client-diff-header-clickable {
|
|
148
151
|
cursor: pointer;
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
.jp-jupyter-ai-acp-client-diff-header:hover {
|
|
154
|
+
.jp-jupyter-ai-acp-client-diff-header-clickable:hover {
|
|
152
155
|
text-decoration: underline;
|
|
153
156
|
}
|
|
154
157
|
|