From 37c3dd0ea4d2fa2cc7ef83aba5f430e8729f8b4d Mon Sep 17 00:00:00 2001 From: ranveerd11 Date: Tue, 17 Feb 2026 19:42:53 +0530 Subject: [PATCH 1/5] Fix circular dependency between aiModelRegistryService and usageTrackingService - Move IAIModelRegistryService decorator/interface to aiModelRegistryTypes.ts - Move IUsageTrackingService decorator/interface to usageTrackingTypes.ts - Replace direct @IUsageTrackingService injection with lazy resolution via @IInstantiationService - Update 3 test files for new constructor signature Closes #110 Built by AINative Dev Team --- .../ainative/common/aiModelRegistryService.ts | 122 +++++------------- .../ainative/common/aiModelRegistryTypes.ts | 82 ++++++++++++ .../ainative/common/usageTrackingService.ts | 110 +--------------- .../ainative/common/usageTrackingTypes.ts | 104 +++++++++++++++ .../test/browser/modelRegistryFlow.test.ts | 12 +- .../test/common/authIntegration.test.ts | 10 +- .../integration/authenticationFlow.test.ts | 9 +- 7 files changed, 254 insertions(+), 195 deletions(-) diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryService.ts b/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryService.ts index e528fe35c..4fb322409 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryService.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryService.ts @@ -8,9 +8,9 @@ * Integrates with AINative's AI Model Registry for browsing, selecting, and invoking AI models */ -import { Event, Emitter } from '../../../../base/common/event.js'; +import { Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { IAINativeCloudAuthService } from './ainativeCloudAuthTypes.js'; @@ -27,89 +27,14 @@ import { ModelRegistryErrorCode, ModelCapability, PricingTier, - ModelParameterType + ModelParameterType, + IAIModelRegistryService } from './aiModelRegistryTypes.js'; import { IModelConfigManager, ModelConfigManager } from './aiModelConfig.js'; -import { IUsageTrackingService } from './usageTrackingService.js'; +import { IUsageTrackingService } from './usageTrackingTypes.js'; -/** - * Service interface for AI Model Registry - */ -export const IAIModelRegistryService = createDecorator('aiModelRegistryService'); - -export interface IAIModelRegistryService { - readonly _serviceBrand: undefined; - - /** - * Event fired when model list is updated - */ - readonly onDidUpdateModels: Event; - - /** - * Event fired when model selection changes - */ - readonly onDidChangeModelSelection: Event; - - /** - * List available AI models - * @param filters Optional filters to apply - * @returns List of matching models - */ - listModels(filters?: ModelFilters): Promise; - - /** - * Get a specific model by ID - * @param modelId Model identifier - * @returns Model details - */ - getModel(modelId: string): Promise; - - /** - * Select a model for a project - * @param modelId Model identifier - * @param projectId Project identifier - * @param parameters Optional custom parameters - */ - selectModel(modelId: string, projectId: string, parameters?: Record): Promise; - - /** - * Get selected model for a project - * @param projectId Project identifier - * @returns Selected model or null - */ - getSelectedModel(projectId: string): Promise; - - /** - * Invoke a model - * @param request Invocation request - * @returns Model response - */ - invokeModel(request: ModelInvocationRequest): Promise; - - /** - * Invoke a model with streaming - * @param request Invocation request - * @param onChunk Callback for each chunk - */ - streamModel(request: ModelInvocationRequest, onChunk: (chunk: ModelStreamChunk) => void): Promise; - - /** - * Get usage statistics - * @returns Usage stats for current user - */ - getUsageStats(): Promise; - - /** - * Get quota information - * @returns Quota info for current user - */ - getQuota(): Promise; - - /** - * Refresh model list from registry - */ - refreshModels(): Promise; -} +// Re-export for backward compatibility +export { IAIModelRegistryService } from './aiModelRegistryTypes.js'; /** * AI Model Registry Service Implementation @@ -130,15 +55,15 @@ export class AIModelRegistryService extends Disposable implements IAIModelRegist private _cacheTimestamp: number = 0; private _configManager: IModelConfigManager; private _usageTrackingService: IUsageTrackingService | null = null; + private _usageTrackingResolved = false; constructor( @IAINativeCloudAuthService private readonly cloudAuthService: IAINativeCloudAuthService, @IStorageService storageService: IStorageService, - @IUsageTrackingService usageTrackingService: IUsageTrackingService + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); - this._usageTrackingService = usageTrackingService; this._configManager = new ModelConfigManager(storageService); this._register(this._configManager.onDidChangeModelSelection(config => { this._onDidChangeModelSelection.fire(config); @@ -153,6 +78,25 @@ export class AIModelRegistryService extends Disposable implements IAIModelRegist })); } + /** + * Lazily resolve IUsageTrackingService to avoid circular DI dependency. + * usageTrackingService depends on aiModelRegistryService, so we cannot + * inject it directly in the constructor. + */ + private _getUsageTrackingService(): IUsageTrackingService | null { + if (!this._usageTrackingResolved) { + this._usageTrackingResolved = true; + try { + this._usageTrackingService = this._instantiationService.invokeFunction( + accessor => accessor.get(IUsageTrackingService) + ); + } catch { + // Service not yet available + } + } + return this._usageTrackingService; + } + /** * Fetch models from API */ @@ -450,8 +394,9 @@ export class AIModelRegistryService extends Disposable implements IAIModelRegist // Track invocation for usage stats (both cloud and local) await this._trackInvocation(request.modelId, data.usage); - if (this._usageTrackingService && data.usage) { - await this._usageTrackingService.trackUsage( + const usageService = this._getUsageTrackingService(); + if (usageService && data.usage) { + await usageService.trackUsage( request.modelId, data.usage.input_tokens ?? 0, data.usage.output_tokens ?? 0 @@ -584,8 +529,9 @@ export class AIModelRegistryService extends Disposable implements IAIModelRegist // Track invocation after streaming completes (both cloud and local) if (finalUsage) { await this._trackInvocation(request.modelId, finalUsage); - if (this._usageTrackingService) { - await this._usageTrackingService.trackUsage( + const usageService = this._getUsageTrackingService(); + if (usageService) { + await usageService.trackUsage( request.modelId, finalUsage.input_tokens ?? finalUsage.inputTokens ?? 0, finalUsage.output_tokens ?? finalUsage.outputTokens ?? 0 diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryTypes.ts b/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryTypes.ts index 9b2b1c05f..8909d9815 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryTypes.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/common/aiModelRegistryTypes.ts @@ -8,6 +8,9 @@ * Defines interfaces for AI model management, selection, and invocation */ +import { Event } from '../../../../base/common/event.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; + /** * Pricing tiers for AI models */ @@ -527,3 +530,82 @@ export class ModelRegistryError extends Error { this.name = 'ModelRegistryError'; } } + +/** + * Service interface for AI Model Registry + */ +export const IAIModelRegistryService = createDecorator('aiModelRegistryService'); + +export interface IAIModelRegistryService { + readonly _serviceBrand: undefined; + + /** + * Event fired when model list is updated + */ + readonly onDidUpdateModels: Event; + + /** + * Event fired when model selection changes + */ + readonly onDidChangeModelSelection: Event; + + /** + * List available AI models + * @param filters Optional filters to apply + * @returns List of matching models + */ + listModels(filters?: ModelFilters): Promise; + + /** + * Get a specific model by ID + * @param modelId Model identifier + * @returns Model details + */ + getModel(modelId: string): Promise; + + /** + * Select a model for a project + * @param modelId Model identifier + * @param projectId Project identifier + * @param parameters Optional custom parameters + */ + selectModel(modelId: string, projectId: string, parameters?: Record): Promise; + + /** + * Get selected model for a project + * @param projectId Project identifier + * @returns Selected model or null + */ + getSelectedModel(projectId: string): Promise; + + /** + * Invoke a model + * @param request Invocation request + * @returns Model response + */ + invokeModel(request: ModelInvocationRequest): Promise; + + /** + * Invoke a model with streaming + * @param request Invocation request + * @param onChunk Callback for each chunk + */ + streamModel(request: ModelInvocationRequest, onChunk: (chunk: ModelStreamChunk) => void): Promise; + + /** + * Get usage statistics + * @returns Usage stats for current user + */ + getUsageStats(): Promise; + + /** + * Get quota information + * @returns Quota info for current user + */ + getQuota(): Promise; + + /** + * Refresh model list from registry + */ + refreshModels(): Promise; +} diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingService.ts b/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingService.ts index 13883bdae..2582b2e62 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingService.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingService.ts @@ -8,13 +8,12 @@ * Tracks local token usage, calculates costs, monitors quotas, and syncs with cloud API */ -import { Event, Emitter } from '../../../../base/common/event.js'; +import { Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IAINativeCloudAuthService } from './ainativeCloudAuthTypes.js'; -import { IAIModelRegistryService } from './aiModelRegistryService.js'; +import { IAIModelRegistryService } from './aiModelRegistryTypes.js'; import { AIModel } from './aiModelRegistryTypes.js'; import { UsageRecord, @@ -24,7 +23,8 @@ import { UsagePeriod, ManagedUsageRecord, CreditsStatus, - CreditsHistory + CreditsHistory, + IUsageTrackingService } from './usageTrackingTypes.js'; // Re-export types for backwards compatibility @@ -39,106 +39,8 @@ export { CreditsHistory } from './usageTrackingTypes.js'; -/** - * Service interface for usage tracking - */ -export const IUsageTrackingService = createDecorator('usageTrackingService'); - -export interface IUsageTrackingService { - readonly _serviceBrand: undefined; - - /** - * Event fired when usage is updated - */ - readonly onDidUpdateUsage: Event; - - /** - * Event fired when quota status changes - */ - readonly onDidUpdateQuota: Event; - - /** - * Event fired when credits status is updated - */ - readonly onDidUpdateCredits: Event; - - /** - * Event fired when credits are running low - */ - readonly onCreditsLow: Event; - - /** - * Track a model invocation - * @param modelId Model identifier - * @param inputTokens Number of input tokens - * @param outputTokens Number of output tokens - */ - trackUsage(modelId: string, inputTokens: number, outputTokens: number): Promise; - - /** - * Get current usage statistics - * @param period Optional period filter ('day' | 'week' | 'month' | 'all') - * @returns Aggregated usage statistics - */ - getUsage(period?: UsagePeriod): Promise; - - /** - * Get quota status - * @returns Current quota status - */ - getQuotaStatus(): Promise; - - /** - * Calculate cost for a potential usage - * @param modelId Model identifier - * @param inputTokens Number of input tokens - * @param outputTokens Number of output tokens - * @returns Cost calculation - */ - calculateCost(modelId: string, inputTokens: number, outputTokens: number): Promise; - - /** - * Sync local usage with cloud API - */ - syncWithCloud(): Promise; - - /** - * Clear all local usage data - */ - clearLocalUsage(): Promise; - - /** - * Reset usage tracking (called on logout) - */ - reset(): void; - - /** - * Track managed API usage with credits - * @param modelId Model identifier - * @param tokensUsed Total tokens consumed - * @param creditsConsumed Credits charged for this invocation - */ - trackManagedUsage(modelId: string, tokensUsed: number, creditsConsumed: number): Promise; - - /** - * Get current credits status from backend - * @returns Current credits status - */ - getCreditsStatus(): Promise; - - /** - * Check if credits are running low (< 20% remaining) - * @returns True if credits are low - */ - isCreditsLow(): boolean; - - /** - * Get credits usage history - * @param days Number of days to retrieve (default: 30) - * @returns Credits usage history - */ - getCreditsHistory(days?: number): Promise; -} +// Re-export service interface for backward compatibility +export { IUsageTrackingService } from './usageTrackingTypes.js'; /** * Usage Tracking Service Implementation diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingTypes.ts b/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingTypes.ts index db841b0f7..5166261b5 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingTypes.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/common/usageTrackingTypes.ts @@ -8,6 +8,9 @@ * Type definitions for usage tracking, cost calculation, and quota monitoring */ +import { Event } from '../../../../base/common/event.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; + /** * Usage record for a single model invocation */ @@ -424,3 +427,104 @@ export interface CreditsHistory { */ readonly totalTokens: number; } + +/** + * Service interface for usage tracking + */ +export const IUsageTrackingService = createDecorator('usageTrackingService'); + +export interface IUsageTrackingService { + readonly _serviceBrand: undefined; + + /** + * Event fired when usage is updated + */ + readonly onDidUpdateUsage: Event; + + /** + * Event fired when quota status changes + */ + readonly onDidUpdateQuota: Event; + + /** + * Event fired when credits status is updated + */ + readonly onDidUpdateCredits: Event; + + /** + * Event fired when credits are running low + */ + readonly onCreditsLow: Event; + + /** + * Track a model invocation + * @param modelId Model identifier + * @param inputTokens Number of input tokens + * @param outputTokens Number of output tokens + */ + trackUsage(modelId: string, inputTokens: number, outputTokens: number): Promise; + + /** + * Get current usage statistics + * @param period Optional period filter ('day' | 'week' | 'month' | 'all') + * @returns Aggregated usage statistics + */ + getUsage(period?: UsagePeriod): Promise; + + /** + * Get quota status + * @returns Current quota status + */ + getQuotaStatus(): Promise; + + /** + * Calculate cost for a potential usage + * @param modelId Model identifier + * @param inputTokens Number of input tokens + * @param outputTokens Number of output tokens + * @returns Cost calculation + */ + calculateCost(modelId: string, inputTokens: number, outputTokens: number): Promise; + + /** + * Sync local usage with cloud API + */ + syncWithCloud(): Promise; + + /** + * Clear all local usage data + */ + clearLocalUsage(): Promise; + + /** + * Reset usage tracking (called on logout) + */ + reset(): void; + + /** + * Track managed API usage with credits + * @param modelId Model identifier + * @param tokensUsed Total tokens consumed + * @param creditsConsumed Credits charged for this invocation + */ + trackManagedUsage(modelId: string, tokensUsed: number, creditsConsumed: number): Promise; + + /** + * Get current credits status from backend + * @returns Current credits status + */ + getCreditsStatus(): Promise; + + /** + * Check if credits are running low (< 20% remaining) + * @returns True if credits are low + */ + isCreditsLow(): boolean; + + /** + * Get credits usage history + * @param days Number of days to retrieve (default: 30) + * @returns Credits usage history + */ + getCreditsHistory(days?: number): Promise; +} diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/test/browser/modelRegistryFlow.test.ts b/ainative-studio/src/vs/workbench/contrib/ainative/test/browser/modelRegistryFlow.test.ts index 4f155f067..02d997150 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/test/browser/modelRegistryFlow.test.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/test/browser/modelRegistryFlow.test.ts @@ -147,10 +147,20 @@ suite('Model Registry Flow Integration Tests - Issue #47', () => { storageService )); + const mockInstantiationService = { + _serviceBrand: undefined, + invokeFunction: (fn: any) => fn({ + get: () => usageTracking + }), + createInstance: () => { throw new Error('Not implemented'); }, + createChild: () => { throw new Error('Not implemented'); }, + dispose: () => { } + }; + modelRegistry = disposables.add(new AIModelRegistryService( authService as any, storageService, - usageTracking + mockInstantiationService as any )); // Update usageTracking with modelRegistry reference diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/test/common/authIntegration.test.ts b/ainative-studio/src/vs/workbench/contrib/ainative/test/common/authIntegration.test.ts index 255beb651..05ef2578d 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/test/common/authIntegration.test.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/test/common/authIntegration.test.ts @@ -205,10 +205,18 @@ suite('Comprehensive Integration Tests - Issue #47 AINative Authentication', () storageService )); + const mockInstantiationService = { + _serviceBrand: undefined, + invokeFunction: (fn: any) => fn({ get: () => usageTracking }), + createInstance: () => { throw new Error('Not implemented'); }, + createChild: () => { throw new Error('Not implemented'); }, + dispose: () => { } + }; + modelRegistry = disposables.add(new AIModelRegistryService( authService, storageService, - usageTracking + mockInstantiationService as any )); // Update cross-references diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/test/integration/authenticationFlow.test.ts b/ainative-studio/src/vs/workbench/contrib/ainative/test/integration/authenticationFlow.test.ts index 6533ebc59..5a0a845cb 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/test/integration/authenticationFlow.test.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/test/integration/authenticationFlow.test.ts @@ -348,7 +348,14 @@ suite('Authentication Integration Tests', () => { usageTrackingService = new MockUsageTrackingService(); authService = new AINativeAuthService(encryptionService, storageService); tokenService = new TokenService(encryptionService, storageService); - modelRegistry = new AIModelRegistryService(authService as any, storageService, usageTrackingService); + const mockInstantiationService = { + _serviceBrand: undefined, + invokeFunction: (fn: any) => fn({ get: () => usageTrackingService }), + createInstance: () => { throw new Error('Not implemented'); }, + createChild: () => { throw new Error('Not implemented'); }, + dispose: () => { } + }; + modelRegistry = new AIModelRegistryService(authService as any, storageService, mockInstantiationService as any); disposables.add(authService); disposables.add(tokenService); From 3971e3572d9147f5ab4b9328c2a585d4620bd9df Mon Sep 17 00:00:00 2001 From: ranveerd11 Date: Tue, 17 Feb 2026 19:43:05 +0530 Subject: [PATCH 2/5] Add missing registerSingleton for ainativeAuthService - Service had createDecorator and full implementation but no DI registration - Add registerSingleton(IAINativeAuthService, AINativeAuthService, InstantiationType.Delayed) Closes #111 Built by AINative Dev Team --- .../workbench/contrib/ainative/common/ainativeAuthService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeAuthService.ts b/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeAuthService.ts index 68d636146..7bbda0f32 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeAuthService.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeAuthService.ts @@ -5,6 +5,7 @@ import { Event } from '../../../../base/common/event.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; export const IAINativeAuthService = createDecorator('ainativeAuthService'); @@ -505,3 +506,6 @@ export class AINativeAuthService extends Disposable implements IAINativeAuthServ return this._authState; } } + +// Register the service with VS Code dependency injection +registerSingleton(IAINativeAuthService, AINativeAuthService, InstantiationType.Delayed); From 178119cf64ffd3c082d11c6014de8c7fb96852cf Mon Sep 17 00:00:00 2001 From: ranveerd11 Date: Tue, 17 Feb 2026 19:43:12 +0530 Subject: [PATCH 3/5] Add ainativeCloud to provider display info functions - Add ainativeCloud case to displayInfoOfProviderName() - Add ainativeCloud case to subTextMdOfProviderName() - Fixes Paid tab crash with Unknown provider name Closes #112 Built by AINative Dev Team --- .../contrib/ainative/common/ainativeSettingsTypes.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeSettingsTypes.ts b/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeSettingsTypes.ts index 08571055d..ee94ac474 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeSettingsTypes.ts +++ b/ainative-studio/src/vs/workbench/contrib/ainative/common/ainativeSettingsTypes.ts @@ -109,6 +109,9 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn else if (providerName === 'awsBedrock') { return { title: 'AWS Bedrock', } } + else if (providerName === 'ainativeCloud') { + return { title: 'AINative Cloud', } + } throw new Error(`descOfProviderName: Unknown provider name: "${providerName}"`) } @@ -131,6 +134,7 @@ export const subTextMdOfProviderName = (providerName: ProviderName): string => { if (providerName === 'vLLM') return 'Read more about custom [Endpoints here](https://docs.vllm.ai/en/latest/getting_started/quickstart.html#openai-compatible-server).' if (providerName === 'lmStudio') return 'Read more about custom [Endpoints here](https://lmstudio.ai/docs/app/api/endpoints/openai).' if (providerName === 'liteLLM') return 'Read more about endpoints [here](https://docs.litellm.ai/docs/providers/openai_compatible).' + if (providerName === 'ainativeCloud') return 'AINative Cloud managed API. Sign in to use your credits across all supported models.' throw new Error(`subTextMdOfProviderName: Unknown provider name: "${providerName}"`) } From f850e365fe02f6ea10f46aa52743b9dc57683d38 Mon Sep 17 00:00:00 2001 From: ranveerd11 Date: Tue, 17 Feb 2026 19:43:20 +0530 Subject: [PATCH 4/5] Expose IGitHubOAuthService to React accessor - Add IGitHubOAuthService import and mapping in getReactAccessor() - Fixes settings page crash when clicking Sign in to AINative Cloud Closes #113 Built by AINative Dev Team --- .../contrib/ainative/browser/react/src/util/services.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/util/services.tsx b/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/util/services.tsx index 4af7c0b40..53f1c5954 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/util/services.tsx +++ b/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/util/services.tsx @@ -55,6 +55,7 @@ import { IMCPService } from '../../../../common/mcpService.js'; import { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js' import { OPT_OUT_KEY } from '../../../../common/storageKeys.js' import { IAINativeAuthService, AuthState, AINativeUser } from '../../../../common/ainativeAuthService.js' +import { IGitHubOAuthService } from '../../../../common/githubOAuthService.js' // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes @@ -243,6 +244,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => { IStorageService: accessor.get(IStorageService), IAINativeAuthService: accessor.get(IAINativeAuthService), + IGitHubOAuthService: accessor.get(IGitHubOAuthService), } as const return reactAccessor From e335c17258e72586581391b8b0192a1044a1f51c Mon Sep 17 00:00:00 2001 From: ranveerd11 Date: Tue, 17 Feb 2026 19:43:28 +0530 Subject: [PATCH 5/5] Fix login modal rendering as unstyled inline content - Fix CSS selectors to match scope-tailwind prefixed class names - Use createPortal to render modal into document.body - Bypasses VS Code parent transforms that break position: fixed Closes #114 Built by AINative Dev Team --- .../AINativeLoginModal.css | 75 ++++++++++--------- .../AINativeLoginModal.tsx | 6 +- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.css b/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.css index 6321350d6..2f553bbd8 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.css +++ b/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.css @@ -3,8 +3,15 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------------*/ +/* + * NOTE: All class names here must be prefixed with "ainative-" to match the + * scope-tailwind build step which adds "ainative-" prefix to className strings + * in TSX files. The source TSX uses e.g. "ainative-login-modal-overlay" which + * becomes "ainative-ainative-login-modal-overlay" after scope-tailwind processing. + */ + /* Modal overlay */ -.ainative-login-modal-overlay { +.ainative-ainative-login-modal-overlay { position: fixed; top: 0; left: 0; @@ -28,7 +35,7 @@ } /* Modal container */ -.ainative-login-modal { +.ainative-ainative-login-modal { background-color: var(--vscode-editor-background, #1e1e1e); border: 1px solid var(--vscode-widget-border, #454545); border-radius: 8px; @@ -50,7 +57,7 @@ } /* Modal header */ -.modal-header { +.ainative-modal-header { display: flex; align-items: center; justify-content: space-between; @@ -58,14 +65,14 @@ border-bottom: 1px solid var(--vscode-widget-border, #454545); } -.modal-header h2 { +.ainative-modal-header h2 { margin: 0; font-size: 18px; font-weight: 600; color: var(--vscode-foreground, #cccccc); } -.close-button { +.ainative-close-button { background: none; border: none; font-size: 28px; @@ -82,22 +89,22 @@ transition: background-color 0.15s ease; } -.close-button:hover { +.ainative-close-button:hover { background-color: var(--vscode-toolbar-hoverBackground, #2a2d2e); } -.close-button:focus { +.ainative-close-button:focus { outline: 2px solid var(--vscode-focusBorder, #007acc); outline-offset: 2px; } /* Modal body */ -.modal-body { +.ainative-modal-body { padding: 24px; } /* Error message */ -.error-message { +.ainative-error-message { background-color: var(--vscode-inputValidation-errorBackground, #5a1d1d); border: 1px solid var(--vscode-inputValidation-errorBorder, #be1100); color: var(--vscode-errorForeground, #f48771); @@ -108,11 +115,11 @@ } /* Form group */ -.form-group { +.ainative-form-group { margin-bottom: 16px; } -.form-group label { +.ainative-form-group label { display: block; margin-bottom: 6px; font-size: 13px; @@ -120,7 +127,7 @@ color: var(--vscode-foreground, #cccccc); } -.form-group input { +.ainative-form-group input { width: 100%; padding: 10px 12px; font-size: 14px; @@ -132,18 +139,18 @@ box-sizing: border-box; } -.form-group input:focus { +.ainative-form-group input:focus { outline: none; border-color: var(--vscode-focusBorder, #007acc); box-shadow: 0 0 0 1px var(--vscode-focusBorder, #007acc); } -.form-group input::placeholder { +.ainative-form-group input::placeholder { color: var(--vscode-input-placeholderForeground, #717171); } /* Submit button */ -.submit-button { +.ainative-submit-button { width: 100%; padding: 10px 16px; font-size: 14px; @@ -157,26 +164,26 @@ margin-top: 8px; } -.submit-button:hover:not(:disabled) { +.ainative-submit-button:hover:not(:disabled) { background-color: #1177cb; } -.submit-button:active:not(:disabled) { +.ainative-submit-button:active:not(:disabled) { background-color: #0c5fa0; } -.submit-button:focus { +.ainative-submit-button:focus { outline: 2px solid var(--vscode-focusBorder, #007acc); outline-offset: 2px; } -.submit-button:disabled { +.ainative-submit-button:disabled { opacity: 0.6; cursor: not-allowed; } /* Divider */ -.divider { +.ainative-divider { text-align: center; margin: 20px 0; position: relative; @@ -184,8 +191,8 @@ font-size: 13px; } -.divider::before, -.divider::after { +.ainative-divider::before, +.ainative-divider::after { content: ''; position: absolute; top: 50%; @@ -194,16 +201,16 @@ background-color: var(--vscode-widget-border, #454545); } -.divider::before { +.ainative-divider::before { left: 0; } -.divider::after { +.ainative-divider::after { right: 0; } /* GitHub sign-in button */ -.github-signin-button { +.ainative-github-signin-button { width: 100%; padding: 10px 16px; font-size: 14px; @@ -216,35 +223,35 @@ transition: background-color 0.15s ease; } -.github-signin-button:hover:not(:disabled) { +.ainative-github-signin-button:hover:not(:disabled) { background-color: var(--vscode-button-secondaryHoverBackground, #45494d); } -.github-signin-button:disabled { +.ainative-github-signin-button:disabled { opacity: 0.5; cursor: not-allowed; } /* Sign-up link */ -.signup-link { +.ainative-signup-link { text-align: center; margin-top: 20px; font-size: 13px; color: var(--vscode-descriptionForeground, #999999); } -.signup-link a { +.ainative-signup-link a { color: var(--vscode-textLink-foreground, #3794ff); text-decoration: none; font-weight: 500; margin-left: 4px; } -.signup-link a:hover { +.ainative-signup-link a:hover { text-decoration: underline; } -.signup-link a:focus { +.ainative-signup-link a:focus { outline: 2px solid var(--vscode-focusBorder, #007acc); outline-offset: 2px; border-radius: 2px; @@ -252,13 +259,13 @@ /* Responsive design */ @media (max-width: 500px) { - .ainative-login-modal { + .ainative-ainative-login-modal { width: 95%; margin: 0 auto; } - .modal-header, - .modal-body { + .ainative-modal-header, + .ainative-modal-body { padding: 16px; } } diff --git a/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.tsx b/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.tsx index 21d24ee45..34f7366a5 100644 --- a/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.tsx +++ b/ainative-studio/src/vs/workbench/contrib/ainative/browser/react/src/ainative-settings-tsx/AINativeLoginModal.tsx @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import React, { useState, useEffect, useRef } from 'react'; +import { createPortal } from 'react-dom'; import { isValidEmail } from '../util/validation.js'; import { useAINativeAuth, useAccessor } from '../util/services.js'; import './AINativeLoginModal.css'; @@ -107,7 +108,7 @@ export const AINativeLoginModal: React.FC = ({ onClose, onSuccess }) => { } }; - return ( + return createPortal(
= ({ onClose, onSuccess }) => {
- + , + document.body ); };