mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
fix(models): repair provider-wrapped session overrides
This commit is contained in:
@@ -69,6 +69,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Models/config: explain missing `models.providers.<provider>.models[]` registration when a model exists only in `agents.defaults.models`, instead of returning a bare unknown-model error. Fixes #80089.
|
||||
- MCP/tools: prefix bundle MCP server/tool fragments that would start with digits, keeping generated tool names valid for Moonshot/Kimi and other strict providers. Fixes #79179.
|
||||
- Models/OpenRouter: treat `403 API key budget limit exceeded` as billing so model fallback advances instead of retrying the exhausted primary. Fixes #60191. Thanks @omgitsgela.
|
||||
- Models/OpenRouter: repair stale session overrides that lost the outer `openrouter/` provider wrapper, so sessions return to the configured OpenRouter model instead of failing as an unknown direct-provider model. Fixes #78161. Thanks @hjamal7-bit.
|
||||
- Kimi Code: use Kimi's stable `kimi-for-coding` API model id in bundled catalog, onboarding, and docs while normalizing legacy `kimi-code` and `k2p5` refs. Fixes #79965.
|
||||
- Volcengine/Kimi: strip provider-unsupported tool schema length and item constraint keywords for direct and coding-plan models so hosted Kimi runs do not reject message tools with `minLength`. Fixes #38817.
|
||||
- DeepSeek: backfill V4 `reasoning_content` replay fields for unowned OpenAI-compatible proxy providers, preventing follow-up request failures outside the bundled DeepSeek and OpenRouter routes. Fixes #79608.
|
||||
|
||||
@@ -241,6 +241,7 @@ vi.mock("../sessions/level-overrides.js", () => ({
|
||||
|
||||
vi.mock("../sessions/model-overrides.js", () => ({
|
||||
applyModelOverrideToSessionEntry: () => ({ updated: false }),
|
||||
repairProviderWrappedModelOverride: () => ({ updated: false }),
|
||||
}));
|
||||
|
||||
vi.mock("../sessions/send-policy.js", () => ({
|
||||
|
||||
@@ -24,7 +24,10 @@ import {
|
||||
} from "../routing/session-key.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { applyVerboseOverride } from "../sessions/level-overrides.js";
|
||||
import { applyModelOverrideToSessionEntry } from "../sessions/model-overrides.js";
|
||||
import {
|
||||
applyModelOverrideToSessionEntry,
|
||||
repairProviderWrappedModelOverride,
|
||||
} from "../sessions/model-overrides.js";
|
||||
import { resolveSendPolicy } from "../sessions/send-policy.js";
|
||||
import { createLazyImportLoader } from "../shared/lazy-promise.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
@@ -778,6 +781,19 @@ async function agentCommandInternal(
|
||||
|
||||
if (sessionEntry && sessionStore && sessionKey && hasStoredOverride) {
|
||||
const entry = sessionEntry;
|
||||
const repaired = repairProviderWrappedModelOverride({
|
||||
entry,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
});
|
||||
if (repaired.updated) {
|
||||
await persistSessionEntry({
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
entry,
|
||||
});
|
||||
}
|
||||
const overrideProvider = sessionEntry.providerOverride?.trim() || defaultProvider;
|
||||
const overrideModel = sessionEntry.modelOverride?.trim();
|
||||
if (overrideModel) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { applyModelOverrideToSessionEntry } from "./model-overrides.js";
|
||||
import {
|
||||
applyModelOverrideToSessionEntry,
|
||||
repairProviderWrappedModelOverride,
|
||||
} from "./model-overrides.js";
|
||||
|
||||
function applyOpenAiSelection(entry: SessionEntry) {
|
||||
return applyModelOverrideToSessionEntry({
|
||||
@@ -173,3 +176,57 @@ describe("applyModelOverrideToSessionEntry", () => {
|
||||
expect(withFlagEntry.liveModelSwitchPending).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("repairProviderWrappedModelOverride", () => {
|
||||
it("restores a provider-wrapped override from aligned runtime model fields", () => {
|
||||
const before = Date.now() - 5_000;
|
||||
const entry: SessionEntry = {
|
||||
sessionId: "sess-openrouter-repair-runtime",
|
||||
updatedAt: before,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-haiku-4.5",
|
||||
modelOverrideSource: "user",
|
||||
modelProvider: "openrouter",
|
||||
model: "anthropic/claude-haiku-4.5",
|
||||
contextTokens: 200_000,
|
||||
};
|
||||
|
||||
const result = repairProviderWrappedModelOverride({
|
||||
entry,
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.4",
|
||||
});
|
||||
|
||||
expect(result.updated).toBe(true);
|
||||
expect(entry.providerOverride).toBe("openrouter");
|
||||
expect(entry.modelOverride).toBe("anthropic/claude-haiku-4.5");
|
||||
expect(entry.modelOverrideSource).toBe("user");
|
||||
expect(entry.modelProvider).toBeUndefined();
|
||||
expect(entry.model).toBeUndefined();
|
||||
expect(entry.contextTokens).toBeUndefined();
|
||||
expect((entry.updatedAt ?? 0) > before).toBe(true);
|
||||
});
|
||||
|
||||
it("clears a provider-wrapped override that matches the configured default", () => {
|
||||
const before = Date.now() - 5_000;
|
||||
const entry: SessionEntry = {
|
||||
sessionId: "sess-openrouter-repair-default",
|
||||
updatedAt: before,
|
||||
providerOverride: "anthropic",
|
||||
modelOverride: "claude-haiku-4.5",
|
||||
modelOverrideSource: "user",
|
||||
};
|
||||
|
||||
const result = repairProviderWrappedModelOverride({
|
||||
entry,
|
||||
defaultProvider: "openrouter",
|
||||
defaultModel: "anthropic/claude-haiku-4.5",
|
||||
});
|
||||
|
||||
expect(result.updated).toBe(true);
|
||||
expect(entry.providerOverride).toBeUndefined();
|
||||
expect(entry.modelOverride).toBeUndefined();
|
||||
expect(entry.modelOverrideSource).toBeUndefined();
|
||||
expect((entry.updatedAt ?? 0) > before).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,3 +125,48 @@ export function applyModelOverrideToSessionEntry(params: {
|
||||
|
||||
return { updated };
|
||||
}
|
||||
|
||||
function wrappedOverrideModel(provider: string, model: string): string {
|
||||
return `${provider}/${model}`;
|
||||
}
|
||||
|
||||
export function repairProviderWrappedModelOverride(params: {
|
||||
entry: SessionEntry;
|
||||
defaultProvider: string;
|
||||
defaultModel?: string;
|
||||
}): { updated: boolean } {
|
||||
const overrideProvider = normalizeOptionalString(params.entry.providerOverride);
|
||||
const overrideModel = normalizeOptionalString(params.entry.modelOverride);
|
||||
if (!overrideProvider || !overrideModel) {
|
||||
return { updated: false };
|
||||
}
|
||||
|
||||
const wrappedModel = wrappedOverrideModel(overrideProvider, overrideModel);
|
||||
const runtimeProvider = normalizeOptionalString(params.entry.modelProvider);
|
||||
const runtimeModel = normalizeOptionalString(params.entry.model);
|
||||
if (runtimeProvider && runtimeModel === wrappedModel && runtimeProvider !== overrideProvider) {
|
||||
return applyModelOverrideToSessionEntry({
|
||||
entry: params.entry,
|
||||
selection: {
|
||||
provider: runtimeProvider,
|
||||
model: runtimeModel,
|
||||
isDefault:
|
||||
runtimeProvider === params.defaultProvider && runtimeModel === params.defaultModel,
|
||||
},
|
||||
selectionSource: params.entry.modelOverrideSource === "auto" ? "auto" : "user",
|
||||
});
|
||||
}
|
||||
|
||||
if (params.defaultProvider !== overrideProvider && params.defaultModel === wrappedModel) {
|
||||
return applyModelOverrideToSessionEntry({
|
||||
entry: params.entry,
|
||||
selection: {
|
||||
provider: params.defaultProvider,
|
||||
model: params.defaultModel,
|
||||
isDefault: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { updated: false };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user