AGC 把执行器与线协议拆成正交维度
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 3m1s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 1m37s
Project CI / AI game creator shell Rust crates (push) Successful in 1m25s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m6s
Project CI / Frontend tests (push) Successful in 2m52s
Project CI / Backend tests (push) Failing after 4m14s
Project CI / Repository checks (push) Successful in 3m42s
Project CI / AI game creator shell web tests (push) Successful in 2m5s
Project CI / Native shell tests (push) Successful in 8m2s

AgcAgentMode 回退为仅 codex/cc,只表示执行器;新增 AgcModelProtocol 表示线协议
协议取值 openai_responses/openai_chat/anthropic,与执行器组合封闭并在校验时失败关闭
存量目录缺 protocol 时按执行器推导;历史误写的 agentMode=anthropic 读取时归一为 cc
后台 AGC 模型页拆成 Agent 模式与协议两列,新增协议下拉与对应测试
公开目录 /api/llm/models 与后台 DTO 同时下发解析后的 protocol
客户端持久化 selectedModelProtocol,选模型时按执行器推导兜底协议并校验组合
Codex wire_api 按协议在 responses/chat 间切换,不再硬编码 responses
同步 appSurface 与模型选择测试断言,补 protocol 字段
同步模型别名文档与后端架构文档的正交维度说明
This commit is contained in:
kdletters
2026-10-02 11:43:17 +08:00
parent fbd30d7c66
commit 6bf57c1e51
20 changed files with 346 additions and 35 deletions
+7 -1
View File
@@ -1075,7 +1075,11 @@ export interface AdminRechargeRefundActionResponse {
export interface AdminWalletRestrictionResponse {
wallet: AdminProfileWalletPayload;
}
export type AdminAgcAgentMode = 'codex' | 'cc' | 'anthropic';
export type AdminAgcAgentMode = 'codex' | 'cc';
export type AdminAgcModelProtocol =
| 'openai_responses'
| 'openai_chat'
| 'anthropic';
export interface AdminAgcModel {
id: string;
@@ -1083,6 +1087,8 @@ export interface AdminAgcModel {
modelId: string;
enabled: boolean;
agentMode: AdminAgcAgentMode;
/** 上游线协议,与执行器正交。 */
protocol?: AdminAgcModelProtocol;
}
export interface AdminAgcModelCatalog {
@@ -104,3 +104,49 @@ test('new models default to Codex and save the selected Agent mode', async () =>
});
});
});
test('协议与 Agent 模式是独立字段,可按模型单独选择', async () => {
const catalog = {
revision: 3,
defaultModelId: 'quality',
models: [
{
id: 'quality',
alias: '高质量',
modelId: 'claude-opus-5-5',
enabled: true,
agentMode: 'cc' as const,
protocol: 'anthropic' as const,
},
],
};
vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog);
vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => ({
...input,
revision: 4,
}));
render(<AdminAgcModelsPage token="test" onUnauthorized={vi.fn()} />);
await screen.findByDisplayValue('claude-opus-5-5');
const agentMode = screen.getByLabelText('模型 1 Agent 模式') as HTMLSelectElement;
const protocol = screen.getByLabelText('模型 1 协议') as HTMLSelectElement;
expect(agentMode.value).toBe('cc');
expect(protocol.value).toBe('anthropic');
// 执行器下拉里不再混入协议取值。
expect(
Array.from(agentMode.options).map((option) => option.value),
).toEqual(['codex', 'cc']);
fireEvent.change(agentMode, { target: { value: 'codex' } });
fireEvent.change(protocol, { target: { value: 'openai_chat' } });
fireEvent.click(screen.getByRole('button', { name: '保存' }));
await waitFor(() => {
const calls = vi.mocked(saveAgcModelCatalog).mock.calls;
const payload = calls[calls.length - 1]?.[1];
expect(payload?.models[0]).toMatchObject({
agentMode: 'codex',
protocol: 'openai_chat',
});
});
});
@@ -117,6 +117,7 @@ export function AdminAgcModelsPage({
{ key: 'alias', label: '别名' },
{ key: 'modelId', label: '实际模型名' },
{ key: 'agentMode', label: 'Agent 模式' },
{ key: 'protocol', label: '协议' },
{ key: 'enabled', label: '启用' },
{ key: 'default', label: '默认' },
{ key: 'actions', label: '操作' },
@@ -157,6 +158,7 @@ export function AdminAgcModelsPage({
modelId: '',
enabled: true,
agentMode: 'codex',
protocol: 'openai_responses',
},
],
},
@@ -226,7 +228,22 @@ export function AdminAgcModelsPage({
>
<option value="codex">Codex</option>
<option value="cc">Claude Code</option>
<option value="anthropic">Anthropic 协议</option>
</select>
</td>
<td>
<select
aria-label={`模型 ${index + 1} 协议`}
value={model.protocol ?? 'openai_responses'}
disabled={busy}
onChange={(e) =>
update(model.id, {
protocol: e.target.value as AdminAgcModel['protocol'],
})
}
>
<option value="openai_responses">OpenAI Responses</option>
<option value="openai_chat">OpenAI Chat Completions</option>
<option value="anthropic">Anthropic Messages</option>
</select>
</td>
<td>