← back to ruoshuidegit__L-Hub

Function bodies 57 total

All specs Real LLM only Function bodies
syncKeysToFile function · typescript · L18-L41 (24 LOC)
src/extension.ts
async function syncKeysToFile(settings: SettingsManager, dbPath?: string) {
    try {
        // Read existing file to preserve v2 `models` and other fields
        let existing: Record<string, any> = {};
        try {
            if (fs.existsSync(KEYS_FILE)) {
                existing = JSON.parse(fs.readFileSync(KEYS_FILE, 'utf8'));
            }
        } catch { }

        // Update only the legacy keys section — leave models/version intact
        const legacyKeys: Record<string, string> = {};
        for (const provider of SUPPORTED_PROVIDERS) {
            const k = await settings.getApiKey(provider);
            if (k) legacyKeys[provider] = k;
        }
        const merged: Record<string, any> = { ...existing, legacy: legacyKeys };
        // ── Write DB path so standalone mcp-server can log to the same SQLite ──
        if (dbPath) { merged.dbPath = dbPath; }
        fs.writeFileSync(KEYS_FILE, JSON.stringify(merged, null, 2), 'utf8');
    } catch (e) {
        console.erro
autoRegisterMcpConfig function · typescript · L47-L69 (23 LOC)
src/extension.ts
function autoRegisterMcpConfig(extensionPath: string) {
    const serverPath = path.join(extensionPath, 'dist', 'mcp-server.js');
    const mcpConfigPath = path.join(os.homedir(), '.gemini', 'antigravity', 'mcp_config.json');

    if (!fs.existsSync(mcpConfigPath)) return; // Antigravity not installed

    try {
        const raw = fs.readFileSync(mcpConfigPath, 'utf8');
        const config = JSON.parse(raw);
        if (!config.mcpServers) config.mcpServers = {};

        const existing = config.mcpServers['l-hub'];
        const newEntry = { command: 'node', args: [serverPath], env: {} };

        if (!existing || existing.args?.[0] !== serverPath) {
            config.mcpServers['l-hub'] = newEntry;
            fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 4), 'utf8');
            console.log('[L-Hub] Auto-registered mcp-server.js in Antigravity mcp_config.json');
        }
    } catch (err) {
        console.error('[L-Hub] Failed to auto-register MCP config:', err);
activate function · typescript · L71-L113 (43 LOC)
src/extension.ts
export async function activate(context: vscode.ExtensionContext) {
    console.log('[L-Hub] Activating...');

    const settings = new SettingsManager(context);

    // ── STEP 1: Register commands immediately — BEFORE anything that could throw ──
    let storage: HistoryStorage | null = null;

    const openPanelCommand = vscode.commands.registerCommand('l-hub.openPanel', () => {
        if (!storage) {
            vscode.window.showWarningMessage(
                'L-Hub: History storage is unavailable (SQLite load failed). Settings panel will still work.',
            );
        }
        DashboardPanel.createOrShow(context.extensionUri, storage!, settings);
    });
    context.subscriptions.push(openPanelCommand);

    console.log('[L-Hub] Command l-hub.openPanel registered ✅');

    // ── STEP 2: Initialize history storage (may fail if better-sqlite3 ABI mismatch) ──
    try {
        const storagePath = context.globalStorageUri.fsPath;
        storage = new HistoryStorage(storageP
deactivate function · typescript · L115-L119 (5 LOC)
src/extension.ts
export function deactivate() {
    if (mcpServer) {
        mcpServer.stop();
    }
}
getHistoryDb function · typescript · L20-L37 (18 LOC)
src/mcp-server.ts
function getHistoryDb(dbPath?: string): any {
    if (_historyDb) { return _historyDb; }
    if (!dbPath || !fs.existsSync(path.dirname(dbPath))) { return null; }
    try {
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const Database = require('better-sqlite3');
        _historyDb = new Database(dbPath);
        _historyDb.exec(`CREATE TABLE IF NOT EXISTS request_history (
            id TEXT PRIMARY KEY, timestamp INTEGER NOT NULL,
            client_name TEXT, client_version TEXT, method TEXT NOT NULL,
            tool_name TEXT, model TEXT, duration INTEGER,
            input_tokens INTEGER, output_tokens INTEGER, total_tokens INTEGER,
            request_preview TEXT, response_preview TEXT,
            status TEXT NOT NULL, error_message TEXT
        )`);
        return _historyDb;
    } catch { return null; }
}
saveHistory function · typescript · L38-L60 (23 LOC)
src/mcp-server.ts
function saveHistory(dbPath: string | undefined, rec: {
    model?: string; method: string; toolName?: string;
    duration: number; inputTokens?: number; outputTokens?: number;
    requestPreview: string; responsePreview: string;
    status: 'success' | 'error'; errorMessage?: string;
}) {
    const db = getHistoryDb(dbPath);
    if (!db) { return; }
    try {
        const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
        db.prepare(`INSERT OR IGNORE INTO request_history
            (id,timestamp,client_name,method,tool_name,model,duration,input_tokens,output_tokens,total_tokens,request_preview,response_preview,status,error_message)
            VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
        ).run(
            id, Date.now(), 'l-hub-mcp', rec.method, rec.toolName ?? rec.method,
            rec.model ?? null, rec.duration,
            rec.inputTokens ?? null, rec.outputTokens ?? null,
            (rec.inputTokens ?? 0) + (rec.outputTokens ?? 0) || null,
           
readConfig function · typescript · L83-L90 (8 LOC)
src/mcp-server.ts
function readConfig(): LHubConfig {
    try {
        if (fs.existsSync(KEYS_FILE)) {
            return JSON.parse(fs.readFileSync(KEYS_FILE, 'utf8')) as LHubConfig;
        }
    } catch { /* file missing or malformed */ }
    return {};
}
Repobility · open methodology · https://repobility.com/research/
detectTaskType function · typescript · L133-L150 (18 LOC)
src/mcp-server.ts
function detectTaskType(message: string): string {
    const msg = message.toLowerCase();

    // ── Length-aware routing (takes priority over keywords) ───────────────
    if (message.length > 3000) {
        return 'long_context'; // GLM handles long text best
    }
    if (message.length < 100 && (msg.includes('code') || msg.includes('代码') || msg.includes('function') || msg.includes('函数'))) {
        return 'code_gen'; // DeepSeek for quick short code tasks
    }

    for (const [taskId, keywords] of Object.entries(TASK_KEYWORDS)) {
        if (keywords.some(k => msg.includes(k))) {
            return taskId;
        }
    }
    return 'code_gen'; // safe default
}
resolveRoute function · typescript · L159-L221 (63 LOC)
src/mcp-server.ts
function resolveRoute(message: string, config: LHubConfig, forcedProvider?: string): RouteResult | null {
    const enabledModels = (config.models || []).filter(m => m.enabled && m.apiKey);

    if (enabledModels.length > 0) {
        // v2: use user's configured models
        let chosen: ModelConfig | undefined;

        if (forcedProvider) {
            // Match by label, modelId, or partial model group name
            const fp = forcedProvider.toLowerCase();
            chosen = enabledModels.find(m =>
                m.modelId.toLowerCase().includes(fp) ||
                m.label.toLowerCase().includes(fp)
            );
        }

        if (!chosen) {
            const taskType = detectTaskType(message);
            const matching = enabledModels
                .filter(m => m.tasks.includes(taskType))
                .sort((a, b) => a.priority - b.priority);
            chosen = matching[0] || enabledModels[0];
        }

        if (!chosen) { return null; }
        return {
callProvider function · typescript · L225-L254 (30 LOC)
src/mcp-server.ts
async function callProvider(route: RouteResult, message: string, systemPrompt?: string) {
    const url = route.baseUrl.endsWith('/chat/completions')
        ? route.baseUrl
        : `${route.baseUrl}/chat/completions`;

    const messages: Array<{ role: string; content: string }> = [];
    if (systemPrompt) { messages.push({ role: 'system', content: systemPrompt }); }
    messages.push({ role: 'user', content: message });

    const res = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${route.apiKey}`,
        },
        body: JSON.stringify({ model: route.modelId, messages, temperature: 0.7 }),
    });

    if (!res.ok) {
        const errText = await res.text();
        throw new Error(`${route.label} API ${res.status}: ${errText.slice(0, 300)}`);
    }

    const data = await res.json() as any;
    return {
        text: (data.choices?.[0]?.message?.content as string) || 'No respons
isRetryableError function · typescript · L262-L275 (14 LOC)
src/mcp-server.ts
function isRetryableError(msg: string): boolean {
    return (
        msg.includes('ETIMEDOUT') ||
        msg.includes('ECONNREFUSED') ||
        msg.includes('ENOTFOUND') ||
        msg.includes('fetch failed') ||
        msg.includes('network') ||
        msg.includes('429') ||
        msg.includes('500') ||
        msg.includes('502') ||
        msg.includes('503') ||
        msg.includes('504')
    );
}
callProviderWithFallback function · typescript · L281-L286 (6 LOC)
src/mcp-server.ts
async function callProviderWithFallback(
    primaryRoute: RouteResult,
    message: string,
    systemPrompt: string | undefined,
    config: LHubConfig
): Promise<{ text: string; inputTokens: number; outputTokens: number; usedModel: string; didFallback: boolean }> {
callCodex function · typescript · L395-L413 (19 LOC)
src/mcp-server.ts
function callCodex(task: string, workingDir?: string): string {
    const cwd = workingDir || process.cwd();
    const result = spawnSync(
        'codex',
        ['exec', '--skip-git-repo-check', '--full-auto', task],
        { cwd, encoding: 'utf8', timeout: 300_000 }
    );
    if (result.error) {
        const err = result.error as NodeJS.ErrnoException;
        if (err.code === 'ENOENT') {
            throw new Error('Codex CLI not found. Install: npm install -g @openai/codex, then: codex login');
        }
        throw new Error(`Codex CLI error: ${err.message}`);
    }
    if (result.status !== 0 && !result.stdout?.trim()) {
        throw new Error(result.stderr?.trim() || `Codex exited with code ${result.status}`);
    }
    return result.stdout?.trim() || '(Codex completed with no output)';
}
callGemini function · typescript · L421-L455 (35 LOC)
src/mcp-server.ts
function callGemini(prompt: string, model?: string, workingDir?: string): string {
    const cwd = workingDir || process.cwd();

    // Build args: use -p (short for --prompt) + --yolo to auto-accept any tool confirmations
    const args: string[] = ['-p', prompt, '--yolo'];
    if (model) { args.push('-m', model); }

    const result = spawnSync('gemini', args, {
        cwd,
        encoding: 'utf8',
        timeout: 300_000,
        maxBuffer: 10 * 1024 * 1024, // 10 MB
        env: { ...process.env },
    });

    if (result.error) {
        const err = result.error as NodeJS.ErrnoException;
        if (err.code === 'ENOENT') {
            throw new Error('Gemini CLI not found. Install: https://github.com/google-gemini/gemini-cli — then run `gemini` to log in.');
        }
        throw new Error(`Gemini CLI error: ${err.message}`);
    }

    // Gemini CLI may write to stderr for progress; non-zero exit on hard error
    if (result.status !== 0 && !result.stdout?.trim()) {
       
main function · typescript · L459-L792 (334 LOC)
src/mcp-server.ts
async function main() {
    const server = new Server(
        { name: 'l-hub', version: '0.1.3' },
        { capabilities: { tools: {} } }
    );

    server.setRequestHandler(ListToolsRequestSchema, async () => ({
        tools: [
            {
                name: 'ai_ask',
                description: 'Ask a question to a specialized AI expert. L-Hub auto-routes to the best model based on your configured models and task types. Specify a provider to force a specific one.',
                inputSchema: {
                    type: 'object',
                    properties: {
                        message: { type: 'string', description: 'The question or task.' },
                        provider: { type: 'string', description: 'Force a specific provider (e.g. "deepseek", "glm"). Omit for smart auto-routing.' },
                        system_prompt: { type: 'string', description: 'Optional system-level instructions.' },
                        file_paths: {
                          
Repobility · code-quality intelligence platform · https://repobility.com
getModelsInGroup function · typescript · L250-L252 (3 LOC)
src/model-registry.ts
export function getModelsInGroup(group: string): Array<[string, ModelDefinition]> {
    return Object.entries(MODEL_REGISTRY).filter(([, def]) => def.providerGroup === group);
}
SettingsManager class · typescript · L24-L136 (113 LOC)
src/settings.ts
export class SettingsManager {
    private secretStorage: vscode.SecretStorage;
    private static readonly MODELS_KEY = 'l-hub.models.v2';

    constructor(context: vscode.ExtensionContext) {
        this.secretStorage = context.secrets;
    }

    // ── Legacy API key methods (still used for mcp-server.js sync) ───────────
    public async saveApiKey(provider: string, apiKey: string): Promise<void> {
        await this.secretStorage.store(`apikey.${provider}`, apiKey);
    }

    public async getApiKey(provider: string): Promise<string | undefined> {
        return await this.secretStorage.get(`apikey.${provider}`);
    }

    public async getAllApiKeys(): Promise<Record<string, string>> {
        const keys: Record<string, string> = {};
        for (const provider of SUPPORTED_PROVIDERS) {
            const key = await this.getApiKey(provider);
            if (key) { keys[provider] = key; }
        }
        return keys;
    }

    // ── v2: ModelConfig list ────────────────────────
constructor method · typescript · L28-L30 (3 LOC)
src/settings.ts
    constructor(context: vscode.ExtensionContext) {
        this.secretStorage = context.secrets;
    }
saveApiKey method · typescript · L33-L35 (3 LOC)
src/settings.ts
    public async saveApiKey(provider: string, apiKey: string): Promise<void> {
        await this.secretStorage.store(`apikey.${provider}`, apiKey);
    }
getApiKey method · typescript · L37-L39 (3 LOC)
src/settings.ts
    public async getApiKey(provider: string): Promise<string | undefined> {
        return await this.secretStorage.get(`apikey.${provider}`);
    }
getAllApiKeys method · typescript · L41-L48 (8 LOC)
src/settings.ts
    public async getAllApiKeys(): Promise<Record<string, string>> {
        const keys: Record<string, string> = {};
        for (const provider of SUPPORTED_PROVIDERS) {
            const key = await this.getApiKey(provider);
            if (key) { keys[provider] = key; }
        }
        return keys;
    }
getModels method · typescript · L52-L80 (29 LOC)
src/settings.ts
    public async getModels(): Promise<ModelConfig[]> {
        const raw = await this.secretStorage.get(SettingsManager.MODELS_KEY);
        if (!raw) { return this.getDefaultModels(); }
        try {
            const models = JSON.parse(raw) as ModelConfig[];
            // ── Auto-migrate stale base URLs & model IDs ──────────────────────
            const URL_MIGRATIONS: [string, string][] = [
                ['https://api.minimaxi.com/v1', 'https://api.minimax.io/v1'],
                ['https://api.minimax.chat/v1', 'https://api.minimax.io/v1'],
                ['https://api.minimaxi.io/v1', 'https://api.minimax.io/v1'], // hybrid-typo
            ];
            const MODEL_ID_MIGRATIONS: [string, string][] = [
                ['minimax-text-2.5', 'MiniMax-M2.5'],
                ['minimax-text-01', 'MiniMax-M2.5'],
            ];
            let dirty = false;
            for (const m of models) {
                for (const [from, to] of URL_MIGRATIONS) {
                    if (
saveModels method · typescript · L82-L84 (3 LOC)
src/settings.ts
    public async saveModels(models: ModelConfig[]): Promise<void> {
        await this.secretStorage.store(SettingsManager.MODELS_KEY, JSON.stringify(models));
    }
Repobility analyzer · published findings · https://repobility.com
addModel method · typescript · L86-L90 (5 LOC)
src/settings.ts
    public async addModel(model: ModelConfig): Promise<void> {
        const models = await this.getModels();
        models.push(model);
        await this.saveModels(models);
    }
updateModel method · typescript · L92-L97 (6 LOC)
src/settings.ts
    public async updateModel(id: string, patch: Partial<ModelConfig>): Promise<void> {
        const models = await this.getModels();
        const idx = models.findIndex(m => m.id === id);
        if (idx !== -1) { models[idx] = { ...models[idx], ...patch }; }
        await this.saveModels(models);
    }
removeModel method · typescript · L99-L102 (4 LOC)
src/settings.ts
    public async removeModel(id: string): Promise<void> {
        const models = await this.getModels();
        await this.saveModels(models.filter(m => m.id !== id));
    }
getDefaultModels method · typescript · L106-L126 (21 LOC)
src/settings.ts
    private getDefaultModels(): ModelConfig[] {
        // L-Hub routing philosophy (2026-Q1, based on actual subscriptions):
        // Claude Sonnet 4.6 (Antigravity) handles planning/arch natively — NOT routed here.
        // Code writing → GPT/Codex 5.3 (primary), then MiniMax M2.5 (SWE-bench 80.2%!)
        // MiniMax M2.5 Coding Plan: SWE-bench 80.2% ≈ Claude Opus 4.6, BFCL 76.8% #1
        // GLM-5 Coding Plan: SWE-bench 77.8%, open-source #1, strong agentic & tool use
        // Claude intentionally absent: Antigravity IS Claude Sonnet 4.6.
        return [
            // ── Code tier ─────────────────────────────────────────────────────────
            { id: 'default-gpt', modelId: 'gpt-5.3-codex', label: 'GPT/Codex 5.3 (代码首选)', baseUrl: 'https://api.openai.com/v1', tasks: ['code_gen', 'code_review'], enabled: true, priority: 0 },
            // MiniMax M2.5 Coding Plan: SWE-bench 80.2%, BFCL tool-calling 76.8% (beats Claude Opus 4.6!)
            { id: 'default-minimax', mod
getGeneralConfig method · typescript · L129-L135 (7 LOC)
src/settings.ts
    public getGeneralConfig(): BridgeConfig {
        const config = vscode.workspace.getConfiguration('l-hub');
        return {
            defaultModel: config.get<string>('defaultModel', 'deepseek'),
            retentionDays: config.get<number>('retentionDays', 30),
        };
    }
HistoryStorage class · typescript · L23-L123 (101 LOC)
src/storage.ts
export class HistoryStorage {
    private db: Database.Database;

    constructor(storagePath: string) {
        if (!fs.existsSync(storagePath)) {
            fs.mkdirSync(storagePath, { recursive: true });
        }
        const dbPath = path.join(storagePath, 'history.db');
        this.db = new Database(dbPath);
        this.initSchema();
    }

    private initSchema() {
        this.db.exec(`
            CREATE TABLE IF NOT EXISTS request_history (
                id TEXT PRIMARY KEY,
                timestamp INTEGER NOT NULL,
                client_name TEXT,
                client_version TEXT,
                method TEXT NOT NULL,
                tool_name TEXT,
                model TEXT,
                duration INTEGER,
                input_tokens INTEGER,
                output_tokens INTEGER,
                total_tokens INTEGER,
                request_preview TEXT,
                response_preview TEXT,
                status TEXT NOT NULL,
                error_mess
constructor method · typescript · L26-L33 (8 LOC)
src/storage.ts
    constructor(storagePath: string) {
        if (!fs.existsSync(storagePath)) {
            fs.mkdirSync(storagePath, { recursive: true });
        }
        const dbPath = path.join(storagePath, 'history.db');
        this.db = new Database(dbPath);
        this.initSchema();
    }
initSchema method · typescript · L35-L59 (25 LOC)
src/storage.ts
    private initSchema() {
        this.db.exec(`
            CREATE TABLE IF NOT EXISTS request_history (
                id TEXT PRIMARY KEY,
                timestamp INTEGER NOT NULL,
                client_name TEXT,
                client_version TEXT,
                method TEXT NOT NULL,
                tool_name TEXT,
                model TEXT,
                duration INTEGER,
                input_tokens INTEGER,
                output_tokens INTEGER,
                total_tokens INTEGER,
                request_preview TEXT,
                response_preview TEXT,
                status TEXT NOT NULL,
                error_message TEXT
            );
            
            CREATE INDEX IF NOT EXISTS idx_timestamp ON request_history(timestamp DESC);
            CREATE INDEX IF NOT EXISTS idx_client ON request_history(client_name);
            CREATE INDEX IF NOT EXISTS idx_model ON request_history(model);
        `);
    }
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
saveRecord method · typescript · L61-L78 (18 LOC)
src/storage.ts
    public saveRecord(record: RequestRecord) {
        try {
            const stmt = this.db.prepare(`
                INSERT INTO request_history (
                    id, timestamp, client_name, client_version, method, tool_name, model,
                    duration, input_tokens, output_tokens, total_tokens, request_preview,
                    response_preview, status, error_message
                ) VALUES (
                    @id, @timestamp, @clientName, @clientVersion, @method, @toolName, @model,
                    @duration, @inputTokens, @outputTokens, @totalTokens, @requestPreview,
                    @responsePreview, @status, @errorMessage
                )
            `);
            stmt.run(record);
        } catch (error) {
            console.error('Failed to save history record:', error);
        }
    }
cleanupOldRecords method · typescript · L91-L94 (4 LOC)
src/storage.ts
    public cleanupOldRecords(daysToKeep: number = 30) {
        const cutoff = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
        this.db.prepare('DELETE FROM request_history WHERE timestamp < ?').run(cutoff);
    }
clearAll method · typescript · L96-L98 (3 LOC)
src/storage.ts
    public clearAll() {
        this.db.exec('DELETE FROM request_history');
    }
close method · typescript · L100-L102 (3 LOC)
src/storage.ts
    public close() {
        this.db.close();
    }
mapDbRowToRecord method · typescript · L104-L122 (19 LOC)
src/storage.ts
    private mapDbRowToRecord(row: any): RequestRecord {
        return {
            id: row.id,
            timestamp: row.timestamp,
            clientName: row.client_name,
            clientVersion: row.client_version,
            method: row.method,
            toolName: row.tool_name,
            model: row.model,
            duration: row.duration,
            inputTokens: row.input_tokens,
            outputTokens: row.output_tokens,
            totalTokens: row.total_tokens,
            requestPreview: row.request_preview,
            responsePreview: row.response_preview,
            status: row.status,
            errorMessage: row.error_message
        };
    }
DashboardPanel class · typescript · L6-L259 (254 LOC)
src/webview-provider.ts
export class DashboardPanel {
    public static currentPanel: DashboardPanel | undefined;
    private readonly _panel: vscode.WebviewPanel;
    private readonly _extensionUri: vscode.Uri;
    private _disposables: vscode.Disposable[] = [];

    public static createOrShow(extensionUri: vscode.Uri, storage: HistoryStorage, settings: SettingsManager) {
        const column = vscode.window.activeTextEditor
            ? vscode.window.activeTextEditor.viewColumn
            : undefined;

        if (DashboardPanel.currentPanel) {
            DashboardPanel.currentPanel._panel.reveal(column);
            return;
        }

        const panel = vscode.window.createWebviewPanel(
            'lhubDashboard',
            'L-Hub',
            column || vscode.ViewColumn.One,
            {
                enableScripts: true,
                localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist')],
                retainContextWhenHidden: true
            }
        );

        DashboardPan
createOrShow method · typescript · L12-L34 (23 LOC)
src/webview-provider.ts
    public static createOrShow(extensionUri: vscode.Uri, storage: HistoryStorage, settings: SettingsManager) {
        const column = vscode.window.activeTextEditor
            ? vscode.window.activeTextEditor.viewColumn
            : undefined;

        if (DashboardPanel.currentPanel) {
            DashboardPanel.currentPanel._panel.reveal(column);
            return;
        }

        const panel = vscode.window.createWebviewPanel(
            'lhubDashboard',
            'L-Hub',
            column || vscode.ViewColumn.One,
            {
                enableScripts: true,
                localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'dist')],
                retainContextWhenHidden: true
            }
        );

        DashboardPanel.currentPanel = new DashboardPanel(panel, extensionUri, storage, settings);
    }
constructor method · typescript · L36-L187 (152 LOC)
src/webview-provider.ts
    private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, private storage: HistoryStorage, private settings: SettingsManager) {
        this._panel = panel;
        this._extensionUri = extensionUri;

        this._update();
        this._panel.onDidDispose(() => this.dispose(), null, this._disposables);

        this._panel.webview.onDidReceiveMessage(
            async (message) => {
                switch (message.command) {
                    // ── Legacy key management ─────────────────────────────────
                    case 'getApiKeys': {
                        const keys = await this.settings.getAllApiKeys();
                        this._panel.webview.postMessage({ command: 'loadApiKeys', data: keys });
                        break;
                    }
                    case 'saveApiKey': {
                        if (message.provider && message.key !== undefined) {
                            await this.settings.saveApiKey(message.provider, messag
Repobility · open methodology · https://repobility.com/research/
async method · typescript · L44-L183 (140 LOC)
src/webview-provider.ts
            async (message) => {
                switch (message.command) {
                    // ── Legacy key management ─────────────────────────────────
                    case 'getApiKeys': {
                        const keys = await this.settings.getAllApiKeys();
                        this._panel.webview.postMessage({ command: 'loadApiKeys', data: keys });
                        break;
                    }
                    case 'saveApiKey': {
                        if (message.provider && message.key !== undefined) {
                            await this.settings.saveApiKey(message.provider, message.key);
                        }
                        break;
                    }

                    // ── v2 Model management ───────────────────────────────────
                    case 'getModelsV2': {
                        const models = await this.settings.getModels();
                        // collect per-model API keys
                        const apiKeys:
_syncModelsToFile method · typescript · L190-L208 (19 LOC)
src/webview-provider.ts
    private async _syncModelsToFile() {
        const fs = require('fs');
        const os = require('os');
        const path = require('path');
        const keysFile = path.join(os.homedir(), '.l-hub-keys.json');
        try {
            const models = await this.settings.getModels();
            const enriched = [];
            for (const m of models) {
                const k = await this.settings.getApiKey(`model.${m.id}`);
                enriched.push({ ...m, apiKey: k || '' });
            }
            // Also keep legacy keys for backward compat
            const legacy = await this.settings.getAllApiKeys();
            fs.writeFileSync(keysFile, JSON.stringify({ version: 2, models: enriched, legacy }, null, 2), 'utf8');
        } catch (e) {
            console.error('[L-Hub] Failed to sync models to file:', e);
        }
    }
dispose method · typescript · L210-L217 (8 LOC)
src/webview-provider.ts
    public dispose() {
        DashboardPanel.currentPanel = undefined;
        this._panel.dispose();
        while (this._disposables.length) {
            const x = this._disposables.pop();
            if (x) { x.dispose(); }
        }
    }
_update method · typescript · L219-L222 (4 LOC)
src/webview-provider.ts
    private _update() {
        const webview = this._panel.webview;
        this._panel.webview.html = this._getHtmlForWebview(webview);
    }
_getHtmlForWebview method · typescript · L224-L258 (35 LOC)
src/webview-provider.ts
    private _getHtmlForWebview(webview: vscode.Webview) {
        const scriptPathOnDisk = vscode.Uri.joinPath(this._extensionUri, 'dist', 'webview.js');
        const scriptUri = webview.asWebviewUri(scriptPathOnDisk);

        return `<!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>L-Hub</title>
                <style>
                    body {
                        padding: 0;
                        margin: 0;
                        background-color: var(--vscode-editor-background);
                        color: var(--vscode-editor-foreground);
                        font-family: var(--vscode-font-family);
                        height: 100vh;
                        display: flex;
                        flex-direction: column;
                    }
                    #root {
                        flex: 1;
  
WsTransport class · typescript · L13-L41 (29 LOC)
src/ws-server.ts
class WsTransport implements Transport {
    public onclose?: () => void;
    public onerror?: (error: Error) => void;
    public onmessage?: (message: JSONRPCMessage) => void;

    constructor(private ws: import('ws').WebSocket) {
        ws.on('message', (message: Buffer) => {
            try {
                const reqStr = message.toString('utf8');
                const req = JSON.parse(reqStr);
                this.onmessage?.(req);
            } catch (e) {
                console.error('WsTransport parse error:', e);
            }
        });
        ws.on('close', () => this.onclose?.());
        ws.on('error', (err: Error) => this.onerror?.(err));
    }

    async start() {}

    async close() {
        this.ws.close();
    }

    async send(message: JSONRPCMessage) {
        this.ws.send(JSON.stringify(message) + '\n');
    }
}
constructor method · typescript · L18-L30 (13 LOC)
src/ws-server.ts
    constructor(private ws: import('ws').WebSocket) {
        ws.on('message', (message: Buffer) => {
            try {
                const reqStr = message.toString('utf8');
                const req = JSON.parse(reqStr);
                this.onmessage?.(req);
            } catch (e) {
                console.error('WsTransport parse error:', e);
            }
        });
        ws.on('close', () => this.onclose?.());
        ws.on('error', (err: Error) => this.onerror?.(err));
    }
close method · typescript · L34-L36 (3 LOC)
src/ws-server.ts
    async close() {
        this.ws.close();
    }
Repobility · code-quality intelligence platform · https://repobility.com
send method · typescript · L38-L40 (3 LOC)
src/ws-server.ts
    async send(message: JSONRPCMessage) {
        this.ws.send(JSON.stringify(message) + '\n');
    }
LinglanMcpServer class · typescript · L43-L259 (217 LOC)
src/ws-server.ts
export class LinglanMcpServer {
    private httpServer: http.Server;
    private wss: WebSocketServer;
    private portFile = path.join(os.homedir(), '.l-hub.port');

    constructor(
        private storage: HistoryStorage,
        private settings: SettingsManager
    ) {
        this.httpServer = http.createServer();
        this.wss = new WebSocketServer({ server: this.httpServer });

        this.wss.on('connection', (ws) => {
            const transport = new WsTransport(ws);
            const mcpServer = new Server({
                name: "l-hub",
                version: "0.0.1"
            }, {
                capabilities: {
                    tools: {}
                }
            });

            // Set up lists
            mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
                return {
                    tools: [
                        {
                            name: "ai_ask",
                            description: "Ask a single questio
constructor method · typescript · L48-L164 (117 LOC)
src/ws-server.ts
    constructor(
        private storage: HistoryStorage,
        private settings: SettingsManager
    ) {
        this.httpServer = http.createServer();
        this.wss = new WebSocketServer({ server: this.httpServer });

        this.wss.on('connection', (ws) => {
            const transport = new WsTransport(ws);
            const mcpServer = new Server({
                name: "l-hub",
                version: "0.0.1"
            }, {
                capabilities: {
                    tools: {}
                }
            });

            // Set up lists
            mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
                return {
                    tools: [
                        {
                            name: "ai_ask",
                            description: "Ask a single question to an AI provider. Route complex architecture queries to GLM, general coding to DeepSeek, translation to Qwen, UI/design to MiniMax.",
                            in
page 1 / 2next ›