2747 Commits

Author SHA1 Message Date
wucm667
271aba1abe fix(ops): exclude IP-denied access from SLA 2026-05-19 15:41:54 +08:00
Wesley Liddick
1b6ed24c33
Merge pull request #2492 from DaydreamCoding/feat/dingtalk-login
feat(dingtalk): 钉钉 OAuth 登录接入 + internal_only 用户属性同步
2026-05-19 15:36:13 +08:00
DaydreamCoding
b19da9c7fe feat(dingtalk): 钉钉 OAuth 登录接入与 internal_only 用户属性同步
⚠️ 应用类型约束:当前实现仅支持「钉钉登录-企业内部应用」(DingTalk 开放平台
internal_app 类型)。第三方个人应用、第三方企业应用类型暂不支持——OAuth 流程
相同但 corp 校验、跨企业行为不同。backend 通过 DingTalkAppKind 校验对非
internal_app 类型 fail-closed(硬约束)。

钉钉 OAuth 登录主链
- 4 步 OAuth 链:ExchangeCodeForUserToken / GetUnionIdByUserToken /
  GetUserIdByUnionId / GetStaffInfoByUserId;app token 缓存
- pending session 机制持久化 OAuth 中间态;cookie-only token 持久化
- 三种分流:bind_login_required / email_completion / choose_account_action
- corp_restriction_policy 支持 none + internal_only;stale "whitelist" 在
  加载层与写入层均静默 coerce 为 none + slog.Warn
- bypass_registration 开关:企业内部模式豁免全局 REGISTRATION_DISABLED
- isReservedEmail / signup_source / canUnbindProvider / OAuth pending flow
  等横切点支持 dingtalk provider
- migration 136:4 表 CHECK 约束加入 'dingtalk' provider 值

internal_only 模式同步企业邮箱/姓名/部门到用户属性
- SyncCorpEmail / SyncDisplayName / SyncDept 三个独立开关 + 对应
  SyncXxxAttrKey 目标属性 key(默认 dingtalk_email / dingtalk_name /
  dingtalk_department);非 internal_only policy 在写入层与加载层均
  coerce 为 false,admin handler 与 setting_service 双层兜底
- 同步语义:首次注册写 users.username(昵称优先 → 企业姓名 fallback),
  之后每次登录刷新 3 个属性;空值也写入以覆盖旧值
- 邮箱三级 fallback:org_email > email > extension["企业邮箱"]
  (钉钉自定义字段 JSON)
- 部门路径递归向上拼接,跳过 dept_id=1 选首个真实子部门,剥离根组织名
- GetUnionIdByUserToken 同时返回 OIDC /contact/users/me 的 nick 字段;
  新增 GetDeptInfo 调用 OAPI /topapi/v2/department/get
- AuthHandler 注入 UserAttributeService;OAuth pending flow 在
  createPendingOAuthAccount / bindPendingOAuthLogin 分别派发到
  AfterRegistration(syncUsername=true)/ AfterLogin
- migration 137 seed dingtalk_email/name/department 三个用户属性定义

附带修复(同集成路径暴露的两个 OAuth 注册回归)
- LoginOrRegisterOAuthWithTokenPair 新建用户分支用 inferLegacySignupSource
  覆写 caller 显式传入的 signupSource,导致 dingtalk/linuxdo/oidc/wechat
  渠道授权按 email 渠道读取;改为只在 caller 未显式传入时回退邮箱推断
- mergeProviderDefaultGrantSettings 把 parse fallback 默认值
  (Concurrency=5 / Balance=0) 当作"未配置"哨兵,admin 显式设 5 时被误判
  退回全局默认(复现:全局默认 1 + 渠道默认并发 5 + grant_on_signup → 新
  用户实际 concurrency=1);去掉哨兵,admin 任何 >=0 值都覆盖 globalDefaults

前端
- DingTalk Login / Callback / EmailCompletion / ChoiceAccount / Error
  视图;router + auth API client
- admin SettingsView:corp policy radio(none / internal_only)+ bypass
  注册开关 + i18n;internal_only 下展示三同步开关 + 目标 attr key 下拉
  (拉取 user attribute definitions),展示 fieldEmail /
  qyapi_get_department_list 钉钉权限申请提示
