diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7b2b5a2bd..6694b803105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ Docs: https://docs.openclaw.ai ### Changes -- **Featherless AI provider:** add an official external provider plugin with API-key onboarding, a tool-capable Qwen3 default, and support for arbitrary Featherless model ids. - **Gateway host status:** show the connected Gateway's host, network address, OS, runtime, uptime, CPU, memory, and disk details in Control UI Settings. (#100478) - **iOS offline chat:** pre-paint recent sessions and canonical transcripts from a protected, bounded per-gateway cache, keep sending disabled offline, and purge cached conversation text when pairing is reset. (#100194) - **Slack progress indicators:** use Slack's native assistant thread status and rotating loading messages by default while keeping acknowledgement reactions static; lifecycle reaction updates now require `messages.statusReactions.enabled: true`. diff --git a/docs/docs_map.md b/docs/docs_map.md index 8c4190eb4a4..fee489ec6c9 100644 --- a/docs/docs_map.md +++ b/docs/docs_map.md @@ -6062,6 +6062,15 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: Surface - H2: Related docs +## plugins/reference/featherless.md + +- Route: /plugins/reference/featherless +- Headings: + - H1: Featherless plugin + - H2: Distribution + - H2: Surface + - H2: Related docs + ## plugins/reference/feishu.md - Route: /plugins/reference/feishu @@ -7436,6 +7445,16 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: Music generation - H2: Related +## providers/featherless.md + +- Route: /providers/featherless +- Headings: + - H2: Setup + - H2: Default model + - H2: Other Featherless models + - H2: Troubleshooting + - H2: Related + ## providers/fireworks.md - Route: /providers/fireworks diff --git a/extensions/featherless/index.test.ts b/extensions/featherless/index.test.ts index f7aca4fe472..c685a6c92f3 100644 --- a/extensions/featherless/index.test.ts +++ b/extensions/featherless/index.test.ts @@ -115,6 +115,29 @@ describe("featherless provider plugin", () => { }); }); + it("applies provider compat to configured models without overriding explicit values", async () => { + const provider = await registerSingleProviderPlugin(featherlessPlugin); + const normalized = provider.normalizeResolvedModel?.({ + provider: "featherless", + modelId: "google/gemma-3-27b-it", + model: { + ...createDefaultRuntimeModel(), + id: "google/gemma-3-27b-it", + name: "Gemma 3 27B", + compat: { + supportsStore: true, + thinkingFormat: "deepseek", + }, + }, + }); + + expect(normalized?.compat).toMatchObject({ + ...FEATHERLESS_DYNAMIC_COMPAT, + supportsStore: true, + thinkingFormat: "deepseek", + }); + }); + it("defers the curated model to static catalog resolution", async () => { const provider = await registerSingleProviderPlugin(featherlessPlugin); const resolved = provider.resolveDynamicModel?.( diff --git a/extensions/featherless/index.ts b/extensions/featherless/index.ts index 351793d7c1a..fe36fbe31e8 100644 --- a/extensions/featherless/index.ts +++ b/extensions/featherless/index.ts @@ -1,5 +1,8 @@ // Featherless plugin entrypoint registers its OpenClaw integration. -import type { ProviderResolveDynamicModelContext } from "openclaw/plugin-sdk/plugin-entry"; +import type { + ProviderResolveDynamicModelContext, + ProviderRuntimeModel, +} from "openclaw/plugin-sdk/plugin-entry"; import { readConfiguredProviderCatalogEntries } from "openclaw/plugin-sdk/provider-catalog-shared"; import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry"; import { @@ -58,6 +61,16 @@ function resolveFeatherlessDynamicModel(ctx: ProviderResolveDynamicModelContext) ); } +function normalizeFeatherlessResolvedModel(model: ProviderRuntimeModel): ProviderRuntimeModel { + return { + ...model, + compat: { + ...FEATHERLESS_DYNAMIC_COMPAT, + ...model.compat, + }, + }; +} + export default defineSingleProviderPluginEntry({ id: PROVIDER_ID, name: "Featherless AI Provider", @@ -94,6 +107,7 @@ export default defineSingleProviderPluginEntry({ config, providerId: PROVIDER_ID, }), + normalizeResolvedModel: ({ model }) => normalizeFeatherlessResolvedModel(model), ...buildProviderReplayFamilyHooks({ family: "openai-compatible", dropReasoningFromHistory: false, diff --git a/extensions/featherless/models.ts b/extensions/featherless/models.ts index 5792006ea34..ebde8942bb0 100644 --- a/extensions/featherless/models.ts +++ b/extensions/featherless/models.ts @@ -1,6 +1,9 @@ // Featherless model catalog helpers derive their values from the plugin manifest. import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared"; -import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; +import type { + ModelCompatConfig, + ModelDefinitionConfig, +} from "openclaw/plugin-sdk/provider-model-shared"; import manifest from "./openclaw.plugin.json" with { type: "json" }; const FEATHERLESS_MANIFEST_PROVIDER = buildManifestModelProviderConfig({ @@ -26,10 +29,10 @@ const FEATHERLESS_DEFAULT_MODEL = requireFeatherlessManifestModel(FEATHERLESS_DE export const FEATHERLESS_DEFAULT_CONTEXT_WINDOW = FEATHERLESS_DEFAULT_MODEL.contextWindow; export const FEATHERLESS_DEFAULT_MAX_TOKENS = FEATHERLESS_DEFAULT_MODEL.maxTokens; -export const FEATHERLESS_DYNAMIC_COMPAT = { +export const FEATHERLESS_DYNAMIC_COMPAT: ModelCompatConfig = { ...FEATHERLESS_DEFAULT_MODEL.compat, thinkingFormat: "openai", -} as const; +}; export function isFeatherlessCatalogModelId(modelId: string): boolean { return FEATHERLESS_MANIFEST_PROVIDER.models.some((model) => model.id === modelId); diff --git a/extensions/featherless/openclaw.plugin.json b/extensions/featherless/openclaw.plugin.json index 0323e076330..b0ff8a44601 100644 --- a/extensions/featherless/openclaw.plugin.json +++ b/extensions/featherless/openclaw.plugin.json @@ -27,6 +27,7 @@ "provider": "featherless", "method": "api-key", "choiceId": "featherless-api-key", + "appGuidedSecret": true, "choiceLabel": "Featherless AI API key", "choiceHint": "OpenAI-compatible access to open models", "groupId": "featherless",