mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 14:21:20 +00:00
fix: route OpenRouter profiles through v1 API
This commit is contained in:
@@ -9,6 +9,11 @@ function ensureSupportedProtocol(parsed: URL): void {
|
||||
}
|
||||
}
|
||||
|
||||
function isOpenRouterHost(hostname: string): boolean {
|
||||
const normalized = hostname.toLowerCase();
|
||||
return normalized === 'openrouter.ai' || normalized.endsWith('.openrouter.ai');
|
||||
}
|
||||
|
||||
function buildResolvedUrl(baseUrl: string, suffix: string): string {
|
||||
const parsed = new URL(baseUrl);
|
||||
ensureSupportedProtocol(parsed);
|
||||
@@ -18,6 +23,11 @@ function buildResolvedUrl(baseUrl: string, suffix: string): string {
|
||||
return parsed.toString();
|
||||
}
|
||||
|
||||
if (isOpenRouterHost(parsed.hostname) && pathname.endsWith('/api')) {
|
||||
parsed.pathname = `${pathname}/v1${suffix}`;
|
||||
return parsed.toString();
|
||||
}
|
||||
|
||||
if (pathname.endsWith('/v1') || pathname.endsWith('/api')) {
|
||||
parsed.pathname = `${pathname}${suffix.startsWith('/') ? suffix : `/${suffix}`}`;
|
||||
return parsed.toString();
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface ProviderPresetDefinition {
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export const OPENROUTER_BASE_URL = 'https://openrouter.ai/api';
|
||||
export const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
||||
|
||||
/**
|
||||
* Legacy aliases mapped to canonical preset IDs.
|
||||
|
||||
@@ -134,7 +134,7 @@ describe('profile-writer Anthropic direct', () => {
|
||||
it('preserves OpenRouter ANTHROPIC_API_KEY blank behavior', () => {
|
||||
const result = createApiProfile(
|
||||
'openrouter-test',
|
||||
'https://openrouter.ai/api',
|
||||
'https://openrouter.ai/api/v1',
|
||||
'sk-or-testkey',
|
||||
{ default: 'anthropic/claude-opus-4.5', opus: 'anthropic/claude-opus-4.5', sonnet: 'anthropic/claude-opus-4.5', haiku: 'anthropic/claude-opus-4.5' }
|
||||
);
|
||||
@@ -145,7 +145,7 @@ describe('profile-writer Anthropic direct', () => {
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
|
||||
// OpenRouter: proxy mode with ANTHROPIC_API_KEY explicitly blank
|
||||
expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://openrouter.ai/api');
|
||||
expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://openrouter.ai/api/v1');
|
||||
expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe('sk-or-testkey');
|
||||
expect(settings.env.ANTHROPIC_API_KEY).toBe('');
|
||||
});
|
||||
|
||||
@@ -92,6 +92,11 @@ describe('provider-presets', () => {
|
||||
expect(isValidPresetId('hf')).toBe(true);
|
||||
});
|
||||
|
||||
it('uses OpenRouter v1 as the OpenAI-compatible API root', () => {
|
||||
const preset = getPresetById('openrouter');
|
||||
expect(preset?.baseUrl).toBe('https://openrouter.ai/api/v1');
|
||||
});
|
||||
|
||||
it('keeps Anthropic direct last in the recommended preset order and reuses the Claude logo', () => {
|
||||
const recommendedPresetIds = PROVIDER_PRESETS.filter(
|
||||
(preset) => preset.category === 'recommended'
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import {
|
||||
resolveOpenAIChatCompletionsUrl,
|
||||
resolveOpenAIModelsUrl,
|
||||
} from '../../../src/proxy/upstream-url';
|
||||
|
||||
describe('OpenAI-compatible upstream URL resolution', () => {
|
||||
it('routes current OpenRouter API roots through /api/v1', () => {
|
||||
expect(resolveOpenAIChatCompletionsUrl('https://openrouter.ai/api/v1')).toBe(
|
||||
'https://openrouter.ai/api/v1/chat/completions'
|
||||
);
|
||||
expect(resolveOpenAIModelsUrl('https://openrouter.ai/api/v1')).toBe(
|
||||
'https://openrouter.ai/api/v1/models'
|
||||
);
|
||||
});
|
||||
|
||||
it('repairs legacy OpenRouter /api roots before appending OpenAI endpoints', () => {
|
||||
expect(resolveOpenAIChatCompletionsUrl('https://openrouter.ai/api')).toBe(
|
||||
'https://openrouter.ai/api/v1/chat/completions'
|
||||
);
|
||||
expect(resolveOpenAIModelsUrl('https://openrouter.ai/api')).toBe(
|
||||
'https://openrouter.ai/api/v1/models'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not rewrite non-OpenRouter /api roots', () => {
|
||||
expect(resolveOpenAIChatCompletionsUrl('https://example.test/api')).toBe(
|
||||
'https://example.test/api/chat/completions'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user