Skip to content

Commit 926603d

Browse files
authored
feat: support context provider api (#924)
* feat: support context provider api * style: fix format style * ci: fix * perf: update code to comments
1 parent b3fd5db commit 926603d

File tree

7 files changed

+926
-1
lines changed

7 files changed

+926
-1
lines changed

package-lock.json

Lines changed: 173 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,7 @@
11101110
"webpack-cli": "^4.10.0"
11111111
},
11121112
"dependencies": {
1113+
"@github/copilot-language-server": "^1.388.0",
11131114
"await-lock": "^2.2.2",
11141115
"fmtr": "^1.1.4",
11151116
"fs-extra": "^10.1.0",

src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ export namespace Commands {
136136

137137
export const JAVA_PROJECT_GET_DEPENDENCIES = "java.project.getDependencies";
138138

139+
export const JAVA_PROJECT_GET_IMPORT_CLASS_CONTENT = "java.project.getImportClassContent";
140+
139141
export const JAVA_UPGRADE_WITH_COPILOT = "_java.upgradeWithCopilot";
140142

141143
/**

src/copilot/contextProvider.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
import {
6+
ResolveRequest,
7+
SupportedContextItem,
8+
type ContextProvider,
9+
} from '@github/copilot-language-server';
10+
import * as vscode from 'vscode';
11+
import { CopilotHelper } from './copilotHelper';
12+
import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper";
13+
import {
14+
JavaContextProviderUtils,
15+
CancellationError,
16+
InternalCancellationError,
17+
CopilotCancellationError,
18+
ContextResolverFunction,
19+
CopilotApi,
20+
ContextProviderRegistrationError,
21+
ContextProviderResolverError
22+
} from './utils';
23+
24+
export async function registerCopilotContextProviders(
25+
context: vscode.ExtensionContext
26+
) {
27+
try {
28+
const apis = await JavaContextProviderUtils.getCopilotApis();
29+
if (!apis.clientApi || !apis.chatApi) {
30+
return;
31+
}
32+
// Register the Java completion context provider
33+
const provider: ContextProvider<SupportedContextItem> = {
34+
id: 'vscjava.vscode-java-dependency', // use extension id as provider id for now
35+
selector: [{ language: "java" }],
36+
resolver: { resolve: createJavaContextResolver() }
37+
};
38+
const installCount = await JavaContextProviderUtils.installContextProviderOnApis(apis, provider, context, installContextProvider);
39+
if (installCount === 0) {
40+
return;
41+
}
42+
sendInfo("", {
43+
"action": "registerCopilotContextProvider",
44+
"status": "succeeded",
45+
"installCount": installCount
46+
});
47+
}
48+
catch (error) {
49+
const errorMessage = (error as Error).message || "unknown_error";
50+
sendError(new ContextProviderRegistrationError(
51+
'Failed to register Copilot context provider: ' + errorMessage
52+
));
53+
}
54+
}
55+
56+
/**
57+
* Create the Java context resolver function
58+
*/
59+
function createJavaContextResolver(): ContextResolverFunction {
60+
return async (request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise<SupportedContextItem[]> => {
61+
try {
62+
// Check for immediate cancellation
63+
JavaContextProviderUtils.checkCancellation(copilotCancel);
64+
return await resolveJavaContext(request, copilotCancel);
65+
} catch (error: any) {
66+
sendError(new ContextProviderResolverError('Java Context Resolution Failed: ' + ((error as Error).message || "unknown_error")));
67+
// This should never be reached due to handleError throwing, but TypeScript requires it
68+
return [];
69+
}
70+
};
71+
}
72+
73+
/**
74+
* Send telemetry data for Java context resolution
75+
*/
76+
function sendContextTelemetry(request: ResolveRequest, start: number, items: SupportedContextItem[], status: string, error?: string) {
77+
const duration = Math.round(performance.now() - start);
78+
const tokenCount = JavaContextProviderUtils.calculateTokenCount(items);
79+
const telemetryData: any = {
80+
"action": "resolveJavaContext",
81+
"completionId": request.completionId,
82+
"duration": duration,
83+
"itemCount": items.length,
84+
"tokenCount": tokenCount,
85+
"status": status
86+
};
87+
if (error) {
88+
telemetryData.error = error;
89+
}
90+
sendInfo("", telemetryData);
91+
}
92+
93+
async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise<SupportedContextItem[]> {
94+
const items: SupportedContextItem[] = [];
95+
const start = performance.now();
96+
try {
97+
// Check for cancellation before starting
98+
JavaContextProviderUtils.checkCancellation(copilotCancel);
99+
// Resolve project dependencies and convert to context items
100+
const projectDependencyItems = await CopilotHelper.resolveAndConvertProjectDependencies(
101+
vscode.window.activeTextEditor,
102+
copilotCancel,
103+
JavaContextProviderUtils.checkCancellation
104+
);
105+
JavaContextProviderUtils.checkCancellation(copilotCancel);
106+
items.push(...projectDependencyItems);
107+
108+
JavaContextProviderUtils.checkCancellation(copilotCancel);
109+
110+
// Resolve local imports and convert to context items
111+
const localImportItems = await CopilotHelper.resolveAndConvertLocalImports(
112+
vscode.window.activeTextEditor,
113+
copilotCancel,
114+
JavaContextProviderUtils.checkCancellation
115+
);
116+
JavaContextProviderUtils.checkCancellation(copilotCancel);
117+
items.push(...localImportItems);
118+
} catch (error: any) {
119+
if (error instanceof CopilotCancellationError) {
120+
sendContextTelemetry(request, start, items, "cancelled_by_copilot");
121+
throw error;
122+
}
123+
if (error instanceof vscode.CancellationError || error.message === CancellationError.CANCELED) {
124+
sendContextTelemetry(request, start, items, "cancelled_internally");
125+
throw new InternalCancellationError();
126+
}
127+
128+
// Send telemetry for general errors (but continue with partial results)
129+
sendContextTelemetry(request, start, items, "error_partial_results", error.message || "unknown_error");
130+
131+
// Return partial results and log completion for error case
132+
return items;
133+
}
134+
135+
// Send telemetry data once at the end for success case
136+
sendContextTelemetry(request, start, items, "succeeded");
137+
138+
return items;
139+
}
140+
141+
export async function installContextProvider(
142+
copilotAPI: CopilotApi,
143+
contextProvider: ContextProvider<SupportedContextItem>
144+
): Promise<vscode.Disposable | undefined> {
145+
const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function';
146+
if (hasGetContextProviderAPI) {
147+
const contextAPI = await copilotAPI.getContextProviderAPI('v1');
148+
if (contextAPI) {
149+
return contextAPI.registerContextProvider(contextProvider);
150+
}
151+
}
152+
return undefined;
153+
}

0 commit comments

Comments
 (0)