/** * Models Handler * * Handles /v1/models endpoints. */ import * as http from 'node:http'; import type { IModelInfo, IListModelsResponse, IApiError, } from '../../interfaces/api.ts'; import { logger } from '../../logger.ts'; import { ContainerManager } from '../../containers/container-manager.ts'; import { ModelRegistry } from '../../models/registry.ts'; /** * Handler for model-related requests */ export class ModelsHandler { private containerManager: ContainerManager; private modelRegistry: ModelRegistry; constructor(containerManager: ContainerManager, modelRegistry: ModelRegistry) { this.containerManager = containerManager; this.modelRegistry = modelRegistry; } /** * Handle GET /v1/models */ public async handleListModels(res: http.ServerResponse): Promise { try { const models = await this.getAvailableModels(); const response: IListModelsResponse = { object: 'list', data: models, }; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(response)); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`Failed to list models: ${message}`); this.sendError(res, 500, `Failed to list models: ${message}`, 'server_error'); } } /** * Handle GET /v1/models/:model */ public async handleGetModel(res: http.ServerResponse, modelId: string): Promise { try { const models = await this.getAvailableModels(); const model = models.find((m) => m.id === modelId); if (!model) { this.sendError(res, 404, `Model "${modelId}" not found`, 'model_not_found'); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(model)); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error(`Failed to get model info: ${message}`); this.sendError(res, 500, `Failed to get model info: ${message}`, 'server_error'); } } /** * Get all available models from containers and greenlist */ private async getAvailableModels(): Promise { const models: IModelInfo[] = []; const seen = new Set(); const timestamp = Math.floor(Date.now() / 1000); // Get models from running containers const containerModels = await this.containerManager.getAllAvailableModels(); for (const [modelId, modelInfo] of containerModels) { if (!seen.has(modelId)) { seen.add(modelId); models.push({ id: modelId, object: 'model', created: timestamp, owned_by: `modelgrid-${modelInfo.container}`, }); } } // Add greenlit models that aren't loaded yet const greenlitModels = await this.modelRegistry.getAllGreenlitModels(); for (const greenlit of greenlitModels) { if (!seen.has(greenlit.name)) { seen.add(greenlit.name); models.push({ id: greenlit.name, object: 'model', created: timestamp, owned_by: `modelgrid-${greenlit.container}`, }); } } // Sort alphabetically models.sort((a, b) => a.id.localeCompare(b.id)); return models; } /** * Send error response */ private sendError( res: http.ServerResponse, statusCode: number, message: string, type: string, param?: string, ): void { const error: IApiError = { error: { message, type, param, code: null, }, }; res.writeHead(statusCode, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(error)); } }