1. 08 Apr, 2026 2 commits
    • 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检测为第三方应用的问题 · 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
  2. 07 Apr, 2026 6 commits
  3. 05 Apr, 2026 10 commits
    • erio's avatar
      refactor(channel): split long functions, extract shared validation, move... · 9151d34d
      erio authored
      refactor(channel): split long functions, extract shared validation, move billing validation to service
      
      - Split Update (98→25 lines), buildCache (54→20 lines), Create (51→25 lines)
        into focused sub-functions: applyUpdateInput, checkGroupConflicts,
        fetchChannelData, populateChannelCache, storeErrorCache, getOldGroupIDs,
        invalidateAuthCacheForGroups
      - Extract validateChannelConfig to eliminate duplicated validation calls
        between Create and Update
      - Move validatePricingBillingMode from handler to service layer for
        proper separation of concerns
      - Add error logging to IsModelRestricted (was silently swallowing errors)
      - Add 12 new tests: ToUsageFields, billing mode validation, antigravity
        wildcard mapping isolation, Create/Update mapping conflict integration
      9151d34d
    • erio's avatar
      fix: remove cross-platform pricing/mapping leakage for antigravity groups · c5688fef
      erio authored
      Antigravity groups were incorrectly matching pricing and model mapping
      entries from anthropic/gemini platform tabs. Each platform should be
      strictly isolated — antigravity groups only use antigravity-tagged pricing.
      c5688fef
    • erio's avatar
      fix: gofmt formatting · 19655a15
      erio authored
      19655a15
    • erio's avatar
      fix: use upstream versions of shared files and remove only Sora code · f345b0f5
      erio authored
      Restore gateway_service.go, setting_handler.go, routes/admin.go,
      dto/settings.go, group_repo.go, api_key_repo.go, wire_gen.go to
      upstream/main versions and surgically remove only Sora references.
      
      This preserves upstream-only features (RequireOauthOnly, RequirePrivacySet,
      GroupResolution, etc.) that were missing when using release branch versions.
      f345b0f5
    • shaw's avatar
      fix(billing): prevent channel_mapped override from reverting BillingModel when channel did not map · f585a15e
      shaw authored
      When a channel has no model mapping for the requested model, ChannelMappedModel
      equals OriginalModel (the user's arbitrary input). Combined with the default
      BillingModelSource="channel_mapped", this incorrectly overrides the BillingModel
      set by the OpenAI format conversion layer (e.g., gpt-5.4 from DefaultMappedModel)
      back to the unmapped original model (e.g., glm) which has no pricing — resulting
      in zero-cost billing.
      
      Add guard condition so the channel_mapped override only fires when the channel
      actually changed the model (ChannelMappedModel != OriginalModel).
      f585a15e
    • erio's avatar
      fix: gofmt formatting · a29f5a48
      erio authored
      a29f5a48
    • erio's avatar
    • erio's avatar
      fix: resolve CI failures — gofmt, unused functions, test parameter mismatches · 5bb8b2ad
      erio authored
      - gofmt: user.go, config_test.go, group_handler.go, smart_retry_test.go
      - Remove unused: mergeGroupIDs, resolveProxyURL, "time" import
      - Fix api_contract_test.go: remove extra Sora args from NewAdminService,
        NewSettingHandler, NewAccountHandler; remove Sora field expectations
      - Fix account_test_service_openai_test.go: restore test helpers
      5bb8b2ad
    • erio's avatar
      fix: resolve CI failures — gofmt, unused functions, missing test helpers · 93b42ccf
      erio authored
      - Run gofmt on user schema, config test, group handler
      - Remove unused mergeGroupIDs function
      - Restore shared test helpers (newJSONResponse, queuedHTTPUpstream)
        that were in deleted Sora test file
      93b42ccf
    • erio's avatar
      revert: completely remove all Sora functionality · 62e80c60
      erio authored
      62e80c60
  4. 04 Apr, 2026 22 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
      revert: remove antigravity credits precheck logic (not part of channel feature) · d4ff835b
      erio authored
      Restore account_usage_service.go, antigravity_gateway_service.go,
      antigravity_credits_overages.go and its test to upstream/main state.
      These credits balance precheck changes were accidentally included
      during cherry-pick of channel management commits.
      d4ff835b
    • erio's avatar
      refactor: remove resolveOpenAIUpstreamModel, use normalizeCodexModel directly · e27b0adb
      erio authored
      Eliminates unnecessary indirection layer. The wrapper function only
      called normalizeCodexModel with a special case for "gpt 5.3 codex spark"
      (space-separated variant) that is no longer needed.
      
      All call sites now use normalizeCodexModel directly.
      e27b0adb
    • 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: channel cache fail-close, group conflict check across pages, status toggle stale data · feb6999d
      erio authored
      - GetGroupPlatforms failure now stores error-TTL cache and returns error (fail-close)
      - Frontend group-to-channel conflict map loads all channels instead of current page only
      - Toggle channel status reloads list when active filter would hide the changed item
      feb6999d
    • 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
      test: add unit tests for channel pricing restriction in scheduling phase · 6d3ea64a
      erio authored
      20 test cases covering:
      - billingModelForRestriction: 4 cases (requested/channel_mapped/upstream/empty)
      - resolveAccountUpstreamModel: 3 cases (antigravity/unsupported/non-antigravity)
      - checkChannelPricingRestriction: 10 cases (nil guards, 3 billing sources,
        RestrictModels disabled, no channel)
      - isUpstreamModelRestrictedByChannel: 3 cases (restricted/allowed/unsupported)
      6d3ea64a
    • 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: extract computeTokenBreakdown to deduplicate billing logic · 3cd398b0
      erio authored
      - calculateTokenCost reduced from 80 to 15 lines
      - calculateCostInternal reduced from 91 to 15 lines
      - Shared logic in computeTokenBreakdown + computeCacheCreationCost
      - Unified rateMultiplier <= 0 protection in both paths
      3cd398b0
    • erio's avatar
      refactor: split buildCache into sub-functions, reduce nesting 5→2 · 6de1d0cb
      erio authored
      - Extract newEmptyChannelCache() factory to deduplicate map init
      - Extract expandPricingToCache() for model pricing expansion
      - Extract expandMappingToCache() for model mapping expansion
      - buildCache reduced from 110 to 50 lines
      6de1d0cb
    • 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
    • erio's avatar
      feat: channel token pricing takes priority over per-image billing · 38da737e
      erio authored
      When ImageCount > 0, check if channel has token pricing configured:
      - YES (source=channel, mode=token) → use token billing with image_output_tokens
      - NO → fall back to CalculateImageCost (original per-image billing)
      
      This allows channels to configure $/MTok pricing for image generation
      models while maintaining backward compatibility for setups without
      channel pricing.
      38da737e
    • erio's avatar
      fix: address audit findings - cache sync, validation, consistency · 9b213115
      erio authored
      - clearCreditsExhausted: sync Redis scheduler cache after DB update
      - Image billing mode UI: write to per_request_price instead of image_output_price
      - OpenAI RecordUsage: use BillingModelSourceRequested constant, add s.cfg nil guard
      - Fix i18n key path: admin.channels.perRequestPriceRequired → admin.channels.form.perRequestPriceRequired
      9b213115
    • erio's avatar
      test: add unit tests for channel platform matching, interval validation, credits check · 55343473
      erio authored
      - TestIsPlatformPricingMatch: 12 cases covering all platform combinations
      - TestMatchingPlatforms: 4 cases for platform expansion
      - TestGetChannelModelPricing_AntigravityCrossPlatform: antigravity sees anthropic pricing
      - TestGetChannelModelPricing_AnthropicCannotSeeAntigravityPricing: no reverse leakage
      - TestResolveChannelMapping_AntigravityCrossPlatform: antigravity uses anthropic mapping
      - TestFilterValidIntervals: 8 cases for empty interval filtering
      - TestHasEnoughCredits: 10 cases for credits balance threshold logic
      - Extract hasEnoughCredits() pure function for testability
      55343473
    • erio's avatar
      fix: validate empty intervals + antigravity platform pricing match · 2355029d
      erio authored
      - Backend: reject intervals with all-null price fields on save
      - Backend: filterValidIntervals skips empty intervals in pricing resolver
      - Frontend: red border + asterisk on empty interval rows
      - Backend: antigravity groups now match anthropic/gemini channel pricing
      2355029d
    • erio's avatar
      fix: antigravity groups now match anthropic/gemini channel pricing · 8d25335b
      erio authored
      Antigravity platform serves both Claude and Gemini models, but channel
      pricing/mapping is configured under Anthropic/Gemini tabs. The cache
      builder was using strict platform equality, causing antigravity groups
      to miss all channel pricing entries, resulting in $0 billing.
      
      Add isPlatformPricingMatch() to treat antigravity as superset of
      anthropic+gemini for pricing and mapping cache indexing.
      8d25335b