Skip to content

Commit 61fc5ea

Browse files
authored
Merge pull request #8 from posit-dev/feature/copilot-sdk-begone
Hooks for enabling Copilot Chat as a completion provider for Positron
2 parents c96c8fe + ed4fa21 commit 61fc5ea

File tree

9 files changed

+119
-42
lines changed

9 files changed

+119
-42
lines changed

src/extension/completions-core/vscode-node/extension/src/config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,26 @@ export function isCompletionEnabled(accessor: ServicesAccessor): boolean | undef
148148
return isCompletionEnabledForDocument(accessor, editor.document);
149149
}
150150

151+
// --- Start Positron ---
152+
const PositronInlineCompletionsEnableConfigKey = 'positron.assistant.inlineCompletions.enable';
153+
const PositronInlineCompletionsEnableDefault: { [key: string]: boolean } = { '*': true };
154+
155+
function isPositronCompletionEnabledForLanguage(languageId: string): boolean {
156+
const enabledLanguages = vscode.workspace.getConfiguration().get<{ [key: string]: boolean }>(PositronInlineCompletionsEnableConfigKey) ?? PositronInlineCompletionsEnableDefault;
157+
const enabledLanguagesMap = new Map(Object.entries(enabledLanguages));
158+
if (!enabledLanguagesMap.has('*')) {
159+
enabledLanguagesMap.set('*', false);
160+
}
161+
return enabledLanguagesMap.has(languageId) ? enabledLanguagesMap.get(languageId)! : enabledLanguagesMap.get('*')!;
162+
}
163+
// --- End Positron ---
164+
151165
export function isCompletionEnabledForDocument(accessor: ServicesAccessor, document: vscode.TextDocument): boolean {
166+
// --- Start Positron ---
167+
if (!isPositronCompletionEnabledForLanguage(document.languageId)) {
168+
return false;
169+
}
170+
// --- End Positron ---
152171
return getEnabledConfig(accessor, document.languageId);
153172
}
154173

