deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as React from '@theia/core/shared/react';
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { DialogProps, DialogError } from '@theia/core/lib/browser/dialogs';
import { ReactDialog } from '@theia/core/lib/browser/dialogs/react-dialog';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { CommandService, nls } from '@theia/core';
import { CopilotAuthService, DeviceCodeResponse } from '../common/copilot-auth-service';
const OPEN_AI_CONFIG_VIEW_COMMAND = 'aiConfiguration:open';
type AuthDialogState = 'loading' | 'waiting' | 'polling' | 'success' | 'error';
@injectable()
export class CopilotAuthDialogProps extends DialogProps {
enterpriseUrl?: string;
}
@injectable()
export class CopilotAuthDialog extends ReactDialog<boolean> {
@inject(CopilotAuthService)
protected readonly authService: CopilotAuthService;
@inject(ClipboardService)
protected readonly clipboardService: ClipboardService;
@inject(WindowService)
protected readonly windowService: WindowService;
@inject(CommandService)
protected readonly commandService: CommandService;
protected state: AuthDialogState = 'loading';
protected deviceCodeResponse?: DeviceCodeResponse;
protected errorMessage?: string;
protected copied = false;
static readonly ID = 'copilot-auth-dialog';
constructor(
@inject(CopilotAuthDialogProps) protected override readonly props: CopilotAuthDialogProps
) {
super(props);
}
@postConstruct()
protected init(): void {
this.titleNode.textContent = nls.localize('theia/ai/copilot/auth/title', 'Sign in to GitHub Copilot');
this.appendAcceptButton(nls.localize('theia/ai/copilot/auth/authorize', 'I have authorized'));
this.appendCloseButton(nls.localizeByDefault('Cancel'));
}
protected updateButtonStates(): void {
const isPolling = this.state === 'polling';
const isSuccess = this.state === 'success';
if (this.acceptButton) {
this.acceptButton.disabled = isPolling || isSuccess;
if (isSuccess) {
this.acceptButton.style.display = 'none';
}
}
if (this.closeButton) {
if (isSuccess) {
this.closeButton.textContent = nls.localizeByDefault('Close');
}
}
}
override async open(): Promise<boolean | undefined> {
this.initiateFlow();
return super.open();
}
override update(): void {
super.update();
this.updateButtonStates();
}
protected async initiateFlow(): Promise<void> {
try {
this.state = 'loading';
this.update();
this.deviceCodeResponse = await this.authService.initiateDeviceFlow(this.props.enterpriseUrl);
this.state = 'waiting';
this.update();
} catch (error) {
this.state = 'error';
this.errorMessage = error instanceof Error ? error.message : String(error);
this.update();
}
}
protected override async accept(): Promise<void> {
if (this.state !== 'waiting' || !this.deviceCodeResponse) {
return;
}
this.state = 'polling';
this.update();
try {
const success = await this.authService.pollForToken(
this.deviceCodeResponse.device_code,
this.deviceCodeResponse.interval,
this.props.enterpriseUrl
);
if (success) {
this.state = 'success';
this.update();
} else {
this.state = 'error';
this.errorMessage = nls.localize('theia/ai/copilot/auth/expired',
'Authorization expired or was denied. Please try again.');
this.update();
}
} catch (error) {
this.state = 'error';
this.errorMessage = error instanceof Error ? error.message : String(error);
this.update();
}
}
get value(): boolean {
return this.state === 'success';
}
protected override isValid(_value: boolean, _mode: DialogError): DialogError {
if (this.state === 'error') {
return this.errorMessage ?? 'An error occurred';
}
return '';
}
protected handleCopyCode = async (): Promise<void> => {
if (this.deviceCodeResponse) {
await this.clipboardService.writeText(this.deviceCodeResponse.user_code);
this.copied = true;
this.update();
setTimeout(() => {
this.copied = false;
this.update();
}, 2000);
}
};
protected handleOpenUrl = (): void => {
if (this.deviceCodeResponse) {
this.windowService.openNewWindow(this.deviceCodeResponse.verification_uri, { external: true });
}
};
protected render(): React.ReactNode {
return (
<div className="theia-copilot-auth-dialog-content">
{this.renderContent()}
</div>
);
}
protected renderContent(): React.ReactNode {
switch (this.state) {
case 'loading':
return this.renderLoading();
case 'waiting':
return this.renderWaiting();
case 'polling':
return this.renderPolling();
case 'success':
return this.renderSuccess();
case 'error':
return this.renderError();
default:
return undefined;
}
}
protected renderLoading(): React.ReactNode {
return (
<div className="theia-copilot-auth-state">
<div className="theia-spin">
<span className="codicon codicon-loading"></span>
</div>
<p>{nls.localize('theia/ai/copilot/auth/initiating', 'Initiating authentication...')}</p>
</div>
);
}
protected renderWaiting(): React.ReactNode {
const response = this.deviceCodeResponse!;
return (
<div className="theia-copilot-auth-waiting">
<p className="theia-copilot-auth-instructions">
{nls.localize('theia/ai/copilot/auth/instructions',
'To authorize Theia to use GitHub Copilot, visit the URL below and enter the code:')}
</p>
<div className="theia-copilot-auth-code-section">
<div className="theia-copilot-auth-code-display">
<span className="theia-copilot-auth-code">{response.user_code}</span>
<button
className="theia-button secondary theia-copilot-copy-button"
onClick={this.handleCopyCode}
title={this.copied
? nls.localize('theia/ai/copilot/auth/copied', 'Copied!')
: nls.localize('theia/ai/copilot/auth/copyCode', 'Copy code')}
>
<span className={`codicon ${this.copied ? 'codicon-check' : 'codicon-copy'}`}></span>
{nls.localizeByDefault('Copy')}
</button>
</div>
</div>
<div className="theia-copilot-auth-url-section">
<button
className="theia-button theia-copilot-open-url-button"
onClick={this.handleOpenUrl}
>
<span className="codicon codicon-link-external"></span>
{nls.localize('theia/ai/copilot/auth/openGitHub', 'Open GitHub')}
</button>
<span className="theia-copilot-auth-url">{response.verification_uri}</span>
</div>
<p className="theia-copilot-auth-hint">
{nls.localize('theia/ai/copilot/auth/hint',
'After entering the code and authorizing, click "I have authorized" below.')}
</p>
<div className="theia-copilot-auth-privacy">
<p className="theia-copilot-auth-privacy-text">
{nls.localize('theia/ai/copilot/auth/privacy',
'Theia is an open-source project. We only request access to your GitHub username ' +
'to connect to GitHub Copilot services — no other data is accessed or stored.')}
</p>
<p className="theia-copilot-auth-tos-text">
{nls.localize('theia/ai/copilot/auth/tos',
'By signing in, you agree to the ')}
<a
href="https://docs.github.com/en/site-policy/github-terms/github-terms-of-service"
target="_blank"
rel="noopener noreferrer"
onClick={this.handleOpenTos}
>
{nls.localize('theia/ai/copilot/auth/tosLink', 'GitHub Terms of Service')}
</a>.
</p>
</div>
</div>
);
}
protected handleOpenTos = (e: React.MouseEvent): void => {
e.preventDefault();
this.windowService.openNewWindow('https://docs.github.com/en/site-policy/github-terms/github-terms-of-service', { external: true });
};
protected renderPolling(): React.ReactNode {
return (
<div className="theia-copilot-auth-state">
<div className="theia-spin">
<span className="codicon codicon-loading"></span>
</div>
<p>{nls.localize('theia/ai/copilot/auth/verifying', 'Verifying authorization...')}</p>
</div>
);
}
protected renderSuccess(): React.ReactNode {
return (
<div className="theia-copilot-auth-state theia-copilot-auth-success">
<span className="codicon codicon-check"></span>
<p>{nls.localize('theia/ai/copilot/auth/success', 'Successfully signed in to GitHub Copilot!')}</p>
<p className="theia-copilot-auth-success-hint">
{nls.localize('theia/ai/copilot/auth/successHint',
'If your GitHub account has access to Copilot, you can now configure Copilot language models in the ')}
<a href="#" onClick={this.handleOpenAIConfig}>
{nls.localize('theia/ai/copilot/auth/aiConfiguration', 'AI Configuration')}
</a>.
</p>
</div>
);
}
protected handleOpenAIConfig = (e: React.MouseEvent): void => {
e.preventDefault();
this.commandService.executeCommand(OPEN_AI_CONFIG_VIEW_COMMAND);
};
protected handleRetry = (): void => {
this.initiateFlow();
};
protected renderError(): React.ReactNode {
return (
<div className="theia-copilot-auth-state theia-copilot-auth-error">
<span className="codicon codicon-error"></span>
<p>{this.errorMessage}</p>
<button
className="theia-button"
onClick={this.handleRetry}
>
{nls.localizeByDefault('Try Again')}
</button>
</div>
);
}
}

