From 3ad95fc985b7b554d7dc2ddb28956e86b1d2acd6 Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:22:08 +0000 Subject: [PATCH 1/2] refactor(ai-gateway): split provider definitions Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../lib/ai-gateway/auto-model/resolution.ts | 2 +- apps/web/src/lib/ai-gateway/local-fake-llm.ts | 2 +- .../web/src/lib/ai-gateway/model-api-kinds.ts | 3 +- apps/web/src/lib/ai-gateway/processUsage.ts | 3 +- .../src/lib/ai-gateway/providerHash.test.ts | 3 +- apps/web/src/lib/ai-gateway/providerHash.ts | 3 +- .../providers/definitions/alibaba.ts | 19 +++ .../providers/definitions/longcat.ts | 22 +++ .../providers/definitions/martian.ts | 15 ++ .../providers/definitions/mistral.ts | 13 ++ .../openrouter.ts} | 5 - .../ai-gateway/providers/definitions/seed.ts | 32 ++++ .../providers/definitions/streamlake.ts | 15 ++ .../definitions/try-get-provider-by-id.ts | 32 ++++ .../providers/definitions/vercel.ts | 16 ++ .../providers/get-provider.local-fake.test.ts | 3 +- .../lib/ai-gateway/providers/get-provider.ts | 8 +- .../providers/getEmbeddingProvider.test.ts | 3 +- .../ai-gateway/providers/openrouter/index.ts | 2 +- .../providers/openrouter/sync-providers.ts | 3 +- .../providers/provider-definitions.test.ts | 8 +- .../providers/provider-definitions.ts | 148 ------------------ .../lib/coding-plans/inventory-validation.ts | 2 +- apps/web/src/routers/byok-router.ts | 2 +- apps/web/src/scripts/byok/test.ts | 2 +- .../src/tests/openrouterApi.timeout.test.ts | 2 +- 26 files changed, 191 insertions(+), 177 deletions(-) create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/alibaba.ts create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/longcat.ts create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/martian.ts create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/mistral.ts rename apps/web/src/lib/ai-gateway/providers/{openrouter-definition.ts => definitions/openrouter.ts} (66%) create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/seed.ts create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/streamlake.ts create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/try-get-provider-by-id.ts create mode 100644 apps/web/src/lib/ai-gateway/providers/definitions/vercel.ts delete mode 100644 apps/web/src/lib/ai-gateway/providers/provider-definitions.ts diff --git a/apps/web/src/lib/ai-gateway/auto-model/resolution.ts b/apps/web/src/lib/ai-gateway/auto-model/resolution.ts index d7b6fe5eb4..e06c6f9af3 100644 --- a/apps/web/src/lib/ai-gateway/auto-model/resolution.ts +++ b/apps/web/src/lib/ai-gateway/auto-model/resolution.ts @@ -30,7 +30,7 @@ import { selectAutoFreeCandidate, } from '@/lib/ai-gateway/models'; import { getOpenRouterModelsFromDatabase } from '@/lib/ai-gateway/providers/gateway-models-cache'; -import { tryGetProviderById } from '@/lib/ai-gateway/providers/provider-definitions'; +import { tryGetProviderById } from '@/lib/ai-gateway/providers/definitions/try-get-provider-by-id'; import type { ProviderId } from '@/lib/ai-gateway/providers/types'; import { getOrganizationAutoRoute, diff --git a/apps/web/src/lib/ai-gateway/local-fake-llm.ts b/apps/web/src/lib/ai-gateway/local-fake-llm.ts index 34c6191920..1e2a6cb6a9 100644 --- a/apps/web/src/lib/ai-gateway/local-fake-llm.ts +++ b/apps/web/src/lib/ai-gateway/local-fake-llm.ts @@ -1,5 +1,5 @@ import { buildDirectProvider } from '@/lib/ai-gateway/experiments/build-direct-provider'; -import { OPENROUTER } from '@/lib/ai-gateway/providers/openrouter-definition'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; import type { Provider } from '@/lib/ai-gateway/providers/types'; import type { OpenRouterModel } from '@/lib/organizations/organization-types'; diff --git a/apps/web/src/lib/ai-gateway/model-api-kinds.ts b/apps/web/src/lib/ai-gateway/model-api-kinds.ts index f2a633170e..fcf12a1b07 100644 --- a/apps/web/src/lib/ai-gateway/model-api-kinds.ts +++ b/apps/web/src/lib/ai-gateway/model-api-kinds.ts @@ -1,5 +1,6 @@ import { findKiloExclusiveModel } from '@/lib/ai-gateway/models'; -import { OPENROUTER, tryGetProviderById } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { tryGetProviderById } from '@/lib/ai-gateway/providers/definitions/try-get-provider-by-id'; import type { GatewayChatApiKind } from '@/lib/ai-gateway/providers/types'; const GATEWAY_CHAT_API_KINDS: readonly GatewayChatApiKind[] = [ diff --git a/apps/web/src/lib/ai-gateway/processUsage.ts b/apps/web/src/lib/ai-gateway/processUsage.ts index 34c312075f..f858b156e7 100644 --- a/apps/web/src/lib/ai-gateway/processUsage.ts +++ b/apps/web/src/lib/ai-gateway/processUsage.ts @@ -16,7 +16,8 @@ import type { OpenRouterGeneration, } from './providers/openrouter/types'; import { fetchGeneration } from './providers/upstream-request'; -import { OPENROUTER, VERCEL_AI_GATEWAY } from './providers/provider-definitions'; +import { OPENROUTER } from './providers/definitions/openrouter'; +import { VERCEL_AI_GATEWAY } from './providers/definitions/vercel'; import { toMicrodollars } from '../utils'; import { captureException, captureMessage, startSpan, startInactiveSpan } from '@sentry/nextjs'; import type { Span } from '@sentry/nextjs'; diff --git a/apps/web/src/lib/ai-gateway/providerHash.test.ts b/apps/web/src/lib/ai-gateway/providerHash.test.ts index e27fac134c..6978f4c4b8 100644 --- a/apps/web/src/lib/ai-gateway/providerHash.test.ts +++ b/apps/web/src/lib/ai-gateway/providerHash.test.ts @@ -1,4 +1,5 @@ -import { OPENROUTER, VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import type { GatewayRequest } from '@/lib/ai-gateway/providers/openrouter/types'; import { applyTrackingIds, diff --git a/apps/web/src/lib/ai-gateway/providerHash.ts b/apps/web/src/lib/ai-gateway/providerHash.ts index df193847eb..172e8b968f 100644 --- a/apps/web/src/lib/ai-gateway/providerHash.ts +++ b/apps/web/src/lib/ai-gateway/providerHash.ts @@ -1,6 +1,7 @@ import crypto from 'crypto'; import { type Provider } from '@/lib/ai-gateway/providers/types'; -import { OPENROUTER, VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { getEnvVariable } from '@/lib/dotenvx'; import type { GatewayRequest } from '@/lib/ai-gateway/providers/openrouter/types'; diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/alibaba.ts b/apps/web/src/lib/ai-gateway/providers/definitions/alibaba.ts new file mode 100644 index 0000000000..90834e66d3 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/alibaba.ts @@ -0,0 +1,19 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; +import type { Provider } from '@/lib/ai-gateway/providers/types'; + +export const ALIBABA = { + id: 'alibaba', + apiUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', + apiUrlOverrides: {}, + apiKey: getEnvVariable('ALIBABA_API_KEY'), + apiKeyHeader: null, + supportedChatApis: [ + 'chat_completions', + // 'responses', // supported, not tested + ], + responseTransforms: null, + async transformRequest(context) { + context.request.body.enable_thinking = !isReasoningExplicitlyDisabled(context.request); + }, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/longcat.ts b/apps/web/src/lib/ai-gateway/providers/definitions/longcat.ts new file mode 100644 index 0000000000..a32582c8bc --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/longcat.ts @@ -0,0 +1,22 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; +import { ReasoningDetailsTransform, type Provider } from '@/lib/ai-gateway/providers/types'; + +export const LONGCAT = { + id: 'longcat', + apiUrl: 'https://api.longcat.ai/openai/v1', + apiUrlOverrides: {}, + apiKey: getEnvVariable('LONGCAT_API_KEY'), + apiKeyHeader: null, + supportedChatApis: ['chat_completions'], + responseTransforms: ReasoningDetailsTransform.ReasoningContent, + async transformRequest(context) { + context.request.body.thinking = { + type: isReasoningExplicitlyDisabled(context.request) ? 'disabled' : 'enabled', + }; + delete context.request.body.provider; + if (context.request.body.user) { + context.extraHeaders['Mt-User-Id'] = context.request.body.user; + } + }, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/martian.ts b/apps/web/src/lib/ai-gateway/providers/definitions/martian.ts new file mode 100644 index 0000000000..1a00678e3e --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/martian.ts @@ -0,0 +1,15 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import type { Provider } from '@/lib/ai-gateway/providers/types'; + +export const MARTIAN = { + id: 'martian', + apiUrl: 'https://api.withmartian.com/v1', + apiUrlOverrides: {}, + apiKey: getEnvVariable('MARTIAN_API_KEY'), + apiKeyHeader: null, + supportedChatApis: ['chat_completions', 'responses', 'messages'], + responseTransforms: null, + async transformRequest(context) { + delete context.request.body.provider; + }, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/mistral.ts b/apps/web/src/lib/ai-gateway/providers/definitions/mistral.ts new file mode 100644 index 0000000000..ab524ac8cd --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/mistral.ts @@ -0,0 +1,13 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import type { Provider } from '@/lib/ai-gateway/providers/types'; + +export const MISTRAL = { + id: 'mistral', + apiUrl: 'https://api.mistral.ai/v1', + apiUrlOverrides: {}, + apiKey: getEnvVariable('MISTRAL_API_KEY'), + apiKeyHeader: null, + supportedChatApis: [], + responseTransforms: null, + async transformRequest() {}, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter-definition.ts b/apps/web/src/lib/ai-gateway/providers/definitions/openrouter.ts similarity index 66% rename from apps/web/src/lib/ai-gateway/providers/openrouter-definition.ts rename to apps/web/src/lib/ai-gateway/providers/definitions/openrouter.ts index 166944ce39..f6326082fb 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter-definition.ts +++ b/apps/web/src/lib/ai-gateway/providers/definitions/openrouter.ts @@ -1,11 +1,6 @@ import { getEnvVariable } from '@/lib/dotenvx'; import type { Provider } from '@/lib/ai-gateway/providers/types'; -/** - * Self-contained OpenRouter provider definition. Kept in a leaf module so - * importers that only need `OPENROUTER` (for example the local fake LLM) do - * not pull the rest of the provider graph into a circular dependency. - */ export const OPENROUTER = { id: 'openrouter', apiUrl: 'https://openrouter.ai/api/v1', diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/seed.ts b/apps/web/src/lib/ai-gateway/providers/definitions/seed.ts new file mode 100644 index 0000000000..3ad423fc73 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/seed.ts @@ -0,0 +1,32 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; +import type { Provider } from '@/lib/ai-gateway/providers/types'; + +export const SEED = { + id: 'seed', + apiUrl: 'https://ark.ap-southeast.bytepluses.com/api/v3', + apiUrlOverrides: {}, + apiKey: getEnvVariable('BYTEDANCE_API_KEY'), + apiKeyHeader: null, + supportedChatApis: [ + 'chat_completions', + // 'responses', // supported, not tested + ], + responseTransforms: null, + async transformRequest(context) { + if (!isReasoningExplicitlyDisabled(context.request)) { + context.request.body.thinking = { type: 'enabled' }; + if (context.request.kind === 'chat_completions') { + context.request.body.reasoning_effort ??= context.request.body.reasoning?.effort; + } + } else { + context.request.body.thinking = { type: 'disabled' }; + } + if (context.request.kind === 'responses') { + delete context.request.body.prompt_cache_key; + delete context.request.body.safety_identifier; + delete context.request.body.user; + delete context.request.body.provider; + } + }, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/streamlake.ts b/apps/web/src/lib/ai-gateway/providers/definitions/streamlake.ts new file mode 100644 index 0000000000..5c7b2ffe04 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/streamlake.ts @@ -0,0 +1,15 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import type { Provider } from '@/lib/ai-gateway/providers/types'; + +export const STREAMLAKE = { + id: 'streamlake', + apiUrl: 'https://vanchin.streamlake.ai/api/gateway/v1/endpoints', + apiUrlOverrides: {}, + apiKey: getEnvVariable('STREAMLAKE_API_KEY'), + apiKeyHeader: null, + supportedChatApis: ['chat_completions'], + responseTransforms: null, + async transformRequest(context) { + delete context.request.body.provider; + }, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/try-get-provider-by-id.ts b/apps/web/src/lib/ai-gateway/providers/definitions/try-get-provider-by-id.ts new file mode 100644 index 0000000000..abe6bbe696 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/try-get-provider-by-id.ts @@ -0,0 +1,32 @@ +import { ALIBABA } from '@/lib/ai-gateway/providers/definitions/alibaba'; +import { LONGCAT } from '@/lib/ai-gateway/providers/definitions/longcat'; +import { MARTIAN } from '@/lib/ai-gateway/providers/definitions/martian'; +import { MISTRAL } from '@/lib/ai-gateway/providers/definitions/mistral'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { SEED } from '@/lib/ai-gateway/providers/definitions/seed'; +import { STREAMLAKE } from '@/lib/ai-gateway/providers/definitions/streamlake'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; +import type { Provider, ProviderId } from '@/lib/ai-gateway/providers/types'; + +export function tryGetProviderById(providerId: ProviderId): Provider | undefined { + switch (providerId) { + case 'openrouter': + return OPENROUTER; + case 'alibaba': + return ALIBABA; + case 'seed': + return SEED; + case 'longcat': + return LONGCAT; + case 'martian': + return MARTIAN; + case 'mistral': + return MISTRAL; + case 'streamlake': + return STREAMLAKE; + case 'vercel': + return VERCEL_AI_GATEWAY; + default: + return undefined; + } +} diff --git a/apps/web/src/lib/ai-gateway/providers/definitions/vercel.ts b/apps/web/src/lib/ai-gateway/providers/definitions/vercel.ts new file mode 100644 index 0000000000..686bce3b86 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/definitions/vercel.ts @@ -0,0 +1,16 @@ +import { getEnvVariable } from '@/lib/dotenvx'; +import type { Provider } from '@/lib/ai-gateway/providers/types'; +import { applyVercelSettings } from '@/lib/ai-gateway/providers/vercel'; + +export const VERCEL_AI_GATEWAY = { + id: 'vercel', + apiUrl: 'https://ai-gateway.vercel.sh/v1', + apiUrlOverrides: {}, + apiKey: getEnvVariable('VERCEL_AI_GATEWAY_API_KEY'), + apiKeyHeader: null, + supportedChatApis: ['chat_completions', 'messages', 'responses'], + responseTransforms: null, + async transformRequest(context) { + await applyVercelSettings(context.model, context.request, context.userByok); + }, +} as const satisfies Provider; diff --git a/apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts b/apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts index 7bf7e05513..a948328e6f 100644 --- a/apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, test } from '@jest/globals'; import { getProvider, getTranscriptionProvider } from '@/lib/ai-gateway/providers/get-provider'; -import { OPENROUTER, VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { shouldRouteToVercel } from '@/lib/ai-gateway/providers/vercel'; import type { GatewayRequest } from '@/lib/ai-gateway/providers/openrouter/types'; import type { User } from '@kilocode/db/schema'; diff --git a/apps/web/src/lib/ai-gateway/providers/get-provider.ts b/apps/web/src/lib/ai-gateway/providers/get-provider.ts index ce748bc30b..2042f0feb7 100644 --- a/apps/web/src/lib/ai-gateway/providers/get-provider.ts +++ b/apps/web/src/lib/ai-gateway/providers/get-provider.ts @@ -16,11 +16,9 @@ import { eq } from 'drizzle-orm'; import type { AnonymousUserContext } from '@/lib/anonymous'; import { isAnonymousContext } from '@/lib/anonymous'; import type { BYOKResult, Provider } from '@/lib/ai-gateway/providers/types'; -import { - OPENROUTER, - tryGetProviderById, - VERCEL_AI_GATEWAY, -} from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { tryGetProviderById } from '@/lib/ai-gateway/providers/definitions/try-get-provider-by-id'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { getDirectByokModel } from '@/lib/ai-gateway/providers/direct-byok'; import { CustomLlmCredentialsSchema, CustomLlmDefinitionSchema } from '@kilocode/db/schema-types'; import { buildDirectProvider } from '@/lib/ai-gateway/experiments/build-direct-provider'; diff --git a/apps/web/src/lib/ai-gateway/providers/getEmbeddingProvider.test.ts b/apps/web/src/lib/ai-gateway/providers/getEmbeddingProvider.test.ts index 149a67e8fd..e719126627 100644 --- a/apps/web/src/lib/ai-gateway/providers/getEmbeddingProvider.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/getEmbeddingProvider.test.ts @@ -3,7 +3,8 @@ import { getEmbeddingProvider, getTranscriptionProvider, } from '@/lib/ai-gateway/providers/get-provider'; -import { OPENROUTER, VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { createAnonymousContext } from '@/lib/anonymous'; import { getModelUserByokProviders, diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts index 9e951d45fb..28ca1f2378 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts @@ -8,7 +8,7 @@ import { getLocalFakeTranscriptionModelsUrl, LOCAL_FAKE_LLM_API_KEY, } from '@/lib/ai-gateway/local-fake-llm'; -import { OPENROUTER } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; import type { OpenRouterModel } from '@/lib/organizations/organization-types'; import { OpenRouterModelsResponseSchema, diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts index a0aa2715fe..c8c4ee570e 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts @@ -17,7 +17,8 @@ import { ai_gateway_sync_providers_state, modelsByProvider } from '@kilocode/db/ import { db } from '@/lib/drizzle'; import { desc, eq, lt, sql } from 'drizzle-orm'; import { captureException } from '@sentry/nextjs'; -import { OPENROUTER, VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { logAutoModelChangesForAllOrgs } from '@/lib/organizations/auto-model-change-log'; import type { Provider } from '@/lib/ai-gateway/providers/types'; import type { StoredModel } from '@kilocode/db/schema-types'; diff --git a/apps/web/src/lib/ai-gateway/providers/provider-definitions.test.ts b/apps/web/src/lib/ai-gateway/providers/provider-definitions.test.ts index d2c3f97e17..2174aaa7f0 100644 --- a/apps/web/src/lib/ai-gateway/providers/provider-definitions.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/provider-definitions.test.ts @@ -1,10 +1,8 @@ import { describe, expect, test } from '@jest/globals'; -import { - LONGCAT, - OPENROUTER, - tryGetProviderById, -} from '@/lib/ai-gateway/providers/provider-definitions'; +import { LONGCAT } from '@/lib/ai-gateway/providers/definitions/longcat'; +import { OPENROUTER } from '@/lib/ai-gateway/providers/definitions/openrouter'; +import { tryGetProviderById } from '@/lib/ai-gateway/providers/definitions/try-get-provider-by-id'; import type { GatewayRequest } from '@/lib/ai-gateway/providers/openrouter/types'; import { ReasoningDetailsTransform, diff --git a/apps/web/src/lib/ai-gateway/providers/provider-definitions.ts b/apps/web/src/lib/ai-gateway/providers/provider-definitions.ts deleted file mode 100644 index 3d3cfc864f..0000000000 --- a/apps/web/src/lib/ai-gateway/providers/provider-definitions.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { getEnvVariable } from '@/lib/dotenvx'; -import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; -import { - ReasoningDetailsTransform, - type Provider, - type ProviderId, -} from '@/lib/ai-gateway/providers/types'; -import { applyVercelSettings } from '@/lib/ai-gateway/providers/vercel'; -import { OPENROUTER } from './openrouter-definition'; - -export { OPENROUTER }; - -export const ALIBABA = { - id: 'alibaba', - apiUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', - apiUrlOverrides: {}, - apiKey: getEnvVariable('ALIBABA_API_KEY'), - apiKeyHeader: null, - supportedChatApis: [ - 'chat_completions', - // 'responses', // supported, not tested - ], - responseTransforms: null, - async transformRequest(context) { - context.request.body.enable_thinking = !isReasoningExplicitlyDisabled(context.request); - }, -} as const satisfies Provider; - -export const SEED = { - id: 'seed', - apiUrl: 'https://ark.ap-southeast.bytepluses.com/api/v3', - apiUrlOverrides: {}, - apiKey: getEnvVariable('BYTEDANCE_API_KEY'), - apiKeyHeader: null, - supportedChatApis: [ - 'chat_completions', - // 'responses', // supported, not tested - ], - responseTransforms: null, - async transformRequest(context) { - if (!isReasoningExplicitlyDisabled(context.request)) { - context.request.body.thinking = { type: 'enabled' }; - if (context.request.kind === 'chat_completions') { - context.request.body.reasoning_effort ??= context.request.body.reasoning?.effort; - } - } else { - context.request.body.thinking = { type: 'disabled' }; - } - if (context.request.kind === 'responses') { - delete context.request.body.prompt_cache_key; - delete context.request.body.safety_identifier; - delete context.request.body.user; - delete context.request.body.provider; - } - }, -} as const satisfies Provider; - -export const LONGCAT = { - id: 'longcat', - apiUrl: 'https://api.longcat.ai/openai/v1', - apiUrlOverrides: {}, - apiKey: getEnvVariable('LONGCAT_API_KEY'), - apiKeyHeader: null, - supportedChatApis: ['chat_completions'], - responseTransforms: ReasoningDetailsTransform.ReasoningContent, - async transformRequest(context) { - context.request.body.thinking = { - type: isReasoningExplicitlyDisabled(context.request) ? 'disabled' : 'enabled', - }; - delete context.request.body.provider; - if (context.request.body.user) { - context.extraHeaders['Mt-User-Id'] = context.request.body.user; - } - }, -} as const satisfies Provider; - -export const MARTIAN = { - id: 'martian', - apiUrl: 'https://api.withmartian.com/v1', - apiUrlOverrides: {}, - apiKey: getEnvVariable('MARTIAN_API_KEY'), - apiKeyHeader: null, - supportedChatApis: ['chat_completions', 'responses', 'messages'], - responseTransforms: null, - async transformRequest(context) { - delete context.request.body.provider; - }, -} as const satisfies Provider; - -export const MISTRAL = { - id: 'mistral', - apiUrl: 'https://api.mistral.ai/v1', - apiUrlOverrides: {}, - apiKey: getEnvVariable('MISTRAL_API_KEY'), - apiKeyHeader: null, - supportedChatApis: [], - responseTransforms: null, - async transformRequest() {}, -} as const satisfies Provider; - -export const STREAMLAKE = { - id: 'streamlake', - apiUrl: 'https://vanchin.streamlake.ai/api/gateway/v1/endpoints', - apiUrlOverrides: {}, - apiKey: getEnvVariable('STREAMLAKE_API_KEY'), - apiKeyHeader: null, - supportedChatApis: ['chat_completions'], - responseTransforms: null, - async transformRequest(context) { - delete context.request.body.provider; - }, -} as const satisfies Provider; - -export const VERCEL_AI_GATEWAY = { - id: 'vercel', - apiUrl: 'https://ai-gateway.vercel.sh/v1', - apiUrlOverrides: {}, - apiKey: getEnvVariable('VERCEL_AI_GATEWAY_API_KEY'), - apiKeyHeader: null, - supportedChatApis: ['chat_completions', 'messages', 'responses'], - responseTransforms: null, - async transformRequest(context) { - await applyVercelSettings(context.model, context.request, context.userByok); - }, -} as const satisfies Provider; - -export function tryGetProviderById(providerId: ProviderId): Provider | undefined { - switch (providerId) { - case 'openrouter': - return OPENROUTER; - case 'alibaba': - return ALIBABA; - case 'seed': - return SEED; - case 'longcat': - return LONGCAT; - case 'martian': - return MARTIAN; - case 'mistral': - return MISTRAL; - case 'streamlake': - return STREAMLAKE; - case 'vercel': - return VERCEL_AI_GATEWAY; - default: - return undefined; - } -} diff --git a/apps/web/src/lib/coding-plans/inventory-validation.ts b/apps/web/src/lib/coding-plans/inventory-validation.ts index 9e42d0c6cc..1a650accae 100644 --- a/apps/web/src/lib/coding-plans/inventory-validation.ts +++ b/apps/web/src/lib/coding-plans/inventory-validation.ts @@ -19,7 +19,7 @@ import { } from '@/lib/coding-plans/byteplus-control-plane'; import type { CodingPlanId, CodingPlanProviderId } from '@/lib/coding-plans/pricing'; import { getCodingPlanPrice } from '@/lib/coding-plans/pricing'; -import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { sentryLogger } from '@/lib/utils.server'; const logWarning = sentryLogger('coding-plans-inventory-validation', 'warning'); diff --git a/apps/web/src/routers/byok-router.ts b/apps/web/src/routers/byok-router.ts index 58e1d5cff5..75df785c34 100644 --- a/apps/web/src/routers/byok-router.ts +++ b/apps/web/src/routers/byok-router.ts @@ -31,7 +31,7 @@ import { getOpenRouterModelsMetadataFromDatabase, } from '@/lib/ai-gateway/providers/gateway-models-cache'; import { createGateway, generateText } from 'ai'; -import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import { getVercelInferenceProviderConfigForUserByok } from '@/lib/ai-gateway/providers/vercel'; import { decryptByokRow } from '@/lib/ai-gateway/byok'; import type { GatewayProviderOptions } from '@ai-sdk/gateway'; diff --git a/apps/web/src/scripts/byok/test.ts b/apps/web/src/scripts/byok/test.ts index e1556d20b3..ce0e18beb1 100644 --- a/apps/web/src/scripts/byok/test.ts +++ b/apps/web/src/scripts/byok/test.ts @@ -1,6 +1,6 @@ import 'dotenv/config'; import OpenAI from 'openai'; -import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/provider-definitions'; +import { VERCEL_AI_GATEWAY } from '@/lib/ai-gateway/providers/definitions/vercel'; import type { Stream } from 'openai/streaming'; import type { ChatCompletionChunk } from 'openai/resources'; diff --git a/apps/web/src/tests/openrouterApi.timeout.test.ts b/apps/web/src/tests/openrouterApi.timeout.test.ts index 3d6758d5cf..a28e577a28 100644 --- a/apps/web/src/tests/openrouterApi.timeout.test.ts +++ b/apps/web/src/tests/openrouterApi.timeout.test.ts @@ -1,6 +1,6 @@ import { captureException } from '@sentry/nextjs'; import { upstreamRequest } from '../lib/ai-gateway/providers/upstream-request'; -import { OPENROUTER } from '../lib/ai-gateway/providers/provider-definitions'; +import { OPENROUTER } from '../lib/ai-gateway/providers/definitions/openrouter'; jest.mock('@sentry/nextjs', () => ({ captureException: jest.fn(), From 4b8b18cba0f39b83d9768cfc4a51a7a22077f27e Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:26:47 +0000 Subject: [PATCH 2/2] docs: update provider definition paths Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- ENVIRONMENT.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index 33c5de1641..f4e6547e2e 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -264,11 +264,11 @@ Manage shared web env var additions and rotations with `pnpm web:env set