8000 feat 006: add feima embedding by ivenxu · Pull Request #5 · ivenxu/feima-code · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ export class GitHubToFeimaModelMappingService implements IGitHubToFeimaModelMapp
* Maps both:
* 1. GitHub canonical families: 'copilot-fast', 'copilot-base'
* 2. Resolved model IDs: 'gpt-4o-mini', 'gpt-41-copilot'
* 3. Embeddings models: 'text-embedding-3-small', 'text3small-512'
*
* Target models are selected from #file:001_initial_schema.py:
* Target models are selected from database model catalog:
* - qwen-flash: Free tier, 1M context, ultra-fast
* - qwen-coder-turbo: Free tier (assumed), specialized for code
* - text-embedding-v4: Ali Cloud embeddings, 512 dimensions
*/
private readonly _modelMap: Map<string, string> = new Map([
// GitHub canonical families → Feima free-tier models
Expand All @@ -69,6 +71,11 @@ export class GitHubToFeimaModelMappingService implements IGitHubToFeimaModelMapp
// GitHub resolved model IDs → Feima equivalents
['gpt-4o-mini', 'qwen3'], // Lightweight model → fast Feima model
['gpt-41-copilot', 'qwen-coder-turbo'], // Code-specialized → coder turbo

// GitHub embeddings models → Feima embeddings
['text-embedding-3-small', 'text-embedding-v4'], // GitHub embeddings → Ali Cloud
['text-embedding-3-small-512', 'text-embedding-v4'], // GitHub full ID → Feima model
['text3small-512', 'text-embedding-v4'], // Internal short ID → Feima model
]);

