feat(codex): 拆分openai:compact为独立端点,简化Codex请求为透传模式

- 新增openai:compact端点类型(EndpointKind.COMPACT),独立于openai:cli
- OpenAICompactAdapter继承OpenAICliAdapter,自动标记compact模式
- Codex请求补丁改为纯透传:仅清理内部标记,不再修改客户端payload
- stream_policy支持openai:compact独立策略,compact端点移除stream字段
- candidate_builder支持compact回退到cli端点
- auth_type: vertex_ai重命名为service_account,保持向后兼容
- Vertex Provider新增api_formats与auth_type组合校验
- KeyAllowedModels对话框改为从Provider获取模型,展示provider_model_name
- Dialog内Select组件自动禁用Portal,修复层级遮挡问题
- 新增Codex compact端点回填迁移脚本
This commit is contained in:
fawney19
2026-03-01 23:55:26 +08:00
parent 4bf3a453e7
commit 97d42703da
37 changed files with 650 additions and 286 deletions

View File

@@ -723,6 +723,7 @@ const emit = defineEmits<{
const AVAILABLE_API_FORMATS = [
{ value: 'openai:chat', label: 'OpenAI Chat' },
{ value: 'openai:cli', label: 'OpenAI CLI' },
{ value: 'openai:compact', label: 'OpenAI Compact' },
{ value: 'openai:video', label: 'OpenAI Video' },
{ value: 'claude:chat', label: 'Claude Chat' },
{ value: 'claude:cli', label: 'Claude CLI' },

View File

@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest'
import { parseResponse, renderResponse } from '../registry'
describe('Gemini conversation parser', () => {
const requestBody = { model: 'gemini-3-pro-preview' }
const normalizedResponse = {
status: 'completed',
output: [
{
type: 'message',
role: 'assistant',
content: [
{
type: 'output_text',
text: 'Hello!',
},
],
},
],
}
it('parses normalized output payload when hint is gemini', () => {
const parsed = parseResponse(normalizedResponse, requestBody, 'gemini:chat')
expect(parsed.apiFormat).toBe('gemini')
expect(parsed.messages).toHaveLength(1)
expect(parsed.messages[0]?.role).toBe('assistant')
expect(parsed.messages[0]?.content[0]).toMatchObject({
type: 'text',
text: 'Hello!',
})
})
it('renders normalized output payload when hint is gemini', () => {
const rendered = renderResponse(normalizedResponse, requestBody, 'gemini:chat')
expect(rendered.error).toBeUndefined()
expect(rendered.blocks).toHaveLength(1)
expect(rendered.blocks[0]).toMatchObject({
type: 'message',
role: 'assistant',
})
const firstBlock = rendered.blocks[0]
if (!firstBlock || firstBlock.type !== 'message') {
throw new Error('expected first render block to be message')
}
expect(firstBlock.content[0]).toMatchObject({
type: 'text',
content: 'Hello!',
})
})
})