@octocodeai/octocode-engine 16.5.0

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.
Files changed (95) hide show
  1. package/README.md +558 -0
  2. package/dist/lsp/client.d.ts +28 -0
  3. package/dist/lsp/client.js +98 -0
  4. package/dist/lsp/config.d.ts +3 -0
  5. package/dist/lsp/config.js +7 -0
  6. package/dist/lsp/evidence.d.ts +4 -0
  7. package/dist/lsp/evidence.js +28 -0
  8. package/dist/lsp/index.d.ts +7 -0
  9. package/dist/lsp/index.js +6 -0
  10. package/dist/lsp/initConstants.d.ts +4 -0
  11. package/dist/lsp/initConstants.js +27 -0
  12. package/dist/lsp/lspClientPool.d.ts +27 -0
  13. package/dist/lsp/lspClientPool.js +87 -0
  14. package/dist/lsp/lspErrorCodes.d.ts +12 -0
  15. package/dist/lsp/lspErrorCodes.js +12 -0
  16. package/dist/lsp/manager.d.ts +23 -0
  17. package/dist/lsp/manager.js +94 -0
  18. package/dist/lsp/native.d.ts +35 -0
  19. package/dist/lsp/native.js +3 -0
  20. package/dist/lsp/resolver.d.ts +26 -0
  21. package/dist/lsp/resolver.js +73 -0
  22. package/dist/lsp/schemas.d.ts +9 -0
  23. package/dist/lsp/schemas.js +30 -0
  24. package/dist/lsp/types.d.ts +85 -0
  25. package/dist/lsp/types.js +1 -0
  26. package/dist/lsp/uri.d.ts +15 -0
  27. package/dist/lsp/uri.js +24 -0
  28. package/dist/lsp/validation.d.ts +6 -0
  29. package/dist/lsp/validation.js +23 -0
  30. package/dist/lsp/workspaceRoot.d.ts +2 -0
  31. package/dist/lsp/workspaceRoot.js +7 -0
  32. package/dist/security/commandUtils.d.ts +1 -0
  33. package/dist/security/commandUtils.js +7 -0
  34. package/dist/security/commandValidator.d.ts +7 -0
  35. package/dist/security/commandValidator.js +472 -0
  36. package/dist/security/contentSanitizer.d.ts +2 -0
  37. package/dist/security/contentSanitizer.js +181 -0
  38. package/dist/security/filePatterns.d.ts +1 -0
  39. package/dist/security/filePatterns.js +209 -0
  40. package/dist/security/ignoredPathFilter.d.ts +3 -0
  41. package/dist/security/ignoredPathFilter.js +72 -0
  42. package/dist/security/index.d.ts +16 -0
  43. package/dist/security/index.js +13 -0
  44. package/dist/security/mask.d.ts +1 -0
  45. package/dist/security/mask.js +49 -0
  46. package/dist/security/maskUtils.d.ts +1 -0
  47. package/dist/security/maskUtils.js +7 -0
  48. package/dist/security/native.d.ts +9 -0
  49. package/dist/security/native.js +161 -0
  50. package/dist/security/paramExtractors.d.ts +7 -0
  51. package/dist/security/paramExtractors.js +75 -0
  52. package/dist/security/pathPatterns.d.ts +1 -0
  53. package/dist/security/pathPatterns.js +26 -0
  54. package/dist/security/pathUtils.d.ts +1 -0
  55. package/dist/security/pathUtils.js +37 -0
  56. package/dist/security/pathValidator.d.ts +22 -0
  57. package/dist/security/pathValidator.js +229 -0
  58. package/dist/security/regexes/ai-providers.d.ts +2 -0
  59. package/dist/security/regexes/ai-providers.js +177 -0
  60. package/dist/security/regexes/analytics.d.ts +2 -0
  61. package/dist/security/regexes/analytics.js +50 -0
  62. package/dist/security/regexes/auth-crypto.d.ts +6 -0
  63. package/dist/security/regexes/auth-crypto.js +255 -0
  64. package/dist/security/regexes/aws.d.ts +2 -0
  65. package/dist/security/regexes/aws.js +68 -0
  66. package/dist/security/regexes/cloudProviders.d.ts +2 -0
  67. package/dist/security/regexes/cloudProviders.js +328 -0
  68. package/dist/security/regexes/communications.d.ts +4 -0
  69. package/dist/security/regexes/communications.js +260 -0
  70. package/dist/security/regexes/databases.d.ts +2 -0
  71. package/dist/security/regexes/databases.js +135 -0
  72. package/dist/security/regexes/devTools.d.ts +2 -0
  73. package/dist/security/regexes/devTools.js +236 -0
  74. package/dist/security/regexes/index.d.ts +3 -0
  75. package/dist/security/regexes/index.js +31 -0
  76. package/dist/security/regexes/monitoring.d.ts +2 -0
  77. package/dist/security/regexes/monitoring.js +92 -0
  78. package/dist/security/regexes/payments-commerce.d.ts +3 -0
  79. package/dist/security/regexes/payments-commerce.js +197 -0
  80. package/dist/security/regexes/types.d.ts +2 -0
  81. package/dist/security/regexes/types.js +1 -0
  82. package/dist/security/regexes/vcs.d.ts +2 -0
  83. package/dist/security/regexes/vcs.js +105 -0
  84. package/dist/security/registry.d.ts +48 -0
  85. package/dist/security/registry.js +156 -0
  86. package/dist/security/securityConstants.d.ts +3 -0
  87. package/dist/security/securityConstants.js +12 -0
  88. package/dist/security/types.d.ts +35 -0
  89. package/dist/security/types.js +1 -0
  90. package/dist/security/withSecurityValidation.d.ts +21 -0
  91. package/dist/security/withSecurityValidation.js +107 -0
  92. package/index.cjs +97 -0
  93. package/index.d.ts +934 -0
  94. package/index.js +652 -0
  95. package/package.json +311 -0
