Path: blob/main/extensions/copilot/src/extension/languageContextProvider/vscode-node/languageContextProviderService.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { type CancellationToken, languages, type TextDocument, type Disposable as VscodeDisposable } from 'vscode';6import { Copilot } from '../../../platform/inlineCompletions/common/api';7import { ILanguageContextProviderService, ProviderTarget } from '../../../platform/languageContextProvider/common/languageContextProviderService';8import { ContextItem, ContextKind, KnownSources, SnippetContext, TraitContext } from '../../../platform/languageServer/common/languageContextService';9import { filterMap } from '../../../util/common/arrays';10import { AsyncIterableObject } from '../../../util/vs/base/common/async';11import { Disposable, toDisposable } from '../../../util/vs/base/common/lifecycle';12import { URI } from '../../../util/vs/base/common/uri';1314export class LanguageContextProviderService extends Disposable implements ILanguageContextProviderService {15_serviceBrand: undefined;1617private providers: { provider: Copilot.ContextProvider<Copilot.SupportedContextItem>; targets: ProviderTarget[] }[] = [];1819public registerContextProvider<T extends Copilot.SupportedContextItem>(provider: Copilot.ContextProvider<T>, targets: ProviderTarget[]): VscodeDisposable {20if (targets.length === 0) {21throw new Error('At least one ProviderTarget must be specified when registering a context provider.');22}2324this.providers.push({ provider, targets });25return toDisposable(() => {26const index = this.providers.findIndex(p => p.provider === provider);27if (index > -1) {28this.providers.splice(index, 1);29}30});31}3233public getAllProviders(target: ProviderTarget[]): readonly Copilot.ContextProvider<Copilot.SupportedContextItem>[] {34return this.providers.filter(p => target.some(t => p.targets.includes(t))).map(p => p.provider);35}3637public getContextProviders(doc: TextDocument, target: ProviderTarget): Copilot.ContextProvider<Copilot.SupportedContextItem>[] {38return this.getAllProviders([target]).filter(provider => languages.match(provider.selector, doc));39}4041public override dispose(): void {42super.dispose();43this.providers.length = 0;44}4546public getContextItems(doc: TextDocument, request: Copilot.ResolveRequest, cancellationToken: CancellationToken): AsyncIterable<ContextItem> {47const providers = this.getContextProviders(doc, request.source === KnownSources.nes ? ProviderTarget.NES : ProviderTarget.Completions);4849const items = new AsyncIterableObject<Copilot.SupportedContextItem>(async emitter => {50async function runProvider(provider: Copilot.ContextProvider<Copilot.SupportedContextItem>) {51const langCtx = provider.resolver.resolve(request, cancellationToken);52if (typeof (langCtx as any)[Symbol.asyncIterator] === 'function') {53for await (const context of langCtx as AsyncIterable<Copilot.SupportedContextItem>) {54emitter.emitOne(context);55}56return;57}58const result = await langCtx;59if (Array.isArray(result)) {60for (const context of result) {61emitter.emitOne(context);62}63} else if (typeof (result as any)[Symbol.asyncIterator] !== 'function') {64// Only push if it's a single SupportedContextItem, not an AsyncIterable65emitter.emitOne(result as Copilot.SupportedContextItem);66}67}6869await Promise.allSettled(providers.map(runProvider));70});7172const contextItems = items.map(v => LanguageContextProviderService.convertCopilotContextItem(v));7374return contextItems;75}7677private static convertCopilotContextItem(item: Copilot.SupportedContextItem): ContextItem {78const isSnippet = item && typeof item === 'object' && (item as any).uri !== undefined;79if (isSnippet) {80const ctx = item as Copilot.CodeSnippet;81return {82kind: ContextKind.Snippet,83priority: LanguageContextProviderService.convertImportanceToPriority(ctx.importance),84uri: URI.parse(ctx.uri),85value: ctx.value,86additionalUris: ctx.additionalUris?.map(uri => URI.parse(uri)),87} satisfies SnippetContext;88} else {89const ctx = item as Copilot.Trait;90return {91kind: ContextKind.Trait,92priority: LanguageContextProviderService.convertImportanceToPriority(ctx.importance),93name: ctx.name,94value: ctx.value,95} satisfies TraitContext;96}97}9899// importance is coined by the copilot extension and must be an integer in [0, 100], while priority is by the chat extension and spans [0, 1]100private static convertImportanceToPriority(importance: number | undefined): number {101if (importance === undefined || importance < 0) {102return 0;103}104if (importance > 100) {105return 1;106}107return importance / 100;108}109110public getContextItemsOnTimeout(doc: TextDocument, request: Copilot.ResolveRequest): ContextItem[] {111const providers = this.getContextProviders(doc, request.source === KnownSources.nes ? ProviderTarget.NES : ProviderTarget.Completions);112113const unprocessedResults = filterMap(providers, p => p.resolver.resolveOnTimeout?.(request));114115const copilotCtxItems = unprocessedResults.flat();116117const ctxItems = copilotCtxItems.map(v => LanguageContextProviderService.convertCopilotContextItem(v));118119return ctxItems;120}121122}123124125