@duckmind/dm-windows-x64 0.63.6 → 0.63.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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +174 -11
- package/extensions/dm-mcp-adapter/OAUTH.md +3 -0
- package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +4 -0
- package/extensions/dm-mcp-adapter/abort.js +35 -0
- package/extensions/dm-mcp-adapter/agent-dir.js +45 -0
- package/extensions/dm-mcp-adapter/agent-plugin-loader.js +349 -0
- package/extensions/dm-mcp-adapter/app-bridge.bundle.js +5843 -0
- package/extensions/dm-mcp-adapter/cli.js +354 -0
- package/extensions/dm-mcp-adapter/commands.js +619 -0
- package/extensions/dm-mcp-adapter/config.js +1060 -0
- package/extensions/dm-mcp-adapter/consent-manager.js +60 -0
- package/extensions/dm-mcp-adapter/direct-tools.js +544 -0
- package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
- package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +349 -0
- package/extensions/dm-mcp-adapter/dist/config.js +1060 -0
- package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +311 -0
- package/extensions/dm-mcp-adapter/dist/metadata-cache.js +285 -0
- package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
- package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
- package/extensions/dm-mcp-adapter/dist/types.js +247 -0
- package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
- package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
- package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
- package/extensions/dm-mcp-adapter/dist/utils.js +307 -0
- package/extensions/dm-mcp-adapter/elicitation-handler.js +306 -0
- package/extensions/dm-mcp-adapter/error-signal.js +16 -0
- package/extensions/dm-mcp-adapter/errors.js +225 -0
- package/extensions/dm-mcp-adapter/failure-backoff.js +19 -0
- package/extensions/dm-mcp-adapter/glimpse-ui.js +61 -0
- package/extensions/dm-mcp-adapter/host-html-template.js +464 -0
- package/extensions/dm-mcp-adapter/index.js +1079 -0
- package/extensions/dm-mcp-adapter/init.js +624 -0
- package/extensions/dm-mcp-adapter/json-schema-validator.js +53 -0
- package/extensions/dm-mcp-adapter/lifecycle.js +385 -0
- package/extensions/dm-mcp-adapter/logger.js +115 -0
- package/extensions/dm-mcp-adapter/mcp-auth-flow.js +824 -0
- package/extensions/dm-mcp-adapter/mcp-auth.js +732 -0
- package/extensions/dm-mcp-adapter/mcp-bearer-store.js +311 -0
- package/extensions/dm-mcp-adapter/mcp-callback-server.js +388 -0
- package/extensions/dm-mcp-adapter/mcp-code.js +291 -0
- package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +88 -0
- package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +490 -0
- package/extensions/dm-mcp-adapter/mcp-output-guard.js +386 -0
- package/extensions/dm-mcp-adapter/mcp-panel.js +951 -0
- package/extensions/dm-mcp-adapter/mcp-probe.js +151 -0
- package/extensions/dm-mcp-adapter/mcp-references.js +263 -0
- package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +126 -0
- package/extensions/dm-mcp-adapter/mcp-setup-panel.js +578 -0
- package/extensions/dm-mcp-adapter/mcp-status.js +80 -0
- package/extensions/dm-mcp-adapter/mcp-trace.js +215 -0
- package/extensions/dm-mcp-adapter/metadata-cache.js +292 -0
- package/extensions/dm-mcp-adapter/namespace-tools.js +155 -0
- package/extensions/dm-mcp-adapter/npx-resolver.js +465 -0
- package/extensions/dm-mcp-adapter/oauth-handler.js +17 -0
- package/extensions/dm-mcp-adapter/oauth.js +17 -0
- package/extensions/dm-mcp-adapter/onboarding-state.js +62 -0
- package/extensions/dm-mcp-adapter/package-mcp-loader.js +144 -0
- package/extensions/dm-mcp-adapter/package.json +68 -0
- package/extensions/dm-mcp-adapter/panel-keys.js +26 -0
- package/extensions/dm-mcp-adapter/prompts.js +258 -0
- package/extensions/dm-mcp-adapter/proxy-modes.js +1336 -0
- package/extensions/dm-mcp-adapter/request-headers-command.js +293 -0
- package/extensions/dm-mcp-adapter/resource-tools.js +7 -0
- package/extensions/dm-mcp-adapter/runtime-owner.js +79 -0
- package/extensions/dm-mcp-adapter/sampling-handler.js +222 -0
- package/extensions/dm-mcp-adapter/sandbox-proxy-template.js +190 -0
- package/extensions/dm-mcp-adapter/search-ranking.js +198 -0
- package/extensions/dm-mcp-adapter/server-manager.js +1258 -0
- package/extensions/dm-mcp-adapter/session-recovery.js +82 -0
- package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +39 -0
- package/extensions/dm-mcp-adapter/state.js +0 -0
- package/extensions/dm-mcp-adapter/test-runner.mjs +18 -0
- package/extensions/dm-mcp-adapter/tool-approval.js +151 -0
- package/extensions/dm-mcp-adapter/tool-metadata.js +247 -0
- package/extensions/dm-mcp-adapter/tool-registrar.js +212 -0
- package/extensions/dm-mcp-adapter/tool-result-renderer.js +355 -0
- package/extensions/dm-mcp-adapter/ts-shape.js +157 -0
- package/extensions/dm-mcp-adapter/types.js +264 -0
- package/extensions/dm-mcp-adapter/ui-app-bridge-helpers.js +36 -0
- package/extensions/dm-mcp-adapter/ui-resource-handler.js +209 -0
- package/extensions/dm-mcp-adapter/ui-server.js +815 -0
- package/extensions/dm-mcp-adapter/ui-session.js +455 -0
- package/extensions/dm-mcp-adapter/ui-stream-types.js +58 -0
- package/extensions/dm-mcp-adapter/ui-tool-visibility.js +26 -0
- package/extensions/dm-mcp-adapter/unix-socket-transport.js +83 -0
- package/extensions/dm-mcp-adapter/utils.js +307 -0
- package/package.json +13 -1
- package/extensions/dm-localllm-provider/detect.test.js +0 -1051
- package/extensions/dm-localllm-provider/index.test.js +0 -149
- package/extensions/dm-localllm-provider/keychain.test.js +0 -61
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const MAX_BINARY_RESOURCE_BYTES = 10 * 1024 * 1024;
|
|
5
|
+
const MAX_SESSION_RESOURCE_BYTES = 100 * 1024 * 1024;
|
|
6
|
+
const MAX_SESSION_RESOURCE_FILES = 1e4;
|
|
7
|
+
const CLEANUP_RETRY_DELAY_MS = 30000;
|
|
8
|
+
const MAX_CLEANUP_RETRY_ATTEMPTS = 3;
|
|
9
|
+
function createMaterializedResourceSession() {
|
|
10
|
+
return {
|
|
11
|
+
directory: undefined,
|
|
12
|
+
bytes: 0,
|
|
13
|
+
files: 0,
|
|
14
|
+
sequence: 0
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const defaultMaterializedResourceSession = createMaterializedResourceSession();
|
|
18
|
+
const scopedMaterializedResourceSessions = new WeakMap;
|
|
19
|
+
const pendingCleanupDirectories = new Set;
|
|
20
|
+
const cleanupRetryAttempts = new Map;
|
|
21
|
+
let pendingCleanupRetry;
|
|
22
|
+
function isAbortedScope(scope) {
|
|
23
|
+
return !!scope && "aborted" in scope && scope.aborted === true;
|
|
24
|
+
}
|
|
25
|
+
function getMaterializedResourceSession(scope) {
|
|
26
|
+
if (isAbortedScope(scope))
|
|
27
|
+
return;
|
|
28
|
+
if (!scope)
|
|
29
|
+
return defaultMaterializedResourceSession;
|
|
30
|
+
let session = scopedMaterializedResourceSessions.get(scope);
|
|
31
|
+
if (!session) {
|
|
32
|
+
session = createMaterializedResourceSession();
|
|
33
|
+
scopedMaterializedResourceSessions.set(scope, session);
|
|
34
|
+
}
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
function hasRetryableCleanupDirectory() {
|
|
38
|
+
for (const directory of pendingCleanupDirectories) {
|
|
39
|
+
if ((cleanupRetryAttempts.get(directory) ?? 0) < MAX_CLEANUP_RETRY_ATTEMPTS)
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
function schedulePendingCleanupRetry() {
|
|
45
|
+
if (pendingCleanupRetry || !hasRetryableCleanupDirectory())
|
|
46
|
+
return;
|
|
47
|
+
for (const directory of pendingCleanupDirectories) {
|
|
48
|
+
const attempts = cleanupRetryAttempts.get(directory) ?? 0;
|
|
49
|
+
if (attempts < MAX_CLEANUP_RETRY_ATTEMPTS)
|
|
50
|
+
cleanupRetryAttempts.set(directory, attempts + 1);
|
|
51
|
+
}
|
|
52
|
+
pendingCleanupRetry = setTimeout(() => {
|
|
53
|
+
pendingCleanupRetry = undefined;
|
|
54
|
+
try {
|
|
55
|
+
drainPendingCleanupDirectories();
|
|
56
|
+
} catch {}
|
|
57
|
+
}, CLEANUP_RETRY_DELAY_MS);
|
|
58
|
+
}
|
|
59
|
+
function drainPendingCleanupDirectories() {
|
|
60
|
+
const failures = [];
|
|
61
|
+
for (const directory of Array.from(pendingCleanupDirectories)) {
|
|
62
|
+
try {
|
|
63
|
+
rmSync(directory, { recursive: true, force: true });
|
|
64
|
+
pendingCleanupDirectories.delete(directory);
|
|
65
|
+
cleanupRetryAttempts.delete(directory);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
failures.push(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (pendingCleanupDirectories.size === 0 && pendingCleanupRetry) {
|
|
71
|
+
clearTimeout(pendingCleanupRetry);
|
|
72
|
+
pendingCleanupRetry = undefined;
|
|
73
|
+
}
|
|
74
|
+
if (failures.length > 0) {
|
|
75
|
+
schedulePendingCleanupRetry();
|
|
76
|
+
throw new AggregateError(failures, "Failed to clean materialized MCP resources");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function cleanupMaterializedBinaryResources(scope) {
|
|
80
|
+
const session = scope ? scopedMaterializedResourceSessions.get(scope) : defaultMaterializedResourceSession;
|
|
81
|
+
if (session?.directory)
|
|
82
|
+
pendingCleanupDirectories.add(session.directory);
|
|
83
|
+
if (session) {
|
|
84
|
+
session.directory = undefined;
|
|
85
|
+
session.bytes = 0;
|
|
86
|
+
session.files = 0;
|
|
87
|
+
session.sequence = 0;
|
|
88
|
+
if (scope)
|
|
89
|
+
scopedMaterializedResourceSessions.delete(scope);
|
|
90
|
+
}
|
|
91
|
+
drainPendingCleanupDirectories();
|
|
92
|
+
}
|
|
93
|
+
function replaceBlob(resource, text) {
|
|
94
|
+
delete resource.blob;
|
|
95
|
+
resource.text = text;
|
|
96
|
+
return text;
|
|
97
|
+
}
|
|
98
|
+
function omitBinaryResource(resource, reason) {
|
|
99
|
+
return replaceBlob(resource, [
|
|
100
|
+
`[Resource: ${resource.uri ?? "(no URI)"}]`,
|
|
101
|
+
`Binary content omitted: ${reason}`,
|
|
102
|
+
`MIME type: ${resource.mimeType ?? "application/octet-stream"}`
|
|
103
|
+
].join(`
|
|
104
|
+
`));
|
|
105
|
+
}
|
|
106
|
+
function materializeBinaryResource(resource, scope) {
|
|
107
|
+
const session = getMaterializedResourceSession(scope);
|
|
108
|
+
if (!session)
|
|
109
|
+
return omitBinaryResource(resource, "runtime stopped");
|
|
110
|
+
const decodedBytes = Buffer.byteLength(resource.blob, "base64");
|
|
111
|
+
if (decodedBytes > MAX_BINARY_RESOURCE_BYTES) {
|
|
112
|
+
return omitBinaryResource(resource, "decoded size exceeds 10 MiB");
|
|
113
|
+
}
|
|
114
|
+
if (session.bytes + decodedBytes > MAX_SESSION_RESOURCE_BYTES || session.files >= MAX_SESSION_RESOURCE_FILES) {
|
|
115
|
+
return omitBinaryResource(resource, "session resource limit reached");
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
session.directory ??= mkdtempSync(join(tmpdir(), "pi-mcp-resource-"));
|
|
119
|
+
} catch {
|
|
120
|
+
return omitBinaryResource(resource, "could not be saved");
|
|
121
|
+
}
|
|
122
|
+
const filePath = join(session.directory, `resource-${++session.sequence}.bin`);
|
|
123
|
+
session.bytes += decodedBytes;
|
|
124
|
+
session.files += 1;
|
|
125
|
+
try {
|
|
126
|
+
writeFileSync(filePath, Buffer.from(resource.blob, "base64"), { flag: "wx", mode: 384 });
|
|
127
|
+
} catch {
|
|
128
|
+
try {
|
|
129
|
+
rmSync(filePath, { force: true });
|
|
130
|
+
session.bytes -= decodedBytes;
|
|
131
|
+
session.files -= 1;
|
|
132
|
+
} catch {}
|
|
133
|
+
return omitBinaryResource(resource, "could not be saved");
|
|
134
|
+
}
|
|
135
|
+
return replaceBlob(resource, [
|
|
136
|
+
`[Resource: ${resource.uri ?? "(no URI)"}]`,
|
|
137
|
+
`Binary content saved to ${filePath}`,
|
|
138
|
+
`MIME type: ${resource.mimeType ?? "application/octet-stream"}`
|
|
139
|
+
].join(`
|
|
140
|
+
`));
|
|
141
|
+
}
|
|
142
|
+
export function transformMcpResourceContents(contents, scope) {
|
|
143
|
+
return contents.map((resource) => {
|
|
144
|
+
if (typeof resource.text === "string")
|
|
145
|
+
return { type: "text", text: resource.text };
|
|
146
|
+
if (typeof resource.blob === "string")
|
|
147
|
+
return { type: "text", text: materializeBinaryResource(resource, scope) };
|
|
148
|
+
return { type: "text", text: JSON.stringify(resource) };
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export function transformMcpContent(content, scope) {
|
|
152
|
+
return content.map((c) => {
|
|
153
|
+
if (c.type === "text") {
|
|
154
|
+
return { type: "text", text: c.text ?? "" };
|
|
155
|
+
}
|
|
156
|
+
if (c.type === "image") {
|
|
157
|
+
return {
|
|
158
|
+
type: "image",
|
|
159
|
+
data: c.data ?? "",
|
|
160
|
+
mimeType: c.mimeType ?? "image/png"
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (c.type === "resource") {
|
|
164
|
+
const resourceUri = c.resource?.uri ?? "(no URI)";
|
|
165
|
+
if (c.resource && "blob" in c.resource && typeof c.resource.blob === "string") {
|
|
166
|
+
const binaryResource = c.resource;
|
|
167
|
+
return {
|
|
168
|
+
type: "text",
|
|
169
|
+
text: materializeBinaryResource(binaryResource, scope)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const resourceContent = c.resource?.text ?? (c.resource ? JSON.stringify(c.resource) : "(no content)");
|
|
173
|
+
return {
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `[Resource: ${resourceUri}]
|
|
176
|
+
${resourceContent}`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (c.type === "resource_link") {
|
|
180
|
+
const linkName = c.name ?? c.uri ?? "unknown";
|
|
181
|
+
const linkUri = c.uri ?? "(no URI)";
|
|
182
|
+
return {
|
|
183
|
+
type: "text",
|
|
184
|
+
text: `[Resource Link: ${linkName}]
|
|
185
|
+
URI: ${linkUri}`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (c.type === "audio") {
|
|
189
|
+
return {
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `[Audio content: ${c.mimeType ?? "audio/*"}]`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return { type: "text", text: JSON.stringify(c) };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
export function resolveMcpResultContent(result, scope) {
|
|
198
|
+
const blocks = transformMcpContent(Array.isArray(result.content) ? result.content : [], scope);
|
|
199
|
+
if (blocks.length > 0)
|
|
200
|
+
return blocks;
|
|
201
|
+
if (result.structuredContent !== undefined && result.structuredContent !== null) {
|
|
202
|
+
return [{ type: "text", text: stringifyStructuredContent(result.structuredContent) }];
|
|
203
|
+
}
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
function stringifyStructuredContent(value) {
|
|
207
|
+
try {
|
|
208
|
+
return JSON.stringify(value, null, 2) ?? String(value);
|
|
209
|
+
} catch {
|
|
210
|
+
return String(value);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { Text, truncateToWidth, visibleWidth } from "@duckmind/dm-tui";
|
|
2
|
+
const plainTheme = { fg: (_name, text) => text };
|
|
3
|
+
const DEFAULT_MAX_CALL_INPUT_CHARS = 1500;
|
|
4
|
+
const DEFAULT_MAX_COMPACT_INPUT_CHARS = 240;
|
|
5
|
+
const DEFAULT_BOXED_COLLAPSED_LINES = 3;
|
|
6
|
+
const DEFAULT_COMPACT_COLLAPSED_LINES = 1;
|
|
7
|
+
const DEFAULT_MAX_COLLAPSED_CHARS = 8000;
|
|
8
|
+
const COLLAPSED_RENDER_CHAR_SLACK = 8;
|
|
9
|
+
|
|
10
|
+
class EmptyComponent {
|
|
11
|
+
render() {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
invalidate() {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class CompactMcpToolResult {
|
|
18
|
+
title;
|
|
19
|
+
inputPreview;
|
|
20
|
+
display;
|
|
21
|
+
theme;
|
|
22
|
+
rendered = null;
|
|
23
|
+
constructor(title, inputPreview, display, theme) {
|
|
24
|
+
this.title = title;
|
|
25
|
+
this.inputPreview = inputPreview;
|
|
26
|
+
this.display = display;
|
|
27
|
+
this.theme = theme;
|
|
28
|
+
}
|
|
29
|
+
render(width) {
|
|
30
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
31
|
+
if (this.rendered?.width === safeWidth)
|
|
32
|
+
return this.rendered.lines;
|
|
33
|
+
const resultLines = this.display.lines.filter((line, index, lines) => {
|
|
34
|
+
return !(this.display.truncated && index === lines.length - 1 && line === "…");
|
|
35
|
+
});
|
|
36
|
+
const lines = resultLines.length > 0 ? resultLines : [""];
|
|
37
|
+
const bodies = lines.map((line, index) => {
|
|
38
|
+
const prefix = index === 0 ? this.renderPrefix(safeWidth) : "";
|
|
39
|
+
return `${prefix}${this.theme.fg("toolOutput", line)}`;
|
|
40
|
+
});
|
|
41
|
+
const hiddenText = this.display.truncated || bodies.some((body) => visibleWidth(body) > safeWidth);
|
|
42
|
+
const rendered = bodies.map((body, index) => {
|
|
43
|
+
const suffix = hiddenText && index === bodies.length - 1 ? " … (Ctrl+O to expand)" : "";
|
|
44
|
+
if (!suffix)
|
|
45
|
+
return truncateToWidth(body, safeWidth, "…");
|
|
46
|
+
if (safeWidth >= suffix.length + 20) {
|
|
47
|
+
return `${truncateToWidth(body, safeWidth - suffix.length, "…")}${this.theme.fg("muted", suffix)}`;
|
|
48
|
+
}
|
|
49
|
+
const shortSuffix = " (Ctrl+O)";
|
|
50
|
+
if (safeWidth >= shortSuffix.length + 5) {
|
|
51
|
+
return `${truncateToWidth(body, safeWidth - shortSuffix.length, "…")}${this.theme.fg("muted", shortSuffix)}`;
|
|
52
|
+
}
|
|
53
|
+
return truncateToWidth(this.theme.fg("muted", shortSuffix.trim()), safeWidth, "…");
|
|
54
|
+
});
|
|
55
|
+
this.rendered = { width: safeWidth, lines: rendered };
|
|
56
|
+
return rendered;
|
|
57
|
+
}
|
|
58
|
+
invalidate() {
|
|
59
|
+
this.rendered = null;
|
|
60
|
+
}
|
|
61
|
+
renderPrefix(width) {
|
|
62
|
+
if (!this.title)
|
|
63
|
+
return "";
|
|
64
|
+
const arrow = " → ";
|
|
65
|
+
if (!this.inputPreview)
|
|
66
|
+
return `${this.theme.fg("toolTitle", this.title)}${arrow}`;
|
|
67
|
+
const maxPrefixWidth = Math.max(12, Math.floor(width * 0.55));
|
|
68
|
+
const titleWidth = visibleWidth(this.title);
|
|
69
|
+
const inputWidth = Math.max(0, maxPrefixWidth - titleWidth - 1);
|
|
70
|
+
if (inputWidth <= 3) {
|
|
71
|
+
return `${this.theme.fg("toolTitle", truncateToWidth(this.title, maxPrefixWidth, "…"))}${arrow}`;
|
|
72
|
+
}
|
|
73
|
+
const input = truncateToWidth(this.inputPreview, inputWidth, "…");
|
|
74
|
+
return `${this.theme.fg("toolTitle", this.title)} ${this.theme.fg("muted", input)}${arrow}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class CollapsibleText {
|
|
79
|
+
text;
|
|
80
|
+
expanded;
|
|
81
|
+
maxCollapsedLines;
|
|
82
|
+
preTruncated;
|
|
83
|
+
fullText;
|
|
84
|
+
footerText;
|
|
85
|
+
collapsedText = null;
|
|
86
|
+
collapsedRender = null;
|
|
87
|
+
constructor(text, expanded, maxCollapsedLines, ellipsis, expandHint, preTruncated = false) {
|
|
88
|
+
this.text = text;
|
|
89
|
+
this.expanded = expanded;
|
|
90
|
+
this.maxCollapsedLines = maxCollapsedLines;
|
|
91
|
+
this.preTruncated = preTruncated;
|
|
92
|
+
this.fullText = new Text(text, 0, 0);
|
|
93
|
+
this.footerText = new Text(`${ellipsis}
|
|
94
|
+
${expandHint}`, 0, 0);
|
|
95
|
+
}
|
|
96
|
+
render(width) {
|
|
97
|
+
if (this.expanded) {
|
|
98
|
+
return this.fullText.render(width);
|
|
99
|
+
}
|
|
100
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
101
|
+
const charBudget = safeWidth * (this.maxCollapsedLines + 1) * COLLAPSED_RENDER_CHAR_SLACK;
|
|
102
|
+
if (!this.collapsedText || this.collapsedText.charBudget !== charBudget) {
|
|
103
|
+
const prefix = this.text.length > charBudget ? this.text.slice(0, charBudget) : this.text;
|
|
104
|
+
this.collapsedText = {
|
|
105
|
+
charBudget,
|
|
106
|
+
fullyIncluded: prefix === this.text,
|
|
107
|
+
text: new Text(prefix, 0, 0)
|
|
108
|
+
};
|
|
109
|
+
this.collapsedRender = null;
|
|
110
|
+
}
|
|
111
|
+
const lines = this.collapsedText.text.render(width);
|
|
112
|
+
if (!this.preTruncated && this.collapsedText.fullyIncluded && lines.length <= this.maxCollapsedLines)
|
|
113
|
+
return lines;
|
|
114
|
+
if (this.collapsedRender?.width === width && this.collapsedRender.charBudget === charBudget) {
|
|
115
|
+
return this.collapsedRender.lines;
|
|
116
|
+
}
|
|
117
|
+
const rendered = [
|
|
118
|
+
...lines.slice(0, this.maxCollapsedLines),
|
|
119
|
+
...this.footerText.render(width)
|
|
120
|
+
];
|
|
121
|
+
this.collapsedRender = { width, charBudget, lines: rendered };
|
|
122
|
+
return rendered;
|
|
123
|
+
}
|
|
124
|
+
invalidate() {
|
|
125
|
+
this.fullText.invalidate();
|
|
126
|
+
this.footerText.invalidate();
|
|
127
|
+
this.collapsedText?.text.invalidate();
|
|
128
|
+
this.collapsedRender = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function truncateText(value, maxChars) {
|
|
132
|
+
if (value.length <= maxChars)
|
|
133
|
+
return value;
|
|
134
|
+
return `${value.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
135
|
+
}
|
|
136
|
+
function formatJsonish(value, maxChars) {
|
|
137
|
+
if (typeof value === "string") {
|
|
138
|
+
try {
|
|
139
|
+
return truncateText(JSON.stringify(JSON.parse(value), null, 2), maxChars);
|
|
140
|
+
} catch {
|
|
141
|
+
return truncateText(value, maxChars);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
return truncateText(JSON.stringify(value, null, 2), maxChars);
|
|
146
|
+
} catch {
|
|
147
|
+
return truncateText(String(value), maxChars);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function hasUsefulObjectContent(value) {
|
|
151
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
152
|
+
}
|
|
153
|
+
export function formatMcpProxyToolCallLines(args, maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS) {
|
|
154
|
+
if (args.action === "ui-messages")
|
|
155
|
+
return [`mcp ${args.action}`];
|
|
156
|
+
if (args.tool) {
|
|
157
|
+
const target = args.server ? `${args.tool} @ ${args.server}` : args.tool;
|
|
158
|
+
const lines = [`mcp call ${target}`];
|
|
159
|
+
if (args.args)
|
|
160
|
+
lines.push(formatJsonish(args.args, maxInputChars));
|
|
161
|
+
return lines;
|
|
162
|
+
}
|
|
163
|
+
if (args.connect)
|
|
164
|
+
return [`mcp connect ${args.connect}`];
|
|
165
|
+
if (args.describe)
|
|
166
|
+
return [`mcp describe ${args.describe}`];
|
|
167
|
+
if (args.search) {
|
|
168
|
+
let line = `mcp search ${args.search}`;
|
|
169
|
+
if (args.server)
|
|
170
|
+
line += ` @ ${args.server}`;
|
|
171
|
+
if (args.regex === true)
|
|
172
|
+
line += " (regex)";
|
|
173
|
+
if (args.includeSchemas === false)
|
|
174
|
+
line += " (schemas hidden)";
|
|
175
|
+
return [line];
|
|
176
|
+
}
|
|
177
|
+
if (args.server)
|
|
178
|
+
return [`mcp list ${args.server}`];
|
|
179
|
+
if (args.action)
|
|
180
|
+
return [`mcp ${args.action}`];
|
|
181
|
+
return ["mcp status"];
|
|
182
|
+
}
|
|
183
|
+
export function formatMcpDirectToolCallLines(displayName, args, maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS) {
|
|
184
|
+
if (!hasUsefulObjectContent(args))
|
|
185
|
+
return [displayName];
|
|
186
|
+
return [displayName, formatJsonish(args, maxInputChars)];
|
|
187
|
+
}
|
|
188
|
+
export function formatMcpScriptToolCallLines(args, maxInputChars = DEFAULT_MAX_CALL_INPUT_CHARS) {
|
|
189
|
+
return ["mcpScript", truncateText(args.code, maxInputChars)];
|
|
190
|
+
}
|
|
191
|
+
function renderToolCallLines(lines, theme) {
|
|
192
|
+
const activeTheme = theme ?? plainTheme;
|
|
193
|
+
const [title = "mcp", ...rest] = lines;
|
|
194
|
+
const styledTitle = activeTheme.fg("toolTitle", activeTheme.bold ? activeTheme.bold(title) : title);
|
|
195
|
+
const styledRest = rest.map((line) => activeTheme.fg("muted", line));
|
|
196
|
+
return new Text([styledTitle, ...styledRest].join(`
|
|
197
|
+
`), 0, 0);
|
|
198
|
+
}
|
|
199
|
+
function formatCompactInputPreview(lines, maxChars = DEFAULT_MAX_COMPACT_INPUT_CHARS) {
|
|
200
|
+
return truncateText(lines.slice(1).join(" ").replace(/\s+/g, " ").trim(), maxChars);
|
|
201
|
+
}
|
|
202
|
+
export function resolveMcpToolRenderOptions(settings) {
|
|
203
|
+
const resultRendering = settings?.toolResultRendering === "boxed" ? "boxed" : "compact";
|
|
204
|
+
const collapsedLines = settings?.collapsedResultLines;
|
|
205
|
+
const defaultLines = resultRendering === "boxed" ? DEFAULT_BOXED_COLLAPSED_LINES : DEFAULT_COMPACT_COLLAPSED_LINES;
|
|
206
|
+
return {
|
|
207
|
+
resultRendering,
|
|
208
|
+
collapsedResultLines: collapsedLines === 1 || collapsedLines === 2 || collapsedLines === 3 ? collapsedLines : defaultLines
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function shouldUseCompactFinalRender(options, context) {
|
|
212
|
+
return options.resultRendering === "compact" && context !== undefined && context.isPartial === false && context.expanded !== true && context.isError !== true;
|
|
213
|
+
}
|
|
214
|
+
function renderToolCall(lines, theme, context, options) {
|
|
215
|
+
if (context?.state) {
|
|
216
|
+
context.state.compactTitle = lines[0] ?? "mcp";
|
|
217
|
+
context.state.compactInputPreview = formatCompactInputPreview(lines);
|
|
218
|
+
}
|
|
219
|
+
if (shouldUseCompactFinalRender(options, context))
|
|
220
|
+
return new EmptyComponent;
|
|
221
|
+
return renderToolCallLines(lines, theme);
|
|
222
|
+
}
|
|
223
|
+
export function renderMcpProxyToolCall(args, theme, context) {
|
|
224
|
+
return renderToolCall(formatMcpProxyToolCallLines(args), theme, context, resolveMcpToolRenderOptions());
|
|
225
|
+
}
|
|
226
|
+
export function createMcpProxyToolCallRenderer(options) {
|
|
227
|
+
return (args, theme, context) => {
|
|
228
|
+
return renderToolCall(formatMcpProxyToolCallLines(args), theme, context, options);
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
export function createMcpDirectToolCallRenderer(displayName, options = resolveMcpToolRenderOptions()) {
|
|
232
|
+
return (args, theme, context) => {
|
|
233
|
+
return renderToolCall(formatMcpDirectToolCallLines(displayName, args), theme, context, options);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
export function createMcpScriptToolCallRenderer(options = resolveMcpToolRenderOptions()) {
|
|
237
|
+
return (args, theme, context) => {
|
|
238
|
+
if (shouldUseCompactFinalRender(options, context))
|
|
239
|
+
return new EmptyComponent;
|
|
240
|
+
return renderToolCallLines(formatMcpScriptToolCallLines(args), theme);
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function blockToLines(block) {
|
|
244
|
+
if (block.type === "text") {
|
|
245
|
+
return block.text.split(`
|
|
246
|
+
`);
|
|
247
|
+
}
|
|
248
|
+
return [`[image: ${block.mimeType}]`];
|
|
249
|
+
}
|
|
250
|
+
function collectCollapsedResultLines(content, maxLines, maxChars) {
|
|
251
|
+
if (content.length === 0)
|
|
252
|
+
return { lines: ["(empty result)"], truncated: false };
|
|
253
|
+
const lines = [];
|
|
254
|
+
let remainingChars = maxChars;
|
|
255
|
+
let truncated = false;
|
|
256
|
+
const appendLine = (line) => {
|
|
257
|
+
if (lines.length === 0) {
|
|
258
|
+
const previewWidth = Math.min(line.length, remainingChars);
|
|
259
|
+
if (line.slice(0, previewWidth).trim() === "") {
|
|
260
|
+
if (line.length >= remainingChars) {
|
|
261
|
+
truncated = true;
|
|
262
|
+
remainingChars = 0;
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
remainingChars -= line.length + 1;
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (lines.length >= maxLines || remainingChars <= 0) {
|
|
270
|
+
truncated = true;
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (line.length > remainingChars) {
|
|
274
|
+
lines.push(line.slice(0, remainingChars));
|
|
275
|
+
truncated = true;
|
|
276
|
+
remainingChars = 0;
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
lines.push(line);
|
|
280
|
+
remainingChars -= line.length + 1;
|
|
281
|
+
return true;
|
|
282
|
+
};
|
|
283
|
+
for (const block of content) {
|
|
284
|
+
if (block.type !== "text") {
|
|
285
|
+
if (!appendLine(`[image: ${block.mimeType}]`))
|
|
286
|
+
break;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
let start = 0;
|
|
290
|
+
while (start <= block.text.length) {
|
|
291
|
+
const newline = block.text.indexOf(`
|
|
292
|
+
`, start);
|
|
293
|
+
const line = newline === -1 ? block.text.slice(start) : block.text.slice(start, newline);
|
|
294
|
+
if (!appendLine(line))
|
|
295
|
+
break;
|
|
296
|
+
if (newline === -1)
|
|
297
|
+
break;
|
|
298
|
+
start = newline + 1;
|
|
299
|
+
}
|
|
300
|
+
if (truncated)
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
if (lines.length === 0)
|
|
304
|
+
lines.push(truncated ? "(leading blank output omitted)" : "");
|
|
305
|
+
if (truncated && lines.length >= maxLines)
|
|
306
|
+
lines.push("…");
|
|
307
|
+
return { lines, truncated };
|
|
308
|
+
}
|
|
309
|
+
export function formatMcpToolResultIdentity(details) {
|
|
310
|
+
if (details?.mode !== "call")
|
|
311
|
+
return null;
|
|
312
|
+
const server = typeof details.server === "string" ? details.server : typeof details.hintServer === "string" ? details.hintServer : null;
|
|
313
|
+
if (!server)
|
|
314
|
+
return null;
|
|
315
|
+
if (typeof details.tool === "string")
|
|
316
|
+
return `MCP ${server}/${details.tool}`;
|
|
317
|
+
if (typeof details.resourceUri === "string")
|
|
318
|
+
return `MCP ${server} resource ${details.resourceUri}`;
|
|
319
|
+
if (typeof details.requestedTool === "string")
|
|
320
|
+
return `MCP ${server}/${details.requestedTool}`;
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
export function formatMcpToolResultLines(result, expanded, maxCollapsedLines = DEFAULT_BOXED_COLLAPSED_LINES, maxCollapsedChars = DEFAULT_MAX_COLLAPSED_CHARS) {
|
|
324
|
+
if (!expanded) {
|
|
325
|
+
return collectCollapsedResultLines(result.content, maxCollapsedLines, maxCollapsedChars);
|
|
326
|
+
}
|
|
327
|
+
const allLines = result.content.flatMap(blockToLines);
|
|
328
|
+
const lines = allLines.length > 0 ? allLines : ["(empty result)"];
|
|
329
|
+
return { lines, truncated: false };
|
|
330
|
+
}
|
|
331
|
+
export function renderMcpToolResult(result, options, theme, context, renderOptions = resolveMcpToolRenderOptions()) {
|
|
332
|
+
const activeTheme = theme ?? plainTheme;
|
|
333
|
+
if (options.isPartial) {
|
|
334
|
+
return new Text(activeTheme.fg("warning", "Running MCP tool..."), 0, 0);
|
|
335
|
+
}
|
|
336
|
+
const hasErrorDetails = Boolean(result.details.error);
|
|
337
|
+
const expanded = options.expanded || context?.isError === true || hasErrorDetails;
|
|
338
|
+
if (!expanded && renderOptions.resultRendering === "compact") {
|
|
339
|
+
const display = formatMcpToolResultLines(result, false, renderOptions.collapsedResultLines);
|
|
340
|
+
const title = context?.state?.compactTitle ?? formatMcpToolResultIdentity(result.details) ?? "";
|
|
341
|
+
const inputPreview = context?.state?.compactInputPreview ?? "";
|
|
342
|
+
return new CompactMcpToolResult(title, inputPreview, display, activeTheme);
|
|
343
|
+
}
|
|
344
|
+
const display = formatMcpToolResultLines(result, expanded, renderOptions.collapsedResultLines);
|
|
345
|
+
const identity = formatMcpToolResultIdentity(result.details);
|
|
346
|
+
const output = [
|
|
347
|
+
...identity ? [activeTheme.fg("muted", identity)] : [],
|
|
348
|
+
...display.lines.map((line) => activeTheme.fg("toolOutput", line))
|
|
349
|
+
].join(`
|
|
350
|
+
`);
|
|
351
|
+
return new CollapsibleText(output, expanded, renderOptions.collapsedResultLines + (identity ? 1 : 0), activeTheme.fg("muted", "…"), activeTheme.fg("muted", "(Ctrl+O to expand)"), display.truncated);
|
|
352
|
+
}
|
|
353
|
+
export function createMcpToolResultRenderer(renderOptions) {
|
|
354
|
+
return (result, options, theme, context) => renderMcpToolResult(result, options, theme, context, renderOptions);
|
|
355
|
+
}
|