From d35e6f79e16d446f694959a4d53ec087ec5ca885 Mon Sep 17 00:00:00 2001 From: "openclaw-clownfish[bot]" <280122609+openclaw-clownfish[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:57:48 -0700 Subject: [PATCH] fix: canonicalize extra params model lookup keys Carries forward https://github.com/openclaw/openclaw/pull/44319 by @HenryXiaoYang. --- CHANGELOG.md | 1 + .../pi-embedded-runner-extraparams.test.ts | 64 +++++++++++++++++-- src/agents/pi-embedded-runner/extra-params.ts | 8 ++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d3161164e..7bde1c3cb0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai - Shared labels: preserve Unicode combining marks and NFC-equivalent accented text in group/channel slug normalization so non-Latin labels no longer lose meaningful characters. Fixes #58932; carries forward #58942 and #58995. Thanks @fengqing-git, @Starhappysh, and @koen666. - Channels/Telegram: include probed video width and height when sending regular Telegram videos, so portrait clips render with the correct orientation instead of being stretched by clients. (#18915) Thanks @storyarcade. - Docs/Hetzner: clarify that SSH tunnel access requires `AllowTcpForwarding local` before running `ssh -L`, so hardened VPS sshd configs do not block loopback Gateway access. Fixes #54557; carries forward #54564; refs #54954. Thanks @satishkc7, @blackstrype, and @Aftabbs. +- Agents/model config: resolve per-model extra params through canonical model keys while preserving legacy double-prefixed fallback entries, so provider-prefixed model ids such as `openrouter/auto` keep their configured runtime params. (#44319) Thanks @HenryXiaoYang. - Gateway/shutdown: report structured shutdown warnings and HTTP close timeout warnings through `ShutdownResult` while preserving lifecycle hook hardening. Carries forward #41296. Thanks @edenfunf. - Control UI: keep Agents Overview and config-form select dropdowns on their configured value after options render while preserving inherited agent model placeholders. Fixes #40352; carries forward #52948. Thanks @xiaoquanidea. - Plugins/QA: prebuild the private QA channel runtime before plugin gauntlet source runs so wrapper CPU/RSS measurements are not polluted by private QA dist rebuild work. Thanks @vincentkoc. diff --git a/src/agents/pi-embedded-runner-extraparams.test.ts b/src/agents/pi-embedded-runner-extraparams.test.ts index 2370a46ed65..c8dfc220b0f 100644 --- a/src/agents/pi-embedded-runner-extraparams.test.ts +++ b/src/agents/pi-embedded-runner-extraparams.test.ts @@ -466,7 +466,7 @@ describe("applyExtraParamsToAgent", () => { }; } - function buildAnthropicModelConfig(modelKey: string, params: Record) { + function buildModelConfig(modelKey: string, params: Record) { return { agents: { defaults: { @@ -817,6 +817,60 @@ describe("applyExtraParamsToAgent", () => { expect(payload.parallel_tool_calls).toBe(false); }); + it("uses canonical model config keys for provider-prefixed model ids", () => { + const payload = runParallelToolCallsPayloadMutationCase({ + applyProvider: "openrouter", + applyModelId: "openrouter/auto", + cfg: { + agents: { + defaults: { + models: { + "openrouter/auto": { + params: { + parallel_tool_calls: false, + }, + }, + }, + }, + }, + }, + model: { + api: "openai-completions", + provider: "openrouter", + id: "openrouter/auto", + } as Model<"openai-completions">, + }); + + expect(payload.parallel_tool_calls).toBe(false); + }); + + it("keeps legacy double-prefixed model config fallback for provider-prefixed model ids", () => { + const payload = runParallelToolCallsPayloadMutationCase({ + applyProvider: "openrouter", + applyModelId: "openrouter/auto", + cfg: { + agents: { + defaults: { + models: { + "openrouter/openrouter/auto": { + params: { + parallel_tool_calls: false, + }, + }, + }, + }, + }, + }, + model: { + api: "openai-completions", + provider: "openrouter", + id: "openrouter/auto", + } as Model<"openai-completions">, + }); + + expect(payload.parallel_tool_calls).toBe(false); + }); + it("strips store from proxied openai-completions payloads", () => { const payload = runResponsesPayloadMutationCase({ applyProvider: "google", @@ -2445,7 +2499,7 @@ describe("applyExtraParamsToAgent", () => { it("adds Anthropic 1M beta header when context1m is enabled for Opus/Sonnet", () => { const { calls, agent } = createOptionsCaptureAgent(); - const cfg = buildAnthropicModelConfig("anthropic/claude-opus-4-6", { context1m: true }); + const cfg = buildModelConfig("anthropic/claude-opus-4-6", { context1m: true }); applyExtraParamsToAgent(agent, cfg, "anthropic", "claude-opus-4-6"); @@ -2472,7 +2526,7 @@ describe("applyExtraParamsToAgent", () => { }); it("does not add Anthropic 1M beta header when context1m is not enabled", () => { - const cfg = buildAnthropicModelConfig("anthropic/claude-opus-4-6", { + const cfg = buildModelConfig("anthropic/claude-opus-4-6", { temperature: 0.2, }); const headers = runAnthropicHeaderCase({ @@ -2529,7 +2583,7 @@ describe("applyExtraParamsToAgent", () => { }); it("merges existing anthropic-beta headers with configured betas", () => { - const cfg = buildAnthropicModelConfig("anthropic/claude-sonnet-4-5", { + const cfg = buildModelConfig("anthropic/claude-sonnet-4-5", { context1m: true, anthropicBeta: ["files-api-2025-04-14"], }); @@ -2549,7 +2603,7 @@ describe("applyExtraParamsToAgent", () => { }); it("ignores context1m for non-Opus/Sonnet Anthropic models", () => { - const cfg = buildAnthropicModelConfig("anthropic/claude-haiku-3-5", { context1m: true }); + const cfg = buildModelConfig("anthropic/claude-haiku-3-5", { context1m: true }); const headers = runAnthropicHeaderCase({ cfg, modelId: "claude-haiku-3-5", diff --git a/src/agents/pi-embedded-runner/extra-params.ts b/src/agents/pi-embedded-runner/extra-params.ts index 58484a6403d..d9dae287f2b 100644 --- a/src/agents/pi-embedded-runner/extra-params.ts +++ b/src/agents/pi-embedded-runner/extra-params.ts @@ -10,6 +10,7 @@ import { wrapProviderStreamFn as wrapProviderStreamFnRuntime, } from "../../plugins/provider-hook-runtime.js"; import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js"; +import { legacyModelKey, modelKey } from "../model-selection-normalize.js"; import { supportsGptParallelToolCallsPayload } from "../provider-api-families.js"; import { resolveProviderRequestPolicyConfig } from "../provider-request-config.js"; import { createGoogleThinkingPayloadWrapper } from "./google-stream-wrappers.js"; @@ -71,8 +72,11 @@ export function resolveExtraParams(params: { agentId?: string; }): Record | undefined { const defaultParams = params.cfg?.agents?.defaults?.params ?? undefined; - const modelKey = `${params.provider}/${params.modelId}`; - const modelConfig = params.cfg?.agents?.defaults?.models?.[modelKey]; + const canonicalKey = modelKey(params.provider, params.modelId); + const legacyKey = legacyModelKey(params.provider, params.modelId); + const configuredModels = params.cfg?.agents?.defaults?.models; + const modelConfig = + configuredModels?.[canonicalKey] ?? (legacyKey ? configuredModels?.[legacyKey] : undefined); const globalParams = modelConfig?.params ? { ...modelConfig.params } : undefined; const agentParams = params.agentId && params.cfg?.agents?.list