@renxqoo/renx-code 0.0.4 → 0.0.5
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/bin/renx.cjs +16 -0
- package/package.json +2 -45
- package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
- package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
- package/src/agent/runtime/runtime.test.ts +7 -4
- package/src/agent/runtime/runtime.ts +3 -9
- package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
- package/src/agent/runtime/source-modules.test.ts +16 -35
- package/src/agent/runtime/source-modules.ts +17 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
- package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
- package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
- package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
- package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
- package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
- package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
- package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
- package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
- package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
- package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
- package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
- package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
- package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
- package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
- package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
- package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
- package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
- package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
- package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
- package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
- package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
- package/vendor/agent-root/src/agent/agent/error.ts +198 -0
- package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
- package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
- package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
- package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
- package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
- package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
- package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
- package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
- package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
- package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
- package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
- package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
- package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
- package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
- package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
- package/vendor/agent-root/src/agent/app/index.ts +5 -0
- package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
- package/vendor/agent-root/src/agent/app/ports.ts +72 -0
- package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
- package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
- package/vendor/agent-root/src/agent/error-contract.ts +154 -0
- package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
- package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
- package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
- package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
- package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
- package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
- package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
- package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
- package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
- package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
- package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
- package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
- package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
- package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
- package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
- package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
- package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
- package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
- package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
- package/vendor/agent-root/src/agent/tool/error.ts +131 -0
- package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
- package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
- package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
- package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
- package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
- package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
- package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
- package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
- package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
- package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
- package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
- package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
- package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
- package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
- package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
- package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
- package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
- package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
- package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
- package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
- package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
- package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
- package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
- package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
- package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
- package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
- package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
- package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
- package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
- package/vendor/agent-root/src/agent/tool/task.ts +209 -0
- package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
- package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
- package/vendor/agent-root/src/agent/tool/types.ts +116 -0
- package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
- package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
- package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
- package/vendor/agent-root/src/agent/types.ts +232 -0
- package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
- package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
- package/vendor/agent-root/src/agent/utils/index.ts +16 -0
- package/vendor/agent-root/src/agent/utils/message.ts +171 -0
- package/vendor/agent-root/src/agent/utils/token.ts +28 -0
- package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
- package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
- package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
- package/vendor/agent-root/src/config/index.ts +54 -0
- package/vendor/agent-root/src/config/loader.ts +431 -0
- package/vendor/agent-root/src/config/paths.ts +30 -0
- package/vendor/agent-root/src/config/runtime.ts +163 -0
- package/vendor/agent-root/src/config/types.ts +70 -0
- package/vendor/agent-root/src/logger/index.ts +57 -0
- package/vendor/agent-root/src/logger/logger.ts +819 -0
- package/vendor/agent-root/src/logger/types.ts +150 -0
- package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
- package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
- package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
- package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
- package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
- package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
- package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
- package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
- package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
- package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
- package/vendor/agent-root/src/providers/http/client.ts +289 -0
- package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
- package/vendor/agent-root/src/providers/index.ts +76 -0
- package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
- package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
- package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
- package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
- package/vendor/agent-root/src/providers/registry.ts +135 -0
- package/vendor/agent-root/src/providers/types/api.ts +284 -0
- package/vendor/agent-root/src/providers/types/config.ts +58 -0
- package/vendor/agent-root/src/providers/types/errors.ts +323 -0
- package/vendor/agent-root/src/providers/types/index.ts +72 -0
- package/vendor/agent-root/src/providers/types/provider.ts +45 -0
- package/vendor/agent-root/src/providers/types/registry.ts +88 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as ts from 'typescript';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import { BaseTool, ToolResult } from './base-tool';
|
|
6
|
+
import { ToolExecutionError } from './error';
|
|
7
|
+
import { ensurePathWithinAllowed, normalizeAllowedDirectories } from './path-security';
|
|
8
|
+
import { LSP_TOOL_DESCRIPTION } from './tool-prompts';
|
|
9
|
+
import type { ToolExecutionContext } from './types';
|
|
10
|
+
|
|
11
|
+
// 支持的操作类型
|
|
12
|
+
const OPERATIONS = ['goToDefinition', 'findReferences', 'hover', 'documentSymbols'] as const;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 创建 TypeScript 语言服务
|
|
16
|
+
* 每次调用创建新实例以确保文件列表是最新的
|
|
17
|
+
*/
|
|
18
|
+
function createLanguageService(filePath: string): ts.LanguageService {
|
|
19
|
+
const configPath = findTsConfig(filePath);
|
|
20
|
+
const { options, fileNames } = loadCompilerOptions(configPath);
|
|
21
|
+
// 确保目标文件在文件列表中
|
|
22
|
+
const resolvedPath = path.resolve(filePath);
|
|
23
|
+
const allFileNames = fileNames.includes(resolvedPath) ? fileNames : [...fileNames, resolvedPath];
|
|
24
|
+
const host = createServiceHost(options, allFileNames);
|
|
25
|
+
return ts.createLanguageService(host);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 查找最近的 tsconfig.json
|
|
30
|
+
*/
|
|
31
|
+
function findTsConfig(startPath: string): string | undefined {
|
|
32
|
+
let dir = path.dirname(path.resolve(startPath));
|
|
33
|
+
const root = path.parse(dir).root;
|
|
34
|
+
|
|
35
|
+
while (dir !== root) {
|
|
36
|
+
const configPath = path.join(dir, 'tsconfig.json');
|
|
37
|
+
if (fs.existsSync(configPath)) {
|
|
38
|
+
return configPath;
|
|
39
|
+
}
|
|
40
|
+
dir = path.dirname(dir);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 加载编译器选项
|
|
48
|
+
*/
|
|
49
|
+
function loadCompilerOptions(configPath?: string): {
|
|
50
|
+
options: ts.CompilerOptions;
|
|
51
|
+
fileNames: string[];
|
|
52
|
+
} {
|
|
53
|
+
if (configPath) {
|
|
54
|
+
const configText = fs.readFileSync(configPath, 'utf-8');
|
|
55
|
+
const result = ts.parseConfigFileTextToJson(configPath, configText);
|
|
56
|
+
if (result.error) {
|
|
57
|
+
return getDefaultOptions();
|
|
58
|
+
}
|
|
59
|
+
const parsed = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(configPath));
|
|
60
|
+
return { options: parsed.options, fileNames: parsed.fileNames };
|
|
61
|
+
}
|
|
62
|
+
return getDefaultOptions();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getDefaultOptions(): { options: ts.CompilerOptions; fileNames: string[] } {
|
|
66
|
+
return {
|
|
67
|
+
options: {
|
|
68
|
+
target: ts.ScriptTarget.ESNext,
|
|
69
|
+
module: ts.ModuleKind.ESNext,
|
|
70
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
71
|
+
allowJs: true,
|
|
72
|
+
checkJs: false,
|
|
73
|
+
strict: false,
|
|
74
|
+
skipLibCheck: true,
|
|
75
|
+
noEmit: true,
|
|
76
|
+
},
|
|
77
|
+
fileNames: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 创建语言服务宿主
|
|
83
|
+
*/
|
|
84
|
+
function createServiceHost(
|
|
85
|
+
options: ts.CompilerOptions,
|
|
86
|
+
fileNames: string[]
|
|
87
|
+
): ts.LanguageServiceHost {
|
|
88
|
+
const files = new Map<string, { version: number; content: string }>();
|
|
89
|
+
const knownFileNames = new Set<string>();
|
|
90
|
+
|
|
91
|
+
// 预加载已知文件
|
|
92
|
+
for (const fileName of fileNames) {
|
|
93
|
+
try {
|
|
94
|
+
const resolved = path.resolve(fileName);
|
|
95
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
96
|
+
files.set(resolved, { version: 0, content });
|
|
97
|
+
knownFileNames.add(resolved);
|
|
98
|
+
} catch {
|
|
99
|
+
// 忽略读取失败的文件
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function ensureFileLoaded(fileName: string): void {
|
|
104
|
+
const resolved = path.resolve(fileName);
|
|
105
|
+
if (!files.has(resolved)) {
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
108
|
+
files.set(resolved, { version: 0, content });
|
|
109
|
+
knownFileNames.add(resolved);
|
|
110
|
+
} catch {
|
|
111
|
+
// 文件不存在或无法读取
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
getScriptFileNames: () => Array.from(knownFileNames),
|
|
118
|
+
getScriptVersion: (fileName) => {
|
|
119
|
+
ensureFileLoaded(fileName);
|
|
120
|
+
const file = files.get(path.resolve(fileName));
|
|
121
|
+
return file ? String(file.version) : '0';
|
|
122
|
+
},
|
|
123
|
+
getScriptSnapshot: (fileName) => {
|
|
124
|
+
ensureFileLoaded(fileName);
|
|
125
|
+
const resolved = path.resolve(fileName);
|
|
126
|
+
const file = files.get(resolved);
|
|
127
|
+
if (!file) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
return ts.ScriptSnapshot.fromString(file.content);
|
|
131
|
+
},
|
|
132
|
+
getCurrentDirectory: () => process.cwd(),
|
|
133
|
+
getCompilationSettings: () => options,
|
|
134
|
+
getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts),
|
|
135
|
+
fileExists: (fileName) => fs.existsSync(fileName),
|
|
136
|
+
readFile: (fileName) => {
|
|
137
|
+
try {
|
|
138
|
+
return fs.readFileSync(fileName, 'utf-8');
|
|
139
|
+
} catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
readDirectory: ts.sys.readDirectory,
|
|
144
|
+
directoryExists: ts.sys.directoryExists,
|
|
145
|
+
getDirectories: ts.sys.getDirectories,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 将行/列转换为文件偏移位置
|
|
151
|
+
*/
|
|
152
|
+
function getPositionFromLineCharacter(
|
|
153
|
+
service: ts.LanguageService,
|
|
154
|
+
fileName: string,
|
|
155
|
+
line: number,
|
|
156
|
+
character: number
|
|
157
|
+
): number {
|
|
158
|
+
const sourceFile = service.getProgram()?.getSourceFile(fileName);
|
|
159
|
+
if (!sourceFile) {
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
return sourceFile.getPositionOfLineAndCharacter(line - 1, character - 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 格式化位置信息
|
|
167
|
+
*/
|
|
168
|
+
function formatLocation(fileName: string, line: number, character: number): string {
|
|
169
|
+
return `${fileName}:${line + 1}:${character + 1}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取行和列偏移(兼容不同 TypeScript 版本)
|
|
174
|
+
*/
|
|
175
|
+
function getLineAndCharacter(
|
|
176
|
+
service: ts.LanguageService,
|
|
177
|
+
fileName: string,
|
|
178
|
+
position: number
|
|
179
|
+
): { line: number; character: number } {
|
|
180
|
+
// TypeScript 5.5+ 提供 toLineColumnOffset
|
|
181
|
+
if (typeof service.toLineColumnOffset === 'function') {
|
|
182
|
+
return service.toLineColumnOffset(fileName, position);
|
|
183
|
+
}
|
|
184
|
+
// 回退:使用 ScriptSnapshot 计算
|
|
185
|
+
const snapshot = service.getProgram()?.getSourceFile(fileName);
|
|
186
|
+
if (snapshot) {
|
|
187
|
+
const pos = ts.getLineAndCharacterOfPosition(snapshot, position);
|
|
188
|
+
return { line: pos.line, character: pos.character };
|
|
189
|
+
}
|
|
190
|
+
return { line: 0, character: 0 };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 执行 goToDefinition 操作
|
|
195
|
+
*/
|
|
196
|
+
function executeGoToDefinition(
|
|
197
|
+
service: ts.LanguageService,
|
|
198
|
+
filePath: string,
|
|
199
|
+
line: number,
|
|
200
|
+
character: number
|
|
201
|
+
): ToolResult {
|
|
202
|
+
const position = getPositionFromLineCharacter(service, filePath, line, character);
|
|
203
|
+
const definition = service.getDefinitionAndBoundSpan(filePath, position);
|
|
204
|
+
|
|
205
|
+
if (!definition || !definition.definitions || definition.definitions.length === 0) {
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
output: 'No definition found',
|
|
209
|
+
metadata: { found: false },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const lines: string[] = [`Found ${definition.definitions.length} definition(s):\n`];
|
|
214
|
+
for (const def of definition.definitions) {
|
|
215
|
+
const start = getLineAndCharacter(service, def.fileName, def.textSpan.start);
|
|
216
|
+
lines.push(` ${formatLocation(def.fileName, start.line, start.character)}`);
|
|
217
|
+
if (def.kind) {
|
|
218
|
+
lines.push(` Kind: ${def.kind}`);
|
|
219
|
+
}
|
|
220
|
+
if (def.containerName) {
|
|
221
|
+
lines.push(` Container: ${def.containerName}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
output: lines.join('\n'),
|
|
228
|
+
metadata: {
|
|
229
|
+
found: true,
|
|
230
|
+
definitions: definition.definitions.map((d) => ({
|
|
231
|
+
fileName: d.fileName,
|
|
232
|
+
start: d.textSpan.start,
|
|
233
|
+
length: d.textSpan.length,
|
|
234
|
+
kind: d.kind,
|
|
235
|
+
name: d.name,
|
|
236
|
+
containerName: d.containerName,
|
|
237
|
+
})),
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 执行 findReferences 操作
|
|
244
|
+
*/
|
|
245
|
+
function executeFindReferences(
|
|
246
|
+
service: ts.LanguageService,
|
|
247
|
+
filePath: string,
|
|
248
|
+
line: number,
|
|
249
|
+
character: number
|
|
250
|
+
): ToolResult {
|
|
251
|
+
const position = getPositionFromLineCharacter(service, filePath, line, character);
|
|
252
|
+
const references = service.getReferencesAtPosition(filePath, position);
|
|
253
|
+
|
|
254
|
+
if (!references || references.length === 0) {
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
output: 'No references found',
|
|
258
|
+
metadata: { found: false },
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const lines: string[] = [`Found ${references.length} reference(s):\n`];
|
|
263
|
+
for (const ref of references) {
|
|
264
|
+
const start = getLineAndCharacter(service, ref.fileName, ref.textSpan.start);
|
|
265
|
+
const isWrite = ref.isWriteAccess ? ' (write)' : ' (read)';
|
|
266
|
+
lines.push(` ${formatLocation(ref.fileName, start.line, start.character)}${isWrite}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
output: lines.join('\n'),
|
|
272
|
+
metadata: {
|
|
273
|
+
found: true,
|
|
274
|
+
count: references.length,
|
|
275
|
+
references: references.map((r) => ({
|
|
276
|
+
fileName: r.fileName,
|
|
277
|
+
start: r.textSpan.start,
|
|
278
|
+
length: r.textSpan.length,
|
|
279
|
+
isWrite: r.isWriteAccess,
|
|
280
|
+
})),
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 执行 hover 操作
|
|
287
|
+
*/
|
|
288
|
+
function executeHover(
|
|
289
|
+
service: ts.LanguageService,
|
|
290
|
+
filePath: string,
|
|
291
|
+
line: number,
|
|
292
|
+
character: number
|
|
293
|
+
): ToolResult {
|
|
294
|
+
const position = getPositionFromLineCharacter(service, filePath, line, character);
|
|
295
|
+
const hover = service.getQuickInfoAtPosition(filePath, position);
|
|
296
|
+
|
|
297
|
+
if (!hover) {
|
|
298
|
+
return {
|
|
299
|
+
success: true,
|
|
300
|
+
output: 'No hover information available',
|
|
301
|
+
metadata: { found: false },
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const displayParts = hover.displayParts?.map((p) => p.text).join('') || '';
|
|
306
|
+
const documentation = hover.documentation?.map((d) => d.text).join('\n') || '';
|
|
307
|
+
const tags =
|
|
308
|
+
hover.tags
|
|
309
|
+
?.map((t) => `@${t.name} ${t.text?.map((tt) => tt.text).join('') || ''}`)
|
|
310
|
+
.join('\n') || '';
|
|
311
|
+
|
|
312
|
+
const lines: string[] = [];
|
|
313
|
+
if (displayParts) {
|
|
314
|
+
lines.push('```typescript');
|
|
315
|
+
lines.push(displayParts);
|
|
316
|
+
lines.push('```');
|
|
317
|
+
}
|
|
318
|
+
if (documentation) {
|
|
319
|
+
lines.push('');
|
|
320
|
+
lines.push(documentation);
|
|
321
|
+
}
|
|
322
|
+
if (tags) {
|
|
323
|
+
lines.push('');
|
|
324
|
+
lines.push(tags);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
success: true,
|
|
329
|
+
output: lines.join('\n') || 'No information',
|
|
330
|
+
metadata: {
|
|
331
|
+
found: true,
|
|
332
|
+
displayParts,
|
|
333
|
+
documentation,
|
|
334
|
+
kind: hover.kind,
|
|
335
|
+
kindModifiers: hover.kindModifiers,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 执行 documentSymbols 操作
|
|
342
|
+
*/
|
|
343
|
+
function executeDocumentSymbols(service: ts.LanguageService, filePath: string): ToolResult {
|
|
344
|
+
const navTree = service.getNavigationTree(filePath);
|
|
345
|
+
|
|
346
|
+
if (!navTree) {
|
|
347
|
+
return {
|
|
348
|
+
success: true,
|
|
349
|
+
output: 'No symbols found',
|
|
350
|
+
metadata: { found: false },
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const symbols: Array<{ name: string; kind: string; line: number }> = [];
|
|
355
|
+
|
|
356
|
+
function walkTree(node: ts.NavigationTree, depth = 0) {
|
|
357
|
+
const span = node.spans[0];
|
|
358
|
+
if (span) {
|
|
359
|
+
const start = getLineAndCharacter(service, filePath, span.start);
|
|
360
|
+
symbols.push({
|
|
361
|
+
name: ' '.repeat(depth) + node.text,
|
|
362
|
+
kind: node.kind,
|
|
363
|
+
line: start.line + 1,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
for (const child of node.childItems || []) {
|
|
367
|
+
walkTree(child, depth + 1);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
walkTree(navTree);
|
|
372
|
+
|
|
373
|
+
const lines: string[] = [`Found ${symbols.length} symbol(s):\n`];
|
|
374
|
+
for (const sym of symbols) {
|
|
375
|
+
lines.push(` ${sym.name} (${sym.kind}) - Line ${sym.line}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
output: lines.join('\n'),
|
|
381
|
+
metadata: {
|
|
382
|
+
found: true,
|
|
383
|
+
count: symbols.length,
|
|
384
|
+
symbols,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const schema = z
|
|
390
|
+
.object({
|
|
391
|
+
operation: z.enum(OPERATIONS).describe('LSP operation to perform'),
|
|
392
|
+
filePath: z.string().min(1).describe('Absolute or relative path to the file'),
|
|
393
|
+
line: z.number().int().min(1).describe('Line number (1-based)').optional(),
|
|
394
|
+
character: z.number().int().min(1).describe('Character offset (1-based)').optional(),
|
|
395
|
+
})
|
|
396
|
+
.strict()
|
|
397
|
+
.refine(
|
|
398
|
+
(data) => {
|
|
399
|
+
if (['goToDefinition', 'findReferences', 'hover'].includes(data.operation)) {
|
|
400
|
+
return data.line !== undefined && data.character !== undefined;
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
message:
|
|
406
|
+
'line and character are required for goToDefinition, findReferences, and hover operations',
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
export interface LspToolOptions {
|
|
411
|
+
allowedDirectories?: string[];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export class LspTool extends BaseTool<typeof schema> {
|
|
415
|
+
readonly name = 'lsp';
|
|
416
|
+
readonly description = LSP_TOOL_DESCRIPTION;
|
|
417
|
+
readonly parameters = schema;
|
|
418
|
+
|
|
419
|
+
private readonly allowedDirectories: string[];
|
|
420
|
+
|
|
421
|
+
constructor(options?: LspToolOptions) {
|
|
422
|
+
super();
|
|
423
|
+
this.allowedDirectories = normalizeAllowedDirectories(options?.allowedDirectories);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async execute(
|
|
427
|
+
args: z.infer<typeof schema>,
|
|
428
|
+
_context?: ToolExecutionContext
|
|
429
|
+
): Promise<ToolResult> {
|
|
430
|
+
const { operation, filePath, line = 1, character = 1 } = args;
|
|
431
|
+
|
|
432
|
+
// 路径安全检查
|
|
433
|
+
const absolutePath = ensurePathWithinAllowed(
|
|
434
|
+
path.resolve(filePath),
|
|
435
|
+
this.allowedDirectories,
|
|
436
|
+
'LSP_PATH_NOT_ALLOWED'
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// 检查文件是否存在
|
|
440
|
+
if (!fs.existsSync(absolutePath)) {
|
|
441
|
+
throw new ToolExecutionError(`File not found: ${filePath}`, 2030);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// 检查文件类型
|
|
445
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
446
|
+
const supportedExtensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
447
|
+
if (!supportedExtensions.includes(ext)) {
|
|
448
|
+
throw new ToolExecutionError(
|
|
449
|
+
`Unsupported file type: ${ext}. Supported: ${supportedExtensions.join(', ')}`,
|
|
450
|
+
2031
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const service = createLanguageService(absolutePath);
|
|
456
|
+
|
|
457
|
+
switch (operation) {
|
|
458
|
+
case 'goToDefinition':
|
|
459
|
+
return executeGoToDefinition(service, absolutePath, line, character);
|
|
460
|
+
case 'findReferences':
|
|
461
|
+
return executeFindReferences(service, absolutePath, line, character);
|
|
462
|
+
case 'hover':
|
|
463
|
+
return executeHover(service, absolutePath, line, character);
|
|
464
|
+
case 'documentSymbols':
|
|
465
|
+
return executeDocumentSymbols(service, absolutePath);
|
|
466
|
+
default:
|
|
467
|
+
throw new ToolExecutionError(`Unknown operation: ${operation}`, 2032);
|
|
468
|
+
}
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (error instanceof ToolExecutionError) {
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
throw new ToolExecutionError(
|
|
474
|
+
`LSP operation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
475
|
+
2033
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export default LspTool;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface PathAccessAssessment {
|
|
6
|
+
normalizedCandidate: string;
|
|
7
|
+
allowed: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function expandHomePath(rawPath: string): string {
|
|
12
|
+
if (rawPath === '~') {
|
|
13
|
+
return os.homedir();
|
|
14
|
+
}
|
|
15
|
+
if (rawPath.startsWith('~/')) {
|
|
16
|
+
return path.join(os.homedir(), rawPath.slice(2));
|
|
17
|
+
}
|
|
18
|
+
return rawPath;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeExistingPath(targetPath: string): string {
|
|
22
|
+
const resolved = path.resolve(targetPath);
|
|
23
|
+
try {
|
|
24
|
+
return fs.realpathSync(resolved);
|
|
25
|
+
} catch {
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeAllowedDirectories(inputDirectories?: string[]): string[] {
|
|
31
|
+
const directories =
|
|
32
|
+
inputDirectories && inputDirectories.length > 0 ? inputDirectories : [process.cwd()];
|
|
33
|
+
return directories.map((directory) => normalizeExistingPath(expandHomePath(directory)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveRequestedPath(requestedPath: string, baseDirectory = process.cwd()): string {
|
|
37
|
+
const expanded = expandHomePath(requestedPath.trim());
|
|
38
|
+
if (path.isAbsolute(expanded)) {
|
|
39
|
+
return path.resolve(expanded);
|
|
40
|
+
}
|
|
41
|
+
return path.resolve(baseDirectory, expanded);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizePathWithExistingAncestor(inputPath: string): string {
|
|
45
|
+
const absolute = path.resolve(inputPath);
|
|
46
|
+
let current = absolute;
|
|
47
|
+
const trailingSegments: string[] = [];
|
|
48
|
+
|
|
49
|
+
for (;;) {
|
|
50
|
+
try {
|
|
51
|
+
const realCurrent = fs.realpathSync(current);
|
|
52
|
+
if (trailingSegments.length === 0) {
|
|
53
|
+
return realCurrent;
|
|
54
|
+
}
|
|
55
|
+
return path.join(realCurrent, ...trailingSegments.reverse());
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const nodeError = error as NodeJS.ErrnoException;
|
|
58
|
+
if (nodeError.code !== 'ENOENT' && nodeError.code !== 'ENOTDIR') {
|
|
59
|
+
return absolute;
|
|
60
|
+
}
|
|
61
|
+
const parent = path.dirname(current);
|
|
62
|
+
if (parent === current) {
|
|
63
|
+
return absolute;
|
|
64
|
+
}
|
|
65
|
+
trailingSegments.push(path.basename(current));
|
|
66
|
+
current = parent;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPathInsideDirectory(candidatePath: string, directoryPath: string): boolean {
|
|
72
|
+
return candidatePath === directoryPath || candidatePath.startsWith(`${directoryPath}${path.sep}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isWithinAllowedDirectories(
|
|
76
|
+
candidatePath: string,
|
|
77
|
+
allowedDirectories: string[]
|
|
78
|
+
): boolean {
|
|
79
|
+
const normalizedCandidate = normalizePathWithExistingAncestor(candidatePath);
|
|
80
|
+
return allowedDirectories.some((allowedDirectory) =>
|
|
81
|
+
isPathInsideDirectory(normalizedCandidate, allowedDirectory)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function assessPathAccess(
|
|
86
|
+
candidatePath: string,
|
|
87
|
+
allowedDirectories: string[],
|
|
88
|
+
errorPrefix = 'PATH_NOT_ALLOWED'
|
|
89
|
+
): PathAccessAssessment {
|
|
90
|
+
const normalizedCandidate = normalizePathWithExistingAncestor(candidatePath);
|
|
91
|
+
const allowed = allowedDirectories.some((allowedDirectory) =>
|
|
92
|
+
isPathInsideDirectory(normalizedCandidate, allowedDirectory)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
normalizedCandidate,
|
|
97
|
+
allowed,
|
|
98
|
+
message: `${errorPrefix}: ${candidatePath} is outside allowed directories: ${allowedDirectories.join(', ')}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function ensurePathWithinAllowed(
|
|
103
|
+
candidatePath: string,
|
|
104
|
+
allowedDirectories: string[],
|
|
105
|
+
errorPrefix = 'PATH_NOT_ALLOWED',
|
|
106
|
+
allowOutsideAllowedDirectories = false
|
|
107
|
+
): string {
|
|
108
|
+
const assessment = assessPathAccess(candidatePath, allowedDirectories, errorPrefix);
|
|
109
|
+
if (!assessment.allowed && !allowOutsideAllowedDirectories) {
|
|
110
|
+
throw new Error(assessment.message);
|
|
111
|
+
}
|
|
112
|
+
return assessment.normalizedCandidate;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function toPosixPath(rawPath: string): string {
|
|
116
|
+
return rawPath.split(path.sep).join('/');
|
|
117
|
+
}
|