Files
TORCH/src/adapters/registry.mjs
T

95 lines
3.5 KiB
JavaScript

import { TorchError } from '../kernel/errors.mjs';
import { createClaudeAdapter } from './claude.mjs';
import { createCodexAdapter } from './codex.mjs';
import { createPiAdapter } from './pi.mjs';
import { validateRuntimeAdapter } from './runtime.mjs';
import { inspectRuntimePluginTrust, loadTrustedRuntimeAdapters } from './plugins.mjs';
import { managedProvider } from './updates.mjs';
export class RuntimeAdapterRegistry {
constructor({ env = process.env, adapters = [], loadPlugins = false, pluginNames } = {}) {
this.adapters = new Map([
['claude', createClaudeAdapter({ env, executable: managedProvider('claude', { env })?.executable ?? 'claude' })],
['codex', createCodexAdapter({ env, executable: managedProvider('codex', { env })?.executable ?? 'codex' })],
['pi', createPiAdapter({ env, executable: managedProvider('pi', { env })?.executable ?? 'pi' })],
]);
this.pluginErrors = [];
this.trustedPlugins = [];
try {
const inspected = inspectRuntimePluginTrust({ env });
this.trustedPlugins = inspected.plugins;
if (loadPlugins) {
const loaded = loadTrustedRuntimeAdapters({ env, names: pluginNames });
this.pluginErrors.push(...loaded.errors);
for (const adapter of loaded.adapters) {
try { this.register(adapter); } catch (error) {
this.pluginErrors.push({
name: adapter.name, code: error.code ?? 'RUNTIME_PLUGIN_LOAD_FAILED', message: error.message,
details: error.details,
});
}
}
} else {
for (const plugin of inspected.plugins.filter((entry) => entry.status !== 'trusted')) {
this.pluginErrors.push({
name: plugin.name,
code: plugin.status === 'hash-mismatch' ? 'RUNTIME_PLUGIN_HASH_MISMATCH' : 'RUNTIME_PLUGIN_UNAVAILABLE',
message: plugin.error?.message ?? `Runtime plugin is ${plugin.status}.`,
});
}
}
} catch (error) {
this.pluginErrors.push({
name: null, code: error.code ?? 'RUNTIME_PLUGIN_STORE_INVALID', message: error.message,
details: error.details,
});
}
for (const adapter of adapters) this.register(adapter);
}
register(adapter) {
validateRuntimeAdapter(adapter);
if (!/^[a-z][a-z0-9-]*$/.test(adapter.name)) {
throw new TorchError(`Invalid runtime adapter name: ${adapter.name}`, {
code: 'INVALID_RUNTIME_ADAPTER', details: { name: adapter.name },
});
}
if (this.adapters.has(adapter.name)) {
throw new TorchError(`Runtime adapter already registered: ${adapter.name}`, {
code: 'RUNTIME_ADAPTER_EXISTS', details: { name: adapter.name },
});
}
this.adapters.set(adapter.name, adapter);
return adapter;
}
replace(adapter) {
validateRuntimeAdapter(adapter);
if (!this.adapters.has(adapter.name)) {
throw new TorchError(`Cannot replace an unregistered runtime adapter: ${adapter.name}`, {
code: 'RUNTIME_ADAPTER_MISSING', details: { name: adapter.name },
});
}
this.adapters.set(adapter.name, adapter);
return adapter;
}
get(name) { return this.adapters.get(name); }
has(name) { return this.adapters.has(name); }
names() { return [...this.adapters.keys()]; }
toMap() { return new Map(this.adapters); }
configuration(name) {
const adapter = this.get(name);
if (!adapter) return undefined;
return structuredClone(adapter.configuration ?? {});
}
}
export function createRuntimeAdapterRegistry(options) {
return new RuntimeAdapterRegistry(options);
}