View File

@@ -0,0 +1,93 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { Command, CommandContribution, CommandRegistry, Disposable, DisposableCollection, PreferenceService } from '@theia/core';
import { CopilotAuthService, CopilotAuthState } from '../common/copilot-auth-service';
import { CopilotAuthDialog, CopilotAuthDialogProps } from './copilot-auth-dialog';
import { COPILOT_ENTERPRISE_URL_PREF } from '../common/copilot-preferences';
export namespace CopilotCommands {
export const SIGN_IN: Command = Command.toLocalizedCommand(
{ id: 'copilot.signIn', label: 'Sign in to GitHub Copilot', category: 'Copilot' },
'theia/ai/copilot/commands/signIn',
'theia/ai/copilot/category'
);
export const SIGN_OUT: Command = Command.toLocalizedCommand(
{ id: 'copilot.signOut', label: 'Sign out of GitHub Copilot', category: 'Copilot' },
'theia/ai/copilot/commands/signOut',
'theia/ai/copilot/category'
);
}
/**
* Command contribution for GitHub Copilot authentication commands.
*/
@injectable()
export class CopilotCommandContribution implements CommandContribution, Disposable {
@inject(CopilotAuthService)
protected readonly authService: CopilotAuthService;
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@inject(CopilotAuthDialogProps)
protected readonly dialogProps: CopilotAuthDialogProps;
@inject(CopilotAuthDialog)
protected readonly authDialog: CopilotAuthDialog;
protected authState: CopilotAuthState = { isAuthenticated: false };
protected readonly toDispose = new DisposableCollection();
@postConstruct()
protected init(): void {
this.authService.getAuthState().then(state => {
this.authState = state;
});
this.toDispose.push(this.authService.onAuthStateChanged(state => {
this.authState = state;
}));
}
dispose(): void {
this.toDispose.dispose();
}
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(CopilotCommands.SIGN_IN, {
execute: async () => {
const enterpriseUrl = this.preferenceService.get<string>(COPILOT_ENTERPRISE_URL_PREF);
this.dialogProps.enterpriseUrl = enterpriseUrl || undefined;
const result = await this.authDialog.open();
if (result) {
this.authState = await this.authService.getAuthState();
}
},
isEnabled: () => !this.authState.isAuthenticated
});
registry.registerCommand(CopilotCommands.SIGN_OUT, {
execute: async () => {
await this.authService.signOut();
},
isEnabled: () => this.authState.isAuthenticated
});
}
}

View File

