Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ body:
- Human Relay Provider
- LiteLLM
- LM Studio
- MakeHub
- Mistral AI
- Ollama
- OpenAI
Expand Down
4 changes: 4 additions & 0 deletions evals/apps/web/src/app/runs/new/new-run.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export function NewRun() {
ollamaModelId,
lmStudioModelId,
openAiModelId,
makehubModelId,
} = providerSettings

switch (apiProvider) {
Expand Down Expand Up @@ -210,6 +211,9 @@ export function NewRun() {
case "lmstudio":
setValue("model", lmStudioModelId ?? "")
break
case "makehub":
setValue("model", makehubModelId ?? "")
break
default:
throw new Error(`Unsupported API provider: ${apiProvider}`)
}
Expand Down
17 changes: 17 additions & 0 deletions evals/packages/types/src/roo-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const providerNames = [
"human-relay",
"fake-ai",
"xai",
"makehub",
] as const

export const providerNamesSchema = z.enum(providerNames)
Expand Down Expand Up @@ -477,6 +478,11 @@ const litellmSchema = z.object({
litellmModelId: z.string().optional(),
})

const makehubSchema = z.object({
makehubApiKey: z.string().optional(),
makehubModelId: z.string().optional(),
})

const defaultSchema = z.object({
apiProvider: z.undefined(),
})
Expand Down Expand Up @@ -588,6 +594,11 @@ export const providerSettingsSchemaDiscriminated = z
apiProvider: z.literal("litellm"),
}),
),
makehubSchema.merge(
z.object({
apiProvider: z.literal("makehub"),
}),
),
defaultSchema,
])
.and(genericProviderSettingsSchema)
Expand Down Expand Up @@ -616,6 +627,7 @@ export const providerSettingsSchema = z.object({
...chutesSchema.shape,
...litellmSchema.shape,
...genericProviderSettingsSchema.shape,
...makehubSchema.shape,
})

export type ProviderSettings = z.infer<typeof providerSettingsSchema>
Expand Down Expand Up @@ -715,6 +727,9 @@ const providerSettingsRecord: ProviderSettingsRecord = {
litellmBaseUrl: undefined,
litellmApiKey: undefined,
litellmModelId: undefined,
// MakeHub
makehubApiKey: undefined,
makehubModelId: undefined,
}

export const PROVIDER_SETTINGS_KEYS = Object.keys(providerSettingsRecord) as Keys<ProviderSettings>[]
Expand Down Expand Up @@ -909,6 +924,7 @@ export type SecretState = Pick<
| "unboundApiKey"
| "requestyApiKey"
| "xaiApiKey"
| "makehubApiKey"
>

type SecretStateRecord = Record<Keys<SecretState>, undefined>
Expand All @@ -928,6 +944,7 @@ const secretStateRecord: SecretStateRecord = {
unboundApiKey: undefined,
requestyApiKey: undefined,
xaiApiKey: undefined,
makehubApiKey: undefined,
}

export const SECRET_STATE_KEYS = Object.keys(secretStateRecord) as Keys<SecretState>[]
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export type SecretState = Pick<
| "groqApiKey"
| "chutesApiKey"
| "litellmApiKey"
| "makehubApiKey"
| "codeIndexOpenAiKey"
| "codeIndexQdrantApiKey"
>
Expand All @@ -243,6 +244,7 @@ export const SECRET_STATE_KEYS = keysOf<SecretState>()([
"groqApiKey",
"chutesApiKey",
"litellmApiKey",
"makehubApiKey",
"codeIndexOpenAiKey",
"codeIndexQdrantApiKey",
])
Expand Down
13 changes: 13 additions & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const providerNames = [
"groq",
"chutes",
"litellm",
"makehub",
] as const

export const providerNamesSchema = z.enum(providerNames)
Expand Down Expand Up @@ -202,6 +203,12 @@ const litellmSchema = baseProviderSettingsSchema.extend({
litellmModelId: z.string().optional(),
})

const makehubSchema = baseProviderSettingsSchema.extend({
makehubApiKey: z.string().optional(),
makehubModelId: z.string().optional(),
makehubPerfRatio: z.number().optional(),
})

const defaultSchema = z.object({
apiProvider: z.undefined(),
})
Expand All @@ -228,6 +235,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
groqSchema.merge(z.object({ apiProvider: z.literal("groq") })),
chutesSchema.merge(z.object({ apiProvider: z.literal("chutes") })),
litellmSchema.merge(z.object({ apiProvider: z.literal("litellm") })),
makehubSchema.merge(z.object({ apiProvider: z.literal("makehub") })),
defaultSchema,
])

Expand All @@ -254,6 +262,7 @@ export const providerSettingsSchema = z.object({
...groqSchema.shape,
...chutesSchema.shape,
...litellmSchema.shape,
...makehubSchema.shape,
...codebaseIndexProviderSchema.shape,
})

Expand Down Expand Up @@ -357,4 +366,8 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
"litellmBaseUrl",
"litellmApiKey",
"litellmModelId",
// MakeHub
"makehubApiKey",
"makehubModelId",
"makehubPerfRatio",
])
3 changes: 3 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
GroqHandler,
ChutesHandler,
LiteLLMHandler,
MakeHubHandler,
} from "./providers"

export interface SingleCompletionHandler {
Expand Down Expand Up @@ -106,6 +107,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
return new ChutesHandler(options)
case "litellm":
return new LiteLLMHandler(options)
case "makehub":
return new MakeHubHandler(options)
default:
return new AnthropicHandler(options)
}
Expand Down
140 changes: 140 additions & 0 deletions src/api/providers/fetchers/makehub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import axios from "axios"
import type { ModelRecord } from "../../../shared/api"

