mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
* refactor(agents): prepare model runtime catalogs Build lifecycle-owned model and auth snapshots, carry prepared stores into hot agent paths, and serialize config/auth publication. Credits @zeroaltitude's #90741 investigation and benchmark approach. * refactor: finish lifecycle-owned model discovery * fix: align prepared model catalog contracts * test: align lifecycle catalog mocks * test: fix prepared catalog type fixtures * refactor: split prepared model runtime ownership * fix: import prepared runtime replacement gate type * test: split media runtime coverage * test: preserve image auth fixture key types * test: isolate lifecycle gate fixtures * chore: keep release changelog owned * refactor: finish prepared model catalog migration * test: keep catalog review fixtures scanner-safe * refactor: preserve lifecycle model runtime ownership * fix: close prepared runtime lifecycle races * fix: preserve compaction workspace fallback * chore: document btw generation rebinding * fix: preserve prepared generation boundaries * fix: keep model-list discovery flag explicit * fix: serialize standalone model runtime activation * refactor: migrate subagent model catalog lookup * refactor: clarify doctor catalog lookup seam * chore: refresh plugin sdk api baseline * test: migrate swarm catalog dependency * refactor(telegram): rename runtime catalog seam * refactor: extract model-aware tool context * test(models): isolate lifecycle catalog fixtures * refactor(agents): avoid btw parameter rebinding * fix(net-policy): align root ipaddr dependency * fix(build): keep net policy dependency bundled * fix(deadcode): document net policy compile dependency
166 lines
6.3 KiB
TypeScript
166 lines
6.3 KiB
TypeScript
/** Request-isolated registry views forked from lifecycle-owned model generations. */
|
|
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import type { Model } from "../llm/types.js";
|
|
import { normalizeDiscoveredAgentModel } from "./agent-model-discovery.js";
|
|
import {
|
|
resolveAgentDir,
|
|
resolveAgentWorkspaceDir,
|
|
resolveDefaultAgentDir,
|
|
resolveDefaultAgentId,
|
|
} from "./agent-scope.js";
|
|
import {
|
|
acquireReadOnlyPreparedModelRuntime,
|
|
prepareModelRuntimeSnapshot,
|
|
PreparedModelRuntimeOwnerNotPublishedError,
|
|
type PreparedModelRuntimeInput,
|
|
type PreparedModelRuntimeLease,
|
|
} from "./prepared-model-runtime.js";
|
|
import { AuthStorage, type ModelRegistry } from "./sessions/index.js";
|
|
|
|
export type LoadPreparedAgentModelRegistryOptions = {
|
|
agentId?: string;
|
|
agentDir?: string;
|
|
loadAvailability?: boolean;
|
|
providerFilter?: string;
|
|
normalizeModels?: boolean;
|
|
skipCredentials?: boolean;
|
|
workspaceDir?: string;
|
|
};
|
|
|
|
function usesCredentialFreeRegistry(options: LoadPreparedAgentModelRegistryOptions): boolean {
|
|
return options.skipCredentials === true || options.loadAvailability === false;
|
|
}
|
|
|
|
function createRegistryView(params: {
|
|
registry: ModelRegistry;
|
|
agentDir: string;
|
|
config: OpenClawConfig;
|
|
providerFilter?: string;
|
|
normalizeModels?: boolean;
|
|
workspaceDir?: string;
|
|
}): ModelRegistry {
|
|
const { registry } = params;
|
|
const getAll = registry.getAll.bind(registry);
|
|
const getAvailable = registry.getAvailable.bind(registry);
|
|
const find = registry.find.bind(registry);
|
|
const providerFilter = params.providerFilter ? normalizeProviderId(params.providerFilter) : "";
|
|
const matchesProviderFilter = (entry: Model) =>
|
|
!providerFilter || normalizeProviderId(entry.provider) === providerFilter;
|
|
const shouldNormalize = params.normalizeModels !== false;
|
|
const normalizeEntry = (entry: Model) =>
|
|
shouldNormalize
|
|
? normalizeDiscoveredAgentModel(entry, params.agentDir, {
|
|
config: params.config,
|
|
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
|
})
|
|
: entry;
|
|
let normalizedAll: Model[] | undefined;
|
|
let normalizedAvailable: Model[] | undefined;
|
|
const loadNormalizedAll = () => (normalizedAll ??= getAll().map(normalizeEntry));
|
|
const loadNormalizedAvailable = () =>
|
|
(normalizedAvailable ??= getAvailable().map(normalizeEntry));
|
|
const findCache = new Map<string, Model | undefined>();
|
|
|
|
registry.getAll = () => loadNormalizedAll().filter(matchesProviderFilter);
|
|
registry.getAvailable = () => loadNormalizedAvailable().filter(matchesProviderFilter);
|
|
// Provider filters constrain list enumeration only. Direct configured-row lookups historically
|
|
// remain available so model-list fallback construction can resolve an explicit entry.
|
|
registry.find = (provider: string, modelId: string) => {
|
|
const key = `${normalizeProviderId(provider)}\0${modelId}`;
|
|
if (findCache.has(key)) {
|
|
return findCache.get(key);
|
|
}
|
|
const entry = find(provider, modelId);
|
|
const resolved = entry
|
|
? normalizeEntry(entry)
|
|
: loadNormalizedAll().find(
|
|
(candidate) =>
|
|
normalizeProviderId(candidate.provider) === normalizeProviderId(provider) &&
|
|
candidate.id === modelId,
|
|
);
|
|
findCache.set(key, resolved);
|
|
return resolved;
|
|
};
|
|
return registry;
|
|
}
|
|
|
|
function registryOwnerCandidates(
|
|
input: PreparedModelRuntimeInput,
|
|
allowConfiguredWorkspaceFallback: boolean,
|
|
): PreparedModelRuntimeInput[] {
|
|
if (!allowConfiguredWorkspaceFallback || !input.workspaceDir) {
|
|
return [input];
|
|
}
|
|
const { workspaceDir: _workspaceDir, ...workspaceFree } = input;
|
|
return [workspaceFree, input];
|
|
}
|
|
|
|
async function loadReadSnapshot(
|
|
input: PreparedModelRuntimeInput,
|
|
allowConfiguredWorkspaceFallback: boolean,
|
|
): Promise<PreparedModelRuntimeLease> {
|
|
for (const candidate of registryOwnerCandidates(input, allowConfiguredWorkspaceFallback)) {
|
|
try {
|
|
const prepared = await prepareModelRuntimeSnapshot(candidate);
|
|
// The lifecycle owner is authoritative when this read overlaps a config replacement.
|
|
return { snapshot: prepared, release: () => {} };
|
|
} catch (error) {
|
|
if (!(error instanceof PreparedModelRuntimeOwnerNotPublishedError)) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
return await acquireReadOnlyPreparedModelRuntime(input);
|
|
}
|
|
|
|
function resolveInput(
|
|
config: OpenClawConfig,
|
|
options: LoadPreparedAgentModelRegistryOptions = {},
|
|
): PreparedModelRuntimeInput {
|
|
const agentId = options.agentId ?? resolveDefaultAgentId(config);
|
|
const agentDir = options.agentDir ?? resolveAgentDir(config, agentId);
|
|
const workspaceDir = options.workspaceDir ?? resolveAgentWorkspaceDir(config, agentId);
|
|
return {
|
|
agentId,
|
|
agentDir,
|
|
config,
|
|
inheritedAuthDir: resolveDefaultAgentDir(config),
|
|
...(usesCredentialFreeRegistry(options) ? { skipCredentials: true } : {}),
|
|
...(workspaceDir ? { workspaceDir } : {}),
|
|
};
|
|
}
|
|
|
|
/** Loads and forks one registry from the owning command lifecycle generation. */
|
|
export async function loadPreparedAgentModelRegistry(
|
|
config: OpenClawConfig,
|
|
options: LoadPreparedAgentModelRegistryOptions = {},
|
|
): Promise<{ agentDir: string; config: OpenClawConfig; registry: ModelRegistry }> {
|
|
const input = resolveInput(config, options);
|
|
const lease = await loadReadSnapshot(input, options.workspaceDir === undefined);
|
|
try {
|
|
const snapshot = lease.snapshot;
|
|
const stores = snapshot.createStores();
|
|
// ModelRegistry.fork() restores the lifecycle template's raw base catalog before this view
|
|
// applies normalization. Credential-free owners therefore preserve configured IDs as well as
|
|
// preventing credential-dependent discovery during generation construction.
|
|
const modelRegistry = usesCredentialFreeRegistry(options)
|
|
? stores.modelRegistry.fork(AuthStorage.inMemory({}))
|
|
: stores.modelRegistry;
|
|
return {
|
|
agentDir: snapshot.agentDir,
|
|
config: snapshot.config,
|
|
registry: createRegistryView({
|
|
registry: modelRegistry,
|
|
agentDir: snapshot.agentDir,
|
|
config: snapshot.config,
|
|
providerFilter: options.providerFilter,
|
|
normalizeModels: options.normalizeModels,
|
|
workspaceDir: snapshot.workspaceDir ?? input.workspaceDir,
|
|
}),
|
|
};
|
|
} finally {
|
|
lease.release();
|
|
}
|
|
}
|