From f4c9e71e4eab26787290d9a71a6498bce6c28415 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Apr 2026 03:16:35 +0100 Subject: [PATCH] fix(models): guard provider policy model shape --- CHANGELOG.md | 1 + ...els-config.providers.policy.lookup.test.ts | 27 +++++++++++++++++++ .../models-config.providers.policy.lookup.ts | 4 ++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4962e9ebaf9..077f90dd81e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Docs: https://docs.openclaw.ai - CLI/image describe: pass `--prompt` and `--timeout-ms` through `infer image describe` and `describe-many`, so custom vision instructions and slow local model budgets reach media-understanding providers such as Ollama, OpenAI, Google, and OpenRouter. Refs #63700. Thanks @cedricjanssens. - Model selection: include the rejected provider/model ref and allowlist recovery hint when a stored session override is cleared, so local model selections such as Gemma GGUF variants do not fall back to the default with a generic message. Refs #71069. Thanks @CyberRaccoonTeam. - Local model prompt caching: keep stable Project Context above volatile channel/session prompt guidance and stop embedding current channel names in the message tool description, so Ollama, MLX, llama.cpp, and other prefix-cache backends avoid avoidable full prompt reprocessing across channel turns. Fixes #40256; supersedes #40296. Thanks @rhclaw and @sriram369. +- Gateway/OpenAI-compatible API: guard provider policy lookup against runtime providers with non-array `models` values, so `/v1/chat/completions` no longer fails with `provider?.models?.some is not a function`. Fixes #66744; carries forward #66761. Thanks @MightyMoud, @MukundaKatta. - WhatsApp/Web: pass explicit Baileys socket timings into every WhatsApp Web socket and expose `web.whatsapp.*` keepalive, connect, and query timeout settings so unstable networks can avoid repeated 408 disconnect and opening-handshake timeout loops. Fixes #56365. (#73580) Thanks @velvet-shark. - Channels/Telegram: persist native command metadata on target sessions so topic, helper, and ACP-bound slash commands keep their session metadata attached to the routed conversation. (#57548) Thanks @GaosCode. - Channels/native commands: keep validated native slash command replies visible in group chats while preserving explicit owner allowlists for command authorization. (#73672) Thanks @obviyus. diff --git a/src/agents/models-config.providers.policy.lookup.test.ts b/src/agents/models-config.providers.policy.lookup.test.ts index bb82b8b3935..f7b95ec8be4 100644 --- a/src/agents/models-config.providers.policy.lookup.test.ts +++ b/src/agents/models-config.providers.policy.lookup.test.ts @@ -49,4 +49,31 @@ describe("resolveProviderPluginLookupKey", () => { }), ).toBe("google"); }); + + it("does not throw when runtime provider models is an object map", () => { + expect(() => + resolveProviderPluginLookupKey("openrouter", { + baseUrl: "https://openrouter.ai/api/v1", + models: { "some/model": { api: "openai-completions" } } as never, + }), + ).not.toThrow(); + }); + + it("does not throw when runtime provider models is undefined", () => { + expect(() => + resolveProviderPluginLookupKey("openrouter", { + baseUrl: "https://openrouter.ai/api/v1", + models: undefined as never, + }), + ).not.toThrow(); + }); + + it("falls through to the provider key when runtime provider models is non-array", () => { + expect( + resolveProviderPluginLookupKey("openrouter", { + baseUrl: "https://openrouter.ai/api/v1", + models: { some: "garbage" } as never, + }), + ).toBe("openrouter"); + }); }); diff --git a/src/agents/models-config.providers.policy.lookup.ts b/src/agents/models-config.providers.policy.lookup.ts index a4e8e4a7368..342599bdf90 100644 --- a/src/agents/models-config.providers.policy.lookup.ts +++ b/src/agents/models-config.providers.policy.lookup.ts @@ -21,8 +21,10 @@ export function resolveProviderPluginLookupKey( ) { return "google"; } + // Runtime plugin data can be looser than ProviderConfig; guard before .some(). if ( - provider?.models?.some((model) => normalizeOptionalString(model.api) === "google-generative-ai") + Array.isArray(provider?.models) && + provider.models.some((model) => normalizeOptionalString(model.api) === "google-generative-ai") ) { return "google"; }