const MAKEHUB_BASE_URL = "https://api.makehub.ai/v1"

interface MakehubModelResponse {
data: Array<{
context: number
model_id: string
model_name: string
display_name?: string
organisation: string
price_per_input_token: number
price_per_output_token: number
provider_name: string
quantisation: string | null
max_tokens?: number
supports_images?: boolean
supports_prompt_cache?: boolean
cache_writes_price?: number
cache_reads_price?: number
assistant_ready: boolean
providers_available?: string[]
thinking_config?: {
max_budget?: number
output_price?: number
}
tiers?: Array<{
context_window: number
input_price?: number
output_price?: number
cache_writes_price?: number
cache_reads_price?: number
}>
capabilities?: {
image_input?: boolean
tool_calling?: boolean
json_mode?: boolean
}
}>
}

/**
* Fetches available models from the MakeHub API
*
* @param apiKey - The API key for authentication
* @returns A promise that resolves to a record of model IDs to model info
*/
export const getMakehubModels = async (apiKey?: string): Promise<ModelRecord> => {
try {
// Configure headers based on whether API key is provided
const headers: Record<string, string> = {
Accept: "application/json",
"Content-Type": "application/json",
"HTTP-Referer": "vscode.dev",
"X-Title": "RooCode",
}

// Add Authorization header if API key is provided
if (apiKey && apiKey.trim()) {
headers.Authorization = `Bearer ${apiKey.trim()}`
}

const response = await axios.get<MakehubModelResponse>(`${MAKEHUB_BASE_URL}/models`, {
headers,
timeout: 15000,
})

if (!response.data?.data) {
console.error("MakeHub: Invalid API response format:", response.data)
throw new Error("Invalid API response format from MakeHub")
}

const modelRecord: ModelRecord = {}

for (const model of response.data.data) {
if (!model.model_id || !model.assistant_ready) {
continue
}

// Create a model ID that includes provider information
const fullModelId = model.model_id.includes("/")
? model.model_id // Already has organization format
: `${model.organisation}/${model.model_id}` // Add organization prefix

// Validate pricing data
if (typeof model.price_per_input_token !== "number" || typeof model.price_per_output_token !== "number") {
console.warn(`MakeHub: Invalid pricing for model ${fullModelId}`, {
input: model.price_per_input_token,
output: model.price_per_output_token,
})
continue
}

modelRecord[fullModelId] = {
maxTokens: model.max_tokens ?? undefined,
contextWindow: model.context,
supportsImages: model.capabilities?.image_input ?? false,
supportsComputerUse: model.capabilities?.tool_calling ?? false,
supportsPromptCache: model.supports_prompt_cache ?? false,
inputPrice: model.price_per_input_token,
outputPrice: model.price_per_output_token,
cacheWritesPrice: model.cache_writes_price,
cacheReadsPrice: model.cache_reads_price,
description: model.display_name,
tiers: model.tiers?.map((tier) => ({
contextWindow: tier.context_window,
inputPrice: tier.input_price,
outputPrice: tier.output_price,
cacheWritesPrice: tier.cache_writes_price,
cacheReadsPrice: tier.cache_reads_price,
})),
}
}

return modelRecord
} catch (error) {
console.error("MakeHub: Error fetching models:", error)
if (axios.isAxiosError(error)) {
console.error("MakeHub: HTTP Error Details:", {
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
hasApiKey: !!apiKey,
})

if (error.response?.status === 401) {
throw new Error("MakeHub: Invalid API key. Please check your API key configuration.")
} else if (error.response?.status === 403) {
throw new Error("MakeHub: Access forbidden. Please check your API key permissions.")
} else if (error.response && error.response.status >= 500) {
throw new Error("MakeHub: Server error. Please try again later.")
} else if (error.code === "ECONNABORTED") {
throw new Error("MakeHub: Request timeout. Please check your internet connection.")
}
}

throw new Error(`MakeHub: Failed to fetch models - ${error.message || "Unknown error"}`)
}
}
5 changes: 5 additions & 0 deletions src/api/providers/fetchers/modelCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getRequestyModels } from "./requesty"
import { getGlamaModels } from "./glama"
import { getUnboundModels } from "./unbound"
import { getLiteLLMModels } from "./litellm"
import { getMakehubModels } from "./makehub"
import { GetModelsOptions } from "../../../shared/api"
const memoryCache = new NodeCache({ stdTTL: 5 * 60, checkperiod: 5 * 60 })

Expand Down Expand Up @@ -68,6 +69,10 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
// Type safety ensures apiKey and baseUrl are always provided for litellm
models = await getLiteLLMModels(options.apiKey, options.baseUrl)
break
case "makehub":
// Type safety ensures apiKey is always provided for makehub
models = await getMakehubModels(options.apiKey)
break
default: {
// Ensures router is exhaustively checked if RouterName is a strict union
const exhaustiveCheck: never = provider
Expand Down
4 changes: 4 additions & 0 deletions src/api/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export { UnboundHandler } from "./unbound"
export { VertexHandler } from "./vertex"
export { VsCodeLmHandler } from "./vscode-lm"
export { XAIHandler } from "./xai"
export { GroqHandler } from "./groq"
export { ChutesHandler } from "./chutes"
export { LiteLLMHandler } from "./litellm"
export { MakeHubHandler } from "./makehub"
Loading
Loading