getFeimaModel(githubModelOrFamily: string): string | undefined {
Expand Down
13 changes: 9 additions & 4 deletions src/extension/extension/vscode-node/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { VSCodeCopilotTokenManager } from '../../../platform/authentication/vsco
import { IChatAgentService } from '../../../platform/chat/common/chatAgents';
import { IChatMLFetcher } from '../../../platform/chat/common/chatMLFetcher';
import { IChunkingEndpointClient } from '../../../platform/chunking/common/chunkingEndpointClient';
import { ChunkingEndpointClientImpl } from '../../../platform/chunking/common/chunkingEndpointClientImpl';
import { FeimaChunkingClient } from '../../../platform/chunking/common/feimaChunkingClient';
import { INaiveChunkingService, NaiveChunkingService } from '../../../platform/chunking/node/naiveChunkerService';
import { IDevContainerConfigurationService } from '../../../platform/devcontainer/common/devContainerConfigurationService';
import { IDiffService } from '../../../platform/diff/common/diffService';
import { DiffServiceImpl } from '../../../platform/diff/node/diffServiceImpl';
import { IEmbeddingsComputer } from '../../../platform/embeddings/common/embeddingsComputer';
import { FeimaEmbeddingsComputer } from '../../../platform/embeddings/common/feimaEmbeddingsComputer';
import { ICAPIClientService } from '../../../platform/endpoint/common/capiClient';
import { IDomainService } from '../../../platform/endpoint/common/domainService';
import { IEndpointProvider, IFeimaEndpointProvider, IGitHubEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
Expand Down Expand Up @@ -72,7 +74,8 @@ import { IWorkspaceMutationManager } from '../../../platform/testing/common/work
import { ISetupTestsDetector, SetupTestsDetector } from '../../../platform/testing/node/setupTestDetector';
import { ITestDepsResolver, TestDepsResolver } from '../../../platform/testing/node/testDepsResolver';
import { ITokenizerProvider, TokenizerProvider } from '../../../platform/tokenizer/node/tokenizer';
import { GithubAvailableEmbeddingTypesService, IGithubAvailableEmbeddingTypesService } from '../../../platform/workspaceChunkSearch/common/githubAvailableEmbeddingTypes';
import { FeimaEmbeddingTypesService } from '../../../platform/workspaceChunkSearch/common/feimaAvailableEmbeddingTypes';
import { IGithubAvailableEmbeddingTypesService } from '../../../platform/workspaceChunkSearch/common/githubAvailableEmbeddingTypes';
import { IRerankerService, RerankerService } from '../../../platform/workspaceChunkSearch/common/rerankerService';
import { IWorkspaceChunkSearchService, WorkspaceChunkSearchService } from '../../../platform/workspaceChunkSearch/node/workspaceChunkSearchService';
import { IWorkspaceFileIndex, WorkspaceFileIndex } from '../../../platform/workspaceChunkSearch/node/workspaceFileIndex';
Expand Down Expand Up @@ -205,7 +208,9 @@ export function registerServices(builder: IInstantiationServiceBuilder, extensio
builder.define(IIntentService, new SyncDescriptor(IntentService));
builder.define(INaiveChunkingService, new SyncDescriptor(NaiveChunkingService));
builder.define(IWorkspaceFileIndex, new SyncDescriptor(WorkspaceFileIndex));
builder.define(IChunkingEndpointClient, new SyncDescriptor(ChunkingEndpointClientImpl));
builder.define(IChunkingEndpointClient, new SyncDescriptor(FeimaChunkingClient));
// Override IEmbeddingsComputer from common services to use Feima composite router
builder.define(IEmbeddingsComputer, new SyncDescriptor(FeimaEmbeddingsComputer));
builder.define(ICommandService, new SyncDescriptor(CommandServiceImpl));
builder.define(IDocsSearchClient, new SyncDescriptor(DocsSearchClient));
builder.define(ISearchService, new SyncDescriptor(SearchServiceImpl));
Expand Down Expand Up @@ -241,7 +246,7 @@ export function registerServices(builder: IInstantiationServiceBuilder, extensio
builder.define(IWorkspaceListenerService, new SyncDescriptor(WorkspacListenerService));
builder.define(ICodeSearchAuthenticationService, new SyncDescriptor(VsCodeCodeSearchAuthenticationService));
builder.define(ITodoListContextProvider, new SyncDescriptor(TodoListContextProvider));
builder.define(IGithubAvailableEmbeddingTypesService, new SyncDescriptor(GithubAvailableEmbeddingTypesService));
builder.define(IGithubAvailableEmbeddingTypesService, new SyncDescriptor(FeimaEmbeddingTypesService));
builder.define(IRerankerService, new SyncDescriptor(RerankerService));
builder.define(IProxyModelsService, new SyncDescriptor(ProxyModelsService));
builder.define(IInlineEditsModelService, new SyncDescriptor(InlineEditsModelService));
Expand Down
35 changes: 32 additions & 3 deletions src/extension/prompt/vscode-node/feimaEndpointProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IAuthenticationService } from '../../../platform/authentication/common/
import { IFeimaAuthenticationService } from '../../../platform/authentication/node/feimaAuthenticationService';
import { ChatEndpointFamily, EmbeddingsEndpointFamily, IChatModelInformation, ICompletionModelInformation, IEndpointProvider, IFeimaEndpointProvider, IGitHubEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
import { FeimaChatEndpoint } from '../../../platform/endpoint/node/feimaChatEndpoint';
import { FeimaEmbeddingsEndpoint } from '../../../platform/endpoint/node/feimaEmbeddingsEndpoint';
import { IFeimaModelMetadataFetcher } from '../../../platform/endpoint/node/feimaModelMetadataFetcher';
import { ILogService } from '../../../platform/log/common/logService';
import { IChatEndpoint, IEmbeddingsEndpoint } from '../../../platform/networking/common/networking';
Expand All @@ -35,6 +36,7 @@ export class FeimaOnlyEndpointProvider implements IEndpointProvider {
constructor(
@IFeimaAuthenticationService private readonly feimaAuthService: IFeimaAuthenticationService,
@IFeimaModelMetadataFetcher private readonly feimaModelFetcher: IFeimaModelMetadataFetcher,
@IFeimaConfigService private readonly feimaConfigService: IFeimaConfigService,
@ILogService private readonly logService: ILogService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
Expand Down Expand Up @@ -145,8 +147,25 @@ export class FeimaOnlyEndpointProvider implements IEndpointProvider {
}

async getEmbeddingsEndpoint(family?: EmbeddingsEndpointFamily): Promise<IEmbeddingsEndpoint> {
// Feima embeddings not yet supported
throw new Error('Feima embeddings not yet supported');
this.logService.trace('[FeimaOnlyEndpointProvider] Getting embeddings endpoint');

// Check authentication
const isAuthenticated = await this.feimaAuthService.isAuthenticated();
if (!isAuthenticated) {
throw new Error('Feima not authenticated');
}

// Fetch embedding model metadata (use 'text-embedding-3-small' literal for compatibility)
const model = await this.feimaModelFetcher.getEmbeddingsModel('text-embedding-3-small');
if (!model) {
throw new Error('Feima embedding model not found');
}

// Get Feima API endpoint from config
const feimaApiEndpoint = this.feimaConfigService.getConfig().apiBaseUrl;

// Create and return FeimaEmbeddingsEndpoint
return this.instantiationService.createInstance(FeimaEmbeddingsEndpoint, model, feimaApiEndpoint);
}
}

Expand Down Expand Up @@ -363,6 +382,16 @@ export class CombinedEndpointProvider implements IEndpointProvider {
const isFeimaAuthenticated = await this.feimaAuthService.isAuthenticated();
const config = this.feimaConfigService.getConfig();

// Map GitHub embedding families to Feima equivalents when appropriate
let resolvedFamily = family;
if (family && config.preferFeimaModels && isFeimaAuthenticated) {
const feimaModelId = this.modelMappingService.getFeimaModel(family);
if (feimaModelId) {
this.logService.trace(`[CombinedEndpointProvider] Pre-mapping GitHub embedding family ${family} to Feima model ${feimaModelId}`);
resolvedFamily = feimaModelId as EmbeddingsEndpointFamily;
}
}

// Determine primary and fallback providers based on preference
const primaryProvider = config.preferFeimaModels ? this.feimaProvider : this.githubProvider;
const fallbackProvider = config.preferFeimaModels ? this.githubProvider : this.feimaProvider;
Expand All @@ -372,7 +401,7 @@ export class CombinedEndpointProvider implements IEndpointProvider {
// If both are authenticated, try primary then fallback
if (isFeimaAuthenticated) {
try {
const endpoint = await primaryProvider.getEmbeddingsEndpoint(family);
const endpoint = await primaryProvider.getEmbeddingsEndpoint(resolvedFamily);
this.logService.trace(`[CombinedEndpointProvider] Resolved to ${primaryName} embeddings endpoint`);
return endpoint;
} catch (primaryError) {
Expand Down
92 changes: 92 additions & 0 deletions src/platform/authentication/common/feimaAuthentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { createServiceIdentifier } from '../../../util/common/services';
import type { Event } from '../../../util/vs/base/common/event';

/**
* Service interface for Feima authentication.
*
* Provides both consumer-facing methods (getToken, isAuthenticated) AND
* VS Code AuthenticationProvider methods (getSessions, createSession, removeSession).
*
* Architecture:
* - FeimaAuthenticationService: Heavy implementation (OAuth2, session management, secrets)
* - FeimaAuthProvider: Thin VS Code adapter that delegates to the service
* - Other services: Inject IFeimaAuthenticationService via DI for getToken/isAuthenticated
*/
export interface IFeimaAuthenticationService {
readonly _serviceBrand: undefined;

// ============ Consumer-Facing Methods (for DI injection) ============

/**
* Get current JWT token for Feima API.
* @returns JWT token string or undefined if not authenticated or expired
*/
getToken(): Promise<string | undefined>;

/**
* Check if user is authenticated with Feima.
* @returns true if authenticated, false otherwise
*/
isAuthenticated(): Promise<boolean>;

/**
* Refresh JWT token from authentication server.
* @returns Fresh JWT token or undefined if refresh fails
*/
refreshToken(): Promise<string | undefined>;

/**
* Sign out and clear all stored tokens.
*/
signOut(): Promise<void>;

/**
* Event fired when authentication state changes (sign-in or sign-out).
*/
onDidChangeAuthenticationState: Event<boolean>;

// ============ Provider Methods (for FeimaAuthProvider delegation) ============

/**
* Get all authentication sessions.
* Used by VS Code AuthenticationProvider interface.
*/
getSessions(scopes: readonly string[] | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession[]>;

/**
* Create a new authentication session via OAuth2 + PKCE flow.
* Used by VS Code AuthenticationProvider interface.
*/
createSession(scopes: readonly string[], options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession>;

/**
* Remove an authentication session.
* Used by VS Code AuthenticationProvider interface.
*/
removeSession(sessionId: string): Promise<void>;

/**
* Get cached sessions synchronously (non-blocking).
* Used by FeimaAuthProvider for fast checks.
*/
getCachedSessions(): vscode.AuthenticationSession[];

/**
* Event fired when sessions change.
* Used by VS Code AuthenticationProvider interface.
*/
onDidChangeSessions: Event<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>;

/**
* Handle OAuth callback URI (called by VS Code UriHandler).
* Used by FeimaAuthProvider for OAuth2 flow.
*/
handleUri(uri: vscode.Uri): void;
}

export const IFeimaAuthenticationService = createServiceIdentifier<IFeimaAuthenticationService>('feimaAuthenticationService');
90 changes: 2 additions & 88 deletions src/platform/authentication/node/feimaAuthenticationService.ts
37CB
Original file line number Diff line number Diff line change
Expand Up @@ -2,91 +2,5 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { createServiceIdentifier } from '../../../util/common/services';
import type { Event } from '../../../util/vs/base/common/event';

/**
* Service interface for Feima authentication.
*
* Provides both consumer-facing methods (getToken, isAuthenticated) AND
* VS Code AuthenticationProvider methods (getSessions, createSession, removeSession).
*
* Architecture:
* - FeimaAuthenticationService: Heavy implementation (OAuth2, session management, secrets)
* - FeimaAuthProvider: Thin VS Code adapter that delegates to the service
* - Other services: Inject IFeimaAuthenticationService via DI for getToken/isAuthenticated
*/
export interface IFeimaAuthenticationService {
readonly _serviceBrand: undefined;

// ============ Consumer-Facing Methods (for DI injection) ============

/**
* Get current JWT token for Feima API.
* @returns JWT token string or undefined if not authenticated or expired
*/
getToken(): Promise<string | undefined>;

/**
* Check if user is authenticated with Feima.
* @returns true if authenticated, false otherwise
*/
isAuthenticated(): Promise<boolean>;

/**
* Refresh JWT token from authentication server.
* @returns Fresh JWT token or undefined if refresh fails
*/
refreshToken(): Promise<string | undefined>;

/**
* Sign out and clear all stored tokens.
*/
signOut(): Promise<void>;

/**
* Event fired when authentication state changes (sign-in or sign-out).
*/
onDidChangeAuthenticationState: Event<boolean>;

// ============ Provider Methods (for FeimaAuthProvider delegation) ============

/**
* Get all authentication sessions.
* Used by VS Code AuthenticationProvider interface.
*/
getSessions(scopes: readonly string[] | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession[]>;

/**
* Create a new authentication session via OAuth2 + PKCE flow.
* Used by VS Code AuthenticationProvider interface.
*/
createSession(scopes: readonly string[], options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession>;

/**
* Remove an authentication session.
* Used by VS Code AuthenticationProvider interface.
*/
removeSession(sessionId: string): Promise<void>;

/**
* Get cached sessions synchronously (non-blocking).
* Used by FeimaAuthProvider for fast checks.
*/
getCachedSessions(): vscode.AuthenticationSession[];

/**
* Event fired when sessions change.
* Used by VS Code AuthenticationProvider interface.
*/
onDidChangeSessions: Event<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>;

/**
* Handle OAuth callback URI (called by VS Code UriHandler).
* Used by FeimaAuthProvider for OAuth2 flow.
*/
handleUri(uri: vscode.Uri): void;
}

export const IFeimaAuthenticationService = createServiceIdentifier<IFeimaAuthenticationService>('feimaAuthenticationService');
// Re-export interface and service identifier from common layer
export * from '../common/feimaAuthentication';
Loading
Loading
0