/** * In-memory implementations of kernel ports for testing. * These mirror the idealized reference model's in-memory adapters. */ import type { Recipe } from "../modules/lifecycle/types"; import type { RecipeId } from "../types/brand"; import type { ActiveRuntime, ChatMessage, ChatRun, ChatSession, ChatStore, CompletionResult, ControllerEvent, DownloadRecord, DownloadStore, EventBus, JobRecord, JobStore, LayerAllocation, LifetimeMetrics, MetricsStore, NodeRecord, NodeRegistry, ProviderResolution, ProviderRouter, RecipeRegistry, RuntimeLeaseManager, Usage, } from "./interfaces"; import { DOWNLOAD_STATUS, EVENT_TYPE, JOB_STATUS, RUN_STATUS, } from "./contracts"; let _counter = 0; const nextId = (prefix: string): string => `${prefix}-${String(++_counter).padStart(4, "0")}`; const now = (): string => new Date().toISOString(); // --------------------------------------------------------------------------- // EventBus // --------------------------------------------------------------------------- export class InMemoryEventBus implements EventBus { private readonly events: ControllerEvent[] = []; publish(type: string, data: T): ControllerEvent { const event: ControllerEvent = { id: nextId("evt"), type, ts: now(), data }; this.events.push(event as ControllerEvent); return event; } list(): ControllerEvent[] { return [...this.events]; } } // --------------------------------------------------------------------------- // RecipeRegistry // --------------------------------------------------------------------------- export class InMemoryRecipeRegistry implements RecipeRegistry { private readonly byId = new Map(); private readonly byModel = new Map(); upsert(recipe: Recipe): Recipe { this.byId.set(recipe.id, recipe); if (recipe.served_model_name) { this.byModel.set(recipe.served_model_name, recipe); } return recipe; } getById(recipeId: string): Recipe | undefined { return this.byId.get(recipeId); } getByServedModelName(name: string): Recipe | undefined { return this.byModel.get(name); } list(): Recipe[] { return [...this.byId.values()]; } delete(recipeId: string): boolean { return this.byId.delete(recipeId); } } // --------------------------------------------------------------------------- // RuntimeLeaseManager // --------------------------------------------------------------------------- export class InMemoryRuntimeLeaseManager implements RuntimeLeaseManager { private active: ActiveRuntime | undefined; private readonly events: EventBus; constructor(events: EventBus) { this.events = events; } getActive(): ActiveRuntime | undefined { return this.active ? { ...this.active } : undefined; } async ensure(recipe: Recipe): Promise { if (this.active && this.active.recipeId === recipe.id) { return { ...this.active }; } this.events.publish(EVENT_TYPE.RUNTIME_ACTIVATING, { recipeId: recipe.id, servedModelName: recipe.served_model_name ?? recipe.id, backend: recipe.backend, }); const runtime: ActiveRuntime = { recipeId: recipe.id, servedModelName: recipe.served_model_name ?? recipe.id, backend: recipe.backend, port: recipe.port ?? 8000, pid: null, activatedAt: now(), }; this.active = runtime; this.events.publish(EVENT_TYPE.RUNTIME_ACTIVATED, runtime); return { ...runtime }; } async evict(): Promise { const previous = this.active; this.active = undefined; if (previous) { this.events.publish(EVENT_TYPE.RUNTIME_EVICTED, previous); } return previous; } } // --------------------------------------------------------------------------- // ProviderRouter // --------------------------------------------------------------------------- export class InMemoryProviderRouter implements ProviderRouter { private readonly recipes: RecipeRegistry; private readonly externalProviders: Map; constructor(recipes: RecipeRegistry, externalProviders?: Record) { this.recipes = recipes; this.externalProviders = new Map( Object.entries(externalProviders ?? { openai: "https://api.openai.com/v1", anthropic: "https://api.anthropic.com/v1", local: "http://127.0.0.1:8000/v1", }), ); } resolve(requestedModel: string, fallbackProvider = "openai"): ProviderResolution { const byId = this.recipes.getById(requestedModel); if (byId) { return { kind: "managed", provider: "local", requestedModel, resolvedModel: byId.served_model_name ?? byId.id, recipe: byId }; } const byName = this.recipes.getByServedModelName(requestedModel); if (byName) { return { kind: "managed", provider: "local", requestedModel, resolvedModel: byName.served_model_name ?? byName.id, recipe: byName }; } const slash = requestedModel.indexOf("/"); if (slash > 0) { const provider = requestedModel.slice(0, slash); const model = requestedModel.slice(slash + 1); if (this.externalProviders.has(provider) && model) { return { kind: "external", provider, requestedModel, resolvedModel: model, baseUrl: this.externalProviders.get(provider) }; } } return { kind: "external", provider: fallbackProvider, requestedModel, resolvedModel: requestedModel, baseUrl: this.externalProviders.get(fallbackProvider) }; } } // --------------------------------------------------------------------------- // ChatStore // --------------------------------------------------------------------------- export class InMemoryChatStore implements ChatStore { private readonly sessions = new Map(); private readonly messagesBySession = new Map(); private readonly runs = new Map(); createSession(title = "Untitled chat"): ChatSession { const session: ChatSession = { id: nextId("sess"), title, model: null, createdAt: now(), updatedAt: now() }; this.sessions.set(session.id, session); this.messagesBySession.set(session.id, []); return { ...session }; } getSession(sessionId: string): ChatSession | undefined { const s = this.sessions.get(sessionId); return s ? { ...s } : undefined; } listSessions(): ChatSession[] { return [...this.sessions.values()].map((s) => ({ ...s })); } deleteSession(sessionId: string): boolean { this.messagesBySession.delete(sessionId); return this.sessions.delete(sessionId); } appendMessage(sessionId: string, message: Omit): ChatMessage { if (!this.sessions.has(sessionId)) throw new Error(`Unknown session: ${sessionId}`); const stored: ChatMessage = { id: nextId("msg"), createdAt: now(), ...message }; this.messagesBySession.get(sessionId)!.push(stored); const session = this.sessions.get(sessionId)!; session.updatedAt = stored.createdAt; return { ...stored }; } listMessages(sessionId: string): ChatMessage[] { return [...(this.messagesBySession.get(sessionId) ?? [])].map((m) => ({ ...m })); } startRun(run: Omit): ChatRun { const ts = now(); const stored: ChatRun = { id: nextId("run"), createdAt: ts, updatedAt: ts, ...run }; this.runs.set(stored.id, stored); return { ...stored }; } finishRun(runId: string, patch: Partial): ChatRun { const run = this.runs.get(runId); if (!run) throw new Error(`Unknown run: ${runId}`); Object.assign(run, patch, { updatedAt: now() }); return { ...run }; } abortRunsForModel(model: string, provider: string): number { let count = 0; for (const run of this.runs.values()) { if (run.provider === provider && run.resolvedModel === model && (run.status === RUN_STATUS.STARTING || run.status === RUN_STATUS.RUNNING)) { run.status = RUN_STATUS.ABORTED; run.updatedAt = now(); count++; } } return count; } listRuns(sessionId?: string): ChatRun[] { const runs = [...this.runs.values()]; return (sessionId ? runs.filter((r) => r.sessionId === sessionId) : runs).map((r) => ({ ...r })); } getRun(runId: string): ChatRun | undefined { const run = this.runs.get(runId); return run ? { ...run } : undefined; } } // --------------------------------------------------------------------------- // MetricsStore // --------------------------------------------------------------------------- export class InMemoryMetricsStore implements MetricsStore { private state: LifetimeMetrics = { requestsTotal: 0, promptTokensTotal: 0, completionTokensTotal: 0, totalTokensTotal: 0, modelSwitchesTotal: 0, }; snapshot(): LifetimeMetrics { return { ...this.state }; } addUsage(usage: Usage): void { this.state.promptTokensTotal += usage.promptTokens; this.state.completionTokensTotal += usage.completionTokens; this.state.totalTokensTotal += usage.totalTokens; } addRequest(): void { this.state.requestsTotal++; } addModelSwitch(): void { this.state.modelSwitchesTotal++; } } // --------------------------------------------------------------------------- // DownloadStore // --------------------------------------------------------------------------- export class InMemoryDownloadStore implements DownloadStore { private readonly downloads = new Map(); create(modelId: string, targetDir: string): DownloadRecord { const ts = now(); const record: DownloadRecord = { id: nextId("dl"), modelId, targetDir, status: DOWNLOAD_STATUS.QUEUED, createdAt: ts, updatedAt: ts }; this.downloads.set(record.id, record); return { ...record }; } update(downloadId: string, patch: Partial): DownloadRecord { const record = this.downloads.get(downloadId); if (!record) throw new Error(`Unknown download: ${downloadId}`); Object.assign(record, patch, { updatedAt: now() }); return { ...record }; } list(): DownloadRecord[] { return [...this.downloads.values()].map((d) => ({ ...d })); } } // --------------------------------------------------------------------------- // JobStore // --------------------------------------------------------------------------- export class InMemoryJobStore implements JobStore { private readonly jobs = new Map(); create(type: string, input: unknown): JobRecord { const ts = now(); const job: JobRecord = { id: nextId("job"), type, status: JOB_STATUS.QUEUED, progress: 0, createdAt: ts, updatedAt: ts, input, logs: [] }; this.jobs.set(job.id, job); return { ...job }; } update(jobId: string, patch: Partial): JobRecord { const job = this.jobs.get(jobId); if (!job) throw new Error(`Unknown job: ${jobId}`); Object.assign(job, patch, { updatedAt: now() }); return { ...job }; } list(): JobRecord[] { return [...this.jobs.values()].map((j) => ({ ...j })); } } // --------------------------------------------------------------------------- // NodeRegistry // --------------------------------------------------------------------------- export class InMemoryNodeRegistry implements NodeRegistry { private readonly nodes = new Map(); private readonly allocations = new Map(); register(nodeId: string, host: string, labels: Record = {}): NodeRecord { const ts = now(); const node: NodeRecord = { nodeId, host, labels, registeredAt: this.nodes.get(nodeId)?.registeredAt ?? ts, lastHeartbeatAt: ts, }; this.nodes.set(nodeId, node); return { ...node }; } heartbeat(nodeId: string): NodeRecord { const node = this.nodes.get(nodeId); if (!node) throw new Error(`Unknown node: ${nodeId}`); node.lastHeartbeatAt = now(); return { ...node }; } setAllocation(nodeId: string, modelKey: string, startLayer: number, endLayer: number): LayerAllocation { if (!this.nodes.has(nodeId)) throw new Error(`Unknown node: ${nodeId}`); if (startLayer > endLayer) throw new Error("startLayer must be <= endLayer"); for (const alloc of this.allocations.values()) { if (alloc.modelKey !== modelKey) continue; if (!(endLayer < alloc.startLayer || startLayer > alloc.endLayer)) { throw new Error( `Layer range ${startLayer}-${endLayer} overlaps existing ${alloc.startLayer}-${alloc.endLayer} for model ${modelKey}`, ); } } const record: LayerAllocation = { allocationId: nextId("alloc"), nodeId, modelKey, startLayer, endLayer }; this.allocations.set(record.allocationId, record); return { ...record }; } clearAllocation(modelKey: string, nodeId: string): boolean { for (const [key, alloc] of this.allocations) { if (alloc.modelKey === modelKey && alloc.nodeId === nodeId) { this.allocations.delete(key); return true; } } return false; } listNodes(): NodeRecord[] { return [...this.nodes.values()].map((n) => ({ ...n })); } listAllocations(modelKey?: string): LayerAllocation[] { return [...this.allocations.values()] .filter((a) => (modelKey ? a.modelKey === modelKey : true)) .map((a) => ({ ...a })); } } // --------------------------------------------------------------------------- // Factory // --------------------------------------------------------------------------- export interface TestKernelParts { events: InMemoryEventBus; recipes: InMemoryRecipeRegistry; runtime: InMemoryRuntimeLeaseManager; providers: InMemoryProviderRouter; chats: InMemoryChatStore; metrics: InMemoryMetricsStore; downloads: InMemoryDownloadStore; jobs: InMemoryJobStore; cluster: InMemoryNodeRegistry; } export function createTestKernelParts(): TestKernelParts { const events = new InMemoryEventBus(); const recipes = new InMemoryRecipeRegistry(); const runtime = new InMemoryRuntimeLeaseManager(events); const providers = new InMemoryProviderRouter(recipes); const chats = new InMemoryChatStore(); const metrics = new InMemoryMetricsStore(); const downloads = new InMemoryDownloadStore(); const jobs = new InMemoryJobStore(); const cluster = new InMemoryNodeRegistry(); return { events, recipes, runtime, providers, chats, metrics, downloads, jobs, cluster }; }