Function bodies 57 total
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.erroautoRegisterMcpConfig 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(storagePdeactivate 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 responsisRetryableError 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', modgetGeneralConfig 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_messconstructor 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
}
);
DashboardPancreateOrShow 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, messagRepobility · 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 questioconstructor 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.",
inpage 1 / 2next ›