[codex] add coding plan site initialization flow (#351)

* add coding plan site initialization flow

* fix ci for coding plan site init flow

* address coding plan review comments
This commit is contained in:
Cita
2026-03-31 21:55:25 +08:00
committed by GitHub
parent e74e11375c
commit 0de9c5e3f1
20 changed files with 1272 additions and 67 deletions
+21
View File
@@ -0,0 +1,21 @@
export type SiteInitializationPresetId =
| 'codingplan-openai'
| 'codingplan-claude'
| 'zhipu-coding-plan-openai'
| 'zhipu-coding-plan-claude';
export type SiteInitializationPreset = {
id: SiteInitializationPresetId;
label: string;
providerLabel: string;
description: string;
platform: string;
defaultUrl?: string;
initialSegment: 'session' | 'apikey';
recommendedSkipModelFetch: boolean;
recommendedModels: string[];
docsUrl?: string;
};
export declare function listSiteInitializationPresets(): SiteInitializationPreset[];
export declare function getSiteInitializationPreset(id: string | null | undefined): SiteInitializationPreset | null;
export declare function detectSiteInitializationPreset(url: string, platform?: string | null): SiteInitializationPreset | null;
+142
View File
@@ -0,0 +1,142 @@
function normalizeUrlCandidate(url) {
return typeof url === 'string' ? url.trim() : '';
}
function parseUrlCandidate(url) {
const normalized = normalizeUrlCandidate(url);
if (!normalized) return null;
const candidates = normalized.includes('://')
? [normalized]
: [`https://${normalized}`];
for (const candidate of candidates) {
try {
return new URL(candidate);
} catch {}
}
return null;
}
function normalizePathname(pathname) {
let normalized = typeof pathname === 'string' ? pathname.trim() : '';
if (!normalized.startsWith('/')) normalized = `/${normalized}`;
while (normalized.length > 1 && normalized.endsWith('/')) {
normalized = normalized.slice(0, -1);
}
return normalized;
}
const CODINGPLAN_RECOMMENDED_MODELS = Object.freeze([
'qwen3-coder-plus',
'qwen3-coder-next',
'qwen3.5-plus',
'glm-5',
]);
const ZHIPU_CODING_PLAN_RECOMMENDED_MODELS = Object.freeze([
'glm-4.7',
'glm-4.6',
'glm-4.5',
'glm-4.5-air',
]);
const SITE_INITIALIZATION_PRESETS = Object.freeze([
Object.freeze({
id: 'codingplan-openai',
label: '阿里云 CodingPlan / OpenAI',
providerLabel: '阿里云 CodingPlan',
description: '适合阿里云 CodingPlan 的 OpenAI 兼容入口,建议先添加 API Key,再补入推荐模型完成初始化。',
platform: 'openai',
defaultUrl: 'https://coding.dashscope.aliyuncs.com/v1',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
recommendedModels: CODINGPLAN_RECOMMENDED_MODELS,
docsUrl: 'https://help.aliyun.com/zh/model-studio/coding-plan-faq',
matches(url) {
const parsed = parseUrlCandidate(url);
if (!parsed) return false;
return parsed.hostname === 'coding.dashscope.aliyuncs.com'
&& normalizePathname(parsed.pathname) === '/v1';
},
}),
Object.freeze({
id: 'codingplan-claude',
label: '阿里云 CodingPlan / Claude',
providerLabel: '阿里云 CodingPlan',
description: '适合阿里云 CodingPlan 的 Claude 兼容入口,建议先添加 API Key,再补入推荐模型完成初始化。',
platform: 'claude',
defaultUrl: 'https://coding.dashscope.aliyuncs.com/apps/anthropic',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
recommendedModels: CODINGPLAN_RECOMMENDED_MODELS,
docsUrl: 'https://help.aliyun.com/zh/model-studio/coding-plan-faq',
matches(url) {
const parsed = parseUrlCandidate(url);
if (!parsed) return false;
const pathname = normalizePathname(parsed.pathname);
return parsed.hostname === 'coding.dashscope.aliyuncs.com'
&& (pathname === '/apps/anthropic' || pathname.startsWith('/apps/anthropic/'));
},
}),
Object.freeze({
id: 'zhipu-coding-plan-openai',
label: '智谱 Coding Plan / OpenAI',
providerLabel: '智谱 Coding Plan',
description: '适合智谱 Coding Plan 的 OpenAI 兼容入口,建议先添加 API Key,再补入常用 GLM 编程模型。',
platform: 'openai',
defaultUrl: 'https://open.bigmodel.cn/api/coding/paas/v4',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
recommendedModels: ZHIPU_CODING_PLAN_RECOMMENDED_MODELS,
docsUrl: 'https://docs.bigmodel.cn/cn/coding-plan/faq',
matches(url) {
const parsed = parseUrlCandidate(url);
if (!parsed) return false;
return parsed.hostname === 'open.bigmodel.cn'
&& normalizePathname(parsed.pathname) === '/api/coding/paas/v4';
},
}),
Object.freeze({
id: 'zhipu-coding-plan-claude',
label: '智谱 Coding Plan / Claude',
providerLabel: '智谱 Coding Plan',
description: '适合智谱 Coding Plan 的 Claude 兼容入口。由于该地址也可作为通用兼容入口,这里默认只提供手动预设,不按 URL 强制自动识别。',
platform: 'claude',
defaultUrl: 'https://open.bigmodel.cn/api/anthropic',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
recommendedModels: ZHIPU_CODING_PLAN_RECOMMENDED_MODELS,
docsUrl: 'https://docs.bigmodel.cn/cn/coding-plan/faq',
matches() {
return false;
},
}),
]);
function clonePreset(preset) {
if (!preset) return null;
return {
...preset,
recommendedModels: [...preset.recommendedModels],
};
}
export function listSiteInitializationPresets() {
return SITE_INITIALIZATION_PRESETS.map((preset) => clonePreset(preset));
}
export function getSiteInitializationPreset(id) {
const normalizedId = typeof id === 'string' ? id.trim() : '';
if (!normalizedId) return null;
return clonePreset(SITE_INITIALIZATION_PRESETS.find((preset) => preset.id === normalizedId) || null);
}
export function detectSiteInitializationPreset(url, platform) {
const normalizedPlatform = typeof platform === 'string' ? platform.trim().toLowerCase() : '';
for (const preset of SITE_INITIALIZATION_PRESETS) {
if (normalizedPlatform && preset.platform !== normalizedPlatform) continue;
if (preset.matches(url)) return clonePreset(preset);
}
return null;
}
@@ -0,0 +1,83 @@
import { describe, expect, it } from 'vitest';
import {
detectSiteInitializationPreset,
getSiteInitializationPreset,
listSiteInitializationPresets,
} from './siteInitializationPresets.js';
describe('siteInitializationPresets', () => {
it('exposes CodingPlan presets with recommended API-key-first initialization', () => {
const presetIds = listSiteInitializationPresets().map((preset) => preset.id);
expect(presetIds).toEqual(expect.arrayContaining([
'codingplan-openai',
'codingplan-claude',
'zhipu-coding-plan-openai',
'zhipu-coding-plan-claude',
]));
const openaiPreset = getSiteInitializationPreset('codingplan-openai');
expect(openaiPreset).toMatchObject({
id: 'codingplan-openai',
platform: 'openai',
defaultUrl: 'https://coding.dashscope.aliyuncs.com/v1',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
});
expect(openaiPreset?.recommendedModels).toEqual(expect.arrayContaining(['qwen3-coder-plus', 'qwen3.5-plus']));
const claudePreset = getSiteInitializationPreset('codingplan-claude');
expect(claudePreset).toMatchObject({
id: 'codingplan-claude',
platform: 'claude',
defaultUrl: 'https://coding.dashscope.aliyuncs.com/apps/anthropic',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
});
expect(claudePreset?.recommendedModels).toEqual(expect.arrayContaining(['qwen3-coder-next', 'glm-5']));
const zhipuOpenAiPreset = getSiteInitializationPreset('zhipu-coding-plan-openai');
expect(zhipuOpenAiPreset).toMatchObject({
id: 'zhipu-coding-plan-openai',
platform: 'openai',
defaultUrl: 'https://open.bigmodel.cn/api/coding/paas/v4',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
});
expect(zhipuOpenAiPreset?.recommendedModels).toEqual(['glm-4.7', 'glm-4.6', 'glm-4.5', 'glm-4.5-air']);
const zhipuClaudePreset = getSiteInitializationPreset('zhipu-coding-plan-claude');
expect(zhipuClaudePreset).toMatchObject({
id: 'zhipu-coding-plan-claude',
platform: 'claude',
defaultUrl: 'https://open.bigmodel.cn/api/anthropic',
initialSegment: 'apikey',
recommendedSkipModelFetch: true,
});
expect(zhipuClaudePreset?.recommendedModels).toEqual(['glm-4.7', 'glm-4.6', 'glm-4.5', 'glm-4.5-air']);
});
it('detects Aliyun CodingPlan endpoints by URL', () => {
expect(detectSiteInitializationPreset('https://coding.dashscope.aliyuncs.com/v1')).toMatchObject({
id: 'codingplan-openai',
platform: 'openai',
});
expect(detectSiteInitializationPreset('https://coding.dashscope.aliyuncs.com/apps/anthropic')).toMatchObject({
id: 'codingplan-claude',
platform: 'claude',
});
expect(detectSiteInitializationPreset('https://api.openai.com/v1')).toBeNull();
});
it('detects Zhipu Coding Plan OpenAI endpoints by URL but keeps Claude-compatible entry manual-only', () => {
expect(detectSiteInitializationPreset('https://open.bigmodel.cn/api/coding/paas/v4')).toMatchObject({
id: 'zhipu-coding-plan-openai',
platform: 'openai',
});
expect(detectSiteInitializationPreset('https://open.bigmodel.cn/api/coding/paas/v4/')).toMatchObject({
id: 'zhipu-coding-plan-openai',
platform: 'openai',
});
expect(detectSiteInitializationPreset('https://open.bigmodel.cn/api/anthropic')).toBeNull();
});
});