5524 Commits
Author SHA1 Message Date
Wesley Liddick 5637f469fa Merge pull request #7802 from feeeei/feat/recharge-promotion-tiers
feat(payment): 充值优惠阶梯,支持赠金与折扣两种模式
2026-10-02 14:30:29 +08:00
shaw fa49647c6d test(model): 错误透传平台列表守卫纳入 typesafe 2026-10-02 14:00:12 +08:00
shaw 9eb394e3e9 fix(typesafe): 堵住校验解析差异与审计盲区,收紧模型列表口径
- System One 校验拒绝重复键与大小写/Unicode 折叠变体键(请求顶层、
  questions、问题对象):encoding/json 大小写不敏感且取最后一个重复键,
  而原始 body 原样转发,此前 {"model":"x","MODEL":"jev-latest"} 可绕过
  jev-latest 限制、模型白名单与流式限制。
- Prompt 审计与旧审核同时收集对象键名、问题 ID 与未知扩展字段(不含规范
  字段名与已校验的 type),此前把内容放进键名即可完全绕过审计。
- 上游成功响应的 usage/model 宽松解码(浮点、数字字符串),避免上游已
  计费而网关整体 502 不记账;解码失败记录 response_error ops 事件。
- 上游 413 与 400/422 同视为请求错误:透传状态码、不切换、不改账号状态。
- TypeSafe base_url 末尾的 /v1 自动剥离,避免拼出 /v1/v1/systemone;
  /v1/systemone 改挂文本请求体上限。
- Composite 静态兜底模型列表、管理端候选列表恢复为不含 jev-latest;
  Codex 清单永不列出 TypeSafe 模型;TypeSafe 分组候选默认 jev-latest。
- 创建账号从 Grok 切到 TypeSafe 时重置为白名单模式;错误透传平台列表补 typesafe。
2026-10-02 13:51:58 +08:00
shaw 2d8a386811 fix(typesafe): 补齐平台迁移、审计覆盖、端点隔离与错误策略
- 新增 241 迁移:user_platform_quotas / composite_model_routes 的 CHECK 约束
  加入 typesafe。此前设置 typesafe 默认配额会让注册时的多行配额快照整体
  违约(fail-open 后新用户所有平台配额丢失),单用户配额与 Composite 路由保存 500。
- Prompt 审计新增 typesafe_systemone 协议提取(state + 各问题 instructions/
  criteria/选项标签,键排序保证哈希稳定);此前提取为空,阻断与异步审计均被放行。
  旧内容审核同样覆盖问题文本,且不再丢弃含 <system-reminder> 的 System One 文本。
- TypeSafe 分组及路由到 TypeSafe 的 Composite 请求访问 Messages / count_tokens /
  Chat Completions / Responses 时返回 404,避免以 x-api-key 打到错误上游路径并
  污染账号状态;TypeSafe 账号 base_url 为空时不再回退 api.anthropic.com。
- SystemOne 补在途余额预留、利润控制准入终检、wrapReleaseOnDone,失败切换改用
  共享 FailoverState(同账号重试 / 池模式 / 临时封禁)。
- 上游错误沿用共享账号错误策略:402/403、自定义错误码、临时不可调度规则生效,
  记录 ops 上游错误;400/422 仍不切换且永不改变账号状态。
- 账号测试改走原生 System One 探测,可用模型只返回 jev-latest;
  响应超限显式报错,响应 Content-Type 仅透传 JSON 类型。
2026-10-02 13:08:40 +08:00
aofee bb97ab364a fix(typesafe): align System One validation with upstream schema 2026-10-02 09:44:50 +08:00
aofee 7225538358 merge: sync Jev support with latest upstream main 2026-10-02 09:37:30 +08:00
William 0cecf3c367 feat(keys): support sorting API keys by group name 2026-10-01 13:01:01 -04:00
feeeei 1aa34d478d feat(payment): 充值优惠阶梯支持折扣模式,快捷金额角标改为促销价签
- 新增全局模式开关 RECHARGE_BONUS_MODE(bonus / discount),接入支付配置、后台设置接口与 checkout-info
- 折扣模式:到账不变,实付基数按百分比打折(按币种精度取整),手续费 / 每日限额 / 渠道选实例沿用折后基数;
  百分比须 < 100,运行时 ≥ 100 按无优惠处理
