Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/
export default {
id: 'byteplus-coding',
base_url: 'https://ark.ap-southeast.bytepluses.com/api/coding/v3',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
context.request.body.thinking = {
type: isReasoningExplicitlyDisabled(context.request) ? 'disabled' : 'enabled',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { REASONING_VARIANTS_LOW_MEDIUM_HIGH } from '@/lib/ai-gateway/providers/m
export default {
id: 'chutes-byok',
base_url: 'https://llm.chutes.ai/v1',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
const { request } = context;
if (request.kind !== 'chat_completions') {
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/direct-byok/crofai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/direct-byok/model-list';
import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/types';
import { REASONING_VARIANTS_LOW_MEDIUM_HIGH } from '@/lib/ai-gateway/providers/model-settings';

export default {
id: 'crofai',
base_url: 'https://crof.ai/v1',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
const { request } = context;
if (request.kind !== 'chat_completions') {
return;
}
request.body.reasoning_effort ??= request.body.reasoning?.effort ?? undefined;
},
models: cachedEnhancedDirectByokModelList({
providerId: 'crofai',
recommendedModels: [
{
id: 'kimi-k2.6',
name: 'Kimi K2.6',
flags: ['vision'],
context_length: 262144,
max_completion_tokens: 65535,
},
],
variants: REASONING_VARIANTS_LOW_MEDIUM_HIGH,
}),
} satisfies DirectByokProvider;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/types';
import type { DirectByokProvider } from './types';
import byteplusCoding from './byteplus-coding';
import chutesByok from './chutes-byok';
import kimiCoding from './kimi-coding';
Expand All @@ -7,13 +7,21 @@ import ollamaCloud from './ollama-cloud';
import xiaomiTokenPlanAms from './xiaomi-token-plan-ams';
import xiaomiTokenPlanSgp from './xiaomi-token-plan-sgp';
import zaiCoding from './zai-coding';
import crofai from './crofai';
Comment thread
chrarnoldus marked this conversation as resolved.
Outdated
import martian from './martian';
import orcarouter from './orcarouter';
import synthetic from './synthetic';

export default [
byteplusCoding,
chutesByok,
crofai,
kimiCoding,
martian,
neuralwatt,
ollamaCloud,
orcarouter,
synthetic,
xiaomiTokenPlanAms,
xiaomiTokenPlanSgp,
zaiCoding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import type { DirectUserByokInferenceProviderId } from '@/lib/ai-gateway/provide
export const DIRECT_BYOK_PROVIDERS_META = {
'byteplus-coding': 'BytePlus Coding Plan',
'chutes-byok': 'Chutes BYOK',
crofai: 'CrofAI',
'kimi-coding': 'Kimi Code',
martian: 'Martian',
neuralwatt: 'Neuralwatt',
'ollama-cloud': 'Ollama Cloud',
orcarouter: 'OrcaRouter',
synthetic: 'Synthetic',
'xiaomi-token-plan-ams': 'Xiaomi Token Plan (Europe)',
'xiaomi-token-plan-sgp': 'Xiaomi Token Plan (Singapore)',
'zai-coding': 'Z.ai Coding Plan',
Expand Down
10 changes: 6 additions & 4 deletions apps/web/src/lib/ai-gateway/providers/direct-byok/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { readDb } from '@/lib/drizzle';
import { preferredModels } from '@/lib/ai-gateway/models';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import type { OpenCodeSettings } from '@kilocode/db';
import { getCreatorRecommendedAiSdkProvider } from '@/lib/ai-gateway/providers/model-settings';

export function formatDirectByokModelId(provider: DirectByokProvider, model: DirectByokModel) {
return (provider.id + '/' + model.id).toLowerCase();
Expand Down Expand Up @@ -57,7 +58,7 @@ function convertModel(
default_parameters: {},
preferredIndex: model.flags?.includes('recommended') ? preferredIndex : undefined,
opencode: {
ai_sdk_provider: provider.ai_sdk_provider,
ai_sdk_provider: getCreatorRecommendedAiSdkProvider(id) ?? provider.default_ai_sdk_provider,
variants: model.variants,
} satisfies OpenCodeSettings,
};
Expand Down Expand Up @@ -110,8 +111,9 @@ export async function getDirectByokModelsForUser(userId: string) {

export function createAiSdkProvider(directByokProvider: DirectByokProvider, apiKey: string) {
if (
directByokProvider.ai_sdk_provider === 'openai-compatible' ||
directByokProvider.ai_sdk_provider === 'alibaba'
directByokProvider.default_ai_sdk_provider === 'openai-compatible' ||
directByokProvider.default_ai_sdk_provider === 'openrouter' ||
directByokProvider.default_ai_sdk_provider === 'alibaba'
) {
return createOpenAICompatible({
baseURL: directByokProvider.base_url,
Expand All @@ -124,6 +126,6 @@ export function createAiSdkProvider(directByokProvider: DirectByokProvider, apiK
},
});
} else {
throw new Error('Unrecognized AI SDK provider: ' + directByokProvider.ai_sdk_provider);
throw new Error('Unrecognized AI SDK provider: ' + directByokProvider.default_ai_sdk_provider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { isRooCodeBasedClient } from '@/lib/utils';
export default {
id: 'kimi-coding',
base_url: 'https://api.kimi.com/coding/v1',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
const reasoningDisabled =
isRooCodeBasedClient(context.originalHeaders) ||
Expand Down
24 changes: 24 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/direct-byok/martian.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/direct-byok/model-list';
import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/types';
import { REASONING_VARIANTS_LOW_MEDIUM_HIGH } from '@/lib/ai-gateway/providers/model-settings';

export default {
id: 'martian',
base_url: 'https://api.withmartian.com/v1',
supported_chat_apis: ['chat_completions', 'messages', 'responses'],
default_ai_sdk_provider: 'openrouter',
transformRequest() {},
models: cachedEnhancedDirectByokModelList({
providerId: 'martian',
recommendedModels: [
{
id: 'moonshotai/Kimi-K2.6',
name: 'Kimi-K2.6',
flags: ['vision'],
context_length: 262144,
max_completion_tokens: 65535,
},
],
variants: REASONING_VARIANTS_LOW_MEDIUM_HIGH,
}),
} satisfies DirectByokProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/
export default {
id: 'neuralwatt',
base_url: 'https://api.neuralwatt.com/v1',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(_context) {},
models: cachedEnhancedDirectByokModelList({
providerId: 'neuralwatt',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { REASONING_VARIANTS_NONE_LOW_MEDIUM_HIGH } from '@/lib/ai-gateway/provid
export default {
id: 'ollama-cloud',
base_url: 'https://ollama.com/v1',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
const { request } = context;
if (request.kind !== 'chat_completions') {
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/direct-byok/orcarouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/direct-byok/model-list';
import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/types';
import { REASONING_VARIANTS_LOW_MEDIUM_HIGH } from '@/lib/ai-gateway/providers/model-settings';

export default {
id: 'orcarouter',
base_url: 'https://api.orcarouter.ai/v1',
supported_chat_apis: ['chat_completions', 'messages', 'responses'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
const { request } = context;
if (request.kind !== 'chat_completions') {
return;
}
request.body.reasoning_effort ??= request.body.reasoning?.effort ?? undefined;
},
models: cachedEnhancedDirectByokModelList({
providerId: 'orcarouter',
recommendedModels: [
{
id: 'kimi/kimi-k2.6',
name: 'kimi-k2.6',
flags: ['vision'],
context_length: 262144,
max_completion_tokens: 65535,
},
],
variants: REASONING_VARIANTS_LOW_MEDIUM_HIGH,
}),
} satisfies DirectByokProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { DirectUserByokInferenceProviderId } from '@/lib/ai-gateway/provide
import { redisSet } from '@/lib/redis';
import { directByokModelsRedisKey } from '@/lib/redis-keys';

const DEFAULT_CONTENT_LENGTH = 200_000;
const DEFAULT_MAX_COMPLETION_TOKENS = 32_000;

const ModalitySchema = z
Expand Down Expand Up @@ -134,6 +135,26 @@ const FETCHERS: ReadonlyArray<ProviderFetcher> = [
label: 'Chutes',
url: 'https://llm.chutes.ai/v1/models',
}),
openAICompatibleFetcher({
providerId: 'crofai',
label: 'CrofAI',
url: 'https://crof.ai/v1/models',
}),
openAICompatibleFetcher({
providerId: 'orcarouter',
label: 'OrcaRouter',
url: 'https://api.orcarouter.ai/v1/models',
}),
openAICompatibleFetcher({
providerId: 'martian',
label: 'Martian',
url: 'https://api.withmartian.com/v1/models',
}),
openAICompatibleFetcher({
providerId: 'synthetic',
label: 'Synthetic',
url: 'https://api.synthetic.new/v1/models',
}),
modelsDevFetcher('zai-coding', 'zai-coding-plan'),
modelsDevFetcher('ollama-cloud', 'ollama-cloud'),
modelsDevFetcher('xiaomi-token-plan-ams', 'xiaomi-token-plan-ams'),
Expand All @@ -151,7 +172,7 @@ async function syncProvider(fetcher: ProviderFetcher, ctx: SyncContext): Promise

for (const raw of fetched) {
const name = raw.name ?? modelIdToDisplayName(raw.id);
const context_length = raw.context_length ?? DEFAULT_MAX_COMPLETION_TOKENS;
const context_length = raw.context_length ?? DEFAULT_CONTENT_LENGTH;
const max_completion_tokens = Math.min(
raw.max_completion_tokens ?? DEFAULT_MAX_COMPLETION_TOKENS,
context_length
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/direct-byok/synthetic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/direct-byok/model-list';
import type { DirectByokProvider } from '@/lib/ai-gateway/providers/direct-byok/types';
import { REASONING_VARIANTS_LOW_MEDIUM_HIGH } from '@/lib/ai-gateway/providers/model-settings';

export default {
id: 'synthetic',
base_url: 'https://api.synthetic.new/v1',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
const { request } = context;
if (request.kind !== 'chat_completions') {
return;
}
request.body.reasoning_effort ??= request.body.reasoning?.effort ?? undefined;
},
models: cachedEnhancedDirectByokModelList({
providerId: 'synthetic',
recommendedModels: [
{
id: 'moonshotai/Kimi-K2.6',
name: 'Kimi-K2.6',
flags: ['vision'],
context_length: 262144,
max_completion_tokens: 65535,
},
],
variants: REASONING_VARIANTS_LOW_MEDIUM_HIGH,
}),
} satisfies DirectByokProvider;
5 changes: 3 additions & 2 deletions apps/web/src/lib/ai-gateway/providers/direct-byok/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as z from 'zod';
import { OpenCodeVariantSchema } from '@kilocode/db/schema-types';
import type { DirectByokProviderMetaId } from '@/lib/ai-gateway/providers/direct-byok/direct-byok-meta';
import type { TransformRequestContext } from '@/lib/ai-gateway/providers/types';
import type { GatewayChatApiKind, TransformRequestContext } from '@/lib/ai-gateway/providers/types';
import type { CustomLlmProvider } from '@kilocode/db';

export const DirectByokModelFlagSchema = z.enum(['recommended', 'vision']);
Expand All @@ -28,7 +28,8 @@ export type DirectByokProvider = {
id: DirectByokProviderMetaId;
base_url: string;
models: () => Promise<ReadonlyArray<DirectByokModel>>;
ai_sdk_provider: CustomLlmProvider;
supported_chat_apis: ReadonlyArray<GatewayChatApiKind>;
default_ai_sdk_provider: CustomLlmProvider;
transformRequest(context: TransformRequestContext): void;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/di
export default {
id: 'xiaomi-token-plan-ams',
base_url: 'https://token-plan-ams.xiaomimimo.com/v1',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest() {},
models: cachedEnhancedDirectByokModelList({
providerId: 'xiaomi-token-plan-ams',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/di
export default {
id: 'xiaomi-token-plan-sgp',
base_url: 'https://token-plan-sgp.xiaomimimo.com/v1',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest() {},
models: cachedEnhancedDirectByokModelList({
providerId: 'xiaomi-token-plan-sgp',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { cachedEnhancedDirectByokModelList } from '@/lib/ai-gateway/providers/di
export default {
id: 'zai-coding',
base_url: 'https://api.z.ai/api/coding/paas/v4',
ai_sdk_provider: 'openai-compatible',
supported_chat_apis: ['chat_completions'],
default_ai_sdk_provider: 'openai-compatible',
transformRequest(context) {
context.request.body.thinking = {
type: isReasoningExplicitlyDisabled(context.request) ? 'disabled' : 'enabled',
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/ai-gateway/providers/get-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { CustomLlmDefinitionSchema } from '@kilocode/db';
import {
buildDirectProvider,
inferSupportedChatApis,

Check failure on line 21 in apps/web/src/lib/ai-gateway/providers/get-provider.ts

View workflow job for this annotation

GitHub Actions / lint

eslint(no-unused-vars)

Identifier 'inferSupportedChatApis' is imported but never used.
} from '@/lib/ai-gateway/experiments/build-direct-provider';
import { isPublicIdExperimented } from '@/lib/ai-gateway/experiments/membership';
import {
Expand Down Expand Up @@ -81,7 +81,7 @@
id: 'direct-byok',
apiUrl: directByok.base_url,
apiKey: userByok[0].decryptedAPIKey,
supportedChatApis: inferSupportedChatApis(directByok.ai_sdk_provider),
supportedChatApis: directByok.supported_chat_apis,
transformRequest(context) {
context.request.body.model = directByokModel.id;
directByok.transformRequest(context);
Expand Down
26 changes: 17 additions & 9 deletions apps/web/src/lib/ai-gateway/providers/model-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,24 @@ export function getModelVariants(model: string): OpenCodeSettings['variants'] {
return undefined;
}

export function getCreatorRecommendedAiSdkProvider(model: string): CustomLlmProvider | undefined {
if (isClaudeModel(model)) {
// on Vercel AI Gateway, this is necessary to support document attachments
return 'anthropic';
}
if (isOpenAiModel(model) || isGrokModel(model)) {
// OpenAI: "While Chat Completions remains supported, Responses is recommended for all new projects.""
// xAI: "The Responses API is the recommended way to interact with xAI models."
return 'openai';
}
return undefined;
}

function getAiSdkProvider(model: string): CustomLlmProvider | undefined {
const creatorRecommendedAiSdkProvider = getCreatorRecommendedAiSdkProvider(model);
if (creatorRecommendedAiSdkProvider) {
return creatorRecommendedAiSdkProvider;
}
if (isAlibabaDirectModel(model)) {
// with 'openai' (Responses) prompt caching doesn't work
// with 'openai-compatible' (Chat Completions) cost is wrong (cache writes are not counted)
Expand All @@ -122,15 +139,6 @@ function getAiSdkProvider(model: string): CustomLlmProvider | undefined {
// with 'openai' (Responses API) prompt caching doesn't work
return 'openai-compatible';
}
if (isClaudeModel(model)) {
// on Vercel AI Gateway, this is necessary to support document attachments
return 'anthropic';
}
if (isOpenAiModel(model) || isGrokModel(model)) {
// OpenAI: "While Chat Completions remains supported, Responses is recommended for all new projects.""
// xAI: "The Responses API is the recommended way to interact with xAI models."
return 'openai';
}
return undefined;
}

Expand Down
Loading
Loading