- Profile:S1 主动绑定 / S5 解绑钉钉按钮 + 合成邮箱防自锁

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 15:27:47 +08:00
DaydreamCoding
664e9fdcd4 feat(usage): 用户用量按平台拆分 + UsersView 列设置可配置 + 用量列排序
后端
- BatchUserUsageStats / UserDashboardStats 新增 ByPlatform 字段
  复用 ops 路径 COALESCE(g.platform, a.platform) 语义,不冗余 DB 字段
- 抽出 usageLogEffectivePlatformExpr 常量供管理员与用户两路径共用
- GetBatchUsersUsage cacheKey 加 v=2 + 当日日期,修复跨午夜旧缓存兼容新字段

前端
- 新建 PlatformUsageBreakdown:管理员用量列 hover tooltip 展示各平台 today/total
- 新建 PlatformCostCell:单平台 today/total 紧凑单元格
- UsersView 列设置新增 Claude/OpenAI/Gemini/Antigravity 四个平台子列,默认隐藏可手动启用
- 普通用户 Dashboard 新增 Row 3 平台拆分卡片,受 isSimple 控制
- 平台之和 < 总值时显式展示"其他"行,避免数字对不齐
- last_active_at 从 FORCED_VISIBLE_COLUMNS 移除,允许用户隐藏并持久化
- 列设置加 schema 版本号 + 迁移机制,老用户升级时新增默认隐藏列自动应用
- UsersView 用量列(汇总 + 4 平台子列)加入前端单页排序:列头单按钮 + 弹出菜单
  切换"今日 / 近30天",三态循环 desc → asc → off;菜单底部备注"仅对本页数据排序"
- sortedUsers computed 在 server-side-sort 结果之上叠加本地排序,缺失值按 0 处理;
  usageSort 状态独立 localStorage 持久化,互不干扰后端 sort_by
- i18n 新增 admin.users.sortBy / sortCurrentPageOnly

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:25:34 +08:00
shaw
b006e36af9 style(openai-ws): 修复 passthrough_relay gofmt 对齐
PR #2444 引入的 activeTurn 字段未按 gofmt 对齐,导致 golangci-lint
报 gofmt 错误。运行 gofmt -w 调整字段类型对齐到与同结构其他字段一致。
2026-05-19 15:20:02 +08:00
Wesley Liddick
2ec9b0d7ba
Merge pull request #2412 from gaoren002/fix/codex-auto-review-gpt55
fix(openai): add codex auto review model pricing
2026-05-19 15:14:36 +08:00
shaw
0c34e31881 fix(gemini): 修复 chat completions compat 编译失败
PR #2451 引入的 gemini_chat_completions_compat_service.go 调用了
已被 4840194b 删除的 extractImageSize 方法,导致 main 分支自合并
后无法编译,连带 test/golangci-lint/govulncheck 三个 CI 全部失败。

