Commit Graph
4346 Commits
Author SHA1 Message Date
Wesley Liddick a1073843ac Merge pull request #5437 from Brisbanehuang/codex/upstream-response-model-audit-followup
perf(usage): 优化上游响应模型观察热路径
2026-08-09 14:55:15 +08:00
Wesley Liddick 7a113fb7a3 Merge pull request #5352 from feeeei/main
fix(gemini): 修复Gemini池模式时,依然被 429 response 触发账户限流问题
2026-08-09 14:55:02 +08:00
shaw d92edc01be Merge origin/main into feat/channel-monitor-v2-ops-ui
Resolves three conflicts, all of the "both branches appended to the same
block" shape. Every one is resolved as a union of both sides; nothing from
either parent is dropped.

- handler/admin/setting_handler_update.go: keep ChannelMonitorHideThroughput
  (V2) alongside GrokDefaultTextModel / GrokCrossClientModelMapEnabled /
  GrokDefaultBaseURLMode (#5408). UpdateSettings writes every key on each
  save, so dropping either side would reset those settings to zero values.
- service/domain_constants.go: keep SettingKeyChannelMonitorHideThroughput
  and the three SettingKeyGrok* constants.
- repository/migrations_runner.go: keep the 195 checksum rule (V2) and the
  218/219/220 rules (#5408).
2026-08-09 12:11:35 +08:00
Brisbanehuang 6e34fb09c9 perf(usage): optimize upstream response model observation 2026-08-08 22:04:49 -04:00
IanShaw027 5315896b30 fix(grok): align free soft-gate tests with async fail-open cache
Treat cacheTTL=0 as non-expiring known entries, always store negative
refresh markers, and update sticky getSchedulableAccount tests to expect
first-hit fail-open then block after background stats warm.
2026-08-08 23:38:50 +08:00
IanShaw027 a54a4b674b fix(grok): clear golangci errcheck and gofmt on free-quota path
Check the sync.Map type assertion in free-quota refresh coalescing, and
gofmt migration checksum rules plus prompt-audit route map alignment.
2026-08-08 22:52:31 +08:00
li b6eb6c1efa fix(gemini): 原生生图按上游实际回吐的图片张数计费
/v1beta/models/{model}:generateContent 与 Anthropic→Gemini 兼容路径的
ImageCount 只由 isImageGenerationModel(originalModel) 决定,而该白名单是
按 Google 官方模型名精确/前缀匹配写死的(antigravity_image_test.go 里
明确断言 my-gemini-3-pro-image-test 这类自定义名返回 false)。

GeminiMessagesCompatService 服务的却主要是 API Key + 自定义模型映射的账号:
客户端请求名和 GetMappedModel 后的上游名都可能是站长自取的别名,白名单必然
判不出来 → ImageCount=0 → calculateRecordUsageCost 里 `if result.ImageCount > 0`
的按次计费分支整条不触发 → 生图请求全部记 $0(issue #5358)。

改为优先按上游响应里真实的 inlineData 图片 part 计数:
- 新增请求级计数器,挂在 gin.Context 上,与既有的
  upstreamResponseModelObserver 同一批调用点取解包后的响应体;
- 取「单个 payload 内的最大值」而非累加:Gemini 兼容上游的 SSE 分片可能是
  累积式的(computeGeminiTextDelta 正是为此存在),逐 chunk 累加会把同一张图
  重复计费。max 保证累积式流与非流式都得到真实张数,增量式多图流最差退化到 1,
  与改动前同值,不构成回退;
- 每次 Forward 开头重置计数器,避免 failover 复用同一个 gin.Context 时
  把失败账号已回吐的图叠加到成功账号账单上;
- 响应里数不出图时(fileData 引用式回图等)退回原有模型名启发式,并额外认
  映射后的上游模型名,与 shouldSkipCodexPlanGatedImageModelCooldown 同时取
  requestedModel / modelKey 的口径一致。

inlineData / inline_data 两种字段风格都认(官方 SDK 与部分中转回 snake_case),
只统计带 base64 数据且 MIME 为图片的 part。

Fixes #5358
2026-08-08 21:36:18 +08:00
lyen1688 4999231d61 修复邮箱域名注册额度策略 2026-08-08 21:05:20 +08:00
feeeei cbc2a3dd46 修复池模式 Gemini 账号被 429 打上账号级限流
- 429 的标记点在重试循环内,先于 CheckErrorPolicy 执行,池模式豁免只能落在
  handleGeminiUpstreamError 自身;否则一次上游 429 会把账号锁到 PST 午夜,
  即便重试已经成功返回客户端
- 判定条件与 HandleUpstreamError 对齐:自定义错误码优先级高于池模式;
  401/403/529 仍委派给 RateLimitService,临时不可调度规则不受影响
- chat completions 路径的策略分发改为只有 None / Matched 才处理账号状态,
  与 messages 兼容层的 switch 一致,ErrorPolicySkipped 不再漏进来
2026-08-08 16:51:48 +08:00
li cbf2be05a3 fix(openai): 非流式生图脱钩上游 context,客户端断开不再导致图已出却不扣费
forwardOpenAIImagesAPIKey 走的是 detachStreamUpstreamContext(ctx, parsed.Stream),
该函数在非流式时原样返回请求 context(gateway_usage_billing.go:488)。于是客户端
中途断开会连带取消已经在出图的上游调用:上游那边图已生成并计费,网关这边拿到
context canceled,记 502,result 为 nil,handler 的 result.ImageCount > 0 兜底
无法命中,本次请求不产生扣费。

生图是长耗时(数十秒)且上游侧已产生实际成本的操作,与普通非流式 chat 不同:
提前取消并不能省下上游开销,只会丢掉已付费的产出。

同一端点的 OAuth 分支 forwardOpenAIImagesOAuth(openai_images_responses.go:1690)
以及同属媒体生成的 grok_media.go 本来就用无条件脱钩的 detachUpstreamContext,
OpenAI 网关侧 13 个转发点里只有这一处是例外。这里对齐。

Fixes #5411
2026-08-08 15:24:34 +08:00
IanShaw027 e91b494168 fix(test): stabilize OpenAI streaming preamble keepalive assertion
Keepalive is gated on downstream idle, not upstream tick cadence. The old
fixture wrote progress events every 250ms and could finish without a true
1s idle window on loaded CI runners, so ":\n\n" never appeared. Pause after
preamble long enough for the keepalive ticker before completing the stream.
2026-08-08 15:00:33 +08:00
IanShaw027 04d9eeaf07 fix(channel-monitor-v2): clear CI lint and align trend axis with range zoom
Fix golangci unused/gofmt on the gentle-backfill path. Plot matrix and
line-chart X axes on the selected [requested_start, requested_end)
window (empty slots while backfill lags), and let plain mouse-wheel zoom
narrow the visible interval so pulse blocks grow wider.
2026-08-08 14:46:35 +08:00
IanShaw027 7eb1310701 fix(grok): close free-by-default billing and related review blockers
H1/H2: bill search and voice with code defaults when group prices are
nil (explicit 0 remains free); bump API key auth snapshot to v19 and
refresh incomplete media/search/audio projections.

M1–M6: free-quota soft gate fails open on cache miss with background
refresh and 60s default TTL; correct password_auth config docs; default
cross-client model map to true (→ grok-4.5); audit /tts and /web_search;
exclude composite from migration 220 video-price clears; never let a
search surcharge mask token pricing failures.
2026-08-08 14:39:22 +08:00
Yuxin Qiao 280c1c8623 fix(openai): fail over empty response.completed streams instead of recording 0/0 success
A Responses stream that ends with response.completed but carries no output,
no usage and no error (only response.created + response.completed observed)
is a silent upstream refusal. It was previously treated as a successful
turn, recording 0 input / 0 output tokens without triggering failover, so
clients saw an empty reply and usage was never billed.

Detect the empty terminal event in both the standard and passthrough
streaming paths while nothing has been written to the client, and return an
UpstreamFailoverError so the next account is tried; when failover is
exhausted the client gets an explicit Responses-format error instead of an
empty success. Streams with any semantic output, usage or error are
untouched. Fixes #5009.
2026-08-08 14:35:10 +08:00
Yuxin Qiao 9f31df3fa8 fix(openai): strip invalid reasoning item IDs in API key passthrough
OpenAI upstream requires reasoning input item ids to begin with 'rs' and
rejects replayed item_* ids with 400 ("Expected an ID that begins with
'rs'."). shouldStripOpenAIResponsesInputItemID handled message (msg) and
tool call (fc) ids but not reasoning items, so long Codex sessions restored
through the OpenAI API key passthrough path failed with a converted 502.

Strip non-rs reasoning ids while preserving valid rs_* ids, mirroring the
existing message/tool-call handling. Fixes #5410.
2026-08-08 14:34:53 +08:00
IanShaw027 825f9c78d5 fix(channel-monitor-v2): default mode v1 and gentle adaptive backfill
Default channel_monitor_mode to v1 (opt-in V2) so upgrades keep active
probes; existing explicit v2 rows are left alone via ON CONFLICT DO NOTHING
plus migration checksum compatibility for already-applied 195.

V2 first-enable backfill no longer compresses ticks to 5s or uses 24h
chunks. Each tick does recent overlap plus at most one historical chunk
with depth-based ceilings (2h/4h/6h), adaptive grow/shrink, and failure
backoff. Error request_id dedup is bounded by a 90-minute lookback so
ops_error_logs is not scanned for full history.

Also align hide_throughput parse default with privacy-preserving public
runtime (missing key → true).
2026-08-08 13:59:11 +08:00
IanShaw027 cec922d335 fix(grok): clear golangci-lint findings on complete-integration branch
Check Close/CloseNow errors, drop unused helpers and dead constants,
lowercase ST1005 error strings, and stop discarding unwrap status as an
unused assignment so CI golangci-lint passes.
2026-08-08 12:56:40 +08:00
IanShaw027 3c22aeeb3d fix(channel-monitor-v2): default hide_throughput true for privacy parity
Align InitializeDefaultSettings and parseSettings with the public-settings
path and migration 206 so admin and user views agree when the key is unset.
2026-08-08 12:49:39 +08:00
IanShaw027 1f58e25ab3 Merge upstream/main into feat/grok-complete-integration
冲突集中在 chat completions / messages 两条 Responses 转发路径:
upstream 给 OpenAIForwardResult 增加了 UpstreamResponseModel 与
UpstreamResponseModelConflict(配套 beginUpstreamResponseModelObservation
观测器),本分支在同样位置把返回值改成了具名变量以便挂 Grok 原生搜索计数。
两侧不互斥,合并结果同时保留上游的响应模型观测字段与 Grok SearchCount 逻辑。

frontend/pnpm-lock.yaml 取 upstream 版本:package.json 与 upstream 完全一致,
本地差异只是 pnpm install 的重解析噪音。
2026-08-08 11:12:56 +08:00
IanShaw027 07b46e93e4 fix(grok): 修正 voice 路由推导、视频价归一化与门禁缓存驻留
- custom-voices endpoint 改由匹配到的路由模板推导(c.FullPath())。
  原实现按请求 URL 字面后缀判 /audio,voice_id 恰为 "audio" 时
  GET /custom-voices/audio 会被改写成 custom-voices/audio/audio,
  把档案查询变成音频下载。补 voice_id="audio" 的路由推导测试。

- NormalizeVideoModelPrices 不再把无法识别的分辨率静默折算成 480p:
  新增 LookupVideoBillingResolution 报告未知档位,配置解析路径丢弃并告警,
  运行时计费仍走 OrDefault 兜底。model/tier 两层遍历改为排序遍历,
  多个别名收敛到同一 family 时结果不再随 Go map 顺序漂移,冲突单价告警。

- grok free quota 软门禁缓存新增过期淘汰。条目按 account_id 键控且只写不删,
  账号下线后会驻留至进程结束;淘汰只挂在已受 TTL 约束的查询路径上,
  不影响缓存命中热路径。

- 迁移 220 清空非 Grok 分组视频价前先落快照表 groups_video_price_backup_220,
  原 UPDATE 不可回滚。

- 简化 grok_search_count 两处等价冗余的事件类型分支。
2026-08-08 11:02:22 +08:00
Wesley Liddick cc67b1aca1 Merge pull request #5406 from bestony/fix/openai-oauth-routing-hints
fix(openai): forward OAuth routing hints
2026-08-08 10:58:53 +08:00
IanShaw027 f3bac4619e feat(keys): expand Grok client samples and tune free soft-gate default
Ship Use Key templates that match Grok Build / Codex best practice: env
vars + multi-model config.toml with api_backend=responses, env_key over
hardcoded secrets, and clearer shell/path guidance for Claude/Codex/OpenCode.

Also set free_quota_token_limit default to 500k (24h soft-gate), clean up
personal-dev-only comments, and keep billing test fixtures aligned.
2026-08-08 10:26:16 +08:00
IanShaw027 2e857b20b8 chore(grok): 从 PR 中移除进度文档与 lockfile 噪音
- 删除 CHANNEL_MONITOR_V2_DESIGN.md(属于其它分支的设计草稿)
- 删除 backend/docs/GROK_INTEGRATION_PROGRESS.md(开发期进度记录,不属于产物)
- 还原 frontend/pnpm-lock.yaml 至 upstream/main(package.json 无变化,
  差异全部来自本地 pnpm install 重解析)
2026-08-08 10:00:56 +08:00
IanShaw027 e01ce90d47 fix(grok): harden voice request ids, video pending, and search pricing alerts
Mint durable grok_audio/grok_realtime usage ids, avoid CLI headers on api.x.ai
voice, retry video pending store and fail-closed when snapshot is missing without
status duration, align pure-video ImageCount tests, and escalate unset search
price_per_1k to error-level logs.
2026-08-08 09:45:12 +08:00
IanShaw027 12db0f906a fix(grok): drop account-test ZDR path and align media CLI headers
Remove optional upload_url / fake connectivity-only success from admin video
tests. Stamp Grok CLI headers only on the CLI proxy so OAuth media against
api.x.ai can complete and preview video like the gateway path.
2026-08-08 09:45:12 +08:00
IanShaw027 8005335742 fix(channel-monitor-v2): align admin error details dedup 2026-08-08 09:00:15 +08:00
IanShaw027 64d1ebe4a5 fix(channel-monitor-v2): close dedup and migration privacy gaps 2026-08-08 08:59:18 +08:00
IanShaw027 9f3ee38d4e fix(channel-monitor-v2): preserve migration checksums and privacy defaults 2026-08-08 08:55:59 +08:00
IanShaw027 77e39fe608 docs(grok): note custom-voices coverage and free 24h soft-gate scope
Document voice CRUD/audio surface area and clarify free-window local soft-gate
semantics versus upstream metering.
2026-08-08 08:48:38 +08:00
IanShaw027 8399a30417 fix(grok): treat free-usage and billing exhaustion as recoverable
Classify free-usage bodies during account tests without quarantining content
policy, and mark billing/spending-limit refresh failures as transient so
accounts stay probe-eligible.
2026-08-08 08:48:38 +08:00
IanShaw027 35faaa6d21 feat(grok): register custom-voices CRUD and audio download gateway routes
Forward list/get/patch/delete and reference-audio paths with safe path segment
encoding, method passthrough, and empty-body GET/DELETE handling.
2026-08-08 08:48:38 +08:00
IanShaw027 85b65284ec fix(grok): set async video duration_ms from create accept to done discovery
Store CreatedAt on pending billing at video create and use wall-clock E2E
latency when status/content first observes official done+video.url, so usage
logs no longer record only the single poll hop.
2026-08-08 08:48:38 +08:00
IanShaw027 0d98176c59 fix(channel-monitor-v2): correct aggregation privacy and backfill 2026-08-08 01:59:44 +08:00
IanShaw027 165b072908 fix(grok): gateway media/voice routing, models, and status UI polish
Align gateway Grok media/voice paths and model lists, harden upstream failure
and quota handling, clear non-Grok video generation config migration, and polish
temp-unsched/status indicators with model whitelist updates.
2026-08-08 01:07:27 +08:00
IanShaw027 2526a04226 fix(admin): empty web-search config on missing setting and reset dialog scroll
Return a disabled empty web-search-emulation config when the setting key is
absent, and reset BaseDialog body scroll on open for long modals.
2026-08-08 01:07:27 +08:00
IanShaw027 68faeac837 fix(grok): restore base URL resolution and operator settings wiring
Honor account GetGrokBaseURLOr policy for official vs custom endpoints,
and wire settings resolution used by responses/chat URL builders.
2026-08-08 01:07:19 +08:00
IanShaw027 6d632eec45 fix(grok): tighten OAuth SSO flow and hide password login
Require oauth state/redirect consistency, fail closed on missing proxy,
and remove password login from create/reauth UI (admin-only password path stays off by default).
2026-08-08 01:07:19 +08:00
IanShaw027 d0767eab9d feat(grok): admin account test modes with real media preview
Add mode-first connectivity probes for text/image/video/search/tts/stt/realtime,
standalone voice and web-search paths, media upload options, and in-browser
image/audio/video preview (including ZDR-safe b64 images and edit validation).
2026-08-08 01:07:08 +08:00
wucm667 9763765ebf fix(openai): fail over OAuth image stream errors 2026-08-08 01:05:29 +08:00
cyh 74fcdf3d42 fix(openai): preserve exponential capacity backoff 2026-08-08 01:02:02 +08:00
github-actions[bot] 68d8f122e4 chore: sync VERSION to 0.1.172 [skip ci] 2026-08-07 15:39:12 +00:00
Wesley Liddick 155c494964 Merge pull request #5399 from fengshao1227/fix/responses-anthropic-invalid-content-blocks
fix(apicompat): Responses→Anthropic 转换不再发出上游会拒收的 content block
2026-08-07 23:21:59 +08:00
Wesley Liddick 8991574873 Merge pull request #5345 from puppywang/fix/oauth-pending-account-takeover
fix(security): block OAuth account takeover via pending exchange
2026-08-07 23:20:22 +08:00
Wesley Liddick 8f7b0a314d Merge pull request #5398 from Wei-Shaw/fix/openai-capacity-shed-stream-recovery
fix(gateway): 流内降载错误恢复 pre-output failover 并对客户端改写为可重试错误码
2026-08-07 23:11:20 +08:00
li 64090de664 fix(apicompat): Responses→Anthropic 转换不再发出上游会拒收的 content block
convertResponsesInputToAnthropic 的 default 分支把未知 item 的 content 逐字
透传成 Anthropic user 消息,Responses 专有的分片类型会原样进入上游请求体。
最典型的是工具执行后回放的 reasoning item:带 content 数组时,reasoning_text
块直接发给 Anthropic,上游回 400 Request body format invalid,而该 item 会一直
留在会话历史里,导致此后每一轮都继续失败。

同时修正两处会产出空内容消息的路径——Anthropic 拒收空内容消息与空白 text 块:
分片全部不可识别时,user 消息退化成 content:""、assistant 消息退化成单个空
text 块。

改动:
- type=reasoning 显式跳过。Anthropic 无法摄入 OpenAI reasoning:encrypted_content
  不透明,thinking 重放需要上游签发的 signature。Codex 常见形态(只带 summary +
  encrypted_content)本来就会被丢弃,这里让带 content 的形态行为一致。
- default 分支改走 convertResponsesUserToAnthropicContent 白名单转换,保留其中
  可识别的文本/图片,丢弃其余分片。
- user / assistant 分支在转换结果为空内容或纯空白文本时跳过该消息。

Fixes #5329
2026-08-07 23:09:03 +08:00
shaw 14a27f1960 test(gateway): 校准 error 帧边界 flush 期望至 pre-output failover 新契约
可重试类 error 帧按设计不再算客户端输出(保留在 attempt 缓冲中,
为随后的 response.failed 保住 pre-output failover),因此不在自身
边界单独 flush,而是与终止帧一起出站(1 次 flush)。原用例按旧行为
断言 2 次 flush 导致 CI 失败。

同时补充不可重试类(invalid_request)error 帧仍在边界 flush 的对照
用例,锁住两类错误帧的行为区分。
2026-08-07 22:55:42 +08:00
shaw c33c3208e3 fix(gateway): 流内降载错误恢复 pre-output failover 并对客户端改写为可重试错误码
上游容量降载的真实序列是「event: error → event: response.failed」。此前
{"type":"error"} 帧被当作首个客户端输出立即 flush,clientOutputStarted 被固化,
随后的 response.failed 永远进不了 pre-output failover 分支,降载错误被原样转发;
Codex CLI 对 server_is_overloaded/slow_down 按闭集判致命,直接终止会话并提示
"Selected model is at capacity. Please try a different model."。

修复:
- 可重试类 error 帧不再算客户端输出,恢复既有的同账号重试+切号链路;
  不可重试类(content_policy/invalid_request 等)维持原样转发,保留上游错误细节
- 必须转发给客户端时(流中途已有输出 / WS 桥接),把 server_is_overloaded、
  slow_down 改写为致命集之外的 server_error,触发 Codex 内置退避重试;
  错误消息原样保留,rate_limit_exceeded 等其他错误码一律不动
- 监控、计费与账号状态判定均基于改写前的原始事件
2026-08-07 22:42:33 +08:00
白宦成 de349187d9 fix(openai): harden priority routing hints 2026-08-07 21:47:54 +08:00
白宦成 815035fcc9 fix(openai): send OAuth routing hints 2026-08-07 21:47:53 +08:00
白宦成 915cc7e7bd fix(openai): stop injecting legacy beta on OAuth responses 2026-08-07 21:47:53 +08:00