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

Commit cb602b8

Browse files
Add abliteration.ai provider
1 parent b867ec9 commit cb602b8

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",
@@ -27,6 +28,10 @@ export function getProviderSettings(
2728
const config: RooCodeSettings = { apiProvider: provider }
2829

2930
switch (provider) {
31+
case "abliteration":
32+
if (apiKey) config.abliterationApiKey = apiKey
33+
if (model) config.apiModelId = model
34+
break
3035
case "anthropic":
3136
if (apiKey) config.apiKey = apiKey
3237
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
@@ -249,6 +249,7 @@ export type RooCodeSettings = GlobalSettings & ProviderSettings
249249
export const SECRET_STATE_KEYS = [
250250
"apiKey",
251251
"openRouterApiKey",
252+
"abliterationApiKey",
252253
"awsAccessKey",
253254
"awsApiKey",
254255
"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,
@@ -102,6 +103,7 @@ export const providerNames = [
102103
...internalProviders,
103104
...customProviders,
104105
...fauxProviders,
106+
"abliteration",
105107
"anthropic",
106108
"bedrock",
107109
"baseten",
@@ -201,6 +203,10 @@ const anthropicSchema = apiModelIdProviderModelSchema.extend({
201203
anthropicBeta1MContext: z.boolean().optional(), // Enable 'context-1m-2025-08-07' beta for 1M context window.
202204
})
203205

206+
const abliterationSchema = apiModelIdProviderModelSchema.extend({
207+
abliterationApiKey: z.string().optional(),
208+
})
209+
204210
const openRouterSchema = baseProviderSettingsSchema.extend({
205211
openRouterApiKey: z.string().optional(),
206212
openRouterModelId: z.string().optional(),
@@ -386,6 +392,7 @@ const defaultSchema = z.object({
386392
})
387393

388394
export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProvider", [
395+
abliterationSchema.merge(z.object({ apiProvider: z.literal("abliteration") })),
389396
anthropicSchema.merge(z.object({ apiProvider: z.literal("anthropic") })),
390397
openRouterSchema.merge(z.object({ apiProvider: z.literal("openrouter") })),
391398
bedrockSchema.merge(z.object({ apiProvider: z.literal("bedrock") })),
@@ -419,6 +426,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
419426

420427
export const providerSettingsSchema = z.object({
421428
apiProvider: providerNamesWithRetiredSchema.optional(),
429+
...abliterationSchema.shape,
422430
...anthropicSchema.shape,
423431
...openRouterSchema.shape,
424432
...bedrockSchema.shape,
@@ -496,6 +504,7 @@ export const isTypicalProvider = (key: unknown): key is TypicalProvider =>
496504
isProviderName(key) && !isInternalProvider(key) && !isCustomProvider(key) && !isFauxProvider(key)
497505

498506
export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
507+
abliteration: "apiModelId",
499508
anthropic: "apiModelId",
500509
openrouter: "openRouterModelId",
501510
bedrock: "apiModelId",
@@ -555,6 +564,11 @@ export const MODELS_BY_PROVIDER: Record<
555564
Exclude<ProviderName, "fake-ai" | "gemini-cli" | "openai">,
556565
{ id: ProviderName; label: string; models: string[] }
557566
> = {
567+
abliteration: {
568+
id: "abliteration",
569+
label: "abliteration.ai",
570+
models: Object.keys(abliterationModels),
571+
},
558572
anthropic: {
559573
id: "anthropic",
560574
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"
@@ -25,6 +26,7 @@ export * from "./vercel-ai-gateway.js"
2526
export * from "./zai.js"
2627
export * from "./minimax.js"
2728

29+
import { abliterationDefaultModelId } from "./abliteration.js"
2830
import { anthropicDefaultModelId } from "./anthropic.js"
2931
import { basetenDefaultModelId } from "./baseten.js"
3032
import { bedrockDefaultModelId } from "./bedrock.js"
@@ -61,6 +63,8 @@ export function getProviderDefaultModelId(
6163
options: { isChina?: boolean } = { isChina: false },
6264
): string {
6365
switch (provider) {
66+
case "abliteration":
67+
return abliterationDefaultModelId
6468
case "openrouter":
6569
return openRouterDefaultModelId
6670
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,
@@ -121,6 +122,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
121122
}
122123

123124
switch (apiProvider) {
125+
case "abliteration":
126+
return new AbliterationHandler(options)
124127
case "anthropic":
125128
return new AnthropicHandler(options)
126129
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)