feat: Featherless provider (#7235)

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: cte <cestreich@gmail.com>
This commit is contained in:
DarinVerheijke 2025-08-20 19:42:57 +02:00 committed by GitHub
parent c608392a85
commit 57ea6257dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 583 additions and 11 deletions

View file

@ -25,6 +25,7 @@ body:
- AWS Bedrock
- Chutes AI
- DeepSeek
- Featherless AI
- Fireworks AI
- Glama
- Google Gemini

View file

@ -1,6 +1,6 @@
{
"name": "@roo-code/types",
"version": "1.53.0",
"version": "1.55.0",
"description": "TypeScript type definitions for Roo Code.",
"publishConfig": {
"access": "public",

View file

@ -196,6 +196,7 @@ export const SECRET_STATE_KEYS = [
"sambaNovaApiKey",
"zaiApiKey",
"fireworksApiKey",
"featherlessApiKey",
"ioIntelligenceApiKey",
] as const satisfies readonly (keyof ProviderSettings)[]
export type SecretState = Pick<ProviderSettings, (typeof SECRET_STATE_KEYS)[number]>

View file

@ -46,6 +46,7 @@ export const providerNames = [
"sambanova",
"zai",
"fireworks",
"featherless",
"io-intelligence",
"roo",
] as const
@ -284,6 +285,10 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({
fireworksApiKey: z.string().optional(),
})
const featherlessSchema = apiModelIdProviderModelSchema.extend({
featherlessApiKey: z.string().optional(),
})
const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({
ioIntelligenceModelId: z.string().optional(),
ioIntelligenceApiKey: z.string().optional(),
@ -328,6 +333,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
featherlessSchema.merge(z.object({ apiProvider: z.literal("featherless") })),
ioIntelligenceSchema.merge(z.object({ apiProvider: z.literal("io-intelligence") })),
rooSchema.merge(z.object({ apiProvider: z.literal("roo") })),
defaultSchema,
@ -365,6 +371,7 @@ export const providerSettingsSchema = z.object({
...sambaNovaSchema.shape,
...zaiSchema.shape,
...fireworksSchema.shape,
...featherlessSchema.shape,
...ioIntelligenceSchema.shape,
...rooSchema.shape,
...codebaseIndexProviderSchema.shape,

View file

@ -0,0 +1,58 @@
import type { ModelInfo } from "../model.js"
export type FeatherlessModelId =
| "deepseek-ai/DeepSeek-V3-0324"
| "deepseek-ai/DeepSeek-R1-0528"
| "moonshotai/Kimi-K2-Instruct"
| "openai/gpt-oss-120b"
| "Qwen/Qwen3-Coder-480B-A35B-Instruct"
export const featherlessModels = {
"deepseek-ai/DeepSeek-V3-0324": {
maxTokens: 4096,
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek V3 0324 model.",
},
"deepseek-ai/DeepSeek-R1-0528": {
maxTokens: 4096,
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "DeepSeek R1 0528 model.",
},
"moonshotai/Kimi-K2-Instruct": {
maxTokens: 4096,
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "Kimi K2 Instruct model.",
},
"openai/gpt-oss-120b": {
maxTokens: 4096,
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "GPT-OSS 120B model.",
},
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
maxTokens: 4096,
contextWindow: 32678,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
description: "Qwen3 Coder 480B A35B Instruct model.",
},
} as const satisfies Record<string, ModelInfo>
export const featherlessDefaultModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"

View file

@ -26,3 +26,4 @@ export * from "./doubao.js"
export * from "./zai.js"
export * from "./fireworks.js"
export * from "./roo.js"
export * from "./featherless.js"

18
pnpm-lock.yaml generated
View file

@ -584,8 +584,8 @@ importers:
specifier: ^1.14.0
version: 1.14.0(typescript@5.8.3)
'@roo-code/cloud':
specifier: ^0.18.0
version: 0.18.0
specifier: ^0.19.0
version: 0.19.0
'@roo-code/ipc':
specifier: workspace:^
version: link:../packages/ipc
@ -3106,11 +3106,11 @@ packages:
cpu: [x64]
os: [win32]
'@roo-code/cloud@0.18.0':
resolution: {integrity: sha512-Y2jbcUVB9RCQFAxHDPrfjWQU1o7yRvWaPAdA3eZjsUf+zfDL59Rwfghg6loqDfE/8HCkcJmHfLCKovNX5ju5qA==}
'@roo-code/cloud@0.19.0':
resolution: {integrity: sha512-alZ3X4+TPqRr0xSs9v/UDo3eTlcHaI8ZW8AbWPDtgqf86P8govnyM2hVUMhGXete3AlbYIPRE/9w3/7MrcIjsA==}
'@roo-code/types@1.54.0':
resolution: {integrity: sha512-Xj3Zn2FhXbG2bpwXuhrjKnkeuWypQCIPKljOLXnOCUqaMUhP1zkWwNZ+I3gIBUpDng/iWN3KHon1if0UaoXYQw==}
'@roo-code/types@1.55.0':
resolution: {integrity: sha512-+T5MP8IQcDp7htnGDnk3M4n7S5eYk6jNkw3VBSUBZRhS4EE2GuPDI+CcdmhnDiMb6NMV6yseL+CT4G4QV5ktUw==}
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@ -12314,9 +12314,9 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.40.2':
optional: true
'@roo-code/cloud@0.18.0':
'@roo-code/cloud@0.19.0':
dependencies:
'@roo-code/types': 1.54.0
'@roo-code/types': 1.55.0
ioredis: 5.6.1
p-wait-for: 5.0.2
socket.io-client: 4.8.1
@ -12326,7 +12326,7 @@ snapshots:
- supports-color
- utf-8-validate
'@roo-code/types@1.54.0':
'@roo-code/types@1.55.0':
dependencies:
zod: 3.25.76

View file

@ -36,6 +36,7 @@ import {
ZAiHandler,
FireworksHandler,
RooHandler,
FeatherlessHandler,
} from "./providers"
import { NativeOllamaHandler } from "./providers/native-ollama"
@ -143,6 +144,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new IOIntelligenceHandler(options)
case "roo":
return new RooHandler(options)
case "featherless":
return new FeatherlessHandler(options)
default:
apiProvider satisfies "gemini-cli" | undefined
return new AnthropicHandler(options)

View file

@ -0,0 +1,286 @@
// npx vitest run api/providers/__tests__/featherless.spec.ts
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import {
type FeatherlessModelId,
featherlessDefaultModelId,
featherlessModels,
DEEP_SEEK_DEFAULT_TEMPERATURE,
} from "@roo-code/types"
import { FeatherlessHandler } from "../featherless"
// Create mock functions
const mockCreate = vi.fn()
// Mock OpenAI module
vi.mock("openai", () => ({
default: vi.fn(() => ({
chat: {
completions: {
create: mockCreate,
},
},
})),
}))
describe("FeatherlessHandler", () => {
let handler: FeatherlessHandler
beforeEach(() => {
vi.clearAllMocks()
// Set up default mock implementation
mockCreate.mockImplementation(async () => ({
[Symbol.asyncIterator]: async function* () {
yield {
choices: [
{
delta: { content: "Test response" },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: {},
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
},
}
},
}))
handler = new FeatherlessHandler({ featherlessApiKey: "test-key" })
})
afterEach(() => {
vi.restoreAllMocks()
})
it("should use the correct Featherless base URL", () => {
new FeatherlessHandler({ featherlessApiKey: "test-featherless-api-key" })
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.featherless.ai/v1" }))
})
it("should use the provided API key", () => {
const featherlessApiKey = "test-featherless-api-key"
new FeatherlessHandler({ featherlessApiKey })
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: featherlessApiKey }))
})
it("should handle DeepSeek R1 reasoning format", async () => {
// Override the mock for this specific test
mockCreate.mockImplementationOnce(async () => ({
[Symbol.asyncIterator]: async function* () {
yield {
choices: [
{
delta: { content: "<think>Thinking..." },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: { content: "</think>Hello" },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: {},
index: 0,
},
],
usage: { prompt_tokens: 10, completion_tokens: 5 },
}
},
}))
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
vi.spyOn(handler, "getModel").mockReturnValue({
id: "deepseek-ai/DeepSeek-R1-0528",
info: { maxTokens: 1024, temperature: 0.7 },
} as any)
const stream = handler.createMessage(systemPrompt, messages)
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
expect(chunks).toEqual([
{ type: "reasoning", text: "Thinking..." },
{ type: "text", text: "Hello" },
{ type: "usage", inputTokens: 10, outputTokens: 5 },
])
})
it("should fall back to base provider for non-DeepSeek models", async () => {
// Use default mock implementation which returns text content
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
vi.spyOn(handler, "getModel").mockReturnValue({
id: "some-other-model",
info: { maxTokens: 1024, temperature: 0.7 },
} as any)
const stream = handler.createMessage(systemPrompt, messages)
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
expect(chunks).toEqual([
{ type: "text", text: "Test response" },
{ type: "usage", inputTokens: 10, outputTokens: 5 },
])
})
it("should return default model when no model is specified", () => {
const model = handler.getModel()
expect(model.id).toBe(featherlessDefaultModelId)
expect(model.info).toEqual(expect.objectContaining(featherlessModels[featherlessDefaultModelId]))
})
it("should return specified model when valid model is provided", () => {
const testModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
const handlerWithModel = new FeatherlessHandler({
apiModelId: testModelId,
featherlessApiKey: "test-featherless-api-key",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe(testModelId)
expect(model.info).toEqual(expect.objectContaining(featherlessModels[testModelId]))
})
it("completePrompt method should return text from Featherless API", async () => {
const expectedResponse = "This is a test response from Featherless"
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
const result = await handler.completePrompt("test prompt")
expect(result).toBe(expectedResponse)
})
it("should handle errors in completePrompt", async () => {
const errorMessage = "Featherless API error"
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
await expect(handler.completePrompt("test prompt")).rejects.toThrow(
`Featherless completion error: ${errorMessage}`,
)
})
it("createMessage should yield text content from stream", async () => {
const testContent = "This is test content from Featherless stream"
mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
next: vi
.fn()
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: { content: testContent } }] },
})
.mockResolvedValueOnce({ done: true }),
}),
}
})
const stream = handler.createMessage("system prompt", [])
const firstChunk = await stream.next()
expect(firstChunk.done).toBe(false)
expect(firstChunk.value).toEqual({ type: "text", text: testContent })
})
it("createMessage should yield usage data from stream", async () => {
mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
next: vi
.fn()
.mockResolvedValueOnce({
done: false,
value: { choices: [{ delta: {} }], usage: { prompt_tokens: 10, completion_tokens: 20 } },
})
.mockResolvedValueOnce({ done: true }),
}),
}
})
const stream = handler.createMessage("system prompt", [])
const firstChunk = await stream.next()
expect(firstChunk.done).toBe(false)
expect(firstChunk.value).toEqual({ type: "usage", inputTokens: 10, outputTokens: 20 })
})
it("createMessage should pass correct parameters to Featherless client for DeepSeek R1", async () => {
const modelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
// Clear previous mocks and set up new implementation
mockCreate.mockClear()
mockCreate.mockImplementationOnce(async () => ({
[Symbol.asyncIterator]: async function* () {
// Empty stream for this test
},
}))
const handlerWithModel = new FeatherlessHandler({
apiModelId: modelId,
featherlessApiKey: "test-featherless-api-key",
})
const systemPrompt = "Test system prompt for Featherless"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message for Featherless" }]
const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages)
await messageGenerator.next()
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: modelId,
messages: [
{
role: "user",
content: `${systemPrompt}\n${messages[0].content}`,
},
],
}),
)
})
it("should apply DeepSeek default temperature for R1 models", () => {
const testModelId: FeatherlessModelId = "deepseek-ai/DeepSeek-R1-0528"
const handlerWithModel = new FeatherlessHandler({
apiModelId: testModelId,
featherlessApiKey: "test-featherless-api-key",
})
const model = handlerWithModel.getModel()
expect(model.info.temperature).toBe(DEEP_SEEK_DEFAULT_TEMPERATURE)
})
it("should use default temperature for non-DeepSeek models", () => {
const testModelId: FeatherlessModelId = "moonshotai/Kimi-K2-Instruct"
const handlerWithModel = new FeatherlessHandler({
apiModelId: testModelId,
featherlessApiKey: "test-featherless-api-key",
})
const model = handlerWithModel.getModel()
expect(model.info.temperature).toBe(0.5)
})
})