@@ -0,0 +1,98 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { nativeBinding } from './native.js';
3
+ export class LSPClient {
4
+ nativeClient;
5
+ initialized = false;
6
+ constructor(config) {
7
+ this.nativeClient = new nativeBinding.NativeLspClient({
8
+ command: config.command,
9
+ args: config.args,
10
+ workspaceRoot: config.workspaceRoot,
11
+ languageId: config.languageId,
12
+ initializationOptions: config.initializationOptions,
13
+ env: config.env,
14
+ });
15
+ }
16
+ async start() {
17
+ await this.nativeClient.start();
18
+ this.initialized = true;
19
+ }
20
+ async stop() {
21
+ await this.nativeClient.stop();
22
+ this.initialized = false;
23
+ }
24
+ async waitForReady(timeoutMs = 45_000) {
25
+ await this.nativeClient.waitForReady(timeoutMs);
26
+ }
27
+ async gotoDefinition(filePath, position, content) {
28
+ return this.getDefinition(filePath, position, content);
29
+ }
30
+ async getDefinition(filePath, position, content) {
31
+ await this.openDocument(filePath, content);
32
+ return (await this.nativeClient.getDefinition(filePath, position.line, position.character));
33
+ }
34
+ async findReferences(filePath, position, includeDeclaration = true, content) {
35
+ await this.openDocument(filePath, content);
36
+ return (await this.nativeClient.getReferences(filePath, position.line, position.character, includeDeclaration));
37
+ }
38
+ async getHover(filePath, position, content) {
39
+ await this.openDocument(filePath, content);
40
+ return this.nativeClient.getHover(filePath, position.line, position.character);
41
+ }
42
+ async hover(filePath, position, content) {
43
+ return this.getHover(filePath, position, content);
44
+ }
45
+ async getTypeDefinition(filePath, position, content) {
46
+ await this.openDocument(filePath, content);
47
+ return (await this.nativeClient.getTypeDefinition(filePath, position.line, position.character));
48
+ }
49
+ async typeDefinition(filePath, position, content) {
50
+ return this.getTypeDefinition(filePath, position, content);
51
+ }
52
+ async getImplementation(filePath, position, content) {
53
+ await this.openDocument(filePath, content);
54
+ return (await this.nativeClient.getImplementation(filePath, position.line, position.character));
55
+ }
56
+ async implementation(filePath, position, content) {
57
+ return this.getImplementation(filePath, position, content);
58
+ }
59
+ async getDocumentSymbols(filePath, content) {
60
+ await this.openDocument(filePath, content);
61
+ return this.nativeClient.getDocumentSymbols(filePath);
62
+ }
63
+ async documentSymbols(filePath, content) {
64
+ return this.getDocumentSymbols(filePath, content);
65
+ }
66
+ async prepareCallHierarchy(filePath, position, content) {
67
+ await this.openDocument(filePath, content);
68
+ const result = await this.nativeClient.prepareCallHierarchy(filePath, position.line, position.character);
69
+ return Array.isArray(result) ? result : [];
70
+ }
71
+ async getIncomingCalls(item) {
72
+ const result = await this.nativeClient.incomingCalls(item);
73
+ return Array.isArray(result) ? result : [];
74
+ }
75
+ async getOutgoingCalls(item) {
76
+ const result = await this.nativeClient.outgoingCalls(item);
77
+ return Array.isArray(result) ? result : [];
78
+ }
79
+ hasCapability(_capability) {
80
+ return (this.initialized &&
81
+ (this.nativeClient.hasCapability?.(_capability) ?? true));
82
+ }
83
+ getRecentStderr() {
84
+ return this.nativeClient.getRecentStderr?.() ?? [];
85
+ }
86
+ async openDocument(filePath, content) {
87
+ await this.ensureDocumentSynced(filePath, content ?? (await fs.readFile(filePath, 'utf8')));
88
+ }
89
+ async ensureDocumentSynced(filePath, content) {
90
+ await this.nativeClient.openDocument(filePath, content ?? (await fs.readFile(filePath, 'utf8')));
91
+ }
92
+ async closeDocument(filePath) {
93
+ // Drives the native `textDocument/didClose` and clears the document's
94
+ // version state, so a later openDocument starts a fresh didOpen. A no-op
95
+ // here leaves the server holding stale in-memory documents forever.
96
+ await this.nativeClient.closeDocument?.(filePath);
97
+ }
98
+ }
@@ -0,0 +1,3 @@
1
+ import type { LanguageServerConfig } from './types.js';
2
+ export declare function detectLanguageId(filePath: string): string;
3
+ export declare function getLanguageServerForFile(filePath: string, workspaceRoot?: string): Promise<LanguageServerConfig | null>;
@@ -0,0 +1,7 @@
1
+ import { nativeBinding } from './native.js';
2
+ export function detectLanguageId(filePath) {
3
+ return nativeBinding.detectLanguageId(filePath) ?? 'plaintext';
4
+ }
5
+ export async function getLanguageServerForFile(filePath, workspaceRoot = process.cwd()) {
6
+ return (nativeBinding.getLanguageServerForFile(filePath, workspaceRoot) ?? null);
7
+ }
@@ -0,0 +1,4 @@
1
+ export declare function attachLspEvidence<T>(result: T, opts: {
2
+ kind: 'calls' | 'references';
3
+ paginationKey: 'pagination' | 'outputPagination';
4
+ }): T;
@@ -0,0 +1,28 @@
1
+ export function attachLspEvidence(result, opts) {
2
+ const status = result.status;
3
+ if (status !== undefined && status !== 'empty')
4
+ return result;
5
+ const hasResults = status === undefined;
6
+ const pagination = result[opts.paginationKey];
7
+ const paginationHasMore = pagination?.hasMore ?? false;
8
+ const reasons = [];
9
+ if (!hasResults) {
10
+ reasons.push(opts.kind === 'references'
11
+ ? 'No references were resolved for the supplied symbol and line hint.'
12
+ : 'No calls were resolved for the supplied symbol and line hint.');
13
+ }
14
+ if (paginationHasMore) {
15
+ reasons.push(opts.paginationKey === 'pagination'
16
+ ? 'LSP result pagination has more results.'
17
+ : 'LSP output pagination has more data.');
18
+ }
19
+ const evidence = {
20
+ kind: opts.kind,
21
+ answerReady: hasResults,
22
+ complete: hasResults && !paginationHasMore,
23
+ confidence: 'high',
24
+ ...(reasons.length > 0 ? { reason: reasons.join(' ') } : {}),
25
+ };
26
+ result.evidence = evidence;
27
+ return result;
28
+ }
@@ -0,0 +1,7 @@
1
+ export { LSPClient } from './client.js';
2
+ export { detectLanguageId, getLanguageServerForFile } from './config.js';
3
+ export { acquirePooledClient, getLspStatus, isLanguageServerAvailable, LSP_UNAVAILABLE_HINT, pooledClientCount, releaseAllPooledClients, releasePooledClientForFile, type LspStatusInput, type LspStatusResult, } from './manager.js';
4
+ export { SymbolResolver } from './resolver.js';
5
+ export { safeReadFile, validateLSPServerPath } from './validation.js';
6
+ export { resolveWorkspaceRootForFile } from './workspaceRoot.js';
7
+ export type { CallHierarchyItem, CodeSnippet, ExactPosition, FuzzyPosition, IncomingCall, InitializationOptions, LanguageServerCommand, LanguageServerConfig, LSPPaginationInfo, LSPRange, OutgoingCall, ReferenceLocation, ReferencesByFile, SymbolKind, UserLanguageServerConfig, } from './types.js';
@@ -0,0 +1,6 @@
1
+ export { LSPClient } from './client.js';
2
+ export { detectLanguageId, getLanguageServerForFile } from './config.js';
3
+ export { acquirePooledClient, getLspStatus, isLanguageServerAvailable, LSP_UNAVAILABLE_HINT, pooledClientCount, releaseAllPooledClients, releasePooledClientForFile, } from './manager.js';
4
+ export { SymbolResolver } from './resolver.js';
5
+ export { safeReadFile, validateLSPServerPath } from './validation.js';
6
+ export { resolveWorkspaceRootForFile } from './workspaceRoot.js';
@@ -0,0 +1,4 @@
1
+ export declare const CLIENT_NAME = "octocode-engine";
2
+ export declare const CLIENT_VERSION: string;
3
+ export declare const TSSERVER_LANGUAGE_IDS: Set<string>;
4
+ export declare const TSSERVER_DEFAULT_OPTIONS: Record<string, unknown>;
@@ -0,0 +1,27 @@
1
+ export const CLIENT_NAME = 'octocode-engine';
2
+ export const CLIENT_VERSION = resolveClientVersion();
3
+ function resolveClientVersion() {
4
+ try {
5
+ const pkg = require('../../package.json');
6
+ return pkg.version ?? '0.0.0-dev';
7
+ }
8
+ catch {
9
+ return '0.0.0-dev';
10
+ }
11
+ }
12
+ export const TSSERVER_LANGUAGE_IDS = new Set([
13
+ 'typescript',
14
+ 'typescriptreact',
15
+ 'javascript',
16
+ 'javascriptreact',
17
+ ]);
18
+ export const TSSERVER_DEFAULT_OPTIONS = {
19
+ tsserver: {
20
+ maxTsServerMemory: 2048,
21
+ useSyntaxServer: 'auto',
22
+ disableAutomaticTypeAcquisition: true,
23
+ },
24
+ preferences: {
25
+ includePackageJsonAutoImports: 'off',
26
+ },
27
+ };
@@ -0,0 +1,27 @@
1
+ export interface PoolKey {
2
+ workspaceRoot: string;
3
+ filePath: string;
4
+ languageId: string;
5
+ serverId?: string;
6
+ }
7
+ interface PooledClient {
8
+ stop(): Promise<void>;
9
+ }
10
+ interface LspClientPoolOptions<T extends PooledClient> {
11
+ idleTimeoutMs: number;
12
+ factory: (key: PoolKey) => Promise<T | null>;
13
+ }
14
+ export declare class LspClientPool<T extends PooledClient> {
15
+ private readonly options;
16
+ private readonly entries;
17
+ private readonly inflight;
18
+ constructor(options: LspClientPoolOptions<T>);
19
+ acquire(key: PoolKey): Promise<T | null>;
20
+ clear(key: PoolKey): Promise<void>;
21
+ clearAll(): Promise<void>;
22
+ size(): number;
23
+ keys(): PoolKey[];
24
+ private resetIdleTimer;
25
+ private startIdleTimer;
26
+ }
27
+ export {};
@@ -0,0 +1,87 @@
1
+ export class LspClientPool {
2
+ options;
3
+ entries = new Map();
4
+ inflight = new Map();
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ async acquire(key) {
9
+ const k = serializeKey(key);
10
+ const cached = this.entries.get(k);
11
+ if (cached) {
12
+ this.resetIdleTimer(k);
13
+ return cached.client;
14
+ }
15
+ const inflight = this.inflight.get(k);
16
+ if (inflight)
17
+ return inflight;
18
+ const promise = (async () => {
19
+ try {
20
+ const client = await this.options.factory(key);
21
+ if (!client)
22
+ return null;
23
+ const timer = this.startIdleTimer(k);
24
+ this.entries.set(k, { client, timer, key });
25
+ return client;
26
+ }
27
+ finally {
28
+ this.inflight.delete(k);
29
+ }
30
+ })();
31
+ this.inflight.set(k, promise);
32
+ return promise;
33
+ }
34
+ async clear(key) {
35
+ const k = serializeKey(key);
36
+ const entry = this.entries.get(k);
37
+ if (!entry)
38
+ return;
39
+ clearTimeout(entry.timer);
40
+ this.entries.delete(k);
41
+ await safeStop(entry.client);
42
+ }
43
+ async clearAll() {
44
+ const all = [...this.entries.values()];
45
+ for (const entry of all)
46
+ clearTimeout(entry.timer);
47
+ this.entries.clear();
48
+ await Promise.all(all.map(e => safeStop(e.client)));
49
+ }
50
+ size() {
51
+ return this.entries.size;
52
+ }
53
+ keys() {
54
+ return [...this.entries.values()].map(entry => entry.key);
55
+ }
56
+ resetIdleTimer(k) {
57
+ const entry = this.entries.get(k);
58
+ if (!entry)
59
+ return;
60
+ clearTimeout(entry.timer);
61
+ entry.timer = this.startIdleTimer(k);
62
+ }
63
+ startIdleTimer(k) {
64
+ const timer = setTimeout(() => {
65
+ const entry = this.entries.get(k);
66
+ if (!entry)
67
+ return;
68
+ this.entries.delete(k);
69
+ void safeStop(entry.client);
70
+ }, this.options.idleTimeoutMs);
71
+ if (typeof timer === 'object' && 'unref' in timer) {
72
+ timer.unref();
73
+ }
74
+ return timer;
75
+ }
76
+ }
77
+ function serializeKey(key) {
78
+ return `${key.serverId ?? key.languageId}\u0000${key.workspaceRoot}`;
79
+ }
80
+ async function safeStop(client) {
81
+ try {
82
+ await client.stop();
83
+ }
84
+ catch {
85
+ void 0;
86
+ }
87
+ }
@@ -0,0 +1,12 @@
1
+ export declare const LSP_ERROR_CODES: {
2
+ readonly LSP_NOT_INSTALLED: "LSP_NOT_INSTALLED";
3
+ readonly LSP_TIMEOUT: "LSP_TIMEOUT";
4
+ readonly LSP_INITIALIZE_FAILED: "LSP_INITIALIZE_FAILED";
5
+ readonly LSP_REQUEST_FAILED: "LSP_REQUEST_FAILED";
6
+ readonly LSP_EMPTY: "LSP_EMPTY";
7
+ readonly LSP_CAPABILITY_UNSUPPORTED: "LSP_CAPABILITY_UNSUPPORTED";
8
+ readonly LSP_FALLBACK_TO_TEXT: "LSP_FALLBACK_TO_TEXT";
9
+ readonly SYMBOL_NOT_FOUND: "SYMBOL_NOT_FOUND";
10
+ readonly SYMBOL_AMBIGUOUS: "SYMBOL_AMBIGUOUS";
11
+ readonly UNSAFE_URI: "UNSAFE_URI";
12
+ };
@@ -0,0 +1,12 @@
1
+ export const LSP_ERROR_CODES = {
2
+ LSP_NOT_INSTALLED: 'LSP_NOT_INSTALLED',
3
+ LSP_TIMEOUT: 'LSP_TIMEOUT',
4
+ LSP_INITIALIZE_FAILED: 'LSP_INITIALIZE_FAILED',
5
+ LSP_REQUEST_FAILED: 'LSP_REQUEST_FAILED',
6
+ LSP_EMPTY: 'LSP_EMPTY',
7
+ LSP_CAPABILITY_UNSUPPORTED: 'LSP_CAPABILITY_UNSUPPORTED',
8
+ LSP_FALLBACK_TO_TEXT: 'LSP_FALLBACK_TO_TEXT',
9
+ SYMBOL_NOT_FOUND: 'SYMBOL_NOT_FOUND',
10
+ SYMBOL_AMBIGUOUS: 'SYMBOL_AMBIGUOUS',
11
+ UNSAFE_URI: 'UNSAFE_URI',
12
+ };
@@ -0,0 +1,23 @@
1
+ import { LSPClient } from './client.js';
2
+ import { type PoolKey } from './lspClientPool.js';
3
+ export declare function isLanguageServerAvailable(filePath: string, workspaceRoot?: string): Promise<boolean>;
4
+ export declare const LSP_UNAVAILABLE_HINT = "No language server is available for this file, so no semantic results were returned. Install a matching language server or set the relevant OCTOCODE_*_SERVER_PATH environment variable. For a text-based search meanwhile, use localSearchCode.";
5
+ export declare function acquirePooledClient(workspaceRoot: string, filePath: string): Promise<LSPClient | null>;
6
+ export declare function releaseAllPooledClients(): Promise<void>;
7
+ export declare function releasePooledClientForFile(workspaceRoot: string, filePath: string): Promise<boolean>;
8
+ export type LspStatusInput = {
9
+ filePath?: string;
10
+ workspaceRoot?: string;
11
+ };
12
+ export type LspStatusResult = {
13
+ enabled: true;
14
+ pooledClientCount: number;
15
+ pooledClients: PoolKey[];
16
+ filePath?: string;
17
+ workspaceRoot?: string;
18
+ languageId?: string;
19
+ serverAvailable?: boolean;
20
+ hints: string[];
21
+ };
22
+ export declare function getLspStatus(input?: LspStatusInput): Promise<LspStatusResult>;
23
+ export declare function pooledClientCount(): number;
@@ -0,0 +1,94 @@
1
+ import { LSPClient } from './client.js';
2
+ import { getLanguageServerForFile } from './config.js';
3
+ import { LspClientPool } from './lspClientPool.js';
4
+ import { nativeBinding } from './native.js';
5
+ import { resolveWorkspaceRootForFile } from './workspaceRoot.js';
6
+ export async function isLanguageServerAvailable(filePath, workspaceRoot) {
7
+ const serverConfig = await getLanguageServerForFile(filePath, workspaceRoot ?? process.cwd());
8
+ if (!serverConfig)
9
+ return false;
10
+ return nativeBinding.isCommandAvailable(serverConfig.command);
11
+ }
12
+ export const LSP_UNAVAILABLE_HINT = 'No language server is available for this file, so no semantic results were returned. Install a matching language server or set the relevant OCTOCODE_*_SERVER_PATH environment variable. For a text-based search meanwhile, use localSearchCode.';
13
+ const POOL_IDLE_TIMEOUT_MS = parseInt(process.env.OCTOCODE_LSP_POOL_IDLE_MS || '60000', 10);
14
+ const sharedPool = new LspClientPool({
15
+ idleTimeoutMs: POOL_IDLE_TIMEOUT_MS,
16
+ factory: async (key) => {
17
+ const serverConfig = await getLanguageServerForFile(synthesizeFilePathForKey(key), key.workspaceRoot);
18
+ if (!serverConfig)
19
+ return null;
20
+ const client = new LSPClient(serverConfig);
21
+ try {
22
+ await client.start();
23
+ return client;
24
+ }
25
+ catch {
26
+ await client.stop().catch(() => undefined);
27
+ return null;
28
+ }
29
+ },
30
+ });
31
+ export async function acquirePooledClient(workspaceRoot, filePath) {
32
+ const key = await poolKeyForFile(workspaceRoot, filePath);
33
+ if (!key)
34
+ return null;
35
+ return sharedPool.acquire(key);
36
+ }
37
+ export async function releaseAllPooledClients() {
38
+ await sharedPool.clearAll();
39
+ }
40
+ export async function releasePooledClientForFile(workspaceRoot, filePath) {
41
+ const key = await poolKeyForFile(workspaceRoot, filePath);
42
+ if (!key)
43
+ return false;
44
+ await sharedPool.clear(key);
45
+ return true;
46
+ }
47
+ export async function getLspStatus(input = {}) {
48
+ const base = {
49
+ enabled: true,
50
+ pooledClientCount: sharedPool.size(),
51
+ pooledClients: sharedPool.keys(),
52
+ };
53
+ if (!input.filePath) {
54
+ return {
55
+ ...base,
56
+ hints: [
57
+ 'Provide filePath to check language server availability for a specific file.',
58
+ ],
59
+ };
60
+ }
61
+ const workspaceRoot = input.workspaceRoot ?? (await resolveWorkspaceRootForFile(input.filePath));
62
+ const serverConfig = await getLanguageServerForFile(input.filePath, workspaceRoot);
63
+ const languageId = serverConfig?.languageId;
64
+ const serverAvailable = serverConfig
65
+ ? nativeBinding.isCommandAvailable(serverConfig.command)
66
+ : false;
67
+ return {
68
+ ...base,
69
+ filePath: input.filePath,
70
+ workspaceRoot,
71
+ languageId,
72
+ serverAvailable,
73
+ hints: serverAvailable
74
+ ? ['Language server appears available for this file.']
75
+ : [LSP_UNAVAILABLE_HINT],
76
+ };
77
+ }
78
+ export function pooledClientCount() {
79
+ return sharedPool.size();
80
+ }
81
+ function synthesizeFilePathForKey(key) {
82
+ return key.filePath;
83
+ }
84
+ async function poolKeyForFile(workspaceRoot, filePath) {
85
+ const serverConfig = await getLanguageServerForFile(filePath, workspaceRoot);
86
+ if (!serverConfig)
87
+ return null;
88
+ return {
89
+ workspaceRoot,
90
+ filePath,
91
+ languageId: serverConfig.languageId ?? 'unknown',
92
+ serverId: `${serverConfig.command} ${(serverConfig.args ?? []).join(' ')}`.trim(),
93
+ };
94
+ }
@@ -0,0 +1,35 @@
1
+ export type NativeLspClientBinding = {
2
+ start(): Promise<void>;
3
+ stop(): Promise<void>;
4
+ waitForReady(timeoutMs?: number): Promise<void>;
5
+ hasCapability?(capability: string): boolean;
6
+ getRecentStderr?(): string[];
7
+ openDocument(filePath: string, content: string): Promise<void>;
8
+ closeDocument?(filePath: string): Promise<void>;
9
+ getDefinition(filePath: string, line: number, character: number): Promise<unknown[]>;
10
+ getReferences(filePath: string, line: number, character: number, includeDeclaration?: boolean): Promise<unknown[]>;
11
+ getHover(filePath: string, line: number, character: number): Promise<unknown>;
12
+ getTypeDefinition(filePath: string, line: number, character: number): Promise<unknown[]>;
13
+ getImplementation(filePath: string, line: number, character: number): Promise<unknown[]>;
14
+ getDocumentSymbols(filePath: string): Promise<unknown>;
15
+ prepareCallHierarchy(filePath: string, line: number, character: number): Promise<unknown>;
16
+ incomingCalls(item: unknown): Promise<unknown>;
17
+ outgoingCalls(item: unknown): Promise<unknown>;
18
+ };
19
+ type NativeBinding = {
20
+ NativeLspClient: new (config: unknown) => NativeLspClientBinding;
21
+ resolvePosition(filePath: string, fuzzy: unknown): unknown;
22
+ resolvePositionFromContent(content: string, fuzzy: unknown): unknown;
23
+ toUri(path: string): string;
24
+ fromUri(uri: string): string;
25
+ resolveWorkspaceRootForFile(filePath: string): string;
26
+ detectLanguageId(filePath: string): string | undefined;
27
+ getLanguageServerForFile(filePath: string, workspaceRoot: string): unknown | undefined;
28
+ isCommandAvailable(command: string): boolean;
29
+ safeReadFile(filePath: string): string;
30
+ validateLspServerPath(command: string): string;
31
+ convertSymbolKind(kind?: number): string;
32
+ toLspSymbolKind(kind: string): number;
33
+ };
34
+ export declare const nativeBinding: NativeBinding;
35
+ export {};
@@ -0,0 +1,3 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ export const nativeBinding = require('../../index.cjs');
@@ -0,0 +1,26 @@
1
+ import type { ExactPosition, FuzzyPosition } from './types.js';
2
+ export declare class SymbolResolutionError extends Error {
3
+ readonly symbolName: string;
4
+ readonly lineHint: number;
5
+ readonly reason: string;
6
+ readonly searchRadius: number;
7
+ constructor(symbolName: string, lineHint: number, reason: string, searchRadius?: number);
8
+ }
9
+ interface SymbolResolverConfig {
10
+ lineSearchRadius?: number;
11
+ }
12
+ interface ResolvedSymbol {
13
+ position: ExactPosition;
14
+ foundAtLine: number;
15
+ lineOffset: number;
16
+ lineContent: string;
17
+ }
18
+ export declare function resolveSymbolPosition(filePath: string, symbolName: string, lineHint?: number, orderHint?: number): Promise<ResolvedSymbol>;
19
+ export declare function resolveSymbolPosition(content: string, fuzzy: FuzzyPosition): ResolvedSymbol;
20
+ export declare class SymbolResolver {
21
+ readonly lineSearchRadius: number;
22
+ constructor(config?: SymbolResolverConfig);
23
+ resolvePosition(filePath: string, fuzzy: FuzzyPosition): Promise<ResolvedSymbol>;
24
+ resolvePositionFromContent(content: string, fuzzy: FuzzyPosition): ResolvedSymbol;
25
+ }
26
+ export {};
@@ -0,0 +1,73 @@
1
+ import { nativeBinding } from './native.js';
2
+ export class SymbolResolutionError extends Error {
3
+ symbolName;
4
+ lineHint;
5
+ reason;
6
+ searchRadius;
7
+ constructor(symbolName, lineHint, reason, searchRadius = 5) {
8
+ super(`Could not find symbol '${symbolName}' at or near line ${lineHint}. ${reason}`);
9
+ this.symbolName = symbolName;
10
+ this.lineHint = lineHint;
11
+ this.reason = reason;
12
+ this.searchRadius = searchRadius;
13
+ this.name = 'SymbolResolutionError';
14
+ }
15
+ }
16
+ function normalizeResolvedSymbol(value) {
17
+ const record = value;
18
+ return {
19
+ position: record.position,
20
+ foundAtLine: record.foundAtLine ?? record.found_at_line ?? 0,
21
+ lineOffset: record.lineOffset ?? record.line_offset ?? 0,
22
+ lineContent: record.lineContent ?? record.line_content ?? '',
23
+ };
24
+ }
25
+ function toSymbolResolutionError(error, fuzzy, searchRadius = 5) {
26
+ if (error instanceof SymbolResolutionError)
27
+ return error;
28
+ const reason = error instanceof Error ? error.message : String(error);
29
+ return new SymbolResolutionError(fuzzy.symbolName, fuzzy.lineHint ?? 0, reason, searchRadius);
30
+ }
31
+ export function resolveSymbolPosition(fileOrContent, fuzzyOrSymbolName, lineHint, orderHint) {
32
+ if (typeof fuzzyOrSymbolName === 'string') {
33
+ const fuzzy = {
34
+ symbolName: fuzzyOrSymbolName,
35
+ lineHint,
36
+ orderHint,
37
+ };
38
+ try {
39
+ return Promise.resolve(normalizeResolvedSymbol(nativeBinding.resolvePosition(fileOrContent, fuzzy)));
40
+ }
41
+ catch (error) {
42
+ return Promise.reject(toSymbolResolutionError(error, fuzzy));
43
+ }
44
+ }
45
+ try {
46
+ return normalizeResolvedSymbol(nativeBinding.resolvePositionFromContent(fileOrContent, fuzzyOrSymbolName));
47
+ }
48
+ catch (error) {
49
+ throw toSymbolResolutionError(error, fuzzyOrSymbolName);
50
+ }
51
+ }
52
+ export class SymbolResolver {
53
+ lineSearchRadius;
54
+ constructor(config) {
55
+ this.lineSearchRadius = config?.lineSearchRadius ?? 5;
56
+ }
57
+ async resolvePosition(filePath, fuzzy) {
58
+ try {
59
+ return normalizeResolvedSymbol(nativeBinding.resolvePosition(filePath, fuzzy));
60
+ }
61
+ catch (error) {
62
+ throw toSymbolResolutionError(error, fuzzy, this.lineSearchRadius);
63
+ }
64
+ }
65
+ resolvePositionFromContent(content, fuzzy) {
66
+ try {
67
+ return normalizeResolvedSymbol(nativeBinding.resolvePositionFromContent(content, fuzzy));
68
+ }
69
+ catch (error) {
70
+ throw toSymbolResolutionError(error, fuzzy, this.lineSearchRadius);
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ export declare const LSPConfigFileSchema: z.ZodObject<{
3
+ languageServers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
4
+ command: z.ZodString;
5
+ args: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ languageId: z.ZodString;
7
+ initializationOptions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
+ }, z.core.$loose>>>;
9
+ }, z.core.$loose>;
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ const MAX_COMMAND_LENGTH = 512;
3
+ const MAX_ARG_LENGTH = 1024;
4
+ const MAX_ARGS_COUNT = 64;
5
+ const EXTENSION_KEY_PATTERN = /^\.[a-z0-9._+-]+$/i;
6
+ const UserLanguageServerConfigSchema = z.looseObject({
7
+ command: z
8
+ .string()
9
+ .min(1)
10
+ .max(MAX_COMMAND_LENGTH)
11
+ .refine(value => !/[\0\r\n]/.test(value), {
12
+ message: 'command contains invalid control characters',
13
+ }),
14
+ args: z
15
+ .array(z
16
+ .string()
17
+ .max(MAX_ARG_LENGTH)
18
+ .refine(value => !/[\0\r\n]/.test(value), {
19
+ message: 'argument contains invalid control characters',
20
+ }))
21
+ .max(MAX_ARGS_COUNT)
22
+ .optional(),
23
+ languageId: z.string().min(1).max(64),
24
+ initializationOptions: z.record(z.string(), z.unknown()).optional(),
25
+ });
26
+ export const LSPConfigFileSchema = z.looseObject({
27
+ languageServers: z
28
+ .record(z.string().regex(EXTENSION_KEY_PATTERN), UserLanguageServerConfigSchema)
29
+ .optional(),
30
+ });