[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:
@@ -1,3 +1,5 @@
|
||||
import { analyzePrimarySiteUrl } from './sitePrimaryUrl.js';
|
||||
|
||||
function normalizeUrlCandidate(url) {
|
||||
return typeof url === 'string' ? url.trim() : '';
|
||||
}
|
||||
@@ -298,5 +300,17 @@ export function detectSiteInitializationPreset(url, platform) {
|
||||
if (normalizedPlatform && preset.platform !== normalizedPlatform) continue;
|
||||
if (preset.matches(url)) return clonePreset(preset);
|
||||
}
|
||||
|
||||
if (!normalizedPlatform) return null;
|
||||
|
||||
const analyzed = analyzePrimarySiteUrl(url);
|
||||
for (const preset of SITE_INITIALIZATION_PRESETS) {
|
||||
if (preset.platform !== normalizedPlatform) continue;
|
||||
if (!preset.defaultUrl) continue;
|
||||
const presetAnalyzed = analyzePrimarySiteUrl(preset.defaultUrl);
|
||||
if (!presetAnalyzed.persistedUrl) continue;
|
||||
if (presetAnalyzed.persistedUrl === analyzed.persistedUrl) return clonePreset(preset);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -190,4 +190,15 @@ describe('siteInitializationPresets', () => {
|
||||
platform: 'openai',
|
||||
});
|
||||
});
|
||||
|
||||
it('reselects openai presets from canonicalized root urls when platform is already known', () => {
|
||||
expect(detectSiteInitializationPreset('https://api.deepseek.com', 'openai')).toMatchObject({
|
||||
id: 'deepseek-openai',
|
||||
platform: 'openai',
|
||||
});
|
||||
expect(detectSiteInitializationPreset('https://coding.dashscope.aliyuncs.com', 'openai')).toMatchObject({
|
||||
id: 'codingplan-openai',
|
||||
platform: 'openai',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
export type PrimarySiteUrlAction =
|
||||
| 'unchanged'
|
||||
| 'auto_strip_known_api_suffix'
|
||||
| 'preserve_api_path'
|
||||
| 'preserve_semantic_path'
|
||||
| 'preserve_unknown_path';
|
||||
|
||||
export type PrimarySiteUrlAnalysis = {
|
||||
canonicalUrl: string;
|
||||
persistedUrl: string;
|
||||
matchedPath: string;
|
||||
action: PrimarySiteUrlAction;
|
||||
};
|
||||
|
||||
export declare function analyzePrimarySiteUrl(url: string | null | undefined): PrimarySiteUrlAnalysis;
|
||||
@@ -0,0 +1,107 @@
|
||||
function normalizePathname(pathname) {
|
||||
let normalized = typeof pathname === 'string' ? pathname.trim() : '';
|
||||
if (!normalized || normalized === '/') return '/';
|
||||
if (!normalized.startsWith('/')) normalized = `/${normalized}`;
|
||||
while (normalized.length > 1 && normalized.endsWith('/')) {
|
||||
normalized = normalized.slice(0, -1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function parseUrlCandidate(url) {
|
||||
const trimmed = typeof url === 'string' ? url.trim() : '';
|
||||
if (!trimmed) return null;
|
||||
|
||||
const candidates = trimmed.includes('://')
|
||||
? [trimmed]
|
||||
: [`https://${trimmed}`];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
return new URL(candidate);
|
||||
} catch {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const AUTO_STRIP_PRIMARY_SITE_PATHS = new Set([
|
||||
'/v1',
|
||||
'/v1beta',
|
||||
'/v1/models',
|
||||
'/v1/chat/completions',
|
||||
'/v1/responses',
|
||||
'/v1/messages',
|
||||
'/v1beta/models',
|
||||
]);
|
||||
|
||||
const SEMANTIC_PRIMARY_SITE_PATHS = new Set([
|
||||
'/backend-api/codex',
|
||||
'/anthropic',
|
||||
'/apps/anthropic',
|
||||
'/api/anthropic',
|
||||
'/api/coding/paas/v4',
|
||||
'/v1beta/openai',
|
||||
]);
|
||||
|
||||
export function analyzePrimarySiteUrl(url) {
|
||||
const parsed = parseUrlCandidate(url);
|
||||
if (!parsed) {
|
||||
const trimmed = typeof url === 'string' ? url.trim().replace(/\/+$/, '') : '';
|
||||
return {
|
||||
canonicalUrl: trimmed,
|
||||
persistedUrl: trimmed,
|
||||
matchedPath: '',
|
||||
action: 'unchanged',
|
||||
};
|
||||
}
|
||||
|
||||
parsed.search = '';
|
||||
parsed.hash = '';
|
||||
const matchedPath = normalizePathname(parsed.pathname);
|
||||
const canonicalUrl = matchedPath === '/'
|
||||
? parsed.origin
|
||||
: `${parsed.origin}${matchedPath}`;
|
||||
|
||||
if (matchedPath === '/') {
|
||||
return {
|
||||
canonicalUrl,
|
||||
persistedUrl: canonicalUrl,
|
||||
matchedPath,
|
||||
action: 'unchanged',
|
||||
};
|
||||
}
|
||||
|
||||
if (SEMANTIC_PRIMARY_SITE_PATHS.has(matchedPath)) {
|
||||
return {
|
||||
canonicalUrl,
|
||||
persistedUrl: canonicalUrl,
|
||||
matchedPath,
|
||||
action: 'preserve_semantic_path',
|
||||
};
|
||||
}
|
||||
|
||||
if (AUTO_STRIP_PRIMARY_SITE_PATHS.has(matchedPath)) {
|
||||
return {
|
||||
canonicalUrl,
|
||||
persistedUrl: parsed.origin,
|
||||
matchedPath,
|
||||
action: 'auto_strip_known_api_suffix',
|
||||
};
|
||||
}
|
||||
|
||||
if (matchedPath.startsWith('/api')) {
|
||||
return {
|
||||
canonicalUrl,
|
||||
persistedUrl: canonicalUrl,
|
||||
matchedPath,
|
||||
action: 'preserve_api_path',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
canonicalUrl,
|
||||
persistedUrl: canonicalUrl,
|
||||
matchedPath,
|
||||
action: 'preserve_unknown_path',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { analyzePrimarySiteUrl } from './sitePrimaryUrl.js';
|
||||
|
||||
describe('sitePrimaryUrl', () => {
|
||||
it('returns unchanged for empty, nullish, and invalid inputs', () => {
|
||||
expect(analyzePrimarySiteUrl('')).toMatchObject({
|
||||
canonicalUrl: '',
|
||||
persistedUrl: '',
|
||||
action: 'unchanged',
|
||||
matchedPath: '',
|
||||
});
|
||||
expect(analyzePrimarySiteUrl(null as unknown as string)).toMatchObject({
|
||||
canonicalUrl: '',
|
||||
persistedUrl: '',
|
||||
action: 'unchanged',
|
||||
matchedPath: '',
|
||||
});
|
||||
expect(analyzePrimarySiteUrl(undefined as unknown as string)).toMatchObject({
|
||||
canonicalUrl: '',
|
||||
persistedUrl: '',
|
||||
action: 'unchanged',
|
||||
matchedPath: '',
|
||||
});
|
||||
expect(analyzePrimarySiteUrl(' not a valid url/// ')).toMatchObject({
|
||||
canonicalUrl: 'not a valid url',
|
||||
persistedUrl: 'not a valid url',
|
||||
action: 'unchanged',
|
||||
matchedPath: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns unchanged for root-only urls', () => {
|
||||
expect(analyzePrimarySiteUrl('https://example.com/')).toMatchObject({
|
||||
canonicalUrl: 'https://example.com',
|
||||
persistedUrl: 'https://example.com',
|
||||
action: 'unchanged',
|
||||
matchedPath: '/',
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-strips known non-api request suffixes to the host root', () => {
|
||||
expect(analyzePrimarySiteUrl('https://api.openai.com/v1/messages?trace=1#frag')).toMatchObject({
|
||||
canonicalUrl: 'https://api.openai.com/v1/messages',
|
||||
persistedUrl: 'https://api.openai.com',
|
||||
action: 'auto_strip_known_api_suffix',
|
||||
matchedPath: '/v1/messages',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves api-prefixed paths and marks them as warnings', () => {
|
||||
expect(analyzePrimarySiteUrl('api.example.com/api/v1/models')).toMatchObject({
|
||||
canonicalUrl: 'https://api.example.com/api/v1/models',
|
||||
persistedUrl: 'https://api.example.com/api/v1/models',
|
||||
action: 'preserve_api_path',
|
||||
matchedPath: '/api/v1/models',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves known semantic paths without warnings', () => {
|
||||
expect(analyzePrimarySiteUrl('https://chatgpt.com/backend-api/codex/')).toMatchObject({
|
||||
canonicalUrl: 'https://chatgpt.com/backend-api/codex',
|
||||
persistedUrl: 'https://chatgpt.com/backend-api/codex',
|
||||
action: 'preserve_semantic_path',
|
||||
matchedPath: '/backend-api/codex',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves unknown non-api paths and marks them as warnings', () => {
|
||||
expect(analyzePrimarySiteUrl('https://gateway.example.com/custom-base')).toMatchObject({
|
||||
canonicalUrl: 'https://gateway.example.com/custom-base',
|
||||
persistedUrl: 'https://gateway.example.com/custom-base',
|
||||
action: 'preserve_unknown_path',
|
||||
matchedPath: '/custom-base',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user