1. 17 Apr, 2026 1 commit
    • erio's avatar
      refactor: extract ReadUpstreamResponseBody to deduplicate upstream response... · c0b2cacb
      erio authored and 陈曦's avatar 陈曦 committed
      refactor: extract ReadUpstreamResponseBody to deduplicate upstream response read + too-large error handling
      
      Consolidates 9 call sites of resolveUpstreamResponseReadLimit + readUpstreamResponseBodyLimited + ErrUpstreamResponseBodyTooLarge error handling into a single ReadUpstreamResponseBody function with TooLargeWriter callback for API-format-specific error responses (Anthropic, OpenAI, countTokens).
      c0b2cacb
  2. 14 Apr, 2026 13 commits
    • erio's avatar
      fix: round-2 audit fixes — security, code quality, and UI improvements · a9880ee7
      erio authored
      Security (HIGH):
      - Normalize all Redis cache keys to lowercase (verifyCode, passwordReset)
      - Fix verify code TTL renewal on failed attempts: use remaining TTL via
        ExpiresAt field instead of resetting to full 15-minute window
      - Add 3 missing fields to diffSettings audit log (promo_code, invitation_code,
        custom_endpoints)
      
      Code quality (MEDIUM):
      - Extract filterVerifiedEmails shared helper (balance_notify_service.go)
      - Add Pricing array non-empty validation for channel pricing rules
      - Add platform token semantics comment in gateway_service.go
      - Complete validatePlanPatch test coverage (+10 test cases)
      - Replace string types with QuotaThresholdType/QuotaResetMode across frontend
      - Remove duplicate getPlatformTextColor/getRateBadgeClass in ChannelsView
      - Return EMAIL_NOT_FOUND error on RemoveNotifyEmail miss
      
      UI improvements:
      - Reorder cost tooltip: user billing above separator, account billing below
      - Add NaN guard to accountBilled function
      - Move timezone selector inline into reset-mode row (no longer standalone)
      a9880ee7
    • erio's avatar
      fix: correct account stats pricing priority order · 98c9d517
      erio authored
      Priority was wrong:
      - Before: custom rules → LiteLLM (when ApplyPricingToAccountStats) → nil
      - After:  custom rules → totalCost (when ApplyPricingToAccountStats) → LiteLLM → nil
      
      When ApplyPricingToAccountStats is enabled, use the request's actual
      client billing cost (before multiplier) as account_stats_cost, instead
      of recalculating from LiteLLM per-token prices which produced incorrect
      values for per-request billing mode.
      
      LiteLLM model pricing is now the final fallback (priority 3), used only
      when neither custom rules nor ApplyPricingToAccountStats apply.
      98c9d517
    • erio's avatar
      feat: WebSearch tri-state, account stats pricing fix, quota cache fix, usage tooltip · 1262654d
      erio authored
      WebSearch tri-state switch:
      - Account-level web_search_emulation changed from bool to tri-state
        string: "default" (follow channel) / "enabled" / "disabled"
      - shouldEmulateWebSearch checks channel config when account is "default"
      - SQL migration converts old bool values
      - Frontend select replaces toggle in Edit/CreateAccountModal
      
      Account stats pricing:
      - resolveAccountStatsCost uses upstream model (post-mapping) for matching
      - Priority: custom rules → model pricing file (when toggle on) → default
      - Custom rules always configurable, independent of toggle
      - Account ID field changed to searchable selector filtered by platform
      - Description updated to reflect new behavior
      
      Quota notification cache fix:
      - CheckAccountQuotaAfterIncrement fetches real-time account from DB
      - Reconstructs pre-increment usage for accurate threshold crossing detection
      - New AccountQuotaReader interface (minimal: GetByID only)
      
      Usage tooltip:
      - Per-request/image billing shows per-request price instead of $0 token price
      - Token billing continues to show input/output price per million tokens
      1262654d
    • erio's avatar
      fix(channel): use upstream model for account stats pricing and remove channel pricing fallback · 11c46068
      erio authored
      - resolveAccountStatsCost now uses the final upstream model (after
        account-level mapping) to match custom pricing rules, fixing the
        issue where requested model (e.g. claude-sonnet-4-5) didn't match
        rules configured for upstream model (e.g. claude-opus-4-6)
      - Remove tryChannelPricing fallback — only custom rules are applied,
        unmatched requests use default formula (total_cost × rate)
      - Remove unused billingService and serviceTier parameters
      - Update description: "启用后将支持自定义账号统计的模型价格"
      11c46068
    • erio's avatar
      fix(notify): use real-time balance for crossing detection and simplify email logic · 31550a2c
      erio authored
      - Fix cached balance causing threshold crossing to never trigger:
        read real-time balance from billingCacheService instead of stale
        API key auth snapshot
      - Remove email="" placeholder concept; all emails are user-managed
      - Only send notifications to verified && non-disabled emails
      - Frontend: pre-fill user's email in add input when list is empty
      - Remove FilterEnabledEmails/IsPrimaryDisabled helpers (no longer needed)
      31550a2c
    • erio's avatar
      fix(notify): address review findings - accountCost formula, dedup, refactor · c3812ce1
      erio authored
      - Fix accountCost calculation in finalizePostUsageBilling to match
        postUsageBilling (always multiply by AccountRateMultiplier)
      - Use strings.EqualFold for email dedup in collectBalanceNotifyRecipients
      - Extract CheckAccountQuotaAfterIncrement into smaller functions:
        buildQuotaDims + asyncSendQuotaAlert (< 30 lines each)
      - Add "not splittable" comments for HTML template functions
      - Extract QuotaNotifyToggle.vue sub-component to reduce
        QuotaLimitCard.vue from 404 to 339 lines
      c3812ce1
    • erio's avatar
      feat(notify): add balance low & account quota notification system · b32d1a2c
      erio authored
      - User balance low notification: email alert when balance drops below
        configurable threshold (user email + verified extra emails)
      - Account quota notification: broadcast email to admin-configured
        recipients when daily/weekly/total quota usage exceeds alert threshold
      - Admin settings: global enable/disable, default threshold, quota
        notification email list (Email Settings tab)
      - User profile: enable/disable, custom threshold, add/remove extra
        notification emails with verification code flow
      - Account quota: per-dimension alert toggle and threshold in quota
        control card
      - Trigger logic: first-crossing only (old >= threshold && new < threshold
        for balance; old < threshold && new >= threshold for quota), naturally
        prevents duplicate notifications without Redis dedup
      b32d1a2c
    • erio's avatar
      feat(channels): add custom account stats pricing rules · 7535e312
      erio authored
      Allow channels to configure independent model pricing for account
      statistics cost calculation, decoupled from user billing.
      
      Backend:
      - Migration 101: channels.apply_pricing_to_account_stats toggle,
        channel_account_stats_pricing_rules/model_pricing tables,
        usage_logs.account_stats_cost column
      - resolveAccountStatsCost: match rules by group/account, then channel
        pricing, fallback to original formula when unconfigured
      - Integrate into both GatewayService.recordUsageCore and
        OpenAIGatewayService.RecordUsage
      - Update 8 account stats SQL queries to use
        COALESCE(account_stats_cost, total_cost) * account_rate_multiplier
      - 23 unit tests for matching, pricing lookup, and cost calculation
      
      Frontend:
      - Channel edit dialog: toggle + custom rules UI with group/account
        multi-select and pricing entry cards
      - API types and i18n (zh/en)
      7535e312
    • erio's avatar
      feat(gateway): add web search emulation for Anthropic API Key accounts · 1b53ffca
      erio authored
      Inject web search capability for Claude Console (API Key) accounts that
      don't natively support Anthropic's web_search tool. When a pure
      web_search request is detected, the gateway calls Brave Search or Tavily
      API directly and constructs an Anthropic-protocol-compliant SSE/JSON
      response without forwarding to upstream.
      
      Backend:
      - New `pkg/websearch/` SDK: Brave and Tavily provider implementations
        with io.LimitReader, proxy support, and Redis-based quota tracking
        (Lua atomic INCR + TTL, DECR rollback on failure)
      - Global config via `settings.web_search_emulation_config` (JSON) with
        in-process cache + singleflight, input validation, API key merge on
        save, and sanitized API responses
      - Channel-level toggle via `channels.features_config` JSONB column
        (DB migration 101)
      - Account-level toggle via `accounts.extra.web_search_emulation`
      - Request interception in `Forward()` with SSE streaming response
        construction using json.Marshal (no manual string concatenation)
      - Manager hot-reload: `RebuildWebSearchManager()` called on config save
        and startup via `SetWebSearchRedisClient()`
      - 70 unit tests covering providers, manager, config validation,
        sanitization, tool detection, query extraction, and response building
      
      Frontend:
      - Settings → Gateway tab: Web Search Emulation config card with global
        toggle, provider list (add/remove, API key, priority, quota, proxy)
      - Channels → Anthropic tab: web search emulation toggle with global
        state linkage (disabled when global off)
      - Account Create/Edit modals: web search emulation toggle for API Key
        type with Toggle component
      - Full i18n coverage (zh + en)
      1b53ffca
    • erio's avatar
      fix: gofmt formatting · 37c23ecc
      erio authored
      37c23ecc
    • erio's avatar
      feat(channel): improve cache strategy and add restriction logging · e3748741
      erio authored
      - Change channel cache TTL from 60s to 10min (reduce unnecessary DB queries)
      - Actively rebuild cache after CRUD instead of lazy invalidation
      - Add slog.Warn logging for channel pricing restriction blocks (4 places)
      e3748741
    • erio's avatar
      fix: address review findings for channel restriction refactoring · 160903fc
      erio authored
      - Fix 7 stale comments still mentioning "限制检查" in handlers/services
      - Make billingModelForRestriction explicitly list channel_mapped case
      - Add slog.Warn for error swallowing in ResolveChannelMapping and
        needsUpstreamChannelRestrictionCheck
      - Document sticky session upstream check exemption
      160903fc
    • erio's avatar
      refactor: move channel model restriction from handler to scheduling phase · 2dce4306
      erio authored
      Move the model pricing restriction check from 8 handler entry points
      to the account scheduling phase (SelectAccountForModelWithExclusions /
      SelectAccountWithLoadAwareness), aligning restriction with billing:
      
      - requested: check original request model against pricing list
      - channel_mapped: check channel-mapped model against pricing list
      - upstream: per-account check using account-mapped model
      
      Handler layer now only resolves channel mapping (no restriction).
      Scheduling layer performs pre-check for requested/channel_mapped,
      and per-account filtering for upstream billing source.
      2dce4306
  3. 08 Apr, 2026 10 commits
    • ius's avatar
      265687b5
    • shaw's avatar
      feat: sync billing header cc_version with User-Agent and add opt-in CCH signing · 8e69957b
      shaw authored and 陈曦's avatar 陈曦 committed
      - Sync cc_version in x-anthropic-billing-header with the fingerprint
        User-Agent version, preserving the message-derived suffix
      - Implement xxHash64-based CCH signing to replace the cch=00000
        placeholder with a computed hash
      - Add admin toggle (enable_cch_signing) under gateway forwarding settings,
        disabled by default
      8e69957b
    • shaw's avatar
      fix: 修复非CC客户端OAuth伪装被Anthropic检测为第三方应用的问题 · d845c3ce
      shaw authored and 陈曦's avatar 陈曦 committed
      commit f3aa54b7 的 rewriteSystemForNonClaudeCode 未能通过 Anthropic 第三方检测,
      根因是两个关键信号与真实 Claude Code 不一致:
      
      1. anthropic-beta 头缺少 claude-code-20250219:伪装路径主动将该 beta
         加入 drop set 并移除,但 Anthropic 依赖此 beta 识别 Claude Code 请求。
         修复:非 haiku 模型的伪装请求强制包含 claude-code beta。
      
      2. system 字段使用 string 格式而非 array+cache_control:真实 Claude Code
         始终以 [{type,text,cache_control:{type:"ephemeral"}}] 发送 system,
         string 格式成为第三方检测信号。
         修复:rewriteSystemForNonClaudeCode 改为注入 array 格式。
      
      附带调整:stripSystemCacheControl 按 system 是否被重写动态决定,
      重写时保留 CC prompt 的 cache_control,未重写时(haiku/已含CC前缀)
      保持原有剥离行为。
      d845c3ce
    • shaw's avatar
      feat: Beta策略支持按模型区分处理(模型白名单) · f1f9640c
      shaw authored and 陈曦's avatar 陈曦 committed
      f1f9640c
    • shaw's avatar
      fix: 非Claude Code客户端system prompt迁移至messages以绕过第三方应用检测 · b717956c
      shaw authored and 陈曦's avatar 陈曦 committed
      Anthropic近期引入基于system参数内容的第三方应用检测机制,原有的前置追加
      Claude Code提示词策略无法通过检测(后续内容仍为非Claude Code格式触发429)。
      
      新策略:对非Claude Code客户端的OAuth/SetupToken账号请求,将system字段
      完整替换为Claude Code标识提示词,原始system内容作为user/assistant消息对
      注入messages开头,模型仍接收完整指令。
      
      仅影响/v1/messages路径,chat_completions和responses路径保持原有逻辑不变。
      真正的Claude Code客户端请求完全不受影响(原样透传)。
      b717956c
    • shaw's avatar
      feat: sync billing header cc_version with User-Agent and add opt-in CCH signing · e51c9e50
      shaw authored
      - Sync cc_version in x-anthropic-billing-header with the fingerprint
        User-Agent version, preserving the message-derived suffix
      - Implement xxHash64-based CCH signing to replace the cch=00000
        placeholder with a computed hash
      - Add admin toggle (enable_cch_signing) under gateway forwarding settings,
        disabled by default
      e51c9e50
    • shaw's avatar
      fix: 修复非CC客户端OAuth伪装被Anthropic检测为第三方应用的问题 · 49c19a55
      shaw authored and 陈曦's avatar 陈曦 committed
      commit f3aa54b7 的 rewriteSystemForNonClaudeCode 未能通过 Anthropic 第三方检测,
      根因是两个关键信号与真实 Claude Code 不一致:
      
      1. anthropic-beta 头缺少 claude-code-20250219:伪装路径主动将该 beta
         加入 drop set 并移除,但 Anthropic 依赖此 beta 识别 Claude Code 请求。
         修复:非 haiku 模型的伪装请求强制包含 claude-code beta。
      
      2. system 字段使用 string 格式而非 array+cache_control:真实 Claude Code
         始终以 [{type,text,cache_control:{type:"ephemeral"}}] 发送 system,
         string 格式成为第三方检测信号。
         修复:rewriteSystemForNonClaudeCode 改为注入 array 格式。
      
      附带调整:stripSystemCacheControl 按 system 是否被重写动态决定,
      重写时保留 CC prompt 的 cache_control,未重写时(haiku/已含CC前缀)
      保持原有剥离行为。
      49c19a55
    • shaw's avatar
      fix: 修复非CC客户端OAuth伪装被Anthropic检测为第三方应用的问题 · 1c9a2128
      shaw authored
      commit f3aa54b7 的 rewriteSystemForNonClaudeCode 未能通过 Anthropic 第三方检测,
      根因是两个关键信号与真实 Claude Code 不一致:
      
      1. anthropic-beta 头缺少 claude-code-20250219:伪装路径主动将该 beta
         加入 drop set 并移除,但 Anthropic 依赖此 beta 识别 Claude Code 请求。
         修复:非 haiku 模型的伪装请求强制包含 claude-code beta。
      
      2. system 字段使用 string 格式而非 array+cache_control:真实 Claude Code
         始终以 [{type,text,cache_control:{type:"ephemeral"}}] 发送 system,
         string 格式成为第三方检测信号。
         修复:rewriteSystemForNonClaudeCode 改为注入 array 格式。
      
      附带调整:stripSystemCacheControl 按 system 是否被重写动态决定,
      重写时保留 CC prompt 的 cache_control,未重写时(haiku/已含CC前缀)
      保持原有剥离行为。
      1c9a2128
    • shaw's avatar
      feat: Beta策略支持按模型区分处理(模型白名单) · 248fe092
      shaw authored and 陈曦's avatar 陈曦 committed
      248fe092
    • shaw's avatar
      fix: 非Claude Code客户端system prompt迁移至messages以绕过第三方应用检测 · f568ec76
      shaw authored and 陈曦's avatar 陈曦 committed
      Anthropic近期引入基于system参数内容的第三方应用检测机制,原有的前置追加
      Claude Code提示词策略无法通过检测(后续内容仍为非Claude Code格式触发429)。
      
      新策略:对非Claude Code客户端的OAuth/SetupToken账号请求,将system字段
      完整替换为Claude Code标识提示词,原始system内容作为user/assistant消息对
      注入messages开头,模型仍接收完整指令。
      
      仅影响/v1/messages路径,chat_completions和responses路径保持原有逻辑不变。
      真正的Claude Code客户端请求完全不受影响(原样透传)。
      f568ec76
  4. 07 Apr, 2026 2 commits
    • shaw's avatar
      7c60ee3c
    • shaw's avatar
      fix: 非Claude Code客户端system prompt迁移至messages以绕过第三方应用检测 · f3aa54b7
      shaw authored
      Anthropic近期引入基于system参数内容的第三方应用检测机制,原有的前置追加
      Claude Code提示词策略无法通过检测(后续内容仍为非Claude Code格式触发429)。
      
      新策略:对非Claude Code客户端的OAuth/SetupToken账号请求,将system字段
      完整替换为Claude Code标识提示词,原始system内容作为user/assistant消息对
      注入messages开头,模型仍接收完整指令。
      
      仅影响/v1/messages路径,chat_completions和responses路径保持原有逻辑不变。
      真正的Claude Code客户端请求完全不受影响(原样透传)。
      f3aa54b7
  5. 05 Apr, 2026 3 commits
  6. 04 Apr, 2026 11 commits
    • erio's avatar
      refactor: unify interval filtering and eliminate redundant Resolve calls · e88b2890
      erio authored
      - applyRequestTierOverrides now uses filterValidIntervals consistently
        with applyTokenOverrides (per_request/image modes were not filtering)
      - CostInput accepts optional pre-resolved pricing via Resolved field,
        eliminating duplicate Resolver.Resolve() calls in gateway billing paths
      e88b2890
    • erio's avatar
      fix: resolve golangci-lint issues — remove unused constants and functions, fix gofmt · 1b5ae71d
      erio authored
      - Remove unused claudeMax*Tokens constants (Claude Max feature not included)
      - Remove unused UsageMapHook type, SetUsageMapHook method, and usageToMap function
      - Fix gofmt formatting in channel_service.go, openai_model_mapping_test.go,
        chatcompletions_to_responses.go
      1b5ae71d
    • erio's avatar
      fix: resolve cherry-pick compilation and test issues · e59fa863
      erio authored
      - Add int64(0) param to SelectAccountWithLoadAwareness callers (signature change from channel scheduling refactor)
      - Add UsageMapHook type and struct field to StreamingProcessor
      - Revert Claude Max cache billing code to upstream/main (not part of channel feature)
      - Revert credits overages logic to upstream/main (non-channel change)
      - Remove Instructions field reference (non-channel OpenAI feature)
      - Restore sora_client_handler_test.go from upstream + add channel service nil params
      e59fa863
    • erio's avatar
      feat(channel): improve cache strategy and add restriction logging · 58f758c8
      erio authored
      - Change channel cache TTL from 60s to 10min (reduce unnecessary DB queries)
      - Actively rebuild cache after CRUD instead of lazy invalidation
      - Add slog.Warn logging for channel pricing restriction blocks (4 places)
      58f758c8
    • erio's avatar
      fix: resolve 5 audit findings in channel/credits/scheduling · 71f61bbc
      erio authored
      P0-1: Credits degraded response retry + fail-open
      - Add isAntigravityDegradedResponse() to detect transient API failures
      - Retry up to 3 times with exponential backoff (500ms/1s/2s)
      - Invalidate singleflight cache between retries
      - Fail-open after exhausting retries instead of 5h circuit break
      
      P1-1: Fix channel restriction pre-check timing conflict
      - Swap checkClaudeCodeRestriction before checkChannelPricingRestriction
      - Ensures channel restriction is checked against final fallback groupID
      
      P1-2: Add interval pricing validation (frontend + backend)
      - Backend: ValidateIntervals() with boundary, price, overlap checks
      - Frontend: validateIntervals() with Chinese error messages
      - Rules: MinTokens>=0, MaxTokens>MinTokens, prices>=0, no overlap
      
      P2: Fix cross-platform same-model pricing/mapping override
      - Store cache keys using original platform instead of group platform
      - Lookup across matching platforms (antigravity→anthropic→gemini)
      - Prevents anthropic/gemini same-name models from overwriting each other
      71f61bbc
    • erio's avatar
      fix: address review findings for channel restriction refactoring · 1fca2bfa
      erio authored
      - Fix 7 stale comments still mentioning "限制检查" in handlers/services
      - Make billingModelForRestriction explicitly list channel_mapped case
      - Add slog.Warn for error swallowing in ResolveChannelMapping and
        needsUpstreamChannelRestrictionCheck
      - Document sticky session upstream check exemption
      1fca2bfa
    • erio's avatar
      refactor: move channel model restriction from handler to scheduling phase · ce41afb7
      erio authored
      Move the model pricing restriction check from 8 handler entry points
      to the account scheduling phase (SelectAccountForModelWithExclusions /
      SelectAccountWithLoadAwareness), aligning restriction with billing:
      
      - requested: check original request model against pricing list
      - channel_mapped: check channel-mapped model against pricing list
      - upstream: per-account check using account-mapped model
      
      Handler layer now only resolves channel mapping (no restriction).
      Scheduling layer performs pre-check for requested/channel_mapped,
      and per-account filtering for upstream billing source.
      ce41afb7
    • erio's avatar
      refactor: extract helpers to reduce duplication and function length in gateway billing · b4a42a64
      erio authored
      - Extract resolveChannelPricing to DRY the resolver pattern shared by calculateImageCost/calculateTokenCost
      - Remove unnecessary IIFE wrapper and pass accountRateMultiplier as parameter
      - Extract resolveBillingMode, resolveMediaType, optionalSubscriptionID to simplify buildRecordUsageLog (104→65 lines)
      - Extract shouldDeductAPIKeyQuota/shouldUpdateRateLimits/shouldUpdateAccountQuota methods on postUsageBillingParams to unify duplicated billing conditions
      b4a42a64
    • erio's avatar
      refactor: merge RecordUsage and RecordUsageWithLongContext into shared core · 58b26cb4
      erio authored
      - Extract recordUsageCore with recordUsageOpts for parameterized differences
      - RecordUsage (276 lines) → thin wrapper (~40 lines)
      - RecordUsageWithLongContext (251 lines) → thin wrapper (~20 lines)
      - Split billing logic into calculateSoraMediaCost, calculateImageCost,
        calculateTokenCost sub-functions
      - Extract buildRecordUsageLog for usage log construction
      - Net reduction: -79 lines, eliminated ~170 lines of duplication
      58b26cb4
    • erio's avatar
      refactor: replace magic strings with named constants · 0d241d52
      erio authored
      - PricingSourceChannel/LiteLLM/Fallback for resolver source
      - MediaTypeImage/Video/Prompt for result.MediaType
      - Reuse BillingModeToken/BillingModeImage for billing mode
      - Reuse BillingModelSourceChannelMapped/PlatformAnthropic in handler
      0d241d52
    • erio's avatar
      fix: billing mode display follows cost calculation result · f3ab3fe5
      erio authored
      Instead of hardcoding BillingMode="image" when ImageCount>0,
      let cost.BillingMode (set by CalculateCostUnified/CalculateImageCost)
      take priority. This ensures channel token pricing shows "token" mode.
      f3ab3fe5