Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit e095a76

Browse files
Add abliteration.ai provider
1 parent ad25634 commit e095a76

39 files changed

Lines changed: 319 additions & 0 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Add abliteration.ai as a provider.

apps/cli/src/lib/utils/__tests__/provider.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ describe("getApiKeyFromEnv", () => {
1717
expect(getApiKeyFromEnv("anthropic")).toBe("test-anthropic-key")
1818
})
1919

20+
it("should return API key from environment variable for abliteration.ai", () => {
21+
process.env.ABLIT_KEY = "test-abliteration-key"
22+
expect(getApiKeyFromEnv("abliteration")).toBe("test-abliteration-key")
23+
})
24+
2025
it("should return API key from environment variable for openrouter", () => {
2126
process.env.OPENROUTER_API_KEY = "test-openrouter-key"
2227
expect(getApiKeyFromEnv("openrouter")).toBe("test-openrouter-key")

apps/cli/src/lib/utils/provider.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { RooCodeSettings } from "@roo-code/types"
33
import type { SupportedProvider } from "@/types/index.js"
44

55
const envVarMap: Record<SupportedProvider, string> = {
6+
abliteration: "ABLIT_KEY",
67
anthropic: "ANTHROPIC_API_KEY",
78
"openai-native": "OPENAI_API_KEY",
89
gemini: "GOOGLE_API_KEY",
@@ -28,6 +29,10 @@ export function getProviderSettings(
2829
const config: RooCodeSettings = { apiProvider: provider }
2930

3031
switch (provider) {
32+
case "abliteration":
33+
if (apiKey) config.abliterationApiKey = apiKey
34+
if (model) config.apiModelId = model
35+
break
3136
case "anthropic":
3237
if (apiKey) config.apiKey = apiKey
3338
if (model) config.apiModelId = model

apps/cli/src/types/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ProviderName, ReasoningEffortExtended } from "@roo-code/types"
22
import type { OutputFormat } from "./json-events.js"
33

44
export const supportedProviders = [
5+
"abliteration",
56
"anthropic",
67
"openai-native",
78
"gemini",

packages/types/src/global-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ export type RooCodeSettings = GlobalSettings & ProviderSettings
252252
export const SECRET_STATE_KEYS = [
253253
"apiKey",
254254
"openRouterApiKey",
255+
"abliterationApiKey",
255256
"awsAccessKey",
256257
"awsApiKey",
257258
"awsSecretKey",

packages/types/src/provider-settings.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { z } from "zod"
33
import { modelInfoSchema, reasoningEffortSettingSchema, verbosityLevelsSchema, serviceTierSchema } from "./model.js"
44
import { codebaseIndexProviderSchema } from "./codebase-index.js"
55
import {
6+
abliterationModels,
67
anthropicModels,
78
basetenModels,
89
bedrockModels,
@@ -110,6 +111,7 @@ export const providerNames = [
110111
...internalProviders,
111112
...customProviders,
112113
...fauxProviders,
114+
"abliteration",
113115
"anthropic",
114116
"bedrock",
115117
"baseten",
@@ -209,6 +211,10 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
209211
anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
210212
})
211213

214+
const abliterationSchema = apiModelIdProviderModelSchema.extend({
215+
abliterationApiKey: z.string().optional(),
216+
})
217+
212218
const openRouterSchema = baseProviderSettingsSchema.extend({
213219
openRouterApiKey: z.string().optional(),
214220
openRouterModelId: z.string().optional(),
@@ -399,6 +405,7 @@ const defaultSchema = z.object({
399405
})
400406

401407
export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProvider", [
408+
abliterationSchema.merge(z.object({ apiProvider: z.literal("abliteration") })),
402409
anthropicSchema.merge(z.object({ apiProvider: z.literal("anthropic") })),
403410
openRouterSchema.merge(z.object({ apiProvider: z.literal("openrouter") })),
404411
bedrockSchema.merge(z.object({ apiProvider: z.literal("bedrock") })),
@@ -433,6 +440,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
433440

434441
export const providerSettingsSchema = z.object({
435442
apiProvider: providerNamesWithRetiredSchema.optional(),
443+
...abliterationSchema.shape,
436444
...anthropicSchema.shape,
437445
...openRouterSchema.shape,
438446
...bedrockSchema.shape,
@@ -511,6 +519,7 @@ export const isTypicalProvider = (key: unknown): key is TypicalProvider =>
511519
isProviderName(key) && !isInternalProvider(key) && !isCustomProvider(key) && !isFauxProvider(key)
512520

513521
export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
522+
abliteration: "apiModelId",
514523
anthropic: "apiModelId",
515524
openrouter: "openRouterModelId",
516525
bedrock: "apiModelId",
@@ -576,6 +585,11 @@ export const MODELS_BY_PROVIDER: Record<
576585
Exclude<ProviderName, "fake-ai" | "gemini-cli" | "openai">,
577586
{ id: ProviderName; label: string; models: string[] }
578587
> = {
588+
abliteration: {
589+
id: "abliteration",
590+
label: "abliteration.ai",
591+
models: Object.keys(abliterationModels),
592+
},
579593
anthropic: {
580594
id: "anthropic",
581595
label: "Anthropic",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ModelInfo } from "../model.js"
2+
3+
// https://docs.abliteration.ai/models
4+
export type AbliterationModelId = "abliterated-model"
5+
6+
export const abliterationDefaultModelId: AbliterationModelId = "abliterated-model"
7+
8+
export const abliterationModels = {
9+
"abliterated-model": {
10+
maxTokens: 8192,
11+
contextWindow: 150_000,
12+
supportsImages: true,
13+
supportsPromptCache: false,
14+
description:
15+
"Default abliteration.ai model. Supports OpenAI-compatible chat completions, streaming, tool calling, and vision.",
16+
},
17+
} as const satisfies Record<string, ModelInfo>

packages/types/src/providers/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./abliteration.js"
12
export * from "./anthropic.js"
23
export * from "./baseten.js"
34
export * from "./bedrock.js"
@@ -26,6 +27,7 @@ export * from "./vercel-ai-gateway.js"
2627
export * from "./zai.js"
2728
export * from "./minimax.js"
2829

30+
import { abliterationDefaultModelId } from "./abliteration.js"
2931
import { anthropicDefaultModelId } from "./anthropic.js"
3032
import { basetenDefaultModelId } from "./baseten.js"
3133
import { bedrockDefaultModelId } from "./bedrock.js"
@@ -63,6 +65,8 @@ export function getProviderDefaultModelId(
6365
options: { isChina?: boolean } = { isChina: false },
6466
): string {
6567
switch (provider) {
68+
case "abliteration":
69+
return abliterationDefaultModelId
6670
case "openrouter":
6771
return openRouterDefaultModelId
6872
case "requesty":

src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ApiStream } from "./transform/stream"
77

88
import {
99
AnthropicHandler,
10+
AbliterationHandler,
1011
AwsBedrockHandler,
1112
OpenRouterHandler,
1213
PoeHandler,
@@ -119,6 +120,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
119120
}
120121

121122
switch (apiProvider) {
123+
case "abliteration":
124+
return new AbliterationHandler(options)
122125
case "anthropic":
123126
return new AnthropicHandler(options)
124127
case "openrouter":
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// npx vitest run src/api/providers/__tests__/abliteration.spec.ts
2+
3+
import { Anthropic } from "@anthropic-ai/sdk"
4+
import OpenAI from "openai"
5+
6+
import { abliterationDefaultModelId, abliterationModels } from "@roo-code/types"
7+
8+
import { AbliterationHandler } from "../abliteration"
9+
10+
const mockCreate = vi.fn()
11+
12+
vi.mock("openai", () => ({
13+
default: vi.fn(() => ({
14+
chat: {
15+
completions: {
16+
create: mockCreate,
17+
},
18+
},
19+
})),
20+
}))
21+
22+
describe("AbliterationHandler", () => {
23+
let handler: AbliterationHandler
24+
25+
beforeEach(() => {
26+
vi.clearAllMocks()
27+
handler = new AbliterationHandler({ abliterationApiKey: "test-abliteration-api-key" })
28+
})
29+
30+
it("should use the correct abliteration.ai base URL", () => {
31+
new AbliterationHandler({ abliterationApiKey: "test-abliteration-api-key" })
32+
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ baseURL: "https://api.abliteration.ai/v1" }))
33+
})
34+
35+
it("should use the provided API key", () => {
36+
const abliterationApiKey = "test-abliteration-api-key"
37+
new AbliterationHandler({ abliterationApiKey })
38+
expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: abliterationApiKey }))
39+
})
40+
41+
it("should throw error when API key is not provided", () => {
42+
expect(() => new AbliterationHandler({})).toThrow("API key is required")
43+
})
44+
45+
it("should return default model when no model is specified", () => {
46+
const model = handler.getModel()
47+
expect(model.id).toBe(abliterationDefaultModelId)
48+
expect(model.info).toEqual(abliterationModels[abliterationDefaultModelId])
49+
})
50+
51+
it("should return specified model when valid model is provided", () => {
52+
const handlerWithModel = new AbliterationHandler({
53+
apiModelId: "abliterated-model",
54+
abliterationApiKey: "test-abliteration-api-key",
55+
})
56+
const model = handlerWithModel.getModel()
57+
expect(model.id).toBe("abliterated-model")
58+
expect(model.info).toEqual(abliterationModels["abliterated-model"])
59+
})
60+
61+
it("completePrompt method should return text from abliteration.ai API", async () => {
62+
const expectedResponse = "This is a test response from abliteration.ai"
63+
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
64+
const result = await handler.completePrompt("test prompt")
65+
expect(result).toBe(expectedResponse)
66+
})
67+
68+
it("createMessage should yield text content from stream", async () => {
69+
const testContent = "This is test content from abliteration.ai stream"
70+
71+
mockCreate.mockImplementationOnce(() => {
72+
return {
73+
[Symbol.asyncIterator]: () => ({
74+
next: vi
75+
.fn()
76+
.mockResolvedValueOnce({
77+
done: false,
78+
value: { choices: [{ delta: { content: testContent } }] },
79+
})
80+
.mockResolvedValueOnce({ done: true }),
81+
}),
82+
}
83+
})
84+
85+
const stream = handler.createMessage("system prompt", [])
86+
const firstChunk = await stream.next()
87+
88+
expect(firstChunk.done).toBe(false)
89+
expect(firstChunk.value).toEqual({ type: "text", text: testContent })
90+
})
91+
92+
it("createMessage should pass correct parameters to abliteration.ai client", async () => {
93+
const modelInfo = abliterationModels[abliterationDefaultModelId]
94+
const handlerWithModel = new AbliterationHandler({
95+
apiModelId: abliterationDefaultModelId,
96+
abliterationApiKey: "test-abliteration-api-key",
97+
})
98+
99+
mockCreate.mockImplementationOnce(() => {
100+
return {
101+
[Symbol.asyncIterator]: () => ({
102+
async next() {
103+
return { done: true }
104+
},
105+
}),
106+
}
107+
})
108+
109+
const systemPrompt = "Test system prompt for abliteration.ai"
110+
const messages: Anthropic.Messages.MessageParam[] = [
111+
{ role: "user", content: "Test message for abliteration.ai" },
112+
]
113+
114+
const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages)
115+
await messageGenerator.next()
116+
117+
expect(mockCreate).toHaveBeenCalledWith(
118+
expect.objectContaining({
119+
model: abliterationDefaultModelId,
120+
max_tokens: modelInfo.maxTokens,
121+
temperature: 0,
122+
messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]),
123+
stream: true,
124+
stream_options: { include_usage: true },
125+
}),
126+
undefined,
127+
)
128+
})
129+
})

0 commit comments

Comments
 (0)