diff --git a/src/extension/extension/vscode/extension.ts b/src/extension/extension/vscode/extension.ts index 57d3bfc646..1c69050602 100644 --- a/src/extension/extension/vscode/extension.ts +++ b/src/extension/extension/vscode/extension.ts @@ -74,6 +74,25 @@ export async function baseActivate(configuration: IExtensionActivationConfigurat await contributions.waitForActivationBlockers(); }); + // DEBUG: Test call to organization custom instructions service + instantiationService.invokeFunction(async accessor => { + const { IOrgCustomInstructionsService } = await import('../../../platform/customInstructions/common/orgCustomInstructionsService'); + const orgInstructionsService = accessor.get(IOrgCustomInstructionsService); + console.log('[DEBUG] Testing OrgCustomInstructionsService...'); + try { + const instructions = await orgInstructionsService.getOrgCustomInstructions(); + if (instructions) { + console.log('[DEBUG] Successfully fetched org custom instructions:', instructions.substring(0, 200) + (instructions.length > 200 ? '...' : '')); + } else { + console.log('[DEBUG] No org custom instructions available'); + } + } catch (error) { + console.error('[DEBUG] Error fetching org custom instructions:', error); + } + }).catch(error => { + console.error('[DEBUG] Failed to test OrgCustomInstructionsService:', error); + }); + if (ExtensionMode.Test === context.extensionMode && !isScenarioAutomation) { return instantiationService; // The returned accessor is used in tests } diff --git a/src/extension/extension/vscode/services.ts b/src/extension/extension/vscode/services.ts index 0817d374e3..0eefd2cb91 100644 --- a/src/extension/extension/vscode/services.ts +++ b/src/extension/extension/vscode/services.ts @@ -19,6 +19,7 @@ import { RunCommandExecutionServiceImpl } from '../../../platform/commands/vscod import { IConfigurationService } from '../../../platform/configuration/common/configurationService'; import { ConfigurationServiceImpl } from '../../../platform/configuration/vscode/configurationServiceImpl'; import { CustomInstructionsService, ICustomInstructionsService } from '../../../platform/customInstructions/common/customInstructionsService'; +import { IOrgCustomInstructionsService, OrgCustomInstructionsService } from '../../../platform/customInstructions/common/orgCustomInstructionsService'; import { IDebugOutputService } from '../../../platform/debug/common/debugOutputService'; import { DebugOutputServiceImpl } from '../../../platform/debug/vscode/debugOutputServiceImpl'; import { IDialogService } from '../../../platform/dialog/common/dialogService'; @@ -158,6 +159,7 @@ export function registerServices(builder: IInstantiationServiceBuilder, extensio builder.define(IEditLogService, new SyncDescriptor(EditLogService)); builder.define(IMultiFileEditInternalTelemetryService, new SyncDescriptor(MultiFileEditInternalTelemetryService)); builder.define(ICustomInstructionsService, new SyncDescriptor(CustomInstructionsService)); + builder.define(IOrgCustomInstructionsService, new SyncDescriptor(OrgCustomInstructionsService)); builder.define(ILaunchConfigService, new SyncDescriptor(LaunchConfigService)); builder.define(ISurveyService, new SyncDescriptor(SurveyService)); builder.define(IEditSurvivalTrackerService, new SyncDescriptor(EditSurvivalTrackerService)); diff --git a/src/platform/customInstructions/common/orgCustomInstructionsService.ts b/src/platform/customInstructions/common/orgCustomInstructionsService.ts new file mode 100644 index 0000000000..9fc07a41bb --- /dev/null +++ b/src/platform/customInstructions/common/orgCustomInstructionsService.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RequestType } from '@vscode/copilot-api'; +import { createServiceIdentifier } from '../../../util/common/services'; +import { IAuthenticationService } from '../../authentication/common/authentication'; +import { ICAPIClientService } from '../../endpoint/common/capiClient'; +import { ILogService } from '../../log/common/logService'; + +export const IOrgCustomInstructionsService = createServiceIdentifier('IOrgCustomInstructionsService'); + +export interface IOrgCustomInstructionsService { + readonly _serviceBrand: undefined; + + /** + * Fetches custom instructions for the current active GitHub organization + * @returns The custom instructions as a string, or undefined if not available + */ + getOrgCustomInstructions(): Promise; +} + +export class OrgCustomInstructionsService implements IOrgCustomInstructionsService { + readonly _serviceBrand: undefined; + + constructor( + @ICAPIClientService private readonly _capiClientService: ICAPIClientService, + @IAuthenticationService private readonly _authenticationService: IAuthenticationService, + @ILogService private readonly _logService: ILogService, + ) { } + + async getOrgCustomInstructions(): Promise { + try { + // Get the copilot token to access organization info + const copilotToken = await this._authenticationService.getCopilotToken(); + if (!copilotToken) { + this._logService.debug('[OrgCustomInstructions] No copilot token found'); + return undefined; + } + + // Check for numeric organization ID from enterprise list first (preferred) + let organizationId: number | undefined; + if (copilotToken.enterpriseList && copilotToken.enterpriseList.length > 0) { + organizationId = copilotToken.enterpriseList[0]; + this._logService.debug(`[OrgCustomInstructions] Using enterprise ID: ${organizationId}`); + } else if (copilotToken.organizationList && copilotToken.organizationList.length > 0) { + // Try to parse the organization ID from the organization list (hash-based IDs) + // Note: The organizationList contains hash strings, not numeric IDs + // For the example request showing organization_id: 12345, this would need to come from + // a different source or API that maps org names to numeric IDs + const orgHash = copilotToken.organizationList[0]; + this._logService.debug(`[OrgCustomInstructions] Organization hash found: ${orgHash}`); + this._logService.warn('[OrgCustomInstructions] Cannot convert organization hash to numeric ID. Numeric organization ID required.'); + return undefined; + } else { + this._logService.debug('[OrgCustomInstructions] No organization or enterprise ID found in copilot token'); + return undefined; + } + + this._logService.debug(`[OrgCustomInstructions] Fetching instructions for organization ID: ${organizationId}`); + + const response = await this._capiClientService.makeRequest( + { + method: 'POST', + json: { organization_id: organizationId }, + headers: { 'Content-Type': 'application/json' } + }, + { type: RequestType.OrgCustomInstructions, organizationId } + ); + + if (!response.ok) { + this._logService.warn(`[OrgCustomInstructions] Failed to fetch instructions: ${response.status} ${response.statusText}`); + return undefined; + } + + const data = await response.json(); + + // Assuming the API returns an object with an 'instructions' field + if (typeof data === 'object' && data !== null && 'instructions' in data && typeof data.instructions === 'string') { + this._logService.debug(`[OrgCustomInstructions] Successfully fetched instructions (${data.instructions.length} chars)`); + return data.instructions; + } + + this._logService.warn('[OrgCustomInstructions] Unexpected response format from API'); + return undefined; + + } catch (error) { + this._logService.error(`[OrgCustomInstructions] Error fetching organization custom instructions: ${error}`); + return undefined; + } + } +}