diff --git a/extensions/cerebras/onboard.test.ts b/extensions/cerebras/onboard.test.ts new file mode 100644 index 000000000000..fd9a4143ae43 --- /dev/null +++ b/extensions/cerebras/onboard.test.ts @@ -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" }, + }); + }); +}); diff --git a/extensions/cerebras/onboard.ts b/extensions/cerebras/onboard.ts index 23b855c3f83e..ee0009584f35 100644 --- a/extensions/cerebras/onboard.ts +++ b/extensions/cerebras/onboard.ts @@ -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); } diff --git a/extensions/cerebras/openclaw.plugin.json b/extensions/cerebras/openclaw.plugin.json index 2d4fbc4fd1a4..7c9766f73949 100644 --- a/extensions/cerebras/openclaw.plugin.json +++ b/extensions/cerebras/openclaw.plugin.json @@ -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", diff --git a/extensions/chutes/api.ts b/extensions/chutes/api.ts index 0e8c172ddf02..ba9b624e0293 100644 --- a/extensions/chutes/api.ts +++ b/extensions/chutes/api.ts @@ -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"; diff --git a/extensions/chutes/models.test.ts b/extensions/chutes/models.test.ts index a7c6754d8de9..5baaf0e9282a 100644 --- a/extensions/chutes/models.test.ts +++ b/extensions/chutes/models.test.ts @@ -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", diff --git a/extensions/chutes/models.ts b/extensions/chutes/models.ts index d6e2382c1cc5..19228acae20e 100644 --- a/extensions/chutes/models.ts +++ b/extensions/chutes/models.ts @@ -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; diff --git a/extensions/chutes/onboard.ts b/extensions/chutes/onboard.ts index d163e7d3a13d..11bca10d1a72 100644 --- a/extensions/chutes/onboard.ts +++ b/extensions/chutes/onboard.ts @@ -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); } diff --git a/extensions/chutes/openclaw.plugin.json b/extensions/chutes/openclaw.plugin.json index d0b029d930b5..58066a3e8ef9 100644 --- a/extensions/chutes/openclaw.plugin.json +++ b/extensions/chutes/openclaw.plugin.json @@ -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", diff --git a/extensions/cohere/index.test.ts b/extensions/cohere/index.test.ts index 335f35dcfeb6..8c79e54fc84a 100644 --- a/extensions/cohere/index.test.ts +++ b/extensions/cohere/index.test.ts @@ -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"; diff --git a/extensions/cohere/models.ts b/extensions/cohere/models.ts index 5a7aaf73ce30..dbfd420a4ebe 100644 --- a/extensions/cohere/models.ts +++ b/extensions/cohere/models.ts @@ -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"; diff --git a/extensions/cohere/onboard.ts b/extensions/cohere/onboard.ts index 0ca04e0bc109..5f2a7263c6f1 100644 --- a/extensions/cohere/onboard.ts +++ b/extensions/cohere/onboard.ts @@ -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, diff --git a/extensions/cohere/openclaw.plugin.json b/extensions/cohere/openclaw.plugin.json index 795ed023edce..815386c370fc 100644 --- a/extensions/cohere/openclaw.plugin.json +++ b/extensions/cohere/openclaw.plugin.json @@ -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", diff --git a/extensions/deepseek/onboard.test.ts b/extensions/deepseek/onboard.test.ts new file mode 100644 index 000000000000..0f82acb6b3c4 --- /dev/null +++ b/extensions/deepseek/onboard.test.ts @@ -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" }, + }); + }); +}); diff --git a/extensions/deepseek/onboard.ts b/extensions/deepseek/onboard.ts index 12f0bf3a09a7..3e3e363588c6 100644 --- a/extensions/deepseek/onboard.ts +++ b/extensions/deepseek/onboard.ts @@ -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); } diff --git a/extensions/deepseek/openclaw.plugin.json b/extensions/deepseek/openclaw.plugin.json index dad4ab0885e4..9144815e3510 100644 --- a/extensions/deepseek/openclaw.plugin.json +++ b/extensions/deepseek/openclaw.plugin.json @@ -28,6 +28,7 @@ "deepseek": { "baseUrl": "https://api.deepseek.com", "api": "openai-completions", + "defaultModel": "deepseek-v4-flash", "models": [ { "id": "deepseek-v4-flash", diff --git a/extensions/fireworks/index.ts b/extensions/fireworks/index.ts index 29be3f0abcd5..da0c248690ba 100644 --- a/extensions/fireworks/index.ts +++ b/extensions/fireworks/index.ts @@ -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"; diff --git a/extensions/fireworks/onboard.test.ts b/extensions/fireworks/onboard.test.ts new file mode 100644 index 000000000000..9b8eb9b06957 --- /dev/null +++ b/extensions/fireworks/onboard.test.ts @@ -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" }, + }); + }); +}); diff --git a/extensions/fireworks/onboard.ts b/extensions/fireworks/onboard.ts index 8759b6583064..4da9e96bd665 100644 --- a/extensions/fireworks/onboard.ts +++ b/extensions/fireworks/onboard.ts @@ -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) => { diff --git a/extensions/fireworks/openclaw.plugin.json b/extensions/fireworks/openclaw.plugin.json index 7aa8f614b71b..7ff7f04620f8 100644 --- a/extensions/fireworks/openclaw.plugin.json +++ b/extensions/fireworks/openclaw.plugin.json @@ -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", diff --git a/extensions/fireworks/provider-catalog.ts b/extensions/fireworks/provider-catalog.ts index 8ab58c54ee14..8eccc63348ad 100644 --- a/extensions/fireworks/provider-catalog.ts +++ b/extensions/fireworks/provider-catalog.ts @@ -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); diff --git a/extensions/groq/index.test.ts b/extensions/groq/index.test.ts index 986ea389b576..f4dfecf485d6 100644 --- a/extensions/groq/index.test.ts +++ b/extensions/groq/index.test.ts @@ -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", diff --git a/extensions/groq/index.ts b/extensions/groq/index.ts index d846b02daa5c..961c9cddbf0c 100644 --- a/extensions/groq/index.ts +++ b/extensions/groq/index.ts @@ -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; diff --git a/extensions/groq/openclaw.plugin.json b/extensions/groq/openclaw.plugin.json index 31ca0f237376..b804b40bf075 100644 --- a/extensions/groq/openclaw.plugin.json +++ b/extensions/groq/openclaw.plugin.json @@ -48,6 +48,7 @@ "groq": { "baseUrl": "https://api.groq.com/openai/v1", "api": "openai-completions", + "defaultModel": "openai/gpt-oss-120b", "models": [ { "id": "groq/compound", diff --git a/extensions/mistral/api.ts b/extensions/mistral/api.ts index 8b1ac60a2650..7cd8a96d7838 100644 --- a/extensions/mistral/api.ts +++ b/extensions/mistral/api.ts @@ -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"; diff --git a/extensions/mistral/index.ts b/extensions/mistral/index.ts index fc0ed1885b41..0771268017c4 100644 --- a/extensions/mistral/index.ts +++ b/extensions/mistral/index.ts @@ -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"; diff --git a/extensions/mistral/model-definitions.ts b/extensions/mistral/model-definitions.ts index 42f6d0eda21c..a972da82317e 100644 --- a/extensions/mistral/model-definitions.ts +++ b/extensions/mistral/model-definitions.ts @@ -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); diff --git a/extensions/mistral/onboard.test.ts b/extensions/mistral/onboard.test.ts index 71cdf2fc3ecb..03410f42041d 100644 --- a/extensions/mistral/onboard.test.ts +++ b/extensions/mistral/onboard.test.ts @@ -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", () => { diff --git a/extensions/mistral/onboard.ts b/extensions/mistral/onboard.ts index b45e4831470a..72359c9f9d2e 100644 --- a/extensions/mistral/onboard.ts +++ b/extensions/mistral/onboard.ts @@ -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) => ({ diff --git a/extensions/mistral/openclaw.plugin.json b/extensions/mistral/openclaw.plugin.json index 30a6262fd5eb..af45d5fb25fb 100644 --- a/extensions/mistral/openclaw.plugin.json +++ b/extensions/mistral/openclaw.plugin.json @@ -24,6 +24,7 @@ "mistral": { "baseUrl": "https://api.mistral.ai/v1", "api": "openai-completions", + "defaultModel": "mistral-large-latest", "models": [ { "id": "codestral-latest", diff --git a/extensions/tencent/index.ts b/extensions/tencent/index.ts index 23451b366ffe..0d1d42b7f42f 100644 --- a/extensions/tencent/index.ts +++ b/extensions/tencent/index.ts @@ -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"; diff --git a/extensions/tencent/onboard.test.ts b/extensions/tencent/onboard.test.ts new file mode 100644 index 000000000000..a9b14fa15900 --- /dev/null +++ b/extensions/tencent/onboard.test.ts @@ -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)" }, + }); + }); +}); diff --git a/extensions/tencent/onboard.ts b/extensions/tencent/onboard.ts index d241896f6a76..f9f3e5881d87 100644 --- a/extensions/tencent/onboard.ts +++ b/extensions/tencent/onboard.ts @@ -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); } diff --git a/extensions/tencent/openclaw.plugin.json b/extensions/tencent/openclaw.plugin.json index b3249d1a098b..ba4e9b5a0e90 100644 --- a/extensions/tencent/openclaw.plugin.json +++ b/extensions/tencent/openclaw.plugin.json @@ -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", diff --git a/extensions/venice/index.ts b/extensions/venice/index.ts index b0df635e9b37..6577ada4e216 100644 --- a/extensions/venice/index.ts +++ b/extensions/venice/index.ts @@ -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"; diff --git a/extensions/venice/models.ts b/extensions/venice/models.ts index d325c61cef13..4ec92bb6932b 100644 --- a/extensions/venice/models.ts +++ b/extensions/venice/models.ts @@ -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 = { diff --git a/extensions/venice/onboard.test.ts b/extensions/venice/onboard.test.ts new file mode 100644 index 000000000000..8d1df44c287c --- /dev/null +++ b/extensions/venice/onboard.test.ts @@ -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" }, + }); + }); +}); diff --git a/extensions/venice/onboard.ts b/extensions/venice/onboard.ts index 98058035689c..4f43647cf625 100644 --- a/extensions/venice/onboard.ts +++ b/extensions/venice/onboard.ts @@ -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) => ({ diff --git a/extensions/venice/openclaw.plugin.json b/extensions/venice/openclaw.plugin.json index 0cfbd25e983c..3c516bb0ff3f 100644 --- a/extensions/venice/openclaw.plugin.json +++ b/extensions/venice/openclaw.plugin.json @@ -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", diff --git a/extensions/zai/model-definitions.ts b/extensions/zai/model-definitions.ts index e2b6b45af414..e5099483032c 100644 --- a/extensions/zai/model-definitions.ts +++ b/extensions/zai/model-definitions.ts @@ -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; diff --git a/extensions/zai/onboard.test.ts b/extensions/zai/onboard.test.ts index d11fd7943f53..541a55bc9781 100644 --- a/extensions/zai/onboard.test.ts +++ b/extensions/zai/onboard.test.ts @@ -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], diff --git a/extensions/zai/openclaw.plugin.json b/extensions/zai/openclaw.plugin.json index b61d7e3082a0..7b76a3bb5602 100644 --- a/extensions/zai/openclaw.plugin.json +++ b/extensions/zai/openclaw.plugin.json @@ -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", diff --git a/packages/model-catalog-core/src/model-catalog-normalize.test.ts b/packages/model-catalog-core/src/model-catalog-normalize.test.ts index 3267c642f268..cdea7ebe1420 100644 --- a/packages/model-catalog-core/src/model-catalog-normalize.test.ts +++ b/packages/model-catalog-core/src/model-catalog-normalize.test.ts @@ -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: [ { diff --git a/packages/model-catalog-core/src/model-catalog-normalize.ts b/packages/model-catalog-core/src/model-catalog-normalize.ts index a6dbc94a429d..150e956fd93a 100644 --- a/packages/model-catalog-core/src/model-catalog-normalize.ts +++ b/packages/model-catalog-core/src/model-catalog-normalize.ts @@ -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, }; diff --git a/packages/model-catalog-core/src/model-catalog-types.ts b/packages/model-catalog-core/src/model-catalog-types.ts index 741597788a0c..10322e1bb409 100644 --- a/packages/model-catalog-core/src/model-catalog-types.ts +++ b/packages/model-catalog-core/src/model-catalog-types.ts @@ -245,6 +245,8 @@ export type ModelCatalogProvider = { baseUrl?: string; api?: ModelCatalogApi; headers?: Record; + /** Provider-recommended primary model id. */ + defaultModel?: string; /** Provider-recommended small model id for short internal utility tasks. */ defaultUtilityModel?: string; models: ModelCatalogModel[]; diff --git a/packages/model-catalog-core/src/remote-catalog-bundle.test.ts b/packages/model-catalog-core/src/remote-catalog-bundle.test.ts index ba1d3affd84c..98c6110a36ba 100644 --- a/packages/model-catalog-core/src/remote-catalog-bundle.test.ts +++ b/packages/model-catalog-core/src/remote-catalog-bundle.test.ts @@ -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: {} }); diff --git a/packages/model-catalog-core/src/remote-catalog-bundle.ts b/packages/model-catalog-core/src/remote-catalog-bundle.ts index 7d0851ccb037..87a4e776c9b7 100644 --- a/packages/model-catalog-core/src/remote-catalog-bundle.ts +++ b/packages/model-catalog-core/src/remote-catalog-bundle.ts @@ -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), }) diff --git a/src/plugin-sdk/provider-catalog-shared.test.ts b/src/plugin-sdk/provider-catalog-shared.test.ts index a8d0e1943916..72251ffabf17 100644 --- a/src/plugin-sdk/provider-catalog-shared.test.ts +++ b/src/plugin-sdk/provider-catalog-shared.test.ts @@ -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", () => { diff --git a/src/plugin-sdk/provider-catalog-shared.ts b/src/plugin-sdk/provider-catalog-shared.ts index 8c51a9b3b42d..026212d5f616 100644 --- a/src/plugin-sdk/provider-catalog-shared.ts +++ b/src/plugin-sdk/provider-catalog-shared.ts @@ -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 } }) + ?.modelCatalog?.providers?.[providerId]; + const defaultModel = normalizeOptionalString( + (catalog as { defaultModel?: unknown })?.defaultModel, + ); + return defaultModel ? buildModelCatalogRef(providerId, defaultModel) : undefined; +} + function cloneManifestCatalogTieredCost( tier: ModelCatalogTieredCost, ): NonNullable[number] {