From 59ea107d094f1c9496ecbf1436d6938247532a8a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 11 Aug 2026 23:37:15 -0700 Subject: [PATCH] refactor(agents): delete dead model-selection surface and consolidate compaction targets (#122474) * refactor(agents): delete dead model-selection surface, consolidate compaction target assembly Removes production surface with zero live callers: buildConfiguredAllowlistKeys (orphaned since fallback allowlist filtering was split out), the retryTransientProviderRuntimeMiss resolveModelAsync option (its gateway startup prewarming caller was replaced by prepared runtime snapshots), the ignored useAsyncModelResolution plumbing (kept only as a deprecated no-op field on the plugin-SDK-shipped prepareSimpleCompletionModelForAgent), and dead facade re-exports (inferUniqueProviderFromCatalog, ThinkLevel, ModelRefStatus). Consolidates resolveEmbeddedCompactionTarget's five hand-built result sites into one assembleTarget helper owning the auth-profile-drop-on-provider-change rule; resolution precedence is unchanged and now pinned by new table-driven cases (unique-provider inference, ambiguous literal, profile-suffix preservation). Net -93 production LOC, -106 test LOC. * chore(sdk): regenerate plugin SDK API baseline after facade export removals --- .../agent-harness-runtime.json | 2 +- .../agent-harness.json | 2 +- .../plugin-sdk-api-baseline/channel-core.json | 2 +- .../channel-entry-contract.json | 2 +- .../channel-message.json | 2 +- .../channel-outbound.json | 2 +- .../channel-plugin-common.json | 2 +- .../plugin-sdk-api-baseline/core.json | 2 +- .../plugin-sdk-api-baseline/discord.json | 2 +- .../inbound-reply-dispatch.json | 2 +- .../meeting-runtime.json | 2 +- .../plugin-sdk-api-baseline/plugin-entry.json | 2 +- .../plugin-runtime.json | 2 +- .../provider-catalog-runtime.json | 2 +- .../plugin-sdk-api-baseline/tool-plugin.json | 2 +- .../webhook-ingress.json | 2 +- .../compaction-runtime-context.test.ts | 64 +++++++++ .../compaction-runtime-context.ts | 62 +++------ .../model.startup-retry.test.ts | 128 ------------------ .../embedded-agent-runner/model.test.ts | 1 - src/agents/embedded-agent-runner/model.ts | 7 - src/agents/model-selection-shared.ts | 46 +------ src/agents/model-selection.test.ts | 40 ------ src/agents/model-selection.ts | 9 +- src/agents/simple-completion-runtime.ts | 5 +- .../reply/progress-narrator-model.ts | 1 - src/plugins/contracts/tts-contract-suites.ts | 1 - src/tts/tts-core.ts | 1 - 28 files changed, 99 insertions(+), 298 deletions(-) delete mode 100644 src/agents/embedded-agent-runner/model.startup-retry.test.ts diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json index 1ba9f2e103c0..706088d0dc2d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json @@ -1 +1 @@ -{"contentHash":"ab9f09ea675935d06a12fff912c1f9109311be8be93a5b8d982a9569d9163359","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"92f86077bf8bd7e0ade16592677b6e824e8ace048e40849aa5fd5cd9fe17f280","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json index 07ccac7be166..911561a94fd2 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json @@ -1 +1 @@ -{"contentHash":"0c4bd22dd46955da487cb750a995732a37ffa6000e94e2381621a8dc58333246","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"82523c6b1d5b8bc1b0933455ef0d8f798fb938e2b0e2676a434522926fd1d1ed","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-core.json b/docs/.generated/plugin-sdk-api-baseline/channel-core.json index 8ed4302b79a5..75b83b372c06 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-core.json @@ -1 +1 @@ -{"contentHash":"91cc5b30ad903aea2c6c002f64d21c8236fb141a2f8f8f0ca5217d46c7e79883","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"b771ba5e57fca93e4b49512f7cde5cd1c1b0fe4cf247985aeae5bfd44cb54923","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json index 35022086ab59..94540322a878 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json @@ -1 +1 @@ -{"contentHash":"2c922f70f8c8155d9be8fb0fb0c6dab4b03f7d1765ad98bab4668a49f984aa5f","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"8bc9cfdf0f7299079e3d39ce377b1eb8cf2b4b99ff39d3f8ceee1f7ac72d0dbd","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-message.json b/docs/.generated/plugin-sdk-api-baseline/channel-message.json index e7c8c61ad8c0..96aadff0c4d3 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-message.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-message.json @@ -1 +1 @@ -{"contentHash":"3c2ea28cb7f23bf09c7ad0cab39751fcefd46055c563d1d2ce226b628f86dc48","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"69ed324324c86ad863f2804fd60dfc61f6b79040ee34fa52d33d1277857db4b1","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json index 9b2b4d646527..51b4f05da2d5 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json @@ -1 +1 @@ -{"contentHash":"a31b1c8f7db35e0e316a59afaa46f27f076b55f11a1dc08dc7630224115038de","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"a98fa525383537f792dfc06ffb40d1a89cf7c5d1d381e253178f5a2ebad79276","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json index 3cde632c6f71..2f5c29760554 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json @@ -1 +1 @@ -{"contentHash":"4edbdb687dfc1780b629e3e45efd1eb183a07d3d08c14d776a2e188b76f7c49b","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"8fdceb1f341434d335cbbb84052651df83b29436d1016c694461b072a75121c1","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} diff --git a/docs/.generated/plugin-sdk-api-baseline/core.json b/docs/.generated/plugin-sdk-api-baseline/core.json index c2212f81ac9f..ee55b7e02682 100644 --- a/docs/.generated/plugin-sdk-api-baseline/core.json +++ b/docs/.generated/plugin-sdk-api-baseline/core.json @@ -1 +1 @@ -{"contentHash":"cb3406cb7205502c40a8bbf80521e58fd083620bbed99213d83a804b8deb8264","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"121f16d39c0b147393ca289221d4b020856a661dfc989dc978b5a499bfce5222","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/discord.json b/docs/.generated/plugin-sdk-api-baseline/discord.json index e17e1259574e..5a43a582629b 100644 --- a/docs/.generated/plugin-sdk-api-baseline/discord.json +++ b/docs/.generated/plugin-sdk-api-baseline/discord.json @@ -1 +1 @@ -{"contentHash":"78ef9651b379d1b673d6c17bc9b636cdaaf2ea4cbe44c546482d53e7b7c8fdd9","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"00e179ad017e2921d85f9d2ffcf5f9e1bb415984d19e6fdc62a1832dbee659b0","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} diff --git a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json index 14d5ae222edb..2bdb8846687f 100644 --- a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json +++ b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json @@ -1 +1 @@ -{"contentHash":"077ae11669eaa9cba548cae506db3c11261712b3ffc804c48a28bf159caf124c","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"bfde664c63043f9719c8c12ef1e144dc387c05348b0f8b26bb2156438518dc21","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} diff --git a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json index 9b54c33ff1cb..d8be367b6c13 100644 --- a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json @@ -1 +1 @@ -{"contentHash":"bd05a541d2ba0e7f335ed0c4d4f187bed1c04953479682616d2b6c416bbd0459","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"2608b548ecca0c2981d80bff3596982d74572558c0fe7eb846a971880b1563af","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json index 455a193b261b..f655ce4ffdbd 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json @@ -1 +1 @@ -{"contentHash":"f995b2ac4652ccf6a6107700f3a15b9d8e7bd8513466704424b899a831abdb41","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"8220986c0c848a5fe05c909f5c0626a6ebe0800fe5517a196718d4f21d30ebb6","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json index 9a62dcabf752..4d3025465523 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json @@ -1 +1 @@ -{"contentHash":"97a6f344d3efa2dd1f7d7620562f22bf632873648a82be69a26cad6a8bd0b762","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"8350a80731ab2e020c859ea1994acd004fbaa9ca9c0d92fb09acd8703bc79ed3","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json index e9307b9a192f..f7fcdd9e6351 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json @@ -1 +1 @@ -{"contentHash":"2d76a60bfd593777339ee478306234773ac9ad626c564c60c95d49f82019ae2c","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"f61cc543e5d007e5da4951ccc61e12fc107fed478dfc8285b37389a77d2a6224","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json index 36ef5bdff544..ffa394876c59 100644 --- a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json +++ b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json @@ -1 +1 @@ -{"contentHash":"11c6288303d0e1b2f75ad06f5acac4fbafd94ea98eaa6212d7e0adf87db43607","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"f27ec37aa7c772f12aeda7b8aa969b34a7a7bb7877e6d95b26a8b3e9174f4899","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} diff --git a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json index 53b3e1f657d1..7b8b290d000f 100644 --- a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json +++ b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json @@ -1 +1 @@ -{"contentHash":"9204480482168d7f4d8f9601466c147d056403b42e3ba7979cae746792c268a8","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"6579285290ce10e5bf4ae930bbb2d28de52672fc4c7dd0f55f0dcdc1d711d3c1","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts index f4eaebb37003..4b8617cb483b 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts @@ -792,6 +792,70 @@ describe("buildEmbeddedCompactionRuntimeContext", () => { expect(result.authProfileId).toBe("openai:default"); }); + it.each([ + { + name: "infers a different provider for a uniquely configured bare literal", + config: { + models: { + providers: { + anthropic: { models: [{ id: "compact-model" }] }, + }, + }, + agents: { defaults: { compaction: { model: "compact-model" } } }, + }, + provider: "openai", + authProfileId: "openai:default", + expectedProvider: "anthropic", + expectedModel: "compact-model", + expectedAuthProfileId: undefined, + }, + { + name: "keeps an ambiguous configured bare literal on the current provider", + config: { + models: { + providers: { + openai: { models: [{ id: "shared-model" }] }, + anthropic: { models: [{ id: "shared-model" }] }, + }, + }, + agents: { defaults: { compaction: { model: "shared-model" } } }, + }, + provider: "google", + authProfileId: "google:default", + expectedProvider: "google", + expectedModel: "shared-model", + expectedAuthProfileId: "google:default", + }, + { + name: "preserves a multi-segment model id and trailing profile suffix", + config: { + agents: { + defaults: { + compaction: { model: "openrouter/meta-llama/llama-3.3-70b:free@work" }, + }, + }, + }, + provider: "openrouter", + authProfileId: "openrouter:default", + expectedProvider: "openrouter", + expectedModel: "meta-llama/llama-3.3-70b:free@work", + expectedAuthProfileId: "openrouter:default", + }, + ])("$name", (fixture) => { + const result = resolveEmbeddedCompactionTarget({ + config: fixture.config as unknown as OpenClawConfig, + provider: fixture.provider, + modelId: "current-model", + authProfileId: fixture.authProfileId, + defaultProvider: fixture.provider, + defaultModel: "current-model", + }); + + expect(result.provider).toBe(fixture.expectedProvider); + expect(result.model).toBe(fixture.expectedModel); + expect(result.authProfileId).toBe(fixture.expectedAuthProfileId); + }); + it("leaves non-openai providers unchanged", () => { const result = resolveEmbeddedCompactionTarget({ provider: "anthropic", diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.ts index 106150f5d18d..6683f04abe3c 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.ts @@ -150,29 +150,26 @@ export function resolveEmbeddedCompactionTarget(params: { ...(useNativeHarnessRuntime ? { nativeHarnessCompaction: true } : {}), }; }; - if (!override) { - const authProfileId = params.authProfileId ?? undefined; + const assembleTarget = (targetProvider: string | undefined, targetModel: string | undefined) => { + // A provider switch cannot inherit credentials selected for the session's + // original provider; all target paths share that boundary. + const authProfileId = + targetProvider !== provider ? undefined : (params.authProfileId ?? undefined); return { - provider, - ...resolveTargetProviders(provider, authProfileId), - model, + provider: targetProvider, + ...resolveTargetProviders(targetProvider, authProfileId), + model: targetModel, authProfileId, }; + }; + if (!override) { + return assembleTarget(provider, model); } const slashIdx = override.indexOf("/"); if (slashIdx > 0) { const overrideProvider = override.slice(0, slashIdx).trim(); const overrideModel = override.slice(slashIdx + 1).trim() || params.defaultModel; - // When switching provider via override, drop the primary auth profile to - // avoid sending the wrong credentials. - const authProfileId = - overrideProvider !== provider ? undefined : (params.authProfileId ?? undefined); - return { - provider: overrideProvider, - ...resolveTargetProviders(overrideProvider, authProfileId), - model: overrideModel, - authProfileId, - }; + return assembleTarget(overrideProvider, overrideModel); } const config = params.config ?? {}; const currentProvider = provider?.trim(); @@ -184,27 +181,14 @@ export function resolveEmbeddedCompactionTarget(params: { model: override, }) ) { - const authProfileId = params.authProfileId ?? undefined; - return { - provider: currentProvider, - ...resolveTargetProviders(currentProvider, authProfileId), - model: override, - authProfileId, - }; + return assembleTarget(currentProvider, override); } const inferredLiteralProvider = inferUniqueProviderFromConfiguredModels({ cfg: config, model: override, }); if (inferredLiteralProvider) { - const authProfileId = - inferredLiteralProvider !== provider ? undefined : (params.authProfileId ?? undefined); - return { - provider: inferredLiteralProvider, - ...resolveTargetProviders(inferredLiteralProvider, authProfileId), - model: override, - authProfileId, - }; + return assembleTarget(inferredLiteralProvider, override); } const defaultProvider = provider || DEFAULT_PROVIDER; const aliasResolution = resolveModelRefFromString({ @@ -217,23 +201,9 @@ export function resolveEmbeddedCompactionTarget(params: { }), }); if (aliasResolution?.alias) { - const resolvedProvider = aliasResolution.ref.provider; - const authProfileId = - resolvedProvider !== provider ? undefined : (params.authProfileId ?? undefined); - return { - provider: resolvedProvider, - ...resolveTargetProviders(resolvedProvider, authProfileId), - model: aliasResolution.ref.model, - authProfileId, - }; + return assembleTarget(aliasResolution.ref.provider, aliasResolution.ref.model); } - const authProfileId = params.authProfileId ?? undefined; - return { - provider, - ...resolveTargetProviders(provider, authProfileId), - model: override, - authProfileId, - }; + return assembleTarget(provider, override); } function normalizeCompactionConfigKey(value: string): string { diff --git a/src/agents/embedded-agent-runner/model.startup-retry.test.ts b/src/agents/embedded-agent-runner/model.startup-retry.test.ts deleted file mode 100644 index e76c7f151a85..000000000000 --- a/src/agents/embedded-agent-runner/model.startup-retry.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Coverage for retrying transient model-runtime misses during startup. -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; - -const discoverAuthStorageMock = vi.fn<(agentDir?: string) => { mocked: true }>(() => ({ - mocked: true, -})); -const discoverModelsMock = vi.fn< - (authStorage: unknown, agentDir: string) => { find: ReturnType } ->(() => ({ find: vi.fn(() => null) })); - -const prepareProviderDynamicModelMock = vi.fn<(params: unknown) => Promise>(async () => {}); -let dynamicAttempts = 0; -const runProviderDynamicModelMock = vi.fn<(params: unknown) => unknown>(() => - // First dynamic lookup simulates startup catalog warmup; the retry path must - // resolve on the second attempt only when explicitly enabled. - dynamicAttempts > 1 - ? { - id: "gpt-5.4", - name: "gpt-5.4", - provider: "openai", - api: "openai-chatgpt-responses", - baseUrl: "https://chatgpt.com/backend-api", - reasoning: true, - input: ["text"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 1_050_000, - maxTokens: 128_000, - } - : undefined, -); - -vi.mock("../agent-model-discovery.js", () => ({ - discoverAuthStorage: discoverAuthStorageMock, - discoverModels: discoverModelsMock, -})); - -vi.mock("../prepared-model-runtime.js", () => ({ - getPreparedModelRuntimeSnapshot: () => undefined, - loadPreparedModelRuntimeSnapshot: async ({ agentDir }: { agentDir: string }) => { - const authStorage = discoverAuthStorageMock(agentDir); - return { - agentDir, - config: {}, - createStores: () => ({ - authStorage, - modelRegistry: discoverModelsMock(authStorage, agentDir), - }), - }; - }, -})); - -vi.mock("../../plugins/provider-runtime.js", () => ({ - applyProviderResolvedTransportWithPlugin: () => undefined, - buildProviderUnknownModelHintWithPlugin: () => undefined, - normalizeProviderResolvedModelWithPlugin: () => undefined, - normalizeProviderTransportWithPlugin: () => undefined, - prepareProviderDynamicModel: async () => {}, - resolveExternalAuthProfilesWithPlugins: () => [], - runProviderDynamicModel: () => undefined, - shouldPreferProviderRuntimeResolvedModel: () => false, -})); - -describe("resolveModelAsync startup retry", () => { - let resolveModelAsync: typeof import("./model.js").resolveModelAsync; - - const runtimeHooks = { - buildProviderUnknownModelHintWithPlugin: () => undefined, - normalizeProviderResolvedModelWithPlugin: () => undefined, - normalizeProviderTransportWithPlugin: () => undefined, - prepareProviderDynamicModel: (params: unknown) => prepareProviderDynamicModelMock(params), - runProviderDynamicModel: (params: unknown) => runProviderDynamicModelMock(params), - applyProviderResolvedTransportWithPlugin: () => undefined, - }; - - beforeAll(async () => { - ({ resolveModelAsync } = await import("./model.js")); - }); - - beforeEach(() => { - dynamicAttempts = 0; - prepareProviderDynamicModelMock.mockClear(); - prepareProviderDynamicModelMock.mockImplementation(async () => { - dynamicAttempts += 1; - }); - runProviderDynamicModelMock.mockClear(); - discoverAuthStorageMock.mockClear(); - discoverModelsMock.mockClear(); - }); - - it("retries once after a transient provider-runtime miss", async () => { - const result = await resolveModelAsync( - "openai", - "gpt-5.4", - "/tmp/agent", - {}, - { - agentRuntimeId: "openclaw", - retryTransientProviderRuntimeMiss: true, - runtimeHooks, - }, - ); - - expect(result.error).toBeUndefined(); - expect(result.model?.provider).toBe("openai"); - expect(result.model?.id).toBe("gpt-5.4"); - expect(result.model?.api).toBe("openai-chatgpt-responses"); - expect(prepareProviderDynamicModelMock).toHaveBeenCalledTimes(2); - expect(runProviderDynamicModelMock).toHaveBeenCalledTimes(2); - for (const call of [prepareProviderDynamicModelMock, runProviderDynamicModelMock]) { - expect(call).toHaveBeenCalledWith( - expect.objectContaining({ - context: expect.objectContaining({ agentRuntimeId: "openclaw" }), - }), - ); - } - }); - - it("does not retry during steady-state misses", async () => { - // Normal runtime lookups should not double-hit providers after startup; that - // would add latency and duplicate plugin side effects. - const result = await resolveModelAsync("openai", "gpt-5.4", "/tmp/agent", {}, { runtimeHooks }); - - expect(result.model).toBeUndefined(); - expect(result.error).toBe("Unknown model: openai/gpt-5.4"); - expect(prepareProviderDynamicModelMock).toHaveBeenCalledTimes(1); - expect(runProviderDynamicModelMock).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/agents/embedded-agent-runner/model.test.ts b/src/agents/embedded-agent-runner/model.test.ts index 26ad6bbbec82..b990bf7d5d83 100644 --- a/src/agents/embedded-agent-runner/model.test.ts +++ b/src/agents/embedded-agent-runner/model.test.ts @@ -380,7 +380,6 @@ function resolveModelAsyncForTest( options?: { allowBundledStaticCatalogFallback?: boolean; preferBundledStaticCatalogTransport?: boolean; - retryTransientProviderRuntimeMiss?: boolean; runtimeHooks?: ReturnType; skipAgentDiscovery?: boolean; }, diff --git a/src/agents/embedded-agent-runner/model.ts b/src/agents/embedded-agent-runner/model.ts index 5b3556dd8070..246e930e9636 100644 --- a/src/agents/embedded-agent-runner/model.ts +++ b/src/agents/embedded-agent-runner/model.ts @@ -62,7 +62,6 @@ type CommonModelResolutionOptions = { type AsyncModelResolutionOptions = CommonModelResolutionOptions & { allowBundledStaticCatalogFallback?: boolean; preferBundledStaticCatalogTransport?: boolean; - retryTransientProviderRuntimeMiss?: boolean; agentRuntimeId?: string; skipAgentDiscovery?: boolean; preparedModelRuntime?: PreparedModelRuntimeSnapshot; @@ -412,12 +411,6 @@ export async function resolveModelAsync( ? explicitModel.model : undefined; model ??= await resolveDynamicAttempt(); - if (!model && !explicitModel && options?.retryTransientProviderRuntimeMiss) { - // Startup can race the first provider-runtime snapshot load on a fresh - // gateway boot. Retry once before surfacing a user-visible "Unknown model" - // that disappears on the next message. - model = await resolveDynamicAttempt(); - } if (!model && !explicitModel && options?.allowBundledStaticCatalogFallback) { model = await resolveStaticCatalogFallbackModel(); } diff --git a/src/agents/model-selection-shared.ts b/src/agents/model-selection-shared.ts index 588086b63804..006bdded42f5 100644 --- a/src/agents/model-selection-shared.ts +++ b/src/agents/model-selection-shared.ts @@ -275,7 +275,7 @@ export function inferUniqueProviderFromConfiguredModels( } /** Infer a unique provider for a bare model from a provider catalog. */ -export function inferUniqueProviderFromCatalog(params: { +function inferUniqueProviderFromCatalog(params: { catalog: readonly ModelCatalogEntry[]; model: string; }): string | undefined { @@ -526,50 +526,6 @@ function resolveAllowlistModelKey( return modelKey(parsed.provider, parsed.model); } -/** Build the exact configured model keys that constrain model visibility. */ -export function buildConfiguredAllowlistKeys( - params: { - cfg: OpenClawConfig | undefined; - defaultProvider: string; - agentId?: string; - allowManifestNormalization?: boolean; - allowPluginNormalization?: boolean; - } & ModelManifestNormalizationContext, -): Set | null { - const visibility = parseConfiguredModelVisibilityEntries({ - cfg: params.cfg, - agentId: params.agentId, - }); - if (visibility.exactModelRefs.length === 0) { - return null; - } - - const aliasIndex = buildModelAliasIndex({ - cfg: params.cfg ?? {}, - defaultProvider: params.defaultProvider, - agentId: resolvePolicyAliasAgentId(visibility.configPath, params.agentId), - allowManifestNormalization: params.allowManifestNormalization, - allowPluginNormalization: params.allowPluginNormalization, - manifestPlugins: params.manifestPlugins, - }); - const keys = new Set(); - for (const raw of visibility.exactModelRefs) { - const key = resolveAllowlistModelKey({ - cfg: params.cfg, - raw, - defaultProvider: params.defaultProvider, - aliasIndex, - allowManifestNormalization: params.allowManifestNormalization, - allowPluginNormalization: params.allowPluginNormalization, - manifestPlugins: params.manifestPlugins, - }); - if (key) { - keys.add(key); - } - } - return keys.size > 0 ? keys : null; -} - type BuildModelAliasIndexParams = { cfg: OpenClawConfig; defaultProvider: string; diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index 4ae0b809a751..976e6a7d8fbe 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -11,7 +11,6 @@ import { import { isModelKeyAllowedBySet } from "./model-selection-shared.js"; import { buildAllowedModelSet, - buildConfiguredAllowlistKeys, buildConfiguredModelCatalog, inferUniqueProviderFromConfiguredModels, getModelRefStatus, @@ -940,45 +939,6 @@ describe("model-selection", () => { }); }); - describe("buildConfiguredAllowlistKeys", () => { - it("resolves per-agent policy aliases to the enforcement key", () => { - const cfg = { - agents: { - defaults: { - model: { primary: "openai/gpt-5.5" }, - }, - list: [ - { - id: "research", - models: { - "anthropic/claude-sonnet-4-6": { alias: "sonnet" }, - }, - modelPolicy: { allow: ["sonnet"] }, - }, - ], - }, - } as OpenClawConfig; - - const keys = buildConfiguredAllowlistKeys({ - cfg, - defaultProvider: "openai", - agentId: "research", - }); - const policy = createModelVisibilityPolicy({ - cfg, - catalog: [], - defaultProvider: "openai", - defaultModel: "gpt-5.5", - agentId: "research", - }); - - expect(keys).toEqual(new Set(["anthropic/claude-sonnet-4-6"])); - expect(keys?.has("openai/sonnet")).toBe(false); - expect(policy.allowsKey("anthropic/claude-sonnet-4-6")).toBe(true); - expect(policy.allowsKey("openai/sonnet")).toBe(false); - }); - }); - describe("buildAllowedModelSet", () => { it("keeps explicitly allowlisted models even when missing from bundled catalog", () => { const result = buildAllowedModelSet({ diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index 47b7144bebf9..3b23311ef18d 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -36,10 +36,8 @@ import { } from "./model-selection-resolve.js"; import { buildAllowedModelSetWithFallbacks, - buildConfiguredAllowlistKeys, buildConfiguredModelCatalog, buildModelAliasIndex, - inferUniqueProviderFromCatalog, inferUniqueProviderFromConfiguredModels, normalizeModelSelection, resolveBareModelDefaultProvider, @@ -48,23 +46,18 @@ import { resolveModelAliasFromPair, resolveModelRefFromString, type ModelAliasIndex, - type ModelRefStatus, } from "./model-selection-shared.js"; -export type { ModelAliasIndex, ModelManifestNormalizationContext, ModelRef, ModelRefStatus }; - -export type { ThinkLevel } from "../auto-reply/thinking.shared.js"; +export type { ModelAliasIndex, ModelManifestNormalizationContext, ModelRef }; export { resolveDefaultModelForAgent, resolveSubagentConfiguredModelSelection }; export { - buildConfiguredAllowlistKeys, buildConfiguredModelCatalog, buildModelAliasIndex, findNormalizedProviderKey, findNormalizedProviderValue, inferUniqueProviderFromConfiguredModels, - inferUniqueProviderFromCatalog, legacyModelKey, modelKey, normalizeModelRef, diff --git a/src/agents/simple-completion-runtime.ts b/src/agents/simple-completion-runtime.ts index 33dcad4075c2..8bf14c8f336e 100644 --- a/src/agents/simple-completion-runtime.ts +++ b/src/agents/simple-completion-runtime.ts @@ -242,8 +242,6 @@ export async function prepareSimpleCompletionModel(params: { preferredProfile?: string; allowMissingApiKeyModes?: ReadonlyArray; allowBundledStaticCatalogFallback?: boolean; - /** @deprecated Model resolution is lifecycle-backed and always asynchronous. */ - useAsyncModelResolution?: boolean; skipAgentDiscovery?: boolean; bindAuthOwner?: boolean; modelResolver?: typeof resolveModelAsync; @@ -480,7 +478,7 @@ export async function prepareSimpleCompletionModelForAgent(params: { preferredProfile?: string; allowMissingApiKeyModes?: ReadonlyArray; allowBundledStaticCatalogFallback?: boolean; - /** @deprecated Model resolution is lifecycle-backed and always asynchronous. */ + /** @deprecated no-op; kept for plugin-SDK source compatibility, remove at next SDK-breaking window. */ useAsyncModelResolution?: boolean; skipAgentDiscovery?: boolean; bindAuthOwner?: boolean; @@ -510,7 +508,6 @@ export async function prepareSimpleCompletionModelForAgent(params: { ...(params.allowBundledStaticCatalogFallback !== undefined ? { allowBundledStaticCatalogFallback: params.allowBundledStaticCatalogFallback } : {}), - useAsyncModelResolution: params.useAsyncModelResolution, skipAgentDiscovery: params.skipAgentDiscovery, bindAuthOwner: params.bindAuthOwner, modelResolver: params.modelResolver, diff --git a/src/auto-reply/reply/progress-narrator-model.ts b/src/auto-reply/reply/progress-narrator-model.ts index 96d1d950a9f5..7522b49101ae 100644 --- a/src/auto-reply/reply/progress-narrator-model.ts +++ b/src/auto-reply/reply/progress-narrator-model.ts @@ -68,7 +68,6 @@ export async function prepareNarrationModel(params: { cfg: OpenClawConfig; agent cfg: params.cfg, agentId: params.agentId, useUtilityModel: true, - useAsyncModelResolution: true, allowMissingApiKeyModes: ["aws-sdk"], }); if ("error" in prepared) { diff --git a/src/plugins/contracts/tts-contract-suites.ts b/src/plugins/contracts/tts-contract-suites.ts index 825c8607844f..a0cebecd6492 100644 --- a/src/plugins/contracts/tts-contract-suites.ts +++ b/src/plugins/contracts/tts-contract-suites.ts @@ -927,7 +927,6 @@ export function describeTtsSummarizationContract() { cfg, provider: "openai", modelId: "gpt-4.1-mini", - useAsyncModelResolution: true, }); }); diff --git a/src/tts/tts-core.ts b/src/tts/tts-core.ts index 1f82fb3e4e71..77e0e6ef6e04 100644 --- a/src/tts/tts-core.ts +++ b/src/tts/tts-core.ts @@ -104,7 +104,6 @@ export async function summarizeText( cfg, provider: ref.provider, modelId: ref.model, - useAsyncModelResolution: true, }); if ("error" in prepared) { throw new Error(prepared.error);