opencode-idea 0.1.1 → 0.1.2
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/package.json +2 -1
- package/src/mcp/idea-tool-output.js +118 -0
- package/src/mcp/idea.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-idea",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenCode plugin that connects to JetBrains IDE MCP servers and prefers IDE tools when the current project is open in the IDE",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"src/mcp/idea.js",
|
|
15
15
|
"src/mcp/enhance.js",
|
|
16
16
|
"src/mcp/idea-tool-defaults.js",
|
|
17
|
+
"src/mcp/idea-tool-output.js",
|
|
17
18
|
"src/project.js",
|
|
18
19
|
"src/ide-launcher.js",
|
|
19
20
|
"src/ide-guidance.js",
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// IDE 工具的**输出**增强。
|
|
2
|
+
//
|
|
3
|
+
// 和 `idea-tool-defaults.js` 分开:那个管"调用前要传什么",这个管"回来以后怎么读"。
|
|
4
|
+
//
|
|
5
|
+
// `list_directory_tree` 的输出在到达 maxDepth 边界时会**静默停止** —— 没有 `…`、没有"已达深度上限",
|
|
6
|
+
// 一棵被截断的树和一个空目录在文本里长得一模一样。实测(2026-10):模型把 `maxDepth=6` 截断后的空包目录
|
|
7
|
+
// 读成"工具看不到",随后整条查询链退回 `shell` 里的 `find` / `grep` / `sed`。
|
|
8
|
+
//
|
|
9
|
+
// 参数侧(`RAISED_ARGUMENTS`)已经把下限抬到 50,这里再加一道**看得见**的提示:只要树的深度顶到了上限,
|
|
10
|
+
// 就在结果后面追加一句,让"被截断"和"真的空"不再同形。提示是追加的,不改原有内容。
|
|
11
|
+
|
|
12
|
+
/** ` ├── name` / ` │ └── name`:每一层固定占 4 个字符(空格或竖线)。 */
|
|
13
|
+
const TREE_ENTRY = /^([\s│]*)(?:├──|└──)\s/;
|
|
14
|
+
|
|
15
|
+
const LIST_DIRECTORY_TREE_SUFFIX = 'list_directory_tree';
|
|
16
|
+
|
|
17
|
+
/** 与 `RAISED_ARGUMENTS` 的下限一致:拿不到输入时按这个判断。 */
|
|
18
|
+
const DEFAULT_MAX_DEPTH = 50;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {unknown} input
|
|
22
|
+
* @returns {number}
|
|
23
|
+
*/
|
|
24
|
+
function readMaxDepth(input) {
|
|
25
|
+
const raw = input !== null && typeof input === 'object' ? input.maxDepth : undefined;
|
|
26
|
+
return typeof raw === 'number' && Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MAX_DEPTH;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 从 MCP 的 text 里取出树本身;不是那个 JSON 就整段当树。
|
|
31
|
+
*
|
|
32
|
+
* @param {string} text
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
function treeText(text) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(text);
|
|
38
|
+
if (parsed !== null && typeof parsed === 'object' && typeof parsed.tree === 'string') {
|
|
39
|
+
return parsed.tree;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// 不是 JSON:按纯文本树处理
|
|
43
|
+
}
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 最深的条目在第几层(根目录下的直接子项是第 1 层)。
|
|
49
|
+
*
|
|
50
|
+
* @param {string} text
|
|
51
|
+
* @returns {number}
|
|
52
|
+
*/
|
|
53
|
+
function deepestLevel(text) {
|
|
54
|
+
let deepest = 0;
|
|
55
|
+
for (const line of text.split('\n')) {
|
|
56
|
+
const match = TREE_ENTRY.exec(line);
|
|
57
|
+
if (!match) continue;
|
|
58
|
+
const level = Math.floor(match[1].length / 4);
|
|
59
|
+
if (level > deepest) deepest = level;
|
|
60
|
+
}
|
|
61
|
+
return deepest;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 把一段提示接到结果的文本后面,不改原有内容。
|
|
66
|
+
*
|
|
67
|
+
* `Tool.Result.content` 允许 `string` 或 `Content[]`,两种都要覆盖。
|
|
68
|
+
*
|
|
69
|
+
* @param {{ content?: unknown }} result
|
|
70
|
+
* @param {string} text
|
|
71
|
+
* @returns {unknown} 新结果;格式不认识时返回 `undefined`。
|
|
72
|
+
*/
|
|
73
|
+
function appendText(result, text) {
|
|
74
|
+
const content = result.content;
|
|
75
|
+
if (typeof content === 'string') {
|
|
76
|
+
return { ...result, content: `${content}\n\n${text}` };
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(content)) {
|
|
79
|
+
return { ...result, content: [...content, { type: 'text', text }] };
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 如果这棵目录树顶到了 `maxDepth` 的边界,追加一句截断提示。
|
|
86
|
+
*
|
|
87
|
+
* @param {unknown} result `Tool.Result`(只读,不改原对象)
|
|
88
|
+
* @param {{ tool: string, input: unknown, prefixes: readonly string[] }} call
|
|
89
|
+
* @returns {unknown} 新的 result;不该动时返回**原引用**,调用方据此跳过改写。
|
|
90
|
+
*/
|
|
91
|
+
export function appendDirectoryTreeTruncationNotice(result, { tool, input, prefixes }) {
|
|
92
|
+
if (typeof tool !== 'string') return result;
|
|
93
|
+
if (!prefixes.some((prefix) => tool === `${prefix}${LIST_DIRECTORY_TREE_SUFFIX}`)) return result;
|
|
94
|
+
if (result === null || typeof result !== 'object') return result;
|
|
95
|
+
|
|
96
|
+
const maxDepth = readMaxDepth(input);
|
|
97
|
+
const content = result.content;
|
|
98
|
+
const texts =
|
|
99
|
+
typeof content === 'string'
|
|
100
|
+
? [content]
|
|
101
|
+
: Array.isArray(content)
|
|
102
|
+
? content
|
|
103
|
+
.filter((item) => item?.type === 'text' && typeof item.text === 'string')
|
|
104
|
+
.map((item) => item.text)
|
|
105
|
+
: [];
|
|
106
|
+
if (texts.length === 0) return result;
|
|
107
|
+
|
|
108
|
+
const deepest = Math.max(...texts.map((text) => deepestLevel(treeText(text))));
|
|
109
|
+
if (deepest < maxDepth - 1) return result;
|
|
110
|
+
|
|
111
|
+
const next = appendText(
|
|
112
|
+
result,
|
|
113
|
+
`注意:这棵树已经到达 maxDepth=${maxDepth} 的边界(最深渲染到第 ${deepest} 层),` +
|
|
114
|
+
`更深的条目可能没有显示。要看完整内容就把 maxDepth 调大(例如 ${maxDepth * 2})重试,` +
|
|
115
|
+
'或者把 directoryPath 直接指到目标子目录再看一层。',
|
|
116
|
+
);
|
|
117
|
+
return next ?? result;
|
|
118
|
+
}
|
package/src/mcp/idea.js
CHANGED
|
@@ -127,7 +127,7 @@ export async function callTool(port, projectPath, name, args = {}, options = {})
|
|
|
127
127
|
params: {
|
|
128
128
|
protocolVersion: PROTOCOL_VERSION,
|
|
129
129
|
capabilities: {},
|
|
130
|
-
clientInfo: { name: 'opencode-idea', version: '0.1.
|
|
130
|
+
clientInfo: { name: 'opencode-idea', version: '0.1.2' },
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
// The IDE's Streamable-HTTP endpoint binds follow-up requests to the session
|