View file

@ -0,0 +1,103 @@
import { DEEP_SEEK_DEFAULT_TEMPERATURE, type FeatherlessModelId, featherlessDefaultModelId, featherlessModels } from "@roo-code/types"
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import type { ApiHandlerOptions } from "../../shared/api"
import { XmlMatcher } from "../../utils/xml-matcher"
import { convertToR1Format } from "../transform/r1-format"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
export class FeatherlessHandler extends BaseOpenAiCompatibleProvider<FeatherlessModelId> {
constructor(options: ApiHandlerOptions) {
super({
...options,
providerName: "Featherless",
baseURL: "https://api.featherless.ai/v1",
apiKey: options.featherlessApiKey,
defaultProviderModelId: featherlessDefaultModelId,
providerModels: featherlessModels,
defaultTemperature: 0.5,
})
}
private getCompletionParams(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
): OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming {
const {
id: model,
info: { maxTokens: max_tokens },
} = this.getModel()
const temperature = this.options.modelTemperature ?? this.getModel().info.temperature
return {
model,
max_tokens,
temperature,
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
stream: true,
stream_options: { include_usage: true },
}
}
override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const model = this.getModel()
if (model.id.includes("DeepSeek-R1")) {
const stream = await this.client.chat.completions.create({
...this.getCompletionParams(systemPrompt, messages),
messages: convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]),
})
const matcher = new XmlMatcher(
"think",
(chunk) =>
({
type: chunk.matched ? "reasoning" : "text",
text: chunk.data,
}) as const,
)
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
for (const processedChunk of matcher.update(delta.content)) {
yield processedChunk
}
}
if (chunk.usage) {
yield {
type: "usage",
inputTokens: chunk.usage.prompt_tokens || 0,
outputTokens: chunk.usage.completion_tokens || 0,
}
}
}
// Process any remaining content
for (const processedChunk of matcher.final()) {
yield processedChunk
}
} else {
yield* super.createMessage(systemPrompt, messages)
}
}
override getModel() {
const model = super.getModel()
const isDeepSeekR1 = model.id.includes("DeepSeek-R1")
return {
...model,
info: {
...model.info,
temperature: isDeepSeekR1 ? DEEP_SEEK_DEFAULT_TEMPERATURE : this.defaultTemperature,
},
}
}
}

