CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
lsp.ts1225 linesDownload Raw Back to tools
1/**2 * @license3 * Copyright 2025 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import path from 'node:path';8import { fileURLToPath, pathToFileURL } from 'node:url';9import type { ToolInvocation, ToolResult } from './tools.js';10import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';11import { ToolDisplayNames, ToolNames } from './tool-names.js';12import { unescapePath } from '../utils/paths.js';13import type { Config } from '../config/config.js';14import type {15  LspCallHierarchyIncomingCall,16  LspCallHierarchyItem,17  LspCallHierarchyOutgoingCall,18  LspClient,19  LspCodeAction,20  LspCodeActionContext,21  LspCodeActionKind,22  LspDefinition,23  LspDiagnostic,24  LspFileDiagnostics,25  LspLocation,26  LspRange,27  LspReference,28  LspSymbolInformation,29} from '../lsp/types.js';30 31/**32 * Supported LSP operations.33 */34export type LspOperation =35  | 'goToDefinition'36  | 'findReferences'37  | 'hover'38  | 'documentSymbol'39  | 'workspaceSymbol'40  | 'goToImplementation'41  | 'prepareCallHierarchy'42  | 'incomingCalls'43  | 'outgoingCalls'44  | 'diagnostics'45  | 'workspaceDiagnostics'46  | 'codeActions';47 48/**49 * Parameters for the unified LSP tool.50 */51export interface LspToolParams {52  /** Operation to perform. */53  operation: LspOperation;54  /** File path (absolute or workspace-relative). */55  filePath?: string;56  /** 1-based line number when targeting a specific file location. */57  line?: number;58  /** 1-based character/column number when targeting a specific file location. */59  character?: number;60  /** End line for range-based operations (1-based). */61  endLine?: number;62  /** End character for range-based operations (1-based). */63  endCharacter?: number;64  /** Whether to include the declaration in reference results. */65  includeDeclaration?: boolean;66  /** Query string for workspace symbol search. */67  query?: string;68  /** Call hierarchy item from a previous call hierarchy operation. */69  callHierarchyItem?: LspCallHierarchyItem;70  /** Optional server name override. */71  serverName?: string;72  /** Optional maximum number of results. */73  limit?: number;74  /** Diagnostics for code action context. */75  diagnostics?: LspDiagnostic[];76  /** Code action kinds to filter by. */77  codeActionKinds?: LspCodeActionKind[];78}79 80type ResolvedTarget =81  | {82      location: LspLocation;83      description: string;84    }85  | { error: string };86 87/** Operations that require filePath and line. */88const LOCATION_REQUIRED_OPERATIONS = new Set<LspOperation>([89  'goToDefinition',90  'findReferences',91  'hover',92  'goToImplementation',93  'prepareCallHierarchy',94]);95 96/** Operations that only require filePath. */97const FILE_REQUIRED_OPERATIONS = new Set<LspOperation>([98  'documentSymbol',99  'diagnostics',100]);101 102/** Operations that require query. */103const QUERY_REQUIRED_OPERATIONS = new Set<LspOperation>(['workspaceSymbol']);104 105/** Operations that require callHierarchyItem. */106const ITEM_REQUIRED_OPERATIONS = new Set<LspOperation>([107  'incomingCalls',108  'outgoingCalls',109]);110 111/** Operations that require filePath and range for code actions. */112const RANGE_REQUIRED_OPERATIONS = new Set<LspOperation>(['codeActions']);113 114class LspToolInvocation extends BaseToolInvocation<LspToolParams, ToolResult> {115  constructor(116    private readonly config: Config,117    params: LspToolParams,118  ) {119    super(params);120  }121 122  getDescription(): string {123    const operationLabel = this.getOperationLabel();124    if (this.params.operation === 'workspaceSymbol') {125      return `LSP ${operationLabel} for "${this.params.query ?? ''}"`;126    }127    if (this.params.operation === 'documentSymbol') {128      return this.params.filePath129        ? `LSP ${operationLabel} for ${this.params.filePath}`130        : `LSP ${operationLabel}`;131    }132    if (133      this.params.operation === 'incomingCalls' ||134      this.params.operation === 'outgoingCalls'135    ) {136      return `LSP ${operationLabel} for ${this.describeCallHierarchyItemShort()}`;137    }138    if (this.params.filePath && this.params.line !== undefined) {139      return `LSP ${operationLabel} at ${this.params.filePath}:${this.params.line}:${this.params.character ?? 1}`;140    }141    if (this.params.filePath) {142      return `LSP ${operationLabel} for ${this.params.filePath}`;143    }144    return `LSP ${operationLabel}`;145  }146 147  async execute(_signal: AbortSignal): Promise<ToolResult> {148    const client = this.config.getLspClient();149    if (!client || !this.config.isLspEnabled()) {150      const message = `LSP ${this.getOperationLabel()} is unavailable (LSP disabled or not initialized).`;151      return { llmContent: message, returnDisplay: message };152    }153 154    switch (this.params.operation) {155      case 'goToDefinition':156        return this.executeDefinitions(client);157      case 'findReferences':158        return this.executeReferences(client);159      case 'hover':160        return this.executeHover(client);161      case 'documentSymbol':162        return this.executeDocumentSymbols(client);163      case 'workspaceSymbol':164        return this.executeWorkspaceSymbols(client);165      case 'goToImplementation':166        return this.executeImplementations(client);167      case 'prepareCallHierarchy':168        return this.executePrepareCallHierarchy(client);169      case 'incomingCalls':170        return this.executeIncomingCalls(client);171      case 'outgoingCalls':172        return this.executeOutgoingCalls(client);173      case 'diagnostics':174        return this.executeDiagnostics(client);175      case 'workspaceDiagnostics':176        return this.executeWorkspaceDiagnostics(client);177      case 'codeActions':178        return this.executeCodeActions(client);179      default: {180        const message = `Unsupported LSP operation: ${this.params.operation}`;181        return { llmContent: message, returnDisplay: message };182      }183    }184  }185 186  private async executeDefinitions(client: LspClient): Promise<ToolResult> {187    const target = this.resolveLocationTarget();188    if ('error' in target) {189      return { llmContent: target.error, returnDisplay: target.error };190    }191 192    const limit = this.params.limit ?? 20;193    let definitions: LspDefinition[] = [];194    try {195      definitions = await client.definitions(196        target.location,197        this.params.serverName,198        limit,199      );200    } catch (error) {201      const message = `LSP go-to-definition failed: ${202        (error as Error)?.message || String(error)203      }`;204      return { llmContent: message, returnDisplay: message };205    }206 207    if (!definitions.length) {208      const message = `No definitions found for ${target.description}.`;209      return { llmContent: message, returnDisplay: message };210    }211 212    const workspaceRoot = this.config.getProjectRoot();213    const lines = definitions214      .slice(0, limit)215      .map(216        (definition, index) =>217          `${index + 1}. ${this.formatLocationWithServer(definition, workspaceRoot)}`,218      );219 220    const heading = `Definitions for ${target.description}:`;221    return {222      llmContent: [heading, ...lines].join('\n'),223      returnDisplay: lines.join('\n'),224    };225  }226 227  private async executeImplementations(client: LspClient): Promise<ToolResult> {228    const target = this.resolveLocationTarget();229    if ('error' in target) {230      return { llmContent: target.error, returnDisplay: target.error };231    }232 233    const limit = this.params.limit ?? 20;234    let implementations: LspDefinition[] = [];235    try {236      implementations = await client.implementations(237        target.location,238        this.params.serverName,239        limit,240      );241    } catch (error) {242      const message = `LSP go-to-implementation failed: ${243        (error as Error)?.message || String(error)244      }`;245      return { llmContent: message, returnDisplay: message };246    }247 248    if (!implementations.length) {249      const message = `No implementations found for ${target.description}.`;250      return { llmContent: message, returnDisplay: message };251    }252 253    const workspaceRoot = this.config.getProjectRoot();254    const lines = implementations255      .slice(0, limit)256      .map(257        (implementation, index) =>258          `${index + 1}. ${this.formatLocationWithServer(implementation, workspaceRoot)}`,259      );260 261    const heading = `Implementations for ${target.description}:`;262    return {263      llmContent: [heading, ...lines].join('\n'),264      returnDisplay: lines.join('\n'),265    };266  }267 268  private async executeReferences(client: LspClient): Promise<ToolResult> {269    const target = this.resolveLocationTarget();270    if ('error' in target) {271      return { llmContent: target.error, returnDisplay: target.error };272    }273 274    const limit = this.params.limit ?? 50;275    let references: LspReference[] = [];276    try {277      references = await client.references(278        target.location,279        this.params.serverName,280        this.params.includeDeclaration ?? false,281        limit,282      );283    } catch (error) {284      const message = `LSP find-references failed: ${285        (error as Error)?.message || String(error)286      }`;287      return { llmContent: message, returnDisplay: message };288    }289 290    if (!references.length) {291      const message = `No references found for ${target.description}.`;292      return { llmContent: message, returnDisplay: message };293    }294 295    const workspaceRoot = this.config.getProjectRoot();296    const lines = references297      .slice(0, limit)298      .map(299        (reference, index) =>300          `${index + 1}. ${this.formatLocationWithServer(reference, workspaceRoot)}`,301      );302 303    const heading = `References for ${target.description}:`;304    return {305      llmContent: [heading, ...lines].join('\n'),306      returnDisplay: lines.join('\n'),307    };308  }309 310  private async executeHover(client: LspClient): Promise<ToolResult> {311    const target = this.resolveLocationTarget();312    if ('error' in target) {313      return { llmContent: target.error, returnDisplay: target.error };314    }315 316    let hoverText = '';317    try {318      const result = await client.hover(319        target.location,320        this.params.serverName,321      );322      if (result) {323        hoverText = result.contents ?? '';324      }325    } catch (error) {326      const message = `LSP hover failed: ${327        (error as Error)?.message || String(error)328      }`;329      return { llmContent: message, returnDisplay: message };330    }331 332    if (!hoverText || hoverText.trim().length === 0) {333      const message = `No hover information found for ${target.description}.`;334      return { llmContent: message, returnDisplay: message };335    }336 337    const heading = `Hover for ${target.description}:`;338    const content = hoverText.trim();339    return {340      llmContent: `${heading}\n${content}`,341      returnDisplay: content,342    };343  }344 345  private async executeDocumentSymbols(client: LspClient): Promise<ToolResult> {346    const workspaceRoot = this.config.getProjectRoot();347    const filePath = this.params.filePath ?? '';348    const uri = this.resolveUri(filePath, workspaceRoot);349    if (!uri) {350      const message = 'A valid filePath is required for document symbols.';351      return { llmContent: message, returnDisplay: message };352    }353 354    const limit = this.params.limit ?? 50;355    let symbols: LspSymbolInformation[] = [];356    try {357      symbols = await client.documentSymbols(358        uri,359        this.params.serverName,360        limit,361      );362    } catch (error) {363      const message = `LSP document symbols failed: ${364        (error as Error)?.message || String(error)365      }`;366      return { llmContent: message, returnDisplay: message };367    }368 369    if (!symbols.length) {370      const fileLabel = this.formatUriForDisplay(uri, workspaceRoot);371      const message = `No document symbols found for ${fileLabel}.`;372      return { llmContent: message, returnDisplay: message };373    }374 375    const lines = symbols.slice(0, limit).map((symbol, index) => {376      const location = this.formatLocationWithoutServer(377        symbol.location,378        workspaceRoot,379      );380      const serverSuffix = symbol.serverName ? ` [${symbol.serverName}]` : '';381      const kind = symbol.kind ? ` (${symbol.kind})` : '';382      const container = symbol.containerName383        ? ` in ${symbol.containerName}`384        : '';385      return `${index + 1}. ${symbol.name}${kind}${container} - ${location}${serverSuffix}`;386    });387 388    const fileLabel = this.formatUriForDisplay(uri, workspaceRoot);389    const heading = `Document symbols for ${fileLabel}:`;390    return {391      llmContent: [heading, ...lines].join('\n'),392      returnDisplay: lines.join('\n'),393    };394  }395 396  private async executeWorkspaceSymbols(397    client: LspClient,398  ): Promise<ToolResult> {399    const limit = this.params.limit ?? 20;400    const query = this.params.query ?? '';401    let symbols: LspSymbolInformation[] = [];402    try {403      symbols = await client.workspaceSymbols(query, limit);404    } catch (error) {405      const message = `LSP workspace symbol search failed: ${406        (error as Error)?.message || String(error)407      }`;408      return { llmContent: message, returnDisplay: message };409    }410 411    if (!symbols.length) {412      const message = `No symbols found for query "${query}".`;413      return { llmContent: message, returnDisplay: message };414    }415 416    const workspaceRoot = this.config.getProjectRoot();417    const lines = symbols.slice(0, limit).map((symbol, index) => {418      const location = this.formatLocationWithoutServer(419        symbol.location,420        workspaceRoot,421      );422      const serverSuffix = symbol.serverName ? ` [${symbol.serverName}]` : '';423      const kind = symbol.kind ? ` (${symbol.kind})` : '';424      const container = symbol.containerName425        ? ` in ${symbol.containerName}`426        : '';427      return `${index + 1}. ${symbol.name}${kind}${container} - ${location}${serverSuffix}`;428    });429 430    const heading = `Found ${Math.min(symbols.length, limit)} of ${431      symbols.length432    } symbols for query "${query}":`;433 434    // Also fetch references for the top match to provide additional context.435    let referenceSection = '';436    const topSymbol = symbols[0];437    if (topSymbol) {438      try {439        const referenceLimit = Math.min(20, Math.max(limit, 5));440        const references = await client.references(441          topSymbol.location,442          topSymbol.serverName,443          false,444          referenceLimit,445        );446        if (references.length > 0) {447          const refLines = references.map((ref, index) => {448            const location = this.formatLocationWithoutServer(449              ref,450              workspaceRoot,451            );452            const serverSuffix = ref.serverName ? ` [${ref.serverName}]` : '';453            return `${index + 1}. ${location}${serverSuffix}`;454          });455          referenceSection = [456            '',457            `References for top match (${topSymbol.name}):`,458            ...refLines,459          ].join('\n');460        }461      } catch (error) {462        referenceSection = `\nReferences lookup failed: ${463          (error as Error)?.message || String(error)464        }`;465      }466    }467 468    const llmParts = referenceSection469      ? [heading, ...lines, referenceSection]470      : [heading, ...lines];471    const displayParts = referenceSection472      ? [...lines, referenceSection]473      : [...lines];474 475    return {476      llmContent: llmParts.join('\n'),477      returnDisplay: displayParts.join('\n'),478    };479  }480 481  private async executePrepareCallHierarchy(482    client: LspClient,483  ): Promise<ToolResult> {484    const target = this.resolveLocationTarget();485    if ('error' in target) {486      return { llmContent: target.error, returnDisplay: target.error };487    }488 489    const limit = this.params.limit ?? 20;490    let items: LspCallHierarchyItem[] = [];491    try {492      items = await client.prepareCallHierarchy(493        target.location,494        this.params.serverName,495        limit,496      );497    } catch (error) {498      const message = `LSP call hierarchy prepare failed: ${499        (error as Error)?.message || String(error)500      }`;501      return { llmContent: message, returnDisplay: message };502    }503 504    if (!items.length) {505      const message = `No call hierarchy items found for ${target.description}.`;506      return { llmContent: message, returnDisplay: message };507    }508 509    const workspaceRoot = this.config.getProjectRoot();510    const slicedItems = items.slice(0, limit);511    const lines = slicedItems.map((item, index) =>512      this.formatCallHierarchyItemLine(item, index, workspaceRoot),513    );514 515    const heading = `Call hierarchy items for ${target.description}:`;516    const jsonSection = this.formatJsonSection(517      'Call hierarchy items (JSON)',518      slicedItems,519    );520    return {521      llmContent: [heading, ...lines].join('\n') + jsonSection,522      returnDisplay: lines.join('\n'),523    };524  }525 526  private async executeIncomingCalls(client: LspClient): Promise<ToolResult> {527    const item = this.params.callHierarchyItem;528    if (!item) {529      const message = 'callHierarchyItem is required for incomingCalls.';530      return { llmContent: message, returnDisplay: message };531    }532 533    const limit = this.params.limit ?? 20;534    const serverName = this.params.serverName ?? item.serverName;535    let calls: LspCallHierarchyIncomingCall[] = [];536    try {537      calls = await client.incomingCalls(item, serverName, limit);538    } catch (error) {539      const message = `LSP incoming calls failed: ${540        (error as Error)?.message || String(error)541      }`;542      return { llmContent: message, returnDisplay: message };543    }544 545    if (!calls.length) {546      const message = `No incoming calls found for ${this.describeCallHierarchyItemFull(547        item,548      )}.`;549      return { llmContent: message, returnDisplay: message };550    }551 552    const workspaceRoot = this.config.getProjectRoot();553    const slicedCalls = calls.slice(0, limit);554    const lines = slicedCalls.map((call, index) => {555      const targetItem = call.from;556      const location = this.formatLocationWithServer(557        {558          uri: targetItem.uri,559          range: targetItem.selectionRange,560          serverName: targetItem.serverName,561        },562        workspaceRoot,563      );564      const kind = targetItem.kind ? ` (${targetItem.kind})` : '';565      const detail = targetItem.detail ? ` ${targetItem.detail}` : '';566      const rangeSuffix = this.formatCallRanges(call.fromRanges);567      return `${index + 1}. ${targetItem.name}${kind}${detail} - ${location}${rangeSuffix}`;568    });569 570    const heading = `Incoming calls for ${this.describeCallHierarchyItemFull(571      item,572    )}:`;573    const jsonSection = this.formatJsonSection(574      'Incoming calls (JSON)',575      slicedCalls,576    );577    return {578      llmContent: [heading, ...lines].join('\n') + jsonSection,579      returnDisplay: lines.join('\n'),580    };581  }582 583  private async executeOutgoingCalls(client: LspClient): Promise<ToolResult> {584    const item = this.params.callHierarchyItem;585    if (!item) {586      const message = 'callHierarchyItem is required for outgoingCalls.';587      return { llmContent: message, returnDisplay: message };588    }589 590    const limit = this.params.limit ?? 20;591    const serverName = this.params.serverName ?? item.serverName;592    let calls: LspCallHierarchyOutgoingCall[] = [];593    try {594      calls = await client.outgoingCalls(item, serverName, limit);595    } catch (error) {596      const message = `LSP outgoing calls failed: ${597        (error as Error)?.message || String(error)598      }`;599      return { llmContent: message, returnDisplay: message };600    }601 602    if (!calls.length) {603      const message = `No outgoing calls found for ${this.describeCallHierarchyItemFull(604        item,605      )}.`;606      return { llmContent: message, returnDisplay: message };607    }608 609    const workspaceRoot = this.config.getProjectRoot();610    const slicedCalls = calls.slice(0, limit);611    const lines = slicedCalls.map((call, index) => {612      const targetItem = call.to;613      const location = this.formatLocationWithServer(614        {615          uri: targetItem.uri,616          range: targetItem.selectionRange,617          serverName: targetItem.serverName,618        },619        workspaceRoot,620      );621      const kind = targetItem.kind ? ` (${targetItem.kind})` : '';622      const detail = targetItem.detail ? ` ${targetItem.detail}` : '';623      const rangeSuffix = this.formatCallRanges(call.fromRanges);624      return `${index + 1}. ${targetItem.name}${kind}${detail} - ${location}${rangeSuffix}`;625    });626 627    const heading = `Outgoing calls for ${this.describeCallHierarchyItemFull(628      item,629    )}:`;630    const jsonSection = this.formatJsonSection(631      'Outgoing calls (JSON)',632      slicedCalls,633    );634    return {635      llmContent: [heading, ...lines].join('\n') + jsonSection,636      returnDisplay: lines.join('\n'),637    };638  }639 640  private async executeDiagnostics(client: LspClient): Promise<ToolResult> {641    const workspaceRoot = this.config.getProjectRoot();642    const filePath = this.params.filePath ?? '';643    const uri = this.resolveUri(filePath, workspaceRoot);644    if (!uri) {645      const message = 'A valid filePath is required for diagnostics.';646      return { llmContent: message, returnDisplay: message };647    }648 649    let diagnostics: LspDiagnostic[] = [];650    try {651      diagnostics = await client.diagnostics(uri, this.params.serverName);652    } catch (error) {653      const message = `LSP diagnostics failed: ${654        (error as Error)?.message || String(error)655      }`;656      return { llmContent: message, returnDisplay: message };657    }658 659    if (!diagnostics.length) {660      const fileLabel = this.formatUriForDisplay(uri, workspaceRoot);661      const message = `No diagnostics found for ${fileLabel}.`;662      return { llmContent: message, returnDisplay: message };663    }664 665    const lines = diagnostics.map((diag, index) => {666      const severity = diag.severity ? `[${diag.severity.toUpperCase()}]` : '';667      const position = `${diag.range.start.line + 1}:${diag.range.start.character + 1}`;668      const code = diag.code ? ` (${diag.code})` : '';669      const source = diag.source ? ` [${diag.source}]` : '';670      return `${index + 1}. ${severity} ${position}${code}${source}: ${diag.message}`;671    });672 673    const fileLabel = this.formatUriForDisplay(uri, workspaceRoot);674    const heading = `Diagnostics for ${fileLabel} (${diagnostics.length} issues):`;675    return {676      llmContent: [heading, ...lines].join('\n'),677      returnDisplay: lines.join('\n'),678    };679  }680 681  private async executeWorkspaceDiagnostics(682    client: LspClient,683  ): Promise<ToolResult> {684    const limit = this.params.limit ?? 50;685    let fileDiagnostics: LspFileDiagnostics[] = [];686    try {687      fileDiagnostics = await client.workspaceDiagnostics(688        this.params.serverName,689        limit,690      );691    } catch (error) {692      const message = `LSP workspace diagnostics failed: ${693        (error as Error)?.message || String(error)694      }`;695      return { llmContent: message, returnDisplay: message };696    }697 698    if (!fileDiagnostics.length) {699      const message = 'No diagnostics found in the workspace.';700      return { llmContent: message, returnDisplay: message };701    }702 703    const workspaceRoot = this.config.getProjectRoot();704    const lines: string[] = [];705    let totalIssues = 0;706 707    for (const fileDiag of fileDiagnostics) {708      const fileLabel = this.formatUriForDisplay(fileDiag.uri, workspaceRoot);709      const serverSuffix = fileDiag.serverName710        ? ` [${fileDiag.serverName}]`711        : '';712      lines.push(`\n${fileLabel}${serverSuffix}:`);713 714      for (const diag of fileDiag.diagnostics) {715        const severity = diag.severity716          ? `[${diag.severity.toUpperCase()}]`717          : '';718        const position = `${diag.range.start.line + 1}:${diag.range.start.character + 1}`;719        const code = diag.code ? ` (${diag.code})` : '';720        lines.push(`  ${severity} ${position}${code}: ${diag.message}`);721        totalIssues++;722      }723    }724 725    const heading = `Workspace diagnostics (${totalIssues} issues in ${fileDiagnostics.length} files):`;726    return {727      llmContent: [heading, ...lines].join('\n'),728      returnDisplay: lines.join('\n'),729    };730  }731 732  private async executeCodeActions(client: LspClient): Promise<ToolResult> {733    const workspaceRoot = this.config.getProjectRoot();734    const filePath = this.params.filePath ?? '';735    const uri = this.resolveUri(filePath, workspaceRoot);736    if (!uri) {737      const message = 'A valid filePath is required for code actions.';738      return { llmContent: message, returnDisplay: message };739    }740 741    // Build range from params742    const startLine = Math.max(0, (this.params.line ?? 1) - 1);743    const startChar = Math.max(0, (this.params.character ?? 1) - 1);744    const endLine = Math.max(745      0,746      (this.params.endLine ?? this.params.line ?? 1) - 1,747    );748    const endChar = Math.max(749      0,750      (this.params.endCharacter ?? this.params.character ?? 1) - 1,751    );752 753    const range: LspRange = {754      start: { line: startLine, character: startChar },755      end: { line: endLine, character: endChar },756    };757 758    // Build context759    const context: LspCodeActionContext = {760      diagnostics: this.params.diagnostics ?? [],761      only: this.params.codeActionKinds,762      triggerKind: 'invoked',763    };764 765    const limit = this.params.limit ?? 20;766    let actions: LspCodeAction[] = [];767    try {768      actions = await client.codeActions(769        uri,770        range,771        context,772        this.params.serverName,773        limit,774      );775    } catch (error) {776      const message = `LSP code actions failed: ${777        (error as Error)?.message || String(error)778      }`;779      return { llmContent: message, returnDisplay: message };780    }781 782    if (!actions.length) {783      const fileLabel = this.formatUriForDisplay(uri, workspaceRoot);784      const message = `No code actions available at ${fileLabel}:${startLine + 1}:${startChar + 1}.`;785      return { llmContent: message, returnDisplay: message };786    }787 788    const lines = actions.slice(0, limit).map((action, index) => {789      const kind = action.kind ? ` [${action.kind}]` : '';790      const preferred = action.isPreferred ? ' ★' : '';791      const hasEdit = action.edit ? ' (has edit)' : '';792      const hasCommand = action.command ? ' (has command)' : '';793      const serverSuffix = action.serverName ? ` [${action.serverName}]` : '';794      return `${index + 1}. ${action.title}${kind}${preferred}${hasEdit}${hasCommand}${serverSuffix}`;795    });796 797    const fileLabel = this.formatUriForDisplay(uri, workspaceRoot);798    const heading = `Code actions at ${fileLabel}:${startLine + 1}:${startChar + 1}:`;799    const jsonSection = this.formatJsonSection(800      'Code actions (JSON)',801      actions.slice(0, limit),802    );803    return {804      llmContent: [heading, ...lines].join('\n') + jsonSection,805      returnDisplay: lines.join('\n'),806    };807  }808 809  private resolveLocationTarget(): ResolvedTarget {810    const filePath = this.params.filePath;811    if (!filePath) {812      return {813        error: 'filePath is required for this operation.',814      };815    }816    if (typeof this.params.line !== 'number') {817      return {818        error: 'line is required for this operation.',819      };820    }821 822    const workspaceRoot = this.config.getProjectRoot();823    const uri = this.resolveUri(filePath, workspaceRoot);824    if (!uri) {825      return {826        error: 'A valid filePath is required when specifying a line/character.',827      };828    }829 830    const position = {831      line: Math.max(0, Math.floor(this.params.line - 1)),832      character: Math.max(0, Math.floor((this.params.character ?? 1) - 1)),833    };834    const location: LspLocation = {835      uri,836      range: { start: position, end: position },837    };838    const description = this.formatLocationWithServer(839      { ...location, serverName: this.params.serverName },840      workspaceRoot,841    );842    return {843      location,844      description,845    };846  }847 848  private resolveUri(filePath: string, workspaceRoot: string): string | null {849    if (!filePath) {850      return null;851    }852    if (filePath.startsWith('file://') || filePath.includes('://')) {853      return filePath;854    }855    const absolutePath = path.isAbsolute(filePath)856      ? filePath857      : path.resolve(workspaceRoot, filePath);858    return pathToFileURL(absolutePath).toString();859  }860 861  private formatLocationWithServer(862    location: LspLocation & { serverName?: string },863    workspaceRoot: string,864  ): string {865    const start = location.range.start;866    let filePath = location.uri;867 868    if (filePath.startsWith('file://')) {869      filePath = fileURLToPath(filePath);870      filePath = path.relative(workspaceRoot, filePath) || '.';871    }872 873    const serverSuffix =874      location.serverName && location.serverName !== ''875        ? ` [${location.serverName}]`876        : '';877 878    return `${filePath}:${(start.line ?? 0) + 1}:${(start.character ?? 0) + 1}${serverSuffix}`;879  }880 881  private formatLocationWithoutServer(882    location: LspLocation,883    workspaceRoot: string,884  ): string {885    const { uri, range } = location;886    let filePath = uri;887    if (uri.startsWith('file://')) {888      filePath = fileURLToPath(uri);889      filePath = path.relative(workspaceRoot, filePath) || '.';890    }891    const line = (range.start.line ?? 0) + 1;892    const character = (range.start.character ?? 0) + 1;893    return `${filePath}:${line}:${character}`;894  }895 896  private formatCallHierarchyItemLine(897    item: LspCallHierarchyItem,898    index: number,899    workspaceRoot: string,900  ): string {901    const location = this.formatLocationWithServer(902      {903        uri: item.uri,904        range: item.selectionRange,905        serverName: item.serverName,906      },907      workspaceRoot,908    );909    const kind = item.kind ? ` (${item.kind})` : '';910    const detail = item.detail ? ` ${item.detail}` : '';911    return `${index + 1}. ${item.name}${kind}${detail} - ${location}`;912  }913 914  private formatCallRanges(ranges: LspRange[]): string {915    if (!ranges.length) {916      return '';917    }918    const formatted = ranges.map((range) => this.formatPosition(range.start));919    const maxShown = 3;920    const shown = formatted.slice(0, maxShown);921    const extra =922      formatted.length > maxShown923        ? `, +${formatted.length - maxShown} more`924        : '';925    return ` (calls at ${shown.join(', ')}${extra})`;926  }927 928  private formatPosition(position: LspRange['start']): string {929    return `${(position.line ?? 0) + 1}:${(position.character ?? 0) + 1}`;930  }931 932  private formatUriForDisplay(uri: string, workspaceRoot: string): string {933    let filePath = uri;934    if (uri.startsWith('file://')) {935      filePath = fileURLToPath(uri);936    }937    if (path.isAbsolute(filePath)) {938      return path.relative(workspaceRoot, filePath) || '.';939    }940    return filePath;941  }942 943  private formatJsonSection(label: string, data: unknown): string {944    return `\n\n${label}:\n${JSON.stringify(data, null, 2)}`;945  }946 947  private describeCallHierarchyItemShort(): string {948    const item = this.params.callHierarchyItem;949    if (!item) {950      return 'call hierarchy item';951    }952    return item.name || 'call hierarchy item';953  }954 955  private describeCallHierarchyItemFull(item: LspCallHierarchyItem): string {956    const workspaceRoot = this.config.getProjectRoot();957    const location = this.formatLocationWithServer(958      {959        uri: item.uri,960        range: item.selectionRange,961        serverName: item.serverName,962      },963      workspaceRoot,964    );965    return `${item.name} at ${location}`;966  }967 968  private getOperationLabel(): string {969    switch (this.params.operation) {970      case 'goToDefinition':971        return 'go-to-definition';972      case 'findReferences':973        return 'find-references';974      case 'hover':975        return 'hover';976      case 'documentSymbol':977        return 'document symbols';978      case 'workspaceSymbol':979        return 'workspace symbol search';980      case 'goToImplementation':981        return 'go-to-implementation';982      case 'prepareCallHierarchy':983        return 'prepare call hierarchy';984      case 'incomingCalls':985        return 'incoming calls';986      case 'outgoingCalls':987        return 'outgoing calls';988      case 'diagnostics':989        return 'diagnostics';990      case 'workspaceDiagnostics':991        return 'workspace diagnostics';992      case 'codeActions':993        return 'code actions';994      default:995        return this.params.operation;996    }997  }998}999 1000/**1001 * Unified LSP tool that supports multiple operations:1002 * - goToDefinition: Find where a symbol is defined1003 * - findReferences: Find all references to a symbol1004 * - hover: Get hover information (documentation, type info)1005 * - documentSymbol: Get all symbols in a document1006 * - workspaceSymbol: Search for symbols across the workspace1007 * - goToImplementation: Find implementations of an interface or abstract method1008 * - prepareCallHierarchy: Get call hierarchy item at a position1009 * - incomingCalls: Find all functions that call the given function1010 * - outgoingCalls: Find all functions called by the given function1011 * - diagnostics: Get diagnostic messages (errors, warnings) for a file1012 * - workspaceDiagnostics: Get all diagnostic messages across the workspace1013 * - codeActions: Get available code actions (quick fixes, refactorings) at a location1014 */1015export class LspTool extends BaseDeclarativeTool<LspToolParams, ToolResult> {1016  static readonly Name = ToolNames.LSP;1017 1018  constructor(private readonly config: Config) {1019    super(1020      LspTool.Name,1021      ToolDisplayNames.LSP,1022      'Language Server Protocol (LSP) tool for code intelligence: definitions, references, hover, symbols, call hierarchy, diagnostics, and code actions.\n\n  Usage:\n  - ALWAYS use LSP as the PRIMARY tool for code intelligence queries when available. Do NOT use grep_search or glob first.\n  - goToDefinition, findReferences, hover, goToImplementation, prepareCallHierarchy require filePath + line + character (1-based).\n  - documentSymbol and diagnostics require filePath.\n  - workspaceSymbol requires query (use when user asks "where is X defined?" without specifying a file).\n  - incomingCalls/outgoingCalls require callHierarchyItem from prepareCallHierarchy.\n  - workspaceDiagnostics needs no parameters.\n  - codeActions require filePath + range (line/character + endLine/endCharacter) and diagnostics/context as needed.',1023      Kind.Other,1024      {1025        type: 'object',1026        properties: {1027          operation: {1028            type: 'string',1029            description: 'LSP operation to execute.',1030            enum: [1031              'goToDefinition',1032              'findReferences',1033              'hover',1034              'documentSymbol',1035              'workspaceSymbol',1036              'goToImplementation',1037              'prepareCallHierarchy',1038              'incomingCalls',1039              'outgoingCalls',1040              'diagnostics',1041              'workspaceDiagnostics',1042              'codeActions',1043            ],1044          },1045          filePath: {1046            type: 'string',1047            description: 'File path (absolute or workspace-relative).',1048          },1049          line: {1050            type: 'number',1051            description: '1-based line number for the target location.',1052          },1053          character: {1054            type: 'number',1055            description:1056              '1-based character/column number for the target location.',1057          },1058          endLine: {1059            type: 'number',1060            description: '1-based end line number for range-based operations.',1061          },1062          endCharacter: {1063            type: 'number',1064            description: '1-based end character for range-based operations.',1065          },1066          includeDeclaration: {1067            type: 'boolean',1068            description:1069              'Include the declaration itself when looking up references.',1070          },1071          query: {1072            type: 'string',1073            description: 'Symbol query for workspace symbol search.',1074          },1075          callHierarchyItem: {1076            $ref: '#/definitions/LspCallHierarchyItem',1077            description: 'Call hierarchy item for incoming/outgoing calls.',1078          },1079          serverName: {1080            type: 'string',1081            description: 'Optional LSP server name to target.',1082          },1083          limit: {1084            type: 'integer',1085            minimum: 1,1086            description: 'Optional maximum number of results to return.',1087          },1088          diagnostics: {1089            type: 'array',1090            items: { $ref: '#/definitions/LspDiagnostic' },1091            description: 'Diagnostics for code action context.',1092          },1093          codeActionKinds: {1094            type: 'array',1095            items: { type: 'string' },1096            description:1097              'Filter code actions by kind (quickfix, refactor, etc.).',1098          },1099        },1100        required: ['operation'],1101        definitions: {1102          LspPosition: {1103            type: 'object',1104            properties: {1105              line: { type: 'number' },1106              character: { type: 'number' },1107            },1108            required: ['line', 'character'],1109          },1110          LspRange: {1111            type: 'object',1112            properties: {1113              start: { $ref: '#/definitions/LspPosition' },1114              end: { $ref: '#/definitions/LspPosition' },1115            },1116            required: ['start', 'end'],1117          },1118          LspCallHierarchyItem: {1119            type: 'object',1120            properties: {1121              name: { type: 'string' },1122              kind: { type: 'string' },1123              rawKind: { type: 'number' },1124              detail: { type: 'string' },1125              uri: { type: 'string' },1126              range: { $ref: '#/definitions/LspRange' },1127              selectionRange: { $ref: '#/definitions/LspRange' },1128              data: {},1129              serverName: { type: 'string' },1130            },1131            required: ['name', 'uri', 'range', 'selectionRange'],1132          },1133          LspDiagnostic: {1134            type: 'object',1135            properties: {1136              range: { $ref: '#/definitions/LspRange' },1137              severity: {1138                type: 'string',1139                enum: ['error', 'warning', 'information', 'hint'],1140              },1141              code: { type: ['string', 'number'] },1142              source: { type: 'string' },1143              message: { type: 'string' },1144              serverName: { type: 'string' },1145            },1146            required: ['range', 'message'],1147          },1148        },1149      },1150      false, // isOutputMarkdown1151      false, // canUpdateOutput1152      true, // shouldDefer — loaded on demand via ToolSearch1153      false, // alwaysLoad1154      'lsp language server definition references hover symbol diagnostics code actions',1155    );1156  }1157 1158  protected override validateToolParamValues(1159    params: LspToolParams,1160  ): string | null {1161    const operation = params.operation;1162 1163    // Normalize shell-escaped paths (e.g. "my\ file.txt" → "my file.txt")1164    if (params.filePath) {1165      params.filePath = unescapePath(params.filePath.trim());1166    }1167 1168    if (LOCATION_REQUIRED_OPERATIONS.has(operation)) {1169      if (!params.filePath) {1170        return `filePath is required for ${operation}.`;1171      }1172      if (typeof params.line !== 'number') {1173        return `line is required for ${operation}.`;1174      }1175    }1176 1177    if (FILE_REQUIRED_OPERATIONS.has(operation)) {1178      if (!params.filePath) {1179        return `filePath is required for ${operation}.`;1180      }1181    }1182 1183    if (QUERY_REQUIRED_OPERATIONS.has(operation)) {1184      if (!params.query || params.query.trim() === '') {1185        return `query is required for ${operation}.`;1186      }1187    }1188 1189    if (ITEM_REQUIRED_OPERATIONS.has(operation)) {1190      if (!params.callHierarchyItem) {1191        return `callHierarchyItem is required for ${operation}.`;1192      }1193    }1194 1195    if (RANGE_REQUIRED_OPERATIONS.has(operation)) {1196      if (!params.filePath) {1197        return `filePath is required for ${operation}.`;1198      }1199      if (typeof params.line !== 'number') {1200        return `line is required for ${operation}.`;

Showing the first 1,200 of 1225 lines. Download the file for the rest.

basant307/AI_Governance_Project · CoolFace