mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-11-06 12:50:54 -04:00
fix(requesty): bind route identity and discovery to the canonical endpoint
Requesty's credential was bound to the canonical https /v1 hosts, but the sibling sites that apply the same boundary for LLMTR, Merge, Command Code and API Route did not include Requesty: - URL route resolution, active-profile and capability resolution, and provider validation treated any Requesty host or retargeted profile as `requesty`, so validation accepted REQUESTY_API_KEY for a URL whose requests withhold it and capability surfaces kept the Requesty route. - Hybrid discovery forwarded a caller or saved key to a noncanonical base URL, and a retargeted profile still triggered startup discovery. - A profile saved for a noncanonical URL persisted no route identity, so the relaunch retarget guard missed it and its saved custom auth and headers followed a shell retarget onto canonical Requesty. - REQUESTY_API_KEY was not protected from settings overrides when the host manages the provider. Register Requesty at each of those sites, matching its siblings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ const originalEnv = {
|
|||||||
OPENAI_AUTH_SCHEME: process.env.OPENAI_AUTH_SCHEME,
|
OPENAI_AUTH_SCHEME: process.env.OPENAI_AUTH_SCHEME,
|
||||||
APISMART_API_KEY: process.env.APISMART_API_KEY,
|
APISMART_API_KEY: process.env.APISMART_API_KEY,
|
||||||
MERGE_GATEWAY_API_KEY: process.env.MERGE_GATEWAY_API_KEY,
|
MERGE_GATEWAY_API_KEY: process.env.MERGE_GATEWAY_API_KEY,
|
||||||
|
REQUESTY_API_KEY: process.env.REQUESTY_API_KEY,
|
||||||
ANTHROPIC_CUSTOM_HEADERS: process.env.ANTHROPIC_CUSTOM_HEADERS,
|
ANTHROPIC_CUSTOM_HEADERS: process.env.ANTHROPIC_CUSTOM_HEADERS,
|
||||||
CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,
|
CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,
|
||||||
CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,
|
CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,
|
||||||
@@ -73,6 +74,7 @@ function clearProviderEnv(): void {
|
|||||||
delete process.env.OPENAI_AUTH_SCHEME
|
delete process.env.OPENAI_AUTH_SCHEME
|
||||||
delete process.env.APISMART_API_KEY
|
delete process.env.APISMART_API_KEY
|
||||||
delete process.env.MERGE_GATEWAY_API_KEY
|
delete process.env.MERGE_GATEWAY_API_KEY
|
||||||
|
delete process.env.REQUESTY_API_KEY
|
||||||
delete process.env.ANTHROPIC_CUSTOM_HEADERS
|
delete process.env.ANTHROPIC_CUSTOM_HEADERS
|
||||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||||
@@ -237,6 +239,32 @@ describe('discoverModelsForRoute', () => {
|
|||||||
expect(authorizations).toEqual([])
|
expect(authorizations).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('withholds explicit and ambient Requesty keys from retargeted discovery', async () => {
|
||||||
|
const { discoverModelsForRoute } = await loadDiscoveryServiceModule()
|
||||||
|
process.env.REQUESTY_API_KEY = 'requesty-secret'
|
||||||
|
const authorizations: Array<string | null> = []
|
||||||
|
setMockFetch(mock((_input: string | URL | Request, init?: RequestInit) => {
|
||||||
|
authorizations.push(new Headers(init?.headers).get('authorization'))
|
||||||
|
return Promise.resolve(new Response(JSON.stringify({ data: [] })))
|
||||||
|
}) as unknown as typeof globalThis.fetch)
|
||||||
|
for (const baseUrl of [
|
||||||
|
'https://proxy.example/v1',
|
||||||
|
'http://router.requesty.ai/v1',
|
||||||
|
]) {
|
||||||
|
for (const apiKey of [undefined, 'requesty-secret']) {
|
||||||
|
await discoverModelsForRoute('requesty', {
|
||||||
|
baseUrl, apiKey, forceRefresh: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(authorizations).toEqual([null, null, null, null])
|
||||||
|
|
||||||
|
await discoverModelsForRoute('requesty', {
|
||||||
|
baseUrl: 'https://router.requesty.ai/v1', forceRefresh: true,
|
||||||
|
})
|
||||||
|
expect(authorizations.at(-1)).toBe('Bearer requesty-secret')
|
||||||
|
})
|
||||||
|
|
||||||
test('caches the complete native Merge catalog after cursor continuation', async () => {
|
test('caches the complete native Merge catalog after cursor continuation', async () => {
|
||||||
const { discoverModelsForRoute } = await loadDiscoveryServiceModule()
|
const { discoverModelsForRoute } = await loadDiscoveryServiceModule()
|
||||||
process.env.MERGE_GATEWAY_API_KEY = 'merge-key'
|
process.env.MERGE_GATEWAY_API_KEY = 'merge-key'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
getRouteDescriptor,
|
getRouteDescriptor,
|
||||||
isCanonicalApismartInferenceBaseUrl,
|
isCanonicalApismartInferenceBaseUrl,
|
||||||
isCanonicalMergeGatewayInferenceBaseUrl,
|
isCanonicalMergeGatewayInferenceBaseUrl,
|
||||||
|
isCanonicalRequestyInferenceBaseUrl,
|
||||||
isCanonicalXaiInferenceBaseUrl,
|
isCanonicalXaiInferenceBaseUrl,
|
||||||
resolveActiveRouteIdFromEnv,
|
resolveActiveRouteIdFromEnv,
|
||||||
resolveRouteCredentialValue,
|
resolveRouteCredentialValue,
|
||||||
@@ -228,7 +229,9 @@ function getRouteDiscoveryApiKey(
|
|||||||
(routeId === 'apismart' &&
|
(routeId === 'apismart' &&
|
||||||
!isCanonicalApismartInferenceBaseUrl(baseUrl)) ||
|
!isCanonicalApismartInferenceBaseUrl(baseUrl)) ||
|
||||||
(routeId === 'merge-gateway' &&
|
(routeId === 'merge-gateway' &&
|
||||||
!isCanonicalMergeGatewayInferenceBaseUrl(baseUrl))
|
!isCanonicalMergeGatewayInferenceBaseUrl(baseUrl)) ||
|
||||||
|
(routeId === 'requesty' &&
|
||||||
|
!isCanonicalRequestyInferenceBaseUrl(baseUrl))
|
||||||
) {
|
) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,6 +555,34 @@ test('Requesty regional hosts resolve to the requesty route', () => {
|
|||||||
expect(
|
expect(
|
||||||
resolveRouteIdFromBaseUrl('https://router.requesty.ai.evil.example/v1'),
|
resolveRouteIdFromBaseUrl('https://router.requesty.ai.evil.example/v1'),
|
||||||
).toBe(null)
|
).toBe(null)
|
||||||
|
// Requesty route identity follows the same boundary as its credential.
|
||||||
|
for (const baseUrl of [
|
||||||
|
'http://router.requesty.ai/v1',
|
||||||
|
'https://router.requesty.ai:8443/v1',
|
||||||
|
'https://router.requesty.ai/v1?x=1',
|
||||||
|
'https://router.requesty.ai/v1/models',
|
||||||
|
'https://router.eu.requesty.ai/v2',
|
||||||
|
]) {
|
||||||
|
expect(resolveRouteIdFromBaseUrl(baseUrl)).toBe(null)
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
resolveActiveRouteIdFromEnv(
|
||||||
|
{ CLAUDE_CODE_USE_OPENAI: '1' },
|
||||||
|
{
|
||||||
|
activeProfileProvider: 'requesty',
|
||||||
|
activeProfileBaseUrl: 'https://proxy.example/v1',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).not.toBe('requesty')
|
||||||
|
expect(
|
||||||
|
resolveActiveRouteIdFromEnv(
|
||||||
|
{ CLAUDE_CODE_USE_OPENAI: '1' },
|
||||||
|
{
|
||||||
|
activeProfileProvider: 'requesty',
|
||||||
|
activeProfileBaseUrl: 'https://router.requesty.ai/v1',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toBe('requesty')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Requesty credential is limited to the canonical inference base URLs', () => {
|
test('Requesty credential is limited to the canonical inference base URLs', () => {
|
||||||
|
|||||||
@@ -1553,6 +1553,12 @@ export function resolveRouteIdFromBaseUrl(
|
|||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
route.id === 'requesty' &&
|
||||||
|
!isCanonicalRequestyInferenceBaseUrl(baseUrl)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return route.id
|
return route.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1577,7 +1583,9 @@ export function resolveRouteIdFromBaseUrl(
|
|||||||
(route.id === 'commandcode' &&
|
(route.id === 'commandcode' &&
|
||||||
!isCanonicalCommandcodeInferenceBaseUrl(baseUrl)) ||
|
!isCanonicalCommandcodeInferenceBaseUrl(baseUrl)) ||
|
||||||
(route.id === 'api-route' &&
|
(route.id === 'api-route' &&
|
||||||
!isApiRouteBaseUrl(baseUrl))
|
!isApiRouteBaseUrl(baseUrl)) ||
|
||||||
|
(route.id === 'requesty' &&
|
||||||
|
!isCanonicalRequestyInferenceBaseUrl(baseUrl))
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1629,6 +1637,9 @@ function profileRouteHonorsBaseUrlBoundary(
|
|||||||
if (routeId === 'api-route') {
|
if (routeId === 'api-route') {
|
||||||
return isCanonicalApiRouteInferenceBaseUrl(baseUrl)
|
return isCanonicalApiRouteInferenceBaseUrl(baseUrl)
|
||||||
}
|
}
|
||||||
|
if (routeId === 'requesty') {
|
||||||
|
return isCanonicalRequestyInferenceBaseUrl(baseUrl)
|
||||||
|
}
|
||||||
if (routeId === 'zai') {
|
if (routeId === 'zai') {
|
||||||
return !baseUrl || isCanonicalZaiCodingPlanBaseUrl(baseUrl)
|
return !baseUrl || isCanonicalZaiCodingPlanBaseUrl(baseUrl)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const ENV_KEYS = [
|
|||||||
'COMMAND_CODE_API_KEY',
|
'COMMAND_CODE_API_KEY',
|
||||||
'API_ROUTE_API_KEY',
|
'API_ROUTE_API_KEY',
|
||||||
'API_ROUTE_MODEL',
|
'API_ROUTE_MODEL',
|
||||||
|
'REQUESTY_API_KEY',
|
||||||
'OPENAI_API_KEY',
|
'OPENAI_API_KEY',
|
||||||
'OPENAI_AZURE_STYLE',
|
'OPENAI_AZURE_STYLE',
|
||||||
'OPENAI_BASE_URL',
|
'OPENAI_BASE_URL',
|
||||||
@@ -147,6 +148,21 @@ describe('host-managed API Route settings boundary', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('applyConfigEnvironmentVariables', () => {
|
describe('applyConfigEnvironmentVariables', () => {
|
||||||
|
it('preserves a host-managed Requesty key against settings env', () => {
|
||||||
|
process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST = '1'
|
||||||
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||||
|
process.env.OPENAI_BASE_URL = 'https://router.requesty.ai/v1'
|
||||||
|
process.env.REQUESTY_API_KEY = 'host-requesty-key'
|
||||||
|
saveGlobalConfig(current => ({
|
||||||
|
...current,
|
||||||
|
env: { REQUESTY_API_KEY: 'settings-requesty-key' },
|
||||||
|
}))
|
||||||
|
|
||||||
|
applyConfigEnvironmentVariables()
|
||||||
|
|
||||||
|
expect(process.env.REQUESTY_API_KEY).toBe('host-requesty-key')
|
||||||
|
})
|
||||||
|
|
||||||
it('preserves the complete host-managed Command Code route against settings env', () => {
|
it('preserves the complete host-managed Command Code route against settings env', () => {
|
||||||
process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST = '1'
|
process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST = '1'
|
||||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ const PROVIDER_MANAGED_ENV_VARS = new Set([
|
|||||||
'COMMAND_CODE_API_KEY',
|
'COMMAND_CODE_API_KEY',
|
||||||
'API_ROUTE_API_KEY',
|
'API_ROUTE_API_KEY',
|
||||||
'MERGE_GATEWAY_API_KEY',
|
'MERGE_GATEWAY_API_KEY',
|
||||||
|
'REQUESTY_API_KEY',
|
||||||
'ORCAROUTER_API_KEY',
|
'ORCAROUTER_API_KEY',
|
||||||
'RUNCRATE_API_KEY',
|
'RUNCRATE_API_KEY',
|
||||||
'OPENAI_API_KEY',
|
'OPENAI_API_KEY',
|
||||||
|
|||||||
@@ -620,6 +620,39 @@ describe('applyProviderProfileToProcessEnv', () => {
|
|||||||
expect(startupEnv.API_ROUTE_API_KEY).toBeUndefined()
|
expect(startupEnv.API_ROUTE_API_KEY).toBeUndefined()
|
||||||
}, 20_000)
|
}, 20_000)
|
||||||
|
|
||||||
|
test('retargeted Requesty profile runs generically and skips discovery', async () => {
|
||||||
|
const fetchMock = mock(() => Promise.resolve(new Response('{}')))
|
||||||
|
globalThis.fetch = fetchMock as unknown as typeof fetch
|
||||||
|
const { resolveProfileCapabilityRouteId, setActiveProviderProfile } =
|
||||||
|
await importFreshProviderProfileModules()
|
||||||
|
const profile = buildRequestyProfile({
|
||||||
|
id: 'requesty_proxy',
|
||||||
|
baseUrl: 'https://proxy.example/v1',
|
||||||
|
})
|
||||||
|
saveMockGlobalConfig(current => ({
|
||||||
|
...current,
|
||||||
|
providerProfiles: [profile],
|
||||||
|
}))
|
||||||
|
|
||||||
|
setActiveProviderProfile(profile.id, { configDir: testConfigDir ?? undefined })
|
||||||
|
await Promise.resolve()
|
||||||
|
|
||||||
|
expect(process.env.OPENAI_API_KEY).toBeUndefined()
|
||||||
|
expect(process.env.REQUESTY_API_KEY).toBeUndefined()
|
||||||
|
expect(fetchMock).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
for (const baseUrl of [
|
||||||
|
'https://proxy.example/v1',
|
||||||
|
'http://router.requesty.ai/v1',
|
||||||
|
'https://router.requesty.ai/v1/models',
|
||||||
|
]) {
|
||||||
|
expect(resolveProfileCapabilityRouteId('requesty', baseUrl)).toBe('custom')
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
resolveProfileCapabilityRouteId('requesty', 'https://router.eu.requesty.ai/v1'),
|
||||||
|
).toBe('requesty')
|
||||||
|
}, 20_000)
|
||||||
|
|
||||||
test('keyless canonical LLMTR profile adopts its ambient dedicated key', async () => {
|
test('keyless canonical LLMTR profile adopts its ambient dedicated key', async () => {
|
||||||
const { applyProviderProfileToProcessEnv } =
|
const { applyProviderProfileToProcessEnv } =
|
||||||
await importFreshProviderProfileModules()
|
await importFreshProviderProfileModules()
|
||||||
@@ -5365,6 +5398,67 @@ describe('setActiveProviderProfile', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('a saved Requesty proxy profile keeps its auth off a shell retarget onto canonical Requesty', async () => {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-'))
|
||||||
|
const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-'))
|
||||||
|
process.chdir(tempDir)
|
||||||
|
process.env.CLAUDE_CONFIG_DIR = configDir
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { setActiveProviderProfile } =
|
||||||
|
await importFreshProviderProfileModules()
|
||||||
|
const proxy = buildRequestyProfile({
|
||||||
|
id: 'requesty_proxy',
|
||||||
|
baseUrl: 'https://requesty-proxy.example/v1',
|
||||||
|
apiKey: 'saved-proxy-key',
|
||||||
|
authHeader: 'X-Proxy-Auth',
|
||||||
|
authScheme: 'raw',
|
||||||
|
authHeaderValue: 'saved-proxy-secret',
|
||||||
|
customHeaders: { 'X-Proxy-Trace': 'saved-proxy-header' },
|
||||||
|
})
|
||||||
|
saveMockGlobalConfig(current => ({
|
||||||
|
...current,
|
||||||
|
providerProfiles: [proxy],
|
||||||
|
}))
|
||||||
|
|
||||||
|
setActiveProviderProfile('requesty_proxy', { configDir })
|
||||||
|
const persisted = JSON.parse(
|
||||||
|
readFileSync(join(configDir, '.openclaude-profile.json'), 'utf8'),
|
||||||
|
)
|
||||||
|
expect(persisted.env).toMatchObject({
|
||||||
|
CLAUDE_CODE_PROVIDER_ROUTE_ID: 'requesty',
|
||||||
|
OPENAI_BASE_URL: 'https://requesty-proxy.example/v1',
|
||||||
|
OPENAI_AUTH_HEADER_VALUE: 'saved-proxy-secret',
|
||||||
|
})
|
||||||
|
expect(persisted.env.OPENAI_API_KEY).toBeUndefined()
|
||||||
|
|
||||||
|
const { buildStartupEnvFromProfile } = await import(
|
||||||
|
`./providerProfile.js?ts=${Date.now()}-${Math.random()}`
|
||||||
|
)
|
||||||
|
const ownUrlEnv = await buildStartupEnvFromProfile({
|
||||||
|
persisted,
|
||||||
|
processEnv: {},
|
||||||
|
})
|
||||||
|
expect(ownUrlEnv.OPENAI_AUTH_HEADER_VALUE).toBe('saved-proxy-secret')
|
||||||
|
|
||||||
|
const retargetedEnv = await buildStartupEnvFromProfile({
|
||||||
|
persisted,
|
||||||
|
processEnv: { OPENAI_BASE_URL: 'https://router.requesty.ai/v1' },
|
||||||
|
})
|
||||||
|
expect(retargetedEnv.OPENAI_BASE_URL).toBe('https://router.requesty.ai/v1')
|
||||||
|
expect(retargetedEnv.OPENAI_API_KEY).toBeUndefined()
|
||||||
|
expect(retargetedEnv.REQUESTY_API_KEY).toBeUndefined()
|
||||||
|
expect(retargetedEnv.OPENAI_AUTH_HEADER).toBeUndefined()
|
||||||
|
expect(retargetedEnv.OPENAI_AUTH_SCHEME).toBeUndefined()
|
||||||
|
expect(retargetedEnv.OPENAI_AUTH_HEADER_VALUE).toBeUndefined()
|
||||||
|
expect(retargetedEnv.ANTHROPIC_CUSTOM_HEADERS).toBeUndefined()
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd)
|
||||||
|
rmSync(tempDir, { recursive: true, force: true })
|
||||||
|
rmSync(configDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test('keyed canonical LLMTR profiles persist their saved dedicated credential across restart', async () => {
|
test('keyed canonical LLMTR profiles persist their saved dedicated credential across restart', async () => {
|
||||||
const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-'))
|
const tempDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-'))
|
||||||
const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-'))
|
const configDir = mkdtempSync(join(tmpdir(), 'openclaude-provider-config-'))
|
||||||
|
|||||||
@@ -377,7 +377,8 @@ export function resolveProfileCapabilityRouteId(
|
|||||||
providerRouteId === 'api-route' ||
|
providerRouteId === 'api-route' ||
|
||||||
providerRouteId === 'llmtr' ||
|
providerRouteId === 'llmtr' ||
|
||||||
providerRouteId === 'merge-gateway' ||
|
providerRouteId === 'merge-gateway' ||
|
||||||
providerRouteId === 'commandcode') &&
|
providerRouteId === 'commandcode' ||
|
||||||
|
providerRouteId === 'requesty') &&
|
||||||
baseUrl &&
|
baseUrl &&
|
||||||
!(providerRouteId === 'cloudflare'
|
!(providerRouteId === 'cloudflare'
|
||||||
? isCloudflareBaseUrl(baseUrl)
|
? isCloudflareBaseUrl(baseUrl)
|
||||||
@@ -391,6 +392,8 @@ export function resolveProfileCapabilityRouteId(
|
|||||||
? isCanonicalCommandcodeInferenceBaseUrl(baseUrl)
|
? isCanonicalCommandcodeInferenceBaseUrl(baseUrl)
|
||||||
: providerRouteId === 'merge-gateway'
|
: providerRouteId === 'merge-gateway'
|
||||||
? isCanonicalMergeGatewayInferenceBaseUrl(baseUrl)
|
? isCanonicalMergeGatewayInferenceBaseUrl(baseUrl)
|
||||||
|
: providerRouteId === 'requesty'
|
||||||
|
? isCanonicalRequestyInferenceBaseUrl(baseUrl)
|
||||||
: isCanonicalLlmtrInferenceBaseUrl(baseUrl))
|
: isCanonicalLlmtrInferenceBaseUrl(baseUrl))
|
||||||
) {
|
) {
|
||||||
return 'custom'
|
return 'custom'
|
||||||
@@ -2103,6 +2106,11 @@ function buildOpenAICompatibleStartupEnv(
|
|||||||
if (activeProfileRouteId === 'commandcode') {
|
if (activeProfileRouteId === 'commandcode') {
|
||||||
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'commandcode'
|
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'commandcode'
|
||||||
}
|
}
|
||||||
|
// Relaunch uses this identity to keep a saved non-canonical Requesty
|
||||||
|
// profile's custom auth and headers off a shell retarget to Requesty.
|
||||||
|
if (activeProfileRouteId === 'requesty') {
|
||||||
|
env.CLAUDE_CODE_PROVIDER_ROUTE_ID = 'requesty'
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
activeProfile.apiKey &&
|
activeProfile.apiKey &&
|
||||||
!withholdRetargetedApismartCredential &&
|
!withholdRetargetedApismartCredential &&
|
||||||
@@ -2439,6 +2447,9 @@ function triggerStartupDiscoveryRefreshForProfile(
|
|||||||
if (route.routeId === 'commandcode' && !isCommandcodeProfile(profile)) {
|
if (route.routeId === 'commandcode' && !isCommandcodeProfile(profile)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (route.routeId === 'requesty' && !isRequestyProfile(profile)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
void refreshStartupDiscoveryForRoute(route.routeId, {
|
void refreshStartupDiscoveryForRoute(route.routeId, {
|
||||||
baseUrl: profile.baseUrl,
|
baseUrl: profile.baseUrl,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const ENV_KEYS = [
|
|||||||
'LONGCAT_API_KEY',
|
'LONGCAT_API_KEY',
|
||||||
'LLMTR_API_KEY',
|
'LLMTR_API_KEY',
|
||||||
'MERGE_GATEWAY_API_KEY',
|
'MERGE_GATEWAY_API_KEY',
|
||||||
|
'REQUESTY_API_KEY',
|
||||||
'CMD_API_KEY',
|
'CMD_API_KEY',
|
||||||
'COMMANDCODE_API_KEY',
|
'COMMANDCODE_API_KEY',
|
||||||
'COMMAND_CODE_API_KEY',
|
'COMMAND_CODE_API_KEY',
|
||||||
@@ -596,6 +597,27 @@ test('noncanonical ApiSmart paths do not validate a dedicated credential', async
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('noncanonical Requesty host paths do not validate a dedicated credential', async () => {
|
||||||
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||||
|
process.env.OPENAI_MODEL = 'openai/gpt-5-mini'
|
||||||
|
process.env.REQUESTY_API_KEY = 'requesty-key'
|
||||||
|
delete process.env.OPENAI_API_KEY
|
||||||
|
delete process.env.OPENAI_API_KEYS
|
||||||
|
|
||||||
|
process.env.OPENAI_BASE_URL = 'https://router.requesty.ai/v1'
|
||||||
|
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
|
||||||
|
|
||||||
|
// The request withholds REQUESTY_API_KEY here, so validation must not accept it.
|
||||||
|
process.env.OPENAI_BASE_URL = 'https://router.requesty.ai/v1/models'
|
||||||
|
const message = await getProviderValidationError(process.env)
|
||||||
|
expect(message).toContain(
|
||||||
|
'OPENAI_API_KEYS or OPENAI_API_KEY is required when CLAUDE_CODE_USE_OPENAI=1',
|
||||||
|
)
|
||||||
|
|
||||||
|
process.env.OPENAI_API_KEY = 'generic-proxy-key'
|
||||||
|
await expect(getProviderValidationError(process.env)).resolves.toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
test('noncanonical Concentrate host paths fall back to generic OpenAI validation', async () => {
|
test('noncanonical Concentrate host paths fall back to generic OpenAI validation', async () => {
|
||||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||||
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
process.env.OPENAI_BASE_URL = 'https://api.concentrate.ai/staging/v1'
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
isCanonicalConcentrateInferenceBaseUrl,
|
isCanonicalConcentrateInferenceBaseUrl,
|
||||||
isCanonicalCommandcodeInferenceBaseUrl,
|
isCanonicalCommandcodeInferenceBaseUrl,
|
||||||
isCanonicalMergeGatewayInferenceBaseUrl,
|
isCanonicalMergeGatewayInferenceBaseUrl,
|
||||||
|
isCanonicalRequestyInferenceBaseUrl,
|
||||||
isCloudflareBaseUrl,
|
isCloudflareBaseUrl,
|
||||||
isLongcatBaseUrl,
|
isLongcatBaseUrl,
|
||||||
matchHostnameAgainstRouteHosts,
|
matchHostnameAgainstRouteHosts,
|
||||||
@@ -326,7 +327,9 @@ function getRuntimeValidationTarget(
|
|||||||
(target.descriptor.id === 'merge-gateway' &&
|
(target.descriptor.id === 'merge-gateway' &&
|
||||||
!isCanonicalMergeGatewayInferenceBaseUrl(request.baseUrl)) ||
|
!isCanonicalMergeGatewayInferenceBaseUrl(request.baseUrl)) ||
|
||||||
(target.descriptor.id === 'commandcode' &&
|
(target.descriptor.id === 'commandcode' &&
|
||||||
!isCanonicalCommandcodeInferenceBaseUrl(request.baseUrl)))
|
!isCanonicalCommandcodeInferenceBaseUrl(request.baseUrl)) ||
|
||||||
|
(target.descriptor.id === 'requesty' &&
|
||||||
|
!isCanonicalRequestyInferenceBaseUrl(request.baseUrl)))
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user