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);