mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(models): land #31202 normalize custom provider keys (@stakeswky)
Landed from contributor PR #31202 by @stakeswky. Co-authored-by: stakeswky <stakeswky@users.noreply.github.com>
This commit is contained in:
@@ -112,6 +112,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Models/Custom provider keys: trim custom provider map keys during normalization so image-capable models remain discoverable when provider keys are configured with leading/trailing whitespace. Landed from contributor PR #31202 by @stakeswky. Thanks @stakeswky.
|
||||
- Discord/Agent component interactions: accept Components v2 `cid` payloads alongside legacy `componentId`, and safely decode percent-encoded IDs without throwing on malformed `%` sequences. Landed from contributor PR #29013 by @Jacky1n7. Thanks @Jacky1n7.
|
||||
- Matrix/Directory room IDs: preserve original room-ID casing for direct `!roomId` group lookups (without `:server`) so allowlist checks do not fail on case-sensitive IDs. Landed from contributor PR #31201 by @williamos-dev. Thanks @williamos-dev.
|
||||
- Discord/Inbound media fallback: preserve attachment and sticker metadata when Discord CDN fetch/save fails by keeping URL-based media entries in context, with regression coverage for save failures and mixed success/failure ordering. Landed from contributor PR #28906 by @Sid-Qin. Thanks @Sid-Qin.
|
||||
|
||||
76
src/agents/models-config.providers.normalize-keys.test.ts
Normal file
76
src/agents/models-config.providers.normalize-keys.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { normalizeProviders } from "./models-config.providers.js";
|
||||
|
||||
describe("normalizeProviders", () => {
|
||||
it("trims provider keys so image models remain discoverable for custom providers", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
|
||||
try {
|
||||
const providers: NonNullable<NonNullable<OpenClawConfig["models"]>["providers"]> = {
|
||||
" dashscope-vision ": {
|
||||
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
api: "openai-completions",
|
||||
apiKey: "DASHSCOPE_API_KEY",
|
||||
models: [
|
||||
{
|
||||
id: "qwen-vl-max",
|
||||
name: "Qwen VL Max",
|
||||
input: ["text", "image"],
|
||||
reasoning: false,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 32000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const normalized = normalizeProviders({ providers, agentDir });
|
||||
expect(Object.keys(normalized ?? {})).toEqual(["dashscope-vision"]);
|
||||
expect(normalized?.["dashscope-vision"]?.models?.[0]?.id).toBe("qwen-vl-max");
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the latest provider config when duplicate keys only differ by whitespace", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
|
||||
try {
|
||||
const providers: NonNullable<NonNullable<OpenClawConfig["models"]>["providers"]> = {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
api: "openai-completions",
|
||||
apiKey: "OPENAI_API_KEY",
|
||||
models: [],
|
||||
},
|
||||
" openai ": {
|
||||
baseUrl: "https://example.com/v1",
|
||||
api: "openai-completions",
|
||||
apiKey: "CUSTOM_OPENAI_API_KEY",
|
||||
models: [
|
||||
{
|
||||
id: "gpt-4.1-mini",
|
||||
name: "GPT-4.1 mini",
|
||||
input: ["text"],
|
||||
reasoning: false,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 16384,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const normalized = normalizeProviders({ providers, agentDir });
|
||||
expect(Object.keys(normalized ?? {})).toEqual(["openai"]);
|
||||
expect(normalized?.openai?.baseUrl).toBe("https://example.com/v1");
|
||||
expect(normalized?.openai?.apiKey).toBe("CUSTOM_OPENAI_API_KEY");
|
||||
expect(normalized?.openai?.models?.[0]?.id).toBe("gpt-4.1-mini");
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -496,6 +496,13 @@ export function normalizeProviders(params: {
|
||||
|
||||
for (const [key, provider] of Object.entries(providers)) {
|
||||
const normalizedKey = key.trim();
|
||||
if (!normalizedKey) {
|
||||
mutated = true;
|
||||
continue;
|
||||
}
|
||||
if (normalizedKey !== key) {
|
||||
mutated = true;
|
||||
}
|
||||
let normalizedProvider = provider;
|
||||
const configuredApiKey = normalizedProvider.apiKey;
|
||||
|
||||
@@ -554,7 +561,19 @@ export function normalizeProviders(params: {
|
||||
normalizedProvider = antigravityNormalized;
|
||||
}
|
||||
|
||||
next[key] = normalizedProvider;
|
||||
const existing = next[normalizedKey];
|
||||
if (existing) {
|
||||
// Keep deterministic behavior if users accidentally define duplicate
|
||||
// provider keys that only differ by surrounding whitespace.
|
||||
mutated = true;
|
||||
next[normalizedKey] = {
|
||||
...existing,
|
||||
...normalizedProvider,
|
||||
models: normalizedProvider.models ?? existing.models,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
next[normalizedKey] = normalizedProvider;
|
||||
}
|
||||
|
||||
return mutated ? next : providers;
|
||||
|
||||
Reference in New Issue
Block a user