按照 gemini_messages_compat_service.go 的现行模式,改用
extractImageInputSize + normalizeOpenAIImageSizeTier 两步调用,
并补齐 ForwardResult.ImageInputSize 字段以保持行为一致。
2026-05-19 15:10:24 +08:00
Wesley Liddick
548c71c8bb
Merge pull request #2557 from Arron196/fix/issue-2542-daily-card-expiry-mode
fix: 修复日卡跨日重复刷新额度
2026-05-19 14:53:22 +08:00
Wesley Liddick
8a9f7ae2f1
Merge pull request #2444 from astro-ge/fix/passthrough-first-token-ms
fix(passthrough): 修复WSv2模式下first_token_ms测量错误
2026-05-19 14:52:55 +08:00
Wesley Liddick
902af920b7
Merge pull request #2456 from Evsdrg/fix/unbounded-queries-and-sort
fix(repo): 公告查询添加分页上限,优化分组按账户数排序的数据加载方式
2026-05-19 14:51:59 +08:00
Wesley Liddick
8a4ee578cb
Merge pull request #2451 from wucm667/codex/issue-2237-gemini-chat-completions
fix(gateway): 修复 Gemini 组 Chat Completions 路由
2026-05-19 14:47:52 +08:00
Wesley Liddick
e365aae450
Merge pull request #2450 from wucm667/codex/issue-2431-responses-api-support
feat: 支持后台配置 OpenAI Responses API 路由
2026-05-19 14:47:10 +08:00
Wesley Liddick
ac856633aa
Merge pull request #2449 from wucm667/fix/payment-product-name-affix
fix: 修复订阅支付商品名前后缀不生效
2026-05-19 14:46:09 +08:00
Wesley Liddick
36e461e7c9
Merge pull request #2424 from wucm667/fix/openai-versioned-base-url
fix(openai): handle versioned compatible base URLs
2026-05-19 14:44:37 +08:00
Wesley Liddick
23e95b77b7
Merge pull request #2528 from wucm667/fix/openai-responses-null-content
fix(openai): 修复 chat-completions 转 responses 时 content 为 null 导致上游 400
2026-05-19 14:43:55 +08:00
Wesley Liddick
03473d3ee8
Merge pull request #2554 from Arron196/feature/sync-upstream-models-pr
feat: 支持从上游同步账号可用模型列表
2026-05-19 14:42:47 +08:00
Wesley Liddick
ae4c738887
Merge pull request #2457 from wucm667/fix/openai-fast-policy-default-pass
fix: 默认透传 OpenAI service_tier
2026-05-19 14:34:37 +08:00
Wesley Liddick
aa1460feb3
Merge pull request #2475 from is7Qin/feat/available-channels-default-pricing
feat(channels): 「可用渠道」对未填价的 pricing 条目按 LiteLLM 默认价展示
2026-05-19 14:33:17 +08:00
Wesley Liddick
c65522641d
Merge pull request #2473 from is7Qin/fix/image-tier-validation
fix(channels): 按次/图片计费模式下区间校验跳过 token 上下文重叠规则
2026-05-19 14:28:33 +08:00
Wesley Liddick
57603e169e
Merge pull request #2461 from wucm667/fix/image-gen-upstream-context-detach
fix(gateway): 修复图片生成上游请求过早取消
2026-05-19 14:14:50 +08:00
Wesley Liddick
be0c857240
Merge pull request #2502 from yangzc2004-bit/codex/gemini-google-one-429-cooldown
fix: use tier cooldown for google one gemini 429
2026-05-19 14:07:25 +08:00
Wesley Liddick
a340002c6d
Merge pull request #2401 from 2ue/fix/normalize-image-billing-size
修复图片计费尺寸归一化与使用记录展示
2026-05-19 14:00:24 +08:00
name
0ad69d1ffb Merge branch 'main' into fix/openai-token-missing-refresh-disable 2026-05-19 11:06:19 +08:00
name
ddab7de916 Merge branch 'main' into fix/image-tier-validation 2026-05-19 11:06:17 +08:00
name
d70497a669 Merge branch 'main' into feat/available-channels-default-pricing 2026-05-19 11:06:15 +08:00
Wesley Liddick
14f54be03f
Merge pull request #2481 from weak-fox/lyp/fix-issue-2223-capacity-retry
fix: 修复 OpenAI 模型容量错误未进入自动重试
2026-05-19 10:24:18 +08:00
Wesley Liddick
f9fec78b70
Merge pull request #2505 from is7Qin/fix/openai-compat-usage-parsing
修复 Claude 映射 GPT 后被记为 0 token 的计费漏洞
2026-05-19 09:53:50 +08:00
shaw
a729752de6 test: align codex tool-call id assertions with fc_ prefix 2026-05-19 09:45:05 +08:00
Wesley Liddick
11870cf84f
Merge pull request #2499 from yetone/fix/codex-transform-fc-underscore
fix(codex-transform): preserve underscore when rewriting `call_*` tool-call ids
2026-05-19 09:39:25 +08:00
Wesley Liddick
e318376e88
Merge pull request #2548 from Arron196/fix/sla-exclude-capacity-errors
fix: 统一 Ops SLA 与请求错误统计口径
2026-05-19 09:29:22 +08:00
Wesley Liddick
d1910751b6
Merge pull request #2541 from DanisJiang/fix/alipay-pagepay-qrcode
fix(payment): 修复支付宝官方扫码二维码生成错误
2026-05-19 09:24:23 +08:00
benjamin
a4884b4e75 fix(subscription): 将日卡改为一次性每日配额
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 21:09:11 +08:00
benjamin
36c00374d3 feat: expose upstream model sync admin API
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 19:01:33 +08:00
benjamin
b9ecf25207 fix: harden Antigravity model list requests
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 19:01:23 +08:00
benjamin
ba676e43fd feat: add upstream model sync service
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 19:01:12 +08:00
lyen1688
164e2f610c fix: add keepalive for Anthropic passthrough streams 2026-05-18 18:41:25 +08:00
benjamin
6acb46c113 fix: 标记通用网关本地调度容量错误
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 16:52:32 +08:00
benjamin
429adbc721 fix: 标记 OpenAI 本地调度容量错误
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 16:52:20 +08:00
benjamin
ae6ee23e2e fix: 调整 Ops 错误分类的 SLA 排除逻辑
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 16:52:06 +08:00
nanobabanan
e9a25e7b92 fix(apicompat): preserve empty streaming thinking blocks
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-05-18 15:10:35 +08:00
L494264Tt
1d47fd6300 This preserves DeepSeek reasoning_content across chat compatibility paths.
DeepSeek thinking-mode tool-call conversations may require the assistant
  reasoning_content from previous turns to be sent back in later requests. Without
  preserving it, those conversations can fail or lose reasoning context.

  Changes:
  - Preserve assistant reasoning_content when converting Chat Completions messages
    to Responses input by wrapping it as a thinking block.
  - Add regression coverage for non-streaming DeepSeek responses.
  - Add regression coverage for streaming DeepSeek deltas.
  - Add regression coverage that request-side messages[].reasoning_content is
    passed through with tool calls.

  Tests:
  go test -tags=unit ./internal/pkg/apicompat ./internal/service -run
  'TestChatCompletionsToResponses_AssistantReasoningContentPreserved|
  TestChatCompletionsToResponses_AssistantThinkingTagPreserved|
  TestForwardAsRawChatCompletions_PreservesDeepSeekReasoningContent|
  TestForwardAsRawChatCompletions_ForcesStreamUsageUpstreamAndPassesUsageDownstream