- 充值页角标改为单行红色价签(赠金「+20%」/ 折扣「20% OFF」),第二行按模式显示到账或折后实付;
  摘要卡折扣模式显示优惠行,渠道限额校验改用折后实付
- 后台「充值优惠阶梯」拆为独立卡片:赠金 / 折扣切换、紧凑档位表格、区间预览、活动文案
- 补前后端单测与 zh/en 文案
2026-10-02 00:25:41 +08:00
feeeei 1b1039f4fd feat(payment): 充值赠送阶梯(按金额档位额外赠送到账额度)
- 后台支付设置新增「充值赠送阶梯」(满 X 送 Y%,RECHARGE_BONUS_TIERS)与 Markdown 活动文案
  (RECHARGE_BONUS_NOTICE):阈值按用户输入的支付金额命中(取不超过金额的最大档),
  赠送按到账基数(输入 × 充值倍率)计算;严格校验写入、宽松解析读取,订阅订单不参与
- 下单时按当时配置计算赠送并落库:payment_orders 新增 bonus_amount 列(迁移 241),
  amount 仍为到账总额,到账/兑换码/退款逻辑不变;推广返利基数改为 amount - bonus_amount
- checkout-info 下发 recharge_bonus_tiers / recharge_bonus_notice;创建订单与订单查询响应带 bonus_amount
- 充值页:快捷金额按钮右上角「+N%」角标 + 第二行到账金额,金额卡顶部渲染活动文案,
  订单摘要新增赠送额度行并在有赠送时始终显示到账余额;订单列表/详情/支付结果/成功面板显示含赠送
- 后台新增 RechargeBonusTierEditor(行内校验重复/非法阈值,自动排序并显示区间预览)
- 补后端单测、契约测试与前端 vitest,zh/en 文案
2026-10-02 00:25:41 +08:00
wucm667 1888de135d fix(models): list composite exact route aliases 2026-10-01 22:54:05 +08:00
SPHINX998andClaude Opus 5.5 b6a157c19c fix(openai): account image input usage on WS passthrough
The WS v2 passthrough relay only read output_tokens_details.image_tokens,
so image generation over WebSocket billed image input tokens at the text
input rate and ignored the hosted response.tool_usage.image_gen breakdown
that the HTTP Responses path already merges (extractOpenAIUsageFromJSONBytes
+ mergeHostedImageGenToolUsage).

The relay now reads image input tokens from input_tokens_details or
prompt_tokens_details, fills missing image input/output counters from
response.tool_usage.image_gen (usage values keep precedence), and carries
ImageInputTokens per turn and in the session aggregate into the
OpenAIForwardResult used for billing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:31:39 +08:00
SPHINX998andClaude Opus 5.5 775c1a3c1d fix(apicompat): preserve refusals when converting Responses to Chat
A Responses refusal arrives as a {"type":"refusal","refusal":...} content
part (streamed as response.refusal.delta), not as output_text. The
Responses → Chat Completions converters only read output_text, and
ResponsesContentPart had no refusal field, so Chat clients received an
empty assistant message when the model refused.

Map refusal parts to choices[0].message.refusal, response.refusal.delta
to delta.refusal, and let BufferedResponseAccumulator keep refusal deltas
so non-streaming replies rebuilt from deltas keep them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:03:51 +08:00
SPHINX998andClaude Opus 5.5 15f469c86b fix(gateway): ignore negative content block index in buffered Anthropic paths
The buffered Anthropic aggregators index finalResp.Content with the
upstream content_block_delta index after checking only idx < len. A
malformed or third-party Anthropic-compatible upstream that sends a
negative index makes the request goroutine panic, so the client gets no
response and the forward never returns a result to record usage.

