diff --git a/extensions/baseten/api.ts b/extensions/baseten/api.ts index 0327a4cc3c64..9270c2210c10 100644 --- a/extensions/baseten/api.ts +++ b/extensions/baseten/api.ts @@ -5,7 +5,6 @@ export { BASETEN_DEFAULT_MODEL_REF, BASETEN_MODEL_CATALOG, buildBasetenModelCompat, - buildBasetenModelDefinition, buildStaticBasetenModels, discoverBasetenModels, projectBasetenLiveModels, diff --git a/extensions/baseten/models.ts b/extensions/baseten/models.ts index 61319c3b5546..1a7654c27e08 100644 --- a/extensions/baseten/models.ts +++ b/extensions/baseten/models.ts @@ -5,7 +5,10 @@ import { getCachedLiveProviderModelRows, type LiveModelCatalogFetchGuard, } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { + buildManifestModelDefinition, + readManifestProviderDefaultModelRef, +} from "openclaw/plugin-sdk/provider-catalog-shared"; import type { ModelCompatConfig, ModelDefinitionConfig, @@ -49,9 +52,9 @@ const BASE_COMPAT: ModelCompatConfig = { /** Base URL for Baseten's OpenAI-compatible Model APIs. */ export const BASETEN_BASE_URL = BASETEN_MANIFEST_CATALOG.baseUrl; /** Default Baseten model id used for onboarding. */ -export const BASETEN_DEFAULT_MODEL_ID = "thinkingmachines/inkling"; +export const BASETEN_DEFAULT_MODEL_ID = BASETEN_MANIFEST_CATALOG.defaultModel; /** Default Baseten model ref used for onboarding. */ -export const BASETEN_DEFAULT_MODEL_REF = `baseten/${BASETEN_DEFAULT_MODEL_ID}`; +export const BASETEN_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "baseten")!; /** Bundled fallback rows for all Baseten Model APIs available at release time. */ export const BASETEN_MODEL_CATALOG = BASETEN_MANIFEST_CATALOG.models; @@ -110,27 +113,18 @@ export function buildBasetenModelCompat(modelId: string): ModelCompatConfig { }; } -/** Builds one normalized Baseten model definition from a manifest entry. */ -export function buildBasetenModelDefinition( - model: (typeof BASETEN_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - const provider = buildManifestModelProviderConfig({ - providerId: "baseten", - catalog: { ...BASETEN_MANIFEST_CATALOG, models: [model] }, - }); - const normalized = provider.models[0]; - if (!normalized) { - throw new Error(`Missing normalized Baseten model ${model.id}`); - } - return { - ...normalized, - compat: buildBasetenModelCompat(normalized.id), - }; -} - /** Builds the network-free fallback catalog. */ export function buildStaticBasetenModels(): ModelDefinitionConfig[] { - return BASETEN_MODEL_CATALOG.map(buildBasetenModelDefinition); + return BASETEN_MODEL_CATALOG.map( + buildManifestModelDefinition({ + providerId: "baseten", + catalog: BASETEN_MANIFEST_CATALOG, + decorate: (normalized) => ({ + ...normalized, + compat: buildBasetenModelCompat(normalized.id), + }), + }), + ); } type BasetenLiveModelRow = { diff --git a/extensions/baseten/openclaw.plugin.json b/extensions/baseten/openclaw.plugin.json index a9be576d81e5..9d1c2b62244d 100644 --- a/extensions/baseten/openclaw.plugin.json +++ b/extensions/baseten/openclaw.plugin.json @@ -24,6 +24,7 @@ "baseten": { "baseUrl": "https://inference.baseten.co/v1", "api": "openai-completions", + "defaultModel": "thinkingmachines/inkling", "models": [ { "id": "deepseek-ai/DeepSeek-V4-Pro", diff --git a/extensions/byteplus/api.ts b/extensions/byteplus/api.ts index d0444988e456..89def689c125 100644 --- a/extensions/byteplus/api.ts +++ b/extensions/byteplus/api.ts @@ -3,7 +3,6 @@ */ export { buildBytePlusCodingProvider, buildBytePlusProvider } from "./provider-catalog.js"; export { - buildBytePlusModelDefinition, BYTEPLUS_BASE_URL, BYTEPLUS_CODING_BASE_URL, BYTEPLUS_CODING_MODEL_CATALOG, diff --git a/extensions/byteplus/index.test.ts b/extensions/byteplus/index.test.ts index df2f7882026e..8fb7fc8a8f2c 100644 --- a/extensions/byteplus/index.test.ts +++ b/extensions/byteplus/index.test.ts @@ -10,6 +10,7 @@ import { BYTEPLUS_CODING_MODEL_CATALOG, BYTEPLUS_MODEL_CATALOG } from "./models. describe("byteplus plugin", () => { it("augments the catalog with bundled standard and plan models", async () => { const provider = await registerSingleProviderPlugin(plugin); + expect(provider.auth?.[0]?.starterModel).toBe("byteplus-plan/ark-code-latest"); const standardModel = expectDefined(BYTEPLUS_MODEL_CATALOG[0], "BytePlus standard model"); const codingModel = expectDefined(BYTEPLUS_CODING_MODEL_CATALOG[0], "BytePlus coding model"); const entries = await provider.augmentModelCatalog?.({ diff --git a/extensions/byteplus/index.ts b/extensions/byteplus/index.ts index 58c3d08405bf..9d34b4b43aab 100644 --- a/extensions/byteplus/index.ts +++ b/extensions/byteplus/index.ts @@ -4,12 +4,14 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { buildOpenAICompatibleLiveModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; +import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared"; import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard"; +import manifest from "./openclaw.plugin.json" with { type: "json" }; import { BYTEPLUS_PROVIDER_CATALOG_ENTRIES } from "./provider-catalog.js"; import { buildBytePlusVideoGenerationProvider } from "./video-generation-provider.js"; const PROVIDER_ID = "byteplus"; -const BYTEPLUS_DEFAULT_MODEL_REF = "byteplus-plan/ark-code-latest"; +const BYTEPLUS_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "byteplus-plan")!; export default definePluginEntry({ id: PROVIDER_ID, diff --git a/extensions/byteplus/models.ts b/extensions/byteplus/models.ts index d380d42c6d70..a5012ea0b214 100644 --- a/extensions/byteplus/models.ts +++ b/extensions/byteplus/models.ts @@ -25,12 +25,3 @@ export const BYTEPLUS_MODEL_CATALOG: ModelDefinitionConfig[] = BYTEPLUS_MANIFEST /** BytePlus coding/planning model catalog entries. */ export const BYTEPLUS_CODING_MODEL_CATALOG: ModelDefinitionConfig[] = BYTEPLUS_CODING_MANIFEST_PROVIDER.models; - -/** Clones one manifest model definition so callers can mutate safely. */ -export function buildBytePlusModelDefinition(entry: ModelDefinitionConfig): ModelDefinitionConfig { - return { - ...entry, - input: [...entry.input], - cost: { ...entry.cost }, - }; -} diff --git a/extensions/byteplus/openclaw.plugin.json b/extensions/byteplus/openclaw.plugin.json index 95e7530003f1..c71e1f1707c2 100644 --- a/extensions/byteplus/openclaw.plugin.json +++ b/extensions/byteplus/openclaw.plugin.json @@ -114,6 +114,7 @@ "byteplus-plan": { "baseUrl": "https://ark.ap-southeast.bytepluses.com/api/coding/v3", "api": "openai-completions", + "defaultModel": "ark-code-latest", "models": [ { "id": "ark-code-latest", diff --git a/extensions/cerebras/api.ts b/extensions/cerebras/api.ts index e53a96f762ce..216114314280 100644 --- a/extensions/cerebras/api.ts +++ b/extensions/cerebras/api.ts @@ -1,10 +1,6 @@ /** * Public Cerebras provider plugin API exports. */ -export { - buildCerebrasModelDefinition, - CEREBRAS_BASE_URL, - CEREBRAS_MODEL_CATALOG, -} from "./models.js"; +export { buildCerebrasCatalogModels, CEREBRAS_BASE_URL, CEREBRAS_MODEL_CATALOG } from "./models.js"; export { buildCerebrasProvider } from "./provider-catalog.js"; export { applyCerebrasConfig, CEREBRAS_DEFAULT_MODEL_REF } from "./onboard.js"; diff --git a/extensions/cerebras/models.ts b/extensions/cerebras/models.ts index 2784304058b8..28fb3cca2e08 100644 --- a/extensions/cerebras/models.ts +++ b/extensions/cerebras/models.ts @@ -1,8 +1,7 @@ /** * Cerebras model catalog helpers derived from the plugin manifest. */ -import { expectDefined } from "openclaw/plugin-sdk/expect-runtime"; -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } 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" }; @@ -15,19 +14,7 @@ export const CEREBRAS_MODEL_CATALOG = CEREBRAS_MANIFEST_CATALOG.models; /** Builds normalized Cerebras catalog model definitions. */ export function buildCerebrasCatalogModels(): ModelDefinitionConfig[] { - return buildManifestModelProviderConfig({ - providerId: "cerebras", - catalog: CEREBRAS_MANIFEST_CATALOG, - }).models; -} - -/** Builds one normalized Cerebras model definition from a manifest entry. */ -export function buildCerebrasModelDefinition( - model: (typeof CEREBRAS_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - const providerConfig = buildManifestModelProviderConfig({ - providerId: "cerebras", - catalog: { ...CEREBRAS_MANIFEST_CATALOG, models: [model] }, - }); - return expectDefined(providerConfig.models.at(0), "normalized Cerebras manifest model"); + return CEREBRAS_MODEL_CATALOG.map( + buildManifestModelDefinition({ providerId: "cerebras", catalog: CEREBRAS_MANIFEST_CATALOG }), + ); } diff --git a/extensions/cerebras/onboard.ts b/extensions/cerebras/onboard.ts index 978528ecd675..01a17cdf7e7b 100644 --- a/extensions/cerebras/onboard.ts +++ b/extensions/cerebras/onboard.ts @@ -3,11 +3,7 @@ import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { - buildCerebrasModelDefinition, - CEREBRAS_BASE_URL, - CEREBRAS_MODEL_CATALOG, -} from "./models.js"; +import { buildCerebrasCatalogModels, CEREBRAS_BASE_URL } from "./models.js"; import manifest from "./openclaw.plugin.json" with { type: "json" }; export const CEREBRAS_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef( @@ -21,7 +17,7 @@ const cerebrasPresetAppliers = createModelCatalogPresetAppliers({ providerId: "cerebras", api: "openai-completions", baseUrl: CEREBRAS_BASE_URL, - catalogModels: CEREBRAS_MODEL_CATALOG.map(buildCerebrasModelDefinition), + catalogModels: buildCerebrasCatalogModels(), aliases: [{ modelRef: CEREBRAS_DEFAULT_MODEL_REF, alias: "Cerebras GPT OSS 120B" }], }), }); diff --git a/extensions/chutes/api.ts b/extensions/chutes/api.ts index ba9b624e0293..33fb3e6eb1fb 100644 --- a/extensions/chutes/api.ts +++ b/extensions/chutes/api.ts @@ -1,9 +1,4 @@ -export { - buildChutesModelDefinition, - CHUTES_BASE_URL, - CHUTES_MODEL_CATALOG, - discoverChutesModels, -} from "./models.js"; +export { CHUTES_BASE_URL, CHUTES_MODEL_CATALOG, discoverChutesModels } from "./models.js"; export { buildChutesProvider } from "./provider-catalog.js"; export { applyChutesApiKeyConfig, diff --git a/extensions/chutes/models.test.ts b/extensions/chutes/models.test.ts index 5baaf0e9282a..2c4d86d92276 100644 --- a/extensions/chutes/models.test.ts +++ b/extensions/chutes/models.test.ts @@ -3,11 +3,7 @@ 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_MODEL_CATALOG, - discoverChutesModels, -} from "./models.js"; +import { CHUTES_MODEL_CATALOG, discoverChutesModels } from "./models.js"; import { applyChutesConfig, CHUTES_DEFAULT_MODEL_REF } from "./onboard.js"; import manifest from "./openclaw.plugin.json" with { type: "json" }; @@ -102,9 +98,9 @@ describe("chutes-models", () => { clearLiveCatalogCacheForTests(); }); - it("buildChutesModelDefinition returns config with required fields", () => { + it("builds static definitions with required fields", () => { const entry = expectDefined(CHUTES_MODEL_CATALOG[0], "first Chutes catalog model"); - const def = buildChutesModelDefinition(entry); + const def = entry; expect(def.id).toBe(entry.id); expect(def.name).toBe(entry.name); expect(def.reasoning).toBe(entry.reasoning); @@ -126,7 +122,7 @@ describe("chutes-models", () => { if (!model) { throw new Error(`expected ${id}`); } - expect(buildChutesModelDefinition(model).input).toContain("image"); + expect(model.input).toContain("image"); } }); diff --git a/extensions/chutes/models.ts b/extensions/chutes/models.ts index 19228acae20e..7b8ebf66f233 100644 --- a/extensions/chutes/models.ts +++ b/extensions/chutes/models.ts @@ -6,7 +6,7 @@ import { getCachedLiveProviderModelRows, LiveModelCatalogHttpError, } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } from "openclaw/plugin-sdk/provider-catalog-shared"; import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env"; import { @@ -23,22 +23,15 @@ import manifest from "./openclaw.plugin.json" with { type: "json" }; const log = createSubsystemLogger("chutes-models"); -const CHUTES_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: "chutes", - catalog: manifest.modelCatalog.providers.chutes, -}); +const CHUTES_MANIFEST_CATALOG = manifest.modelCatalog.providers.chutes; /** Base URL for Chutes OpenAI-compatible inference. */ -export const CHUTES_BASE_URL = CHUTES_MANIFEST_PROVIDER.baseUrl; +export const CHUTES_BASE_URL = CHUTES_MANIFEST_CATALOG.baseUrl; const CHUTES_DEFAULT_CONTEXT_WINDOW = 128000; const CHUTES_DEFAULT_MAX_TOKENS = 4096; -/** Bundled fallback Chutes model catalog, normalized from the plugin manifest. */ -export const CHUTES_MODEL_CATALOG = CHUTES_MANIFEST_PROVIDER.models; - -/** Adds Chutes provider compat metadata to one model catalog entry. */ -export function buildChutesModelDefinition(model: ModelDefinitionConfig): ModelDefinitionConfig { +function decorateChutesModelDefinition(model: ModelDefinitionConfig): ModelDefinitionConfig { return { ...model, compat: { @@ -48,6 +41,15 @@ export function buildChutesModelDefinition(model: ModelDefinitionConfig): ModelD }; } +/** Bundled fallback Chutes model catalog, normalized from the plugin manifest. */ +export const CHUTES_MODEL_CATALOG: ModelDefinitionConfig[] = CHUTES_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: "chutes", + catalog: CHUTES_MANIFEST_CATALOG, + decorate: decorateChutesModelDefinition, + }), +); + interface ChutesModelEntry { id: string; name?: string; @@ -87,10 +89,10 @@ export async function discoverChutesModels(accessToken?: string): Promise CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition); + const staticCatalog = () => structuredClone(CHUTES_MODEL_CATALOG); try { const data = await fetchChutesModelRows(trimmedKey || undefined); diff --git a/extensions/chutes/onboard.ts b/extensions/chutes/onboard.ts index 11bca10d1a72..2cb539446664 100644 --- a/extensions/chutes/onboard.ts +++ b/extensions/chutes/onboard.ts @@ -4,7 +4,7 @@ import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { CHUTES_BASE_URL, CHUTES_MODEL_CATALOG, buildChutesModelDefinition } from "./models.js"; +import { CHUTES_BASE_URL, CHUTES_MODEL_CATALOG } from "./models.js"; import manifest from "./openclaw.plugin.json" with { type: "json" }; export const CHUTES_DEFAULT_MODEL_ID = manifest.modelCatalog.providers.chutes.defaultModel; @@ -16,7 +16,7 @@ const chutesPresetAppliers = createModelCatalogPresetAppliers({ providerId: "chutes", api: "openai-completions", baseUrl: CHUTES_BASE_URL, - catalogModels: CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition), + catalogModels: structuredClone(CHUTES_MODEL_CATALOG), aliases: [ ...CHUTES_MODEL_CATALOG.map((model) => `chutes/${model.id}`), { diff --git a/extensions/chutes/provider-catalog.ts b/extensions/chutes/provider-catalog.ts index 5ad33d89d78e..d491d91b9ec9 100644 --- a/extensions/chutes/provider-catalog.ts +++ b/extensions/chutes/provider-catalog.ts @@ -2,19 +2,14 @@ * Chutes provider builders for static and dynamically discovered catalogs. */ import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; -import { - CHUTES_BASE_URL, - CHUTES_MODEL_CATALOG, - buildChutesModelDefinition, - discoverChutesModels, -} from "./models.js"; +import { CHUTES_BASE_URL, CHUTES_MODEL_CATALOG, discoverChutesModels } from "./models.js"; /** Builds the static Chutes provider catalog from bundled model metadata. */ export function buildStaticChutesProvider(): ModelProviderConfig { return { baseUrl: CHUTES_BASE_URL, api: "openai-completions", - models: CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition), + models: structuredClone(CHUTES_MODEL_CATALOG), }; } @@ -28,6 +23,6 @@ export async function buildChutesProvider(accessToken?: string): Promise 0 ? models : CHUTES_MODEL_CATALOG.map(buildChutesModelDefinition), + models: models.length > 0 ? models : structuredClone(CHUTES_MODEL_CATALOG), }; } diff --git a/extensions/cohere/models.ts b/extensions/cohere/models.ts index dbfd420a4ebe..30e539153535 100644 --- a/extensions/cohere/models.ts +++ b/extensions/cohere/models.ts @@ -1,15 +1,14 @@ /** * Cohere model catalog helpers derived from the plugin manifest. */ -import { expectDefined } from "openclaw/plugin-sdk/expect-runtime"; -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } 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 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; +const COHERE_MODEL_CATALOG = COHERE_MANIFEST_CATALOG.models; 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"; @@ -26,18 +25,7 @@ export function isModernCohereModelId(modelId: string): boolean { } export function buildCohereCatalogModels(): ModelDefinitionConfig[] { - return buildManifestModelProviderConfig({ - providerId: "cohere", - catalog: COHERE_MANIFEST_CATALOG, - }).models; -} - -export function buildCohereModelDefinition( - model: (typeof COHERE_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - const providerConfig = buildManifestModelProviderConfig({ - providerId: "cohere", - catalog: { ...COHERE_MANIFEST_CATALOG, models: [model] }, - }); - return expectDefined(providerConfig.models.at(0), "normalized Cohere manifest model"); + return COHERE_MODEL_CATALOG.map( + buildManifestModelDefinition({ providerId: "cohere", catalog: COHERE_MANIFEST_CATALOG }), + ); } diff --git a/extensions/cohere/onboard.test.ts b/extensions/cohere/onboard.test.ts index 0f7883abcdee..28413c2143e3 100644 --- a/extensions/cohere/onboard.test.ts +++ b/extensions/cohere/onboard.test.ts @@ -1,8 +1,9 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard"; import { describe, expect, it } from "vitest"; -import { buildCohereCatalogModels, COHERE_BASE_URL, COHERE_MODEL_CATALOG } from "./models.js"; +import { buildCohereCatalogModels, COHERE_BASE_URL } from "./models.js"; import { applyCohereConfig, COHERE_DEFAULT_MODEL_REF } from "./onboard.js"; +import manifest from "./openclaw.plugin.json" with { type: "json" }; const COHERE_DEFAULT_MODEL_ID = "command-a-plus-05-2026"; const COHERE_COMMAND_A_REASONING_MODEL_ID = "command-a-reasoning-08-2025"; @@ -25,7 +26,9 @@ describe("Cohere onboarding", () => { COHERE_COMMAND_A_VISION_MODEL_ID, COHERE_NORTH_MINI_CODE_MODEL_ID, ]); - expect(buildCohereCatalogModels()).toHaveLength(COHERE_MODEL_CATALOG.length); + expect(buildCohereCatalogModels()).toHaveLength( + manifest.modelCatalog.providers.cohere.models.length, + ); }); it("sets Cohere only when there is no primary model", () => { diff --git a/extensions/cohere/onboard.ts b/extensions/cohere/onboard.ts index 5f2a7263c6f1..5936b894d0dc 100644 --- a/extensions/cohere/onboard.ts +++ b/extensions/cohere/onboard.ts @@ -3,7 +3,7 @@ import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { buildCohereModelDefinition, COHERE_BASE_URL, COHERE_MODEL_CATALOG } from "./models.js"; +import { buildCohereCatalogModels, COHERE_BASE_URL } from "./models.js"; import manifest from "./openclaw.plugin.json" with { type: "json" }; export const COHERE_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "cohere")!; @@ -14,7 +14,7 @@ const coherePresetAppliers = createModelCatalogPresetAppliers({ providerId: "cohere", api: "openai-completions", baseUrl: COHERE_BASE_URL, - catalogModels: COHERE_MODEL_CATALOG.map(buildCohereModelDefinition), + catalogModels: buildCohereCatalogModels(), aliases: [{ modelRef: COHERE_DEFAULT_MODEL_REF, alias: "Cohere Command A+" }], }), }); diff --git a/extensions/deepseek/api.ts b/extensions/deepseek/api.ts index 6154b8f3e7b7..fe99cff4b54d 100644 --- a/extensions/deepseek/api.ts +++ b/extensions/deepseek/api.ts @@ -1,8 +1,4 @@ // Deepseek API module exposes the plugin public contract. -export { - buildDeepSeekModelDefinition, - DEEPSEEK_BASE_URL, - DEEPSEEK_MODEL_CATALOG, -} from "./models.js"; +export { DEEPSEEK_BASE_URL, DEEPSEEK_MODEL_CATALOG } from "./models.js"; export { buildDeepSeekProvider } from "./provider-catalog.js"; export { createDeepSeekV4ThinkingWrapper } from "./stream.js"; diff --git a/extensions/deepseek/models.ts b/extensions/deepseek/models.ts index ed987fa71048..1c5831b21604 100644 --- a/extensions/deepseek/models.ts +++ b/extensions/deepseek/models.ts @@ -1,25 +1,18 @@ // Deepseek plugin module implements models behavior. -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } 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 DEEPSEEK_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: "deepseek", - catalog: manifest.modelCatalog.providers.deepseek, -}); +const DEEPSEEK_MANIFEST_CATALOG = manifest.modelCatalog.providers.deepseek; +export const DEEPSEEK_BASE_URL = DEEPSEEK_MANIFEST_CATALOG.baseUrl; -export const DEEPSEEK_BASE_URL = DEEPSEEK_MANIFEST_PROVIDER.baseUrl; - -export const DEEPSEEK_MODEL_CATALOG: ModelDefinitionConfig[] = DEEPSEEK_MANIFEST_PROVIDER.models; - -export function buildDeepSeekModelDefinition( - model: (typeof DEEPSEEK_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - return { - ...model, - api: "openai-completions", - }; -} +export const DEEPSEEK_MODEL_CATALOG: ModelDefinitionConfig[] = DEEPSEEK_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: "deepseek", + catalog: DEEPSEEK_MANIFEST_CATALOG, + decorate: (model) => ({ ...model, api: "openai-completions" }), + }), +); const DEEPSEEK_V4_MODEL_IDS = new Set(["deepseek-v4-flash", "deepseek-v4-pro"]); diff --git a/extensions/deepseek/onboard.ts b/extensions/deepseek/onboard.ts index 3e3e363588c6..eebfd356c0d7 100644 --- a/extensions/deepseek/onboard.ts +++ b/extensions/deepseek/onboard.ts @@ -3,11 +3,7 @@ import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { - buildDeepSeekModelDefinition, - DEEPSEEK_BASE_URL, - DEEPSEEK_MODEL_CATALOG, -} from "./models.js"; +import { DEEPSEEK_BASE_URL, DEEPSEEK_MODEL_CATALOG } from "./models.js"; import manifest from "./openclaw.plugin.json" with { type: "json" }; export const DEEPSEEK_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef( @@ -21,7 +17,7 @@ const deepSeekPresetAppliers = createModelCatalogPresetAppliers({ providerId: "deepseek", api: "openai-completions", baseUrl: DEEPSEEK_BASE_URL, - catalogModels: DEEPSEEK_MODEL_CATALOG.map(buildDeepSeekModelDefinition), + catalogModels: structuredClone(DEEPSEEK_MODEL_CATALOG), aliases: [{ modelRef: DEEPSEEK_DEFAULT_MODEL_REF, alias: "DeepSeek" }], }), }); diff --git a/extensions/deepseek/provider-catalog.ts b/extensions/deepseek/provider-catalog.ts index afdff7887fbc..9d4f1d9258a2 100644 --- a/extensions/deepseek/provider-catalog.ts +++ b/extensions/deepseek/provider-catalog.ts @@ -1,15 +1,11 @@ // Deepseek provider module implements model/runtime integration. import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; -import { - buildDeepSeekModelDefinition, - DEEPSEEK_BASE_URL, - DEEPSEEK_MODEL_CATALOG, -} from "./models.js"; +import { DEEPSEEK_BASE_URL, DEEPSEEK_MODEL_CATALOG } from "./models.js"; export function buildDeepSeekProvider(): ModelProviderConfig { return { baseUrl: DEEPSEEK_BASE_URL, api: "openai-completions", - models: DEEPSEEK_MODEL_CATALOG.map(buildDeepSeekModelDefinition), + models: structuredClone(DEEPSEEK_MODEL_CATALOG), }; } diff --git a/extensions/gmi/models.ts b/extensions/gmi/models.ts index a2c86eaf0434..6069498e4302 100644 --- a/extensions/gmi/models.ts +++ b/extensions/gmi/models.ts @@ -1,20 +1,18 @@ // Gmi plugin module implements models behavior. -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { + buildManifestModelDefinition, + 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 GMI_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: "gmi", - catalog: manifest.modelCatalog.providers.gmi, -}); - -export const GMI_BASE_URL = GMI_MANIFEST_PROVIDER.baseUrl; -export const GMI_MODEL_CATALOG: ModelDefinitionConfig[] = GMI_MANIFEST_PROVIDER.models; -export const GMI_DEFAULT_MODEL_REF = "gmi/openai/gpt-5.6-sol"; - -export function buildGmiModelDefinition(model: ModelDefinitionConfig): ModelDefinitionConfig { - return { - ...model, - api: "openai-completions", - }; -} +const GMI_MANIFEST_CATALOG = manifest.modelCatalog.providers.gmi; +export const GMI_BASE_URL = GMI_MANIFEST_CATALOG.baseUrl; +export const GMI_MODEL_CATALOG: ModelDefinitionConfig[] = GMI_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: "gmi", + catalog: GMI_MANIFEST_CATALOG, + decorate: (model) => ({ ...model, api: "openai-completions" }), + }), +); +export const GMI_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "gmi")!; diff --git a/extensions/gmi/openclaw.plugin.json b/extensions/gmi/openclaw.plugin.json index ccb813f11274..679b67666cbd 100644 --- a/extensions/gmi/openclaw.plugin.json +++ b/extensions/gmi/openclaw.plugin.json @@ -73,6 +73,7 @@ "gmi": { "baseUrl": "https://api.gmi-serving.com/v1", "api": "openai-completions", + "defaultModel": "openai/gpt-5.6-sol", "models": [ { "id": "zai-org/GLM-5.2-FP8", diff --git a/extensions/gmi/provider-catalog.ts b/extensions/gmi/provider-catalog.ts index f2f7492291db..267a8af6445b 100644 --- a/extensions/gmi/provider-catalog.ts +++ b/extensions/gmi/provider-catalog.ts @@ -1,11 +1,11 @@ // Gmi provider module implements model/runtime integration. import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; -import { GMI_BASE_URL, GMI_MODEL_CATALOG, buildGmiModelDefinition } from "./models.js"; +import { GMI_BASE_URL, GMI_MODEL_CATALOG } from "./models.js"; export function buildGmiProvider(): ModelProviderConfig { return { baseUrl: GMI_BASE_URL, api: "openai-completions", - models: GMI_MODEL_CATALOG.map(buildGmiModelDefinition), + models: structuredClone(GMI_MODEL_CATALOG), }; } diff --git a/extensions/meta/api.ts b/extensions/meta/api.ts index 99a639a53b9f..853f4cbafbe4 100644 --- a/extensions/meta/api.ts +++ b/extensions/meta/api.ts @@ -1,11 +1,6 @@ /** * Public Meta provider plugin API exports. */ -export { - buildMetaCatalogModels, - buildMetaModelDefinition, - META_BASE_URL, - META_MODEL_CATALOG, -} from "./models.js"; +export { buildMetaCatalogModels, META_BASE_URL, META_MODEL_CATALOG } from "./models.js"; export { buildMetaProvider } from "./provider-catalog.js"; export { applyMetaConfig, META_DEFAULT_MODEL_REF } from "./onboard.js"; diff --git a/extensions/meta/index.test.ts b/extensions/meta/index.test.ts index bcd6da778e1d..1dbb65f9d74a 100644 --- a/extensions/meta/index.test.ts +++ b/extensions/meta/index.test.ts @@ -30,6 +30,7 @@ describe("meta provider", () => { id: "api-key", kind: "api_key", label: "Meta API key", + starterModel: "meta/muse-spark-1.1", }); }); diff --git a/extensions/meta/models.ts b/extensions/meta/models.ts index f4981183b534..8a7cd9e14618 100644 --- a/extensions/meta/models.ts +++ b/extensions/meta/models.ts @@ -1,8 +1,7 @@ /** * Meta model catalog helpers derived from the plugin manifest. */ -import { expectDefined } from "openclaw/plugin-sdk/expect-runtime"; -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } 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" }; @@ -15,19 +14,7 @@ export const META_MODEL_CATALOG = META_MANIFEST_CATALOG.models; /** Builds normalized Meta catalog model definitions. */ export function buildMetaCatalogModels(): ModelDefinitionConfig[] { - return buildManifestModelProviderConfig({ - providerId: "meta", - catalog: META_MANIFEST_CATALOG, - }).models; -} - -/** Builds one normalized Meta model definition from a manifest entry. */ -export function buildMetaModelDefinition( - model: (typeof META_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - const providerConfig = buildManifestModelProviderConfig({ - providerId: "meta", - catalog: { ...META_MANIFEST_CATALOG, models: [model] }, - }); - return expectDefined(providerConfig.models.at(0), "normalized Meta manifest model"); + return META_MODEL_CATALOG.map( + buildManifestModelDefinition({ providerId: "meta", catalog: META_MANIFEST_CATALOG }), + ); } diff --git a/extensions/meta/onboard.ts b/extensions/meta/onboard.ts index 78184fed149d..ac9b96586611 100644 --- a/extensions/meta/onboard.ts +++ b/extensions/meta/onboard.ts @@ -1,14 +1,16 @@ /** * Meta onboarding config helpers. */ +import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared"; import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { buildMetaModelDefinition, META_BASE_URL, META_MODEL_CATALOG } from "./models.js"; +import { buildMetaCatalogModels, META_BASE_URL } from "./models.js"; +import manifest from "./openclaw.plugin.json" with { type: "json" }; /** Default Meta model reference used after onboarding. */ -export const META_DEFAULT_MODEL_REF = "meta/muse-spark-1.1"; +export const META_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "meta")!; const metaPresetAppliers = createModelCatalogPresetAppliers({ primaryModelRef: META_DEFAULT_MODEL_REF, @@ -16,7 +18,7 @@ const metaPresetAppliers = createModelCatalogPresetAppliers({ providerId: "meta", api: "openai-responses", baseUrl: META_BASE_URL, - catalogModels: META_MODEL_CATALOG.map(buildMetaModelDefinition), + catalogModels: buildMetaCatalogModels(), aliases: [{ modelRef: META_DEFAULT_MODEL_REF, alias: "Muse Spark 1.1" }], }), }); diff --git a/extensions/meta/openclaw.plugin.json b/extensions/meta/openclaw.plugin.json index 437039f24ef3..bd4c7a18dd34 100644 --- a/extensions/meta/openclaw.plugin.json +++ b/extensions/meta/openclaw.plugin.json @@ -27,6 +27,7 @@ "meta": { "baseUrl": "https://api.meta.ai/v1", "api": "openai-responses", + "defaultModel": "muse-spark-1.1", "models": [ { "id": "muse-spark-1.1", diff --git a/extensions/novita/models.ts b/extensions/novita/models.ts index 91de111c774c..48810282cf4b 100644 --- a/extensions/novita/models.ts +++ b/extensions/novita/models.ts @@ -1,20 +1,19 @@ // Novita plugin module implements models behavior. -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { + buildManifestModelDefinition, + 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 NOVITA_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: "novita", - catalog: manifest.modelCatalog.providers.novita, -}); +const NOVITA_MANIFEST_CATALOG = manifest.modelCatalog.providers.novita; -export const NOVITA_BASE_URL = NOVITA_MANIFEST_PROVIDER.baseUrl; -export const NOVITA_MODEL_CATALOG: ModelDefinitionConfig[] = NOVITA_MANIFEST_PROVIDER.models; -export const NOVITA_DEFAULT_MODEL_REF = "novita/deepseek/deepseek-v4-pro"; - -export function buildNovitaModelDefinition(model: ModelDefinitionConfig): ModelDefinitionConfig { - return { - ...model, - api: "openai-completions", - }; -} +export const NOVITA_BASE_URL = NOVITA_MANIFEST_CATALOG.baseUrl; +export const NOVITA_MODEL_CATALOG: ModelDefinitionConfig[] = NOVITA_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: "novita", + catalog: NOVITA_MANIFEST_CATALOG, + decorate: (model) => ({ ...model, api: "openai-completions" }), + }), +); +export const NOVITA_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "novita")!; diff --git a/extensions/novita/openclaw.plugin.json b/extensions/novita/openclaw.plugin.json index 4347c60a0d5d..ee0fcc0386c3 100644 --- a/extensions/novita/openclaw.plugin.json +++ b/extensions/novita/openclaw.plugin.json @@ -71,6 +71,7 @@ "novita": { "baseUrl": "https://api.novita.ai/openai/v1", "api": "openai-completions", + "defaultModel": "deepseek/deepseek-v4-pro", "models": [ { "id": "moonshotai/kimi-k3", diff --git a/extensions/novita/provider-catalog.ts b/extensions/novita/provider-catalog.ts index 2f1997efc710..010e664b7724 100644 --- a/extensions/novita/provider-catalog.ts +++ b/extensions/novita/provider-catalog.ts @@ -1,11 +1,11 @@ // Novita provider module implements model/runtime integration. import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; -import { NOVITA_BASE_URL, NOVITA_MODEL_CATALOG, buildNovitaModelDefinition } from "./models.js"; +import { NOVITA_BASE_URL, NOVITA_MODEL_CATALOG } from "./models.js"; export function buildNovitaProvider(): ModelProviderConfig { return { baseUrl: NOVITA_BASE_URL, api: "openai-completions", - models: NOVITA_MODEL_CATALOG.map(buildNovitaModelDefinition), + models: structuredClone(NOVITA_MODEL_CATALOG), }; } diff --git a/extensions/tencent/api.ts b/extensions/tencent/api.ts index 36711a352298..8bad2d998bb4 100644 --- a/extensions/tencent/api.ts +++ b/extensions/tencent/api.ts @@ -1,7 +1,5 @@ // Tencent API module exposes the plugin public contract. export { - buildTokenHubModelDefinition, - buildTokenPlanModelDefinition, TOKENHUB_BASE_URL, TOKENHUB_MODEL_CATALOG, TOKENHUB_PROVIDER_ID, diff --git a/extensions/tencent/models.ts b/extensions/tencent/models.ts index b94b59dfd46a..0e737d0bc02c 100644 --- a/extensions/tencent/models.ts +++ b/extensions/tencent/models.ts @@ -1,5 +1,5 @@ // Tencent plugin module implements models behavior. -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } 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,42 +7,29 @@ import manifest from "./openclaw.plugin.json" with { type: "json" }; export const TOKENHUB_PROVIDER_ID = "tencent-tokenhub"; -const TOKENHUB_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: TOKENHUB_PROVIDER_ID, - catalog: manifest.modelCatalog.providers[TOKENHUB_PROVIDER_ID], -}); +export const TOKENHUB_BASE_URL = manifest.modelCatalog.providers[TOKENHUB_PROVIDER_ID].baseUrl; -export const TOKENHUB_BASE_URL = TOKENHUB_MANIFEST_PROVIDER.baseUrl; - -export const TOKENHUB_MODEL_CATALOG: ModelDefinitionConfig[] = TOKENHUB_MANIFEST_PROVIDER.models; - -export function buildTokenHubModelDefinition( - model: (typeof TOKENHUB_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - return { - ...model, - api: "openai-completions", - }; -} +const TOKENHUB_MANIFEST_CATALOG = manifest.modelCatalog.providers[TOKENHUB_PROVIDER_ID]; +export const TOKENHUB_MODEL_CATALOG: ModelDefinitionConfig[] = TOKENHUB_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: TOKENHUB_PROVIDER_ID, + catalog: TOKENHUB_MANIFEST_CATALOG, + decorate: (model) => ({ ...model, api: "openai-completions" }), + }), +); // ---------- TokenPlan provider ---------- export const TOKENPLAN_PROVIDER_ID = "tencent-tokenplan"; -const TOKENPLAN_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: TOKENPLAN_PROVIDER_ID, - catalog: manifest.modelCatalog.providers[TOKENPLAN_PROVIDER_ID], -}); +export const TOKENPLAN_BASE_URL = manifest.modelCatalog.providers[TOKENPLAN_PROVIDER_ID].baseUrl; -export const TOKENPLAN_BASE_URL = TOKENPLAN_MANIFEST_PROVIDER.baseUrl; - -export const TOKENPLAN_MODEL_CATALOG: ModelDefinitionConfig[] = TOKENPLAN_MANIFEST_PROVIDER.models; - -export function buildTokenPlanModelDefinition( - model: (typeof TOKENPLAN_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - return { - ...model, - api: "openai-completions", - }; -} +const TOKENPLAN_MANIFEST_CATALOG = manifest.modelCatalog.providers[TOKENPLAN_PROVIDER_ID]; +export const TOKENPLAN_MODEL_CATALOG: ModelDefinitionConfig[] = + TOKENPLAN_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: TOKENPLAN_PROVIDER_ID, + catalog: TOKENPLAN_MANIFEST_CATALOG, + decorate: (model) => ({ ...model, api: "openai-completions" }), + }), + ); diff --git a/extensions/tencent/onboard.ts b/extensions/tencent/onboard.ts index f9f3e5881d87..517c202629b1 100644 --- a/extensions/tencent/onboard.ts +++ b/extensions/tencent/onboard.ts @@ -4,8 +4,6 @@ import { type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; import { - buildTokenHubModelDefinition, - buildTokenPlanModelDefinition, TOKENHUB_BASE_URL, TOKENHUB_MODEL_CATALOG, TOKENHUB_PROVIDER_ID, @@ -27,7 +25,7 @@ const tokenHubPresetAppliers = createModelCatalogPresetAppliers({ providerId: TOKENHUB_PROVIDER_ID, api: "openai-completions", baseUrl: TOKENHUB_BASE_URL, - catalogModels: TOKENHUB_MODEL_CATALOG.map(buildTokenHubModelDefinition), + catalogModels: structuredClone(TOKENHUB_MODEL_CATALOG), aliases: [ { modelRef: TOKENHUB_DEFAULT_MODEL_REF, alias: "Hy3 (TokenHub)" }, { modelRef: TOKENHUB_PREVIEW_MODEL_REF, alias: "Hy3 preview (TokenHub)" }, @@ -50,7 +48,7 @@ const tokenPlanPresetAppliers = createModelCatalogPresetAppliers({ providerId: TOKENPLAN_PROVIDER_ID, api: "openai-completions", baseUrl: TOKENPLAN_BASE_URL, - catalogModels: TOKENPLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition), + catalogModels: structuredClone(TOKENPLAN_MODEL_CATALOG), aliases: [{ modelRef: TOKENPLAN_DEFAULT_MODEL_REF, alias: "Hy3 (TokenPlan)" }], }), }); diff --git a/extensions/tencent/provider-catalog.ts b/extensions/tencent/provider-catalog.ts index afc1a02baa04..9396887e4c02 100644 --- a/extensions/tencent/provider-catalog.ts +++ b/extensions/tencent/provider-catalog.ts @@ -1,8 +1,6 @@ // Tencent provider module implements model/runtime integration. import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; import { - buildTokenHubModelDefinition, - buildTokenPlanModelDefinition, TOKENHUB_BASE_URL, TOKENHUB_MODEL_CATALOG, TOKENPLAN_BASE_URL, @@ -13,7 +11,7 @@ export function buildTokenHubProvider(): ModelProviderConfig { return { baseUrl: TOKENHUB_BASE_URL, api: "openai-completions", - models: TOKENHUB_MODEL_CATALOG.map(buildTokenHubModelDefinition), + models: structuredClone(TOKENHUB_MODEL_CATALOG), }; } @@ -21,6 +19,6 @@ export function buildTokenPlanProvider(): ModelProviderConfig { return { baseUrl: TOKENPLAN_BASE_URL, api: "openai-completions", - models: TOKENPLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition), + models: structuredClone(TOKENPLAN_MODEL_CATALOG), }; } diff --git a/extensions/together/api.ts b/extensions/together/api.ts index 547bb02d0c83..81822296ae8e 100644 --- a/extensions/together/api.ts +++ b/extensions/together/api.ts @@ -1,8 +1,4 @@ // Together API module exposes the plugin public contract. -export { - buildTogetherModelDefinition, - TOGETHER_BASE_URL, - TOGETHER_MODEL_CATALOG, -} from "./models.js"; +export { TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./models.js"; export { buildTogetherProvider } from "./provider-catalog.js"; export { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js"; diff --git a/extensions/together/models.ts b/extensions/together/models.ts index c9c014b6f019..b8c934a461f9 100644 --- a/extensions/together/models.ts +++ b/extensions/together/models.ts @@ -1,24 +1,15 @@ // Together plugin module implements models behavior. -import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { buildManifestModelDefinition } 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 TOGETHER_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: "together", - catalog: manifest.modelCatalog.providers.together, -}); +const TOGETHER_MANIFEST_CATALOG = manifest.modelCatalog.providers.together; +export const TOGETHER_BASE_URL = TOGETHER_MANIFEST_CATALOG.baseUrl; -export const TOGETHER_BASE_URL = TOGETHER_MANIFEST_PROVIDER.baseUrl; - -export const TOGETHER_MODEL_CATALOG: ModelDefinitionConfig[] = TOGETHER_MANIFEST_PROVIDER.models; - -export function buildTogetherModelDefinition( - model: (typeof TOGETHER_MODEL_CATALOG)[number], -): ModelDefinitionConfig { - return { - ...model, - api: "openai-completions", - input: [...model.input], - cost: { ...model.cost }, - }; -} +export const TOGETHER_MODEL_CATALOG: ModelDefinitionConfig[] = TOGETHER_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: "together", + catalog: TOGETHER_MANIFEST_CATALOG, + decorate: (model) => ({ ...model, api: "openai-completions" }), + }), +); diff --git a/extensions/together/onboard.test.ts b/extensions/together/onboard.test.ts new file mode 100644 index 000000000000..30c7b6e6cb95 --- /dev/null +++ b/extensions/together/onboard.test.ts @@ -0,0 +1,24 @@ +import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard"; +import { describe, expect, it } from "vitest"; +import { TOGETHER_MODEL_CATALOG } from "./models.js"; +import { applyTogetherConfig, TOGETHER_DEFAULT_MODEL_REF } from "./onboard.js"; +import manifest from "./openclaw.plugin.json" with { type: "json" }; + +describe("Together onboarding", () => { + it("applies the manifest catalog, default, and alias", () => { + const config = applyTogetherConfig({}); + + expect(config.models?.providers?.together?.models.map((model) => model.id)).toEqual( + TOGETHER_MODEL_CATALOG.map((model) => model.id), + ); + expect(resolveAgentModelPrimaryValue(config.agents?.defaults?.model)).toBe( + TOGETHER_DEFAULT_MODEL_REF, + ); + expect(TOGETHER_DEFAULT_MODEL_REF).toBe( + `together/${manifest.modelCatalog.providers.together.defaultModel}`, + ); + expect(config.agents?.defaults?.models?.[TOGETHER_DEFAULT_MODEL_REF]).toEqual({ + alias: "Together AI", + }); + }); +}); diff --git a/extensions/together/onboard.ts b/extensions/together/onboard.ts index 83c558ad186d..6f57a4a1d8d4 100644 --- a/extensions/together/onboard.ts +++ b/extensions/together/onboard.ts @@ -1,15 +1,16 @@ // Together setup module handles plugin onboarding behavior. +import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared"; import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { - buildTogetherModelDefinition, - TOGETHER_BASE_URL, - TOGETHER_MODEL_CATALOG, -} from "./models.js"; +import { TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./models.js"; +import manifest from "./openclaw.plugin.json" with { type: "json" }; -export const TOGETHER_DEFAULT_MODEL_REF = "together/meta-llama/Llama-3.3-70B-Instruct-Turbo"; +export const TOGETHER_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef( + manifest, + "together", +)!; const togetherPresetAppliers = createModelCatalogPresetAppliers({ primaryModelRef: TOGETHER_DEFAULT_MODEL_REF, @@ -17,7 +18,7 @@ const togetherPresetAppliers = createModelCatalogPresetAppliers({ providerId: "together", api: "openai-completions", baseUrl: TOGETHER_BASE_URL, - catalogModels: TOGETHER_MODEL_CATALOG.map(buildTogetherModelDefinition), + catalogModels: structuredClone(TOGETHER_MODEL_CATALOG), aliases: [{ modelRef: TOGETHER_DEFAULT_MODEL_REF, alias: "Together AI" }], }), }); diff --git a/extensions/together/openclaw.plugin.json b/extensions/together/openclaw.plugin.json index 53e81857e117..854cc1dc0320 100644 --- a/extensions/together/openclaw.plugin.json +++ b/extensions/together/openclaw.plugin.json @@ -44,6 +44,7 @@ "together": { "baseUrl": "https://api.together.xyz/v1", "api": "openai-completions", + "defaultModel": "meta-llama/Llama-3.3-70B-Instruct-Turbo", "models": [ { "id": "moonshotai/Kimi-K2.6", diff --git a/extensions/venice/api.ts b/extensions/venice/api.ts index 2633206e725c..06e3ff028697 100644 --- a/extensions/venice/api.ts +++ b/extensions/venice/api.ts @@ -1,6 +1,5 @@ // Venice API module exposes the plugin public contract. export { - buildVeniceModelDefinition, discoverVeniceModels, VENICE_BASE_URL, VENICE_DEFAULT_MODEL_REF, diff --git a/extensions/venice/models.test.ts b/extensions/venice/models.test.ts index c76f63a603e1..e48fbfb098e8 100644 --- a/extensions/venice/models.test.ts +++ b/extensions/venice/models.test.ts @@ -2,11 +2,7 @@ import { expectDefined } from "@openclaw/normalization-core"; import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { - buildVeniceModelDefinition, - discoverVeniceModels, - VENICE_MODEL_CATALOG, -} from "./models.js"; +import { discoverVeniceModels, VENICE_MODEL_CATALOG } from "./models.js"; import manifest from "./openclaw.plugin.json" with { type: "json" }; const ORIGINAL_NODE_ENV = process.env.NODE_ENV; @@ -119,9 +115,9 @@ describe("venice-models", () => { restoreDiscoveryEnv(); }); - it("buildVeniceModelDefinition returns config with required fields", () => { + it("builds static definitions with required fields", () => { const entry = expectDefined(VENICE_MODEL_CATALOG[0], "first Venice catalog model"); - const def = buildVeniceModelDefinition(entry); + const def = entry; expect(def.id).toBe(entry.id); expect(def.name).toBe(entry.name); expect(def.reasoning).toBe(entry.reasoning); @@ -241,9 +237,7 @@ describe("venice-models", () => { }); it("keeps tools enabled for DeepSeek V3.2", () => { - const model = buildVeniceModelDefinition( - VENICE_MODEL_CATALOG.find((entry) => entry.id === "deepseek-v3.2")!, - ); + const model = VENICE_MODEL_CATALOG.find((entry) => entry.id === "deepseek-v3.2")!; expect(model.compat?.supportsTools).toBeUndefined(); }); diff --git a/extensions/venice/models.ts b/extensions/venice/models.ts index 4ec92bb6932b..b5fad01a719b 100644 --- a/extensions/venice/models.ts +++ b/extensions/venice/models.ts @@ -3,7 +3,7 @@ import { LiveModelCatalogHttpError, } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; import { - buildManifestModelProviderConfig, + buildManifestModelDefinition, readManifestProviderDefaultModelRef, } from "openclaw/plugin-sdk/provider-catalog-shared"; import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; @@ -12,13 +12,9 @@ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coer import manifest from "./openclaw.plugin.json" with { type: "json" }; const log = createSubsystemLogger("venice-models"); +const VENICE_MANIFEST_CATALOG = manifest.modelCatalog.providers.venice; -const VENICE_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ - providerId: "venice", - catalog: manifest.modelCatalog.providers.venice, -}); - -export const VENICE_BASE_URL = VENICE_MANIFEST_PROVIDER.baseUrl; +export const VENICE_BASE_URL = VENICE_MANIFEST_CATALOG.baseUrl; export const VENICE_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(manifest, "venice")!; const VENICE_ALLOWED_HOSTNAMES = ["api.venice.ai"]; @@ -51,11 +47,7 @@ const VENICE_DISCOVERY_RETRYABLE_NETWORK_CODES = new Set([ "UND_ERR_SOCKET", ]); -export const VENICE_MODEL_CATALOG: ModelDefinitionConfig[] = VENICE_MANIFEST_PROVIDER.models; - -type VeniceCatalogEntry = ModelDefinitionConfig; - -export function buildVeniceModelDefinition(entry: VeniceCatalogEntry): ModelDefinitionConfig { +function decorateVeniceModelDefinition(entry: ModelDefinitionConfig): ModelDefinitionConfig { return { id: entry.id, name: entry.name, @@ -71,6 +63,15 @@ export function buildVeniceModelDefinition(entry: VeniceCatalogEntry): ModelDefi }; } +/** Venice's decorated network-free fallback catalog. */ +export const VENICE_MODEL_CATALOG: ModelDefinitionConfig[] = VENICE_MANIFEST_CATALOG.models.map( + buildManifestModelDefinition({ + providerId: "venice", + catalog: VENICE_MANIFEST_CATALOG, + decorate: decorateVeniceModelDefinition, + }), +); + interface VeniceModelSpec { name: string; privacy: "private" | "anonymized"; @@ -88,10 +89,6 @@ interface VeniceModel { model_spec?: VeniceModelSpec; } -function staticVeniceModelDefinitions(): ModelDefinitionConfig[] { - return VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition); -} - function hasRetryableNetworkCode(err: unknown): boolean { const queue: unknown[] = [err]; const seen = new Set(); @@ -177,7 +174,7 @@ export async function discoverVeniceModels( options: VeniceModelDiscoveryOptions = {}, ): Promise { if (process.env.NODE_ENV === "test" || process.env.VITEST) { - return staticVeniceModelDefinitions(); + return structuredClone(VENICE_MODEL_CATALOG); } try { @@ -203,11 +200,11 @@ export async function discoverVeniceModels( if (data.length === 0) { log.warn("No models found from API, using static catalog"); - return staticVeniceModelDefinitions(); + return structuredClone(VENICE_MODEL_CATALOG); } - const catalogById = new Map( - VENICE_MODEL_CATALOG.map((m) => [m.id, m]), + const catalogById = new Map( + structuredClone(VENICE_MODEL_CATALOG).map((model) => [model.id, model]), ); const models: ModelDefinitionConfig[] = []; @@ -219,7 +216,12 @@ export async function discoverVeniceModels( }); const apiSupportsTools = resolveApiSupportsTools(apiModel); if (catalogEntry) { - const definition = buildVeniceModelDefinition(catalogEntry); + const definition: ModelDefinitionConfig = { + ...catalogEntry, + input: [...catalogEntry.input], + cost: { ...catalogEntry.cost }, + ...(catalogEntry.compat ? { compat: { ...catalogEntry.compat } } : {}), + }; if (apiMaxTokens !== undefined) { definition.maxTokens = apiMaxTokens; } @@ -258,13 +260,13 @@ export async function discoverVeniceModels( } } - return models.length > 0 ? models : staticVeniceModelDefinitions(); + return models.length > 0 ? models : structuredClone(VENICE_MODEL_CATALOG); } catch (error) { if (error instanceof LiveModelCatalogHttpError) { log.warn(`Failed to discover models: HTTP ${error.status}, using static catalog`); - return staticVeniceModelDefinitions(); + return structuredClone(VENICE_MODEL_CATALOG); } log.warn(`Discovery failed: ${String(error)}, using static catalog`); - return staticVeniceModelDefinitions(); + return structuredClone(VENICE_MODEL_CATALOG); } } diff --git a/extensions/venice/onboard.ts b/extensions/venice/onboard.ts index 290b058a85b4..277224b8328b 100644 --- a/extensions/venice/onboard.ts +++ b/extensions/venice/onboard.ts @@ -3,12 +3,7 @@ import { createModelCatalogPresetAppliers, type OpenClawConfig, } from "openclaw/plugin-sdk/provider-onboard"; -import { - buildVeniceModelDefinition, - VENICE_BASE_URL, - VENICE_DEFAULT_MODEL_REF, - VENICE_MODEL_CATALOG, -} from "./api.js"; +import { VENICE_BASE_URL, VENICE_DEFAULT_MODEL_REF, VENICE_MODEL_CATALOG } from "./api.js"; const venicePresetAppliers = createModelCatalogPresetAppliers({ primaryModelRef: VENICE_DEFAULT_MODEL_REF, @@ -16,7 +11,7 @@ const venicePresetAppliers = createModelCatalogPresetAppliers({ providerId: "venice", api: "openai-completions", baseUrl: VENICE_BASE_URL, - catalogModels: VENICE_MODEL_CATALOG.map(buildVeniceModelDefinition), + catalogModels: structuredClone(VENICE_MODEL_CATALOG), aliases: [{ modelRef: VENICE_DEFAULT_MODEL_REF, alias: "Kimi K2.6" }], }), }); diff --git a/extensions/volcengine/api.ts b/extensions/volcengine/api.ts index ede48c6d8018..255ebee5f2b5 100644 --- a/extensions/volcengine/api.ts +++ b/extensions/volcengine/api.ts @@ -50,7 +50,6 @@ export function applyVolcengineToolSchemaCompat { it("augments the catalog with bundled standard and plan models", async () => { const provider = await registerSingleProviderPlugin(plugin); + expect(provider.auth?.[0]?.starterModel).toBe("volcengine-plan/ark-code-latest"); const entries = await provider.augmentModelCatalog?.({ env: process.env, entries: [], diff --git a/extensions/volcengine/index.ts b/extensions/volcengine/index.ts index f9a2c435b785..af169638398f 100644 --- a/extensions/volcengine/index.ts +++ b/extensions/volcengine/index.ts @@ -2,13 +2,18 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; import { buildOpenAICompatibleLiveModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-live-runtime"; +import { readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared"; import { ensureModelAllowlistEntry } from "openclaw/plugin-sdk/provider-onboard"; import { applyVolcengineToolSchemaCompat } from "./api.js"; +import manifest from "./openclaw.plugin.json" with { type: "json" }; import { VOLCENGINE_PROVIDER_CATALOG_ENTRIES } from "./provider-catalog.js"; import { buildVolcengineSpeechProvider } from "./speech-provider.js"; const PROVIDER_ID = "volcengine"; -const VOLCENGINE_DEFAULT_MODEL_REF = "volcengine-plan/ark-code-latest"; +const VOLCENGINE_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef( + manifest, + "volcengine-plan", +)!; export default definePluginEntry({ id: PROVIDER_ID, diff --git a/extensions/volcengine/models.ts b/extensions/volcengine/models.ts index b79f8b8c2930..82c4ffc17384 100644 --- a/extensions/volcengine/models.ts +++ b/extensions/volcengine/models.ts @@ -19,11 +19,3 @@ export const DOUBAO_CODING_BASE_URL = DOUBAO_CODING_MANIFEST_PROVIDER.baseUrl; export const DOUBAO_MODEL_CATALOG: ModelDefinitionConfig[] = DOUBAO_MANIFEST_PROVIDER.models; export const DOUBAO_CODING_MODEL_CATALOG: ModelDefinitionConfig[] = DOUBAO_CODING_MANIFEST_PROVIDER.models; - -export function buildDoubaoModelDefinition(entry: ModelDefinitionConfig): ModelDefinitionConfig { - return { - ...entry, - input: [...entry.input], - cost: { ...entry.cost }, - }; -} diff --git a/extensions/volcengine/openclaw.plugin.json b/extensions/volcengine/openclaw.plugin.json index bc19f0928a36..1379262130b2 100644 --- a/extensions/volcengine/openclaw.plugin.json +++ b/extensions/volcengine/openclaw.plugin.json @@ -230,6 +230,7 @@ "volcengine-plan": { "baseUrl": "https://ark.cn-beijing.volces.com/api/coding/v3", "api": "openai-completions", + "defaultModel": "ark-code-latest", "models": [ { "id": "ark-code-latest", diff --git a/src/plugin-sdk/provider-catalog-shared.test.ts b/src/plugin-sdk/provider-catalog-shared.test.ts index 72251ffabf17..3f4acc4997fc 100644 --- a/src/plugin-sdk/provider-catalog-shared.test.ts +++ b/src/plugin-sdk/provider-catalog-shared.test.ts @@ -3,6 +3,7 @@ import type { ModelCatalogProvider } from "@openclaw/model-catalog-core/model-ca import { beforeEach, describe, expect, it, vi } from "vitest"; import { applyProviderNativeStreamingUsageCompat, + buildManifestModelDefinition, buildManifestModelProviderConfig, clearLiveCatalogCacheForTests, getCachedLiveCatalogValue, @@ -376,6 +377,20 @@ describe("provider-catalog-shared manifest provider configs", () => { "example", ), ).toBe("example/example-model"); + + expect( + buildManifestModelDefinition({ + providerId: "example", + catalog, + decorate: (model) => ({ + ...model, + compat: { ...model.compat, supportsUsageInStreaming: false }, + }), + })(catalog.models[0]), + ).toEqual({ + ...buildManifestModelProviderConfig({ providerId: "example", catalog }).models[0], + compat: { supportsUsageInStreaming: false }, + }); }); 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 026212d5f616..521bdab75ccc 100644 --- a/src/plugin-sdk/provider-catalog-shared.ts +++ b/src/plugin-sdk/provider-catalog-shared.ts @@ -246,6 +246,32 @@ export function buildManifestModelProviderConfig(params: { }; } +/** Builds one normalized runtime model row from a provider manifest catalog entry. */ +export function buildManifestModelDefinition(params: { + /** Provider id that owns the manifest catalog row. */ + providerId: string; + /** Raw manifest modelCatalog provider block that contains the row. */ + catalog: unknown; + /** Optional provider policy applied after manifest normalization. */ + decorate?: (model: ModelDefinitionConfig) => ModelDefinitionConfig; +}): (model: unknown) => ModelDefinitionConfig { + if (!params.catalog || typeof params.catalog !== "object" || Array.isArray(params.catalog)) { + throw new Error(`Missing modelCatalog.providers.${params.providerId}`); + } + const catalog = params.catalog; + return (rawModel) => { + const provider = buildManifestModelProviderConfig({ + providerId: params.providerId, + catalog: { ...catalog, models: [rawModel] }, + }); + const model = provider.models[0]; + if (!model) { + throw new Error(`Missing modelCatalog.providers.${params.providerId}.models[0]`); + } + return params.decorate?.(model) ?? model; + }; +} + function normalizeConfiguredCatalogModelInput( input: unknown, ): ConfiguredProviderCatalogEntry["input"] | undefined {