From 1a8120260716e04050db87600bc899cb1cd5987a Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Mon, 28 Sep 2026 15:43:34 +0100 Subject: [PATCH 1/4] feat(gateways): add Requesty hybrid gateway Register Requesty as an OpenAI-compatible gateway with a short curated catalog plus public /v1/models discovery. REQUESTY_API_KEY is only resolved for the exact https /v1 base on the global, EU, US and AP hosts. Signed-off-by: Thibault Jaigu --- src/integrations/compatibility.test.ts | 1 + src/integrations/gateways/requesty.ts | 122 ++++++++++++++++++ .../integrationArtifacts.generated.ts | 3 +- .../integrationManifest.generated.ts | 12 ++ src/integrations/routeMetadata.ts | 44 +++++++ src/utils/envFile.ts | 1 + 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/integrations/gateways/requesty.ts diff --git a/src/integrations/compatibility.test.ts b/src/integrations/compatibility.test.ts index 7ed084485e..bc9ba71a82 100644 --- a/src/integrations/compatibility.test.ts +++ b/src/integrations/compatibility.test.ts @@ -55,6 +55,7 @@ const EXPECTED_PRESETS = [ 'longcat', 'llmtr', 'commandcode', + 'requesty', 'opencode', 'opencode-go', 'clinepass', diff --git a/src/integrations/gateways/requesty.ts b/src/integrations/gateways/requesty.ts new file mode 100644 index 0000000000..9a59e0e727 --- /dev/null +++ b/src/integrations/gateways/requesty.ts @@ -0,0 +1,122 @@ +import { defineGateway } from '../define.js' +import type { ModelCatalogEntry } from '../descriptors.js' +import { + firstPositiveNumber, + getTrimmedString, + isKnownNonCodingModelId, + isRecord, +} from '../modelMapping.js' + +// Model ids are rendered in the model picker. Drop anything carrying control +// characters (including ESC for ANSI sequences) instead of trying to clean it. +// eslint-disable-next-line no-control-regex +const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f-\u009f]/ + +/** + * Map Requesty's public GET /v1/models payload into a catalog entry. + * Requesty reports `context_window`, `max_output_tokens` and boolean + * `supports_*` flags instead of OpenRouter's `context_length` and + * `supported_parameters`. + */ +export function mapRequestyModel(raw: unknown): ModelCatalogEntry | null { + if (!isRecord(raw)) { + return null + } + + const id = getTrimmedString(raw, 'id') + if ( + !id || + CONTROL_CHARACTER_PATTERN.test(id) || + isKnownNonCodingModelId(id) + ) { + return null + } + + const api = getTrimmedString(raw, 'api') + if (api && api !== 'chat') { + return null + } + + const toolCall = raw.supports_tool_calling === true + const reasoning = raw.supports_reasoning === true + const contextWindow = firstPositiveNumber(raw.context_window) + const maxOutputTokens = firstPositiveNumber(raw.max_output_tokens) + + return { + id, + apiName: id, + label: id, + ...(contextWindow ? { contextWindow } : {}), + ...(maxOutputTokens ? { maxOutputTokens } : {}), + ...(toolCall || reasoning + ? { + capabilities: { + ...(toolCall ? { supportsFunctionCalling: true } : {}), + ...(reasoning ? { supportsReasoning: true } : {}), + }, + } + : {}), + } +} + +export default defineGateway({ + id: 'requesty', + label: 'Requesty', + category: 'aggregating', + defaultBaseUrl: 'https://router.requesty.ai/v1', + defaultModel: 'openai/gpt-5-mini', + supportsModelRouting: true, + setup: { + requiresAuth: true, + authMode: 'api-key', + credentialEnvVars: ['REQUESTY_API_KEY'], + }, + startup: { + probeReadiness: 'openai-compatible-models', + }, + transportConfig: { + kind: 'openai-compatible', + openaiShim: { + supportsAuthHeaders: true, + }, + }, + preset: { + id: 'requesty', + description: 'Requesty OpenAI-compatible gateway', + apiKeyEnvVars: ['REQUESTY_API_KEY'], + vendorId: 'openai', + }, + validation: { + kind: 'credential-env', + routing: { + matchDefaultBaseUrl: true, + matchBaseUrlHosts: [ + 'router.requesty.ai', + 'router.eu.requesty.ai', + 'router.us.requesty.ai', + 'router.ap.requesty.ai', + ], + }, + credentialEnvVars: ['REQUESTY_API_KEY', 'OPENAI_API_KEYS', 'OPENAI_API_KEY'], + missingCredentialMessage: + 'Set REQUESTY_API_KEY or OPENAI_API_KEYS / OPENAI_API_KEY for the Requesty gateway.', + }, + catalog: { + source: 'hybrid', + discovery: { + kind: 'openai-compatible', + // The public model list works without a key; inference still needs one. + requiresAuth: false, + mapModel: mapRequestyModel, + }, + discoveryCacheTtl: '1d', + discoveryRefreshMode: 'background-if-stale', + allowManualRefresh: true, + models: [ + { id: 'requesty-gpt-5-mini', apiName: 'openai/gpt-5-mini', label: 'GPT-5 Mini (via Requesty)', modelDescriptorId: 'gpt-5-mini' }, + { id: 'requesty-claude-sonnet-4-6', apiName: 'anthropic/claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (via Requesty)', modelDescriptorId: 'claude-sonnet-4-6' }, + { id: 'requesty-grok-4.6', apiName: 'xai/grok-4.6', label: 'Grok 4.6 (via Requesty)', modelDescriptorId: 'grok-4.6' }, + ], + }, + usage: { supported: false }, +}) diff --git a/src/integrations/generated/integrationArtifacts.generated.ts b/src/integrations/generated/integrationArtifacts.generated.ts index ee24bd6065..23869ebe09 100644 --- a/src/integrations/generated/integrationArtifacts.generated.ts +++ b/src/integrations/generated/integrationArtifacts.generated.ts @@ -47,6 +47,7 @@ import gatewayOllama from '../gateways/ollama.js' import gatewayOpencodeGo from '../gateways/opencode-go.js' import gatewayOpencode from '../gateways/opencode.js' import gatewayOpenrouter from '../gateways/openrouter.js' +import gatewayRequesty from '../gateways/requesty.js' import gatewayTogether from '../gateways/together.js' import gatewayVertex from '../gateways/vertex.js' import gatewayXiaomiMimoToken from '../gateways/xiaomi-mimo-token.js' @@ -94,7 +95,7 @@ import modelXai from '../models/xai.js' import modelXiaomiMimo from '../models/xiaomi-mimo.js' export const VENDOR_DESCRIPTORS = [vendorAnthropic, vendorBankr, vendorDeepseek, vendorFireworks, vendorGemini, vendorLongcat, vendorMinimax, vendorMoonshot, vendorNearai, vendorOpenai, vendorVenice, vendorXai, vendorXiaomiMimo, vendorZai] as const satisfies readonly VendorDescriptor[] -export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApiRoute, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCommandcode, gatewayConcentrate, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLlmtr, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[] +export const GATEWAY_DESCRIPTORS = [gatewayAimlapi, gatewayApiRoute, gatewayApismart, gatewayAtlasCloud, gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayClinepass, gatewayCloudflare, gatewayCommandcode, gatewayConcentrate, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLlmtr, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayRequesty, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[] export const ANTHROPIC_PROXY_DESCRIPTORS = [anthropicproxyCustom] as const satisfies readonly AnthropicProxyDescriptor[] export const BRAND_DESCRIPTORS = [brandClaude, brandDeepseek, brandFireworks, brandGemini, brandGlm, brandGpt, brandKimi, brandLing, brandLlama, brandLongcat, brandMacaron, brandMinimax, brandMistral, brandNearai, brandNemotron, brandOpenaiCompatibleAlias, brandQwen, brandTencent, brandXai, brandXiaomiMimo] as const satisfies readonly BrandDescriptor[] export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelKimi, modelLing, modelLlama, modelLongcat, modelMacaron, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelTencent, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[] diff --git a/src/integrations/generated/integrationManifest.generated.ts b/src/integrations/generated/integrationManifest.generated.ts index 77b29a5b85..42c3405964 100644 --- a/src/integrations/generated/integrationManifest.generated.ts +++ b/src/integrations/generated/integrationManifest.generated.ts @@ -464,6 +464,17 @@ export const PROVIDER_PRESET_MANIFEST = [ "OPENROUTER_API_KEY" ] }, + { + "preset": "requesty", + "routeKind": "gateway", + "routeId": "requesty", + "vendorId": "openai", + "gatewayId": "requesty", + "description": "Requesty OpenAI-compatible gateway", + "apiKeyEnvVars": [ + "REQUESTY_API_KEY" + ] + }, { "preset": "together", "routeKind": "gateway", @@ -635,6 +646,7 @@ export const ORDERED_PROVIDER_PRESETS = [ "opencode-go", "opencode", "openrouter", + "requesty", "together", "venice", "xai", diff --git a/src/integrations/routeMetadata.ts b/src/integrations/routeMetadata.ts index d7e520a229..20fb89968c 100644 --- a/src/integrations/routeMetadata.ts +++ b/src/integrations/routeMetadata.ts @@ -511,6 +511,43 @@ export function isCanonicalLlmtrInferenceBaseUrl( } } +const REQUESTY_INFERENCE_HOSTS = new Set([ + 'router.requesty.ai', + 'router.eu.requesty.ai', + 'router.us.requesty.ai', + 'router.ap.requesty.ai', +]) + +/** + * Requesty serves the same OpenAI-compatible /v1 API from a global host and + * regional hosts (EU, US, AP) that accept the same key. Only those exact https + * origins with the /v1 path may receive REQUESTY_API_KEY. + */ +export function isCanonicalRequestyInferenceBaseUrl( + value: string | undefined, +): boolean { + const trimmed = value?.trim() + if (!trimmed) { + return false + } + + try { + const candidate = new URL(trimmed) + return ( + candidate.protocol === 'https:' && + !candidate.port && + !candidate.username && + !candidate.password && + !candidate.search && + !candidate.hash && + REQUESTY_INFERENCE_HOSTS.has(candidate.hostname.toLowerCase()) && + candidate.pathname.replace(/\/+$/, '') === '/v1' + ) + } catch { + return false + } +} + const COMMANDCODE_CANONICAL_INFERENCE_BASE_URL = 'https://api.commandcode.ai/provider/v1' @@ -1276,6 +1313,13 @@ export function resolveRouteCredentialValue( ) { return undefined } + if ( + routeId === 'requesty' && + options?.baseUrl !== undefined && + !isCanonicalRequestyInferenceBaseUrl(options.baseUrl) + ) { + return undefined + } return getRouteCredentialValue(routeId, processEnv) } diff --git a/src/utils/envFile.ts b/src/utils/envFile.ts index 26353069a7..fe96dbfa18 100644 --- a/src/utils/envFile.ts +++ b/src/utils/envFile.ts @@ -114,6 +114,7 @@ const ALLOWED_ENV_FILE_KEYS = new Set([ 'OPENAI_AZURE_STYLE', 'OPENAI_BASE_URL', 'OPENAI_MODEL', + 'REQUESTY_API_KEY', 'TAVILY_API_KEY', 'TOGETHER_API_KEY', 'VENICE_API_KEY', From 757d4497c800006ea47808c1a97496fa5cff1086 Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Mon, 28 Sep 2026 15:43:34 +0100 Subject: [PATCH 2/4] test(gateways): cover Requesty model mapping and credential boundary Signed-off-by: Thibault Jaigu --- src/integrations/gateways/requesty.test.ts | 91 ++++++++++++++++++++++ src/integrations/routeMetadata.test.ts | 79 +++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/integrations/gateways/requesty.test.ts diff --git a/src/integrations/gateways/requesty.test.ts b/src/integrations/gateways/requesty.test.ts new file mode 100644 index 0000000000..2462de89fb --- /dev/null +++ b/src/integrations/gateways/requesty.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, test } from 'bun:test' +import requesty, { mapRequestyModel } from './requesty.js' + +describe('requesty gateway live model mapping', () => { + test('uses hybrid discovery against the public models list', () => { + expect(requesty.catalog?.source).toBe('hybrid') + expect(requesty.catalog?.discovery).toEqual( + expect.objectContaining({ + kind: 'openai-compatible', + requiresAuth: false, + }), + ) + expect(requesty.catalog?.discovery?.mapModel).toBe(mapRequestyModel) + }) + + test('maps Requesty model fields into catalog entries', () => { + expect( + mapRequestyModel({ + id: 'anthropic/claude-sonnet-4-6', + object: 'model', + api: 'chat', + context_window: 1000000, + max_output_tokens: 64000, + supports_tool_calling: true, + supports_reasoning: true, + supports_vision: true, + input_price: 0.000003, + }), + ).toEqual({ + id: 'anthropic/claude-sonnet-4-6', + apiName: 'anthropic/claude-sonnet-4-6', + label: 'anthropic/claude-sonnet-4-6', + contextWindow: 1000000, + maxOutputTokens: 64000, + capabilities: { + supportsFunctionCalling: true, + supportsReasoning: true, + }, + }) + + expect( + mapRequestyModel({ + id: 'xai/grok-4.6', + api: 'chat', + context_window: 256000, + max_output_tokens: 0, + supports_tool_calling: false, + supports_reasoning: false, + }), + ).toEqual({ + id: 'xai/grok-4.6', + apiName: 'xai/grok-4.6', + label: 'xai/grok-4.6', + contextWindow: 256000, + }) + }) + + test('filters non-chat and non-coding models', () => { + expect( + mapRequestyModel({ + id: 'openai/text-embedding-3-small', + api: 'embedding', + }), + ).toBeNull() + + expect( + mapRequestyModel({ + id: 'vendor/some-model', + api: 'image', + }), + ).toBeNull() + + expect(mapRequestyModel({ id: 'openai/text-embedding-3-large' })).toBeNull() + expect(mapRequestyModel({})).toBeNull() + expect(mapRequestyModel({ id: ' ' })).toBeNull() + expect(mapRequestyModel(null)).toBeNull() + expect(mapRequestyModel('openai/gpt-5-mini')).toBeNull() + }) + + test('drops model ids carrying control or ANSI escape characters', () => { + expect( + mapRequestyModel({ id: 'openai/gpt-5-mini\u001b[31m', api: 'chat' }), + ).toBeNull() + expect( + mapRequestyModel({ id: 'openai/gpt\u0007-5-mini', api: 'chat' }), + ).toBeNull() + expect( + mapRequestyModel({ id: 'openai/gpt-5-mini\u009b', api: 'chat' }), + ).toBeNull() + }) +}) diff --git a/src/integrations/routeMetadata.test.ts b/src/integrations/routeMetadata.test.ts index 08ec33ca8a..f8373a8d14 100644 --- a/src/integrations/routeMetadata.test.ts +++ b/src/integrations/routeMetadata.test.ts @@ -8,6 +8,7 @@ import { getRouteProviderTypeLabel, isApismartBaseUrl, isCanonicalApismartInferenceBaseUrl, + isCanonicalRequestyInferenceBaseUrl, isCloudflareBaseUrl, isConcentrateBaseUrl, isLongcatBaseUrl, @@ -505,6 +506,84 @@ test('isCanonicalApismartInferenceBaseUrl requires the exact /v1 inference path' expect(isApismartBaseUrl('https://gw.apismart.ai')).toBe(true) }) +test('isCanonicalRequestyInferenceBaseUrl accepts only the https /v1 hosts', () => { + for (const baseUrl of [ + 'https://router.requesty.ai/v1', + 'https://router.requesty.ai/v1/', + 'https://router.eu.requesty.ai/v1', + 'https://router.us.requesty.ai/v1', + 'https://router.ap.requesty.ai/v1', + ]) { + expect(isCanonicalRequestyInferenceBaseUrl(baseUrl)).toBe(true) + } + + for (const baseUrl of [ + undefined, + '', + 'not a url', + 'http://router.requesty.ai/v1', + 'https://router.requesty.ai:8443/v1', + 'https://user:pass@router.requesty.ai/v1', + 'https://router.requesty.ai/v1?x=1', + 'https://router.requesty.ai/v1#fragment', + 'https://router.requesty.ai', + 'https://router.requesty.ai/v1/models', + 'https://router.requesty.ai.evil.example/v1', + 'https://evil-router.requesty.ai/v1', + 'https://requesty.ai/v1', + ]) { + expect(isCanonicalRequestyInferenceBaseUrl(baseUrl)).toBe(false) + } +}) + +test('Requesty regional hosts resolve to the requesty route', () => { + expect(resolveRouteIdFromBaseUrl('https://router.requesty.ai/v1')).toBe( + 'requesty', + ) + expect(resolveRouteIdFromBaseUrl('https://router.eu.requesty.ai/v1')).toBe( + 'requesty', + ) + expect( + resolveRouteIdFromBaseUrl('https://router.requesty.ai.evil.example/v1'), + ).toBe(null) +}) + +test('Requesty credential is limited to the canonical inference base URLs', () => { + const processEnv = { + REQUESTY_API_KEY: 'requesty-secret', + OPENAI_API_KEY: 'sk-openai-fallback', + } + + expect( + resolveRouteCredentialValue({ + routeId: 'requesty', + baseUrl: 'https://router.eu.requesty.ai/v1', + processEnv, + }), + ).toBe('requesty-secret') + expect( + resolveRouteCredentialValue({ + routeId: 'requesty', + baseUrl: 'https://router.requesty.ai/v1', + processEnv: { OPENAI_API_KEY: 'sk-openai-fallback' }, + }), + ).toBe('sk-openai-fallback') + expect( + resolveRouteCredentialValue({ + routeId: 'requesty', + baseUrl: 'http://router.requesty.ai/v1', + processEnv, + }), + ).toBeUndefined() + expect( + resolveRouteCredentialValue({ + routeId: 'requesty', + baseUrl: 'https://router.requesty.ai/v1/models', + processEnv, + }), + ).toBeUndefined() +}) + test('AI/ML API route credential discovery ignores placeholder dedicated key', () => { expect( resolveRouteCredentialValue({ From 39419fc5428a236c3b544b3dec1c80fc3617829f Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Mon, 28 Sep 2026 15:43:34 +0100 Subject: [PATCH 3/4] docs: add Requesty gateway setup Signed-off-by: Thibault Jaigu --- .env.example | 7 +++++++ README.md | 1 + web/src/data/providers.ts | 8 ++++++++ 3 files changed, 16 insertions(+) diff --git a/.env.example b/.env.example index 7e516a003b..180c7899ac 100644 --- a/.env.example +++ b/.env.example @@ -228,6 +228,13 @@ ANTHROPIC_API_KEY=sk-ant-your-key-here # OPENAI_BASE_URL=https://llmtr.com/v1 # OPENAI_MODEL=deepseek/deepseek-v4-flash +# For Requesty, prefer its dedicated key. Raw env setup must also set +# OPENAI_BASE_URL and OPENAI_MODEL. OPENAI_API_KEY remains supported. +# For EU processing use OPENAI_BASE_URL=https://router.eu.requesty.ai/v1: +# REQUESTY_API_KEY=your-requesty-key-here +# OPENAI_BASE_URL=https://router.requesty.ai/v1 +# OPENAI_MODEL=openai/gpt-5-mini + # For Command Code, use its dedicated key. Claude models on this provider # require the Anthropic /messages API and are not routed here. Select the # route with /provider or --provider commandcode (or CLAUDE_CODE_USE_OPENAI=1); diff --git a/README.md b/README.md index a26fe90666..ffba37fe6e 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,7 @@ Advanced and source-build guides: | ApiSmart | `/provider` or `APISMART_API_KEY` | Uses `https://gw.apismart.ai/v1`, defaults to `DEEPSEEK_V4_FLASH`, and supports optional `APISMART_MODEL` plus authenticated model discovery | | API Route | `/provider` or `API_ROUTE_API_KEY` | Uses `https://global.api-route.com/v1`, defaults to `claude-sonnet-4-6`, and supports optional `API_ROUTE_MODEL` plus authenticated model discovery | | Hicap | `/provider` or OpenAI-compatible env vars | Uses `api-key` auth, discovers models from unauthenticated `/models`, and supports Responses mode for `gpt-` models | +| Requesty | `/provider` or OpenAI-compatible env vars | OpenAI-compatible gateway at `https://router.requesty.ai/v1` (EU: `https://router.eu.requesty.ai/v1`); `/provider` and `--provider requesty` default to `openai/gpt-5-mini`, while raw env setup must set `OPENAI_BASE_URL` and `OPENAI_MODEL`; accepts `REQUESTY_API_KEY` or `OPENAI_API_KEY` after the route is selected and discovers chat models from the public `/models` catalog | | Fireworks AI | `/provider` or env vars | First-class provider with 276 curated models (DeepSeek, Qwen, Llama, Gemma, and more); uses `FIREWORKS_API_KEY` | | LongCat | `/provider` or env vars | Meituan LongCat OpenAI-compatible API at `https://api.longcat.chat/openai/v1`; uses `LONGCAT_API_KEY` and defaults to `LongCat-2.0` | | ClinePass | `/provider` or env vars | AI model gateway with usage limits (5hr, weekly, monthly); uses `CLINE_API_KEY` at `https://api.cline.bot/api/v1` | diff --git a/web/src/data/providers.ts b/web/src/data/providers.ts index 44e46e6ffe..f004c32387 100644 --- a/web/src/data/providers.ts +++ b/web/src/data/providers.ts @@ -140,6 +140,14 @@ export const providers: Provider[] = [ envVars: ['OPENROUTER_API_KEY'], notes: 'OpenAI-compatible aggregation across hundreds of hosted models.', }, + { + id: 'requesty', + name: 'Requesty', + group: 'gateways', + setup: '/provider or OpenAI-compatible env vars', + envVars: ['REQUESTY_API_KEY', 'OPENAI_API_KEY'], + notes: 'OpenAI-compatible gateway at https://router.requesty.ai/v1 (EU: https://router.eu.requesty.ai/v1); /provider and --provider requesty default to openai/gpt-5-mini. Uses public discovery of chat models.', + }, { id: 'llmtr', name: 'LLMTR', From fcbefd93609d8df1150705b754bdf841ad9a60dd Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Mon, 28 Sep 2026 17:56:27 +0100 Subject: [PATCH 4/4] test(env): cover loading REQUESTY_API_KEY from provider env files Signed-off-by: Thibault Jaigu --- src/utils/envFile.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/utils/envFile.test.ts b/src/utils/envFile.test.ts index 9d9febc408..87369c11be 100644 --- a/src/utils/envFile.test.ts +++ b/src/utils/envFile.test.ts @@ -32,6 +32,7 @@ const TEST_ENV_KEYS = [ 'OPENAI_BASE_URL', 'OPENAI_MODEL', 'OPENCLAUDE_OLLAMA_NUM_CTX', + 'REQUESTY_API_KEY', 'OLLAMA_API_KEY', 'OLLAMA_BASE_URL', 'WEB_AUTH_HEADER', @@ -354,6 +355,17 @@ describe('loadEnvFile', () => { expect(loaded).toEqual({ LLMTR_API_KEY: 'llmtr-key' }) }) + it('loads the dedicated Requesty credential without selecting a route', () => { + const filePath = writeTempEnvFile('REQUESTY_API_KEY=requesty-key') + + const loaded = loadEnvFile(filePath) + + expect(process.env.REQUESTY_API_KEY).toBe('requesty-key') + expect(process.env.CLAUDE_CODE_USE_OPENAI).toBeUndefined() + expect(process.env.OPENAI_BASE_URL).toBeUndefined() + expect(loaded).toEqual({ REQUESTY_API_KEY: 'requesty-key' }) + }) + it('loads the dedicated Command Code credential without selecting a route', () => { const filePath = writeTempEnvFile('CMD_API_KEY=cmd-key')