src/extension/completions/vscode-node/completionsCoreContribution.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,21 @@ export class CompletionsCoreContribution extends Disposable {
3535
this._register(autorun(reader => {
3636
const unificationStateValue = unificationState.read(reader);
3737
const configEnabled = configurationService.getExperimentBasedConfigObservable<boolean>(ConfigKey.Internal.InlineEditsEnableGhCompletionsProvider, experimentationService).read(reader);
38-
const extensionUnification = unificationStateValue?.extensionUnification ?? false;
38+
// --- Start Positron ---
39+
// Always enable extension unification for Positron; we do not have
40+
// access to the proprietary GitHub Copilot extension that would
41+
// otherwise provide completions.
42+
//
43+
// const extensionUnification = unificationStateValue?.extensionUnification ?? false;
44+
const extensionUnification = true;
45+
// --- End Positron ---
3946

40-
if (unificationStateValue?.codeUnification || extensionUnification || configEnabled || this._copilotToken.read(reader)?.isNoAuthUser) {
47+
if (unificationStateValue?.codeUnification || extensionUnification || configEnabled || this._copilotToken.read(reader)?.isNoAuthUser || Math.random() > 0) {
4148
const provider = this._getOrCreateProvider();
42-
reader.store.add(languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider, { debounceDelayMs: 0, excludes: ['github.copilot'], groupId: 'completions' }));
49+
// --- Start Positron ---
50+
// Added displayName property for showing in Completion Providers
51+
reader.store.add(languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider, { displayName: 'GitHub Copilot', debounceDelayMs: 0, excludes: ['github.copilot'], groupId: 'completions' }));
52+
// --- End Positron ---
4353
}
4454

4555
void commands.executeCommand('setContext', 'github.copilot.extensionUnification.activated', extensionUnification);

src/extension/extension/vscode-node/extension.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import '../../intents/node/allIntents';
2222

2323
// --- Start Positron ---
2424
import * as vscode from 'vscode';
25-
import { getFileBasedAuthSession } from '../../../platform/authentication/vscode-node/fileBasedAuth.js';
2625
// --- End Positron ---
2726

2827
function configureDevPackages() {
@@ -48,28 +47,7 @@ export function activate(context: ExtensionContext, forceActivation?: boolean) {
4847
return;
4948
}
5049

51-
// Don't perform activation if we have no auth session; the original
52-
// extension has an "installed but not signed in" state, but we don't
53-
// support that in Positron.
54-
const authSession = getFileBasedAuthSession();
55-
if (!authSession) {
56-
// There's no auth session yet, but we don't want to require a restart
57-
// when one is established. Listen for a Copilot auth session to be
58-
// established.
59-
console.log(`[Copilot Chat] No auth session found, extension will not activate until sign-in`);
60-
const api = vscode.extensions.getExtension('positron.positron-assistant')?.exports;
61-
if (api) {
62-
api.onProviderSignIn((provider: string) => {
63-
if (provider === 'copilot') {
64-
console.info('[Copilot Chat] Detected Copilot sign-in, activating extension');
65-
activate(context, forceActivation);
66-
}
67-
});
68-
} else {
69-
console.error('[Copilot Chat] Failed to get Positron API');
70-
}
71-
return;
72-
}
50+
// TODO: Don't activate until we have an auth session
7351
// --- End Positron ---
7452

7553
return baseActivate({
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
3+
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
/**
7+
* Configuration key for Positron inline completions enable setting.
8+
* This has the same format as github.copilot.enable: { [languageId]: boolean }
9+
*/
10+
export const PositronInlineCompletionsEnableConfigKey = 'positron.assistant.inlineCompletions.enable';
11+
12+
/**
13+
* Default value for the Positron inline completions enable setting.
14+
*/
15+
export const PositronInlineCompletionsEnableDefault: { [key: string]: boolean } = {
16+
'*': true,
17+
};

src/extension/inlineEdits/vscode-node/inlineCompletionProvider.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import { InlineEditLogger } from './parts/inlineEditLogger';
3939
import { IVSCodeObservableDocument } from './parts/vscodeWorkspace';
4040
import { toExternalRange } from './utils/translations';
4141

42+
// --- Start Positron ---
43+
import { PositronInlineCompletionsEnableConfigKey, PositronInlineCompletionsEnableDefault } from '../common/positronConfig';
44+
// --- End Positron ---
45+
4246
const learnMoreAction: Command = {
4347
title: l10n.t('Learn More'),
4448
command: learnMoreCommandId,
@@ -122,16 +126,18 @@ export class InlineCompletionProviderImpl implements InlineCompletionItemProvide
122126
this._tracer = createTracer(['NES', 'Provider'], (s) => this._logService.trace(s));
123127
this._displayNextEditorNES = this._configurationService.getExperimentBasedConfig(ConfigKey.Internal.UseAlternativeNESNotebookFormat, this._expService);
124128
}
125-
129+
// --- Start Positron ---
130+
// Use Positron's inline completions enable config key instead of Copilot's
126131
// copied from `vscodeWorkspace.ts` `DocumentFilter#_enabledLanguages`
127132
private _isCompletionsEnabled(document: TextDocument): boolean {
128-
const enabledLanguages = this._configurationService.getConfig(ConfigKey.Enable);
133+
const enabledLanguages = this._configurationService.getNonExtensionConfig<{ [key: string]: boolean }>(PositronInlineCompletionsEnableConfigKey) ?? PositronInlineCompletionsEnableDefault;
129134
const enabledLanguagesMap = new Map(Object.entries(enabledLanguages));
130135
if (!enabledLanguagesMap.has('*')) {
131136
enabledLanguagesMap.set('*', false);
132137
}
133138
return enabledLanguagesMap.has(document.languageId) ? enabledLanguagesMap.get(document.languageId)! : enabledLanguagesMap.get('*')!;
134139
}
140+
// --- End Positron ---
135141

136142
public async provideInlineCompletionItems(
137143
document: TextDocument,
@@ -141,7 +147,15 @@ export class InlineCompletionProviderImpl implements InlineCompletionItemProvide
141147
): Promise<NesCompletionList | undefined> {
142148
const tracer = this._tracer.sub(['provideInlineCompletionItems', shortOpportunityId(context.requestUuid)]);
143149

150+
// --- Start Positron ---
151+
// If inline completions are disabled for this language, don't
152+
// provide any completions.
144153
const isCompletionsEnabled = this._isCompletionsEnabled(document);
154+
if (!isCompletionsEnabled) {
155+
tracer.returns('inline completions disabled for this language');
156+
return undefined;
157+
}
158+
// --- End Positron ---
145159

146160
const unification = this._configurationService.getExperimentBasedConfig(ConfigKey.Internal.InlineEditsUnification, this._expService);
147161

src/extension/inlineEdits/vscode-node/parts/documentFilter.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService
99
import { isNotebookCellOrNotebookChatInput } from '../../../../util/common/notebooks';
1010
import { derived } from '../../../../util/vs/base/common/observableInternal';
1111

12+
// --- Start Positron ---
13+
import { PositronInlineCompletionsEnableConfigKey, PositronInlineCompletionsEnableDefault } from '../../common/positronConfig';
14+
// --- End Positron ---
15+
1216
export class DocumentFilter {
1317
private readonly _enabledLanguagesObs;
1418
private readonly _ignoreCompletionsDisablement;
@@ -17,7 +21,13 @@ export class DocumentFilter {
1721
@IIgnoreService private readonly _ignoreService: IIgnoreService,
1822
@IConfigurationService private readonly _configurationService: IConfigurationService
1923
) {
20-
this._enabledLanguagesObs = this._configurationService.getConfigObservable(ConfigKey.Enable);
24+
// --- Start Positron ---
25+
// Use Positron's inline completions enable config key instead of Copilot's
26+
this._enabledLanguagesObs = this._configurationService.getNonExtensionConfigObservable<{ [key: string]: boolean }>(
27+
PositronInlineCompletionsEnableConfigKey,
28+
PositronInlineCompletionsEnableDefault
29+
);
30+
// --- End Positron ---
2131
this._ignoreCompletionsDisablement = this._configurationService.getConfigObservable(ConfigKey.Internal.InlineEditsIgnoreCompletionsDisablement);
2232
}
2333

src/platform/authentication/vscode-node/session.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ import { AuthPermissionMode, AuthProviderId, ConfigKey, IConfigurationService }
99
import { GITHUB_SCOPE_ALIGNED, GITHUB_SCOPE_READ_USER, GITHUB_SCOPE_USER_EMAIL, MinimalModeError } from '../common/authentication';
1010
import { mixin } from '../../../util/vs/base/common/objects';
1111

12-
// --- Start Positron ---
13-
import { getFileBasedAuthSession } from './fileBasedAuth';
14-
// --- End Positron ---
15-
1612
export const SESSION_LOGIN_MESSAGE = 'You are not signed in to GitHub. Please sign in to use Copilot.';
1713
// These types are subsets of the "real" types AuthenticationSessionAccountInformation and
1814
// AuthenticationSession. They allow us to use the type system to validate which fields
@@ -73,15 +69,6 @@ async function getAuthSession(providerId: string, defaultScopes: string[], getSi
7369
* @deprecated use `IAuthenticationService` instead
7470
*/
7571
export function getAnyAuthSession(configurationService: IConfigurationService, options?: AuthenticationGetSessionOptions): Promise<AuthenticationSession | undefined> {
76-
// --- Start Positron ---
77-
// First, try to get authentication from the file-based GitHub Copilot apps.json
78-
const fileBasedSession = getFileBasedAuthSession();
79-
if (fileBasedSession) {
80-
return Promise.resolve(fileBasedSession);
81-
}
82-
83-
// Fall back to VS Code authentication system
84-
// --- End Positron ---
8572

8673
const providerId = authProviderId(configurationService);
8774

src/platform/configuration/common/configurationService.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ export interface IConfigurationService {
117117
*/
118118
getNonExtensionConfig<T>(configKey: string): T | undefined;
119119

120+
// --- Start Positron ---
121+
122+
/**
123+
* Gets an observable for a configuration value that is not in the Copilot namespace.
124+
* @param configKey The fully qualified config key to look up (e.g., 'positron.assistant.inlineCompletions.enable')
125+
* @param defaultValue The default value to use if the config is not set
126+
*/
127+
getNonExtensionConfigObservable<T>(configKey: string, defaultValue: T): IObservable<T>;
128+
// --- End Positron ---
129+
120130
/**
121131
* Sets user configuration for a key in vscode.
122132
*/
@@ -260,6 +270,29 @@ export abstract class AbstractConfigurationService extends Disposable implements
260270

261271
private observables = new Map<string, IObservable<any>>();
262272

273+
// --- Start Positron ---
274+
public getNonExtensionConfigObservable<T>(configKey: string, defaultValue: T): IObservable<T> {
275+
return this._getNonExtensionObservable(configKey, defaultValue);
276+
}
277+
278+
private _getNonExtensionObservable<T>(configKey: string, defaultValue: T): IObservable<T> {
279+
let observable = this.observables.get(configKey);
280+
if (!observable) {
281+
observable = observableFromEventOpts(
282+
{ debugName: () => `Non-Extension Configuration Key "${configKey}"` },
283+
(handleChange) => this._register(this.onDidChangeConfiguration(e => {
284+
if (e.affectsConfiguration(configKey)) {
285+
handleChange(e);
286+
}
287+
})),
288+
() => this.getNonExtensionConfig(configKey) ?? defaultValue
289+
);
290+
this.observables.set(configKey, observable);
291+
}
292+
return observable;
293+
}
294+
// --- End Positron ---
295+
263296
private _getObservable_$show2FramesUp<T>(key: BaseConfig<T>, getValue: () => T): IObservable<T> {
264297
let observable = this.observables.get(key.id);
265298
if (!observable) {
@@ -739,6 +772,9 @@ export namespace ConfigKey {
739772
export const Gpt5AlternativePatch = defineExpSetting<boolean>('chat.advanced.gpt5AlternativePatch', false);
740773
}
741774

775+
// --- Start Positron ---
776+
// Note: Not used in Positron, but kept here to avoid breaking changes
777+
// --- End Positron ---
742778
export const Enable = defineSetting<{ [key: string]: boolean }>('enable', {
743779
"*": true,
744780
"plaintext": false,

src/platform/configuration/test/common/inMemoryConfigurationService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,17 @@ export class InMemoryConfigurationService extends AbstractConfigurationService {
4646

4747
override setConfig<T>(key: BaseConfig<T>, value: T): Promise<void> {
4848
this.overrides.set(key, value);
49+
// --- Start Positron ---
50+
this._onDidChangeConfiguration.fire({ affectsConfiguration: (k) => k === key.fullyQualifiedId });
51+
// --- End Positron ---
4952
return Promise.resolve();
5053
}
5154

5255
setNonExtensionConfig<T>(key: string, value: T): Promise<void> {
5356
this.nonExtensionOverrides.set(key, value);
57+
// --- Start Positron ---
58+
this._onDidChangeConfiguration.fire({ affectsConfiguration: (k) => k === key });
59+
// --- End Positron ---
5460
return Promise.resolve();
5561
}
5662

0 commit comments

Comments
 (0)