[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:
Cita
2026-04-05 20:09:34 +08:00
committed by GitHub
parent df75a80bf9
commit d5560fb036
15 changed files with 857 additions and 61 deletions
+14
View File
@@ -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',
});
});
});
+15
View File
@@ -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;
+107
View File
@@ -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',
};
}
+77
View File
@@ -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',
});
});
});