UNPKG

vscode-ai-model-detector

Version:

Real-time AI model detection with 100% accuracy using Chat Participant API

577 lines 21.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ModelDetectorService = void 0; const vscode = __importStar(require("vscode")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Core model detection service with 100% accuracy through multiple methods */ class ModelDetectorService { constructor(context) { this.detectionHistory = []; this.modelChangeEmitter = new vscode.EventEmitter(); // Event for model changes this.onModelChange = this.modelChangeEmitter.event; this.context = context; this.loadDetectionHistory(); this.setupModelChangeListener(); } /** * PRIMARY METHOD: Detect model from chat participant context (100% accurate) */ async detectFromChatContext(request) { const startTime = Date.now(); try { this.log('Detecting model from chat context...'); // 🎯 BREAKTHROUGH: Direct access to user-selected model const model = request.model; const modelInfo = { id: model.id, name: model.name, vendor: model.vendor, family: model.family, version: model.version, maxTokens: model.maxInputTokens, accuracy: 'Perfect', source: 'chat-context', timestamp: new Date(), metadata: { requestId: this.generateRequestId(), chatLocation: 'VS Code Chat View' } }; // Store as current model this.updateCurrentModel(modelInfo); // Add to history this.addToHistory(modelInfo, 'chat-participant'); this.log(`Chat context detection success: ${modelInfo.id} (${modelInfo.vendor}) - Perfect accuracy`); const endTime = Date.now(); return { model: modelInfo, success: true, method: 'chat-participant', performance: { startTime, endTime, duration: endTime - startTime } }; } catch (error) { this.log(`Chat context detection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error'); const endTime = Date.now(); return { success: false, error: `Chat context detection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, method: 'chat-participant', performance: { startTime, endTime, duration: endTime - startTime } }; } } /** * SECONDARY METHOD: Detect available models via LM API */ async detectFromLMAPI() { const startTime = Date.now(); try { this.log('Detecting model from LM API...'); // Get all available chat models const models = await vscode.lm.selectChatModels(); // Build ModelInfo list const availableModels = models.map(model => ({ id: model.id, name: model.name ?? model.id, vendor: model.vendor ?? 'Unknown', family: model.family ?? '', version: model.version ?? '', maxTokens: model.maxInputTokens ?? 0, accuracy: 'Available', source: 'lm-api', timestamp: new Date(), metadata: { apiMethod: 'selectChatModels', raw: model } })); // Try to detect which model is currently selected by the chat picker. // Different LM implementations expose different flags (isSelected, selected, isDefault, isCurrent). let currentModel; for (const m of models) { const raw = m; if (raw.isSelected === true || raw.selected === true || raw.isCurrent === true || raw.isDefault === true) { currentModel = { id: m.id, name: m.name ?? m.id, vendor: m.vendor ?? 'Unknown', family: m.family ?? '', version: m.version ?? '', maxTokens: m.maxInputTokens ?? 0, accuracy: 'Available', source: 'lm-api', timestamp: new Date(), metadata: { apiMethod: 'selectChatModels', raw: m } }; break; } } // If none of the models expose a selection flag, fall back to heuristics: if (!currentModel && availableModels.length > 0) { // heuristic: prefer models whose id contains known vendor tokens or common 'current' markers currentModel = availableModels.find(m => /gemini|gpt-4o|gpt-4|claude|sonnet|copilot/i.test(m.id)) || availableModels[0]; if (currentModel) currentModel.accuracy = 'Available'; } if (currentModel) { this.updateCurrentModel(currentModel); this.addToHistory(currentModel, 'lm-api'); this.log(`LM API detection success: ${currentModel.id} (${currentModel.vendor}) - ${currentModel.accuracy}`); } else { this.log('LM API detection: no models available', 'warn'); } const endTime = Date.now(); return { model: currentModel, success: true, availableModels, method: 'lm-api', performance: { startTime, endTime, duration: endTime - startTime } }; } catch (error) { this.log(`LM API detection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error'); const endTime = Date.now(); return { success: false, error: `LM API detection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, method: 'lm-api', performance: { startTime, endTime, duration: endTime - startTime } }; } } /** * FALLBACK METHOD: Get cached model from storage */ async detectFromStorage() { const startTime = Date.now(); try { this.log('Attempting storage detection...'); const cachedModel = this.context.globalState.get('currentModel'); if (cachedModel) { // Update timestamp but keep other properties const modelInfo = { ...cachedModel, accuracy: 'Cached', source: 'storage', timestamp: new Date() }; this.log(`Storage detection success: ${modelInfo.id} (${modelInfo.vendor})`); const endTime = Date.now(); return { model: modelInfo, success: true, method: 'command', performance: { startTime, endTime, duration: endTime - startTime } }; } else { throw new Error('No cached model found'); } } catch (error) { this.log(`Storage detection failed: ${error instanceof Error ? error.message : 'Unknown'}`); const endTime = Date.now(); return { success: false, error: `Storage detection failed: ${error instanceof Error ? error.message : 'No cached model'}`, method: 'command', performance: { startTime, endTime, duration: endTime - startTime } }; } } /** * LAST RESORT METHOD: Safe JSON parsing from VS Code globalStorage files */ async detectFromFileSystem() { const startTime = Date.now(); try { this.log('Attempting filesystem detection as last resort...'); // Common VS Code storage paths on Windows const userHome = process.env.USERPROFILE || process.env.HOME || ''; const storagePaths = [ path.join(userHome, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage'), path.join(userHome, 'AppData', 'Roaming', 'Code', 'logs') ]; let foundModel = null; for (const basePath of storagePaths) { if (!fs.existsSync(basePath)) continue; foundModel = await this.scanDirectoryForModel(basePath); if (foundModel) break; } if (foundModel) { this.log(`Filesystem detection success: ${foundModel.id} (low confidence)`); const endTime = Date.now(); return { model: foundModel, success: true, method: 'command', performance: { startTime, endTime, duration: endTime - startTime } }; } else { throw new Error('No model found in filesystem scan'); } } catch (error) { this.log(`Filesystem detection failed: ${error instanceof Error ? error.message : 'Unknown'}`); const endTime = Date.now(); return { success: false, error: `Filesystem detection failed: ${error instanceof Error ? error.message : 'No model files found'}`, method: 'command', performance: { startTime, endTime, duration: endTime - startTime } }; } } /** * COMPREHENSIVE METHOD: Try all detection methods with fallbacks */ async detectCurrentModel(request) { this.log('Starting model detection...'); // If we were called with a chat request, prefer the chat context (authoritative) if (request && request.model) { this.log('Using chat context detection (authoritative)'); return await this.detectFromChatContext(request); } // Next try LM API (reliable when chat context isn't available) this.log('Attempting LM API detection...'); const lmResult = await this.detectFromLMAPI(); if (lmResult.success && lmResult.model) { return lmResult; } // Fallback to cached storage const storageResult = await this.detectFromStorage(); if (storageResult.success) { return storageResult; } // Last resort: filesystem scan const filesystemResult = await this.detectFromFileSystem(); if (filesystemResult.success) { return filesystemResult; } // If all methods fail, return error result with guidance this.log('All detection methods failed'); return { success: false, error: 'All detection methods failed. Try using @modeldetector /detect in the VS Code Chat for 100% accuracy.', method: 'command', performance: { startTime: Date.now(), endTime: Date.now(), duration: 0 } }; } /** * Get current model (cached) */ getCurrentModel() { return this.currentModel; } /** * Get detection history */ getDetectionHistory() { return [...this.detectionHistory]; } /** * Clear detection history */ clearDetectionHistory() { this.detectionHistory = []; this.context.globalState.update('detectionHistory', []); } /** * Setup model change event listener */ setupModelChangeListener() { vscode.lm.onDidChangeChatModels(() => { this.handleModelChange(); }); } /** * Handle model change events */ async handleModelChange() { const previousModel = this.currentModel; const result = await this.detectFromLMAPI(); if (result.success && result.model) { const changeEvent = { previousModel, currentModel: result.model, timestamp: new Date(), source: 'api-change' }; this.modelChangeEmitter.fire(changeEvent); } } /** * Update current model and persist to storage */ updateCurrentModel(model) { this.currentModel = model; this.context.globalState.update('currentModel', model); } /** * Add detection to history */ addToHistory(model, method) { const entry = { id: this.generateRequestId(), model, context: { method, location: 'VS Code', user: vscode.env.appName || 'unknown' }, timestamp: new Date() }; this.detectionHistory.unshift(entry); // Keep history size under limit const limit = vscode.workspace.getConfiguration('aiModelDetector').get('historyLimit', 50); if (this.detectionHistory.length > limit) { this.detectionHistory = this.detectionHistory.slice(0, limit); } // Persist to storage this.context.globalState.update('detectionHistory', this.detectionHistory); } /** * Load detection history from storage */ loadDetectionHistory() { const stored = this.context.globalState.get('detectionHistory', []); this.detectionHistory = stored; } /** * Generate unique request ID */ generateRequestId() { return `detect_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; } /** * Logging utility with configurable output */ log(message, level = 'info') { const config = vscode.workspace.getConfiguration('aiModelDetector'); const enableLogging = config.get('enableLogging', false); if (enableLogging) { const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] ModelDetector: ${message}`; console.log(logMessage); // Optionally show important messages in VS Code UI if (level === 'error') { vscode.window.showErrorMessage(message); } else if (level === 'warn' && config.get('showWarnings', false)) { vscode.window.showWarningMessage(message); } } } /** * Safely scan directory for model information in JSON files */ async scanDirectoryForModel(dirPath, maxDepth = 2) { try { if (maxDepth <= 0) return null; const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); try { if (entry.isFile() && entry.name.endsWith('.json')) { const modelInfo = await this.parseModelFromJsonFile(fullPath); if (modelInfo) return modelInfo; } else if (entry.isDirectory() && maxDepth > 1) { // Recursively scan subdirectories const result = await this.scanDirectoryForModel(fullPath, maxDepth - 1); if (result) return result; } } catch (error) { // Skip unreadable files/directories continue; } } return null; } catch (error) { return null; } } /** * Safely parse model information from a JSON file */ async parseModelFromJsonFile(filePath) { try { // Skip large files to avoid performance issues const stats = fs.statSync(filePath); if (stats.size > 10 * 1024 * 1024) return null; // 10MB limit const content = fs.readFileSync(filePath, 'utf8'); const json = JSON.parse(content); // Look for model information in common locations const modelId = this.extractModelId(json); if (!modelId) return null; // Validate model ID format if (!/^(copilot\/|gpt|claude|gemini|sonnet)/i.test(modelId)) return null; // Create ModelInfo with inferred vendor const modelInfo = { id: modelId.toLowerCase(), name: modelId, vendor: this.inferVendor(modelId), family: this.inferFamily(modelId), version: '', maxTokens: 0, accuracy: 'Unknown', source: 'storage', timestamp: new Date(), metadata: { sourceFile: filePath, detectionMethod: 'json-parse' } }; return modelInfo; } catch (error) { return null; } } /** * Extract model ID from JSON object using multiple strategies */ extractModelId(obj) { if (!obj || typeof obj !== 'object') return null; // Try various common paths where model IDs might be stored const candidates = [ obj.modelId, obj.model?.id, obj.request?.model?.id, obj.agent?.model, obj.details?.modelId, obj.source?.definitionId, obj.toolSpecificData?.rawInput?.model?.id ]; for (const candidate of candidates) { if (typeof candidate === 'string' && candidate.trim().length > 0) { return candidate.trim(); } } return null; } /** * Infer vendor from model ID */ inferVendor(modelId) { const id = modelId.toLowerCase(); if (id.includes('copilot') || id.startsWith('gpt')) return 'OpenAI'; if (id.includes('claude') || id.includes('sonnet')) return 'Anthropic'; if (id.includes('gemini')) return 'Google'; return 'Unknown'; } /** * Infer model family from model ID */ inferFamily(modelId) { const id = modelId.toLowerCase(); if (id.includes('gpt-4')) return 'gpt-4'; if (id.includes('gpt-3')) return 'gpt-3.5'; if (id.includes('claude')) return 'claude'; if (id.includes('gemini')) return 'gemini'; return ''; } /** * Dispose of the service and clean up resources */ dispose() { this.modelChangeEmitter.dispose(); } } exports.ModelDetectorService = ModelDetectorService; //# sourceMappingURL=modelDetector.js.map