[codex] normalize primary site URL handling (#424)
* normalize primary site url handling * fix site create redirect test selector * fix sub2api gemini model discovery and site detect race * fix sub2api antigravity fallback and test selectors * fix site modal wording test * guard blur detect against manual platform changes
This commit is contained in:
@@ -514,7 +514,7 @@ describe('sites proxy settings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('canonicalizes create payload url and platform before persistence and conflict checks', async () => {
|
||||
it('canonicalizes create payload url, strips known non-api api suffixes, and normalizes platform before persistence and conflict checks', async () => {
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sites',
|
||||
@@ -528,7 +528,7 @@ describe('sites proxy settings', () => {
|
||||
expect(created.statusCode).toBe(200);
|
||||
expect(created.json()).toMatchObject({
|
||||
name: 'Aliyun Generic OpenAI',
|
||||
url: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||
url: 'https://coding.dashscope.aliyuncs.com',
|
||||
platform: 'openai',
|
||||
});
|
||||
|
||||
@@ -537,7 +537,7 @@ describe('sites proxy settings', () => {
|
||||
url: '/api/sites',
|
||||
payload: {
|
||||
name: 'Aliyun Generic OpenAI Duplicate',
|
||||
url: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||
url: 'https://coding.dashscope.aliyuncs.com',
|
||||
platform: 'openai',
|
||||
},
|
||||
});
|
||||
@@ -546,7 +546,7 @@ describe('sites proxy settings', () => {
|
||||
expect((duplicate.json() as { error?: string }).error).toContain('already exists');
|
||||
});
|
||||
|
||||
it('canonicalizes update payload url and platform before saving', async () => {
|
||||
it('canonicalizes update payload url, strips known non-api api suffixes, and normalizes platform before saving', async () => {
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sites',
|
||||
@@ -569,7 +569,75 @@ describe('sites proxy settings', () => {
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toMatchObject({
|
||||
url: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||
url: 'https://coding.dashscope.aliyuncs.com',
|
||||
platform: 'openai',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves /api-prefixed main site paths instead of auto-stripping them', async () => {
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sites',
|
||||
payload: {
|
||||
name: 'API Path Site',
|
||||
url: 'https://panel.example.com/api/v1/models',
|
||||
platform: 'openai',
|
||||
},
|
||||
});
|
||||
|
||||
expect(created.statusCode).toBe(200);
|
||||
expect(created.json()).toMatchObject({
|
||||
url: 'https://panel.example.com/api/v1/models',
|
||||
platform: 'openai',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves known semantic paths like codex backend-api roots', async () => {
|
||||
const created = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sites',
|
||||
payload: {
|
||||
name: 'Codex Site',
|
||||
url: 'https://chatgpt.com/backend-api/codex',
|
||||
platform: 'codex',
|
||||
},
|
||||
});
|
||||
|
||||
expect(created.statusCode).toBe(200);
|
||||
expect(created.json()).toMatchObject({
|
||||
url: 'https://chatgpt.com/backend-api/codex',
|
||||
platform: 'codex',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns canonical root urls for detect requests that hit known non-api api suffixes', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sites/detect',
|
||||
payload: {
|
||||
url: 'https://api.openai.com/v1/messages',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toMatchObject({
|
||||
url: 'https://api.openai.com',
|
||||
platform: 'openai',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not strip /api-prefixed paths from detect responses', async () => {
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/sites/detect',
|
||||
payload: {
|
||||
url: 'https://api.openai.com/api/v1/models',
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toMatchObject({
|
||||
url: 'https://api.openai.com/api/v1/models',
|
||||
platform: 'openai',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { db, schema } from '../../db/index.js';
|
||||
import { getInsertedRowId } from '../../db/insertHelpers.js';
|
||||
import { and, asc, eq, inArray } from 'drizzle-orm';
|
||||
import { detectSite } from '../../services/siteDetector.js';
|
||||
import { invalidateSiteProxyCache, normalizeSiteUrl, parseSiteProxyUrlInput } from '../../services/siteProxy.js';
|
||||
import { invalidateSiteProxyCache, parseSiteProxyUrlInput } from '../../services/siteProxy.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
import { invalidateTokenRouterCache } from '../../services/tokenRouter.js';
|
||||
import { parseSiteCustomHeadersInput } from '../../services/siteCustomHeaders.js';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '../../contracts/siteRoutePayloads.js';
|
||||
import { getSiteInitializationPreset } from '../../../shared/siteInitializationPresets.js';
|
||||
import { normalizeSiteApiEndpointBaseUrl } from '../../services/siteApiEndpointService.js';
|
||||
import { analyzePrimarySiteUrl } from '../../../shared/sitePrimaryUrl.js';
|
||||
|
||||
function normalizeSiteStatus(input: unknown): 'active' | 'disabled' | null {
|
||||
if (input === undefined || input === null) return null;
|
||||
@@ -93,9 +94,7 @@ type ErrorLike = {
|
||||
};
|
||||
|
||||
function normalizeCanonicalSiteUrl(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
const withScheme = trimmed.includes('://') ? trimmed : `https://${trimmed}`;
|
||||
return normalizeSiteUrl(withScheme);
|
||||
return analyzePrimarySiteUrl(value).persistedUrl;
|
||||
}
|
||||
|
||||
function normalizeSitePlatform(value: string | undefined): string | null {
|
||||
@@ -520,7 +519,9 @@ export async function sitesRoutes(app: FastifyInstance) {
|
||||
|
||||
const existingSites = await db.select().from(schema.sites).all();
|
||||
const maxSortOrder = existingSites.reduce((max, site) => Math.max(max, site.sortOrder || 0), -1);
|
||||
const canonicalUrl = normalizeCanonicalSiteUrl(url);
|
||||
const analyzedPrimarySiteUrl = analyzePrimarySiteUrl(url);
|
||||
const canonicalUrl = analyzedPrimarySiteUrl.persistedUrl;
|
||||
const detectionUrl = analyzedPrimarySiteUrl.canonicalUrl || canonicalUrl;
|
||||
const canonicalPlatform = normalizeSitePlatform(platform);
|
||||
let detectedPlatform = canonicalPlatform;
|
||||
let responseInitializationPresetId: string | null = explicitInitializationPreset?.id || null;
|
||||
@@ -528,7 +529,7 @@ export async function sitesRoutes(app: FastifyInstance) {
|
||||
if (explicitInitializationPreset) {
|
||||
detectedPlatform = explicitInitializationPreset.platform;
|
||||
} else {
|
||||
const detected = await detectSite(canonicalUrl);
|
||||
const detected = await detectSite(detectionUrl);
|
||||
detectedPlatform = detected?.platform ?? null;
|
||||
responseInitializationPresetId = detected?.initializationPresetId || null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user