diff --git a/.github/labeler.yml b/.github/labeler.yml index ea9f4748a0d..3d1ab872cf0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -299,6 +299,11 @@ - changed-files: - any-glob-to-any-file: - "extensions/byteplus/**" +"extensions: cerebras": + - changed-files: + - any-glob-to-any-file: + - "extensions/cerebras/**" + - "docs/providers/cerebras.md" "extensions: deepseek": - changed-files: - any-glob-to-any-file: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5866889223c..1d401e686a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Changes +- Providers: add Cerebras as a bundled plugin with onboarding, static model catalog, docs, and manifest-owned endpoint metadata. Thanks @codex. - Plugins/providers: move pre-runtime model-id normalization, provider endpoint host metadata, and OpenAI-compatible request-family hints into plugin manifests so core no longer carries bundled-provider routing tables. Thanks @codex. - Plugins/install: allow `OPENCLAW_PLUGIN_STAGE_DIR` to contain layered runtime-dependency roots, resolving read-only preinstalled deps before installing missing deps into the final writable root. Fixes #72396. Thanks @liorb-mountapps. - Control UI: polish the quick settings dashboard grid so common cards align across desktop, tablet, and mobile layouts without wasting horizontal space. Thanks @BunsDev. diff --git a/extensions/cerebras/api.ts b/extensions/cerebras/api.ts new file mode 100644 index 00000000000..a5cb771c9d8 --- /dev/null +++ b/extensions/cerebras/api.ts @@ -0,0 +1,7 @@ +export { + buildCerebrasModelDefinition, + 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/index.ts b/extensions/cerebras/index.ts new file mode 100644 index 00000000000..99fa2fe8340 --- /dev/null +++ b/extensions/cerebras/index.ts @@ -0,0 +1,41 @@ +import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry"; +import { applyCerebrasConfig, CEREBRAS_DEFAULT_MODEL_REF } from "./onboard.js"; +import { buildCerebrasProvider } from "./provider-catalog.js"; + +const PROVIDER_ID = "cerebras"; + +export default defineSingleProviderPluginEntry({ + id: PROVIDER_ID, + name: "Cerebras Provider", + description: "Bundled Cerebras provider plugin", + provider: { + label: "Cerebras", + docsPath: "/providers/cerebras", + auth: [ + { + methodId: "api-key", + label: "Cerebras API key", + hint: "Fast OpenAI-compatible inference", + optionKey: "cerebrasApiKey", + flagName: "--cerebras-api-key", + envVar: "CEREBRAS_API_KEY", + promptMessage: "Enter Cerebras API key", + defaultModel: CEREBRAS_DEFAULT_MODEL_REF, + applyConfig: (cfg) => applyCerebrasConfig(cfg), + noteMessage: [ + "Cerebras provides high-speed OpenAI-compatible inference for GPT OSS, GLM, Qwen, and Llama models.", + "Get your API key at: https://cloud.cerebras.ai", + ].join("\n"), + noteTitle: "Cerebras", + wizard: { + groupLabel: "Cerebras", + groupHint: "Fast OpenAI-compatible inference", + }, + }, + ], + catalog: { + buildProvider: buildCerebrasProvider, + buildStaticProvider: buildCerebrasProvider, + }, + }, +}); diff --git a/extensions/cerebras/models.ts b/extensions/cerebras/models.ts new file mode 100644 index 00000000000..228b722f8e8 --- /dev/null +++ b/extensions/cerebras/models.ts @@ -0,0 +1,49 @@ +import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; + +export const CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1"; + +export const CEREBRAS_MODEL_CATALOG = [ + { + id: "zai-glm-4.7", + name: "Z.ai GLM 4.7", + reasoning: true, + input: ["text"], + cost: { input: 2.25, output: 2.75, cacheRead: 2.25, cacheWrite: 2.75 }, + }, + { + id: "gpt-oss-120b", + name: "GPT OSS 120B", + reasoning: true, + input: ["text"], + cost: { input: 0.35, output: 0.75, cacheRead: 0.35, cacheWrite: 0.75 }, + }, + { + id: "qwen-3-235b-a22b-instruct-2507", + name: "Qwen 3 235B Instruct", + reasoning: false, + input: ["text"], + cost: { input: 0.6, output: 1.2, cacheRead: 0.6, cacheWrite: 1.2 }, + }, + { + id: "llama3.1-8b", + name: "Llama 3.1 8B", + reasoning: false, + input: ["text"], + cost: { input: 0.1, output: 0.1, cacheRead: 0.1, cacheWrite: 0.1 }, + }, +] as const; + +export function buildCerebrasModelDefinition( + model: (typeof CEREBRAS_MODEL_CATALOG)[number], +): ModelDefinitionConfig { + return { + id: model.id, + name: model.name, + api: "openai-completions", + reasoning: model.reasoning, + input: [...model.input], + cost: model.cost, + contextWindow: 128_000, + maxTokens: 8192, + }; +} diff --git a/extensions/cerebras/onboard.ts b/extensions/cerebras/onboard.ts new file mode 100644 index 00000000000..32207171c0e --- /dev/null +++ b/extensions/cerebras/onboard.ts @@ -0,0 +1,30 @@ +import { + createModelCatalogPresetAppliers, + type OpenClawConfig, +} from "openclaw/plugin-sdk/provider-onboard"; +import { + buildCerebrasModelDefinition, + CEREBRAS_BASE_URL, + CEREBRAS_MODEL_CATALOG, +} from "./models.js"; + +export const CEREBRAS_DEFAULT_MODEL_REF = "cerebras/zai-glm-4.7"; + +const cerebrasPresetAppliers = createModelCatalogPresetAppliers({ + primaryModelRef: CEREBRAS_DEFAULT_MODEL_REF, + resolveParams: (_cfg: OpenClawConfig) => ({ + providerId: "cerebras", + api: "openai-completions", + baseUrl: CEREBRAS_BASE_URL, + catalogModels: CEREBRAS_MODEL_CATALOG.map(buildCerebrasModelDefinition), + aliases: [{ modelRef: CEREBRAS_DEFAULT_MODEL_REF, alias: "Cerebras GLM 4.7" }], + }), +}); + +export function applyCerebrasProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + return cerebrasPresetAppliers.applyProviderConfig(cfg); +} + +export function applyCerebrasConfig(cfg: OpenClawConfig): OpenClawConfig { + return cerebrasPresetAppliers.applyConfig(cfg); +} diff --git a/extensions/cerebras/openclaw.plugin.json b/extensions/cerebras/openclaw.plugin.json new file mode 100644 index 00000000000..11faf0faadc --- /dev/null +++ b/extensions/cerebras/openclaw.plugin.json @@ -0,0 +1,41 @@ +{ + "id": "cerebras", + "enabledByDefault": true, + "providers": ["cerebras"], + "providerEndpoints": [ + { + "endpointClass": "cerebras-native", + "hosts": ["api.cerebras.ai"] + } + ], + "providerRequest": { + "providers": { + "cerebras": { + "family": "cerebras" + } + } + }, + "providerAuthEnvVars": { + "cerebras": ["CEREBRAS_API_KEY"] + }, + "providerAuthChoices": [ + { + "provider": "cerebras", + "method": "api-key", + "choiceId": "cerebras-api-key", + "choiceLabel": "Cerebras API key", + "groupId": "cerebras", + "groupLabel": "Cerebras", + "groupHint": "Fast OpenAI-compatible inference", + "optionKey": "cerebrasApiKey", + "cliFlag": "--cerebras-api-key", + "cliOption": "--cerebras-api-key ", + "cliDescription": "Cerebras API key" + } + ], + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/extensions/cerebras/package.json b/extensions/cerebras/package.json new file mode 100644 index 00000000000..504f3cd5ccc --- /dev/null +++ b/extensions/cerebras/package.json @@ -0,0 +1,15 @@ +{ + "name": "@openclaw/cerebras-provider", + "version": "2026.4.26", + "private": true, + "description": "OpenClaw Cerebras provider plugin", + "type": "module", + "devDependencies": { + "@openclaw/plugin-sdk": "workspace:*" + }, + "openclaw": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/extensions/cerebras/provider-catalog.ts b/extensions/cerebras/provider-catalog.ts new file mode 100644 index 00000000000..d3c2a09c705 --- /dev/null +++ b/extensions/cerebras/provider-catalog.ts @@ -0,0 +1,14 @@ +import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; +import { + buildCerebrasModelDefinition, + CEREBRAS_BASE_URL, + CEREBRAS_MODEL_CATALOG, +} from "./models.js"; + +export function buildCerebrasProvider(): ModelProviderConfig { + return { + baseUrl: CEREBRAS_BASE_URL, + api: "openai-completions", + models: CEREBRAS_MODEL_CATALOG.map(buildCerebrasModelDefinition), + }; +} diff --git a/extensions/cerebras/tsconfig.json b/extensions/cerebras/tsconfig.json new file mode 100644 index 00000000000..b8a85a99ac3 --- /dev/null +++ b/extensions/cerebras/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.package-boundary.base.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["./*.ts", "./src/**/*.ts"], + "exclude": [ + "./**/*.test.ts", + "./dist/**", + "./node_modules/**", + "./src/test-support/**", + "./src/**/*test-helpers.ts", + "./src/**/*test-harness.ts", + "./src/**/*test-support.ts" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fa376a3b50..ffb1a1e1c9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,6 +361,12 @@ importers: specifier: workspace:* version: link:../../packages/plugin-sdk + extensions/cerebras: + devDependencies: + '@openclaw/plugin-sdk': + specifier: workspace:* + version: link:../../packages/plugin-sdk + extensions/chutes: devDependencies: '@openclaw/plugin-sdk': @@ -877,19 +883,6 @@ importers: specifier: workspace:* version: link:../.. - extensions/migrate-hermes: - dependencies: - yaml: - specifier: ^2.8.3 - version: 2.8.3 - devDependencies: - '@openclaw/plugin-sdk': - specifier: workspace:* - version: link:../../packages/plugin-sdk - openclaw: - specifier: workspace:* - version: link:../.. - extensions/microsoft: dependencies: node-edge-tts: @@ -906,6 +899,19 @@ importers: specifier: workspace:* version: link:../../packages/plugin-sdk + extensions/migrate-hermes: + dependencies: + yaml: + specifier: ^2.8.3 + version: 2.8.3 + devDependencies: + '@openclaw/plugin-sdk': + specifier: workspace:* + version: link:../../packages/plugin-sdk + openclaw: + specifier: workspace:* + version: link:../.. + extensions/minimax: devDependencies: '@openclaw/plugin-sdk': diff --git a/src/agents/provider-attribution.test.ts b/src/agents/provider-attribution.test.ts index b5501302b4b..8c5e583819a 100644 --- a/src/agents/provider-attribution.test.ts +++ b/src/agents/provider-attribution.test.ts @@ -7,6 +7,7 @@ const providerEndpointPlugins = vi.hoisted(() => [ { endpointClass: "openai-codex", hosts: ["chatgpt.com"] }, { endpointClass: "azure-openai", hostSuffixes: [".openai.azure.com"] }, { endpointClass: "anthropic-public", hosts: ["api.anthropic.com"] }, + { endpointClass: "cerebras-native", hosts: ["api.cerebras.ai"] }, { endpointClass: "mistral-public", hosts: ["api.mistral.ai"] }, { endpointClass: "chutes-native", hosts: ["llm.chutes.ai"] }, { endpointClass: "deepseek-native", hosts: ["api.deepseek.com"] }, @@ -47,6 +48,7 @@ const providerEndpointPlugins = vi.hoisted(() => [ providerRequest: { providers: { anthropic: { family: "anthropic" }, + cerebras: { family: "cerebras" }, chutes: { family: "chutes" }, deepseek: { family: "deepseek" }, "github-copilot": { family: "github-copilot" }, diff --git a/src/agents/provider-attribution.ts b/src/agents/provider-attribution.ts index c6293a3d88a..b5f1da16ef3 100644 --- a/src/agents/provider-attribution.ts +++ b/src/agents/provider-attribution.ts @@ -355,9 +355,6 @@ export function resolveProviderEndpoint( if (manifestEndpoint) { return manifestEndpoint; } - if (host === "api.cerebras.ai") { - return { endpointClass: "cerebras-native", hostname: host }; - } if (isLocalEndpointHost(host)) { return { endpointClass: "local", hostname: host }; }