Skip to content

Commit 402658c

Browse files
committed
pref: adjust the invoke chain
1 parent b8dd2b3 commit 402658c

4 files changed

Lines changed: 64 additions & 12 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
{
5353
"name": "lsp_java_getFileStructure",
5454
"toolReferenceName": "javaFileStructure",
55-
"modelDescription": "Get the outline of a known Java file: classes, interfaces, methods, fields, symbol kinds, and line ranges.\n\nUse this after lsp_java_findSymbol returns a relevant file, or when the user already provided a Java file path. It helps choose a precise read_file range instead of reading the whole file.\n\nDo not use this for workspace-wide search. For Java class, method, or field lookup across files, use lsp_java_findSymbol first. Only pass file paths confirmed by the user or prior tool results; do not guess paths. Do not call this repeatedly for the same file unless the first result was empty or stale.",
55+
"modelDescription": "Get a known Java file's outline: classes, interfaces, methods, fields, symbol kinds, and line ranges, to pick a precise read_file range instead of reading the whole file.\n\nUse after lsp_java_findSymbol returns a file, or when the user gave a Java file path; do not guess paths. Not for workspace-wide search\u2014use lsp_java_findSymbol for that. Do not re-call for the same file unless the first result was empty.",
5656
"displayName": "Java: Get File Structure",
5757
"userDescription": "Get a Java file outline with classes, methods, fields, and line ranges.",
5858
"tags": [
@@ -80,7 +80,7 @@
8080
{
8181
"name": "lsp_java_findSymbol",
8282
"toolReferenceName": "javaFindSymbol",
83-
"modelDescription": "Search Java symbols across the workspace by identifier and return concise definition locations.\n\nUse for precise Java class, interface, method, or field navigation. Prefer this over grep_search, file_search, semantic_search, or search subagents when the user is looking for a Java symbol by name or partial identifier.\n\nAfter this returns relevant symbols, do not call lsp_java_findSymbol again with the same or similar query. Continue with lsp_java_getFileStructure for the returned file, or read_file only the relevant line range.\n\nDo not use for non-Java files, string literals, comments, build files, XML, natural-language concepts, or broad codebase exploration. If there are no matches, retry at most once with a shorter materially different identifier, then fall back to generic search.",
83+
"modelDescription": "Find Java class, interface, method, or field definitions across the workspace by name or partial identifier. Prefer over grep_search, file_search, semantic_search, or search subagents for Java symbol lookup.\n\nOn relevant results, do not repeat with a similar query; continue with lsp_java_getFileStructure or read_file on the returned line range. The tool retries internally, so on an empty result do not re-search\u2014retry once only if it reports indexing in progress, otherwise use generic search.\n\nDo not use for non-Java files, literals, comments, build/XML files, or conceptual exploration.",
8484
"displayName": "Java: Find Symbol",
8585
"userDescription": "Find Java class, method, field, or interface definitions by name.",
8686
"tags": [

resources/instruments/javaLspContext.instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ If these tools are not already available in the current tool list, load them wit
1212

1313
Use `lsp_java_findSymbol` before `grep_search`, `search_subagent`, `semantic_search`, or `file_search` only when the task is to locate Java symbols by name or partial identifier. If it returns relevant symbols, do not call it again with the same or similar query; next use `lsp_java_getFileStructure` for the returned file or `read_file` on the smallest useful line range.
1414

15-
Use `lsp_java_getFileStructure` only with a path confirmed by the user or a previous tool result. Do not guess paths. Use generic search for string literals, comments, XML, Gradle/Maven files, non-Java files, or broad conceptual exploration. If `findSymbol` returns no matches, retry at most once with a shorter, materially different identifier before falling back to generic search.
15+
Use `lsp_java_getFileStructure` only with a path confirmed by the user or a previous tool result. Do not guess paths. Use generic search for string literals, comments, XML, Gradle/Maven files, non-Java files, or broad conceptual exploration. `findSymbol` already retries internally with a normalized identifier, so do not re-issue the same search on an empty result: if it reports indexing in progress, retry once after a short pause; otherwise fall back to generic search.

resources/skills/java-lsp-tools/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ If `findSymbol` returns relevant symbols, move forward to `getFileStructure` or
4040

4141
## Fallback
4242

43-
- `findSymbol` returns empty → retry at most once with a shorter, materially different identifier, then fall back to `grep_search`
44-
- Path error → use `findSymbol` to discover correct path first
43+
- `findSymbol` returns empty → it already retried internally with a normalized identifier, so do not re-issue the same search. If the result says indexing is in progress, retry once after a short pause; otherwise fall back to `grep_search`
44+
- Path error (`fileNotFound`) → use `findSymbol` to discover the correct path first; do not guess paths
4545
- Tool error / jdtls not ready → fall back to `grep_search` + `read_file`, don't retry more than once

src/copilot/tools/javaContextTools.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import * as path from "path";
2424
import * as vscode from "vscode";
2525
import { Commands } from "../../commands";
26+
import { languageServerApiManager } from "../../languageServerApi/languageServerApiManager";
2627
import { sendInfo } from "vscode-extension-telemetry-wrapper";
2728

2829
// Hard caps to keep tool responses within the < 200 token budget.
@@ -43,6 +44,27 @@ function getResponseCharCount(data: unknown): number {
4344
return typeof data === "string" ? data.length : JSON.stringify(data, null, 2).length;
4445
}
4546

47+
/**
48+
* Normalize a workspace-symbol query for a single fallback retry.
49+
* Strips a fully-qualified package prefix (com.foo.Bar -> Bar), generic parameters
50+
* (List<String> -> List), and method parameter lists (foo() -> foo). jdtls already
51+
* performs camel-hump matching, so the contiguous identifier is preserved.
52+
*/
53+
function normalizeSymbolQuery(query: string): string {
54+
if (!query) {
55+
return "";
56+
}
57+
let q = query.trim();
58+
// Drop generic parameters and method parens: List<String> / foo(args) -> List / foo
59+
q = q.replace(/[<(].*$/, "");
60+
// Drop a fully-qualified package/qualifier prefix: com.foo.Bar / Foo#bar -> Bar / bar
61+
const lastSep = Math.max(q.lastIndexOf("."), q.lastIndexOf("#"));
62+
if (lastSep >= 0 && lastSep < q.length - 1) {
63+
q = q.substring(lastSep + 1);
64+
}
65+
return q.trim();
66+
}
67+
4668
function getToolErrorCode(error: unknown): string {
4769
const message = error instanceof Error ? error.message : String(error);
4870
if (message.includes("No workspace folder")) {
@@ -125,7 +147,12 @@ const fileStructureTool: vscode.LanguageModelTool<FileStructureInput> = {
125147
} catch {
126148
status = "error";
127149
errorCode = "fileNotFound";
128-
const fileNotFoundPayload = { error: "File not found." };
150+
// Most fileNotFound errors come from the model guessing a path. Return an
151+
// actionable hint instead of a dead end so it can self-correct via findSymbol.
152+
const fileNotFoundPayload = {
153+
error: "File not found.",
154+
hint: "Call lsp_java_findSymbol to obtain the exact workspace path before retrying. Do not guess file paths.",
155+
};
129156
responseCharCount = getResponseCharCount(fileNotFoundPayload);
130157
return toResult(fileNotFoundPayload);
131158
}
@@ -134,8 +161,13 @@ const fileStructureTool: vscode.LanguageModelTool<FileStructureInput> = {
134161
);
135162
if (!symbols || symbols.length === 0) {
136163
status = "empty";
137-
emptyReason = "documentSymbolProviderEmpty";
138-
const noSymbolsPayload = { error: "No symbols found. The file may not be recognized by the Java language server." };
164+
// Separate "index not ready yet" from a genuine no-symbol result so the model
165+
// (and telemetry) can tell a transient state apart from an unrecognized file.
166+
const indexing = !languageServerApiManager.isFullyReady();
167+
emptyReason = indexing ? "indexingInProgress" : "documentSymbolProviderEmpty";
168+
const noSymbolsPayload = indexing
169+
? { error: "Java language server is still indexing. Retry shortly." }
170+
: { error: "No symbols found. The file may not be recognized by the Java language server." };
139171
responseCharCount = getResponseCharCount(noSymbolsPayload);
140172
return toResult(noSymbolsPayload);
141173
}
@@ -214,14 +246,33 @@ const findSymbolTool: vscode.LanguageModelTool<FindSymbolInput> = {
214246
let errorCode = "";
215247
let emptyReason = "";
216248
let responseCharCount = 0;
249+
let retried = false;
217250
try {
218-
const symbols = await vscode.commands.executeCommand<vscode.SymbolInformation[]>(
219-
"vscode.executeWorkspaceSymbolProvider", options.input.query,
251+
const rawQuery = options.input.query ?? "";
252+
let symbols = await vscode.commands.executeCommand<vscode.SymbolInformation[]>(
253+
"vscode.executeWorkspaceSymbolProvider", rawQuery,
220254
);
255+
// Server-side fallback: if the verbatim query misses, retry once with a
256+
// normalized identifier (strip package qualifier, generics, and parameter
257+
// lists) so the model does not have to chain repeated findSymbol calls itself.
258+
if (!symbols || symbols.length === 0) {
259+
const normalized = normalizeSymbolQuery(rawQuery);
260+
if (normalized && normalized !== rawQuery) {
261+
retried = true;
262+
symbols = await vscode.commands.executeCommand<vscode.SymbolInformation[]>(
263+
"vscode.executeWorkspaceSymbolProvider", normalized,
264+
);
265+
}
266+
}
221267
if (!symbols || symbols.length === 0) {
222268
status = "empty";
223-
emptyReason = "workspaceSymbolNoMatch";
224-
const noMatchesPayload = { results: [], message: "No symbols found." };
269+
// Distinguish a transient "index not ready" state from a real no-match so the
270+
// model can retry later instead of concluding the symbol does not exist.
271+
const indexing = !languageServerApiManager.isFullyReady();
272+
emptyReason = indexing ? "indexingInProgress" : "workspaceSymbolNoMatch";
273+
const noMatchesPayload = indexing
274+
? { results: [], message: "Java language server is still indexing. Retry shortly or use grep_search as a fallback." }
275+
: { results: [], message: "No symbols found." };
225276
responseCharCount = getResponseCharCount(noMatchesPayload);
226277
return toResult(noMatchesPayload);
227278
}
@@ -247,6 +298,7 @@ const findSymbolTool: vscode.LanguageModelTool<FindSymbolInput> = {
247298
status,
248299
...(errorCode && { errorCode }),
249300
...(emptyReason && { emptyReason }),
301+
retried: retried ? "true" : "false",
250302
limit,
251303
resultCount,
252304
totalResults,

0 commit comments

Comments
 (0)