diff options
| author | Adam Malczewski <[email protected]> | 2026-05-20 15:04:26 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-20 15:04:26 +0900 |
| commit | adc8bd185b54935e7a31aae04da3175b7989927a (patch) | |
| tree | 031fa53009a4708ce0cc15c0e3dcb2f2a73cf2d9 /packages/core/src/models/registry.ts | |
| parent | 52af4ca33b1f83b8f863a6267d447944f55e729d (diff) | |
| download | dispatch-adc8bd185b54935e7a31aae04da3175b7989927a.tar.gz dispatch-adc8bd185b54935e7a31aae04da3175b7989927a.zip | |
feat: phase 3 — config, skills, model groups, task list, and sidebar UI
- Config system: TOML-based dispatch.toml with hot-reload via chokidar
- Model/key resolution: tag-based model selection, key fallback chains
- Skills system: directory loader with TOML frontmatter, agent mappings
- Task list tool: add/update/list/get operations with WebSocket events
- API routes: GET /config, /skills, /skills/:name, /models, /models/resolve
- Frontend: sidebar with model status, task list, config viewer, skills browser, permission log
- Sliding sidebar animation using CSS transitions (not Svelte transitions)
Diffstat (limited to 'packages/core/src/models/registry.ts')
| -rw-r--r-- | packages/core/src/models/registry.ts | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/packages/core/src/models/registry.ts b/packages/core/src/models/registry.ts new file mode 100644 index 0000000..f78fe0e --- /dev/null +++ b/packages/core/src/models/registry.ts @@ -0,0 +1,134 @@ +import type { KeyDefinition, KeyState, ModelDefinition } from "../types/index.js"; + +export class ModelRegistry { + private models: ModelDefinition[]; + private keyStates: Map<string, KeyState>; + private fallbackOrder: string[]; + + constructor(models: ModelDefinition[], keys: KeyDefinition[], fallbackOrder: string[]) { + this.models = []; + this.keyStates = new Map(); + this.fallbackOrder = []; + this._initConfig(models, keys, fallbackOrder, new Map()); + } + + private _initConfig( + models: ModelDefinition[], + keys: KeyDefinition[], + fallbackOrder: string[], + existingStates: Map<string, KeyState>, + ): void { + this.models = [...models]; + this.fallbackOrder = this._buildFallbackOrder(keys, fallbackOrder); + + const newStates = new Map<string, KeyState>(); + for (const key of keys) { + const existing = existingStates.get(key.id); + if (existing) { + // Preserve existing state but update definition + newStates.set(key.id, { ...existing, definition: key }); + } else { + newStates.set(key.id, { definition: key, status: "active" }); + } + } + this.keyStates = newStates; + } + + private _buildFallbackOrder(keys: KeyDefinition[], fallbackOrder: string[]): string[] { + const ordered: string[] = []; + const keyIds = new Set(keys.map((k) => k.id)); + + // Add keys from fallbackOrder first (if they exist) + for (const id of fallbackOrder) { + if (keyIds.has(id) && !ordered.includes(id)) { + ordered.push(id); + } + } + + // Append remaining keys not in fallbackOrder + for (const key of keys) { + if (!ordered.includes(key.id)) { + ordered.push(key.id); + } + } + + return ordered; + } + + getModels(): ModelDefinition[] { + return [...this.models]; + } + + getKeys(): KeyState[] { + return this.fallbackOrder + .map((id) => this.keyStates.get(id)) + .filter((state): state is KeyState => state !== undefined); + } + + getModelsByTag(tag: string): ModelDefinition[] { + return this.models.filter((m) => m.tags.includes(tag)); + } + + getAllTags(): string[] { + const tags = new Set<string>(); + for (const model of this.models) { + for (const tag of model.tags) { + tags.add(tag); + } + } + return [...tags]; + } + + markKeyExhausted(keyId: string, error?: string): void { + const state = this.keyStates.get(keyId); + if (!state) return; + this.keyStates.set(keyId, { + ...state, + status: "exhausted", + lastError: error, + exhaustedAt: Date.now(), + }); + } + + markKeyActive(keyId: string): void { + const state = this.keyStates.get(keyId); + if (!state) return; + const updated: KeyState = { + definition: state.definition, + status: "active", + }; + this.keyStates.set(keyId, updated); + } + + hasAvailableKey(provider: string): boolean { + for (const state of this.keyStates.values()) { + if (state.definition.provider === provider && state.status === "active") { + return true; + } + } + return false; + } + + allKeysExhausted(): boolean { + for (const state of this.keyStates.values()) { + if (state.status === "active") { + return false; + } + } + return true; + } + + updateConfig(models: ModelDefinition[], keys: KeyDefinition[], fallbackOrder: string[]): void { + this._initConfig(models, keys, fallbackOrder, this.keyStates); + } + + // Internal: get ordered key states for a specific provider + getOrderedKeysForProvider(provider: string): KeyState[] { + return this.fallbackOrder + .map((id) => this.keyStates.get(id)) + .filter( + (state): state is KeyState => + state !== undefined && state.definition.provider === provider, + ); + } +} |
