refactor(providers): manifest-declared default models and unified onboarding presets (#113794)

* feat(model-catalog): declare provider default models

* refactor(providers): source onboarding defaults from manifests

* fix(cohere): remove unused model id export

* fix(chutes): preserve public default model id

* fix(model-catalog): accept provider default models
This commit is contained in:
Peter Steinberger
2026-07-25 12:54:53 -07:00
committed by GitHub
parent 54df949aaa
commit be6ec97e11
48 changed files with 301 additions and 155 deletions
+20
View File
@@ -0,0 +1,20 @@
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
import { describe, expect, it } from "vitest";
import { applyCerebrasConfig, CEREBRAS_DEFAULT_MODEL_REF } from "./onboard.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
describe("Cerebras onboarding", () => {
it("applies the manifest catalog, default, and alias", () => {
const config = applyCerebrasConfig({});
expect(config.models?.providers?.cerebras?.models.map((model) => model.id)).toEqual(
manifest.modelCatalog.providers.cerebras.models.map((model) => model.id),
);
expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe(
CEREBRAS_DEFAULT_MODEL_REF,
);
expect(config.agents?.defaults?.models).toEqual({
[CEREBRAS_DEFAULT_MODEL_REF]: { alias: "Cerebras GLM 4.7" },
});
});
});
+6 -6
View File
@@ -1,6 +1,4 @@
/**
* Cerebras onboarding config helpers.
*/
import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
createModelCatalogPresetAppliers,
type OpenClawConfig,
@@ -10,9 +8,12 @@ import {
CEREBRAS_BASE_URL,
CEREBRAS_MODEL_CATALOG,
} from "./models.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
/** Default Cerebras model reference used after onboarding. */
export const CEREBRAS_DEFAULT_MODEL_REF = "cerebras/zai-glm-4.7";
export const CEREBRAS_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(
manifest,
"cerebras",
)!;
const cerebrasPresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: CEREBRAS_DEFAULT_MODEL_REF,
@@ -25,7 +26,6 @@ const cerebrasPresetAppliers = createModelCatalogPresetAppliers({
}),
});
/** Applies Cerebras provider/catalog config and default model aliases. */
export function applyCerebrasConfig(cfg: OpenClawConfig): OpenClawConfig {
return cerebrasPresetAppliers.applyConfig(cfg);
}
+1
View File
@@ -23,6 +23,7 @@
"cerebras": {
"baseUrl": "https://api.cerebras.ai/v1",
"api": "openai-completions",
"defaultModel": "zai-glm-4.7",
"models": [
{
"id": "zai-glm-4.7",
+2 -5
View File
@@ -1,11 +1,6 @@
/**
* Public Chutes provider plugin API exports.
*/
export {
buildChutesModelDefinition,
CHUTES_BASE_URL,
CHUTES_DEFAULT_MODEL_ID,
CHUTES_DEFAULT_MODEL_REF,
CHUTES_MODEL_CATALOG,
discoverChutesModels,
} from "./models.js";
@@ -14,4 +9,6 @@ export {
applyChutesApiKeyConfig,
applyChutesConfig,
applyChutesProviderConfig,
CHUTES_DEFAULT_MODEL_ID,
CHUTES_DEFAULT_MODEL_REF,
} from "./onboard.js";
+11 -4
View File
@@ -2,14 +2,13 @@
import { expectDefined } from "@openclaw/normalization-core";
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { CHUTES_DEFAULT_MODEL_ID } from "./api.js";
import {
buildChutesModelDefinition,
CHUTES_DEFAULT_MODEL_ID,
CHUTES_DEFAULT_MODEL_REF,
CHUTES_MODEL_CATALOG,
discoverChutesModels,
} from "./models.js";
import { applyChutesConfig } from "./onboard.js";
import { applyChutesConfig, CHUTES_DEFAULT_MODEL_REF } from "./onboard.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const EXPECTED_STATIC_MODEL_IDS = [
@@ -136,7 +135,8 @@ describe("chutes-models", () => {
const runtimeIds = CHUTES_MODEL_CATALOG.map((model) => model.id);
expect(manifestIds).toEqual(EXPECTED_STATIC_MODEL_IDS);
expect(runtimeIds).toEqual(EXPECTED_STATIC_MODEL_IDS);
expect(CHUTES_DEFAULT_MODEL_ID).toBe("zai-org/GLM-5.2-TEE");
expect(CHUTES_DEFAULT_MODEL_ID).toBe(manifest.modelCatalog.providers.chutes.defaultModel);
expect(manifest.modelCatalog.providers.chutes.defaultModel).toBe("zai-org/GLM-5.2-TEE");
expect(
manifest.modelCatalog.providers.chutes.models
.filter((model) => "status" in model && model.status === "deprecated")
@@ -171,6 +171,13 @@ describe("chutes-models", () => {
expect(cfg.agents?.defaults?.models?.["chutes-vision"]?.alias).toBe(
"chutes/moonshotai/Kimi-K2.6-TEE",
);
expect(Object.keys(cfg.agents?.defaults?.models ?? {}).toSorted()).toEqual(
[
...EXPECTED_STATIC_MODEL_IDS.map((id) => `chutes/${id}`),
"chutes-pro",
"chutes-vision",
].toSorted(),
);
const catalogBackedTargets = [
CHUTES_DEFAULT_MODEL_REF,
"chutes/deepseek-ai/DeepSeek-V3.2-TEE",
-4
View File
@@ -30,10 +30,6 @@ const CHUTES_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
/** Base URL for Chutes OpenAI-compatible inference. */
export const CHUTES_BASE_URL = CHUTES_MANIFEST_PROVIDER.baseUrl;
/** Default Chutes model id used for onboarding. */
export const CHUTES_DEFAULT_MODEL_ID = "zai-org/GLM-5.2-TEE";
/** Default Chutes model ref used for onboarding. */
export const CHUTES_DEFAULT_MODEL_REF = `chutes/${CHUTES_DEFAULT_MODEL_ID}`;
const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000;
const CHUTES_DEFAULT_MAX_TOKENS = 4096;
+14 -22
View File
@@ -1,26 +1,18 @@
/**
* Chutes onboarding config helpers for OAuth and API-key setup.
*/
import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalogPreset,
createModelCatalogPresetAppliers,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
CHUTES_BASE_URL,
CHUTES_DEFAULT_MODEL_REF,
CHUTES_MODEL_CATALOG,
buildChutesModelDefinition,
} from "./models.js";
import { CHUTES_BASE_URL, CHUTES_MODEL_CATALOG, buildChutesModelDefinition } from "./models.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
export { CHUTES_DEFAULT_MODEL_REF };
export const CHUTES_DEFAULT_MODEL_ID = manifest.modelCatalog.providers.chutes.defaultModel;
export const CHUTES_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "chutes")!;
/**
* Apply Chutes provider configuration without changing the default model.
* Registers all catalog models and convenience aliases.
*/
export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyProviderConfigWithModelCatalogPreset(cfg, {
const chutesPresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: CHUTES_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
providerId: "chutes",
api: "openai-completions",
baseUrl: CHUTES_BASE_URL,
@@ -33,12 +25,13 @@ export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
},
{ modelRef: "chutes-pro", alias: "chutes/deepseek-ai/DeepSeek-V3.2-TEE" },
],
});
}),
});
export function applyChutesProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
return chutesPresetAppliers.applyProviderConfig(cfg);
}
/**
* Apply Chutes provider configuration AND set Chutes as the default model.
*/
export function applyChutesConfig(cfg: OpenClawConfig): OpenClawConfig {
const next = applyChutesProviderConfig(cfg);
return {
@@ -60,7 +53,6 @@ export function applyChutesConfig(cfg: OpenClawConfig): OpenClawConfig {
};
}
/** Applies Chutes provider config and sets the default model for API-key auth. */
export function applyChutesApiKeyConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(applyChutesProviderConfig(cfg), CHUTES_DEFAULT_MODEL_REF);
}
+1
View File
@@ -64,6 +64,7 @@
"chutes": {
"baseUrl": "https://llm.chutes.ai/v1",
"api": "openai-completions",
"defaultModel": "zai-org/GLM-5.2-TEE",
"models": [
{
"id": "deepseek-ai/DeepSeek-V3.2-TEE",
+1 -1
View File
@@ -5,10 +5,10 @@ import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-ru
import { buildOpenAICompletionsParams } from "openclaw/plugin-sdk/provider-transport-runtime";
import { describe, expect, it } from "vitest";
import plugin from "./index.js";
import { COHERE_COMMAND_A_PLUS_MODEL_ID } from "./models.js";
import { buildCohereProvider, COHERE_LIVE_MODEL_DISCOVERY } from "./provider-catalog.js";
import { createCohereCompletionsWrapper } from "./stream.js";
const COHERE_COMMAND_A_PLUS_MODEL_ID = "command-a-plus-05-2026";
const COHERE_COMMAND_A_REASONING_MODEL_ID = "command-a-reasoning-08-2025";
const COHERE_COMMAND_A_VISION_MODEL_ID = "command-a-vision-07-2025";
const COHERE_NORTH_MINI_CODE_MODEL_ID = "north-mini-code-1-0";
+1 -1
View File
@@ -10,7 +10,7 @@ const COHERE_MANIFEST_CATALOG = manifest.modelCatalog.providers.cohere;
export const COHERE_BASE_URL = COHERE_MANIFEST_CATALOG.baseUrl;
export const COHERE_MODEL_CATALOG = COHERE_MANIFEST_CATALOG.models;
export const COHERE_COMMAND_A_PLUS_MODEL_ID = "command-a-plus-05-2026";
const COHERE_COMMAND_A_PLUS_MODEL_ID = "command-a-plus-05-2026";
const COHERE_COMMAND_A_REASONING_MODEL_ID = "command-a-reasoning-08-2025";
const COHERE_NORTH_MINI_CODE_MODEL_ID = "north-mini-code-1-0";
+4 -8
View File
@@ -1,16 +1,12 @@
import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
createModelCatalogPresetAppliers,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
buildCohereModelDefinition,
COHERE_BASE_URL,
COHERE_COMMAND_A_PLUS_MODEL_ID,
COHERE_MODEL_CATALOG,
} from "./models.js";
import { buildCohereModelDefinition, COHERE_BASE_URL, COHERE_MODEL_CATALOG } from "./models.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const COHERE_DEFAULT_MODEL_ID = COHERE_COMMAND_A_PLUS_MODEL_ID;
export const COHERE_DEFAULT_MODEL_REF = `cohere/${COHERE_DEFAULT_MODEL_ID}`;
export const COHERE_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "cohere")!;
const coherePresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: COHERE_DEFAULT_MODEL_REF,
+1
View File
@@ -12,6 +12,7 @@
"cohere": {
"baseUrl": "https://api.cohere.ai/compatibility/v1",
"api": "openai-completions",
"defaultModel": "command-a-plus-05-2026",
"models": [
{
"id": "command-a-plus-05-2026",
+20
View File
@@ -0,0 +1,20 @@
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
import { describe, expect, it } from "vitest";
import { applyDeepSeekConfig, DEEPSEEK_DEFAULT_MODEL_REF } from "./onboard.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
describe("DeepSeek onboarding", () => {
it("applies the manifest catalog, default, and alias", () => {
const config = applyDeepSeekConfig({});
expect(config.models?.providers?.deepseek?.models.map((model) => model.id)).toEqual(
manifest.modelCatalog.providers.deepseek.models.map((model) => model.id),
);
expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe(
DEEPSEEK_DEFAULT_MODEL_REF,
);
expect(config.agents?.defaults?.models).toEqual({
[DEEPSEEK_DEFAULT_MODEL_REF]: { alias: "DeepSeek" },
});
});
});
+19 -20
View File
@@ -1,32 +1,31 @@
// Deepseek setup module handles plugin onboarding behavior.
import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
createModelCatalogPresetAppliers,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import { buildDeepSeekModelDefinition, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL_CATALOG } from "./api.js";
import {
buildDeepSeekModelDefinition,
DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL_CATALOG,
} from "./models.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
export const DEEPSEEK_DEFAULT_MODEL_REF = "deepseek/deepseek-v4-flash";
export const DEEPSEEK_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(
manifest,
"deepseek",
)!;
function applyDeepSeekProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[DEEPSEEK_DEFAULT_MODEL_REF] = {
...models[DEEPSEEK_DEFAULT_MODEL_REF],
alias: models[DEEPSEEK_DEFAULT_MODEL_REF]?.alias ?? "DeepSeek",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
const deepSeekPresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: DEEPSEEK_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
providerId: "deepseek",
api: "openai-completions",
baseUrl: DEEPSEEK_BASE_URL,
catalogModels: DEEPSEEK_MODEL_CATALOG.map(buildDeepSeekModelDefinition),
});
}
aliases: [{ modelRef: DEEPSEEK_DEFAULT_MODEL_REF, alias: "DeepSeek" }],
}),
});
export function applyDeepSeekConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyDeepSeekProviderConfig(cfg),
DEEPSEEK_DEFAULT_MODEL_REF,
);
return deepSeekPresetAppliers.applyConfig(cfg);
}
+1
View File
@@ -28,6 +28,7 @@
"deepseek": {
"baseUrl": "https://api.deepseek.com",
"api": "openai-completions",
"defaultModel": "deepseek-v4-flash",
"models": [
{
"id": "deepseek-v4-flash",
+2 -2
View File
@@ -1,4 +1,3 @@
// Fireworks plugin entrypoint registers its OpenClaw integration.
import type { ProviderResolveDynamicModelContext } from "openclaw/plugin-sdk/plugin-entry";
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
@@ -8,13 +7,14 @@ import {
normalizeModelCompat,
} from "openclaw/plugin-sdk/provider-model-shared";
import { isFireworksKimiModelId } from "./model-id.js";
import { applyFireworksConfig, FIREWORKS_DEFAULT_MODEL_REF } from "./onboard.js";
import { applyFireworksConfig } from "./onboard.js";
import {
buildFireworksProvider,
FIREWORKS_BASE_URL,
FIREWORKS_DEFAULT_CONTEXT_WINDOW,
FIREWORKS_DEFAULT_MAX_TOKENS,
FIREWORKS_DEFAULT_MODEL_ID,
FIREWORKS_DEFAULT_MODEL_REF,
isFireworksCatalogModelId,
} from "./provider-catalog.js";
import { wrapFireworksProviderStream } from "./stream.js";
+18
View File
@@ -0,0 +1,18 @@
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
import { describe, expect, it } from "vitest";
import { applyFireworksConfig } from "./onboard.js";
import { FIREWORKS_DEFAULT_MODEL_REF, buildFireworksCatalogModels } from "./provider-catalog.js";
describe("Fireworks onboarding", () => {
it("applies the manifest catalog, default, and alias", () => {
const config = applyFireworksConfig({});
expect(config.models?.providers?.fireworks?.models).toEqual(buildFireworksCatalogModels());
expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe(
FIREWORKS_DEFAULT_MODEL_REF,
);
expect(config.agents?.defaults?.models).toEqual({
[FIREWORKS_DEFAULT_MODEL_REF]: { alias: "Kimi K2.6 Turbo" },
});
});
});
+1 -3
View File
@@ -1,4 +1,3 @@
// Fireworks setup module handles plugin onboarding behavior.
import {
createDefaultModelsPresetAppliers,
type OpenClawConfig,
@@ -7,10 +6,9 @@ import {
buildFireworksCatalogModels,
buildFireworksProvider,
FIREWORKS_DEFAULT_MODEL_ID,
FIREWORKS_DEFAULT_MODEL_REF,
} from "./provider-catalog.js";
export const FIREWORKS_DEFAULT_MODEL_REF = `fireworks/${FIREWORKS_DEFAULT_MODEL_ID}`;
const fireworksPresetAppliers = createDefaultModelsPresetAppliers({
primaryModelRef: FIREWORKS_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => {
@@ -34,6 +34,7 @@
"fireworks": {
"baseUrl": "https://api.fireworks.ai/inference/v1",
"api": "openai-completions",
"defaultModel": "accounts/fireworks/routers/kimi-k2p6-turbo",
"models": [
{
"id": "accounts/fireworks/models/kimi-k2p6",
+9 -3
View File
@@ -1,5 +1,7 @@
// Fireworks provider module implements model/runtime integration.
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
buildManifestModelProviderConfig,
readManifestProviderDefaultModelRef,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import type {
ModelDefinitionConfig,
ModelProviderConfig,
@@ -10,9 +12,13 @@ const FIREWORKS_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
providerId: "fireworks",
catalog: manifest.modelCatalog.providers.fireworks,
});
export const FIREWORKS_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(
manifest,
"fireworks",
)!;
export const FIREWORKS_BASE_URL = FIREWORKS_MANIFEST_PROVIDER.baseUrl;
export const FIREWORKS_DEFAULT_MODEL_ID = "accounts/fireworks/routers/kimi-k2p6-turbo";
export const FIREWORKS_DEFAULT_MODEL_ID = FIREWORKS_DEFAULT_MODEL_REF.slice("fireworks/".length);
function requireFireworksManifestModel(id: string): ModelDefinitionConfig {
const model = FIREWORKS_MANIFEST_PROVIDER.models.find((entry) => entry.id === id);
+2 -1
View File
@@ -8,6 +8,7 @@ import { capturePluginRegistration } from "openclaw/plugin-sdk/plugin-test-runti
import { describe, expect, it } from "vitest";
import { resolveGroqReasoningCompatPatch } from "./api.js";
import plugin from "./index.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
describe("groq provider compat", () => {
it("recovers only matching implicit-budget rejections without changing normal tools", async () => {
@@ -343,10 +344,10 @@ describe("groq provider compat", () => {
});
expect(provider.auth).toHaveLength(1);
expect(provider.auth[0]).toMatchObject({
defaultModel: "groq/openai/gpt-oss-120b",
id: "api-key",
kind: "api_key",
label: "Groq API key",
starterModel: `groq/${manifest.modelCatalog.providers.groq.defaultModel}`,
wizard: {
choiceId: "groq-api-key",
groupId: "groq",
+5 -3
View File
@@ -4,15 +4,17 @@ import {
streamSimple,
type AssistantMessageEvent,
} from "openclaw/plugin-sdk/llm";
// Groq plugin entrypoint registers its OpenClaw integration.
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { buildOpenAICompatibleProviderCatalog } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
buildManifestModelProviderConfig,
readManifestProviderDefaultModelRef,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import { groqMediaUnderstandingProvider } from "./media-understanding-provider.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const GROQ_DEFAULT_MODEL_REF = "groq/openai/gpt-oss-120b";
const GROQ_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "groq")!;
const GROQ_OVERSIZED_RECOVERY_MODEL_ID = "llama-3.3-70b-versatile";
const GROQ_FALLBACK_MAX_TOKENS = 1_024;
+1
View File
@@ -48,6 +48,7 @@
"groq": {
"baseUrl": "https://api.groq.com/openai/v1",
"api": "openai-completions",
"defaultModel": "openai/gpt-oss-120b",
"models": [
{
"id": "groq/compound",
+2 -5
View File
@@ -4,12 +4,9 @@ export {
buildMistralModelDefinition,
MISTRAL_BASE_URL,
MISTRAL_DEFAULT_MODEL_ID,
} from "./model-definitions.js";
export {
applyMistralConfig,
applyMistralProviderConfig,
MISTRAL_DEFAULT_MODEL_REF,
} from "./onboard.js";
} from "./model-definitions.js";
export { applyMistralConfig, applyMistralProviderConfig } from "./onboard.js";
const MISTRAL_MAX_TOKENS_FIELD = "max_tokens";
+2 -2
View File
@@ -1,4 +1,3 @@
// Mistral plugin entrypoint registers its OpenClaw integration.
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import {
applyMistralModelCompat,
@@ -8,7 +7,8 @@ import {
} from "./api.js";
import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
import { mistralMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
import { applyMistralConfig, MISTRAL_DEFAULT_MODEL_REF } from "./onboard.js";
import { MISTRAL_DEFAULT_MODEL_REF } from "./model-definitions.js";
import { applyMistralConfig } from "./onboard.js";
import { buildMistralProvider } from "./provider-catalog.js";
import { buildMistralRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js";
+6 -3
View File
@@ -1,12 +1,15 @@
// Mistral plugin module implements model definitions behavior.
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
buildManifestModelProviderConfig,
readManifestProviderDefaultModelRef,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const MISTRAL_MANIFEST_CATALOG = manifest.modelCatalog.providers.mistral;
export const MISTRAL_BASE_URL = MISTRAL_MANIFEST_CATALOG.baseUrl;
export const MISTRAL_DEFAULT_MODEL_ID = "mistral-large-latest";
export const MISTRAL_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "mistral")!;
export const MISTRAL_DEFAULT_MODEL_ID = MISTRAL_DEFAULT_MODEL_REF.slice("mistral/".length);
export function buildMistralModelDefinition(): ModelDefinitionConfig {
const model = buildMistralCatalogModels().find((entry) => entry.id === MISTRAL_DEFAULT_MODEL_ID);
+2 -5
View File
@@ -5,11 +5,8 @@ import {
} from "openclaw/plugin-sdk/provider-test-contracts";
import { describe, expect, it } from "vitest";
import { buildMistralModelDefinition as buildBundledMistralModelDefinition } from "./model-definitions.js";
import {
applyMistralConfig,
applyMistralProviderConfig,
MISTRAL_DEFAULT_MODEL_REF,
} from "./onboard.js";
import { MISTRAL_DEFAULT_MODEL_REF } from "./model-definitions.js";
import { applyMistralConfig, applyMistralProviderConfig } from "./onboard.js";
describe("mistral onboard", () => {
it("adds Mistral provider with correct settings", () => {
+1 -3
View File
@@ -1,4 +1,3 @@
// Mistral setup module handles plugin onboarding behavior.
import {
createDefaultModelPresetAppliers,
type OpenClawConfig,
@@ -7,10 +6,9 @@ import {
buildMistralModelDefinition,
MISTRAL_BASE_URL,
MISTRAL_DEFAULT_MODEL_ID,
MISTRAL_DEFAULT_MODEL_REF,
} from "./model-definitions.js";
export const MISTRAL_DEFAULT_MODEL_REF = `mistral/${MISTRAL_DEFAULT_MODEL_ID}`;
const mistralPresetAppliers = createDefaultModelPresetAppliers({
primaryModelRef: MISTRAL_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
+1
View File
@@ -24,6 +24,7 @@
"mistral": {
"baseUrl": "https://api.mistral.ai/v1",
"api": "openai-completions",
"defaultModel": "mistral-large-latest",
"models": [
{
"id": "codestral-latest",
+1 -1
View File
@@ -10,8 +10,8 @@ import {
} from "./models.js";
import {
applyTokenHubConfig,
TOKENHUB_DEFAULT_MODEL_REF,
applyTokenPlanConfig,
TOKENHUB_DEFAULT_MODEL_REF,
TOKENPLAN_DEFAULT_MODEL_REF,
} from "./onboard.js";
import { buildTokenHubProvider, buildTokenPlanProvider } from "./provider-catalog.js";
+40
View File
@@ -0,0 +1,40 @@
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
import { describe, expect, it } from "vitest";
import {
applyTokenHubConfig,
applyTokenPlanConfig,
TOKENHUB_DEFAULT_MODEL_REF,
TOKENPLAN_DEFAULT_MODEL_REF,
} from "./onboard.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
describe("Tencent onboarding", () => {
it("applies the TokenHub manifest catalog, default, and aliases", () => {
const config = applyTokenHubConfig({});
expect(config.models?.providers?.["tencent-tokenhub"]?.models.map((model) => model.id)).toEqual(
manifest.modelCatalog.providers["tencent-tokenhub"].models.map((model) => model.id),
);
expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe(
TOKENHUB_DEFAULT_MODEL_REF,
);
expect(config.agents?.defaults?.models).toEqual({
[TOKENHUB_DEFAULT_MODEL_REF]: { alias: "Hy3 (TokenHub)" },
"tencent-tokenhub/hy3-preview": { alias: "Hy3 preview (TokenHub)" },
});
});
it("applies the TokenPlan manifest catalog, default, and alias", () => {
const config = applyTokenPlanConfig({});
expect(
config.models?.providers?.["tencent-tokenplan"]?.models.map((model) => model.id),
).toEqual(manifest.modelCatalog.providers["tencent-tokenplan"].models.map((model) => model.id));
expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe(
TOKENPLAN_DEFAULT_MODEL_REF,
);
expect(config.agents?.defaults?.models).toEqual({
[TOKENPLAN_DEFAULT_MODEL_REF]: { alias: "Hy3 (TokenPlan)" },
});
});
});
+29 -44
View File
@@ -1,7 +1,6 @@
// Tencent setup module handles plugin onboarding behavior.
import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
applyAgentDefaultModelPrimary,
applyProviderConfigWithModelCatalog,
createModelCatalogPresetAppliers,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import {
@@ -13,63 +12,49 @@ import {
TOKENPLAN_BASE_URL,
TOKENPLAN_MODEL_CATALOG,
TOKENPLAN_PROVIDER_ID,
} from "./api.js";
} from "./models.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
// ---------- TokenHub ----------
export const TOKENHUB_DEFAULT_MODEL_REF = `${TOKENHUB_PROVIDER_ID}/hy3`;
const TOKENHUB_PREVIEW_MODEL_REF = `${TOKENHUB_PROVIDER_ID}/hy3-preview`;
export const TOKENHUB_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(
manifest,
TOKENHUB_PROVIDER_ID,
)!;
function applyTokenHubProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[TOKENHUB_DEFAULT_MODEL_REF] = {
...models[TOKENHUB_DEFAULT_MODEL_REF],
alias: models[TOKENHUB_DEFAULT_MODEL_REF]?.alias ?? "Hy3 (TokenHub)",
};
models[TOKENHUB_PREVIEW_MODEL_REF] = {
...models[TOKENHUB_PREVIEW_MODEL_REF],
alias: models[TOKENHUB_PREVIEW_MODEL_REF]?.alias ?? "Hy3 preview (TokenHub)",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
const tokenHubPresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: TOKENHUB_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
providerId: TOKENHUB_PROVIDER_ID,
api: "openai-completions",
baseUrl: TOKENHUB_BASE_URL,
catalogModels: TOKENHUB_MODEL_CATALOG.map(buildTokenHubModelDefinition),
});
}
aliases: [
{ modelRef: TOKENHUB_DEFAULT_MODEL_REF, alias: "Hy3 (TokenHub)" },
{ modelRef: TOKENHUB_PREVIEW_MODEL_REF, alias: "Hy3 preview (TokenHub)" },
],
}),
});
export function applyTokenHubConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyTokenHubProviderConfig(cfg),
TOKENHUB_DEFAULT_MODEL_REF,
);
return tokenHubPresetAppliers.applyConfig(cfg);
}
// ---------- TokenPlan ----------
export const TOKENPLAN_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(
manifest,
TOKENPLAN_PROVIDER_ID,
)!;
export const TOKENPLAN_DEFAULT_MODEL_REF = `${TOKENPLAN_PROVIDER_ID}/hy3`;
function applyTokenPlanProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
const models = { ...cfg.agents?.defaults?.models };
models[TOKENPLAN_DEFAULT_MODEL_REF] = {
...models[TOKENPLAN_DEFAULT_MODEL_REF],
alias: models[TOKENPLAN_DEFAULT_MODEL_REF]?.alias ?? "Hy3 (TokenPlan)",
};
return applyProviderConfigWithModelCatalog(cfg, {
agentModels: models,
const tokenPlanPresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: TOKENPLAN_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
providerId: TOKENPLAN_PROVIDER_ID,
api: "openai-completions",
baseUrl: TOKENPLAN_BASE_URL,
catalogModels: TOKENPLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition),
});
}
aliases: [{ modelRef: TOKENPLAN_DEFAULT_MODEL_REF, alias: "Hy3 (TokenPlan)" }],
}),
});
export function applyTokenPlanConfig(cfg: OpenClawConfig): OpenClawConfig {
return applyAgentDefaultModelPrimary(
applyTokenPlanProviderConfig(cfg),
TOKENPLAN_DEFAULT_MODEL_REF,
);
return tokenPlanPresetAppliers.applyConfig(cfg);
}
+2
View File
@@ -11,6 +11,7 @@
"tencent-tokenhub": {
"baseUrl": "https://tokenhub.tencentmaas.com/v1",
"api": "openai-completions",
"defaultModel": "hy3",
"models": [
{
"id": "hy3-preview",
@@ -80,6 +81,7 @@
"tencent-tokenplan": {
"baseUrl": "https://api.lkeap.cloud.tencent.com/plan/v3",
"api": "openai-completions",
"defaultModel": "hy3",
"models": [
{
"id": "hy3",
+2 -1
View File
@@ -5,7 +5,8 @@ import {
type ModelCompatConfig,
} from "openclaw/plugin-sdk/provider-model-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import { applyVeniceConfig, VENICE_DEFAULT_MODEL_REF } from "./onboard.js";
import { VENICE_DEFAULT_MODEL_REF } from "./models.js";
import { applyVeniceConfig } from "./onboard.js";
import { buildVeniceProvider } from "./provider-catalog.js";
import { createVeniceDeepSeekV4Wrapper } from "./stream.js";
import { fetchVeniceUsage } from "./usage.js";
+5 -4
View File
@@ -1,9 +1,11 @@
// Venice plugin module implements models behavior.
import {
getCachedLiveProviderModelRows,
LiveModelCatalogHttpError,
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
buildManifestModelProviderConfig,
readManifestProviderDefaultModelRef,
} from "openclaw/plugin-sdk/provider-catalog-shared";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
import { createSubsystemLogger, retryAsync } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -17,8 +19,7 @@ const VENICE_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
});
export const VENICE_BASE_URL = VENICE_MANIFEST_PROVIDER.baseUrl;
const VENICE_DEFAULT_MODEL_ID = "kimi-k2-5";
export const VENICE_DEFAULT_MODEL_REF = `venice/${VENICE_DEFAULT_MODEL_ID}`;
export const VENICE_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "venice")!;
const VENICE_ALLOWED_HOSTNAMES = ["api.venice.ai"];
const VENICE_DEFAULT_COST = {
+21
View File
@@ -0,0 +1,21 @@
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
import { describe, expect, it } from "vitest";
import { VENICE_DEFAULT_MODEL_REF } from "./models.js";
import { applyVeniceConfig } from "./onboard.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };
describe("Venice onboarding", () => {
it("applies the manifest catalog, default, and alias", () => {
const config = applyVeniceConfig({});
expect(config.models?.providers?.venice?.models.map((model) => model.id)).toEqual(
manifest.modelCatalog.providers.venice.models.map((model) => model.id),
);
expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe(
VENICE_DEFAULT_MODEL_REF,
);
expect(config.agents?.defaults?.models).toEqual({
[VENICE_DEFAULT_MODEL_REF]: { alias: "Kimi K2.5" },
});
});
});
-2
View File
@@ -10,8 +10,6 @@ import {
VENICE_MODEL_CATALOG,
} from "./api.js";
export { VENICE_DEFAULT_MODEL_REF };
const venicePresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: VENICE_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
+1
View File
@@ -43,6 +43,7 @@
"venice": {
"baseUrl": "https://api.venice.ai/api/v1",
"api": "openai-completions",
"defaultModel": "kimi-k2-5",
"models": [
{
"id": "zai-org-glm-5-2",
+1 -2
View File
@@ -1,4 +1,3 @@
// Zai plugin module implements model definitions behavior.
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
import manifest from "./openclaw.plugin.json" with { type: "json" };
@@ -7,7 +6,7 @@ export const ZAI_CODING_GLOBAL_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
export const ZAI_CODING_CN_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
export const ZAI_GLOBAL_BASE_URL = "https://api.z.ai/api/paas/v4";
export const ZAI_CN_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
export const ZAI_DEFAULT_MODEL_ID = "glm-5.1";
export const ZAI_DEFAULT_MODEL_ID = manifest.modelCatalog.providers.zai.defaultModel;
export const ZAI_CODING_DEFAULT_MODEL_ID = "glm-5.2";
const ZAI_MANIFEST_CATALOG = manifest.modelCatalog.providers.zai;
+7
View File
@@ -40,6 +40,13 @@ describe("zai onboard", () => {
).not.toHaveProperty("baseUrl");
});
it("uses the manifest default and alias for a fresh general endpoint setup", () => {
expect(resolveAgentModelPrimaryValue(defaultCfg.agents?.defaults?.model)).toBe("zai/glm-5.1");
expect(defaultCfg.agents?.defaults?.models).toEqual({
"zai/glm-5.1": { alias: "GLM" },
});
});
it("resolves GLM-5.2 through the selected Coding Plan or custom endpoint", async () => {
for (const [name, cfg, expectedBaseUrl] of [
["coding-cn", applyZaiConfig({}, { endpoint: "coding-cn" }), ZAI_CODING_CN_BASE_URL],
+1
View File
@@ -32,6 +32,7 @@
"zai": {
"baseUrl": "https://api.z.ai/api/paas/v4",
"api": "openai-completions",
"defaultModel": "glm-5.1",
"models": [
{
"id": "glm-5.2",
@@ -14,6 +14,7 @@ describe("model catalog normalization", () => {
headers: {
"x-provider": "openai",
},
defaultModel: " gpt-5.4 ",
defaultUtilityModel: " gpt-5.6-luna ",
models: [
{
@@ -130,6 +131,7 @@ describe("model catalog normalization", () => {
headers: {
"x-provider": "openai",
},
defaultModel: "gpt-5.4",
defaultUtilityModel: "gpt-5.6-luna",
models: [
{
@@ -537,11 +537,13 @@ function normalizeModelCatalogProvider(value: unknown): ModelCatalogProvider | u
const baseUrl = normalizeOptionalString(value.baseUrl) ?? "";
const api = normalizeModelCatalogApi(value.api);
const headers = normalizeStringMap(value.headers);
const defaultModel = normalizeOptionalString(value.defaultModel) ?? "";
const defaultUtilityModel = normalizeOptionalString(value.defaultUtilityModel) ?? "";
return {
...(baseUrl ? { baseUrl } : {}),
...(api ? { api } : {}),
...(headers ? { headers } : {}),
...(defaultModel ? { defaultModel } : {}),
...(defaultUtilityModel ? { defaultUtilityModel } : {}),
models,
};
@@ -245,6 +245,8 @@ export type ModelCatalogProvider = {
baseUrl?: string;
api?: ModelCatalogApi;
headers?: Record<string, string>;
/** Provider-recommended primary model id. */
defaultModel?: string;
/** Provider-recommended small model id for short internal utility tasks. */
defaultUtilityModel?: string;
models: ModelCatalogModel[];
@@ -13,6 +13,8 @@ const validBundle = {
anthropic: {
baseUrl: "https://evil.test",
headers: { Authorization: "bad" },
defaultModel: "claude-test",
defaultUtilityModel: "claude-test",
models: [
{
id: "claude-test",
@@ -35,6 +37,10 @@ describe("remote model catalog bundle", () => {
}
expect(anthropic).not.toHaveProperty("baseUrl");
expect(anthropic).not.toHaveProperty("headers");
expect(anthropic).toMatchObject({
defaultModel: "claude-test",
defaultUtilityModel: "claude-test",
});
expect(anthropic.models[0]).not.toHaveProperty("baseUrl");
expect(anthropic.models[0]).not.toHaveProperty("headers");
expect(anthropic.models[0]?.compat).toEqual({ nested: {} });
@@ -57,6 +57,7 @@ export const remoteModelCatalogProviderSchema = z
baseUrl: z.string().optional(),
api: z.enum(MODEL_CATALOG_APIS).optional(),
headers: stringMapSchema.optional(),
defaultModel: z.string().optional(),
defaultUtilityModel: z.string().optional(),
models: z.array(modelSchema).min(1),
})
@@ -7,6 +7,7 @@ import {
clearLiveCatalogCacheForTests,
getCachedLiveCatalogValue,
readConfiguredProviderCatalogEntries,
readManifestProviderDefaultModelRef,
supportsNativeStreamingUsageCompat,
} from "./provider-catalog-shared.js";
import type { ModelDefinitionConfig } from "./provider-model-shared.js";
@@ -298,6 +299,7 @@ describe("provider-catalog-shared manifest provider configs", () => {
const catalog: ModelCatalogProvider = {
baseUrl: "https://api.example.test/v1",
api: "openai-completions",
defaultModel: " example-model ",
headers: { "x-provider": "example" },
models: [
{
@@ -368,6 +370,12 @@ describe("provider-catalog-shared manifest provider configs", () => {
},
],
});
expect(
readManifestProviderDefaultModelRef(
{ modelCatalog: { providers: { example: catalog } } },
"example",
),
).toBe("example/example-model");
});
it("normalizes retired nested Gemini ids before emitting manifest provider config", () => {
+15
View File
@@ -1,6 +1,7 @@
// Provider catalog helpers normalize, hash, and expose model catalogs for provider plugins.
import { createHash } from "node:crypto";
import { normalizeModelCatalog } from "@openclaw/model-catalog-core/model-catalog-normalize";
import { buildModelCatalogRef } from "@openclaw/model-catalog-core/model-catalog-refs";
import type {
ModelCatalogCost,
ModelCatalogMediaInputConfig,
@@ -12,6 +13,7 @@ import {
isFutureDateTimestampMs,
resolveExpiresAtMsFromDurationMs,
} from "../../packages/normalization-core/src/number-coercion.js";
import { normalizeOptionalString } from "../../packages/normalization-core/src/string-coerce.js";
import { normalizeConfiguredProviderCatalogModelId } from "../agents/model-ref-shared.js";
import { resolveProviderRequestCapabilities } from "../agents/provider-attribution.js";
import type { ModelDefinitionConfig } from "../config/types.models.js";
@@ -125,6 +127,19 @@ function countRawManifestCatalogModels(catalog: unknown): number | undefined {
return Array.isArray(models) ? models.length : undefined;
}
/** Reads a provider's normalized manifest default as a fully qualified model ref. */
export function readManifestProviderDefaultModelRef(
manifest: unknown,
providerId: string,
): string | undefined {
const catalog = (manifest as { modelCatalog?: { providers?: Record<string, unknown> } })
?.modelCatalog?.providers?.[providerId];
const defaultModel = normalizeOptionalString(
(catalog as { defaultModel?: unknown })?.defaultModel,
);
return defaultModel ? buildModelCatalogRef(providerId, defaultModel) : undefined;
}
function cloneManifestCatalogTieredCost(
tier: ModelCatalogTieredCost,
): NonNullable<ModelDefinitionConfig["cost"]["tieredPricing"]>[number] {