diff --git a/src/agents/auth-profiles.runtime.ts b/src/agents/auth-profiles.runtime.ts index ea41ce4fa148..5a2126231cdc 100644 --- a/src/agents/auth-profiles.runtime.ts +++ b/src/agents/auth-profiles.runtime.ts @@ -1,15 +1,9 @@ -/** - * Runtime seam for auth-profile store loading. - * Tests can stub this facade without importing the full auth profile store - * implementation. - */ -import { ensureAuthProfileStore as ensureAuthProfileStoreImpl } from "./auth-profiles/store.js"; - -type EnsureAuthProfileStore = typeof import("./auth-profiles/store.js").ensureAuthProfileStore; - -/** Ensure an auth-profile store using the production store implementation. */ -export function ensureAuthProfileStore( - ...args: Parameters -): ReturnType { - return ensureAuthProfileStoreImpl(...args); -} +/** Runtime auth-profile facade for lazy model selection and fallback paths. */ +export { resolveAuthProfileOrder } from "./auth-profiles/order.js"; +export { ensureAuthProfileStore, loadAuthProfileStoreForRuntime } from "./auth-profiles/store.js"; +export { + getSoonestCooldownExpiry, + isProfileInCooldown, + maybeReprobeWhamBlockedProfiles, + resolveProfilesUnavailableReason, +} from "./auth-profiles/usage.js"; diff --git a/src/agents/embedded-agent-runner/extra-params.test-support.ts b/src/agents/embedded-agent-runner/extra-params.test-support.ts index 245b7b6b6636..1f4ecfff224c 100644 --- a/src/agents/embedded-agent-runner/extra-params.test-support.ts +++ b/src/agents/embedded-agent-runner/extra-params.test-support.ts @@ -11,6 +11,7 @@ import { applyExtraParamsToAgent } from "./extra-params.js"; import type { ProviderThinkLevel } from "./utils.js"; type ExtraParamsTestApi = { + supportsGptParallelToolCallsPayload(api: unknown): boolean; setProviderRuntimeDepsForTest( deps: | Partial<{ diff --git a/src/agents/embedded-agent-runner/extra-params.ts b/src/agents/embedded-agent-runner/extra-params.ts index df9e8fd3846e..54befb9808ca 100644 --- a/src/agents/embedded-agent-runner/extra-params.ts +++ b/src/agents/embedded-agent-runner/extra-params.ts @@ -35,7 +35,6 @@ import { } from "../../plugins/provider-hook-runtime.js"; import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js"; import { resolveModelExtraParamSources } from "../model-extra-params.js"; -import { supportsGptParallelToolCallsPayload } from "../provider-api-families.js"; import { resolveProviderRequestPolicyConfig } from "../provider-request-config.js"; import type { AgentRuntimeTransport } from "../runtime-plan/types.js"; import type { StreamFn } from "../runtime/index.js"; @@ -63,8 +62,20 @@ const providerRuntimeDeps = { let preparedExtraParamsCache = new WeakMap>>(); const REQUEST_SCOPED_EXTRA_PARAM_KEYS = new Set(["response_format", "responseFormat", "stop"]); +const GPT_PARALLEL_TOOL_CALLS_APIS = new Set([ + "openai-completions", + "openai-responses", + "openai-chatgpt-responses", + "azure-openai-responses", +]); + +/** True when a provider API accepts GPT parallel-tool-call payload settings. */ +function supportsGptParallelToolCallsPayload(api: unknown): boolean { + return typeof api === "string" && GPT_PARALLEL_TOOL_CALLS_APIS.has(api); +} const testing = { + supportsGptParallelToolCallsPayload, setProviderRuntimeDepsForTest( deps: Partial | undefined, ): void { diff --git a/src/agents/model-alias-lines.ts b/src/agents/model-alias-lines.ts deleted file mode 100644 index d6e121a57df6..000000000000 --- a/src/agents/model-alias-lines.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Formats configured model aliases for prompt-visible model guidance. - */ -import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; - -/** Builds deterministic prompt lines for configured model aliases. */ -export function buildModelAliasLines(cfg?: OpenClawConfig) { - const models = cfg?.agents?.defaults?.models ?? {}; - const entries: Array<{ alias: string; model: string }> = []; - for (const [keyRaw, entryRaw] of Object.entries(models)) { - const model = normalizeOptionalString(keyRaw) ?? ""; - if (!model) { - continue; - } - const alias = - normalizeOptionalString((entryRaw as { alias?: string } | undefined)?.alias) ?? ""; - if (!alias) { - continue; - } - entries.push({ alias, model }); - } - return entries - .toSorted((a, b) => a.alias.localeCompare(b.alias)) - .map((entry) => `- ${entry.alias}: ${entry.model}`); -} diff --git a/src/agents/model-catalog-scope.test.ts b/src/agents/model-catalog-scope.test.ts index faac6479c9e7..a4c27b86e28f 100644 --- a/src/agents/model-catalog-scope.test.ts +++ b/src/agents/model-catalog-scope.test.ts @@ -1,7 +1,7 @@ // Verifies model catalog lookup scope for custom and manifest-owned models. import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { resolveModelCatalogScope } from "./model-catalog-scope.js"; +import { resolveModelCatalogScope } from "./model-discovery-context.js"; describe("resolveModelCatalogScope", () => { it("keeps explicit custom provider models scoped to that provider", () => { diff --git a/src/agents/model-catalog-scope.ts b/src/agents/model-catalog-scope.ts deleted file mode 100644 index 0c5057630e2d..000000000000 --- a/src/agents/model-catalog-scope.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Resolves model catalog scope from config and discovery options. - */ -import { findNormalizedProviderValue } from "@openclaw/model-catalog-core/provider-id"; -import { normalizeUniqueSingleOrTrimmedStringList } from "@openclaw/normalization-core/string-normalization"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; - -function providerConfigDeclaresModel( - providerConfig: { models?: readonly { id?: string }[] } | undefined, - model: string, -): boolean { - const trimmedModel = model.trim(); - return Boolean( - trimmedModel && - providerConfig?.models?.some((candidate) => candidate.id?.trim() === trimmedModel), - ); -} - -/** Resolves provider/model refs used to scope model catalog discovery. */ -export function resolveModelCatalogScope(params: { - cfg?: OpenClawConfig; - provider: string; - model: string; -}): { providerRefs: string[]; modelRefs: string[] } { - const provider = params.provider.trim(); - const model = params.model.trim(); - const providerConfig = findNormalizedProviderValue(params.cfg?.models?.providers, provider); - const modelRefs = providerConfigDeclaresModel(providerConfig, model) - ? [provider && model ? `${provider}/${model}` : model] - : [provider && model ? `${provider}/${model}` : model, model]; - // Scope ordering feeds deterministic discovery and prompt/cache inputs. - return { - providerRefs: normalizeUniqueSingleOrTrimmedStringList([provider, providerConfig?.api]), - modelRefs: normalizeUniqueSingleOrTrimmedStringList(modelRefs), - }; -} diff --git a/src/agents/model-discovery-context.ts b/src/agents/model-discovery-context.ts index efc479d72687..c6f9775e9e78 100644 --- a/src/agents/model-discovery-context.ts +++ b/src/agents/model-discovery-context.ts @@ -3,6 +3,8 @@ * Keeps callers from reaching into runtime config or plugin metadata snapshot * plumbing directly. */ +import { findNormalizedProviderValue } from "@openclaw/model-catalog-core/provider-id"; +import { normalizeUniqueSingleOrTrimmedStringList } from "@openclaw/normalization-core/string-normalization"; import { getRuntimeConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js"; @@ -10,6 +12,36 @@ import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapsh import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js"; import type { PluginModelCatalogMetadataSnapshot } from "./plugin-model-catalog.js"; +function providerConfigDeclaresModel( + providerConfig: { models?: readonly { id?: string }[] } | undefined, + model: string, +): boolean { + const trimmedModel = model.trim(); + return Boolean( + trimmedModel && + providerConfig?.models?.some((candidate) => candidate.id?.trim() === trimmedModel), + ); +} + +/** Resolves provider/model refs used to scope model catalog discovery. */ +export function resolveModelCatalogScope(params: { + cfg?: OpenClawConfig; + provider: string; + model: string; +}): { providerRefs: string[]; modelRefs: string[] } { + const provider = params.provider.trim(); + const model = params.model.trim(); + const providerConfig = findNormalizedProviderValue(params.cfg?.models?.providers, provider); + const modelRefs = providerConfigDeclaresModel(providerConfig, model) + ? [provider && model ? `${provider}/${model}` : model] + : [provider && model ? `${provider}/${model}` : model, model]; + // Scope ordering feeds deterministic discovery and prompt/cache inputs. + return { + providerRefs: normalizeUniqueSingleOrTrimmedStringList([provider, providerConfig?.api]), + modelRefs: normalizeUniqueSingleOrTrimmedStringList(modelRefs), + }; +} + /** Resolve the workspace directory model discovery should use for agent scope. */ export function resolveModelWorkspaceDir( cfg: OpenClawConfig | undefined, diff --git a/src/agents/model-fallback-auth.runtime.ts b/src/agents/model-fallback-auth.runtime.ts deleted file mode 100644 index c6a0be553f70..000000000000 --- a/src/agents/model-fallback-auth.runtime.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Runtime auth profile barrel for fallback/provider selection code. - * - * These exports keep the hot runtime path on the auth-profile submodules without - * pulling the broader model config surface into provider fallback logic. - */ -export { resolveAuthProfileOrder } from "./auth-profiles/order.js"; -export { ensureAuthProfileStore, loadAuthProfileStoreForRuntime } from "./auth-profiles/store.js"; -export { - getSoonestCooldownExpiry, - isProfileInCooldown, - maybeReprobeWhamBlockedProfiles, - resolveProfilesUnavailableReason, -} from "./auth-profiles/usage.js"; diff --git a/src/agents/model-fallback.test-support.ts b/src/agents/model-fallback.test-support.ts index 2d4cc4554848..cccf33f59fef 100644 --- a/src/agents/model-fallback.test-support.ts +++ b/src/agents/model-fallback.test-support.ts @@ -16,7 +16,7 @@ type ModelFallbackTestApi = { hasFallbackCandidates: boolean; now: number; probeThrottleKey: string; - authRuntime: typeof import("./model-fallback-auth.runtime.js"); + authRuntime: typeof import("./auth-profiles.runtime.js"); authStore: AuthProfileStore; profileIds: string[]; }): CooldownDecision; diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index e64ea089c098..b1b7580e2e62 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -200,7 +200,7 @@ const authRuntimeMock = vi.hoisted(() => { }; }); -vi.mock("./model-fallback-auth.runtime.js", () => authRuntimeMock.runtime); +vi.mock("./auth-profiles.runtime.js", () => authRuntimeMock.runtime); const makeCfg = makeModelFallbackCfg; let authTempRoot = ""; diff --git a/src/agents/model-fallback.ts b/src/agents/model-fallback.ts index 18fe304ed3c5..b43fc140946f 100644 --- a/src/agents/model-fallback.ts +++ b/src/agents/model-fallback.ts @@ -361,10 +361,10 @@ type ModelFallbackClassifiedResult = Pick< "result" | "provider" | "model" >; -type ModelFallbackAuthRuntime = typeof import("./model-fallback-auth.runtime.js"); +type ModelFallbackAuthRuntime = typeof import("./auth-profiles.runtime.js"); const modelFallbackAuthRuntimeLoader = createLazyImportLoader( - () => import("./model-fallback-auth.runtime.js"), + () => import("./auth-profiles.runtime.js"), ); const MAX_FALLBACK_CANDIDATE_CACHE_ENTRIES = 256; const fallbackCandidateCache = new Map(); diff --git a/src/agents/model-picker-visibility.ts b/src/agents/model-picker-visibility.ts deleted file mode 100644 index 909dc43cb689..000000000000 --- a/src/agents/model-picker-visibility.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Filters provider/model refs for model picker visibility. - */ -import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { listCliRuntimeProviderIds } from "./cli-backends.js"; - -// Retired provider ids and CLI runtime aliases are implementation surfaces, not -// model picker choices. Hide them while keeping real provider/model refs visible. -const RETIRED_MODEL_PICKER_PROVIDERS = new Set(["codex", "codex-cli"]); - -/** True for retired provider ids that should stay out of model selection surfaces. */ -export function isRetiredModelPickerProvider(provider: string): boolean { - return RETIRED_MODEL_PICKER_PROVIDERS.has(normalizeProviderId(provider)); -} - -/** Creates a provider visibility predicate for model picker rendering. */ -export function createModelPickerVisibleProviderPredicate( - params: { config?: OpenClawConfig; env?: NodeJS.ProcessEnv; includeSetupRegistry?: boolean } = {}, -): (provider: string) => boolean { - const cliRuntimeProviders = new Set( - listCliRuntimeProviderIds({ - config: params.config, - env: params.env, - includeSetupRegistry: params.includeSetupRegistry ?? false, - }), - ); - return (provider: string): boolean => { - const normalized = normalizeProviderId(provider); - return !isRetiredModelPickerProvider(normalized) && !cliRuntimeProviders.has(normalized); - }; -} diff --git a/src/agents/model-registry-loader.ts b/src/agents/model-registry-loader.ts deleted file mode 100644 index 71e5fdbc1fcd..000000000000 --- a/src/agents/model-registry-loader.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** Lifecycle-backed model-registry view for command paths. */ -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { - loadPreparedAgentModelRegistry, - type LoadPreparedAgentModelRegistryOptions, -} from "./prepared-model-registry.js"; -import type { ModelRegistry } from "./sessions/index.js"; - -/** Options controlling the prepared registry view. */ -type LoadAgentModelRegistryOptions = LoadPreparedAgentModelRegistryOptions & { - readOnly?: boolean; -}; - -/** Forks a registry from the generation prepared by the owning command lifecycle. */ -export async function loadAgentModelRegistry( - config: OpenClawConfig, - options: LoadAgentModelRegistryOptions = {}, -): Promise<{ agentDir: string; config: OpenClawConfig; registry: ModelRegistry }> { - return await loadPreparedAgentModelRegistry(config, options); -} diff --git a/src/agents/model-runtime-aliases.test.ts b/src/agents/model-runtime-aliases.test.ts index 7a08481d445b..07b5da232760 100644 --- a/src/agents/model-runtime-aliases.test.ts +++ b/src/agents/model-runtime-aliases.test.ts @@ -5,7 +5,7 @@ import { testing as cliBackendsTesting } from "./cli-backends.test-support.js"; import { createModelPickerVisibleProviderPredicate, isRetiredModelPickerProvider, -} from "./model-picker-visibility.js"; +} from "./model-runtime-aliases.js"; import { areRuntimeModelRefsEquivalent, isCliRuntimeProvider, diff --git a/src/agents/model-runtime-aliases.ts b/src/agents/model-runtime-aliases.ts index ba879dd7ce97..6cb6697dcf68 100644 --- a/src/agents/model-runtime-aliases.ts +++ b/src/agents/model-runtime-aliases.ts @@ -15,6 +15,30 @@ import { import { resolveModelRuntimePolicy } from "./model-runtime-policy.js"; import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; +const RETIRED_MODEL_PICKER_PROVIDERS = new Set(["codex", "codex-cli"]); + +/** True for retired provider ids that should stay out of model selection surfaces. */ +export function isRetiredModelPickerProvider(provider: string): boolean { + return RETIRED_MODEL_PICKER_PROVIDERS.has(normalizeProviderId(provider)); +} + +/** Creates a provider visibility predicate for model picker rendering. */ +export function createModelPickerVisibleProviderPredicate( + params: { config?: OpenClawConfig; env?: NodeJS.ProcessEnv; includeSetupRegistry?: boolean } = {}, +): (provider: string) => boolean { + const cliRuntimeProviders = new Set( + listCliRuntimeProviderIds({ + config: params.config, + env: params.env, + includeSetupRegistry: params.includeSetupRegistry ?? false, + }), + ); + return (provider: string): boolean => { + const normalized = normalizeProviderId(provider); + return !isRetiredModelPickerProvider(normalized) && !cliRuntimeProviders.has(normalized); + }; +} + /** True for CLI runtime provider ids such as `claude-cli` and `google-gemini-cli`. */ export function isCliRuntimeProvider( provider: string, diff --git a/src/agents/models-config.merge.ts b/src/agents/models-config.merge.ts index f5ecf11a772e..a938d3d530a2 100644 --- a/src/agents/models-config.merge.ts +++ b/src/agents/models-config.merge.ts @@ -3,12 +3,39 @@ * preserved secret fields. Setup and doctor flows use this boundary to update * model catalogs without discarding existing credentials. */ +import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { isNonSecretApiKeyMarker } from "./model-auth-markers.js"; import { resolveCatalogOwnedModelCompat } from "./model-compat-catalog.js"; -import { normalizeProviderMapKeys } from "./models-config.providers.keys.js"; import type { ProviderConfig } from "./models-config.providers.secrets.js"; +export function normalizeProviderMapKeys( + providers: Record | null | undefined, +): Record { + const normalized: Record = {}; + const canonicalKeys = new Set(); + for (const [key, value] of Object.entries(providers ?? {})) { + const providerKey = normalizeProviderId(key); + if (!providerKey) { + continue; + } + if (key === providerKey) { + canonicalKeys.add(providerKey); + // A prior alias inserted this key at the alias's position. Reinsert it so + // canonical spelling also controls deterministic provider order. + delete normalized[providerKey]; + normalized[providerKey] = value; + continue; + } + // Exact canonical spelling wins over aliases regardless of object order. + // Without one, the later variant wins, matching existing trim-collision behavior. + if (!canonicalKeys.has(providerKey)) { + normalized[providerKey] = value; + } + } + return normalized; +} + /** Existing provider config shape that may carry persisted secret/base URL fields. */ export type ExistingProviderConfig = ProviderConfig & { apiKey?: string; diff --git a/src/agents/models-config.providers.keys.ts b/src/agents/models-config.providers.keys.ts deleted file mode 100644 index d345224fbdf1..000000000000 --- a/src/agents/models-config.providers.keys.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** Canonical provider-key handling shared by models.json merge boundaries. */ -import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; - -export function normalizeProviderMapKeys( - providers: Record | null | undefined, -): Record { - const normalized: Record = {}; - const canonicalKeys = new Set(); - for (const [key, value] of Object.entries(providers ?? {})) { - const providerKey = normalizeProviderId(key); - if (!providerKey) { - continue; - } - if (key === providerKey) { - canonicalKeys.add(providerKey); - // A prior alias inserted this key at the alias's position. Reinsert it so - // canonical spelling also controls deterministic provider order. - delete normalized[providerKey]; - normalized[providerKey] = value; - continue; - } - // Exact canonical spelling wins over aliases regardless of object order. - // Without one, the later variant wins, matching existing trim-collision behavior. - if (!canonicalKeys.has(providerKey)) { - normalized[providerKey] = value; - } - } - return normalized; -} diff --git a/src/agents/models-config.providers.source-managed.ts b/src/agents/models-config.providers.source-managed.ts index 129020c82dd1..e699f73339b6 100644 --- a/src/agents/models-config.providers.source-managed.ts +++ b/src/agents/models-config.providers.source-managed.ts @@ -10,7 +10,7 @@ import { resolveNonEnvSecretRefHeaderValueMarker, resolveEnvSecretRefHeaderValueMarker, } from "./model-auth-markers.js"; -import { normalizeProviderMapKeys } from "./models-config.providers.keys.js"; +import { normalizeProviderMapKeys } from "./models-config.merge.js"; import type { ProviderConfig, SecretDefaults } from "./models-config.providers.secrets.js"; /** diff --git a/src/agents/prepared-model-registry.ts b/src/agents/prepared-model-registry.ts index 9878687d6f49..13eb441e25d4 100644 --- a/src/agents/prepared-model-registry.ts +++ b/src/agents/prepared-model-registry.ts @@ -18,7 +18,7 @@ import { } from "./prepared-model-runtime.js"; import { AuthStorage, type ModelRegistry } from "./sessions/index.js"; -export type LoadPreparedAgentModelRegistryOptions = { +type LoadPreparedAgentModelRegistryOptions = { agentId?: string; agentDir?: string; loadAvailability?: boolean; diff --git a/src/agents/provider-api-families.test.ts b/src/agents/provider-api-families.test.ts index 78598c8d5b08..b1d78142e072 100644 --- a/src/agents/provider-api-families.test.ts +++ b/src/agents/provider-api-families.test.ts @@ -1,6 +1,6 @@ // Verifies provider API family helpers gate GPT parallel tool-call payloads. import { describe, expect, it } from "vitest"; -import { supportsGptParallelToolCallsPayload } from "./provider-api-families.js"; +import { testing as extraParamsTesting } from "./embedded-agent-runner/extra-params.test-support.js"; describe("provider api families", () => { it.each([ @@ -9,11 +9,13 @@ describe("provider api families", () => { "openai-chatgpt-responses", "azure-openai-responses", ])("classifies %s as supporting the GPT parallel_tool_calls payload patch", (api) => { - expect(supportsGptParallelToolCallsPayload(api)).toBe(true); + expect(extraParamsTesting.supportsGptParallelToolCallsPayload(api)).toBe(true); }); it("rejects unrelated APIs", () => { - expect(supportsGptParallelToolCallsPayload("anthropic-messages")).toBe(false); - expect(supportsGptParallelToolCallsPayload(undefined)).toBe(false); + expect(extraParamsTesting.supportsGptParallelToolCallsPayload("anthropic-messages")).toBe( + false, + ); + expect(extraParamsTesting.supportsGptParallelToolCallsPayload(undefined)).toBe(false); }); }); diff --git a/src/agents/provider-api-families.ts b/src/agents/provider-api-families.ts deleted file mode 100644 index 678f429fdc53..000000000000 --- a/src/agents/provider-api-families.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Small API-family predicates used when constructing provider payloads. The - * sets here encode transport-level compatibility, not provider identity. - */ -const GPT_PARALLEL_TOOL_CALLS_APIS = new Set([ - "openai-completions", - "openai-responses", - "openai-chatgpt-responses", - "azure-openai-responses", -]); - -/** True when a provider API accepts GPT parallel-tool-call payload settings. */ -export function supportsGptParallelToolCallsPayload(api: unknown): boolean { - return typeof api === "string" && GPT_PARALLEL_TOOL_CALLS_APIS.has(api); -} diff --git a/src/agents/system-prompt-config.ts b/src/agents/system-prompt-config.ts index c95e2829bdc4..b8b19c94ae8c 100644 --- a/src/agents/system-prompt-config.ts +++ b/src/agents/system-prompt-config.ts @@ -4,10 +4,10 @@ * This module gathers agent/config knobs before rendering the canonical system * prompt so callers do not duplicate owner, TTS, alias, memory, or FS policy. */ +import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { buildTtsSystemPromptHint } from "../tts/tts-settings.js"; import { resolveAgentConfig } from "./agent-scope.js"; -import { buildModelAliasLines } from "./model-alias-lines.js"; import { resolveOwnerDisplaySetting } from "./owner-display.js"; import { buildAgentSystemPrompt } from "./system-prompt.js"; import { resolveEffectiveToolFsWorkspaceOnly } from "./tool-fs-policy.js"; @@ -31,6 +31,20 @@ type ConfiguredAgentSystemPromptParams = AgentSystemPromptRenderParams & { agentId?: string; }; +function buildModelAliasLines(cfg?: OpenClawConfig) { + const entries: Array<{ alias: string; model: string }> = []; + for (const [keyRaw, entryRaw] of Object.entries(cfg?.agents?.defaults?.models ?? {})) { + const model = normalizeOptionalString(keyRaw) ?? ""; + const alias = normalizeOptionalString(entryRaw?.alias) ?? ""; + if (model && alias) { + entries.push({ alias, model }); + } + } + return entries + .toSorted((a, b) => a.alias.localeCompare(b.alias)) + .map((entry) => `- ${entry.alias}: ${entry.model}`); +} + /** Resolves all config-derived system prompt fields for an agent. */ function resolveAgentSystemPromptConfig(params: { config?: OpenClawConfig; diff --git a/src/agents/transport-params-runtime-contract.test.ts b/src/agents/transport-params-runtime-contract.test.ts index 58b0e19dece1..75c64d13b457 100644 --- a/src/agents/transport-params-runtime-contract.test.ts +++ b/src/agents/transport-params-runtime-contract.test.ts @@ -17,7 +17,6 @@ import { resolvePreparedExtraParams, } from "./embedded-agent-runner/extra-params.js"; import { testing as extraParamsTesting } from "./embedded-agent-runner/extra-params.test-support.js"; -import { supportsGptParallelToolCallsPayload } from "./provider-api-families.js"; beforeEach(() => { installNoopProviderRuntimeDeps(); @@ -74,14 +73,14 @@ describe("transport params runtime contract (embedded OpenClaw/OpenAI path)", () it.each(GPT_PARALLEL_TOOL_CALLS_PAYLOAD_APIS)( "advertises %s as accepting the GPT parallel_tool_calls payload patch", (api) => { - expect(supportsGptParallelToolCallsPayload(api)).toBe(true); + expect(extraParamsTesting.supportsGptParallelToolCallsPayload(api)).toBe(true); }, ); it.each(UNRELATED_TOOL_CALLS_PAYLOAD_APIS)( "does not advertise %s as accepting the GPT parallel_tool_calls payload patch", (api) => { - expect(supportsGptParallelToolCallsPayload(api)).toBe(false); + expect(extraParamsTesting.supportsGptParallelToolCallsPayload(api)).toBe(false); }, ); diff --git a/src/auto-reply/reply/commands-models.ts b/src/auto-reply/reply/commands-models.ts index e50a8c279532..cf6edb074c99 100644 --- a/src/auto-reply/reply/commands-models.ts +++ b/src/auto-reply/reply/commands-models.ts @@ -18,8 +18,8 @@ import { resolveLogicalVisibleModelCatalog, type ModelCatalogAuthChecker, } from "../../agents/model-catalog-visibility.js"; -import { isRetiredModelPickerProvider } from "../../agents/model-picker-visibility.js"; import { createProviderAuthChecker } from "../../agents/model-provider-auth.js"; +import { isRetiredModelPickerProvider } from "../../agents/model-runtime-aliases.js"; import { modelCatalogLogicalKey } from "../../agents/model-selection-shared.js"; import { buildModelAliasIndex, diff --git a/src/commands/models/list.registry-load.ts b/src/commands/models/list.registry-load.ts index 8c6f7e841707..30fa83f1f18d 100644 --- a/src/commands/models/list.registry-load.ts +++ b/src/commands/models/list.registry-load.ts @@ -1,6 +1,6 @@ -/** Registry-loading adapters for model-list row construction. */ -import { loadAgentModelRegistry } from "../../agents/model-registry-loader.js"; import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js"; +/** Registry-loading adapters for model-list row construction. */ +import { loadPreparedAgentModelRegistry as loadAgentModelRegistry } from "../../agents/prepared-model-registry.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { ModelRegistry } from "../../llm/model-registry.js"; import type { Model } from "../../llm/types.js"; diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 837e5cf7f80c..4d3780760824 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -1,9 +1,9 @@ -/** Model registry access helpers for `openclaw models list`. */ -import { loadAgentModelRegistry } from "../../agents/model-registry-loader.js"; import { shouldSuppressBuiltInModel, shouldSuppressBuiltInModelFromManifest, } from "../../agents/model-suppression.js"; +/** Model registry access helpers for `openclaw models list`. */ +import { loadPreparedAgentModelRegistry as loadAgentModelRegistry } from "../../agents/prepared-model-registry.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { ModelRegistry } from "../../llm/model-registry.js"; import type { Model } from "../../llm/types.js"; diff --git a/src/flows/model-picker.ts b/src/flows/model-picker.ts index 3648120e0832..f5de44c050bd 100644 --- a/src/flows/model-picker.ts +++ b/src/flows/model-picker.ts @@ -11,12 +11,12 @@ import { } from "../agents/model-catalog-visibility.js"; import type { ModelCatalogEntry } from "../agents/model-catalog.js"; import type { ModelCatalogSnapshot } from "../agents/model-catalog.types.js"; -import { createModelPickerVisibleProviderPredicate } from "../agents/model-picker-visibility.js"; import { createProviderAuthChecker, type ProviderModelAuthChecker, } from "../agents/model-provider-auth.js"; import { formatLiteralProviderPrefixedModelRef } from "../agents/model-ref-shared.js"; +import { createModelPickerVisibleProviderPredicate } from "../agents/model-runtime-aliases.js"; import { buildConfiguredModelCatalog, buildModelAliasIndex, diff --git a/src/plugins/provider-hook-runtime.ts b/src/plugins/provider-hook-runtime.ts index b6465d8de9a3..96013079e9bb 100644 --- a/src/plugins/provider-hook-runtime.ts +++ b/src/plugins/provider-hook-runtime.ts @@ -7,7 +7,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; -import { resolveModelCatalogScope } from "../agents/model-catalog-scope.js"; +import { resolveModelCatalogScope } from "../agents/model-discovery-context.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { getLoadedRuntimePluginRegistry } from "./active-runtime-registry.js"; import {