Files
ocp/model-registry.mjs
T
taodengandClaude Opus 4.6 1f1b3871da feat: Phase 1 — modular backend architecture (v4.0-alpha)
Internal refactor: extract BackendAdapter, ModelRegistry, AgentRouter,
SessionManager, and StatsCollector as standalone modules. Claude CLI
logic encapsulated in ClaudeCliAdapter.

External API completely unchanged — all existing endpoints behave
identically. Legacy request path (callClaude/callClaudeStreaming)
untouched. v4 modules load in parallel for validation.

New endpoints:
  GET /backends  — backend health and status
  GET /routing   — agent routing table

Architecture:
  unified-chunk.mjs    — streaming protocol (delta/done/error)
  backends/base.mjs    — BackendAdapter interface
  backends/claude-cli.mjs — Claude CLI adapter (core)
  model-registry.mjs   — model → backend mapping (Layer 1)
  agent-router.mjs     — agent policy routing (Layer 2)
  session-manager.mjs  — conversation session management
  stats-collector.mjs  — per-model/backend/agent metrics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:02:53 +10:00

100 lines
2.6 KiB
JavaScript

/**
* Model Registry — Layer 1 of OCP routing.
*
* Static mapping: model ID → backend ID.
* This is infrastructure, not policy. Every model must map to exactly one backend.
*
* Non-agent callers (Cline, Aider, etc.) only send a model name — this module
* is the only thing that tells OCP where to route the request.
*/
export class ModelRegistry {
constructor() {
/** @type {Map<string, {backendId: string, canonical: string}>} */
this._models = new Map();
/** @type {Map<string, Object>} model id → display metadata */
this._metadata = new Map();
}
/**
* Register models from a backend adapter.
* Called during startup for each enabled backend.
*
* @param {import("./backends/base.mjs").BackendAdapter} adapter
*/
registerBackend(adapter) {
for (const modelId of adapter.models) {
this._models.set(modelId, {
backendId: adapter.id,
canonical: modelId,
});
this._metadata.set(modelId, {
backendId: adapter.id,
backendName: adapter.displayName,
tier: adapter.tier,
costType: adapter.costType,
});
}
// Also register aliases if the adapter supports resolveModel
if (typeof adapter.resolveModel === "function") {
// We don't auto-register aliases here — they're handled by
// supportsModel() on the adapter. The registry only knows canonical IDs.
}
}
/**
* Look up which backend serves a given model.
*
* @param {string} model — model ID (could be canonical or alias)
* @returns {{backendId: string, canonical: string} | null}
*/
resolve(model) {
// Direct lookup first
const direct = this._models.get(model);
if (direct) return direct;
// No alias resolution here — that's the adapter's job.
// The router will ask each adapter if it supports the model.
return null;
}
/**
* Get all registered models with metadata.
* Used by GET /v1/models endpoint.
*/
listModels() {
const result = [];
for (const [id, meta] of this._metadata) {
result.push({ id, ...meta });
}
return result;
}
/**
* Check if a model is known to the registry.
*/
has(model) {
return this._models.has(model);
}
/**
* Get the backend ID for a model, or null.
*/
getBackendId(model) {
return this._models.get(model)?.backendId || null;
}
/**
* Get all models for a specific backend.
*/
getModelsForBackend(backendId) {
const models = [];
for (const [id, entry] of this._models) {
if (entry.backendId === backendId) models.push(id);
}
return models;
}
}