```
├── .dockerignore (200 tokens)
├── .gitattributes (omitted)
├── .github/
   ├── audit-exceptions.yml (300 tokens)
   ├── workflows/
      ├── backend-ci.yml (400 tokens)
      ├── cla.yml (500 tokens)
      ├── release.yml (2k tokens)
      ├── security-scan.yml (300 tokens)
├── .gitignore (400 tokens)
├── .goreleaser.simple.yaml (400 tokens)
├── .goreleaser.yaml (1400 tokens)
├── CLA.md (1400 tokens)
├── DEV_GUIDE.md (1300 tokens)
├── Dockerfile (1300 tokens)
├── Dockerfile.goreleaser (400 tokens)
├── LICENSE (omitted)
├── Makefile (200 tokens)
├── README.md (9.9k tokens)
├── README_CN.md (5.2k tokens)
├── README_JA.md (5.7k tokens)
├── assets/
   ├── logo.svg (400 tokens)
   ├── partners/
      ├── logos/
         ├── RoxyBrowser.png
         ├── aigocode.png
         ├── aimzoon.jpg
         ├── apikey-fun.png
         ├── bestproxy.png
         ├── bmoplus.jpg
         ├── cctk.jpg
         ├── claudeapi.jpg
         ├── code0.jpg
         ├── ctok.png
         ├── etok.png
         ├── fastaitoken.jpg
         ├── fennoai.jpg
         ├── haoai.png
         ├── haoai.svg (800 tokens)
         ├── lanox.jpg
         ├── nagora.png
         ├── novada.png
         ├── openmodel.jpg
         ├── pateway.png
         ├── pincc-logo.png
         ├── poixe.png
         ├── pptoken.png
         ├── proxy4free.png
         ├── qiniu.jpg
         ├── rapidproxy.jpg
         ├── runapi.png
         ├── silkapi.png
         ├── sui-xiang.jpg
         ├── veilx.png
├── backend/
   ├── .dockerignore
   ├── .golangci.yml (1000 tokens)
   ├── Dockerfile (100 tokens)
   ├── Makefile (100 tokens)
   ├── cmd/
      ├── cleanup-ingress-reject-logs/
         ├── README.md (200 tokens)
         ├── main.go (1200 tokens)
         ├── main_test.go (300 tokens)
      ├── jwtgen/
         ├── main.go (300 tokens)
      ├── profit-preview/
         ├── main.go (1500 tokens)
         ├── main_test.go (500 tokens)
      ├── server/
         ├── VERSION
         ├── main.go (1000 tokens)
         ├── wire.go (2k tokens)
         ├── wire_gen.go (8.4k tokens)
         ├── wire_gen_test.go (600 tokens)
   ├── ent/
      ├── account.go (4.7k tokens)
      ├── account/
         ├── account.go (4.2k tokens)
         ├── where.go (13.6k tokens)
      ├── account_create.go (17.7k tokens)
      ├── account_delete.go (500 tokens)
      ├── account_query.go (6.4k tokens)
      ├── account_update.go (14.6k tokens)
      ├── accountgroup.go (1200 tokens)
      ├── accountgroup/
         ├── accountgroup.go (900 tokens)
         ├── where.go (1500 tokens)
      ├── accountgroup_create.go (3.9k tokens)
      ├── accountgroup_delete.go (500 tokens)
      ├── accountgroup_query.go (3.8k tokens)
      ├── accountgroup_update.go (2.9k tokens)
      ├── announcement.go (1800 tokens)
      ├── announcement/
         ├── announcement.go (1300 tokens)
         ├── where.go (5.2k tokens)
      ├── announcement_create.go (7.5k tokens)
      ├── announcement_delete.go (500 tokens)
      ├── announcement_query.go (3.8k tokens)
      ├── announcement_update.go (5.4k tokens)
      ├── announcementread.go (1300 tokens)
      ├── announcementread/
         ├── announcementread.go (900 tokens)
         ├── where.go (1900 tokens)
      ├── announcementread_create.go (4.2k tokens)
      ├── announcementread_delete.go (500 tokens)
      ├── announcementread_query.go (4.4k tokens)
      ├── announcementread_update.go (2.9k tokens)
      ├── apikey.go (3.2k tokens)
      ├── apikey/
         ├── apikey.go (2.6k tokens)
         ├── where.go (8.6k tokens)
      ├── apikey_create.go (12.7k tokens)
      ├── apikey_delete.go (400 tokens)
      ├── apikey_query.go (4.6k tokens)
      ├── apikey_update.go (9.6k tokens)
      ├── authidentity.go (2000 tokens)
      ├── authidentity/
         ├── authidentity.go (1700 tokens)
         ├── where.go (4.6k tokens)
      ├── authidentity_create.go (6.5k tokens)
      ├── authidentity_delete.go (500 tokens)
      ├── authidentity_query.go (5k tokens)
      ├── authidentity_update.go (6k tokens)
      ├── authidentitychannel.go (1800 tokens)
      ├── authidentitychannel/
         ├── authidentitychannel.go (1200 tokens)
         ├── where.go (4.7k tokens)
      ├── authidentitychannel_create.go (6.4k tokens)
      ├── authidentitychannel_delete.go (500 tokens)
      ├── authidentitychannel_query.go (4k tokens)
      ├── authidentitychannel_update.go (4k tokens)
      ├── batchimageevent.go (1100 tokens)
      ├── batchimageevent/
         ├── batchimageevent.go (600 tokens)
         ├── where.go (2.7k tokens)
      ├── batchimageevent_create.go (4.5k tokens)
      ├── batchimageevent_delete.go (500 tokens)
      ├── batchimageevent_query.go (3.4k tokens)
      ├── batchimageevent_update.go (2.4k tokens)
      ├── batchimageitem.go (2.4k tokens)
      ├── batchimageitem/
         ├── batchimageitem.go (1600 tokens)
         ├── where.go (10k tokens)
      ├── batchimageitem_create.go (11.5k tokens)
      ├── batchimageitem_delete.go (500 tokens)
      ├── batchimageitem_query.go (3.4k tokens)
      ├── batchimageitem_update.go (7.7k tokens)
      ├── batchimagejob.go (4.8k tokens)
      ├── batchimagejob/
         ├── batchimagejob.go (3.6k tokens)
         ├── where.go (20.8k tokens)
      ├── batchimagejob_create.go (23k tokens)
      ├── batchimagejob_delete.go (500 tokens)
      ├── batchimagejob_query.go (3.3k tokens)
      ├── batchimagejob_update.go (15.3k tokens)
      ├── channelmonitor.go (3.1k tokens)
      ├── channelmonitor/
         ├── channelmonitor.go (2.7k tokens)
         ├── where.go (8.1k tokens)
      ├── channelmonitor_create.go (11.8k tokens)
      ├── channelmonitor_delete.go (500 tokens)
      ├── channelmonitor_query.go (5.1k tokens)
      ├── channelmonitor_update.go (9.8k tokens)
      ├── channelmonitordailyrollup.go (2.4k tokens)
      ├── channelmonitordailyrollup/
         ├── channelmonitordailyrollup.go (1700 tokens)
         ├── where.go (6.5k tokens)
      ├── channelmonitordailyrollup_create.go (11.1k tokens)
      ├── channelmonitordailyrollup_delete.go (500 tokens)
      ├── channelmonitordailyrollup_query.go (4.2k tokens)
      ├── channelmonitordailyrollup_update.go (6.8k tokens)
      ├── channelmonitorhistory.go (1600 tokens)
      ├── channelmonitorhistory/
         ├── channelmonitorhistory.go (1100 tokens)
         ├── where.go (3.6k tokens)
      ├── channelmonitorhistory_create.go (6.4k tokens)
      ├── channelmonitorhistory_delete.go (500 tokens)
      ├── channelmonitorhistory_query.go (4.1k tokens)
      ├── channelmonitorhistory_update.go (4.3k tokens)
      ├── channelmonitorrequesttemplate.go (1900 tokens)
      ├── channelmonitorrequesttemplate/
         ├── channelmonitorrequesttemplate.go (1400 tokens)
         ├── where.go (4.5k tokens)
      ├── channelmonitorrequesttemplate_create.go (7.5k tokens)
      ├── channelmonitorrequesttemplate_delete.go (600 tokens)
      ├── channelmonitorrequesttemplate_query.go (4.4k tokens)
      ├── channelmonitorrequesttemplate_update.go (5.1k tokens)
      ├── client.go (56.6k tokens)
      ├── compositemodelroute.go (2k tokens)
      ├── compositemodelroute/
         ├── compositemodelroute.go (1600 tokens)
         ├── where.go (6.3k tokens)
      ├── compositemodelroute_create.go (8.3k tokens)
      ├── compositemodelroute_delete.go (500 tokens)
      ├── compositemodelroute_query.go (4k tokens)
      ├── compositemodelroute_update.go (5.1k tokens)
      ├── driver_access.go
      ├── ent.go (4.1k tokens)
      ├── enttest/
         ├── enttest.go (400 tokens)
      ├── errorpassthroughrule.go (2.2k tokens)
      ├── errorpassthroughrule/
         ├── errorpassthroughrule.go (1300 tokens)
         ├── where.go (5.5k tokens)
      ├── errorpassthroughrule_create.go (9.9k tokens)
      ├── errorpassthroughrule_delete.go (500 tokens)
      ├── errorpassthroughrule_query.go (3.5k tokens)
      ├── errorpassthroughrule_update.go (5.8k tokens)
      ├── generate.go (100 tokens)
      ├── group.go (7.5k tokens)
      ├── group/
         ├── group.go (7.6k tokens)
         ├── where.go (20k tokens)
      ├── group_create.go (31.2k tokens)
      ├── group_delete.go (400 tokens)
      ├── group_query.go (7.7k tokens)
      ├── group_update.go (25.3k tokens)
      ├── hook/
         ├── hook.go (4.9k tokens)
      ├── idempotencyrecord.go (1700 tokens)
      ├── idempotencyrecord/
         ├── idempotencyrecord.go (1200 tokens)
         ├── where.go (6.4k tokens)
      ├── idempotencyrecord_create.go (7.7k tokens)
      ├── idempotencyrecord_delete.go (500 tokens)
      ├── idempotencyrecord_query.go (3.4k tokens)
      ├── idempotencyrecord_update.go (4.6k tokens)
      ├── identityadoptiondecision.go (1800 tokens)
      ├── identityadoptiondecision/
         ├── identityadoptiondecision.go (1300 tokens)
         ├── where.go (2.9k tokens)
      ├── identityadoptiondecision_create.go (6k tokens)
      ├── identityadoptiondecision_delete.go (500 tokens)
      ├── identityadoptiondecision_query.go (4.8k tokens)
      ├── identityadoptiondecision_update.go (3.7k tokens)
      ├── intercept/
         ├── intercept.go (11.1k tokens)
      ├── migrate/
         ├── auth_identity_fk_ondelete_test.go (600 tokens)
         ├── migrate.go (500 tokens)
         ├── schema.go (21.4k tokens)
      ├── mutation.go (362.8k tokens)
      ├── paymentauditlog.go (1000 tokens)
      ├── paymentauditlog/
         ├── paymentauditlog.go (600 tokens)
         ├── where.go (3k tokens)
      ├── paymentauditlog_create.go (4.3k tokens)
      ├── paymentauditlog_delete.go (500 tokens)
      ├── paymentauditlog_query.go (3.4k tokens)
      ├── paymentauditlog_update.go (2.3k tokens)
      ├── paymentorder.go (4.9k tokens)
      ├── paymentorder/
         ├── paymentorder.go (3.5k tokens)
         ├── where.go (20k tokens)
      ├── paymentorder_create.go (21.1k tokens)
      ├── paymentorder_delete.go (500 tokens)
      ├── paymentorder_query.go (3.8k tokens)
      ├── paymentorder_update.go (14.2k tokens)
      ├── paymentproviderinstance.go (1800 tokens)
      ├── paymentproviderinstance/
         ├── paymentproviderinstance.go (1300 tokens)
         ├── where.go (5.8k tokens)
      ├── paymentproviderinstance_create.go (8.3k tokens)
      ├── paymentproviderinstance_delete.go (500 tokens)
      ├── paymentproviderinstance_query.go (3.6k tokens)
      ├── paymentproviderinstance_update.go (4.3k tokens)
      ├── pendingauthsession.go (3.4k tokens)
      ├── pendingauthsession/
         ├── pendingauthsession.go (2.5k tokens)
         ├── where.go (11.3k tokens)
      ├── pendingauthsession_create.go (13.2k tokens)
      ├── pendingauthsession_delete.go (500 tokens)
      ├── pendingauthsession_query.go (4.6k tokens)
      ├── pendingauthsession_update.go (8.4k tokens)
      ├── predicate/
         ├── predicate.go (900 tokens)
      ├── promocode.go (1500 tokens)
      ├── promocode/
         ├── promocode.go (1200 tokens)
         ├── where.go (4.2k tokens)
      ├── promocode_create.go (6.3k tokens)
      ├── promocode_delete.go (500 tokens)
      ├── promocode_query.go (3.8k tokens)
      ├── promocode_update.go (4.5k tokens)
      ├── promocodeusage.go (1300 tokens)
      ├── promocodeusage/
         ├── promocodeusage.go (900 tokens)
         ├── where.go (1900 tokens)
      ├── promocodeusage_create.go (4.3k tokens)
      ├── promocodeusage_delete.go (500 tokens)
      ├── promocodeusage_query.go (4.3k tokens)
      ├── promocodeusage_update.go (3.2k tokens)
      ├── proxy.go (2.2k tokens)
      ├── proxy/
         ├── proxy.go (1900 tokens)
         ├── where.go (6.7k tokens)
      ├── proxy_create.go (8.3k tokens)
      ├── proxy_delete.go (400 tokens)
      ├── proxy_query.go (4.1k tokens)
      ├── proxy_update.go (6.4k tokens)
      ├── redeemcode.go (1900 tokens)
      ├── redeemcode/
         ├── redeemcode.go (1400 tokens)
         ├── where.go (5.1k tokens)
      ├── redeemcode_create.go (7.3k tokens)
      ├── redeemcode_delete.go (500 tokens)
      ├── redeemcode_query.go (4.2k tokens)
      ├── redeemcode_update.go (5k tokens)
      ├── runtime.go
      ├── runtime/
         ├── runtime.go (34k tokens)
      ├── schema/
         ├── account.go (1500 tokens)
         ├── account_group.go (300 tokens)
         ├── announcement.go (500 tokens)
         ├── announcement_read.go (300 tokens)
         ├── api_key.go (800 tokens)
         ├── auth_identity.go (500 tokens)
         ├── auth_identity_channel.go (400 tokens)
         ├── auth_identity_schema_test.go (900 tokens)
         ├── batch_image_event.go (200 tokens)
         ├── batch_image_item.go (400 tokens)
         ├── batch_image_job.go (900 tokens)
         ├── channel_monitor.go (700 tokens)
         ├── channel_monitor_daily_rollup.go (400 tokens)
         ├── channel_monitor_history.go (300 tokens)
         ├── channel_monitor_request_template.go (500 tokens)
         ├── composite_model_route.go (400 tokens)
         ├── error_passthrough_rule.go (600 tokens)
         ├── group.go (1900 tokens)
         ├── idempotency_record.go (200 tokens)
         ├── identity_adoption_decision.go (300 tokens)
         ├── mixins/
            ├── soft_delete.go (900 tokens)
            ├── time.go (100 tokens)
         ├── payment_audit_log.go (200 tokens)
         ├── payment_order.go (1000 tokens)
         ├── payment_provider_instance.go (400 tokens)
         ├── pending_auth_session.go (800 tokens)
         ├── promo_code.go (400 tokens)
         ├── promo_code_usage.go (300 tokens)
         ├── proxy.go (400 tokens)
         ├── redeem_code.go (400 tokens)
         ├── security_secret.go (200 tokens)
         ├── setting.go (200 tokens)
         ├── subscription_plan.go (400 tokens)
         ├── tls_fingerprint_profile.go (500 tokens)
         ├── usage_cleanup_task.go (300 tokens)
         ├── usage_log.go (1300 tokens)
         ├── user.go (800 tokens)
         ├── user_allowed_group.go (200 tokens)
         ├── user_attribute_definition.go (600 tokens)
         ├── user_attribute_value.go (400 tokens)
         ├── user_platform_quota.go (600 tokens)
         ├── user_subscription.go (600 tokens)
      ├── securitysecret.go (900 tokens)
      ├── securitysecret/
         ├── securitysecret.go (600 tokens)
         ├── where.go (2.2k tokens)
      ├── securitysecret_create.go (3.8k tokens)
      ├── securitysecret_delete.go (500 tokens)
      ├── securitysecret_query.go (3.4k tokens)
      ├── securitysecret_update.go (1900 tokens)
      ├── setting.go (800 tokens)
      ├── setting/
         ├── setting.go (400 tokens)
         ├── where.go (1700 tokens)
      ├── setting_create.go (3.3k tokens)
      ├── setting_delete.go (500 tokens)
      ├── setting_query.go (3.2k tokens)
      ├── setting_update.go (1700 tokens)
      ├── subscriptionplan.go (1900 tokens)
      ├── subscriptionplan/
         ├── subscriptionplan.go (1400 tokens)
         ├── where.go (6.7k tokens)
      ├── subscriptionplan_create.go (9k tokens)
      ├── subscriptionplan_delete.go (500 tokens)
      ├── subscriptionplan_query.go (3.4k tokens)
      ├── subscriptionplan_update.go (5.2k tokens)
      ├── tlsfingerprintprofile.go (2.3k tokens)
      ├── tlsfingerprintprofile/
         ├── tlsfingerprintprofile.go (900 tokens)
         ├── where.go (3.5k tokens)
      ├── tlsfingerprintprofile_create.go (9.4k tokens)
      ├── tlsfingerprintprofile_delete.go (500 tokens)
      ├── tlsfingerprintprofile_query.go (3.5k tokens)
      ├── tlsfingerprintprofile_update.go (6.2k tokens)
      ├── tx.go (2.9k tokens)
      ├── tx_context.go (100 tokens)
      ├── usagecleanuptask.go (1700 tokens)
      ├── usagecleanuptask/
         ├── usagecleanuptask.go (1000 tokens)
         ├── where.go (5k tokens)
      ├── usagecleanuptask_create.go (7.7k tokens)
      ├── usagecleanuptask_delete.go (500 tokens)
      ├── usagecleanuptask_query.go (3.4k tokens)
      ├── usagecleanuptask_update.go (4.5k tokens)
      ├── usagelog.go (6.3k tokens)
      ├── usagelog/
         ├── usagelog.go (5.4k tokens)
         ├── where.go (21.1k tokens)
      ├── usagelog_create.go (27.3k tokens)
      ├── usagelog_delete.go (500 tokens)
      ├── usagelog_query.go (5.6k tokens)
      ├── usagelog_update.go (20.1k tokens)
      ├── user.go (4.8k tokens)
      ├── user/
         ├── user.go (6.4k tokens)
         ├── where.go (12.9k tokens)
      ├── user_create.go (15.6k tokens)
      ├── user_delete.go (400 tokens)
      ├── user_query.go (10.7k tokens)
      ├── user_update.go (21.9k tokens)
      ├── userallowedgroup.go (1100 tokens)
      ├── userallowedgroup/
         ├── userallowedgroup.go (800 tokens)
         ├── where.go (1200 tokens)
      ├── userallowedgroup_create.go (3.5k tokens)
      ├── userallowedgroup_delete.go (500 tokens)
      ├── userallowedgroup_query.go (3.9k tokens)
      ├── userallowedgroup_update.go (2.6k tokens)
      ├── userattributedefinition.go (2.2k tokens)
      ├── userattributedefinition/
         ├── userattributedefinition.go (1600 tokens)
         ├── where.go (5.5k tokens)
      ├── userattributedefinition_create.go (8.8k tokens)
      ├── userattributedefinition_delete.go (500 tokens)
      ├── userattributedefinition_query.go (4.2k tokens)
      ├── userattributedefinition_update.go (5.8k tokens)
      ├── userattributevalue.go (1500 tokens)
      ├── userattributevalue/
         ├── userattributevalue.go (1000 tokens)
         ├── where.go (2.5k tokens)
      ├── userattributevalue_create.go (4.7k tokens)
      ├── userattributevalue_delete.go (500 tokens)
      ├── userattributevalue_query.go (4.5k tokens)
      ├── userattributevalue_update.go (3.2k tokens)
      ├── userplatformquota.go (2.4k tokens)
      ├── userplatformquota/
         ├── userplatformquota.go (1600 tokens)
         ├── where.go (6.9k tokens)
      ├── userplatformquota_create.go (10.4k tokens)
      ├── userplatformquota_delete.go (500 tokens)
      ├── userplatformquota_query.go (3.9k tokens)
      ├── userplatformquota_update.go (6.8k tokens)
      ├── usersubscription.go (3k tokens)
      ├── usersubscription/
         ├── usersubscription.go (2.5k tokens)
         ├── where.go (8k tokens)
      ├── usersubscription_create.go (11.4k tokens)
      ├── usersubscription_delete.go (500 tokens)
      ├── usersubscription_query.go (5.5k tokens)
      ├── usersubscription_update.go (8.8k tokens)
   ├── go.mod (2000 tokens)
   ├── go.sum (13.7k tokens)
   ├── internal/
      ├── config/
         ├── config.go (34.1k tokens)
         ├── config_test.go (17.3k tokens)
         ├── env_reachability_test.go (500 tokens)
         ├── image_storage_env_test.go (300 tokens)
         ├── validate_dingtalk.go (200 tokens)
         ├── validate_dingtalk_test.go (300 tokens)
         ├── webauthn_test.go (500 tokens)
         ├── wire.go
      ├── domain/
         ├── announcement.go (1100 tokens)
         ├── constants.go (1300 tokens)
         ├── constants_test.go (600 tokens)
         ├── models_list_config.go
         ├── openai_messages_dispatch.go (100 tokens)
         ├── reasoning_effort.go
      ├── handler/
         ├── admin/
            ├── account_codex_agent_identity_import_test.go (1400 tokens)
            ├── account_codex_import.go (8k tokens)
            ├── account_codex_import_test.go (7.3k tokens)
            ├── account_data.go (4.6k tokens)
            ├── account_data_handler_test.go (1800 tokens)
            ├── account_handler.go (18.4k tokens)
            ├── account_handler_available_models_test.go (2k tokens)
            ├── account_handler_batch_delete_test.go (1000 tokens)
            ├── account_handler_duplicate_test.go (2k tokens)
            ├── account_handler_grok_refresh_test.go (600 tokens)
            ├── account_handler_list_test.go (2000 tokens)
            ├── account_handler_long_context_billing_test.go (1200 tokens)
            ├── account_handler_mixed_channel_test.go (1900 tokens)
            ├── account_handler_passthrough_test.go (300 tokens)
            ├── account_handler_spark_shadow_test.go (200 tokens)
            ├── account_ollama_cloud_usage.go (800 tokens)
            ├── account_ollama_cloud_usage_test.go (2.3k tokens)
            ├── account_shadow_parent.go (300 tokens)
            ├── account_shadow_parent_test.go (300 tokens)
            ├── account_today_stats_cache.go (100 tokens)
            ├── account_upstream_billing_probe.go (700 tokens)
            ├── account_upstream_billing_probe_test.go (600 tokens)
            ├── admin_basic_handlers_test.go (3.1k tokens)
            ├── admin_helpers_test.go (1800 tokens)
            ├── admin_service_stub_test.go (5.4k tokens)
            ├── affiliate_handler.go (1900 tokens)
            ├── announcement_handler.go (1500 tokens)
            ├── announcement_handler_sort_test.go (900 tokens)
            ├── antigravity_oauth_handler.go (600 tokens)
            ├── apikey_handler.go (500 tokens)
            ├── apikey_handler_test.go (1600 tokens)
            ├── audit_log_handler.go (1000 tokens)
            ├── backup_handler.go (1300 tokens)
            ├── batch_update_credentials_test.go (1300 tokens)
            ├── channel_handler.go (4.2k tokens)
            ├── channel_handler_test.go (2.9k tokens)
            ├── channel_monitor_duplicate_test.go (1200 tokens)
            ├── channel_monitor_handler.go (3.5k tokens)
            ├── channel_monitor_template_handler.go (1700 tokens)
            ├── compliance_handler.go (300 tokens)
            ├── content_moderation_handler.go (1800 tokens)
            ├── dashboard_handler.go (4.7k tokens)
            ├── dashboard_handler_cache_test.go (800 tokens)
            ├── dashboard_handler_request_type_test.go (1800 tokens)
            ├── dashboard_handler_user_breakdown_test.go (1900 tokens)
            ├── dashboard_query_cache.go (1600 tokens)
            ├── dashboard_snapshot_v2_handler.go (1800 tokens)
            ├── data_management_handler.go (3.4k tokens)
            ├── data_management_handler_test.go (500 tokens)
            ├── error_passthrough_handler.go (1500 tokens)
            ├── gemini_oauth_handler.go (1000 tokens)
            ├── grok_import_probe.go (1000 tokens)
            ├── grok_import_probe_handler_test.go (800 tokens)
            ├── grok_import_probe_test.go (1300 tokens)
            ├── grok_oauth_handler.go (3.3k tokens)
            ├── grok_oauth_handler_test.go (2.6k tokens)
            ├── group_handler.go (6.9k tokens)
            ├── group_handler_duplicate_test.go (1200 tokens)
            ├── group_handler_reasoning_effort_test.go (200 tokens)
            ├── id_list_utils.go (100 tokens)
            ├── id_list_utils_test.go (300 tokens)
            ├── idempotency_helper.go (700 tokens)
            ├── idempotency_helper_test.go (1800 tokens)
            ├── openai_oauth_handler.go (4.4k tokens)
            ├── openai_oauth_handler_reset_quota_test.go (3.4k tokens)
            ├── openai_oauth_handler_spark_shadow_test.go (600 tokens)
            ├── ops_alerts_handler.go (3.4k tokens)
            ├── ops_auth_cache_health_handler.go (100 tokens)
            ├── ops_dashboard_handler.go (1900 tokens)
            ├── ops_handler.go (4.3k tokens)
            ├── ops_ingress_reject_handler.go (800 tokens)
            ├── ops_ingress_reject_handler_test.go (300 tokens)
            ├── ops_realtime_handler.go (1400 tokens)
            ├── ops_runtime_logging_handler_test.go (1000 tokens)
            ├── ops_settings_handler.go (1700 tokens)
            ├── ops_snapshot_v2_handler.go (800 tokens)
            ├── ops_system_log_handler.go (1100 tokens)
            ├── ops_system_log_handler_test.go (2.2k tokens)
            ├── ops_ws_handler.go (3.6k tokens)
            ├── payment_handler.go (3.3k tokens)
            ├── payment_handler_test.go (600 tokens)
            ├── promo_handler.go (1200 tokens)
            ├── proxy_data.go (2000 tokens)
            ├── proxy_data_handler_test.go (1500 tokens)
            ├── proxy_handler.go (2.3k tokens)
            ├── redeem_export_handler_test.go (300 tokens)
            ├── redeem_handler.go (2.8k tokens)
            ├── redeem_handler_test.go (1100 tokens)
            ├── scheduled_test_handler.go (900 tokens)
            ├── search_truncate_test.go (400 tokens)
            ├── setting_handler.go (6.9k tokens)
            ├── setting_handler_audit.go (6.5k tokens)
            ├── setting_handler_auth_source_defaults_test.go (4.2k tokens)
            ├── setting_handler_codex_whitelist_test.go (400 tokens)
            ├── setting_handler_dingtalk_test.go (2.5k tokens)
            ├── setting_handler_email.go (2.2k tokens)
            ├── setting_handler_partial_payload_test.go (1400 tokens)
            ├── setting_handler_platform_quota_test.go (1300 tokens)
            ├── setting_handler_runtime.go (3.1k tokens)
            ├── setting_handler_stepup_switch_test.go (1700 tokens)
            ├── setting_handler_update.go (26.5k tokens)
            ├── snapshot_cache.go (600 tokens)
            ├── snapshot_cache_test.go (900 tokens)
            ├── subscription_handler.go (2.2k tokens)
            ├── system_handler.go (1700 tokens)
            ├── system_handler_test.go (2.3k tokens)
            ├── tls_fingerprint_profile_handler.go (1400 tokens)
            ├── usage_cleanup_handler_test.go (3.2k tokens)
            ├── usage_handler.go (3.8k tokens)
            ├── usage_handler_request_type_test.go (1200 tokens)
            ├── usage_handler_search_users_test.go (300 tokens)
            ├── usage_handler_sort_test.go (200 tokens)
            ├── usage_query_cache.go (400 tokens)
            ├── usage_query_cache_test.go (200 tokens)
            ├── user_attribute_handler.go (2.3k tokens)
            ├── user_handler.go (5.9k tokens)
            ├── user_handler_activity_test.go (700 tokens)
            ├── user_handler_batch_limits_test.go (900 tokens)
            ├── user_handler_get_deleted_test.go (300 tokens)
            ├── user_handler_list_apikey_group_test.go (300 tokens)
            ├── user_handler_role_stepup_test.go (500 tokens)
            ├── user_platform_quota_admin_test.go (2.1k tokens)
            ├── user_platform_quotas_handler_test.go (800 tokens)
         ├── announcement_handler.go (400 tokens)
         ├── api_key_handler.go (1700 tokens)
         ├── auth_captcha_request_test.go (600 tokens)
         ├── auth_current_user_test.go (500 tokens)
         ├── auth_dingtalk_client.go (2.4k tokens)
         ├── auth_dingtalk_client_test.go (1000 tokens)
         ├── auth_dingtalk_oauth.go (7.7k tokens)
         ├── auth_dingtalk_oauth_test.go (3k tokens)
         ├── auth_email_oauth.go (4.5k tokens)
         ├── auth_email_oauth_test.go (3.7k tokens)
         ├── auth_handler.go (4.5k tokens)
         ├── auth_linuxdo_oauth.go (8k tokens)
         ├── auth_linuxdo_oauth_test.go (10.1k tokens)
         ├── auth_oauth_captcha_start.go (300 tokens)
         ├── auth_oauth_captcha_start_test.go (1400 tokens)
         ├── auth_oauth_logout_test.go (500 tokens)
         ├── auth_oauth_pending_flow.go (13.3k tokens)
         ├── auth_oauth_pending_flow_test.go (25.2k tokens)
         ├── auth_oauth_test_helpers_test.go (300 tokens)
         ├── auth_oidc_oauth.go (8k tokens)
         ├── auth_oidc_oauth_test.go (10.3k tokens)
         ├── auth_session_revocation_test.go (400 tokens)
         ├── auth_wechat_oauth.go (9.2k tokens)
         ├── auth_wechat_oauth_test.go (12k tokens)
         ├── available_channel_handler.go (2k tokens)
         ├── available_channel_handler_test.go (1800 tokens)
         ├── batch_image_handler.go (2000 tokens)
         ├── channel_monitor_user_handler.go (1200 tokens)
         ├── chat_completions_image_model_test.go (700 tokens)
         ├── composite_platform.go (700 tokens)
         ├── composite_platform_test.go (1200 tokens)
         ├── concurrency_error_response.go (200 tokens)
         ├── concurrency_error_response_test.go (400 tokens)
         ├── content_moderation_helper.go (900 tokens)
         ├── dto/
            ├── account_mapper_redact_test.go (700 tokens)
            ├── announcement.go (400 tokens)
            ├── api_key_mapper_last_used_test.go (200 tokens)
            ├── channel_monitor.go (100 tokens)
            ├── credentials_redact.go (200 tokens)
            ├── credentials_redact_test.go (600 tokens)
            ├── group_mapper_profit_visibility_test.go (400 tokens)
            ├── mappers.go (5.5k tokens)
            ├── mappers_deleted_user_test.go (100 tokens)
            ├── mappers_usage_test.go (1600 tokens)
            ├── notify_email_entry.go (200 tokens)
            ├── public_settings_injection_schema_test.go (500 tokens)
            ├── settings.go (6.9k tokens)
            ├── types.go (5.5k tokens)
            ├── user_mapper_activity_test.go (200 tokens)
         ├── endpoint.go (2.7k tokens)
         ├── endpoint_test.go (3.5k tokens)
         ├── failover_loop.go (1800 tokens)
         ├── failover_loop_profit_veto_test.go (900 tokens)
         ├── failover_loop_test.go (7k tokens)
         ├── gateway_handler.go (17.3k tokens)
         ├── gateway_handler_billing_error_test.go (1000 tokens)
         ├── gateway_handler_cancellation_test.go (700 tokens)
         ├── gateway_handler_chat_completions.go (3k tokens)
         ├── gateway_handler_error_fallback_test.go (1300 tokens)
         ├── gateway_handler_intercept_test.go (400 tokens)
         ├── gateway_handler_responses.go (2.9k tokens)
         ├── gateway_handler_stream_failover_test.go (1000 tokens)
         ├── gateway_handler_usage_test.go (300 tokens)
         ├── gateway_handler_warmup_intercept_unit_test.go (2.7k tokens)
         ├── gateway_helper.go (2.8k tokens)
         ├── gateway_helper_backoff_test.go (500 tokens)
         ├── gateway_helper_fastpath_test.go (1100 tokens)
         ├── gateway_helper_hotpath_test.go (3.5k tokens)
         ├── gateway_helper_test.go (800 tokens)
         ├── gateway_key_billing.go (800 tokens)
         ├── gateway_key_billing_test.go (2.1k tokens)
         ├── gateway_models_test.go (3.9k tokens)
         ├── gemini_cli_session_test.go (900 tokens)
         ├── gemini_v1beta_handler.go (5.6k tokens)
         ├── gemini_v1beta_handler_test.go (900 tokens)
         ├── grok_media.go (3.9k tokens)
         ├── grok_media_test.go (1200 tokens)
         ├── handler.go (600 tokens)
         ├── idempotency_helper.go (400 tokens)
         ├── idempotency_helper_test.go (1700 tokens)
         ├── image_concurrency_limiter.go (500 tokens)
         ├── image_concurrency_limiter_test.go (1500 tokens)
         ├── image_task_admin_toggle_test.go (1000 tokens)
         ├── image_task_handler.go (2.3k tokens)
         ├── image_task_handler_test.go (1100 tokens)
         ├── logging.go (100 tokens)
         ├── model_plaza_handler.go (1300 tokens)
         ├── model_plaza_handler_test.go (900 tokens)
         ├── no_account_error.go (1000 tokens)
         ├── no_account_error_test.go (1600 tokens)
         ├── openai_alpha_search.go (2.1k tokens)
         ├── openai_body_limit_failover_test.go (400 tokens)
         ├── openai_chat_completions.go (3.2k tokens)
         ├── openai_codex_models_handler.go (600 tokens)
         ├── openai_codex_models_handler_test.go (2.1k tokens)
         ├── openai_embeddings.go (2.1k tokens)
         ├── openai_gateway_compact_body_signal_test.go (1600 tokens)
         ├── openai_gateway_compact_log_test.go (1200 tokens)
         ├── openai_gateway_count_tokens.go (1600 tokens)
         ├── openai_gateway_credential_failover_loop_test.go (7.3k tokens)
         ├── openai_gateway_credential_failover_test.go (2000 tokens)
         ├── openai_gateway_cyber_test.go (1500 tokens)
         ├── openai_gateway_endpoint_normalization_test.go (300 tokens)
         ├── openai_gateway_first_output_timeout_test.go (300 tokens)
         ├── openai_gateway_handler.go (24.9k tokens)
         ├── openai_gateway_handler_test.go (20.3k tokens)
         ├── openai_gateway_reasoning_failover.go (400 tokens)
         ├── openai_gateway_reasoning_failover_test.go (1500 tokens)
         ├── openai_gateway_usage_context_test.go (300 tokens)
         ├── openai_grok_image_intent_gate_test.go (900 tokens)
         ├── openai_image_intent_hint_test.go (200 tokens)
         ├── openai_images.go (3.3k tokens)
         ├── openai_images_controls_test.go (300 tokens)
         ├── openai_images_failover_test.go (1200 tokens)
         ├── openai_live.go (1500 tokens)
         ├── openai_live_test.go (800 tokens)
         ├── openai_profit_slot_recheck_test.go (1100 tokens)
         ├── openai_profit_veto_budget_test.go (400 tokens)
         ├── openai_quota_platform_contract_test.go (300 tokens)
         ├── openai_responses_failover_cancel_test.go (1100 tokens)
         ├── openai_responses_image_intent_benchmark_test.go (400 tokens)
         ├── openai_stream_validation.go (100 tokens)
         ├── openai_stream_validation_test.go (800 tokens)
         ├── openai_ws_turn_pricing_test.go (200 tokens)
         ├── ops_capture_writer_nil_test.go (400 tokens)
         ├── ops_error_logger.go (10.2k tokens)
         ├── ops_error_logger_attribution_test.go (100 tokens)
         ├── ops_error_logger_cyber_test.go (100 tokens)
         ├── ops_error_logger_test.go (7.8k tokens)
         ├── ops_ingress_reject_capture_test.go (200 tokens)
         ├── ops_platform_test.go (100 tokens)
         ├── page_handler.go (1500 tokens)
         ├── page_handler_test.go (700 tokens)
         ├── passkey_handler.go (1600 tokens)
         ├── passkey_handler_test.go (1400 tokens)
         ├── payment_handler.go (4.9k tokens)
         ├── payment_handler_resume_test.go (2.4k tokens)
         ├── payment_webhook_handler.go (1500 tokens)
         ├── payment_webhook_handler_test.go (1600 tokens)
         ├── quotaview/
            ├── helpers.go (700 tokens)
            ├── helpers_test.go (900 tokens)
         ├── redeem_handler.go (400 tokens)
         ├── request_body_limit.go (200 tokens)
         ├── request_body_limit_test.go (200 tokens)
         ├── request_body_parse_log.go (400 tokens)
         ├── request_body_parse_log_test.go (600 tokens)
         ├── security_audit_errors.go (1300 tokens)
         ├── security_audit_errors_test.go (1400 tokens)
         ├── security_audit_helper.go (1300 tokens)
         ├── security_audit_helper_test.go (500 tokens)
         ├── security_audit_media_submit_test.go (1800 tokens)
         ├── security_audit_order_test.go (700 tokens)
         ├── setting_handler.go (1400 tokens)
         ├── setting_handler_public_test.go (1100 tokens)
         ├── stream_error_event.go (1000 tokens)
         ├── stream_error_event_test.go (2k tokens)
         ├── subscription_handler.go (1200 tokens)
         ├── totp_handler.go (1200 tokens)
         ├── usage_handler.go (4.1k tokens)
         ├── usage_handler_daily_test.go (1100 tokens)
         ├── usage_handler_request_type_test.go (2.5k tokens)
         ├── usage_handler_sort_test.go (200 tokens)
         ├── usage_record_submit_task_test.go (1000 tokens)
         ├── usage_record_task_fallback_test.go (500 tokens)
         ├── user_handler.go (3.9k tokens)
         ├── user_handler_test.go (5.7k tokens)
         ├── user_msg_queue_helper.go (1200 tokens)
         ├── user_platform_quotas_handler_test.go (1400 tokens)
         ├── wire.go (2.2k tokens)
      ├── integration/
         ├── e2e_gateway_test.go (4.2k tokens)
         ├── e2e_helpers_test.go (300 tokens)
         ├── e2e_user_flow_test.go (1400 tokens)
      ├── middleware/
         ├── rate_limiter.go (1000 tokens)
         ├── rate_limiter_integration_test.go (800 tokens)
         ├── rate_limiter_test.go (1400 tokens)
      ├── model/
         ├── error_passthrough_rule.go (600 tokens)
         ├── tls_fingerprint_profile.go (400 tokens)
      ├── payment/
         ├── amount.go
         ├── amount_test.go (1400 tokens)
         ├── crypto.go (700 tokens)
         ├── crypto_test.go (800 tokens)
         ├── currency.go (800 tokens)
         ├── fee.go (100 tokens)
         ├── fee_test.go (700 tokens)
         ├── load_balancer.go (2.9k tokens)
         ├── load_balancer_test.go (3.4k tokens)
         ├── provider/
            ├── airwallex.go (4.3k tokens)
            ├── airwallex_test.go (2.6k tokens)
            ├── alipay.go (2.5k tokens)
            ├── alipay_test.go (2.7k tokens)
            ├── easypay.go (3.3k tokens)
            ├── easypay_query_test.go (800 tokens)
            ├── easypay_refund_test.go (1900 tokens)
            ├── easypay_sign_test.go (800 tokens)
            ├── factory.go (100 tokens)
            ├── stripe.go (2.1k tokens)
            ├── stripe_test.go (400 tokens)
            ├── wxpay.go (3.7k tokens)
            ├── wxpay_test.go (4k tokens)
         ├── registry.go (400 tokens)
         ├── registry_test.go (1100 tokens)
         ├── types.go (1900 tokens)
         ├── wire.go (500 tokens)
         ├── wire_test.go (300 tokens)
      ├── pkg/
         ├── anthropicfp/
            ├── dateline.go (1800 tokens)
            ├── dateline_test.go (1700 tokens)
         ├── antigravity/
            ├── claude_types.go (2.1k tokens)
            ├── claude_types_test.go (200 tokens)
            ├── client.go (5.4k tokens)
            ├── client_test.go (10.9k tokens)
            ├── gemini_types.go (1500 tokens)
            ├── oauth.go (2.1k tokens)
            ├── oauth_test.go (3.7k tokens)
            ├── request_transformer.go (4.7k tokens)
            ├── request_transformer_test.go (3.4k tokens)
            ├── response_transformer.go (2k tokens)
            ├── response_transformer_test.go (500 tokens)
            ├── schema_cleaner.go (2.4k tokens)
            ├── stream_transformer.go (2.8k tokens)
         ├── apicompat/
            ├── anthropic_responses_test.go (12.4k tokens)
            ├── anthropic_to_responses.go (3.1k tokens)
            ├── anthropic_to_responses_response.go (3.9k tokens)
            ├── anthropic_to_responses_stream_test.go (1500 tokens)
            ├── chatcompletions_anthropic_bridge.go (6.3k tokens)
            ├── chatcompletions_anthropic_bridge_test.go (8.6k tokens)
            ├── chatcompletions_responses_bridge.go (12k tokens)
            ├── chatcompletions_responses_bridge_custom_tools_test.go (6.7k tokens)
            ├── chatcompletions_responses_bridge_test.go (900 tokens)
            ├── chatcompletions_responses_request_invariants_test.go (1500 tokens)
            ├── chatcompletions_responses_stream_lifecycle_test.go (2k tokens)
            ├── chatcompletions_responses_test.go (10.4k tokens)
            ├── chatcompletions_responses_tool_output_media_test.go (2.6k tokens)
            ├── chatcompletions_to_responses.go (2.6k tokens)
            ├── response_format.go (400 tokens)
            ├── responses_anthropic_cache_creation_test.go (600 tokens)
            ├── responses_client_tools.go (3.8k tokens)
            ├── responses_client_tools_test.go (2.2k tokens)
            ├── responses_namespace.go (1400 tokens)
            ├── responses_namespace_test.go (1400 tokens)
            ├── responses_stream_event_wire.go (1400 tokens)
            ├── responses_stream_event_wire_test.go (1400 tokens)
            ├── responses_to_anthropic.go (4.1k tokens)
            ├── responses_to_anthropic_cc_chain_test.go (900 tokens)
            ├── responses_to_anthropic_instructions_test.go (700 tokens)
            ├── responses_to_anthropic_invalid_blocks_test.go (1400 tokens)
            ├── responses_to_anthropic_parallel_tool_test.go (2k tokens)
            ├── responses_to_anthropic_read_tool_test.go (800 tokens)
            ├── responses_to_anthropic_request.go (4.4k tokens)
            ├── responses_to_anthropic_tool_pairing_test.go (1500 tokens)
            ├── responses_to_anthropic_tools_test.go (900 tokens)
            ├── responses_to_chatcompletions.go (3.3k tokens)
            ├── responses_to_chatcompletions_codex_events_test.go (500 tokens)
            ├── streaming_stop_reason_test.go (900 tokens)
            ├── types.go (6.2k tokens)
         ├── claude/
            ├── constants.go (1500 tokens)
         ├── ctxkey/
            ├── ctxkey.go (600 tokens)
         ├── errors/
            ├── errors.go (800 tokens)
            ├── errors_test.go (800 tokens)
            ├── http.go (200 tokens)
            ├── types.go (800 tokens)
         ├── gemini/
            ├── models.go (500 tokens)
            ├── models_test.go (200 tokens)
         ├── geminicli/
            ├── codeassist_types.go (400 tokens)
            ├── constants.go (600 tokens)
            ├── drive_client.go (900 tokens)
            ├── drive_client_test.go (100 tokens)
            ├── models.go (300 tokens)
            ├── models_test.go (100 tokens)
            ├── oauth.go (1600 tokens)
            ├── oauth_test.go (4.6k tokens)
            ├── sanitize.go (200 tokens)
            ├── token_types.go (100 tokens)
         ├── googleapi/
            ├── error.go (600 tokens)
            ├── error_test.go (800 tokens)
            ├── status.go (100 tokens)
         ├── httpclient/
            ├── pool.go (1100 tokens)
            ├── pool_test.go (600 tokens)
         ├── httputil/
            ├── body.go (900 tokens)
            ├── body_lenient_json_test.go (1000 tokens)
            ├── body_test.go (800 tokens)
         ├── ip/
            ├── ip.go (1900 tokens)
            ├── ip_test.go (1700 tokens)
         ├── logger/
            ├── config_adapter.go (200 tokens)
            ├── logger.go (2.4k tokens)
            ├── logger_test.go (1000 tokens)
            ├── options.go (700 tokens)
            ├── options_test.go (600 tokens)
            ├── slog_handler.go (700 tokens)
            ├── slog_handler_test.go (400 tokens)
            ├── stdlog_bridge_test.go (900 tokens)
         ├── oauth/
            ├── oauth.go (1100 tokens)
            ├── oauth_test.go (300 tokens)
         ├── openai/
            ├── allowed_client.go (700 tokens)
            ├── allowed_client_freeform_test.go (200 tokens)
            ├── allowed_client_skip_test.go (200 tokens)
            ├── allowed_client_test.go (600 tokens)
            ├── allowed_client_whitelistable_test.go (300 tokens)
            ├── constants.go (800 tokens)
            ├── constants_test.go (100 tokens)
            ├── engine_fingerprint_signal.go (800 tokens)
            ├── engine_fingerprint_signal_test.go (800 tokens)
            ├── instructions.txt (1300 tokens)
            ├── instructions_gpt5_1.txt (4.8k tokens)
            ├── instructions_gpt5_2.txt (4.3k tokens)
            ├── instructions_gpt5_5.txt (4.3k tokens)
            ├── instructions_test.go (300 tokens)
            ├── oauth.go (2.4k tokens)
            ├── oauth_test.go (300 tokens)
            ├── request.go (2.4k tokens)
            ├── request_codex_version_test.go (400 tokens)
            ├── request_identity_test.go (600 tokens)
            ├── request_test.go (1600 tokens)
            ├── request_version_test.go (200 tokens)
         ├── openai_compat/
            ├── upstream_capability.go (800 tokens)
            ├── upstream_capability_test.go (800 tokens)
         ├── pagination/
            ├── pagination.go (300 tokens)
            ├── pagination_test.go (600 tokens)
         ├── proxyurl/
            ├── parse.go (300 tokens)
            ├── parse_test.go (1100 tokens)
         ├── proxyutil/
            ├── dialer.go (400 tokens)
            ├── dialer_test.go (1200 tokens)
            ├── dialer_timeout_test.go (300 tokens)
         ├── response/
            ├── response.go (900 tokens)
            ├── response_test.go (3.3k tokens)
         ├── servertiming/
            ├── collector.go (1800 tokens)
            ├── collector_test.go (900 tokens)
            ├── http.go (600 tokens)
            ├── http_test.go (1200 tokens)
         ├── sysutil/
            ├── restart.go (300 tokens)
         ├── timezone/
            ├── timezone.go (900 tokens)
            ├── timezone_test.go (900 tokens)
         ├── tlsfingerprint/
            ├── dialer.go (3.4k tokens)
            ├── dialer_capture_test.go (2.5k tokens)
            ├── dialer_integration_test.go (1500 tokens)
            ├── dialer_test.go (2.5k tokens)
            ├── test_types_test.go (200 tokens)
         ├── usagestats/
            ├── account_stats.go (100 tokens)
            ├── usage_log_types.go (3.1k tokens)
            ├── usage_log_types_test.go (300 tokens)
         ├── websearch/
            ├── brave.go (600 tokens)
            ├── brave_test.go (800 tokens)
            ├── helpers.go (100 tokens)
            ├── helpers_test.go (100 tokens)
            ├── manager.go (3.4k tokens)
            ├── manager_test.go (2.1k tokens)
            ├── provider.go (100 tokens)
            ├── tavily.go (600 tokens)
            ├── tavily_test.go (400 tokens)
            ├── types.go (100 tokens)
         ├── xai/
            ├── billing.go (2.5k tokens)
            ├── billing_test.go (1000 tokens)
            ├── models.go (500 tokens)
            ├── oauth.go (3.8k tokens)
            ├── oauth_test.go (2.5k tokens)
            ├── quota.go (1100 tokens)
            ├── quota_test.go (500 tokens)
            ├── sso_device.go (2.5k tokens)
            ├── sso_device_test.go (900 tokens)
      ├── platform/
         ├── liveattestation/
            ├── attestation.go (100 tokens)
            ├── attestation_darwin.go (1800 tokens)
            ├── attestation_unsupported.go (100 tokens)
            ├── attestation_unsupported_test.go (100 tokens)
      ├── repository/
         ├── account_repo.go (23.9k tokens)
         ├── account_repo_auto_pause_test.go (400 tokens)
         ├── account_repo_compact_extra_test.go (200 tokens)
         ├── account_repo_duplicate_integration_test.go (600 tokens)
         ├── account_repo_grok_billing_test.go (100 tokens)
         ├── account_repo_integration_test.go (12.9k tokens)
         ├── account_repo_model_availability_test.go (400 tokens)
         ├── account_repo_ollama_cloud_usage.go (3.6k tokens)
         ├── account_repo_ollama_cloud_usage_integration_test.go (5.9k tokens)
         ├── account_repo_ollama_cloud_usage_test.go (2.8k tokens)
         ├── account_repo_schedulable_projection_integration_test.go (900 tokens)
         ├── account_repo_schedulable_projection_test.go (600 tokens)
         ├── account_repo_sort_integration_test.go (900 tokens)
         ├── account_repo_spark_shadow_test.go (800 tokens)
         ├── account_repo_temp_unsched_test.go (3.2k tokens)
         ├── account_repo_test.go (800 tokens)
         ├── account_repo_upstream_billing_probe_cas_test.go (1700 tokens)
         ├── account_repo_upstream_billing_probe_due_integration_test.go (1600 tokens)
         ├── account_repo_upstream_billing_probe_due_test.go (400 tokens)
         ├── account_repo_upstream_billing_probe_test.go (100 tokens)
         ├── account_repo_upstream_billing_probe_update_test.go (4k tokens)
         ├── aes_encryptor.go (500 tokens)
         ├── aes_encryptor_test.go (1200 tokens)
         ├── affiliate_repo.go (7k tokens)
         ├── affiliate_repo_integration_test.go (3.2k tokens)
         ├── affiliate_repo_test.go (200 tokens)
         ├── aliyun_captcha_verifier.go (500 tokens)
         ├── aliyun_captcha_verifier_test.go (700 tokens)
         ├── allowed_groups_contract_integration_test.go (900 tokens)
         ├── announcement_read_repo.go (500 tokens)
         ├── announcement_repo.go (1300 tokens)
         ├── announcement_repo_sort_test.go (200 tokens)
         ├── api_key_cache.go (700 tokens)
         ├── api_key_cache_integration_test.go (700 tokens)
         ├── api_key_cache_subscriber_test.go (300 tokens)
         ├── api_key_cache_test.go (200 tokens)
         ├── api_key_repo.go (6.1k tokens)
         ├── api_key_repo_integration_test.go (3.9k tokens)
         ├── api_key_repo_last_used_unit_test.go (1700 tokens)
         ├── api_key_repo_lost_update_integration_test.go (700 tokens)
         ├── api_key_repo_messages_dispatch_unit_test.go (500 tokens)
         ├── api_key_repo_profit_projection_integration_test.go (500 tokens)
         ├── api_key_repo_sort_integration_test.go (300 tokens)
         ├── audit_log_repo.go (1900 tokens)
         ├── auth_cache_invalidation_outbox_integration_test.go (1000 tokens)
         ├── auth_cache_invalidation_outbox_repo.go (900 tokens)
         ├── auth_cache_invalidation_outbox_repo_test.go (1000 tokens)
         ├── auth_cache_invalidation_profit_integration_test.go (700 tokens)
         ├── auth_identity_compat_backfill_integration_test.go (500 tokens)
         ├── auth_identity_legacy_migration_integration_test.go (6.8k tokens)
         ├── backup_pg_dumper.go (500 tokens)
         ├── backup_s3_store.go (600 tokens)
         ├── batch_image_download_limiter.go (600 tokens)
         ├── batch_image_download_limiter_test.go (200 tokens)
         ├── batch_image_queue.go (2k tokens)
         ├── batch_image_queue_test.go (1400 tokens)
         ├── batch_image_repo.go (7.3k tokens)
         ├── batch_image_repo_integration_test.go (2.6k tokens)
         ├── billing_cache.go (4.1k tokens)
         ├── billing_cache_integration_test.go (2.5k tokens)
         ├── billing_cache_jitter_test.go (400 tokens)
         ├── billing_cache_test.go (400 tokens)
         ├── billing_cache_user_platform_quota_test.go (900 tokens)
         ├── channel_monitor_duplicate_test.go (200 tokens)
         ├── channel_monitor_repo.go (5.5k tokens)
         ├── channel_monitor_template_duplicate_metadata_integration_test.go (400 tokens)
         ├── channel_monitor_template_duplicate_metadata_unit_test.go (800 tokens)
         ├── channel_monitor_template_repo.go (1900 tokens)
         ├── channel_repo.go (3.3k tokens)
         ├── channel_repo_account_stats_pricing.go (1800 tokens)
         ├── channel_repo_pricing.go (2k tokens)
         ├── channel_repo_test.go (900 tokens)
         ├── claude_oauth_service.go (1700 tokens)
         ├── claude_oauth_service_test.go (2.4k tokens)
         ├── claude_usage_service.go (700 tokens)
         ├── claude_usage_service_test.go (800 tokens)
         ├── composite_model_route_repo.go (700 tokens)
         ├── concurrency_cache.go (8.2k tokens)
         ├── concurrency_cache_benchmark_test.go (600 tokens)
         ├── concurrency_cache_integration_test.go (6.9k tokens)
         ├── concurrency_cache_live_test.go (500 tokens)
         ├── content_moderation_hash_cache.go (400 tokens)
         ├── content_moderation_repo.go (1800 tokens)
         ├── content_moderation_repo_test.go (400 tokens)
         ├── dashboard_aggregation_repo.go (3.3k tokens)
         ├── dashboard_cache.go (300 tokens)
         ├── dashboard_cache_test.go (100 tokens)
         ├── db_pool.go (400 tokens)
         ├── db_pool_test.go (500 tokens)
         ├── decompress_response_test.go (1100 tokens)
         ├── email_cache.go (1100 tokens)
         ├── email_cache_integration_test.go (600 tokens)
         ├── email_cache_test.go (200 tokens)
         ├── ent.go (600 tokens)
         ├── error_passthrough_cache.go (600 tokens)
         ├── error_passthrough_repo.go (900 tokens)
         ├── error_translate.go (500 tokens)
         ├── fixtures_integration_test.go (2000 tokens)
         ├── gateway_cache.go (1500 tokens)
         ├── gateway_cache_integration_test.go (800 tokens)
         ├── gateway_cache_live_test.go (500 tokens)
         ├── gateway_routing_integration_test.go (1500 tokens)
         ├── gemini_drive_client.go (100 tokens)
         ├── gemini_oauth_client.go (800 tokens)
         ├── gemini_token_cache.go (300 tokens)
         ├── gemini_token_cache_integration_test.go (200 tokens)
         ├── gemini_token_cache_test.go (100 tokens)
         ├── geminicli_codeassist_client.go (1000 tokens)
         ├── github_release_service.go (1400 tokens)
         ├── github_release_service_test.go (3.2k tokens)
         ├── grok_oauth_client.go (1300 tokens)
         ├── grok_oauth_client_test.go (900 tokens)
         ├── group_repo.go (6.6k tokens)
         ├── group_repo_duplicate_integration_test.go (700 tokens)
         ├── group_repo_integration_test.go (7.2k tokens)
         ├── group_repo_sort_integration_test.go (1000 tokens)
         ├── http_upstream.go (9k tokens)
         ├── http_upstream_benchmark_test.go (300 tokens)
         ├── http_upstream_dial_timeout_test.go (500 tokens)
         ├── http_upstream_http2_keepalive_test.go (500 tokens)
         ├── http_upstream_test.go (7.3k tokens)
         ├── idempotency_repo.go (1100 tokens)
         ├── idempotency_repo_integration_test.go (900 tokens)
         ├── identity_cache.go (400 tokens)
         ├── identity_cache_integration_test.go (400 tokens)
         ├── identity_cache_test.go (200 tokens)
         ├── image_storage_s3.go (400 tokens)
         ├── image_task_store.go (200 tokens)
         ├── image_task_store_test.go (200 tokens)
         ├── inprocess_transport_test.go (300 tokens)
         ├── integration_harness_test.go (2.2k tokens)
         ├── internal500_counter_cache.go (300 tokens)
         ├── leader_lock_cache.go (300 tokens)
         ├── leader_lock_cache_test.go (500 tokens)
         ├── migrations_runner.go (4.2k tokens)
         ├── migrations_runner_checksum_test.go (1100 tokens)
         ├── migrations_runner_extra_test.go (2.7k tokens)
         ├── migrations_runner_notx_test.go (2.9k tokens)
         ├── migrations_schema_integration_test.go (3k tokens)
         ├── openai_403_counter_cache.go (300 tokens)
         ├── openai_long_context_billing_migration_integration_test.go (1100 tokens)
         ├── openai_oauth_service.go (900 tokens)
         ├── openai_oauth_service_test.go (2.2k tokens)
         ├── ops_error_where_test.go (1300 tokens)
         ├── ops_ingress_reject_repo.go (1000 tokens)
         ├── ops_ingress_reject_repo_test.go (200 tokens)
         ├── ops_repo.go (6.5k tokens)
         ├── ops_repo_alerts.go (3.7k tokens)
         ├── ops_repo_args_test.go (200 tokens)
         ├── ops_repo_dashboard.go (6.1k tokens)
         ├── ops_repo_dashboard_timeout_test.go (100 tokens)
         ├── ops_repo_error_where_test.go (300 tokens)
         ├── ops_repo_get_error_log_by_id_integration_test.go (400 tokens)
         ├── ops_repo_histograms.go (400 tokens)
         ├── ops_repo_latency_histogram_buckets.go (300 tokens)
         ├── ops_repo_latency_histogram_buckets_test.go (100 tokens)
         ├── ops_repo_metrics.go (1800 tokens)
         ├── ops_repo_openai_token_stats.go (800 tokens)
         ├── ops_repo_openai_token_stats_test.go (900 tokens)
         ├── ops_repo_preagg.go (2.8k tokens)
         ├── ops_repo_realtime_traffic.go (700 tokens)
         ├── ops_repo_replay_cleanup_test.go (200 tokens)
         ├── ops_repo_request_details.go (1500 tokens)
         ├── ops_repo_system_logs_test.go (600 tokens)
         ├── ops_repo_trends.go (3.4k tokens)
         ├── ops_repo_window_stats.go (200 tokens)
         ├── ops_write_pressure_integration_test.go (1200 tokens)
         ├── pagination.go (100 tokens)
         ├── passkey_repo.go (1300 tokens)
         ├── passkey_session_store.go (400 tokens)
         ├── pricing_service.go (600 tokens)
         ├── pricing_service_test.go (1000 tokens)
         ├── promo_code_repo.go (1600 tokens)
         ├── proxy_expiry_integration_test.go (1000 tokens)
         ├── proxy_expiry_test.go (100 tokens)
         ├── proxy_latency_cache.go (300 tokens)
         ├── proxy_probe_service.go (1100 tokens)
         ├── proxy_probe_service_test.go (1100 tokens)
         ├── proxy_repo.go (4.7k tokens)
         ├── proxy_repo_integration_test.go (2.2k tokens)
         ├── proxy_repo_sort_integration_test.go (600 tokens)
         ├── proxy_repo_upstream_billing_probe_test.go (1400 tokens)
         ├── redeem_cache.go (300 tokens)
         ├── redeem_cache_integration_test.go (700 tokens)
         ├── redeem_cache_test.go (300 tokens)
         ├── redeem_code_repo.go (2.3k tokens)
         ├── redeem_code_repo_integration_test.go (3.7k tokens)
         ├── redeem_code_repo_sort_integration_test.go (200 tokens)
         ├── redis.go (300 tokens)
         ├── redis_test.go (300 tokens)
         ├── refresh_token_cache.go (900 tokens)
         ├── req_client_pool.go (500 tokens)
         ├── req_client_pool_test.go (800 tokens)
         ├── rpm_cache.go (700 tokens)
         ├── s3_client.go (300 tokens)
         ├── scheduled_test_repo.go (1300 tokens)
         ├── scheduler_cache.go (6.5k tokens)
         ├── scheduler_cache_integration_test.go (1400 tokens)
         ├── scheduler_cache_last_used_unit_test.go (1300 tokens)
         ├── scheduler_cache_rate_multiplier_unit_test.go (500 tokens)
         ├── scheduler_cache_test.go (800 tokens)
         ├── scheduler_cache_unit_test.go (8.6k tokens)
         ├── scheduler_outbox_repo.go (1200 tokens)
         ├── scheduler_outbox_repo_test.go (1100 tokens)
         ├── scheduler_snapshot_outbox_integration_test.go (400 tokens)
         ├── security_secret_bootstrap.go (900 tokens)
         ├── security_secret_bootstrap_test.go (2.2k tokens)
         ├── server_timing_redis.go (200 tokens)
         ├── server_timing_redis_test.go (400 tokens)
         ├── server_timing_sql.go (1800 tokens)
         ├── server_timing_sql_test.go (1400 tokens)
         ├── session_limit_cache.go (2000 tokens)
         ├── setting_repo.go (500 tokens)
         ├── setting_repo_integration_test.go (1200 tokens)
         ├── simple_mode_admin_concurrency.go (300 tokens)
         ├── simple_mode_default_groups.go (600 tokens)
         ├── simple_mode_default_groups_integration_test.go (1200 tokens)
         ├── soft_delete_ent_integration_test.go (1500 tokens)
         ├── sql_scan.go (200 tokens)
         ├── temp_unsched_cache.go (500 tokens)
         ├── tencent_captcha_service.go (600 tokens)
         ├── tencent_captcha_service_test.go (500 tokens)
         ├── timeout_counter_cache.go (400 tokens)
         ├── tls_fingerprint_profile_cache.go (600 tokens)
         ├── tls_fingerprint_profile_repo.go (1100 tokens)
         ├── totp_cache.go (1000 tokens)
         ├── turnstile_service.go (300 tokens)
         ├── turnstile_service_test.go (900 tokens)
         ├── update_cache.go (100 tokens)
         ├── update_cache_integration_test.go (400 tokens)
         ├── upstream_billing_probe_persistence_integration_test.go (4.1k tokens)
         ├── usage_billing_repo.go (3.7k tokens)
         ├── usage_billing_repo_integration_test.go (2.5k tokens)
         ├── usage_billing_repo_unit_test.go (2.1k tokens)
         ├── usage_cleanup_repo.go (3.1k tokens)
         ├── usage_cleanup_repo_ent_test.go (1700 tokens)
         ├── usage_cleanup_repo_test.go (3.4k tokens)
         ├── usage_log_effective_platform_test.go (100 tokens)
         ├── usage_log_repo.go (1800 tokens)
         ├── usage_log_repo_breakdown_test.go (500 tokens)
         ├── usage_log_repo_dashboard.go (4.1k tokens)
         ├── usage_log_repo_deleted_user_integration_test.go (500 tokens)
         ├── usage_log_repo_insert.go (6.8k tokens)
         ├── usage_log_repo_integration_test.go (12.8k tokens)
         ├── usage_log_repo_query.go (4.8k tokens)
         ├── usage_log_repo_request_type_test.go (7.7k tokens)
         ├── usage_log_repo_sort_integration_test.go (400 tokens)
         ├── usage_log_repo_stats.go (7.8k tokens)
         ├── usage_log_repo_stats_integration_test.go (800 tokens)
         ├── usage_log_repo_trend.go (5.8k tokens)
         ├── usage_log_repo_unit_test.go (300 tokens)
         ├── usage_log_session_id_integration_test.go (400 tokens)
         ├── usage_log_session_id_unit_test.go (700 tokens)
         ├── user_attribute_repo.go (2.1k tokens)
         ├── user_group_rate_repo.go (2.5k tokens)
         ├── user_msg_queue_cache.go (1700 tokens)
         ├── user_msg_queue_cache_integration_test.go (1300 tokens)
         ├── user_platform_quota_adapter_test.go (600 tokens)
         ├── user_platform_quota_repo.go (3.8k tokens)
         ├── user_platform_quota_repo_integration_test.go (2.9k tokens)
         ├── user_platform_quota_repo_test.go (600 tokens)
         ├── user_platform_quota_service_adapter.go (2000 tokens)
         ├── user_platform_quota_upsert_test.go (1000 tokens)
         ├── user_profile_identity_repo.go (5.2k tokens)
         ├── user_profile_identity_repo_contract_test.go (3.7k tokens)
         ├── user_profile_identity_repo_unit_test.go (1300 tokens)
         ├── user_repo.go (8.3k tokens)
         ├── user_repo_apikey_group_filter_integration_test.go (1100 tokens)
         ├── user_repo_delete_atomicity_integration_test.go (800 tokens)
         ├── user_repo_email_alias_test.go (700 tokens)
         ├── user_repo_email_identity_integration_test.go (500 tokens)
         ├── user_repo_email_lookup_unit_test.go (1200 tokens)
         ├── user_repo_include_deleted_integration_test.go (400 tokens)
         ├── user_repo_integration_test.go (4.9k tokens)
         ├── user_repo_lost_update_integration_test.go (1300 tokens)
         ├── user_repo_redeem_adjustment_test.go (400 tokens)
         ├── user_repo_sort_integration_test.go (1300 tokens)
         ├── user_rpm_cache.go (600 tokens)
         ├── user_subscription_lock_test.go (300 tokens)
         ├── user_subscription_repo.go (4.6k tokens)
         ├── user_subscription_repo_integration_test.go (6.9k tokens)
         ├── wire.go (1300 tokens)
      ├── securityaudit/
         ├── coordinator.go (800 tokens)
         ├── coordinator_legacy.go (200 tokens)
         ├── coordinator_test.go (1500 tokens)
         ├── prompt_config.go (3.7k tokens)
         ├── prompt_config_integration_test.go (1800 tokens)
         ├── prompt_config_store.go (3.3k tokens)
         ├── prompt_config_test.go (4.5k tokens)
         ├── prompt_enqueue.go (700 tokens)
         ├── prompt_event_repository.go (2.7k tokens)
         ├── prompt_guard.go (2000 tokens)
         ├── prompt_guard_test.go (2.8k tokens)
         ├── prompt_handler.go (2.2k tokens)
         ├── prompt_handler_test.go (2.1k tokens)
         ├── prompt_issue_summary.go (400 tokens)
         ├── prompt_logging.go (1400 tokens)
         ├── prompt_logging_test.go (500 tokens)
         ├── prompt_metrics.go (700 tokens)
         ├── prompt_metrics_test.go (300 tokens)
         ├── prompt_module.go (100 tokens)
         ├── prompt_outbound_security.go (600 tokens)
         ├── prompt_outbound_security_test.go (2k tokens)
         ├── prompt_payload_store.go (300 tokens)
         ├── prompt_payload_store_integration_test.go (600 tokens)
         ├── prompt_qwen3guard.go (2.3k tokens)
         ├── prompt_qwen3guard_test.go (1500 tokens)
         ├── prompt_repository.go (3.5k tokens)
         ├── prompt_repository_integration_test.go (3.8k tokens)
         ├── prompt_repository_test.go (200 tokens)
         ├── prompt_scanner.go (700 tokens)
         ├── prompt_service.go (3.4k tokens)
         ├── prompt_service_test.go (1200 tokens)
         ├── prompt_snapshot.go (4.2k tokens)
         ├── prompt_snapshot_test.go (4k tokens)
         ├── prompt_types.go (1900 tokens)
         ├── prompt_worker.go (2.4k tokens)
         ├── prompt_worker_test.go (4.5k tokens)
      ├── server/
         ├── api_contract_test.go (21.3k tokens)
         ├── http.go (1200 tokens)
         ├── http_ingress_test.go (1000 tokens)
         ├── middleware/
            ├── admin_auth.go (1200 tokens)
            ├── admin_auth_test.go (1700 tokens)
            ├── admin_compliance.go (300 tokens)
            ├── admin_compliance_test.go (500 tokens)
            ├── admin_only.go (100 tokens)
            ├── api_key_auth.go (2.9k tokens)
            ├── api_key_auth_google.go (1800 tokens)
            ├── api_key_auth_google_test.go (6.4k tokens)
            ├── api_key_auth_image_task_test.go (100 tokens)
            ├── api_key_auth_test.go (11.3k tokens)
            ├── audit_log.go (2.4k tokens)
            ├── audit_log_test.go (1500 tokens)
            ├── auth_subject.go (100 tokens)
            ├── backend_mode_guard.go (600 tokens)
            ├── backend_mode_guard_test.go (2000 tokens)
            ├── client_request_id.go (300 tokens)
            ├── client_request_id_test.go (400 tokens)
            ├── cors.go (700 tokens)
            ├── cors_test.go (1800 tokens)
            ├── ingress_reject.go (1100 tokens)
            ├── ingress_reject_access_sampler.go (300 tokens)
            ├── ingress_reject_access_sampler_test.go (500 tokens)
            ├── ingress_reject_test.go (300 tokens)
            ├── invalid_auth_abuse_test.go (1100 tokens)
            ├── jwt_auth.go (600 tokens)
            ├── jwt_auth_test.go (1800 tokens)
            ├── logger.go (500 tokens)
            ├── middleware.go (800 tokens)
            ├── misc_coverage_test.go (600 tokens)
            ├── openai_fast_policy_forwarding_test.go (1200 tokens)
            ├── optional_jwt_auth.go (200 tokens)
            ├── optional_jwt_auth_test.go (600 tokens)
            ├── panel_rate_limit.go (900 tokens)
            ├── panel_rate_limit_test.go (2.2k tokens)
            ├── recovery.go (300 tokens)
            ├── recovery_test.go (500 tokens)
            ├── request_access_logger_test.go (1600 tokens)
            ├── request_body_limit.go (100 tokens)
            ├── request_logger.go (200 tokens)
            ├── request_metadata.go (200 tokens)
            ├── security_headers.go (1600 tokens)
            ├── security_headers_test.go (3.1k tokens)
            ├── server_timing.go (1100 tokens)
            ├── server_timing_test.go (1900 tokens)
            ├── session_binding.go (700 tokens)
            ├── session_binding_test.go (900 tokens)
            ├── step_up.go (900 tokens)
            ├── step_up_test.go (1100 tokens)
            ├── wire.go (100 tokens)
         ├── router.go (1000 tokens)
         ├── routes/
            ├── admin.go (6.8k tokens)
            ├── auth.go (2.4k tokens)
            ├── auth_rate_limit_integration_test.go (500 tokens)
            ├── auth_rate_limit_test.go (400 tokens)
            ├── common.go (100 tokens)
            ├── composite_platform_test.go (1600 tokens)
            ├── gateway.go (4.5k tokens)
            ├── gateway_codex_models_test.go (200 tokens)
            ├── gateway_key_billing_test.go (1100 tokens)
            ├── gateway_test.go (2.6k tokens)
            ├── model_plaza.go (200 tokens)
            ├── ops_ingress_reject_routes_test.go (400 tokens)
            ├── payment.go (800 tokens)
            ├── prompt_audit_route_coverage_test.go (1100 tokens)
            ├── user.go (900 tokens)
      ├── service/
         ├── account.go (14.8k tokens)
         ├── account_anthropic_passthrough_test.go (600 tokens)
         ├── account_base_url_test.go (2.1k tokens)
         ├── account_billing_rate_multiplier_test.go (100 tokens)
         ├── account_codex_cli_only_app_server_test.go (300 tokens)
         ├── account_credential_shadow_skip_test.go (600 tokens)
         ├── account_credentials_persistence.go (400 tokens)
         ├── account_credentials_redact.go (300 tokens)
         ├── account_credentials_redact_test.go (600 tokens)
         ├── account_expiry_service.go (300 tokens)
         ├── account_grok_media_eligibility_test.go (1900 tokens)
         ├── account_group.go
         ├── account_header_override.go (1900 tokens)
         ├── account_header_override_test.go (2.3k tokens)
         ├── account_intercept_warmup_test.go (300 tokens)
         ├── account_load_factor_test.go (300 tokens)
         ├── account_long_context_billing_test.go (2.1k tokens)
         ├── account_openai_compact_test.go (1700 tokens)
         ├── account_openai_passthrough_test.go (1900 tokens)
         ├── account_pool_mode_test.go (500 tokens)
         ├── account_pool_retry_status_codes_test.go (800 tokens)
         ├── account_quota_reset_test.go (3.9k tokens)
         ├── account_quota_schedulable_test.go (500 tokens)
         ├── account_repository_refresh_candidates_unit_test.go (200 tokens)
         ├── account_rpm_test.go (1200 tokens)
         ├── account_service.go (3.7k tokens)
         ├── account_service_delete_test.go (2000 tokens)
         ├── account_spark_shadow_test.go (100 tokens)
         ├── account_stats_pricing.go (1400 tokens)
         ├── account_stats_pricing_test.go (4.8k tokens)
         ├── account_test_service.go (13.4k tokens)
         ├── account_test_service_gemini_test.go (400 tokens)
         ├── account_test_service_grok_test.go (1500 tokens)
         ├── account_test_service_openai_compact_test.go (1500 tokens)
         ├── account_test_service_openai_image_test.go (700 tokens)
         ├── account_test_service_openai_test.go (4.3k tokens)
         ├── account_usage_service.go (11.9k tokens)
         ├── account_usage_service_fable_test.go (800 tokens)
         ├── account_usage_service_spark_shadow_test.go (1000 tokens)
         ├── account_usage_service_test.go (1700 tokens)
         ├── account_usage_session_window_test.go (800 tokens)
         ├── account_websearch_test.go (600 tokens)
         ├── account_wildcard_test.go (3.5k tokens)
         ├── admin_account.go (11.5k tokens)
         ├── admin_account_concurrency_test.go (100 tokens)
         ├── admin_account_upstream_billing_probe_test.go (5.2k tokens)
         ├── admin_balance_history_test.go (400 tokens)
         ├── admin_compliance.go (1100 tokens)
         ├── admin_compliance_test.go (900 tokens)
         ├── admin_group.go (8.4k tokens)
         ├── admin_group_duplicate.go (1700 tokens)
         ├── admin_group_duplicate_test.go (2.4k tokens)
         ├── admin_proxy.go (3.4k tokens)
         ├── admin_service.go (5.8k tokens)
         ├── admin_service_apikey_test.go (5k tokens)
         ├── admin_service_auth_identity_binding_test.go (1900 tokens)
         ├── admin_service_batch_limits_test.go (500 tokens)
         ├── admin_service_bulk_update_test.go (2.1k tokens)
         ├── admin_service_clear_error_test.go (500 tokens)
         ├── admin_service_composite_group_test.go (1300 tokens)
         ├── admin_service_create_user_test.go (900 tokens)
         ├── admin_service_credentials_merge_test.go (600 tokens)
         ├── admin_service_delete_test.go (5k tokens)
         ├── admin_service_duplicate_account_test.go (2.5k tokens)
         ├── admin_service_email_identity_sync_test.go (1300 tokens)
         ├── admin_service_get_deleted_test.go (100 tokens)
         ├── admin_service_group_rate_test.go (1500 tokens)
         ├── admin_service_group_test.go (11.3k tokens)
         ├── admin_service_list_users_test.go (1100 tokens)
         ├── admin_service_overages_test.go (1200 tokens)
         ├── admin_service_proxy_quality_test.go (800 tokens)
         ├── admin_service_role_test.go (1000 tokens)
         ├── admin_service_rpm_status_test.go (600 tokens)
         ├── admin_service_search_test.go (2k tokens)
         ├── admin_service_spark_shadow_test.go (7.6k tokens)
         ├── admin_service_update_balance_test.go (1600 tokens)
         ├── admin_service_update_user_rpm_test.go (400 tokens)
         ├── admin_user.go (8k tokens)
         ├── affiliate_service.go (4.4k tokens)
         ├── affiliate_service_test.go (900 tokens)
         ├── aliyun_captcha_service.go (1200 tokens)
         ├── aliyun_captcha_service_test.go (1600 tokens)
         ├── announcement.go (600 tokens)
         ├── announcement_service.go (2000 tokens)
         ├── announcement_service_test.go (400 tokens)
         ├── announcement_targeting_test.go (400 tokens)
         ├── anthropic_apikey_auth.go (200 tokens)
         ├── anthropic_session.go (400 tokens)
         ├── anthropic_session_test.go (1800 tokens)
         ├── antigravity_credits_overages.go (1600 tokens)
         ├── antigravity_credits_overages_test.go (3.8k tokens)
         ├── antigravity_default_test_stubs_test.go (300 tokens)
         ├── antigravity_gateway_claude.go (5.2k tokens)
         ├── antigravity_gateway_compat.go (3.7k tokens)
         ├── antigravity_gateway_compat_stream.go (2.9k tokens)
         ├── antigravity_gateway_compat_test.go (4.4k tokens)
         ├── antigravity_gateway_gemini.go (3.7k tokens)
         ├── antigravity_gateway_retry.go (9.5k tokens)
         ├── antigravity_gateway_service.go (4k tokens)
         ├── antigravity_gateway_service_test.go (12.3k tokens)
         ├── antigravity_gateway_streaming.go (7.6k tokens)
         ├── antigravity_gateway_upstream.go (2.4k tokens)
         ├── antigravity_image_test.go (1000 tokens)
         ├── antigravity_internal500_penalty.go (700 tokens)
         ├── antigravity_internal500_penalty_test.go (2.1k tokens)
         ├── antigravity_model_mapping_test.go (1700 tokens)
         ├── antigravity_oauth_service.go (2.7k tokens)
         ├── antigravity_oauth_service_test.go (300 tokens)
         ├── antigravity_privacy_service.go (400 tokens)
         ├── antigravity_privacy_service_test.go (400 tokens)
         ├── antigravity_quota_fetcher.go (1600 tokens)
         ├── antigravity_quota_fetcher_test.go (3.3k tokens)
         ├── antigravity_quota_scope.go (400 tokens)
         ├── antigravity_rate_limit_test.go (7.9k tokens)
         ├── antigravity_single_account_retry_test.go (6.3k tokens)
         ├── antigravity_smart_retry_test.go (9.9k tokens)
         ├── antigravity_subscription_service.go (200 tokens)
         ├── antigravity_subscription_test.go (400 tokens)
         ├── antigravity_thinking_test.go (400 tokens)
         ├── antigravity_token_provider.go (1600 tokens)
         ├── antigravity_token_provider_test.go (600 tokens)
         ├── antigravity_token_refresher.go (800 tokens)
         ├── api_key.go (800 tokens)
         ├── api_key_auth_cache.go (1400 tokens)
         ├── api_key_auth_cache_impl.go (3.7k tokens)
         ├── api_key_auth_cache_invalidate.go (200 tokens)
         ├── api_key_auth_cache_profit_test.go (600 tokens)
         ├── api_key_auth_cache_version_test.go (300 tokens)
         ├── api_key_rate_limit_test.go (1200 tokens)
         ├── api_key_service.go (7.1k tokens)
         ├── api_key_service_cache_test.go (4.2k tokens)
         ├── api_key_service_delete_test.go (3.4k tokens)
         ├── api_key_service_quota_test.go (1300 tokens)
         ├── api_key_service_touch_last_used_test.go (900 tokens)
         ├── api_key_service_update_fields_test.go (800 tokens)
         ├── audit_log.go (1500 tokens)
         ├── audit_log_service.go (1000 tokens)
         ├── audit_log_test.go (1200 tokens)
         ├── auth_cache_invalidation_outbox.go (1700 tokens)
         ├── auth_cache_invalidation_outbox_test.go (2.1k tokens)
         ├── auth_cache_invalidation_test.go (200 tokens)
         ├── auth_email_binding.go (1800 tokens)
         ├── auth_email_oauth_auto.go (1900 tokens)
         ├── auth_email_oauth_auto_test.go (400 tokens)
         ├── auth_oauth_email_flow.go (3k tokens)
         ├── auth_oauth_email_flow_test.go (2.3k tokens)
         ├── auth_oauth_first_bind.go (600 tokens)
         ├── auth_pending_identity_service.go (3.2k tokens)
         ├── auth_pending_identity_service_test.go (3.3k tokens)
         ├── auth_service.go (12.6k tokens)
         ├── auth_service_captcha_test.go (1500 tokens)
         ├── auth_service_email_bind_test.go (6.8k tokens)
         ├── auth_service_identity_sync_test.go (3.1k tokens)
         ├── auth_service_platform_quota_test.go (1300 tokens)
         ├── auth_service_register_test.go (6k tokens)
         ├── auth_service_test.go (100 tokens)
         ├── auth_service_turnstile_register_test.go (600 tokens)
         ├── backup_service.go (6.5k tokens)
         ├── backup_service_test.go (4.1k tokens)
         ├── balance_notify_check_test.go (2.8k tokens)
         ├── balance_notify_email_body_test.go (1000 tokens)
         ├── balance_notify_service.go (4.8k tokens)
         ├── balance_notify_service_test.go (2000 tokens)
         ├── batch_image.go (4k tokens)
         ├── batch_image_billing_hold.go (700 tokens)
         ├── batch_image_billing_recovery.go (600 tokens)
         ├── batch_image_billing_recovery_test.go (800 tokens)
         ├── batch_image_cleanup.go (1900 tokens)
         ├── batch_image_cleanup_test.go (1900 tokens)
         ├── batch_image_download.go (4k tokens)
         ├── batch_image_download_test.go (2.3k tokens)
         ├── batch_image_mvp_smoke_test.go (2k tokens)
         ├── batch_image_processor.go (4.3k tokens)
         ├── batch_image_processor_test.go (7.4k tokens)
         ├── batch_image_provider.go (1200 tokens)
         ├── batch_image_provider_gemini.go (4.6k tokens)
         ├── batch_image_provider_gemini_test.go (2.9k tokens)
         ├── batch_image_provider_vertex.go (6.6k tokens)
         ├── batch_image_provider_vertex_test.go (3.7k tokens)
         ├── batch_image_public.go (9.8k tokens)
         ├── batch_image_public_test.go (7.5k tokens)
         ├── batch_image_queue.go (500 tokens)
         ├── batch_image_settlement.go (2.8k tokens)
         ├── batch_image_settlement_test.go (4.4k tokens)
         ├── batch_image_test.go (600 tokens)
         ├── batch_image_worker.go (1600 tokens)
         ├── batch_image_worker_runtime.go (600 tokens)
         ├── batch_image_worker_runtime_redis_test.go (400 tokens)
         ├── batch_image_worker_runtime_test.go (500 tokens)
         ├── batch_image_worker_test.go (1000 tokens)
         ├── bedrock_request.go (5.2k tokens)
         ├── bedrock_request_test.go (9.9k tokens)
         ├── bedrock_signer.go (400 tokens)
         ├── bedrock_signer_test.go (200 tokens)
         ├── bedrock_stream.go (2.2k tokens)
         ├── bedrock_stream_test.go (2000 tokens)
         ├── billing_cache_port.go (100 tokens)
         ├── billing_cache_service.go (8.9k tokens)
         ├── billing_cache_service_balance_test.go (900 tokens)
         ├── billing_cache_service_rpm_test.go (1900 tokens)
         ├── billing_cache_service_singleflight_test.go (900 tokens)
         ├── billing_cache_service_test.go (900 tokens)
         ├── billing_cache_service_user_platform_quota_test.go (5.8k tokens)
         ├── billing_service.go (12.3k tokens)
         ├── billing_service_image_test.go (1100 tokens)
         ├── billing_service_rate_multiplier_test.go (300 tokens)
         ├── billing_service_test.go (12k tokens)
         ├── billing_service_unified_test.go (1700 tokens)
         ├── channel.go (3.5k tokens)
         ├── channel_available.go (1100 tokens)
         ├── channel_available_test.go (2.2k tokens)
         ├── channel_bedrock_cc_test.go (400 tokens)
         ├── channel_monitor_aggregator.go (1800 tokens)
         ├── channel_monitor_challenge.go (400 tokens)
         ├── channel_monitor_checker.go (4.1k tokens)
         ├── channel_monitor_checker_body_test.go (3.4k tokens)
         ├── channel_monitor_const.go (1300 tokens)
         ├── channel_monitor_duplicate_test.go (1700 tokens)
         ├── channel_monitor_runner.go (1700 tokens)
         ├── channel_monitor_runner_test.go (2.1k tokens)
         ├── channel_monitor_service.go (5k tokens)
         ├── channel_monitor_service_grok_test.go (500 tokens)
         ├── channel_monitor_ssrf.go (800 tokens)
         ├── channel_monitor_template_service.go (1900 tokens)
         ├── channel_monitor_template_types.go (700 tokens)
         ├── channel_monitor_types.go (1300 tokens)
         ├── channel_monitor_validate.go (800 tokens)
         ├── channel_plaza.go (1500 tokens)
         ├── channel_plaza_test.go (2.5k tokens)
         ├── channel_service.go (6.7k tokens)
         ├── channel_service_test.go (16.2k tokens)
         ├── channel_test.go (4.6k tokens)
         ├── channel_websearch_test.go (300 tokens)
         ├── claude_code_detection_test.go (1600 tokens)
         ├── claude_code_validator.go (2.1k tokens)
         ├── claude_code_validator_test.go (3.8k tokens)
         ├── claude_opus5_test.go (1100 tokens)
         ├── claude_token_provider.go (1100 tokens)
         ├── claude_token_provider_test.go (5.3k tokens)
         ├── codex_image_generation_bridge.go (700 tokens)
         ├── composite_model_route.go (900 tokens)
         ├── composite_platform.go (1200 tokens)
         ├── composite_platform_test.go (500 tokens)
         ├── composite_route_resolver.go (700 tokens)
         ├── composite_route_resolver_test.go (1700 tokens)
         ├── concurrency_service.go (4.9k tokens)
         ├── concurrency_service_test.go (4.2k tokens)
         ├── concurrency_slot_cleanup_test.go (100 tokens)
         ├── content_moderation.go (21.4k tokens)
         ├── content_moderation_cyber_test.go (2000 tokens)
         ├── content_moderation_email.go (1700 tokens)
         ├── content_moderation_input.go (2.1k tokens)
         ├── content_moderation_input_test.go (1100 tokens)
         ├── content_moderation_keyword_matcher.go (1200 tokens)
         ├── content_moderation_keyword_matcher_test.go (400 tokens)
         ├── content_moderation_proxy_test.go (2.2k tokens)
         ├── content_moderation_redact.go (300 tokens)
         ├── content_moderation_runtime_cache_test.go (2.7k tokens)
         ├── content_moderation_test.go (13.1k tokens)
         ├── credential_shadow.go (200 tokens)
         ├── credential_shadow_test.go (300 tokens)
         ├── crs_sync_helpers_test.go (1300 tokens)
         ├── crs_sync_long_context_billing_test.go (2k tokens)
         ├── crs_sync_service.go (9.9k tokens)
         ├── crs_sync_spark_shadow_test.go (600 tokens)
         ├── dashboard_aggregation_service.go (2.3k tokens)
         ├── dashboard_aggregation_service_test.go (1000 tokens)
         ├── dashboard_service.go (3.3k tokens)
         ├── dashboard_service_test.go (2.3k tokens)
         ├── data_management_grpc.go (1900 tokens)
         ├── data_management_grpc_test.go (200 tokens)
         ├── data_management_service.go (600 tokens)
         ├── data_management_service_test.go (200 tokens)
         ├── deferred_service.go (400 tokens)
         ├── digest_session_store.go (400 tokens)
         ├── digest_session_store_test.go (1800 tokens)
         ├── domain_constants.go (6.8k tokens)
         ├── domain_constants_test.go (200 tokens)
         ├── email_html_escape_test.go (400 tokens)
         ├── email_message.go (600 tokens)
         ├── email_message_test.go (800 tokens)
         ├── email_queue_service.go (800 tokens)
         ├── email_service.go (4.3k tokens)
         ├── email_service_smtp_test.go (2.1k tokens)
         ├── error_passthrough_runtime.go (300 tokens)
         ├── error_passthrough_runtime_test.go (2.8k tokens)
         ├── error_passthrough_service.go (2.1k tokens)
         ├── error_passthrough_service_test.go (5.6k tokens)
         ├── error_policy_integration_test.go (3.1k tokens)
         ├── error_policy_test.go (3.6k tokens)
         ├── force_cache_billing_test.go (700 tokens)
         ├── gateway_account_selection_test.go (1300 tokens)
         ├── gateway_anthropic_apikey_passthrough_benchmark_test.go (500 tokens)
         ├── gateway_anthropic_apikey_passthrough_test.go (12.9k tokens)
         ├── gateway_anthropic_passthrough.go (5.6k tokens)
         ├── gateway_anthropic_vertex_beta_filter_test.go (1300 tokens)
         ├── gateway_anthropic_vertex_service_account_test.go (1100 tokens)
         ├── gateway_bedrock.go (2.5k tokens)
         ├── gateway_beta_test.go (2.4k tokens)
         ├── gateway_billing_block.go (500 tokens)
         ├── gateway_billing_header.go (300 tokens)
         ├── gateway_billing_header_test.go (400 tokens)
         ├── gateway_body_order_test.go (1900 tokens)
         ├── gateway_cached_tokens_test.go (1600 tokens)
         ├── gateway_channel_restriction_fallback_test.go (700 tokens)
         ├── gateway_channel_restriction_test.go (2.1k tokens)
         ├── gateway_claude_oauth_body.go (7.6k tokens)
         ├── gateway_context_management_test.go (6.7k tokens)
         ├── gateway_count_tokens.go (4.1k tokens)
         ├── gateway_dateline_normalization_test.go (1200 tokens)
         ├── gateway_debug_env_test.go (100 tokens)
         ├── gateway_forward.go (7.5k tokens)
         ├── gateway_forward_as_chat_completions.go (3.4k tokens)
         ├── gateway_forward_as_chat_completions_test.go (1400 tokens)
         ├── gateway_forward_as_responses.go (4.1k tokens)
         ├── gateway_forward_as_responses_test.go (2.6k tokens)
         ├── gateway_forward_partial_usage_test.go (1700 tokens)
         ├── gateway_group_isolation_test.go (2.3k tokens)
         ├── gateway_hotpath_optimization_test.go (5k tokens)
         ├── gateway_messages_cache.go (900 tokens)
         ├── gateway_model_availability.go (800 tokens)
         ├── gateway_model_availability_test.go (1900 tokens)
         ├── gateway_multiplatform_test.go (21.6k tokens)
         ├── gateway_non_streaming_response_test.go (1800 tokens)
         ├── gateway_oauth_metadata_test.go (700 tokens)
         ├── gateway_profit_control.go (900 tokens)
         ├── gateway_profit_control_v2_test.go (3.4k tokens)
         ├── gateway_prompt_test.go (3.7k tokens)
         ├── gateway_record_usage_test.go (4.4k tokens)
         ├── gateway_request.go (9.1k tokens)
         ├── gateway_request_invalid_json_test.go (300 tokens)
         ├── gateway_request_pricing.go (400 tokens)
         ├── gateway_request_pricing_test.go (100 tokens)
         ├── gateway_request_test.go (10.7k tokens)
         ├── gateway_sanitize_test.go (100 tokens)
         ├── gateway_scheduling.go (17.4k tokens)
         ├── gateway_service.go (9.7k tokens)
         ├── gateway_service_antigravity_whitelist_test.go (1700 tokens)
         ├── gateway_service_bedrock_beta_test.go (1500 tokens)
         ├── gateway_service_bedrock_model_support_test.go (300 tokens)
         ├── gateway_service_benchmark_test.go (2.1k tokens)
         ├── gateway_service_selection_failure_stats_test.go (700 tokens)
         ├── gateway_service_streaming_test.go (1500 tokens)
         ├── gateway_service_subscription_billing_test.go (500 tokens)
         ├── gateway_soonest_reset_test.go (500 tokens)
         ├── gateway_streaming_test.go (3.9k tokens)
         ├── gateway_tool_rewrite.go (2000 tokens)
         ├── gateway_tool_rewrite_test.go (2.6k tokens)
         ├── gateway_upstream_request.go (5.9k tokens)
         ├── gateway_upstream_response.go (9.4k tokens)
         ├── gateway_usage_billing.go (8.4k tokens)
         ├── gateway_usage_billing_fallback_test.go (500 tokens)
         ├── gateway_waiting_queue_test.go (700 tokens)
         ├── gateway_websearch_block_filter.go (800 tokens)
         ├── gateway_websearch_block_filter_test.go (1300 tokens)
         ├── gateway_websearch_emulation.go (2.5k tokens)
         ├── gateway_websearch_emulation_test.go (2.9k tokens)
         ├── gemini_chat_completions_compat_service.go (5.8k tokens)
         ├── gemini_chat_completions_compat_service_test.go (1100 tokens)
         ├── gemini_error_policy_test.go (3.3k tokens)
         ├── gemini_messages_compat_service.go (22.2k tokens)
         ├── gemini_messages_compat_service_test.go (6.4k tokens)
         ├── gemini_multiplatform_test.go (6.6k tokens)
         ├── gemini_native_signature_cleaner.go (400 tokens)
         ├── gemini_native_signature_cleaner_test.go (400 tokens)
         ├── gemini_oauth.go (100 tokens)
         ├── gemini_oauth_service.go (8.1k tokens)
         ├── gemini_oauth_service_test.go (9.1k tokens)
         ├── gemini_quota.go (2.6k tokens)
         ├── gemini_session.go (600 tokens)
         ├── gemini_session_integration_test.go (1000 tokens)
         ├── gemini_session_test.go (2.2k tokens)
         ├── gemini_token_cache.go (100 tokens)
         ├── gemini_token_provider.go (1200 tokens)
         ├── gemini_token_refresher.go (300 tokens)
         ├── gemini_upstream_url.go (300 tokens)
         ├── gemini_upstream_url_test.go (400 tokens)
         ├── geminicli_codeassist.go (100 tokens)
         ├── generate_session_hash_test.go (6.6k tokens)
         ├── grok_credential_failure.go (5.5k tokens)
         ├── grok_credential_failure_test.go (13.4k tokens)
         ├── grok_media.go (7k tokens)
         ├── grok_media_content_test.go (2.9k tokens)
         ├── grok_oauth_reconciliation.go (2.4k tokens)
         ├── grok_oauth_reconciliation_test.go (4k tokens)
         ├── grok_oauth_service.go (2.2k tokens)
         ├── grok_oauth_service_test.go (700 tokens)
         ├── grok_quota_fetcher.go (1400 tokens)
         ├── grok_quota_fetcher_test.go (1900 tokens)
         ├── grok_quota_service.go (4.1k tokens)
         ├── grok_quota_service_test.go (8.4k tokens)
         ├── grok_token_provider.go (2.5k tokens)
         ├── grok_token_provider_test.go (3.5k tokens)
         ├── grok_token_refresher.go (400 tokens)
         ├── grok_upstream_errors.go (1300 tokens)
         ├── grok_upstream_errors_test.go (2.9k tokens)
         ├── grok_upstream_url.go (900 tokens)
         ├── grok_upstream_url_test.go (2.1k tokens)
         ├── group.go (2.5k tokens)
         ├── group_capacity_service.go (1800 tokens)
         ├── group_capacity_service_test.go (1000 tokens)
         ├── group_models_list.go (100 tokens)
         ├── group_peak_rate_test.go (1700 tokens)
         ├── group_profit_platform_test.go (200 tokens)
         ├── group_service.go (1700 tokens)
         ├── group_test.go (400 tokens)
         ├── header_util.go (1100 tokens)
         ├── http_upstream_port.go (100 tokens)
         ├── http_upstream_profile.go (300 tokens)
         ├── http_upstream_profile_test.go (200 tokens)
         ├── idempotency.go (3.5k tokens)
         ├── idempotency_cleanup_service.go (400 tokens)
         ├── idempotency_cleanup_service_test.go (400 tokens)
         ├── idempotency_observability.go (1200 tokens)
         ├── idempotency_test.go (5.7k tokens)
         ├── identity_service.go (2.7k tokens)
         ├── identity_service_order_test.go (600 tokens)
         ├── image_billing_multiplier.go (100 tokens)
         ├── image_billing_size.go (1200 tokens)
         ├── image_billing_size_test.go (600 tokens)
         ├── image_generation_intent.go (3.2k tokens)
         ├── image_generation_intent_benchmark_test.go (300 tokens)
         ├── image_generation_intent_explicit_test.go (400 tokens)
         ├── image_generation_intent_grok_test.go (800 tokens)
         ├── image_generation_intent_test.go (2.9k tokens)
         ├── image_output_accounting.go (1000 tokens)
         ├── image_output_accounting_test.go (1900 tokens)
         ├── image_storage.go (1600 tokens)
         ├── image_storage_settings.go (2.1k tokens)
         ├── image_storage_settings_test.go (2000 tokens)
         ├── image_storage_test.go (1900 tokens)
         ├── image_task.go (1800 tokens)
         ├── image_task_test.go (600 tokens)
         ├── internal500_counter.go (100 tokens)
         ├── invalid_auth_abuse_limiter.go (1400 tokens)
         ├── invalid_auth_abuse_limiter_test.go (600 tokens)
         ├── leader_lock.go (500 tokens)
         ├── leader_lock_test.go (1000 tokens)
         ├── media_price_config.go (200 tokens)
         ├── metadata_userid.go (600 tokens)
         ├── metadata_userid_test.go (1600 tokens)
         ├── model_not_found_error.go (500 tokens)
         ├── model_not_found_error_test.go (700 tokens)
         ├── model_pricing_resolver.go (2000 tokens)
         ├── model_pricing_resolver_test.go (5.9k tokens)
         ├── model_rate_limit.go (1100 tokens)
         ├── model_rate_limit_test.go (2.9k tokens)
         ├── notification_email_service.go (13.8k tokens)
         ├── notification_email_service_test.go (4.8k tokens)
         ├── notify_email_entry.go (500 tokens)
         ├── notify_email_entry_test.go (1000 tokens)
         ├── oauth_refresh_api.go (3.1k tokens)
         ├── oauth_refresh_api_test.go (7.6k tokens)
         ├── oauth_service.go (2.2k tokens)
         ├── oauth_service_test.go (3.6k tokens)
         ├── ollama_cloud_usage.go (8.7k tokens)
         ├── ollama_cloud_usage_parser.go (2.6k tokens)
         ├── ollama_cloud_usage_test.go (10k tokens)
         ├── openai_403_counter.go (100 tokens)
         ├── openai_account_model_transient.go (1100 tokens)
         ├── openai_account_model_transient_test.go (1300 tokens)
         ├── openai_account_runtime_block_fastpath.go (2.6k tokens)
         ├── openai_account_runtime_block_fastpath_test.go (3.2k tokens)
         ├── openai_account_runtime_transient_test.go (1000 tokens)
         ├── openai_account_scheduler.go (19.1k tokens)
         ├── openai_account_scheduler_benchmark_test.go (400 tokens)
         ├── openai_account_scheduler_compact_test.go (1500 tokens)
         ├── openai_account_scheduler_reset_test.go (1300 tokens)
         ├── openai_account_scheduler_spark_route_test.go (1600 tokens)
         ├── openai_account_scheduler_test.go (26.3k tokens)
         ├── openai_account_scheduler_upstream_cost_test.go (7.7k tokens)
         ├── openai_account_scheduler_ws_snapshot_test.go (400 tokens)
         ├── openai_agent_identity.go (3.5k tokens)
         ├── openai_agent_identity_compat_test.go (4.3k tokens)
         ├── openai_agent_identity_test.go (1600 tokens)
         ├── openai_alpha_search.go (4.6k tokens)
         ├── openai_alpha_search_billing_test.go (700 tokens)
         ├── openai_alpha_search_test.go (4.2k tokens)
         ├── openai_apikey_responses_probe.go (1600 tokens)
         ├── openai_apikey_responses_probe_test.go (900 tokens)
         ├── openai_capacity_shed_test.go (2000 tokens)
         ├── openai_channel_restriction_test.go (800 tokens)
         ├── openai_chatgpt_headers.go (200 tokens)
         ├── openai_chatgpt_headers_test.go (400 tokens)
         ├── openai_client_restriction_detector.go (1800 tokens)
         ├── openai_client_restriction_detector_hardening_test.go (1000 tokens)
         ├── openai_client_restriction_detector_test.go (3.4k tokens)
         ├── openai_client_transport.go (400 tokens)
         ├── openai_client_transport_test.go (600 tokens)
         ├── openai_codex_function_call_id_test.go (800 tokens)
         ├── openai_codex_identity.go (1400 tokens)
         ├── openai_codex_identity_test.go (2.2k tokens)
         ├── openai_codex_instructions_template.go (300 tokens)
         ├── openai_codex_message_item_id_test.go (1000 tokens)
         ├── openai_codex_models_service.go (5.3k tokens)
         ├── openai_codex_models_service_test.go (10.1k tokens)
         ├── openai_codex_pat_service.go (1300 tokens)
         ├── openai_codex_pat_service_test.go (900 tokens)
         ├── openai_codex_transform.go (8.9k tokens)
         ├── openai_codex_transform_additions_test.go (600 tokens)
         ├── openai_codex_transform_test.go (12k tokens)
         ├── openai_codex_version_consistency_test.go (100 tokens)
         ├── openai_codex_version_sync_service.go (1300 tokens)
         ├── openai_codex_version_sync_service_test.go (3.1k tokens)
         ├── openai_compact_body_signal.go (100 tokens)
         ├── openai_compact_body_signal_test.go (300 tokens)
         ├── openai_compact_model_mapping_test.go (1100 tokens)
         ├── openai_compact_probe.go (600 tokens)
         ├── openai_compact_probe_test.go (1000 tokens)
         ├── openai_compact_service_tier_test.go (500 tokens)
         ├── openai_compact_sse_keepalive.go (1400 tokens)
         ├── openai_compact_sse_keepalive_test.go (2.1k tokens)
         ├── openai_compact_stream_bridge.go (1300 tokens)
         ├── openai_compact_stream_bridge_test.go (4.8k tokens)
         ├── openai_compat_model.go (600 tokens)
         ├── openai_compat_model_test.go (19.1k tokens)
         ├── openai_compat_prompt_cache_key.go (1100 tokens)
         ├── openai_compat_prompt_cache_key_test.go (1000 tokens)
         ├── openai_content_session_seed.go (1900 tokens)
         ├── openai_content_session_seed_benchmark_test.go (200 tokens)
         ├── openai_content_session_seed_test.go (4.1k tokens)
         ├── openai_cursor_warmup_pipeline_test.go (1600 tokens)
         ├── openai_cyber_policy.go (600 tokens)
         ├── openai_cyber_policy_test.go (600 tokens)
         ├── openai_cyber_session_block.go (600 tokens)
         ├── openai_cyber_session_block_test.go (1400 tokens)
         ├── openai_embeddings.go (1600 tokens)
         ├── openai_embeddings_test.go (700 tokens)
         ├── openai_endpoint_url.go (400 tokens)
         ├── openai_endpoint_url_test.go (300 tokens)
         ├── openai_failover_cached_body_test.go (1100 tokens)
         ├── openai_fast_policy_test.go (3.2k tokens)
         ├── openai_fast_policy_ws_test.go (10.8k tokens)
         ├── openai_first_output_timeout.go (1800 tokens)
         ├── openai_first_output_timeout_test.go (5.2k tokens)
         ├── openai_gateway_403_reset_test.go (300 tokens)
         ├── openai_gateway_apikey_item_id_test.go (700 tokens)
         ├── openai_gateway_cc_pipeline.go (2.3k tokens)
         ├── openai_gateway_chat_completions.go (7.1k tokens)
         ├── openai_gateway_chat_completions_raw.go (3.3k tokens)
         ├── openai_gateway_chat_completions_raw_test.go (5.8k tokens)
         ├── openai_gateway_chat_completions_test.go (8.2k tokens)
         ├── openai_gateway_compat_cyber_test.go (1200 tokens)
         ├── openai_gateway_count_tokens.go (3.4k tokens)
         ├── openai_gateway_count_tokens_test.go (2.6k tokens)
         ├── openai_gateway_forward.go (8.5k tokens)
         ├── openai_gateway_grok.go (8.9k tokens)
         ├── openai_gateway_grok_405_test.go (200 tokens)
         ├── openai_gateway_grok_cache.go (3.8k tokens)
         ├── openai_gateway_grok_cache_test.go (9.1k tokens)
         ├── openai_gateway_grok_cache_tool_test.go (600 tokens)
         ├── openai_gateway_grok_chat_bridge.go (4.6k tokens)
         ├── openai_gateway_grok_chat_bridge_test.go (6.7k tokens)
         ├── openai_gateway_grok_compact.go (1900 tokens)
         ├── openai_gateway_grok_sse_filter.go (1400 tokens)
         ├── openai_gateway_grok_sse_filter_test.go (2000 tokens)
         ├── openai_gateway_grok_test.go (27.4k tokens)
         ├── openai_gateway_grok_tool_protocol.go (1400 tokens)
         ├── openai_gateway_grok_tool_protocol_test.go (5.1k tokens)
         ├── openai_gateway_messages.go (9k tokens)
         ├── openai_gateway_messages_chat_fallback.go (1900 tokens)
         ├── openai_gateway_messages_chat_fallback_test.go (4.3k tokens)
         ├── openai_gateway_messages_failed_response_test.go (1500 tokens)
         ├── openai_gateway_messages_transport_failover_test.go (700 tokens)
         ├── openai_gateway_messages_usage_test.go (100 tokens)
         ├── openai_gateway_model_availability.go (500 tokens)
         ├── openai_gateway_passthrough.go (10.6k tokens)
         ├── openai_gateway_passthrough_flush_test.go (2.1k tokens)
         ├── openai_gateway_passthrough_function_args_test.go (2.1k tokens)
         ├── openai_gateway_passthrough_image_intent_benchmark_test.go (200 tokens)
         ├── openai_gateway_passthrough_image_intent_test.go (400 tokens)
         ├── openai_gateway_record_usage_test.go (18.9k tokens)
         ├── openai_gateway_request_body.go (8.7k tokens)
         ├── openai_gateway_request_body_reasoning_test.go (1400 tokens)
         ├── openai_gateway_response_failed_passthrough_test.go (1800 tokens)
         ├── openai_gateway_response_flush_test.go (3.6k tokens)
         ├── openai_gateway_response_handling.go (11.9k tokens)
         ├── openai_gateway_response_handling_image_usage_test.go (900 tokens)
         ├── openai_gateway_response_handling_type_test.go (600 tokens)
         ├── openai_gateway_responses_chat_fallback.go (1800 tokens)
         ├── openai_gateway_responses_chat_fallback_test.go (1700 tokens)
         ├── openai_gateway_scheduling.go (9.9k tokens)
         ├── openai_gateway_service.go (8.2k tokens)
         ├── openai_gateway_service_codex_cli_only_test.go (3.7k tokens)
         ├── openai_gateway_service_codex_snapshot_test.go (1500 tokens)
         ├── openai_gateway_service_hotpath_test.go (7.9k tokens)
         ├── openai_gateway_service_session_isolation_test.go (300 tokens)
         ├── openai_gateway_service_shadow_token_test.go (300 tokens)
         ├── openai_gateway_service_test.go (27.1k tokens)
         ├── openai_gateway_service_tool_correction_test.go (700 tokens)
         ├── openai_gateway_upstream_errors.go (4.5k tokens)
         ├── openai_gateway_usage.go (6k tokens)
         ├── openai_gpt56_max_test.go (2.6k tokens)
         ├── openai_image_dimensions.go (600 tokens)
         ├── openai_image_generation_controls_test.go (6.1k tokens)
         ├── openai_image_intent_hint.go (400 tokens)
         ├── openai_image_intent_hint_test.go (2.5k tokens)
         ├── openai_images.go (9.6k tokens)
         ├── openai_images_actual_size_test.go (1400 tokens)
         ├── openai_images_incomplete_test.go (1500 tokens)
         ├── openai_images_json_keepalive.go (1300 tokens)
         ├── openai_images_json_keepalive_test.go (1800 tokens)
         ├── openai_images_responses.go (12.4k tokens)
         ├── openai_images_test.go (15.8k tokens)
         ├── openai_json_optimization_benchmark_test.go (1900 tokens)
         ├── openai_live.go (5.1k tokens)
         ├── openai_live_attestation.go (600 tokens)
         ├── openai_live_lifecycle_test.go (3.7k tokens)
         ├── openai_live_test.go (1600 tokens)
         ├── openai_live_types.go (600 tokens)
         ├── openai_messages_bridge.go (300 tokens)
         ├── openai_messages_continuation.go (1900 tokens)
         ├── openai_messages_digest_session.go (800 tokens)
         ├── openai_messages_dispatch.go (700 tokens)
         ├── openai_messages_dispatch_test.go (400 tokens)
         ├── openai_messages_replay_guard.go (400 tokens)
         ├── openai_messages_replay_guard_test.go (400 tokens)
         ├── openai_messages_todo_guard.go (600 tokens)
         ├── openai_model_alias.go (1000 tokens)
         ├── openai_model_alias_test.go (200 tokens)
         ├── openai_model_mapping.go (600 tokens)
         ├── openai_model_mapping_test.go (2000 tokens)
         ├── openai_oauth_model_support_test.go (900 tokens)
         ├── openai_oauth_passthrough_test.go (18.7k tokens)
         ├── openai_oauth_service.go (3k tokens)
         ├── openai_oauth_service_auth_url_test.go (300 tokens)
         ├── openai_oauth_service_refresh_test.go (1500 tokens)
         ├── openai_oauth_service_state_test.go (600 tokens)
         ├── openai_passthrough_normalization_test.go (800 tokens)
         ├── openai_previous_response_id.go (200 tokens)
         ├── openai_previous_response_id_test.go (200 tokens)
         ├── openai_privacy_retry_test.go (400 tokens)
         ├── openai_privacy_service.go (2.1k tokens)
         ├── openai_profit_control.go (3.6k tokens)
         ├── openai_profit_control_paths_test.go (3.1k tokens)
         ├── openai_profit_control_pricing_test.go (2.4k tokens)
         ├── openai_profit_control_test.go (2.6k tokens)
         ├── openai_proxy_stream_circuit.go (2k tokens)
         ├── openai_proxy_stream_circuit_test.go (1000 tokens)
         ├── openai_quota_reset_credits.go (800 tokens)
         ├── openai_quota_reset_credits_test.go (1300 tokens)
         ├── openai_quota_service.go (5.3k tokens)
         ├── openai_quota_spark_window_test.go (5.2k tokens)
         ├── openai_reasoning_effort_candidates_test.go (700 tokens)
         ├── openai_reasoning_effort_policy.go (1400 tokens)
         ├── openai_reasoning_effort_policy_test.go (1300 tokens)
         ├── openai_request_body_limit_failover_test.go (1000 tokens)
         ├── openai_responses_item_id.go (400 tokens)
         ├── openai_responses_lite_tools.go (1400 tokens)
         ├── openai_responses_lite_tools_test.go (2.3k tokens)
         ├── openai_responses_namespace.go (1600 tokens)
         ├── openai_responses_namespace_forward_test.go (1200 tokens)
         ├── openai_responses_namespace_test.go (2.4k tokens)
         ├── openai_responses_rejected_field_retry.go (900 tokens)
         ├── openai_responses_rejected_field_retry_test.go (2.4k tokens)
         ├── openai_responses_tool_schema.go (800 tokens)
         ├── openai_responses_tool_schema_test.go (1900 tokens)
         ├── openai_routing_hint.go (800 tokens)
         ├── openai_routing_hint_test.go (2.7k tokens)
         ├── openai_silent_refusal.go (1600 tokens)
         ├── openai_spark_shadow_parent_health_test.go (1300 tokens)
         ├── openai_sse_concatenated_json_test.go (3k tokens)
         ├── openai_sse_data.go (300 tokens)
         ├── openai_sse_json_documents.go (600 tokens)
         ├── openai_sticky_compat.go (1200 tokens)
         ├── openai_sticky_compat_test.go (600 tokens)
         ├── openai_stream_read_error.go (400 tokens)
         ├── openai_subscription_test.go (800 tokens)
         ├── openai_token_provider.go (2.3k tokens)
         ├── openai_token_provider_test.go (5.6k tokens)
         ├── openai_tool_continuation.go (2.5k tokens)
         ├── openai_tool_continuation_test.go (2.1k tokens)
         ├── openai_tool_corrector.go (2.2k tokens)
         ├── openai_tool_corrector_test.go (2.9k tokens)
         ├── openai_upstream_transport_error.go (1700 tokens)
         ├── openai_upstream_transport_error_handle_test.go (2.5k tokens)
         ├── openai_upstream_transport_error_test.go (700 tokens)
         ├── openai_ws_account_sticky_test.go (2.4k tokens)
         ├── openai_ws_client.go (1900 tokens)
         ├── openai_ws_client_read.go (600 tokens)
         ├── openai_ws_client_read_test.go (900 tokens)
         ├── openai_ws_client_test.go (700 tokens)
         ├── openai_ws_fallback_test.go (2.1k tokens)
         ├── openai_ws_forwarder.go (2.2k tokens)
         ├── openai_ws_forwarder_benchmark_test.go (800 tokens)
         ├── openai_ws_forwarder_hotpath_optimization_test.go (900 tokens)
         ├── openai_ws_forwarder_ingress.go (13.5k tokens)
         ├── openai_ws_forwarder_ingress_session_test.go (32k tokens)
         ├── openai_ws_forwarder_ingress_test.go (6.6k tokens)
         ├── openai_ws_forwarder_logutil.go (3.6k tokens)
         ├── openai_ws_forwarder_payload.go (4.5k tokens)
         ├── openai_ws_forwarder_retry_payload_test.go (400 tokens)
         ├── openai_ws_forwarder_success_test.go (12.4k tokens)
         ├── openai_ws_forwarder_support.go (4.9k tokens)
         ├── openai_ws_forwarder_test.go (1900 tokens)
         ├── openai_ws_forwarder_v2.go (5.8k tokens)
         ├── openai_ws_http_bridge.go (3.8k tokens)
         ├── openai_ws_http_bridge_test.go (8.8k tokens)
         ├── openai_ws_passthrough_turn_pricing_test.go (700 tokens)
         ├── openai_ws_pool.go (10.4k tokens)
         ├── openai_ws_pool_benchmark_test.go (300 tokens)
         ├── openai_ws_pool_test.go (13.9k tokens)
         ├── openai_ws_protocol_forward_test.go (12.9k tokens)
         ├── openai_ws_protocol_resolver.go (800 tokens)
         ├── openai_ws_protocol_resolver_test.go (1600 tokens)
         ├── openai_ws_ratelimit_signal_test.go (3.8k tokens)
         ├── openai_ws_state_store.go (2.5k tokens)
         ├── openai_ws_state_store_test.go (1500 tokens)
         ├── openai_ws_v2/
            ├── caddy_adapter.go (100 tokens)
            ├── entry.go (100 tokens)
            ├── metrics.go (200 tokens)
            ├── passthrough_relay.go (6.2k tokens)
            ├── passthrough_relay_internal_test.go (3.2k tokens)
            ├── passthrough_relay_test.go (6k tokens)
         ├── openai_ws_v2_passthrough_adapter.go (10.1k tokens)
         ├── openai_ws_v2_passthrough_adapter_effort_test.go (300 tokens)
         ├── openai_ws_v2_passthrough_lifecycle_test.go (4.9k tokens)
         ├── ops_account_availability.go (1000 tokens)
         ├── ops_advisory_lock.go (300 tokens)
         ├── ops_aggregation_service.go (2.2k tokens)
         ├── ops_alert_evaluator_service.go (5.7k tokens)
         ├── ops_alert_evaluator_service_test.go (1300 tokens)
         ├── ops_alert_models.go (500 tokens)
         ├── ops_alerts.go (1500 tokens)
         ├── ops_cleanup_executor.go (700 tokens)
         ├── ops_cleanup_overlay_test.go (1600 tokens)
         ├── ops_cleanup_service.go (2.4k tokens)
         ├── ops_cleanup_service_test.go (300 tokens)
         ├── ops_concurrency.go (2.1k tokens)
         ├── ops_dashboard.go (700 tokens)
         ├── ops_dashboard_models.go (600 tokens)
         ├── ops_errors.go (500 tokens)
         ├── ops_health_score.go (800 tokens)
         ├── ops_health_score_test.go (2.3k tokens)
         ├── ops_histograms.go (200 tokens)
         ├── ops_ingress_reject.go (2.3k tokens)
         ├── ops_ingress_reject_test.go (700 tokens)
         ├── ops_log_runtime.go (1600 tokens)
         ├── ops_log_runtime_test.go (3.1k tokens)
         ├── ops_metrics_collector.go (4.9k tokens)
         ├── ops_metrics_collector_projection_test.go (800 tokens)
         ├── ops_metrics_collector_test.go (400 tokens)
         ├── ops_models.go (1300 tokens)
         ├── ops_openai_token_stats.go (400 tokens)
         ├── ops_openai_token_stats_models.go (300 tokens)
         ├── ops_openai_token_stats_test.go (800 tokens)
         ├── ops_port.go (2k tokens)
         ├── ops_query_mode.go (300 tokens)
         ├── ops_query_mode_test.go (400 tokens)
         ├── ops_queue_sanitize_test.go (400 tokens)
         ├── ops_realtime.go (200 tokens)
         ├── ops_realtime_models.go (700 tokens)
         ├── ops_realtime_traffic.go (300 tokens)
         ├── ops_realtime_traffic_models.go (100 tokens)
         ├── ops_repo_mock_test.go (1400 tokens)
         ├── ops_request_details.go (700 tokens)
         ├── ops_runtime_snapshot_test.go (1200 tokens)
         ├── ops_scheduled_report_service.go (5.2k tokens)
         ├── ops_scheduled_report_service_test.go (1100 tokens)
         ├── ops_service.go (5.9k tokens)
         ├── ops_service_batch_test.go (1000 tokens)
         ├── ops_service_redaction_test.go (500 tokens)
         ├── ops_service_user_error_test.go (1000 tokens)
         ├── ops_settings.go (3.8k tokens)
         ├── ops_settings_advanced_test.go (1200 tokens)
         ├── ops_settings_models.go (1100 tokens)
         ├── ops_system_log_service.go (800 tokens)
         ├── ops_system_log_service_test.go (1700 tokens)
         ├── ops_system_log_sink.go (1900 tokens)
         ├── ops_system_log_sink_backoff_test.go (1300 tokens)
         ├── ops_system_log_sink_test.go (2000 tokens)
         ├── ops_trend_models.go (400 tokens)
         ├── ops_trends.go (200 tokens)
         ├── ops_upstream_context.go (2.2k tokens)
         ├── ops_upstream_context_test.go (200 tokens)
         ├── ops_user_error.go (900 tokens)
         ├── ops_user_error_cyber_test.go (100 tokens)
         ├── ops_user_error_test.go (1200 tokens)
         ├── ops_window_stats.go (100 tokens)
         ├── overload_cooldown_test.go (2.2k tokens)
         ├── parse_integral_number_unit.go (200 tokens)
         ├── passkey.go (2.3k tokens)
         ├── passkey_test.go (700 tokens)
         ├── payment_amounts.go (300 tokens)
         ├── payment_config_limits.go (2.2k tokens)
         ├── payment_config_limits_test.go (3.6k tokens)
         ├── payment_config_plans.go (1600 tokens)
         ├── payment_config_plans_validation_test.go (1400 tokens)
         ├── payment_config_providers.go (4k tokens)
         ├── payment_config_providers_test.go (4.7k tokens)
         ├── payment_config_service.go (4.6k tokens)
         ├── payment_config_service_test.go (3.6k tokens)
         ├── payment_currency.go (100 tokens)
         ├── payment_fulfillment.go (5.8k tokens)
         ├── payment_fulfillment_order_not_found_test.go (800 tokens)
         ├── payment_fulfillment_test.go (7.2k tokens)
         ├── payment_order.go (6.4k tokens)
         ├── payment_order_expiry_service.go (700 tokens)
         ├── payment_order_jsapi_test.go (500 tokens)
         ├── payment_order_lifecycle.go (3.1k tokens)
         ├── payment_order_lifecycle_test.go (4.4k tokens)
         ├── payment_order_provider_snapshot.go (1600 tokens)
         ├── payment_order_provider_snapshot_test.go (1100 tokens)
         ├── payment_order_result_test.go (3.8k tokens)
         ├── payment_refund.go (5.5k tokens)
         ├── payment_refund_test.go (4.6k tokens)
         ├── payment_resume_lookup.go (500 tokens)
         ├── payment_resume_lookup_test.go (1900 tokens)
         ├── payment_resume_service.go (3.1k tokens)
         ├── payment_resume_service_test.go (5.1k tokens)
         ├── payment_service.go (2.5k tokens)
         ├── payment_stats.go (1100 tokens)
         ├── payment_stats_test.go (600 tokens)
         ├── payment_visible_method_instances.go (1600 tokens)
         ├── payment_webhook_provider.go (1000 tokens)
         ├── payment_webhook_provider_test.go (3k tokens)
         ├── post_billing_platform_test.go (500 tokens)
         ├── pricing_service.go (7.4k tokens)
         ├── pricing_service_test.go (5.4k tokens)
         ├── profit_control_threshold_helpers_test.go (300 tokens)
         ├── profit_preview.go (1500 tokens)
         ├── profit_preview_test.go (1000 tokens)
         ├── promo_code.go (300 tokens)
         ├── promo_code_repository.go (200 tokens)
         ├── promo_service.go (1500 tokens)
         ├── prompts/
            ├── codex_opencode_bridge.txt (1100 tokens)
            ├── tool_remap_message.txt (400 tokens)
         ├── proxy.go (300 tokens)
         ├── proxy_expiry_service.go (300 tokens)
         ├── proxy_fallback.go (200 tokens)
         ├── proxy_fallback_test.go (500 tokens)
         ├── proxy_latency_cache.go (200 tokens)
         ├── proxy_service.go (1100 tokens)
         ├── proxy_test.go (400 tokens)
         ├── proxy_update_probe_invalidation_test.go (300 tokens)
         ├── quota_fetcher.go (100 tokens)
         ├── rate_limit_429_cooldown_test.go (1300 tokens)
         ├── ratelimit_service.go (16.2k tokens)
         ├── ratelimit_service_401_db_fallback_test.go (1000 tokens)
         ├── ratelimit_service_401_test.go (2.4k tokens)
         ├── ratelimit_service_403_test.go (500 tokens)
         ├── ratelimit_service_anthropic_test.go (2.3k tokens)
         ├── ratelimit_service_anthropic_window_limit_test.go (1900 tokens)
         ├── ratelimit_service_clear_test.go (2000 tokens)
         ├── ratelimit_service_model_not_found_test.go (3.4k tokens)
         ├── ratelimit_service_openai_image_test.go (1100 tokens)
         ├── ratelimit_service_openai_test.go (3.7k tokens)
         ├── ratelimit_session_window_test.go (3.4k tokens)
         ├── redeem_code.go (200 tokens)
         ├── redeem_code_test.go (300 tokens)
         ├── redeem_service.go (4.3k tokens)
         ├── redeem_service_batch_update_test.go (400 tokens)
         ├── redeem_service_redeem_test.go (600 tokens)
         ├── refresh_policy.go (600 tokens)
         ├── refresh_token_cache.go (500 tokens)
         ├── registration_email_alias.go (1100 tokens)
         ├── registration_email_alias_test.go (1100 tokens)
         ├── registration_email_policy.go (800 tokens)
         ├── registration_email_policy_test.go (400 tokens)
         ├── request_metadata.go (1300 tokens)
         ├── request_metadata_test.go (900 tokens)
         ├── response_header_filter.go (100 tokens)
         ├── rpm_cache.go (100 tokens)
         ├── scheduled_test_port.go (400 tokens)
         ├── scheduled_test_runner_service.go (1000 tokens)
         ├── scheduled_test_service.go (600 tokens)
         ├── scheduler_cache.go (900 tokens)
         ├── scheduler_events.go (100 tokens)
         ├── scheduler_layered_filter_test.go (1900 tokens)
         ├── scheduler_outbox.go (200 tokens)
         ├── scheduler_shuffle_test.go (2.2k tokens)
         ├── scheduler_snapshot_batch_query_test.go (6.2k tokens)
         ├── scheduler_snapshot_bulk_event_test.go (1600 tokens)
         ├── scheduler_snapshot_cancellation_test.go (500 tokens)
         ├── scheduler_snapshot_full_rebuild_lifecycle_test.go (4.9k tokens)
         ├── scheduler_snapshot_full_rebuild_test.go (900 tokens)
         ├── scheduler_snapshot_group_lifecycle_test.go (5.9k tokens)
         ├── scheduler_snapshot_hydration_test.go (1700 tokens)
         ├── scheduler_snapshot_outbox_cleanup_test.go (5.7k tokens)
         ├── scheduler_snapshot_retirement_test.go (1600 tokens)
         ├── scheduler_snapshot_service.go (10.3k tokens)
         ├── session_binding.go (300 tokens)
         ├── session_id.go (500 tokens)
         ├── session_id_test.go (1100 tokens)
         ├── session_limit_cache.go (400 tokens)
         ├── setting.go
         ├── setting_features.go (7.2k tokens)
         ├── setting_gateway_runtime.go (7.7k tokens)
         ├── setting_oauth.go (8.4k tokens)
         ├── setting_panel_rate_limit.go (1100 tokens)
         ├── setting_panel_rate_limit_test.go (1100 tokens)
         ├── setting_parse.go (12.1k tokens)
         ├── setting_public.go (6.5k tokens)
         ├── setting_service.go (2.7k tokens)
         ├── setting_service_auth_source_defaults_test.go (1000 tokens)
         ├── setting_service_backend_mode_test.go (1100 tokens)
         ├── setting_service_claude_oauth_system_prompt_test.go (300 tokens)
         ├── setting_service_codex_policy_test.go (1700 tokens)
         ├── setting_service_codex_validate_test.go (400 tokens)
         ├── setting_service_oidc_config_test.go (1700 tokens)
         ├── setting_service_platform_quota_test.go (2.4k tokens)
         ├── setting_service_public_test.go (1300 tokens)
         ├── setting_service_update_test.go (6.8k tokens)
         ├── setting_service_user_error_persist_test.go (200 tokens)
         ├── setting_service_wechat_config_test.go (1100 tokens)
         ├── setting_update.go (7.8k tokens)
         ├── setting_user_error_view_test.go (100 tokens)
         ├── settings_view.go (5.1k tokens)
         ├── shadow_routing.go (300 tokens)
         ├── shadow_routing_test.go (100 tokens)
         ├── slice_helpers.go
         ├── spark_shadow_integration_test.go (1300 tokens)
         ├── sql_errors.go
         ├── sse_scanner_buffer_pool.go (100 tokens)
         ├── sse_scanner_buffer_pool_test.go (100 tokens)
         ├── sticky_session_test.go (900 tokens)
         ├── subscription_assign_idempotency_test.go (4.6k tokens)
         ├── subscription_calculate_progress_test.go (1400 tokens)
         ├── subscription_daily_midnight_reset_test.go (1100 tokens)
         ├── subscription_expiry_service.go (1100 tokens)
         ├── subscription_expiry_service_test.go (1200 tokens)
         ├── subscription_maintenance_queue.go (300 tokens)
         ├── subscription_maintenance_queue_test.go (600 tokens)
         ├── subscription_monthly_window_test.go (1900 tokens)
         ├── subscription_renewal_lock_test.go (1000 tokens)
         ├── subscription_reset_quota_test.go (1700 tokens)
         ├── subscription_revoke_cache_test.go (900 tokens)
         ├── subscription_service.go (7.6k tokens)
         ├── system_operation_lock_service.go (1100 tokens)
         ├── system_operation_lock_service_test.go (2.1k tokens)
         ├── temp_unsched.go (300 tokens)
         ├── temp_unsched_test.go (1600 tokens)
         ├── tencent_captcha_service.go (1000 tokens)
         ├── tencent_captcha_service_test.go (1100 tokens)
         ├── tencent_captcha_settings_test.go (600 tokens)
         ├── testdata/
            ├── ollama_settings_usage.html (400 tokens)
            ├── security_monitor_system_prompt.txt (8k tokens)
         ├── testhelpers_test.go (100 tokens)
         ├── thinking_protocol.go (900 tokens)
         ├── thinking_protocol_filter_integration_test.go (600 tokens)
         ├── thinking_protocol_test.go (1100 tokens)
         ├── timing_wheel_service.go (400 tokens)
         ├── timing_wheel_service_test.go (900 tokens)
         ├── tls_fingerprint_profile_service.go (1500 tokens)
         ├── token_cache_invalidator.go (700 tokens)
         ├── token_cache_invalidator_test.go (2.8k tokens)
         ├── token_cache_key.go (100 tokens)
         ├── token_cache_key_test.go (1200 tokens)
         ├── token_refresh_pool_health_test.go (6.8k tokens)
         ├── token_refresh_service.go (9.2k tokens)
         ├── token_refresh_service_candidates_test.go (1500 tokens)
         ├── token_refresh_service_test.go (11k tokens)
         ├── token_refresher.go (800 tokens)
         ├── token_refresher_test.go (1200 tokens)
         ├── totp_service.go (3.4k tokens)
         ├── totp_verification_method_test.go (700 tokens)
         ├── turnstile_service.go (700 tokens)
         ├── update_service.go (3.8k tokens)
         ├── update_service_test.go (1100 tokens)
         ├── upstream_billing_probe.go (8.4k tokens)
         ├── upstream_billing_probe_multiplatform_test.go (1900 tokens)
         ├── upstream_billing_probe_test.go (9.3k tokens)
         ├── upstream_models.go (4.3k tokens)
         ├── upstream_models_test.go (3.1k tokens)
         ├── upstream_path_guard.go (500 tokens)
         ├── upstream_path_guard_test.go (900 tokens)
         ├── upstream_response_limit.go (500 tokens)
         ├── upstream_response_limit_test.go (500 tokens)
         ├── upstream_response_model.go (900 tokens)
         ├── upstream_response_model_test.go (600 tokens)
         ├── usage_billing.go (1300 tokens)
         ├── usage_billing_quantize_test.go (1100 tokens)
         ├── usage_cleanup.go (500 tokens)
         ├── usage_cleanup_service.go (3k tokens)
         ├── usage_cleanup_service_test.go (5.5k tokens)
         ├── usage_log.go (1300 tokens)
         ├── usage_log_create_result.go (300 tokens)
         ├── usage_log_cyber_test.go (100 tokens)
         ├── usage_log_helpers.go (100 tokens)
         ├── usage_log_test.go (600 tokens)
         ├── usage_record_worker_pool.go (3.2k tokens)
         ├── usage_record_worker_pool_test.go (3.2k tokens)
         ├── usage_service.go (3.8k tokens)
         ├── user.go (600 tokens)
         ├── user_attribute.go (900 tokens)
         ├── user_attribute_service.go (1800 tokens)
         ├── user_group_rate.go (500 tokens)
         ├── user_group_rate_resolver.go (500 tokens)
         ├── user_group_rate_resolver_test.go (500 tokens)
         ├── user_msg_queue_service.go (1600 tokens)
         ├── user_msg_queue_service_test.go (300 tokens)
         ├── user_platform_quota_flusher.go (1800 tokens)
         ├── user_platform_quota_flusher_test.go (3.5k tokens)
         ├── user_platform_quota_port.go (600 tokens)
         ├── user_rpm_cache.go (200 tokens)
         ├── user_service.go (9.6k tokens)
         ├── user_service_email_identity_sync_test.go (200 tokens)
         ├── user_service_test.go (6k tokens)
         ├── user_service_update_fields_test.go (500 tokens)
         ├── user_subscription.go (1200 tokens)
         ├── user_subscription_daily_quota_test.go (1400 tokens)
         ├── user_subscription_days_remaining_test.go (300 tokens)
         ├── user_subscription_port.go (500 tokens)
         ├── vertex_service_account.go (2.4k tokens)
         ├── vertex_service_account_test.go (1300 tokens)
         ├── video_billing_resolution.go (300 tokens)
         ├── websearch_config.go (2.2k tokens)
         ├── websearch_config_test.go (1600 tokens)
         ├── wire.go (6.6k tokens)
         ├── wire_test.go (200 tokens)
      ├── setup/
         ├── cli.go (1500 tokens)
         ├── handler.go (2.1k tokens)
         ├── setup.go (4.2k tokens)
         ├── setup_test.go (1300 tokens)
      ├── testutil/
         ├── fixtures.go (300 tokens)
         ├── httptest.go (100 tokens)
         ├── stubs.go (1000 tokens)
      ├── util/
         ├── httputil/
            ├── httputil.go (800 tokens)
         ├── logredact/
            ├── redact.go (1100 tokens)
            ├── redact_test.go (500 tokens)
         ├── responseheaders/
            ├── responseheaders.go (600 tokens)
            ├── responseheaders_test.go (400 tokens)
         ├── urlvalidator/
            ├── validator.go (900 tokens)
            ├── validator_test.go (600 tokens)
      ├── web/
         ├── embed_off.go (300 tokens)
         ├── embed_on.go (2.1k tokens)
         ├── embed_test.go (5.3k tokens)
         ├── html_cache.go (300 tokens)
         ├── static_cache.go (300 tokens)
         ├── static_cache_test.go (500 tokens)
   ├── migrations/
      ├── 001_init.sql (1700 tokens)
      ├── 002_account_type_migration.sql (200 tokens)
      ├── 003_subscription.sql (700 tokens)
      ├── 004_add_redeem_code_notes.sql
      ├── 005_schema_parity.sql (500 tokens)
      ├── 006_add_users_allowed_groups_compat.sql (100 tokens)
      ├── 006_fix_invalid_subscription_expires_at.sql (100 tokens)
      ├── 006b_guard_users_allowed_groups.sql (200 tokens)
      ├── 007_add_user_allowed_groups.sql (200 tokens)
      ├── 008_seed_default_group.sql
      ├── 009_fix_usage_logs_cache_columns.sql (200 tokens)
      ├── 010_add_usage_logs_aggregated_indexes.sql (100 tokens)
      ├── 011_remove_duplicate_unique_indexes.sql (200 tokens)
      ├── 012_add_user_subscription_soft_delete.sql (100 tokens)
      ├── 013_log_orphan_allowed_groups.sql (200 tokens)
      ├── 014_drop_legacy_allowed_groups.sql (100 tokens)
      ├── 015_fix_settings_unique_constraint.sql (100 tokens)
      ├── 016_soft_delete_partial_unique_indexes.sql (400 tokens)
      ├── 018_user_attributes.sql (400 tokens)
      ├── 019_migrate_wechat_to_attributes.sql (600 tokens)
      ├── 020_add_temp_unschedulable.sql (100 tokens)
      ├── 024_add_gemini_tier_id.sql (200 tokens)
      ├── 026_ops_metrics_aggregation_tables.sql (900 tokens)
      ├── 027_usage_billing_consistency.sql (500 tokens)
      ├── 028_add_account_notes.sql
      ├── 028_add_usage_logs_user_agent.sql (100 tokens)
      ├── 028_group_image_pricing.sql (100 tokens)
      ├── 029_add_group_claude_code_restriction.sql (200 tokens)
      ├── 029_usage_log_image_fields.sql
      ├── 030_add_account_expires_at.sql (100 tokens)
      ├── 031_add_ip_address.sql (100 tokens)
      ├── 032_add_api_key_ip_restriction.sql (100 tokens)
      ├── 033_add_promo_codes.sql (300 tokens)
      ├── 033_ops_monitoring_vnext.sql (4.9k tokens)
      ├── 034_ops_upstream_error_events.sql (100 tokens)
      ├── 034_usage_dashboard_aggregation_tables.sql (700 tokens)
      ├── 035_usage_logs_partitioning.sql (400 tokens)
      ├── 036_ops_error_logs_add_is_count_tokens.sql (100 tokens)
      ├── 036_scheduler_outbox.sql (100 tokens)
      ├── 037_add_account_rate_multiplier.sql (100 tokens)
      ├── 037_ops_alert_silences.sql (100 tokens)
      ├── 038_ops_errors_resolution_retry_results_and_standardize_classification.sql (800 tokens)
      ├── 039_ops_job_heartbeats_add_last_result.sql (100 tokens)
      ├── 040_add_group_model_routing.sql (100 tokens)
      ├── 041_add_model_routing_enabled.sql
      ├── 042_add_usage_cleanup_tasks.sql (100 tokens)
      ├── 042b_add_ops_system_metrics_switch_count.sql
      ├── 043_add_usage_cleanup_cancel_audit.sql (100 tokens)
      ├── 043b_add_group_invalid_request_fallback.sql (100 tokens)
      ├── 044_add_user_totp.sql (100 tokens)
      ├── 044b_add_group_mcp_xml_inject.sql
      ├── 045_add_accounts_extra_index.sql (100 tokens)
      ├── 045_add_announcements.sql (400 tokens)
      ├── 045_add_api_key_quota.sql (200 tokens)
      ├── 046_add_sora_accounts.sql (200 tokens)
      ├── 046_add_usage_log_reasoning_effort.sql
      ├── 046b_add_group_supported_model_scopes.sql (100 tokens)
      ├── 047_add_sora_pricing_and_media_type.sql (100 tokens)
      ├── 047_add_user_group_rate_multipliers.sql (200 tokens)
      ├── 048_add_error_passthrough_rules.sql (200 tokens)
      ├── 049_unify_antigravity_model_mapping.sql (300 tokens)
      ├── 050_map_opus46_to_opus45.sql (100 tokens)
      ├── 051_migrate_opus45_to_opus46_thinking.sql (400 tokens)
      ├── 052_add_group_sort_order.sql (100 tokens)
      ├── 052_migrate_upstream_to_apikey.sql (100 tokens)
      ├── 053_add_security_secrets.sql (100 tokens)
      ├── 053_add_skip_monitoring_to_error_passthrough.sql (100 tokens)
      ├── 054_drop_legacy_cache_columns.sql (100 tokens)
      ├── 054_ops_system_logs.sql (400 tokens)
      ├── 055_add_cache_ttl_overridden.sql
      ├── 056_add_api_key_last_used_at.sql
      ├── 057_add_idempotency_records.sql (200 tokens)
      ├── 058_add_sonnet46_to_model_mapping.sql (400 tokens)
      ├── 059_add_gemini31_pro_to_model_mapping.sql (400 tokens)
      ├── 060_add_gemini31_flash_image_to_model_mapping.sql (400 tokens)
      ├── 060_add_usage_log_openai_ws_mode.sql
      ├── 061_add_usage_log_request_type.sql (400 tokens)
      ├── 062_add_scheduler_and_usage_composite_indexes_notx.sql (100 tokens)
      ├── 063_add_sora_client_tables.sql (400 tokens)
      ├── 064_add_api_key_rate_limits.sql (200 tokens)
      ├── 065_add_search_trgm_indexes.sql (300 tokens)
      ├── 066_add_scheduled_test_tables.sql (300 tokens)
      ├── 067_add_account_load_factor.sql
      ├── 068_add_announcement_notify_mode.sql
      ├── 069_add_group_messages_dispatch.sql
      ├── 070_add_scheduled_test_auto_recover.sql (100 tokens)
      ├── 070_add_usage_log_service_tier.sql
      ├── 071_add_gemini25_flash_image_to_model_mapping.sql (500 tokens)
      ├── 071_add_usage_billing_dedup.sql (100 tokens)
      ├── 072_add_usage_billing_dedup_created_at_brin_notx.sql (100 tokens)
      ├── 073_add_usage_billing_dedup_archive.sql (100 tokens)
      ├── 074_add_usage_log_endpoints.sql (100 tokens)
      ├── 075_add_usage_log_upstream_model.sql (100 tokens)
      ├── 075_map_haiku45_to_sonnet46.sql (100 tokens)
      ├── 076_add_usage_log_upstream_model_index_notx.sql
      ├── 077_add_usage_log_requested_model.sql (100 tokens)
      ├── 078_add_usage_log_requested_model_index_notx.sql
      ├── 079_ops_error_logs_add_endpoint_fields.sql (300 tokens)
      ├── 080_create_tls_fingerprint_profiles.sql (300 tokens)
      ├── 081_add_group_account_filter.sql
      ├── 081_create_channels.sql (500 tokens)
      ├── 082_refactor_channel_pricing.sql (600 tokens)
      ├── 083_channel_model_mapping.sql (100 tokens)
      ├── 084_channel_billing_model_source.sql (100 tokens)
      ├── 085_channel_restrict_and_per_request_price.sql (100 tokens)
      ├── 086_channel_platform_pricing.sql (200 tokens)
      ├── 087_usage_log_billing_mode.sql
      ├── 088_channel_billing_model_source_channel_mapped.sql
      ├── 089_usage_log_image_output_tokens.sql
      ├── 090_drop_sora.sql (300 tokens)
      ├── 091_add_group_messages_dispatch_model_config.sql
      ├── 092_payment_orders.sql (400 tokens)
      ├── 093_payment_audit_logs.sql (100 tokens)
      ├── 094_removed_payment_channels.sql
      ├── 095_channel_features.sql
      ├── 095_subscription_plans.sql (200 tokens)
      ├── 096_payment_provider_instances.sql (100 tokens)
      ├── 097_fix_settings_updated_at_default.sql (200 tokens)
      ├── 098_migrate_purchase_subscription_to_custom_menu.sql (400 tokens)
      ├── 099_fix_migrated_purchase_menu_label_icon.sql (400 tokens)
      ├── 100_remove_easypay_from_enabled_payment_types.sql (100 tokens)
      ├── 101_add_account_stats_pricing.sql (400 tokens)
      ├── 101_add_balance_notify_fields.sql (100 tokens)
      ├── 101_add_channel_features_config.sql
      ├── 101_add_payment_mode.sql (200 tokens)
      ├── 102_add_balance_notify_threshold_type.sql (100 tokens)
      ├── 102_add_out_trade_no_to_payment_orders.sql (100 tokens)
      ├── 103_add_allow_user_refund.sql
      ├── 104_migrate_notify_emails_to_struct.sql (300 tokens)
      ├── 105_migrate_websearch_emulation_to_tristate.sql (100 tokens)
      ├── 106_add_account_stats_pricing_intervals.sql (200 tokens)
      ├── 107_add_account_cost_to_dashboard_tables.sql (100 tokens)
      ├── 108_auth_identity_foundation_core.sql (1100 tokens)
      ├── 108a_widen_auth_identity_migration_report_type.sql (100 tokens)
      ├── 109_auth_identity_compat_backfill.sql (800 tokens)
      ├── 110_pending_auth_and_provider_default_grants.sql (500 tokens)
      ├── 111_payment_routing_and_scheduler_flags.sql (100 tokens)
      ├── 112_add_payment_order_provider_key_snapshot.sql (100 tokens)
      ├── 113_normalize_legacy_wechat_provider_key.sql (700 tokens)
      ├── 114_auth_identity_migration_report_resolution.sql (100 tokens)
      ├── 115_auth_identity_legacy_external_backfill.sql (1700 tokens)
      ├── 116_auth_identity_legacy_external_safety_reports.sql (4.1k tokens)
      ├── 117_add_payment_order_provider_snapshot.sql
      ├── 118_wechat_dual_mode_and_auth_source_defaults.sql (200 tokens)
      ├── 119_enforce_payment_orders_out_trade_no_unique.sql
      ├── 120_enforce_payment_orders_out_trade_no_unique_notx.sql (100 tokens)
      ├── 120a_align_payment_orders_out_trade_no_index_name.sql (100 tokens)
      ├── 121_auth_identity_migration_report_type_widen.sql
      ├── 122_pending_auth_completion_token_cleanup.sql (100 tokens)
      ├── 123_fix_legacy_auth_source_grant_on_signup_defaults.sql (700 tokens)
      ├── 124_backfill_legacy_oidc_security_flags.sql (200 tokens)
      ├── 125_add_channel_monitors.sql (500 tokens)
      ├── 125_add_group_rpm_limit.sql (100 tokens)
      ├── 126_add_channel_monitor_aggregation.sql (500 tokens)
      ├── 126_add_user_rpm_limit.sql (100 tokens)
      ├── 127_add_user_group_rpm_override.sql (200 tokens)
      ├── 127_drop_channel_monitor_deleted_at.sql (100 tokens)
      ├── 128_add_channel_monitor_request_templates.sql (600 tokens)
      ├── 129_seed_claude_code_template.sql (300 tokens)
      ├── 130_add_user_affiliates.sql (200 tokens)
      ├── 131_affiliate_rebate_hardening.sql (400 tokens)
      ├── 132_affiliate_custom_settings.sql (100 tokens)
      ├── 133_affiliate_rebate_freeze.sql (200 tokens)
      ├── 134_affiliate_ledger_audit_snapshots.sql (700 tokens)
      ├── 134_image_generation_group_controls.sql (200 tokens)
      ├── 135_allow_email_oauth_provider_types.sql (200 tokens)
      ├── 135_content_moderation.sql (600 tokens)
      ├── 136_add_dingtalk_provider_type.sql (200 tokens)
      ├── 136_remove_ops_retry_replay.sql (100 tokens)
      ├── 136_usage_log_image_size_metadata.sql (300 tokens)
      ├── 137_redeem_code_expires_at.sql (100 tokens)
      ├── 138_channel_monitor_openai_api_mode.sql (300 tokens)
      ├── 139_seed_openai_monitor_templates.sql (300 tokens)
      ├── 140_extend_user_provider_default_grants_check.sql (100 tokens)
      ├── 141_subscription_expiry_notify_enabled.sql
      ├── 142_user_platform_quotas.sql (300 tokens)
      ├── 143_group_models_list_config.sql
      ├── 144_add_opus48_to_model_mapping.sql (100 tokens)
      ├── 145_deleted_api_key_audit.sql (200 tokens)
      ├── 145_ops_metrics_ttft_sample_count.sql (200 tokens)
      ├── 147_ops_error_log_api_key_prefix.sql (100 tokens)
      ├── 148_add_ops_error_logs_user_time_index_notx.sql (100 tokens)
      ├── 149_proxy_expiry_fallback.sql (200 tokens)
      ├── 150_account_group_scheduler_indexes_notx.sql (100 tokens)
      ├── 151_account_autopause_expiry_index_notx.sql
      ├── 151_channel_monitor_jitter.sql (100 tokens)
      ├── 152_scheduler_outbox_dedup_key.sql
      ├── 153_scheduler_outbox_pending_dedup_key_index_notx.sql
      ├── 154_account_spark_shadow.sql (300 tokens)
      ├── 154_add_ops_system_logs_api_key_id.sql
      ├── 154a_account_spark_shadow_indexes_notx.sql (100 tokens)
      ├── 155_add_ops_system_logs_api_key_id_index_notx.sql (100 tokens)
      ├── 156_content_moderation_matched_keyword.sql
      ├── 157_user_platform_quotas_add_grok.sql (200 tokens)
      ├── 158_add_group_peak_rate_multiplier.sql (100 tokens)
      ├── 158_enable_grok_media_generation_groups.sql (100 tokens)
      ├── 159_batch_image_foundation.sql (800 tokens)
      ├── 160_add_user_frozen_balance.sql
      ├── 160_batch_image_provider_refs.sql
      ├── 161_batch_image_pricing_snapshot.sql (300 tokens)
      ├── 162_add_group_batch_image_generation_gate.sql
      ├── 163_batch_image_default_discount_and_hold_ratio.sql (100 tokens)
      ├── 164_batch_image_download_and_user_delete.sql (100 tokens)
      ├── 165_hide_pre_upstream_batch_image_failures.sql (100 tokens)
      ├── 166_batch_image_task_name.sql (100 tokens)
      ├── 167_clear_auto_batch_image_task_names.sql
      ├── 168_restore_empty_batch_image_task_names.sql
      ├── 169_batch_image_parent_batch.sql (100 tokens)
      ├── 170_add_grok_video_pricing_controls.sql (200 tokens)
      ├── 171_allow_video_usage_without_image_size.sql (100 tokens)
      ├── 172_composite_model_routes.sql (300 tokens)
      ├── 172_video_per_second_billing_metadata.sql (400 tokens)
      ├── 173_allow_cyber_blocked_usage_request_type.sql (100 tokens)
      ├── 174_add_usage_log_long_context_billing.sql (100 tokens)
      ├── 174_add_usage_logs_api_key_latest_ip_index_notx.sql (100 tokens)
      ├── 174_group_web_search_price_per_call.sql
      ├── 175_add_ops_system_logs_host.sql
      ├── 175_default_openai_long_context_billing.sql (1200 tokens)
      ├── 175a_add_ops_system_logs_host_index_notx.sql
      ├── 176_channel_monitor_grok_provider.sql (300 tokens)
      ├── 177_add_subscription_plan_currency.sql
      ├── 178_channel_image_input_price.sql (100 tokens)
      ├── 179_usage_log_image_input_tokens.sql (100 tokens)
      ├── 180_audit_logs.sql (300 tokens)
      ├── 181_group_duplicate_operation_id.sql (100 tokens)
      ├── 181_prompt_audit.sql (1500 tokens)
      ├── 182_prompt_audit_full_prompt.sql (100 tokens)
      ├── 183_ops_ingress_reject_aggregates.sql (200 tokens)
      ├── 184_auth_cache_invalidation_outbox.sql (1300 tokens)
      ├── 185_group_reasoning_effort_policy.sql (100 tokens)
      ├── 186_alipay_mobile_precreate_deep_link.sql
      ├── 186_group_auth_cache_image_generation.sql (200 tokens)
      ├── 187_add_usage_log_session_id.sql (200 tokens)
      ├── 188_allow_live_usage_request_type.sql
      ├── 189_add_group_allow_live.sql
      ├── 190_add_users_email_alias_dedup_index_notx.sql (100 tokens)
      ├── 191_passkey_credentials.sql (200 tokens)
      ├── 192_group_profit_control.sql (100 tokens)
      ├── 193_group_profit_control_auth_cache_invalidation.sql (400 tokens)
      ├── 194_add_usage_log_upstream_response_model.sql
      ├── 195_add_usage_log_upstream_model_mismatch_index_notx.sql
      ├── README.md (1000 tokens)
      ├── auth_identity_payment_migrations_regression_test.go (2.1k tokens)
      ├── channel_monitor_grok_provider_migration_test.go (100 tokens)
      ├── latest_api_key_ip_index_test.go (100 tokens)
      ├── migrations.go (100 tokens)
      ├── openai_long_context_billing_migration_test.go (300 tokens)
   ├── resources/
      ├── model-pricing/
         ├── README.md (300 tokens)
         ├── model_prices_and_context_window.json (39.9k tokens)
   ├── scripts/
      ├── finalize-ingress-reject-cleanup.sql (200 tokens)
      ├── resolve-version.sh (100 tokens)
├── deploy/
   ├── .env.example (3.6k tokens)
   ├── .gitignore (100 tokens)
   ├── APPLE_CONTAINER.md (2.1k tokens)
   ├── Caddyfile (600 tokens)
   ├── DATAMANAGEMENTD_CN.md (300 tokens)
   ├── DOCKER.md (300 tokens)
   ├── Dockerfile (900 tokens)
   ├── EDGE_SECURITY.md (1800 tokens)
   ├── Makefile (200 tokens)
   ├── README.md (4k tokens)
   ├── apple-container.sh (5.5k tokens)
   ├── build_image.sh (100 tokens)
   ├── codex-instructions.md.tmpl
   ├── config.example.yaml (9.9k tokens)
   ├── docker-compose.dev.yml (1400 tokens)
   ├── docker-compose.local.yml (2.6k tokens)
   ├── docker-compose.standalone.yml (1400 tokens)
   ├── docker-compose.yml (2.8k tokens)
   ├── docker-deploy.sh (1100 tokens)
   ├── docker-entrypoint.sh (200 tokens)
   ├── install-datamanagementd.sh (600 tokens)
   ├── install.sh (7.8k tokens)
   ├── sub2api-datamanagementd.service (100 tokens)
   ├── sub2api.service (200 tokens)
   ├── test-caddyfile-cache.sh (500 tokens)
   ├── tests/
      ├── apple-container-test.sh (500 tokens)
      ├── docker-compose-security-test.sh (200 tokens)
      ├── docker-runtime-resources-test.sh (200 tokens)
      ├── fixtures/
         ├── bin/
            ├── container (900 tokens)
            ├── curl
      ├── install-github-token-test.sh (900 tokens)
├── docs/
   ├── ADMIN_PAYMENT_INTEGRATION_API.md (1200 tokens)
   ├── ASYNC_IMAGE_TASKS.md (1700 tokens)
   ├── BATCH_IMAGE_MVP.md (3k tokens)
   ├── COMPOSITE_GROUPS.md (1100 tokens)
   ├── PAYMENT.md (2.8k tokens)
   ├── PAYMENT_CN.md (1400 tokens)
   ├── legal/
      ├── admin-compliance.en.md (1500 tokens)
      ├── admin-compliance.zh.md (400 tokens)
   ├── screenshots/
      ├── mobile-account-actions-menu.png
├── frontend/
   ├── .eslintignore
   ├── .eslintrc.cjs (200 tokens)
   ├── .npmrc
   ├── audit.json (800 tokens)
   ├── index.html (100 tokens)
   ├── package.json (400 tokens)
   ├── pnpm-lock.yaml (66.1k tokens)
   ├── postcss.config.js
   ├── public/
      ├── logo.svg (400 tokens)
   ├── src/
      ├── App.vue (900 tokens)
      ├── __tests__/
         ├── integration/
            ├── data-import.spec.ts (1200 tokens)
            ├── navigation.spec.ts (2.2k tokens)
            ├── proxy-data-import.spec.ts (400 tokens)
         ├── setup.ts (500 tokens)
      ├── api/
         ├── __tests__/
            ├── admin.accounts.duplicate.spec.ts (400 tokens)
            ├── admin.accounts.ollamaCloudUsage.spec.ts (500 tokens)
            ├── admin.accounts.upstreamBillingProbe.spec.ts (400 tokens)
            ├── admin.channelMonitor.duplicate.spec.ts (800 tokens)
            ├── admin.grok.spec.ts (200 tokens)
            ├── admin.groups.duplicate.spec.ts (800 tokens)
            ├── admin.system.rollback.spec.ts (300 tokens)
            ├── admin.users.spec.ts (800 tokens)
            ├── adminUIRequest.spec.ts (600 tokens)
            ├── auth-captcha-oauth-start.spec.ts (300 tokens)
            ├── auth-oauth-adoption.spec.ts (1400 tokens)
            ├── client.spec.ts (2.8k tokens)
            ├── passkey.spec.ts (1100 tokens)
            ├── payment.spec.ts (200 tokens)
            ├── settings.authSourceDefaults.spec.ts (2.3k tokens)
            ├── settings.paymentVisibleMethods.spec.ts (400 tokens)
            ├── settings.wechatConnect.spec.ts (200 tokens)
            ├── tokenRefresh.spec.ts (1300 tokens)
            ├── user.spec.ts (200 tokens)
         ├── admin/
            ├── accounts.ts (5.9k tokens)
            ├── affiliates.ts (1100 tokens)
            ├── announcements.ts (400 tokens)
            ├── antigravity.ts (300 tokens)
            ├── apiKeys.ts (200 tokens)
            ├── audit.ts (400 tokens)
            ├── backup.ts (900 tokens)
            ├── channelMonitor.ts (1500 tokens)
            ├── channelMonitorTemplate.ts (700 tokens)
            ├── channels.ts (1000 tokens)
            ├── compliance.ts (200 tokens)
            ├── dashboard.ts (1700 tokens)
            ├── dataManagement.ts (1800 tokens)
            ├── errorPassthrough.ts (700 tokens)
            ├── gemini.ts (300 tokens)
            ├── grok.ts (900 tokens)
            ├── groups.ts (2.8k tokens)
            ├── index.ts (700 tokens)
            ├── ops.ts (7.5k tokens)
            ├── payment.ts (1100 tokens)
            ├── promo.ts (400 tokens)
            ├── proxies.ts (1400 tokens)
            ├── redeem.ts (1000 tokens)
            ├── riskControl.ts (1600 tokens)
            ├── scheduledTests.ts (400 tokens)
            ├── settings.ts (9.9k tokens)
            ├── subscriptions.ts (1000 tokens)
            ├── system.ts (600 tokens)
            ├── tlsFingerprintProfile.ts (500 tokens)
            ├── usage.ts (1100 tokens)
            ├── userAttributes.ts (600 tokens)
            ├── users.ts (2.2k tokens)
         ├── adminUIRequest.ts (500 tokens)
         ├── announcements.ts (100 tokens)
         ├── auth.ts (4.1k tokens)
         ├── batchImage.ts (1500 tokens)
         ├── channelMonitor.ts (400 tokens)
         ├── channels.ts (400 tokens)
         ├── client.ts (1800 tokens)
         ├── groups.ts (200 tokens)
         ├── index.ts (200 tokens)
         ├── keys.ts (800 tokens)
         ├── modelPlaza.ts (400 tokens)
         ├── passkey.ts (1200 tokens)
         ├── payment.ts (600 tokens)
         ├── redeem.ts (300 tokens)
         ├── setup.ts (300 tokens)
         ├── subscriptions.ts (400 tokens)
         ├── tokenRefresh.ts (1500 tokens)
         ├── totp.ts (600 tokens)
         ├── url.ts (300 tokens)
         ├── usage.ts (1900 tokens)
         ├── user.ts (1200 tokens)
      ├── assets/
         ├── icons/
            ├── airwallex.svg (200 tokens)
            ├── alipay.svg (300 tokens)
            ├── easypay.svg (800 tokens)
            ├── payment.svg (200 tokens)
            ├── stripe.svg (200 tokens)
            ├── wxpay.svg (200 tokens)
      ├── components/
         ├── AliyunCaptchaWidget.vue (1900 tokens)
         ├── CaptchaChallenge.vue (500 tokens)
         ├── Guide/
            ├── steps.ts (1700 tokens)
         ├── TencentCaptchaGate.vue (1200 tokens)
         ├── TurnstileWidget.vue (800 tokens)
         ├── __tests__/
            ├── AliyunCaptchaWidget.spec.ts (1200 tokens)
            ├── ApiKeyCreate.spec.ts (900 tokens)
            ├── Dashboard.spec.ts (900 tokens)
            ├── LoginForm.spec.ts (1000 tokens)
            ├── TencentCaptchaGate.spec.ts (2.5k tokens)
         ├── account/
            ├── AccountCapacityCell.vue (1900 tokens)
            ├── AccountGroupsCell.vue (1000 tokens)
            ├── AccountQuotaInfo.vue (1400 tokens)
            ├── AccountStatsModal.vue (5.5k tokens)
            ├── AccountStatusIndicator.vue (2.7k tokens)
            ├── AccountTestModal.vue (3.8k tokens)
            ├── AccountTodayStatsCell.vue (500 tokens)
            ├── AccountUsageCell.vue (12k tokens)
            ├── BulkEditAccountModal.vue (15.9k tokens)
            ├── CapacityBadge.vue (100 tokens)
            ├── CreateAccountModal.vue (51k tokens)
            ├── EditAccountModal.vue (40.8k tokens)
            ├── GrokBaseUrlPresets.vue (200 tokens)
            ├── GrokQuotaProbeCell.vue (1000 tokens)
            ├── HeaderOverrideEditor.vue (500 tokens)
            ├── HeaderOverrideJsonTools.vue (700 tokens)
            ├── ModelWhitelistSelector.vue (2.4k tokens)
            ├── OAuthAuthorizationFlow.vue (8.7k tokens)
            ├── OllamaCloudUsageCell.vue (200 tokens)
            ├── OllamaCloudUsageSettings.vue (2.2k tokens)
            ├── OpenAIQuotaResetCell.vue (3.2k tokens)
            ├── QuotaBadge.vue (400 tokens)
            ├── QuotaDimensionRow.vue (1200 tokens)
            ├── QuotaLimitCard.vue (2.1k tokens)
            ├── QuotaNotifyToggle.vue (400 tokens)
            ├── ReAuthAccountModal.vue (3.7k tokens)
            ├── SyncFromCrsModal.vue (2.6k tokens)
            ├── TempUnschedStatusModal.vue (1700 tokens)
            ├── UpstreamBillingRateCell.vue (2.1k tokens)
            ├── UsageProgressBar.vue (1300 tokens)
            ├── __tests__/
               ├── AccountStatusIndicator.spec.ts (1200 tokens)
               ├── AccountTestModal.spec.ts (1000 tokens)
               ├── AccountUsageCell.spec.ts (8.2k tokens)
               ├── BulkEditAccountModal.spec.ts (4k tokens)
               ├── CreateAccountModal.grok.spec.ts (300 tokens)
               ├── CreateAccountModal.spec.ts (2.6k tokens)
               ├── EditAccountModal.grokUpstream.spec.ts (2.2k tokens)
               ├── EditAccountModal.spec.ts (8.6k tokens)
               ├── GrokQuotaProbeCell.spec.ts (300 tokens)
               ├── ModelWhitelistSelector.spec.ts (500 tokens)
               ├── OllamaCloudUsageCell.spec.ts (700 tokens)
               ├── OllamaCloudUsageSettings.spec.ts (1300 tokens)
               ├── OpenAIQuotaResetCell.spark_shadow.spec.ts (2.4k tokens)
               ├── UpstreamBillingRateCell.spec.ts (2.8k tokens)
               ├── UsageProgressBar.spec.ts (800 tokens)
               ├── credentialsBuilder.spec.ts (3.7k tokens)
            ├── credentialsBuilder.ts (2k tokens)
            ├── index.ts (200 tokens)
         ├── admin/
            ├── AdminComplianceDialog.vue (1500 tokens)
            ├── ErrorPassthroughRulesModal.vue (5k tokens)
            ├── TLSFingerprintProfilesModal.vue (4.6k tokens)
            ├── account/
               ├── AccountActionMenu.vue (1400 tokens)
               ├── AccountBulkActionsBar.vue (800 tokens)
               ├── AccountStatsModal.vue (5.2k tokens)
               ├── AccountTableActions.vue (100 tokens)
               ├── AccountTableFilters.vue (800 tokens)
               ├── AccountTestModal.vue (3.9k tokens)
               ├── ImportDataModal.vue (2.1k tokens)
               ├── ReAuthAccountModal.vue (4k tokens)
               ├── ScheduledTestsPanel.vue (5.6k tokens)
               ├── __tests__/
                  ├── AccountActionMenu.spark_shadow.spec.ts (1200 tokens)
                  ├── AccountBulkActionsBar.spec.ts (300 tokens)
                  ├── AccountTestModal.spec.ts (1300 tokens)
            ├── announcements/
               ├── AnnouncementReadStatusDialog.vue (1300 tokens)
               ├── AnnouncementTargetingEditor.vue (2.8k tokens)
               ├── __tests__/
                  ├── AnnouncementReadStatusDialog.spec.ts (400 tokens)
            ├── channel/
               ├── IntervalRow.vue (1200 tokens)
               ├── ModelTagInput.vue (500 tokens)
               ├── PricingEntryCard.vue (3k tokens)
               ├── __tests__/
                  ├── types.spec.ts (700 tokens)
               ├── types.ts (1700 tokens)
            ├── group/
               ├── GroupRPMOverridesModal.vue (3.3k tokens)
               ├── GroupRateMultipliersModal.vue (3.8k tokens)
               ├── ReasoningEffortPolicyFields.vue (1400 tokens)
            ├── monitor/
               ├── MonitorActionsCell.spec.ts (500 tokens)
               ├── MonitorActionsCell.vue (500 tokens)
               ├── MonitorAdvancedRequestConfig.vue (2.2k tokens)
               ├── MonitorFiltersBar.vue (700 tokens)
               ├── MonitorFormDialog.vue (4.2k tokens)
               ├── MonitorKeyPickerDialog.vue (800 tokens)
               ├── MonitorPrimaryModelCell.vue (600 tokens)
               ├── MonitorRunResultDialog.vue (300 tokens)
               ├── MonitorTemplateApplyPickerDialog.vue (1100 tokens)
               ├── MonitorTemplateManagerDialog.vue (3.5k tokens)
            ├── payment/
               ├── AdminOrderDetail.vue (1400 tokens)
               ├── AdminOrderTable.vue (1800 tokens)
               ├── AdminRefundDialog.vue (1900 tokens)
               ├── DailyRevenueChart.vue (600 tokens)
               ├── OrderStatsCards.vue (700 tokens)
               ├── PaymentMethodChart.vue (700 tokens)
               ├── TopUsersLeaderboard.vue (500 tokens)
               ├── __tests__/
                  ├── orderCurrencyDisplay.spec.ts (800 tokens)
            ├── proxy/
               ├── ImportDataModal.vue (1200 tokens)
            ├── usage/
               ├── UsageCleanupDialog.vue (2.5k tokens)
               ├── UsageExportProgress.vue (100 tokens)
               ├── UsageFilters.vue (3.6k tokens)
               ├── UsageStatsCards.vue (1200 tokens)
               ├── UsageTable.vue (7.9k tokens)
               ├── UserTokenRanking.vue (1500 tokens)
               ├── __tests__/
                  ├── UsageFilters.spec.ts (1800 tokens)
                  ├── UsageStatsCards.spec.ts (400 tokens)
                  ├── UsageTable.spec.ts (3.5k tokens)
                  ├── UserTokenRanking.spec.ts (500 tokens)
            ├── user/
               ├── BulkEditUserModal.vue (1300 tokens)
               ├── GroupReplaceModal.vue (1000 tokens)
               ├── UserAllowedGroupsModal.vue (3k tokens)
               ├── UserApiKeysModal.vue (2.3k tokens)
               ├── UserBalanceHistoryModal.vue (2.6k tokens)
               ├── UserBalanceModal.vue (900 tokens)
               ├── UserCreateModal.vue (1000 tokens)
               ├── UserEditModal.vue (1400 tokens)
               ├── UserPlatformQuotaModal.vue (2.2k tokens)
               ├── __tests__/
                  ├── BulkEditUserModal.spec.ts (800 tokens)
                  ├── UserPlatformQuotaModal.spec.ts (1800 tokens)
         ├── auth/
            ├── DingTalkOAuthSection.vue (400 tokens)
            ├── EmailOAuthButtons.vue (600 tokens)
            ├── GitHubMark.vue (100 tokens)
            ├── GoogleMark.vue (100 tokens)
            ├── LinuxDoOAuthSection.vue (600 tokens)
            ├── LoginAgreementPrompt.vue (1900 tokens)
            ├── OidcOAuthSection.vue (400 tokens)
            ├── PendingOAuthCreateAccountForm.vue (2.3k tokens)
            ├── TotpLoginModal.vue (1400 tokens)
            ├── TotpStepUpDialog.vue (1200 tokens)
            ├── WechatOAuthSection.vue (600 tokens)
            ├── __tests__/
               ├── EmailOAuthButtons.spec.ts (700 tokens)
               ├── OAuthLoginSections.spec.ts (300 tokens)
               ├── PendingOAuthCreateAccountForm.spec.ts (2.8k tokens)
               ├── TotpLoginModal.spec.ts (200 tokens)
               ├── WechatOAuthSection.spec.ts (1300 tokens)
         ├── channels/
            ├── AvailableChannelsTable.vue (3.3k tokens)
            ├── PricingRow.vue (100 tokens)
            ├── SupportedModelChip.vue (2.2k tokens)
            ├── __tests__/
               ├── AvailableChannelsTable.spec.ts (1300 tokens)
         ├── charts/
            ├── EndpointDistributionChart.vue (2.2k tokens)
            ├── GroupDistributionChart.vue (2000 tokens)
            ├── ModelDistributionChart.vue (4k tokens)
            ├── TokenUsageTrend.vue (1200 tokens)
            ├── UserBreakdownSubTable.vue (500 tokens)
            ├── __tests__/
               ├── GroupDistributionChart.spec.ts (700 tokens)
               ├── ModelDistributionChart.spec.ts (1300 tokens)
               ├── TokenUsageTrend.spec.ts (600 tokens)
         ├── common/
            ├── AnnouncementBell.vue (4.4k tokens)
            ├── AnnouncementPopup.vue (1500 tokens)
            ├── AutoRefreshButton.vue (800 tokens)
            ├── BaseDialog.vue (900 tokens)
            ├── ConfirmDialog.vue (400 tokens)
            ├── DataTable.vue (7.3k tokens)
            ├── DateRangePicker.vue (2.3k tokens)
            ├── EmptyState.vue (400 tokens)
            ├── ExportProgressDialog.vue (500 tokens)
            ├── GrokFreeIcon.vue (200 tokens)
            ├── GroupBadge.vue (1400 tokens)
            ├── GroupCapacityBadge.vue (700 tokens)
            ├── GroupOptionItem.vue (900 tokens)
            ├── GroupSelector.vue (900 tokens)
            ├── HelpTooltip.vue (800 tokens)
            ├── ImageUpload.vue (900 tokens)
            ├── Input.vue (600 tokens)
            ├── IpGeoBatchToolbar.vue (300 tokens)
            ├── IpGeoCell.vue (600 tokens)
            ├── LoadingSpinner.vue (300 tokens)
            ├── LocaleSwitcher.vue (600 tokens)
            ├── ModelIcon.vue (7.4k tokens)
            ├── NavigationProgress.vue (400 tokens)
            ├── Pagination.vue (1700 tokens)
            ├── PlatformIcon.vue (1100 tokens)
            ├── PlatformTypeBadge.vue (1800 tokens)
            ├── ProxyAdBanner.vue (100 tokens)
            ├── ProxySelector.vue (2.5k tokens)
            ├── README.md (1300 tokens)
            ├── SearchInput.vue (200 tokens)
            ├── Select.vue (3.6k tokens)
            ├── Skeleton.vue (200 tokens)
            ├── StatCard.vue (400 tokens)
            ├── StatusBadge.vue (100 tokens)
            ├── SubscriptionProgressMini.vue (2.4k tokens)
            ├── TextArea.vue (400 tokens)
            ├── Toast.vue (900 tokens)
            ├── Toggle.vue (200 tokens)
            ├── VersionBadge.vue (8.1k tokens)
            ├── __tests__/
               ├── AnnouncementPopup.spec.ts (900 tokens)
               ├── DataTable.spec.ts (2.4k tokens)
               ├── DateRangePicker.spec.ts (600 tokens)
               ├── GroupOptionItem.spec.ts (300 tokens)
               ├── HelpTooltip.spec.ts (400 tokens)
               ├── IpGeoCell.spec.ts (800 tokens)
               ├── NavigationProgress.spec.ts (500 tokens)
               ├── PlatformTypeBadge.grok.spec.ts (600 tokens)
               ├── Select.spec.ts (600 tokens)
            ├── index.ts (100 tokens)
            ├── types.ts
         ├── icons/
            ├── Icon.vue (3.4k tokens)
            ├── index.ts
         ├── keys/
            ├── EndpointPopover.vue (1200 tokens)
            ├── UseKeyModal.vue (8.3k tokens)
            ├── __tests__/
               ├── EndpointPopover.spec.ts (400 tokens)
               ├── UseKeyModal.spec.ts (4.1k tokens)
         ├── layout/
            ├── AppHeader.vue (3.5k tokens)
            ├── AppLayout.vue (300 tokens)
            ├── AppSidebar.vue (8.2k tokens)
            ├── AuthLayout.vue (600 tokens)
            ├── EXAMPLES.md (2.6k tokens)
            ├── INTEGRATION.md (2.2k tokens)
            ├── README.md (900 tokens)
            ├── TablePageLayout.vue (500 tokens)
            ├── __tests__/
               ├── AppSidebar.spec.ts (500 tokens)
               ├── TablePageLayout.spec.ts (200 tokens)
               ├── docUrlSanitization.spec.ts (300 tokens)
               ├── siteLogoSanitization.spec.ts (300 tokens)
            ├── index.ts (100 tokens)
         ├── modelPlaza/
            ├── ModelPlazaContent.vue (1200 tokens)
            ├── PlazaFilterBar.vue (1600 tokens)
            ├── PlazaGroupSection.vue (700 tokens)
            ├── PlazaModelPricingTable.vue (3.2k tokens)
            ├── PlazaNavBar.vue (600 tokens)
            ├── __tests__/
               ├── PlazaModelPricingTable.spec.ts (2.5k tokens)
         ├── payment/
            ├── AmountInput.vue (600 tokens)
            ├── OrderStatusBadge.vue (500 tokens)
            ├── OrderTable.vue (700 tokens)
            ├── PaymentMethodSelector.vue (800 tokens)
            ├── PaymentProviderDialog.vue (7.1k tokens)
            ├── PaymentProviderList.vue (1000 tokens)
            ├── PaymentQRDialog.vue (2.1k tokens)
            ├── PaymentStatusPanel.vue (4.2k tokens)
            ├── ProviderCard.vue (1000 tokens)
            ├── StripePaymentInline.vue (1700 tokens)
            ├── SubscriptionPlanCard.vue (1700 tokens)
            ├── ToggleSwitch.vue (200 tokens)
            ├── __tests__/
               ├── PaymentMethodSelector.spec.ts (500 tokens)
               ├── PaymentProviderDialog.spec.ts (2000 tokens)
               ├── PaymentQRDialog.spec.ts (500 tokens)
               ├── PaymentStatusPanel.spec.ts (1900 tokens)
               ├── SubscriptionPlanCard.spec.ts (1200 tokens)
               ├── alipayDeepLink.spec.ts (900 tokens)
               ├── currency.spec.ts (200 tokens)
               ├── paymentFlow.spec.ts (3.1k tokens)
               ├── providerConfig.spec.ts (800 tokens)
               ├── validity.spec.ts (300 tokens)
            ├── alipayDeepLink.ts (600 tokens)
            ├── currency.ts (300 tokens)
            ├── orderUtils.ts (200 tokens)
            ├── paymentFlow.ts (2.3k tokens)
            ├── providerConfig.ts (1700 tokens)
            ├── validity.ts (200 tokens)
         ├── user/
            ├── MonitorDetailDialog.vue (800 tokens)
            ├── PlatformCostCell.vue (200 tokens)
            ├── PlatformUsageBreakdown.vue (700 tokens)
            ├── UserAttributeForm.vue (1100 tokens)
            ├── UserAttributesConfigModal.vue (3k tokens)
            ├── UserConcurrencyCell.vue (300 tokens)
            ├── UserErrorDetailModal.vue (1000 tokens)
            ├── UserErrorRequestsTable.vue (1500 tokens)
            ├── UserPlatformQuotaCell.vue (500 tokens)
            ├── __tests__/
               ├── UserPlatformQuotaCell.spec.ts (500 tokens)
            ├── dashboard/
               ├── UserDashboardCharts.vue (1100 tokens)
               ├── UserDashboardQuickActions.vue (900 tokens)
               ├── UserDashboardRecentUsage.vue (600 tokens)
               ├── UserDashboardStats.vue (3.5k tokens)
            ├── monitor/
               ├── MonitorAvailabilityRow.vue (200 tokens)
               ├── MonitorCard.vue (900 tokens)
               ├── MonitorCardGrid.vue (500 tokens)
               ├── MonitorHero.vue (800 tokens)
               ├── MonitorMetricPair.vue (300 tokens)
               ├── MonitorTimeline.vue (700 tokens)
               ├── ProviderIcon.vue (1100 tokens)
            ├── profile/
               ├── ProfileAccountBindingsCard.vue (200 tokens)
               ├── ProfileAvatarCard.vue (1700 tokens)
               ├── ProfileBalanceNotifyCard.vue (3.2k tokens)
               ├── ProfileEditForm.vue (500 tokens)
               ├── ProfileIdentityBindingsSection.vue (4.2k tokens)
               ├── ProfileInfoCard.vue (2.6k tokens)
               ├── ProfilePasskeyCard.vue (2.1k tokens)
               ├── ProfilePasswordForm.vue (700 tokens)
               ├── ProfileTotpCard.vue (1100 tokens)
               ├── TotpDisableDialog.vue (1200 tokens)
               ├── TotpSetupModal.vue (2.7k tokens)
               ├── __tests__/
                  ├── ProfileAvatarCard.spec.ts (1700 tokens)
                  ├── ProfileIdentityBindingsSection.spec.ts (4.4k tokens)
                  ├── ProfileInfoCard.spec.ts (1200 tokens)
                  ├── ProfilePasswordForm.spec.ts (600 tokens)
                  ├── totp-timer-cleanup.spec.ts (900 tokens)
      ├── composables/
         ├── __tests__/
            ├── useAntigravityOAuth.spec.ts (300 tokens)
            ├── useClipboard.spec.ts (800 tokens)
            ├── useForm.spec.ts (700 tokens)
            ├── useGrokOAuth.spec.ts (400 tokens)
            ├── useKeyedDebouncedSearch.spec.ts (500 tokens)
            ├── useModelWhitelist.spec.ts (1000 tokens)
            ├── useNavigationLoading.spec.ts (900 tokens)
            ├── useOpenAIOAuth.spec.ts (600 tokens)
            ├── usePersistedPageSize.spec.ts (100 tokens)
            ├── useRoutePrefetch.spec.ts (1600 tokens)
            ├── useStepUp.spec.ts (700 tokens)
            ├── useSwipeSelect.spec.ts (600 tokens)
            ├── useTableLoader.spec.ts (1300 tokens)
         ├── useAccountOAuth.ts (1000 tokens)
         ├── useAntigravityOAuth.ts (800 tokens)
         ├── useAutoRefresh.ts (500 tokens)
         ├── useBatchImageAccess.ts (400 tokens)
         ├── useChannelMonitorFormat.ts (1500 tokens)
         ├── useClipboard.ts (300 tokens)
         ├── useForm.ts (200 tokens)
         ├── useGeminiOAuth.ts (1000 tokens)
         ├── useGrokOAuth.ts (900 tokens)
         ├── useKeyedDebouncedSearch.ts (500 tokens)
         ├── useModelWhitelist.ts (5.6k tokens)
         ├── useNavigationLoading.ts (500 tokens)
         ├── useOnboardingTour.ts (3.9k tokens)
         ├── useOpenAIOAuth.ts (1300 tokens)
         ├── usePersistedPageSize.ts (200 tokens)
         ├── useQuotaNotifyState.ts (500 tokens)
         ├── useRoutePrefetch.ts (1000 tokens)
         ├── useStepUp.ts (700 tokens)
         ├── useSwipeSelect.ts (4.5k tokens)
         ├── useTableLoader.ts (500 tokens)
         ├── useTableSelection.ts (500 tokens)
      ├── constants/
         ├── account.ts (600 tokens)
         ├── channel.ts (200 tokens)
         ├── channelMonitor.ts (300 tokens)
      ├── features/
         ├── prompt-audit/
            ├── PromptAuditView.vue (4k tokens)
            ├── __tests__/
               ├── PromptAuditView.spec.ts (3.1k tokens)
               ├── api.spec.ts (500 tokens)
               ├── components.spec.ts (3.7k tokens)
               ├── integrationSurface.spec.ts (400 tokens)
               ├── viewModel.spec.ts (700 tokens)
            ├── api.ts (700 tokens)
            ├── components/
               ├── EndpointPool.vue (2.6k tokens)
               ├── EventDetailDialog.vue (2.2k tokens)
               ├── EventWorkspace.vue (2.6k tokens)
               ├── FilterDeleteDialog.vue (2.2k tokens)
               ├── PolicyPanel.vue (1300 tokens)
               ├── RuntimeOverview.vue (1400 tokens)
            ├── types.ts (1100 tokens)
            ├── viewModel.ts (1200 tokens)
      ├── i18n/
         ├── __tests__/
            ├── ipGeoLocales.spec.ts (200 tokens)
            ├── localesMessageCompile.spec.ts (300 tokens)
            ├── localesNoKeyCollision.spec.ts (600 tokens)
            ├── openaiFastPolicyLocales.spec.ts (200 tokens)
            ├── opsLocaleKeys.spec.ts (300 tokens)
            ├── riskControlLocales.spec.ts (200 tokens)
            ├── usageServiceTierLocales.spec.ts (100 tokens)
            ├── wsModeLocaleDesc.spec.ts (100 tokens)
         ├── index.ts (600 tokens)
         ├── locales/
            ├── en/
               ├── admin/
                  ├── accounts.ts (16.5k tokens)
                  ├── audit.ts (300 tokens)
                  ├── channels.ts (7.4k tokens)
                  ├── index.ts (100 tokens)
                  ├── ops.ts (7.4k tokens)
                  ├── overview.ts (10.6k tokens)
                  ├── promptAudit.ts (2.1k tokens)
                  ├── resources.ts (4.9k tokens)
                  ├── settings.ts (17.4k tokens)
               ├── batchImage.ts (2.5k tokens)
               ├── common.ts (4.1k tokens)
               ├── dashboard.ts (7.3k tokens)
               ├── index.ts (100 tokens)
               ├── landing.ts (1700 tokens)
               ├── misc.ts (7.8k tokens)
            ├── zh/
               ├── admin/
                  ├── accounts.ts (11.5k tokens)
                  ├── audit.ts (200 tokens)
                  ├── channels.ts (5.1k tokens)
                  ├── index.ts (100 tokens)
                  ├── ops.ts (5.3k tokens)
                  ├── overview.ts (7.4k tokens)
                  ├── promptAudit.ts (1300 tokens)
                  ├── resources.ts (3.6k tokens)
                  ├── settings.ts (11.6k tokens)
               ├── batchImage.ts (1500 tokens)
               ├── common.ts (2.7k tokens)
               ├── dashboard.ts (5.2k tokens)
               ├── index.ts (100 tokens)
               ├── landing.ts (1200 tokens)
               ├── misc.ts (5.7k tokens)
      ├── main.ts (400 tokens)
      ├── router/
         ├── README.md (1600 tokens)
         ├── __tests__/
            ├── feature-access.spec.ts (1000 tokens)
            ├── guards.spec.ts (3.2k tokens)
            ├── title.spec.ts (300 tokens)
            ├── wechat-route.spec.ts (400 tokens)
         ├── index.ts (5.6k tokens)
         ├── meta.d.ts (omitted)
         ├── setupRedirect.ts
         ├── title.ts (300 tokens)
      ├── stores/
         ├── README.md (1000 tokens)
         ├── __tests__/
            ├── app.spec.ts (2.8k tokens)
            ├── auth.spec.ts (2.3k tokens)
            ├── subscriptions.spec.ts (1400 tokens)
         ├── adminCompliance.ts (700 tokens)
         ├── adminSettings.ts (1000 tokens)
         ├── announcements.ts (700 tokens)
         ├── app.ts (2.8k tokens)
         ├── auth.ts (3.2k tokens)
         ├── index.ts (100 tokens)
         ├── onboarding.ts (500 tokens)
         ├── payment.ts (600 tokens)
         ├── subscriptions.ts (700 tokens)
      ├── style.css (3.8k tokens)
      ├── styles/
         ├── announcement-markdown.css (600 tokens)
         ├── onboarding.css (1500 tokens)
      ├── types/
         ├── global.d.ts (omitted)
         ├── index.ts (12.1k tokens)
         ├── payment.ts (1200 tokens)
      ├── utils/
         ├── __tests__/
            ├── accountSelection.spec.ts (400 tokens)
            ├── accountUsageRefresh.spec.ts (300 tokens)
            ├── authError.spec.ts (200 tokens)
            ├── branding.spec.ts (100 tokens)
            ├── ccswitchImport.spec.ts (600 tokens)
            ├── device.spec.ts (600 tokens)
            ├── embedded-url.spec.ts (500 tokens)
            ├── floatingPanel.spec.ts (200 tokens)
            ├── formatCompactNumber.spec.ts (200 tokens)
            ├── formatDateLocalInput.spec.ts (100 tokens)
            ├── formatDateTimeToMinute.spec.ts (100 tokens)
            ├── formatMultiplier.spec.ts (200 tokens)
            ├── ipGeoLookup.spec.ts (2k tokens)
            ├── latencyHealth.spec.ts (200 tokens)
            ├── oauthAffiliate.spec.ts (300 tokens)
            ├── openaiWsMode.spec.ts (600 tokens)
            ├── proxyExpiry.spec.ts (400 tokens)
            ├── registrationEmailPolicy.spec.ts (1100 tokens)
            ├── stableObjectKey.spec.ts (200 tokens)
            ├── subscriptionQuota.spec.ts (500 tokens)
            ├── tablePreferences.spec.ts (500 tokens)
            ├── usageServiceTier.spec.ts (300 tokens)
         ├── accountSelection.ts (200 tokens)
         ├── accountUsageRefresh.ts (200 tokens)
         ├── apiError.ts (1000 tokens)
         ├── authError.ts (100 tokens)
         ├── billingMode.ts (400 tokens)
         ├── branding.ts (100 tokens)
         ├── ccswitchImport.ts (400 tokens)
         ├── device.ts (500 tokens)
         ├── embedded-url.ts (300 tokens)
         ├── errorBadges.ts (500 tokens)
         ├── errorCategory.ts (200 tokens)
         ├── featureFlags.ts (1200 tokens)
         ├── floatingPanel.ts (400 tokens)
         ├── format.ts (2000 tokens)
         ├── formatters.ts (100 tokens)
         ├── imageUsage.ts (700 tokens)
         ├── ipGeoLookup.ts (1200 tokens)
         ├── latencyHealth.ts (400 tokens)
         ├── maskApiKey.ts (100 tokens)
         ├── oauthAffiliate.ts (700 tokens)
         ├── openaiWsMode.ts (500 tokens)
         ├── peak-rate.ts (200 tokens)
         ├── platformColors.ts (1900 tokens)
         ├── pricing.ts (200 tokens)
         ├── proxyExpiry.ts (300 tokens)
         ├── registrationEmailPolicy.ts (1100 tokens)
         ├── sanitize.ts
         ├── stableObjectKey.ts (100 tokens)
         ├── subscriptionQuota.ts (600 tokens)
         ├── tablePreferences.ts (400 tokens)
         ├── tencentCaptcha.ts (800 tokens)
         ├── url.ts (200 tokens)
         ├── usageLoadQueue.ts (100 tokens)
         ├── usagePricing.ts (300 tokens)
         ├── usageRequestType.ts (200 tokens)
         ├── usageServiceTier.ts (200 tokens)
      ├── views/
         ├── HomeView.vue (5.6k tokens)
         ├── KeyUsageView.vue (9.1k tokens)
         ├── ModelPlazaView.vue (300 tokens)
         ├── NotFoundView.vue (600 tokens)
         ├── __tests__/
            ├── HomeView.compact.spec.ts (800 tokens)
            ├── KeyUsageView.spec.ts (1400 tokens)
         ├── admin/
            ├── AccountsView.vue (18.5k tokens)
            ├── AnnouncementsView.vue (4.3k tokens)
            ├── AuditLogView.vue (5k tokens)
            ├── BackupView.vue (7k tokens)
            ├── ChannelMonitorView.vue (2.2k tokens)
            ├── ChannelsView.vue (13.3k tokens)
            ├── DashboardView.vue (5.2k tokens)
            ├── GroupsView.vue (48.9k tokens)
            ├── PromoCodesView.vue (4.8k tokens)
            ├── ProxiesView.vue (14.6k tokens)
            ├── RedeemView.vue (8.3k tokens)
            ├── RiskControlView.vue (23.6k tokens)
            ├── SettingsView.vue (103.4k tokens)
            ├── SubscriptionsView.vue (11.2k tokens)
            ├── UsageView.vue (7.5k tokens)
            ├── UsersView.vue (14.9k tokens)
            ├── __tests__/
               ├── AccountsView.bulkEdit.spec.ts (4.1k tokens)
               ├── AccountsView.schedulerScore.spec.ts (1400 tokens)
               ├── AccountsView.selectAllResults.spec.ts (1400 tokens)
               ├── AccountsView.sparkShadow.spec.ts (2.8k tokens)
               ├── AccountsView.usageWindowsHint.spec.ts (1400 tokens)
               ├── ChannelMonitorView.duplicate.spec.ts (1300 tokens)
               ├── ChannelMonitorView.grok.spec.ts (900 tokens)
               ├── DashboardView.spec.ts (700 tokens)
               ├── GroupsView.columnSettings.spec.ts (2k tokens)
               ├── GroupsView.duplicate.spec.ts (1500 tokens)
               ├── RedeemView.batchUpdate.spec.ts (1000 tokens)
               ├── RiskControlView.spec.ts (2.5k tokens)
               ├── SettingsView.spec.ts (13k tokens)
               ├── UsageView.spec.ts (4.1k tokens)
               ├── UsersView.spec.ts (2.3k tokens)
               ├── apiKeyGroupFilterOptions.spec.ts (800 tokens)
               ├── codexFingerprintSignals.spec.ts (200 tokens)
               ├── groupsImagePricing.spec.ts (400 tokens)
               ├── groupsMessagesDispatch.spec.ts (600 tokens)
               ├── groupsModelsList.spec.ts (700 tokens)
               ├── groupsModelsListCandidates.spec.ts (400 tokens)
               ├── groupsModelsListLayout.spec.ts (100 tokens)
               ├── groupsProfitControl.spec.ts (1100 tokens)
               ├── groupsReasoningEffort.spec.ts (600 tokens)
               ├── groupsSupportedModelScopes.spec.ts (200 tokens)
            ├── affiliates/
               ├── AdminAffiliateInvitesView.vue
               ├── AdminAffiliateRebatesView.vue
               ├── AdminAffiliateRecordsTable.vue (3.3k tokens)
               ├── AdminAffiliateTransfersView.vue
            ├── apiKeyGroupFilterOptions.ts (500 tokens)
            ├── codexFingerprintSignals.ts (300 tokens)
            ├── groupsImagePricing.ts (500 tokens)
            ├── groupsMessagesDispatch.ts (500 tokens)
            ├── groupsModelsList.ts (600 tokens)
            ├── groupsModelsListCandidates.ts (200 tokens)
            ├── groupsProfitControl.ts (400 tokens)
            ├── groupsReasoningEffort.ts (700 tokens)
            ├── groupsSupportedModelScopes.ts
            ├── ops/
               ├── OpsDashboard.vue (5.5k tokens)
               ├── components/
                  ├── OpsAlertEventsCard.vue (6.5k tokens)
                  ├── OpsAlertRulesCard.vue (5k tokens)
                  ├── OpsConcurrencyCard.vue (5k tokens)
                  ├── OpsDashboardHeader.vue (14.1k tokens)
                  ├── OpsDashboardSkeleton.vue (1100 tokens)
                  ├── OpsEmailNotificationCard.vue (4.1k tokens)
                  ├── OpsErrorDetailModal.vue (3.2k tokens)
                  ├── OpsErrorDetailsModal.vue (1900 tokens)
                  ├── OpsErrorDistributionChart.vue (1200 tokens)
                  ├── OpsErrorLogTable.vue (2.9k tokens)
                  ├── OpsErrorTrendChart.vue (1300 tokens)
                  ├── OpsLatencyChart.vue (600 tokens)
                  ├── OpsOpenAITokenStatsCard.vue (2.3k tokens)
                  ├── OpsRequestDetailsModal.vue (2.9k tokens)
                  ├── OpsRuntimeSettingsCard.vue (4.7k tokens)
                  ├── OpsSettingsDialog.vue (5.5k tokens)
                  ├── OpsSwitchRateTrendChart.vue (900 tokens)
                  ├── OpsSystemLogTable.vue (4.7k tokens)
                  ├── OpsThroughputTrendChart.vue (2.1k tokens)
                  ├── __tests__/
                     ├── OpsErrorLogTable.spec.ts (600 tokens)
                     ├── OpsErrorScopeCharts.spec.ts (800 tokens)
                     ├── OpsOpenAITokenStatsCard.spec.ts (1100 tokens)
                     ├── OpsSystemLogTable.spec.ts (800 tokens)
                     ├── OpsThroughputTrendChart.spec.ts (300 tokens)
               ├── types.ts (100 tokens)
               ├── utils/
                  ├── __tests__/
                     ├── errorDetailResponse.spec.ts (900 tokens)
                     ├── opsErrorParams.spec.ts (100 tokens)
                  ├── errorDetailResponse.ts (500 tokens)
                  ├── opsErrorParams.ts (100 tokens)
                  ├── opsFormatters.ts (500 tokens)
            ├── orders/
               ├── AdminOrdersView.vue (3.6k tokens)
               ├── AdminPaymentDashboardView.vue (1300 tokens)
               ├── AdminPaymentPlansView.vue (1700 tokens)
               ├── PlanEditDialog.vue (2.3k tokens)
               ├── __tests__/
                  ├── AdminPaymentPlansView.spec.ts (500 tokens)
                  ├── PlanEditDialog.spec.ts (1000 tokens)
            ├── settings/
               ├── EmailTemplateEditor.vue (4.8k tokens)
               ├── OpenAIFastPolicyUserSelector.vue (1400 tokens)
               ├── __tests__/
                  ├── OpenAIFastPolicyUserSelector.spec.ts (700 tokens)
         ├── auth/
            ├── DingTalkCallbackView.vue (5.8k tokens)
            ├── DingTalkEmailCompletionView.vue (900 tokens)
            ├── EmailVerifyView.vue (5.4k tokens)
            ├── ForgotPasswordView.vue (2.1k tokens)
            ├── LinuxDoCallbackView.vue (5.6k tokens)
            ├── LoginView.vue (4.9k tokens)
            ├── OAuthCallbackView.vue (2.9k tokens)
            ├── OidcCallbackView.vue (5.7k tokens)
            ├── README.md (1500 tokens)
            ├── RegisterView.vue (7.3k tokens)
            ├── ResetPasswordView.vue (2.2k tokens)
            ├── USAGE_EXAMPLES.md (3k tokens)
            ├── VISUAL_GUIDE.md (3.5k tokens)
            ├── WechatCallbackView.vue (7.3k tokens)
            ├── WechatPaymentCallbackView.vue (900 tokens)
            ├── __tests__/
               ├── EmailVerifyView.spec.ts (4.6k tokens)
               ├── LinuxDoCallbackView.spec.ts (4.8k tokens)
               ├── OAuthCallbackView.spec.ts (1400 tokens)
               ├── OidcCallbackView.spec.ts (4.4k tokens)
               ├── RegisterView.spec.ts (600 tokens)
               ├── TencentCaptchaActionGate.spec.ts (1400 tokens)
               ├── TencentCaptchaForgotPassword.spec.ts (600 tokens)
               ├── WechatCallbackView.spec.ts (7.2k tokens)
               ├── WechatPaymentCallbackView.spec.ts (700 tokens)
            ├── index.ts
         ├── public/
            ├── LegalDocumentView.vue (1800 tokens)
         ├── setup/
            ├── SetupWizardView.vue (4.6k tokens)
         ├── user/
            ├── AffiliateView.vue (2.1k tokens)
            ├── AirwallexPaymentView.vue (900 tokens)
            ├── AvailableChannelsView.vue (900 tokens)
            ├── BatchImageGuideView.vue (21.7k tokens)
            ├── ChannelStatusView.vue (1000 tokens)
            ├── CustomPageView.vue (3.3k tokens)
            ├── DashboardView.vue (800 tokens)
            ├── KeysView.vue (15.8k tokens)
            ├── PaymentQRCodeView.vue (1500 tokens)
            ├── PaymentResultView.vue (3.3k tokens)
            ├── PaymentView.vue (10k tokens)
            ├── ProfileView.vue (900 tokens)
            ├── RedeemView.vue (3.6k tokens)
            ├── StripePaymentView.vue (2.8k tokens)
            ├── StripePopupView.vue (1400 tokens)
            ├── SubscriptionsView.vue (3.2k tokens)
            ├── UsageView.vue (6.5k tokens)
            ├── UserOrdersView.vue (1600 tokens)
            ├── __tests__/
               ├── AffiliateView.spec.ts (600 tokens)
               ├── AirwallexPaymentView.spec.ts (700 tokens)
               ├── KeysView.spec.ts (2.5k tokens)
               ├── PaymentResultView.spec.ts (2.8k tokens)
               ├── PaymentView.spec.ts (4.1k tokens)
               ├── ProfileView.spec.ts (600 tokens)
               ├── StripePaymentView.spec.ts (700 tokens)
               ├── UsageView.spec.ts (2000 tokens)
               ├── paymentUx.spec.ts (600 tokens)
               ├── paymentWechatResume.spec.ts (300 tokens)
               ├── stripeLazyLoading.spec.ts (200 tokens)
            ├── paymentUx.ts (700 tokens)
            ├── paymentWechatResume.ts (500 tokens)
      ├── vite-env.d.ts (omitted)
   ├── tailwind.config.js (900 tokens)
   ├── tsconfig.json (200 tokens)
   ├── tsconfig.node.json
   ├── vite.config.ts (900 tokens)
   ├── vitest.config.ts (200 tokens)
├── openspec/
   ├── changes/
      ├── add-openai-compatible-prompt-audit/
         ├── .openspec.yaml
         ├── README.md (100 tokens)
         ├── design.md (4.7k tokens)
         ├── implementation-evidence.md (300 tokens)
         ├── implementation-guide.md (4.4k tokens)
         ├── proposal.md (600 tokens)
         ├── source-baseline.md (1000 tokens)
         ├── source-feature-map.md (2.1k tokens)
         ├── source-freeze/
            ├── MANIFEST.md (500 tokens)
            ├── aicodex-prompt-audit-tracked.patch (23.8k tokens)
            ├── aicodex-prompt-audit-untracked.tar.gz
         ├── specs/
            ├── prompt-input-audit/
               ├── spec.md (1900 tokens)
            ├── prompt-input-guard/
               ├── spec.md (1500 tokens)
            ├── security-audit-console/
               ├── spec.md (1100 tokens)
         ├── tasks.md (2.4k tokens)
         ├── verification.md (4.3k tokens)
   ├── config.yaml (100 tokens)
├── skills/
   ├── sub2api-admin/
      ├── SKILL.md (600 tokens)
      ├── agents/
         ├── openai.yaml
      ├── references/
         ├── admin-cli.md (1800 tokens)
      ├── scripts/
         ├── sub2api-admin.js (5.3k tokens)
├── tools/
   ├── check_pnpm_audit_exceptions.py (1700 tokens)
```


## /.dockerignore

```dockerignore path="/.dockerignore" 
# =============================================================================
# Docker Ignore File for Sub2API
# =============================================================================

# Git
.git
.gitignore
.gitattributes

# Documentation
*.md
!deploy/DOCKER.md
docs/
# admin-compliance gate (LegalDocumentView.vue) build-time imports
# docs/legal/*.md?raw into the frontend bundle; keep that subtree in the
# Docker build context even though docs/ is otherwise excluded.
!docs/legal/
!docs/legal/*.md

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS files
.DS_Store
Thumbs.db

# Build artifacts
dist/
build/

# Node modules (will be installed in container)
frontend/node_modules/
node_modules/

# Go build cache (will be built in container)
backend/vendor/

# Test files
*_test.go
**/*.test.js
coverage/
.nyc_output/

# Environment files
.env
.env.*
!.env.example

# Local config
config.yaml
config.local.yaml

# Logs
*.log
logs/

# Temporary files
tmp/
temp/
*.tmp

# Deploy files (not needed in image)
deploy/install.sh
deploy/sub2api.service
deploy/sub2api-sudoers
deploy/data/
deploy/postgres_data/
deploy/redis_data/

# GoReleaser
.goreleaser.yaml

# GitHub
.github/

# Claude files
.claude/
issues/
CLAUDE.md

```

## /.github/audit-exceptions.yml

```yml path="/.github/audit-exceptions.yml" 
version: 1
exceptions:
  - package: xlsx
    advisory: "GHSA-4r6h-8v6p-xvw6"
    severity: high
    reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2023-30533)"
    mitigation: "Load only on export; restrict export permissions and data scope"
    expires_on: "2026-10-06"
    owner: "security@your-domain"
  - package: xlsx
    advisory: "GHSA-5pgg-2g8v-p4x9"
    severity: high
    reason: "Admin export only; switched to dynamic import to reduce exposure (CVE-2024-22363)"
    mitigation: "Load only on export; restrict export permissions and data scope"
    expires_on: "2026-10-06"
    owner: "security@your-domain"
  - package: lodash
    advisory: "GHSA-r5fr-rjxr-66jc"
    severity: high
    reason: "lodash _.template not used with untrusted input; only internal admin UI templates"
    mitigation: "No user-controlled template strings; plan to migrate to lodash-es tree-shaken imports"
    expires_on: "2026-07-02"
    owner: "security@your-domain"
  - package: lodash-es
    advisory: "GHSA-r5fr-rjxr-66jc"
    severity: high
    reason: "lodash-es _.template not used with untrusted input; only internal admin UI templates"
    mitigation: "No user-controlled template strings; plan to migrate to native JS alternatives"
    expires_on: "2026-07-02"
    owner: "security@your-domain"
  - package: axios
    advisory: "GHSA-3p68-rc4w-qgx5"
    severity: critical
    reason: "NO_PROXY bypass not exploitable; all API calls go to known endpoints via server-side proxy"
    mitigation: "Proxy configuration not user-controlled; upgrade when axios releases fix"
    expires_on: "2026-07-10"
    owner: "security@your-domain"

```

## /.github/workflows/backend-ci.yml

```yml path="/.github/workflows/backend-ci.yml" 
name: CI

on:
  push:
  pull_request:

permissions:
  contents: read

jobs:
  shell:
    runs-on: macos-15
    steps:
      - uses: actions/checkout@v6
      - name: Check deployment scripts
        run: |
          /bin/bash -n deploy/apple-container.sh
          /bin/bash deploy/tests/apple-container-test.sh
          /bin/sh deploy/tests/docker-compose-security-test.sh
          /bin/sh deploy/tests/docker-runtime-resources-test.sh
          /bin/sh deploy/test-caddyfile-cache.sh

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version-file: backend/go.mod
          check-latest: false
          cache: true
          cache-dependency-path: backend/go.sum
      - name: Verify Go version
        run: |
          go version | grep -q 'go1.26.5'
      - name: Unit tests
        working-directory: backend
        run: make test-unit
      - name: Integration tests
        working-directory: backend
        run: make test-integration

  frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v6
        with:
          version: 9
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'pnpm'
          cache-dependency-path: frontend/pnpm-lock.yaml
      - name: Install frontend dependencies
        working-directory: frontend
        run: pnpm install --frozen-lockfile
      - name: Frontend typecheck and critical vitest
        run: make test-frontend

  golangci-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version-file: backend/go.mod
          check-latest: false
          cache: true
          cache-dependency-path: backend/go.sum
      - name: Verify Go version
        run: |
          go version | grep -q 'go1.26.5'
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v9
        with:
          version: v2.9
          args: --timeout=30m
          working-directory: backend

```

## /.github/workflows/cla.yml

```yml path="/.github/workflows/cla.yml" 
name: "CLA Assistant"

on:
  issue_comment:
    types: [created]
  pull_request_target:
    types: [opened, reopened, closed, synchronize]

permissions:
  actions: write
  contents: write
  pull-requests: write
  statuses: write

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

jobs:
  cla-check:
    if: |
      github.repository == 'Wei-Shaw/sub2api' &&
      (
        github.event_name == 'issue_comment' ||
        (github.event_name == 'pull_request_target' && github.event.action != 'closed')
      )
    runs-on: ubuntu-latest
    steps:
      - name: "CLA Assistant"
        if: |
          (github.event.comment.body == 'recheck' ||
           github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') ||
          github.event_name == 'pull_request_target'
        uses: contributor-assistant/github-action@v2.6.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          path-to-signatures: "cla.json"
          path-to-document: "https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md"
          branch: "cla-signatures"
          allowlist: "dependabot[bot],renovate[bot],bot*"
          lock-pullrequest-aftermerge: false
          custom-notsigned-prcomment: |
            Thank you for your contribution! Before we can merge this PR, we need $you to sign our [Contributor License Agreement (CLA)](https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md).

            **To sign**, please reply with the following comment:

            > I have read the CLA Document and I hereby sign the CLA

            You only need to sign once — it will be valid for all your future contributions to this project.
          custom-pr-sign-comment: "I have read the CLA Document and I hereby sign the CLA"
          custom-allsigned-prcomment: "All contributors have signed the CLA. ✅"

  cla-lock:
    if: |
      github.repository == 'Wei-Shaw/sub2api' &&
      github.event_name == 'pull_request_target' &&
      github.event.action == 'closed' &&
      github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - name: "Lock merged PR"
        uses: contributor-assistant/github-action@v2.6.1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          path-to-signatures: "cla.json"
          path-to-document: "https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md"
          branch: "cla-signatures"
          lock-pullrequest-aftermerge: true

```

## /.github/workflows/release.yml

```yml path="/.github/workflows/release.yml" 
name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      tag:
        description: 'Tag to release (e.g., v1.0.0)'
        required: true
        type: string
      simple_release:
        description: 'Simple release: only x86_64 GHCR image, skip other artifacts'
        required: false
        type: boolean
        default: false

# 环境变量：合并 workflow_dispatch 输入和 repository variable
# tag push 触发时读取 vars.SIMPLE_RELEASE，workflow_dispatch 时使用输入参数
env:
  SIMPLE_RELEASE: ${{ github.event.inputs.simple_release == 'true' || vars.SIMPLE_RELEASE == 'true' }}

permissions:
  contents: write
  packages: write

jobs:
  # Update VERSION file with tag version
  update-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Update VERSION file
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            VERSION=${{ github.event.inputs.tag }}
            VERSION=${VERSION#v}
          else
            VERSION=${GITHUB_REF#refs/tags/v}
          fi
          echo "$VERSION" > backend/cmd/server/VERSION
          echo "Updated VERSION file to: $VERSION"

      - name: Upload VERSION artifact
        uses: actions/upload-artifact@v7
        with:
          name: version-file
          path: backend/cmd/server/VERSION
          retention-days: 1

  build-frontend:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Setup pnpm
        uses: pnpm/action-setup@v6
        with:
          version: 9

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'pnpm'
          cache-dependency-path: frontend/pnpm-lock.yaml

      - name: Install dependencies
        run: pnpm install --frozen-lockfile
        working-directory: frontend

      - name: Build frontend
        run: pnpm run build
        working-directory: frontend

      - name: Upload frontend artifact
        uses: actions/upload-artifact@v7
        with:
          name: frontend-dist
          path: backend/internal/web/dist/
          retention-days: 1

  release:
    needs: [update-version, build-frontend]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          ref: ${{ github.event.inputs.tag || github.ref }}

      - name: Download VERSION artifact
        uses: actions/download-artifact@v8
        with:
          name: version-file
          path: backend/cmd/server/

      - name: Download frontend artifact
        uses: actions/download-artifact@v8
        with:
          name: frontend-dist
          path: backend/internal/web/dist/

      - name: Setup Go
        uses: actions/setup-go@v6
        with:
          go-version-file: backend/go.mod
          check-latest: false
          cache-dependency-path: backend/go.sum

      - name: Verify Go version
        run: |
          go version | grep -q 'go1.26.5'

      # Docker setup for GoReleaser
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to DockerHub
        if: ${{ env.DOCKERHUB_USERNAME != '' }}
        uses: docker/login-action@v3
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Fetch tags with annotations
        run: |
          # 确保获取完整的 annotated tag 信息
          git fetch --tags --force

      - name: Get tag message
        id: tag_message
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            TAG_NAME=${{ github.event.inputs.tag }}
          else
            TAG_NAME=${GITHUB_REF#refs/tags/}
          fi
          echo "Processing tag: $TAG_NAME"

          # 获取完整的 tag message（跳过第一行标题）
          TAG_MESSAGE=$(git tag -l --format='%(contents:body)' "$TAG_NAME")

          # 调试输出
          echo "Tag message length: ${#TAG_MESSAGE}"
          echo "Tag message preview:"
          echo "$TAG_MESSAGE" | head -10

          # 使用 EOF 分隔符处理多行内容
          echo "message<<EOF" >> $GITHUB_OUTPUT
          echo "$TAG_MESSAGE" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Set lowercase owner for GHCR
        id: lowercase
        run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT

      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v7
        with:
          version: '~> v2'
          args: release --clean --skip=validate ${{ env.SIMPLE_RELEASE == 'true' && '--config=.goreleaser.simple.yaml' || '' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TAG_MESSAGE: ${{ steps.tag_message.outputs.message }}
          GITHUB_REPO_OWNER: ${{ github.repository_owner }}
          GITHUB_REPO_OWNER_LOWER: ${{ steps.lowercase.outputs.owner }}
          GITHUB_REPO_NAME: ${{ github.event.repository.name }}
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME || 'skip' }}

      # Update DockerHub description
      - name: Update DockerHub description
        if: ${{ env.SIMPLE_RELEASE != 'true' && env.DOCKERHUB_USERNAME != '' }}
        uses: peter-evans/dockerhub-description@v5
        env:
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
          repository: ${{ secrets.DOCKERHUB_USERNAME }}/sub2api
          short-description: "Sub2API - AI API Gateway Platform"
          readme-filepath: ./deploy/DOCKER.md

      # Send Telegram notification
      - name: Send Telegram Notification
        if: ${{ env.SIMPLE_RELEASE != 'true' }}
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
          DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
        continue-on-error: true
        run: |
          # 检查必要的环境变量
          if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
            echo "Telegram credentials not configured, skipping notification"
            exit 0
          fi

          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            TAG_NAME=${{ github.event.inputs.tag }}
          else
            TAG_NAME=${GITHUB_REF#refs/tags/}
          fi
          VERSION=${TAG_NAME#v}
          REPO="${{ github.repository }}"
          GHCR_IMAGE="ghcr.io/${REPO,,}"  # ${,,} converts to lowercase

          # 获取 tag message 内容并转义 Markdown 特殊字符
          TAG_MESSAGE='${{ steps.tag_message.outputs.message }}'
          TAG_MESSAGE=$(echo "$TAG_MESSAGE" | sed 's/\([_*`\[]\)/\\\1/g')

          # 限制消息长度（Telegram 消息限制 4096 字符，预留空间给头尾固定内容）
          if [ ${#TAG_MESSAGE} -gt 3500 ]; then
            TAG_MESSAGE="${TAG_MESSAGE:0:3500}..."
          fi

          # 构建消息内容
          MESSAGE="🚀 *Sub2API 新版本发布！*"$'\n'$'\n'
          MESSAGE+="📦 版本号: \`${VERSION}\`"$'\n'$'\n'

          # 添加更新内容
          if [ -n "$TAG_MESSAGE" ]; then
            MESSAGE+="${TAG_MESSAGE}"$'\n'$'\n'
          fi

          MESSAGE+="🐳 *Docker 部署:*"$'\n'
          MESSAGE+="\`\`\`bash"$'\n'
          # 根据是否配置 DockerHub 动态生成
          if [ -n "$DOCKERHUB_USERNAME" ]; then
            DOCKER_IMAGE="${DOCKERHUB_USERNAME}/sub2api"
            MESSAGE+="# Docker Hub"$'\n'
            MESSAGE+="docker pull ${DOCKER_IMAGE}:${VERSION}"$'\n'
            MESSAGE+="# GitHub Container Registry"$'\n'
          fi
          MESSAGE+="docker pull ${GHCR_IMAGE}:${VERSION}"$'\n'
          MESSAGE+="\`\`\`"$'\n'$'\n'
          MESSAGE+="🔗 *相关链接:*"$'\n'
          MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG_NAME})"$'\n'
          if [ -n "$DOCKERHUB_USERNAME" ]; then
            MESSAGE+="• [Docker Hub](https://hub.docker.com/r/${DOCKER_IMAGE})"$'\n'
          fi
          MESSAGE+="• [GitHub Packages](https://github.com/${REPO}/pkgs/container/sub2api)"$'\n'$'\n'
          MESSAGE+="#Sub2API #Release #${TAG_NAME//./_}"

          # 发送消息
          curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
            -H "Content-Type: application/json" \
            -d "$(jq -n \
              --arg chat_id "${TELEGRAM_CHAT_ID}" \
              --arg text "${MESSAGE}" \
              '{
                chat_id: $chat_id,
                text: $text,
                parse_mode: "Markdown",
                disable_web_page_preview: true
              }')"

  sync-version-file:
    needs: [release]
    if: ${{ needs.release.result == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout default branch
        uses: actions/checkout@v6
        with:
          ref: ${{ github.event.repository.default_branch }}

      - name: Sync VERSION file to released tag
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            VERSION=${{ github.event.inputs.tag }}
            VERSION=${VERSION#v}
          else
            VERSION=${GITHUB_REF#refs/tags/v}
          fi

          CURRENT_VERSION=$(tr -d '\r\n' < backend/cmd/server/VERSION || true)
          if [ "$CURRENT_VERSION" = "$VERSION" ]; then
            echo "VERSION file already matches $VERSION"
            exit 0
          fi

          echo "$VERSION" > backend/cmd/server/VERSION

          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add backend/cmd/server/VERSION
          git commit -m "chore: sync VERSION to ${VERSION} [skip ci]"
          git push origin HEAD:${{ github.event.repository.default_branch }}

```

## /.github/workflows/security-scan.yml

```yml path="/.github/workflows/security-scan.yml" 
name: Security Scan

on:
  push:
  pull_request:
  schedule:
    - cron: '0 3 * * 1'

permissions:
  contents: read

jobs:
  backend-security:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v6
      - name: Set up Go
        uses: actions/setup-go@v6
        with:
          go-version-file: backend/go.mod
          check-latest: false
          cache-dependency-path: backend/go.sum
      - name: Verify Go version
        run: |
          go version | grep -q 'go1.26.5'
      - name: Run govulncheck
        working-directory: backend
        run: |
          go install golang.org/x/vuln/cmd/govulncheck@latest
          govulncheck ./...

  frontend-security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Set up pnpm
        uses: pnpm/action-setup@v6
        with:
          version: 9
      - name: Set up Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '20'
          cache: 'pnpm'
          cache-dependency-path: frontend/pnpm-lock.yaml
      - name: Install dependencies
        working-directory: frontend
        run: pnpm install --frozen-lockfile
      - name: Run pnpm audit
        working-directory: frontend
        run: |
          pnpm audit --prod --audit-level=high --json > audit.json || true
      - name: Check audit exceptions
        run: |
          python tools/check_pnpm_audit_exceptions.py \
            --audit frontend/audit.json \
            --exceptions .github/audit-exceptions.yml

```

## /.gitignore

```gitignore path="/.gitignore" 
docs/claude-relay-service/
.codex

# ===================
# Go 后端
# ===================
# 二进制文件
*.exe
*.exe~
*.dll
*.so
*.dylib
backend/bin/
backend/server
backend/sub2api
backend/main

# Go 测试二进制
*.test

# 测试覆盖率
*.out
coverage.html

# 依赖（使用 go mod）
vendor/

# Go 编译缓存
backend/.gocache/

# ===================
# Node.js / Vue 前端
# ===================
node_modules/
frontend/node_modules/
frontend/dist/
*.local
*.tsbuildinfo
vite.config.d.ts
vite.config.js.timestamp-*

# 日志
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# ===================
# 环境配置
# ===================
.env
.env.local
.env.*.local
*.env
!.env.example
docker-compose.override.yml

# ===================
# IDE / 编辑器
# ===================
.idea/
.vscode/
*.swp
*.swo
*~
.project
.settings/
.classpath

# ===================
# 操作系统
# ===================
.DS_Store
Thumbs.db
Desktop.ini

# ===================
# 临时文件
# ===================
tmp/
temp/
*.tmp
*.temp
*.log
*.bak
.cache/
.dev/
.serena/

# ===================
# 构建产物
# ===================
dist/
build/
release/

# 后端嵌入的前端构建产物
# Keep a placeholder file so `//go:embed all:dist` always has a match in CI/lint,
# while still ignoring generated frontend build outputs.
backend/internal/web/dist/
!backend/internal/web/dist/
backend/internal/web/dist/*
!backend/internal/web/dist/.keep

# 后端运行时缓存数据
backend/data/

# ===================
# 本地配置文件（包含敏感信息）
# ===================
backend/config.yaml
deploy/config.yaml
backend/.installed

# ===================
# 其他
# ===================
tests
!deploy/tests/
!deploy/tests/**
CLAUDE.md
.claude
scripts
!backend/scripts/
!backend/scripts/resolve-version.sh
.code-review-state
#openspec/
code-reviews/
AGENTS.md
backend/cmd/server/server
deploy/docker-compose.override.yml
.gocache/
vite.config.js
docs/*
!docs/PAYMENT.md
!docs/PAYMENT_CN.md
!docs/ADMIN_PAYMENT_INTEGRATION_API.md
!docs/ASYNC_IMAGE_TASKS.md
!docs/legal/
!docs/legal/*.md
.serena/
.codex/
frontend/coverage/
aicodex
output/

```

## /.goreleaser.simple.yaml

```yaml path="/.goreleaser.simple.yaml" 
# 简化版 GoReleaser 配置 - 仅发布 x86_64 GHCR 镜像
version: 2

project_name: sub2api

before:
  hooks:
    - go mod tidy -C backend

builds:
  - id: sub2api
    dir: backend
    main: ./cmd/server
    binary: sub2api
    flags:
      - -tags=embed
    env:
      - CGO_ENABLED=0
    goos:
      - linux
    goarch:
      - amd64
    ldflags:
      - -s -w
      - -X main.Commit={{.Commit}}
      - -X main.Date={{.Date}}
      - -X main.BuildType=release

# 跳过 archives
archives: []

# 跳过 checksum
checksum:
  disable: true

changelog:
  disable: true

# 仅 GHCR x86_64 镜像
dockers:
  - id: ghcr-amd64
    goos: linux
    goarch: amd64
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}"
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
    dockerfile: Dockerfile.goreleaser
    use: buildx
    extra_files:
      - deploy/docker-entrypoint.sh
      - backend/resources
    build_flag_templates:
      - "--platform=linux/amd64"
      - "--label=org.opencontainers.image.version={{ .Version }}"
      - "--label=org.opencontainers.image.revision={{ .Commit }}"
      - "--label=org.opencontainers.image.source=https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}"

# 跳过 manifests（单架构不需要）
docker_manifests: []

release:
  github:
    owner: "{{ .Env.GITHUB_REPO_OWNER }}"
    name: "{{ .Env.GITHUB_REPO_NAME }}"
  draft: false
  prerelease: auto
  name_template: "Sub2API {{.Version}} (Simple)"
  # 跳过上传二进制包
  skip_upload: true
  header: |
    > AI API Gateway Platform - 将 AI 订阅配额分发和管理
    > ⚡ Simple Release: 仅包含 x86_64 GHCR 镜像

    {{ .Env.TAG_MESSAGE }}

  footer: |
    ---

    ## 📥 Installation

    **Docker (x86_64 only):**
    \`\`\`bash
    docker pull ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}
    \`\`\`

    ## 📚 Documentation

    - [GitHub Repository](https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }})

```

## /.goreleaser.yaml

```yaml path="/.goreleaser.yaml" 
version: 2

project_name: sub2api

before:
  hooks:
    - go mod tidy -C backend

builds:
  - id: sub2api
    dir: backend
    main: ./cmd/server
    binary: sub2api
    flags:
      - -tags=embed
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin
    goarch:
      - amd64
      - arm64
    ignore:
      - goos: windows
        goarch: arm64
    ldflags:
      - -s -w
      - -X main.Commit={{.Commit}}
      - -X main.Date={{.Date}}
      - -X main.BuildType=release

archives:
  - id: default
    format: tar.gz
    name_template: >-
      {{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}
    format_overrides:
      - goos: windows
        format: zip
    files:
      - LICENSE*
      - README*
      - deploy/*

checksum:
  name_template: 'checksums.txt'
  algorithm: sha256

changelog:
  # 禁用自动 changelog，完全使用 tag 消息
  disable: true

# Docker images
dockers:
  # DockerHub images (skipped if DOCKERHUB_USERNAME is 'skip')
  - id: amd64
    goos: linux
    goarch: amd64
    skip_push: '{{ if eq .Env.DOCKERHUB_USERNAME "skip" }}true{{ else }}false{{ end }}'
    image_templates:
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
    dockerfile: Dockerfile.goreleaser
    use: buildx
    extra_files:
      - deploy/docker-entrypoint.sh
      - backend/resources
    build_flag_templates:
      - "--platform=linux/amd64"
      - "--label=org.opencontainers.image.version={{ .Version }}"
      - "--label=org.opencontainers.image.revision={{ .Commit }}"

  - id: arm64
    goos: linux
    goarch: arm64
    skip_push: '{{ if eq .Env.DOCKERHUB_USERNAME "skip" }}true{{ else }}false{{ end }}'
    image_templates:
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"
    dockerfile: Dockerfile.goreleaser
    use: buildx
    extra_files:
      - deploy/docker-entrypoint.sh
      - backend/resources
    build_flag_templates:
      - "--platform=linux/arm64"
      - "--label=org.opencontainers.image.version={{ .Version }}"
      - "--label=org.opencontainers.image.revision={{ .Commit }}"

  # GHCR images (owner must be lowercase)
  - id: ghcr-amd64
    goos: linux
    goarch: amd64
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
    dockerfile: Dockerfile.goreleaser
    use: buildx
    extra_files:
      - deploy/docker-entrypoint.sh
      - backend/resources
    build_flag_templates:
      - "--platform=linux/amd64"
      - "--label=org.opencontainers.image.version={{ .Version }}"
      - "--label=org.opencontainers.image.revision={{ .Commit }}"
      - "--label=org.opencontainers.image.source=https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}"

  - id: ghcr-arm64
    goos: linux
    goarch: arm64
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
    dockerfile: Dockerfile.goreleaser
    use: buildx
    extra_files:
      - deploy/docker-entrypoint.sh
      - backend/resources
    build_flag_templates:
      - "--platform=linux/arm64"
      - "--label=org.opencontainers.image.version={{ .Version }}"
      - "--label=org.opencontainers.image.revision={{ .Commit }}"
      - "--label=org.opencontainers.image.source=https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}"

# Docker manifests for multi-arch support
docker_manifests:
  # DockerHub manifests (skipped if DOCKERHUB_USERNAME is 'skip')
  - name_template: "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}"
    skip_push: '{{ if eq .Env.DOCKERHUB_USERNAME "skip" }}true{{ else }}false{{ end }}'
    image_templates:
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"

  - name_template: "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:latest"
    skip_push: '{{ if eq .Env.DOCKERHUB_USERNAME "skip" }}true{{ else }}false{{ end }}'
    image_templates:
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"

  - name_template: "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Major }}.{{ .Minor }}"
    skip_push: '{{ if eq .Env.DOCKERHUB_USERNAME "skip" }}true{{ else }}false{{ end }}'
    image_templates:
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"

  - name_template: "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Major }}"
    skip_push: '{{ if eq .Env.DOCKERHUB_USERNAME "skip" }}true{{ else }}false{{ end }}'
    image_templates:
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
      - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"

  # GHCR manifests (owner must be lowercase)
  - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}"
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"

  - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"

  - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Major }}.{{ .Minor }}"
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"

  - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Major }}"
    image_templates:
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
      - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"

release:
  github:
    owner: "{{ .Env.GITHUB_REPO_OWNER }}"
    name: "{{ .Env.GITHUB_REPO_NAME }}"
  draft: false
  prerelease: auto
  name_template: "Sub2API {{.Version}}"
  # 完全使用 tag 消息作为 release 内容（通过环境变量传入）
  header: |
    > AI API Gateway Platform - 将 AI 订阅配额分发和管理

    {{ .Env.TAG_MESSAGE }}

  footer: |

    ---

    ## 📥 Installation

    **Docker:**
    \`\`\`bash
    {{ if ne .Env.DOCKERHUB_USERNAME "skip" -}}
    # Docker Hub
    docker pull {{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}

    {{ end -}}
    # GitHub Container Registry
    docker pull ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}
    \`\`\`

    **One-line install (Linux):**
    \`\`\`bash
    curl -sSL https://raw.githubusercontent.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}/main/deploy/install.sh | sudo bash
    \`\`\`

    **Manual download:**
    Download the appropriate archive for your platform from the assets below.

    ## 📚 Documentation

    - [GitHub Repository](https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }})
    - [Installation Guide](https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}/blob/main/deploy/README.md)

```

## /CLA.md

# Sub2API Individual Contributor License Agreement (v1.0)

Thank you for your interest in contributing to Sub2API ("the Project"). This Contributor License Agreement ("Agreement") documents the rights granted by contributors to the Project.

By signing this Agreement, you accept and agree to the following terms and conditions for your present and future contributions submitted to the Project.

## 1. Definitions

- **"You" (or "Your")** means the copyright owner or legal entity authorized by the copyright owner that is making this Agreement.
- **"Contribution"** means any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project for inclusion in, or documentation of, any of the products owned or managed by the Project. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Project, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
- **"Project Owner"** means Wesley Liddick, or any individual or legal entity to whom Wesley Liddick has explicitly assigned or transferred ownership of the Project in writing, and their respective successors and assigns.

## 2. Grant of Copyright License

Subject to the terms and conditions of this Agreement, You hereby grant to the Project Owner a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. This license includes, without limitation, the right to sublicense, assign, and transfer these rights to any third party, including without limitation any successor, assignee, or acquiring entity of the Project or the Project Owner, and to use Your Contributions under any license, including proprietary or commercial licenses.

## 3. Moral Rights

To the fullest extent permitted by applicable law, You irrevocably waive and agree not to assert any moral rights (including rights of attribution and integrity) that You may have in Your Contributions, and agree that the Project Owner and its licensees may use, modify, and distribute Your Contributions without attribution or other obligations arising from moral rights.

## 4. Grant of Patent License

Subject to the terms and conditions of this Agreement, You hereby grant to the Project Owner a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer Your Contributions, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Project to which such Contribution(s) was submitted.

## 5. Representations and Warranties

You represent and warrant that:

(a) You are legally entitled to grant the above licenses.

(b) If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You have received permission to make Contributions on behalf of that employer, or that Your employer has waived such rights for Your Contributions to the Project.

(c) Each of Your Contributions is Your original creation, or You have sufficient rights to submit it under the terms of this Agreement. You agree to provide, upon request, reasonable documentation or explanation of any third-party materials included in Your Contributions.

## 6. No Warranty

Your Contributions are provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support.

## 7. No Obligation

You understand that the decision to include Your Contribution in any product or project is entirely at the discretion of the Project Owner, and this Agreement does not obligate the Project Owner to use Your Contribution.

## 8. Retention of Rights

You retain ownership of the copyright in Your Contributions. This Agreement does not transfer any copyright or other intellectual property rights from You to the Project Owner. This Agreement only grants the licenses described above.

## 9. Term and Termination

This Agreement shall remain in effect indefinitely. You may terminate this Agreement prospectively by providing written notice to the Project Owner, but such termination shall not affect the licenses granted for Contributions submitted prior to the effective date of termination. The licenses granted herein for Contributions submitted prior to termination are perpetual and irrevocable.

## 10. Electronic Signature

You agree that Your electronic signature (including but not limited to typing a specific phrase in a pull request, issue, or other electronic communication) is legally binding and has the same force and effect as a handwritten signature. You consent to the use of electronic means to enter into this Agreement and acknowledge that this Agreement is enforceable as if executed in a traditional written format.

## 11. General Provisions

**Entire Agreement.** This Agreement constitutes the entire agreement between You and the Project Owner with respect to Your Contributions and supersedes all prior or contemporaneous understandings regarding such subject matter.

**Severability.** If any provision of this Agreement is held to be unenforceable or invalid, that provision will be enforced to the maximum extent possible and the remaining provisions will remain in full force and effect.

**No Waiver.** The failure of the Project Owner to enforce any provision of this Agreement shall not constitute a waiver of that provision or any other provision.

**Amendment.** This Agreement may only be modified by a written instrument signed by both parties. Modifications to this Agreement apply only to Contributions submitted after the modified Agreement is published and accepted by You. Prior Contributions remain governed by the version of the Agreement in effect at the time of submission.

**Notification.** Notices under this Agreement shall be sent to the Project Owner via a GitHub issue on the Project repository. Notices are effective upon receipt.

---

**By signing this CLA, you acknowledge that you have read and understood this Agreement and agree to be bound by its terms.**

To sign, reply in the pull request with:

> I have read the CLA Document and I hereby sign the CLA


## /DEV_GUIDE.md

# sub2api 项目开发指南

> 本文档记录项目环境配置、常见坑点和注意事项，供 Claude Code 和团队成员参考。

## 一、项目基本信息

| 项目 | 说明 |
|------|------|
| **上游仓库** | Wei-Shaw/sub2api |
| **Fork 仓库** | bayma888/sub2api-bmai |
| **技术栈** | Go 后端 (Ent ORM + Gin) + Vue3 前端 (pnpm) |
| **数据库** | PostgreSQL 16 + Redis |
| **包管理** | 后端: go modules, 前端: **pnpm**（不是 npm） |

## 二、本地环境配置

### PostgreSQL 16 (Windows 服务)

| 配置项 | 值 |
|--------|-----|
| 端口 | 5432 |
| psql 路径 | `C:\Program Files\PostgreSQL\16\bin\psql.exe` |
| pg_hba.conf | `C:\Program Files\PostgreSQL\16\data\pg_hba.conf` |
| 数据库凭据 | user=`sub2api`, password=`sub2api`, dbname=`sub2api` |
| 超级用户 | user=`postgres`, password=`postgres` |

### Redis

| 配置项 | 值 |
|--------|-----|
| 端口 | 6379 |
| 密码 | 无 |

### 开发工具

```bash
# golangci-lint v2.7
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7

# pnpm (前端包管理)
npm install -g pnpm
```

## 三、CI/CD 流水线

### GitHub Actions Workflows

| Workflow | 触发条件 | 检查内容 |
|----------|----------|----------|
| **backend-ci.yml** | push, pull_request | 单元测试 + 集成测试 + golangci-lint v2.7 |
| **security-scan.yml** | push, pull_request, 每周一 | govulncheck + gosec + pnpm audit |
| **release.yml** | tag `v*` | 构建发布（PR 不触发） |

### CI 要求

- Go 版本必须是 **1.25.7**
- 前端使用 `pnpm install --frozen-lockfile`，必须提交 `pnpm-lock.yaml`

### 本地测试命令

```bash
# 后端单元测试
cd backend && go test -tags=unit ./...

# 后端集成测试
cd backend && go test -tags=integration ./...

# 代码质量检查
cd backend && golangci-lint run ./...

# 前端依赖安装（必须用 pnpm）
cd frontend && pnpm install
```

## 四、常见坑点 & 解决方案

### 坑 1：pnpm-lock.yaml 必须同步提交

**问题**：`package.json` 新增依赖后，CI 的 `pnpm install --frozen-lockfile` 失败。

**原因**：上游 CI 使用 pnpm，lock 文件不同步会报错。

**解决**：
```bash
cd frontend
pnpm install  # 更新 pnpm-lock.yaml
git add pnpm-lock.yaml
git commit -m "chore: update pnpm-lock.yaml"
```

---

### 坑 2：npm 和 pnpm 的 node_modules 冲突

**问题**：之前用 npm 装过 `node_modules`，pnpm install 报 `EPERM` 错误。

**解决**：
```bash
cd frontend
rm -rf node_modules  # 或 PowerShell: Remove-Item -Recurse -Force node_modules
pnpm install
```

---

### 坑 3：PowerShell 中 bcrypt hash 的 `$` 被转义

**问题**：bcrypt hash 格式如 `$2a$10$xxx...`，PowerShell 把 `$2a` 当变量解析，导致数据丢失。

**解决**：将 SQL 写入文件，用 `psql -f` 执行：
```bash
# 错误示范（PowerShell 会吃掉 $）
psql -c "INSERT INTO users ... VALUES ('$2a$10$...')"

# 正确做法
echo "INSERT INTO users ... VALUES ('\$2a\$10\$...')" > temp.sql
psql -U sub2api -h 127.0.0.1 -d sub2api -f temp.sql
```

---

### 坑 4：psql 不支持中文路径

**问题**：`psql -f "D:\中文路径\file.sql"` 报错找不到文件。

**解决**：复制到纯英文路径再执行：
```bash
cp "D:\中文路径\file.sql" "C:\temp.sql"
psql -f "C:\temp.sql"
```

---

### 坑 5：PostgreSQL 密码重置流程

**场景**：忘记 PostgreSQL 密码。

**步骤**：
1. 修改 `C:\Program Files\PostgreSQL\16\data\pg_hba.conf`
   ```
   # 将 scram-sha-256 改为 trust
   host    all    all    127.0.0.1/32    trust
   ```
2. 重启 PostgreSQL 服务
   ```powershell
   Restart-Service postgresql-x64-16
   ```
3. 无密码登录并重置
   ```bash
   psql -U postgres -h 127.0.0.1
   ALTER USER sub2api WITH PASSWORD 'sub2api';
   ALTER USER postgres WITH PASSWORD 'postgres';
   ```
4. 改回 `scram-sha-256` 并重启

---

### 坑 6：Go interface 新增方法后 test stub 必须补全

**问题**：给 interface 新增方法后，编译报错 `does not implement interface (missing method XXX)`。

**原因**：所有测试文件中实现该 interface 的 stub/mock 都必须补上新方法。

**解决**：
```bash
# 搜索所有实现该 interface 的 struct
cd backend
grep -r "type.*Stub.*struct" internal/
grep -r "type.*Mock.*struct" internal/

# 逐一补全新方法
```

---

### 坑 7：Windows 上 psql 连 localhost 的 IPv6 问题

**问题**：psql 连 `localhost` 先尝试 IPv6 (::1)，可能报错后再回退 IPv4。

**建议**：直接用 `127.0.0.1` 代替 `localhost`。

---

### 坑 8：Windows 没有 make 命令

**问题**：CI 里用 `make test-unit`，本地 Windows 没有 make。

**解决**：直接用 Makefile 里的原始命令：
```bash
# 代替 make test-unit
go test -tags=unit ./...

# 代替 make test-integration
go test -tags=integration ./...
```

---

### 坑 9：Ent Schema 修改后必须重新生成

**问题**：修改 `ent/schema/*.go` 后，代码不生效。

**解决**：
```bash
cd backend
go generate ./ent  # 重新生成 ent 代码
git add ent/       # 生成的文件也要提交
```

---

### 坑 10：前端测试看似正常，但后端调用失败（模型映射被批量误改）

**典型现象**：
- 前端按钮点测看起来正常；
- 实际通过 API/客户端调用时返回 `Service temporarily unavailable` 或提示无可用账号；
- 常见于 OpenAI 账号（例如 Codex 模型）在批量修改后突然不可用。

**根因**：
- OpenAI 账号编辑页默认不显式展示映射规则，容易让人误以为“没映射也没关系”；
- 但在**批量修改同时选中不同平台账号**（OpenAI + Antigravity/Gemini）时，模型白名单/映射可能被跨平台策略覆盖；
- 结果是 OpenAI 账号的关键模型映射丢失或被改坏，后端选不到可用账号。

**修复方案（按优先级）**：
1. **快速修复（推荐）**：在批量修改中补回正确的透传映射（例如 `gpt-5.3-codex -> gpt-5.3-codex-spark`）。
2. **彻底重建**：删除并重新添加全部相关账号（最稳但成本高）。

**关键经验**：
- 如果某模型已被软件内置默认映射覆盖，通常不需要额外再加透传；
- 但当上游模型更新快于本仓库默认映射时，**手动批量添加透传映射**是最简单、最低风险的临时兜底方案；
- 批量操作前尽量按平台分组，不要混选不同平台账号。

---

### 坑 11：PR 提交前检查清单

提交 PR 前务必本地验证：

- [ ] `go test -tags=unit ./...` 通过
- [ ] `go test -tags=integration ./...` 通过
- [ ] `golangci-lint run ./...` 无新增问题
- [ ] `pnpm-lock.yaml` 已同步（如果改了 package.json）
- [ ] 所有 test stub 补全新接口方法（如果改了 interface）
- [ ] Ent 生成的代码已提交（如果改了 schema）

## 五、常用命令速查

### 数据库操作

```bash
# 连接数据库
psql -U sub2api -h 127.0.0.1 -d sub2api

# 查看所有用户
psql -U postgres -h 127.0.0.1 -c "\du"

# 查看所有数据库
psql -U postgres -h 127.0.0.1 -c "\l"

# 执行 SQL 文件
psql -U sub2api -h 127.0.0.1 -d sub2api -f migration.sql
```

### Git 操作

```bash
# 同步上游
git fetch upstream
git checkout main
git merge upstream/main
git push origin main

# 创建功能分支
git checkout -b feature/xxx

# Rebase 到最新 main
git fetch upstream
git rebase upstream/main
```

### 前端操作

```bash
# 安装依赖（必须用 pnpm）
cd frontend
pnpm install

# 开发服务器
pnpm dev

# 构建
pnpm build
```

### 后端操作

```bash
# 运行服务器
cd backend
go run ./cmd/server/

# 生成 Ent 代码
go generate ./ent

# 运行测试
go test -tags=unit ./...
go test -tags=integration ./...

# Lint 检查
golangci-lint run ./...
```

## 六、项目结构速览

```
sub2api-bmai/
├── backend/
│   ├── cmd/server/          # 主程序入口
│   ├── ent/                 # Ent ORM 生成代码
│   │   └── schema/          # 数据库 Schema 定义
│   ├── internal/
│   │   ├── handler/         # HTTP 处理器
│   │   ├── service/         # 业务逻辑
│   │   ├── repository/      # 数据访问层
│   │   └── server/          # 服务器配置
│   ├── migrations/          # 数据库迁移脚本
│   └── config.yaml          # 配置文件
├── frontend/
│   ├── src/
│   │   ├── api/             # API 调用
│   │   ├── components/      # Vue 组件
│   │   ├── views/           # 页面视图
│   │   ├── types/           # TypeScript 类型
│   │   └── i18n/            # 国际化
│   ├── package.json         # 依赖配置
│   └── pnpm-lock.yaml       # pnpm 锁文件（必须提交）
└── .claude/
    └── CLAUDE.md            # 本文档
```

## 七、参考资源

- [上游仓库](https://github.com/Wei-Shaw/sub2api)
- [Ent 文档](https://entgo.io/docs/getting-started)
- [Vue3 文档](https://vuejs.org/)
- [pnpm 文档](https://pnpm.io/)


## /Dockerfile

``` path="/Dockerfile" 
# syntax=docker/dockerfile:1.7
# =============================================================================
# Sub2API Multi-Stage Dockerfile
# =============================================================================
# Stage 1: Build frontend
# Stage 2: Build Go backend with embedded frontend
# Stage 3: Final minimal image
# =============================================================================

ARG NODE_IMAGE=node:24-alpine
ARG GOLANG_IMAGE=golang:1.26.5-alpine
ARG ALPINE_IMAGE=alpine:3.21
ARG POSTGRES_IMAGE=postgres:18-alpine
ARG GOPROXY=https://goproxy.cn,direct
ARG GOSUMDB=sum.golang.google.cn
ARG NPM_CONFIG_REGISTRY=

# -----------------------------------------------------------------------------
# Stage 1: Frontend Builder
# -----------------------------------------------------------------------------
# --platform=$BUILDPLATFORM: the frontend output is JS (arch-neutral), so build
# it on the native host arch instead of under QEMU emulation for the target.
FROM --platform=${BUILDPLATFORM} ${NODE_IMAGE} AS frontend-builder
ARG NPM_CONFIG_REGISTRY

WORKDIR /app/frontend

# Install pnpm (pinned to v9 to match CI and keep builds reproducible)
RUN corepack enable && corepack prepare pnpm@9 --activate

# Install dependencies first (better caching)
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN --mount=type=cache,id=sub2api-pnpm-store,target=/root/.local/share/pnpm/store \
    if [ -n "${NPM_CONFIG_REGISTRY}" ]; then pnpm config set registry "${NPM_CONFIG_REGISTRY}"; fi && \
    pnpm install --frozen-lockfile --prefer-offline

# Copy frontend source and build.
# LegalDocumentView.vue (admin-compliance gate) build-time imports
# ../../../../docs/legal/*.md?raw, so docs/legal/ must sit beside frontend/
# in the image (WORKDIR /app/frontend -> resolves to /app/docs/legal/*.md).
# Copy only that subtree to keep the build dependency minimal.
COPY frontend/ ./
COPY docs/legal/ /app/docs/legal/
RUN pnpm run build

# -----------------------------------------------------------------------------
# Stage 2: Backend Builder
# -----------------------------------------------------------------------------
# --platform=$BUILDPLATFORM: run the Go toolchain on the native host arch and
# cross-compile to the target arch below. The binary is CGO_ENABLED=0, so this
# is a clean pure-Go cross-compile — no QEMU emulation of go mod download / go
# build (emulated networking here was dropping module fetches with EOF).
FROM --platform=${BUILDPLATFORM} ${GOLANG_IMAGE} AS backend-builder

# Build arguments for version info (set by CI)
ARG VERSION=
ARG COMMIT=docker
ARG DATE
ARG GOPROXY
ARG GOSUMDB
# Populated by buildx from the --platform target (e.g. linux/amd64).
ARG TARGETOS
ARG TARGETARCH

ENV GOPROXY=${GOPROXY}
ENV GOSUMDB=${GOSUMDB}

# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata

WORKDIR /app/backend

# Copy go mod files first (better caching)
COPY backend/go.mod backend/go.sum ./
# Cache mount keeps the module cache across builds so a transient CDN blip on
# retry resumes instead of re-fetching every zip from scratch.
RUN --mount=type=cache,id=sub2api-gomod,target=/go/pkg/mod \
    go mod download

# Copy backend source first
COPY backend/ ./

# Copy frontend dist from previous stage (must be after backend copy to avoid being overwritten)
COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist

# Build the binary (BuildType=release for CI builds, embed frontend)
# Version precedence: build arg VERSION > exact git tag > cmd/server/VERSION
RUN --mount=type=cache,id=sub2api-gomod,target=/go/pkg/mod \
    --mount=type=cache,id=sub2api-gobuild,target=/root/.cache/go-build \
    VERSION_VALUE="${VERSION}" && \
    if [ -z "${VERSION_VALUE}" ]; then VERSION_VALUE="$(./scripts/resolve-version.sh)"; fi && \
    DATE_VALUE="${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}" && \
    CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build \
    -tags embed \
    -ldflags="-s -w -X main.Version=${VERSION_VALUE} -X main.Commit=${COMMIT} -X main.Date=${DATE_VALUE} -X main.BuildType=release" \
    -trimpath \
    -o /app/sub2api \
    ./cmd/server

# -----------------------------------------------------------------------------
# Stage 3: PostgreSQL Client (version-matched with docker-compose)
# -----------------------------------------------------------------------------
FROM ${POSTGRES_IMAGE} AS pg-client

# -----------------------------------------------------------------------------
# Stage 4: Final Runtime Image
# -----------------------------------------------------------------------------
FROM ${ALPINE_IMAGE}

# Labels
LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform"
LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"

# Install runtime dependencies
RUN apk add --no-cache \
    ca-certificates \
    tzdata \
    su-exec \
    libpq \
    zstd-libs \
    lz4-libs \
    krb5-libs \
    libldap \
    libedit \
    && rm -rf /var/cache/apk/*

# Copy pg_dump and psql from the same postgres image used in docker-compose
# This ensures version consistency between backup tools and the database server
COPY --from=pg-client /usr/local/bin/pg_dump /usr/local/bin/pg_dump
COPY --from=pg-client /usr/local/bin/psql /usr/local/bin/psql
COPY --from=pg-client /usr/local/lib/libpq.so.5* /usr/local/lib/

# Create non-root user
RUN addgroup -g 1000 sub2api && \
    adduser -u 1000 -G sub2api -s /bin/sh -D sub2api

# Set working directory
WORKDIR /app

# Copy binary/resources with ownership to avoid extra full-layer chown copy
COPY --from=backend-builder --chown=sub2api:sub2api /app/sub2api /app/sub2api
COPY --from=backend-builder --chown=sub2api:sub2api /app/backend/resources /app/resources

# Create data directory
RUN mkdir -p /app/data && chown sub2api:sub2api /app/data

# Copy entrypoint script (fixes volume permissions then drops to sub2api)
COPY deploy/docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh

# Expose port (can be overridden by SERVER_PORT env var)
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
    CMD wget -q -T 5 -O /dev/null http://localhost:${SERVER_PORT:-8080}/health || exit 1

# Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["/app/sub2api"]

```

## /Dockerfile.goreleaser

```goreleaser path="/Dockerfile.goreleaser" 
# =============================================================================
# Sub2API Dockerfile for GoReleaser
# =============================================================================
# This Dockerfile is used by GoReleaser to build Docker images.
# It only packages the pre-built binary, no compilation needed.
# =============================================================================

ARG ALPINE_IMAGE=alpine:3.21
ARG POSTGRES_IMAGE=postgres:18-alpine

FROM ${POSTGRES_IMAGE} AS pg-client

FROM ${ALPINE_IMAGE}

LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform"
LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"

# Install runtime dependencies
RUN apk add --no-cache \
    ca-certificates \
    tzdata \
    curl \
    su-exec \
    libpq \
    zstd-libs \
    lz4-libs \
    krb5-libs \
    libldap \
    libedit \
    && rm -rf /var/cache/apk/*

# Copy pg_dump and psql from a version-matched PostgreSQL image so backup and
# restore work in the runtime container without requiring Docker socket access.
COPY --from=pg-client /usr/local/bin/pg_dump /usr/local/bin/pg_dump
COPY --from=pg-client /usr/local/bin/psql /usr/local/bin/psql
COPY --from=pg-client /usr/local/lib/libpq.so.5* /usr/local/lib/

# Create non-root user
RUN addgroup -g 1000 sub2api && \
    adduser -u 1000 -G sub2api -s /bin/sh -D sub2api

WORKDIR /app

# Copy the pre-built binary and runtime fallback resources from GoReleaser
COPY sub2api /app/sub2api
COPY --chown=sub2api:sub2api backend/resources /app/resources

# Create data directory
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app

# Copy entrypoint script (fixes volume permissions then drops to sub2api)
COPY deploy/docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
    CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1

# Run the application (entrypoint fixes /app/data ownership then execs as sub2api)
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["/app/sub2api"]

```

## /Makefile

``` path="/Makefile" 
.PHONY: build build-backend build-frontend test test-backend test-frontend test-frontend-critical

FRONTEND_CRITICAL_VITEST := \
	src/api/__tests__/client.spec.ts \
	src/api/__tests__/tokenRefresh.spec.ts \
	src/views/auth/__tests__/LinuxDoCallbackView.spec.ts \
	src/views/auth/__tests__/WechatCallbackView.spec.ts \
	src/views/user/__tests__/PaymentView.spec.ts \
	src/views/user/__tests__/PaymentResultView.spec.ts \
	src/components/user/profile/__tests__/ProfileInfoCard.spec.ts \
	src/views/admin/__tests__/SettingsView.spec.ts

# 一键编译前后端
build: build-backend build-frontend

# 编译后端（复用 backend/Makefile）
build-backend:
	@$(MAKE) -C backend build

# 编译前端（需要已安装依赖）
build-frontend:
	@pnpm --dir frontend run build

# 运行测试（后端 + 前端）
test: test-backend test-frontend

test-backend:
	@$(MAKE) -C backend test

test-frontend:
	@pnpm --dir frontend run lint:check
	@pnpm --dir frontend run typecheck
	@$(MAKE) test-frontend-critical

test-frontend-critical:
	@pnpm --dir frontend exec vitest run $(FRONTEND_CRITICAL_VITEST)

```

## /README.md

<div align="center">

<img src="assets/logo.svg" alt="Sub2API Logo" width="128" />

# Sub2API

[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/)
[![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/)
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)

<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>

**AI API Gateway Platform for Subscription Quota Distribution**

English | [中文](README_CN.md) | [日本語](README_JA.md)

</div>

## ⚠️ Important Notice

Please read the following carefully before using this project:

- **🚨 Terms of Service Risk**: Using this project may violate the terms of service of Anthropic and other upstream providers. Please review the relevant providers' user agreements before use; all risks arising from such use are borne solely by the user.
- **⚖️ Compliant Use**: Use this project only in compliance with the laws and regulations of your country or region. Any unlawful use is strictly prohibited.
- **📖 Disclaimer**: This project is provided for technical learning and research purposes only. The authors assume no liability for account bans, service interruptions, data loss, or any other direct or indirect damages resulting from the use of this project.
- **🚫 No Commercial Authorization**: The developers of this project have never authorized any individual or organization to conduct any form of commercial operation based on this project. Any commercial activity conducted in the name of or based on this project is unrelated to this project and its developers, and all resulting disputes, losses, and legal liabilities shall be borne solely by the party conducting such activity.

## ❤️ Sponsors

> [Want to appear here?](mailto:support@sub2api.org)

<table>

<tr>
<td width="180"><a href="https://cctk.ai/register?aff=SUB2API"><img src="assets/partners/logos/cctk.jpg" alt="CCTK.AI" width="150"></a></td>
<td>Thanks to CCTK.AI for sponsoring this project! <a href="https://cctk.ai/register?aff=SUB2API">CCTK.AI</a> is an AI API gateway focused on stability and cost-effectiveness, offering fast relay services for Claude, OpenAI, Gemini, and other popular models. It works seamlessly with Claude Code, Codex, and other mainstream coding tools, delivering the same model capabilities at a fraction of the official cost. Register via <a href="https://cctk.ai/register?aff=SUB2API">this link</a> for faster, more stable, and more affordable AI API access.</td>
</tr>

<tr>
<td width="180"><a href="https://www.openmodel.ai?ref=sub2api"><img src="assets/partners/logos/openmodel.jpg" alt="openmodel" width="150"></a></td>
<td>One API, every top model! <a href="https://www.openmodel.ai?ref=sub2api">OpenModel</a> is a production-grade, high-availability AI API gateway that makes your applications truly fast and stable: automatic failover, smart routing to the best-performing channel, and a production-grade SLA. An SLA that far surpasses any single provider — making stability your core competitive advantage. Works directly with Claude Code, Codex, and Gemini CLI. Register via this link to get started.</td>
</tr>

<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>Thanks to ETok.ai for sponsoring this project! ETok.ai is dedicated to building a one-stop AI programming tool service platform. We offer professional Claude Code packages and technical community services, with support for Google Gemini and OpenAI Codex. Through carefully designed plans and a professional tech community, we provide developers with reliable service guarantees and continuous technical support, making AI-assisted programming a true productivity tool. Click <a href="https://etok.ai">here</a> to register!</td>
</tr>

<tr>
<td width="180"><a href="https://apikey.fun/register?aff=SUB2API"><img src="assets/partners/logos/apikey-fun.png" alt="APIKEY.FUN" width="150"></a></td>
<td>Thanks to APIKEY.FUN for sponsoring this project! <a href="https://apikey.fun/register?aff=SUB2API">APIKEY.FUN</a> is one of the core contributors to the sub2api open-source project, dedicated to providing open, stable, and cost-effective AI API access. The platform supports API relay services for Claude, OpenAI, Gemini, and other popular models, with pricing starting from as low as 7% of the original rate. Register via the exclusive link: <a href="https://apikey.fun/register?aff=SUB2API">APIKEY</a> to enjoy up to 5% off on all recharges.</td>
</tr>

<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for sub2api users: if you register via <a href="https://aigocode.com/invite/SUB2API">this link</a>, you'll receive an extra 10% bonus credit on your first top-up!</td>
</tr>

<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>Huge thanks to BmoPlus for sponsoring this project! BmoPlus is a highly reliable AI account provider built strictly for heavy AI users and developers. They offer rock-solid, ready-to-use accounts and official top-up services for ChatGPT Plus / ChatGPT Pro (Full Warranty) / Claude Pro / Super Grok / Gemini Pro. By registering and ordering through <a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus - Premium AI Accounts & Top-ups</a>, users can unlock the mind-blowing rate of 10% of the official GPT subscription price (90% OFF)</td>
</tr>

<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>Thanks to Bestproxy for sponsoring this project! <a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> provides high-purity residential IPs with dedicated one-IP-per-account support. By combining real home networks with fingerprint isolation, it enables link environment isolation and reduces the probability of association-based risk control.</td>
</tr>

<tr>
<td width="180"><a href="https://pateway.ai/?ch=1tsfr51"><img src="assets/partners/logos/pateway.png" alt="pateway" width="150"></a></td>
<td>Thanks to PatewayAI for sponsoring this project! <a href="https://pateway.ai/?ch=1tsfr51">PatewayAI</a> is a premium API relay built for heavy AI developers, offering the full Claude and Codex series sourced 100% from official providers, with transparent token-level billing. Enterprise plans include high concurrency, dedicated management, contracts, and invoicing. Register now to get $3 in trial credits, top-ups from 60% off, and referral bonuses up to $150.</td>
</tr>

<tr>
<td width="180"><a href="https://api.pptoken.cc/register?promo=SUB2API"><img src="assets/partners/logos/pptoken.png" alt="pptoken" width="150"></a></td>
<td>Thanks to PPToken.cc for sponsoring this project! <a href="https://api.pptoken.cc/register?promo=SUB2API">PPToken.cc</a> specializes in GPT model API relay services, supporting Codex, Claude Code, OpenAI-compatible clients, and Gemini CLI integration. Top-ups are 1:1 (¥1 = $1 credit); GPT models start at 0.16x rate multiplier, with overall cost at roughly 2.2% of official pricing and first-token latency around 1 second — ideal for developers seeking low-cost, high-speed access to GPT model capabilities. Technical support: 24/7 real human responses (no bots), @tech in the group chat and get a reply within 10 minutes. Sponsor benefit: the first 200 users who register via the <a href="https://api.pptoken.cc/register?promo=SUB2API">exclusive registration link</a> and enter promo code `SUB2API` can claim free Codex / Claude Code trial credits — no minimum spend, no card required.
</td>
</tr>

<tr>
<td width="180"><a href="https://veilx.io/#/hello/SJRBRVDV"><img src="assets/partners/logos/veilx.png" alt="veilx" width="150"></a></td>
<td>Thanks to Veilx for sponsoring this project! <a href="https://veilx.io/#/hello/SJRBRVDV">Veilx</a> CDN is purpose-built for large-scale AI API traffic, deeply optimized for relay services and call chains across OpenAI, Claude, Gemini, and scenarios like chat, image generation, embeddings, and streaming — delivering lower latency and higher stability under heavy concurrency. It also offers China three-network optimized return lines, making it ideal for global AI relay platforms, overseas AI SaaS, and cross-border high-concurrency deployments.
</td>
</tr>

<tr>
<td width="180"><a href="https://roxybrowser.com/invite/bgGKG7"><img src="assets/partners/logos/RoxyBrowser.png" alt="veilx" width="150"></a></td>
<td>Thanks to RoxyBrowser for sponsoring this project! <a href="https://roxybrowser.com/invite/bgGKG7">RoxyBrowser</a> RoxyBrowser is the perfect partner for Sub2API: it features a built-in native Roxy AI Agent and high-quality native residential IPs, supports batch automation via simple commands, and significantly boosts security and efficiency for multi-account management! Click <a href="https://roxybrowser.com/invite/bgGKG7">this link</a> to sign up and receive a free residential IP package plus a 10% lifetime discount.
</td>
</tr>

<tr>
<td width="180"><a href="https://sui-xiang.com/"><img src="assets/partners/logos/sui-xiang.jpg" alt="sui-xiang" width="150"></a></td>
<td>Thanks to Suixiang AI Gateway for sponsoring this project! <a href="https://sui-xiang.com/">Suixiang AI Gateway</a> is a reliable and efficient API relay service provider offering relay services for Claude, Codex, Gemini, and more. A privacy-focused relay — no data reselling, no model dilution; privacy, transparency, and lightning-fast after-sales support. New accounts get ¥0.5 in trial credit daily by signing in; top-ups are 1:1, no subscription required, pay-as-you-go. Multi-line redundancy, cross-region disaster recovery, automatic failover, and uninterrupted long-link SSE. 99.9% availability — critical calls never fall behind.
</td>
</tr>

<tr>
<td width="180"><a href="https://www.proxy4free.com/?keyword=4yjqecpc"><img src="assets/partners/logos/proxy4free.png" alt="proxy4free" width="150"></a></td>
<td>Thanks to Proxy4Free for sponsoring this project! Proxy4Free is a data proxy service provider for developers and AI applications, offering residential proxies, static residential proxies, ISP proxies, and datacenter proxies for scenarios such as Web Scraping, Browser Automation, and AI Agents. With global IP resources, stable connections, and flexible switching, it helps developers improve data collection success rates and reduce the risk of IP bans. Register via <a href="https://www.proxy4free.com/?keyword=4yjqecpc">this link</a> to get started and easily build more stable and efficient automation workflows.
</td>
</tr>

<tr>
<td width="180"><a href="http://www.fastaitoken.com/register"><img src="assets/partners/logos/fastaitoken.jpg" alt="fastaitoken" width="150"></a></td>
<td>🎉 Thanks to FastAIToken for sponsoring this project! <a href="http://www.fastaitoken.com/register">FastAIToken</a> is an AI API aggregation platform for developers, supporting mainstream large models such as OpenAI, Claude, and Gemini. Top-up at 1:1 — 1 CNY = 1 USD of API credit — letting developers use the world's leading large model services at lower cost and with greater convenience.<br>

🚀 The platform offers a variety of channels to choose from: an ultra-low-price 0.02x OpenAI promotional group (limited time), groups as low as 0.25x OpenAI, 0.7x Claude with 95% fixed cache, and a 1.2x Claude Max channel. It also provides a public status page showing real-time availability, latency, and operating status of each group for transparent and reliable service, plus 7×24 human technical support (not bots) with fast responses to developer needs.
</td>
</tr>

<tr>
<td width="180"><a href="http://aimzoon.com"><img src="assets/partners/logos/aimzoon.jpg" alt="aimzoon" width="150"></a></td>
<td>Thanks to Aimzoon for sponsoring this project! <a href="http://aimzoon.com">Aimzoon</a> provides stable, cost-effective AI API access services, enabling developers to quickly connect popular AI services to coding tools such as Codex, Claude Code, and Gemini CLI. No complex configuration — faster onboarding, more stable calls, and lower costs. Ongoing promotions including discounted Codex rates and special pricing, with free trial credits upon registration, bringing AI coding into your daily workflow. <a href="http://aimzoon.com">Click here</a> to register and try it out!
</td>
</tr>

<tr>
<td width="180"><a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z"><img src="assets/partners/logos/claudeapi.jpg" alt="claudeapi" width="150"></a></td>
<td>Thanks to Claude API for sponsoring this project! <a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z">Claude API</a> is an official-channel API provider focused on Claude models. Built on official Anthropic keys and the official AWS Bedrock channel, it delivers a stable integration experience for Claude Code and Agent applications, supports the full Claude model lineup, and retains official capabilities such as Tool Use and long context. The service involves no reverse engineering and no model degradation, making it a great fit for heavy Claude Code users, Agent engineers, and enterprise engineering teams. Register via the <a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z">exclusive link</a> and contact customer support to receive free trial credits; invoicing and team onboarding are also supported.
</td>
</tr>

<tr>
<td width="180"><a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default"><img src="assets/partners/logos/code0.jpg" alt="code0" width="150"></a></td>
<td>Thanks to code0.ai for sponsoring this project! <a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default">code0.ai</a> is an AI coding workbench for developers and engineering teams, aggregating mainstream agent coding capabilities such as Claude Code and Codex, and covering common development scenarios including code generation, project understanding, debugging and fixing, code review, and documentation generation. It suits independent developers, Agent engineers, open-source maintainers, and enterprise R&D teams, with invoicing and team onboarding supported. Register via the <a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default">exclusive link</a> and contact customer support to receive free trial credits and experience a more efficient AI coding workflow.
</td>
</tr>

<tr>
<td width="180"><a href="https://nagora.ai/"><img src="assets/partners/logos/nagora.png" alt="Nagora" width="150"></a></td>
<td><a href="https://nagora.ai/">Nagora</a> is a multi-model AI API gateway built for developers and teams. With a single account and API key, you can access more than 26 leading text and image models through one unified interface. It is compatible with OpenAI, Anthropic, and Gemini protocols and integrates seamlessly with development tools such as Claude Code, Codex, and Gemini CLI. The platform provides intelligent routing, automatic failover, transparent pricing, and consolidated billing, along with budget management, rate limiting, and concurrency controls. This makes AI usage more reliable and manageable across individual development, team collaboration, and production environments. No changes to your existing application are required. Simply replace the Base URL and API key to complete the integration in as little as one minute.</td>
</tr>

<tr>
<td width="180"><a href="https://www.novada.com/?sub2api/"><img src="assets/partners/logos/novada.png" alt="Novada" width="150"></a></td>
<td>Thanks to <a href="https://www.novada.com/?sub2api/">Novada</a> for sponsoring this project! Novada provides residential, ISP, datacenter, and mobile proxies, along with Web Unlocker and Scraper APIs for developers building AI applications and automation workflows. With global IP coverage, flexible rotating and sticky sessions, and precise geo-targeting, Novada helps teams access web data reliably for AI agent workflows, cross-region testing, web research, and browser automation. Explore Novada to build more stable and scalable AI workflows.</td>
</tr>

<tr>
<td width="180"><a href="https://s.qiniu.com/u6rQrq"><img src="assets/partners/logos/qiniu.jpg" alt="Qiniu AI" width="150"></a></td>
<td>Thanks to Qiniu AI for sponsoring this project! Qiniu AI is the enterprise-grade large-model MaaS platform under Qiniu Cloud (02567.HK), offering one-stop access to 150+ mainstream models worldwide, compatible with the protocols of major global model providers, and covering full-modality capabilities including text, image, audio, video, and file processing, serving over 1.69 million enterprises and developers. Qiniu AI offers an exclusive benefit for Sub2API users: register via <a href="https://s.qiniu.com/u6rQrq">this link</a> — enterprise users get 12 million tokens free, and developers get 3 million tokens free.</td>
</tr>

<tr>
<td width="180"><a href="https://api.fenno.ai/s/dC4k"><img src="assets/partners/logos/fennoai.jpg" alt="FennoAI" width="150"></a></td>
<td>Thanks to FennoAI for sponsoring this project! FennoAI is a high-stability, high-performance API relay provider for enterprise R&D teams and developers, compatible with the OpenAI and Anthropic protocols and seamlessly integrating with mainstream AI coding tools such as Codex, Claude Code, and OpenCode. The platform delivers enterprise-grade stability, supporting call volumes of 100 billion tokens per day, and supports business-to-business settlement and invoicing for both domestic and overseas entities to meet enterprise R&D and procurement needs. As an exclusive benefit for Sub2API users, purchase a subscription via the <a href="https://api.fenno.ai/s/dC4k">exclusive link</a> to get $50 worth of Coding Plan credit for only $1.99. Referral rewards are also available: invite friends to purchase and earn up to 20% commission — the more you invite, the more you earn.</td>
</tr>

<tr>
<td width="180"><a href="https://lanox.ai/?c=6"><img src="assets/partners/logos/lanox.jpg" alt="LanoX AI" width="150"></a></td>
<td>Thank you to LanoX AI for sponsoring this project! <a href="https://lanox.ai/?c=6">LanoX AI</a> provides stable, cost-effective global model access services for developers, teams, and enterprises. 🎁 New User Benefits — Claim millions of free tokens, plus 500+ free models for easier low-cost testing, validation, and deployment 🧠 Global Leading Models — GPT · Claude · Gemini · Qwen · Grok... 🎬 Multimodal Creation — Seedance 2.0 · GPT Image · Gemini Nano Banana 🛡️ Enterprise-Grade Reliability — High availability 💎 native capability output 💎 no intelligence degradation 💎 no model mixing 💎 transparent usage and billing 💎 💰 Lower API Costs — Top-tier models from as low as 10% of official pricing, with clear documentation, simple integration, invoicing support, and enterprise-scale batch usage 🏢 Enterprise Choice — Ideal for AI products, Agents, content platforms, and R&D teams with high-volume model usage</td>
</tr>

<tr>
<td width="180"><a href="https://www.rapidproxy.io/?ref=sub2api"><img src="assets/partners/logos/rapidproxy.jpg" alt="RapidProxy" width="150"></a></td>
<td><a href="https://www.rapidproxy.io/?ref=sub2api">RapidProxy</a> is a data collection proxy solution built for developers, providing stable and reliable residential proxy services. With 90M+ global residential IPs and 200+ country coverage, intelligent rotation, and precise geo-targeting, it helps projects such as web scraping, AI data training, SEO monitoring, and e-commerce data analysis break through access restrictions and improve data collection efficiency. It supports mainstream automation frameworks such as Playwright, Selenium, and Puppeteer, with prices as low as $0.65/GB — <a href="https://www.rapidproxy.io/?ref=sub2api">start your free test now</a>.</td>
</tr>

<tr>
<td width="180"><a href="https://hao.ai"><img src="assets/partners/logos/haoai.png" alt="hao.ai" width="150"></a></td>
<td><a href="https://hao.ai">hao.ai</a> is a high-speed, stable unified large-model API gateway for developers and teams. With a single API Key and a unified interface, you can access mainstream models such as GPT, Claude, and xAI Grok, with compatibility for common protocols and SDKs including OpenAI and Anthropic. The platform provides model routing, failover, team management, and complete request logs, with model prices as low as 15% of official reference pricing, helping users build AI applications more simply, more reliably, and at lower cost.</td>
</tr>

</table>

## Overview

Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions. Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.

## Features

- **Multi-Account Management** - Support multiple upstream account types (OAuth, API Key)
- **API Key Distribution** - Generate and manage API Keys for users
- **Precise Billing** - Token-level usage tracking and cost calculation
- **Smart Scheduling** - Intelligent account selection with sticky sessions
- **Concurrency Control** - Per-user and per-account concurrency limits
- **Rate Limiting** - Configurable request and token rate limits
- **Built-in Payment System** - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed ([Configuration Guide](docs/PAYMENT.md))
- **Admin Dashboard** - Web interface for monitoring and management
- **Composite Groups** - Admin routing layer that resolves requested models to concrete providers for multi-provider groups ([Operator Guide](docs/COMPOSITE_GROUPS.md))
- **External System Integration** - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard

## Ecosystem

Community projects that extend or integrate with Sub2API:

| Project | Description | Features |
|---------|-------------|----------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~Self-service payment system~~ | **Now Built-in** — Payment is now integrated into Sub2API, no separate deployment needed. See [Payment Configuration Guide](docs/PAYMENT.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | Mobile admin console | Cross-platform app (iOS/Android/Web) for user management, account management, monitoring dashboard, and multi-backend switching; built with Expo + React Native |

## Tech Stack

| Component | Technology |
|-----------|------------|
| Backend | Go 1.25.7, Gin, Ent |
| Frontend | Vue 3.4+, Vite 5+, TailwindCSS |
| Database | PostgreSQL 15+ |
| Cache/Queue | Redis 7+ |

---

## Nginx Reverse Proxy Note

When using Nginx as a reverse proxy for Sub2API (or CRS) with Codex CLI, add the following to the `http` block in your Nginx configuration:

```nginx
underscores_in_headers on;
```

Nginx drops headers containing underscores by default (e.g. `session_id`), which breaks sticky session routing in multi-account setups.

---

## Deployment

### Method 1: Script Installation (Recommended)

One-click installation script that downloads pre-built binaries from GitHub Releases.

#### Prerequisites

- Linux server (amd64 or arm64)
- PostgreSQL 15+ (installed and running)
- Redis 7+ (installed and running)
- Root privileges

#### Installation Steps

```bash
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
```

The script will:
1. Detect your system architecture
2. Download the latest release
3. Install binary to `/opt/sub2api`
4. Create systemd service
5. Configure system user and permissions

#### Post-Installation

```bash
# 1. Start the service
sudo systemctl start sub2api

# 2. Enable auto-start on boot
sudo systemctl enable sub2api

# 3. Open Setup Wizard in browser
# http://YOUR_SERVER_IP:8080
```

The Setup Wizard will guide you through:
- Database configuration
- Redis configuration
- Admin account creation

#### Upgrade

You can upgrade directly from the **Admin Dashboard** by clicking the **Check for Updates** button in the top-left corner.

The web interface will:
- Check for new versions automatically
- Download and apply updates with one click
- Support rollback if needed

#### Useful Commands

```bash
# Check status
sudo systemctl status sub2api

# View logs
sudo journalctl -u sub2api -f

# Restart service
sudo systemctl restart sub2api

# Uninstall
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
```

---

### Method 2: Docker Compose (Recommended)

Deploy with Docker Compose, including PostgreSQL and Redis containers.

#### Prerequisites

- Docker 20.10+
- Docker Compose v2+

#### Quick Start (One-Click Deployment)

Use the automated deployment script for easy setup:

```bash
# Create deployment directory
mkdir -p sub2api-deploy && cd sub2api-deploy

# Download and run deployment preparation script
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash

# Start services
docker compose up -d

# View logs
docker compose logs -f sub2api
```

**What the script does:**
- Downloads `docker-compose.local.yml` (saved as `docker-compose.yml`) and `.env.example`
- Generates secure credentials (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
- Creates `.env` file with auto-generated secrets
- Creates data directories (uses local directories for easy backup/migration)
- Displays generated credentials for your reference

#### Manual Deployment

If you prefer manual setup:

```bash
# 1. Clone the repository
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy

# 2. Copy environment configuration
cp .env.example .env
chmod 600 .env

# 3. Edit configuration (generate secure passwords)
nano .env
```

**Required configuration in `.env`:**

```bash
# PostgreSQL password (REQUIRED)
POSTGRES_PASSWORD=your_secure_password_here

# JWT Secret (RECOMMENDED - keeps users logged in after restart)
JWT_SECRET=your_jwt_secret_here

# TOTP Encryption Key (RECOMMENDED - preserves 2FA after restart)
TOTP_ENCRYPTION_KEY=your_totp_key_here

# Optional: Admin account
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password

# Optional: Custom port
SERVER_PORT=8080
```

**Generate secure secrets:**
```bash
# Generate JWT_SECRET
openssl rand -hex 32

# Generate TOTP_ENCRYPTION_KEY
openssl rand -hex 32

# Generate POSTGRES_PASSWORD
openssl rand -hex 32
```

```bash
# 4. Create data directories (for local version)
mkdir -p data postgres_data redis_data

# 5. Start all services
# Option A: Local directory version (recommended - easy migration)
docker compose -f docker-compose.local.yml up -d

# Option B: Named volumes version (simple setup)
docker compose up -d

# 6. Check status
docker compose -f docker-compose.local.yml ps

# 7. View logs
docker compose -f docker-compose.local.yml logs -f sub2api
```

#### Deployment Versions

| Version | Data Storage | Migration | Best For |
|---------|-------------|-----------|----------|
| **docker-compose.local.yml** | Local directories | ✅ Easy (tar entire directory) | Production, frequent backups |
| **docker-compose.yml** | Named volumes | ⚠️ Requires docker commands | Simple setup |

**Recommendation:** Use `docker-compose.local.yml` (deployed by script) for easier data management.

#### Access

Open `http://YOUR_SERVER_IP:8080` in your browser.

If admin password was auto-generated, find it in logs:
```bash
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```

#### Upgrade

```bash
# Pull latest image and recreate container
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```

#### Easy Migration (Local Directory Version)

When using `docker-compose.local.yml`, migrate to a new server easily:

```bash
# On source server
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/

# Transfer to new server
scp sub2api-complete.tar.gz user@new-server:/path/

# On new server
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker compose -f docker-compose.local.yml up -d
```

#### Useful Commands

```bash
# Stop all services
docker compose -f docker-compose.local.yml down

# Restart
docker compose -f docker-compose.local.yml restart

# View all logs
docker compose -f docker-compose.local.yml logs -f

# Remove all data (caution!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```

---

### Method 3: Apple container (macOS)

Apple-silicon Macs running macOS 26 can run the full Sub2API, PostgreSQL, and Redis stack with Apple `container` 1.1.0 or newer:

```bash
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
```

This is an operator-managed local workflow; Docker Compose remains the recommended production path. See [deploy/APPLE_CONTAINER.md](deploy/APPLE_CONTAINER.md) for lifecycle commands, persistence, upgrades, and runtime limitations.

---

### Method 4: Build from Source

Build and run from source code for development or customization.

#### Prerequisites

- Go 1.21+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+

#### Build Steps

```bash
# 1. Clone the repository
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api

# 2. Install pnpm (if not already installed)
npm install -g pnpm

# 3. Build frontend
cd frontend
pnpm install
pnpm run build
# Output will be in ../backend/internal/web/dist/

# 4. Build backend with embedded frontend
cd ../backend
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server

# 5. Create configuration file
cp ../deploy/config.example.yaml ./config.yaml

# 6. Edit configuration
nano config.yaml
```

> **Note:** The `-tags embed` flag embeds the frontend into the binary. Without this flag, the binary will not serve the frontend UI.

**Key configuration in `config.yaml`:**

```yaml
server:
  host: "0.0.0.0"
  port: 8080
  mode: "release"

database:
  host: "localhost"
  port: 5432
  user: "postgres"
  password: "your_password"
  dbname: "sub2api"

redis:
  host: "localhost"
  port: 6379
  username: ""
  password: ""

jwt:
  secret: "change-this-to-a-secure-random-string"
  expire_hour: 24

default:
  user_concurrency: 5
  user_balance: 0
  api_key_prefix: "sk-"
  rate_multiplier: 1.0
```

### Sora Status (Temporarily Unavailable)

> ⚠️ Sora-related features are temporarily unavailable due to technical issues in upstream integration and media delivery.
> Please do not rely on Sora in production at this time.
> Existing `gateway.sora_*` configuration keys are reserved and may not take effect until these issues are resolved.

Additional security-related options are available in `config.yaml`:

- `cors.allowed_origins` for CORS allowlist
- `security.url_allowlist` for upstream/pricing/CRS host allowlists
- `security.url_allowlist.enabled` to disable URL validation (use with caution)
- `security.url_allowlist.allow_insecure_http` to allow HTTP URLs when validation is disabled
- `security.url_allowlist.allow_private_hosts` to allow private/local IP addresses
- `security.response_headers.enabled` to enable configurable response header filtering (disabled uses default allowlist)
- `security.csp` to control Content-Security-Policy headers
- `billing.circuit_breaker` to fail closed on billing errors
- `security.trust_forwarded_ip_for_api_key_acl` enables legacy raw forwarded-header takeover (enabled by default for upgrade compatibility); disable it to enforce `server.trusted_proxies`, which should contain only the exact proxy CIDRs that connect directly to Sub2API
- `security.forwarded_client_ip_headers` configures up to 16 third-party CDN client-IP header names; they are checked in order before the built-in headers only while legacy takeover is enabled
- `turnstile.required` to require Turnstile in release mode

Custom client-IP headers can be set in YAML or as a comma-separated environment variable:

```bash
SECURITY_FORWARDED_CLIENT_IP_HEADERS=True-Client-IP,X-CDN-Client-IP
```

Header names are validated, canonicalized, and de-duplicated. The admin security settings can update the list without a restart; new installations persist YAML/environment defaults and existing installations backfill a missing database value. When legacy takeover is disabled, all custom and built-in raw forwarding headers are ignored and Gin uses only `server.trusted_proxies`. While takeover is enabled, firewall the origin to CDN/proxy addresses and make the edge overwrite every trusted client-IP header. See [`deploy/EDGE_SECURITY.md`](deploy/EDGE_SECURITY.md) for the complete migration and trust-boundary rules.

**⚠️ Security Warning: HTTP URL Configuration**

When `security.url_allowlist.enabled=false`, the system performs minimal URL validation and **allows HTTP URLs by default** (dev-friendly mode; Docker Compose deployments use the same default). For production, explicitly tighten this to HTTPS-only:

```yaml
security:
  url_allowlist:
    enabled: false                # Disable allowlist checks
    allow_insecure_http: false    # HTTPS only (recommended for production)
```

**Or via environment variable:**

```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```

**Risks of allowing HTTP:**
- API keys and data transmitted in **plaintext** (vulnerable to interception)
- Susceptible to **man-in-the-middle (MITM) attacks**
- **NOT suitable for production** environments

**When to use HTTP:**
- ✅ Development/testing with local servers (http://localhost)
- ✅ Internal networks with trusted endpoints
- ✅ Testing account connectivity before obtaining HTTPS
- ❌ Production environments (use HTTPS only)

**Example error for HTTP URLs when `allow_insecure_http: false` is set:**
```
Invalid base URL: invalid url scheme: http
```

If you disable URL validation or response header filtering, harden your network layer:
- Enforce an egress allowlist for upstream domains/IPs
- Block private/loopback/link-local ranges
- Enforce TLS-only outbound traffic
- Strip sensitive upstream response headers at the proxy

#### OpenAI Responses WebSocket ingress limits

`gateway.openai_ws` bounds the lifetime and aggregate count of client-facing
Responses WebSocket sessions. These safeguards apply independently from
per-turn user and account concurrency slots, which are released between turns.

```yaml
gateway:
  openai_ws:
    # Total time to receive and decompress the first client message.
    client_first_message_timeout_seconds: 30
    # Close a client socket idle between completed turns; 0 disables this safeguard.
    ingress_inter_turn_idle_timeout_seconds: 300
    # Distributed API-key limit for live client ingress sessions; 0 disables it.
    max_ingress_connections_per_api_key: 64
```

The first-message timeout is a total read deadline. Deployments that accept
large contexts or image-heavy requests over slower links can raise it to
120-300 seconds. It expires before HTTP bridge routing, so bridge mode does not
override this limit.

The connection cap is coordinated through Redis using a 60-second lease that
is refreshed every 20 seconds. A process that cannot confirm a lease for a
full lease lifetime closes its local WebSocket rather than continuing outside
the global cap.

Enable the v2 mode router before selecting an account-level WS mode such as
`http_bridge`:

```yaml
gateway:
  openai_ws:
    mode_router_v2_enabled: true
```

Or set `GATEWAY_OPENAI_WS_MODE_ROUTER_V2_ENABLED=true` in the environment.
Use `http_bridge` for client-WebSocket/upstream-HTTP operation when rolling out
or mitigating upstream WebSocket issues.

#### ⚠️ Important: Creating the Admin Account

The initial admin account is **only created via the setup wizard** (served at `http://<host>:8080` on first run). The `default.admin_email` / `default.admin_password` fields in `config.yaml` are **not used** to create it — they exist in the template for historical reasons.

Because step 5 above pre-creates `config.yaml`, the setup wizard will be **skipped on first run**: the server detects an existing config and boots straight into normal mode with an empty `users` table, so the first login attempt fails with `invalid email or password`.

**Two ways to create the admin account:**

1. **Recommended — let the wizard generate `config.yaml`:** Skip step 5 (do not run the `cp`). Start `./sub2api` directly; the setup wizard at `http://localhost:8080` walks you through database, Redis, and admin account setup, then writes `config.yaml` for you.

2. **If you already created `config.yaml`:** Temporarily move it aside so the wizard can trigger on first run, then restore it afterwards:
   ```bash
   mv config.yaml config.yaml.bak
   ./sub2api        # wizard runs at http://localhost:8080 and writes a fresh config.yaml
   # stop the server (Ctrl+C) once the wizard completes, then restore your config:
   mv config.yaml.bak config.yaml
   ./sub2api        # restart in normal mode and log in with the admin you just created
   ```

```bash
# 6. Run the application
./sub2api
```

#### Development Mode

```bash
# Backend (with hot reload)
cd backend
go run ./cmd/server

# Frontend (with hot reload)
cd frontend
pnpm run dev
```

#### Code Generation

When editing `backend/ent/schema`, regenerate Ent + Wire:

```bash
cd backend
go generate ./ent
go generate ./cmd/server
```

---

## Simple Mode

Simple Mode is designed for individual developers or internal teams who want quick access without full SaaS features.

- Enable: Set environment variable `RUN_MODE=simple`
- Difference: Hides SaaS-related features and skips billing process
- Security note: In production, you must also set `SIMPLE_MODE_CONFIRM=true` to allow startup

---

## Asynchronous Image Tasks

Long-running OpenAI/Grok image generation and editing can be submitted through `/v1/images/generations/async` or `/v1/images/edits/async`, then polled at `/v1/images/tasks/{task_id}` without holding a CDN connection open. See [Asynchronous Image Tasks](docs/ASYNC_IMAGE_TASKS.md) for request and response examples.

---

## Grok / xAI Support

Sub2API supports both Grok subscription accounts through xAI OAuth and standard xAI API-key accounts. Both account types forward OpenAI-compatible Responses traffic to xAI.

### Supported Scope

- Platform name: `grok`
- Account types: OAuth subscription accounts and xAI API-key accounts
- Public Responses targets: `/v1/responses`, `/responses`, and `/backend-api/codex/responses`, forwarded to the Grok subscription proxy for OAuth accounts or `https://api.x.ai/v1/responses` for API-key accounts
- Public Claude-compatible target: `/v1/messages`, converted to xAI Responses and returned as Anthropic Messages output for Claude CLI style clients
- Public Chat Completions targets: `/v1/chat/completions` and `/chat/completions`, forwarded to the account-type-specific xAI upstream
- Codex CLI style Responses WebSocket ingress is accepted on the Responses targets and bridged to xAI HTTP/SSE Responses upstream
- Text models: `grok-4.5`, `grok-4.3`, `grok-build-0.1`, `grok-composer-2.5-fast`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning`, and `grok-4.20-multi-agent-0309`
- Media targets for Grok groups: `/v1/images/generations`, `/images/generations`, `/v1/images/edits`, `/images/edits`, `/v1/videos/generations`, `/videos/generations`, `/v1/videos/edits`, `/videos/edits`, `/v1/videos/extensions`, `/videos/extensions`, `/v1/videos/{request_id}`, and `/videos/{request_id}`. Generation, editing, and extension requests require the group image-generation permission.
- Media models: `grok-imagine`, `grok-imagine-image-quality`, `grok-imagine-image`, `grok-imagine-edit`, `grok-imagine-video`, and `grok-imagine-video-1.5`
- JSON image-edit and video-generation requests accept image references in `image`, `images`, `reference_images`, and `mask` objects. Use `url` for xAI-compatible payloads; the legacy `image_url` field remains accepted and is normalized to `url` before forwarding.
- Out of scope for this provider: TTS, transcription, browser automation, cookies, and Grok web scraping

### OAuth Configuration

The Grok OAuth flow uses PKCE and does not require committing private secrets. The default client details follow the public xAI OAuth flow used by compatible clients, and every value can be overridden by environment variable:

| Variable | Default |
|----------|---------|
| `XAI_OAUTH_CLIENT_ID` | Public xAI OAuth client ID |
| `XAI_OAUTH_SCOPE` | `openid profile email offline_access grok-cli:access api:access` |
| `XAI_OAUTH_REDIRECT_URI` | `http://127.0.0.1:56121/callback` |
| `XAI_OAUTH_AUTHORIZE_URL` | `https://auth.x.ai/oauth2/authorize` |
| `XAI_OAUTH_TOKEN_URL` | `https://auth.x.ai/oauth2/token` |
| `XAI_BASE_URL` | `https://api.x.ai/v1`; runtime-diagnostics override (account `base_url` controls request forwarding) |
| `XAI_GROK_CLI_VERSION` | `0.2.114`; optional override for the client identity sent to `cli-chat-proxy.grok.com`. The pinned value is also the floor: an override below it is dropped |

Administrators can create Grok OAuth or API-key accounts from the dashboard. OAuth authorization and reauthorization are also available through the admin API:

| Endpoint | Purpose |
|----------|---------|
| `POST /api/v1/admin/grok/oauth/auth-url` | Generate an xAI OAuth authorization URL |
| `POST /api/v1/admin/grok/oauth/exchange-code` | Exchange a callback URL, query string, or code for OAuth credentials |
| `POST /api/v1/admin/grok/oauth/refresh-token` | Validate or refresh a Grok refresh token |
| `POST /api/v1/admin/grok/accounts/:id/refresh` | Refresh an existing Grok account |

OAuth credential storage reuses the existing account JSON fields: `access_token`, `refresh_token`, `token_type`, `expires_at`, `base_url`, optional `email`, optional `subscription_tier`, and `entitlement_status`. OAuth inference defaults to `https://cli-chat-proxy.grok.com/v1`; existing OAuth accounts that stored the old `https://api.x.ai/v1` default are redirected to the subscription proxy at runtime. Explicit custom upstreams remain unchanged.

For API-key accounts, select **Grok → API Key** in the create-account dialog. The official base URL defaults to `https://api.x.ai/v1`; credentials use the existing `base_url` and `api_key` account fields. OAuth accounts continue to use the subscription flow above.

### Grok Build CLI Configuration

1. In the Sub2API admin dashboard, add either a `grok` OAuth account and complete xAI authorization, or add a Grok API-key account.
2. Create a Grok group, attach the account to it, then create a Sub2API API key assigned to that group.
3. In the user API-key page, click **Use Key** and select **Grok CLI**. The modal generates the correct file and base URL for macOS/Linux or Windows. It also provides an OpenCode configuration on the **OpenCode** tab.
4. If configuring manually, save the following as `~/.grok/config.toml` (Windows: `%USERPROFILE%\.grok\config.toml`):

```toml
[models]
default = "grok"
web_search = "grok"

[model."grok"]
model = "grok-4.5"
base_url = "https://your-sub2api.example.com/v1"
name = "Grok 4.5"
api_key = "sk-your-sub2api-key"
api_backend = "responses"
context_window = 1000000
supports_backend_search = true
```

Back up an existing `config.toml` before merging the entry. The file contains a Sub2API API key, so keep it private and restrict its permissions where supported. Verify the effective configuration and make a smoke request:

```bash
grok inspect
grok -p "Reply with sub2api-ok" -m grok
```

The `base_url` above is the public Sub2API URL ending in `/v1`, not `api.x.ai` or the internal xAI OAuth proxy URL.

### Usage And Quota Display

xAI quota is passive. Sub2API does not invent subscription quota values; it records whitelisted xAI rate-limit headers from successful or rate-limited upstream responses when xAI sends them. Before the first usable upstream response, the dashboard shows quota as unknown and still displays local Sub2API usage stats.

`401` responses temporarily remove accounts with invalid credentials from scheduling. `403` responses are treated as access or entitlement failures instead of token-refresh loops. `429` responses use `Retry-After` or a short cooldown to temporarily remove the account from scheduling.

New Grok image and video generation requests use a media-specific eligibility check. API-key accounts remain eligible. OAuth accounts require positive paid-entitlement evidence from the xAI billing probe; Free, forbidden, missing, malformed, and inconclusive billing observations are excluded from new media generation. Unobserved OAuth accounts are probed before the first media request is forwarded, and imports run the billing-first quota probe proactively. Chat requests and video status lookups are not affected by this media-only quarantine. If no eligible account remains, the media endpoint returns HTTP `503` with error type `grok_media_no_eligible_account`.

Administrators can override automatic media eligibility through the account create/update API by setting `extra.grok_media_eligible` to `false` (exclude) or `true` (force eligible). On update, set it to `null` to remove the override and return to automatic probe-based behavior; omitting the field preserves the current override. A weekly allowance period alone is not treated as a paid tier signal. Successful image responses must contain at least one actual image output; empty HTTP `200` responses trigger account failover instead of being counted and returned as successful generations.

---

## Antigravity Support

Sub2API supports [Antigravity](https://antigravity.so/) accounts. After authorization, dedicated endpoints are available for Claude and Gemini models.

### Dedicated Endpoints

| Endpoint | Model |
|----------|-------|
| `/antigravity/v1/messages` | Claude models |
| `/antigravity/v1beta/` | Gemini models |

### Claude Code Configuration

```bash
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
export ANTHROPIC_AUTH_TOKEN="sk-xxx"
```

### Hybrid Scheduling Mode

Antigravity accounts support optional **hybrid scheduling**. When enabled, the general endpoints `/v1/messages` and `/v1beta/` will also route requests to Antigravity accounts.

> **⚠️ Warning**: Anthropic Claude and Antigravity Claude **cannot be mixed within the same conversation context**. Use groups to isolate them properly.

---

## Project Structure

```
sub2api/
├── backend/                  # Go backend service
│   ├── cmd/server/           # Application entry
│   ├── internal/             # Internal modules
│   │   ├── config/           # Configuration
│   │   ├── model/            # Data models
│   │   ├── service/          # Business logic
│   │   ├── handler/          # HTTP handlers
│   │   └── gateway/          # API gateway core
│   └── resources/            # Static resources
│
├── frontend/                 # Vue 3 frontend
│   └── src/
│       ├── api/              # API calls
│       ├── stores/           # State management
│       ├── views/            # Page components
│       └── components/       # Reusable components
│
└── deploy/                   # Deployment files
    ├── docker-compose.yml    # Docker Compose configuration
    ├── .env.example          # Environment variables for Docker Compose
    ├── config.example.yaml   # Full config file for binary deployment
    └── install.sh            # One-click installation script
```

## Star History

<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
 </picture>
</a>

---

## License

This project is licensed under the [GNU Lesser General Public License v3.0](LICENSE) (or later).

Copyright (c) 2026 Wesley Liddick

---

<div align="center">

**If you find this project useful, please give it a star!**

</div>


## /README_CN.md

<div align="center">

<img src="assets/logo.svg" alt="Sub2API Logo" width="128" />

# Sub2API

[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/)
[![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/)
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)

<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>

**AI API 网关平台 - 订阅配额分发管理**

[English](README.md) | 中文 | [日本語](README_JA.md)

</div>


## ⚠️ 重要提醒

使用本项目前，请务必仔细阅读以下内容：

- **🚨 服务条款风险**：使用本项目可能违反 Anthropic 等上游服务商的服务条款。请在使用前仔细阅读相关服务商的用户协议，由此产生的一切风险由用户自行承担。
- **⚖️ 合规使用**：请在符合您所在国家或地区法律法规的前提下使用本项目，严禁将其用于任何违法违规用途。
- **📖 免责声明**：本项目仅供技术学习与研究使用，作者不对因使用本项目导致的账户封禁、服务中断、数据丢失或其他任何直接或间接损失承担责任。
- **🚫 无商业授权**：本项目从未授权任何个人或组织基于本项目开展任何形式的商业化运营。任何以本项目名义或基于本项目从事的商业行为均与本项目及其开发者无关，由此产生的一切纠纷、损失和法律责任由行为主体自行承担。

## ❤️ 赞助商

> [想出现在这里？](mailto:support@sub2api.org)

<table>

<tr>
<td width="180"><a href="https://cctk.ai/register?aff=SUB2API"><img src="assets/partners/logos/cctk.jpg" alt="CCTK.AI" width="150"></a></td>
<td>感谢 CCTK.AI 赞助了本项目！<a href="https://cctk.ai/register?aff=SUB2API">CCTK.AI</a> 是一个专注于稳定与性价比的 AI API 网关平台，提供 Claude、OpenAI、Gemini 等主流模型的高速中转服务，无缝兼容 Claude Code、Codex 等主流编程工具，以远低于官方的成本获得同等的模型能力。点击<a href="https://cctk.ai/register?aff=SUB2API">此链接</a>注册，即刻体验更快、更稳、更省的 AI API 接入。</td>
</tr>

<tr>
<td width="180"><a href="https://www.openmodel.ai?ref=sub2api"><img src="assets/partners/logos/openmodel.jpg" alt="openmodel" width="150"></a></td>
<td>一个API，顶级模型随便用！<a href="https://www.openmodel.ai?ref=sub2api">OpenModel</a> 专注于生产级、高可用的 AI API 网关，让你的应用真正做到高速稳定：自动故障转移、智能选最优渠道、生产级 SLA 保障。远超单一供应商的 SLA，让稳定性成为您的核心竞争力。</td>
</tr>

<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>感谢 ETok.ai 赞助了本项目！ETok.ai 致力于打造一站式 AI 编程工具服务平台。我们提供 Claude Code 专业套餐及技术社群服务，同时支持 Google Gemini 和 OpenAI Codex。通过精心设计的套餐方案和专业的技术社群，为开发者提供稳定的服务保障和持续的技术支持，让 AI 辅助编程真正成为开发者的生产力工具。点击<a href="https://etok.ai">这里</a>注册！</td>
</tr>

<tr>
<td width="180"><a href="https://apikey.fun/register?aff=SUB2API"><img src="assets/partners/logos/apikey-fun.png" alt="APIKEY.FUN" width="150"></a></td>
<td>感谢 APIKEY.FUN 赞助了本项目！<a href="https://apikey.fun/register?aff=SUB2API">APIKEY.FUN</a> 是 sub2api 开源项目的核心贡献者之一，致力于提供开放、稳定、高性价比的 AI API 接入服务。平台支持 Claude、OpenAI、Gemini 等热门模型的 API 中转服务，价格低至官方原价的 7%。通过专属链接 <a href="https://apikey.fun/register?aff=SUB2API">APIKEY</a> 注册，可享受充值最高 95 折优惠。</td>
</tr>

<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>感谢 AIGoCode 赞助了本项目！AIGoCode 是一站式集成 Claude Code、Codex 以及最新 Gemini 模型的综合平台，为您提供稳定、高效、高性价比的 AI 编程服务。平台提供灵活的订阅方案，零封号风险，免 VPN 直连，响应极速。AIGoCode 为 sub2api 用户准备了专属福利：通过<a href="https://aigocode.com/invite/SUB2API">此链接</a>注册，首次充值可额外获得 10% 赠送额度！</td>
</tr>

<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>感谢 BmoPlus 赞助了本项目！BmoPlus 是一家专为AI订阅重度用户打造的可靠 AI 账号代充服务商，提供稳定的 ChatGPT Plus / ChatGPT Pro(全程质保) / Claude Pro / Super Grok / Gemini Pro 的官方代充&成品账号。 通过<a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus AI成品号专卖/代充</a>注册下单的用户，可享GPT 官网订阅一折 的震撼价格！</td>
</tr>

<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>感谢 Bestproxy 赞助了本项目！<a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> 是一家提供高纯度住宅IP，支持一号一IP独享，结合真实家庭网络与指纹隔离，可实现链路环境隔离，降低关联风控概率。</td>
</tr>

<tr>
<td width="180"><a href="https://pateway.ai/?ch=1tsfr51"><img src="assets/partners/logos/pateway.png" alt="pateway" width="150"></a></td>
<td>感谢 PatewayAI 赞助了本项目！PatewayAI 是一家面向重度 AI 开发者、专注官方直连的高品质模型 API 中转服务商。提供 Claude 全系列与 Codex 系列模型，100% 官方源直供，不掺假不注水，欢迎检验。计费透明，Token 级账单可逐笔核验。
同时支持企业级高并发，并为企业客户提供了专业的管理平台，企业客户可签订正式合同并开具发票，更多详情进入官网获取联系方式。
现在通过 <a href="https://pateway.ai/?ch=1tsfr51">此链接</a> 注册即送 $3 试用额度，用户充值低至 6 折，邀请好友双向赠送，邀请奖励可达 $150。</td>
</tr>

<tr>
<td width="180"><a href="https://api.pptoken.cc/register?promo=SUB2API"><img src="assets/partners/logos/pptoken.png" alt="pptoken" width="150"></a></td>
<td>感谢 PPToken.cc 赞助本项目！ <a href="https://api.pptoken.cc/register?promo=SUB2API">PPToken.cc</a> 主打 GPT 系列模型 API 中转服务，支持 Codex、Claude Code、OpenAI 兼容客户端及 Gemini CLI 等工具接入。充值 1:1，1 元=1 美元额度；GPT 模型最低 0.16 倍倍率，综合成本约为官方价格的 0.22 折，最快首字 Token 约 1 秒，适合开发者低成本、高响应速度接入 GPT 模型能力。技术支持： 7×24 小时真人响应（不是机器人），群内@技术，10 分钟内有回复 。赞助商福利：前 200 名用户通过 <a href="https://api.pptoken.cc/register?promo=SUB2API">[专属注册链接]</a> 注册，输入优惠码 `SUB2API`，可领取 Codex / Claude Code 免费试用额度，无门槛、不绑卡。
</td>
</tr>

<tr>
<td width="180"><a href="https://veilx.io/#/hello/SJRBRVDV"><img src="assets/partners/logos/veilx.png" alt="veilx" width="150"></a></td>
<td>感谢 Veilx 赞助本项目！ <a href="https://veilx.io/#/hello/SJRBRVDV">Veilx</a> CDN 专为超大规模 API 请求场景打造，针对 AI 中转站业务与 AI API 调用链路进行了深度优化，轻松应对高并发、高频请求与大流量传输，为开发者与企业提供更快、更稳、更低延迟的加速体验。无论是 OpenAI、Claude、Gemini 等 AI 接口中转，还是聊天、绘图、Embedding、流式输出等复杂场景，Veilx 都能显著提升响应速度与连接稳定性，有效降低网络波动带来的超时与失败问题。同时，Veilx 提供中国三网优化回国极速线路，大幅提升中国大陆地区访问海外 AI 服务的速度与稳定性，特别适合全球 AI 中转平台、海外 AI SaaS、跨境业务与高并发 API 系统部署。专为 AI API 而生，让你的 AI 中转服务更快、更稳、更省心。<a href="https://veilx.io/#/hello/SJRBRVDV">购买地址</a>
</td>
</tr>

<tr>
<td width="180"><a href="https://roxybrowser.com/invite/bgGKG7"><img src="assets/partners/logos/RoxyBrowser.png" alt="RoxyBrowser" width="150"></a></td>
<td>感谢 RoxyBrowser 赞助本项目！<a href="https://roxybrowser.com/invite/bgGKG7">RoxyBrowser</a> 是 Sub2API 的理想搭档：内置原生 Roxy AI Agent 与高质量原生住宅 IP，支持通过简单命令实现批量自动化，显著提升多账号管理的安全性与效率！点击<a href="https://roxybrowser.com/invite/bgGKG7">此链接</a>注册，可领取免费住宅 IP 套餐与终身 9 折优惠。
</td>
</tr>

<tr>
<td width="180"><a href="https://sui-xiang.com/"><img src="assets/partners/logos/sui-xiang.jpg" alt="sui-xiang" width="150"></a></td>
<td>感谢 随想AI网关 赞助本项目！<a href="https://sui-xiang.com/">随想AI网关</a>  是一家可靠高效的 API 中继服务提供商，提供 Claude、Codex、Gemini 等的中继服务。注重隐私的中转站·无数据倒卖·无模型掺水，隐私，透明，极速售后。新账户注册每日签到就送 0.5 元测试额度，充值额度 1:1，无需订阅，按量付费。多线路冗余、跨区域容灾、自动故障切换,长链路 SSE 不中断。99.9% 可用性,关键调用从不掉队。
</td>
</tr>

<tr>
<td width="180"><a href="https://www.proxy4free.com/?keyword=4yjqecpc"><img src="assets/partners/logos/proxy4free.png" alt="proxy4free" width="150"></a></td>
<td>感谢 Proxy4Free 赞助本项目！Proxy4Free 是面向开发者和 AI 应用的数据代理服务商，提供住宅代理、静态住宅代理、ISP 代理及数据中心代理等多种代理解决方案，适用于 Web Scraping、Browser Automation、AI Agent 等场景。支持全球 IP 资源、稳定连接与灵活切换，帮助开发者提升数据采集成功率，降低 IP 封禁风险。通过<a href="https://www.proxy4free.com/?keyword=4yjqecpc">此链接注册</a>即可开始体验，轻松构建更稳定、高效的自动化工作流。
</td>
</tr>

<tr>
<td width="180"><a href="http://www.fastaitoken.com/register"><img src="assets/partners/logos/fastaitoken.jpg" alt="fastaitoken" width="150"></a></td>
<td>🎉 感谢 FastAIToken 对本项目的赞助！ <a href="http://www.fastaitoken.com/register">FastAIToken</a> 是面向开发者的 AI API 聚合平台，支持 OpenAI、Claude、Gemini 等主流大模型，充值 1:1，1 元 = 1 美元 API 额度，让开发者以更低成本、更便捷地使用全球领先的大模型服务。<br>

🚀 平台提供多种渠道自由选择：超级低价的0.02x OpenAI 福利分组（限时）、低至 0.25x OpenAI 分组、0.7x Claude 95%固定缓存、1.2x Claude Max 渠道；同时提供公开状态页，实时展示各分组的可用率、延迟及运行状态，服务透明可靠，并提供 7×24 小时真人技术支持（非机器人），快速响应开发者需求。
</td>
</tr>

<tr>
<td width="180"><a href="http://aimzoon.com"><img src="assets/partners/logos/aimzoon.jpg" alt="aimzoon" width="150"></a></td>
<td>感谢 Aimzoon 对本项目的赞助！ <a href="http://aimzoon.com">Aimzoon</a> 提供稳定、高性价比的 AI API 接入服务，支持开发者将常用 AI 服务快速接入 Codex、Claude Code、Gemini CLI 等编程工具。无需复杂配置，更快接入，更稳调用，更省成本。codex倍率优惠，特价倍率等促销不断，注册即送免费体验额度，让 AI 编程真正进入日常工作流。<a href="http://aimzoon.com">点击这里</a>注册体验！
</td>
</tr>

<tr>
<td width="180"><a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z"><img src="assets/partners/logos/claudeapi.jpg" alt="claudeapi" width="150"></a></td>
<td>感谢 Claude API 对本项目的赞助！ <a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z">Claude API</a> 是专注 Claude 模型的官方渠道 API 服务商，基于 Anthropic 官方 Key 与 AWS Bedrock 官方渠道，提供稳定的 Claude Code 与 Agent 应用接入体验，支持 Claude 全系列模型，保留 Tool Use、长上下文等官方能力。服务非逆向、非降智，适合 Claude Code 深度用户、Agent 工程师与企业技术团队使用。通过<a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z">[专属链接]</a>注册后联系客服，可领取免费测试额度，并支持开票和团队对接。
</td>
</tr>

<tr>
<td width="180"><a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default"><img src="assets/partners/logos/code0.jpg" alt="code0" width="150"></a></td>
<td>感谢 code0.ai 对本项目的赞助！ <a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default">code0.ai</a> 是面向开发者与技术团队的 AI 编程工作台，聚合 Claude Code、Codex 等主流 Agent 编程能力，支持代码生成、项目理解、调试修复、代码审查与文档生成等常见研发场景。适合独立开发者、Agent 工程师、开源项目维护者和企业研发团队使用，支持开票和团队对接。通过<a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default">[专属链接]</a>注册后联系客服，可领取免费测试额度，体验更高效的 AI 编程工作流。
</td>
</tr>

<tr>
<td width="180"><a href="https://nagora.ai/"><img src="assets/partners/logos/nagora.png" alt="Nagora" width="150"></a></td>
<td><a href="https://nagora.ai/">Nagora</a> 是专为开发者和团队打造的多模型 AI API 网关。通过一个账户和一枚 API Key，即可统一调用 26+ 款主流文本与图像模型，兼容 OpenAI、Anthropic 与 Gemini 协议，并可无缝接入 Claude Code、Codex、Gemini CLI 等开发工具。平台提供智能路由、自动故障转移、透明计费与统一账单，同时支持预算、限速、并发控制，让个人开发、团队协作和生产环境中的 AI 调用更稳定、更可控。无需改造现有应用，只需替换 Base URL 与 API Key，最快 1 分钟即可完成接入。</td>
</tr>

<tr>
<td width="180"><a href="https://www.novada.com/?sub2api/"><img src="assets/partners/logos/novada.png" alt="Novada" width="150"></a></td>
<td>感谢 <a href="https://www.novada.com/?sub2api/">Novada</a> 赞助本项目！Novada 为构建 AI 应用与自动化工作流的开发者提供住宅代理、ISP 代理、数据中心代理与移动代理，以及 Web Unlocker 和 Scraper API。凭借全球 IP 覆盖、灵活的轮换与粘性会话以及精准的地理定位，Novada 帮助团队在 AI Agent 工作流、跨区域测试、网络调研与浏览器自动化等场景中稳定获取网络数据。立即体验 Novada，构建更稳定、更可扩展的 AI 工作流。</td>
</tr>

<tr>
<td width="180"><a href="https://s.qiniu.com/u6rQrq"><img src="assets/partners/logos/qiniu.jpg" alt="七牛云AI" width="150"></a></td>
<td>感谢 七牛云AI 赞助本项目！七牛云AI 是七牛云（02567.HK）旗下企业级大模型 MaaS 平台，一站式调用全球 150+ 主流模型，兼容全球主流模型厂商协议，覆盖文本、图像、音频、视频、文件处理等全模态处理能力，服务超过169万企业及开发者用户。七牛云 AI 为 Sub2API 的用户提供了专属福利：通过<a href="https://s.qiniu.com/u6rQrq">此链接</a>注册，企业用户免费领1200万Token，开发者免费领300万Token。</td>
</tr>

<tr>
<td width="180"><a href="https://api.fenno.ai/s/dC4k"><img src="assets/partners/logos/fennoai.jpg" alt="FennoAI" width="150"></a></td>
<td>感谢 FennoAI 赞助本项目！FennoAI 是一家面向企业研发团队和开发者的高稳定、高性能 API 中转服务商，兼容 OpenAI 与 Anthropic 协议，可无缝接入 Codex、Claude Code、OpenCode 等主流 AI 编程工具。平台具备企业级稳定性，可支撑千亿 Token/日的调用规模，并支持境内外主体公对公结算及开票，满足企业级研发与采购需求。作为 Sub2API 用户专属福利，通过<a href="https://api.fenno.ai/s/dC4k">专属链接</a>购买订阅，仅需 1.99 美元即可获得价值 50 美元的 Coding Plan 额度。同时支持邀请奖励，邀请好友购买最高可获得 20% 返佣，邀请越多，奖励越高。</td>
</tr>

<tr>
<td width="180"><a href="https://lanox.ai/?c=6"><img src="assets/partners/logos/lanox.jpg" alt="LanoX AI" width="150"></a></td>
<td>感谢 LanoX AI 对本项目的赞助！<a href="https://lanox.ai/?c=6">LanoX AI</a> 为开发者、团队与企业提供稳定、高性价比的全球模型接入服务。 🎁 新用户福利 — 免费领取 百万 Token ,更有500+ 免费模型 — 低成本测试、验证、部署更轻松 🧠 全球主流模型 — GPT · Claude · Gemini · Qwen · Grok... 🎬 多模态创作 — Seedance 2.0 · GPT Image · Gemini Nano Banana 🛡️ 企业级稳定服务 — 高可用💎原生能力输出💎不降智💎不混模💎调用与计费透明💎 💰 更低调用成本 — 顶级模型低至官方价 1 折起，文档清晰、接入简单、支持开票与企业批量调用 🏢 企业优选 — 适用于 AI 产品、Agent、内容平台、研发团队批量调用</td>
</tr>

<tr>
<td width="180"><a href="https://www.rapidproxy.io/?ref=sub2api"><img src="assets/partners/logos/rapidproxy.jpg" alt="RapidProxy" width="150"></a></td>
<td><a href="https://www.rapidproxy.io/?ref=sub2api">RapidProxy</a> 是面向开发者的数据采集代理解决方案，提供稳定可靠的住宅代理服务。通过 9000 万+全球住宅 IP和 200+国家覆盖、智能轮换机制和精准地区定位能力，帮助爬虫、AI 数据训练、SEO 监控、电商数据分析等项目突破访问限制，提高数据采集效率。支持 Playwright、Selenium、Puppeteer 等主流自动化框架，价格低至 $0.65/GB，<a href="https://www.rapidproxy.io/?ref=sub2api">立即免费测试吧</a>。</td>
</tr>

<tr>
<td width="180"><a href="https://hao.ai"><img src="assets/partners/logos/haoai.png" alt="hao.ai" width="150"></a></td>
<td><a href="https://hao.ai">hao.ai</a> 是面向开发者与团队的高速、稳定大模型统一 API 网关。通过一个 API Key 和统一接口，即可接入 GPT、Claude、xAI Grok 等主流模型，兼容 OpenAI、Anthropic 等常用协议与 SDK。平台提供模型路由、故障回退、团队管理及完整调用日志，模型价格低至官方参考价的 1.5 折，帮助用户更简单、更稳定、更低成本地构建 AI 应用。</td>
</tr>

</table>

## 项目概述

Sub2API 是一个 AI API 网关平台，用于分发和管理 AI 产品订阅的 API 配额。用户通过平台生成的 API Key 调用上游 AI 服务，平台负责鉴权、计费、负载均衡和请求转发。

## 核心功能

- **多账号管理** - 支持多种上游账号类型（OAuth、API Key）
- **API Key 分发** - 为用户生成和管理 API Key
- **精确计费** - Token 级别的用量追踪和成本计算
- **智能调度** - 智能账号选择，支持粘性会话
- **并发控制** - 用户级和账号级并发限制
- **速率限制** - 可配置的请求和 Token 速率限制
- **内置支付系统** - 支持 EasyPay 易支付、支付宝官方、微信官方、Stripe，用户自助充值，无需独立部署支付服务（[配置指南](docs/PAYMENT_CN.md)）
- **管理后台** - Web 界面进行监控和管理
- **外部系统集成** - 支持通过 iframe 嵌入外部系统（如工单等），扩展管理后台功能

## 生态项目

围绕 Sub2API 的社区扩展与集成项目：

| 项目 | 说明 | 功能 |
|------|------|------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~自助支付系统~~ | **已内置** — 支付功能已集成到 Sub2API 中，无需独立部署。详见 [支付配置指南](docs/PAYMENT_CN.md) |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | 移动端管理控制台 | 跨平台应用（iOS/Android/Web），支持用户管理、账号管理、监控看板、多后端切换；基于 Expo + React Native 构建 |

## 技术栈

| 组件 | 技术 |
|------|------|
| 后端 | Go 1.25.7, Gin, Ent |
| 前端 | Vue 3.4+, Vite 5+, TailwindCSS |
| 数据库 | PostgreSQL 15+ |
| 缓存/队列 | Redis 7+ |

---

## Nginx 反向代理注意事项

通过 Nginx 反向代理 Sub2API（或 CRS 服务）并搭配 Codex CLI 使用时，需要在 Nginx 配置的 `http` 块中添加：

```nginx
underscores_in_headers on;
```

Nginx 默认会丢弃名称中含下划线的请求头（如 `session_id`），这会导致多账号环境下的粘性会话功能失效。

---

## 部署方式

### 方式一：脚本安装（推荐）

一键安装脚本，自动从 GitHub Releases 下载预编译的二进制文件。

#### 前置条件

- Linux 服务器（amd64 或 arm64）
- PostgreSQL 15+（已安装并运行）
- Redis 7+（已安装并运行）
- Root 权限

#### 安装步骤

```bash
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
```

脚本会自动：
1. 检测系统架构
2. 下载最新版本
3. 安装二进制文件到 `/opt/sub2api`
4. 创建 systemd 服务
5. 配置系统用户和权限

#### 安装后配置

```bash
# 1. 启动服务
sudo systemctl start sub2api

# 2. 设置开机自启
sudo systemctl enable sub2api

# 3. 在浏览器中打开设置向导
# http://你的服务器IP:8080
```

设置向导将引导你完成：
- 数据库配置
- Redis 配置
- 管理员账号创建

#### 升级

可以直接在 **管理后台** 左上角点击 **检测更新** 按钮进行在线升级。

网页升级功能支持：
- 自动检测新版本
- 一键下载并应用更新
- 支持回滚

#### 常用命令

```bash
# 查看状态
sudo systemctl status sub2api

# 查看日志
sudo journalctl -u sub2api -f

# 重启服务
sudo systemctl restart sub2api

# 卸载
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
```

---

### 方式二：Docker Compose（推荐）

使用 Docker Compose 部署，包含 PostgreSQL 和 Redis 容器。

#### 前置条件

- Docker 20.10+
- Docker Compose v2+

#### 快速开始（一键部署）

使用自动化部署脚本快速搭建：

```bash
# 创建部署目录
mkdir -p sub2api-deploy && cd sub2api-deploy

# 下载并运行部署准备脚本
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash

# 启动服务
docker compose up -d

# 查看日志
docker compose logs -f sub2api
```

**脚本功能：**
- 下载 `docker-compose.local.yml`（本地保存为 `docker-compose.yml`）和 `.env.example`
- 自动生成安全凭证（JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD）
- 创建 `.env` 文件并填充自动生成的密钥
- 创建数据目录（使用本地目录，便于备份和迁移）
- 显示生成的凭证供你记录

#### 手动部署

如果你希望手动配置：

```bash
# 1. 克隆仓库
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy

# 2. 复制环境配置文件
cp .env.example .env
chmod 600 .env

# 3. 编辑配置（生成安全密码）
nano .env
```

**`.env` 必须配置项：**

```bash
# PostgreSQL 密码（必需）
POSTGRES_PASSWORD=your_secure_password_here

# JWT 密钥（推荐 - 重启后保持用户登录状态）
JWT_SECRET=your_jwt_secret_here

# TOTP 加密密钥（推荐 - 重启后保留双因素认证）
TOTP_ENCRYPTION_KEY=your_totp_key_here

# 可选：管理员账号
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password

# 可选：自定义端口
SERVER_PORT=8080
```

**生成安全密钥：**
```bash
# 生成 JWT_SECRET
openssl rand -hex 32

# 生成 TOTP_ENCRYPTION_KEY
openssl rand -hex 32

# 生成 POSTGRES_PASSWORD
openssl rand -hex 32
```

```bash
# 4. 创建数据目录（本地版）
mkdir -p data postgres_data redis_data

# 5. 启动所有服务
# 选项 A：本地目录版（推荐 - 易于迁移）
docker compose -f docker-compose.local.yml up -d

# 选项 B：命名卷版（简单设置）
docker compose up -d

# 6. 查看状态
docker compose -f docker-compose.local.yml ps

# 7. 查看日志
docker compose -f docker-compose.local.yml logs -f sub2api
```

#### 部署版本对比

| 版本 | 数据存储 | 迁移便利性 | 适用场景 |
|------|---------|-----------|---------|
| **docker-compose.local.yml** | 本地目录 | ✅ 简单（打包整个目录） | 生产环境、频繁备份 |
| **docker-compose.yml** | 命名卷 | ⚠️ 需要 docker 命令 | 简单设置 |

**推荐：** 使用 `docker-compose.local.yml`（脚本部署）以便更轻松地管理数据。

#### 启用“数据管理”功能（datamanagementd）

如需启用管理后台“数据管理”，需要额外部署宿主机数据管理进程 `datamanagementd`。

关键点：

- 主进程固定探测：`/tmp/sub2api-datamanagement.sock`
- 只有该 Socket 可连通时，数据管理功能才会开启
- Docker 场景需将宿主机 Socket 挂载到容器同路径

详细部署步骤见：`deploy/DATAMANAGEMENTD_CN.md`

#### 访问

在浏览器中打开 `http://你的服务器IP:8080`

如果管理员密码是自动生成的，在日志中查找：
```bash
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```

#### 升级

```bash
# 拉取最新镜像并重建容器
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```

#### 轻松迁移（本地目录版）

使用 `docker-compose.local.yml` 时，可以轻松迁移到新服务器：

```bash
# 源服务器
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/

# 传输到新服务器
scp sub2api-complete.tar.gz user@new-server:/path/

# 新服务器
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker compose -f docker-compose.local.yml up -d
```

#### 常用命令

```bash
# 停止所有服务
docker compose -f docker-compose.local.yml down

# 重启
docker compose -f docker-compose.local.yml restart

# 查看所有日志
docker compose -f docker-compose.local.yml logs -f

# 删除所有数据（谨慎！）
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```

---

### 方式三：Apple container（macOS）

Apple 芯片 Mac 在 macOS 26 上可使用 Apple `container` 1.1.0 或更高版本运行完整的 Sub2API、PostgreSQL 和 Redis：

```bash
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
```

该方式面向本地开发和人工运维，不提供持续重启监管；生产部署仍推荐 Docker Compose。生命周期命令、持久化、升级和运行时限制见 [deploy/APPLE_CONTAINER.md](deploy/APPLE_CONTAINER.md)。

---

### 方式四：源码编译

从源码编译安装，适合开发或定制需求。

#### 前置条件

- Go 1.21+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+

#### 编译步骤

```bash
# 1. 克隆仓库
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api

# 2. 安装 pnpm（如果还没有安装）
npm install -g pnpm

# 3. 编译前端
cd frontend
pnpm install
pnpm run build
# 构建产物输出到 ../backend/internal/web/dist/

# 4. 编译后端（嵌入前端）
cd ../backend
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server

# 5. 创建配置文件
cp ../deploy/config.example.yaml ./config.yaml

# 6. 编辑配置
nano config.yaml
```

> **注意：** `-tags embed` 参数会将前端嵌入到二进制文件中。不使用此参数编译的程序将不包含前端界面。

**`config.yaml` 关键配置：**

```yaml
server:
  host: "0.0.0.0"
  port: 8080
  mode: "release"

database:
  host: "localhost"
  port: 5432
  user: "postgres"
  password: "your_password"
  dbname: "sub2api"

redis:
  host: "localhost"
  port: 6379
  password: ""

jwt:
  secret: "change-this-to-a-secure-random-string"
  expire_hour: 24

default:
  user_concurrency: 5
  user_balance: 0
  api_key_prefix: "sk-"
  rate_multiplier: 1.0
```

### Sora 功能状态（暂不可用）

> ⚠️ 当前 Sora 相关功能因上游接入与媒体链路存在技术问题，暂时不可用。
> 现阶段请勿在生产环境依赖 Sora 能力。
> 文档中的 `gateway.sora_*` 配置仅作预留，待技术问题修复后再恢复可用。

### Sora 媒体签名 URL（功能恢复后可选）

当配置 `gateway.sora_media_signing_key` 且 `gateway.sora_media_signed_url_ttl_seconds > 0` 时，网关会将 Sora 输出的媒体地址改写为临时签名 URL（`/sora/media-signed/...`）。这样无需 API Key 即可在浏览器中直接访问，且具备过期控制与防篡改能力（签名包含 path + query）。

```yaml
gateway:
  # /sora/media 是否强制要求 API Key（默认 false）
  sora_media_require_api_key: false
  # 媒体临时签名密钥（为空则禁用签名）
  sora_media_signing_key: "your-signing-key"
  # 临时签名 URL 有效期（秒）
  sora_media_signed_url_ttl_seconds: 900
```

> 若未配置签名密钥，`/sora/media-signed` 将返回 503。  
> 如需更严格的访问控制，可将 `sora_media_require_api_key` 设为 true，仅允许携带 API Key 的 `/sora/media` 访问。

访问策略说明：
- `/sora/media`：内部调用或客户端携带 API Key 才能下载
- `/sora/media-signed`：外部可访问，但有签名 + 过期控制

`config.yaml` 还支持以下安全相关配置：

- `cors.allowed_origins` 配置 CORS 白名单
- `security.url_allowlist` 配置上游/价格数据/CRS 主机白名单
- `security.url_allowlist.enabled` 可关闭 URL 校验（慎用）
- `security.url_allowlist.allow_insecure_http` 关闭校验时允许 HTTP URL
- `security.url_allowlist.allow_private_hosts` 允许私有/本地 IP 地址
- `security.response_headers.enabled` 可启用可配置响应头过滤（关闭时使用默认白名单）
- `security.csp` 配置 Content-Security-Policy
- `billing.circuit_breaker` 计费异常时 fail-closed
- `security.trust_forwarded_ip_for_api_key_acl` 控制旧版原始转发头接管（为升级兼容默认开启）；关闭后严格使用 `server.trusted_proxies`，其中只应填写直接连接 Sub2API 的精确代理 CIDR
- `security.forwarded_client_ip_headers` 最多配置 16 个第三方 CDN 客户端 IP 请求头；仅在旧版接管开启时按顺序优先于内置请求头解析
- `turnstile.required` 在 release 模式强制启用 Turnstile

自定义客户端 IP 请求头可通过 YAML 配置，也可使用逗号分隔的环境变量：

```bash
SECURITY_FORWARDED_CLIENT_IP_HEADERS=True-Client-IP,X-CDN-Client-IP
```

请求头名称会经过合法性校验、规范化和大小写无关去重。管理员可在安全设置中动态更新列表，无需重启；新安装会持久化 YAML/环境变量默认值，旧安装缺少数据库字段时会自动回填。关闭旧版接管后，自定义头和内置原始转发头均被忽略，只使用 `server.trusted_proxies`。开启接管时必须限制源站仅允许 CDN/代理访问，并确保边缘代理覆盖所有受信客户端 IP 请求头。完整迁移规则和信任边界见 [`deploy/EDGE_SECURITY.md`](deploy/EDGE_SECURITY.md)。

**网关防御纵深建议（重点）**

- `gateway.upstream_response_read_max_bytes`：限制非流式上游响应读取大小（默认 `8MB`），用于防止异常响应导致内存放大。
- `gateway.proxy_probe_response_read_max_bytes`：限制代理探测响应读取大小（默认 `1MB`）。
- `gateway.gemini_debug_response_headers`：默认 `false`，仅在排障时短时开启，避免高频请求日志开销。
- `/auth/register`、`/auth/login`、`/auth/login/2fa`、`/auth/send-verify-code` 已提供服务端兜底限流（Redis 故障时 fail-close）。
- 推荐将 WAF/CDN 作为第一层防护，服务端限流与响应读取上限作为第二层兜底；两层同时保留，避免旁路流量与误配置风险。

**⚠️ 安全警告：HTTP URL 配置**

当 `security.url_allowlist.enabled=false` 时，系统仅执行最小 URL 校验，且**默认允许 HTTP URL**（开发友好模式，Docker Compose 部署的默认值一致）。生产环境建议显式收紧为仅允许 HTTPS：

```yaml
security:
  url_allowlist:
    enabled: false                # 禁用白名单检查
    allow_insecure_http: false    # 仅允许 HTTPS（生产环境推荐）
```

**或通过环境变量：**

```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```

**允许 HTTP 的风险：**
- API 密钥和数据以**明文传输**（可被截获）
- 易受**中间人攻击 (MITM)**
- **不适合生产环境**

**适用场景：**
- ✅ 开发/测试环境的本地服务器（http://localhost）
- ✅ 内网可信端点
- ✅ 获取 HTTPS 前测试账号连通性
- ❌ 生产环境（仅使用 HTTPS）

**设置 `allow_insecure_http: false` 后，HTTP URL 会返回如下错误：**
```
Invalid base URL: invalid url scheme: http
```

如关闭 URL 校验或响应头过滤，请加强网络层防护：
- 出站访问白名单限制上游域名/IP
- 阻断私网/回环/链路本地地址
- 强制仅允许 TLS 出站
- 在反向代理层移除敏感响应头

#### ⚠️ 重要：创建管理员账号

初始管理员账号**只能通过 setup 向导创建**（首次启动时访问 `http://<host>:8080`）。`config.yaml` 中的 `default.admin_email` / `default.admin_password` 字段**不会被用来创建管理员**——它们只是出于历史原因保留在模板里。

由于上面第 5 步预先创建了 `config.yaml`，**setup 向导在首次启动时会被跳过**：服务检测到 config 已存在，会直接进入正常模式，此时 `users` 表为空，首次登录会返回 `invalid email or password`。

**创建管理员的两种方式：**

1. **推荐——让向导自动生成 `config.yaml`：** 跳过上面的第 5 步（不要执行 `cp`）。直接运行 `./sub2api`，访问 `http://localhost:8080`，向导会引导你完成数据库、Redis 和管理员账号配置，并自动写出 `config.yaml`。

2. **如果你已经创建了 `config.yaml`：** 首次启动前先把它临时移走以触发向导，完成后再恢复：
   ```bash
   mv config.yaml config.yaml.bak
   ./sub2api        # 向导在 http://localhost:8080 启动，并生成新的 config.yaml
   # 向导完成后 Ctrl+C 停服，再恢复你的配置：
   mv config.yaml.bak config.yaml
   ./sub2api        # 重启进入正常模式，用刚创建的管理员登录
   ```

```bash
# 6. 运行应用
./sub2api
```

#### HTTP/2 (h2c) 与 HTTP/1.1 回退

后端明文端口默认支持 h2c，并保留 HTTP/1.1 回退用于 WebSocket 与旧客户端。浏览器通常不支持 h2c，性能收益主要在反向代理或内网链路。

**反向代理示例（Caddy）：**

```caddyfile
transport http {
	versions h2c h1
}
```

**验证：**

```bash
# h2c prior knowledge
curl --http2-prior-knowledge -I http://localhost:8080/health
# HTTP/1.1 回退
curl --http1.1 -I http://localhost:8080/health
# WebSocket 回退验证（需管理员 token）
websocat -H="Sec-WebSocket-Protocol: sub2api-admin, jwt.<ADMIN_TOKEN>" ws://localhost:8080/api/v1/admin/ops/ws/qps
```

#### 开发模式

```bash
# 后端（支持热重载）
cd backend
go run ./cmd/server

# 前端（支持热重载）
cd frontend
pnpm run dev
```

#### 代码生成

修改 `backend/ent/schema` 后，需要重新生成 Ent + Wire：

```bash
cd backend
go generate ./ent
go generate ./cmd/server
```

---

## 简易模式

简易模式适合个人开发者或内部团队快速使用，不依赖完整 SaaS 功能。

- 启用方式：设置环境变量 `RUN_MODE=simple`
- 功能差异：隐藏 SaaS 相关功能，跳过计费流程
- 安全注意事项：生产环境需同时设置 `SIMPLE_MODE_CONFIRM=true` 才允许启动

---

## Antigravity 使用说明

Sub2API 支持 [Antigravity](https://antigravity.so/) 账户，授权后可通过专用端点访问 Claude 和 Gemini 模型。

### 专用端点

| 端点 | 模型 |
|------|------|
| `/antigravity/v1/messages` | Claude 模型 |
| `/antigravity/v1beta/` | Gemini 模型 |

### Claude Code 配置示例

```bash
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
export ANTHROPIC_AUTH_TOKEN="sk-xxx"
```

### 混合调度模式

Antigravity 账户支持可选的**混合调度**功能。开启后，通用端点 `/v1/messages` 和 `/v1beta/` 也会调度该账户。

> **⚠️ 注意**：Anthropic Claude 和 Antigravity Claude **不能在同一上下文中混合使用**，请通过分组功能做好隔离。

---

## 项目结构

```
sub2api/
├── backend/                  # Go 后端服务
│   ├── cmd/server/           # 应用入口
│   ├── internal/             # 内部模块
│   │   ├── config/           # 配置管理
│   │   ├── model/            # 数据模型
│   │   ├── service/          # 业务逻辑
│   │   ├── handler/          # HTTP 处理器
│   │   └── gateway/          # API 网关核心
│   └── resources/            # 静态资源
│
├── frontend/                 # Vue 3 前端
│   └── src/
│       ├── api/              # API 调用
│       ├── stores/           # 状态管理
│       ├── views/            # 页面组件
│       └── components/       # 通用组件
│
└── deploy/                   # 部署文件
    ├── docker-compose.yml    # Docker Compose 配置
    ├── .env.example          # Docker Compose 环境变量
    ├── config.example.yaml   # 二进制部署完整配置文件
    └── install.sh            # 一键安装脚本
```

## Star History

<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
 </picture>
</a>

---

## 许可证

本项目基于 [GNU 宽通用公共许可证 v3.0](LICENSE)（或更高版本）授权。

Copyright (c) 2026 Wesley Liddick

---

<div align="center">

**如果觉得有用，请给个 Star 支持一下！**

</div>


## /README_JA.md

<div align="center">

<img src="assets/logo.svg" alt="Sub2API Logo" width="128" />

# Sub2API

[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/)
[![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/)
[![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](https://www.docker.com/)

<a href="https://trendshift.io/repositories/21823" target="_blank"><img src="https://trendshift.io/api/badge/repositories/21823" alt="Wei-Shaw%2Fsub2api | Trendshift" width="250" height="55"/></a>

**サブスクリプションクォータ配分のための AI API ゲートウェイプラットフォーム**

[English](README.md) | [中文](README_CN.md) | 日本語

</div>

## ⚠️ 重要なお知らせ

本プロジェクトをご利用になる前に、以下の内容を必ずよくお読みください：

- **🚨 利用規約のリスク**：本プロジェクトの使用は、Anthropic をはじめとする上流プロバイダーの利用規約に違反する可能性があります。ご利用前に各プロバイダーのユーザー規約を必ずご確認ください。使用により生じるすべてのリスクはユーザーご自身が負うものとします。
- **⚖️ 法令遵守**：お住まいの国または地域の法令を遵守した上で本プロジェクトをご利用ください。いかなる違法な目的での使用も固く禁じます。
- **📖 免責事項**：本プロジェクトは技術的な学習および研究の目的でのみ提供されます。本プロジェクトの使用により生じたアカウントの停止、サービスの中断、データの損失、その他一切の直接的または間接的な損害について、作者は一切の責任を負いません。
- **🚫 商用利用の非許諾**：本プロジェクトの開発者は、いかなる個人または組織に対しても、本プロジェクトを利用したいかなる形態の商業運営も一切許諾していません。本プロジェクトの名義で、または本プロジェクトに基づいて行われる商業行為はすべて本プロジェクトおよびその開発者とは無関係であり、それにより生じる一切の紛争、損失、法的責任は行為者自身が負うものとします。

## ❤️ スポンサー

> [こちらに掲載しませんか？](mailto:support@sub2api.org)

<table>

<tr>
<td width="180"><a href="https://cctk.ai/register?aff=SUB2API"><img src="assets/partners/logos/cctk.jpg" alt="CCTK.AI" width="150"></a></td>
<td>CCTK.AI のご支援に感謝します！<a href="https://cctk.ai/register?aff=SUB2API">CCTK.AI</a> は安定性とコストパフォーマンスにこだわった AI API ゲートウェイで、Claude、OpenAI、Gemini など主要モデルの高速中継サービスを提供しています。Claude Code や Codex などの主要なコーディングツールにシームレスに対応し、公式価格を大きく下回るコストで同等のモデル能力を利用できます。<a href="https://cctk.ai/register?aff=SUB2API">こちらのリンク</a>から登録して、より速く、より安定した、よりお得な AI API 接続をお試しください。</td>
</tr>

<tr>
<td width="180"><a href="https://www.openmodel.ai?ref=sub2api"><img src="assets/partners/logos/openmodel.jpg" alt="openmodel" width="150"></a></td>
<td>1つの API で、トップモデルを使い放題！<a href="https://www.openmodel.ai?ref=sub2api">OpenModel</a> は本番環境グレードで高可用性の AI API ゲートウェイに特化し、アプリを真に高速・安定させます：自動フェイルオーバー、最適なチャネルへのスマートルーティング、本番グレードの SLA 保証。単一プロバイダーをはるかに上回る SLA で、安定性をあなたの核心的な競争力にします。</td>
</tr>

<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>ETok.ai のご支援に感謝します！ETok.ai はワンストップ AI プログラミングツールサービスプラットフォームの構築に取り組んでいます。Claude Code の専用プランと技術コミュニティサービスを提供し、Google Gemini や OpenAI Codex もサポートしています。丁寧に設計されたプランと専門的な技術コミュニティを通じて、開発者に安定したサービス保証と継続的な技術サポートを提供し、AI アシスト プログラミングを真の生産性向上ツールにします。<a href="https://etok.ai">こちら</a>から登録！</td>
</tr>

<tr>
<td width="180"><a href="https://apikey.fun/register?aff=SUB2API"><img src="assets/partners/logos/apikey-fun.png" alt="APIKEY.FUN" width="150"></a></td>
<td>APIKEY.FUN のご支援に感謝します！<a href="https://apikey.fun/register?aff=SUB2API">APIKEY.FUN</a> は sub2api オープンソースプロジェクトのコアコントリビューターの一つであり、オープンで安定した、コストパフォーマンスに優れた AI API アクセスサービスの提供に取り組んでいます。プラットフォームは Claude、OpenAI、Gemini など人気モデルの API 中継サービスをサポートし、価格は公式料金のわずか 7% から。専用リンク <a href="https://apikey.fun/register?aff=SUB2API">APIKEY</a> から登録すると、すべてのチャージで永久 5% 割引をご利用いただけます。</td>
</tr>

<tr>
<td width="180"><a href="https://aigocode.com/invite/SUB2API"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>AIGoCode のご支援に感謝します！AIGoCode は Claude Code、Codex、最新の Gemini モデルを統合したオールインワンプラットフォームで、安定的かつ効率的でコストパフォーマンスに優れた AI コーディングサービスを提供します。柔軟なサブスクリプションプラン、アカウント停止リスクゼロ、VPN 不要の直接アクセス、超高速レスポンスが特長です。AIGoCode は sub2api ユーザー向けに特別特典を用意しています：<a href="https://aigocode.com/invite/SUB2API">こちらのリンク</a>から登録すると、初回チャージ時に 10% のボーナスクレジットを追加プレゼント！</td>
</tr>

<tr>
<td width="180"><a href="https://shop.bmoplus.com/?utm_source=github"><img src="assets/partners/logos/bmoplus.jpg" alt="bmoplus" width="150"></a></td>
<td>本プロジェクトにご支援いただいた BmoPlus に感謝いたします！BmoPlusは、AIサブスクリプションのヘビーユーザー向けに特化した信頼性の高いAIアカウントサービスプロバイダーであり、安定した ChatGPT Plus / ChatGPT Pro (完全保証) / Claude Pro / Super Grok / Gemini Pro の公式代行チャージおよび即納アカウントを提供しています。こちらの<a href="https://shop.bmoplus.com/?utm_source=github">BmoPlus AIアカウント専門店/代行チャージ</a>経由でご登録・ご注文いただいたユーザー様は、GPTを 公式サイト価格の約1割（90% OFF） という驚異的な価格でご利用いただけます！</td>
</tr>

<tr>
<td width="180"><a href="https://bestproxy.com/?keyword=a2e8iuol"><img src="assets/partners/logos/bestproxy.png" alt="bestproxy" width="150"></a></td>
<td>Bestproxy のご支援に感謝します！<a href="https://bestproxy.com/?keyword=a2e8iuol">Bestproxy</a> は高純度の住宅IPを提供し、1アカウント1IP専有をサポートしています。実際の家庭ネットワークとフィンガープリント分離を組み合わせることで、リンク環境の分離を実現し、関連付けによるリスク管理の確率を低減します。</td>
</tr>

<tr>
<td width="180"><a href="https://pateway.ai/?ch=1tsfr51"><img src="assets/partners/logos/pateway.png" alt="pateway" width="150"></a></td>
<td>PatewayAI のご支援に感謝します！PatewayAI は、ヘビーAI開発者向けに公式直結を重視した高品質モデルAPIリレーサービスプロバイダーです。Claude 全シリーズおよび Codex シリーズモデルを提供し、100%公式ソースから直接供給 — 偽りなし、水増しなし、検証歓迎。課金は完全透明で、トークン単位の請求書を1件ずつ監査可能です。
エンタープライズ級の高同時接続にも対応し、法人顧客向けに専用管理プラットフォームを提供しています。法人顧客は正式な契約を締結し、請求書の発行が可能です。詳細は公式サイトでお問い合わせください。
<a href="https://pateway.ai/?ch=1tsfr51">こちらのリンク</a>から登録すると、$3 のトライアルクレジットがもらえます。チャージは最大40%オフ、友達紹介で双方にボーナス付与 — 紹介報酬は最大 $150。</td>
</tr>

<tr>
<td width="180"><a href="https://api.pptoken.cc/register?promo=SUB2API"><img src="assets/partners/logos/pptoken.png" alt="pptoken" width="150"></a></td>
<td>PPToken.cc のご支援に感謝します！<a href="https://api.pptoken.cc/register?promo=SUB2API">PPToken.cc</a> は GPT シリーズモデルの API 中継サービスを専門としており、Codex、Claude Code、OpenAI 互換クライアント、Gemini CLI などのツール接続をサポートしています。チャージは 1:1（1元＝1ドル分のクレジット）、GPT モデルは最低 0.16 倍のレート倍率で、総合コストは公式価格の約 2.2% 、最速ファーストトークンは約1秒 — 開発者が低コスト・高速レスポンスで GPT モデル機能にアクセスするのに最適です。テクニカルサポート：24時間365日リアルな人間が対応（ボットではありません）、グループ内で @技術 すれば 10 分以内に返信。スポンサー特典：先着 200 名のユーザーが<a href="https://api.pptoken.cc/register?promo=SUB2API">専用登録リンク</a>から登録し、プロモコード `SUB2API` を入力すると、Codex / Claude Code の無料トライアルクレジットを獲得できます — 最低利用額なし、カード登録不要。
</td>
</tr>

<tr>
<td width="180"><a href="https://veilx.io/#/hello/SJRBRVDV"><img src="assets/partners/logos/veilx.png" alt="veilx" width="150"></a></td>
<td>Veilx のご支援に感謝します！<a href="https://veilx.io/#/hello/SJRBRVDV">Veilx</a> CDN は超大規模 API リクエストシナリオ向けに設計されており、AI 中継サービスと AI API 呼び出しチェーンに対して深く最適化されています。高並列・高頻度リクエスト・大容量トラフィックに容易に対応し、開発者と企業により高速で安定した、低レイテンシの加速体験を提供します。OpenAI、Claude、Gemini などの AI インターフェース中継はもちろん、チャット、画像生成、Embedding、ストリーミング出力などの複雑なシナリオでも、Veilx は応答速度と接続安定性を大幅に向上させ、ネットワーク変動によるタイムアウトや失敗を効果的に削減します。さらに、Veilx は中国三大ネットワーク最適化の高速回線を提供しており、中国本土から海外 AI サービスへのアクセス速度と安定性を大幅に向上させます。グローバル AI 中継プラットフォーム、海外 AI SaaS、越境ビジネス、高並列 API システム展開に特に適しています。AI API のために生まれ、あなたの AI 中継サービスをより速く、より安定して、より安心に。<a href="https://veilx.io/#/hello/SJRBRVDV">購入リンク</a>
</td>
</tr>

<tr>
<td width="180"><a href="https://roxybrowser.com/invite/bgGKG7"><img src="assets/partners/logos/RoxyBrowser.png" alt="RoxyBrowser" width="150"></a></td>
<td>RoxyBrowser のご支援に感謝します！<a href="https://roxybrowser.com/invite/bgGKG7">RoxyBrowser</a> は Sub2API の理想的なパートナーです：ネイティブ統合された Roxy AI Agent と高品質なネイティブ住宅 IP を搭載し、シンプルなコマンドで一括自動化をサポート、マルチアカウント管理のセキュリティと効率を大幅に向上させます！<a href="https://roxybrowser.com/invite/bgGKG7">このリンク</a>から登録すると、無料の住宅 IP パッケージと生涯 10% 割引を獲得できます。
</td>
</tr>

<tr>
<td width="180"><a href="https://sui-xiang.com/"><img src="assets/partners/logos/sui-xiang.jpg" alt="sui-xiang" width="150"></a></td>
<td>随想AI ゲートウェイのご支援に感謝します！<a href="https://sui-xiang.com/">随想AI ゲートウェイ</a> は信頼性と効率に優れた API 中継サービスプロバイダーで、Claude、Codex、Gemini などの中継サービスを提供しています。プライバシー重視の中継ステーション・データ転売なし・モデル水増しなし、プライバシー・透明性・超高速アフターサービス。新規アカウント登録後、毎日サインインで 0.5 元のテストクレジットがもらえ、チャージは 1:1、サブスクリプション不要、従量課金。マルチライン冗長、クロスリージョン災害復旧、自動フェイルオーバー、長時間 SSE 接続が途切れません。99.9% の可用性、重要な呼び出しは決して遅れません。
</td>
</tr>

<tr>
<td width="180"><a href="https://www.proxy4free.com/?keyword=4yjqecpc"><img src="assets/partners/logos/proxy4free.png" alt="proxy4free" width="150"></a></td>
<td>Proxy4Free のご支援に感謝します！Proxy4Free は開発者と AI アプリケーション向けのデータプロキシサービスプロバイダーで、住宅プロキシ、静的住宅プロキシ、ISP プロキシ、データセンタープロキシなど多様なプロキシソリューションを提供しており、Web Scraping、Browser Automation、AI Agent などのシナリオに適しています。グローバル IP リソース、安定した接続、柔軟な切り替えをサポートし、開発者のデータ収集成功率の向上と IP ブロックリスクの低減を支援します。<a href="https://www.proxy4free.com/?keyword=4yjqecpc">こちらのリンクから登録</a>して、より安定した効率的な自動化ワークフローを簡単に構築しましょう。
</td>
</tr>

<tr>
<td width="180"><a href="http://www.fastaitoken.com/register"><img src="assets/partners/logos/fastaitoken.jpg" alt="fastaitoken" width="150"></a></td>
<td>🎉 FastAIToken のご支援に感謝します！<a href="http://www.fastaitoken.com/register">FastAIToken</a> は開発者向けの AI API アグリゲーションプラットフォームで、OpenAI、Claude、Gemini などの主要な大規模モデルに対応しています。チャージは 1:1（1 元 = 1 米ドル分の API クレジット）で、開発者がより低コスト・より手軽に世界トップクラスの大規模モデルサービスを利用できます。<br>

🚀 プラットフォームでは多彩なチャネルを自由に選択できます：超低価格の 0.02x OpenAI 特典グループ（期間限定）、最低 0.25x の OpenAI グループ、0.7x Claude（95% 固定キャッシュ）、1.2x Claude Max チャネル。さらに、各グループの可用率・レイテンシ・稼働状況をリアルタイムで表示する公開ステータスページを提供し、透明で信頼できるサービスを実現。7×24 時間の有人テクニカルサポート（ボットではありません）により、開発者のニーズに迅速に対応します。
</td>
</tr>

<tr>
<td width="180"><a href="http://aimzoon.com"><img src="assets/partners/logos/aimzoon.jpg" alt="aimzoon" width="150"></a></td>
<td>Aimzoon のご支援に感謝します！<a href="http://aimzoon.com">Aimzoon</a> は安定してコストパフォーマンスに優れた AI API 接続サービスを提供し、開発者が主要な AI サービスを Codex、Claude Code、Gemini CLI などのコーディングツールへ素早く接続できるようにします。複雑な設定は不要で、より速い接続、より安定した呼び出し、より低いコストを実現。Codex レート割引や特価レートなどのキャンペーンも随時開催中、登録するだけで無料お試しクレジットをプレゼント。AI コーディングを日常のワークフローへ。<a href="http://aimzoon.com">こちら</a>から登録してお試しください！
</td>
</tr>

<tr>
<td width="180"><a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z"><img src="assets/partners/logos/claudeapi.jpg" alt="claudeapi" width="150"></a></td>
<td>Claude API のご支援に感謝します！<a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z">Claude API</a> は Claude モデルに特化した公式チャネルの API サービスプロバイダーで、Anthropic 公式キーと AWS Bedrock 公式チャネルをベースに、Claude Code や Agent アプリケーションへの安定した接続体験を提供します。Claude 全シリーズのモデルに対応し、Tool Use や長文コンテキストなどの公式機能もそのまま利用可能。リバースエンジニアリングやモデル劣化のないサービスで、Claude Code のヘビーユーザー、Agent エンジニア、企業の技術チームに最適です。<a href="https://console.claudeapi.com/agent/register/drTKjyn6wGLK061Z">専用リンク</a>から登録後カスタマーサポートへご連絡いただくと、無料お試しクレジットを受け取れます。請求書発行やチーム導入にも対応しています。
</td>
</tr>

<tr>
<td width="180"><a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default"><img src="assets/partners/logos/code0.jpg" alt="code0" width="150"></a></td>
<td>code0.ai のご支援に感謝します！<a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default">code0.ai</a> は開発者と技術チーム向けの AI プログラミングワークベンチで、Claude Code や Codex などの主要な Agent コーディング能力を集約し、コード生成、プロジェクト理解、デバッグと修正、コードレビュー、ドキュメント生成といった一般的な開発シーンをサポートします。個人開発者、Agent エンジニア、OSS メンテナー、企業の開発チームに最適で、請求書発行やチーム導入にも対応。<a href="https://code0.ai/agent/register/LgpIgl9JHtVG53V1?utm_source=zcf&utm_medium=partner&utm_campaign=zcf_2026&utm_content=default">専用リンク</a>から登録後カスタマーサポートへご連絡いただくと、無料お試しクレジットを受け取り、より効率的な AI プログラミングワークフローを体験できます。
</td>
</tr>

<tr>
<td width="180"><a href="https://nagora.ai/"><img src="assets/partners/logos/nagora.png" alt="Nagora" width="150"></a></td>
<td><a href="https://nagora.ai/">Nagora</a>は、開発者やチーム向けに設計されたマルチモデルAI APIゲートウェイです。1つのアカウントと1つのAPIキーだけで、26種類以上の主要なテキストモデルおよび画像モデルを一元的に利用できます。OpenAI、Anthropic、Geminiの各プロトコルに対応し、Claude Code、Codex、Gemini CLIなどの開発ツールにもシームレスに接続できます。 プラットフォームには、インテリジェントルーティング、自動フェイルオーバー、透明性の高い料金体系、請求の一元管理に加え、予算管理、レート制限、同時実行数の制御機能が備わっています。これにより、個人開発、チームでの共同作業、本番環境におけるAI APIの利用を、より安定的かつ柔軟に管理できます。 既存のアプリケーションを改修する必要はありません。Base URLとAPIキーを置き換えるだけで、最短1分で導入を完了できます。</td>
</tr>

<tr>
<td width="180"><a href="https://www.novada.com/?sub2api/"><img src="assets/partners/logos/novada.png" alt="Novada" width="150"></a></td>
<td>Novada のご支援に感謝します！<a href="https://www.novada.com/?sub2api/">Novada</a> は、AI アプリケーションや自動化ワークフローを構築する開発者向けに、レジデンシャル、ISP、データセンター、モバイルプロキシに加え、Web Unlocker と Scraper API を提供しています。グローバルな IP カバレッジ、柔軟なローテーション／スティッキーセッション、精密なジオターゲティングにより、AI エージェントワークフロー、クロスリージョンテスト、Web リサーチ、ブラウザ自動化などのシーンで、チームが Web データへ確実にアクセスできるよう支援します。Novada で、より安定しスケーラブルな AI ワークフローを構築しましょう。</td>
</tr>

<tr>
<td width="180"><a href="https://s.qiniu.com/u6rQrq"><img src="assets/partners/logos/qiniu.jpg" alt="Qiniu AI" width="150"></a></td>
<td>七牛云AI のご支援に感謝します！七牛云AI は、七牛云（02567.HK）傘下のエンタープライズ向け大規模モデル MaaS プラットフォームです。世界の主要モデル 150+ をワンストップで利用でき、グローバル主要モデルプロバイダーのプロトコルに対応し、テキスト、画像、音声、動画、ファイル処理などのフルモーダル処理能力をカバー。169万を超える企業・開発者ユーザーにサービスを提供しています。Sub2API ユーザー向けの限定特典として、<a href="https://s.qiniu.com/u6rQrq">こちらのリンク</a>から登録すると、企業ユーザーは 1200万 Token、開発者は 300万 Token を無料で獲得できます。</td>
</tr>

<tr>
<td width="180"><a href="https://api.fenno.ai/s/dC4k"><img src="assets/partners/logos/fennoai.jpg" alt="FennoAI" width="150"></a></td>
<td>FennoAI のご支援に感謝します！FennoAI は、企業の研究開発チームと開発者向けの高安定・高性能 API 中継サービスプロバイダーです。OpenAI と Anthropic のプロトコルに対応し、Codex、Claude Code、OpenCode などの主要 AI コーディングツールにシームレスに接続できます。エンタープライズ級の安定性を備え、1 日あたり千億規模の Token 呼び出しに対応。国内外法人間の企業間決済と請求書発行もサポートし、企業の研究開発・調達ニーズに応えます。Sub2API ユーザー限定特典として、<a href="https://api.fenno.ai/s/dC4k">専用リンク</a>からサブスクリプションを購入すると、わずか 1.99 ドルで 50 ドル相当の Coding Plan クレジットを獲得できます。さらに招待報酬にも対応しており、友達の購入で最大 20% の還元を獲得可能。招待が多いほど、報酬も増えます。</td>
</tr>

<tr>
<td width="180"><a href="https://lanox.ai/?c=6"><img src="assets/partners/logos/lanox.jpg" alt="LanoX AI" width="150"></a></td>
<td>本プロジェクトをご支援いただいている <a href="https://lanox.ai/?c=6">LanoX AI</a> に感謝します！LanoX AI は、開発者、チーム、企業向けに、安定性とコストパフォーマンスに優れたグローバルモデル接続サービスを提供しています。 🎁 新規ユーザー特典 — 数百万 Token を無料で獲得可能。さらに 500+ の無料モデルで、低コストなテスト、検証、デプロイをより簡単に 🧠 世界の主要モデル — GPT · Claude · Gemini · Qwen · Grok... 🎬 マルチモーダル制作 — Seedance 2.0 · GPT Image · Gemini Nano Banana 🛡️ エンタープライズ級の安定性 — 高可用性 💎 ネイティブ能力の出力 💎 性能劣化なし 💎 モデル混在なし 💎 利用量と課金が透明 💎 💰 より低い利用コスト — トップモデルを公式価格の 1 割から利用可能。明確なドキュメント、簡単な接続、請求書発行、企業向け一括利用に対応 🏢 企業に最適 — AI プロダクト、Agent、コンテンツプラットフォーム、大量利用する開発チームに最適</td>
</tr>

<tr>
<td width="180"><a href="https://www.rapidproxy.io/?ref=sub2api"><img src="assets/partners/logos/rapidproxy.jpg" alt="RapidProxy" width="150"></a></td>
<td><a href="https://www.rapidproxy.io/?ref=sub2api">RapidProxy</a> は開発者向けのデータ収集プロキシソリューションであり、安定して信頼できる住宅用プロキシサービスを提供します。9,000 万以上のグローバル住宅 IP と 200 以上の国・地域のカバー、インテリジェントなローテーション機構、精密な地域ターゲティング機能により、クローラー、AI データ学習、SEO モニタリング、EC データ分析などのプロジェクトがアクセス制限を突破し、データ収集の効率を高めます。Playwright、Selenium、Puppeteer などの主要な自動化フレームワークに対応し、料金は $0.65/GB から。<a href="https://www.rapidproxy.io/?ref=sub2api">今すぐ無料でお試しください</a>。</td>
</tr>

<tr>
<td width="180"><a href="https://hao.ai"><img src="assets/partners/logos/haoai.png" alt="hao.ai" width="150"></a></td>
<td><a href="https://hao.ai">hao.ai</a> は、開発者とチーム向けの高速で安定した大規模モデル統合 API ゲートウェイです。1 つの API Key と統一されたインターフェースで、GPT、Claude、xAI Grok などの主要モデルに接続でき、OpenAI や Anthropic などの一般的なプロトコルと SDK に対応しています。プラットフォームはモデルルーティング、フェイルオーバー、チーム管理、完全な呼び出しログを提供し、モデル価格は公式参考価格の 1.5 割から。よりシンプルに、より安定して、より低コストに AI アプリケーションを構築できます。</td>
</tr>

</table>

## 概要

Sub2API は、AI 製品のサブスクリプションから API クォータを配分・管理するために設計された AI API ゲートウェイプラットフォームです。ユーザーはプラットフォームが生成した API キーを通じて上流の AI サービスにアクセスでき、プラットフォームは認証、課金、負荷分散、リクエスト転送を処理します。

## 機能

- **マルチアカウント管理** - 複数の上流アカウントタイプ（OAuth、APIキー）をサポート
- **APIキー配布** - ユーザー向けの APIキーの生成と管理
- **精密な課金** - トークンレベルの使用量追跡とコスト計算
- **スマートスケジューリング** - スティッキーセッション付きのインテリジェントなアカウント選択
- **同時実行制御** - ユーザーごと・アカウントごとの同時実行数制限
- **レート制限** - 設定可能なリクエスト数およびトークンレート制限
- **内蔵決済システム** - EasyPay、Alipay、WeChat Pay、Stripe に対応。ユーザーのセルフサービスチャージが可能で、別途決済サービスのデプロイは不要（[設定ガイド](docs/PAYMENT.md)）
- **管理ダッシュボード** - 監視・管理のための Web インターフェース
- **外部システム連携** - 外部システム（チケット管理など）を iframe 経由で管理ダッシュボードに埋め込み可能

## エコシステム

Sub2API を拡張・統合するコミュニティプロジェクト:

| プロジェクト | 説明 | 機能 |
|---------|-------------|----------|
| ~~[Sub2ApiPay](https://github.com/touwaeriol/sub2apipay)~~ | ~~セルフサービス決済システム~~ | **内蔵済み** — 決済機能は Sub2API に統合されました。別途デプロイは不要です。[決済設定ガイド](docs/PAYMENT.md)をご参照ください |
| [sub2api-mobile](https://github.com/ckken/sub2api-mobile) | モバイル管理コンソール | ユーザー管理、アカウント管理、監視ダッシュボード、マルチバックエンド切り替えが可能なクロスプラットフォームアプリ（iOS/Android/Web）。Expo + React Native で構築 |

## 技術スタック

| コンポーネント | 技術 |
|-----------|------------|
| バックエンド | Go 1.25.7, Gin, Ent |
| フロントエンド | Vue 3.4+, Vite 5+, TailwindCSS |
| データベース | PostgreSQL 15+ |
| キャッシュ/キュー | Redis 7+ |

---

## Nginx リバースプロキシに関する注意

Sub2API（または CRS）を Nginx でリバースプロキシし、Codex CLI と組み合わせて使用する場合、Nginx の `http` ブロックに以下の設定を追加してください:

```nginx
underscores_in_headers on;
```

Nginx はデフォルトでアンダースコアを含むヘッダー（例: `session_id`）を破棄するため、マルチアカウント構成でのスティッキーセッションルーティングに支障をきたします。

---

## デプロイ

### 方法1: スクリプトによるインストール（推奨）

GitHub Releases からビルド済みバイナリをダウンロードするワンクリックインストールスクリプトです。

#### 前提条件

- Linux サーバー（amd64 または arm64）
- PostgreSQL 15+（インストール済みかつ稼働中）
- Redis 7+（インストール済みかつ稼働中）
- root 権限

#### インストール手順

```bash
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
```

スクリプトは以下を実行します:
1. システムアーキテクチャの検出
2. 最新リリースのダウンロード
3. バイナリを `/opt/sub2api` にインストール
4. systemd サービスの作成
5. システムユーザーと権限の設定

#### インストール後の作業

```bash
# 1. サービスを起動
sudo systemctl start sub2api

# 2. 起動時の自動起動を有効化
sudo systemctl enable sub2api

# 3. ブラウザでセットアップウィザードを開く
# http://YOUR_SERVER_IP:8080
```

セットアップウィザードでは以下の設定を行います:
- データベース設定
- Redis 設定
- 管理者アカウントの作成

#### アップグレード

**管理ダッシュボード**の左上にある**アップデートを確認**ボタンをクリックすることで、ダッシュボードから直接アップグレードできます。

Web インターフェースでは以下が可能です:
- 新しいバージョンの自動確認
- ワンクリックでのアップデートのダウンロードと適用
- 必要に応じたロールバック

#### よく使うコマンド

```bash
# ステータスを確認
sudo systemctl status sub2api

# ログを表示
sudo journalctl -u sub2api -f

# サービスを再起動
sudo systemctl restart sub2api

# アンインストール
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
```

---

### 方法2: Docker Compose（推奨）

PostgreSQL と Redis のコンテナを含む Docker Compose でデプロイします。

#### 前提条件

- Docker 20.10+
- Docker Compose v2+

#### クイックスタート（ワンクリックデプロイ）

自動デプロイスクリプトを使用して簡単にセットアップできます:

```bash
# デプロイ用ディレクトリを作成
mkdir -p sub2api-deploy && cd sub2api-deploy

# デプロイ準備スクリプトをダウンロードして実行
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash

# サービスを起動
docker compose up -d

# ログを表示
docker compose logs -f sub2api
```

**スクリプトの動作内容:**
- `docker-compose.local.yml`（`docker-compose.yml` として保存）と `.env.example` をダウンロード
- セキュアな認証情報（JWT_SECRET、TOTP_ENCRYPTION_KEY、POSTGRES_PASSWORD）を自動生成
- 自動生成されたシークレットで `.env` ファイルを作成
- データディレクトリを作成（バックアップ・移行が容易なローカルディレクトリを使用）
- 生成された認証情報を参照用に表示

#### 手動デプロイ

手動でセットアップする場合:

```bash
# 1. リポジトリをクローン
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy

# 2. 環境設定ファイルをコピー
cp .env.example .env
chmod 600 .env

# 3. 設定を編集（セキュアなパスワードを生成）
nano .env
```

**`.env` の必須設定:**

```bash
# PostgreSQL パスワード（必須）
POSTGRES_PASSWORD=your_secure_password_here

# JWT シークレット（推奨 - 再起動後もユーザーのログイン状態を保持）
JWT_SECRET=your_jwt_secret_here

# TOTP 暗号化キー（推奨 - 再起動後も二要素認証を維持）
TOTP_ENCRYPTION_KEY=your_totp_key_here

# オプション: 管理者アカウント
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_password

# オプション: カスタムポート
SERVER_PORT=8080
```

**セキュアなシークレットの生成方法:**
```bash
# JWT_SECRET を生成
openssl rand -hex 32

# TOTP_ENCRYPTION_KEY を生成
openssl rand -hex 32

# POSTGRES_PASSWORD を生成
openssl rand -hex 32
```

```bash
# 4. データディレクトリを作成（ローカルバージョンの場合）
mkdir -p data postgres_data redis_data

# 5. すべてのサービスを起動
# オプション A: ローカルディレクトリバージョン（推奨 - 移行が容易）
docker compose -f docker-compose.local.yml up -d

# オプション B: 名前付きボリュームバージョン（シンプルなセットアップ）
docker compose up -d

# 6. ステータスを確認
docker compose -f docker-compose.local.yml ps

# 7. ログを表示
docker compose -f docker-compose.local.yml logs -f sub2api
```

#### デプロイバージョン

| バージョン | データストレージ | 移行 | 推奨用途 |
|---------|-------------|-----------|----------|
| **docker-compose.local.yml** | ローカルディレクトリ | ✅ 容易（ディレクトリ全体を tar） | 本番環境、頻繁なバックアップ |
| **docker-compose.yml** | 名前付きボリューム | ⚠️ docker コマンドが必要 | シンプルなセットアップ |

**推奨:** データ管理が容易な `docker-compose.local.yml`（スクリプトによるデプロイ）を使用してください。

#### アクセス

ブラウザで `http://YOUR_SERVER_IP:8080` を開いてください。

管理者パスワードが自動生成された場合は、ログで確認できます:
```bash
docker compose -f docker-compose.local.yml logs sub2api | grep "admin password"
```

#### アップグレード

```bash
# 最新イメージをプルしてコンテナを再作成
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
```

#### 簡単な移行（ローカルディレクトリバージョン）

`docker-compose.local.yml` を使用している場合、新しいサーバーへの移行が簡単です:

```bash
# 移行元サーバーにて
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/

# 新しいサーバーに転送
scp sub2api-complete.tar.gz user@new-server:/path/

# 移行先サーバーにて
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker compose -f docker-compose.local.yml up -d
```

#### よく使うコマンド

```bash
# すべてのサービスを停止
docker compose -f docker-compose.local.yml down

# 再起動
docker compose -f docker-compose.local.yml restart

# すべてのログを表示
docker compose -f docker-compose.local.yml logs -f

# すべてのデータを削除（注意！）
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/
```

---

### 方法3: Apple container（macOS）

Apple シリコン搭載 Mac と macOS 26 では、Apple `container` 1.1.0 以降を使用して Sub2API、PostgreSQL、Redis の完全なスタックを実行できます:

```bash
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
```

これはローカル開発および手動運用向けです。本番環境では引き続き Docker Compose を推奨します。ライフサイクル、永続化、アップグレード、制限については [deploy/APPLE_CONTAINER.md](deploy/APPLE_CONTAINER.md) を参照してください。

---

### 方法4: ソースからビルド

開発やカスタマイズのためにソースコードからビルドして実行します。

#### 前提条件

- Go 1.21+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+

#### ビルド手順

```bash
# 1. リポジトリをクローン
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api

# 2. pnpm をインストール（未インストールの場合）
npm install -g pnpm

# 3. フロントエンドをビルド
cd frontend
pnpm install
pnpm run build
# 出力先: ../backend/internal/web/dist/

# 4. フロントエンドを組み込んだバックエンドをビルド
cd ../backend
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server

# 5. 設定ファイルを作成
cp ../deploy/config.example.yaml ./config.yaml

# 6. 設定を編集
nano config.yaml
```

> **注意:** `-tags embed` フラグはフロントエンドをバイナリに組み込みます。このフラグがない場合、バイナリはフロントエンド UI を提供しません。

**`config.yaml` の主要設定:**

```yaml
server:
  host: "0.0.0.0"
  port: 8080
  mode: "release"

database:
  host: "localhost"
  port: 5432
  user: "postgres"
  password: "your_password"
  dbname: "sub2api"

redis:
  host: "localhost"
  port: 6379
  password: ""

jwt:
  secret: "change-this-to-a-secure-random-string"
  expire_hour: 24

default:
  user_concurrency: 5
  user_balance: 0
  api_key_prefix: "sk-"
  rate_multiplier: 1.0
```

### Sora ステータス（一時的に利用不可）

> ⚠️ Sora 関連の機能は、上流統合およびメディア配信の技術的問題により一時的に利用できません。
> 現時点では本番環境で Sora に依存しないでください。
> 既存の `gateway.sora_*` 設定キーは予約されていますが、これらの問題が解決されるまで有効にならない場合があります。

`config.yaml` では追加のセキュリティ関連オプションも利用できます:

- `cors.allowed_origins` - CORS 許可リスト
- `security.url_allowlist` - 上流/価格/CRS ホストの許可リスト
- `security.url_allowlist.enabled` - URL バリデーションの無効化（注意して使用）
- `security.url_allowlist.allow_insecure_http` - バリデーション無効時に HTTP URL を許可
- `security.url_allowlist.allow_private_hosts` - プライベート/ローカル IP アドレスを許可
- `security.response_headers.enabled` - 設定可能なレスポンスヘッダーフィルタリングを有効化（無効時はデフォルトの許可リストを使用）
- `security.csp` - Content-Security-Policy ヘッダーの制御
- `billing.circuit_breaker` - 課金エラー時にフェイルクローズ
- `security.trust_forwarded_ip_for_api_key_acl` - 従来の生転送ヘッダーによる上書きを制御（アップグレード互換性のため既定で有効）。無効にすると `server.trusted_proxies` を厳格に使用し、Sub2API に直接接続するプロキシの正確な CIDR のみを指定
- `security.forwarded_client_ip_headers` - サードパーティ CDN のクライアント IP ヘッダーを最大 16 個指定。従来モードが有効な場合のみ、設定順で組み込みヘッダーより先に評価
- `turnstile.required` - リリースモードでの Turnstile 必須化

カスタムクライアント IP ヘッダーは YAML またはカンマ区切りの環境変数で設定できます:

```bash
SECURITY_FORWARDED_CLIENT_IP_HEADERS=True-Client-IP,X-CDN-Client-IP
```

ヘッダー名は検証、正規化、大小文字を区別しない重複排除が行われます。管理画面のセキュリティ設定から再起動せずに更新でき、新規インストールでは YAML/環境変数の既定値を保存し、既存環境ではデータベース値がない場合に補完します。従来モードを無効にするとカスタムおよび組み込みの生転送ヘッダーはすべて無視され、`server.trusted_proxies` のみを使用します。有効にする場合はオリジンへの接続元を CDN/プロキシに制限し、エッジで信頼する全クライアント IP ヘッダーを上書きしてください。移行規則と信頼境界の詳細は [`deploy/EDGE_SECURITY.md`](deploy/EDGE_SECURITY.md) を参照してください。

**⚠️ セキュリティ警告: HTTP URL 設定**

`security.url_allowlist.enabled=false` の場合、システムは最小限の URL バリデーションのみを行い、**デフォルトで HTTP URL を許可**します（開発フレンドリーモード。Docker Compose デプロイのデフォルトも同じです）。本番環境では、以下のように明示的に HTTPS のみに制限することを推奨します:

```yaml
security:
  url_allowlist:
    enabled: false                # 許可リストチェックを無効化
    allow_insecure_http: false    # HTTPS のみ許可（本番環境推奨）
```

**または環境変数で設定:**

```bash
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=false
```

**HTTP を許可するリスク:**
- API キーとデータが**平文**で送信される（傍受の危険性）
- **中間者攻撃（MITM）**を受けやすい
- **本番環境には不適切**

**HTTP を使用すべき場面:**
- ✅ ローカルサーバーでの開発・テスト（http://localhost）
- ✅ 信頼できるエンドポイントを持つ内部ネットワーク
- ✅ HTTPS 取得前のアカウント接続テスト
- ❌ 本番環境（HTTPS のみを使用）

**`allow_insecure_http: false` 設定時に HTTP URL で表示されるエラー例:**
```
Invalid base URL: invalid url scheme: http
```

URL バリデーションまたはレスポンスヘッダーフィルタリングを無効にする場合は、ネットワーク層を強化してください:
- 上流ドメイン/IP のエグレス許可リストを適用
- プライベート/ループバック/リンクローカル範囲をブロック
- TLS のみのアウトバウンドトラフィックを強制
- プロキシで機密性の高い上流レスポンスヘッダーを除去

#### ⚠️ 重要：管理者アカウントの作成

初期管理者アカウントは**セットアップウィザード経由でのみ作成**されます（初回起動時に `http://<host>:8080` にアクセス）。`config.yaml` の `default.admin_email` / `default.admin_password` フィールドは**管理者作成には使われません**。テンプレートに残っているのは歴史的経緯によるものです。

上記ステップ 5 で事前に `config.yaml` を作成しているため、**初回起動時にセットアップウィザードはスキップされます**。サーバーは既存の config を検出して通常モードで直接起動し、この時点では `users` テーブルが空のため、初回ログインは `invalid email or password` を返します。

**管理者アカウントを作成する 2 つの方法:**

1. **推奨 — ウィザードに `config.yaml` を自動生成させる:** 上記ステップ 5 をスキップします（`cp` を実行しない）。`./sub2api` を直接起動し、`http://localhost:8080` にアクセスすると、セットアップウィザードがデータベース・Redis・管理者アカウントの設定を案内し、`config.yaml` を自動生成します。

2. **すでに `config.yaml` を作成してしまった場合:** 初回起動前に一時的に退避してウィザードを発生させ、完了後に戻します:
   ```bash
   mv config.yaml config.yaml.bak
   ./sub2api        # ウィザードが http://localhost:8080 で起動し、新しい config.yaml を生成します
   # ウィザード完了後、Ctrl+C でサーバーを停止し、設定を復元します:
   mv config.yaml.bak config.yaml
   ./sub2api        # 通常モードで再起動し、作成した管理者でログインします
   ```

```bash
# 6. アプリケーションを実行
./sub2api
```

#### 開発モード

```bash
# バックエンド（ホットリロード付き）
cd backend
go run ./cmd/server

# フロントエンド（ホットリロード付き）
cd frontend
pnpm run dev
```

#### コード生成

`backend/ent/schema` を編集した場合、Ent + Wire を再生成してください:

```bash
cd backend
go generate ./ent
go generate ./cmd/server
```

---

## シンプルモード

シンプルモードは、フル SaaS 機能を必要とせず、素早くアクセスしたい個人開発者や社内チーム向けに設計されています。

- 有効化: 環境変数 `RUN_MODE=simple` を設定
- 違い: SaaS 関連機能を非表示にし、課金プロセスをスキップ
- セキュリティに関する注意: 本番環境では `SIMPLE_MODE_CONFIRM=true` も設定する必要があります

---

## Antigravity サポート

Sub2API は [Antigravity](https://antigravity.so/) アカウントをサポートしています。認証後、Claude および Gemini モデル用の専用エンドポイントが利用可能になります。

### 専用エンドポイント

| エンドポイント | モデル |
|----------|-------|
| `/antigravity/v1/messages` | Claude モデル |
| `/antigravity/v1beta/` | Gemini モデル |

### Claude Code の設定

```bash
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
export ANTHROPIC_AUTH_TOKEN="sk-xxx"
```

### ハイブリッドスケジューリングモード

Antigravity アカウントはオプションの**ハイブリッドスケジューリング**をサポートしています。有効にすると、汎用エンドポイント `/v1/messages` および `/v1beta/` も Antigravity アカウントにリクエストをルーティングします。

> **⚠️ 警告**: Anthropic Claude と Antigravity Claude は**同じ会話コンテキスト内で混在させることはできません**。グループを使用して適切に分離してください。

---

## プロジェクト構成

```
sub2api/
├── backend/                  # Go バックエンドサービス
│   ├── cmd/server/           # アプリケーションエントリ
│   ├── internal/             # 内部モジュール
│   │   ├── config/           # 設定
│   │   ├── model/            # データモデル
│   │   ├── service/          # ビジネスロジック
│   │   ├── handler/          # HTTP ハンドラー
│   │   └── gateway/          # API ゲートウェイコア
│   └── resources/            # 静的リソース
│
├── frontend/                 # Vue 3 フロントエンド
│   └── src/
│       ├── api/              # API 呼び出し
│       ├── stores/           # 状態管理
│       ├── views/            # ページコンポーネント
│       └── components/       # 再利用可能なコンポーネント
│
└── deploy/                   # デプロイファイル
    ├── docker-compose.yml    # Docker Compose 設定
    ├── .env.example          # Docker Compose 用環境変数
    ├── config.example.yaml   # バイナリデプロイ用フル設定ファイル
    └── install.sh            # ワンクリックインストールスクリプト
```

## スター履歴

<a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
 </picture>
</a>

---

## ライセンス

本プロジェクトは [GNU Lesser General Public License v3.0](LICENSE)（またはそれ以降のバージョン）の下でライセンスされています。

Copyright (c) 2026 Wesley Liddick

---

<div align="center">

**このプロジェクトが役に立ったら、ぜひスターをお願いします！**

</div>


## /assets/logo.svg

```svg path="/assets/logo.svg" 
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title desc">
  <title id="title">Sub2API</title>
  <desc id="desc">An interlocking S symbol representing subscription routing into APIs.</desc>
  <defs>
    <linearGradient id="s2a-bg" x1="72" y1="44" x2="442" y2="478" gradientUnits="userSpaceOnUse">
      <stop stop-color="#142B56"/>
      <stop offset=".52" stop-color="#0A1A39"/>
      <stop offset="1" stop-color="#061127"/>
    </linearGradient>
    <radialGradient id="s2a-ambient" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(168 92) rotate(51) scale(342 382)">
      <stop stop-color="#3E68B0" stop-opacity=".28"/>
      <stop offset="1" stop-color="#3E68B0" stop-opacity="0"/>
    </radialGradient>
    <linearGradient id="s2a-brand" x1="4" y1="4" x2="20" y2="20" gradientUnits="userSpaceOnUse">
      <stop stop-color="#79F4BD"/>
      <stop offset=".48" stop-color="#39D9E7"/>
      <stop offset="1" stop-color="#3875F6"/>
    </linearGradient>
    <filter id="s2a-shadow" x="-25%" y="-25%" width="150%" height="165%" color-interpolation-filters="sRGB">
      <feDropShadow dx="0" dy=".55" stdDeviation=".65" flood-color="#020817" flood-opacity=".34"/>
    </filter>
  </defs>

  <rect x="16" y="16" width="480" height="480" rx="120" fill="url(#s2a-bg)"/>
  <rect x="16" y="16" width="480" height="480" rx="120" fill="url(#s2a-ambient)"/>
  <rect x="16.75" y="16.75" width="478.5" height="478.5" rx="119.25" fill="none" stroke="#A5BFFF" stroke-opacity=".16" stroke-width="1.5"/>

  <g transform="translate(52 52) scale(17)" fill="none" stroke="url(#s2a-brand)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.7" filter="url(#s2a-shadow)">
    <path d="m19.25 7.65-2.55-3.2a1.33 1.33 0 0 0-1.03-.5H8.58c-.34 0-.67.13-.91.37L4.15 7.65c-.93.88-.6 1.52.65 2.3l9.55 5.97"/>
    <path d="m4.75 16.35 2.55 3.2c.25.31.63.5 1.03.5h7.09c.34 0 .67-.13.91-.37l3.52-3.33c.93-.88.6-1.52-.65-2.3L9.65 8.08"/>
  </g>
</svg>

```

## /assets/partners/logos/RoxyBrowser.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/RoxyBrowser.png

## /assets/partners/logos/aigocode.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/aigocode.png

## /assets/partners/logos/aimzoon.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/aimzoon.jpg

## /assets/partners/logos/apikey-fun.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/apikey-fun.png

## /assets/partners/logos/bestproxy.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/bestproxy.png

## /assets/partners/logos/bmoplus.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/bmoplus.jpg

## /assets/partners/logos/cctk.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/cctk.jpg

## /assets/partners/logos/claudeapi.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/claudeapi.jpg

## /assets/partners/logos/code0.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/code0.jpg

## /assets/partners/logos/ctok.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/ctok.png

## /assets/partners/logos/etok.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/etok.png

## /assets/partners/logos/fastaitoken.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/fastaitoken.jpg

## /assets/partners/logos/fennoai.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/fennoai.jpg

## /assets/partners/logos/haoai.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/haoai.png

## /assets/partners/logos/haoai.svg

```svg path="/assets/partners/logos/haoai.svg" 
<svg
  width="340"
  height="112"
  viewBox="0 0 340 112"
  fill="none"
  xmlns="http://www.w3.org/2000/svg"
  role="img"
  aria-label="Hao.ai"
>
  <!-- Fill the compact landscape canvas while preserving the homepage lockup proportions. -->
  <g transform="translate(170 56) scale(1.45) translate(-170 -56)">
  <!-- Homepage HaoAI mark, rendered in the brand green. -->
  <g transform="translate(66.55 12) scale(0.55)">
    <path
      d="M49 30C42.3726 30 37 35.3726 37 42V118C37 124.627 42.3726 130 49 130C55.6274 130 61 124.627 61 118V42C61 35.3726 55.6274 30 49 30ZM111 30C104.373 30 99 35.3726 99 42V118C99 124.627 104.373 130 111 130C117.627 130 123 124.627 123 118V42C123 35.3726 117.627 30 111 30ZM80 92.5C86.9036 92.5 92.5 86.9036 92.5 80C92.5 73.0964 86.9036 67.5 80 67.5C73.0964 67.5 67.5 73.0964 67.5 80C67.5 86.9036 73.0964 92.5 80 92.5Z"
      fill="#1DB954"
      fill-rule="evenodd"
      clip-rule="evenodd"
    />
  </g>

  <!-- Homepage wordmark converted from Inter SemiBold to vector outlines. -->
  <g transform="translate(150.35 69) scale(0.0166015625 -0.0166015625)" fill="#111111">
    <path d="M150 0L150 1490L417 1490L417 874L1110 874L1110 1490L1377 1490L1377 0L1110 0L1110 649L417 649L417 0Z" />
    <path transform="translate(1527 0)" d="M450 -23Q344 -23 259 15.5Q174 54 125 129Q76 204 76 314Q76 409 111.5 471Q147 533 207.5 570Q268 607 344 626Q420 645 502 653Q599 664 660 672Q721 680 749.5 698Q778 716 778 754L778 759Q778 814 756 852.5Q734 891 690.5 911.5Q647 932 583 932Q518 932 470.5 912Q423 892 393 860Q363 828 349 791L108 840Q141 939 210 1003.5Q279 1068 374.5 1100Q470 1132 582 1132Q661 1132 741.5 1113.5Q822 1095 889 1051Q956 1007 997 933Q1038 859 1038 747L1038 0L789 0L789 154L779 154Q754 107 710.5 66.5Q667 26 602.5 1.5Q538 -23 450 -23ZM517 170Q598 170 656.5 202Q715 234 747.5 286.5Q780 339 780 401L780 533Q767 523 736.5 514Q706 505 668.5 498Q631 491 594.5 486Q558 481 533 478Q475 470 428 452Q381 434 354.5 401.5Q328 369 328 317Q328 269 352.5 236.5Q377 204 419.5 187Q462 170 517 170Z" />
    <path transform="translate(2703 0)" d="M623 -23Q460 -23 340 49.5Q220 122 154.5 251.5Q89 381 89 553Q89 727 154.5 857.5Q220 988 340 1060Q460 1132 623 1132Q787 1132 907 1060Q1027 988 1092.5 857.5Q1158 727 1158 553Q1158 381 1092.5 251.5Q1027 122 907 49.5Q787 -23 623 -23ZM623 188Q714 188 774.5 237Q835 286 864.5 369Q894 452 894 554Q894 657 864.5 740Q835 823 774.5 872Q714 921 623 921Q532 921 472.5 872Q413 823 383.5 740.5Q354 658 354 554Q354 452 383.5 369Q413 286 472.5 237Q532 188 623 188Z" />
    <path transform="translate(3950 0)" fill="#1DB954" d="M326 -17Q259 -17 212.5 29Q166 75 166 143Q166 210 212.5 256Q259 302 326 302Q394 302 440.5 256Q487 210 487 143Q487 75 440.5 29Q394 -17 326 -17Z" />
    <path transform="translate(4603 0)" d="M450 -23Q344 -23 259 15.5Q174 54 125 129Q76 204 76 314Q76 409 111.5 471Q147 533 207.5 570Q268 607 344 626Q420 645 502 653Q599 664 660 672Q721 680 749.5 698Q778 716 778 754L778 759Q778 814 756 852.5Q734 891 690.5 911.5Q647 932 583 932Q518 932 470.5 912Q423 892 393 860Q363 828 349 791L108 840Q141 939 210 1003.5Q279 1068 374.5 1100Q470 1132 582 1132Q661 1132 741.5 1113.5Q822 1095 889 1051Q956 1007 997 933Q1038 859 1038 747L1038 0L789 0L789 154L779 154Q754 107 710.5 66.5Q667 26 602.5 1.5Q538 -23 450 -23ZM517 170Q598 170 656.5 202Q715 234 747.5 286.5Q780 339 780 401L780 533Q767 523 736.5 514Q706 505 668.5 498Q631 491 594.5 486Q558 481 533 478Q475 470 428 452Q381 434 354.5 401.5Q328 369 328 317Q328 269 352.5 236.5Q377 204 419.5 187Q462 170 517 170Z" />
    <path transform="translate(5779 0)" d="M138 0L138 1118L398 1118L398 0ZM268 1276Q206 1276 161.5 1318Q117 1360 117 1418Q117 1478 161.5 1519.5Q206 1561 268 1561Q331 1561 375.5 1519.5Q420 1478 420 1419Q420 1360 375.5 1318Q331 1276 268 1276Z" />
  </g>
  </g>
</svg>

```

## /assets/partners/logos/lanox.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/lanox.jpg

## /assets/partners/logos/nagora.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/nagora.png

## /assets/partners/logos/novada.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/novada.png

## /assets/partners/logos/openmodel.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/openmodel.jpg

## /assets/partners/logos/pateway.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/pateway.png

## /assets/partners/logos/pincc-logo.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/pincc-logo.png

## /assets/partners/logos/poixe.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/poixe.png

## /assets/partners/logos/pptoken.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/pptoken.png

## /assets/partners/logos/proxy4free.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/proxy4free.png

## /assets/partners/logos/qiniu.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/qiniu.jpg

## /assets/partners/logos/rapidproxy.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/rapidproxy.jpg

## /assets/partners/logos/runapi.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/runapi.png

## /assets/partners/logos/silkapi.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/silkapi.png

## /assets/partners/logos/sui-xiang.jpg

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/sui-xiang.jpg

## /assets/partners/logos/veilx.png

Binary file available at https://raw.githubusercontent.com/Wei-Shaw/sub2api/refs/heads/main/assets/partners/logos/veilx.png

## /backend/.dockerignore

```dockerignore path="/backend/.dockerignore" 
.cache/
.DS_Store

```

## /backend/.golangci.yml

```yml path="/backend/.golangci.yml" 
version: "2"

linters:
  default: none
  enable:
    - depguard
    - errcheck
    - gosec
    - govet
    - ineffassign
    - staticcheck
    - unused

  settings:
    depguard:
      rules:
        # Enforce: service must not depend on repository.
        service-no-repository:
          list-mode: original
          files:
            - "**/internal/service/**"
            - "!**/internal/service/ops_aggregation_service.go"
            - "!**/internal/service/ops_alert_evaluator_service.go"
            - "!**/internal/service/ops_cleanup_service.go"
            - "!**/internal/service/ops_metrics_collector.go"
            - "!**/internal/service/ops_scheduled_report_service.go"
            - "!**/internal/service/wire.go"
          deny:
            - pkg: github.com/Wei-Shaw/sub2api/internal/repository
              desc: "service must not import repository"
            - pkg: gorm.io/gorm
              desc: "service must not import gorm"
            - pkg: github.com/redis/go-redis/v9
              desc: "service must not import redis"
        handler-no-repository:
          list-mode: original
          files:
            - "**/internal/handler/**"
          deny:
            - pkg: github.com/Wei-Shaw/sub2api/internal/repository
              desc: "handler must not import repository"
            - pkg: gorm.io/gorm
              desc: "handler must not import gorm"
            - pkg: github.com/redis/go-redis/v9
              desc: "handler must not import redis"
    gosec:
      excludes:
        - G101
        - G103
        - G104
        - G109
        - G115
        - G201
        - G202
        - G301
        - G302
        - G304
        - G306
        - G404
      severity: high
      confidence: high
    errcheck:
      # Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
      # Such cases aren't reported by default.
      # Default: false
      check-type-assertions: true
      # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`.
      # Such cases aren't reported by default.
      # Default: false
      check-blank: false
      # To disable the errcheck built-in exclude list.
      # See `-excludeonly` option in https://github.com/kisielk/errcheck#excluding-functions for details.
      # Default: false
      disable-default-exclusions: true
      # List of functions to exclude from checking, where each entry is a single function to exclude.
      # See https://github.com/kisielk/errcheck#excluding-functions for details.
      exclude-functions:
        - io/ioutil.ReadFile
        - io.Copy(*bytes.Buffer)
        - io.Copy(os.Stdout)
        - fmt.Println
        - fmt.Print
        - fmt.Printf
        - fmt.Fprint
        - fmt.Fprintf
        - fmt.Fprintln
      # Display function signature instead of selector.
      # Default: false
      verbose: true
    ineffassign:
      # Check escaping variables of type error, may cause false positives.
      # Default: false
      check-escaping-errors: true
    staticcheck:
      # https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist
      dot-import-whitelist:
        - fmt
      # https://staticcheck.dev/docs/configuration/options/#initialisms
      initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS" ]
      # https://staticcheck.dev/docs/configuration/options/#http_status_code_whitelist
      http-status-code-whitelist: [ "200", "400", "404", "500" ]
      # "all" enables every SA/ST/S/QF check; only list the ones to disable.
      checks:
        - all
        - -ST1000  # Package comment format
        - -ST1003  # Poorly chosen identifier (ApiKey vs APIKey)
        - -ST1020  # Comment on exported method format
        - -ST1021  # Comment on exported type format
        - -ST1022  # Comment on exported variable format
    unused:
      # Default: true
      field-writes-are-uses: true
      # Default: false
      post-statements-are-reads: true
      # Default: true
      exported-fields-are-used: true
      # Default: true
      parameters-are-used: true
      # Default: true
      local-variables-are-used: false
      # Default: true — must be true, ent generates 130K+ lines of code
      generated-is-used: true

formatters:
  enable:
    - gofmt
  settings:
    gofmt:
      # Simplify code: gofmt with `-s` option.
      # Default: true
      simplify: false
      # Apply the rewrite rules to the source before reformatting.
      # https://pkg.go.dev/cmd/gofmt
      # Default: []
      rewrite-rules:
        - pattern: 'interface{}'
          replacement: 'any'
        - pattern: 'a[b:len(a)]'
          replacement: 'a[b:]'

```

## /backend/Dockerfile

``` path="/backend/Dockerfile" 
FROM golang:1.26.5-alpine

WORKDIR /app

# 安装必要的工具
RUN apk add --no-cache git

# 复制go.mod和go.sum
COPY go.mod go.sum ./

# 下载依赖
RUN go mod download

# 复制源代码
COPY . .

# 构建应用
RUN VERSION_VALUE="$(./scripts/resolve-version.sh)" && \
    go build -ldflags="-s -w -X main.Version=${VERSION_VALUE}" -o main ./cmd/server/

# 暴露端口
EXPOSE 8080

# 运行应用
CMD ["./main"]

```

## /backend/Makefile

``` path="/backend/Makefile" 
.PHONY: build generate test test-unit test-integration test-e2e

VERSION ?= $(shell ./scripts/resolve-version.sh)
LDFLAGS ?= -s -w -X main.Version=$(VERSION)

build:
	CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -trimpath -o bin/server ./cmd/server

generate:
	go generate ./ent
	go generate ./cmd/server

test:
	go test ./...
	golangci-lint run ./...

test-unit:
	go test -tags=unit ./...

test-integration:
	go test -tags=integration ./...

test-e2e:
	./scripts/e2e-test.sh

test-e2e-local:
	go test -tags=e2e -v -timeout=300s ./internal/integration/...

```

## /backend/cmd/cleanup-ingress-reject-logs/README.md

# Ingress rejection log cleanup

This maintenance command removes historical admission rejections from
`ops_error_logs` without matching unrelated authentication or upstream errors.
It is a dry run unless `--execute` is supplied, and always requires an explicit
RFC3339 cutoff.

```sh
go run ./cmd/cleanup-ingress-reject-logs --before 2026-07-17T00:00:00Z
go run ./cmd/cleanup-ingress-reject-logs --before 2026-07-17T00:00:00Z --execute
```

Run the execute form only after every application instance has been upgraded so
older instances cannot add new ingress rejection rows below the chosen cutoff.
The classifier intentionally retains invariant failures such as
`USER_NOT_FOUND`, database errors, quota/billing errors, and upstream failures.

After the rollout and cleanup are verified, run
`backend/scripts/finalize-ingress-reject-cleanup.sql` in a maintenance window to
remove the deprecated plaintext-key audit table and attribution columns.


## /backend/cmd/cleanup-ingress-reject-logs/main.go

```go path="/backend/cmd/cleanup-ingress-reject-logs/main.go" 
package main

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	_ "github.com/Wei-Shaw/sub2api/ent/runtime"
	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/repository"
	"github.com/lib/pq"
)

const classifierVersion = "ingress-reject-v1"

type candidate struct {
	id         int64
	statusCode int
	message    string
	body       string
}

func main() {
	beforeRaw := flag.String("before", "", "required RFC3339 cutoff; only older rows are considered")
	execute := flag.Bool("execute", false, "delete matched rows (default is dry-run)")
	batchSize := flag.Int("batch-size", 5000, "scan/delete batch size (1-5000)")
	flag.Parse()

	if *beforeRaw == "" {
		log.Fatal("--before is required")
	}
	before, err := time.Parse(time.RFC3339, *beforeRaw)
	if err != nil {
		log.Fatalf("invalid --before: %v", err)
	}
	if *batchSize < 1 || *batchSize > 5000 {
		log.Fatal("--batch-size must be between 1 and 5000")
	}

	cfg, err := config.LoadForBootstrap()
	if err != nil {
		log.Fatalf("load config: %v", err)
	}
	client, db, err := repository.InitEnt(cfg)
	if err != nil {
		log.Fatalf("initialize database: %v", err)
	}
	defer func() { _ = client.Close() }()

	ctx := context.Background()
	counts, scanned, matched, deleted, err := cleanup(ctx, db, before, *batchSize, *execute)
	if err != nil {
		log.Fatalf("cleanup failed: %v", err)
	}

	digest := sha256.Sum256([]byte(classifierVersion))
	mode := "dry-run"
	if *execute {
		mode = "execute"
	}
	fmt.Printf("mode=%s before=%s classifier=%s scanned=%d matched=%d deleted=%d\n",
		mode, before.UTC().Format(time.RFC3339), hex.EncodeToString(digest[:]), scanned, matched, deleted)
	reasons := make([]string, 0, len(counts))
	for reason := range counts {
		reasons = append(reasons, reason)
	}
	sort.Strings(reasons)
	for _, reason := range reasons {
		fmt.Printf("reason=%s count=%d\n", reason, counts[reason])
	}
	if *execute && deleted > 0 {
		fmt.Println("cleanup complete; schedule VACUUM (ANALYZE) ops_error_logs during normal maintenance")
	}
}

func cleanup(ctx context.Context, db *sql.DB, before time.Time, batchSize int, execute bool) (map[string]int64, int64, int64, int64, error) {
	counts := make(map[string]int64)
	var cursor, scanned, matched, deleted int64
	for {
		rows, err := db.QueryContext(ctx, `
			SELECT id, COALESCE(status_code, 0), COALESCE(error_message, ''), COALESCE(error_body, '')
			FROM ops_error_logs
			WHERE id > $1
			  AND created_at < $2
			  AND error_phase = 'auth'
			  AND account_id IS NULL
			  AND upstream_status_code IS NULL
			  AND COALESCE(upstream_error_message, '') = ''
			  AND COALESCE(upstream_error_detail, '') = ''
			ORDER BY id ASC
			LIMIT $3`, cursor, before, batchSize)
		if err != nil {
			return nil, scanned, matched, deleted, err
		}

		batch := make([]candidate, 0, batchSize)
		for rows.Next() {
			var item candidate
			if err := rows.Scan(&item.id, &item.statusCode, &item.message, &item.body); err != nil {
				_ = rows.Close()
				return nil, scanned, matched, deleted, err
			}
			batch = append(batch, item)
			cursor = item.id
		}
		if err := rows.Err(); err != nil {
			_ = rows.Close()
			return nil, scanned, matched, deleted, err
		}
		_ = rows.Close()
		if len(batch) == 0 {
			break
		}

		ids := make([]int64, 0, len(batch))
		for _, item := range batch {
			scanned++
			if reason, ok := historicalIngressRejectReason(item); ok {
				matched++
				counts[reason]++
				ids = append(ids, item.id)
			}
		}
		if execute && len(ids) > 0 {
			result, err := db.ExecContext(ctx,
				`DELETE FROM ops_error_logs WHERE id = ANY($1) AND created_at < $2`, pq.Array(ids), before)
			if err != nil {
				return nil, scanned, matched, deleted, err
			}
			n, err := result.RowsAffected()
			if err != nil {
				return nil, scanned, matched, deleted, err
			}
			deleted += n
		}
	}
	return counts, scanned, matched, deleted, nil
}

func historicalIngressRejectReason(item candidate) (string, bool) {
	code, message := parseErrorIdentity(item.body, item.message)
	switch code {
	case "API_KEY_REQUIRED":
		return "missing_key", true
	case "INVALID_API_KEY":
		return "invalid_key", true
	case "API_KEY_DISABLED":
		return "key_disabled", true
	case "USER_INACTIVE":
		return "user_inactive", true
	case "GROUP_DELETED":
		return "group_deleted", true
	case "GROUP_DISABLED":
		return "group_disabled", true
	case "GROUP_NOT_ALLOWED":
		return "group_forbidden", true
	case "ACCESS_DENIED":
		return "ip_acl_denied", true
	case "api_key_in_query_deprecated":
		return "query_key_deprecated", true
	}

	normalized := strings.TrimSpace(message)
	switch {
	case normalized == "API key is required":
		return "missing_key", true
	case normalized == "Invalid API key":
		return "invalid_key", true
	case normalized == "API key is disabled":
		return "key_disabled", true
	case normalized == "User account is not active":
		return "user_inactive", true
	case normalized == "API Key 所属分组已删除":
		return "group_deleted", true
	case normalized == "API Key 所属分组已停用":
		return "group_disabled", true
	case normalized == "API Key 所属专属分组不再允许当前用户使用":
		return "group_forbidden", true
	case normalized == "API Key is not assigned to any group and cannot be used. Please contact the administrator to assign it to a group.":
		return "group_unassigned", true
	case strings.HasPrefix(normalized, "Access denied. Your IP is "):
		return "ip_acl_denied", true
	case normalized == "Query parameter api_key is deprecated. Use Authorization header or key instead.":
		return "query_key_deprecated", true
	default:
		return "", false
	}
}

func parseErrorIdentity(body, fallbackMessage string) (string, string) {
	var payload struct {
		Code    string `json:"code"`
		Message string `json:"message"`
		Error   struct {
			Code    json.RawMessage `json:"code"`
			Message string          `json:"message"`
		} `json:"error"`
	}
	if err := json.Unmarshal([]byte(body), &payload); err != nil {
		return "", fallbackMessage
	}
	message := payload.Message
	if message == "" {
		message = payload.Error.Message
	}
	if message == "" {
		message = fallbackMessage
	}
	return strings.TrimSpace(payload.Code), message
}

```

## /backend/cmd/cleanup-ingress-reject-logs/main_test.go

```go path="/backend/cmd/cleanup-ingress-reject-logs/main_test.go" 
package main

import "testing"

func TestHistoricalIngressRejectReason(t *testing.T) {
	tests := []struct {
		name   string
		item   candidate
		reason string
		match  bool
	}{
		{name: "standard invalid key", item: candidate{body: `{"code":"INVALID_API_KEY","message":"Invalid API key"}`}, reason: "invalid_key", match: true},
		{name: "google missing key", item: candidate{body: `{"error":{"code":401,"message":"API key is required","status":"UNAUTHENTICATED"}}`}, reason: "missing_key", match: true},
		{name: "google group deleted", item: candidate{body: `{"error":{"code":403,"message":"API Key 所属分组已删除","status":"PERMISSION_DENIED"}}`}, reason: "group_deleted", match: true},
		{name: "ip acl", item: candidate{body: `{"code":"ACCESS_DENIED","message":"Access denied. Your IP is 192.0.2.1"}`}, reason: "ip_acl_denied", match: true},
		{name: "user not found remains", item: candidate{body: `{"code":"USER_NOT_FOUND","message":"User associated with API key not found"}`}, match: false},
		{name: "quota remains", item: candidate{body: `{"code":"API_KEY_QUOTA_EXHAUSTED","message":"quota"}`}, match: false},
		{name: "database failure remains", item: candidate{statusCode: 500, message: "Failed to validate API key", body: `{"code":"INTERNAL_ERROR","message":"Failed to validate API key"}`}, match: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reason, ok := historicalIngressRejectReason(tt.item)
			if ok != tt.match || reason != tt.reason {
				t.Fatalf("got (%q, %v), want (%q, %v)", reason, ok, tt.reason, tt.match)
			}
		})
	}
}

```

## /backend/cmd/jwtgen/main.go

```go path="/backend/cmd/jwtgen/main.go" 
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"time"

	_ "github.com/Wei-Shaw/sub2api/ent/runtime"
	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/repository"
	"github.com/Wei-Shaw/sub2api/internal/service"
)

func main() {
	email := flag.String("email", "", "Admin email to issue a JWT for (defaults to first active admin)")
	flag.Parse()

	cfg, err := config.LoadForBootstrap()
	if err != nil {
		log.Fatalf("failed to load config: %v", err)
	}

	client, sqlDB, err := repository.InitEnt(cfg)
	if err != nil {
		log.Fatalf("failed to init db: %v", err)
	}
	defer func() {
		if err := client.Close(); err != nil {
			log.Printf("failed to close db: %v", err)
		}
	}()

	userRepo := repository.NewUserRepository(client, sqlDB)
	authService := service.NewAuthService(client, userRepo, nil, nil, cfg, nil, nil, nil, nil, nil, nil, nil, nil)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var user *service.User
	if *email != "" {
		user, err = userRepo.GetByEmail(ctx, *email)
	} else {
		user, err = userRepo.GetFirstAdmin(ctx)
	}
	if err != nil {
		log.Fatalf("failed to resolve admin user: %v", err)
	}

	token, err := authService.GenerateToken(ctx, user)
	if err != nil {
		log.Fatalf("failed to generate token: %v", err)
	}

	fmt.Printf("ADMIN_EMAIL=%s\nADMIN_USER_ID=%d\nJWT=%s\n", user.Email, user.ID, token)
}

```

## /backend/cmd/profit-preview/main.go

```go path="/backend/cmd/profit-preview/main.go" 
// profit-preview 读取生产只读导出的 JSON（分组利润配置、账号倍率与探测状态、
// 用户覆盖倍率、主力模型清单），复用线上 U/D/阈值判定做五平台离线预演。
//
// 用法：
//
//	go run ./cmd/profit-preview -input dump.json [-assume-enabled] [-json]
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/Wei-Shaw/sub2api/internal/service"
)

type inputGroup struct {
	ID                   int64   `json:"id"`
	Name                 string  `json:"name"`
	Platform             string  `json:"platform"`
	RateMultiplier       float64 `json:"rate_multiplier"`
	SubscriptionType     string  `json:"subscription_type"`
	ProfitControlEnabled bool    `json:"profit_control_enabled"`
	ProfitMinMargin      float64 `json:"profit_min_margin"`
	ProfitSafetyBuffer   float64 `json:"profit_safety_buffer"`
	PeakRateEnabled      bool    `json:"peak_rate_enabled"`
	PeakStart            string  `json:"peak_start"`
	PeakEnd              string  `json:"peak_end"`
	PeakRateMultiplier   float64 `json:"peak_rate_multiplier"`
}

type inputAccount struct {
	ID             int64             `json:"id"`
	Name           string            `json:"name"`
	Platform       string            `json:"platform"`
	Type           string            `json:"type"`
	RateMultiplier *float64          `json:"rate_multiplier"`
	Extra          map[string]any    `json:"extra"`
	ModelMapping   map[string]string `json:"model_mapping"`
}

type inputEntry struct {
	Group         inputGroup          `json:"group"`
	Accounts      []inputAccount      `json:"accounts"`
	UserOverrides map[string]*float64 `json:"user_overrides"`
	Models        []string            `json:"models"`
}

type inputDoc struct {
	Groups []inputEntry `json:"groups"`
}

func main() {
	inputPath := flag.String("input", "", "生产只读导出 JSON 路径")
	assumeEnabled := flag.Bool("assume-enabled", false, "把当前关闭的支持平台分组按保存配置视为已启用")
	jsonOut := flag.Bool("json", false, "以 JSON 输出完整报告（默认输出可读表格）")
	flag.Parse()
	if *inputPath == "" {
		fmt.Fprintln(os.Stderr, "usage: profit-preview -input dump.json [-assume-enabled] [-json]")
		os.Exit(2)
	}
	raw, err := os.ReadFile(*inputPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "read input: %v\n", err)
		os.Exit(1)
	}
	inputs, err := parsePreviewInputs(raw, *assumeEnabled)
	if err != nil {
		fmt.Fprintf(os.Stderr, "parse input: %v\n", err)
		os.Exit(1)
	}

	evalAt := time.Now()
	reports := service.PreviewProfitAdmission(inputs, evalAt)
	if len(reports) == 0 {
		fmt.Fprintln(os.Stderr, "input produced no preview reports")
		os.Exit(1)
	}

	if *jsonOut {
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		if err := enc.Encode(map[string]any{"evaluated_at": evalAt, "reports": reports}); err != nil {
			fmt.Fprintf(os.Stderr, "write output: %v\n", err)
			os.Exit(1)
		}
		return
	}

	fmt.Printf("利润门预演 @ %s（U=账号倍率；探测状态仅告警）\n", evalAt.Format(time.RFC3339))
	for _, report := range reports {
		fmt.Printf("\n== 分组 %d %s [%s] ==\n", report.GroupID, report.GroupName, report.Platform)
		fmt.Printf("  利润门生效=%v 假定启用=%v | 默认 D=%.4f 阈值=%.4f | 最低有效 D=%.4f 阈值=%.4f\n",
			report.EffectiveGate, report.AssumedEnabled,
			report.DefaultD, report.ThresholdDefault, report.MinEffectiveD, report.ThresholdMinD)
		counts := map[string]int{}
		for _, v := range report.Verdicts {
			counts[v.Class]++
			rate := "-"
			if v.AccountRate != nil {
				rate = fmt.Sprintf("%.4f", *v.AccountRate)
			}
			flags := make([]string, 0, 2)
			if v.RejectedUnderMinD {
				flags = append(flags, "最低有效D下拒绝")
			}
			if len(v.Warnings) > 0 {
				flags = append(flags, strings.Join(v.Warnings, ","))
			}
			suffix := ""
			if len(flags) > 0 {
				suffix = "  [" + strings.Join(flags, "; ") + "]"
			}
			fmt.Printf("  账号 %-4d %-24s 平台=%-12s U=%-8s 来源=%-19s %s%s\n",
				v.AccountID, v.Name, v.Platform, rate, v.RateSource, v.Class, suffix)
		}
		fmt.Printf("  分类合计: 准入=%d 利润不足=%d 倍率非法=%d\n",
			counts[service.ProfitPreviewClassAdmitted],
			counts[service.ProfitPreviewClassRejectedThreshold],
			counts[service.ProfitPreviewClassRejectedInvalidRate])
		models := make([]string, 0, len(report.RemainingByModel))
		for model := range report.RemainingByModel {
			models = append(models, model)
		}
		sort.Strings(models)
		for _, model := range models {
			fmt.Printf("  模型 %-20s 利润门准入账号: 默认D=%d 最低有效D=%d\n",
				model, report.RemainingByModel[model], report.RemainingByModelMinD[model])
		}
		for _, model := range modelsWithZeroRemaining(report) {
			fmt.Printf("  警告: 模型 %s 启用后利润门准入账号为 0\n", model)
		}
		for _, model := range modelsWithZeroRemainingUnderMinD(report) {
			fmt.Printf("  警告: 模型 %s 在最低有效D（存在低倍率用户覆盖）下利润门准入账号为 0\n", model)
		}
	}
}

func parsePreviewInputs(raw []byte, assumeEnabled bool) ([]service.ProfitPreviewGroupInput, error) {
	var doc inputDoc
	if err := json.Unmarshal(raw, &doc); err != nil {
		return nil, err
	}
	if len(doc.Groups) == 0 {
		return nil, fmt.Errorf("input contains no groups; check the export query and target configuration")
	}

	inputs := make([]service.ProfitPreviewGroupInput, 0, len(doc.Groups))
	for i, entry := range doc.Groups {
		if entry.Group.ID <= 0 || strings.TrimSpace(entry.Group.Platform) == "" {
			return nil, fmt.Errorf("invalid group at index %d: id and platform are required", i)
		}
		group := &service.Group{
			ID:                   entry.Group.ID,
			Name:                 entry.Group.Name,
			Platform:             entry.Group.Platform,
			Status:               service.StatusActive,
			Hydrated:             true,
			RateMultiplier:       entry.Group.RateMultiplier,
			SubscriptionType:     entry.Group.SubscriptionType,
			ProfitControlEnabled: entry.Group.ProfitControlEnabled,
			ProfitMinMargin:      entry.Group.ProfitMinMargin,
			ProfitSafetyBuffer:   entry.Group.ProfitSafetyBuffer,
			PeakRateEnabled:      entry.Group.PeakRateEnabled,
			PeakStart:            entry.Group.PeakStart,
			PeakEnd:              entry.Group.PeakEnd,
			PeakRateMultiplier:   entry.Group.PeakRateMultiplier,
		}
		accounts := make([]*service.Account, 0, len(entry.Accounts))
		for _, a := range entry.Accounts {
			account := &service.Account{
				ID:             a.ID,
				Name:           a.Name,
				Platform:       a.Platform,
				Type:           a.Type,
				RateMultiplier: a.RateMultiplier,
				Extra:          a.Extra,
			}
			if len(a.ModelMapping) > 0 {
				mapping := make(map[string]any, len(a.ModelMapping))
				for k, v := range a.ModelMapping {
					mapping[k] = v
				}
				account.Credentials = map[string]any{"model_mapping": mapping}
			}
			accounts = append(accounts, account)
		}
		overrides := make(map[int64]float64, len(entry.UserOverrides))
		for userID, rate := range entry.UserOverrides {
			if rate == nil {
				continue
			}
			var id int64
			if _, err := fmt.Sscan(userID, &id); err == nil && id > 0 {
				overrides[id] = *rate
			}
		}
		inputs = append(inputs, service.ProfitPreviewGroupInput{
			Group:         group,
			Accounts:      accounts,
			UserOverrides: overrides,
			Models:        entry.Models,
			AssumeEnabled: assumeEnabled,
		})
	}
	return inputs, nil
}

func modelsWithZeroRemaining(report service.ProfitPreviewGroupReport) []string {
	var out []string
	for model, count := range report.RemainingByModel {
		if count == 0 {
			out = append(out, model)
		}
	}
	sort.Strings(out)
	return out
}

// modelsWithZeroRemainingUnderMinD 返回默认 D 下仍有准入账号、但在最低有效 D
// 下会归零的模型。最低有效 D 来自分组内最低的用户级倍率覆盖：这些模型对那部分
// 用户是全黑的，而只看默认 D 的告警完全看不出来。
// 两档都为 0 的模型由 modelsWithZeroRemaining 报告，这里不重复。
func modelsWithZeroRemainingUnderMinD(report service.ProfitPreviewGroupReport) []string {
	var out []string
	for model, count := range report.RemainingByModelMinD {
		if count == 0 && report.RemainingByModel[model] > 0 {
			out = append(out, model)
		}
	}
	sort.Strings(out)
	return out
}

```

## /backend/cmd/profit-preview/main_test.go

```go path="/backend/cmd/profit-preview/main_test.go" 
package main

import (
	"testing"
	"time"

	"github.com/Wei-Shaw/sub2api/internal/service"
	"github.com/stretchr/testify/require"
)

func TestParsePreviewInputsIgnoresNullUserOverride(t *testing.T) {
	raw := []byte(`{
		"groups": [{
			"group": {
				"id": 50,
				"name": "preview",
				"platform": "openai",
				"rate_multiplier": 0.5,
				"subscription_type": "standard",
				"profit_control_enabled": false,
				"profit_min_margin": 0.1,
				"profit_safety_buffer": 0
			},
			"accounts": [{
				"id": 1,
				"name": "cheap",
				"platform": "openai",
				"type": "apikey",
				"rate_multiplier": 0.2
			}],
			"user_overrides": {"40": null, "41": 0.4},
			"models": ["gpt-test"]
		}]
	}`)

	inputs, err := parsePreviewInputs(raw, true)
	require.NoError(t, err)
	require.Len(t, inputs, 1)
	require.Equal(t, map[int64]float64{41: 0.4}, inputs[0].UserOverrides)
	require.True(t, inputs[0].AssumeEnabled)

	report := service.PreviewProfitAdmission(inputs, time.Date(2026, 1, 15, 8, 30, 0, 0, time.UTC))[0]
	require.InDelta(t, 0.4, report.MinEffectiveD, 1e-12, "null 覆盖不能被解码成 0 倍率")
	require.InDelta(t, 0.36, report.ThresholdMinD, 1e-12)
}

func TestParsePreviewInputsRejectsEmptyGroups(t *testing.T) {
	for _, raw := range [][]byte{
		[]byte(`{"groups":null}`),
		[]byte(`{"groups":[]}`),
	} {
		inputs, err := parsePreviewInputs(raw, false)
		require.ErrorContains(t, err, "input contains no groups")
		require.Nil(t, inputs)
	}
}

// TestModelsWithZeroRemainingWarnings 钉死两档 D 的归零告警分工：
// 默认 D 归零由 modelsWithZeroRemaining 报告；默认 D 仍有账号但最低有效 D
// （分组内存在更低的用户级倍率覆盖）归零的模型必须单独告警——那些用户的该
// 模型会全黑，只看默认 D 完全看不出来。两档都为 0 时不重复告警。
func TestModelsWithZeroRemainingWarnings(t *testing.T) {
	report := service.ProfitPreviewGroupReport{
		RemainingByModel: map[string]int{
			"both-zero":      0,
			"min-d-zero":     2,
			"healthy":        3,
			"min-d-zero-alt": 1,
		},
		RemainingByModelMinD: map[string]int{
			"both-zero":      0,
			"min-d-zero":     0,
			"healthy":        3,
			"min-d-zero-alt": 0,
		},
	}

	if got := modelsWithZeroRemaining(report); len(got) != 1 || got[0] != "both-zero" {
		t.Fatalf("默认D归零告警应只覆盖 both-zero，got %v", got)
	}

	got := modelsWithZeroRemainingUnderMinD(report)
	want := []string{"min-d-zero", "min-d-zero-alt"}
	if len(got) != len(want) {
		t.Fatalf("最低有效D归零告警不符: got %v want %v", got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("最低有效D归零告警不符（应按模型名排序）: got %v want %v", got, want)
		}
	}
}

```

## /backend/cmd/server/VERSION

``` path="/backend/cmd/server/VERSION" 
0.1.172

```

## /backend/cmd/server/main.go

```go path="/backend/cmd/server/main.go" 
package main

//go:generate go run github.com/google/wire/cmd/wire

import (
	"context"
	_ "embed"
	"errors"
	"flag"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	_ "github.com/Wei-Shaw/sub2api/ent/runtime"
	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/handler"
	"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
	"github.com/Wei-Shaw/sub2api/internal/server/middleware"
	"github.com/Wei-Shaw/sub2api/internal/setup"
	"github.com/Wei-Shaw/sub2api/internal/web"

	"github.com/gin-gonic/gin"
)

//go:embed VERSION
var embeddedVersion string

// Build-time variables (can be set by ldflags)
var (
	Version   = ""
	Commit    = "unknown"
	Date      = "unknown"
	BuildType = "source" // "source" for manual builds, "release" for CI builds (set by ldflags)
)

func init() {
	// 如果 Version 已通过 ldflags 注入（例如 -X main.Version=...），则不要覆盖。
	if strings.TrimSpace(Version) != "" {
		return
	}

	// 默认从 embedded VERSION 文件读取版本号（编译期打包进二进制）。
	Version = strings.TrimSpace(embeddedVersion)
	if Version == "" {
		Version = "0.0.0-dev"
	}
}

// initLogger configures the default slog handler based on gin.Mode().
// In non-release mode, Debug level logs are enabled.
func main() {
	logger.InitBootstrap()
	defer logger.Sync()

	// Parse command line flags
	setupMode := flag.Bool("setup", false, "Run setup wizard in CLI mode")
	showVersion := flag.Bool("version", false, "Show version information")
	flag.Parse()

	if *showVersion {
		log.Printf("Sub2API %s (commit: %s, built: %s)\n", Version, Commit, Date)
		return
	}

	// CLI setup mode
	if *setupMode {
		if err := setup.RunCLI(); err != nil {
			log.Fatalf("Setup failed: %v", err)
		}
		return
	}

	// Check if setup is needed
	if setup.NeedsSetup() {
		// Check if auto-setup is enabled (for Docker deployment)
		if setup.AutoSetupEnabled() {
			log.Println("Auto setup mode enabled...")
			if err := setup.AutoSetupFromEnv(); err != nil {
				log.Fatalf("Auto setup failed: %v", err)
			}
			// Continue to main server after auto-setup
		} else {
			log.Println("First run detected, starting setup wizard...")
			runSetupServer()
			return
		}
	}

	// Normal server mode
	runMainServer()
}

func runSetupServer() {
	r := gin.New()
	r.Use(middleware.Recovery())
	r.Use(middleware.CORS(config.CORSConfig{}))
	r.Use(middleware.SecurityHeaders(config.CSPConfig{Enabled: true, Policy: config.DefaultCSPPolicy}, nil))

	// Register setup routes
	setup.RegisterRoutes(r)

	// Serve embedded frontend if available
	if web.HasEmbeddedFrontend() {
		r.Use(web.ServeEmbeddedFrontend())
	}

	// Get server address from config.yaml or environment variables (SERVER_HOST, SERVER_PORT)
	// This allows users to run setup on a different address if needed
	addr := config.GetServerAddress()
	log.Printf("Setup wizard available at http://%s", addr)
	log.Println("Complete the setup wizard to configure Sub2API")

	protocols := new(http.Protocols)
	protocols.SetHTTP1(true)
	protocols.SetUnencryptedHTTP2(true)

	server := &http.Server{
		Addr:              addr,
		Handler:           r,
		ReadHeaderTimeout: 30 * time.Second,
		IdleTimeout:       120 * time.Second,
		Protocols:         protocols,
	}

	if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		log.Fatalf("Failed to start setup server: %v", err)
	}
}

func runMainServer() {
	cfg, err := config.LoadForBootstrap()
	if err != nil {
		log.Fatalf("Failed to load config: %v", err)
	}
	if err := logger.Init(logger.OptionsFromConfig(cfg.Log)); err != nil {
		log.Fatalf("Failed to initialize logger: %v", err)
	}
	if cfg.RunMode == config.RunModeSimple {
		log.Println("⚠️  WARNING: Running in SIMPLE mode - billing and quota checks are DISABLED")
	}

	buildInfo := handler.BuildInfo{
		Version:   Version,
		BuildType: BuildType,
	}

	app, err := initializeApplication(buildInfo)
	if err != nil {
		log.Fatalf("Failed to initialize application: %v", err)
	}
	defer app.Cleanup()
	if app.PromptAudit != nil {
		if err := app.PromptAudit.Start(context.Background()); err != nil {
			// Startup continues so unrelated APIs stay up. Fail-closed (unavailable)
			// applies only when a persisted blocking policy was observed; without
			// blocking intent, Prompt Audit stays ModeOff so the gateway remains
			// usable and administrators can still disable the feature (#4560).
			log.Printf("Prompt Audit started in degraded state: %v", err)
		}
	}

	// 启动服务器
	go func() {
		if err := app.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("Failed to start server: %v", err)
		}
	}()

	log.Printf("Server started on %s", app.Server.Addr)

	// 等待中断信号
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	log.Println("Shutting down server...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := app.Server.Shutdown(ctx); err != nil {
		log.Printf("Server forced to shutdown: %v", err)
	}

	log.Println("Server exited")
}

```

## /backend/cmd/server/wire.go

```go path="/backend/cmd/server/wire.go" 
//go:build wireinject
// +build wireinject

package main

import (
	"context"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/Wei-Shaw/sub2api/ent"
	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/handler"
	"github.com/Wei-Shaw/sub2api/internal/payment"
	"github.com/Wei-Shaw/sub2api/internal/repository"
	"github.com/Wei-Shaw/sub2api/internal/securityaudit"
	"github.com/Wei-Shaw/sub2api/internal/server"
	"github.com/Wei-Shaw/sub2api/internal/server/middleware"
	"github.com/Wei-Shaw/sub2api/internal/service"

	"github.com/google/wire"
	"github.com/redis/go-redis/v9"
)

type Application struct {
	Server      *http.Server
	PromptAudit *securityaudit.PromptService
	Cleanup     func()
}

func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
	wire.Build(
		// Infrastructure layer ProviderSets
		config.ProviderSet,

		// Business layer ProviderSets
		repository.ProviderSet,
		service.ProviderSet,
		securityaudit.ProviderSet,
		payment.ProviderSet,
		middleware.ProviderSet,
		handler.ProviderSet,

		// Server layer ProviderSet
		server.ProviderSet,

		// Privacy client factory for OpenAI training opt-out
		providePrivacyClientFactory,

		// BuildInfo provider
		provideServiceBuildInfo,

		// Cleanup function provider
		provideCleanup,

		// Application struct
		wire.Struct(new(Application), "Server", "PromptAudit", "Cleanup"),
	)
	return nil, nil
}

func providePrivacyClientFactory() service.PrivacyClientFactory {
	return repository.CreatePrivacyReqClient
}

func provideServiceBuildInfo(buildInfo handler.BuildInfo) service.BuildInfo {
	return service.BuildInfo{
		Version:   buildInfo.Version,
		BuildType: buildInfo.BuildType,
	}
}

func provideCleanup(
	entClient *ent.Client,
	rdb *redis.Client,
	opsMetricsCollector *service.OpsMetricsCollector,
	opsAggregation *service.OpsAggregationService,
	opsAlertEvaluator *service.OpsAlertEvaluatorService,
	opsCleanup *service.OpsCleanupService,
	opsScheduledReport *service.OpsScheduledReportService,
	opsSystemLogSink *service.OpsSystemLogSink,
	opsService *service.OpsService,
	opsIngressReject *service.OpsIngressRejectAggregator,
	apiKeyService *service.APIKeyService,
	authCacheInvalidationWorker *service.AuthCacheInvalidationWorker,
	schedulerSnapshot *service.SchedulerSnapshotService,
	tokenRefresh *service.TokenRefreshService,
	accountExpiry *service.AccountExpiryService,
	codexVersionSync *service.OpenAICodexVersionSyncService,
	proxyExpiry *service.ProxyExpiryService,
	subscriptionExpiry *service.SubscriptionExpiryService,
	usageCleanup *service.UsageCleanupService,
	idempotencyCleanup *service.IdempotencyCleanupService,
	batchImageCleanup *service.BatchImageCleanupService,
	batchImageWorker *service.BatchImageWorkerRuntime,
	pricing *service.PricingService,
	emailQueue *service.EmailQueueService,
	billingCache *service.BillingCacheService,
	usageRecordWorkerPool *service.UsageRecordWorkerPool,
	subscriptionService *service.SubscriptionService,
	oauth *service.OAuthService,
	openaiOAuth *service.OpenAIOAuthService,
	geminiOAuth *service.GeminiOAuthService,
	antigravityOAuth *service.AntigravityOAuthService,
	grokOAuth *service.GrokOAuthService,
	openAIGateway *service.OpenAIGatewayService,
	scheduledTestRunner *service.ScheduledTestRunnerService,
	backupSvc *service.BackupService,
	paymentOrderExpiry *service.PaymentOrderExpiryService,
	channelMonitorRunner *service.ChannelMonitorRunner,
	quotaFlusher *service.UserPlatformQuotaUsageFlusher,
	upstreamBillingProbe *service.UpstreamBillingProbeService,
	ollamaCloudUsage *service.OllamaCloudUsageService,
	auditLog *service.AuditLogService,
	promptAudit *securityaudit.PromptService,
) func() {
	return func() {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()

		type cleanupStep struct {
			name string
			fn   func() error
		}

		// 应用层清理步骤可并行执行，基础设施资源（Redis/Ent）最后按顺序关闭。
		parallelSteps := []cleanupStep{
			{"OpsIngressRejectAggregator", func() error {
				if opsIngressReject != nil {
					opsIngressReject.Stop()
				}
				return nil
			}},
			{"AuthCacheInvalidationWorker", func() error {
				if authCacheInvalidationWorker != nil {
					authCacheInvalidationWorker.Stop()
				}
				return nil
			}},
			{"AuthCacheInvalidationSubscriber", func() error {
				if apiKeyService != nil {
					apiKeyService.StopAuthCacheInvalidationSubscriber()
				}
				return nil
			}},
			{"OpsRuntimeSettingsRefresh", func() error {
				if opsService != nil {
					opsService.StopRuntimeSettingsRefresh()
				}
				return nil
			}},
			{"PromptAuditService", func() error {
				if promptAudit != nil {
					return promptAudit.Shutdown(ctx)
				}
				return nil
			}},
			{"OpsScheduledReportService", func() error {
				if opsScheduledReport != nil {
					opsScheduledReport.Stop()
				}
				return nil
			}},
			{"OpsCleanupService", func() error {
				if opsCleanup != nil {
					opsCleanup.Stop()
				}
				return nil
			}},
			{"OpsSystemLogSink", func() error {
				if opsSystemLogSink != nil {
					opsSystemLogSink.Stop()
				}
				return nil
			}},
			{"AuditLogService", func() error {
				if auditLog != nil {
					auditLog.Stop()
				}
				return nil
			}},
			{"OpsAlertEvaluatorService", func() error {
				if opsAlertEvaluator != nil {
					opsAlertEvaluator.Stop()
				}
				return nil
			}},
			{"OpsAggregationService", func() error {
				if opsAggregation != nil {
					opsAggregation.Stop()
				}
				return nil
			}},
			{"OpsMetricsCollector", func() error {
				if opsMetricsCollector != nil {
					opsMetricsCollector.Stop()
				}
				return nil
			}},
			{"SchedulerSnapshotService", func() error {
				if schedulerSnapshot != nil {
					schedulerSnapshot.Stop()
				}
				return nil
			}},
			{"UsageCleanupService", func() error {
				if usageCleanup != nil {
					usageCleanup.Stop()
				}
				return nil
			}},
			{"IdempotencyCleanupService", func() error {
				if idempotencyCleanup != nil {
					idempotencyCleanup.Stop()
				}
				return nil
			}},
			{"BatchImageCleanupService", func() error {
				if batchImageCleanup != nil {
					batchImageCleanup.Stop()
				}
				return nil
			}},
			{"BatchImageWorkerRuntime", func() error {
				if batchImageWorker != nil {
					batchImageWorker.Stop()
				}
				return nil
			}},
			{"TokenRefreshService", func() error {
				tokenRefresh.Stop()
				return nil
			}},
			{"AccountExpiryService", func() error {
				accountExpiry.Stop()
				return nil
			}},
			{"OpenAICodexVersionSyncService", func() error {
				codexVersionSync.Stop()
				return nil
			}},
			{"ProxyExpiryService", func() error {
				proxyExpiry.Stop()
				return nil
			}},
			{"SubscriptionExpiryService", func() error {
				subscriptionExpiry.Stop()
				return nil
			}},
			{"SubscriptionService", func() error {
				if subscriptionService != nil {
					subscriptionService.Stop()
				}
				return nil
			}},
			{"PricingService", func() error {
				pricing.Stop()
				return nil
			}},
			{"EmailQueueService", func() error {
				emailQueue.Stop()
				return nil
			}},
			{"BillingCacheService", func() error {
				billingCache.Stop()
				return nil
			}},
			{"UsageRecordWorkerPool", func() error {
				if usageRecordWorkerPool != nil {
					usageRecordWorkerPool.Stop()
				}
				return nil
			}},
			{"OAuthService", func() error {
				oauth.Stop()
				return nil
			}},
			{"OpenAIOAuthService", func() error {
				openaiOAuth.Stop()
				return nil
			}},
			{"GeminiOAuthService", func() error {
				geminiOAuth.Stop()
				return nil
			}},
			{"AntigravityOAuthService", func() error {
				antigravityOAuth.Stop()
				return nil
			}},
			{"GrokOAuthService", func() error {
				if grokOAuth != nil {
					grokOAuth.Stop()
				}
				return nil
			}},
			{"OpenAIWSPool", func() error {
				if openAIGateway != nil {
					openAIGateway.CloseOpenAIWSPool()
				}
				return nil
			}},
			{"ScheduledTestRunnerService", func() error {
				if scheduledTestRunner != nil {
					scheduledTestRunner.Stop()
				}
				return nil
			}},
			{"BackupService", func() error {
				if backupSvc != nil {
					backupSvc.Stop()
				}
				return nil
			}},
			{"PaymentOrderExpiryService", func() error {
				if paymentOrderExpiry != nil {
					paymentOrderExpiry.Stop()
				}
				return nil
			}},
			{"ChannelMonitorRunner", func() error {
				if channelMonitorRunner != nil {
					channelMonitorRunner.Stop()
				}
				return nil
			}},
			{"UserPlatformQuotaUsageFlusher", func() error {
				if quotaFlusher != nil {
					quotaFlusher.Stop()
				}
				return nil
			}},
			{"UpstreamBillingProbeService", func() error {
				if upstreamBillingProbe != nil {
					upstreamBillingProbe.Stop()
				}
				return nil
			}},
			{"OllamaCloudUsageService", func() error {
				if ollamaCloudUsage != nil {
					ollamaCloudUsage.Stop()
				}
				return nil
			}},
		}

		infraSteps := []cleanupStep{
			{"Redis", func() error {
				if rdb == nil {
					return nil
				}
				return rdb.Close()
			}},
			{"Ent", func() error {
				if entClient == nil {
					return nil
				}
				return entClient.Close()
			}},
		}

		runParallel := func(steps []cleanupStep) {
			var wg sync.WaitGroup
			for i := range steps {
				step := steps[i]
				wg.Add(1)
				go func() {
					defer wg.Done()
					if err := step.fn(); err != nil {
						log.Printf("[Cleanup] %s failed: %v", step.name, err)
						return
					}
					log.Printf("[Cleanup] %s succeeded", step.name)
				}()
			}
			wg.Wait()
		}

		runSequential := func(steps []cleanupStep) {
			for i := range steps {
				step := steps[i]
				if err := step.fn(); err != nil {
					log.Printf("[Cleanup] %s failed: %v", step.name, err)
					continue
				}
				log.Printf("[Cleanup] %s succeeded", step.name)
			}
		}

		runParallel(parallelSteps)
		runSequential(infraSteps)

		// Check if context timed out
		select {
		case <-ctx.Done():
			log.Printf("[Cleanup] Warning: cleanup timed out after 10 seconds")
		default:
			log.Printf("[Cleanup] All cleanup steps completed")
		}
	}
}

```

## /backend/cmd/server/wire_gen_test.go

```go path="/backend/cmd/server/wire_gen_test.go" 
package main

import (
	"testing"
	"time"

	"github.com/Wei-Shaw/sub2api/internal/config"
	"github.com/Wei-Shaw/sub2api/internal/handler"
	"github.com/Wei-Shaw/sub2api/internal/service"
	"github.com/stretchr/testify/require"
)

func TestProvideServiceBuildInfo(t *testing.T) {
	in := handler.BuildInfo{
		Version:   "v-test",
		BuildType: "release",
	}
	out := provideServiceBuildInfo(in)
	require.Equal(t, in.Version, out.Version)
	require.Equal(t, in.BuildType, out.BuildType)
}

func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
	cfg := &config.Config{}

	oauthSvc := service.NewOAuthService(nil, nil)
	openAIOAuthSvc := service.NewOpenAIOAuthService(nil, nil)
	geminiOAuthSvc := service.NewGeminiOAuthService(nil, nil, nil, nil, cfg)
	antigravityOAuthSvc := service.NewAntigravityOAuthService(nil)

	tokenRefreshSvc := service.NewTokenRefreshService(
		nil,
		oauthSvc,
		openAIOAuthSvc,
		geminiOAuthSvc,
		antigravityOAuthSvc,
		nil,
		nil,
		cfg,
		nil,
	)
	accountExpirySvc := service.NewAccountExpiryService(nil, time.Second)
	codexVersionSyncSvc := service.NewOpenAICodexVersionSyncService(nil, nil, nil, time.Second)
	proxyExpirySvc := service.NewProxyExpiryService(nil, time.Second)
	subscriptionExpirySvc := service.NewSubscriptionExpiryService(nil, time.Second)
	pricingSvc := service.NewPricingService(cfg, nil)
	emailQueueSvc := service.NewEmailQueueService(nil, 1)
	billingCacheSvc := service.NewBillingCacheService(nil, nil, nil, nil, nil, nil, cfg, nil)
	idempotencyCleanupSvc := service.NewIdempotencyCleanupService(nil, cfg)
	schedulerSnapshotSvc := service.NewSchedulerSnapshotService(nil, nil, nil, nil, cfg)
	opsSystemLogSinkSvc := service.NewOpsSystemLogSink(nil)

	cleanup := provideCleanup(
		nil, // entClient
		nil, // redis
		&service.OpsMetricsCollector{},
		&service.OpsAggregationService{},
		&service.OpsAlertEvaluatorService{},
		&service.OpsCleanupService{},
		&service.OpsScheduledReportService{},
		opsSystemLogSinkSvc,
		nil, // opsService
		nil, // opsIngressRejectAggregator
		nil, // apiKeyService
		nil, // authCacheInvalidationWorker
		schedulerSnapshotSvc,
		tokenRefreshSvc,
		accountExpirySvc,
		codexVersionSyncSvc,
		proxyExpirySvc,
		subscriptionExpirySvc,
		&service.UsageCleanupService{},
		idempotencyCleanupSvc,
		&service.BatchImageCleanupService{},
		nil, // batchImageWorker
		pricingSvc,
		emailQueueSvc,
		billingCacheSvc,
		&service.UsageRecordWorkerPool{},
		&service.SubscriptionService{},
		oauthSvc,
		openAIOAuthSvc,
		geminiOAuthSvc,
		antigravityOAuthSvc,
		nil, // grokOAuth
		nil, // openAIGateway
		nil, // scheduledTestRunner
		nil, // backupSvc
		nil, // paymentOrderExpiry
		nil, // channelMonitorRunner
		nil, // quotaFlusher
		nil, // upstreamBillingProbe
		nil, // ollamaCloudUsage
		nil, // auditLog
		nil, // promptAudit
	)

	require.NotPanics(t, func() {
		cleanup()
	})
}

```

## /backend/ent/account.go

```go path="/backend/ent/account.go" 
// Code generated by ent, DO NOT EDIT.

package ent

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"entgo.io/ent"
	"entgo.io/ent/dialect/sql"
	"github.com/Wei-Shaw/sub2api/ent/account"
	"github.com/Wei-Shaw/sub2api/ent/proxy"
)

// Account is the model entity for the Account schema.
type Account struct {
	config `json:"-"`
	// ID of the ent.
	ID int64 `json:"id,omitempty"`
	// CreatedAt holds the value of the "created_at" field.
	CreatedAt time.Time `json:"created_at,omitempty"`
	// UpdatedAt holds the value of the "updated_at" field.
	UpdatedAt time.Time `json:"updated_at,omitempty"`
	// DeletedAt holds the value of the "deleted_at" field.
	DeletedAt *time.Time `json:"deleted_at,omitempty"`
	// Name holds the value of the "name" field.
	Name string `json:"name,omitempty"`
	// Notes holds the value of the "notes" field.
	Notes *string `json:"notes,omitempty"`
	// Platform holds the value of the "platform" field.
	Platform string `json:"platform,omitempty"`
	// Type holds the value of the "type" field.
	Type string `json:"type,omitempty"`
	// Credentials holds the value of the "credentials" field.
	Credentials map[string]interface{} `json:"credentials,omitempty"`
	// Extra holds the value of the "extra" field.
	Extra map[string]interface{} `json:"extra,omitempty"`
	// ProxyID holds the value of the "proxy_id" field.
	ProxyID *int64 `json:"proxy_id,omitempty"`
	// Original proxy id replaced by expiry-fallback; for manual revert. NULL = not in fallback.
	ProxyFallbackOriginID *int64 `json:"proxy_fallback_origin_id,omitempty"`
	// Concurrency holds the value of the "concurrency" field.
	Concurrency int `json:"concurrency,omitempty"`
	// LoadFactor holds the value of the "load_factor" field.
	LoadFactor *int `json:"load_factor,omitempty"`
	// Priority holds the value of the "priority" field.
	Priority int `json:"priority,omitempty"`
	// RateMultiplier holds the value of the "rate_multiplier" field.
	RateMultiplier float64 `json:"rate_multiplier,omitempty"`
	// Status holds the value of the "status" field.
	Status string `json:"status,omitempty"`
	// ErrorMessage holds the value of the "error_message" field.
	ErrorMessage *string `json:"error_message,omitempty"`
	// LastUsedAt holds the value of the "last_used_at" field.
	LastUsedAt *time.Time `json:"last_used_at,omitempty"`
	// Account expiration time (NULL means no expiration).
	ExpiresAt *time.Time `json:"expires_at,omitempty"`
	// Auto pause scheduling when account expires.
	AutoPauseOnExpired bool `json:"auto_pause_on_expired,omitempty"`
	// Schedulable holds the value of the "schedulable" field.
	Schedulable bool `json:"schedulable,omitempty"`
	// RateLimitedAt holds the value of the "rate_limited_at" field.
	RateLimitedAt *time.Time `json:"rate_limited_at,omitempty"`
	// RateLimitResetAt holds the value of the "rate_limit_reset_at" field.
	RateLimitResetAt *time.Time `json:"rate_limit_reset_at,omitempty"`
	// OverloadUntil holds the value of the "overload_until" field.
	OverloadUntil *time.Time `json:"overload_until,omitempty"`
	// TempUnschedulableUntil holds the value of the "temp_unschedulable_until" field.
	TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"`
	// TempUnschedulableReason holds the value of the "temp_unschedulable_reason" field.
	TempUnschedulableReason *string `json:"temp_unschedulable_reason,omitempty"`
	// SessionWindowStart holds the value of the "session_window_start" field.
	SessionWindowStart *time.Time `json:"session_window_start,omitempty"`
	// SessionWindowEnd holds the value of the "session_window_end" field.
	SessionWindowEnd *time.Time `json:"session_window_end,omitempty"`
	// SessionWindowStatus holds the value of the "session_window_status" field.
	SessionWindowStatus *string `json:"session_window_status,omitempty"`
	// Parent account id for a linked spark shadow (NULL = normal).
	ParentAccountID *int64 `json:"parent_account_id,omitempty"`
	// 'global' (default) or 'spark' (shadow reads codex_bengalfox).
	QuotaDimension account.QuotaDimension `json:"quota_dimension,omitempty"`
	// Edges holds the relations/edges for other nodes in the graph.
	// The values are being populated by the AccountQuery when eager-loading is set.
	Edges        AccountEdges `json:"edges"`
	selectValues sql.SelectValues
}

// AccountEdges holds the relations/edges for other nodes in the graph.
type AccountEdges struct {
	// Groups holds the value of the groups edge.
	Groups []*Group `json:"groups,omitempty"`
	// Proxy holds the value of the proxy edge.
	Proxy *Proxy `json:"proxy,omitempty"`
	// Parent holds the value of the parent edge.
	Parent *Account `json:"parent,omitempty"`
	// Children holds the value of the children edge.
	Children []*Account `json:"children,omitempty"`
	// UsageLogs holds the value of the usage_logs edge.
	UsageLogs []*UsageLog `json:"usage_logs,omitempty"`
	// AccountGroups holds the value of the account_groups edge.
	AccountGroups []*AccountGroup `json:"account_groups,omitempty"`
	// loadedTypes holds the information for reporting if a
	// type was loaded (or requested) in eager-loading or not.
	loadedTypes [6]bool
}

// GroupsOrErr returns the Groups value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) GroupsOrErr() ([]*Group, error) {
	if e.loadedTypes[0] {
		return e.Groups, nil
	}
	return nil, &NotLoadedError{edge: "groups"}
}

// ProxyOrErr returns the Proxy value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AccountEdges) ProxyOrErr() (*Proxy, error) {
	if e.Proxy != nil {
		return e.Proxy, nil
	} else if e.loadedTypes[1] {
		return nil, &NotFoundError{label: proxy.Label}
	}
	return nil, &NotLoadedError{edge: "proxy"}
}

// ParentOrErr returns the Parent value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AccountEdges) ParentOrErr() (*Account, error) {
	if e.Parent != nil {
		return e.Parent, nil
	} else if e.loadedTypes[2] {
		return nil, &NotFoundError{label: account.Label}
	}
	return nil, &NotLoadedError{edge: "parent"}
}

// ChildrenOrErr returns the Children value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) ChildrenOrErr() ([]*Account, error) {
	if e.loadedTypes[3] {
		return e.Children, nil
	}
	return nil, &NotLoadedError{edge: "children"}
}

// UsageLogsOrErr returns the UsageLogs value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) UsageLogsOrErr() ([]*UsageLog, error) {
	if e.loadedTypes[4] {
		return e.UsageLogs, nil
	}
	return nil, &NotLoadedError{edge: "usage_logs"}
}

// AccountGroupsOrErr returns the AccountGroups value or an error if the edge
// was not loaded in eager-loading.
func (e AccountEdges) AccountGroupsOrErr() ([]*AccountGroup, error) {
	if e.loadedTypes[5] {
		return e.AccountGroups, nil
	}
	return nil, &NotLoadedError{edge: "account_groups"}
}

// scanValues returns the types for scanning values from sql.Rows.
func (*Account) scanValues(columns []string) ([]any, error) {
	values := make([]any, len(columns))
	for i := range columns {
		switch columns[i] {
		case account.FieldCredentials, account.FieldExtra:
			values[i] = new([]byte)
		case account.FieldAutoPauseOnExpired, account.FieldSchedulable:
			values[i] = new(sql.NullBool)
		case account.FieldRateMultiplier:
			values[i] = new(sql.NullFloat64)
		case account.FieldID, account.FieldProxyID, account.FieldProxyFallbackOriginID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority, account.FieldParentAccountID:
			values[i] = new(sql.NullInt64)
		case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus, account.FieldQuotaDimension:
			values[i] = new(sql.NullString)
		case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldTempUnschedulableUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
			values[i] = new(sql.NullTime)
		default:
			values[i] = new(sql.UnknownType)
		}
	}
	return values, nil
}

// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the Account fields.
func (_m *Account) assignValues(columns []string, values []any) error {
	if m, n := len(values), len(columns); m < n {
		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
	}
	for i := range columns {
		switch columns[i] {
		case account.FieldID:
			value, ok := values[i].(*sql.NullInt64)
			if !ok {
				return fmt.Errorf("unexpected type %T for field id", value)
			}
			_m.ID = int64(value.Int64)
		case account.FieldCreatedAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field created_at", values[i])
			} else if value.Valid {
				_m.CreatedAt = value.Time
			}
		case account.FieldUpdatedAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field updated_at", values[i])
			} else if value.Valid {
				_m.UpdatedAt = value.Time
			}
		case account.FieldDeletedAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field deleted_at", values[i])
			} else if value.Valid {
				_m.DeletedAt = new(time.Time)
				*_m.DeletedAt = value.Time
			}
		case account.FieldName:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field name", values[i])
			} else if value.Valid {
				_m.Name = value.String
			}
		case account.FieldNotes:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field notes", values[i])
			} else if value.Valid {
				_m.Notes = new(string)
				*_m.Notes = value.String
			}
		case account.FieldPlatform:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field platform", values[i])
			} else if value.Valid {
				_m.Platform = value.String
			}
		case account.FieldType:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field type", values[i])
			} else if value.Valid {
				_m.Type = value.String
			}
		case account.FieldCredentials:
			if value, ok := values[i].(*[]byte); !ok {
				return fmt.Errorf("unexpected type %T for field credentials", values[i])
			} else if value != nil && len(*value) > 0 {
				if err := json.Unmarshal(*value, &_m.Credentials); err != nil {
					return fmt.Errorf("unmarshal field credentials: %w", err)
				}
			}
		case account.FieldExtra:
			if value, ok := values[i].(*[]byte); !ok {
				return fmt.Errorf("unexpected type %T for field extra", values[i])
			} else if value != nil && len(*value) > 0 {
				if err := json.Unmarshal(*value, &_m.Extra); err != nil {
					return fmt.Errorf("unmarshal field extra: %w", err)
				}
			}
		case account.FieldProxyID:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field proxy_id", values[i])
			} else if value.Valid {
				_m.ProxyID = new(int64)
				*_m.ProxyID = value.Int64
			}
		case account.FieldProxyFallbackOriginID:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field proxy_fallback_origin_id", values[i])
			} else if value.Valid {
				_m.ProxyFallbackOriginID = new(int64)
				*_m.ProxyFallbackOriginID = value.Int64
			}
		case account.FieldConcurrency:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field concurrency", values[i])
			} else if value.Valid {
				_m.Concurrency = int(value.Int64)
			}
		case account.FieldLoadFactor:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field load_factor", values[i])
			} else if value.Valid {
				_m.LoadFactor = new(int)
				*_m.LoadFactor = int(value.Int64)
			}
		case account.FieldPriority:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field priority", values[i])
			} else if value.Valid {
				_m.Priority = int(value.Int64)
			}
		case account.FieldRateMultiplier:
			if value, ok := values[i].(*sql.NullFloat64); !ok {
				return fmt.Errorf("unexpected type %T for field rate_multiplier", values[i])
			} else if value.Valid {
				_m.RateMultiplier = value.Float64
			}
		case account.FieldStatus:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field status", values[i])
			} else if value.Valid {
				_m.Status = value.String
			}
		case account.FieldErrorMessage:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field error_message", values[i])
			} else if value.Valid {
				_m.ErrorMessage = new(string)
				*_m.ErrorMessage = value.String
			}
		case account.FieldLastUsedAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field last_used_at", values[i])
			} else if value.Valid {
				_m.LastUsedAt = new(time.Time)
				*_m.LastUsedAt = value.Time
			}
		case account.FieldExpiresAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field expires_at", values[i])
			} else if value.Valid {
				_m.ExpiresAt = new(time.Time)
				*_m.ExpiresAt = value.Time
			}
		case account.FieldAutoPauseOnExpired:
			if value, ok := values[i].(*sql.NullBool); !ok {
				return fmt.Errorf("unexpected type %T for field auto_pause_on_expired", values[i])
			} else if value.Valid {
				_m.AutoPauseOnExpired = value.Bool
			}
		case account.FieldSchedulable:
			if value, ok := values[i].(*sql.NullBool); !ok {
				return fmt.Errorf("unexpected type %T for field schedulable", values[i])
			} else if value.Valid {
				_m.Schedulable = value.Bool
			}
		case account.FieldRateLimitedAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field rate_limited_at", values[i])
			} else if value.Valid {
				_m.RateLimitedAt = new(time.Time)
				*_m.RateLimitedAt = value.Time
			}
		case account.FieldRateLimitResetAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field rate_limit_reset_at", values[i])
			} else if value.Valid {
				_m.RateLimitResetAt = new(time.Time)
				*_m.RateLimitResetAt = value.Time
			}
		case account.FieldOverloadUntil:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field overload_until", values[i])
			} else if value.Valid {
				_m.OverloadUntil = new(time.Time)
				*_m.OverloadUntil = value.Time
			}
		case account.FieldTempUnschedulableUntil:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field temp_unschedulable_until", values[i])
			} else if value.Valid {
				_m.TempUnschedulableUntil = new(time.Time)
				*_m.TempUnschedulableUntil = value.Time
			}
		case account.FieldTempUnschedulableReason:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field temp_unschedulable_reason", values[i])
			} else if value.Valid {
				_m.TempUnschedulableReason = new(string)
				*_m.TempUnschedulableReason = value.String
			}
		case account.FieldSessionWindowStart:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field session_window_start", values[i])
			} else if value.Valid {
				_m.SessionWindowStart = new(time.Time)
				*_m.SessionWindowStart = value.Time
			}
		case account.FieldSessionWindowEnd:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field session_window_end", values[i])
			} else if value.Valid {
				_m.SessionWindowEnd = new(time.Time)
				*_m.SessionWindowEnd = value.Time
			}
		case account.FieldSessionWindowStatus:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field session_window_status", values[i])
			} else if value.Valid {
				_m.SessionWindowStatus = new(string)
				*_m.SessionWindowStatus = value.String
			}
		case account.FieldParentAccountID:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field parent_account_id", values[i])
			} else if value.Valid {
				_m.ParentAccountID = new(int64)
				*_m.ParentAccountID = value.Int64
			}
		case account.FieldQuotaDimension:
			if value, ok := values[i].(*sql.NullString); !ok {
				return fmt.Errorf("unexpected type %T for field quota_dimension", values[i])
			} else if value.Valid {
				_m.QuotaDimension = account.QuotaDimension(value.String)
			}
		default:
			_m.selectValues.Set(columns[i], values[i])
		}
	}
	return nil
}

// Value returns the ent.Value that was dynamically selected and assigned to the Account.
// This includes values selected through modifiers, order, etc.
func (_m *Account) Value(name string) (ent.Value, error) {
	return _m.selectValues.Get(name)
}

// QueryGroups queries the "groups" edge of the Account entity.
func (_m *Account) QueryGroups() *GroupQuery {
	return NewAccountClient(_m.config).QueryGroups(_m)
}

// QueryProxy queries the "proxy" edge of the Account entity.
func (_m *Account) QueryProxy() *ProxyQuery {
	return NewAccountClient(_m.config).QueryProxy(_m)
}

// QueryParent queries the "parent" edge of the Account entity.
func (_m *Account) QueryParent() *AccountQuery {
	return NewAccountClient(_m.config).QueryParent(_m)
}

// QueryChildren queries the "children" edge of the Account entity.
func (_m *Account) QueryChildren() *AccountQuery {
	return NewAccountClient(_m.config).QueryChildren(_m)
}

// QueryUsageLogs queries the "usage_logs" edge of the Account entity.
func (_m *Account) QueryUsageLogs() *UsageLogQuery {
	return NewAccountClient(_m.config).QueryUsageLogs(_m)
}

// QueryAccountGroups queries the "account_groups" edge of the Account entity.
func (_m *Account) QueryAccountGroups() *AccountGroupQuery {
	return NewAccountClient(_m.config).QueryAccountGroups(_m)
}

// Update returns a builder for updating this Account.
// Note that you need to call Account.Unwrap() before calling this method if this Account
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *Account) Update() *AccountUpdateOne {
	return NewAccountClient(_m.config).UpdateOne(_m)
}

// Unwrap unwraps the Account entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *Account) Unwrap() *Account {
	_tx, ok := _m.config.driver.(*txDriver)
	if !ok {
		panic("ent: Account is not a transactional entity")
	}
	_m.config.driver = _tx.drv
	return _m
}

// String implements the fmt.Stringer.
func (_m *Account) String() string {
	var builder strings.Builder
	builder.WriteString("Account(")
	builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
	builder.WriteString("created_at=")
	builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
	builder.WriteString(", ")
	builder.WriteString("updated_at=")
	builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
	builder.WriteString(", ")
	if v := _m.DeletedAt; v != nil {
		builder.WriteString("deleted_at=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	builder.WriteString("name=")
	builder.WriteString(_m.Name)
	builder.WriteString(", ")
	if v := _m.Notes; v != nil {
		builder.WriteString("notes=")
		builder.WriteString(*v)
	}
	builder.WriteString(", ")
	builder.WriteString("platform=")
	builder.WriteString(_m.Platform)
	builder.WriteString(", ")
	builder.WriteString("type=")
	builder.WriteString(_m.Type)
	builder.WriteString(", ")
	builder.WriteString("credentials=")
	builder.WriteString(fmt.Sprintf("%v", _m.Credentials))
	builder.WriteString(", ")
	builder.WriteString("extra=")
	builder.WriteString(fmt.Sprintf("%v", _m.Extra))
	builder.WriteString(", ")
	if v := _m.ProxyID; v != nil {
		builder.WriteString("proxy_id=")
		builder.WriteString(fmt.Sprintf("%v", *v))
	}
	builder.WriteString(", ")
	if v := _m.ProxyFallbackOriginID; v != nil {
		builder.WriteString("proxy_fallback_origin_id=")
		builder.WriteString(fmt.Sprintf("%v", *v))
	}
	builder.WriteString(", ")
	builder.WriteString("concurrency=")
	builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
	builder.WriteString(", ")
	if v := _m.LoadFactor; v != nil {
		builder.WriteString("load_factor=")
		builder.WriteString(fmt.Sprintf("%v", *v))
	}
	builder.WriteString(", ")
	builder.WriteString("priority=")
	builder.WriteString(fmt.Sprintf("%v", _m.Priority))
	builder.WriteString(", ")
	builder.WriteString("rate_multiplier=")
	builder.WriteString(fmt.Sprintf("%v", _m.RateMultiplier))
	builder.WriteString(", ")
	builder.WriteString("status=")
	builder.WriteString(_m.Status)
	builder.WriteString(", ")
	if v := _m.ErrorMessage; v != nil {
		builder.WriteString("error_message=")
		builder.WriteString(*v)
	}
	builder.WriteString(", ")
	if v := _m.LastUsedAt; v != nil {
		builder.WriteString("last_used_at=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.ExpiresAt; v != nil {
		builder.WriteString("expires_at=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	builder.WriteString("auto_pause_on_expired=")
	builder.WriteString(fmt.Sprintf("%v", _m.AutoPauseOnExpired))
	builder.WriteString(", ")
	builder.WriteString("schedulable=")
	builder.WriteString(fmt.Sprintf("%v", _m.Schedulable))
	builder.WriteString(", ")
	if v := _m.RateLimitedAt; v != nil {
		builder.WriteString("rate_limited_at=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.RateLimitResetAt; v != nil {
		builder.WriteString("rate_limit_reset_at=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.OverloadUntil; v != nil {
		builder.WriteString("overload_until=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.TempUnschedulableUntil; v != nil {
		builder.WriteString("temp_unschedulable_until=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.TempUnschedulableReason; v != nil {
		builder.WriteString("temp_unschedulable_reason=")
		builder.WriteString(*v)
	}
	builder.WriteString(", ")
	if v := _m.SessionWindowStart; v != nil {
		builder.WriteString("session_window_start=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.SessionWindowEnd; v != nil {
		builder.WriteString("session_window_end=")
		builder.WriteString(v.Format(time.ANSIC))
	}
	builder.WriteString(", ")
	if v := _m.SessionWindowStatus; v != nil {
		builder.WriteString("session_window_status=")
		builder.WriteString(*v)
	}
	builder.WriteString(", ")
	if v := _m.ParentAccountID; v != nil {
		builder.WriteString("parent_account_id=")
		builder.WriteString(fmt.Sprintf("%v", *v))
	}
	builder.WriteString(", ")
	builder.WriteString("quota_dimension=")
	builder.WriteString(fmt.Sprintf("%v", _m.QuotaDimension))
	builder.WriteByte(')')
	return builder.String()
}

// Accounts is a parsable slice of Account.
type Accounts []*Account

```

## /backend/ent/account_delete.go

```go path="/backend/ent/account_delete.go" 
// Code generated by ent, DO NOT EDIT.

package ent

import (
	"context"

	"entgo.io/ent/dialect/sql"
	"entgo.io/ent/dialect/sql/sqlgraph"
	"entgo.io/ent/schema/field"
	"github.com/Wei-Shaw/sub2api/ent/account"
	"github.com/Wei-Shaw/sub2api/ent/predicate"
)

// AccountDelete is the builder for deleting a Account entity.
type AccountDelete struct {
	config
	hooks    []Hook
	mutation *AccountMutation
}

// Where appends a list predicates to the AccountDelete builder.
func (_d *AccountDelete) Where(ps ...predicate.Account) *AccountDelete {
	_d.mutation.Where(ps...)
	return _d
}

// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AccountDelete) Exec(ctx context.Context) (int, error) {
	return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}

// ExecX is like Exec, but panics if an error occurs.
func (_d *AccountDelete) ExecX(ctx context.Context) int {
	n, err := _d.Exec(ctx)
	if err != nil {
		panic(err)
	}
	return n
}

func (_d *AccountDelete) sqlExec(ctx context.Context) (int, error) {
	_spec := sqlgraph.NewDeleteSpec(account.Table, sqlgraph.NewFieldSpec(account.FieldID, field.TypeInt64))
	if ps := _d.mutation.predicates; len(ps) > 0 {
		_spec.Predicate = func(selector *sql.Selector) {
			for i := range ps {
				ps[i](selector)
			}
		}
	}
	affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
	if err != nil && sqlgraph.IsConstraintError(err) {
		err = &ConstraintError{msg: err.Error(), wrap: err}
	}
	_d.mutation.done = true
	return affected, err
}

// AccountDeleteOne is the builder for deleting a single Account entity.
type AccountDeleteOne struct {
	_d *AccountDelete
}

// Where appends a list predicates to the AccountDelete builder.
func (_d *AccountDeleteOne) Where(ps ...predicate.Account) *AccountDeleteOne {
	_d._d.mutation.Where(ps...)
	return _d
}

// Exec executes the deletion query.
func (_d *AccountDeleteOne) Exec(ctx context.Context) error {
	n, err := _d._d.Exec(ctx)
	switch {
	case err != nil:
		return err
	case n == 0:
		return &NotFoundError{account.Label}
	default:
		return nil
	}
}

// ExecX is like Exec, but panics if an error occurs.
func (_d *AccountDeleteOne) ExecX(ctx context.Context) {
	if err := _d.Exec(ctx); err != nil {
		panic(err)
	}
}

```

## /backend/ent/accountgroup.go

```go path="/backend/ent/accountgroup.go" 
// Code generated by ent, DO NOT EDIT.

package ent

import (
	"fmt"
	"strings"
	"time"

	"entgo.io/ent"
	"entgo.io/ent/dialect/sql"
	"github.com/Wei-Shaw/sub2api/ent/account"
	"github.com/Wei-Shaw/sub2api/ent/accountgroup"
	"github.com/Wei-Shaw/sub2api/ent/group"
)

// AccountGroup is the model entity for the AccountGroup schema.
type AccountGroup struct {
	config `json:"-"`
	// AccountID holds the value of the "account_id" field.
	AccountID int64 `json:"account_id,omitempty"`
	// GroupID holds the value of the "group_id" field.
	GroupID int64 `json:"group_id,omitempty"`
	// Priority holds the value of the "priority" field.
	Priority int `json:"priority,omitempty"`
	// CreatedAt holds the value of the "created_at" field.
	CreatedAt time.Time `json:"created_at,omitempty"`
	// Edges holds the relations/edges for other nodes in the graph.
	// The values are being populated by the AccountGroupQuery when eager-loading is set.
	Edges        AccountGroupEdges `json:"edges"`
	selectValues sql.SelectValues
}

// AccountGroupEdges holds the relations/edges for other nodes in the graph.
type AccountGroupEdges struct {
	// Account holds the value of the account edge.
	Account *Account `json:"account,omitempty"`
	// Group holds the value of the group edge.
	Group *Group `json:"group,omitempty"`
	// loadedTypes holds the information for reporting if a
	// type was loaded (or requested) in eager-loading or not.
	loadedTypes [2]bool
}

// AccountOrErr returns the Account value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AccountGroupEdges) AccountOrErr() (*Account, error) {
	if e.Account != nil {
		return e.Account, nil
	} else if e.loadedTypes[0] {
		return nil, &NotFoundError{label: account.Label}
	}
	return nil, &NotLoadedError{edge: "account"}
}

// GroupOrErr returns the Group value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e AccountGroupEdges) GroupOrErr() (*Group, error) {
	if e.Group != nil {
		return e.Group, nil
	} else if e.loadedTypes[1] {
		return nil, &NotFoundError{label: group.Label}
	}
	return nil, &NotLoadedError{edge: "group"}
}

// scanValues returns the types for scanning values from sql.Rows.
func (*AccountGroup) scanValues(columns []string) ([]any, error) {
	values := make([]any, len(columns))
	for i := range columns {
		switch columns[i] {
		case accountgroup.FieldAccountID, accountgroup.FieldGroupID, accountgroup.FieldPriority:
			values[i] = new(sql.NullInt64)
		case accountgroup.FieldCreatedAt:
			values[i] = new(sql.NullTime)
		default:
			values[i] = new(sql.UnknownType)
		}
	}
	return values, nil
}

// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the AccountGroup fields.
func (_m *AccountGroup) assignValues(columns []string, values []any) error {
	if m, n := len(values), len(columns); m < n {
		return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
	}
	for i := range columns {
		switch columns[i] {
		case accountgroup.FieldAccountID:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field account_id", values[i])
			} else if value.Valid {
				_m.AccountID = value.Int64
			}
		case accountgroup.FieldGroupID:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field group_id", values[i])
			} else if value.Valid {
				_m.GroupID = value.Int64
			}
		case accountgroup.FieldPriority:
			if value, ok := values[i].(*sql.NullInt64); !ok {
				return fmt.Errorf("unexpected type %T for field priority", values[i])
			} else if value.Valid {
				_m.Priority = int(value.Int64)
			}
		case accountgroup.FieldCreatedAt:
			if value, ok := values[i].(*sql.NullTime); !ok {
				return fmt.Errorf("unexpected type %T for field created_at", values[i])
			} else if value.Valid {
				_m.CreatedAt = value.Time
			}
		default:
			_m.selectValues.Set(columns[i], values[i])
		}
	}
	return nil
}

// Value returns the ent.Value that was dynamically selected and assigned to the AccountGroup.
// This includes values selected through modifiers, order, etc.
func (_m *AccountGroup) Value(name string) (ent.Value, error) {
	return _m.selectValues.Get(name)
}

// QueryAccount queries the "account" edge of the AccountGroup entity.
func (_m *AccountGroup) QueryAccount() *AccountQuery {
	return NewAccountGroupClient(_m.config).QueryAccount(_m)
}

// QueryGroup queries the "group" edge of the AccountGroup entity.
func (_m *AccountGroup) QueryGroup() *GroupQuery {
	return NewAccountGroupClient(_m.config).QueryGroup(_m)
}

// Update returns a builder for updating this AccountGroup.
// Note that you need to call AccountGroup.Unwrap() before calling this method if this AccountGroup
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *AccountGroup) Update() *AccountGroupUpdateOne {
	return NewAccountGroupClient(_m.config).UpdateOne(_m)
}

// Unwrap unwraps the AccountGroup entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *AccountGroup) Unwrap() *AccountGroup {
	_tx, ok := _m.config.driver.(*txDriver)
	if !ok {
		panic("ent: AccountGroup is not a transactional entity")
	}
	_m.config.driver = _tx.drv
	return _m
}

// String implements the fmt.Stringer.
func (_m *AccountGroup) String() string {
	var builder strings.Builder
	builder.WriteString("AccountGroup(")
	builder.WriteString("account_id=")
	builder.WriteString(fmt.Sprintf("%v", _m.AccountID))
	builder.WriteString(", ")
	builder.WriteString("group_id=")
	builder.WriteString(fmt.Sprintf("%v", _m.GroupID))
	builder.WriteString(", ")
	builder.WriteString("priority=")
	builder.WriteString(fmt.Sprintf("%v", _m.Priority))
	builder.WriteString(", ")
	builder.WriteString("created_at=")
	builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
	builder.WriteByte(')')
	return builder.String()
}

// AccountGroups is a parsable slice of AccountGroup.
type AccountGroups []*AccountGroup

```

## /backend/ent/accountgroup_delete.go

```go path="/backend/ent/accountgroup_delete.go" 
// Code generated by ent, DO NOT EDIT.

package ent

import (
	"context"

	"entgo.io/ent/dialect/sql"
	"entgo.io/ent/dialect/sql/sqlgraph"
	"github.com/Wei-Shaw/sub2api/ent/accountgroup"
	"github.com/Wei-Shaw/sub2api/ent/predicate"
)

// AccountGroupDelete is the builder for deleting a AccountGroup entity.
type AccountGroupDelete struct {
	config
	hooks    []Hook
	mutation *AccountGroupMutation
}

// Where appends a list predicates to the AccountGroupDelete builder.
func (_d *AccountGroupDelete) Where(ps ...predicate.AccountGroup) *AccountGroupDelete {
	_d.mutation.Where(ps...)
	return _d
}

// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *AccountGroupDelete) Exec(ctx context.Context) (int, error) {
	return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}

// ExecX is like Exec, but panics if an error occurs.
func (_d *AccountGroupDelete) ExecX(ctx context.Context) int {
	n, err := _d.Exec(ctx)
	if err != nil {
		panic(err)
	}
	return n
}

func (_d *AccountGroupDelete) sqlExec(ctx context.Context) (int, error) {
	_spec := sqlgraph.NewDeleteSpec(accountgroup.Table, nil)
	if ps := _d.mutation.predicates; len(ps) > 0 {
		_spec.Predicate = func(selector *sql.Selector) {
			for i := range ps {
				ps[i](selector)
			}
		}
	}
	affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
	if err != nil && sqlgraph.IsConstraintError(err) {
		err = &ConstraintError{msg: err.Error(), wrap: err}
	}
	_d.mutation.done = true
	return affected, err
}

// AccountGroupDeleteOne is the builder for deleting a single AccountGroup entity.
type AccountGroupDeleteOne struct {
	_d *AccountGroupDelete
}

// Where appends a list predicates to the AccountGroupDelete builder.
func (_d *AccountGroupDeleteOne) Where(ps ...predicate.AccountGroup) *AccountGroupDeleteOne {
	_d._d.mutation.Where(ps...)
	return _d
}

// Exec executes the deletion query.
func (_d *AccountGroupDeleteOne) Exec(ctx context.Context) error {
	n, err := _d._d.Exec(ctx)
	switch {
	case err != nil:
		return err
	case n == 0:
		return &NotFoundError{accountgroup.Label}
	default:
		return nil
	}
}

// ExecX is like Exec, but panics if an error occurs.
func (_d *AccountGroupDeleteOne) ExecX(ctx context.Context) {
	if err := _d.Exec(ctx); err != nil {
		panic(err)
	}
}

```

## /backend/ent/driver_access.go

```go path="/backend/ent/driver_access.go" 
package ent

import "entgo.io/ent/dialect"

// Driver 暴露底层 driver，供需要 raw SQL 的集成层使用。
func (c *Client) Driver() dialect.Driver {
	return c.driver
}

```

## /backend/ent/generate.go

```go path="/backend/ent/generate.go" 
// Package ent provides the generated ORM code for database entities.
package ent

// 启用 sql/execquery 以生成 ExecContext/QueryContext 的透传接口，便于事务内执行原生 SQL。
// 启用 sql/lock 以支持 FOR UPDATE 行锁。
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert,intercept,sql/execquery,sql/lock --idtype int64 ./schema

```

## /backend/ent/runtime.go

```go path="/backend/ent/runtime.go" 
// Code generated by ent, DO NOT EDIT.

package ent

// The schema-stitching logic is generated in github.com/Wei-Shaw/sub2api/ent/runtime/runtime.go

```

## /backend/internal/config/wire.go

```go path="/backend/internal/config/wire.go" 
package config

import "github.com/google/wire"

// ProviderSet 提供配置层的依赖
var ProviderSet = wire.NewSet(
	ProvideConfig,
)

// ProvideConfig 提供应用配置
func ProvideConfig() (*Config, error) {
	return LoadForBootstrap()
}

```


The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.