View file

@ -30,3 +30,4 @@ export { XAIHandler } from "./xai"
export { ZAiHandler } from "./zai"
export { FireworksHandler } from "./fireworks"
export { RooHandler } from "./roo"
export { FeatherlessHandler } from "./featherless"

View file

@ -427,7 +427,7 @@
"@mistralai/mistralai": "^1.3.6",
"@modelcontextprotocol/sdk": "^1.9.0",
"@qdrant/js-client-rest": "^1.14.0",
"@roo-code/cloud": "^0.18.0",
"@roo-code/cloud": "^0.19.0",
"@roo-code/ipc": "workspace:^",
"@roo-code/telemetry": "workspace:^",
"@roo-code/types": "workspace:^",

View file

@ -70,6 +70,7 @@ export class ProfileValidator {
case "sambanova":
case "chutes":
case "fireworks":
case "featherless":
return profile.apiModelId
case "litellm":
return profile.litellmModelId

View file

@ -195,6 +195,7 @@ describe("ProfileValidator", () => {
"chutes",
"sambanova",
"fireworks",
"featherless",
]
apiModelProviders.forEach((provider) => {

View file

@ -31,6 +31,7 @@ import {
internationalZAiDefaultModelId,
mainlandZAiDefaultModelId,
fireworksDefaultModelId,
featherlessDefaultModelId,
ioIntelligenceDefaultModelId,
rooDefaultModelId,
} from "@roo-code/types"
@ -87,6 +88,7 @@ import {
XAI,
ZAi,
Fireworks,
Featherless,
} from "./providers"
import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants"
@ -327,6 +329,7 @@ const ApiOptions = ({
: internationalZAiDefaultModelId,
},
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
featherless: { field: "apiModelId", default: featherlessDefaultModelId },
"io-intelligence": { field: "ioIntelligenceModelId", default: ioIntelligenceDefaultModelId },
roo: { field: "apiModelId", default: rooDefaultModelId },
openai: { field: "openAiModelId" },
@ -600,6 +603,10 @@ const ApiOptions = ({
</div>
)}
{selectedProvider === "featherless" && (
<Featherless apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
)}
{selectedProviderModels.length > 0 && (
<>
<div>

View file

@ -19,6 +19,7 @@ import {
internationalZAiModels,
fireworksModels,
rooModels,
featherlessModels,
} from "@roo-code/types"
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
@ -40,6 +41,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
zai: internationalZAiModels,
fireworks: fireworksModels,
roo: rooModels,
featherless: featherlessModels,
}
export const PROVIDERS = [
@ -71,6 +73,7 @@ export const PROVIDERS = [
{ value: "sambanova", label: "SambaNova" },
{ value: "zai", label: "Z AI" },
{ value: "fireworks", label: "Fireworks AI" },
{ value: "featherless", label: "Featherless AI" },
{ value: "io-intelligence", label: "IO Intelligence" },
{ value: "roo", label: "Roo Code Cloud" },
].sort((a, b) => a.label.localeCompare(b.label))

View file

@ -0,0 +1,50 @@
import { useCallback } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
import { inputEventTransform } from "../transforms"
type FeatherlessProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const Featherless = ({ apiConfiguration, setApiConfigurationField }: FeatherlessProps) => {
const { t } = useAppTranslation()
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
field: K,
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
) =>
(event: E | Event) => {
setApiConfigurationField(field, transform(event as E))
},
[setApiConfigurationField],
)
return (
<>
<VSCodeTextField
value={apiConfiguration?.featherlessApiKey || ""}
type="password"
onInput={handleInputChange("featherlessApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.featherlessApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.featherlessApiKey && (
<VSCodeButtonLink href="https://featherless.ai/account/api-keys" appearance="secondary">
{t("settings:providers.getFeatherlessApiKey")}
</VSCodeButtonLink>
)}
</>
)
}

View file

@ -26,3 +26,4 @@ export { XAI } from "./XAI"
export { ZAi } from "./ZAi"
export { LiteLLM } from "./LiteLLM"
export { Fireworks } from "./Fireworks"
export { Featherless } from "./Featherless"

View file

@ -46,6 +46,8 @@ import {
mainlandZAiModels,
fireworksModels,
fireworksDefaultModelId,
featherlessModels,
featherlessDefaultModelId,
ioIntelligenceDefaultModelId,
ioIntelligenceModels,
rooDefaultModelId,
@ -292,6 +294,11 @@ function getSelectedModel({
const info = fireworksModels[id as keyof typeof fireworksModels]
return { id, info }
}
case "featherless": {
const id = apiConfiguration.apiModelId ?? featherlessDefaultModelId
const info = featherlessModels[id as keyof typeof featherlessModels]
return { id, info }
}
case "io-intelligence": {
const id = apiConfiguration.ioIntelligenceModelId ?? ioIntelligenceDefaultModelId
const info =

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Obtenir clau API de Chutes",
"fireworksApiKey": "Clau API de Fireworks",
"getFireworksApiKey": "Obtenir clau API de Fireworks",
"featherlessApiKey": "Clau API de Featherless",
"getFeatherlessApiKey": "Obtenir clau API de Featherless",
"ioIntelligenceApiKey": "Clau API d'IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Introdueix la teva clau d'API de IO Intelligence",
"getIoIntelligenceApiKey": "Obtenir clau API d'IO Intelligence",

View file

@ -271,6 +271,8 @@
"getChutesApiKey": "Chutes API-Schlüssel erhalten",
"fireworksApiKey": "Fireworks API-Schlüssel",
"getFireworksApiKey": "Fireworks API-Schlüssel erhalten",
"featherlessApiKey": "Featherless API-Schlüssel",
"getFeatherlessApiKey": "Featherless API-Schlüssel erhalten",
"ioIntelligenceApiKey": "IO Intelligence API-Schlüssel",
"ioIntelligenceApiKeyPlaceholder": "Gib deinen IO Intelligence API-Schlüssel ein",
"getIoIntelligenceApiKey": "IO Intelligence API-Schlüssel erhalten",

View file

@ -268,6 +268,8 @@
"getChutesApiKey": "Get Chutes API Key",
"fireworksApiKey": "Fireworks API Key",
"getFireworksApiKey": "Get Fireworks API Key",
"featherlessApiKey": "Featherless API Key",
"getFeatherlessApiKey": "Get Featherless API Key",
"ioIntelligenceApiKey": "IO Intelligence API Key",
"ioIntelligenceApiKeyPlaceholder": "Enter your IO Intelligence API key",
"getIoIntelligenceApiKey": "Get IO Intelligence API Key",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Obtener clave API de Chutes",
"fireworksApiKey": "Clave API de Fireworks",
"getFireworksApiKey": "Obtener clave API de Fireworks",
"featherlessApiKey": "Clave API de Featherless",
"getFeatherlessApiKey": "Obtener clave API de Featherless",
"ioIntelligenceApiKey": "Clave API de IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Introduce tu clave de API de IO Intelligence",
"getIoIntelligenceApiKey": "Obtener clave API de IO Intelligence",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Obtenir la clé API Chutes",
"fireworksApiKey": "Clé API Fireworks",
"getFireworksApiKey": "Obtenir la clé API Fireworks",
"featherlessApiKey": "Clé API Featherless",
"getFeatherlessApiKey": "Obtenir la clé API Featherless",
"ioIntelligenceApiKey": "Clé API IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Saisissez votre clé d'API IO Intelligence",
"getIoIntelligenceApiKey": "Obtenir la clé API IO Intelligence",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Chutes API कुंजी प्राप्त करें",
"fireworksApiKey": "Fireworks API कुंजी",
"getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें",
"featherlessApiKey": "Featherless API कुंजी",
"getFeatherlessApiKey": "Featherless API कुंजी प्राप्त करें",
"ioIntelligenceApiKey": "IO Intelligence API कुंजी",
"ioIntelligenceApiKeyPlaceholder": "अपना आईओ इंटेलिजेंस एपीआई कुंजी दर्ज करें",
"getIoIntelligenceApiKey": "IO Intelligence API कुंजी प्राप्त करें",

View file

@ -273,6 +273,8 @@
"getChutesApiKey": "Dapatkan Chutes API Key",
"fireworksApiKey": "Fireworks API Key",
"getFireworksApiKey": "Dapatkan Fireworks API Key",
"featherlessApiKey": "Featherless API Key",
"getFeatherlessApiKey": "Dapatkan Featherless API Key",
"ioIntelligenceApiKey": "IO Intelligence API Key",
"ioIntelligenceApiKeyPlaceholder": "Masukkan kunci API IO Intelligence Anda",
"getIoIntelligenceApiKey": "Dapatkan IO Intelligence API Key",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Ottieni chiave API Chutes",
"fireworksApiKey": "Chiave API Fireworks",
"getFireworksApiKey": "Ottieni chiave API Fireworks",
"featherlessApiKey": "Chiave API Featherless",
"getFeatherlessApiKey": "Ottieni chiave API Featherless",
"ioIntelligenceApiKey": "Chiave API IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Inserisci la tua chiave API IO Intelligence",
"getIoIntelligenceApiKey": "Ottieni chiave API IO Intelligence",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Chutes APIキーを取得",
"fireworksApiKey": "Fireworks APIキー",
"getFireworksApiKey": "Fireworks APIキーを取得",
"featherlessApiKey": "Featherless APIキー",
"getFeatherlessApiKey": "Featherless APIキーを取得",
"ioIntelligenceApiKey": "IO Intelligence APIキー",
"ioIntelligenceApiKeyPlaceholder": "IO Intelligence APIキーを入力してください",
"getIoIntelligenceApiKey": "IO Intelligence APIキーを取得",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Chutes API 키 받기",
"fireworksApiKey": "Fireworks API 키",
"getFireworksApiKey": "Fireworks API 키 받기",
"featherlessApiKey": "Featherless API 키",
"getFeatherlessApiKey": "Featherless API 키 받기",
"ioIntelligenceApiKey": "IO Intelligence API 키",
"ioIntelligenceApiKeyPlaceholder": "IO Intelligence API 키를 입력하세요",
"getIoIntelligenceApiKey": "IO Intelligence API 키 받기",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Chutes API-sleutel ophalen",
"fireworksApiKey": "Fireworks API-sleutel",
"getFireworksApiKey": "Fireworks API-sleutel ophalen",
"featherlessApiKey": "Featherless API-sleutel",
"getFeatherlessApiKey": "Featherless API-sleutel ophalen",
"ioIntelligenceApiKey": "IO Intelligence API-sleutel",
"ioIntelligenceApiKeyPlaceholder": "Voer je IO Intelligence API-sleutel in",
"getIoIntelligenceApiKey": "IO Intelligence API-sleutel ophalen",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Uzyskaj klucz API Chutes",
"fireworksApiKey": "Klucz API Fireworks",
"getFireworksApiKey": "Uzyskaj klucz API Fireworks",
"featherlessApiKey": "Klucz API Featherless",
"getFeatherlessApiKey": "Uzyskaj klucz API Featherless",
"ioIntelligenceApiKey": "Klucz API IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Wprowadź swój klucz API IO Intelligence",
"getIoIntelligenceApiKey": "Uzyskaj klucz API IO Intelligence",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Obter chave de API Chutes",
"fireworksApiKey": "Chave de API Fireworks",
"getFireworksApiKey": "Obter chave de API Fireworks",
"featherlessApiKey": "Chave de API Featherless",
"getFeatherlessApiKey": "Obter chave de API Featherless",
"ioIntelligenceApiKey": "Chave de API IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Insira sua chave de API da IO Intelligence",
"getIoIntelligenceApiKey": "Obter chave de API IO Intelligence",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Получить Chutes API-ключ",
"fireworksApiKey": "Fireworks API-ключ",
"getFireworksApiKey": "Получить Fireworks API-ключ",
"featherlessApiKey": "Featherless API-ключ",
"getFeatherlessApiKey": "Получить Featherless API-ключ",
"ioIntelligenceApiKey": "IO Intelligence API-ключ",
"ioIntelligenceApiKeyPlaceholder": "Введите свой ключ API IO Intelligence",
"getIoIntelligenceApiKey": "Получить IO Intelligence API-ключ",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Chutes API Anahtarı Al",
"fireworksApiKey": "Fireworks API Anahtarı",
"getFireworksApiKey": "Fireworks API Anahtarı Al",
"featherlessApiKey": "Featherless API Anahtarı",
"getFeatherlessApiKey": "Featherless API Anahtarı Al",
"ioIntelligenceApiKey": "IO Intelligence API Anahtarı",
"ioIntelligenceApiKeyPlaceholder": "IO Intelligence API anahtarınızı girin",
"getIoIntelligenceApiKey": "IO Intelligence API Anahtarı Al",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "Lấy khóa API Chutes",
"fireworksApiKey": "Khóa API Fireworks",
"getFireworksApiKey": "Lấy khóa API Fireworks",
"featherlessApiKey": "Khóa API Featherless",
"getFeatherlessApiKey": "Lấy khóa API Featherless",
"ioIntelligenceApiKey": "Khóa API IO Intelligence",
"ioIntelligenceApiKeyPlaceholder": "Nhập khóa API IO Intelligence của bạn",
"getIoIntelligenceApiKey": "Lấy khóa API IO Intelligence",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "获取 Chutes API 密钥",
"fireworksApiKey": "Fireworks API 密钥",
"getFireworksApiKey": "获取 Fireworks API 密钥",
"featherlessApiKey": "Featherless API 密钥",
"getFeatherlessApiKey": "获取 Featherless API 密钥",
"ioIntelligenceApiKey": "IO Intelligence API 密钥",
"ioIntelligenceApiKeyPlaceholder": "输入您的 IO Intelligence API 密钥",
"getIoIntelligenceApiKey": "获取 IO Intelligence API 密钥",

View file

@ -269,6 +269,8 @@
"getChutesApiKey": "取得 Chutes API 金鑰",
"fireworksApiKey": "Fireworks API 金鑰",
"getFireworksApiKey": "取得 Fireworks API 金鑰",
"featherlessApiKey": "Featherless API 金鑰",
"getFeatherlessApiKey": "取得 Featherless API 金鑰",
"ioIntelligenceApiKey": "IO Intelligence API 金鑰",
"ioIntelligenceApiKeyPlaceholder": "輸入您的 IO Intelligence API 金鑰",
"getIoIntelligenceApiKey": "取得 IO Intelligence API 金鑰",

View file

@ -126,6 +126,11 @@ function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): stri
return i18next.t("settings:validation.apiKey")
}
break
case "featherless":
if (!apiConfiguration.featherlessApiKey) {
return i18next.t("settings:validation.apiKey")
}
break
}
return undefined