@@ -0,0 +1,89 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable } from '@theia/core/shared/inversify';
import { PreferenceService } from '@theia/core';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { CopilotLanguageModelsManager, CopilotModelDescription, COPILOT_PROVIDER_ID } from '../common';
import { COPILOT_MODELS_PREF, COPILOT_ENTERPRISE_URL_PREF } from '../common/copilot-preferences';
import { AICorePreferences, PREFERENCE_NAME_MAX_RETRIES } from '@theia/ai-core/lib/common/ai-core-preferences';
@injectable()
export class CopilotFrontendApplicationContribution implements FrontendApplicationContribution {
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@inject(CopilotLanguageModelsManager)
protected readonly manager: CopilotLanguageModelsManager;
@inject(AICorePreferences)
protected readonly aiCorePreferences: AICorePreferences;
protected prevModels: string[] = [];
onStart(): void {
this.preferenceService.ready.then(() => {
const models = this.preferenceService.get<string[]>(COPILOT_MODELS_PREF, []);
this.manager.createOrUpdateLanguageModels(...models.map((modelId: string) => this.createCopilotModelDescription(modelId)));
this.prevModels = [...models];
this.preferenceService.onPreferenceChanged(event => {
if (event.preferenceName === COPILOT_MODELS_PREF) {
this.handleModelChanges(this.preferenceService.get<string[]>(COPILOT_MODELS_PREF, []));
} else if (event.preferenceName === COPILOT_ENTERPRISE_URL_PREF) {
this.manager.refreshModelsStatus();
}
});
this.aiCorePreferences.onPreferenceChanged(event => {
if (event.preferenceName === PREFERENCE_NAME_MAX_RETRIES) {
this.updateAllModels();
}
});
});
}
protected handleModelChanges(newModels: string[]): void {
const oldModels = new Set(this.prevModels);
const updatedModels = new Set(newModels);
const modelsToRemove = [...oldModels].filter(model => !updatedModels.has(model));
const modelsToAdd = [...updatedModels].filter(model => !oldModels.has(model));
this.manager.removeLanguageModels(...modelsToRemove.map(model => `${COPILOT_PROVIDER_ID}/${model}`));
this.manager.createOrUpdateLanguageModels(...modelsToAdd.map((modelId: string) => this.createCopilotModelDescription(modelId)));
this.prevModels = newModels;
}
protected updateAllModels(): void {
const models = this.preferenceService.get<string[]>(COPILOT_MODELS_PREF, []);
this.manager.createOrUpdateLanguageModels(...models.map((modelId: string) => this.createCopilotModelDescription(modelId)));
}
protected createCopilotModelDescription(modelId: string): CopilotModelDescription {
const id = `${COPILOT_PROVIDER_ID}/${modelId}`;
const maxRetries = this.aiCorePreferences.get(PREFERENCE_NAME_MAX_RETRIES) ?? 3;
return {
id,
model: modelId,
enableStreaming: true,
supportsStructuredOutput: true,
maxRetries
};
}
}

View File

