mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
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:
parent
c608392a85
commit
57ea6257dd
38 changed files with 583 additions and 11 deletions
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -25,6 +25,7 @@ body:
|
|||
- AWS Bedrock
|
||||
- Chutes AI
|
||||
- DeepSeek
|
||||
- Featherless AI
|
||||
- Fireworks AI
|
||||
- Glama
|
||||
- Google Gemini
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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]>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
58
packages/types/src/providers/featherless.ts
Normal file
58
packages/types/src/providers/featherless.ts
Normal 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"
|
||||
|
|
@ -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
18
pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
286
src/api/providers/__tests__/featherless.spec.ts
Normal file
286
src/api/providers/__tests__/featherless.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
103
src/api/providers/featherless.ts
Normal file
103
src/api/providers/featherless.ts
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,3 +30,4 @@ export { XAIHandler } from "./xai"
|
|||
export { ZAiHandler } from "./zai"
|
||||
export { FireworksHandler } from "./fireworks"
|
||||
export { RooHandler } from "./roo"
|
||||
export { FeatherlessHandler } from "./featherless"
|
||||
|
|
|
|||
|
|
@ -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:^",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export class ProfileValidator {
|
|||
case "sambanova":
|
||||
case "chutes":
|
||||
case "fireworks":
|
||||
case "featherless":
|
||||
return profile.apiModelId
|
||||
case "litellm":
|
||||
return profile.litellmModelId
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ describe("ProfileValidator", () => {
|
|||
"chutes",
|
||||
"sambanova",
|
||||
"fireworks",
|
||||
"featherless",
|
||||
]
|
||||
|
||||
apiModelProviders.forEach((provider) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
50
webview-ui/src/components/settings/providers/Featherless.tsx
Normal file
50
webview-ui/src/components/settings/providers/Featherless.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -26,3 +26,4 @@ export { XAI } from "./XAI"
|
|||
export { ZAi } from "./ZAi"
|
||||
export { LiteLLM } from "./LiteLLM"
|
||||
export { Fireworks } from "./Fireworks"
|
||||
export { Featherless } from "./Featherless"
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ca/settings.json
generated
2
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/de/settings.json
generated
2
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/es/settings.json
generated
2
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/fr/settings.json
generated
2
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/hi/settings.json
generated
2
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -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 कुंजी प्राप्त करें",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/id/settings.json
generated
2
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/it/settings.json
generated
2
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ja/settings.json
generated
2
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -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キーを取得",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ko/settings.json
generated
2
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -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 키 받기",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/nl/settings.json
generated
2
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/pl/settings.json
generated
2
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
2
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/ru/settings.json
generated
2
webview-ui/src/i18n/locales/ru/settings.json
generated
|
|
@ -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-ключ",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/tr/settings.json
generated
2
webview-ui/src/i18n/locales/tr/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/vi/settings.json
generated
2
webview-ui/src/i18n/locales/vi/settings.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
2
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
|
|
@ -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 密钥",
|
||||
|
|
|
|||
2
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
2
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
|
|
@ -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 金鑰",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue