openclaw/extensions/featherless/index.ts

119 lines
3.8 KiB
TypeScript

// Featherless plugin entrypoint registers its OpenClaw integration.
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 {
buildProviderReplayFamilyHooks,
cloneFirstTemplateModel,
normalizeModelCompat,
} from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";
import { applyFeatherlessConfig, FEATHERLESS_DEFAULT_MODEL_REF } from "./onboard.js";
import {
buildFeatherlessProvider,
FEATHERLESS_BASE_URL,
FEATHERLESS_DEFAULT_MODEL_ID,
FEATHERLESS_DYNAMIC_COMPAT,
FEATHERLESS_DYNAMIC_CONTEXT_WINDOW,
FEATHERLESS_DYNAMIC_MAX_TOKENS,
isFeatherlessCatalogModelId,
} from "./provider-catalog.js";
const PROVIDER_ID = "featherless";
function resolveFeatherlessDynamicModel(ctx: ProviderResolveDynamicModelContext) {
const modelId = ctx.modelId.trim();
if (!modelId || isFeatherlessCatalogModelId(modelId)) {
return undefined;
}
return (
cloneFirstTemplateModel({
providerId: PROVIDER_ID,
modelId,
templateIds: [FEATHERLESS_DEFAULT_MODEL_ID],
ctx,
patch: {
provider: PROVIDER_ID,
reasoning: false,
input: ["text"],
contextWindow: FEATHERLESS_DYNAMIC_CONTEXT_WINDOW,
maxTokens: FEATHERLESS_DYNAMIC_MAX_TOKENS,
compat: FEATHERLESS_DYNAMIC_COMPAT,
},
}) ??
normalizeModelCompat({
id: modelId,
name: modelId,
provider: PROVIDER_ID,
api: "openai-completions",
baseUrl: FEATHERLESS_BASE_URL,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: FEATHERLESS_DYNAMIC_CONTEXT_WINDOW,
maxTokens: FEATHERLESS_DYNAMIC_MAX_TOKENS,
compat: FEATHERLESS_DYNAMIC_COMPAT,
})
);
}
function normalizeFeatherlessResolvedModel(model: ProviderRuntimeModel): ProviderRuntimeModel {
return {
...model,
compat: {
...FEATHERLESS_DYNAMIC_COMPAT,
...model.compat,
},
};
}
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "Featherless AI Provider",
description: "Featherless AI provider plugin",
provider: {
label: "Featherless AI",
docsPath: "/providers/featherless",
envVars: ["FEATHERLESS_API_KEY"],
auth: [
{
methodId: "api-key",
label: "Featherless AI API key",
hint: "OpenAI-compatible access to open models",
optionKey: "featherlessApiKey",
flagName: "--featherless-api-key",
envVar: "FEATHERLESS_API_KEY",
promptMessage: "Enter Featherless AI API key",
defaultModel: FEATHERLESS_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyFeatherlessConfig(cfg),
noteTitle: "Featherless AI",
noteMessage: [
"Featherless AI serves open models through an OpenAI-compatible API.",
"Create an API key at: https://featherless.ai/account/api-keys",
].join("\n"),
},
],
catalog: {
buildProvider: buildFeatherlessProvider,
buildStaticProvider: buildFeatherlessProvider,
allowExplicitBaseUrl: true,
},
augmentModelCatalog: ({ config }) =>
readConfiguredProviderCatalogEntries({
config,
providerId: PROVIDER_ID,
}),
normalizeResolvedModel: ({ model }) => normalizeFeatherlessResolvedModel(model),
...buildProviderReplayFamilyHooks({
family: "openai-compatible",
dropReasoningFromHistory: false,
}),
...buildProviderToolCompatFamilyHooks("openai"),
resolveDynamicModel: (ctx) => resolveFeatherlessDynamicModel(ctx),
isModernModelRef: () => true,
},
});