Add the lower bound in all four buffered aggregators (Chat Completions
and Responses, on both the Anthropic gateway and the OpenAI native
Anthropic paths); the out-of-range delta is skipped like an index past
the end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:00:22 +08:00
SPHINX998andClaude Opus 5.5 7a767d5ce8 fix(apicompat): pair legacy chat function calls with their results
Legacy Chat function calling has no call IDs: the assistant turn carries
function_call and the role=function result refers to it only by name.
ChatCompletionsToResponses ignored the assistant function_call and used
the function name as the result's call_id, so the Responses upstream got
a function_call_output with no matching function_call and rejected the
replayed history.

Emit a function_call item for each legacy assistant call and give the
following same-name result the same call_id. IDs follow message order
(call_legacy_N), so the same history converts to the same input across
turns, and skip IDs already used by tool_calls. A result with no
preceding call keeps the previous name-based call_id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:55:31 +08:00
SPHINX998andClaude Opus 5.5 9a8ab4f88e fix(apicompat): keep chat developer messages as developer role
chatMessageToResponsesItems only mapped "system" explicitly and sent every
other unknown role through the user converter, so Chat Completions
developer messages reached the Responses upstream as user turns and lost
their instruction priority.

The Responses API accepts role "developer" in input messages; convert it
like a system message and keep the original role. Downstream converters
already understand it (ResponsesToAnthropicRequest folds developer into
the system prompt, the Codex transform forwards it unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:53:30 +08:00
SPHINX998andClaude Opus 5.5 f3d752693f fix(apicompat): flatten named chat tool_choice for Responses
Chat Completions names a forced function as
{"type":"function","function":{"name":"X"}}, while the Responses API
expects {"type":"function","name":"X"}. ChatCompletionsToResponses passed
the Chat shape through unchanged, so OpenAI API-key accounts on
/v1/chat/completions forwarded the nested shape to /v1/responses and the
upstream rejected the request.

Flatten the named Chat choice; strings and objects already in the
Responses shape are kept as-is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:51:37 +08:00
lyy0709 71e97d1353 fix(models): enforce account mappings for remote Codex discovery 2026-10-01 03:30:56 -07:00
lyen1688 ad05eda157 依据正常交互式 CLI 抓包修正 Grok 身份头 2026-10-01 12:17:27 +08:00
lyen1688 fdce5b8d01 修复 Grok CLI 版本门槛并对齐官方无界面请求头 2026-10-01 12:04:39 +08:00
SPHINX998andClaude Opus 5.5 73381fc9c7 fix(openai-ws): bill each later turn with the current group pricing
The Responses WebSocket handler reads the API key auth snapshot once at
connect and reuses it for every turn: RecordUsage takes the group pricing
from that snapshot, and the profit gate reinstalled in BeforeTurn takes
its selling price from the connect-time ctxkey.Group. A group price
change therefore never reaches an open connection until the client
reconnects, while HTTP requests pick it up once the auth cache entry is
invalidated.

Each later turn now re-reads the key through APIKeyService.GetByKey (the
auth cache the middleware uses) in BeforeTurn. When the key ID, group ID,
group platform and subscription type are unchanged, the fresh group is
swapped into ctxkey.Group for the turn's profit gate and passed to that
turn's RecordUsage. A failed lookup or a key moved to another group keeps
the connection snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:27:35 +08:00
YahooYuan666 27f4a1780f test(grok): cover empty response.completed failover for PlatformGrok
Adds two regression cases mirroring the #5009 OpenAI ones for Grok
platform accounts (issue #7774):

- TestOpenAIResponsesEmptyCompletedFailsOverGrok: an empty
  response.completed on the forwardGrokResponses streaming path must
  produce UpstreamFailoverError with an empty client body. Verified to
  FAIL on main without the guard extension and PASS with it.
- TestOpenAIResponsesEmptyCompletedWithOutputSucceedsGrok: normal Grok
  streams with semantic output and usage are untouched.

Fixes #7774
2026-10-01 03:01:43 +08:00
YahooYuan666 3613174cf6 fix(grok): fail over silent-refusal empty response.completed on Grok Responses streams
Issue #5009 added a failover guard for OpenAI Responses streams that
terminate with an empty response.completed (no output, no usage, no
error), but the guard is gated on PlatformOpenAI. Grok platform
accounts reach the same handler (forwardGrokResponses ->
handleStreamingResponseWithReasoning) and exhibit the same upstream
silent-refusal shape, so their empty completions are relayed to the
client as successful empty turns: Responses SDK clients map the empty
completed to finishReason stop and accept the turn with no retry.

Extend the guard to PlatformGrok, matching the unconditional guard on
the passthrough path (openai_gateway_passthrough.go).

Fixes #7774
2026-10-01 02:16:22 +08:00
Wesley Liddick 96f4c115c9 Merge pull request #7726 from william-drakemond/feat/claude-reset-redeem
feat(claude): 手动兑换 Claude 重置次数 / Manually redeem Claude limit resets
2026-09-30 14:33:23 +08:00
Wesley Liddick 327c322184 Merge pull request #7730 from lyy0709/codex/gpt61-sol-codex-plans
feat(openai): support GPT-6.1 Sol, Codex plans and Astra Ultrafast
2026-09-30 14:32:59 +08:00
Wesley Liddick 4f0c079e27 Merge pull request #7681 from william-drakemond/fix/balance-inflight-reservation
fix(billing): Redis 在途预留,防止余额模式并发透支
2026-09-30 14:31:04 +08:00
shaw 017bbcb901 chore(api-key): 创建频率默认值调整为每小时 60 次 2026-09-30 13:55:37 +08:00
shaw 9ecb340822 feat(api-key): 增加 API Key 创建数量与频率限制
- 新增 api_key_create.max_active_per_user(默认 200)与
  api_key_create.max_per_user_per_hour(默认 20),0 表示不限制
- 创建频率按固定一小时窗口统计,自定义与自动生成的 Key 统一计入
- 创建计数独立于 Key 生命周期,删除或修改 Key 不再重置计数
- 移除不再使用的 DeleteCreateAttemptCount 缓存方法
2026-09-30 13:44:02 +08:00
Wesley Liddick a0f41f95a0 Merge pull request #7579 from feeeei/fix/claude-code-only-fallback-openai-compat
fix(gateway): Claude Code 限制分组的 OpenAI 兼容入口改走降级分组,不再直接 403
2026-09-30 10:47:08 +08:00
lyy0709 e6d191a83f fix(openai): support Astra Ultrafast capabilities and pricing 2026-09-29 11:50:08 -07:00
lyy0709 c91bb6124a feat(codex): recognize current subscription plans 2026-09-29 11:50:08 -07:00
lyy0709 9688571a83 feat(models): support GPT-6.1 Sol 2026-09-29 11:49:26 -07:00
William DrakeandClaude Opus 5.5 dc2efc6b9a fix(claude): tighten reset redeem outcome handling (review R1)
- R1-1: an explicit upstream "unavailable" answer now fences the organization
  for 15 minutes (CLAUDE_RESET_UPSTREAM_UNAVAILABLE) instead of 24h; ambiguous
  outcomes keep the 24h fence. Frontend tells the operator to retry later.
- R1-2: rotate the idempotency key after definite pre-claim refusals (busy,
  not available, unresolved, upstream unavailable); map IDEMPOTENCY_IN_PROGRESS
  and IDEMPOTENCY_RETRY_BACKOFF to clear zh/en messages.
- R1-3: redeem reason and cleared windows pass only through explicit
  allowlists, so upstream values can never echo a grant ID to clients.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 14:21:10 -04:00
196b47757a feat(claude): redeem native limit resets with server-selected grant
POST /admin/accounts/:id/claude/reset-credits/redeem (Idempotency-Key
required, same middleware chain as the Codex reset-quota route). The server
re-queries eligibility and claims only the upstream next_grant_id when it is
redeemable; grant/org IDs never reach the client. Claims are serialized by
account and organization Redis leases, and a durable per-organization fence
in the idempotency table blocks further redemption after an unconfirmed
outcome (settles after 24h). Ported from upstream PR #7591.

Co-authored-by: korkin25 <eugeny.kuzakov@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 11:41:33 -04:00
wucm667 27de27ac64 fix(risk-control): remove deleted groups from moderation scope 2026-09-29 23:24:45 +08:00
wucm667 a4ea8059b7 fix(openai): recover rejected encrypted reasoning signatures 2026-09-29 16:42:02 +08:00
Wesley Liddick fe9dadfc7d Merge pull request #7679 from PMExtra/feat/risk-control-allowlist
feat: 新增可信用户风控白名单,保留审计并免除本地自动处罚
2026-09-29 15:53:18 +08:00
Wesley Liddick 052e08e764 Merge pull request #7684 from william-drakemond/feat/claude-reset-credits-ui
feat(claude): 查询 Claude 重置次数(只读),UI 复用 Codex 次数按钮 / Read-only Claude reset credit count, reusing Codex count UI
2026-09-29 15:50:53 +08:00
Wesley Liddick 1f955af353 Merge pull request #7380 from wucm667/fix/issue-7377-forward-stream-usage
fix(gateway): forward received chat stream usage
2026-09-29 15:44:10 +08:00
Wesley Liddick ccb027b515 Merge pull request #7378 from wucm667/fix/issue-7368-composite-ws-routing
fix(gateway): resolve composite routes for websocket aliases
2026-09-29 15:43:52 +08:00
Wesley Liddick 883a53fea0 Merge pull request #7643 from wucm667/fix/issue-7637-antigravity-compat-keepalive
fix(antigravity): keep compat streams alive before first content
2026-09-29 15:43:19 +08:00
Wesley Liddick b35a611538 Merge pull request #7646 from wucm667/feat/issue-7642-dashboard-spending-trend
feat(dashboard): toggle recent usage between tokens and spending
2026-09-29 15:43:07 +08:00
Wesley Liddick 1dca86f287 Merge pull request #7701 from wucm667/fix/issue-7699-tool-rewrite
fix(anthropic): rewrite tool names in one body pass
2026-09-29 15:42:45 +08:00
wucm667 27a9421e6a fix(anthropic): rewrite tool names in one body pass 2026-09-29 15:00:16 +08:00
William DrakeandClaude Opus 5.5 860782e6fb fix(claude): harden reset credit projection and cell state (review R2)
- Drop selection_token (unsalted hash of a short grant ID, reversible) and
  claudeGrantSelection from the read-only DTO; assert it never serializes.
- query() returns only the projected result; no raw upstream block escapes.
- Clear a cooldown_until that is already past, using the same `now` as the
  redeemable check; the cell also shows the cooldown only when in the future.
- Invalidate in-flight queries when account id, platform or type changes.
- Sort the earliest-expiry fallback by parsed timestamp, invalid/missing last.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 20:59:39 -04:00
korkin25 48970f61b4 fix(claude): explicitly close status HTTP response 2026-09-28 18:04:14 -04:00
korkin25 6a8f921650 feat(claude): expose sanitized native reset credit status 2026-09-28 18:04:13 -04:00
haruka 2cc459e24f style: satisfy Sonnet 5.5 lint checks 2026-09-29 05:20:04 +08:00
William DrakeandClaude Opus 5.5 475c0839c6 style: gofmt
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 17:08:27 -04:00
William DrakeandClaude Opus 5.5 b886fb8edb fix(billing): read account-level mappings from scheduler snapshot only when unpriced
- only consult account mappings after primary/channel candidates fail to price (or composite)
- source mappings from the scheduler snapshot (same pool as account selection), no direct DB query and no per-model cache
- ungrouped API keys use the scheduler's ungrouped pool
- no negative caching: new mappings take effect on the next request

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 17:08:13 -04:00
haruka 8490a81867 feat: support Claude Sonnet 5.5 2026-09-29 04:55:07 +08:00