1. 15 Mar, 2026 14 commits
    • erio's avatar
      feat: unified OAuth token refresh API with distributed locking · 1fc9dd7b
      erio authored
      Introduce OAuthRefreshAPI as the single entry point for all OAuth token
      refresh operations, eliminating the race condition where background
      refresh and inline refresh could simultaneously use the same
      refresh_token (fixes #1035).
      
      Key changes:
      - Add OAuthRefreshExecutor interface extending TokenRefresher with CacheKey
      - Add OAuthRefreshAPI.RefreshIfNeeded with lock → DB re-read → double-check flow
      - Add ProviderRefreshPolicy / BackgroundRefreshPolicy strategy types
      - Simplify all 4 TokenProviders to delegate to OAuthRefreshAPI
      - Rewrite TokenRefreshService.refreshWithRetry to use unified API path
      - Add MergeCredentials and BuildClaudeAccountCredentials helpers
      - Add 40 unit tests covering all new and modified code paths
      1fc9dd7b
    • erio's avatar
      bdbc8fa0
    • erio's avatar
      fix(ops): match "insufficient account balance" in error filter · 63f3af0f
      erio authored
      The upstream Gemini API returns "Insufficient account balance" which
      doesn't contain the substring "insufficient balance". Add explicit
      match for the full phrase to ensure the filter works correctly.
      63f3af0f
    • IanShaw027's avatar
      style: 修复 gofmt 格式问题 · 686f890f
      IanShaw027 authored
      686f890f
    • shaw's avatar
      ae44a943
    • IanShaw027's avatar
    • IanShaw027's avatar
      fix: 兼容部分限额字段为空的情况 #1021 · c31974c9
      IanShaw027 authored
      修复在填写限额时,如果不填写完整的三个限额额度(日限额、周限额、月限额)就会报错的问题。
      
      变更内容:
      - 后端:添加 optionalLimitField 类型处理空值和空字符串,兼容部分限额字段为空的情况
      - 前端:添加 normalizeOptionalLimit 函数规范化限额输入,将空值、空字符串和无效数字统一处理为 null
      c31974c9
    • erio's avatar
      feat(ops): add ignore insufficient balance errors toggle and extract error constants · cfe72159
      erio authored
      - Add 5th error filter switch IgnoreInsufficientBalanceErrors to suppress
        upstream insufficient balance / insufficient_quota errors from ops log
      - Extract hardcoded error strings into package-level constants for
        shouldSkipOpsErrorLog, normalizeOpsErrorType, classifyOpsPhase, and
        classifyOpsIsBusinessLimited
      - Define ErrNoAvailableAccounts sentinel error and replace all
        errors.New("no available accounts") call sites
      - Update tests to use require.ErrorIs with the sentinel error
      cfe72159
    • Elysia's avatar
      增加测试 · 359e5675
      Elysia authored
      359e5675
    • erio's avatar
      fix(billing): allow clearing group quota limits and treat 0 as zero-limit · 5899784a
      erio authored
      Previously, v-model.number produced "" when input was cleared, causing
      JSON decode errors on the backend. Also, normalizeLimit treated 0 as
      "unlimited" which prevented setting a zero quota. Now "" is converted
      to null (unlimited) in frontend, and 0 is preserved as a valid limit.
      
      Closes Wei-Shaw/sub2api#1021
      5899784a
    • erio's avatar
      fix(billing): treat nil rate limit window as expired to prevent usage accumulation · 9e8959c5
      erio authored
      When Redis cache is populated from DB with a NULL window_1d_start, the
      Lua increment script only updates usage counters without setting window
      timestamps. IsWindowExpired(nil) previously returned false, so the
      accumulated usage was never reset across time windows, effectively
      turning usage_1d into a lifetime counter. Once this exceeded
      rate_limit_1d the key was incorrectly blocked with "日限额已用完".
      
      Fixes Wei-Shaw/sub2api#1022
      9e8959c5
    • YanzheL's avatar
      fix: extract and log Claude output_config.effort in usage records · 1bff2292
      YanzheL authored
      Claude's output_config.effort parameter (low/medium/high/max) was not
      being extracted from requests or logged in the reasoning_effort column
      of usage logs. Only the OpenAI path populated this field.
      
      Changes:
      - Extract output_config.effort in ParseGatewayRequest
      - Add ReasoningEffort field to ForwardResult
      - Populate reasoning_effort in both RecordUsage and RecordUsageWithLongContext
      - Guard against overwriting service-set effort values in handler
      - Update stale comments that described reasoning_effort as OpenAI-only
      - Add unit tests for extraction, normalization, and persistence
      1bff2292
    • Ethan0x0000's avatar
      test: fix usage repo stubs for unit builds · cf924775
      Ethan0x0000 authored
      cf924775
    • Ethan0x0000's avatar
      feat: 完善使用记录端点可观测性与分布统计 · eefab159
      Ethan0x0000 authored
      将入站、上游与路径三类端点分布统一到使用记录页的一致化卡片交互中,并补齐端点元数据与统计链路,提升排障与流量分析效率。
      eefab159
  2. 14 Mar, 2026 16 commits
  3. 13 Mar, 2026 10 commits
    • erio's avatar
      fix: restore OAuth 401 temp-unschedulable for Gemini, update Antigravity tests · 45456fa2
      erio authored
      The 403 detection PR changed the 401 handler condition from
      `account.Type == AccountTypeOAuth` to
      `account.Type == AccountTypeOAuth && account.Platform == PlatformOpenAI`,
      which accidentally excluded Gemini OAuth from the temp-unschedulable path.
      
      Fix: use `!= PlatformAntigravity` instead, preserving Gemini behavior
      while correctly excluding Antigravity (whose 401 is handled by
      applyErrorPolicy's temp_unschedulable_rules).
      
      Update tests to reflect Antigravity's new 401 semantics:
      - HandleUpstreamError: Antigravity OAuth 401 now uses SetError
      - CheckErrorPolicy: Antigravity 401 second hit stays TempUnscheduled
      - DB fallback: split into Gemini (escalates) and Antigravity (stays temp)
      45456fa2
    • erio's avatar
      feat(redeem): support subscription type in create-and-redeem API · 05edb551
      erio authored
      Add group_id and validity_days fields to CreateAndRedeemCodeRequest,
      enabling subscription-type redemption codes to be created and redeemed
      in a single API call.
      
      - Type defaults to "balance" when omitted for backward compatibility
      - Subscription type requires group_id (non-nil) and validity_days (>0)
      - Existing balance/concurrency callers are unaffected
      05edb551
    • Ylarod's avatar
      fix lint · e90ec847
      Ylarod authored
      e90ec847
    • erio's avatar
      feat(antigravity): add 403 forbidden status detection, classification and display · 6344fa2a
      erio authored
      Backend:
      - Detect and classify 403 responses into three types:
        validation (account needs Google verification),
        violation (terms of service / banned),
        forbidden (generic 403)
      - Extract verification/appeal URLs from 403 response body
        (structured JSON parsing with regex fallback)
      - Add needs_verify, is_banned, needs_reauth, error_code fields
        to UsageInfo (omitempty for zero impact on other platforms)
      - Handle 403 in request path: classify and permanently set account error
      - Save validation_url in error_message for degraded path recovery
      - Enrich usage with account error on both success and degraded paths
      - Add singleflight dedup for usage requests with independent context
      - Differentiate cache TTL: success/403 → 3min, errors → 1min
      - Return degraded UsageInfo instead of HTTP 500 on quota fetch errors
      
      Frontend:
      - Display forbidden status badges with color coding (red for banned,
        amber for needs verification, gray for generic)
      - Show clickable verification/appeal URL links
      - Display needs_reauth and degraded error states in usage cell
      - Add Antigravity tier label badge next to platform type
      
      Tests:
      - Comprehensive unit tests for classifyForbiddenType (7 cases)
      - Unit tests for extractValidationURL (8 cases including unicode escapes)
      - Integration test for FetchQuota forbidden path
      6344fa2a
    • Peter's avatar
      feat(ops): allow hiding alert events · 29b0e4a8
      Peter authored
      29b0e4a8
    • Ylarod's avatar
      sub2api: add bedrock support · 11f7b835
      Ylarod authored
      11f7b835
    • Rose Ding's avatar
    • wucm667's avatar
      refactor: 将 ComputeQuotaResetAt 和 ValidateQuotaResetConfig 函数中的 map 类型从... · 2573107b
      wucm667 authored
      refactor: 将 ComputeQuotaResetAt 和 ValidateQuotaResetConfig 函数中的 map 类型从 map[string]interface{} 修改为 map[string]any
      2573107b
    • wucm667's avatar
      feat: 账号配额支持固定时间重置模式 · 5b850059
      wucm667 authored
      - 后端新增 rolling/fixed 两种配额重置模式,支持日配额和周配额
      - fixed 模式下可配置重置时刻(小时)、重置星期几(周配额)及时区(IANA)
      - 在 account_repo.go 中使用 SQL 表达式适配两种模式的过期判断与重置时间推进
      - 新增 ComputeQuotaResetAt / ValidateQuotaResetConfig 等辅助函数
      - DTO 层新增相关字段并在 mappers 中完整映射
      - 前端 QuotaLimitCard 新增 rolling/fixed 切换 UI、时区选择器
      - CreateAccountModal / EditAccountModal 透传新配置字段
      - i18n(zh/en)同步新增相关翻译词条
      5b850059
    • haruka's avatar
      fix: 管理员重置配额补全 monthly 字段并修复 ristretto 缓存异步问题 · e73531ce
      haruka authored
      
      
      - 后端 handler:ResetSubscriptionQuotaRequest 新增 Monthly 字段,
        验证逻辑扩展为 daily/weekly/monthly 至少一项为 true
      - 后端 service:AdminResetQuota 新增 resetMonthly 参数,
        调用 ResetMonthlyUsage;重置后追加 subCacheL1.Wait(),
        保证 ristretto Del() 的异步删除立即生效,消除重置后
        /v1/usage 返回旧用量数据的竞态窗口
      - 后端测试:更新存量测试用例匹配新签名,补充
        TestAdminResetQuota_ResetMonthlyOnly /
        TestAdminResetQuota_ResetMonthlyUsageError 两个新用例
      - 前端 API:resetQuota options 类型新增 monthly: boolean
      - 前端视图:confirmResetQuota 改为同时重置 daily/weekly/monthly
      - i18n:中英文确认提示文案更新,提及每月配额
      Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
      e73531ce