@@ -0,0 +1,86 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import '../../src/browser/style/index.css';
import { ContainerModule } from '@theia/core/shared/inversify';
import { CommandContribution, Emitter, Event, nls, PreferenceContribution } from '@theia/core';
import {
FrontendApplicationContribution,
RemoteConnectionProvider,
ServiceConnectionProvider
} from '@theia/core/lib/browser';
import {
CopilotLanguageModelsManager,
COPILOT_LANGUAGE_MODELS_MANAGER_PATH,
CopilotAuthService,
COPILOT_AUTH_SERVICE_PATH,
CopilotAuthServiceClient,
CopilotAuthState
} from '../common';
import { CopilotPreferencesSchema } from '../common/copilot-preferences';
import { CopilotFrontendApplicationContribution } from './copilot-frontend-application-contribution';
import { CopilotCommandContribution } from './copilot-command-contribution';
import { CopilotStatusBarContribution } from './copilot-status-bar-contribution';
import { CopilotAuthDialog, CopilotAuthDialogProps } from './copilot-auth-dialog';
class CopilotAuthServiceClientImpl implements CopilotAuthServiceClient {
protected readonly onAuthStateChangedEmitter = new Emitter<CopilotAuthState>();
readonly onAuthStateChangedEvent: Event<CopilotAuthState> = this.onAuthStateChangedEmitter.event;
onAuthStateChanged(state: CopilotAuthState): void {
this.onAuthStateChangedEmitter.fire(state);
}
}
export default new ContainerModule(bind => {
bind(PreferenceContribution).toConstantValue({ schema: CopilotPreferencesSchema });
bind(CopilotCommandContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(CopilotCommandContribution);
bind(CopilotStatusBarContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(CopilotStatusBarContribution);
bind(CopilotAuthDialogProps).toConstantValue({
title: nls.localize('theia/ai/copilot/commands/signIn', 'Sign in to GitHub Copilot')
});
bind(CopilotAuthDialog).toSelf().inSingletonScope();
bind(CopilotFrontendApplicationContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(CopilotFrontendApplicationContribution);
bind(CopilotAuthServiceClientImpl).toConstantValue(new CopilotAuthServiceClientImpl());
bind(CopilotAuthServiceClient).toService(CopilotAuthServiceClientImpl);
bind(CopilotLanguageModelsManager).toDynamicValue(ctx => {
const provider = ctx.container.get<ServiceConnectionProvider>(RemoteConnectionProvider);
return provider.createProxy<CopilotLanguageModelsManager>(COPILOT_LANGUAGE_MODELS_MANAGER_PATH);
}).inSingletonScope();
bind(CopilotAuthService).toDynamicValue(ctx => {
const provider = ctx.container.get<ServiceConnectionProvider>(RemoteConnectionProvider);
const clientImpl = ctx.container.get(CopilotAuthServiceClientImpl);
const proxy = provider.createProxy<CopilotAuthService>(COPILOT_AUTH_SERVICE_PATH, clientImpl);
return new Proxy(proxy, {
get(target: CopilotAuthService, prop: string | symbol, receiver: unknown): unknown {
if (prop === 'onAuthStateChanged') {
return clientImpl.onAuthStateChangedEvent;
}
return Reflect.get(target, prop, receiver);
}
}) as CopilotAuthService;
}).inSingletonScope();
});

View File

@@ -0,0 +1,88 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
import { StatusBar, StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar-types';
import { Disposable, DisposableCollection, nls } from '@theia/core';
import { CopilotAuthService, CopilotAuthState } from '../common/copilot-auth-service';
import { CopilotCommands } from './copilot-command-contribution';
const COPILOT_STATUS_BAR_ID = 'copilot-auth-status';
/**
* Frontend contribution that displays GitHub Copilot authentication status in the status bar.
*/
@injectable()
export class CopilotStatusBarContribution implements FrontendApplicationContribution, Disposable {
@inject(StatusBar)
protected readonly statusBar: StatusBar;
@inject(CopilotAuthService)
protected readonly authService: CopilotAuthService;
protected authState: CopilotAuthState = { isAuthenticated: false };
protected readonly toDispose = new DisposableCollection();
@postConstruct()
protected init(): void {
this.toDispose.push(this.authService.onAuthStateChanged(state => {
this.authState = state;
this.updateStatusBar();
}));
}
dispose(): void {
this.toDispose.dispose();
}
onStart(): void {
this.authService.getAuthState().then(state => {
this.authState = state;
this.updateStatusBar();
});
}
protected updateStatusBar(): void {
const isAuthenticated = this.authState.isAuthenticated;
let text: string;
let tooltip: string;
let command: string;
if (isAuthenticated) {
const accountLabel = this.authState.accountLabel ?? 'GitHub';
text = `$(github) ${accountLabel}`;
tooltip = nls.localize('theia/ai/copilot/statusBar/signedIn',
'Signed in to GitHub Copilot as {0}. Click to sign out.', accountLabel);
command = CopilotCommands.SIGN_OUT.id;
} else {
text = '$(github) Copilot';
tooltip = nls.localize('theia/ai/copilot/statusBar/signedOut',
'Not signed in to GitHub Copilot. Click to sign in.');
command = CopilotCommands.SIGN_IN.id;
}
this.statusBar.setElement(COPILOT_STATUS_BAR_ID, {
text,
tooltip,
alignment: StatusBarAlignment.RIGHT,
priority: 100,
command
});
}
}

View File

@@ -0,0 +1,20 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './copilot-auth-dialog';
export * from './copilot-command-contribution';
export * from './copilot-status-bar-contribution';
export * from './copilot-frontend-application-contribution';

View File

@@ -0,0 +1,167 @@
/* *****************************************************************************
* Copyright (C) 2026 EclipseSource GmbH.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
**************************************************************************** */
.theia-copilot-auth-dialog-content {
padding: 16px;
min-width: 400px;
}
.theia-copilot-auth-state {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 24px;
}
.theia-copilot-auth-state .theia-spin {
font-size: 32px;
}
.theia-copilot-auth-success .codicon-check {
font-size: 48px;
color: var(--theia-successBackground);
}
.theia-copilot-auth-error .codicon-error {
font-size: 48px;
color: var(--theia-errorForeground);
}
.theia-copilot-auth-waiting {
display: flex;
flex-direction: column;
gap: 16px;
}
.theia-copilot-auth-instructions {
margin: 0;
line-height: 1.5;
}
.theia-copilot-auth-code-section {
display: flex;
justify-content: center;
}
.theia-copilot-auth-code-display {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--theia-editor-background);
border-radius: 4px;
border: 1px solid var(--theia-panel-border);
}
.theia-copilot-auth-code {
font-family: var(--theia-code-font-family);
font-size: 24px;
font-weight: bold;
letter-spacing: 2px;
color: var(--theia-textLink-foreground);
}
.theia-copilot-copy-button {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
}
.theia-copilot-auth-url-section {
display: flex;
align-items: center;
gap: 12px;
justify-content: center;
}
.theia-copilot-open-url-button {
display: flex;
align-items: center;
gap: 6px;
}
.theia-copilot-auth-url {
font-family: var(--theia-code-font-family);
font-size: 12px;
color: var(--theia-descriptionForeground);
}
.theia-copilot-auth-hint {
margin: 0;
font-size: 12px;
color: var(--theia-descriptionForeground);
text-align: center;
}
.theia-copilot-auth-privacy {
margin-top: 8px;
padding-top: 12px;
border-top: 1px solid var(--theia-panel-border);
}
.theia-copilot-auth-privacy-text,
.theia-copilot-auth-tos-text {
margin: 0 0 8px 0;
font-size: 11px;
color: var(--theia-descriptionForeground);
line-height: 1.4;
}
.theia-copilot-auth-tos-text:last-child {
margin-bottom: 0;
}
.theia-copilot-auth-privacy a {
color: var(--theia-textLink-foreground);
text-decoration: none;
}
.theia-copilot-auth-privacy a:hover {
text-decoration: underline;
}
.theia-copilot-auth-success-hint {
margin: 8px 0 0 0;
font-size: 12px;
color: var(--theia-descriptionForeground);
text-align: center;
}
.theia-copilot-auth-success-hint a {
color: var(--theia-textLink-foreground);
text-decoration: none;
cursor: pointer;
}
.theia-copilot-auth-success-hint a:hover {
text-decoration: underline;
}
/* Spinning animation for loading indicator */
.theia-spin .codicon {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,103 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { Event } from '@theia/core';
export const COPILOT_AUTH_SERVICE_PATH = '/services/copilot/auth';
export const CopilotAuthService = Symbol('CopilotAuthService');
export const CopilotAuthServiceClient = Symbol('CopilotAuthServiceClient');
/**
* Response from GitHub's device code endpoint.
*/
export interface DeviceCodeResponse {
/** URL where user should enter the code (e.g., https://github.com/login/device) */
verification_uri: string;
/** Code to display to the user (e.g., XXXX-XXXX) */
user_code: string;
/** Device code used for polling */
device_code: string;
/** Polling interval in seconds */
interval: number;
/** Expiration time in seconds */
expires_in: number;
}
/**
* Current authentication state.
*/
export interface CopilotAuthState {
/** Whether the user is authenticated */
isAuthenticated: boolean;
/** GitHub username if authenticated */
accountLabel?: string;
/** GitHub Enterprise URL if using enterprise */
enterpriseUrl?: string;
}
/**
* Client interface for receiving auth state change notifications.
*/
export interface CopilotAuthServiceClient {
onAuthStateChanged(state: CopilotAuthState): void;
}
/**
* Service for handling GitHub Copilot OAuth Device Flow authentication.
*/
export interface CopilotAuthService {
/**
* Initiates the OAuth Device Flow.
* Returns device code information for the UI to display.
* @param enterpriseUrl Optional GitHub Enterprise domain
*/
initiateDeviceFlow(enterpriseUrl?: string): Promise<DeviceCodeResponse>;
/**
* Polls for the access token after user authorizes.
* @param deviceCode The device code from initiateDeviceFlow
* @param interval Polling interval in seconds
* @param enterpriseUrl Optional GitHub Enterprise domain
* @returns true if authentication succeeded, false if expired/denied
*/
pollForToken(deviceCode: string, interval: number, enterpriseUrl?: string): Promise<boolean>;
/**
* Get the current authentication state.
*/
getAuthState(): Promise<CopilotAuthState>;
/**
* Get the access token for API calls.
* @returns The access token or undefined if not authenticated
*/
getAccessToken(): Promise<string | undefined>;
/**
* Sign out and clear stored credentials.
*/
signOut(): Promise<void>;
/**
* Set the client to receive auth state change notifications.
*/
setClient(client: CopilotAuthServiceClient | undefined): void;
/**
* Event fired when authentication state changes.
*/
readonly onAuthStateChanged: Event<CopilotAuthState>;
}

View File

@@ -0,0 +1,59 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export const COPILOT_LANGUAGE_MODELS_MANAGER_PATH = '/services/copilot/language-model-manager';
export const CopilotLanguageModelsManager = Symbol('CopilotLanguageModelsManager');
export const COPILOT_PROVIDER_ID = 'copilot';
export interface CopilotModelDescription {
/**
* The identifier of the model which will be shown in the UI.
* Format: copilot/{modelName}
*/
id: string;
/**
* The model ID as used by the Copilot API (e.g., 'gpt-4o', 'claude-3.5-sonnet').
*/
model: string;
/**
* Indicate whether the streaming API shall be used.
*/
enableStreaming: boolean;
/**
* Flag to configure whether the model supports structured output.
*/
supportsStructuredOutput: boolean;
/**
* Maximum number of retry attempts when a request fails.
*/
maxRetries: number;
}
export interface CopilotLanguageModelsManager {
/**
* Create or update language models in the registry.
*/
createOrUpdateLanguageModels(...models: CopilotModelDescription[]): Promise<void>;
/**
* Remove language models from the registry.
*/
removeLanguageModels(...modelIds: string[]): void;
/**
* Refresh the status of all Copilot models (e.g., after authentication state changes).
*/
refreshModelsStatus(): Promise<void>;
}

View File

@@ -0,0 +1,53 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { AI_CORE_PREFERENCES_TITLE } from '@theia/ai-core/lib/common/ai-core-preferences';
import { nls, PreferenceSchema } from '@theia/core';
export const COPILOT_MODELS_PREF = 'ai-features.copilot.models';
export const COPILOT_ENTERPRISE_URL_PREF = 'ai-features.copilot.enterpriseUrl';
export const CopilotPreferencesSchema: PreferenceSchema = {
properties: {
[COPILOT_MODELS_PREF]: {
type: 'array',
description: nls.localize('theia/ai/copilot/models/description',
'GitHub Copilot models to use. Available models depend on your Copilot subscription.'),
title: AI_CORE_PREFERENCES_TITLE,
// https://models.dev/?search=copilot
default: [
'claude-haiku-4.5',
'claude-sonnet-4.5',
'claude-opus-4.5',
'gemini-2.5-pro',
'gpt-4.1',
'gpt-4o',
'gpt-5-mini',
'gpt-5.2',
],
items: {
type: 'string'
}
},
[COPILOT_ENTERPRISE_URL_PREF]: {
type: 'string',
markdownDescription: nls.localize('theia/ai/copilot/enterpriseUrl/mdDescription',
'GitHub Enterprise domain for Copilot API (e.g., `github.mycompany.com`). Leave empty for GitHub.com.'),
title: AI_CORE_PREFERENCES_TITLE,
default: ''
}
}
};

View File

@@ -0,0 +1,19 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './copilot-language-models-manager';
export * from './copilot-auth-service';
export * from './copilot-preferences';

View File

@@ -0,0 +1,274 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable } from '@theia/core/shared/inversify';
import { Emitter, Event } from '@theia/core';
import { KeyStoreService } from '@theia/core/lib/common/key-store';
import {
CopilotAuthService,
CopilotAuthServiceClient,
CopilotAuthState,
DeviceCodeResponse
} from '../common/copilot-auth-service';
const COPILOT_CLIENT_ID = 'Iv23ctNZvWb5IGBKdyPY';
const COPILOT_SCOPE = 'read:user';
const COPILOT_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
const KEYSTORE_SERVICE = 'theia-copilot-auth';
const KEYSTORE_ACCOUNT = 'github-copilot';
const USER_AGENT = 'Theia-Copilot/1.0.0';
/**
* Maximum number of polling attempts for token retrieval.
* With a default 5-second interval, this allows approximately 5 minutes of polling.
*/
const MAX_POLLING_ATTEMPTS = 60;
interface StoredCredentials {
accessToken: string;
accountLabel?: string;
enterpriseUrl?: string;
}
/**
* Backend implementation of the GitHub Copilot OAuth Device Flow authentication service.
* Handles device code generation, token polling, and credential storage.
*/
@injectable()
export class CopilotAuthServiceImpl implements CopilotAuthService {
@inject(KeyStoreService)
protected readonly keyStoreService: KeyStoreService;
protected client: CopilotAuthServiceClient | undefined;
protected cachedState: CopilotAuthState | undefined;
protected readonly onAuthStateChangedEmitter = new Emitter<CopilotAuthState>();
readonly onAuthStateChanged: Event<CopilotAuthState> = this.onAuthStateChangedEmitter.event;
setClient(client: CopilotAuthServiceClient | undefined): void {
this.client = client;
}
protected getOAuthEndpoints(enterpriseUrl?: string): { deviceCodeUrl: string; accessTokenUrl: string } {
if (enterpriseUrl) {
const domain = enterpriseUrl
.replace(/^https?:\/\//, '')
.replace(/\/$/, '');
return {
deviceCodeUrl: `https://${domain}/login/device/code`,
accessTokenUrl: `https://${domain}/login/oauth/access_token`
};
}
return {
deviceCodeUrl: 'https://github.com/login/device/code',
accessTokenUrl: 'https://github.com/login/oauth/access_token'
};
}
async initiateDeviceFlow(enterpriseUrl?: string): Promise<DeviceCodeResponse> {
const endpoints = this.getOAuthEndpoints(enterpriseUrl);
const response = await fetch(endpoints.deviceCodeUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'User-Agent': USER_AGENT
},
body: JSON.stringify({
client_id: COPILOT_CLIENT_ID,
scope: COPILOT_SCOPE
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to initiate device authorization: ${response.status} - ${errorText}`);
}
const data = await response.json() as DeviceCodeResponse;
return data;
}
async pollForToken(deviceCode: string, interval: number, enterpriseUrl?: string): Promise<boolean> {
const endpoints = this.getOAuthEndpoints(enterpriseUrl);
let attempts = 0;
while (attempts < MAX_POLLING_ATTEMPTS) {
await this.delay(interval * 1000);
attempts++;
const response = await fetch(endpoints.accessTokenUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'User-Agent': USER_AGENT
},
body: JSON.stringify({
client_id: COPILOT_CLIENT_ID,
device_code: deviceCode,
grant_type: COPILOT_GRANT_TYPE
})
});
if (!response.ok) {
console.error(`Token request failed: ${response.status}`);
continue;
}
const data = await response.json() as {
access_token?: string;
error?: string;
error_description?: string;
};
if (data.access_token) {
// Get user info for account label
const accountLabel = await this.fetchAccountLabel(data.access_token, enterpriseUrl);
// Store credentials
const credentials: StoredCredentials = {
accessToken: data.access_token,
accountLabel,
enterpriseUrl
};
await this.keyStoreService.setPassword(
KEYSTORE_SERVICE,
KEYSTORE_ACCOUNT,
JSON.stringify(credentials)
);
// Update cached state and notify
const newState: CopilotAuthState = {
isAuthenticated: true,
accountLabel,
enterpriseUrl
};
this.updateAuthState(newState);
return true;
}
if (data.error === 'authorization_pending') {
// User hasn't authorized yet, continue polling
continue;
}
if (data.error === 'slow_down') {
// Increase polling interval
interval += 5;
continue;
}
if (data.error === 'expired_token' || data.error === 'access_denied') {
console.error(`Authorization failed: ${data.error} - ${data.error_description}`);
return false;
}
if (data.error) {
console.error(`Unexpected error: ${data.error} - ${data.error_description}`);
return false;
}
}
return false;
}
protected async fetchAccountLabel(accessToken: string, enterpriseUrl?: string): Promise<string | undefined> {
try {
const apiBaseUrl = enterpriseUrl
? `https://${enterpriseUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')}/api/v3`
: 'https://api.github.com';
const response = await fetch(`${apiBaseUrl}/user`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': USER_AGENT,
'Accept': 'application/vnd.github.v3+json'
}
});
if (response.ok) {
const userData = await response.json() as { login?: string };
return userData.login;
}
} catch (error) {
console.warn('Failed to fetch GitHub user info:', error);
}
return undefined;
}
async getAuthState(): Promise<CopilotAuthState> {
if (this.cachedState) {
return this.cachedState;
}
try {
const stored = await this.keyStoreService.getPassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
if (stored) {
const credentials: StoredCredentials = JSON.parse(stored);
this.cachedState = {
isAuthenticated: true,
accountLabel: credentials.accountLabel,
enterpriseUrl: credentials.enterpriseUrl
};
return this.cachedState;
}
} catch (error) {
console.warn('Failed to retrieve Copilot credentials:', error);
}
this.cachedState = { isAuthenticated: false };
return this.cachedState;
}
async getAccessToken(): Promise<string | undefined> {
try {
const stored = await this.keyStoreService.getPassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
if (stored) {
const credentials: StoredCredentials = JSON.parse(stored);
return credentials.accessToken;
}
} catch (error) {
console.warn('Failed to retrieve Copilot access token:', error);
}
return undefined;
}
async signOut(): Promise<void> {
try {
await this.keyStoreService.deletePassword(KEYSTORE_SERVICE, KEYSTORE_ACCOUNT);
} catch (error) {
console.warn('Failed to delete Copilot credentials:', error);
}
const newState: CopilotAuthState = { isAuthenticated: false };
this.updateAuthState(newState);
}
protected updateAuthState(state: CopilotAuthState): void {
this.cachedState = state;
this.onAuthStateChangedEmitter.fire(state);
this.client?.onAuthStateChanged(state);
}
protected delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

View File

@@ -0,0 +1,58 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ContainerModule } from '@theia/core/shared/inversify';
import { ConnectionHandler, RpcConnectionHandler } from '@theia/core';
import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
import {
CopilotLanguageModelsManager,
COPILOT_LANGUAGE_MODELS_MANAGER_PATH,
CopilotAuthService,
COPILOT_AUTH_SERVICE_PATH,
CopilotAuthServiceClient
} from '../common';
import { CopilotLanguageModelsManagerImpl } from './copilot-language-models-manager-impl';
import { CopilotAuthServiceImpl } from './copilot-auth-service-impl';
const copilotConnectionModule = ConnectionContainerModule.create(({ bind }) => {
bind(CopilotAuthServiceImpl).toSelf().inSingletonScope();
bind(CopilotAuthService).toService(CopilotAuthServiceImpl);
bind(CopilotLanguageModelsManagerImpl).toSelf().inSingletonScope();
bind(CopilotLanguageModelsManager).toService(CopilotLanguageModelsManagerImpl);
bind(ConnectionHandler).toDynamicValue(ctx =>
new RpcConnectionHandler<CopilotAuthServiceClient>(
COPILOT_AUTH_SERVICE_PATH,
client => {
const authService = ctx.container.get<CopilotAuthServiceImpl>(CopilotAuthService);
authService.setClient(client);
return authService;
}
)
).inSingletonScope();
bind(ConnectionHandler).toDynamicValue(ctx =>
new RpcConnectionHandler(
COPILOT_LANGUAGE_MODELS_MANAGER_PATH,
() => ctx.container.get(CopilotLanguageModelsManager)
)
).inSingletonScope();
});
export default new ContainerModule(bind => {
bind(ConnectionContainerModule).toConstantValue(copilotConnectionModule);
});

View File

@@ -0,0 +1,262 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import {
ImageContent,
LanguageModel,
LanguageModelMessage,
LanguageModelParsedResponse,
LanguageModelRequest,
LanguageModelResponse,
LanguageModelStatus,
LanguageModelTextResponse,
TokenUsageService,
UserRequest
} from '@theia/ai-core';
import { CancellationToken } from '@theia/core';
import OpenAI from 'openai';
import { RunnableToolFunctionWithoutParse } from 'openai/lib/RunnableFunction';
import { ChatCompletionMessageParam } from 'openai/resources';
import { StreamingAsyncIterator } from '@theia/ai-openai/lib/node/openai-streaming-iterator';
import { COPILOT_PROVIDER_ID } from '../common';
import type { RunnerOptions } from 'openai/lib/AbstractChatCompletionRunner';
import type { ChatCompletionStream } from 'openai/lib/ChatCompletionStream';
const COPILOT_API_BASE_URL = 'https://api.githubcopilot.com';
const USER_AGENT = 'Theia-Copilot/1.0.0';
/**
* Language model implementation for GitHub Copilot.
* Uses the OpenAI SDK to communicate with the Copilot API.
*/
export class CopilotLanguageModel implements LanguageModel {
protected runnerOptions: RunnerOptions = {
maxChatCompletions: 100
};
constructor(
public readonly id: string,
public model: string,
public status: LanguageModelStatus,
public enableStreaming: boolean,
public supportsStructuredOutput: boolean,
public maxRetries: number,
protected readonly accessTokenProvider: () => Promise<string | undefined>,
protected readonly enterpriseUrlProvider: () => string | undefined,
protected readonly tokenUsageService?: TokenUsageService
) { }
protected getSettings(request: LanguageModelRequest): Record<string, unknown> {
return request.settings ?? {};
}
async request(request: UserRequest, cancellationToken?: CancellationToken): Promise<LanguageModelResponse> {
const openai = await this.initializeCopilotClient();
if (request.response_format?.type === 'json_schema' && this.supportsStructuredOutput) {
return this.handleStructuredOutputRequest(openai, request);
}
const settings = this.getSettings(request);
if (!this.enableStreaming || (typeof settings.stream === 'boolean' && !settings.stream)) {
return this.handleNonStreamingRequest(openai, request);
}
if (cancellationToken?.isCancellationRequested) {
return { text: '' };
}
if (this.id.startsWith(`${COPILOT_PROVIDER_ID}/`)) {
settings['stream_options'] = { include_usage: true };
}
let runner: ChatCompletionStream;
const tools = this.createTools(request);
if (tools) {
runner = openai.chat.completions.runTools({
model: this.model,
messages: this.processMessages(request.messages),
stream: true,
tools: tools,
tool_choice: 'auto',
...settings
}, {
...this.runnerOptions,
maxRetries: this.maxRetries
});
} else {
runner = openai.chat.completions.stream({
model: this.model,
messages: this.processMessages(request.messages),
stream: true,
...settings
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return { stream: new StreamingAsyncIterator(runner as any, request.requestId, cancellationToken, this.tokenUsageService, this.id) };
}
protected async handleNonStreamingRequest(openai: OpenAI, request: UserRequest): Promise<LanguageModelTextResponse> {
const settings = this.getSettings(request);
const response = await openai.chat.completions.create({
model: this.model,
messages: this.processMessages(request.messages),
...settings
});
const message = response.choices[0].message;
if (this.tokenUsageService && response.usage) {
await this.tokenUsageService.recordTokenUsage(
this.id,
{
inputTokens: response.usage.prompt_tokens,
outputTokens: response.usage.completion_tokens,
requestId: request.requestId
}
);
}
return {
text: message.content ?? ''
};
}
protected async handleStructuredOutputRequest(openai: OpenAI, request: UserRequest): Promise<LanguageModelParsedResponse> {
const settings = this.getSettings(request);
const result = await openai.chat.completions.parse({
model: this.model,
messages: this.processMessages(request.messages),
response_format: request.response_format,
...settings
});
const message = result.choices[0].message;
if (message.refusal || message.parsed === undefined) {
console.error('Error in Copilot chat completion:', JSON.stringify(message));
}
if (this.tokenUsageService && result.usage) {
await this.tokenUsageService.recordTokenUsage(
this.id,
{
inputTokens: result.usage.prompt_tokens,
outputTokens: result.usage.completion_tokens,
requestId: request.requestId
}
);
}
return {
content: message.content ?? '',
parsed: message.parsed
};
}
protected createTools(request: LanguageModelRequest): RunnableToolFunctionWithoutParse[] | undefined {
return request.tools?.map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
function: (args_string: string) => tool.handler(args_string)
}
} as RunnableToolFunctionWithoutParse));
}
protected async initializeCopilotClient(): Promise<OpenAI> {
const accessToken = await this.accessTokenProvider();
if (!accessToken) {
throw new Error('Not authenticated with GitHub Copilot. Please sign in first.');
}
const enterpriseUrl = this.enterpriseUrlProvider();
const baseURL = enterpriseUrl
? `https://copilot-api.${enterpriseUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')}`
: COPILOT_API_BASE_URL;
return new OpenAI({
apiKey: accessToken,
baseURL,
defaultHeaders: {
'User-Agent': USER_AGENT,
'Openai-Intent': 'conversation-edits',
'X-Initiator': 'user'
}
});
}
protected processMessages(messages: LanguageModelMessage[]): ChatCompletionMessageParam[] {
return messages.filter(m => m.type !== 'thinking').map(m => this.toOpenAIMessage(m));
}
protected toOpenAIMessage(message: LanguageModelMessage): ChatCompletionMessageParam {
if (LanguageModelMessage.isTextMessage(message)) {
return {
role: this.toOpenAiRole(message),
content: message.text
};
}
if (LanguageModelMessage.isToolUseMessage(message)) {
return {
role: 'assistant',
tool_calls: [{
id: message.id,
function: {
name: message.name,
arguments: JSON.stringify(message.input)
},
type: 'function'
}]
};
}
if (LanguageModelMessage.isToolResultMessage(message)) {
return {
role: 'tool',
tool_call_id: message.tool_use_id,
content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content)
};
}
if (LanguageModelMessage.isImageMessage(message) && message.actor === 'user') {
return {
role: 'user',
content: [{
type: 'image_url',
image_url: {
url: ImageContent.isBase64(message.image)
? `data:${message.image.mimeType};base64,${message.image.base64data}`
: message.image.url
}
}]
};
}
throw new Error(`Unknown message type: '${JSON.stringify(message)}'`);
}
protected toOpenAiRole(message: LanguageModelMessage): 'developer' | 'user' | 'assistant' | 'system' {
if (message.actor === 'system') {
return 'developer';
} else if (message.actor === 'ai') {
return 'assistant';
}
return 'user';
}
}

View File

@@ -0,0 +1,118 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { LanguageModelRegistry, LanguageModelStatus, TokenUsageService } from '@theia/ai-core';
import { Disposable, DisposableCollection } from '@theia/core';
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import { CopilotLanguageModelsManager, CopilotModelDescription, COPILOT_PROVIDER_ID } from '../common';
import { CopilotLanguageModel } from './copilot-language-model';
import { CopilotAuthServiceImpl } from './copilot-auth-service-impl';
/**
* Backend implementation of the Copilot language models manager.
* Manages registration and lifecycle of Copilot language models in the AI language model registry.
*/
@injectable()
export class CopilotLanguageModelsManagerImpl implements CopilotLanguageModelsManager, Disposable {
@inject(LanguageModelRegistry)
protected readonly languageModelRegistry: LanguageModelRegistry;
@inject(TokenUsageService)
protected readonly tokenUsageService: TokenUsageService;
@inject(CopilotAuthServiceImpl)
protected readonly authService: CopilotAuthServiceImpl;
protected enterpriseUrl: string | undefined;
protected readonly toDispose = new DisposableCollection();
@postConstruct()
protected init(): void {
this.toDispose.push(this.authService.onAuthStateChanged(() => {
this.refreshModelsStatus();
}));
}
dispose(): void {
this.toDispose.dispose();
}
setEnterpriseUrl(url: string | undefined): void {
this.enterpriseUrl = url;
}
protected async calculateStatus(): Promise<LanguageModelStatus> {
const authState = await this.authService.getAuthState();
if (authState.isAuthenticated) {
return { status: 'ready' };
}
return { status: 'unavailable', message: 'Not signed in to GitHub Copilot' };
}
async createOrUpdateLanguageModels(...modelDescriptions: CopilotModelDescription[]): Promise<void> {
const status = await this.calculateStatus();
for (const modelDescription of modelDescriptions) {
const model = await this.languageModelRegistry.getLanguageModel(modelDescription.id);
if (model) {
if (!(model instanceof CopilotLanguageModel)) {
console.warn(`Copilot: model ${modelDescription.id} is not a Copilot model`);
continue;
}
await this.languageModelRegistry.patchLanguageModel<CopilotLanguageModel>(modelDescription.id, {
model: modelDescription.model,
enableStreaming: modelDescription.enableStreaming,
supportsStructuredOutput: modelDescription.supportsStructuredOutput,
status,
maxRetries: modelDescription.maxRetries
});
} else {
this.languageModelRegistry.addLanguageModels([
new CopilotLanguageModel(
modelDescription.id,
modelDescription.model,
status,
modelDescription.enableStreaming,
modelDescription.supportsStructuredOutput,
modelDescription.maxRetries,
() => this.authService.getAccessToken(),
() => this.enterpriseUrl,
this.tokenUsageService
)
]);
}
}
}
removeLanguageModels(...modelIds: string[]): void {
this.languageModelRegistry.removeLanguageModels(modelIds);
}
async refreshModelsStatus(): Promise<void> {
const status = await this.calculateStatus();
const allModels = await this.languageModelRegistry.getLanguageModels();
for (const model of allModels) {
if (model instanceof CopilotLanguageModel && model.id.startsWith(`${COPILOT_PROVIDER_ID}/`)) {
await this.languageModelRegistry.patchLanguageModel<CopilotLanguageModel>(model.id, {
status
});
}
}
}
}

View File

@@ -0,0 +1,19 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './copilot-auth-service-impl';
export * from './copilot-language-model';
export * from './copilot-language-models-manager-impl';

View File

@@ -0,0 +1,27 @@
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/* note: this bogus test file is required so that
we are able to run mocha unit tests on this
package, without having any actual unit tests in it.
This way a coverage report will be generated,
showing 0% coverage, instead of no report.
This file can be removed once we have real unit
tests in place. */
describe('ai-copilot package', () => {
it('support code coverage statistics', () => true);
});