li
e652f6e20a
fix(monitor): 配额抓取在 singleflight 执行体内重查缓存,消除重复上游查询
...
ChannelMonitorQuotaFetcher.Fetch 顶部的 cachedSnapshot 与随后的 flight.DoChan
之间存在一个窗口:goroutine A 判定缓存未命中之后、真正入队之前,goroutine B 的
flight 可能已经跑完、写好缓存,并且它的 singleflight key 也已被摘掉。A 于是不会
并入 B 的那次飞行,而是另起一个新 flight,对同一账号再打一次上游——正是
singleflight 想要消除的重复查询。
多个监控可以关联同一账号,interval 最小 15s,国产配额服务自身无缓存,
这条路径的全部意义就是防止打爆上游配额端点(见文件头注释)。
修复:把 flight 执行体提为具名方法 fetchShared,开头重查一次缓存。
这次重查必然命中,所以合并是确定的而非尽力而为:storeSnapshot 发生在执行体
返回之前,singleflight 删 key 发生在返回之后,因此「能新起一次飞行」必然蕴含
「上一次的快照已经可见」。
该窗口也是主线 CI 上 TestQuotaFetcher_ConcurrentFetchesShareSingleFlight
间歇性失败(expected: 1, actual: 2)的成因,本地 -count=900 复现 3 次(约 0.33%),
带修复后 -count=1700 零失败。
新增两条确定性用例:既有并发用例只能以约 1/300 的概率撞上该窗口,故直接钉住
窗口本身——fetchShared 就是 flight 的执行体,直接调用它等价于「已越过顶层缓存
判定、拿到属于自己的那次飞行」这一状态。另一条守住重查不得退化成无条件短路:
缓存过期后必须照常回源。
2026-08-28 16:49:49 +08:00
Wesley Liddick
d4754c211a
Merge pull request #6291 from Whisper-stark/fix/grok-codex-responses
...
fix(grok): sanitize Codex Responses requests
2026-08-28 15:58:39 +08:00
Wesley Liddick
c8a6e93f33
Merge pull request #6310 from fengshao1227/fix/nonstreaming-terminal-failure-failover-v2
...
fix(openai): 非流式路径按流式同一套判定处理 HTTP 200 终止失败事件
2026-08-28 15:57:39 +08:00
Wesley Liddick
d674a04f2a
Merge branch 'main' into feat/per-user-public-group-access
2026-08-28 13:08:41 +08:00
Wesley Liddick
add86cc3bc
Merge branch 'main' into fix/openai-reset-usage-sync
2026-08-28 12:57:48 +08:00
Wesley Liddick
59bb131df4
Merge branch 'main' into fix/anthropic
2026-08-28 12:47:15 +08:00
Wesley Liddick
b588c0bc42
Merge branch 'main' into fix/ws-v2-passthrough-cyber
2026-08-28 12:36:22 +08:00
Wesley Liddick
a0b313018a
Merge branch 'main' into fix/openai-raw-stream-truncation
2026-08-28 12:26:01 +08:00
Wesley Liddick
de084cdfc2
Merge branch 'main' into fix/grok46-xhigh
2026-08-28 12:15:53 +08:00
Wesley Liddick
f7dca22ea5
Merge branch 'main' into fix/responses-synthesized-created-at
2026-08-28 12:05:38 +08:00
Wesley Liddick
28fa458dc7
Merge branch 'main' into fix/issue-6240-preserve-multimodal-tool-output
2026-08-28 11:37:40 +08:00
li
81ac8ccd64
fix(openai): 非流式路径按流式同一套判定处理 HTTP 200 终止失败事件
...
stream=false 时上游仍可能回 SSE(其他 sub2api 实例、部分 OpenAI 兼容上游),
容量/限流错误经 HTTP 200 的 response.failed / error 终止事件回传。
handleSSEToJSON 与 handlePassthroughSSEToJSON 把所有终止事件塞进固定 502
(writeOpenAINonStreamingProtocolError),而几百行外的流式读取器对同一帧走
openAIStreamFailedEventShouldFailover / openAIStreamErrorEventShouldFailover
判定并返回 UpstreamFailoverError。
同一个上游、同一个事件,仅因请求上的 stream 标志而结果相反:流式换号,
非流式把上游原文直接抛给客户端,池里还有可调度账号也不换。
新增 nonStreamingTerminalFailureFailover,在两条非流式路径上复用流式的同一套
分类,并按 terminalType 分派——裸 error 帧走更保守的那个(仅正向识别为瞬时才
换号),response.failed 走完整的那个,与流式读取器逐字一致。
安全前提:响应体已被 ReadUpstreamResponseBody 完整缓冲,判定发生在写出任何
语义字节之前。能否真正换号仍由 handler 的 openAIForwardMayFailover 用
keepalive 调整后的写出量仲裁(#3887),service 侧只额外拒绝 IsResponseCommitted
与 account 为 nil 的场景,不重复实现心跳判定,避免与 handler 口径分叉。
行为变更:未被分类为不可重试的泛化 response.failed 在非流式路径上由「回写 502」
变为「换号」——这正是与流式对齐的结果。既有用例
TestHandleSSEToJSON_ResponseFailedReturnsProtocolError 传的是 nil account,
无账号则无可换对象,故其断言原样保留,仅更名为 ...WithoutAccountReturnsProtocolError
并注明原因;带真实账号的同一报文由新增用例钉死。
Fixes #5281
2026-08-28 11:36:08 +08:00
Wesley Liddick
d24de611f8
Merge branch 'main' into fix/openai-reasoning-replay
2026-08-28 11:27:39 +08:00
Wesley Liddick
ee2c8b97bb
Merge branch 'main' into fix/apikey-namespace-roundtrip
2026-08-28 11:16:48 +08:00
Wesley Liddick
96e9ab8662
Merge branch 'main' into main
2026-08-28 11:06:29 +08:00
Wesley Liddick
acd2f09ddd
Merge branch 'main' into fix/openai-images-capability-loss-cooldown
2026-08-28 10:54:47 +08:00
akihitohyh and Claude Opus 5
b56c61eccd
feat(admin): let admins restrict which public groups a user may access
...
Public groups have always been bindable by every user: CanBindGroup returned
true for any non-exclusive group, and user_allowed_groups only ever carried the
exclusive groups an admin had granted. Admins had no way to hand a single user
a subset of the public groups short of converting a group to exclusive, which
changes it for everyone already using it.
A user now carries restrict_public_groups. While it is false, which is the
default and what every existing row migrates to, nothing changes: every public
group stays bindable. Once an admin turns it on for a user, that user's public
groups are narrowed to the ones listed in user_allowed_groups, the same table
that already gates exclusive groups.
The flag is an administrative control, so it rides on the admin user DTO only
and leaves the shape of the end-user endpoints alone.
The model plaza filter honours the flag as well, so a restricted user is not
shown groups they would be refused when binding a key. Anonymous visitors have
no user record and keep the previous view.
Enforcement rides on the existing CanBindGroup choke point, so it covers key
creation, key updates, and per-request authorization together. An API key bound
to a group that is later withdrawn stops working at request time rather than
lingering as a key that can be listed but not used.
The admin dialog gains a toggle over the public group list. Turning it off
re-checks every public group, so an admin cannot save a list that reads as
restrictive while the restriction itself is disabled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-28 02:40:17 +08:00
Qi HU
f4e3eb1c5a
fix(openai): detect cyber policy in ws v2 passthrough
2026-08-27 21:50:53 +08:00
Whisper-stark
fd872550d5
fix(grok): handle typed invalid tool unions
2026-08-27 21:03:23 +08:00
Whisper-stark
f4820c00dc
fix(grok): simplify invalid tool union roots
2026-08-27 19:24:50 +08:00
Whisper-stark
de6ef7134d
fix(grok): sanitize Codex Responses requests
2026-08-27 19:24:50 +08:00
Ryan Chou and Claude Opus 5
e6ea7b9af0
fix(openai): 图像能力丢失时冷却 image 调度,不再反复选中坏号
...
OAuth 账号被上游收走 image_generation 能力后,/v1/images/* 稳定返回同一句 400
(Tool choice 'image_generation' not found in 'tools' parameter),但网关不做
任何处置,坏号持续被排程选中(报告者实测连续 17 次同 body 400、窗口内 0 成功、
持续 3.5 小时,直到人工停调度)。
RateLimitService 的 400 分支是白名单式,只认 organization has been disabled /
credit balance / identity verification is required 三句,其余 400 明确不处理;
唯一能兜任意 400 的 tryTempUnschedulable 又要求账号开启 temp-unschedulable
开关,OAuth 账号默认没开。两条路都不命中,shouldDisable 恒为 false,也没有任何
model 作用域冷却。
修复收窄到 sub2api 自建 body 的 images 管线:buildOpenAIImagesResponsesRequest
产出的 body 必定同时带 tool_choice 与匹配的 image_generation tool,这条路上出现
该 400 只可能是上游收了账号能力。forwardOpenAIImagesOAuth 在 upstreamCtx 上打
自建标记(覆盖其下游全部三个错误分支),共用错误入口读到标记才把该 400 判为能力
丢失,对 openai:image_generation 作用域冷却 30 分钟(对齐 upstreamModelNotFound
Cooldown),不禁用整颗账号——账号对文本请求依然健康。
未采用 issue 建议①的全局签名匹配:handleOpenAIAccountUpstreamError 同时服务
passthrough 路径,客户端可自带「tool_choice 指 image_generation 但 tools 不带」
的请求触发一字不差的同一句 400,全局匹配等于任何 API 用户一句话即可禁用健康
账号。附 5 个测试,其中防毒杀用例在撤掉该闸门后会实测失败。建议②(连续 N 次
400 熔断)涉及跨请求状态与配置项,另议。
Fixes #4466
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-08-27 16:36:01 +08:00
kk
00efee4303
🐛 fix(grok): 为 Grok 4.6 广告 xhigh 推理档位
...
Grok Build 的 /v1/models 原先给所有可配 reasoning 的 Grok 模型写死三档,
导致 grok-4.6 无法在 /model 里选出官方支持的 xhigh。转发与 Codex catalog
已按模型判断,现与同一条件对齐。
2026-08-27 15:50:20 +08:00
zhaotao1
00b7c855cc
fix(openai): preserve declared API-key namespace calls
2026-08-27 15:22:43 +08:00
li
9192426d2e
merge: 合入 origin/main,与新增的 service_tier 字段并存
...
主线在 ResponsesResponse 与两处流式事件上新增了 service_tier;本分支新增的
created_at 与之并列,无语义冲突。同时跟进 ChatCompletionsResponseToResponses
新增的 functionTools 形参。
2026-08-27 13:40:07 +08:00
li
d077002eba
fix(openai): 图片工具冷却不再由「模型回文字」这一请求级判据触发
...
v0.1.181 起,/v1/images/generations 只要上游回了文字而没回图,就给账号写 30 分钟
openai:image_generation 模型级冷却。该判据由 openAIImagesTextFallbackErrorForText
从模型的自然语言输出**推断**而来,只说明这个 prompt 这一轮模型选择了说话——
仓库自己的既有用例 fixture 就是一句「Here's a polished image prompt for your request.」。
更糟的是同一个错误被判为可重试(502 → IsOpenAIImagesRetryableUpstreamError)并驱动
newOpenAIAccountFailoverError:一次闲聊回复会被逐个账号重试,沿途把每个账号都冷却掉,
表现为「开启后没多久账号就全部被限制」。
改为只在上游 error 帧点名该状态时才写冷却;网关自行推断的判据打上
SynthesizedFromModelText 标记并跳过。换号行为一字未动。
依据是仓库既有政策(alpha/search 路径原话):工具端点的单次失败
「仍允许本次请求换号,但不修改任何账号状态」。
Fixes #6171
2026-08-27 13:36:50 +08:00
zenor0
5f09442fce
fix(openai): refresh usage after quota reset
2026-08-27 10:09:42 +08:00
feeeei
44003d7f6b
fix(gateway): Anthropic/Bedrock 传输层错误转 failover + 持久故障临时摘除账号
...
上游 Do/DoWithTLS 返回非 HTTP 错误(代理/DNS/TCP/TLS)时,Anthropic 五条转发路径
(forward/apikey 透传/Bedrock/ForwardAsResponses/ForwardAsChatCompletions)原先
直接向客户端写 502 且不换号,单账号网络故障期间该账号仍持续被调度,请求全量失败。
对齐 OpenAI 侧 handleOpenAIUpstreamTransportError 的既有语义:
- 传输层错误统一返回 *UpstreamFailoverError(502),由 handler failover 循环换号,
耗尽后才向客户端渲染错误;service 不再写响应
- 持久性故障(connection refused/no route/DNS not found/代理认证失败)额外临时
摘除账号 10 分钟(仅写库,Anthropic 调度以持久化状态为准,无内存快路径)
- context.Canceled 保持原样返回:不换号也不摘账号
- 分类器改为平台无关命名(classifyUpstreamTransportError)供两侧共用
- Ops 错误事件保留各路径原有字段(UpstreamURL/DurationMs/Passthrough)
2026-08-27 09:56:41 +08:00
feeeei
d881bfc0de
fix(openai): 避免 pool 两跳重复计算 system 提示词
2026-08-26 19:55:49 +08:00
shaw
8ba81615e0
merge: resolve conflict with main (upstream endpoint observability)
...
Merge origin/main into contrib/routed-codex-model-catalog.
Single conflict in openai_gateway_forward.go: main added
ClearActualOpenAIUpstreamEndpoint + SetActualOpenAIUpstreamEndpoint
(commit 4795650d2 ) at the same insertion point where the PR added
filterOpenAIResponsesNoneReasoningEffortForAccount. Both changes
are independent — endpoint observability first, then body filtering.
2026-08-26 15:29:01 +08:00
wucm667
50ba146298
fix(openai): preserve multimodal client tool outputs
2026-08-26 15:04:17 +08:00
Wesley Liddick
b9083fc7ad
Merge pull request #6211 from Monika-shipship/codex/ws-v2-tool-id-regression
...
test(openai): 补充 OAuth Responses WS v2 历史 ID 清洗回归测试
2026-08-26 15:03:24 +08:00
Long Li
2abce65031
fix(codex): harden routed catalog capability sync
2026-08-26 12:46:31 +09:00
Long Li and Cursor
195b219707
fix(codex): isolate API-key catalog cache and DeepSeek Codex defaults
...
Copy cached API-key manifests before group-specific mutation, use DeepSeek
model IDs for Codex fallbacks, omit unsupported config.toml effort, and
drop wildcard mapping keys from generated catalogs.
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-08-26 10:56:17 +09:00
shaw
4795650d21
fix(openai): record actual upstream endpoint on errors
2026-08-26 09:42:28 +08:00
Monika
d8694f03bc
test(openai): cover WSv2 cleanup of stale native tool IDs
2026-08-25 21:45:24 +08:00
Wesley Liddick
7a7bd3729c
Merge pull request #6166 from jyking/fix/email-bind-alias-dedup
...
fix(auth): 邮箱换绑增加别名与并发守卫
2026-08-25 21:31:39 +08:00
Wesley Liddick
49ad7021d7
Merge pull request #6164 from jimbirthday/fix/openai-sticky-capacity-spillover
...
fix(openai): preserve sticky binding on capacity spillover
2026-08-25 21:28:38 +08:00
Wesley Liddick
4952b919a4
Merge pull request #6204 from wucm667/fix/issue-6203-kimi-concurrency-403
...
fix(kimi): keep concurrency-limit 403 recoverable
2026-08-25 21:28:08 +08:00
Wesley Liddick
fa0685a4a3
Merge pull request #6175 from wucm667/feat/issue-6172-antigravity-token-clamp
...
fix(antigravity): clamp compatible token limits
2026-08-25 21:27:57 +08:00
Wesley Liddick
8040428716
Merge pull request #6201 from xuhaihan/fix/codex-session-id-affinity
...
fix(openai): honor Codex session-id header
2026-08-25 21:27:24 +08:00
Long Li and Cursor
77b6c5bb0c
merge: sync contrib/routed-codex-model-catalog with upstream v0.1.182
...
Keep catalog membership on schedulable accounts and intersect advertised
capabilities across all active group members that map an alias. Preserve
upstream plugin, service-tier, and models-list body-limit changes.
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-08-25 21:28:57 +09:00
shaw
f1aadd48d5
fix(openai): pause OAuth accounts on exhausted quota 429
2026-08-25 20:25:22 +08:00
Long Li and Cursor
5934981e22
test(codex): cover unschedulable catalog capability intersection
...
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-08-25 21:02:47 +09:00
Long Li and Cursor
db01fb98f8
fix(codex): keep catalog capabilities stable when accounts are unschedulable
...
Intersect advertised alias capabilities across all active group members
that map the model, not only currently schedulable accounts, so rate-limits
cannot widen image input or context windows.
Co-authored-by: Cursor <cursoragent@cursor.com >
2026-08-25 21:01:58 +09:00
wucm667
3802268e2d
fix(kimi): keep concurrency 403 recoverable
2026-08-25 19:40:39 +08:00
hank
8e60d57475
fix(openai): honor Codex session-id header
2026-08-25 17:08:27 +08:00
hank
32064d39e7
fix(openai): normalize cross-provider reasoning replay
2026-08-25 17:04:38 +08:00
wucm667
1a9898a6a4
fix(antigravity): clamp compatible token limits
2026-08-25 13:13:08 +08:00
Wesley Liddick
5a7d469622
Merge pull request #6157 from LiPu-jpg/fix/issue-6147-responses-lite-parallel
...
fix(openai): enforce Responses Lite parallel tool calls across account types
2026-08-25 12:31:32 +08:00