mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-01 18:48:16 +00:00
Models/Config: default missing Anthropic model api fields
This commit is contained in:
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.
|
- Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.
|
||||||
- Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize.
|
- Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize.
|
||||||
- Plugins/Hooks: run legacy `before_agent_start` once per agent turn and reuse that result across model-resolve and prompt-build compatibility paths, preventing duplicate hook side effects (for example duplicate external API calls). (#23289) Thanks @ksato8710.
|
- Plugins/Hooks: run legacy `before_agent_start` once per agent turn and reuse that result across model-resolve and prompt-build compatibility paths, preventing duplicate hook side effects (for example duplicate external API calls). (#23289) Thanks @ksato8710.
|
||||||
|
- Models/Config: default missing Anthropic provider/model `api` fields to `anthropic-messages` during config validation so custom relay model entries are preserved instead of being dropped by runtime model registry validation. (#23332) Thanks @bigbigmonkey123.
|
||||||
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
|
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
|
||||||
- Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.
|
- Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.
|
||||||
- Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit `scopes`, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81.
|
- Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit `scopes`, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
|
import { validateConfigObject } from "../config/validation.js";
|
||||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||||
import {
|
import {
|
||||||
CUSTOM_PROXY_MODELS_CONFIG,
|
CUSTOM_PROXY_MODELS_CONFIG,
|
||||||
@@ -13,6 +14,37 @@ import { ensureOpenClawModelsJson } from "./models-config.js";
|
|||||||
installModelsConfigTestHooks();
|
installModelsConfigTestHooks();
|
||||||
|
|
||||||
describe("models-config", () => {
|
describe("models-config", () => {
|
||||||
|
it("keeps anthropic api defaults when model entries omit api", async () => {
|
||||||
|
await withTempHome(async () => {
|
||||||
|
const validated = validateConfigObject({
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
anthropic: {
|
||||||
|
baseUrl: "https://relay.example.com/api",
|
||||||
|
apiKey: "cr_xxxx",
|
||||||
|
models: [{ id: "claude-opus-4-6", name: "Claude Opus 4.6" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(validated.ok).toBe(true);
|
||||||
|
if (!validated.ok) {
|
||||||
|
throw new Error("expected config to validate");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureOpenClawModelsJson(validated.config);
|
||||||
|
|
||||||
|
const modelPath = path.join(resolveOpenClawAgentDir(), "models.json");
|
||||||
|
const raw = await fs.readFile(modelPath, "utf8");
|
||||||
|
const parsed = JSON.parse(raw) as {
|
||||||
|
providers: Record<string, { api?: string; models?: Array<{ id: string; api?: string }> }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(parsed.providers.anthropic?.api).toBe("anthropic-messages");
|
||||||
|
expect(parsed.providers.anthropic?.models?.[0]?.api).toBe("anthropic-messages");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("fills missing provider.apiKey from env var name when models exist", async () => {
|
it("fills missing provider.apiKey from env var name when models exist", async () => {
|
||||||
await withTempHome(async () => {
|
await withTempHome(async () => {
|
||||||
const prevKey = process.env.MINIMAX_API_KEY;
|
const prevKey = process.env.MINIMAX_API_KEY;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
||||||
import { parseModelRef } from "../agents/model-selection.js";
|
import { normalizeProviderId, parseModelRef } from "../agents/model-selection.js";
|
||||||
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
||||||
import { resolveTalkApiKey } from "./talk.js";
|
import { resolveTalkApiKey } from "./talk.js";
|
||||||
import type { OpenClawConfig } from "./types.js";
|
import type { OpenClawConfig } from "./types.js";
|
||||||
@@ -37,6 +37,16 @@ const DEFAULT_MODEL_MAX_TOKENS = 8192;
|
|||||||
type ModelDefinitionLike = Partial<ModelDefinitionConfig> &
|
type ModelDefinitionLike = Partial<ModelDefinitionConfig> &
|
||||||
Pick<ModelDefinitionConfig, "id" | "name">;
|
Pick<ModelDefinitionConfig, "id" | "name">;
|
||||||
|
|
||||||
|
function resolveDefaultProviderApi(
|
||||||
|
providerId: string,
|
||||||
|
providerApi: ModelDefinitionConfig["api"] | undefined,
|
||||||
|
): ModelDefinitionConfig["api"] | undefined {
|
||||||
|
if (providerApi) {
|
||||||
|
return providerApi;
|
||||||
|
}
|
||||||
|
return normalizeProviderId(providerId) === "anthropic" ? "anthropic-messages" : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function isPositiveNumber(value: unknown): value is number {
|
function isPositiveNumber(value: unknown): value is number {
|
||||||
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
||||||
}
|
}
|
||||||
@@ -181,6 +191,12 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
|
|||||||
if (!Array.isArray(models) || models.length === 0) {
|
if (!Array.isArray(models) || models.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const providerApi = resolveDefaultProviderApi(providerId, provider.api);
|
||||||
|
let nextProvider = provider;
|
||||||
|
if (providerApi && provider.api !== providerApi) {
|
||||||
|
mutated = true;
|
||||||
|
nextProvider = { ...nextProvider, api: providerApi };
|
||||||
|
}
|
||||||
let providerMutated = false;
|
let providerMutated = false;
|
||||||
const nextModels = models.map((model) => {
|
const nextModels = models.map((model) => {
|
||||||
const raw = model as ModelDefinitionLike;
|
const raw = model as ModelDefinitionLike;
|
||||||
@@ -220,6 +236,10 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
|
|||||||
if (raw.maxTokens !== maxTokens) {
|
if (raw.maxTokens !== maxTokens) {
|
||||||
modelMutated = true;
|
modelMutated = true;
|
||||||
}
|
}
|
||||||
|
const api = raw.api ?? providerApi;
|
||||||
|
if (raw.api !== api) {
|
||||||
|
modelMutated = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!modelMutated) {
|
if (!modelMutated) {
|
||||||
return model;
|
return model;
|
||||||
@@ -232,13 +252,17 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
|
|||||||
cost,
|
cost,
|
||||||
contextWindow,
|
contextWindow,
|
||||||
maxTokens,
|
maxTokens,
|
||||||
|
api,
|
||||||
} as ModelDefinitionConfig;
|
} as ModelDefinitionConfig;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!providerMutated) {
|
if (!providerMutated) {
|
||||||
|
if (nextProvider !== provider) {
|
||||||
|
nextProviders[providerId] = nextProvider;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
nextProviders[providerId] = { ...provider, models: nextModels };
|
nextProviders[providerId] = { ...nextProvider, models: nextModels };
|
||||||
mutated = true;
|
mutated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,4 +104,64 @@ describe("applyModelDefaults", () => {
|
|||||||
expect(model?.contextWindow).toBe(32768);
|
expect(model?.contextWindow).toBe(32768);
|
||||||
expect(model?.maxTokens).toBe(32768);
|
expect(model?.maxTokens).toBe(32768);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults anthropic provider and model api to anthropic-messages", () => {
|
||||||
|
const cfg = {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
anthropic: {
|
||||||
|
baseUrl: "https://relay.example.com/api",
|
||||||
|
apiKey: "cr_xxxx",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "claude-opus-4-6",
|
||||||
|
name: "Claude Opus 4.6",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 200_000,
|
||||||
|
maxTokens: 8192,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies OpenClawConfig;
|
||||||
|
|
||||||
|
const next = applyModelDefaults(cfg);
|
||||||
|
const provider = next.models?.providers?.anthropic;
|
||||||
|
const model = provider?.models?.[0];
|
||||||
|
|
||||||
|
expect(provider?.api).toBe("anthropic-messages");
|
||||||
|
expect(model?.api).toBe("anthropic-messages");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("propagates provider api to models when model api is missing", () => {
|
||||||
|
const cfg = {
|
||||||
|
models: {
|
||||||
|
providers: {
|
||||||
|
myproxy: {
|
||||||
|
baseUrl: "https://proxy.example/v1",
|
||||||
|
apiKey: "sk-test",
|
||||||
|
api: "openai-completions",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "gpt-5.2",
|
||||||
|
name: "GPT-5.2",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 200_000,
|
||||||
|
maxTokens: 8192,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies OpenClawConfig;
|
||||||
|
|
||||||
|
const next = applyModelDefaults(cfg);
|
||||||
|
const model = next.models?.providers?.myproxy?.models?.[0];
|
||||||
|
expect(model?.api).toBe("openai-completions");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user