2026-05-18 11:22:23 +08:00
Yuhao Jiang
1b03240515 fix(payment): 修复支付宝官方扫码二维码生成错误
支付宝官方服务商在 precreate(当面付)不可用回退到 page.pay 时,
错误地把网页跳转 URL 当作可扫码二维码内容返回。前端用 QRCode 库
把这段 URL 渲染成二维码后,支付宝 APP 无法识别(扫到的只是个 HTTP
URL,不是支付二维码),用户必须点"重新打开支付页面"跳转到支付宝
收银台才能扫到真正可用的二维码。

- 后端 alipay.go:createPagePayTrade 不再把 PayURL 塞给 QRCode;
  createDesktopTrade 在 paymentMode == "redirect" 时跳过 precreate
  直接走 page.pay,避免没开通"当面付"的商户走一次无用的 API 调用
- 前端管理端 PaymentProviderDialog:让支付宝官方实例可在"支付模式"
  中选择"跳转",开启后始终在新标签页打开支付宝收银台
- ProviderCard 的 modeLabel 增加 redirect 分支
- 补充 TestCreateTradeRedirectModeSkipsPrecreate 单元测试

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 15:01:17 -05:00
lyen1688
cc5328c491 修复 OpenAI Responses SSE 终止事件识别 2026-05-17 15:33:34 +08:00
wucm667
df82a3bc69 fix(openai): avoid null content when converting chat-completions to responses
When a chat-completions message has no usable content parts (empty array,
empty text part, or filtered-out image part), marshalChatInputContent
marshalled a nil slice to JSON null. The upstream Responses API rejects a
null content field with HTTP 400. Fall back to an empty string instead.

