feat: Antigravity 和 Codex 服务支持

- 新增 Antigravity 服务:签名缓存、URL 可用性检测、信封处理
- 新增 Codex 服务:信封处理、元数据收集器
- 重构 provider transport 支持新的服务架构
- 新增 stream_bridge 和 upstream_stream_bridge 处理流式响应
- 优化 OAuth 工具函数
- 添加相关测试用例
This commit is contained in:
fawney19
2026-02-05 15:57:52 +08:00
parent ed2ff5c1d7
commit 440721368f
44 changed files with 3498 additions and 134 deletions

View File

@@ -81,7 +81,7 @@
<div class="p-4 space-y-4">
<!-- URL 配置区 -->
<div class="flex items-end gap-3">
<div class="flex-1 min-w-0 grid grid-cols-3 gap-3">
<div class="flex-1 min-w-0 grid grid-cols-4 gap-3">
<div class="col-span-2 space-y-1.5">
<Label class="text-xs text-muted-foreground">Base URL</Label>
<Input
@@ -100,10 +100,32 @@
@update:model-value="(v) => updateEndpointField(endpoint.id, 'path', v)"
/>
</div>
<div class="space-y-1.5">
<Label class="text-xs text-muted-foreground">上游流式</Label>
<Select
:model-value="getEndpointEditState(endpoint.id)?.upstreamStreamPolicy ?? getEndpointUpstreamStreamPolicy(endpoint)"
@update:model-value="(v) => updateEndpointStreamPolicy(endpoint.id, v as string)"
>
<SelectTrigger class="h-9 text-xs">
<SelectValue placeholder="跟随客户端" />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto">
跟随客户端
</SelectItem>
<SelectItem value="force_stream">
强制流式
</SelectItem>
<SelectItem value="force_non_stream">
强制非流式
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<!-- 保存/撤销按钮URL/路径有修改时显示) -->
<div
v-if="!isFixedProvider && hasUrlChanges(endpoint)"
v-if="hasUrlChanges(endpoint)"
class="flex items-center gap-1 shrink-0"
>
<Button
@@ -521,6 +543,7 @@ interface EditableBodyRule {
interface EndpointEditState {
url: string
path: string
upstreamStreamPolicy: string
rules: EditableRule[]
bodyRules: EditableBodyRule[]
}
@@ -689,6 +712,19 @@ function isCodexUrl(baseUrl: string): boolean {
return url.includes('/backend-api/codex') || url.endsWith('/codex')
}
// 读取端点的上游流式策略endpoint.config.upstream_stream_policy
function getEndpointUpstreamStreamPolicy(endpoint: ProviderEndpoint): string {
const cfg = endpoint.config || {}
const raw = (cfg.upstream_stream_policy ?? cfg.upstreamStreamPolicy ?? cfg.upstream_stream) as any
if (raw === null || raw === undefined) return 'auto'
if (typeof raw === 'boolean') return raw ? 'force_stream' : 'force_non_stream'
const s = String(raw).trim().toLowerCase()
if (!s || s === 'auto' || s === 'follow' || s === 'client' || s === 'default') return 'auto'
if (s === 'force_stream' || s === 'stream' || s === 'sse' || s === 'true' || s === '1') return 'force_stream'
if (s === 'force_non_stream' || s === 'force_sync' || s === 'non_stream' || s === 'sync' || s === 'false' || s === '0') return 'force_non_stream'
return 'auto'
}
// 初始化端点的编辑状态
function initEndpointEditState(endpoint: ProviderEndpoint): EndpointEditState {
const rules: EditableRule[] = []
@@ -720,6 +756,7 @@ function initEndpointEditState(endpoint: ProviderEndpoint): EndpointEditState {
return {
url: endpoint.base_url,
path: endpoint.custom_path || '',
upstreamStreamPolicy: getEndpointUpstreamStreamPolicy(endpoint),
rules,
bodyRules,
}
@@ -743,6 +780,18 @@ function updateEndpointField(endpointId: string, field: 'url' | 'path', value: s
}
}
function updateEndpointStreamPolicy(endpointId: string, value: string) {
if (!endpointEditStates.value[endpointId]) {
const endpoint = localEndpoints.value.find(e => e.id === endpointId)
if (endpoint) {
endpointEditStates.value[endpointId] = initEndpointEditState(endpoint)
}
}
if (endpointEditStates.value[endpointId]) {
endpointEditStates.value[endpointId].upstreamStreamPolicy = value
}
}
// 获取端点的编辑规则
function getEndpointEditRules(endpointId: string): EditableRule[] {
const state = endpointEditStates.value[endpointId]
@@ -1118,6 +1167,7 @@ function hasUrlChanges(endpoint: ProviderEndpoint): boolean {
if (!state) return false
if (state.url !== endpoint.base_url) return true
if (state.path !== (endpoint.custom_path || '')) return true
if (state.upstreamStreamPolicy !== getEndpointUpstreamStreamPolicy(endpoint)) return true
return false
}
@@ -1264,12 +1314,34 @@ async function saveEndpoint(endpoint: ProviderEndpoint) {
savingEndpointId.value = endpoint.id
try {
await updateEndpoint(endpoint.id, {
base_url: state.url,
custom_path: state.path || null,
header_rules: rulesToHeaderRules(state.rules),
body_rules: rulesToBodyRules(state.bodyRules),
})
// 仅提交变更字段,避免 fixed provider 因 base_url/custom_path 被锁定而更新失败
const payload: Record<string, any> = {}
if (!isFixedProvider.value) {
if (state.url !== endpoint.base_url) payload.base_url = state.url
if (state.path !== (endpoint.custom_path || '')) payload.custom_path = state.path || null
}
if (hasRulesChanges(endpoint)) payload.header_rules = rulesToHeaderRules(state.rules)
if (hasBodyRulesChanges(endpoint)) payload.body_rules = rulesToBodyRules(state.bodyRules)
// endpoint.config.upstream_stream_policy
if (state.upstreamStreamPolicy !== getEndpointUpstreamStreamPolicy(endpoint)) {
const merged: Record<string, any> = { ...(endpoint.config || {}) }
// Normalize config keys: keep only canonical `upstream_stream_policy`
delete merged.upstream_stream_policy
delete merged.upstreamStreamPolicy
delete merged.upstream_stream
if (state.upstreamStreamPolicy !== 'auto') {
merged.upstream_stream_policy = state.upstreamStreamPolicy
}
payload.config = Object.keys(merged).length > 0 ? merged : null
}
if (Object.keys(payload).length === 0) return
await updateEndpoint(endpoint.id, payload)
success('端点已更新')
emit('endpointUpdated')
} catch (error: any) {