This commit is contained in:
Abliteration AI 2026-08-01 11:24:14 -07:00 committed by GitHub
commit 1f57881abd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 319 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Add abliteration.ai as a provider.

View file

@ -17,6 +17,11 @@ describe("getApiKeyFromEnv", () => {
expect(getApiKeyFromEnv("anthropic")).toBe("test-anthropic-key")
})
it("should return API key from environment variable for abliteration.ai", () => {
process.env.ABLIT_KEY = "test-abliteration-key"
expect(getApiKeyFromEnv("abliteration")).toBe("test-abliteration-key")
})
it("should return API key from environment variable for openrouter", () => {
process.env.OPENROUTER_API_KEY = "test-openrouter-key"
expect(getApiKeyFromEnv("openrouter")).toBe("test-openrouter-key")

View file

@ -3,6 +3,7 @@ import { RooCodeSettings } from "@roo-code/types"
import type { SupportedProvider } from "@/types/index.js"
const envVarMap: Record<SupportedProvider, string> = {
abliteration: "ABLIT_KEY",
anthropic: "ANTHROPIC_API_KEY",
"openai-native": "OPENAI_API_KEY",
gemini: "GOOGLE_API_KEY",
@ -27,6 +28,10 @@ export function getProviderSettings(
const config: RooCodeSettings = { apiProvider: provider }
switch (provider) {
case "abliteration":
if (apiKey) config.abliterationApiKey = apiKey
if (model) config.apiModelId = model
break
case "anthropic":
if (apiKey) config.apiKey = apiKey
if (model) config.apiModelId = model

View file

@ -2,6 +2,7 @@ import type { ProviderName, ReasoningEffortExtended } from "@roo-code/types"
import type { OutputFormat } from "./json-events.js"
export const supportedProviders = [
"abliteration",
"anthropic",
"openai-native",
"gemini",

View file

@ -249,6 +249,7 @@ export type RooCodeSettings = GlobalSettings & ProviderSettings
export const SECRET_STATE_KEYS = [
"apiKey",
"openRouterApiKey",
"abliterationApiKey",
"awsAccessKey",
"awsApiKey",
"awsSecretKey",

View file

@ -3,6 +3,7 @@ import { z } from "zod"
import { modelInfoSchema, reasoningEffortSettingSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js"
import { codebaseIndexProviderSchema } from "./codebase-index.js"
import {
abliterationModels,
anthropicModels,
basetenModels,
bedrockModels,
@ -102,6 +103,7 @@ export const providerNames = [
...internalProviders,
...customProviders,
...fauxProviders,
"abliteration",
"anthropic",
"bedrock",
"baseten",
@ -201,6 +203,10 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
})
const abliterationSchema = apiModelIdProviderModelSchema.extend({
abliterationApiKey: z.string().optional(),
})
const openRouterSchema = baseProviderSettingsSchema.extend({
openRouterApiKey: z.string().optional(),
openRouterModelId: z.string().optional(),
@ -386,6 +392,7 @@ const defaultSchema = z.object({
})
export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProvider", [
abliterationSchema.merge(z.object({ apiProvider: z.literal("abliteration") })),
anthropicSchema.merge(z.object({ apiProvider: z.literal("anthropic") })),
openRouterSchema.merge(z.object({ apiProvider: z.literal("openrouter") })),
bedrockSchema.merge(z.object({ apiProvider: z.literal("bedrock") })),
@ -419,6 +426,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
export const providerSettingsSchema = z.object({
apiProvider: providerNamesWithRetiredSchema.optional(),
...abliterationSchema.shape,
...anthropicSchema.shape,
...openRouterSchema.shape,
...bedrockSchema.shape,
@ -496,6 +504,7 @@ export const isTypicalProvider = (key: unknown): key is TypicalProvider =>
isProviderName(key) && !isInternalProvider(key) && !isCustomProvider(key) && !isFauxProvider(key)
export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
abliteration: "apiModelId",
anthropic: "apiModelId",
openrouter: "openRouterModelId",
bedrock: "apiModelId",
@ -555,6 +564,11 @@ export const MODELS_BY_PROVIDER: Record<
Exclude<ProviderName, "fake-ai" | "gemini-cli" | "openai">,
{ id: ProviderName; label: string; models: string[] }
> = {
abliteration: {
id: "abliteration",
label: "abliteration.ai",
models: Object.keys(abliterationModels),
},
anthropic: {
id: "anthropic",
label: "Anthropic",

View file

@ -0,0 +1,17 @@
import type { ModelInfo } from "../model.js"
// https://docs.abliteration.ai/models
export type AbliterationModelId = "abliterated-model"
export const abliterationDefaultModelId: AbliterationModelId = "abliterated-model"
export const abliterationModels = {
"abliterated-model": {
maxTokens: 8192,
contextWindow: 150_000,
supportsImages: true,
supportsPromptCache: false,
description:
"Default abliteration.ai model. Supports OpenAI-compatible chat completions, streaming, tool calling, and vision.",
},
} as const satisfies Record<string, ModelInfo>

View file

@ -1,3 +1,4 @@
export * from "./abliteration.js"
export * from "./anthropic.js"
export * from "./baseten.js"
export * from "./bedrock.js"
@ -25,6 +26,7 @@ export * from "./vercel-ai-gateway.js"
export * from "./zai.js"
export * from "./minimax.js"
import { abliterationDefaultModelId } from "./abliteration.js"
import { anthropicDefaultModelId } from "./anthropic.js"
import { basetenDefaultModelId } from "./baseten.js"
import { bedrockDefaultModelId } from "./bedrock.js"
@ -61,6 +63,8 @@ export function getProviderDefaultModelId(
options: { isChina?: boolean } = { isChina: false },
): string {
switch (provider) {
case "abliteration":
return abliterationDefaultModelId
case "openrouter":
return openRouterDefaultModelId
case "requesty":

View file

@ -7,6 +7,7 @@ import { ApiStream } from "./transform/stream"
import {
AnthropicHandler,
AbliterationHandler,
AwsBedrockHandler,
OpenRouterHandler,
PoeHandler,
@ -121,6 +122,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
}
switch (apiProvider) {
case "abliteration":
return new AbliterationHandler(options)
case "anthropic":
return new AnthropicHandler(options)
case "openrouter":

View file

@ -0,0 +1,129 @@
// npx vitest run src/api/providers/__tests__/abliteration.spec.ts
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { abliterationDefaultModelId, abliterationModels } from "@roo-code/types"
import { AbliterationHandler } from "../abliteration"
const mockCreate = vi.fn()
vi.mock("openai", () => ({
default: vi.fn(() => ({
chat: {
completions: {
create: mockCreate,
},
},
})),
}))
describe("AbliterationHandler", () => {
let handler: AbliterationHandler
beforeEach(() => {
vi.clearAllMocks()
handler = new AbliterationHandler({ abliterationApiKey: "test-abliteration-api-key" })
})
it("should use the correct abliteration.ai base URL", () => {
new AbliterationHandler({ abliterationApiKey: "test-abliteration-api-key" })
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.abliteration.ai/v1" }))
})
it("should use the provided API key", () => {
const abliterationApiKey = "test-abliteration-api-key"
new AbliterationHandler({ abliterationApiKey })
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: abliterationApiKey }))
})
it("should throw error when API key is not provided", () => {
expect(() => new AbliterationHandler({})).toThrow("API key is required")
})
it("should return default model when no model is specified", () => {
const model = handler.getModel()
expect(model.id).toBe(abliterationDefaultModelId)
expect(model.info).toEqual(abliterationModels[abliterationDefaultModelId])
})
it("should return specified model when valid model is provided", () => {
const handlerWithModel = new AbliterationHandler({
apiModelId: "abliterated-model",
abliterationApiKey: "test-abliteration-api-key",
})
const model = handlerWithModel.getModel()
expect(model.id).toBe("abliterated-model")
expect(model.info).toEqual(abliterationModels["abliterated-model"])
})
it("completePrompt method should return text from abliteration.ai API", async () => {
const expectedResponse = "This is a test response from abliteration.ai"
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
const result = await handler.completePrompt("test prompt")
expect(result).toBe(expectedResponse)
})
it("createMessage should yield text content from stream", async () => {
const testContent = "This is test content from abliteration.ai 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 pass correct parameters to abliteration.ai client", async () => {
const modelInfo = abliterationModels[abliterationDefaultModelId]
const handlerWithModel = new AbliterationHandler({
apiModelId: abliterationDefaultModelId,
abliterationApiKey: "test-abliteration-api-key",
})
mockCreate.mockImplementationOnce(() => {
return {
[Symbol.asyncIterator]: () => ({
async next() {
return { done: true }
},
}),
}
})
const systemPrompt = "Test system prompt for abliteration.ai"
const messages: Anthropic.Messages.MessageParam[] = [
{ role: "user", content: "Test message for abliteration.ai" },
]
const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages)
await messageGenerator.next()
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: abliterationDefaultModelId,
max_tokens: modelInfo.maxTokens,
temperature: 0,
messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]),
stream: true,
stream_options: { include_usage: true },
}),
undefined,
)
})
})

View file

@ -0,0 +1,18 @@
import { type AbliterationModelId, abliterationDefaultModelId, abliterationModels } from "@roo-code/types"
import type { ApiHandlerOptions } from "../../shared/api"
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
export class AbliterationHandler extends BaseOpenAiCompatibleProvider<AbliterationModelId> {
constructor(options: ApiHandlerOptions) {
super({
...options,
providerName: "abliteration.ai",
baseURL: "https://api.abliteration.ai/v1",
apiKey: options.abliterationApiKey,
defaultProviderModelId: abliterationDefaultModelId,
providerModels: abliterationModels,
})
}
}

View file

@ -1,3 +1,4 @@
export { AbliterationHandler } from "./abliteration"
export { AnthropicVertexHandler } from "./anthropic-vertex"
export { AnthropicHandler } from "./anthropic"
export { AwsBedrockHandler } from "./bedrock"

View file

@ -53,6 +53,7 @@ export class ProfileValidator {
switch (profile.apiProvider) {
case "openai":
return profile.openAiModelId
case "abliteration":
case "anthropic":
case "openai-native":
case "bedrock":

View file

@ -168,6 +168,7 @@ describe("ProfileValidator", () => {
// Test specific providers that use apiModelId
const apiModelProviders = [
"abliteration",
"anthropic",
"openai-native",
"bedrock",

View file

@ -9,6 +9,7 @@ import {
type ProviderSettings,
isRetiredProvider,
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
abliterationDefaultModelId,
openRouterDefaultModelId,
poeDefaultModelId,
requestyDefaultModelId,
@ -92,6 +93,7 @@ import {
Fireworks,
VercelAiGateway,
MiniMax,
Abliteration,
} from "./providers"
import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants"
@ -358,6 +360,7 @@ const ApiOptions = ({
},
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
poe: { field: "apiModelId", default: poeDefaultModelId },
abliteration: { field: "apiModelId", default: abliterationDefaultModelId },
"vercel-ai-gateway": { field: "vercelAiGatewayModelId", default: vercelAiGatewayDefaultModelId },
openai: { field: "openAiModelId" },
ollama: { field: "ollamaModelId" },
@ -530,6 +533,13 @@ const ApiOptions = ({
/>
)}
{selectedProvider === "abliteration" && (
<Abliteration
apiConfiguration={apiConfiguration}
setApiConfigurationField={setApiConfigurationField}
/>
)}
{selectedProvider === "openai-codex" && (
<OpenAICodex
apiConfiguration={apiConfiguration}

View file

@ -1,6 +1,7 @@
import {
type ProviderName,
type ModelInfo,
abliterationModels,
anthropicModels,
bedrockModels,
deepSeekModels,
@ -20,6 +21,7 @@ import {
} from "@roo-code/types"
export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, ModelInfo>>> = {
abliteration: abliterationModels,
anthropic: anthropicModels,
bedrock: bedrockModels,
deepseek: deepSeekModels,
@ -39,6 +41,7 @@ export const MODELS_BY_PROVIDER: Partial<Record<ProviderName, Record<string, Mod
}
export const PROVIDERS = [
{ value: "abliteration", label: "abliteration.ai", proxy: false },
{ value: "openrouter", label: "OpenRouter", proxy: false },
{ value: "anthropic", label: "Anthropic", proxy: false },
{ value: "gemini", label: "Google Gemini", proxy: false },

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 AbliterationProps = {
apiConfiguration: ProviderSettings
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
}
export const Abliteration = ({ apiConfiguration, setApiConfigurationField }: AbliterationProps) => {
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?.abliterationApiKey || ""}
type="password"
onInput={handleInputChange("abliterationApiKey")}
placeholder={t("settings:placeholders.apiKey")}
className="w-full">
<label className="block font-medium mb-1">{t("settings:providers.abliterationApiKey")}</label>
</VSCodeTextField>
<div className="text-sm text-vscode-descriptionForeground -mt-2">
{t("settings:providers.apiKeyStorageNotice")}
</div>
{!apiConfiguration?.abliterationApiKey && (
<VSCodeButtonLink href="https://abliteration.ai" appearance="secondary">
{t("settings:providers.getAbliterationApiKey")}
</VSCodeButtonLink>
)}
</>
)
}

View file

@ -1,3 +1,4 @@
export { Abliteration } from "./Abliteration"
export { Anthropic } from "./Anthropic"
export { Bedrock } from "./Bedrock"
export { DeepSeek } from "./DeepSeek"

View file

@ -1,5 +1,6 @@
import type { ProviderName, ModelInfo, ProviderSettings } from "@roo-code/types"
import {
abliterationDefaultModelId,
anthropicDefaultModelId,
bedrockDefaultModelId,
deepSeekDefaultModelId,
@ -26,6 +27,7 @@ export interface ProviderServiceConfig {
}
export const PROVIDER_SERVICE_CONFIG: Partial<Record<ProviderName, ProviderServiceConfig>> = {
abliteration: { serviceName: "abliteration.ai", serviceUrl: "https://abliteration.ai" },
anthropic: { serviceName: "Anthropic", serviceUrl: "https://console.anthropic.com" },
bedrock: { serviceName: "Amazon Bedrock", serviceUrl: "https://aws.amazon.com/bedrock" },
deepseek: { serviceName: "DeepSeek", serviceUrl: "https://platform.deepseek.com" },
@ -50,6 +52,7 @@ export const PROVIDER_SERVICE_CONFIG: Partial<Record<ProviderName, ProviderServi
}
export const PROVIDER_DEFAULT_MODEL_IDS: Partial<Record<ProviderName, string>> = {
abliteration: abliterationDefaultModelId,
anthropic: anthropicDefaultModelId,
bedrock: bedrockDefaultModelId,
deepseek: deepSeekDefaultModelId,

View file

@ -4,6 +4,7 @@ import {
type ModelInfo,
type ModelRecord,
type RouterModels,
abliterationModels,
anthropicModels,
bedrockModels,
deepSeekModels,
@ -174,6 +175,11 @@ function getSelectedModel({
const info = xaiModels[id as keyof typeof xaiModels]
return info ? { id, info } : { id, info: undefined }
}
case "abliteration": {
const id = apiConfiguration.apiModelId ?? defaultModelId
const info = abliterationModels[id as keyof typeof abliterationModels]
return { id, info }
}
case "baseten": {
const id = apiConfiguration.apiModelId ?? defaultModelId
const info = basetenModels[id as keyof typeof basetenModels]

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "URL base de Poe",
"fireworksApiKey": "Clau API de Fireworks",
"getFireworksApiKey": "Obtenir clau API de Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Clau API de DeepSeek",
"getDeepSeekApiKey": "Obtenir clau API de DeepSeek",
"moonshotApiKey": "Clau API de Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe Basis-URL",
"fireworksApiKey": "Fireworks API-Schlüssel",
"getFireworksApiKey": "Fireworks API-Schlüssel erhalten",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API-Schlüssel",
"getDeepSeekApiKey": "DeepSeek API-Schlüssel erhalten",
"moonshotApiKey": "Moonshot API-Schlüssel",

View file

@ -439,6 +439,8 @@
"poeBaseUrl": "Poe Base URL",
"fireworksApiKey": "Fireworks API Key",
"getFireworksApiKey": "Get Fireworks API Key",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Get DeepSeek API Key",
"moonshotApiKey": "Moonshot API Key",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "URL base de Poe",
"fireworksApiKey": "Clave API de Fireworks",
"getFireworksApiKey": "Obtener clave API de Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Clave API de DeepSeek",
"getDeepSeekApiKey": "Obtener clave API de DeepSeek",
"moonshotApiKey": "Clave API de Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "URL de base Poe",
"fireworksApiKey": "Clé API Fireworks",
"getFireworksApiKey": "Obtenir la clé API Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Clé API DeepSeek",
"getDeepSeekApiKey": "Obtenir la clé API DeepSeek",
"moonshotApiKey": "Clé API Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe बेस URL",
"fireworksApiKey": "Fireworks API कुंजी",
"getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API कुंजी",
"getDeepSeekApiKey": "DeepSeek API कुंजी प्राप्त करें",
"moonshotApiKey": "Moonshot API कुंजी",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe Base URL",
"fireworksApiKey": "Fireworks API Key",
"getFireworksApiKey": "Dapatkan Fireworks API Key",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API Key",
"getDeepSeekApiKey": "Dapatkan DeepSeek API Key",
"moonshotApiKey": "Kunci API Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "URL base Poe",
"fireworksApiKey": "Chiave API Fireworks",
"getFireworksApiKey": "Ottieni chiave API Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Chiave API DeepSeek",
"getDeepSeekApiKey": "Ottieni chiave API DeepSeek",
"moonshotApiKey": "Chiave API Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe ベースURL",
"fireworksApiKey": "Fireworks APIキー",
"getFireworksApiKey": "Fireworks APIキーを取得",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek APIキー",
"getDeepSeekApiKey": "DeepSeek APIキーを取得",
"moonshotApiKey": "Moonshot APIキー",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe 기본 URL",
"fireworksApiKey": "Fireworks API 키",
"getFireworksApiKey": "Fireworks API 키 받기",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API 키",
"getDeepSeekApiKey": "DeepSeek API 키 받기",
"moonshotApiKey": "Moonshot API 키",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe Basis-URL",
"fireworksApiKey": "Fireworks API-sleutel",
"getFireworksApiKey": "Fireworks API-sleutel ophalen",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API-sleutel",
"getDeepSeekApiKey": "DeepSeek API-sleutel ophalen",
"moonshotApiKey": "Moonshot API-sleutel",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Bazowy URL Poe",
"fireworksApiKey": "Klucz API Fireworks",
"getFireworksApiKey": "Uzyskaj klucz API Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Klucz API DeepSeek",
"getDeepSeekApiKey": "Uzyskaj klucz API DeepSeek",
"moonshotApiKey": "Klucz API Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "URL base do Poe",
"fireworksApiKey": "Chave de API Fireworks",
"getFireworksApiKey": "Obter chave de API Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Chave de API DeepSeek",
"getDeepSeekApiKey": "Obter chave de API DeepSeek",
"moonshotApiKey": "Chave de API Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Базовый URL Poe",
"fireworksApiKey": "Fireworks API-ключ",
"getFireworksApiKey": "Получить Fireworks API-ключ",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API-ключ",
"getDeepSeekApiKey": "Получить DeepSeek API-ключ",
"moonshotApiKey": "Moonshot API-ключ",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe Temel URL",
"fireworksApiKey": "Fireworks API Anahtarı",
"getFireworksApiKey": "Fireworks API Anahtarı Al",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API Anahtarı",
"getDeepSeekApiKey": "DeepSeek API Anahtarı Al",
"moonshotApiKey": "Moonshot API Anahtarı",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "URL cơ sở Poe",
"fireworksApiKey": "Khóa API Fireworks",
"getFireworksApiKey": "Lấy khóa API Fireworks",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "Khóa API DeepSeek",
"getDeepSeekApiKey": "Lấy khóa API DeepSeek",
"moonshotApiKey": "Khóa API Moonshot",

View file

@ -377,6 +377,8 @@
"poeBaseUrl": "Poe 基础 URL",
"fireworksApiKey": "Fireworks API 密钥",
"getFireworksApiKey": "获取 Fireworks API 密钥",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API 密钥",
"getDeepSeekApiKey": "获取 DeepSeek API 密钥",
"moonshotApiKey": "Moonshot API 密钥",

View file

@ -387,6 +387,8 @@
"poeBaseUrl": "Poe 基礎 URL",
"fireworksApiKey": "Fireworks API 金鑰",
"getFireworksApiKey": "取得 Fireworks API 金鑰",
"abliterationApiKey": "abliteration.ai API Key",
"getAbliterationApiKey": "Get abliteration.ai API Key",
"deepSeekApiKey": "DeepSeek API 金鑰",
"getDeepSeekApiKey": "取得 DeepSeek API 金鑰",
"moonshotApiKey": "Moonshot API 金鑰",

View file

@ -38,6 +38,11 @@ export function validateApiConfiguration(
function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): string | undefined {
switch (apiConfiguration.apiProvider) {
case "abliteration":
if (!apiConfiguration.abliterationApiKey) {
return i18next.t("settings:validation.apiKey")
}
break
case "openrouter":
if (!apiConfiguration.openRouterApiKey) {
return i18next.t("settings:validation.apiKey")