Fixes #2515
2026-05-17 11:20:05 +08:00
name
bec1e2b697 fix(openai): 永久禁用缺失 refresh_token 的 OAuth 账号
token_provider 在 expires_at 已过且 refresh_token 缺失时,仅返回 error,未做任何降级。
HandleUpstreamError 的 OAuth 401 分支也只走 10min 冷却,不区分账号是否具备刷新能力。
两条路径相加导致缺 refresh_token 的账号被反复选中、每次都在 token 阶段失败,对用户呈现持续 502。

token_provider.GetAccessToken: 命中"过期且无 refresh_token"时调用 SetError 永久禁用并清缓存,
依赖 background context 避免请求 ctx 提前结束影响落库。
ratelimit_service 401 OAuth 分支:refresh_token 为空时直接 SetError,不再写 expires_at、
不再 SetTempUnschedulable,缓存失效保留。RT 账号路径完全不动。

新增/调整测试覆盖两条路径,旧测试为 RT 路径补足 refresh_token 字段以保留原意图。
2026-05-16 19:40:23 +08:00
name
0393bd7c82 Fix OpenAI compat usage parsing 2026-05-16 03:03:43 +08:00
yangzc2004-bit
87fac30459 fix: use tier cooldown for google one gemini 429 2026-05-16 00:16:38 +08:00
yetone
348a487739
fix(codex-transform): preserve underscore when rewriting call_* tool-call ids
`fixCallIDPrefix` builds malformed ids when the input has the standard
OpenAI `call_<nanoid>` prefix:

  input:  call_YYen1qxDejd2myJwcTCf7Nyp
  output: fcYYen1qxDejd2myJwcTCf7Nyp   ← no underscore between 'fc' and the nanoid

ChatGPT's codex backend then rejects the replayed item with:

  400 Invalid 'input[N].id': 'fcYYen1qxDejd2myJwcTCf7Nyp'.
       Expected an ID that contains letters, numbers, underscores, or
       dashes, but this value contained additional characters.

Sub2api wraps that into 502 to the client. Clients using the OpenAI SDK
on the OAuth/codex path see every multi-hop turn (after the first tool
call) fail because the item_reference rewritten this way gets sent on
every subsequent hop.

The other two branches of the same function correctly emit `fc_`
(line 1029: pass-through when already `fc*`; line 1035 fallback:
`fc_" + id`). Only the `call_` → `fc_` rewrite was missing the
underscore — looks like a copy-paste slip during the original commit.

Fix: change `"fc"` to `"fc_"` on the call_ branch. One character.

Repro:
  client (OpenAI SDK) sends a function_call_output whose call_id is
  `call_<nanoid>` (default OpenAI format). The sub2api request body
  also contains an item_reference whose id mirrors the call_id (also
  `call_<nanoid>`). On the codex OAuth path, this rewrite fires for
  the item_reference's id, producing the malformed value.

Affects: `platform=openai type=oauth` accounts whose clients use the
official OpenAI SDK / Responses API conventions (id prefix `call_`).
API-key accounts and bridge-mode requests are untouched.
2026-05-15 23:29:56 +08:00
name
ff6f1640c4 fix(channels): 后端按次/图片计费跳过 token 区间重叠校验
前端在上一个 commit 已对 image / per_request 模式跳过 unbounded-last
和重叠检查, 但保存时后端仍按 token 语义校验, 导致添加第二个图片层级
时报错:

  invalid pricing intervals for platform 'openai' models
  [gpt-image-2 gpt-image-1.5 gpt-image-1]:
    interval #1: unbounded interval (max_tokens=null) must be the last one

ValidateIntervals 加 mode 参数, 与前端校验逻辑对齐:
- token 模式行为不变 (区间重叠 / last-unlimited 仍校验)
- per_request / image 模式跳过区间重叠和 last-unlimited 检查,
  保留单条 min/max 自洽校验与价格非负校验。

调用方 validatePricingIntervals 把 pricing.BillingMode 透给校验器。
既有单测全部加上 BillingModeToken 显式参数, 新增 3 个 image 模式用例
(允许多条 unbounded / 仍拒绝负价 / 仍拒绝 max <= min)。
2026-05-15 13:14:07 +08:00
wucm667
2ec1d331e0 fix(gateway): return Gemini models for Gemini groups 2026-05-15 11:33:26 +08:00