/** * Base Container * * Abstract base class for AI model containers. */ import type { IContainerConfig, IContainerStatus, ILoadedModel, TContainerType, } from '../interfaces/container.ts'; import type { IChatCompletionRequest, IChatCompletionResponse } from '../interfaces/api.ts'; import { ContainerRuntime } from '../docker/container-runtime.ts'; import { logger } from '../logger.ts'; /** * Model pull progress callback */ export type TModelPullProgress = (progress: { model: string; status: string; percent?: number; }) => void; /** * Abstract base class for AI model containers */ export abstract class BaseContainer { /** Container type */ public abstract readonly type: TContainerType; /** Display name */ public abstract readonly displayName: string; /** Default Docker image */ public abstract readonly defaultImage: string; /** Default internal port */ public abstract readonly defaultPort: number; /** Container configuration */ protected config: IContainerConfig; /** Container runtime */ protected runtime: ContainerRuntime; constructor(config: IContainerConfig) { this.config = config; this.runtime = new ContainerRuntime(); } /** * Get the container configuration */ public getConfig(): IContainerConfig { return this.config; } /** * Get the endpoint URL for this container */ public getEndpoint(): string { const port = this.config.externalPort || this.config.port; return `http://localhost:${port}`; } /** * Start the container */ public async start(): Promise { logger.info(`Starting ${this.displayName} container: ${this.config.name}`); return this.runtime.startContainer(this.config); } /** * Stop the container */ public async stop(): Promise { logger.info(`Stopping ${this.displayName} container: ${this.config.name}`); return this.runtime.stopContainer(this.config.id); } /** * Restart the container */ public async restart(): Promise { logger.info(`Restarting ${this.displayName} container: ${this.config.name}`); return this.runtime.restartContainer(this.config.id); } /** * Remove the container */ public async remove(): Promise { logger.info(`Removing ${this.displayName} container: ${this.config.name}`); return this.runtime.removeContainer(this.config.id); } /** * Get container status */ public async getStatus(): Promise { return this.runtime.getContainerStatus(this.config); } /** * Get container logs */ public async getLogs(lines: number = 100): Promise { return this.runtime.getLogs(this.config.id, { lines }); } /** * Check if the container is healthy */ public abstract isHealthy(): Promise; /** * Get list of available models */ public abstract listModels(): Promise; /** * Get list of loaded models with details */ public abstract getLoadedModels(): Promise; /** * Pull a model */ public abstract pullModel(modelName: string, onProgress?: TModelPullProgress): Promise; /** * Remove a model */ public abstract removeModel(modelName: string): Promise; /** * Send a chat completion request */ public abstract chatCompletion(request: IChatCompletionRequest): Promise; /** * Stream a chat completion request */ public abstract chatCompletionStream( request: IChatCompletionRequest, onChunk: (chunk: string) => void, ): Promise; /** * Make HTTP request to container */ protected async fetch( path: string, options: { method?: string; headers?: Record; body?: unknown; timeout?: number; } = {}, ): Promise { const endpoint = this.getEndpoint(); const url = `${endpoint}${path}`; const controller = new AbortController(); const timeout = options.timeout || 30000; const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers, }, body: options.body ? JSON.stringify(options.body) : undefined, signal: controller.signal, }); return response; } finally { clearTimeout(timeoutId); } } /** * Make HTTP request and parse JSON response */ protected async fetchJson( path: string, options: { method?: string; headers?: Record; body?: unknown; timeout?: number; } = {}, ): Promise { const response = await this.fetch(path, options); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } return response.json(); } /** * Generate a unique request ID */ protected generateRequestId(): string { return `chatcmpl-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 8)}`; } }