mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 10:02:04 +00:00
Models/Config: default missing Anthropic model api fields
This commit is contained in:
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { validateConfigObject } from "../config/validation.js";
|
||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||
import {
|
||||
CUSTOM_PROXY_MODELS_CONFIG,
|
||||
@@ -13,6 +14,37 @@ import { ensureOpenClawModelsJson } from "./models-config.js";
|
||||
installModelsConfigTestHooks();
|
||||
|
||||
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 () => {
|
||||
await withTempHome(async () => {
|
||||
const prevKey = process.env.MINIMAX_API_KEY;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { resolveTalkApiKey } from "./talk.js";
|
||||
import type { OpenClawConfig } from "./types.js";
|
||||
@@ -37,6 +37,16 @@ const DEFAULT_MODEL_MAX_TOKENS = 8192;
|
||||
type ModelDefinitionLike = Partial<ModelDefinitionConfig> &
|
||||
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 {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
const providerApi = resolveDefaultProviderApi(providerId, provider.api);
|
||||
let nextProvider = provider;
|
||||
if (providerApi && provider.api !== providerApi) {
|
||||
mutated = true;
|
||||
nextProvider = { ...nextProvider, api: providerApi };
|
||||
}
|
||||
let providerMutated = false;
|
||||
const nextModels = models.map((model) => {
|
||||
const raw = model as ModelDefinitionLike;
|
||||
@@ -220,6 +236,10 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
|
||||
if (raw.maxTokens !== maxTokens) {
|
||||
modelMutated = true;
|
||||
}
|
||||
const api = raw.api ?? providerApi;
|
||||
if (raw.api !== api) {
|
||||
modelMutated = true;
|
||||
}
|
||||
|
||||
if (!modelMutated) {
|
||||
return model;
|
||||
@@ -232,13 +252,17 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
|
||||
cost,
|
||||
contextWindow,
|
||||
maxTokens,
|
||||
api,
|
||||
} as ModelDefinitionConfig;
|
||||
});
|
||||
|
||||
if (!providerMutated) {
|
||||
if (nextProvider !== provider) {
|
||||
nextProviders[providerId] = nextProvider;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
nextProviders[providerId] = { ...provider, models: nextModels };
|
||||
nextProviders[providerId] = { ...nextProvider, models: nextModels };
|
||||
mutated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,4 +104,64 @@ describe("applyModelDefaults", () => {
|
||||
expect(model?.contextWindow).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