mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(models): land #38947 from @davidemanuelDEV
Co-authored-by: davidemanuelDEV <davidemanuelDEV@users.noreply.github.com>
This commit is contained in:
@@ -248,6 +248,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Outbound/message target normalization: ignore empty legacy `to`/`channelId` fields when explicit `target` is provided so valid target-based sends no longer fail legacy-param validation; includes regression coverage. (#38944) Thanks @Narcooo.
|
||||
- Models/auth token prompts: guard cancelled manual token prompts so `Symbol(clack:cancel)` values cannot be persisted into auth profiles; adds regression coverage for cancelled `models auth paste-token`. (#38951) Thanks @MumuTW.
|
||||
- Gateway/loopback announce URLs: treat `http://` and `https://` aliases with the same loopback/private-network policy as websocket URLs so loopback cron announce delivery no longer fails secure URL validation. (#39064) Thanks @Narcooo.
|
||||
- Models/default provider fallback: when the hardcoded default provider is removed from `models.providers`, resolve defaults from configured providers instead of reporting stale removed-provider defaults in status output. (#38947) Thanks @davidemanuelDEV.
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
|
||||
@@ -481,6 +481,83 @@ describe("model-selection", () => {
|
||||
});
|
||||
expect(result).toEqual({ provider: "openai", model: "gpt-4" });
|
||||
});
|
||||
|
||||
it("should prefer configured custom provider when default provider is not in models.providers", () => {
|
||||
const cfg: Partial<OpenClawConfig> = {
|
||||
models: {
|
||||
providers: {
|
||||
n1n: {
|
||||
baseUrl: "https://n1n.example.com",
|
||||
models: [
|
||||
{
|
||||
id: "gpt-5.4",
|
||||
name: "GPT 5.4",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveConfiguredModelRef({
|
||||
cfg: cfg as OpenClawConfig,
|
||||
defaultProvider: "anthropic",
|
||||
defaultModel: "claude-opus-4-6",
|
||||
});
|
||||
expect(result).toEqual({ provider: "n1n", model: "gpt-5.4" });
|
||||
});
|
||||
|
||||
it("should keep default provider when it is in models.providers", () => {
|
||||
const cfg: Partial<OpenClawConfig> = {
|
||||
models: {
|
||||
providers: {
|
||||
anthropic: {
|
||||
baseUrl: "https://api.anthropic.com",
|
||||
models: [
|
||||
{
|
||||
id: "claude-opus-4-6",
|
||||
name: "Claude Opus 4.6",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveConfiguredModelRef({
|
||||
cfg: cfg as OpenClawConfig,
|
||||
defaultProvider: "anthropic",
|
||||
defaultModel: "claude-opus-4-6",
|
||||
});
|
||||
expect(result).toEqual({ provider: "anthropic", model: "claude-opus-4-6" });
|
||||
});
|
||||
|
||||
it("should fall back to hardcoded default when no custom providers have models", () => {
|
||||
const cfg: Partial<OpenClawConfig> = {
|
||||
models: {
|
||||
providers: {
|
||||
"empty-provider": {
|
||||
baseUrl: "https://example.com",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = resolveConfiguredModelRef({
|
||||
cfg: cfg as OpenClawConfig,
|
||||
defaultProvider: "anthropic",
|
||||
defaultModel: "claude-opus-4-6",
|
||||
});
|
||||
expect(result).toEqual({ provider: "anthropic", model: "claude-opus-4-6" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveThinkingDefault", () => {
|
||||
|
||||
@@ -317,6 +317,28 @@ export function resolveConfiguredModelRef(params: {
|
||||
return resolved.ref;
|
||||
}
|
||||
}
|
||||
// Before falling back to the hardcoded default, check if the default provider
|
||||
// is actually available. If it isn't but other providers are configured, prefer
|
||||
// the first configured provider's first model to avoid reporting a stale default
|
||||
// from a removed provider. (See #38880)
|
||||
const configuredProviders = params.cfg.models?.providers;
|
||||
if (configuredProviders && typeof configuredProviders === "object") {
|
||||
const hasDefaultProvider = Boolean(configuredProviders[params.defaultProvider]);
|
||||
if (!hasDefaultProvider) {
|
||||
const availableProvider = Object.entries(configuredProviders).find(
|
||||
([, providerCfg]) =>
|
||||
providerCfg &&
|
||||
Array.isArray(providerCfg.models) &&
|
||||
providerCfg.models.length > 0 &&
|
||||
providerCfg.models[0]?.id,
|
||||
);
|
||||
if (availableProvider) {
|
||||
const [providerName, providerCfg] = availableProvider;
|
||||
const firstModel = providerCfg.models[0];
|
||||
return { provider: providerName, model: firstModel.id };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { provider: params.defaultProvider, model: params.defaultModel };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user