From e50cabac4b0dc406ae4bf16d65c524be471d1671 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Fri, 8 May 2026 11:46:46 +0800 Subject: [PATCH 01/10] chore: upgrade CLIProxyAPI dependency to v7 across the project - Updated all references from v6 to v7 for `github.com/router-for-me/CLIProxyAPI`. - Ensured consistency in imports within core libraries, tests, and integration tests. - Added missing tests for new features in Redis Protocol integration. --- cmd/fetch_antigravity_models/main.go | 10 +- cmd/server/main.go | 38 +- config.example.yaml | 8 + examples/custom-provider/main.go | 16 +- examples/http-request/main.go | 4 +- examples/translator/main.go | 4 +- go.mod | 8 +- go.sum | 6 + internal/access/config_access/provider.go | 4 +- internal/access/reconcile.go | 6 +- .../api/handlers/management/api_key_usage.go | 2 +- .../handlers/management/api_key_usage_test.go | 4 +- internal/api/handlers/management/api_tools.go | 8 +- .../api/handlers/management/api_tools_test.go | 6 +- .../api/handlers/management/auth_files.go | 22 +- .../management/auth_files_batch_test.go | 4 +- .../management/auth_files_delete_test.go | 4 +- .../management/auth_files_download_test.go | 2 +- .../auth_files_download_windows_test.go | 2 +- .../auth_files_patch_fields_test.go | 4 +- .../auth_files_recent_requests_test.go | 4 +- .../handlers/management/config_auth_index.go | 4 +- .../api/handlers/management/config_basic.go | 6 +- .../api/handlers/management/config_lists.go | 2 +- .../config_lists_delete_keys_test.go | 2 +- internal/api/handlers/management/handler.go | 8 +- .../api/handlers/management/handler_test.go | 2 +- internal/api/handlers/management/logs.go | 2 +- .../handlers/management/model_definitions.go | 2 +- .../handlers/management/test_store_test.go | 2 +- internal/api/handlers/management/usage.go | 2 +- .../api/handlers/management/usage_test.go | 2 +- .../api/handlers/management/vertex_import.go | 4 +- internal/api/middleware/request_logging.go | 4 +- internal/api/middleware/response_writer.go | 4 +- .../api/middleware/response_writer_test.go | 4 +- internal/api/modules/amp/amp.go | 6 +- internal/api/modules/amp/amp_test.go | 8 +- internal/api/modules/amp/fallback_handlers.go | 4 +- .../api/modules/amp/fallback_handlers_test.go | 4 +- internal/api/modules/amp/model_mapping.go | 6 +- .../api/modules/amp/model_mapping_test.go | 4 +- internal/api/modules/amp/proxy.go | 2 +- internal/api/modules/amp/proxy_test.go | 2 +- internal/api/modules/amp/routes.go | 14 +- internal/api/modules/amp/routes_test.go | 2 +- internal/api/modules/amp/secret.go | 2 +- internal/api/modules/amp/secret_test.go | 2 +- internal/api/modules/modules.go | 4 +- internal/api/protocol_multiplexer.go | 6 + internal/api/redis_queue_protocol.go | 8 +- .../redis_queue_protocol_integration_test.go | 39 +- internal/api/server.go | 261 +++++++++- internal/api/server_test.go | 38 +- internal/auth/antigravity/auth.go | 6 +- internal/auth/claude/anthropic_auth.go | 2 +- .../auth/claude/anthropic_auth_proxy_test.go | 2 +- internal/auth/claude/token.go | 2 +- internal/auth/claude/utls_transport.go | 4 +- internal/auth/codex/openai_auth.go | 4 +- internal/auth/codex/openai_auth_test.go | 2 +- internal/auth/codex/token.go | 2 +- internal/auth/gemini/gemini_auth.go | 12 +- internal/auth/gemini/gemini_token.go | 2 +- internal/auth/kimi/kimi.go | 4 +- internal/auth/kimi/kimi_proxy_test.go | 2 +- internal/auth/kimi/token.go | 2 +- internal/auth/vertex/vertex_credentials.go | 2 +- internal/cmd/anthropic_login.go | 6 +- internal/cmd/antigravity_login.go | 4 +- internal/cmd/auth_manager.go | 2 +- internal/cmd/kimi_login.go | 4 +- internal/cmd/login.go | 12 +- internal/cmd/openai_device_login.go | 6 +- internal/cmd/openai_login.go | 6 +- internal/cmd/run.go | 6 +- internal/cmd/vertex_import.go | 10 +- internal/config/config.go | 5 +- internal/config/home.go | 9 + internal/config/parse.go | 89 ++++ internal/home/client.go | 374 +++++++++++++++ internal/home/global.go | 25 + internal/home/requests.go | 13 + internal/interfaces/types.go | 2 +- internal/logging/gin_logger.go | 2 +- internal/logging/global_logger.go | 4 +- internal/logging/request_logger.go | 6 +- internal/managementasset/updater.go | 6 +- internal/redisqueue/plugin.go | 4 +- internal/redisqueue/plugin_test.go | 7 +- internal/registry/model_registry.go | 2 +- .../runtime/executor/aistudio_executor.go | 21 +- .../runtime/executor/antigravity_executor.go | 39 +- .../antigravity_executor_buildrequest_test.go | 2 +- .../antigravity_executor_credits_test.go | 8 +- .../antigravity_executor_signature_test.go | 8 +- internal/runtime/executor/claude_executor.go | 23 +- .../runtime/executor/claude_executor_test.go | 12 +- internal/runtime/executor/claude_signing.go | 4 +- internal/runtime/executor/codex_executor.go | 21 +- .../executor/codex_executor_cache_test.go | 6 +- .../executor/codex_executor_compact_test.go | 8 +- .../executor/codex_executor_imagegen_test.go | 2 +- .../codex_executor_instructions_test.go | 8 +- .../codex_executor_stream_output_test.go | 10 +- .../executor/codex_websockets_executor.go | 18 +- .../codex_websockets_executor_store_test.go | 2 +- .../codex_websockets_executor_test.go | 10 +- .../runtime/executor/gemini_cli_executor.go | 102 ++-- internal/runtime/executor/gemini_executor.go | 19 +- .../executor/gemini_vertex_executor.go | 21 +- .../executor/helps/claude_device_profile.go | 4 +- .../runtime/executor/helps/home_refresh.go | 91 ++++ .../runtime/executor/helps/logging_helpers.go | 6 +- .../runtime/executor/helps/payload_helpers.go | 6 +- ...d_helpers_disable_image_generation_test.go | 2 +- .../runtime/executor/helps/proxy_helpers.go | 6 +- .../executor/helps/proxy_helpers_test.go | 6 +- .../executor/helps/thinking_providers.go | 14 +- .../runtime/executor/helps/usage_helpers.go | 6 +- .../executor/helps/usage_helpers_test.go | 2 +- .../runtime/executor/helps/utls_client.go | 6 +- internal/runtime/executor/kimi_executor.go | 19 +- .../executor/openai_compat_executor.go | 18 +- .../openai_compat_executor_compact_test.go | 8 +- internal/store/gitstore.go | 2 +- internal/store/objectstore.go | 4 +- internal/store/postgresstore.go | 4 +- internal/thinking/apply.go | 2 +- internal/thinking/apply_user_defined_test.go | 6 +- internal/thinking/convert.go | 2 +- .../thinking/provider/antigravity/apply.go | 4 +- internal/thinking/provider/claude/apply.go | 4 +- internal/thinking/provider/codex/apply.go | 4 +- internal/thinking/provider/gemini/apply.go | 4 +- internal/thinking/provider/geminicli/apply.go | 4 +- internal/thinking/provider/kimi/apply.go | 4 +- internal/thinking/provider/kimi/apply_test.go | 4 +- internal/thinking/provider/openai/apply.go | 4 +- internal/thinking/types.go | 2 +- internal/thinking/validate.go | 2 +- .../claude/antigravity_claude_request.go | 8 +- .../claude/antigravity_claude_request_test.go | 2 +- .../claude/antigravity_claude_response.go | 6 +- .../antigravity_claude_response_test.go | 2 +- .../translator/antigravity/claude/init.go | 6 +- .../claude/signature_validation.go | 2 +- .../gemini/antigravity_gemini_request.go | 4 +- .../gemini/antigravity_gemini_response.go | 2 +- .../translator/antigravity/gemini/init.go | 6 +- .../antigravity_openai_request.go | 6 +- .../antigravity_openai_response.go | 4 +- .../openai/chat-completions/init.go | 6 +- .../antigravity_openai-responses_request.go | 4 +- .../antigravity_openai-responses_response.go | 2 +- .../antigravity/openai/responses/init.go | 6 +- .../gemini-cli/claude_gemini-cli_request.go | 2 +- .../gemini-cli/claude_gemini-cli_response.go | 4 +- internal/translator/claude/gemini-cli/init.go | 6 +- .../claude/gemini/claude_gemini_request.go | 6 +- .../claude/gemini/claude_gemini_response.go | 2 +- internal/translator/claude/gemini/init.go | 6 +- .../chat-completions/claude_openai_request.go | 4 +- .../claude/openai/chat-completions/init.go | 6 +- .../claude_openai-responses_request.go | 4 +- .../claude_openai-responses_response.go | 2 +- .../claude/openai/responses/init.go | 6 +- .../codex/claude/codex_claude_request.go | 2 +- .../codex/claude/codex_claude_response.go | 4 +- internal/translator/codex/claude/init.go | 6 +- .../gemini-cli/codex_gemini-cli_request.go | 2 +- .../gemini-cli/codex_gemini-cli_response.go | 4 +- internal/translator/codex/gemini-cli/init.go | 6 +- .../codex/gemini/codex_gemini_request.go | 4 +- .../codex/gemini/codex_gemini_response.go | 2 +- internal/translator/codex/gemini/init.go | 6 +- .../codex/openai/chat-completions/init.go | 6 +- .../translator/codex/openai/responses/init.go | 6 +- .../claude/gemini-cli_claude_request.go | 4 +- .../claude/gemini-cli_claude_response.go | 4 +- internal/translator/gemini-cli/claude/init.go | 6 +- .../gemini/gemini-cli_gemini_request.go | 4 +- .../gemini/gemini-cli_gemini_response.go | 2 +- internal/translator/gemini-cli/gemini/init.go | 6 +- .../gemini-cli_openai_request.go | 6 +- .../gemini-cli_openai_response.go | 4 +- .../openai/chat-completions/init.go | 6 +- .../gemini-cli_openai-responses_request.go | 4 +- .../gemini-cli_openai-responses_response.go | 2 +- .../gemini-cli/openai/responses/init.go | 6 +- .../gemini/claude/gemini_claude_request.go | 6 +- .../gemini/claude/gemini_claude_response.go | 4 +- internal/translator/gemini/claude/init.go | 6 +- .../gemini-cli/gemini_gemini-cli_request.go | 4 +- .../gemini-cli/gemini_gemini-cli_response.go | 2 +- internal/translator/gemini/gemini-cli/init.go | 6 +- .../gemini/gemini/gemini_gemini_request.go | 4 +- .../gemini/gemini/gemini_gemini_response.go | 2 +- internal/translator/gemini/gemini/init.go | 6 +- .../chat-completions/gemini_openai_request.go | 6 +- .../gemini_openai_response.go | 2 +- .../gemini/openai/chat-completions/init.go | 6 +- .../gemini_openai-responses_request.go | 4 +- .../gemini_openai-responses_response.go | 4 +- .../gemini/openai/responses/init.go | 6 +- internal/translator/init.go | 54 +-- internal/translator/openai/claude/init.go | 6 +- .../openai/claude/openai_claude_request.go | 2 +- .../openai/claude/openai_claude_response.go | 4 +- internal/translator/openai/gemini-cli/init.go | 6 +- .../gemini-cli/openai_gemini_request.go | 2 +- .../gemini-cli/openai_gemini_response.go | 4 +- internal/translator/openai/gemini/init.go | 6 +- .../openai/gemini/openai_gemini_request.go | 2 +- .../openai/gemini/openai_gemini_response.go | 2 +- .../openai/openai/chat-completions/init.go | 6 +- .../openai/openai/responses/init.go | 6 +- .../openai_openai-responses_response.go | 2 +- internal/translator/translator/translator.go | 4 +- internal/util/provider.go | 4 +- internal/util/proxy.go | 4 +- internal/util/util.go | 2 +- internal/watcher/clients.go | 10 +- internal/watcher/config_reload.go | 6 +- internal/watcher/diff/auth_diff.go | 2 +- internal/watcher/diff/config_diff.go | 2 +- internal/watcher/diff/config_diff_test.go | 4 +- internal/watcher/diff/model_hash.go | 2 +- internal/watcher/diff/model_hash_test.go | 2 +- internal/watcher/diff/models_summary.go | 2 +- internal/watcher/diff/oauth_excluded.go | 2 +- internal/watcher/diff/oauth_excluded_test.go | 2 +- internal/watcher/diff/oauth_model_alias.go | 2 +- internal/watcher/diff/openai_compat.go | 2 +- internal/watcher/diff/openai_compat_test.go | 2 +- internal/watcher/dispatcher.go | 6 +- internal/watcher/synthesizer/config.go | 4 +- internal/watcher/synthesizer/config_test.go | 4 +- internal/watcher/synthesizer/context.go | 2 +- internal/watcher/synthesizer/file.go | 6 +- internal/watcher/synthesizer/file_test.go | 4 +- internal/watcher/synthesizer/helpers.go | 6 +- internal/watcher/synthesizer/helpers_test.go | 6 +- internal/watcher/synthesizer/interface.go | 2 +- internal/watcher/watcher.go | 6 +- internal/watcher/watcher_test.go | 10 +- sdk/api/handlers/claude/code_handlers.go | 8 +- .../handlers/gemini/gemini-cli_handlers.go | 8 +- sdk/api/handlers/gemini/gemini_handlers.go | 8 +- sdk/api/handlers/handlers.go | 38 +- .../handlers/handlers_error_response_test.go | 6 +- sdk/api/handlers/handlers_metadata_test.go | 2 +- .../handlers/handlers_request_details_test.go | 6 +- .../handlers_stream_bootstrap_test.go | 10 +- sdk/api/handlers/openai/openai_handlers.go | 10 +- .../handlers/openai/openai_images_handlers.go | 6 +- .../openai/openai_images_handlers_test.go | 6 +- .../openai/openai_responses_compact_test.go | 10 +- .../openai/openai_responses_handlers.go | 8 +- ...ai_responses_handlers_stream_error_test.go | 6 +- .../openai_responses_handlers_stream_test.go | 6 +- .../openai/openai_responses_websocket.go | 14 +- .../openai/openai_responses_websocket_test.go | 12 +- sdk/api/handlers/stream_forwarder.go | 2 +- sdk/api/management.go | 6 +- sdk/api/options.go | 8 +- sdk/auth/antigravity.go | 12 +- sdk/auth/claude.go | 12 +- sdk/auth/codex.go | 12 +- sdk/auth/codex_device.go | 10 +- sdk/auth/errors.go | 2 +- sdk/auth/filestore.go | 2 +- sdk/auth/gemini.go | 6 +- sdk/auth/interfaces.go | 4 +- sdk/auth/kimi.go | 8 +- sdk/auth/manager.go | 4 +- sdk/auth/refresh_registry.go | 2 +- sdk/auth/store_registry.go | 2 +- sdk/cliproxy/auth/antigravity_credits_test.go | 6 +- sdk/cliproxy/auth/api_key_model_alias_test.go | 2 +- sdk/cliproxy/auth/conductor.go | 197 +++++++- .../auth/conductor_credits_candidates_test.go | 2 +- .../auth/conductor_executor_replace_test.go | 2 +- .../conductor_oauth_alias_suspension_test.go | 8 +- sdk/cliproxy/auth/conductor_overrides_test.go | 6 +- .../auth/conductor_scheduler_refresh_test.go | 4 +- sdk/cliproxy/auth/oauth_model_alias.go | 4 +- sdk/cliproxy/auth/oauth_model_alias_test.go | 2 +- sdk/cliproxy/auth/openai_compat_pool_test.go | 6 +- sdk/cliproxy/auth/scheduler.go | 4 +- sdk/cliproxy/auth/scheduler_benchmark_test.go | 4 +- sdk/cliproxy/auth/scheduler_test.go | 4 +- sdk/cliproxy/auth/selector.go | 6 +- sdk/cliproxy/auth/selector_test.go | 2 +- sdk/cliproxy/auth/types.go | 2 +- sdk/cliproxy/builder.go | 12 +- sdk/cliproxy/executor/types.go | 2 +- sdk/cliproxy/model_registry.go | 2 +- sdk/cliproxy/pipeline/context.go | 6 +- sdk/cliproxy/pprof_server.go | 2 +- sdk/cliproxy/providers.go | 4 +- sdk/cliproxy/rtprovider.go | 4 +- sdk/cliproxy/rtprovider_test.go | 2 +- sdk/cliproxy/service.go | 449 +++++++++++++----- .../service_codex_executor_binding_test.go | 4 +- sdk/cliproxy/service_excluded_models_test.go | 4 +- .../service_oauth_model_alias_test.go | 2 +- sdk/cliproxy/service_stale_state_test.go | 6 +- sdk/cliproxy/types.go | 6 +- sdk/cliproxy/watcher.go | 6 +- sdk/config/config.go | 4 +- sdk/logging/request_logger.go | 2 +- sdk/translator/builtin/builtin.go | 4 +- test/amp_management_test.go | 4 +- test/builtin_tools_translation_test.go | 4 +- test/thinking_conversion_test.go | 22 +- test/usage_logging_test.go | 12 +- 317 files changed, 2415 insertions(+), 1035 deletions(-) create mode 100644 internal/config/home.go create mode 100644 internal/config/parse.go create mode 100644 internal/home/client.go create mode 100644 internal/home/global.go create mode 100644 internal/home/requests.go create mode 100644 internal/runtime/executor/helps/home_refresh.go diff --git a/cmd/fetch_antigravity_models/main.go b/cmd/fetch_antigravity_models/main.go index d4328eb3..250bcbdf 100644 --- a/cmd/fetch_antigravity_models/main.go +++ b/cmd/fetch_antigravity_models/main.go @@ -25,11 +25,11 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - sdkauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + sdkauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/cmd/server/main.go b/cmd/server/main.go index b10bc9c8..44a314ae 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -17,21 +17,21 @@ import ( "time" "github.com/joho/godotenv" - configaccess "github.com/router-for-me/CLIProxyAPI/v6/internal/access/config_access" - "github.com/router-for-me/CLIProxyAPI/v6/internal/buildinfo" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cmd" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/store" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" - "github.com/router-for-me/CLIProxyAPI/v6/internal/tui" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + configaccess "github.com/router-for-me/CLIProxyAPI/v7/internal/access/config_access" + "github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cmd" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/store" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/tui" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) @@ -496,8 +496,10 @@ func main() { // Standalone mode: start an embedded local server and connect TUI client to it. managementasset.StartAutoUpdater(context.Background(), configFilePath) misc.StartAntigravityVersionUpdater(context.Background()) - if !localModel { + if !localModel && !cfg.Home.Enabled { registry.StartModelsUpdater(context.Background()) + } else if cfg.Home.Enabled { + log.Info("Home mode: remote model updates disabled") } hook := tui.NewLogHook(2000) hook.SetFormatter(&logging.LogFormatter{}) @@ -572,8 +574,10 @@ func main() { // Start the main proxy service managementasset.StartAutoUpdater(context.Background(), configFilePath) misc.StartAntigravityVersionUpdater(context.Background()) - if !localModel { + if !localModel && !cfg.Home.Enabled { registry.StartModelsUpdater(context.Background()) + } else if cfg.Home.Enabled { + log.Info("Home mode: remote model updates disabled") } cmd.StartService(cfg, configFilePath, password) } diff --git a/config.example.yaml b/config.example.yaml index d7d5a9f5..f8e5978e 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -11,6 +11,13 @@ tls: cert: "" key: "" +# Optional "home" control plane integration over Redis protocol. +home: + enabled: false + host: "127.0.0.1" + port: 6379 + password: "" + # Management API settings remote-management: # Whether to allow remote (non-localhost) management access. @@ -67,6 +74,7 @@ error-logs-max-files: 10 usage-statistics-enabled: false # How long (in seconds) Redis usage queue items are retained in memory for the RESP interface (LPOP/RPOP). +# Note: the in-process Redis RESP usage output is disabled when home.enabled is true. # Default: 60. Max: 3600. redis-usage-queue-retention-seconds: 60 diff --git a/examples/custom-provider/main.go b/examples/custom-provider/main.go index fdbae275..6f37c341 100644 --- a/examples/custom-provider/main.go +++ b/examples/custom-provider/main.go @@ -24,14 +24,14 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - clipexec "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/logging" - sdktr "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + clipexec "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/logging" + sdktr "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) const ( diff --git a/examples/http-request/main.go b/examples/http-request/main.go index a667a9ca..1e0215ec 100644 --- a/examples/http-request/main.go +++ b/examples/http-request/main.go @@ -16,8 +16,8 @@ import ( "strings" "time" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - clipexec "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + clipexec "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" log "github.com/sirupsen/logrus" ) diff --git a/examples/translator/main.go b/examples/translator/main.go index 88f142a3..524a303e 100644 --- a/examples/translator/main.go +++ b/examples/translator/main.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" - _ "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator/builtin" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" + _ "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator/builtin" ) func main() { diff --git a/go.mod b/go.mod index 7ad363a7..9ad89ae4 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/router-for-me/CLIProxyAPI/v6 +module github.com/router-for-me/CLIProxyAPI/v7 go 1.26.0 @@ -31,6 +31,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/redis/go-redis/v9 v9.19.0 // indirect + go.uber.org/atomic v1.11.0 // indirect +) + require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect diff --git a/go.sum b/go.sum index e811b012..5f0a03fb 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= @@ -158,6 +160,8 @@ github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k= +github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -203,6 +207,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= diff --git a/internal/access/config_access/provider.go b/internal/access/config_access/provider.go index 84e8abcb..915160b7 100644 --- a/internal/access/config_access/provider.go +++ b/internal/access/config_access/provider.go @@ -5,8 +5,8 @@ import ( "net/http" "strings" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) // Register ensures the config-access provider is available to the access manager. diff --git a/internal/access/reconcile.go b/internal/access/reconcile.go index 36601f99..d71e2b8d 100644 --- a/internal/access/reconcile.go +++ b/internal/access/reconcile.go @@ -6,9 +6,9 @@ import ( "sort" "strings" - configaccess "github.com/router-for-me/CLIProxyAPI/v6/internal/access/config_access" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" + configaccess "github.com/router-for-me/CLIProxyAPI/v7/internal/access/config_access" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" log "github.com/sirupsen/logrus" ) diff --git a/internal/api/handlers/management/api_key_usage.go b/internal/api/handlers/management/api_key_usage.go index 3361da5d..dbe6fbd9 100644 --- a/internal/api/handlers/management/api_key_usage.go +++ b/internal/api/handlers/management/api_key_usage.go @@ -6,7 +6,7 @@ import ( "time" "github.com/gin-gonic/gin" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) type apiKeyUsageEntry struct { diff --git a/internal/api/handlers/management/api_key_usage_test.go b/internal/api/handlers/management/api_key_usage_test.go index 2880567f..f2be17d7 100644 --- a/internal/api/handlers/management/api_key_usage_test.go +++ b/internal/api/handlers/management/api_key_usage_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func sumRecentRequestBuckets(buckets []coreauth.RecentRequestBucket) (int64, int64) { diff --git a/internal/api/handlers/management/api_tools.go b/internal/api/handlers/management/api_tools.go index 51b08cea..f1085070 100644 --- a/internal/api/handlers/management/api_tools.go +++ b/internal/api/handlers/management/api_tools.go @@ -11,10 +11,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/geminicli" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/geminicli" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" "golang.org/x/oauth2" "golang.org/x/oauth2/google" diff --git a/internal/api/handlers/management/api_tools_test.go b/internal/api/handlers/management/api_tools_test.go index b27fe639..b089eb4a 100644 --- a/internal/api/handlers/management/api_tools_test.go +++ b/internal/api/handlers/management/api_tools_test.go @@ -5,9 +5,9 @@ import ( "net/http" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestAPICallTransportDirectBypassesGlobalProxy(t *testing.T) { diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 285b3ae2..d7e79897 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -22,17 +22,17 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/antigravity" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - geminiAuth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kimi" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/antigravity" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + geminiAuth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/kimi" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "golang.org/x/oauth2" diff --git a/internal/api/handlers/management/auth_files_batch_test.go b/internal/api/handlers/management/auth_files_batch_test.go index 44cdbd5b..ec001ae5 100644 --- a/internal/api/handlers/management/auth_files_batch_test.go +++ b/internal/api/handlers/management/auth_files_batch_test.go @@ -12,8 +12,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestUploadAuthFile_BatchMultipart(t *testing.T) { diff --git a/internal/api/handlers/management/auth_files_delete_test.go b/internal/api/handlers/management/auth_files_delete_test.go index 7b7b888c..a57c9993 100644 --- a/internal/api/handlers/management/auth_files_delete_test.go +++ b/internal/api/handlers/management/auth_files_delete_test.go @@ -11,8 +11,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestDeleteAuthFile_UsesAuthPathFromManager(t *testing.T) { diff --git a/internal/api/handlers/management/auth_files_download_test.go b/internal/api/handlers/management/auth_files_download_test.go index a2a20d30..88024fbb 100644 --- a/internal/api/handlers/management/auth_files_download_test.go +++ b/internal/api/handlers/management/auth_files_download_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestDownloadAuthFile_ReturnsFile(t *testing.T) { diff --git a/internal/api/handlers/management/auth_files_download_windows_test.go b/internal/api/handlers/management/auth_files_download_windows_test.go index 8c174ccf..88fc7f11 100644 --- a/internal/api/handlers/management/auth_files_download_windows_test.go +++ b/internal/api/handlers/management/auth_files_download_windows_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestDownloadAuthFile_PreventsWindowsSlashTraversal(t *testing.T) { diff --git a/internal/api/handlers/management/auth_files_patch_fields_test.go b/internal/api/handlers/management/auth_files_patch_fields_test.go index 3ca70012..568700a0 100644 --- a/internal/api/handlers/management/auth_files_patch_fields_test.go +++ b/internal/api/handlers/management/auth_files_patch_fields_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestPatchAuthFileFields_MergeHeadersAndDeleteEmptyValues(t *testing.T) { diff --git a/internal/api/handlers/management/auth_files_recent_requests_test.go b/internal/api/handlers/management/auth_files_recent_requests_test.go index 979040f5..404bf484 100644 --- a/internal/api/handlers/management/auth_files_recent_requests_test.go +++ b/internal/api/handlers/management/auth_files_recent_requests_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestListAuthFiles_IncludesRecentRequestsBuckets(t *testing.T) { diff --git a/internal/api/handlers/management/config_auth_index.go b/internal/api/handlers/management/config_auth_index.go index 7b015125..f2bbc2ff 100644 --- a/internal/api/handlers/management/config_auth_index.go +++ b/internal/api/handlers/management/config_auth_index.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/synthesizer" ) type geminiKeyWithAuthIndex struct { diff --git a/internal/api/handlers/management/config_basic.go b/internal/api/handlers/management/config_basic.go index f77e91e9..a0818aa8 100644 --- a/internal/api/handlers/management/config_basic.go +++ b/internal/api/handlers/management/config_basic.go @@ -11,9 +11,9 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) diff --git a/internal/api/handlers/management/config_lists.go b/internal/api/handlers/management/config_lists.go index e487627a..f8ef3203 100644 --- a/internal/api/handlers/management/config_lists.go +++ b/internal/api/handlers/management/config_lists.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) // Generic helpers for list[string] diff --git a/internal/api/handlers/management/config_lists_delete_keys_test.go b/internal/api/handlers/management/config_lists_delete_keys_test.go index aaa43910..a548805e 100644 --- a/internal/api/handlers/management/config_lists_delete_keys_test.go +++ b/internal/api/handlers/management/config_lists_delete_keys_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func writeTestConfigFile(t *testing.T) string { diff --git a/internal/api/handlers/management/handler.go b/internal/api/handlers/management/handler.go index 9abc8a5c..0f884ef0 100644 --- a/internal/api/handlers/management/handler.go +++ b/internal/api/handlers/management/handler.go @@ -13,10 +13,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/buildinfo" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "golang.org/x/crypto/bcrypt" ) diff --git a/internal/api/handlers/management/handler_test.go b/internal/api/handlers/management/handler_test.go index f3a6086e..a77dc36f 100644 --- a/internal/api/handlers/management/handler_test.go +++ b/internal/api/handlers/management/handler_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestAuthenticateManagementKey_LocalhostIPBan_BlocksCorrectKeyDuringBan(t *testing.T) { diff --git a/internal/api/handlers/management/logs.go b/internal/api/handlers/management/logs.go index b64cd619..ca6d7eda 100644 --- a/internal/api/handlers/management/logs.go +++ b/internal/api/handlers/management/logs.go @@ -13,7 +13,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" ) const ( diff --git a/internal/api/handlers/management/model_definitions.go b/internal/api/handlers/management/model_definitions.go index 85ff314b..0d1b8af4 100644 --- a/internal/api/handlers/management/model_definitions.go +++ b/internal/api/handlers/management/model_definitions.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" ) // GetStaticModelDefinitions returns static model metadata for a given channel. diff --git a/internal/api/handlers/management/test_store_test.go b/internal/api/handlers/management/test_store_test.go index cf7dbaf7..2eaacd90 100644 --- a/internal/api/handlers/management/test_store_test.go +++ b/internal/api/handlers/management/test_store_test.go @@ -4,7 +4,7 @@ import ( "context" "sync" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) type memoryAuthStore struct { diff --git a/internal/api/handlers/management/usage.go b/internal/api/handlers/management/usage.go index dfddf503..c1602c04 100644 --- a/internal/api/handlers/management/usage.go +++ b/internal/api/handlers/management/usage.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" ) type usageQueueRecord []byte diff --git a/internal/api/handlers/management/usage_test.go b/internal/api/handlers/management/usage_test.go index ca46d976..bdb8aa2e 100644 --- a/internal/api/handlers/management/usage_test.go +++ b/internal/api/handlers/management/usage_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" ) func TestGetUsageQueuePopsRequestedRecords(t *testing.T) { diff --git a/internal/api/handlers/management/vertex_import.go b/internal/api/handlers/management/vertex_import.go index bad066a2..bb064b9f 100644 --- a/internal/api/handlers/management/vertex_import.go +++ b/internal/api/handlers/management/vertex_import.go @@ -9,8 +9,8 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/vertex" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/vertex" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // ImportVertexCredential handles uploading a Vertex service account JSON and saving it as an auth record. diff --git a/internal/api/middleware/request_logging.go b/internal/api/middleware/request_logging.go index b57dd8aa..7a10fad8 100644 --- a/internal/api/middleware/request_logging.go +++ b/internal/api/middleware/request_logging.go @@ -11,8 +11,8 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" ) const maxErrorOnlyCapturedRequestBodyBytes int64 = 1 << 20 // 1 MiB diff --git a/internal/api/middleware/response_writer.go b/internal/api/middleware/response_writer.go index 7f489267..5a89ed0f 100644 --- a/internal/api/middleware/response_writer.go +++ b/internal/api/middleware/response_writer.go @@ -10,8 +10,8 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" ) const requestBodyOverrideContextKey = "REQUEST_BODY_OVERRIDE" diff --git a/internal/api/middleware/response_writer_test.go b/internal/api/middleware/response_writer_test.go index f5c21deb..fa0bd548 100644 --- a/internal/api/middleware/response_writer_test.go +++ b/internal/api/middleware/response_writer_test.go @@ -7,8 +7,8 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" ) func TestExtractRequestBodyPrefersOverride(t *testing.T) { diff --git a/internal/api/modules/amp/amp.go b/internal/api/modules/amp/amp.go index a12733e2..18c8ac1e 100644 --- a/internal/api/modules/amp/amp.go +++ b/internal/api/modules/amp/amp.go @@ -9,9 +9,9 @@ import ( "sync" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" log "github.com/sirupsen/logrus" ) diff --git a/internal/api/modules/amp/amp_test.go b/internal/api/modules/amp/amp_test.go index 430c4b62..5ca01754 100644 --- a/internal/api/modules/amp/amp_test.go +++ b/internal/api/modules/amp/amp_test.go @@ -9,10 +9,10 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" ) func TestAmpModule_Name(t *testing.T) { diff --git a/internal/api/modules/amp/fallback_handlers.go b/internal/api/modules/amp/fallback_handlers.go index e4e0f8a6..06e0a035 100644 --- a/internal/api/modules/amp/fallback_handlers.go +++ b/internal/api/modules/amp/fallback_handlers.go @@ -8,8 +8,8 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/api/modules/amp/fallback_handlers_test.go b/internal/api/modules/amp/fallback_handlers_test.go index a687fd11..1aacaae2 100644 --- a/internal/api/modules/amp/fallback_handlers_test.go +++ b/internal/api/modules/amp/fallback_handlers_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" ) func TestFallbackHandler_ModelMapping_PreservesThinkingSuffixAndRewritesResponse(t *testing.T) { diff --git a/internal/api/modules/amp/model_mapping.go b/internal/api/modules/amp/model_mapping.go index 4159a2b5..2b68866e 100644 --- a/internal/api/modules/amp/model_mapping.go +++ b/internal/api/modules/amp/model_mapping.go @@ -7,9 +7,9 @@ import ( "strings" "sync" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" ) diff --git a/internal/api/modules/amp/model_mapping_test.go b/internal/api/modules/amp/model_mapping_test.go index 53165d22..dcfb07ee 100644 --- a/internal/api/modules/amp/model_mapping_test.go +++ b/internal/api/modules/amp/model_mapping_test.go @@ -3,8 +3,8 @@ package amp import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" ) func TestNewModelMapper(t *testing.T) { diff --git a/internal/api/modules/amp/proxy.go b/internal/api/modules/amp/proxy.go index c8010854..54f4b734 100644 --- a/internal/api/modules/amp/proxy.go +++ b/internal/api/modules/amp/proxy.go @@ -14,7 +14,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" log "github.com/sirupsen/logrus" ) diff --git a/internal/api/modules/amp/proxy_test.go b/internal/api/modules/amp/proxy_test.go index 49dba956..2852efde 100644 --- a/internal/api/modules/amp/proxy_test.go +++ b/internal/api/modules/amp/proxy_test.go @@ -11,7 +11,7 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) // Helper: compress data with gzip diff --git a/internal/api/modules/amp/routes.go b/internal/api/modules/amp/routes.go index b7253c34..84023d15 100644 --- a/internal/api/modules/amp/routes.go +++ b/internal/api/modules/amp/routes.go @@ -9,11 +9,11 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/claude" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/gemini" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/openai" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/claude" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/gemini" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai" log "github.com/sirupsen/logrus" ) @@ -21,12 +21,12 @@ import ( // from gin.Context to the request context for SecretSource lookup. type clientAPIKeyContextKey struct{} -// clientAPIKeyMiddleware injects the authenticated client API key from gin.Context["apiKey"] +// clientAPIKeyMiddleware injects the authenticated client API key from gin.Context["userApiKey"] // into the request context so that SecretSource can look it up for per-client upstream routing. func clientAPIKeyMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Extract the client API key from gin context (set by AuthMiddleware) - if apiKey, exists := c.Get("apiKey"); exists { + if apiKey, exists := c.Get("userApiKey"); exists { if keyStr, ok := apiKey.(string); ok && keyStr != "" { // Inject into request context for SecretSource.Get(ctx) to read ctx := context.WithValue(c.Request.Context(), clientAPIKeyContextKey{}, keyStr) diff --git a/internal/api/modules/amp/routes_test.go b/internal/api/modules/amp/routes_test.go index 2308a153..a500f815 100644 --- a/internal/api/modules/amp/routes_test.go +++ b/internal/api/modules/amp/routes_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" ) func TestRegisterManagementRoutes(t *testing.T) { diff --git a/internal/api/modules/amp/secret.go b/internal/api/modules/amp/secret.go index f91c72ba..512d263d 100644 --- a/internal/api/modules/amp/secret.go +++ b/internal/api/modules/amp/secret.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/internal/api/modules/amp/secret_test.go b/internal/api/modules/amp/secret_test.go index 6a6f6ba2..17a75b15 100644 --- a/internal/api/modules/amp/secret_test.go +++ b/internal/api/modules/amp/secret_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" ) diff --git a/internal/api/modules/modules.go b/internal/api/modules/modules.go index 8c5447d9..5ddfa609 100644 --- a/internal/api/modules/modules.go +++ b/internal/api/modules/modules.go @@ -6,8 +6,8 @@ import ( "fmt" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" ) // Context encapsulates the dependencies exposed to routing modules during diff --git a/internal/api/protocol_multiplexer.go b/internal/api/protocol_multiplexer.go index 14068dc5..b83e1164 100644 --- a/internal/api/protocol_multiplexer.go +++ b/internal/api/protocol_multiplexer.go @@ -83,6 +83,12 @@ func (s *Server) acceptMuxConnections(listener net.Listener, httpListener *muxLi } if isRedisRESPPrefix(prefix[0]) { + if s.cfg != nil && s.cfg.Home.Enabled { + if errClose := conn.Close(); errClose != nil { + log.Errorf("failed to close redis connection while home mode is enabled: %v", errClose) + } + continue + } if !s.managementRoutesEnabled.Load() { if errClose := conn.Close(); errClose != nil { log.Errorf("failed to close redis connection while management is disabled: %v", errClose) diff --git a/internal/api/redis_queue_protocol.go b/internal/api/redis_queue_protocol.go index caaba231..6f3622d7 100644 --- a/internal/api/redis_queue_protocol.go +++ b/internal/api/redis_queue_protocol.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" log "github.com/sirupsen/logrus" ) @@ -45,6 +45,12 @@ func (s *Server) handleRedisConnection(conn net.Conn, reader *bufio.Reader) { return true } + if s.cfg != nil && s.cfg.Home.Enabled { + _ = writeRedisError(writer, "ERR redis usage output disabled in home mode") + _ = writer.Flush() + return + } + for { if !s.managementRoutesEnabled.Load() { return diff --git a/internal/api/redis_queue_protocol_integration_test.go b/internal/api/redis_queue_protocol_integration_test.go index 93bfeb86..1586d37c 100644 --- a/internal/api/redis_queue_protocol_integration_test.go +++ b/internal/api/redis_queue_protocol_integration_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" ) type remoteAddrConn struct { @@ -204,6 +204,43 @@ func TestRedisProtocol_ManagementDisabled_RejectsConnection(t *testing.T) { } } +func TestRedisProtocol_HomeEnabled_DisablesConnection(t *testing.T) { + t.Setenv("MANAGEMENT_PASSWORD", "test-management-password") + redisqueue.SetEnabled(false) + t.Cleanup(func() { redisqueue.SetEnabled(false) }) + + server := newTestServer(t) + if !server.managementRoutesEnabled.Load() { + t.Fatalf("expected managementRoutesEnabled to be true") + } + if server.cfg == nil { + t.Fatalf("expected server cfg to be non-nil") + } + server.cfg.Home.Enabled = true + redisqueue.SetEnabled(true) + + addr, stop := startRedisMuxListener(t, server) + t.Cleanup(stop) + + conn, errDial := net.DialTimeout("tcp", addr, time.Second) + if errDial != nil { + t.Fatalf("failed to dial redis listener: %v", errDial) + } + t.Cleanup(func() { _ = conn.Close() }) + + _ = conn.SetDeadline(time.Now().Add(2 * time.Second)) + _ = writeTestRESPCommand(conn, "PING") + + buf := make([]byte, 1) + _, errRead := conn.Read(buf) + if errRead == nil { + t.Fatalf("expected connection to be closed when home mode is enabled") + } + if ne, ok := errRead.(net.Error); ok && ne.Timeout() { + t.Fatalf("expected connection to be closed when home mode is enabled, got timeout: %v", errRead) + } +} + func TestRedisProtocol_AUTH_And_PopContracts(t *testing.T) { const managementPassword = "test-management-password" diff --git a/internal/api/server.go b/internal/api/server.go index 487ea571..1e29580f 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -8,6 +8,7 @@ import ( "context" "crypto/subtle" "crypto/tls" + "encoding/json" "errors" "fmt" "net" @@ -15,30 +16,32 @@ import ( "os" "path/filepath" "reflect" + "sort" "strings" "sync" "sync/atomic" "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/access" - managementHandlers "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/management" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/middleware" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules" - ampmodule "github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules/amp" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/claude" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/gemini" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/openai" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/access" + managementHandlers "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api/middleware" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules" + ampmodule "github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules/amp" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/home" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/claude" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/gemini" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "golang.org/x/net/http2" "gopkg.in/yaml.v3" @@ -284,6 +287,10 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk } s.localPassword = optionState.localPassword + // Home heartbeat gate: when home is enabled, block all endpoints with 503 until the + // subscribe-config heartbeat connection is healthy. + engine.Use(s.homeHeartbeatMiddleware()) + // Setup routes s.setupRoutes() @@ -308,7 +315,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk // or when a local management password is provided (e.g. TUI mode). hasManagementSecret := cfg.RemoteManagement.SecretKey != "" || envManagementSecret || s.localPassword != "" s.managementRoutesEnabled.Store(hasManagementSecret) - redisqueue.SetEnabled(hasManagementSecret) + redisqueue.SetEnabled(hasManagementSecret || (cfg != nil && cfg.Home.Enabled)) if hasManagementSecret { s.registerManagementRoutes() } @@ -326,6 +333,28 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk return s } +func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + if s == nil || s.cfg == nil || !s.cfg.Home.Enabled { + c.Next() + return + } + if c != nil && c.Request != nil { + path := c.Request.URL.Path + if strings.HasPrefix(path, "/v0/management/") || path == "/v0/management" || path == "/management.html" { + c.Next() + return + } + } + client := home.Current() + if client == nil || !client.HeartbeatOK() { + c.AbortWithStatus(http.StatusServiceUnavailable) + return + } + c.Next() + } +} + // setupRoutes configures the API routes for the server. // It defines the endpoints and associates them with their respective handlers. func (s *Server) setupRoutes() { @@ -661,6 +690,14 @@ func (s *Server) registerManagementRoutes() { func (s *Server) managementAvailabilityMiddleware() gin.HandlerFunc { return func(c *gin.Context) { + if s == nil || s.cfg == nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + if s.cfg.Home.Enabled { + c.AbortWithStatus(http.StatusNotFound) + return + } if !s.managementRoutesEnabled.Load() { c.AbortWithStatus(http.StatusNotFound) return @@ -671,7 +708,7 @@ func (s *Server) managementAvailabilityMiddleware() gin.HandlerFunc { func (s *Server) serveManagementControlPanel(c *gin.Context) { cfg := s.cfg - if cfg == nil || cfg.RemoteManagement.DisableControlPanel { + if cfg == nil || cfg.Home.Enabled || cfg.RemoteManagement.DisableControlPanel { c.AbortWithStatus(http.StatusNotFound) return } @@ -783,6 +820,11 @@ func (s *Server) watchKeepAlive() { // otherwise it routes to OpenAI handler. func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, claudeHandler *claude.ClaudeCodeAPIHandler) gin.HandlerFunc { return func(c *gin.Context) { + if s != nil && s.cfg != nil && s.cfg.Home.Enabled { + s.handleHomeModels(c) + return + } + userAgent := c.GetHeader("User-Agent") // Route to Claude handler if User-Agent starts with "claude-cli" @@ -796,6 +838,170 @@ func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, cl } } +type homeModelEntry struct { + id string + created int64 + ownedBy string + displayName string +} + +func (s *Server) handleHomeModels(c *gin.Context) { + if s == nil || c == nil || c.Request == nil { + return + } + client := home.Current() + if client == nil { + c.JSON(http.StatusServiceUnavailable, handlers.ErrorResponse{ + Error: handlers.ErrorDetail{ + Message: "home control center unavailable", + Type: "server_error", + }, + }) + return + } + + raw, errGet := client.GetModels(c.Request.Context()) + if errGet != nil { + c.JSON(http.StatusBadGateway, handlers.ErrorResponse{ + Error: handlers.ErrorDetail{ + Message: errGet.Error(), + Type: "server_error", + }, + }) + return + } + + entries, errDecode := decodeHomeModels(raw) + if errDecode != nil { + c.JSON(http.StatusBadGateway, handlers.ErrorResponse{ + Error: handlers.ErrorDetail{ + Message: errDecode.Error(), + Type: "server_error", + }, + }) + return + } + + userAgent := c.GetHeader("User-Agent") + isClaude := strings.HasPrefix(userAgent, "claude-cli") + + if isClaude { + out := make([]map[string]any, 0, len(entries)) + for _, entry := range entries { + model := map[string]any{ + "id": entry.id, + "object": "model", + "owned_by": entry.ownedBy, + } + if entry.created > 0 { + model["created_at"] = entry.created + } + if entry.displayName != "" { + model["display_name"] = entry.displayName + } + out = append(out, model) + } + firstID := "" + lastID := "" + if len(out) > 0 { + if id, ok := out[0]["id"].(string); ok { + firstID = id + } + if id, ok := out[len(out)-1]["id"].(string); ok { + lastID = id + } + } + c.JSON(http.StatusOK, gin.H{ + "data": out, + "has_more": false, + "first_id": firstID, + "last_id": lastID, + }) + return + } + + filtered := make([]map[string]any, 0, len(entries)) + for _, entry := range entries { + model := map[string]any{ + "id": entry.id, + "object": "model", + } + if entry.created > 0 { + model["created"] = entry.created + } + if entry.ownedBy != "" { + model["owned_by"] = entry.ownedBy + } + filtered = append(filtered, model) + } + c.JSON(http.StatusOK, gin.H{ + "object": "list", + "data": filtered, + }) +} + +func decodeHomeModels(raw []byte) ([]homeModelEntry, error) { + if len(raw) == 0 { + return nil, fmt.Errorf("home models payload is empty") + } + + var bySection map[string][]map[string]any + if err := json.Unmarshal(raw, &bySection); err != nil { + return nil, fmt.Errorf("parse home models payload: %w", err) + } + if len(bySection) == 0 { + return nil, fmt.Errorf("home models payload has no sections") + } + + seen := make(map[string]struct{}) + out := make([]homeModelEntry, 0, 256) + for _, models := range bySection { + for _, model := range models { + id, _ := model["id"].(string) + id = strings.TrimSpace(id) + if id == "" { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + + created := int64(0) + switch v := model["created"].(type) { + case float64: + created = int64(v) + case int64: + created = v + case int: + created = int64(v) + case json.Number: + if n, err := v.Int64(); err == nil { + created = n + } + } + + ownedBy, _ := model["owned_by"].(string) + ownedBy = strings.TrimSpace(ownedBy) + displayName, _ := model["display_name"].(string) + displayName = strings.TrimSpace(displayName) + + out = append(out, homeModelEntry{ + id: id, + created: created, + ownedBy: ownedBy, + displayName: displayName, + }) + } + } + + sort.Slice(out, func(i, j int) bool { return out[i].id < out[j].id }) + if len(out) == 0 { + return nil, fmt.Errorf("home models payload contains no models") + } + return out, nil +} + // Start begins listening for and serving HTTP or HTTPS requests. // It's a blocking call and will only return on an unrecoverable error. // @@ -1061,7 +1267,7 @@ func (s *Server) UpdateClients(cfg *config.Config) { s.managementRoutesEnabled.Store(!newSecretEmpty) } } - redisqueue.SetEnabled(s.managementRoutesEnabled.Load()) + redisqueue.SetEnabled(s.managementRoutesEnabled.Load() || (cfg != nil && cfg.Home.Enabled)) s.applyAccessConfig(oldCfg, cfg) s.cfg = cfg @@ -1094,11 +1300,14 @@ func (s *Server) UpdateClients(cfg *config.Config) { } // Count client sources from configuration and auth store. - tokenStore := sdkAuth.GetTokenStore() - if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok { - dirSetter.SetBaseDir(cfg.AuthDir) + authEntries := 0 + if cfg != nil && !cfg.Home.Enabled { + tokenStore := sdkAuth.GetTokenStore() + if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok { + dirSetter.SetBaseDir(cfg.AuthDir) + } + authEntries = util.CountAuthFiles(context.Background(), tokenStore) } - authEntries := util.CountAuthFiles(context.Background(), tokenStore) geminiAPIKeyCount := len(cfg.GeminiKey) claudeAPIKeyCount := len(cfg.ClaudeKey) codexAPIKeyCount := len(cfg.CodexKey) @@ -1146,7 +1355,7 @@ func AuthMiddleware(manager *sdkaccess.Manager) gin.HandlerFunc { result, err := manager.Authenticate(c.Request.Context(), c.Request) if err == nil { if result != nil { - c.Set("apiKey", result.Principal) + c.Set("userApiKey", result.Principal) c.Set("accessProvider", result.Provider) if len(result.Metadata) > 0 { c.Set("accessMetadata", result.Metadata) diff --git a/internal/api/server_test.go b/internal/api/server_test.go index fe37cb72..e107702a 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -11,12 +11,12 @@ import ( "time" gin "github.com/gin-gonic/gin" - proxyconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + proxyconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func newTestServer(t *testing.T) *Server { @@ -147,6 +147,32 @@ func TestManagementUsageRequiresManagementAuthAndPopsArray(t *testing.T) { } } +func TestHomeEnabledHidesManagementEndpointsAndControlPanel(t *testing.T) { + t.Setenv("MANAGEMENT_PASSWORD", "test-management-key") + + server := newTestServer(t) + server.cfg.Home.Enabled = true + + t.Run("management endpoints return 404", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/v0/management/config", nil) + req.Header.Set("Authorization", "Bearer test-management-key") + rr := httptest.NewRecorder() + server.engine.ServeHTTP(rr, req) + if rr.Code != http.StatusNotFound { + t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusNotFound, rr.Body.String()) + } + }) + + t.Run("management control panel returns 404", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/management.html", nil) + rr := httptest.NewRecorder() + server.engine.ServeHTTP(rr, req) + if rr.Code != http.StatusNotFound { + t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusNotFound, rr.Body.String()) + } + }) +} + func TestAmpProviderModelRoutes(t *testing.T) { testCases := []struct { name string diff --git a/internal/auth/antigravity/auth.go b/internal/auth/antigravity/auth.go index 8d3b216f..7bee09bb 100644 --- a/internal/auth/antigravity/auth.go +++ b/internal/auth/antigravity/auth.go @@ -11,9 +11,9 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" ) diff --git a/internal/auth/claude/anthropic_auth.go b/internal/auth/claude/anthropic_auth.go index 60c71b35..d7ca1542 100644 --- a/internal/auth/claude/anthropic_auth.go +++ b/internal/auth/claude/anthropic_auth.go @@ -15,7 +15,7 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" log "github.com/sirupsen/logrus" "golang.org/x/sync/singleflight" ) diff --git a/internal/auth/claude/anthropic_auth_proxy_test.go b/internal/auth/claude/anthropic_auth_proxy_test.go index 50c48757..7cab9cd2 100644 --- a/internal/auth/claude/anthropic_auth_proxy_test.go +++ b/internal/auth/claude/anthropic_auth_proxy_test.go @@ -3,7 +3,7 @@ package claude import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "golang.org/x/net/proxy" ) diff --git a/internal/auth/claude/token.go b/internal/auth/claude/token.go index 6ebb0f2f..10aa3b43 100644 --- a/internal/auth/claude/token.go +++ b/internal/auth/claude/token.go @@ -9,7 +9,7 @@ import ( "os" "path/filepath" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" ) // ClaudeTokenStorage stores OAuth2 token information for Anthropic Claude API authentication. diff --git a/internal/auth/claude/utls_transport.go b/internal/auth/claude/utls_transport.go index 88b69c9b..f4108781 100644 --- a/internal/auth/claude/utls_transport.go +++ b/internal/auth/claude/utls_transport.go @@ -8,8 +8,8 @@ import ( "sync" tls "github.com/refraction-networking/utls" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" "golang.org/x/net/http2" "golang.org/x/net/proxy" diff --git a/internal/auth/codex/openai_auth.go b/internal/auth/codex/openai_auth.go index 67b54b17..681747ca 100644 --- a/internal/auth/codex/openai_auth.go +++ b/internal/auth/codex/openai_auth.go @@ -14,8 +14,8 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" ) diff --git a/internal/auth/codex/openai_auth_test.go b/internal/auth/codex/openai_auth_test.go index a7fe8307..e7d939b0 100644 --- a/internal/auth/codex/openai_auth_test.go +++ b/internal/auth/codex/openai_auth_test.go @@ -8,7 +8,7 @@ import ( "sync/atomic" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) type roundTripFunc func(*http.Request) (*http.Response, error) diff --git a/internal/auth/codex/token.go b/internal/auth/codex/token.go index 7f032071..b2a7bcf2 100644 --- a/internal/auth/codex/token.go +++ b/internal/auth/codex/token.go @@ -9,7 +9,7 @@ import ( "os" "path/filepath" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" ) // CodexTokenStorage stores OAuth2 token information for OpenAI Codex API authentication. diff --git a/internal/auth/gemini/gemini_auth.go b/internal/auth/gemini/gemini_auth.go index 2995a1cb..5b9ee82d 100644 --- a/internal/auth/gemini/gemini_auth.go +++ b/internal/auth/gemini/gemini_auth.go @@ -13,12 +13,12 @@ import ( "net/http" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" diff --git a/internal/auth/gemini/gemini_token.go b/internal/auth/gemini/gemini_token.go index 6848b708..a6ea8c51 100644 --- a/internal/auth/gemini/gemini_token.go +++ b/internal/auth/gemini/gemini_token.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" log "github.com/sirupsen/logrus" ) diff --git a/internal/auth/kimi/kimi.go b/internal/auth/kimi/kimi.go index ccb1a6c2..27c5f73b 100644 --- a/internal/auth/kimi/kimi.go +++ b/internal/auth/kimi/kimi.go @@ -15,8 +15,8 @@ import ( "time" "github.com/google/uuid" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" ) diff --git a/internal/auth/kimi/kimi_proxy_test.go b/internal/auth/kimi/kimi_proxy_test.go index 130f34f5..a95ba01d 100644 --- a/internal/auth/kimi/kimi_proxy_test.go +++ b/internal/auth/kimi/kimi_proxy_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestNewDeviceFlowClientWithDeviceIDAndProxyURL_OverrideDirectDisablesProxy(t *testing.T) { diff --git a/internal/auth/kimi/token.go b/internal/auth/kimi/token.go index 7320d760..347b546c 100644 --- a/internal/auth/kimi/token.go +++ b/internal/auth/kimi/token.go @@ -10,7 +10,7 @@ import ( "path/filepath" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" ) // KimiTokenStorage stores OAuth2 token information for Kimi API authentication. diff --git a/internal/auth/vertex/vertex_credentials.go b/internal/auth/vertex/vertex_credentials.go index 9f830994..db214bd6 100644 --- a/internal/auth/vertex/vertex_credentials.go +++ b/internal/auth/vertex/vertex_credentials.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/anthropic_login.go b/internal/cmd/anthropic_login.go index f7381461..cc1bfc8e 100644 --- a/internal/cmd/anthropic_login.go +++ b/internal/cmd/anthropic_login.go @@ -6,9 +6,9 @@ import ( "fmt" "os" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/antigravity_login.go b/internal/cmd/antigravity_login.go index 2efbaeee..f2bd5505 100644 --- a/internal/cmd/antigravity_login.go +++ b/internal/cmd/antigravity_login.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/auth_manager.go b/internal/cmd/auth_manager.go index 26547179..7896a702 100644 --- a/internal/cmd/auth_manager.go +++ b/internal/cmd/auth_manager.go @@ -1,7 +1,7 @@ package cmd import ( - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" ) // newAuthManager creates a new authentication manager instance with all supported diff --git a/internal/cmd/kimi_login.go b/internal/cmd/kimi_login.go index eb5f11fb..ffc470fd 100644 --- a/internal/cmd/kimi_login.go +++ b/internal/cmd/kimi_login.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/login.go b/internal/cmd/login.go index 22404dac..a71bb282 100644 --- a/internal/cmd/login.go +++ b/internal/cmd/login.go @@ -17,12 +17,12 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/internal/cmd/openai_device_login.go b/internal/cmd/openai_device_login.go index 1b7351e6..3fa9307b 100644 --- a/internal/cmd/openai_device_login.go +++ b/internal/cmd/openai_device_login.go @@ -6,9 +6,9 @@ import ( "fmt" "os" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/openai_login.go b/internal/cmd/openai_login.go index 783a9484..ee8a0250 100644 --- a/internal/cmd/openai_login.go +++ b/internal/cmd/openai_login.go @@ -6,9 +6,9 @@ import ( "fmt" "os" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/run.go b/internal/cmd/run.go index d8c4f019..38f189b4 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -10,9 +10,9 @@ import ( "syscall" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy" log "github.com/sirupsen/logrus" ) diff --git a/internal/cmd/vertex_import.go b/internal/cmd/vertex_import.go index 4aa0d74b..ffb6200b 100644 --- a/internal/cmd/vertex_import.go +++ b/internal/cmd/vertex_import.go @@ -9,11 +9,11 @@ import ( "os" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/vertex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/vertex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/config/config.go b/internal/config/config.go index 46ce4f50..e09f38a8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,7 +13,7 @@ import ( "strings" "syscall" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" log "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v3" @@ -36,6 +36,9 @@ type Config struct { // TLS config controls HTTPS server settings. TLS TLSConfig `yaml:"tls" json:"tls"` + // Home config enables the Redis-based control plane integration. + Home HomeConfig `yaml:"home" json:"-"` + // RemoteManagement nests management-related options under 'remote-management'. RemoteManagement RemoteManagement `yaml:"remote-management" json:"-"` diff --git a/internal/config/home.go b/internal/config/home.go new file mode 100644 index 00000000..03c91732 --- /dev/null +++ b/internal/config/home.go @@ -0,0 +1,9 @@ +package config + +// HomeConfig configures the optional "home" control plane integration over Redis protocol. +type HomeConfig struct { + Enabled bool `yaml:"enabled" json:"enabled"` + Host string `yaml:"host" json:"-"` + Port int `yaml:"port" json:"-"` + Password string `yaml:"password" json:"-"` +} diff --git a/internal/config/parse.go b/internal/config/parse.go new file mode 100644 index 00000000..283740e5 --- /dev/null +++ b/internal/config/parse.go @@ -0,0 +1,89 @@ +package config + +import ( + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" + "gopkg.in/yaml.v3" +) + +// ParseConfigBytes parses a YAML configuration payload into Config and applies the same +// in-memory normalizations as LoadConfigOptional, without persisting any changes to disk. +func ParseConfigBytes(data []byte) (*Config, error) { + if len(data) == 0 { + return nil, fmt.Errorf("config payload is empty") + } + + var cfg Config + // Keep defaults aligned with LoadConfigOptional. + cfg.Host = "" // Default empty: binds to all interfaces (IPv4 + IPv6) + cfg.LoggingToFile = false + cfg.LogsMaxTotalSizeMB = 0 + cfg.ErrorLogsMaxFiles = 10 + cfg.UsageStatisticsEnabled = false + cfg.RedisUsageQueueRetentionSeconds = 60 + cfg.DisableCooling = false + cfg.DisableImageGeneration = DisableImageGenerationOff + cfg.Pprof.Enable = false + cfg.Pprof.Addr = DefaultPprofAddr + cfg.AmpCode.RestrictManagementToLocalhost = false // Default to false: API key auth is sufficient + cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository + + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, fmt.Errorf("parse config payload: %w", err) + } + + // Hash remote management key if plaintext is detected (nested), but do NOT persist. + if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) { + hashed, errHash := bcrypt.GenerateFromPassword([]byte(cfg.RemoteManagement.SecretKey), bcrypt.DefaultCost) + if errHash != nil { + return nil, fmt.Errorf("hash remote management key: %w", errHash) + } + cfg.RemoteManagement.SecretKey = string(hashed) + } + + cfg.RemoteManagement.PanelGitHubRepository = strings.TrimSpace(cfg.RemoteManagement.PanelGitHubRepository) + if cfg.RemoteManagement.PanelGitHubRepository == "" { + cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository + } + + cfg.Pprof.Addr = strings.TrimSpace(cfg.Pprof.Addr) + if cfg.Pprof.Addr == "" { + cfg.Pprof.Addr = DefaultPprofAddr + } + + if cfg.LogsMaxTotalSizeMB < 0 { + cfg.LogsMaxTotalSizeMB = 0 + } + + if cfg.ErrorLogsMaxFiles < 0 { + cfg.ErrorLogsMaxFiles = 10 + } + + if cfg.RedisUsageQueueRetentionSeconds <= 0 { + cfg.RedisUsageQueueRetentionSeconds = 60 + } else if cfg.RedisUsageQueueRetentionSeconds > 3600 { + log.WithField("value", cfg.RedisUsageQueueRetentionSeconds).Warn("redis-usage-queue-retention-seconds too large; clamping to 3600") + cfg.RedisUsageQueueRetentionSeconds = 3600 + } + + if cfg.MaxRetryCredentials < 0 { + cfg.MaxRetryCredentials = 0 + } + + // Apply the same sanitization pipeline. + cfg.SanitizeGeminiKeys() + cfg.SanitizeVertexCompatKeys() + cfg.SanitizeCodexKeys() + cfg.SanitizeCodexHeaderDefaults() + cfg.SanitizeClaudeHeaderDefaults() + cfg.SanitizeClaudeKeys() + cfg.SanitizeOpenAICompatibility() + cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels) + cfg.SanitizeOAuthModelAlias() + cfg.SanitizePayloadRules() + + return &cfg, nil +} diff --git a/internal/home/client.go b/internal/home/client.go new file mode 100644 index 00000000..22a18b32 --- /dev/null +++ b/internal/home/client.go @@ -0,0 +1,374 @@ +package home + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "sync/atomic" + "time" + + "github.com/redis/go-redis/v9" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + log "github.com/sirupsen/logrus" +) + +const ( + redisKeyConfig = "config" + redisChannelConfig = "config" + redisKeyModels = "models" + redisKeyUsage = "usage" + + homeReconnectInterval = time.Second +) + +var ( + ErrDisabled = errors.New("home client disabled") + ErrNotConnected = errors.New("home not connected") + ErrEmptyResponse = errors.New("home returned empty response") + ErrAuthNotFound = errors.New("home auth not found") + ErrConfigNotFound = errors.New("home config not found") + ErrModelsNotFound = errors.New("home models not found") +) + +type Client struct { + homeCfg config.HomeConfig + + cmd *redis.Client + sub *redis.Client + + heartbeatOK atomic.Bool +} + +func New(homeCfg config.HomeConfig) *Client { + return &Client{homeCfg: homeCfg} +} + +func (c *Client) Enabled() bool { + if c == nil { + return false + } + return c.homeCfg.Enabled +} + +func (c *Client) HeartbeatOK() bool { + if c == nil { + return false + } + if !c.Enabled() { + return false + } + return c.heartbeatOK.Load() +} + +func (c *Client) Close() { + if c == nil { + return + } + c.heartbeatOK.Store(false) + if c.cmd != nil { + _ = c.cmd.Close() + } + if c.sub != nil { + _ = c.sub.Close() + } + c.cmd = nil + c.sub = nil +} + +func (c *Client) addr() (string, bool) { + if c == nil { + return "", false + } + host := strings.TrimSpace(c.homeCfg.Host) + if host == "" { + return "", false + } + if c.homeCfg.Port <= 0 { + return "", false + } + return fmt.Sprintf("%s:%d", host, c.homeCfg.Port), true +} + +func (c *Client) ensureClients() error { + if c == nil { + return ErrDisabled + } + if !c.Enabled() { + return ErrDisabled + } + addr, ok := c.addr() + if !ok { + return fmt.Errorf("home: invalid address (host=%q port=%d)", c.homeCfg.Host, c.homeCfg.Port) + } + + if c.cmd == nil { + c.cmd = redis.NewClient(&redis.Options{ + Addr: addr, + Password: c.homeCfg.Password, + }) + } + if c.sub == nil { + c.sub = redis.NewClient(&redis.Options{ + Addr: addr, + Password: c.homeCfg.Password, + }) + } + return nil +} + +func (c *Client) Ping(ctx context.Context) error { + if err := c.ensureClients(); err != nil { + return err + } + if c.cmd == nil { + return ErrNotConnected + } + return c.cmd.Ping(ctx).Err() +} + +func (c *Client) GetConfig(ctx context.Context) ([]byte, error) { + if err := c.ensureClients(); err != nil { + return nil, err + } + raw, err := c.cmd.Get(ctx, redisKeyConfig).Bytes() + if errors.Is(err, redis.Nil) { + return nil, ErrConfigNotFound + } + if err != nil { + return nil, err + } + if len(raw) == 0 { + return nil, ErrEmptyResponse + } + return raw, nil +} + +func (c *Client) GetModels(ctx context.Context) ([]byte, error) { + if err := c.ensureClients(); err != nil { + return nil, err + } + raw, err := c.cmd.Get(ctx, redisKeyModels).Bytes() + if errors.Is(err, redis.Nil) { + return nil, ErrModelsNotFound + } + if err != nil { + return nil, err + } + if len(raw) == 0 { + return nil, ErrEmptyResponse + } + return raw, nil +} + +func headersToLowerMap(headers http.Header) map[string]string { + if len(headers) == 0 { + return nil + } + out := make(map[string]string, len(headers)) + for key, values := range headers { + k := strings.ToLower(strings.TrimSpace(key)) + if k == "" { + continue + } + if len(values) == 0 { + out[k] = "" + continue + } + trimmed := make([]string, 0, len(values)) + for _, v := range values { + trimmed = append(trimmed, strings.TrimSpace(v)) + } + out[k] = strings.Join(trimmed, ", ") + } + if len(out) == 0 { + return nil + } + return out +} + +func (c *Client) RPopAuth(ctx context.Context, requestedModel string, sessionID string, headers http.Header) ([]byte, error) { + if err := c.ensureClients(); err != nil { + return nil, err + } + requestedModel = strings.TrimSpace(requestedModel) + if requestedModel == "" { + return nil, fmt.Errorf("home: requested model is empty") + } + req := authDispatchRequest{ + Type: "auth", + Model: requestedModel, + SessionID: strings.TrimSpace(sessionID), + Headers: headersToLowerMap(headers), + } + keyBytes, err := json.Marshal(&req) + if err != nil { + return nil, err + } + + raw, err := c.cmd.RPop(ctx, string(keyBytes)).Bytes() + if errors.Is(err, redis.Nil) { + return nil, ErrAuthNotFound + } + if err != nil { + return nil, err + } + if len(raw) == 0 { + return nil, ErrEmptyResponse + } + return raw, nil +} + +func (c *Client) GetRefreshAuth(ctx context.Context, authIndex string) ([]byte, error) { + if err := c.ensureClients(); err != nil { + return nil, err + } + authIndex = strings.TrimSpace(authIndex) + if authIndex == "" { + return nil, fmt.Errorf("home: auth_index is empty") + } + req := refreshRequest{ + Type: "refresh", + AuthIndex: authIndex, + } + keyBytes, err := json.Marshal(&req) + if err != nil { + return nil, err + } + + raw, err := c.cmd.Get(ctx, string(keyBytes)).Bytes() + if errors.Is(err, redis.Nil) { + return nil, ErrAuthNotFound + } + if err != nil { + return nil, err + } + if len(raw) == 0 { + return nil, ErrEmptyResponse + } + return raw, nil +} + +func (c *Client) LPushUsage(ctx context.Context, payload []byte) error { + if err := c.ensureClients(); err != nil { + return err + } + if len(payload) == 0 { + return nil + } + return c.cmd.LPush(ctx, redisKeyUsage, payload).Err() +} + +// StartConfigSubscriber connects to home, fetches config once via GET config, then subscribes to +// the "config" channel to receive runtime config updates. +// +// The subscription connection is treated as the home heartbeat. HeartbeatOK is set to true only +// after the initial GET config succeeds and the SUBSCRIBE connection is established. When the +// subscription ends unexpectedly, HeartbeatOK becomes false and the loop reconnects. +func (c *Client) StartConfigSubscriber(ctx context.Context, onConfig func([]byte) error) { + if c == nil { + return + } + if !c.Enabled() { + return + } + if onConfig == nil { + return + } + + for { + if ctx != nil { + select { + case <-ctx.Done(): + c.heartbeatOK.Store(false) + return + default: + } + } + + c.heartbeatOK.Store(false) + c.Close() + + if errEnsure := c.ensureClients(); errEnsure != nil { + log.Warn("unable to connect to home control center, retrying in 1 second") + sleepWithContext(ctx, homeReconnectInterval) + continue + } + + if errPing := c.Ping(ctx); errPing != nil { + log.Warn("unable to connect to home control center, retrying in 1 second") + sleepWithContext(ctx, homeReconnectInterval) + continue + } + + raw, errGet := c.GetConfig(ctx) + if errGet != nil { + log.Warn("unable to fetch config from home control center, retrying in 1 second") + sleepWithContext(ctx, homeReconnectInterval) + continue + } + if errApply := onConfig(raw); errApply != nil { + log.Warn("unable to apply config from home control center, retrying in 1 second") + sleepWithContext(ctx, homeReconnectInterval) + continue + } + + if c.sub == nil { + sleepWithContext(ctx, homeReconnectInterval) + continue + } + + pubsub := c.sub.Subscribe(ctx, redisChannelConfig) + if pubsub == nil { + sleepWithContext(ctx, homeReconnectInterval) + continue + } + + // Ensure the subscription is established before marking heartbeat OK. + if _, errReceive := pubsub.Receive(ctx); errReceive != nil { + _ = pubsub.Close() + sleepWithContext(ctx, homeReconnectInterval) + continue + } + + c.heartbeatOK.Store(true) + + for { + msg, errMsg := pubsub.ReceiveMessage(ctx) + if errMsg != nil { + _ = pubsub.Close() + c.heartbeatOK.Store(false) + sleepWithContext(ctx, homeReconnectInterval) + break + } + if msg == nil { + continue + } + if payload := strings.TrimSpace(msg.Payload); payload != "" { + if errApply := onConfig([]byte(payload)); errApply != nil { + log.Warn("failed to apply config update from home control center, ignoring") + } + } + } + } +} + +func sleepWithContext(ctx context.Context, d time.Duration) { + if d <= 0 { + return + } + timer := time.NewTimer(d) + defer timer.Stop() + if ctx == nil { + <-timer.C + return + } + select { + case <-ctx.Done(): + return + case <-timer.C: + return + } +} diff --git a/internal/home/global.go b/internal/home/global.go new file mode 100644 index 00000000..a79121a4 --- /dev/null +++ b/internal/home/global.go @@ -0,0 +1,25 @@ +package home + +import "sync/atomic" + +var currentClient atomic.Value // *Client + +// SetCurrent sets the active home client used by runtime integrations. +func SetCurrent(client *Client) { + currentClient.Store(client) +} + +// Current returns the active home client instance, if any. +func Current() *Client { + if v := currentClient.Load(); v != nil { + if client, ok := v.(*Client); ok { + return client + } + } + return nil +} + +// ClearCurrent removes the active home client. +func ClearCurrent() { + currentClient.Store((*Client)(nil)) +} diff --git a/internal/home/requests.go b/internal/home/requests.go new file mode 100644 index 00000000..d08f5a5d --- /dev/null +++ b/internal/home/requests.go @@ -0,0 +1,13 @@ +package home + +type authDispatchRequest struct { + Type string `json:"type"` + Model string `json:"model"` + SessionID string `json:"session_id,omitempty"` + Headers map[string]string `json:"headers,omitempty"` +} + +type refreshRequest struct { + Type string `json:"type"` + AuthIndex string `json:"auth_index"` +} diff --git a/internal/interfaces/types.go b/internal/interfaces/types.go index 9fb1e7f3..dfdfc02a 100644 --- a/internal/interfaces/types.go +++ b/internal/interfaces/types.go @@ -3,7 +3,7 @@ // transformation operations, maintaining compatibility with the SDK translator package. package interfaces -import sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" +import sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" // Backwards compatible aliases for translator function types. type TranslateRequestFunc = sdktranslator.RequestTransform diff --git a/internal/logging/gin_logger.go b/internal/logging/gin_logger.go index 4d6d088c..6e3559b8 100644 --- a/internal/logging/gin_logger.go +++ b/internal/logging/gin_logger.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" ) diff --git a/internal/logging/global_logger.go b/internal/logging/global_logger.go index 372222a5..4b4ef62c 100644 --- a/internal/logging/global_logger.go +++ b/internal/logging/global_logger.go @@ -10,8 +10,8 @@ import ( "sync" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" ) diff --git a/internal/logging/request_logger.go b/internal/logging/request_logger.go index 2db2a504..d650212f 100644 --- a/internal/logging/request_logger.go +++ b/internal/logging/request_logger.go @@ -22,9 +22,9 @@ import ( "github.com/klauspost/compress/zstd" log "github.com/sirupsen/logrus" - "github.com/router-for-me/CLIProxyAPI/v6/internal/buildinfo" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" ) var requestLogID atomic.Uint64 diff --git a/internal/managementasset/updater.go b/internal/managementasset/updater.go index ae2bc819..ea7ca3f5 100644 --- a/internal/managementasset/updater.go +++ b/internal/managementasset/updater.go @@ -17,9 +17,9 @@ import ( "sync/atomic" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" log "github.com/sirupsen/logrus" "golang.org/x/sync/singleflight" ) diff --git a/internal/redisqueue/plugin.go b/internal/redisqueue/plugin.go index b33bc8fd..8a99de83 100644 --- a/internal/redisqueue/plugin.go +++ b/internal/redisqueue/plugin.go @@ -6,8 +6,8 @@ import ( "strings" "time" - internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - coreusage "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" + internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" ) func init() { diff --git a/internal/redisqueue/plugin_test.go b/internal/redisqueue/plugin_test.go index 8dcade90..4d7cb465 100644 --- a/internal/redisqueue/plugin_test.go +++ b/internal/redisqueue/plugin_test.go @@ -9,8 +9,8 @@ import ( "time" "github.com/gin-gonic/gin" - internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - coreusage "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" + internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" ) func TestUsageQueuePluginPayloadIncludesStableFieldsAndSuccess(t *testing.T) { @@ -44,6 +44,7 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndSuccess(t *testing.T) { requireStringField(t, payload, "alias", "client-gpt") requireStringField(t, payload, "endpoint", "POST /v1/chat/completions") requireStringField(t, payload, "auth_type", "apikey") + requireStringField(t, payload, "user_api_key", "test-key") requireStringField(t, payload, "request_id", "ctx-request-id") requireBoolField(t, payload, "failed", false) }) @@ -80,6 +81,7 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID(t requireStringField(t, payload, "alias", "client-mini") requireStringField(t, payload, "endpoint", "GET /v1/responses") requireStringField(t, payload, "auth_type", "apikey") + requireStringField(t, payload, "user_api_key", "test-key") requireStringField(t, payload, "request_id", "gin-request-id") requireBoolField(t, payload, "failed", true) }) @@ -123,6 +125,7 @@ func TestUsageQueuePluginAsyncIgnoresRecycledGinContext(t *testing.T) { payload := waitForSinglePayload(t, 2*time.Second) requireStringField(t, payload, "endpoint", "POST /v1/chat/completions") requireStringField(t, payload, "alias", "client-gpt") + requireStringField(t, payload, "user_api_key", "test-key") requireStringField(t, payload, "request_id", "ctx-request-id") requireBoolField(t, payload, "failed", true) }) diff --git a/internal/registry/model_registry.go b/internal/registry/model_registry.go index 3f3f530d..4c215bb7 100644 --- a/internal/registry/model_registry.go +++ b/internal/registry/model_registry.go @@ -11,7 +11,7 @@ import ( "sync" "time" - misc "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" + misc "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" log "github.com/sirupsen/logrus" ) diff --git a/internal/runtime/executor/aistudio_executor.go b/internal/runtime/executor/aistudio_executor.go index 37e85377..392109b5 100644 --- a/internal/runtime/executor/aistudio_executor.go +++ b/internal/runtime/executor/aistudio_executor.go @@ -13,14 +13,14 @@ import ( "net/url" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - "github.com/router-for-me/CLIProxyAPI/v6/internal/wsrelay" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/wsrelay" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -414,7 +414,10 @@ func (e *AIStudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A } // Refresh refreshes the authentication credentials (no-op for AI Studio). -func (e *AIStudioExecutor) Refresh(_ context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { +func (e *AIStudioExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } return auth, nil } diff --git a/internal/runtime/executor/antigravity_executor.go b/internal/runtime/executor/antigravity_executor.go index 418ed7b1..84ff9de0 100644 --- a/internal/runtime/executor/antigravity_executor.go +++ b/internal/runtime/executor/antigravity_executor.go @@ -23,18 +23,18 @@ import ( "time" "github.com/google/uuid" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - antigravityclaude "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/antigravity/claude" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + antigravityclaude "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/antigravity/claude" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -1402,6 +1402,9 @@ attemptLoop: // Refresh refreshes the authentication credentials using the refresh token. func (e *AntigravityExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } if auth == nil { return auth, nil } @@ -1589,6 +1592,18 @@ func (e *AntigravityExecutor) ensureAccessToken(ctx context.Context, auth *clipr refreshCtx = context.WithValue(refreshCtx, "cliproxy.roundtripper", rt) } } + if refreshed, handled, err := helps.RefreshAuthViaHome(refreshCtx, e.cfg, auth); handled { + if err != nil { + return "", nil, err + } + token := metaStringValue(refreshed.Metadata, "access_token") + if strings.TrimSpace(token) == "" { + return "", nil, statusErr{code: http.StatusUnauthorized, msg: "missing access token"} + } + e.maybeRefreshAntigravityCreditsHint(ctx, refreshed, token) + return token, refreshed, nil + } + updated, errRefresh := e.refreshToken(refreshCtx, auth.Clone()) if errRefresh != nil { return "", nil, errRefresh diff --git a/internal/runtime/executor/antigravity_executor_buildrequest_test.go b/internal/runtime/executor/antigravity_executor_buildrequest_test.go index ed2d79e6..f0711752 100644 --- a/internal/runtime/executor/antigravity_executor_buildrequest_test.go +++ b/internal/runtime/executor/antigravity_executor_buildrequest_test.go @@ -6,7 +6,7 @@ import ( "io" "testing" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestAntigravityBuildRequest_SanitizesGeminiToolSchema(t *testing.T) { diff --git a/internal/runtime/executor/antigravity_executor_credits_test.go b/internal/runtime/executor/antigravity_executor_credits_test.go index 4569f5df..e16e6443 100644 --- a/internal/runtime/executor/antigravity_executor_credits_test.go +++ b/internal/runtime/executor/antigravity_executor_credits_test.go @@ -10,10 +10,10 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) func resetAntigravityCreditsRetryState() { diff --git a/internal/runtime/executor/antigravity_executor_signature_test.go b/internal/runtime/executor/antigravity_executor_signature_test.go index 226daf5c..7d84bfe8 100644 --- a/internal/runtime/executor/antigravity_executor_signature_test.go +++ b/internal/runtime/executor/antigravity_executor_signature_test.go @@ -10,10 +10,10 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) func testGeminiSignaturePayload() string { diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index b22f4e44..fe4f22f2 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -17,16 +17,16 @@ import ( "github.com/andybalholm/brotli" "github.com/google/uuid" "github.com/klauspost/compress/zstd" - claudeauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + claudeauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -691,6 +691,9 @@ func (e *ClaudeExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut func (e *ClaudeExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { log.Debugf("claude executor: refresh called") + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } if auth == nil { return nil, fmt.Errorf("claude executor: auth is nil") } diff --git a/internal/runtime/executor/claude_executor_test.go b/internal/runtime/executor/claude_executor_test.go index 2e914044..f5bca55a 100644 --- a/internal/runtime/executor/claude_executor_test.go +++ b/internal/runtime/executor/claude_executor_test.go @@ -17,12 +17,12 @@ import ( "github.com/gin-gonic/gin" "github.com/klauspost/compress/zstd" xxHash64 "github.com/pierrec/xxHash/xxHash64" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/runtime/executor/claude_signing.go b/internal/runtime/executor/claude_signing.go index 697a6882..060e86e8 100644 --- a/internal/runtime/executor/claude_signing.go +++ b/internal/runtime/executor/claude_signing.go @@ -6,8 +6,8 @@ import ( "strings" xxHash64 "github.com/pierrec/xxHash/xxHash64" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index 19cc8e75..36c041b6 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -11,15 +11,15 @@ import ( "strings" "time" - codexauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + codexauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -693,6 +693,9 @@ func countCodexInputTokens(enc tokenizer.Codec, body []byte) (int64, error) { func (e *CodexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { log.Debugf("codex executor: refresh called") + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } if auth == nil { return nil, statusErr{code: 500, msg: "codex executor: auth is nil"} } diff --git a/internal/runtime/executor/codex_executor_cache_test.go b/internal/runtime/executor/codex_executor_cache_test.go index 7a24fd96..cb96a902 100644 --- a/internal/runtime/executor/codex_executor_cache_test.go +++ b/internal/runtime/executor/codex_executor_cache_test.go @@ -8,15 +8,15 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFromAPIKey(t *testing.T) { recorder := httptest.NewRecorder() ginCtx, _ := gin.CreateTestContext(recorder) - ginCtx.Set("apiKey", "test-api-key") + ginCtx.Set("userApiKey", "test-api-key") ctx := context.WithValue(context.Background(), "gin", ginCtx) executor := &CodexExecutor{} diff --git a/internal/runtime/executor/codex_executor_compact_test.go b/internal/runtime/executor/codex_executor_compact_test.go index 02c6db29..549cad9e 100644 --- a/internal/runtime/executor/codex_executor_compact_test.go +++ b/internal/runtime/executor/codex_executor_compact_test.go @@ -7,10 +7,10 @@ import ( "net/http/httptest" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/codex_executor_imagegen_test.go b/internal/runtime/executor/codex_executor_imagegen_test.go index 1657209a..89d2a1c2 100644 --- a/internal/runtime/executor/codex_executor_imagegen_test.go +++ b/internal/runtime/executor/codex_executor_imagegen_test.go @@ -3,7 +3,7 @@ package executor import ( "testing" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/codex_executor_instructions_test.go b/internal/runtime/executor/codex_executor_instructions_test.go index c5dc5aa8..b3c8ac18 100644 --- a/internal/runtime/executor/codex_executor_instructions_test.go +++ b/internal/runtime/executor/codex_executor_instructions_test.go @@ -7,10 +7,10 @@ import ( "net/http/httptest" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/codex_executor_stream_output_test.go b/internal/runtime/executor/codex_executor_stream_output_test.go index a2da45e1..b814c3e9 100644 --- a/internal/runtime/executor/codex_executor_stream_output_test.go +++ b/internal/runtime/executor/codex_executor_stream_output_test.go @@ -7,11 +7,11 @@ import ( "net/http/httptest" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/codex_websockets_executor.go b/internal/runtime/executor/codex_websockets_executor.go index 94b78b66..86078aac 100644 --- a/internal/runtime/executor/codex_websockets_executor.go +++ b/internal/runtime/executor/codex_websockets_executor.go @@ -18,15 +18,15 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/gorilla/websocket" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/runtime/executor/codex_websockets_executor_store_test.go b/internal/runtime/executor/codex_websockets_executor_store_test.go index 1a23fa31..115ed066 100644 --- a/internal/runtime/executor/codex_websockets_executor_store_test.go +++ b/internal/runtime/executor/codex_websockets_executor_store_test.go @@ -3,7 +3,7 @@ package executor import ( "testing" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestCodexWebsocketsExecutor_SessionStoreSurvivesExecutorReplacement(t *testing.T) { diff --git a/internal/runtime/executor/codex_websockets_executor_test.go b/internal/runtime/executor/codex_websockets_executor_test.go index fbcf9c45..4342ed88 100644 --- a/internal/runtime/executor/codex_websockets_executor_test.go +++ b/internal/runtime/executor/codex_websockets_executor_test.go @@ -12,11 +12,11 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/gemini_cli_executor.go b/internal/runtime/executor/gemini_cli_executor.go index b6210e6a..0fa7cbb2 100644 --- a/internal/runtime/executor/gemini_cli_executor.go +++ b/internal/runtime/executor/gemini_cli_executor.go @@ -16,15 +16,15 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/geminicli" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/geminicli" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -599,7 +599,10 @@ func (e *GeminiCLIExecutor) CountTokens(ctx context.Context, auth *cliproxyauth. } // Refresh refreshes the authentication credentials (no-op for Gemini CLI). -func (e *GeminiCLIExecutor) Refresh(_ context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { +func (e *GeminiCLIExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } return auth, nil } @@ -609,37 +612,43 @@ func prepareGeminiCLITokenSource(ctx context.Context, cfg *config.Config, auth * return nil, nil, fmt.Errorf("gemini-cli auth metadata missing") } - var base map[string]any - if tokenRaw, ok := metadata["token"].(map[string]any); ok && tokenRaw != nil { - base = cloneMap(tokenRaw) - } else { - base = make(map[string]any) - } - - var token oauth2.Token - if len(base) > 0 { - if raw, err := json.Marshal(base); err == nil { - _ = json.Unmarshal(raw, &token) + buildToken := func(meta map[string]any) (map[string]any, oauth2.Token) { + var base map[string]any + if tokenRaw, ok := meta["token"].(map[string]any); ok && tokenRaw != nil { + base = cloneMap(tokenRaw) + } else { + base = make(map[string]any) } - } - if token.AccessToken == "" { - token.AccessToken = stringValue(metadata, "access_token") - } - if token.RefreshToken == "" { - token.RefreshToken = stringValue(metadata, "refresh_token") - } - if token.TokenType == "" { - token.TokenType = stringValue(metadata, "token_type") - } - if token.Expiry.IsZero() { - if expiry := stringValue(metadata, "expiry"); expiry != "" { - if ts, err := time.Parse(time.RFC3339, expiry); err == nil { - token.Expiry = ts + var token oauth2.Token + if len(base) > 0 { + if raw, err := json.Marshal(base); err == nil { + _ = json.Unmarshal(raw, &token) } } + + if token.AccessToken == "" { + token.AccessToken = stringValue(meta, "access_token") + } + if token.RefreshToken == "" { + token.RefreshToken = stringValue(meta, "refresh_token") + } + if token.TokenType == "" { + token.TokenType = stringValue(meta, "token_type") + } + if token.Expiry.IsZero() { + if expiry := stringValue(meta, "expiry"); expiry != "" { + if ts, err := time.Parse(time.RFC3339, expiry); err == nil { + token.Expiry = ts + } + } + } + + return base, token } + base, token := buildToken(metadata) + conf := &oauth2.Config{ ClientID: geminiOAuthClientID, ClientSecret: geminiOAuthClientSecret, @@ -652,6 +661,29 @@ func prepareGeminiCLITokenSource(ctx context.Context, cfg *config.Config, auth * ctxToken = context.WithValue(ctxToken, oauth2.HTTPClient, httpClient) } + if cfg != nil && cfg.Home.Enabled { + now := time.Now() + if token.AccessToken == "" || (!token.Expiry.IsZero() && token.Expiry.Before(now.Add(30*time.Second))) { + refreshed, handled, errRefresh := helps.RefreshAuthViaHome(ctx, cfg, auth) + if handled { + if errRefresh != nil { + return nil, nil, errRefresh + } + auth = refreshed + metadata = geminiOAuthMetadata(auth) + if metadata == nil { + return nil, nil, fmt.Errorf("gemini-cli auth metadata missing") + } + base, token = buildToken(metadata) + } + } + if token.AccessToken == "" { + return nil, nil, fmt.Errorf("gemini-cli access token missing") + } + updateGeminiCLITokenMetadata(auth, base, &token) + return oauth2.StaticTokenSource(&token), base, nil + } + src := conf.TokenSource(ctxToken, &token) currentToken, err := src.Token() if err != nil { diff --git a/internal/runtime/executor/gemini_executor.go b/internal/runtime/executor/gemini_executor.go index 2a6e9a6e..c3f08010 100644 --- a/internal/runtime/executor/gemini_executor.go +++ b/internal/runtime/executor/gemini_executor.go @@ -12,13 +12,13 @@ import ( "net/http" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -437,7 +437,10 @@ func (e *GeminiExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut } // Refresh refreshes the authentication credentials (no-op for Gemini API key). -func (e *GeminiExecutor) Refresh(_ context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { +func (e *GeminiExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } return auth, nil } diff --git a/internal/runtime/executor/gemini_vertex_executor.go b/internal/runtime/executor/gemini_vertex_executor.go index 17a93d51..ae0a718b 100644 --- a/internal/runtime/executor/gemini_vertex_executor.go +++ b/internal/runtime/executor/gemini_vertex_executor.go @@ -14,14 +14,14 @@ import ( "strings" "time" - vertexauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/vertex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + vertexauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/vertex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -294,7 +294,10 @@ func (e *GeminiVertexExecutor) CountTokens(ctx context.Context, auth *cliproxyau } // Refresh refreshes the authentication credentials (no-op for Vertex). -func (e *GeminiVertexExecutor) Refresh(_ context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { +func (e *GeminiVertexExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } return auth, nil } diff --git a/internal/runtime/executor/helps/claude_device_profile.go b/internal/runtime/executor/helps/claude_device_profile.go index 154901b5..09f04929 100644 --- a/internal/runtime/executor/helps/claude_device_profile.go +++ b/internal/runtime/executor/helps/claude_device_profile.go @@ -11,8 +11,8 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) const ( diff --git a/internal/runtime/executor/helps/home_refresh.go b/internal/runtime/executor/helps/home_refresh.go new file mode 100644 index 00000000..e52fdd24 --- /dev/null +++ b/internal/runtime/executor/helps/home_refresh.go @@ -0,0 +1,91 @@ +package helps + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/home" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" +) + +type homeStatusErr struct { + code int + msg string +} + +func (e homeStatusErr) Error() string { + if e.msg != "" { + return e.msg + } + return fmt.Sprintf("status %d", e.code) +} + +func (e homeStatusErr) StatusCode() int { return e.code } + +type homeErrorEnvelope struct { + Error *homeErrorDetail `json:"error"` +} + +type homeErrorDetail struct { + Type string `json:"type"` + Message string `json:"message"` + Code string `json:"code,omitempty"` +} + +// RefreshAuthViaHome replaces local refresh logic when home control plane integration is enabled. +// It returns (updatedAuth, true, nil) when home refresh succeeds; (nil, true, err) when home is +// enabled but refresh fails; and (nil, false, nil) when home is disabled. +func RefreshAuthViaHome(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, bool, error) { + if cfg == nil || !cfg.Home.Enabled { + return nil, false, nil + } + if ctx == nil { + ctx = context.Background() + } + if auth == nil { + return nil, true, homeStatusErr{code: http.StatusInternalServerError, msg: "home refresh: auth is nil"} + } + + client := home.Current() + if client == nil || !client.HeartbeatOK() { + return nil, true, homeStatusErr{code: http.StatusServiceUnavailable, msg: "home control center unavailable"} + } + + authIndex := strings.TrimSpace(auth.Index) + if authIndex == "" { + authIndex = strings.TrimSpace(auth.EnsureIndex()) + } + if authIndex == "" { + return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: "home refresh: auth_index is empty"} + } + + raw, err := client.GetRefreshAuth(ctx, authIndex) + if err != nil { + return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: err.Error()} + } + + var env homeErrorEnvelope + if errUnmarshal := json.Unmarshal(raw, &env); errUnmarshal == nil && env.Error != nil { + code := strings.TrimSpace(env.Error.Type) + if code == "" { + code = strings.TrimSpace(env.Error.Code) + } + msg := strings.TrimSpace(env.Error.Message) + if msg == "" { + msg = "home returned error" + } + return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: msg} + } + + var updated cliproxyauth.Auth + if errUnmarshal := json.Unmarshal(raw, &updated); errUnmarshal != nil { + return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: "home returned invalid auth payload"} + } + updated.Index = authIndex + updated.EnsureIndex() + return &updated, true, nil +} diff --git a/internal/runtime/executor/helps/logging_helpers.go b/internal/runtime/executor/helps/logging_helpers.go index a0b30f70..fa714334 100644 --- a/internal/runtime/executor/helps/logging_helpers.go +++ b/internal/runtime/executor/helps/logging_helpers.go @@ -12,9 +12,9 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/helps/payload_helpers.go b/internal/runtime/executor/helps/payload_helpers.go index d6baba27..af69a488 100644 --- a/internal/runtime/executor/helps/payload_helpers.go +++ b/internal/runtime/executor/helps/payload_helpers.go @@ -4,9 +4,9 @@ import ( "encoding/json" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/runtime/executor/helps/payload_helpers_disable_image_generation_test.go b/internal/runtime/executor/helps/payload_helpers_disable_image_generation_test.go index 6fd3a0e0..0faf012b 100644 --- a/internal/runtime/executor/helps/payload_helpers_disable_image_generation_test.go +++ b/internal/runtime/executor/helps/payload_helpers_disable_image_generation_test.go @@ -3,7 +3,7 @@ package helps import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/tidwall/gjson" ) diff --git a/internal/runtime/executor/helps/proxy_helpers.go b/internal/runtime/executor/helps/proxy_helpers.go index 022bc65c..91fdc9be 100644 --- a/internal/runtime/executor/helps/proxy_helpers.go +++ b/internal/runtime/executor/helps/proxy_helpers.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" ) diff --git a/internal/runtime/executor/helps/proxy_helpers_test.go b/internal/runtime/executor/helps/proxy_helpers_test.go index 33117167..fb57b6b7 100644 --- a/internal/runtime/executor/helps/proxy_helpers_test.go +++ b/internal/runtime/executor/helps/proxy_helpers_test.go @@ -5,9 +5,9 @@ import ( "net/http" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestNewProxyAwareHTTPClientDirectBypassesGlobalProxy(t *testing.T) { diff --git a/internal/runtime/executor/helps/thinking_providers.go b/internal/runtime/executor/helps/thinking_providers.go index bbd01962..a776136f 100644 --- a/internal/runtime/executor/helps/thinking_providers.go +++ b/internal/runtime/executor/helps/thinking_providers.go @@ -1,11 +1,11 @@ package helps import ( - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/antigravity" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/codex" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/geminicli" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/kimi" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/openai" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/antigravity" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/codex" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/geminicli" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/kimi" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/openai" ) diff --git a/internal/runtime/executor/helps/usage_helpers.go b/internal/runtime/executor/helps/usage_helpers.go index 312a1d35..c72b5c1a 100644 --- a/internal/runtime/executor/helps/usage_helpers.go +++ b/internal/runtime/executor/helps/usage_helpers.go @@ -9,8 +9,8 @@ import ( "time" "github.com/gin-gonic/gin" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -177,7 +177,7 @@ func APIKeyFromContext(ctx context.Context) string { if !ok || ginCtx == nil { return "" } - if v, exists := ginCtx.Get("apiKey"); exists { + if v, exists := ginCtx.Get("userApiKey"); exists { switch value := v.(type) { case string: return value diff --git a/internal/runtime/executor/helps/usage_helpers_test.go b/internal/runtime/executor/helps/usage_helpers_test.go index ef2c7de5..840f4223 100644 --- a/internal/runtime/executor/helps/usage_helpers_test.go +++ b/internal/runtime/executor/helps/usage_helpers_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" ) func TestParseOpenAIUsageChatCompletions(t *testing.T) { diff --git a/internal/runtime/executor/helps/utls_client.go b/internal/runtime/executor/helps/utls_client.go index 39512a58..29174e47 100644 --- a/internal/runtime/executor/helps/utls_client.go +++ b/internal/runtime/executor/helps/utls_client.go @@ -8,9 +8,9 @@ import ( "time" tls "github.com/refraction-networking/utls" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" "golang.org/x/net/http2" "golang.org/x/net/proxy" diff --git a/internal/runtime/executor/kimi_executor.go b/internal/runtime/executor/kimi_executor.go index 93125d9f..f330321f 100644 --- a/internal/runtime/executor/kimi_executor.go +++ b/internal/runtime/executor/kimi_executor.go @@ -13,14 +13,14 @@ import ( "strings" "time" - kimiauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kimi" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + kimiauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/kimi" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -569,6 +569,9 @@ func fallbackAssistantReasoning(msg gjson.Result, hasLatest bool, latest string) // Refresh refreshes the Kimi token using the refresh token. func (e *KimiExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { log.Debugf("kimi executor: refresh called") + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } if auth == nil { return nil, fmt.Errorf("kimi executor: auth is nil") } diff --git a/internal/runtime/executor/openai_compat_executor.go b/internal/runtime/executor/openai_compat_executor.go index 7e81637c..de12da37 100644 --- a/internal/runtime/executor/openai_compat_executor.go +++ b/internal/runtime/executor/openai_compat_executor.go @@ -10,13 +10,13 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor/helps" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/sjson" ) @@ -374,7 +374,9 @@ func (e *OpenAICompatExecutor) CountTokens(ctx context.Context, auth *cliproxyau // Refresh is a no-op for API-key based compatibility providers. func (e *OpenAICompatExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) { log.Debugf("openai compat executor: refresh called") - _ = ctx + if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled { + return refreshed, err + } return auth, nil } diff --git a/internal/runtime/executor/openai_compat_executor_compact_test.go b/internal/runtime/executor/openai_compat_executor_compact_test.go index 49b2cccb..3aab5c9b 100644 --- a/internal/runtime/executor/openai_compat_executor_compact_test.go +++ b/internal/runtime/executor/openai_compat_executor_compact_test.go @@ -8,10 +8,10 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) diff --git a/internal/store/gitstore.go b/internal/store/gitstore.go index bd84d99a..1610211a 100644 --- a/internal/store/gitstore.go +++ b/internal/store/gitstore.go @@ -18,7 +18,7 @@ import ( "github.com/go-git/go-git/v6/plumbing/object" "github.com/go-git/go-git/v6/plumbing/transport" "github.com/go-git/go-git/v6/plumbing/transport/http" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // gcInterval defines minimum time between garbage collection runs. diff --git a/internal/store/objectstore.go b/internal/store/objectstore.go index a33f6ef8..aa346a13 100644 --- a/internal/store/objectstore.go +++ b/internal/store/objectstore.go @@ -17,8 +17,8 @@ import ( "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/store/postgresstore.go b/internal/store/postgresstore.go index 527b25cc..610fc5b6 100644 --- a/internal/store/postgresstore.go +++ b/internal/store/postgresstore.go @@ -14,8 +14,8 @@ import ( "time" _ "github.com/jackc/pgx/v5/stdlib" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/thinking/apply.go b/internal/thinking/apply.go index 1edeac87..d422a8d8 100644 --- a/internal/thinking/apply.go +++ b/internal/thinking/apply.go @@ -4,7 +4,7 @@ package thinking import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/internal/thinking/apply_user_defined_test.go b/internal/thinking/apply_user_defined_test.go index aa24ab8e..c485d252 100644 --- a/internal/thinking/apply_user_defined_test.go +++ b/internal/thinking/apply_user_defined_test.go @@ -3,9 +3,9 @@ package thinking_test import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/claude" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/claude" "github.com/tidwall/gjson" ) diff --git a/internal/thinking/convert.go b/internal/thinking/convert.go index b22a0879..31945daa 100644 --- a/internal/thinking/convert.go +++ b/internal/thinking/convert.go @@ -3,7 +3,7 @@ package thinking import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" ) // levelToBudgetMap defines the standard Level → Budget mapping. diff --git a/internal/thinking/provider/antigravity/apply.go b/internal/thinking/provider/antigravity/apply.go index d202035f..0a8f1c45 100644 --- a/internal/thinking/provider/antigravity/apply.go +++ b/internal/thinking/provider/antigravity/apply.go @@ -9,8 +9,8 @@ package antigravity import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/provider/claude/apply.go b/internal/thinking/provider/claude/apply.go index 275be469..140a8135 100644 --- a/internal/thinking/provider/claude/apply.go +++ b/internal/thinking/provider/claude/apply.go @@ -9,8 +9,8 @@ package claude import ( - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/provider/codex/apply.go b/internal/thinking/provider/codex/apply.go index 0f336359..83f5ae84 100644 --- a/internal/thinking/provider/codex/apply.go +++ b/internal/thinking/provider/codex/apply.go @@ -7,8 +7,8 @@ package codex import ( - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/provider/gemini/apply.go b/internal/thinking/provider/gemini/apply.go index 39bb4231..8e6e83f3 100644 --- a/internal/thinking/provider/gemini/apply.go +++ b/internal/thinking/provider/gemini/apply.go @@ -12,8 +12,8 @@ package gemini import ( - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/provider/geminicli/apply.go b/internal/thinking/provider/geminicli/apply.go index 5908b6bc..e9311e8c 100644 --- a/internal/thinking/provider/geminicli/apply.go +++ b/internal/thinking/provider/geminicli/apply.go @@ -5,8 +5,8 @@ package geminicli import ( - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/provider/kimi/apply.go b/internal/thinking/provider/kimi/apply.go index ff47c46d..ea3ed572 100644 --- a/internal/thinking/provider/kimi/apply.go +++ b/internal/thinking/provider/kimi/apply.go @@ -7,8 +7,8 @@ package kimi import ( "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/provider/kimi/apply_test.go b/internal/thinking/provider/kimi/apply_test.go index 707f11c7..78069424 100644 --- a/internal/thinking/provider/kimi/apply_test.go +++ b/internal/thinking/provider/kimi/apply_test.go @@ -3,8 +3,8 @@ package kimi import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" ) diff --git a/internal/thinking/provider/openai/apply.go b/internal/thinking/provider/openai/apply.go index c77c1ab8..1e87b72b 100644 --- a/internal/thinking/provider/openai/apply.go +++ b/internal/thinking/provider/openai/apply.go @@ -6,8 +6,8 @@ package openai import ( - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/thinking/types.go b/internal/thinking/types.go index a31d7981..39868a02 100644 --- a/internal/thinking/types.go +++ b/internal/thinking/types.go @@ -4,7 +4,7 @@ // thinking configurations across various AI providers (Claude, Gemini, OpenAI, Codex, Antigravity, Kimi). package thinking -import "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" +import "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" // ThinkingMode represents the type of thinking configuration mode. type ThinkingMode int diff --git a/internal/thinking/validate.go b/internal/thinking/validate.go index 4a3ca97c..2baa93f1 100644 --- a/internal/thinking/validate.go +++ b/internal/thinking/validate.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" log "github.com/sirupsen/logrus" ) diff --git a/internal/translator/antigravity/claude/antigravity_claude_request.go b/internal/translator/antigravity/claude/antigravity_claude_request.go index 8ae69648..7f36b11c 100644 --- a/internal/translator/antigravity/claude/antigravity_claude_request.go +++ b/internal/translator/antigravity/claude/antigravity_claude_request.go @@ -8,10 +8,10 @@ package claude import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/antigravity/claude/antigravity_claude_request_test.go b/internal/translator/antigravity/claude/antigravity_claude_request_test.go index 919e2906..bb3cdf4f 100644 --- a/internal/translator/antigravity/claude/antigravity_claude_request_test.go +++ b/internal/translator/antigravity/claude/antigravity_claude_request_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" "github.com/tidwall/gjson" "google.golang.org/protobuf/encoding/protowire" ) diff --git a/internal/translator/antigravity/claude/antigravity_claude_response.go b/internal/translator/antigravity/claude/antigravity_claude_response.go index 17a31f21..427551df 100644 --- a/internal/translator/antigravity/claude/antigravity_claude_response.go +++ b/internal/translator/antigravity/claude/antigravity_claude_response.go @@ -15,9 +15,9 @@ import ( "sync/atomic" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" diff --git a/internal/translator/antigravity/claude/antigravity_claude_response_test.go b/internal/translator/antigravity/claude/antigravity_claude_response_test.go index 05a3df89..1490ab3c 100644 --- a/internal/translator/antigravity/claude/antigravity_claude_response_test.go +++ b/internal/translator/antigravity/claude/antigravity_claude_response_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" ) // ============================================================================ diff --git a/internal/translator/antigravity/claude/init.go b/internal/translator/antigravity/claude/init.go index 21fe0b26..4d9bd721 100644 --- a/internal/translator/antigravity/claude/init.go +++ b/internal/translator/antigravity/claude/init.go @@ -1,9 +1,9 @@ package claude import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/antigravity/claude/signature_validation.go b/internal/translator/antigravity/claude/signature_validation.go index 63203abd..f82fc2e3 100644 --- a/internal/translator/antigravity/claude/signature_validation.go +++ b/internal/translator/antigravity/claude/signature_validation.go @@ -53,7 +53,7 @@ import ( "strings" "unicode/utf8" - "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" + "github.com/router-for-me/CLIProxyAPI/v7/internal/cache" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "google.golang.org/protobuf/encoding/protowire" diff --git a/internal/translator/antigravity/gemini/antigravity_gemini_request.go b/internal/translator/antigravity/gemini/antigravity_gemini_request.go index 3612c0fb..b33b9c40 100644 --- a/internal/translator/antigravity/gemini/antigravity_gemini_request.go +++ b/internal/translator/antigravity/gemini/antigravity_gemini_request.go @@ -9,8 +9,8 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/antigravity/gemini/antigravity_gemini_response.go b/internal/translator/antigravity/gemini/antigravity_gemini_response.go index 7b43c48d..b0deb732 100644 --- a/internal/translator/antigravity/gemini/antigravity_gemini_response.go +++ b/internal/translator/antigravity/gemini/antigravity_gemini_response.go @@ -9,7 +9,7 @@ import ( "bytes" "context" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/antigravity/gemini/init.go b/internal/translator/antigravity/gemini/init.go index 39558248..dcb33161 100644 --- a/internal/translator/antigravity/gemini/init.go +++ b/internal/translator/antigravity/gemini/init.go @@ -1,9 +1,9 @@ package gemini import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go b/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go index b33be50b..0d9ee6fe 100644 --- a/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go +++ b/internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go @@ -6,9 +6,9 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/antigravity/openai/chat-completions/antigravity_openai_response.go b/internal/translator/antigravity/openai/chat-completions/antigravity_openai_response.go index 9188c75a..2be24102 100644 --- a/internal/translator/antigravity/openai/chat-completions/antigravity_openai_response.go +++ b/internal/translator/antigravity/openai/chat-completions/antigravity_openai_response.go @@ -13,10 +13,10 @@ import ( "sync/atomic" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/chat-completions" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/antigravity/openai/chat-completions/init.go b/internal/translator/antigravity/openai/chat-completions/init.go index 5c5c71e4..2217e791 100644 --- a/internal/translator/antigravity/openai/chat-completions/init.go +++ b/internal/translator/antigravity/openai/chat-completions/init.go @@ -1,9 +1,9 @@ package chat_completions import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/antigravity/openai/responses/antigravity_openai-responses_request.go b/internal/translator/antigravity/openai/responses/antigravity_openai-responses_request.go index 90bfa14c..94a6b852 100644 --- a/internal/translator/antigravity/openai/responses/antigravity_openai-responses_request.go +++ b/internal/translator/antigravity/openai/responses/antigravity_openai-responses_request.go @@ -1,8 +1,8 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/antigravity/gemini" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/antigravity/gemini" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/responses" ) func ConvertOpenAIResponsesRequestToAntigravity(modelName string, inputRawJSON []byte, stream bool) []byte { diff --git a/internal/translator/antigravity/openai/responses/antigravity_openai-responses_response.go b/internal/translator/antigravity/openai/responses/antigravity_openai-responses_response.go index a087e0bd..32569504 100644 --- a/internal/translator/antigravity/openai/responses/antigravity_openai-responses_response.go +++ b/internal/translator/antigravity/openai/responses/antigravity_openai-responses_response.go @@ -3,7 +3,7 @@ package responses import ( "context" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/responses" "github.com/tidwall/gjson" ) diff --git a/internal/translator/antigravity/openai/responses/init.go b/internal/translator/antigravity/openai/responses/init.go index 8d137032..49041f29 100644 --- a/internal/translator/antigravity/openai/responses/init.go +++ b/internal/translator/antigravity/openai/responses/init.go @@ -1,9 +1,9 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/claude/gemini-cli/claude_gemini-cli_request.go b/internal/translator/claude/gemini-cli/claude_gemini-cli_request.go index 831d784d..fd68a957 100644 --- a/internal/translator/claude/gemini-cli/claude_gemini-cli_request.go +++ b/internal/translator/claude/gemini-cli/claude_gemini-cli_request.go @@ -6,7 +6,7 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/claude/gemini" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/claude/gemini" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/claude/gemini-cli/claude_gemini-cli_response.go b/internal/translator/claude/gemini-cli/claude_gemini-cli_response.go index 62e2650f..858886c2 100644 --- a/internal/translator/claude/gemini-cli/claude_gemini-cli_response.go +++ b/internal/translator/claude/gemini-cli/claude_gemini-cli_response.go @@ -7,8 +7,8 @@ package geminiCLI import ( "context" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/claude/gemini" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/claude/gemini" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" ) // ConvertClaudeResponseToGeminiCLI converts Claude Code streaming response format to Gemini CLI format. diff --git a/internal/translator/claude/gemini-cli/init.go b/internal/translator/claude/gemini-cli/init.go index ca364a6e..33a1332d 100644 --- a/internal/translator/claude/gemini-cli/init.go +++ b/internal/translator/claude/gemini-cli/init.go @@ -1,9 +1,9 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/claude/gemini/claude_gemini_request.go b/internal/translator/claude/gemini/claude_gemini_request.go index d2a215e7..d716d28f 100644 --- a/internal/translator/claude/gemini/claude_gemini_request.go +++ b/internal/translator/claude/gemini/claude_gemini_request.go @@ -14,9 +14,9 @@ import ( "strings" "github.com/google/uuid" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/claude/gemini/claude_gemini_response.go b/internal/translator/claude/gemini/claude_gemini_response.go index 846c2605..3f127e32 100644 --- a/internal/translator/claude/gemini/claude_gemini_response.go +++ b/internal/translator/claude/gemini/claude_gemini_response.go @@ -12,7 +12,7 @@ import ( "strings" "time" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/claude/gemini/init.go b/internal/translator/claude/gemini/init.go index 8924f62c..0ed533ce 100644 --- a/internal/translator/claude/gemini/init.go +++ b/internal/translator/claude/gemini/init.go @@ -1,9 +1,9 @@ package gemini import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/claude/openai/chat-completions/claude_openai_request.go b/internal/translator/claude/openai/chat-completions/claude_openai_request.go index e9d8d35b..bad56d12 100644 --- a/internal/translator/claude/openai/chat-completions/claude_openai_request.go +++ b/internal/translator/claude/openai/chat-completions/claude_openai_request.go @@ -14,8 +14,8 @@ import ( "strings" "github.com/google/uuid" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/claude/openai/chat-completions/init.go b/internal/translator/claude/openai/chat-completions/init.go index a18840ba..7474fb2a 100644 --- a/internal/translator/claude/openai/chat-completions/init.go +++ b/internal/translator/claude/openai/chat-completions/init.go @@ -1,9 +1,9 @@ package chat_completions import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/claude/openai/responses/claude_openai-responses_request.go b/internal/translator/claude/openai/responses/claude_openai-responses_request.go index c0479b87..13987495 100644 --- a/internal/translator/claude/openai/responses/claude_openai-responses_request.go +++ b/internal/translator/claude/openai/responses/claude_openai-responses_request.go @@ -9,8 +9,8 @@ import ( "strings" "github.com/google/uuid" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/claude/openai/responses/claude_openai-responses_response.go b/internal/translator/claude/openai/responses/claude_openai-responses_response.go index 10d12c99..6c6b96b3 100644 --- a/internal/translator/claude/openai/responses/claude_openai-responses_response.go +++ b/internal/translator/claude/openai/responses/claude_openai-responses_response.go @@ -8,7 +8,7 @@ import ( "strings" "time" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/claude/openai/responses/init.go b/internal/translator/claude/openai/responses/init.go index 595fecc6..575c9ec7 100644 --- a/internal/translator/claude/openai/responses/init.go +++ b/internal/translator/claude/openai/responses/init.go @@ -1,9 +1,9 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/codex/claude/codex_claude_request.go b/internal/translator/codex/claude/codex_claude_request.go index 1e168f09..029db14e 100644 --- a/internal/translator/codex/claude/codex_claude_request.go +++ b/internal/translator/codex/claude/codex_claude_request.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/codex/claude/codex_claude_response.go b/internal/translator/codex/claude/codex_claude_response.go index a401a1b7..7a40ca4c 100644 --- a/internal/translator/codex/claude/codex_claude_response.go +++ b/internal/translator/codex/claude/codex_claude_response.go @@ -11,8 +11,8 @@ import ( "context" "strings" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/codex/claude/init.go b/internal/translator/codex/claude/init.go index 7126edc3..af44b9dd 100644 --- a/internal/translator/codex/claude/init.go +++ b/internal/translator/codex/claude/init.go @@ -1,9 +1,9 @@ package claude import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/codex/gemini-cli/codex_gemini-cli_request.go b/internal/translator/codex/gemini-cli/codex_gemini-cli_request.go index 8b32453d..b69bab11 100644 --- a/internal/translator/codex/gemini-cli/codex_gemini-cli_request.go +++ b/internal/translator/codex/gemini-cli/codex_gemini-cli_request.go @@ -6,7 +6,7 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/gemini" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/gemini" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/codex/gemini-cli/codex_gemini-cli_response.go b/internal/translator/codex/gemini-cli/codex_gemini-cli_response.go index 0f0068c8..01dbc0f8 100644 --- a/internal/translator/codex/gemini-cli/codex_gemini-cli_response.go +++ b/internal/translator/codex/gemini-cli/codex_gemini-cli_response.go @@ -7,8 +7,8 @@ package geminiCLI import ( "context" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/gemini" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/gemini" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" ) // ConvertCodexResponseToGeminiCLI converts Codex streaming response format to Gemini CLI format. diff --git a/internal/translator/codex/gemini-cli/init.go b/internal/translator/codex/gemini-cli/init.go index 8bcd3de5..2958e0a8 100644 --- a/internal/translator/codex/gemini-cli/init.go +++ b/internal/translator/codex/gemini-cli/init.go @@ -1,9 +1,9 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/codex/gemini/codex_gemini_request.go b/internal/translator/codex/gemini/codex_gemini_request.go index 37399700..5789890f 100644 --- a/internal/translator/codex/gemini/codex_gemini_request.go +++ b/internal/translator/codex/gemini/codex_gemini_request.go @@ -12,8 +12,8 @@ import ( "strconv" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/codex/gemini/codex_gemini_response.go b/internal/translator/codex/gemini/codex_gemini_response.go index a2e4e20e..ecf9cf4d 100644 --- a/internal/translator/codex/gemini/codex_gemini_response.go +++ b/internal/translator/codex/gemini/codex_gemini_response.go @@ -11,7 +11,7 @@ import ( "strings" "time" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/codex/gemini/init.go b/internal/translator/codex/gemini/init.go index 41d30559..b670d8d9 100644 --- a/internal/translator/codex/gemini/init.go +++ b/internal/translator/codex/gemini/init.go @@ -1,9 +1,9 @@ package gemini import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/codex/openai/chat-completions/init.go b/internal/translator/codex/openai/chat-completions/init.go index 8f782fda..94db2a7d 100644 --- a/internal/translator/codex/openai/chat-completions/init.go +++ b/internal/translator/codex/openai/chat-completions/init.go @@ -1,9 +1,9 @@ package chat_completions import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/codex/openai/responses/init.go b/internal/translator/codex/openai/responses/init.go index cab759f2..24e7e356 100644 --- a/internal/translator/codex/openai/responses/init.go +++ b/internal/translator/codex/openai/responses/init.go @@ -1,9 +1,9 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini-cli/claude/gemini-cli_claude_request.go b/internal/translator/gemini-cli/claude/gemini-cli_claude_request.go index 57ebbc2c..3e77b3f7 100644 --- a/internal/translator/gemini-cli/claude/gemini-cli_claude_request.go +++ b/internal/translator/gemini-cli/claude/gemini-cli_claude_request.go @@ -8,8 +8,8 @@ package claude import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini-cli/claude/gemini-cli_claude_response.go b/internal/translator/gemini-cli/claude/gemini-cli_claude_response.go index 0bf4d622..607d6b9f 100644 --- a/internal/translator/gemini-cli/claude/gemini-cli_claude_response.go +++ b/internal/translator/gemini-cli/claude/gemini-cli_claude_response.go @@ -14,8 +14,8 @@ import ( "sync/atomic" "time" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini-cli/claude/init.go b/internal/translator/gemini-cli/claude/init.go index 79ed03c6..fa2fabdf 100644 --- a/internal/translator/gemini-cli/claude/init.go +++ b/internal/translator/gemini-cli/claude/init.go @@ -1,9 +1,9 @@ package claude import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini-cli/gemini/gemini-cli_gemini_request.go b/internal/translator/gemini-cli/gemini/gemini-cli_gemini_request.go index 9bdce339..83dc6260 100644 --- a/internal/translator/gemini-cli/gemini/gemini-cli_gemini_request.go +++ b/internal/translator/gemini-cli/gemini/gemini-cli_gemini_request.go @@ -9,8 +9,8 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/gemini-cli/gemini/gemini-cli_gemini_response.go b/internal/translator/gemini-cli/gemini/gemini-cli_gemini_response.go index 8e23f1d3..0e100c14 100644 --- a/internal/translator/gemini-cli/gemini/gemini-cli_gemini_response.go +++ b/internal/translator/gemini-cli/gemini/gemini-cli_gemini_response.go @@ -9,7 +9,7 @@ import ( "bytes" "context" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini-cli/gemini/init.go b/internal/translator/gemini-cli/gemini/init.go index fbad4ab5..1c2f38f2 100644 --- a/internal/translator/gemini-cli/gemini/init.go +++ b/internal/translator/gemini-cli/gemini/init.go @@ -1,9 +1,9 @@ package gemini import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go index 95bca2d7..1aa3132b 100644 --- a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go +++ b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go @@ -6,9 +6,9 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_response.go b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_response.go index 0947371a..92604058 100644 --- a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_response.go +++ b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_response.go @@ -13,8 +13,8 @@ import ( "sync/atomic" "time" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/chat-completions" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/gemini-cli/openai/chat-completions/init.go b/internal/translator/gemini-cli/openai/chat-completions/init.go index 3bd76c51..fcd85f24 100644 --- a/internal/translator/gemini-cli/openai/chat-completions/init.go +++ b/internal/translator/gemini-cli/openai/chat-completions/init.go @@ -1,9 +1,9 @@ package chat_completions import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.go b/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.go index 657e45fd..bea4b7a1 100644 --- a/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.go +++ b/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_request.go @@ -1,8 +1,8 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-cli/gemini" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini-cli/gemini" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/responses" ) func ConvertOpenAIResponsesRequestToGeminiCLI(modelName string, inputRawJSON []byte, stream bool) []byte { diff --git a/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.go b/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.go index 9bb3ced9..29db8c19 100644 --- a/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.go +++ b/internal/translator/gemini-cli/openai/responses/gemini-cli_openai-responses_response.go @@ -3,7 +3,7 @@ package responses import ( "context" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/responses" "github.com/tidwall/gjson" ) diff --git a/internal/translator/gemini-cli/openai/responses/init.go b/internal/translator/gemini-cli/openai/responses/init.go index b25d6708..e1d43771 100644 --- a/internal/translator/gemini-cli/openai/responses/init.go +++ b/internal/translator/gemini-cli/openai/responses/init.go @@ -1,9 +1,9 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini/claude/gemini_claude_request.go b/internal/translator/gemini/claude/gemini_claude_request.go index e230f5fd..454668cb 100644 --- a/internal/translator/gemini/claude/gemini_claude_request.go +++ b/internal/translator/gemini/claude/gemini_claude_request.go @@ -9,9 +9,9 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini/claude/gemini_claude_response.go b/internal/translator/gemini/claude/gemini_claude_response.go index 28722de1..797636d8 100644 --- a/internal/translator/gemini/claude/gemini_claude_response.go +++ b/internal/translator/gemini/claude/gemini_claude_response.go @@ -13,8 +13,8 @@ import ( "strings" "sync/atomic" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini/claude/init.go b/internal/translator/gemini/claude/init.go index 66fe51e7..d0314095 100644 --- a/internal/translator/gemini/claude/init.go +++ b/internal/translator/gemini/claude/init.go @@ -1,9 +1,9 @@ package claude import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini/gemini-cli/gemini_gemini-cli_request.go b/internal/translator/gemini/gemini-cli/gemini_gemini-cli_request.go index 1b2cdb46..71e7b4a5 100644 --- a/internal/translator/gemini/gemini-cli/gemini_gemini-cli_request.go +++ b/internal/translator/gemini/gemini-cli/gemini_gemini-cli_request.go @@ -8,8 +8,8 @@ package geminiCLI import ( "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go b/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go index d15ea21a..36fa0d39 100644 --- a/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go +++ b/internal/translator/gemini/gemini-cli/gemini_gemini-cli_response.go @@ -8,7 +8,7 @@ import ( "bytes" "context" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini/gemini-cli/init.go b/internal/translator/gemini/gemini-cli/init.go index 2c2224f7..ed18b5f0 100644 --- a/internal/translator/gemini/gemini-cli/init.go +++ b/internal/translator/gemini/gemini-cli/init.go @@ -1,9 +1,9 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini/gemini/gemini_gemini_request.go b/internal/translator/gemini/gemini/gemini_gemini_request.go index abc176b2..35e22d71 100644 --- a/internal/translator/gemini/gemini/gemini_gemini_request.go +++ b/internal/translator/gemini/gemini/gemini_gemini_request.go @@ -7,8 +7,8 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/gemini/gemini/gemini_gemini_response.go b/internal/translator/gemini/gemini/gemini_gemini_response.go index 242dd980..74669a7e 100644 --- a/internal/translator/gemini/gemini/gemini_gemini_response.go +++ b/internal/translator/gemini/gemini/gemini_gemini_response.go @@ -4,7 +4,7 @@ import ( "bytes" "context" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" ) // PassthroughGeminiResponseStream forwards Gemini responses unchanged. diff --git a/internal/translator/gemini/gemini/init.go b/internal/translator/gemini/gemini/init.go index 28c97083..ca9de2c6 100644 --- a/internal/translator/gemini/gemini/init.go +++ b/internal/translator/gemini/gemini/init.go @@ -1,9 +1,9 @@ package gemini import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) // Register a no-op response translator and a request normalizer for Gemini→Gemini. diff --git a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go index c0c4d329..20eaec76 100644 --- a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go +++ b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go @@ -6,9 +6,9 @@ import ( "fmt" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/gemini/openai/chat-completions/gemini_openai_response.go b/internal/translator/gemini/openai/chat-completions/gemini_openai_response.go index 3dc5b095..cc9117f9 100644 --- a/internal/translator/gemini/openai/chat-completions/gemini_openai_response.go +++ b/internal/translator/gemini/openai/chat-completions/gemini_openai_response.go @@ -13,7 +13,7 @@ import ( "sync/atomic" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/internal/translator/gemini/openai/chat-completions/init.go b/internal/translator/gemini/openai/chat-completions/init.go index 800e07db..2eb67331 100644 --- a/internal/translator/gemini/openai/chat-completions/init.go +++ b/internal/translator/gemini/openai/chat-completions/init.go @@ -1,9 +1,9 @@ package chat_completions import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go index 8f3a59fa..e7417576 100644 --- a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go +++ b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go @@ -4,8 +4,8 @@ import ( "encoding/json" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini/openai/responses/gemini_openai-responses_response.go b/internal/translator/gemini/openai/responses/gemini_openai-responses_response.go index 15729aae..36d30df7 100644 --- a/internal/translator/gemini/openai/responses/gemini_openai-responses_response.go +++ b/internal/translator/gemini/openai/responses/gemini_openai-responses_response.go @@ -8,8 +8,8 @@ import ( "sync/atomic" "time" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/gemini/openai/responses/init.go b/internal/translator/gemini/openai/responses/init.go index b53cac3d..404dd68a 100644 --- a/internal/translator/gemini/openai/responses/init.go +++ b/internal/translator/gemini/openai/responses/init.go @@ -1,9 +1,9 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/init.go b/internal/translator/init.go index 084ea7ac..5f88a400 100644 --- a/internal/translator/init.go +++ b/internal/translator/init.go @@ -1,36 +1,36 @@ package translator import ( - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/claude/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/claude/gemini-cli" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/claude/openai/chat-completions" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/claude/openai/responses" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/claude/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/claude/gemini-cli" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/claude/openai/chat-completions" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/claude/openai/responses" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/gemini-cli" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/openai/chat-completions" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/codex/openai/responses" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/gemini-cli" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/openai/chat-completions" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/codex/openai/responses" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-cli/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-cli/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-cli/openai/chat-completions" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini-cli/openai/responses" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini-cli/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini-cli/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini-cli/openai/chat-completions" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini-cli/openai/responses" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/gemini-cli" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/chat-completions" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/openai/responses" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/gemini-cli" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/chat-completions" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/gemini/openai/responses" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini-cli" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/openai/chat-completions" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/openai/responses" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/gemini-cli" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/openai/chat-completions" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/openai/responses" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/antigravity/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/antigravity/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/antigravity/openai/chat-completions" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/antigravity/openai/responses" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/antigravity/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/antigravity/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/antigravity/openai/chat-completions" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/antigravity/openai/responses" ) diff --git a/internal/translator/openai/claude/init.go b/internal/translator/openai/claude/init.go index 0e0f82ea..baeeca84 100644 --- a/internal/translator/openai/claude/init.go +++ b/internal/translator/openai/claude/init.go @@ -1,9 +1,9 @@ package claude import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/openai/claude/openai_claude_request.go b/internal/translator/openai/claude/openai_claude_request.go index f12dd0c6..99fc2763 100644 --- a/internal/translator/openai/claude/openai_claude_request.go +++ b/internal/translator/openai/claude/openai_claude_request.go @@ -8,7 +8,7 @@ package claude import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/openai/claude/openai_claude_response.go b/internal/translator/openai/claude/openai_claude_response.go index af49d306..1925539c 100644 --- a/internal/translator/openai/claude/openai_claude_response.go +++ b/internal/translator/openai/claude/openai_claude_response.go @@ -10,8 +10,8 @@ import ( "context" "strings" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/openai/gemini-cli/init.go b/internal/translator/openai/gemini-cli/init.go index 12aec5ec..7b52d06d 100644 --- a/internal/translator/openai/gemini-cli/init.go +++ b/internal/translator/openai/gemini-cli/init.go @@ -1,9 +1,9 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/openai/gemini-cli/openai_gemini_request.go b/internal/translator/openai/gemini-cli/openai_gemini_request.go index 847c278f..c6518266 100644 --- a/internal/translator/openai/gemini-cli/openai_gemini_request.go +++ b/internal/translator/openai/gemini-cli/openai_gemini_request.go @@ -6,7 +6,7 @@ package geminiCLI import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/gemini" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/openai/gemini-cli/openai_gemini_response.go b/internal/translator/openai/gemini-cli/openai_gemini_response.go index a7369dbf..e54e08fc 100644 --- a/internal/translator/openai/gemini-cli/openai_gemini_response.go +++ b/internal/translator/openai/gemini-cli/openai_gemini_response.go @@ -8,8 +8,8 @@ package geminiCLI import ( "context" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/gemini" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/gemini" ) // ConvertOpenAIResponseToGeminiCLI converts OpenAI Chat Completions streaming response format to Gemini API format. diff --git a/internal/translator/openai/gemini/init.go b/internal/translator/openai/gemini/init.go index 4f056ace..24ae281e 100644 --- a/internal/translator/openai/gemini/init.go +++ b/internal/translator/openai/gemini/init.go @@ -1,9 +1,9 @@ package gemini import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/openai/gemini/openai_gemini_request.go b/internal/translator/openai/gemini/openai_gemini_request.go index b4edbb1d..7369de88 100644 --- a/internal/translator/openai/gemini/openai_gemini_request.go +++ b/internal/translator/openai/gemini/openai_gemini_request.go @@ -11,7 +11,7 @@ import ( "math/big" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/openai/gemini/openai_gemini_response.go b/internal/translator/openai/gemini/openai_gemini_response.go index 092a778e..439ae8fb 100644 --- a/internal/translator/openai/gemini/openai_gemini_response.go +++ b/internal/translator/openai/gemini/openai_gemini_response.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/openai/openai/chat-completions/init.go b/internal/translator/openai/openai/chat-completions/init.go index 90fa3dcd..bfe82cea 100644 --- a/internal/translator/openai/openai/chat-completions/init.go +++ b/internal/translator/openai/openai/chat-completions/init.go @@ -1,9 +1,9 @@ package chat_completions import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/openai/openai/responses/init.go b/internal/translator/openai/openai/responses/init.go index e6f60e0e..c47081ba 100644 --- a/internal/translator/openai/openai/responses/init.go +++ b/internal/translator/openai/openai/responses/init.go @@ -1,9 +1,9 @@ package responses import ( - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/translator" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/translator" ) func init() { diff --git a/internal/translator/openai/openai/responses/openai_openai-responses_response.go b/internal/translator/openai/openai/responses/openai_openai-responses_response.go index 8a44aede..8895b684 100644 --- a/internal/translator/openai/openai/responses/openai_openai-responses_response.go +++ b/internal/translator/openai/openai/responses/openai_openai-responses_response.go @@ -9,7 +9,7 @@ import ( "sync/atomic" "time" - translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common" + translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/internal/translator/translator/translator.go b/internal/translator/translator/translator.go index ab3f68a9..88766a83 100644 --- a/internal/translator/translator/translator.go +++ b/internal/translator/translator/translator.go @@ -7,8 +7,8 @@ package translator import ( "context" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) // registry holds the default translator registry instance. diff --git a/internal/util/provider.go b/internal/util/provider.go index beee9add..6313f58e 100644 --- a/internal/util/provider.go +++ b/internal/util/provider.go @@ -7,8 +7,8 @@ import ( "net/url" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" log "github.com/sirupsen/logrus" ) diff --git a/internal/util/proxy.go b/internal/util/proxy.go index 9b57ca17..781dd54d 100644 --- a/internal/util/proxy.go +++ b/internal/util/proxy.go @@ -6,8 +6,8 @@ package util import ( "net/http" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" ) diff --git a/internal/util/util.go b/internal/util/util.go index 9bf630f2..2c066e3e 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -11,7 +11,7 @@ import ( "regexp" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/internal/watcher/clients.go b/internal/watcher/clients.go index fb0d7865..0a46660e 100644 --- a/internal/watcher/clients.go +++ b/internal/watcher/clients.go @@ -13,11 +13,11 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/synthesizer" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/watcher/config_reload.go b/internal/watcher/config_reload.go index 1bbf4ef2..0471f8b3 100644 --- a/internal/watcher/config_reload.go +++ b/internal/watcher/config_reload.go @@ -9,9 +9,9 @@ import ( "reflect" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" "gopkg.in/yaml.v3" log "github.com/sirupsen/logrus" diff --git a/internal/watcher/diff/auth_diff.go b/internal/watcher/diff/auth_diff.go index 4b6e6008..39fe5e88 100644 --- a/internal/watcher/diff/auth_diff.go +++ b/internal/watcher/diff/auth_diff.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // BuildAuthChangeDetails computes a redacted, human-readable list of auth field changes. diff --git a/internal/watcher/diff/config_diff.go b/internal/watcher/diff/config_diff.go index b414ed5a..c206049e 100644 --- a/internal/watcher/diff/config_diff.go +++ b/internal/watcher/diff/config_diff.go @@ -6,7 +6,7 @@ import ( "reflect" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) // BuildConfigChangeDetails computes a redacted, human-readable list of config changes. diff --git a/internal/watcher/diff/config_diff_test.go b/internal/watcher/diff/config_diff_test.go index b9a9153b..192791ea 100644 --- a/internal/watcher/diff/config_diff_test.go +++ b/internal/watcher/diff/config_diff_test.go @@ -3,8 +3,8 @@ package diff import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestBuildConfigChangeDetails(t *testing.T) { diff --git a/internal/watcher/diff/model_hash.go b/internal/watcher/diff/model_hash.go index 5779facc..fed3386a 100644 --- a/internal/watcher/diff/model_hash.go +++ b/internal/watcher/diff/model_hash.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) // ComputeOpenAICompatModelsHash returns a stable hash for OpenAI-compat models. diff --git a/internal/watcher/diff/model_hash_test.go b/internal/watcher/diff/model_hash_test.go index db06ebd1..b687d4da 100644 --- a/internal/watcher/diff/model_hash_test.go +++ b/internal/watcher/diff/model_hash_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestComputeOpenAICompatModelsHash_Deterministic(t *testing.T) { diff --git a/internal/watcher/diff/models_summary.go b/internal/watcher/diff/models_summary.go index 9c2aa91a..4c9b035a 100644 --- a/internal/watcher/diff/models_summary.go +++ b/internal/watcher/diff/models_summary.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) type GeminiModelsSummary struct { diff --git a/internal/watcher/diff/oauth_excluded.go b/internal/watcher/diff/oauth_excluded.go index 2039cf48..d6320628 100644 --- a/internal/watcher/diff/oauth_excluded.go +++ b/internal/watcher/diff/oauth_excluded.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) type ExcludedModelsSummary struct { diff --git a/internal/watcher/diff/oauth_excluded_test.go b/internal/watcher/diff/oauth_excluded_test.go index f5ad3913..8643f594 100644 --- a/internal/watcher/diff/oauth_excluded_test.go +++ b/internal/watcher/diff/oauth_excluded_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestSummarizeExcludedModels_NormalizesAndDedupes(t *testing.T) { diff --git a/internal/watcher/diff/oauth_model_alias.go b/internal/watcher/diff/oauth_model_alias.go index c5a17d29..8c14089b 100644 --- a/internal/watcher/diff/oauth_model_alias.go +++ b/internal/watcher/diff/oauth_model_alias.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) type OAuthModelAliasSummary struct { diff --git a/internal/watcher/diff/openai_compat.go b/internal/watcher/diff/openai_compat.go index 541b35b3..31d0bcd9 100644 --- a/internal/watcher/diff/openai_compat.go +++ b/internal/watcher/diff/openai_compat.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) // DiffOpenAICompatibility produces human-readable change descriptions. diff --git a/internal/watcher/diff/openai_compat_test.go b/internal/watcher/diff/openai_compat_test.go index db33db14..5683671a 100644 --- a/internal/watcher/diff/openai_compat_test.go +++ b/internal/watcher/diff/openai_compat_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestDiffOpenAICompatibility(t *testing.T) { diff --git a/internal/watcher/dispatcher.go b/internal/watcher/dispatcher.go index 3d7d7527..d0182e2c 100644 --- a/internal/watcher/dispatcher.go +++ b/internal/watcher/dispatcher.go @@ -9,9 +9,9 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/synthesizer" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) var snapshotCoreAuthsFunc = snapshotCoreAuths diff --git a/internal/watcher/synthesizer/config.go b/internal/watcher/synthesizer/config.go index 8026b02f..ba8fe52e 100644 --- a/internal/watcher/synthesizer/config.go +++ b/internal/watcher/synthesizer/config.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // ConfigSynthesizer generates Auth entries from configuration API keys. diff --git a/internal/watcher/synthesizer/config_test.go b/internal/watcher/synthesizer/config_test.go index 437f18d1..c57b8fc7 100644 --- a/internal/watcher/synthesizer/config_test.go +++ b/internal/watcher/synthesizer/config_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestNewConfigSynthesizer(t *testing.T) { diff --git a/internal/watcher/synthesizer/context.go b/internal/watcher/synthesizer/context.go index d973289a..f92b41dd 100644 --- a/internal/watcher/synthesizer/context.go +++ b/internal/watcher/synthesizer/context.go @@ -3,7 +3,7 @@ package synthesizer import ( "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) // SynthesisContext provides the context needed for auth synthesis. diff --git a/internal/watcher/synthesizer/file.go b/internal/watcher/synthesizer/file.go index 49a635e7..47990bc1 100644 --- a/internal/watcher/synthesizer/file.go +++ b/internal/watcher/synthesizer/file.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/geminicli" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/geminicli" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // FileSynthesizer generates Auth entries from OAuth JSON files. diff --git a/internal/watcher/synthesizer/file_test.go b/internal/watcher/synthesizer/file_test.go index f3e44979..63b394aa 100644 --- a/internal/watcher/synthesizer/file_test.go +++ b/internal/watcher/synthesizer/file_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestNewFileSynthesizer(t *testing.T) { diff --git a/internal/watcher/synthesizer/helpers.go b/internal/watcher/synthesizer/helpers.go index 102dc77e..19b4c896 100644 --- a/internal/watcher/synthesizer/helpers.go +++ b/internal/watcher/synthesizer/helpers.go @@ -7,9 +7,9 @@ import ( "sort" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // StableIDGenerator generates stable, deterministic IDs for auth entries. diff --git a/internal/watcher/synthesizer/helpers_test.go b/internal/watcher/synthesizer/helpers_test.go index 46b9c8a0..69ba85d6 100644 --- a/internal/watcher/synthesizer/helpers_test.go +++ b/internal/watcher/synthesizer/helpers_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestNewStableIDGenerator(t *testing.T) { diff --git a/internal/watcher/synthesizer/interface.go b/internal/watcher/synthesizer/interface.go index 1a9aedc9..e0962c11 100644 --- a/internal/watcher/synthesizer/interface.go +++ b/internal/watcher/synthesizer/interface.go @@ -5,7 +5,7 @@ package synthesizer import ( - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // AuthSynthesizer defines the interface for generating Auth entries from various sources. diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index cf890a4c..c18cd84d 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -10,11 +10,11 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "gopkg.in/yaml.v3" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/internal/watcher/watcher_test.go b/internal/watcher/watcher_test.go index 00a7a143..bb3b5577 100644 --- a/internal/watcher/watcher_test.go +++ b/internal/watcher/watcher_test.go @@ -14,11 +14,11 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/diff" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/diff" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/synthesizer" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "gopkg.in/yaml.v3" ) diff --git a/sdk/api/handlers/claude/code_handlers.go b/sdk/api/handlers/claude/code_handlers.go index 074ffc0d..464f385e 100644 --- a/sdk/api/handlers/claude/code_handlers.go +++ b/sdk/api/handlers/claude/code_handlers.go @@ -16,10 +16,10 @@ import ( "net/http" "github.com/gin-gonic/gin" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/sdk/api/handlers/gemini/gemini-cli_handlers.go b/sdk/api/handlers/gemini/gemini-cli_handlers.go index 4c5ddf80..de79f05b 100644 --- a/sdk/api/handlers/gemini/gemini-cli_handlers.go +++ b/sdk/api/handlers/gemini/gemini-cli_handlers.go @@ -15,10 +15,10 @@ import ( "time" "github.com/gin-gonic/gin" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) diff --git a/sdk/api/handlers/gemini/gemini_handlers.go b/sdk/api/handlers/gemini/gemini_handlers.go index e51ad19b..60aed26a 100644 --- a/sdk/api/handlers/gemini/gemini_handlers.go +++ b/sdk/api/handlers/gemini/gemini_handlers.go @@ -13,10 +13,10 @@ import ( "time" "github.com/gin-gonic/gin" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" ) // GeminiAPIHandler contains the handlers for Gemini API endpoints. diff --git a/sdk/api/handlers/handlers.go b/sdk/api/handlers/handlers.go index e89227aa..6e0adb64 100644 --- a/sdk/api/handlers/handlers.go +++ b/sdk/api/handlers/handlers.go @@ -14,14 +14,14 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "golang.org/x/net/context" ) @@ -850,14 +850,22 @@ func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string resolvedModelName := modelName initialSuffix := thinking.ParseSuffix(modelName) if initialSuffix.ModelName == "auto" { - resolvedBase := util.ResolveAutoModel(initialSuffix.ModelName) - if initialSuffix.HasSuffix { - resolvedModelName = fmt.Sprintf("%s(%s)", resolvedBase, initialSuffix.RawSuffix) + if h != nil && h.AuthManager != nil && h.AuthManager.HomeEnabled() { + resolvedModelName = modelName } else { - resolvedModelName = resolvedBase + resolvedBase := util.ResolveAutoModel(initialSuffix.ModelName) + if initialSuffix.HasSuffix { + resolvedModelName = fmt.Sprintf("%s(%s)", resolvedBase, initialSuffix.RawSuffix) + } else { + resolvedModelName = resolvedBase + } } } else { - resolvedModelName = util.ResolveAutoModel(modelName) + if h != nil && h.AuthManager != nil && h.AuthManager.HomeEnabled() { + resolvedModelName = modelName + } else { + resolvedModelName = util.ResolveAutoModel(modelName) + } } parsed := thinking.ParseSuffix(resolvedModelName) @@ -870,6 +878,10 @@ func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string } } + if h != nil && h.AuthManager != nil && h.AuthManager.HomeEnabled() { + return []string{"home"}, resolvedModelName, nil + } + providers = util.GetProviderName(baseModel) // Fallback: if baseModel has no provider but differs from resolvedModelName, // try using the full model name. This handles edge cases where custom models diff --git a/sdk/api/handlers/handlers_error_response_test.go b/sdk/api/handlers/handlers_error_response_test.go index 917971c2..0c206e38 100644 --- a/sdk/api/handlers/handlers_error_response_test.go +++ b/sdk/api/handlers/handlers_error_response_test.go @@ -9,9 +9,9 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestWriteErrorResponse_AddonHeadersDisabledByDefault(t *testing.T) { diff --git a/sdk/api/handlers/handlers_metadata_test.go b/sdk/api/handlers/handlers_metadata_test.go index 99af872d..c5e94f96 100644 --- a/sdk/api/handlers/handlers_metadata_test.go +++ b/sdk/api/handlers/handlers_metadata_test.go @@ -3,7 +3,7 @@ package handlers import ( "testing" - coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" "golang.org/x/net/context" ) diff --git a/sdk/api/handlers/handlers_request_details_test.go b/sdk/api/handlers/handlers_request_details_test.go index c98580f2..3110cbc5 100644 --- a/sdk/api/handlers/handlers_request_details_test.go +++ b/sdk/api/handlers/handlers_request_details_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestGetRequestDetails_PreservesSuffix(t *testing.T) { diff --git a/sdk/api/handlers/handlers_stream_bootstrap_test.go b/sdk/api/handlers/handlers_stream_bootstrap_test.go index f357962f..551baac3 100644 --- a/sdk/api/handlers/handlers_stream_bootstrap_test.go +++ b/sdk/api/handlers/handlers_stream_bootstrap_test.go @@ -8,11 +8,11 @@ import ( "sync" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) type failOnceStreamExecutor struct { diff --git a/sdk/api/handlers/openai/openai_handlers.go b/sdk/api/handlers/openai/openai_handlers.go index 4b4a9833..29dc0ea0 100644 --- a/sdk/api/handlers/openai/openai_handlers.go +++ b/sdk/api/handlers/openai/openai_handlers.go @@ -14,11 +14,11 @@ import ( "sync" "github.com/gin-gonic/gin" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - responsesconverter "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/openai/responses" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + responsesconverter "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/openai/responses" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/sdk/api/handlers/openai/openai_images_handlers.go b/sdk/api/handlers/openai/openai_images_handlers.go index 8d22a4f4..6e6e8ef6 100644 --- a/sdk/api/handlers/openai/openai_images_handlers.go +++ b/sdk/api/handlers/openai/openai_images_handlers.go @@ -14,9 +14,9 @@ import ( "time" "github.com/gin-gonic/gin" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/sdk/api/handlers/openai/openai_images_handlers_test.go b/sdk/api/handlers/openai/openai_images_handlers_test.go index ea65ca3a..77965996 100644 --- a/sdk/api/handlers/openai/openai_images_handlers_test.go +++ b/sdk/api/handlers/openai/openai_images_handlers_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/gin-gonic/gin" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" "github.com/tidwall/gjson" ) diff --git a/sdk/api/handlers/openai/openai_responses_compact_test.go b/sdk/api/handlers/openai/openai_responses_compact_test.go index dcfcc99a..48b7e3bb 100644 --- a/sdk/api/handlers/openai/openai_responses_compact_test.go +++ b/sdk/api/handlers/openai/openai_responses_compact_test.go @@ -9,11 +9,11 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) type compactCaptureExecutor struct { diff --git a/sdk/api/handlers/openai/openai_responses_handlers.go b/sdk/api/handlers/openai/openai_responses_handlers.go index 8dd1a0a7..5b2c006a 100644 --- a/sdk/api/handlers/openai/openai_responses_handlers.go +++ b/sdk/api/handlers/openai/openai_responses_handlers.go @@ -16,10 +16,10 @@ import ( "sort" "github.com/gin-gonic/gin" - . "github.com/router-for-me/CLIProxyAPI/v6/internal/constant" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" + . "github.com/router-for-me/CLIProxyAPI/v7/internal/constant" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/sdk/api/handlers/openai/openai_responses_handlers_stream_error_test.go b/sdk/api/handlers/openai/openai_responses_handlers_stream_error_test.go index 771e46b8..54d14675 100644 --- a/sdk/api/handlers/openai/openai_responses_handlers_stream_error_test.go +++ b/sdk/api/handlers/openai/openai_responses_handlers_stream_error_test.go @@ -8,9 +8,9 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestForwardResponsesStreamTerminalErrorUsesResponsesErrorChunk(t *testing.T) { diff --git a/sdk/api/handlers/openai/openai_responses_handlers_stream_test.go b/sdk/api/handlers/openai/openai_responses_handlers_stream_test.go index 151da9a7..0742b9b3 100644 --- a/sdk/api/handlers/openai/openai_responses_handlers_stream_test.go +++ b/sdk/api/handlers/openai/openai_responses_handlers_stream_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" "github.com/tidwall/gjson" ) diff --git a/sdk/api/handlers/openai/openai_responses_websocket.go b/sdk/api/handlers/openai/openai_responses_websocket.go index c617c946..bfac4921 100644 --- a/sdk/api/handlers/openai/openai_responses_websocket.go +++ b/sdk/api/handlers/openai/openai_responses_websocket.go @@ -13,13 +13,13 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/gorilla/websocket" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/sdk/api/handlers/openai/openai_responses_websocket_test.go b/sdk/api/handlers/openai/openai_responses_websocket_test.go index 319127f0..a76c4625 100644 --- a/sdk/api/handlers/openai/openai_responses_websocket_test.go +++ b/sdk/api/handlers/openai/openai_responses_websocket_test.go @@ -14,12 +14,12 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" "github.com/tidwall/gjson" ) diff --git a/sdk/api/handlers/stream_forwarder.go b/sdk/api/handlers/stream_forwarder.go index 401baca8..63ddc31e 100644 --- a/sdk/api/handlers/stream_forwarder.go +++ b/sdk/api/handlers/stream_forwarder.go @@ -5,7 +5,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" ) type StreamForwardOptions struct { diff --git a/sdk/api/management.go b/sdk/api/management.go index a5a1cfc4..3ed586d8 100644 --- a/sdk/api/management.go +++ b/sdk/api/management.go @@ -6,9 +6,9 @@ package api import ( "github.com/gin-gonic/gin" - internalmanagement "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/management" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + internalmanagement "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) // ManagementTokenRequester exposes a limited subset of management endpoints for requesting tokens. diff --git a/sdk/api/options.go b/sdk/api/options.go index 8497884b..e2bbff78 100644 --- a/sdk/api/options.go +++ b/sdk/api/options.go @@ -8,10 +8,10 @@ import ( "time" "github.com/gin-gonic/gin" - internalapi "github.com/router-for-me/CLIProxyAPI/v6/internal/api" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/logging" + internalapi "github.com/router-for-me/CLIProxyAPI/v7/internal/api" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/logging" ) // ServerOption customises HTTP server construction. diff --git a/sdk/auth/antigravity.go b/sdk/auth/antigravity.go index d52bf1d2..0a947b20 100644 --- a/sdk/auth/antigravity.go +++ b/sdk/auth/antigravity.go @@ -8,12 +8,12 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/antigravity" - "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/antigravity" + "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/claude.go b/sdk/auth/claude.go index d82a718b..726fa922 100644 --- a/sdk/auth/claude.go +++ b/sdk/auth/claude.go @@ -7,13 +7,13 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude" - "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude" + "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" // legacy client removed - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/codex.go b/sdk/auth/codex.go index 269e3d8b..be58c9c5 100644 --- a/sdk/auth/codex.go +++ b/sdk/auth/codex.go @@ -7,13 +7,13 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" // legacy client removed - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/misc" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/codex_device.go b/sdk/auth/codex_device.go index 10f59fb9..d7ea4e1f 100644 --- a/sdk/auth/codex_device.go +++ b/sdk/auth/codex_device.go @@ -13,11 +13,11 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" - "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/errors.go b/sdk/auth/errors.go index 78fe9a17..f950e925 100644 --- a/sdk/auth/errors.go +++ b/sdk/auth/errors.go @@ -3,7 +3,7 @@ package auth import ( "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" + "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" ) // ProjectSelectionError indicates that the user must choose a specific project ID. diff --git a/sdk/auth/filestore.go b/sdk/auth/filestore.go index f8f49f44..39be2d8f 100644 --- a/sdk/auth/filestore.go +++ b/sdk/auth/filestore.go @@ -15,7 +15,7 @@ import ( "sync" "time" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // FileTokenStore persists token records and auth metadata using the filesystem as backing storage. diff --git a/sdk/auth/gemini.go b/sdk/auth/gemini.go index 2b8f9c2b..ba7c7728 100644 --- a/sdk/auth/gemini.go +++ b/sdk/auth/gemini.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini" // legacy client removed - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts. diff --git a/sdk/auth/interfaces.go b/sdk/auth/interfaces.go index 64cf8ed0..e5582a0c 100644 --- a/sdk/auth/interfaces.go +++ b/sdk/auth/interfaces.go @@ -5,8 +5,8 @@ import ( "errors" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported") diff --git a/sdk/auth/kimi.go b/sdk/auth/kimi.go index 12ae101e..4dbff1e8 100644 --- a/sdk/auth/kimi.go +++ b/sdk/auth/kimi.go @@ -6,10 +6,10 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kimi" - "github.com/router-for-me/CLIProxyAPI/v6/internal/browser" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/kimi" + "github.com/router-for-me/CLIProxyAPI/v7/internal/browser" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/manager.go b/sdk/auth/manager.go index c6469a7d..bceb5e19 100644 --- a/sdk/auth/manager.go +++ b/sdk/auth/manager.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) // Manager aggregates authenticators and coordinates persistence via a token store. diff --git a/sdk/auth/refresh_registry.go b/sdk/auth/refresh_registry.go index ae60f56a..fe252315 100644 --- a/sdk/auth/refresh_registry.go +++ b/sdk/auth/refresh_registry.go @@ -3,7 +3,7 @@ package auth import ( "time" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func init() { diff --git a/sdk/auth/store_registry.go b/sdk/auth/store_registry.go index 760449f8..1971947b 100644 --- a/sdk/auth/store_registry.go +++ b/sdk/auth/store_registry.go @@ -3,7 +3,7 @@ package auth import ( "sync" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) var ( diff --git a/sdk/cliproxy/auth/antigravity_credits_test.go b/sdk/cliproxy/auth/antigravity_credits_test.go index 38c08dcf..34a475dc 100644 --- a/sdk/cliproxy/auth/antigravity_credits_test.go +++ b/sdk/cliproxy/auth/antigravity_credits_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) type antigravityCreditsFallbackExecutor struct { diff --git a/sdk/cliproxy/auth/api_key_model_alias_test.go b/sdk/cliproxy/auth/api_key_model_alias_test.go index 70915d9e..25da4df4 100644 --- a/sdk/cliproxy/auth/api_key_model_alias_test.go +++ b/sdk/cliproxy/auth/api_key_model_alias_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestLookupAPIKeyUpstreamModel(t *testing.T) { diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index ab3eca49..f9bf0510 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -16,13 +16,14 @@ import ( "time" "github.com/google/uuid" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/internal/util" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - coreusage "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/home" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + "github.com/router-for-me/CLIProxyAPI/v7/internal/util" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" log "github.com/sirupsen/logrus" ) @@ -377,6 +378,15 @@ func (m *Manager) SetConfig(cfg *internalconfig.Config) { m.rebuildAPIKeyModelAliasFromRuntimeConfig() } +// HomeEnabled reports whether the home control plane integration is enabled in the runtime config. +func (m *Manager) HomeEnabled() bool { + if m == nil { + return false + } + cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config) + return cfg != nil && cfg.Home.Enabled +} + func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) string { if m == nil { return "" @@ -522,6 +532,11 @@ func preserveRequestedModelSuffix(requestedModel, resolved string) string { } func (m *Manager) executionModelCandidates(auth *Auth, routeModel string) []string { + if auth != nil && auth.Attributes != nil { + if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" { + return []string{homeModel} + } + } requestedModel := rewriteModelForAuth(routeModel, auth) requestedModel = m.applyOAuthModelAlias(auth, requestedModel) if pool := m.resolveOpenAICompatUpstreamModelPool(auth, requestedModel); len(pool) > 0 { @@ -555,6 +570,14 @@ func (m *Manager) selectionModelKeyForAuth(auth *Auth, routeModel string) string } func (m *Manager) stateModelForExecution(auth *Auth, routeModel, upstreamModel string, pooled bool) string { + if auth != nil && auth.Attributes != nil { + if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" { + if resolved := strings.TrimSpace(upstreamModel); resolved != "" { + return resolved + } + return homeModel + } + } stateModel := executionResultModel(routeModel, upstreamModel, pooled) selectionModel := m.selectionModelForAuth(auth, routeModel) if canonicalModelKey(selectionModel) == canonicalModelKey(upstreamModel) && strings.TrimSpace(selectionModel) != "" { @@ -2710,6 +2733,11 @@ func (m *Manager) routeAwareSelectionRequired(auth *Auth, routeModel string) boo } func (m *Manager) pickNextLegacy(ctx context.Context, provider, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, error) { + if m.HomeEnabled() { + auth, exec, _, err := m.pickNextViaHome(ctx, model, opts) + return auth, exec, err + } + pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata) disallowFreeAuth := disallowFreeAuthFromMetadata(opts.Metadata) @@ -2779,6 +2807,11 @@ func (m *Manager) pickNextLegacy(ctx context.Context, provider, model string, op } func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, error) { + if m.HomeEnabled() { + auth, exec, _, err := m.pickNextViaHome(ctx, model, opts) + return auth, exec, err + } + if !m.useSchedulerFastPath() { return m.pickNextLegacy(ctx, provider, model, opts, tried) } @@ -2836,6 +2869,10 @@ func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cli } func (m *Manager) pickNextMixedLegacy(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) { + if m.HomeEnabled() { + return m.pickNextViaHome(ctx, model, opts) + } + pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata) disallowFreeAuth := disallowFreeAuthFromMetadata(opts.Metadata) @@ -2928,6 +2965,10 @@ func (m *Manager) pickNextMixedLegacy(ctx context.Context, providers []string, m } func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) { + if m.HomeEnabled() { + return m.pickNextViaHome(ctx, model, opts) + } + if !m.useSchedulerFastPath() { return m.pickNextMixedLegacy(ctx, providers, model, opts, tried) } @@ -3012,6 +3053,148 @@ func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model s } } +type homeErrorEnvelope struct { + Error *homeErrorDetail `json:"error"` +} + +type homeErrorDetail struct { + Type string `json:"type"` + Message string `json:"message"` + Code string `json:"code,omitempty"` +} + +const homeUpstreamModelAttributeKey = "home_upstream_model" + +type homeAuthDispatchResponse struct { + Model string `json:"model"` + Provider string `json:"provider"` + AuthIndex string `json:"auth_index"` + UserAPIKey string `json:"user_api_key"` + Auth Auth `json:"auth"` +} + +func setHomeUserAPIKeyOnGinContext(ctx context.Context, apiKey string) { + apiKey = strings.TrimSpace(apiKey) + if apiKey == "" || ctx == nil { + return + } + ginCtx, ok := ctx.Value("gin").(interface{ Set(string, any) }) + if !ok || ginCtx == nil { + return + } + ginCtx.Set("userApiKey", apiKey) +} + +func (m *Manager) pickNextViaHome(ctx context.Context, model string, opts cliproxyexecutor.Options) (*Auth, ProviderExecutor, string, error) { + if m == nil { + return nil, nil, "", &Error{Code: "auth_not_found", Message: "no auth available"} + } + if ctx == nil { + ctx = context.Background() + } + client := home.Current() + if client == nil || !client.HeartbeatOK() { + return nil, nil, "", &Error{Code: "home_unavailable", Message: "home control center unavailable", HTTPStatus: http.StatusServiceUnavailable} + } + + requestedModel := requestedModelFromMetadata(opts.Metadata, model) + sessionID := ExtractSessionID(opts.Headers, opts.OriginalRequest, opts.Metadata) + + raw, err := client.RPopAuth(ctx, requestedModel, sessionID, opts.Headers) + if err != nil { + return nil, nil, "", &Error{Code: "auth_not_found", Message: err.Error(), HTTPStatus: http.StatusServiceUnavailable} + } + + var env homeErrorEnvelope + if errUnmarshal := json.Unmarshal(raw, &env); errUnmarshal == nil && env.Error != nil { + code := strings.TrimSpace(env.Error.Type) + if code == "" { + code = strings.TrimSpace(env.Error.Code) + } + msg := strings.TrimSpace(env.Error.Message) + if msg == "" { + msg = "home returned error" + } + status := http.StatusBadGateway + switch strings.ToLower(code) { + case "model_not_found": + status = http.StatusNotFound + case "authentication_error", "unauthorized": + status = http.StatusUnauthorized + } + return nil, nil, "", &Error{Code: code, Message: msg, HTTPStatus: status} + } + + var dispatch homeAuthDispatchResponse + if errUnmarshal := json.Unmarshal(raw, &dispatch); errUnmarshal != nil { + return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned invalid auth payload", HTTPStatus: http.StatusBadGateway} + } + setHomeUserAPIKeyOnGinContext(ctx, dispatch.UserAPIKey) + auth := dispatch.Auth + if strings.TrimSpace(auth.ID) == "" { + // Backward compatibility: older home instances returned the auth directly. + if errUnmarshal := json.Unmarshal(raw, &auth); errUnmarshal != nil { + return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned invalid auth payload", HTTPStatus: http.StatusBadGateway} + } + } + if upstreamModel := strings.TrimSpace(dispatch.Model); upstreamModel != "" { + if auth.Attributes == nil { + auth.Attributes = make(map[string]string, 1) + } + auth.Attributes[homeUpstreamModelAttributeKey] = upstreamModel + } + if strings.TrimSpace(auth.ID) == "" { + return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned auth without id", HTTPStatus: http.StatusBadGateway} + } + providerKey := strings.ToLower(strings.TrimSpace(auth.Provider)) + if providerKey == "" { + return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned auth without provider", HTTPStatus: http.StatusBadGateway} + } + + homeAuthIndex := strings.TrimSpace(dispatch.AuthIndex) + if homeAuthIndex != "" { + auth.Index = homeAuthIndex + auth.indexAssigned = true + } else { + auth.EnsureIndex() + } + + executor, ok := m.Executor(providerKey) + if !ok && auth.Attributes != nil && strings.TrimSpace(auth.Attributes["base_url"]) != "" { + executor, ok = m.Executor("openai-compatibility") + if ok { + providerKey = "openai-compatibility" + } + } + if !ok { + return nil, nil, "", &Error{Code: "executor_not_found", Message: "executor not registered", HTTPStatus: http.StatusBadGateway} + } + + return auth.Clone(), executor, providerKey, nil +} + +func requestedModelFromMetadata(metadata map[string]any, fallback string) string { + if metadata != nil { + if v, ok := metadata[cliproxyexecutor.RequestedModelMetadataKey]; ok { + switch typed := v.(type) { + case string: + if trimmed := strings.TrimSpace(typed); trimmed != "" { + return trimmed + } + case []byte: + if trimmed := strings.TrimSpace(string(typed)); trimmed != "" { + return trimmed + } + } + } + } + fallback = strings.TrimSpace(fallback) + if fallback == "" { + return "unknown" + } + return fallback +} + func (m *Manager) findAllAntigravityCreditsCandidateAuths(routeModel string, opts cliproxyexecutor.Options) []creditsCandidateEntry { if m == nil { return nil diff --git a/sdk/cliproxy/auth/conductor_credits_candidates_test.go b/sdk/cliproxy/auth/conductor_credits_candidates_test.go index e66798ac..f9487b0b 100644 --- a/sdk/cliproxy/auth/conductor_credits_candidates_test.go +++ b/sdk/cliproxy/auth/conductor_credits_candidates_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) func TestFindAllAntigravityCreditsCandidateAuths_PrefersKnownCreditsThenUnknown(t *testing.T) { diff --git a/sdk/cliproxy/auth/conductor_executor_replace_test.go b/sdk/cliproxy/auth/conductor_executor_replace_test.go index 2ee91a87..99ecf466 100644 --- a/sdk/cliproxy/auth/conductor_executor_replace_test.go +++ b/sdk/cliproxy/auth/conductor_executor_replace_test.go @@ -6,7 +6,7 @@ import ( "sync" "testing" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) type replaceAwareExecutor struct { diff --git a/sdk/cliproxy/auth/conductor_oauth_alias_suspension_test.go b/sdk/cliproxy/auth/conductor_oauth_alias_suspension_test.go index b4b72204..ba8371dc 100644 --- a/sdk/cliproxy/auth/conductor_oauth_alias_suspension_test.go +++ b/sdk/cliproxy/auth/conductor_oauth_alias_suspension_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - coreusage "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" ) type aliasRoutingExecutor struct { diff --git a/sdk/cliproxy/auth/conductor_overrides_test.go b/sdk/cliproxy/auth/conductor_overrides_test.go index f74621be..017602e3 100644 --- a/sdk/cliproxy/auth/conductor_overrides_test.go +++ b/sdk/cliproxy/auth/conductor_overrides_test.go @@ -8,9 +8,9 @@ import ( "time" "github.com/google/uuid" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) const requestScopedNotFoundMessage = "Item with id 'rs_0b5f3eb6f51f175c0169ca74e4a85881998539920821603a74' not found. Items are not persisted when `store` is set to false. Try again with `store` set to true, or remove this item from your input." diff --git a/sdk/cliproxy/auth/conductor_scheduler_refresh_test.go b/sdk/cliproxy/auth/conductor_scheduler_refresh_test.go index 5c6eff78..508cdfd1 100644 --- a/sdk/cliproxy/auth/conductor_scheduler_refresh_test.go +++ b/sdk/cliproxy/auth/conductor_scheduler_refresh_test.go @@ -6,8 +6,8 @@ import ( "net/http" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) type schedulerProviderTestExecutor struct { diff --git a/sdk/cliproxy/auth/oauth_model_alias.go b/sdk/cliproxy/auth/oauth_model_alias.go index 46c82a9c..7e6740d6 100644 --- a/sdk/cliproxy/auth/oauth_model_alias.go +++ b/sdk/cliproxy/auth/oauth_model_alias.go @@ -3,8 +3,8 @@ package auth import ( "strings" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" ) type modelAliasEntry interface { diff --git a/sdk/cliproxy/auth/oauth_model_alias_test.go b/sdk/cliproxy/auth/oauth_model_alias_test.go index 73ddbe67..521e158e 100644 --- a/sdk/cliproxy/auth/oauth_model_alias_test.go +++ b/sdk/cliproxy/auth/oauth_model_alias_test.go @@ -3,7 +3,7 @@ package auth import ( "testing" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) { diff --git a/sdk/cliproxy/auth/openai_compat_pool_test.go b/sdk/cliproxy/auth/openai_compat_pool_test.go index ff2c4dd0..f052c486 100644 --- a/sdk/cliproxy/auth/openai_compat_pool_test.go +++ b/sdk/cliproxy/auth/openai_compat_pool_test.go @@ -7,9 +7,9 @@ import ( "sync" "testing" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) type openAICompatPoolExecutor struct { diff --git a/sdk/cliproxy/auth/scheduler.go b/sdk/cliproxy/auth/scheduler.go index b5a39282..9947f59c 100644 --- a/sdk/cliproxy/auth/scheduler.go +++ b/sdk/cliproxy/auth/scheduler.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) // schedulerStrategy identifies which built-in routing semantics the scheduler should apply. diff --git a/sdk/cliproxy/auth/scheduler_benchmark_test.go b/sdk/cliproxy/auth/scheduler_benchmark_test.go index 050a7cbd..4d160276 100644 --- a/sdk/cliproxy/auth/scheduler_benchmark_test.go +++ b/sdk/cliproxy/auth/scheduler_benchmark_test.go @@ -6,8 +6,8 @@ import ( "net/http" "testing" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) type schedulerBenchmarkExecutor struct { diff --git a/sdk/cliproxy/auth/scheduler_test.go b/sdk/cliproxy/auth/scheduler_test.go index 8caaa473..864fa938 100644 --- a/sdk/cliproxy/auth/scheduler_test.go +++ b/sdk/cliproxy/auth/scheduler_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) type schedulerTestExecutor struct{} diff --git a/sdk/cliproxy/auth/selector.go b/sdk/cliproxy/auth/selector.go index f0fe237c..5e23c46f 100644 --- a/sdk/cliproxy/auth/selector.go +++ b/sdk/cliproxy/auth/selector.go @@ -18,9 +18,9 @@ import ( log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) // RoundRobinSelector provides a simple provider scoped round-robin selection strategy. diff --git a/sdk/cliproxy/auth/selector_test.go b/sdk/cliproxy/auth/selector_test.go index f6682c6f..99231bdf 100644 --- a/sdk/cliproxy/auth/selector_test.go +++ b/sdk/cliproxy/auth/selector_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) func TestFillFirstSelectorPick_Deterministic(t *testing.T) { diff --git a/sdk/cliproxy/auth/types.go b/sdk/cliproxy/auth/types.go index 76f4c396..3cbd49b5 100644 --- a/sdk/cliproxy/auth/types.go +++ b/sdk/cliproxy/auth/types.go @@ -12,7 +12,7 @@ import ( "sync" "time" - baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth" + baseauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth" ) // PostAuthHook defines a function that is called after an Auth record is created diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go index b8cf991c..152940a0 100644 --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -8,12 +8,12 @@ import ( "strings" "time" - configaccess "github.com/router-for-me/CLIProxyAPI/v6/internal/access/config_access" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + configaccess "github.com/router-for-me/CLIProxyAPI/v7/internal/access/config_access" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) // Builder constructs a Service instance with customizable providers. diff --git a/sdk/cliproxy/executor/types.go b/sdk/cliproxy/executor/types.go index c8bb917d..fd1da2e5 100644 --- a/sdk/cliproxy/executor/types.go +++ b/sdk/cliproxy/executor/types.go @@ -4,7 +4,7 @@ import ( "net/http" "net/url" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) // RequestedModelMetadataKey stores the client-requested model name in Options.Metadata. diff --git a/sdk/cliproxy/model_registry.go b/sdk/cliproxy/model_registry.go index 01cea5b7..9cb928c9 100644 --- a/sdk/cliproxy/model_registry.go +++ b/sdk/cliproxy/model_registry.go @@ -1,6 +1,6 @@ package cliproxy -import "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" +import "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" // ModelInfo re-exports the registry model info structure. type ModelInfo = registry.ModelInfo diff --git a/sdk/cliproxy/pipeline/context.go b/sdk/cliproxy/pipeline/context.go index fc6754eb..4cffb0b4 100644 --- a/sdk/cliproxy/pipeline/context.go +++ b/sdk/cliproxy/pipeline/context.go @@ -4,9 +4,9 @@ import ( "context" "net/http" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) // Context encapsulates execution state shared across middleware, translators, and executors. diff --git a/sdk/cliproxy/pprof_server.go b/sdk/cliproxy/pprof_server.go index 3fafef4c..ec30b4be 100644 --- a/sdk/cliproxy/pprof_server.go +++ b/sdk/cliproxy/pprof_server.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/sdk/cliproxy/providers.go b/sdk/cliproxy/providers.go index 7ce89f76..542b2d9d 100644 --- a/sdk/cliproxy/providers.go +++ b/sdk/cliproxy/providers.go @@ -3,8 +3,8 @@ package cliproxy import ( "context" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) // NewFileTokenClientProvider returns the default token-backed client loader. diff --git a/sdk/cliproxy/rtprovider.go b/sdk/cliproxy/rtprovider.go index 5c4f579a..d07b4cb4 100644 --- a/sdk/cliproxy/rtprovider.go +++ b/sdk/cliproxy/rtprovider.go @@ -5,8 +5,8 @@ import ( "strings" "sync" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil" log "github.com/sirupsen/logrus" ) diff --git a/sdk/cliproxy/rtprovider_test.go b/sdk/cliproxy/rtprovider_test.go index f907081e..6ea08432 100644 --- a/sdk/cliproxy/rtprovider_test.go +++ b/sdk/cliproxy/rtprovider_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" ) func TestRoundTripperForDirectBypassesProxy(t *testing.T) { diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 9f195f56..3459c055 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -12,17 +12,18 @@ import ( "sync" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher" - "github.com/router-for-me/CLIProxyAPI/v6/internal/wsrelay" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api" + "github.com/router-for-me/CLIProxyAPI/v7/internal/home" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher" + "github.com/router-for-me/CLIProxyAPI/v7/internal/wsrelay" + sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" + sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" log "github.com/sirupsen/logrus" ) @@ -36,6 +37,9 @@ type Service struct { // cfgMu protects concurrent access to the configuration. cfgMu sync.RWMutex + // configUpdateMu serializes config updates across watcher + home. + configUpdateMu sync.Mutex + // configPath is the path to the configuration file. configPath string @@ -89,6 +93,9 @@ type Service struct { // wsGateway manages websocket Gemini providers. wsGateway *wsrelay.Manager + + homeClient *home.Client + homeCancel context.CancelFunc } // RegisterUsagePlugin registers a usage plugin on the global usage manager. @@ -462,6 +469,248 @@ func (s *Service) rebindExecutors() { } } +func (s *Service) applyConfigUpdate(newCfg *config.Config) { + if s == nil { + return + } + + s.configUpdateMu.Lock() + defer s.configUpdateMu.Unlock() + + previousStrategy := "" + var previousSessionAffinity bool + var previousSessionAffinityTTL string + s.cfgMu.RLock() + if s.cfg != nil { + previousStrategy = strings.ToLower(strings.TrimSpace(s.cfg.Routing.Strategy)) + previousSessionAffinity = s.cfg.Routing.ClaudeCodeSessionAffinity || s.cfg.Routing.SessionAffinity + previousSessionAffinityTTL = s.cfg.Routing.SessionAffinityTTL + } + s.cfgMu.RUnlock() + + if newCfg == nil { + s.cfgMu.RLock() + newCfg = s.cfg + s.cfgMu.RUnlock() + } + if newCfg == nil { + return + } + + nextStrategy := strings.ToLower(strings.TrimSpace(newCfg.Routing.Strategy)) + normalizeStrategy := func(strategy string) string { + switch strategy { + case "fill-first", "fillfirst", "ff": + return "fill-first" + default: + return "round-robin" + } + } + previousStrategy = normalizeStrategy(previousStrategy) + nextStrategy = normalizeStrategy(nextStrategy) + + nextSessionAffinity := newCfg.Routing.ClaudeCodeSessionAffinity || newCfg.Routing.SessionAffinity + nextSessionAffinityTTL := newCfg.Routing.SessionAffinityTTL + + selectorChanged := previousStrategy != nextStrategy || + previousSessionAffinity != nextSessionAffinity || + previousSessionAffinityTTL != nextSessionAffinityTTL + + if s.coreManager != nil && selectorChanged { + var selector coreauth.Selector + switch nextStrategy { + case "fill-first": + selector = &coreauth.FillFirstSelector{} + default: + selector = &coreauth.RoundRobinSelector{} + } + + if nextSessionAffinity { + ttl := time.Hour + if ttlStr := strings.TrimSpace(nextSessionAffinityTTL); ttlStr != "" { + if parsed, err := time.ParseDuration(ttlStr); err == nil && parsed > 0 { + ttl = parsed + } + } + selector = coreauth.NewSessionAffinitySelectorWithConfig(coreauth.SessionAffinityConfig{ + Fallback: selector, + TTL: ttl, + }) + } + + s.coreManager.SetSelector(selector) + } + + s.applyRetryConfig(newCfg) + s.applyPprofConfig(newCfg) + if s.server != nil { + s.server.UpdateClients(newCfg) + } + s.cfgMu.Lock() + s.cfg = newCfg + s.cfgMu.Unlock() + if s.coreManager != nil { + s.coreManager.SetConfig(newCfg) + s.coreManager.SetOAuthModelAlias(newCfg.OAuthModelAlias) + } + s.rebindExecutors() +} + +func forceHomeRuntimeConfig(cfg *config.Config) { + if cfg == nil { + return + } + cfg.APIKeys = nil + cfg.DisableCooling = true + cfg.WebsocketAuth = false + cfg.EnableGeminiCLIEndpoint = false + cfg.RemoteManagement.AllowRemote = false + cfg.RemoteManagement.DisableControlPanel = true +} + +func (s *Service) registerHomeExecutors() { + if s == nil || s.coreManager == nil || s.cfg == nil { + return + } + + // Register baseline executors so home-dispatched auth entries can execute without + // requiring any local auth-dir credentials. + s.coreManager.RegisterExecutor(executor.NewCodexAutoExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewGeminiVertexExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewAIStudioExecutor(s.cfg, "", s.wsGateway)) + s.coreManager.RegisterExecutor(executor.NewAntigravityExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewKimiExecutor(s.cfg)) + s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor("openai-compatibility", s.cfg)) +} + +func (s *Service) applyHomeOverlay(remoteCfg *config.Config) { + if s == nil || remoteCfg == nil { + return + } + + s.cfgMu.RLock() + baseCfg := s.cfg + s.cfgMu.RUnlock() + if baseCfg == nil { + return + } + + merged := *remoteCfg + merged.Host = baseCfg.Host + merged.Port = baseCfg.Port + merged.TLS = baseCfg.TLS + merged.Home = baseCfg.Home + forceHomeRuntimeConfig(&merged) + + s.applyConfigUpdate(&merged) +} + +func (s *Service) startHomeUsageForwarder(ctx context.Context, client *home.Client) { + if s == nil || client == nil { + return + } + if ctx == nil { + ctx = context.Background() + } + + sleep := func(d time.Duration) bool { + if d <= 0 { + return true + } + timer := time.NewTimer(d) + defer timer.Stop() + select { + case <-ctx.Done(): + return false + case <-timer.C: + return true + } + } + + go func() { + for { + select { + case <-ctx.Done(): + return + default: + } + + if !client.HeartbeatOK() { + if !sleep(time.Second) { + return + } + continue + } + + items := redisqueue.PopOldest(64) + if len(items) == 0 { + if !sleep(500 * time.Millisecond) { + return + } + continue + } + + for i := range items { + if errPush := client.LPushUsage(ctx, items[i]); errPush != nil { + for j := i; j < len(items); j++ { + redisqueue.Enqueue(items[j]) + } + if !sleep(time.Second) { + return + } + break + } + } + } + }() +} + +func (s *Service) startHomeSubscriber(ctx context.Context) { + if s == nil { + return + } + s.cfgMu.RLock() + cfg := s.cfg + s.cfgMu.RUnlock() + if cfg == nil || !cfg.Home.Enabled { + return + } + + if s.homeCancel != nil { + s.homeCancel() + s.homeCancel = nil + } + if s.homeClient != nil { + s.homeClient.Close() + s.homeClient = nil + } + + homeCtx := ctx + if homeCtx == nil { + homeCtx = context.Background() + } + homeCtx, cancel := context.WithCancel(homeCtx) + s.homeCancel = cancel + + client := home.New(cfg.Home) + s.homeClient = client + home.SetCurrent(client) + + go client.StartConfigSubscriber(homeCtx, func(raw []byte) error { + parsed, err := config.ParseConfigBytes(raw) + if err != nil { + log.Warnf("failed to parse home config payload: %v", err) + return err + } + s.applyHomeOverlay(parsed) + return nil + }) + s.startHomeUsageForwarder(homeCtx, client) +} + // Run starts the service and blocks until the context is cancelled or the server stops. // It initializes all components including authentication, file watching, HTTP server, // and starts processing requests. The method blocks until the context is cancelled. @@ -480,6 +729,10 @@ func (s *Service) Run(ctx context.Context) error { } usage.StartDefault(ctx) + homeEnabled := s.cfg != nil && s.cfg.Home.Enabled + if homeEnabled { + forceHomeRuntimeConfig(s.cfg) + } shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) defer shutdownCancel() @@ -489,32 +742,36 @@ func (s *Service) Run(ctx context.Context) error { } }() - if err := s.ensureAuthDir(); err != nil { - return err + if !homeEnabled { + if errEnsureAuthDir := s.ensureAuthDir(); errEnsureAuthDir != nil { + return errEnsureAuthDir + } } s.applyRetryConfig(s.cfg) - if s.coreManager != nil { + if s.coreManager != nil && !homeEnabled { if errLoad := s.coreManager.Load(ctx); errLoad != nil { log.Warnf("failed to load auth store: %v", errLoad) } } - tokenResult, err := s.tokenProvider.Load(ctx, s.cfg) - if err != nil && !errors.Is(err, context.Canceled) { - return err - } - if tokenResult == nil { - tokenResult = &TokenClientResult{} - } + if !homeEnabled { + tokenResult, err := s.tokenProvider.Load(ctx, s.cfg) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + if tokenResult == nil { + tokenResult = &TokenClientResult{} + } - apiKeyResult, err := s.apiKeyProvider.Load(ctx, s.cfg) - if err != nil && !errors.Is(err, context.Canceled) { - return err - } - if apiKeyResult == nil { - apiKeyResult = &APIKeyClientResult{} + apiKeyResult, err := s.apiKeyProvider.Load(ctx, s.cfg) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + if apiKeyResult == nil { + apiKeyResult = &APIKeyClientResult{} + } } // legacy clients removed; no caches to refresh @@ -526,6 +783,10 @@ func (s *Service) Run(ctx context.Context) error { s.authManager = newDefaultAuthManager() } + if homeEnabled { + s.startHomeSubscriber(ctx) + } + s.ensureWebsocketGateway() if s.server != nil && s.wsGateway != nil { s.server.AttachWebsocketRoute(s.wsGateway.Path(), s.wsGateway.Handler()) @@ -547,6 +808,12 @@ func (s *Service) Run(ctx context.Context) error { }) } + if homeEnabled { + s.registerHomeExecutors() + // Home mode does not expose in-process Redis RESP usage output; usage is forwarded to home instead. + redisqueue.SetEnabled(true) + } + if s.hooks.OnBeforeStart != nil { s.hooks.OnBeforeStart(s.cfg) } @@ -607,107 +874,31 @@ func (s *Service) Run(ctx context.Context) error { s.hooks.OnAfterStart(s) } - var watcherWrapper *WatcherWrapper - reloadCallback := func(newCfg *config.Config) { - previousStrategy := "" - var previousSessionAffinity bool - var previousSessionAffinityTTL string - s.cfgMu.RLock() - if s.cfg != nil { - previousStrategy = strings.ToLower(strings.TrimSpace(s.cfg.Routing.Strategy)) - previousSessionAffinity = s.cfg.Routing.ClaudeCodeSessionAffinity || s.cfg.Routing.SessionAffinity - previousSessionAffinityTTL = s.cfg.Routing.SessionAffinityTTL + if !homeEnabled { + var watcherWrapper *WatcherWrapper + reloadCallback := func(newCfg *config.Config) { s.applyConfigUpdate(newCfg) } + + watcherWrapper, errCreate := s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback) + if errCreate != nil { + return fmt.Errorf("cliproxy: failed to create watcher: %w", errCreate) } - s.cfgMu.RUnlock() - - if newCfg == nil { - s.cfgMu.RLock() - newCfg = s.cfg - s.cfgMu.RUnlock() + s.watcher = watcherWrapper + s.ensureAuthUpdateQueue(ctx) + if s.authUpdates != nil { + watcherWrapper.SetAuthUpdateQueue(s.authUpdates) } - if newCfg == nil { - return + watcherWrapper.SetConfig(s.cfg) + + watcherCtx, watcherCancel := context.WithCancel(context.Background()) + s.watcherCancel = watcherCancel + if errStart := watcherWrapper.Start(watcherCtx); errStart != nil { + return fmt.Errorf("cliproxy: failed to start watcher: %w", errStart) } - - nextStrategy := strings.ToLower(strings.TrimSpace(newCfg.Routing.Strategy)) - normalizeStrategy := func(strategy string) string { - switch strategy { - case "fill-first", "fillfirst", "ff": - return "fill-first" - default: - return "round-robin" - } - } - previousStrategy = normalizeStrategy(previousStrategy) - nextStrategy = normalizeStrategy(nextStrategy) - - nextSessionAffinity := newCfg.Routing.ClaudeCodeSessionAffinity || newCfg.Routing.SessionAffinity - nextSessionAffinityTTL := newCfg.Routing.SessionAffinityTTL - - selectorChanged := previousStrategy != nextStrategy || - previousSessionAffinity != nextSessionAffinity || - previousSessionAffinityTTL != nextSessionAffinityTTL - - if s.coreManager != nil && selectorChanged { - var selector coreauth.Selector - switch nextStrategy { - case "fill-first": - selector = &coreauth.FillFirstSelector{} - default: - selector = &coreauth.RoundRobinSelector{} - } - - if nextSessionAffinity { - ttl := time.Hour - if ttlStr := strings.TrimSpace(nextSessionAffinityTTL); ttlStr != "" { - if parsed, err := time.ParseDuration(ttlStr); err == nil && parsed > 0 { - ttl = parsed - } - } - selector = coreauth.NewSessionAffinitySelectorWithConfig(coreauth.SessionAffinityConfig{ - Fallback: selector, - TTL: ttl, - }) - } - - s.coreManager.SetSelector(selector) - } - - s.applyRetryConfig(newCfg) - s.applyPprofConfig(newCfg) - if s.server != nil { - s.server.UpdateClients(newCfg) - } - s.cfgMu.Lock() - s.cfg = newCfg - s.cfgMu.Unlock() - if s.coreManager != nil { - s.coreManager.SetConfig(newCfg) - s.coreManager.SetOAuthModelAlias(newCfg.OAuthModelAlias) - } - s.rebindExecutors() + log.Info("file watcher started for config and auth directory changes") } - watcherWrapper, err = s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback) - if err != nil { - return fmt.Errorf("cliproxy: failed to create watcher: %w", err) - } - s.watcher = watcherWrapper - s.ensureAuthUpdateQueue(ctx) - if s.authUpdates != nil { - watcherWrapper.SetAuthUpdateQueue(s.authUpdates) - } - watcherWrapper.SetConfig(s.cfg) - - watcherCtx, watcherCancel := context.WithCancel(context.Background()) - s.watcherCancel = watcherCancel - if err = watcherWrapper.Start(watcherCtx); err != nil { - return fmt.Errorf("cliproxy: failed to start watcher: %w", err) - } - log.Info("file watcher started for config and auth directory changes") - // Prefer core auth manager auto refresh if available. - if s.coreManager != nil { + if s.coreManager != nil && !homeEnabled { interval := 15 * time.Minute s.coreManager.StartAutoRefresh(context.Background(), interval) log.Infof("core auth auto-refresh started (interval=%s)", interval) @@ -717,8 +908,8 @@ func (s *Service) Run(ctx context.Context) error { case <-ctx.Done(): log.Debug("service context cancelled, shutting down...") return ctx.Err() - case err = <-s.serverErr: - return err + case errServer := <-s.serverErr: + return errServer } } @@ -741,6 +932,16 @@ func (s *Service) Shutdown(ctx context.Context) error { ctx = context.Background() } + if s.homeCancel != nil { + s.homeCancel() + s.homeCancel = nil + } + if s.homeClient != nil { + s.homeClient.Close() + s.homeClient = nil + } + home.ClearCurrent() + // legacy refresh loop removed; only stopping core auth manager below if s.watcherCancel != nil { diff --git a/sdk/cliproxy/service_codex_executor_binding_test.go b/sdk/cliproxy/service_codex_executor_binding_test.go index bb4fc84e..20a9cd7c 100644 --- a/sdk/cliproxy/service_codex_executor_binding_test.go +++ b/sdk/cliproxy/service_codex_executor_binding_test.go @@ -3,8 +3,8 @@ package cliproxy import ( "testing" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestEnsureExecutorsForAuth_CodexDoesNotReplaceInNormalMode(t *testing.T) { diff --git a/sdk/cliproxy/service_excluded_models_test.go b/sdk/cliproxy/service_excluded_models_test.go index 198a5bed..fc16c095 100644 --- a/sdk/cliproxy/service_excluded_models_test.go +++ b/sdk/cliproxy/service_excluded_models_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestRegisterModelsForAuth_UsesPreMergedExcludedModelsAttribute(t *testing.T) { diff --git a/sdk/cliproxy/service_oauth_model_alias_test.go b/sdk/cliproxy/service_oauth_model_alias_test.go index 2caf7a17..7405f7ca 100644 --- a/sdk/cliproxy/service_oauth_model_alias_test.go +++ b/sdk/cliproxy/service_oauth_model_alias_test.go @@ -3,7 +3,7 @@ package cliproxy import ( "testing" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestApplyOAuthModelAlias_Rename(t *testing.T) { diff --git a/sdk/cliproxy/service_stale_state_test.go b/sdk/cliproxy/service_stale_state_test.go index 010218d9..8943d679 100644 --- a/sdk/cliproxy/service_stale_state_test.go +++ b/sdk/cliproxy/service_stale_state_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func TestServiceApplyCoreAuthAddOrUpdate_DeleteReAddDoesNotInheritStaleRuntimeState(t *testing.T) { diff --git a/sdk/cliproxy/types.go b/sdk/cliproxy/types.go index 1521dffe..c30b712b 100644 --- a/sdk/cliproxy/types.go +++ b/sdk/cliproxy/types.go @@ -6,9 +6,9 @@ package cliproxy import ( "context" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) // TokenClientProvider loads clients backed by stored authentication tokens. diff --git a/sdk/cliproxy/watcher.go b/sdk/cliproxy/watcher.go index caeadf19..e4a9081b 100644 --- a/sdk/cliproxy/watcher.go +++ b/sdk/cliproxy/watcher.go @@ -3,9 +3,9 @@ package cliproxy import ( "context" - "github.com/router-for-me/CLIProxyAPI/v6/internal/watcher" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher" + coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) func defaultWatcherFactory(configPath, authDir string, reload func(*config.Config)) (*WatcherWrapper, error) { diff --git a/sdk/config/config.go b/sdk/config/config.go index 14163418..d39e512d 100644 --- a/sdk/config/config.go +++ b/sdk/config/config.go @@ -4,7 +4,7 @@ // embed CLIProxyAPI without importing internal packages. package config -import internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" +import internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" type SDKConfig = internalconfig.SDKConfig @@ -41,6 +41,8 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) { return internalconfig.LoadConfigOptional(configFile, optional) } +func ParseConfigBytes(data []byte) (*Config, error) { return internalconfig.ParseConfigBytes(data) } + func SaveConfigPreserveComments(configFile string, cfg *Config) error { return internalconfig.SaveConfigPreserveComments(configFile, cfg) } diff --git a/sdk/logging/request_logger.go b/sdk/logging/request_logger.go index ddbda6b8..5f8cf754 100644 --- a/sdk/logging/request_logger.go +++ b/sdk/logging/request_logger.go @@ -1,7 +1,7 @@ // Package logging re-exports request logging primitives for SDK consumers. package logging -import internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" +import internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" const defaultErrorLogsMaxFiles = 10 diff --git a/sdk/translator/builtin/builtin.go b/sdk/translator/builtin/builtin.go index 798e43f1..f95e6587 100644 --- a/sdk/translator/builtin/builtin.go +++ b/sdk/translator/builtin/builtin.go @@ -2,9 +2,9 @@ package builtin import ( - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" ) // Registry exposes the default registry populated with all built-in translators. diff --git a/test/amp_management_test.go b/test/amp_management_test.go index e384ef0e..6c694db6 100644 --- a/test/amp_management_test.go +++ b/test/amp_management_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/management" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" ) func init() { diff --git a/test/builtin_tools_translation_test.go b/test/builtin_tools_translation_test.go index 07d76715..70ee0ac1 100644 --- a/test/builtin_tools_translation_test.go +++ b/test/builtin_tools_translation_test.go @@ -3,9 +3,9 @@ package test import ( "testing" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) diff --git a/test/thinking_conversion_test.go b/test/thinking_conversion_test.go index 51671a9c..9173aa01 100644 --- a/test/thinking_conversion_test.go +++ b/test/thinking_conversion_test.go @@ -5,20 +5,20 @@ import ( "testing" "time" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator" // Import provider packages to trigger init() registration of ProviderAppliers - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/antigravity" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/claude" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/codex" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/gemini" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/geminicli" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/kimi" - _ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/openai" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/antigravity" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/claude" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/codex" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/gemini" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/geminicli" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/kimi" + _ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/openai" - "github.com/router-for-me/CLIProxyAPI/v6/internal/registry" - "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/registry" + "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/test/usage_logging_test.go b/test/usage_logging_test.go index ee03c4d7..bcf6d192 100644 --- a/test/usage_logging_test.go +++ b/test/usage_logging_test.go @@ -9,12 +9,12 @@ import ( "testing" "time" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue" - runtimeexecutor "github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor" - cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue" + runtimeexecutor "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" ) func TestGeminiExecutorRecordsSuccessfulZeroUsageInQueue(t *testing.T) { From 1721994111ef247aede7571dc372137e471d7607 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 00:23:45 +0800 Subject: [PATCH 02/10] feat(management): expose additional OAuth and configuration helpers - Added new helper methods for OAuth session management (`RegisterOAuthSession`, `CompleteOAuthSession`, etc.). - Introduced `WriteConfig` for persisting management configurations. - Exported `Handler` type and `NewHandler` constructors for SDK consumers. --- sdk/api/management.go | 83 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/sdk/api/management.go b/sdk/api/management.go index 3ed586d8..689cda3d 100644 --- a/sdk/api/management.go +++ b/sdk/api/management.go @@ -1,16 +1,21 @@ // Package api exposes helpers for embedding CLIProxyAPI. // -// It wraps internal management handler types so external projects can integrate -// management endpoints without importing internal packages. +// It wraps internal management handler types and helpers so external projects +// can integrate management endpoints without importing internal packages. package api import ( + "context" + "github.com/gin-gonic/gin" internalmanagement "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/router-for-me/CLIProxyAPI/v7/sdk/config" ) +// Handler re-exports the management handler used by the internal HTTP API. +type Handler = internalmanagement.Handler + // ManagementTokenRequester exposes a limited subset of management endpoints for requesting tokens. type ManagementTokenRequester interface { RequestAnthropicToken(*gin.Context) @@ -23,13 +28,23 @@ type ManagementTokenRequester interface { } type managementTokenRequester struct { - handler *internalmanagement.Handler + handler *Handler +} + +// NewHandler creates a management handler for SDK consumers. +func NewHandler(cfg *config.Config, configFilePath string, manager *coreauth.Manager) *Handler { + return internalmanagement.NewHandler(cfg, configFilePath, manager) +} + +// NewHandlerWithoutConfigFilePath creates a management handler that skips config file persistence. +func NewHandlerWithoutConfigFilePath(cfg *config.Config, manager *coreauth.Manager) *Handler { + return internalmanagement.NewHandlerWithoutConfigFilePath(cfg, manager) } // NewManagementTokenRequester creates a limited management handler exposing only token request endpoints. func NewManagementTokenRequester(cfg *config.Config, manager *coreauth.Manager) ManagementTokenRequester { return &managementTokenRequester{ - handler: internalmanagement.NewHandlerWithoutConfigFilePath(cfg, manager), + handler: NewHandlerWithoutConfigFilePath(cfg, manager), } } @@ -60,3 +75,63 @@ func (m *managementTokenRequester) GetAuthStatus(c *gin.Context) { func (m *managementTokenRequester) PostOAuthCallback(c *gin.Context) { m.handler.PostOAuthCallback(c) } + +// WriteConfig persists management configuration to disk. +func WriteConfig(path string, data []byte) error { + return internalmanagement.WriteConfig(path, data) +} + +// RegisterOAuthSession records a pending OAuth callback state. +func RegisterOAuthSession(state, provider string) { + internalmanagement.RegisterOAuthSession(state, provider) +} + +// SetOAuthSessionError stores an OAuth session error message. +func SetOAuthSessionError(state, message string) { + internalmanagement.SetOAuthSessionError(state, message) +} + +// CompleteOAuthSession marks a single OAuth session as completed. +func CompleteOAuthSession(state string) { + internalmanagement.CompleteOAuthSession(state) +} + +// CompleteOAuthSessionsByProvider removes all pending OAuth sessions for a provider. +func CompleteOAuthSessionsByProvider(provider string) int { + return internalmanagement.CompleteOAuthSessionsByProvider(provider) +} + +// GetOAuthSession returns the current OAuth session state. +func GetOAuthSession(state string) (provider string, status string, ok bool) { + return internalmanagement.GetOAuthSession(state) +} + +// IsOAuthSessionPending reports whether a provider/state pair is still pending. +func IsOAuthSessionPending(state, provider string) bool { + return internalmanagement.IsOAuthSessionPending(state, provider) +} + +// ValidateOAuthState validates an OAuth state token. +func ValidateOAuthState(state string) error { + return internalmanagement.ValidateOAuthState(state) +} + +// NormalizeOAuthProvider normalizes a provider name to its canonical form. +func NormalizeOAuthProvider(provider string) (string, error) { + return internalmanagement.NormalizeOAuthProvider(provider) +} + +// WriteOAuthCallbackFile writes an OAuth callback payload to disk. +func WriteOAuthCallbackFile(authDir, provider, state, code, errorMessage string) (string, error) { + return internalmanagement.WriteOAuthCallbackFile(authDir, provider, state, code, errorMessage) +} + +// WriteOAuthCallbackFileForPendingSession writes an OAuth callback payload for a pending session. +func WriteOAuthCallbackFileForPendingSession(authDir, provider, state, code, errorMessage string) (string, error) { + return internalmanagement.WriteOAuthCallbackFileForPendingSession(authDir, provider, state, code, errorMessage) +} + +// PopulateAuthContext copies auth metadata from a Gin context into a request context. +func PopulateAuthContext(ctx context.Context, c *gin.Context) context.Context { + return internalmanagement.PopulateAuthContext(ctx, c) +} From c67096b6870d3bbb9c6e4ef7a315e77b3d82b375 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 07:14:44 +0800 Subject: [PATCH 03/10] feat(server): add support for loading configuration from a remote home control plane - Introduced `-home` and `-home-password` flags for specifying home control plane address and authentication. - Implemented fetching and parsing configuration from the home control plane when `-home` is used. - Adjusted server configuration handling to bypass local config files when loading from home. - Ensured compatibility with cloud deploy mode and validation of home configurations. --- cmd/server/main.go | 102 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 44a314ae..48110380 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -10,9 +10,11 @@ import ( "fmt" "io" "io/fs" + "net" "net/url" "os" "path/filepath" + "strconv" "strings" "time" @@ -21,6 +23,7 @@ import ( "github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo" "github.com/router-for-me/CLIProxyAPI/v7/internal/cmd" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" + "github.com/router-for-me/CLIProxyAPI/v7/internal/home" "github.com/router-for-me/CLIProxyAPI/v7/internal/logging" "github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset" "github.com/router-for-me/CLIProxyAPI/v7/internal/misc" @@ -70,6 +73,8 @@ func main() { var vertexImportPrefix string var configPath string var password string + var homeAddr string + var homePassword string var tuiMode bool var standalone bool var localModel bool @@ -88,6 +93,8 @@ func main() { flag.StringVar(&vertexImport, "vertex-import", "", "Import Vertex service account key JSON file") flag.StringVar(&vertexImportPrefix, "vertex-import-prefix", "", "Prefix for Vertex model namespacing (use with -vertex-import)") flag.StringVar(&password, "password", "", "") + flag.StringVar(&homeAddr, "home", "", "Home control plane address in host:port format (loads config from home and skips local config file)") + flag.StringVar(&homePassword, "home-password", "", "Home control plane password (Redis AUTH)") flag.BoolVar(&tuiMode, "tui", false, "Start with terminal management UI") flag.BoolVar(&standalone, "standalone", false, "In TUI mode, start an embedded local server") flag.BoolVar(&localModel, "local-model", false, "Use embedded model catalog only, skip remote model fetching") @@ -126,6 +133,7 @@ func main() { var err error var cfg *config.Config var isCloudDeploy bool + var configLoadedFromHome bool var ( usePostgresStore bool pgStoreDSN string @@ -236,7 +244,67 @@ func main() { // Determine and load the configuration file. // Prefer the Postgres store when configured, otherwise fallback to git or local files. var configFilePath string - if usePostgresStore { + if strings.TrimSpace(homeAddr) != "" { + configLoadedFromHome = true + trimmedHomePassword := strings.TrimSpace(homePassword) + host, portStr, errSplit := net.SplitHostPort(strings.TrimSpace(homeAddr)) + if errSplit != nil { + log.Errorf("invalid -home address %q (expected host:port): %v", homeAddr, errSplit) + return + } + host = strings.TrimSpace(host) + if host == "" { + log.Errorf("invalid -home address %q: host is empty", homeAddr) + return + } + port, errPort := strconv.Atoi(strings.TrimSpace(portStr)) + if errPort != nil || port <= 0 { + log.Errorf("invalid -home address %q: invalid port %q", homeAddr, portStr) + return + } + + homeCfg := config.HomeConfig{ + Enabled: true, + Host: host, + Port: port, + Password: trimmedHomePassword, + } + homeClient := home.New(homeCfg) + defer homeClient.Close() + + ctxHome, cancelHome := context.WithTimeout(context.Background(), 30*time.Second) + raw, errGetConfig := homeClient.GetConfig(ctxHome) + cancelHome() + if errGetConfig != nil { + log.Errorf("failed to fetch config from home: %v", errGetConfig) + return + } + + parsed, errParseConfig := config.ParseConfigBytes(raw) + if errParseConfig != nil { + log.Errorf("failed to parse config payload from home: %v", errParseConfig) + return + } + if parsed == nil { + parsed = &config.Config{} + } + parsed.Home = homeCfg + parsed.Port = 8317 // Default to 8317 for home mode, can be overridden by home config + cfg = parsed + + // Keep a non-empty config path for downstream components (log paths, management assets, etc), + // but do not require the file to exist when loading config from home. + if strings.TrimSpace(configPath) != "" { + configFilePath = configPath + } else { + configFilePath = filepath.Join(wd, "config.yaml") + } + + // Local stores are intentionally disabled when config is loaded from home. + usePostgresStore = false + useObjectStore = false + useGitStore = false + } else if usePostgresStore { if pgStoreLocalPath == "" { pgStoreLocalPath = wd } @@ -400,21 +468,25 @@ func main() { // In cloud deploy mode, check if we have a valid configuration var configFileExists bool if isCloudDeploy { - if info, errStat := os.Stat(configFilePath); errStat != nil { - // Don't mislead: API server will not start until configuration is provided. - log.Info("Cloud deploy mode: No configuration file detected; standing by for configuration") - configFileExists = false - } else if info.IsDir() { - log.Info("Cloud deploy mode: Config path is a directory; standing by for configuration") - configFileExists = false - } else if cfg.Port == 0 { - // LoadConfigOptional returns empty config when file is empty or invalid. - // Config file exists but is empty or invalid; treat as missing config - log.Info("Cloud deploy mode: Configuration file is empty or invalid; standing by for valid configuration") - configFileExists = false + if configLoadedFromHome && cfg != nil { + configFileExists = cfg.Port != 0 } else { - log.Info("Cloud deploy mode: Configuration file detected; starting service") - configFileExists = true + if info, errStat := os.Stat(configFilePath); errStat != nil { + // Don't mislead: API server will not start until configuration is provided. + log.Info("Cloud deploy mode: No configuration file detected; standing by for configuration") + configFileExists = false + } else if info.IsDir() { + log.Info("Cloud deploy mode: Config path is a directory; standing by for configuration") + configFileExists = false + } else if cfg.Port == 0 { + // LoadConfigOptional returns empty config when file is empty or invalid. + // Config file exists but is empty or invalid; treat as missing config + log.Info("Cloud deploy mode: Configuration file is empty or invalid; standing by for valid configuration") + configFileExists = false + } else { + log.Info("Cloud deploy mode: Configuration file detected; starting service") + configFileExists = true + } } } redisqueue.SetUsageStatisticsEnabled(cfg.UsageStatisticsEnabled) From 0f0fcd230488d01e9222e69c15f045a99e89b4c8 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 10:51:27 +0800 Subject: [PATCH 04/10] feat(config): add per-auth `disable_cooling` override support - Introduced `disable_cooling` metadata field for fine-grained control over cooldown scheduling. - Updated `Auth` object to include `Metadata` with conditional logic for handling empty states. - Added YAML configuration support for `disable_cooling` in API key definitions across providers. - Enhanced unit tests to validate `disable_cooling` behavior in various scenarios. --- config.example.yaml | 4 ++ internal/config/config.go | 18 +++++--- internal/watcher/synthesizer/config.go | 41 +++++++++++++++++ internal/watcher/synthesizer/config_test.go | 51 +++++++++++++++++---- sdk/cliproxy/auth/types.go | 11 ++++- 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index f8e5978e..886d775a 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -157,6 +157,7 @@ nonstream-keepalive-interval: 0 # gemini-api-key: # - api-key: "AIzaSy...01" # prefix: "test" # optional: require calls like "test/gemini-3-pro-preview" to target this credential +# disable-cooling: false # optional: per-auth override for auth/model cooldown scheduling # base-url: "https://generativelanguage.googleapis.com" # headers: # X-Custom-Header: "custom-value" @@ -176,6 +177,7 @@ nonstream-keepalive-interval: 0 # codex-api-key: # - api-key: "sk-atSM..." # prefix: "test" # optional: require calls like "test/gpt-5-codex" to target this credential +# disable-cooling: false # optional: per-auth override for auth/model cooldown scheduling # base-url: "https://www.example.com" # use the custom codex API endpoint # headers: # X-Custom-Header: "custom-value" @@ -195,6 +197,7 @@ nonstream-keepalive-interval: 0 # - api-key: "sk-atSM..." # use the official claude API key, no need to set the base url # - api-key: "sk-atSM..." # prefix: "test" # optional: require calls like "test/claude-sonnet-latest" to target this credential +# disable-cooling: false # optional: per-auth override for auth/model cooldown scheduling # base-url: "https://www.example.com" # use the custom claude API endpoint # headers: # X-Custom-Header: "custom-value" @@ -250,6 +253,7 @@ nonstream-keepalive-interval: 0 # disabled: false # optional: set to true to disable this provider without removing it # prefix: "test" # optional: require calls like "test/kimi-k2" to target this provider's credentials # base-url: "https://openrouter.ai/api/v1" # The base URL of the provider. +# disable-cooling: false # optional: per-provider override for auth/model cooldown scheduling # headers: # X-Custom-Header: "custom-value" # api-key-entries: diff --git a/internal/config/config.go b/internal/config/config.go index e09f38a8..6f09f10d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -226,12 +226,6 @@ type RoutingConfig struct { // Supported values: "round-robin" (default), "fill-first". Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"` - // ClaudeCodeSessionAffinity enables session-sticky routing for Claude Code clients. - // When enabled, requests with the same session ID (extracted from metadata.user_id) - // are routed to the same auth credential when available. - // Deprecated: Use SessionAffinity instead for universal session support. - ClaudeCodeSessionAffinity bool `yaml:"claude-code-session-affinity,omitempty" json:"claude-code-session-affinity,omitempty"` - // SessionAffinity enables universal session-sticky routing for all clients. // Session IDs are extracted from multiple sources: // metadata.user_id (Claude Code session format), X-Session-ID, Session_id (Codex), @@ -403,6 +397,9 @@ type ClaudeKey struct { // ExcludedModels lists model IDs that should be excluded for this provider. ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` + // DisableCooling disables auth/model cooldown scheduling for this credential when true. + DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"` + // Cloak configures request cloaking for non-Claude-Code clients. Cloak *CloakConfig `yaml:"cloak,omitempty" json:"cloak,omitempty"` @@ -458,6 +455,9 @@ type CodexKey struct { // ExcludedModels lists model IDs that should be excluded for this provider. ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` + + // DisableCooling disables auth/model cooldown scheduling for this credential when true. + DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"` } func (k CodexKey) GetAPIKey() string { return k.APIKey } @@ -502,6 +502,9 @@ type GeminiKey struct { // ExcludedModels lists model IDs that should be excluded for this provider. ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` + + // DisableCooling disables auth/model cooldown scheduling for this credential when true. + DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"` } func (k GeminiKey) GetAPIKey() string { return k.APIKey } @@ -546,6 +549,9 @@ type OpenAICompatibility struct { // Headers optionally adds extra HTTP headers for requests sent to this provider. Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` + + // DisableCooling disables auth/model cooldown scheduling for this provider when true. + DisableCooling bool `yaml:"disable-cooling,omitempty" json:"disable-cooling,omitempty"` } // OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting. diff --git a/internal/watcher/synthesizer/config.go b/internal/watcher/synthesizer/config.go index ba8fe52e..1eea3dc1 100644 --- a/internal/watcher/synthesizer/config.go +++ b/internal/watcher/synthesizer/config.go @@ -60,6 +60,10 @@ func (s *ConfigSynthesizer) synthesizeGeminiKeys(ctx *SynthesisContext) []*corea "source": fmt.Sprintf("config:gemini[%s]", token), "api_key": key, } + metadata := map[string]any{} + if entry.DisableCooling { + metadata["disable_cooling"] = true + } if entry.Priority != 0 { attrs["priority"] = strconv.Itoa(entry.Priority) } @@ -78,10 +82,14 @@ func (s *ConfigSynthesizer) synthesizeGeminiKeys(ctx *SynthesisContext) []*corea Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, + Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, entry.ExcludedModels, "apikey") + if len(a.Metadata) == 0 { + a.Metadata = nil + } out = append(out, a) } return out @@ -107,6 +115,10 @@ func (s *ConfigSynthesizer) synthesizeClaudeKeys(ctx *SynthesisContext) []*corea "source": fmt.Sprintf("config:claude[%s]", token), "api_key": key, } + metadata := map[string]any{} + if ck.DisableCooling { + metadata["disable_cooling"] = true + } if ck.Priority != 0 { attrs["priority"] = strconv.Itoa(ck.Priority) } @@ -126,10 +138,14 @@ func (s *ConfigSynthesizer) synthesizeClaudeKeys(ctx *SynthesisContext) []*corea Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, + Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, ck.ExcludedModels, "apikey") + if len(a.Metadata) == 0 { + a.Metadata = nil + } out = append(out, a) } return out @@ -154,6 +170,10 @@ func (s *ConfigSynthesizer) synthesizeCodexKeys(ctx *SynthesisContext) []*coreau "source": fmt.Sprintf("config:codex[%s]", token), "api_key": key, } + metadata := map[string]any{} + if ck.DisableCooling { + metadata["disable_cooling"] = true + } if ck.Priority != 0 { attrs["priority"] = strconv.Itoa(ck.Priority) } @@ -176,10 +196,14 @@ func (s *ConfigSynthesizer) synthesizeCodexKeys(ctx *SynthesisContext) []*coreau Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, + Metadata: metadata, CreatedAt: now, UpdatedAt: now, } ApplyAuthExcludedModelsMeta(a, cfg, ck.ExcludedModels, "apikey") + if len(a.Metadata) == 0 { + a.Metadata = nil + } out = append(out, a) } return out @@ -203,6 +227,7 @@ func (s *ConfigSynthesizer) synthesizeOpenAICompat(ctx *SynthesisContext) []*cor providerName = "openai-compatibility" } base := strings.TrimSpace(compat.BaseURL) + disableCooling := compat.DisableCooling // Handle new APIKeyEntries format (preferred) createdEntries := 0 @@ -218,6 +243,10 @@ func (s *ConfigSynthesizer) synthesizeOpenAICompat(ctx *SynthesisContext) []*cor "compat_name": compat.Name, "provider_key": providerName, } + metadata := map[string]any{} + if disableCooling { + metadata["disable_cooling"] = true + } if compat.Priority != 0 { attrs["priority"] = strconv.Itoa(compat.Priority) } @@ -236,9 +265,13 @@ func (s *ConfigSynthesizer) synthesizeOpenAICompat(ctx *SynthesisContext) []*cor Status: coreauth.StatusActive, ProxyURL: proxyURL, Attributes: attrs, + Metadata: metadata, CreatedAt: now, UpdatedAt: now, } + if len(a.Metadata) == 0 { + a.Metadata = nil + } out = append(out, a) createdEntries++ } @@ -252,6 +285,10 @@ func (s *ConfigSynthesizer) synthesizeOpenAICompat(ctx *SynthesisContext) []*cor "compat_name": compat.Name, "provider_key": providerName, } + metadata := map[string]any{} + if disableCooling { + metadata["disable_cooling"] = true + } if compat.Priority != 0 { attrs["priority"] = strconv.Itoa(compat.Priority) } @@ -266,9 +303,13 @@ func (s *ConfigSynthesizer) synthesizeOpenAICompat(ctx *SynthesisContext) []*cor Prefix: prefix, Status: coreauth.StatusActive, Attributes: attrs, + Metadata: metadata, CreatedAt: now, UpdatedAt: now, } + if len(a.Metadata) == 0 { + a.Metadata = nil + } out = append(out, a) } } diff --git a/internal/watcher/synthesizer/config_test.go b/internal/watcher/synthesizer/config_test.go index c57b8fc7..c8526a65 100644 --- a/internal/watcher/synthesizer/config_test.go +++ b/internal/watcher/synthesizer/config_test.go @@ -68,11 +68,26 @@ func TestConfigSynthesizer_GeminiKeys(t *testing.T) { if auths[0].Attributes["api_key"] != "test-key-123" { t.Errorf("expected api_key test-key-123, got %s", auths[0].Attributes["api_key"]) } + if auths[0].Metadata != nil { + t.Errorf("expected metadata to be nil when disable_cooling not set, got %v", auths[0].Metadata) + } if auths[0].Status != coreauth.StatusActive { t.Errorf("expected status active, got %s", auths[0].Status) } }, }, + { + name: "gemini key disable cooling", + geminiKeys: []config.GeminiKey{ + {APIKey: "test-key-123", Prefix: "team-a", DisableCooling: true}, + }, + wantLen: 1, + validate: func(t *testing.T, auths []*coreauth.Auth) { + if v, ok := auths[0].Metadata["disable_cooling"].(bool); !ok || !v { + t.Errorf("expected disable_cooling=true, got %v", auths[0].Metadata["disable_cooling"]) + } + }, + }, { name: "gemini key with base url and proxy", geminiKeys: []config.GeminiKey{ @@ -160,9 +175,10 @@ func TestConfigSynthesizer_ClaudeKeys(t *testing.T) { Config: &config.Config{ ClaudeKey: []config.ClaudeKey{ { - APIKey: "sk-ant-api-xxx", - Prefix: "main", - BaseURL: "https://api.anthropic.com", + APIKey: "sk-ant-api-xxx", + Prefix: "main", + BaseURL: "https://api.anthropic.com", + DisableCooling: true, Models: []config.ClaudeModel{ {Name: "claude-3-opus"}, {Name: "claude-3-sonnet"}, @@ -197,6 +213,9 @@ func TestConfigSynthesizer_ClaudeKeys(t *testing.T) { if _, ok := auths[0].Attributes["models_hash"]; !ok { t.Error("expected models_hash in attributes") } + if v, ok := auths[0].Metadata["disable_cooling"].(bool); !ok || !v { + t.Errorf("expected disable_cooling=true, got %v", auths[0].Metadata["disable_cooling"]) + } } func TestConfigSynthesizer_ClaudeKeys_SkipsEmptyAndHeaders(t *testing.T) { @@ -231,11 +250,12 @@ func TestConfigSynthesizer_CodexKeys(t *testing.T) { Config: &config.Config{ CodexKey: []config.CodexKey{ { - APIKey: "codex-key-123", - Prefix: "dev", - BaseURL: "https://api.openai.com", - ProxyURL: "http://proxy.local", - Websockets: true, + APIKey: "codex-key-123", + Prefix: "dev", + BaseURL: "https://api.openai.com", + ProxyURL: "http://proxy.local", + Websockets: true, + DisableCooling: true, }, }, }, @@ -263,6 +283,9 @@ func TestConfigSynthesizer_CodexKeys(t *testing.T) { if auths[0].Attributes["websockets"] != "true" { t.Errorf("expected websockets=true, got %s", auths[0].Attributes["websockets"]) } + if v, ok := auths[0].Metadata["disable_cooling"].(bool); !ok || !v { + t.Errorf("expected disable_cooling=true, got %v", auths[0].Metadata["disable_cooling"]) + } } func TestConfigSynthesizer_CodexKeys_SkipsEmptyAndHeaders(t *testing.T) { @@ -301,8 +324,9 @@ func TestConfigSynthesizer_OpenAICompat(t *testing.T) { name: "with APIKeyEntries", compat: []config.OpenAICompatibility{ { - Name: "CustomProvider", - BaseURL: "https://custom.api.com", + Name: "CustomProvider", + BaseURL: "https://custom.api.com", + DisableCooling: true, APIKeyEntries: []config.OpenAICompatibilityAPIKey{ {APIKey: "key-1"}, {APIKey: "key-2"}, @@ -365,6 +389,13 @@ func TestConfigSynthesizer_OpenAICompat(t *testing.T) { if len(auths) != tt.wantLen { t.Fatalf("expected %d auths, got %d", tt.wantLen, len(auths)) } + if tt.name == "with APIKeyEntries" { + for i := range auths { + if v, ok := auths[i].Metadata["disable_cooling"].(bool); !ok || !v { + t.Fatalf("expected auth[%d].disable_cooling=true, got %v", i, auths[i].Metadata["disable_cooling"]) + } + } + } }) } } diff --git a/sdk/cliproxy/auth/types.go b/sdk/cliproxy/auth/types.go index 3cbd49b5..44b15652 100644 --- a/sdk/cliproxy/auth/types.go +++ b/sdk/cliproxy/auth/types.go @@ -355,19 +355,28 @@ func (a *Auth) ProxyInfo() string { return "via proxy" } -// DisableCoolingOverride returns the auth-file scoped disable_cooling override when present. +// DisableCoolingOverride returns the auth scoped disable_cooling override when present. // The value is read from metadata key "disable_cooling" (or legacy "disable-cooling"). +// +// NOTE: This override is intentionally "true-only". When the metadata value is false, it is treated +// as "not set" so the global disable-cooling flag can still take effect. func (a *Auth) DisableCoolingOverride() (bool, bool) { if a == nil || a.Metadata == nil { return false, false } if val, ok := a.Metadata["disable_cooling"]; ok { if parsed, okParse := parseBoolAny(val); okParse { + if !parsed { + return false, false + } return parsed, true } } if val, ok := a.Metadata["disable-cooling"]; ok { if parsed, okParse := parseBoolAny(val); okParse { + if !parsed { + return false, false + } return parsed, true } } From 0dcb8bd71401e25ecc511b401f09bcbae34c4342 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 10:51:49 +0800 Subject: [PATCH 05/10] refactor(cliproxy): remove `ClaudeCodeSessionAffinity` support and simplify session affinity logic --- sdk/cliproxy/builder.go | 2 +- sdk/cliproxy/service.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go index 152940a0..c7e187ee 100644 --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -214,7 +214,7 @@ func (b *Builder) Build() (*Service, error) { if b.cfg != nil { strategy = strings.ToLower(strings.TrimSpace(b.cfg.Routing.Strategy)) // Support both legacy ClaudeCodeSessionAffinity and new universal SessionAffinity - sessionAffinity = b.cfg.Routing.ClaudeCodeSessionAffinity || b.cfg.Routing.SessionAffinity + sessionAffinity = b.cfg.Routing.SessionAffinity if ttlStr := strings.TrimSpace(b.cfg.Routing.SessionAffinityTTL); ttlStr != "" { if parsed, err := time.ParseDuration(ttlStr); err == nil && parsed > 0 { sessionAffinityTTL = parsed diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 3459c055..6a94878d 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -483,7 +483,7 @@ func (s *Service) applyConfigUpdate(newCfg *config.Config) { s.cfgMu.RLock() if s.cfg != nil { previousStrategy = strings.ToLower(strings.TrimSpace(s.cfg.Routing.Strategy)) - previousSessionAffinity = s.cfg.Routing.ClaudeCodeSessionAffinity || s.cfg.Routing.SessionAffinity + previousSessionAffinity = s.cfg.Routing.SessionAffinity previousSessionAffinityTTL = s.cfg.Routing.SessionAffinityTTL } s.cfgMu.RUnlock() @@ -509,7 +509,7 @@ func (s *Service) applyConfigUpdate(newCfg *config.Config) { previousStrategy = normalizeStrategy(previousStrategy) nextStrategy = normalizeStrategy(nextStrategy) - nextSessionAffinity := newCfg.Routing.ClaudeCodeSessionAffinity || newCfg.Routing.SessionAffinity + nextSessionAffinity := newCfg.Routing.SessionAffinity nextSessionAffinityTTL := newCfg.Routing.SessionAffinityTTL selectorChanged := previousStrategy != nextStrategy || From c69ff497582b7f60731c4c6f1534f0715c14d4ba Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 19:48:42 +0800 Subject: [PATCH 06/10] feat(auth): add support for persisting `disabled` flag in token storage - Updated `FileTokenStore` and related stores (`objectstore`, `gitstore`, `postgresstore`) to include the `disabled` flag in metadata for token storage. - Adjusted `Auth` metadata handling to initialize empty maps when absent. - Refined logic in `auto_refresh_loop` and `conductor` to exclude `disabled` tokens from refresh checks. - Added comprehensive unit tests to verify proper handling of the `disabled` flag in storage and retrieval operations. --- internal/store/gitstore.go | 8 +++ internal/store/objectstore.go | 8 +++ internal/store/postgresstore.go | 8 +++ sdk/auth/filestore.go | 4 ++ sdk/auth/filestore_disabled_test.go | 64 +++++++++++++++++++++ sdk/cliproxy/auth/auto_refresh_loop.go | 2 +- sdk/cliproxy/auth/auto_refresh_loop_test.go | 28 ++++++++- sdk/cliproxy/auth/conductor.go | 4 +- 8 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 sdk/auth/filestore_disabled_test.go diff --git a/internal/store/gitstore.go b/internal/store/gitstore.go index 1610211a..ba9fe59e 100644 --- a/internal/store/gitstore.go +++ b/internal/store/gitstore.go @@ -287,10 +287,18 @@ func (s *GitTokenStore) Save(_ context.Context, auth *cliproxyauth.Auth) (string switch { case auth.Storage != nil: + if auth.Metadata == nil { + auth.Metadata = make(map[string]any) + } + auth.Metadata["disabled"] = auth.Disabled + if setter, ok := auth.Storage.(interface{ SetMetadata(map[string]any) }); ok { + setter.SetMetadata(auth.Metadata) + } if err = auth.Storage.SaveTokenToFile(path); err != nil { return "", err } case auth.Metadata != nil: + auth.Metadata["disabled"] = auth.Disabled raw, errMarshal := json.Marshal(auth.Metadata) if errMarshal != nil { return "", fmt.Errorf("auth filestore: marshal metadata failed: %w", errMarshal) diff --git a/internal/store/objectstore.go b/internal/store/objectstore.go index aa346a13..5626e6c6 100644 --- a/internal/store/objectstore.go +++ b/internal/store/objectstore.go @@ -184,10 +184,18 @@ func (s *ObjectTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (s switch { case auth.Storage != nil: + if auth.Metadata == nil { + auth.Metadata = make(map[string]any) + } + auth.Metadata["disabled"] = auth.Disabled + if setter, ok := auth.Storage.(interface{ SetMetadata(map[string]any) }); ok { + setter.SetMetadata(auth.Metadata) + } if err = auth.Storage.SaveTokenToFile(path); err != nil { return "", err } case auth.Metadata != nil: + auth.Metadata["disabled"] = auth.Disabled raw, errMarshal := json.Marshal(auth.Metadata) if errMarshal != nil { return "", fmt.Errorf("object store: marshal metadata: %w", errMarshal) diff --git a/internal/store/postgresstore.go b/internal/store/postgresstore.go index 610fc5b6..43b12500 100644 --- a/internal/store/postgresstore.go +++ b/internal/store/postgresstore.go @@ -214,10 +214,18 @@ func (s *PostgresStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (stri switch { case auth.Storage != nil: + if auth.Metadata == nil { + auth.Metadata = make(map[string]any) + } + auth.Metadata["disabled"] = auth.Disabled + if setter, ok := auth.Storage.(interface{ SetMetadata(map[string]any) }); ok { + setter.SetMetadata(auth.Metadata) + } if err = auth.Storage.SaveTokenToFile(path); err != nil { return "", err } case auth.Metadata != nil: + auth.Metadata["disabled"] = auth.Disabled raw, errMarshal := json.Marshal(auth.Metadata) if errMarshal != nil { return "", fmt.Errorf("postgres store: marshal metadata: %w", errMarshal) diff --git a/sdk/auth/filestore.go b/sdk/auth/filestore.go index 39be2d8f..5675caac 100644 --- a/sdk/auth/filestore.go +++ b/sdk/auth/filestore.go @@ -72,6 +72,10 @@ func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (str switch { case auth.Storage != nil: + if auth.Metadata == nil { + auth.Metadata = make(map[string]any) + } + auth.Metadata["disabled"] = auth.Disabled if setter, ok := auth.Storage.(metadataSetter); ok { setter.SetMetadata(auth.Metadata) } diff --git a/sdk/auth/filestore_disabled_test.go b/sdk/auth/filestore_disabled_test.go new file mode 100644 index 00000000..665f9ebf --- /dev/null +++ b/sdk/auth/filestore_disabled_test.go @@ -0,0 +1,64 @@ +package auth + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" +) + +type testTokenStorage struct { + meta map[string]any +} + +func (s *testTokenStorage) SetMetadata(meta map[string]any) { s.meta = meta } + +func (s *testTokenStorage) SaveTokenToFile(authFilePath string) error { + raw, err := json.Marshal(s.meta) + if err != nil { + return err + } + return os.WriteFile(authFilePath, raw, 0o600) +} + +func TestFileTokenStore_Save_DisabledPersistsFlagForTokenStorage(t *testing.T) { + ctx := context.Background() + baseDir := t.TempDir() + path := filepath.Join(baseDir, "disabled.json") + + if err := os.WriteFile(path, []byte(`{"type":"test","disabled":true}`), 0o600); err != nil { + t.Fatalf("seed auth file: %v", err) + } + + store := NewFileTokenStore() + store.SetBaseDir(baseDir) + storage := &testTokenStorage{} + + auth := &cliproxyauth.Auth{ + ID: "disabled.json", + Provider: "test", + FileName: "disabled.json", + Disabled: true, + Storage: storage, + Metadata: map[string]any{"type": "test"}, + } + + if _, err := store.Save(ctx, auth); err != nil { + t.Fatalf("Save() error: %v", err) + } + + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read auth file: %v", err) + } + var meta map[string]any + if err := json.Unmarshal(raw, &meta); err != nil { + t.Fatalf("unmarshal auth file: %v", err) + } + if disabled, _ := meta["disabled"].(bool); !disabled { + t.Fatalf("disabled=%v, want true (raw=%s)", meta["disabled"], string(raw)) + } +} diff --git a/sdk/cliproxy/auth/auto_refresh_loop.go b/sdk/cliproxy/auth/auto_refresh_loop.go index 9767ee58..2b544631 100644 --- a/sdk/cliproxy/auth/auto_refresh_loop.go +++ b/sdk/cliproxy/auth/auto_refresh_loop.go @@ -336,7 +336,7 @@ func (l *authAutoRefreshLoop) remove(authID string) { } func nextRefreshCheckAt(now time.Time, auth *Auth, interval time.Duration) (time.Time, bool) { - if auth == nil || auth.Disabled { + if auth == nil { return time.Time{}, false } diff --git a/sdk/cliproxy/auth/auto_refresh_loop_test.go b/sdk/cliproxy/auth/auto_refresh_loop_test.go index 420aae23..e4edb2df 100644 --- a/sdk/cliproxy/auth/auto_refresh_loop_test.go +++ b/sdk/cliproxy/auth/auto_refresh_loop_test.go @@ -34,9 +34,31 @@ func setRefreshLeadFactory(t *testing.T, provider string, factory func() *time.D func TestNextRefreshCheckAt_DisabledUnschedule(t *testing.T) { now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC) - auth := &Auth{ID: "a1", Provider: "test", Disabled: true} - if _, ok := nextRefreshCheckAt(now, auth, 15*time.Minute); ok { - t.Fatalf("nextRefreshCheckAt() ok = true, want false") + expiry := now.Add(time.Hour) + lead := 10 * time.Minute + setRefreshLeadFactory(t, "disabled-schedule", func() *time.Duration { + d := lead + return &d + }) + + auth := &Auth{ + ID: "a1", + Provider: "disabled-schedule", + Disabled: true, + Status: StatusDisabled, + Metadata: map[string]any{ + "email": "x@example.com", + "expires_at": expiry.Format(time.RFC3339), + }, + } + + got, ok := nextRefreshCheckAt(now, auth, 15*time.Minute) + if !ok { + t.Fatalf("nextRefreshCheckAt() ok = false, want true") + } + want := expiry.Add(-lead) + if !got.Equal(want) { + t.Fatalf("nextRefreshCheckAt() = %s, want %s", got, want) } } diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index f9bf0510..befdfe2c 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -3454,7 +3454,7 @@ func (m *Manager) queueRefreshReschedule(authID string) { } func (m *Manager) shouldRefresh(a *Auth, now time.Time) bool { - if a == nil || a.Disabled { + if a == nil { return false } if !a.NextRefreshAfter.IsZero() && now.Before(a.NextRefreshAfter) { @@ -3661,7 +3661,7 @@ func lookupMetadataTime(meta map[string]any, keys ...string) (time.Time, bool) { func (m *Manager) markRefreshPending(id string, now time.Time) bool { m.mu.Lock() auth, ok := m.auths[id] - if !ok || auth == nil || auth.Disabled { + if !ok || auth == nil { m.mu.Unlock() return false } From 41f4ee7c7d033570c939b296419427675851cff3 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 21:03:11 +0800 Subject: [PATCH 07/10] feat(auth): enhance auth index generation with improved file path handling - Updated `EnsureIndex` logic to incorporate absolute and cleaned file paths when generating auth indexes. - Refined metadata handling to include OAuth type in auth index seed. - Improved compatibility for `json` file paths as sources in auth attributes. - Added unit tests to validate correct auth index behavior for various path and type scenarios. --- sdk/cliproxy/auth/types.go | 73 +++++++++++++++++++++------------ sdk/cliproxy/auth/types_test.go | 38 ++++++++++++++++- 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/sdk/cliproxy/auth/types.go b/sdk/cliproxy/auth/types.go index 44b15652..882c25ea 100644 --- a/sdk/cliproxy/auth/types.go +++ b/sdk/cliproxy/auth/types.go @@ -7,6 +7,7 @@ import ( "encoding/json" "net/http" "net/url" + "path/filepath" "strconv" "strings" "sync" @@ -256,45 +257,65 @@ func (a *Auth) indexSeed() string { return "" } - if fileName := strings.TrimSpace(a.FileName); fileName != "" { - return "file:" + fileName - } - - providerKey := strings.ToLower(strings.TrimSpace(a.Provider)) + provider := strings.ToLower(strings.TrimSpace(a.Provider)) compatName := "" baseURL := "" apiKey := "" - source := "" + filePath := "" if a.Attributes != nil { - if value := strings.TrimSpace(a.Attributes["provider_key"]); value != "" { - providerKey = strings.ToLower(value) - } - compatName = strings.ToLower(strings.TrimSpace(a.Attributes["compat_name"])) + compatName = strings.TrimSpace(a.Attributes["compat_name"]) baseURL = strings.TrimSpace(a.Attributes["base_url"]) apiKey = strings.TrimSpace(a.Attributes["api_key"]) - source = strings.TrimSpace(a.Attributes["source"]) + filePath = strings.TrimSpace(a.Attributes["path"]) + if filePath == "" { + filePath = strings.TrimSpace(a.Attributes["source"]) + } } - proxyURL := strings.TrimSpace(a.ProxyURL) - hasCredentialIdentity := compatName != "" || baseURL != "" || proxyURL != "" || apiKey != "" || source != "" - if providerKey != "" && hasCredentialIdentity { - parts := []string{"provider=" + providerKey} - if compatName != "" { - parts = append(parts, "compat="+compatName) + if filePath == "" { + filePath = strings.TrimSpace(a.FileName) + } + if filePath == "" { + filePath = strings.TrimSpace(a.ID) + } + + if filePath != "" && strings.HasSuffix(strings.ToLower(filePath), ".json") { + abs, errAbs := filepath.Abs(filePath) + if errAbs == nil && strings.TrimSpace(abs) != "" { + filePath = abs } - if baseURL != "" { - parts = append(parts, "base="+baseURL) + filePath = filepath.Clean(filePath) + + authType := "" + if a.Metadata != nil { + if rawType, ok := a.Metadata["type"].(string); ok { + authType = strings.TrimSpace(rawType) + } } - if proxyURL != "" { - parts = append(parts, "proxy="+proxyURL) + if authType == "" { + authType = strings.TrimSpace(provider) } - if apiKey != "" { - parts = append(parts, "api_key="+apiKey) + authType = strings.ToLower(strings.TrimSpace(authType)) + if authType != "" { + return authType + ":" + filePath } - if source != "" { - parts = append(parts, "source="+source) + } + + apiPrefix := "" + if apiKey != "" { + switch { + case compatName != "" || strings.EqualFold(provider, "openai-compatibility"): + apiPrefix = "openai-compatibility" + case strings.EqualFold(provider, "gemini"): + apiPrefix = "gemini-api-key" + case strings.EqualFold(provider, "codex"): + apiPrefix = "codex-api-key" + case strings.EqualFold(provider, "claude"): + apiPrefix = "claude-api-key" } - return "config:" + strings.Join(parts, "\x00") + } + if apiPrefix != "" { + return apiPrefix + ":" + strings.TrimSpace(baseURL) + "+" + strings.TrimSpace(apiKey) } if id := strings.TrimSpace(a.ID); id != "" { diff --git a/sdk/cliproxy/auth/types_test.go b/sdk/cliproxy/auth/types_test.go index 06836da1..f579bfda 100644 --- a/sdk/cliproxy/auth/types_test.go +++ b/sdk/cliproxy/auth/types_test.go @@ -1,6 +1,8 @@ package auth import ( + "os" + "path/filepath" "strings" "testing" "time" @@ -96,8 +98,40 @@ func TestEnsureIndexUsesCredentialIdentity(t *testing.T) { if geminiIndex == altBaseIndex { t.Fatalf("same provider/key with different base_url produced duplicate auth_index %q", geminiIndex) } - if geminiIndex == duplicateIndex { - t.Fatalf("duplicate config entries should be separated by source-derived seed, got %q", geminiIndex) + if geminiIndex != duplicateIndex { + t.Fatalf("same provider/key with different source should share auth_index, got %q vs %q", geminiIndex, duplicateIndex) + } +} + +func TestEnsureIndexUsesOAuthTypeAndAbsolutePath(t *testing.T) { + t.Parallel() + + wd, errWd := os.Getwd() + if errWd != nil { + t.Fatalf("os.Getwd returned error: %v", errWd) + } + + relPath := "test-oauth.json" + absPath := filepath.Join(wd, relPath) + expectedSeed := "gemini:" + filepath.Clean(absPath) + expectedIndex := stableAuthIndex(expectedSeed) + + a := &Auth{ + Provider: "gemini-cli", + Attributes: map[string]string{ + "path": relPath, + }, + Metadata: map[string]any{ + "type": "gemini", + }, + } + + got := a.EnsureIndex() + if got == "" { + t.Fatal("auth index should not be empty") + } + if got != expectedIndex { + t.Fatalf("auth index = %q, want %q", got, expectedIndex) } } From 1abf8625d8215f113d8644e37bae4fd6a672b6e3 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sat, 9 May 2026 23:39:59 +0800 Subject: [PATCH 08/10] feat(logging): add home request-log forwarding support - Introduced `SetHomeEnabled` to enable/disable request-log forwarding to the home control plane. - Implemented `forwardRequestLogToHome` for non-streaming logs and `homeStreamingLogWriter` for real-time streaming logs. - Enhanced `FileRequestLogger` to bypass local logging when home forwarding is enabled. - Updated server configuration to dynamically toggle home request-log forwarding based on changes. - Added corresponding unit tests to ensure correct forwarding behavior and fallback mechanisms. --- internal/api/server.go | 10 +- internal/home/client.go | 11 + internal/logging/request_logger.go | 276 +++++++++++++++++++ internal/logging/request_logger_home_test.go | 154 +++++++++++ 4 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 internal/logging/request_logger_home_test.go diff --git a/internal/api/server.go b/internal/api/server.go index 1e29580f..04f1fb0a 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -67,7 +67,9 @@ type ServerOption func(*serverOptionConfig) func defaultRequestLoggerFactory(cfg *config.Config, configPath string) logging.RequestLogger { configDir := filepath.Dir(configPath) logsDir := logging.ResolveLogDirectory(cfg) - return logging.NewFileRequestLogger(cfg.RequestLog, logsDir, configDir, cfg.ErrorLogsMaxFiles) + logger := logging.NewFileRequestLogger(cfg.RequestLog, logsDir, configDir, cfg.ErrorLogsMaxFiles) + logger.SetHomeEnabled(cfg != nil && cfg.Home.Enabled) + return logger } // WithMiddleware appends additional Gin middleware during server construction. @@ -1197,6 +1199,12 @@ func (s *Server) UpdateClients(cfg *config.Config) { } } + if oldCfg == nil || oldCfg.Home.Enabled != cfg.Home.Enabled { + if setter, ok := s.requestLogger.(interface{ SetHomeEnabled(bool) }); ok { + setter.SetHomeEnabled(cfg.Home.Enabled) + } + } + if oldCfg == nil || oldCfg.LoggingToFile != cfg.LoggingToFile || oldCfg.LogsMaxTotalSizeMB != cfg.LogsMaxTotalSizeMB { if err := logging.ConfigureLogOutput(cfg); err != nil { log.Errorf("failed to reconfigure log output: %v", err) diff --git a/internal/home/client.go b/internal/home/client.go index 22a18b32..e99ef753 100644 --- a/internal/home/client.go +++ b/internal/home/client.go @@ -20,6 +20,7 @@ const ( redisChannelConfig = "config" redisKeyModels = "models" redisKeyUsage = "usage" + redisKeyRequestLog = "request-log" homeReconnectInterval = time.Second ) @@ -261,6 +262,16 @@ func (c *Client) LPushUsage(ctx context.Context, payload []byte) error { return c.cmd.LPush(ctx, redisKeyUsage, payload).Err() } +func (c *Client) RPushRequestLog(ctx context.Context, payload []byte) error { + if err := c.ensureClients(); err != nil { + return err + } + if len(payload) == 0 { + return nil + } + return c.cmd.RPush(ctx, redisKeyRequestLog, payload).Err() +} + // StartConfigSubscriber connects to home, fetches config once via GET config, then subscribes to // the "config" channel to receive runtime config updates. // diff --git a/internal/logging/request_logger.go b/internal/logging/request_logger.go index d650212f..44b2c952 100644 --- a/internal/logging/request_logger.go +++ b/internal/logging/request_logger.go @@ -8,6 +8,8 @@ import ( "bytes" "compress/flate" "compress/gzip" + "context" + "encoding/json" "fmt" "io" "os" @@ -23,12 +25,22 @@ import ( log "github.com/sirupsen/logrus" "github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo" + "github.com/router-for-me/CLIProxyAPI/v7/internal/home" "github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" ) var requestLogID atomic.Uint64 +type homeRequestLogClient interface { + HeartbeatOK() bool + RPushRequestLog(ctx context.Context, payload []byte) error +} + +var currentHomeRequestLogClient = func() homeRequestLogClient { + return home.Current() +} + // RequestLogger defines the interface for logging HTTP requests and responses. // It provides methods for logging both regular and streaming HTTP request/response cycles. type RequestLogger interface { @@ -148,6 +160,58 @@ type FileRequestLogger struct { // errorLogsMaxFiles limits the number of error log files retained. errorLogsMaxFiles int + + homeEnabled bool +} + +type homeRequestLogPayload struct { + Headers map[string][]string `json:"headers,omitempty"` + RequestLog string `json:"request_log,omitempty"` +} + +func cloneHeaders(headers map[string][]string) map[string][]string { + if len(headers) == 0 { + return nil + } + out := make(map[string][]string, len(headers)) + for key, values := range headers { + if strings.TrimSpace(key) == "" { + continue + } + if values == nil { + out[key] = nil + continue + } + copied := make([]string, len(values)) + copy(copied, values) + out[key] = copied + } + if len(out) == 0 { + return nil + } + return out +} + +func (l *FileRequestLogger) forwardRequestLogToHome(ctx context.Context, headers map[string][]string, logText string) error { + if l == nil || !l.homeEnabled { + return nil + } + client := currentHomeRequestLogClient() + if client == nil || !client.HeartbeatOK() { + return nil + } + payload := homeRequestLogPayload{ + Headers: cloneHeaders(headers), + RequestLog: logText, + } + raw, errMarshal := json.Marshal(&payload) + if errMarshal != nil { + return errMarshal + } + if ctx == nil { + ctx = context.Background() + } + return client.RPushRequestLog(ctx, raw) } // NewFileRequestLogger creates a new file-based request logger. @@ -173,9 +237,19 @@ func NewFileRequestLogger(enabled bool, logsDir string, configDir string, errorL enabled: enabled, logsDir: logsDir, errorLogsMaxFiles: errorLogsMaxFiles, + homeEnabled: false, } } +// SetHomeEnabled toggles home request-log forwarding. +// When enabled, request logs are not written to disk and are instead forwarded to home via Redis RESP. +func (l *FileRequestLogger) SetHomeEnabled(enabled bool) { + if l == nil { + return + } + l.homeEnabled = enabled +} + // IsEnabled returns whether request logging is currently enabled. // // Returns: @@ -231,6 +305,38 @@ func (l *FileRequestLogger) logRequest(url, method string, requestHeaders map[st return nil } + if l.homeEnabled && l.enabled { + responseToWrite, decompressErr := l.decompressResponse(responseHeaders, response) + if decompressErr != nil { + responseToWrite = response + } + + var buf bytes.Buffer + writeErr := l.writeNonStreamingLog( + &buf, + url, + method, + requestHeaders, + body, + "", + websocketTimeline, + apiRequest, + apiResponse, + apiWebsocketTimeline, + apiResponseErrors, + statusCode, + responseHeaders, + responseToWrite, + decompressErr, + requestTimestamp, + apiResponseTimestamp, + ) + if writeErr != nil { + return fmt.Errorf("failed to build request log content: %w", writeErr) + } + return l.forwardRequestLogToHome(context.Background(), requestHeaders, buf.String()) + } + // Ensure logs directory exists if errEnsure := l.ensureLogsDir(); errEnsure != nil { return fmt.Errorf("failed to create logs directory: %w", errEnsure) @@ -321,6 +427,14 @@ func (l *FileRequestLogger) LogStreamingRequest(url, method string, headers map[ return &NoOpStreamingLogWriter{}, nil } + if l.homeEnabled { + client := home.Current() + if client == nil || !client.HeartbeatOK() { + return &NoOpStreamingLogWriter{}, nil + } + return newHomeStreamingLogWriter(url, method, headers, body, requestID), nil + } + // Ensure logs directory exists if err := l.ensureLogsDir(); err != nil { return nil, fmt.Errorf("failed to create logs directory: %w", err) @@ -1498,3 +1612,165 @@ func (w *NoOpStreamingLogWriter) SetFirstChunkTimestamp(_ time.Time) {} // Returns: // - error: Always returns nil func (w *NoOpStreamingLogWriter) Close() error { return nil } + +type homeStreamingLogWriter struct { + url string + method string + timestamp time.Time + + requestHeaders map[string][]string + requestBody []byte + + chunkChan chan []byte + doneChan chan struct{} + + responseStatus int + statusWritten bool + responseHeaders map[string][]string + responseBody bytes.Buffer + apiRequest []byte + apiResponse []byte + apiWebsocketTime []byte + apiResponseTS time.Time + firstChunkTS time.Time +} + +func newHomeStreamingLogWriter(url, method string, headers map[string][]string, body []byte, _ string) *homeStreamingLogWriter { + requestHeaders := make(map[string][]string, len(headers)) + for key, values := range headers { + headerValues := make([]string, len(values)) + copy(headerValues, values) + requestHeaders[key] = headerValues + } + + writer := &homeStreamingLogWriter{ + url: url, + method: method, + timestamp: time.Now(), + requestHeaders: requestHeaders, + requestBody: append([]byte(nil), body...), + chunkChan: make(chan []byte, 100), + doneChan: make(chan struct{}), + } + + go writer.asyncWriter() + return writer +} + +func (w *homeStreamingLogWriter) asyncWriter() { + defer close(w.doneChan) + for chunk := range w.chunkChan { + if len(chunk) == 0 { + continue + } + _, _ = w.responseBody.Write(chunk) + } +} + +func (w *homeStreamingLogWriter) WriteChunkAsync(chunk []byte) { + if w == nil || w.chunkChan == nil || len(chunk) == 0 { + return + } + select { + case w.chunkChan <- append([]byte(nil), chunk...): + default: + } +} + +func (w *homeStreamingLogWriter) WriteStatus(status int, headers map[string][]string) error { + if w == nil || status == 0 { + return nil + } + w.responseStatus = status + w.statusWritten = true + if headers != nil { + w.responseHeaders = make(map[string][]string, len(headers)) + for key, values := range headers { + copied := make([]string, len(values)) + copy(copied, values) + w.responseHeaders[key] = copied + } + } + return nil +} + +func (w *homeStreamingLogWriter) WriteAPIRequest(apiRequest []byte) error { + if w == nil || len(apiRequest) == 0 { + return nil + } + w.apiRequest = bytes.Clone(apiRequest) + return nil +} + +func (w *homeStreamingLogWriter) WriteAPIResponse(apiResponse []byte) error { + if w == nil || len(apiResponse) == 0 { + return nil + } + w.apiResponse = bytes.Clone(apiResponse) + return nil +} + +func (w *homeStreamingLogWriter) WriteAPIWebsocketTimeline(apiWebsocketTimeline []byte) error { + if w == nil || len(apiWebsocketTimeline) == 0 { + return nil + } + w.apiWebsocketTime = bytes.Clone(apiWebsocketTimeline) + return nil +} + +func (w *homeStreamingLogWriter) SetFirstChunkTimestamp(timestamp time.Time) { + if w == nil { + return + } + if !timestamp.IsZero() { + w.firstChunkTS = timestamp + w.apiResponseTS = timestamp + } +} + +func (w *homeStreamingLogWriter) Close() error { + if w == nil { + return nil + } + + client := currentHomeRequestLogClient() + if client == nil || !client.HeartbeatOK() { + return nil + } + + if w.chunkChan != nil { + close(w.chunkChan) + <-w.doneChan + w.chunkChan = nil + } + + responsePayload := w.responseBody.Bytes() + + var buf bytes.Buffer + upstreamTransport := inferUpstreamTransport(w.apiRequest, w.apiResponse, w.apiWebsocketTime, nil) + if errWrite := writeRequestInfoWithBody(&buf, w.url, w.method, w.requestHeaders, w.requestBody, "", w.timestamp, "http", upstreamTransport, true); errWrite != nil { + return errWrite + } + if errWrite := writeAPISection(&buf, "=== API WEBSOCKET TIMELINE ===\n", "=== API WEBSOCKET TIMELINE", w.apiWebsocketTime, time.Time{}); errWrite != nil { + return errWrite + } + if errWrite := writeAPISection(&buf, "=== API REQUEST ===\n", "=== API REQUEST", w.apiRequest, time.Time{}); errWrite != nil { + return errWrite + } + if errWrite := writeAPISection(&buf, "=== API RESPONSE ===\n", "=== API RESPONSE", w.apiResponse, w.apiResponseTS); errWrite != nil { + return errWrite + } + if errWrite := writeResponseSection(&buf, w.responseStatus, w.statusWritten, w.responseHeaders, bytes.NewReader(responsePayload), nil, false); errWrite != nil { + return errWrite + } + + payload := homeRequestLogPayload{ + Headers: cloneHeaders(w.requestHeaders), + RequestLog: buf.String(), + } + raw, errMarshal := json.Marshal(&payload) + if errMarshal != nil { + return errMarshal + } + return client.RPushRequestLog(context.Background(), raw) +} diff --git a/internal/logging/request_logger_home_test.go b/internal/logging/request_logger_home_test.go new file mode 100644 index 00000000..f8cdf1e4 --- /dev/null +++ b/internal/logging/request_logger_home_test.go @@ -0,0 +1,154 @@ +package logging + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "os" + "testing" + "time" +) + +type stubHomeRequestLogClient struct { + heartbeatOK bool + pushed [][]byte +} + +func (c *stubHomeRequestLogClient) HeartbeatOK() bool { return c.heartbeatOK } + +func (c *stubHomeRequestLogClient) RPushRequestLog(_ context.Context, payload []byte) error { + c.pushed = append(c.pushed, bytes.Clone(payload)) + return nil +} + +func TestFileRequestLogger_HomeEnabled_ForwardsWhenRequestLogEnabled(t *testing.T) { + original := currentHomeRequestLogClient + defer func() { + currentHomeRequestLogClient = original + }() + + stub := &stubHomeRequestLogClient{heartbeatOK: true} + currentHomeRequestLogClient = func() homeRequestLogClient { + return stub + } + + logsDir := t.TempDir() + logger := NewFileRequestLogger(true, logsDir, "", 0) + logger.SetHomeEnabled(true) + + requestHeaders := map[string][]string{ + "Content-Type": {"application/json"}, + "Authorization": {"Bearer secret"}, + } + + errLog := logger.LogRequest( + "/v1/chat/completions", + http.MethodPost, + requestHeaders, + []byte(`{"input":"hello"}`), + http.StatusOK, + map[string][]string{"Content-Type": {"application/json"}}, + []byte(`{"ok":true}`), + nil, + nil, + nil, + nil, + nil, + "req-1", + time.Now(), + time.Now(), + ) + if errLog != nil { + t.Fatalf("LogRequest error: %v", errLog) + } + + entries, errRead := os.ReadDir(logsDir) + if errRead != nil { + t.Fatalf("failed to read logs dir: %v", errRead) + } + if len(entries) != 0 { + t.Fatalf("expected no local request log files, got entries: %+v", entries) + } + + if len(stub.pushed) != 1 { + t.Fatalf("home pushed records = %d, want 1", len(stub.pushed)) + } + + var got struct { + Headers map[string][]string `json:"headers"` + RequestLog string `json:"request_log"` + } + if errUnmarshal := json.Unmarshal(stub.pushed[0], &got); errUnmarshal != nil { + t.Fatalf("unmarshal payload: %v payload=%s", errUnmarshal, string(stub.pushed[0])) + } + if got.Headers == nil || got.Headers["Content-Type"][0] != "application/json" { + t.Fatalf("headers.content-type = %+v, want application/json", got.Headers["Content-Type"]) + } + if got.Headers == nil || got.Headers["Authorization"][0] != "Bearer secret" { + t.Fatalf("headers.authorization = %+v, want Bearer secret", got.Headers["Authorization"]) + } + if got.RequestLog == "" { + t.Fatalf("request_log empty, want non-empty") + } +} + +func TestFileRequestLogger_HomeEnabled_DoesNotForwardForcedErrorLogsWhenRequestLogDisabled(t *testing.T) { + original := currentHomeRequestLogClient + defer func() { + currentHomeRequestLogClient = original + }() + + stub := &stubHomeRequestLogClient{heartbeatOK: true} + currentHomeRequestLogClient = func() homeRequestLogClient { + return stub + } + + logsDir := t.TempDir() + logger := NewFileRequestLogger(false, logsDir, "", 0) + logger.SetHomeEnabled(true) + + errLog := logger.LogRequestWithOptions( + "/v1/chat/completions", + http.MethodPost, + map[string][]string{"Content-Type": {"application/json"}}, + []byte(`{"input":"hello"}`), + http.StatusBadGateway, + map[string][]string{"Content-Type": {"application/json"}}, + []byte(`{"error":"upstream failure"}`), + nil, + nil, + nil, + nil, + nil, + true, + "req-2", + time.Now(), + time.Now(), + ) + if errLog != nil { + t.Fatalf("LogRequestWithOptions error: %v", errLog) + } + + if len(stub.pushed) != 0 { + t.Fatalf("home pushed records = %d, want 0", len(stub.pushed)) + } + + entries, errRead := os.ReadDir(logsDir) + if errRead != nil { + t.Fatalf("failed to read logs dir: %v", errRead) + } + found := false + for _, entry := range entries { + if entry.IsDir() { + continue + } + if entry.Name() != "" { + found = true + break + } + } + if !found { + t.Fatalf("expected local forced error log file when request-log disabled") + } +} From 66c3dae06b2ae5db101683ce3ef76b30361d55c0 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 10 May 2026 01:30:43 +0800 Subject: [PATCH 09/10] feat(home): implement `count` for home auth dispatch requests and enable usage statistics - Added `count` attribute to `homeAuthCount` requests to improve home message batching. - Enabled usage statistics for home mode by default and added config-level enforcement. - Adjusted failure logging to include detailed metadata in `UsageReporter`. - Updated multiple executors to pass error details to `PublishFailure` for better debugging. - Enhanced unit tests to validate `count` behavior and usage statistics enforcement across components. --- cmd/server/main.go | 1 + internal/home/client.go | 22 +++-- internal/home/client_test.go | 32 +++++++ internal/home/requests.go | 1 + internal/redisqueue/plugin.go | 25 ++++++ internal/redisqueue/plugin_test.go | 44 +++++++++- .../runtime/executor/aistudio_executor.go | 4 +- .../runtime/executor/antigravity_executor.go | 4 +- internal/runtime/executor/claude_executor.go | 4 +- internal/runtime/executor/codex_executor.go | 2 +- .../executor/codex_websockets_executor.go | 6 +- .../runtime/executor/gemini_cli_executor.go | 4 +- internal/runtime/executor/gemini_executor.go | 2 +- .../executor/gemini_vertex_executor.go | 4 +- .../runtime/executor/helps/usage_helpers.go | 51 ++++++++---- internal/runtime/executor/kimi_executor.go | 2 +- .../executor/openai_compat_executor.go | 4 +- sdk/cliproxy/auth/conductor.go | 83 ++++++++++++++++--- sdk/cliproxy/service.go | 2 + sdk/cliproxy/service_stale_state_test.go | 29 +++++++ sdk/cliproxy/usage/manager.go | 7 ++ 21 files changed, 281 insertions(+), 52 deletions(-) create mode 100644 internal/home/client_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 48110380..1ef83006 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -290,6 +290,7 @@ func main() { } parsed.Home = homeCfg parsed.Port = 8317 // Default to 8317 for home mode, can be overridden by home config + parsed.UsageStatisticsEnabled = true cfg = parsed // Keep a non-empty config path for downstream components (log paths, management assets, etc), diff --git a/internal/home/client.go b/internal/home/client.go index e99ef753..23082cc6 100644 --- a/internal/home/client.go +++ b/internal/home/client.go @@ -190,7 +190,20 @@ func headersToLowerMap(headers http.Header) map[string]string { return out } -func (c *Client) RPopAuth(ctx context.Context, requestedModel string, sessionID string, headers http.Header) ([]byte, error) { +func newAuthDispatchRequest(requestedModel string, sessionID string, headers http.Header, count int) authDispatchRequest { + if count <= 0 { + count = 1 + } + return authDispatchRequest{ + Type: "auth", + Model: requestedModel, + Count: count, + SessionID: strings.TrimSpace(sessionID), + Headers: headersToLowerMap(headers), + } +} + +func (c *Client) RPopAuth(ctx context.Context, requestedModel string, sessionID string, headers http.Header, count int) ([]byte, error) { if err := c.ensureClients(); err != nil { return nil, err } @@ -198,12 +211,7 @@ func (c *Client) RPopAuth(ctx context.Context, requestedModel string, sessionID if requestedModel == "" { return nil, fmt.Errorf("home: requested model is empty") } - req := authDispatchRequest{ - Type: "auth", - Model: requestedModel, - SessionID: strings.TrimSpace(sessionID), - Headers: headersToLowerMap(headers), - } + req := newAuthDispatchRequest(requestedModel, sessionID, headers, count) keyBytes, err := json.Marshal(&req) if err != nil { return nil, err diff --git a/internal/home/client_test.go b/internal/home/client_test.go new file mode 100644 index 00000000..625e77bc --- /dev/null +++ b/internal/home/client_test.go @@ -0,0 +1,32 @@ +package home + +import ( + "encoding/json" + "net/http" + "testing" +) + +func TestAuthDispatchRequestIncludesCount(t *testing.T) { + req := newAuthDispatchRequest("gpt-5.4", "session-1", http.Header{"Authorization": {"Bearer test"}}, 2) + + raw, err := json.Marshal(&req) + if err != nil { + t.Fatalf("marshal auth dispatch request: %v", err) + } + + var payload map[string]any + if err := json.Unmarshal(raw, &payload); err != nil { + t.Fatalf("unmarshal auth dispatch request: %v", err) + } + if got := int(payload["count"].(float64)); got != 2 { + t.Fatalf("count = %d, want 2", got) + } +} + +func TestAuthDispatchRequestDefaultsCountToOne(t *testing.T) { + req := newAuthDispatchRequest("gpt-5.4", "", nil, 0) + + if req.Count != 1 { + t.Fatalf("count = %d, want 1", req.Count) + } +} diff --git a/internal/home/requests.go b/internal/home/requests.go index d08f5a5d..07577664 100644 --- a/internal/home/requests.go +++ b/internal/home/requests.go @@ -3,6 +3,7 @@ package home type authDispatchRequest struct { Type string `json:"type"` Model string `json:"model"` + Count int `json:"count"` SessionID string `json:"session_id,omitempty"` Headers map[string]string `json:"headers,omitempty"` } diff --git a/internal/redisqueue/plugin.go b/internal/redisqueue/plugin.go index 8a99de83..e5b74cb2 100644 --- a/internal/redisqueue/plugin.go +++ b/internal/redisqueue/plugin.go @@ -66,6 +66,7 @@ func (p *usageQueuePlugin) HandleUsage(ctx context.Context, record coreusage.Rec if !failed { failed = !resolveSuccess(ctx) } + fail := resolveFail(ctx, record, failed) detail := requestDetail{ Timestamp: timestamp, @@ -74,6 +75,7 @@ func (p *usageQueuePlugin) HandleUsage(ctx context.Context, record coreusage.Rec AuthIndex: record.AuthIndex, Tokens: tokens, Failed: failed, + Fail: fail, } payload, err := json.Marshal(queuedUsageDetail{ @@ -110,6 +112,7 @@ type requestDetail struct { AuthIndex string `json:"auth_index"` Tokens tokenStats `json:"tokens"` Failed bool `json:"failed"` + Fail failDetail `json:"fail"` } type tokenStats struct { @@ -120,6 +123,28 @@ type tokenStats struct { TotalTokens int64 `json:"total_tokens"` } +type failDetail struct { + StatusCode int `json:"status_code"` + Body string `json:"body"` +} + +func resolveFail(ctx context.Context, record coreusage.Record, failed bool) failDetail { + fail := failDetail{ + StatusCode: record.Fail.StatusCode, + Body: strings.TrimSpace(record.Fail.Body), + } + if !failed { + return failDetail{StatusCode: 200} + } + if fail.StatusCode <= 0 { + fail.StatusCode = internallogging.GetResponseStatus(ctx) + } + if fail.StatusCode <= 0 { + fail.StatusCode = 500 + } + return fail +} + func resolveSuccess(ctx context.Context) bool { status := internallogging.GetResponseStatus(ctx) if status == 0 { diff --git a/internal/redisqueue/plugin_test.go b/internal/redisqueue/plugin_test.go index 4d7cb465..e2af6af7 100644 --- a/internal/redisqueue/plugin_test.go +++ b/internal/redisqueue/plugin_test.go @@ -44,9 +44,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndSuccess(t *testing.T) { requireStringField(t, payload, "alias", "client-gpt") requireStringField(t, payload, "endpoint", "POST /v1/chat/completions") requireStringField(t, payload, "auth_type", "apikey") - requireStringField(t, payload, "user_api_key", "test-key") + requireMissingField(t, payload, "user_api_key") requireStringField(t, payload, "request_id", "ctx-request-id") requireBoolField(t, payload, "failed", false) + requireFailField(t, payload, http.StatusOK, "") }) } @@ -68,6 +69,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID(t Source: "user@example.com", RequestedAt: time.Date(2026, 4, 25, 0, 0, 0, 0, time.UTC), Latency: 2500 * time.Millisecond, + Fail: coreusage.Failure{ + StatusCode: http.StatusInternalServerError, + Body: "upstream failed", + }, Detail: coreusage.Detail{ InputTokens: 10, OutputTokens: 20, @@ -81,9 +86,10 @@ func TestUsageQueuePluginPayloadIncludesStableFieldsAndFailureAndGinRequestID(t requireStringField(t, payload, "alias", "client-mini") requireStringField(t, payload, "endpoint", "GET /v1/responses") requireStringField(t, payload, "auth_type", "apikey") - requireStringField(t, payload, "user_api_key", "test-key") + requireMissingField(t, payload, "user_api_key") requireStringField(t, payload, "request_id", "gin-request-id") requireBoolField(t, payload, "failed", true) + requireFailField(t, payload, http.StatusInternalServerError, "upstream failed") }) } @@ -115,6 +121,10 @@ func TestUsageQueuePluginAsyncIgnoresRecycledGinContext(t *testing.T) { Source: "user@example.com", RequestedAt: time.Date(2026, 4, 25, 0, 0, 0, 0, time.UTC), Latency: 1500 * time.Millisecond, + Fail: coreusage.Failure{ + StatusCode: http.StatusBadGateway, + Body: "bad gateway", + }, Detail: coreusage.Detail{ InputTokens: 10, OutputTokens: 20, @@ -125,9 +135,10 @@ func TestUsageQueuePluginAsyncIgnoresRecycledGinContext(t *testing.T) { payload := waitForSinglePayload(t, 2*time.Second) requireStringField(t, payload, "endpoint", "POST /v1/chat/completions") requireStringField(t, payload, "alias", "client-gpt") - requireStringField(t, payload, "user_api_key", "test-key") + requireMissingField(t, payload, "user_api_key") requireStringField(t, payload, "request_id", "ctx-request-id") requireBoolField(t, payload, "failed", true) + requireFailField(t, payload, http.StatusBadGateway, "bad gateway") }) } @@ -217,6 +228,14 @@ func requireStringField(t *testing.T, payload map[string]json.RawMessage, key, w } } +func requireMissingField(t *testing.T, payload map[string]json.RawMessage, key string) { + t.Helper() + + if _, ok := payload[key]; ok { + t.Fatalf("payload unexpectedly contains %q", key) + } +} + type pluginFunc func(context.Context, coreusage.Record) func (fn pluginFunc) HandleUsage(ctx context.Context, record coreusage.Record) { @@ -238,3 +257,22 @@ func requireBoolField(t *testing.T, payload map[string]json.RawMessage, key stri t.Fatalf("%s = %t, want %t", key, got, want) } } + +func requireFailField(t *testing.T, payload map[string]json.RawMessage, wantStatus int, wantBody string) { + t.Helper() + + raw, ok := payload["fail"] + if !ok { + t.Fatalf("payload missing %q", "fail") + } + var got struct { + StatusCode int `json:"status_code"` + Body string `json:"body"` + } + if err := json.Unmarshal(raw, &got); err != nil { + t.Fatalf("unmarshal fail: %v", err) + } + if got.StatusCode != wantStatus || got.Body != wantBody { + t.Fatalf("fail = {status_code:%d body:%q}, want {status_code:%d body:%q}", got.StatusCode, got.Body, wantStatus, wantBody) + } +} diff --git a/internal/runtime/executor/aistudio_executor.go b/internal/runtime/executor/aistudio_executor.go index 392109b5..41365b5f 100644 --- a/internal/runtime/executor/aistudio_executor.go +++ b/internal/runtime/executor/aistudio_executor.go @@ -284,7 +284,7 @@ func (e *AIStudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth processEvent := func(event wsrelay.StreamEvent) bool { if event.Err != nil { helps.RecordAPIResponseError(ctx, e.cfg, event.Err) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, event.Err) select { case out <- cliproxyexecutor.StreamChunk{Err: fmt.Errorf("wsrelay: %v", event.Err)}: case <-ctx.Done(): @@ -336,7 +336,7 @@ func (e *AIStudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth return false case wsrelay.MessageTypeError: helps.RecordAPIResponseError(ctx, e.cfg, event.Err) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, event.Err) select { case out <- cliproxyexecutor.StreamChunk{Err: fmt.Errorf("wsrelay: %v", event.Err)}: case <-ctx.Done(): diff --git a/internal/runtime/executor/antigravity_executor.go b/internal/runtime/executor/antigravity_executor.go index 84ff9de0..2f8dff92 100644 --- a/internal/runtime/executor/antigravity_executor.go +++ b/internal/runtime/executor/antigravity_executor.go @@ -898,7 +898,7 @@ attemptLoop: } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) out <- cliproxyexecutor.StreamChunk{Err: errScan} } else { reporter.EnsurePublished(ctx) @@ -1374,7 +1374,7 @@ attemptLoop: } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index fe4f22f2..eb17864d 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -472,7 +472,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): @@ -512,7 +512,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index 36c041b6..a1bbe6b8 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -524,7 +524,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/internal/runtime/executor/codex_websockets_executor.go b/internal/runtime/executor/codex_websockets_executor.go index 86078aac..2b56f13b 100644 --- a/internal/runtime/executor/codex_websockets_executor.go +++ b/internal/runtime/executor/codex_websockets_executor.go @@ -580,7 +580,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr terminateReason = "read_error" terminateErr = errRead helps.RecordAPIWebsocketError(ctx, e.cfg, "read", errRead) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errRead) _ = send(cliproxyexecutor.StreamChunk{Err: errRead}) return } @@ -590,7 +590,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr terminateReason = "unexpected_binary" terminateErr = err helps.RecordAPIWebsocketError(ctx, e.cfg, "unexpected_binary", err) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, err) if sess != nil { e.invalidateUpstreamConn(sess, conn, "unexpected_binary", err) } @@ -610,7 +610,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr terminateReason = "upstream_error" terminateErr = wsErr helps.RecordAPIWebsocketError(ctx, e.cfg, "upstream_error", wsErr) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, wsErr) if sess != nil { e.invalidateUpstreamConn(sess, conn, "upstream_error", wsErr) } diff --git a/internal/runtime/executor/gemini_cli_executor.go b/internal/runtime/executor/gemini_cli_executor.go index 0fa7cbb2..a298fe8a 100644 --- a/internal/runtime/executor/gemini_cli_executor.go +++ b/internal/runtime/executor/gemini_cli_executor.go @@ -430,7 +430,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): @@ -444,7 +444,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut data, errRead := io.ReadAll(resp.Body) if errRead != nil { helps.RecordAPIResponseError(ctx, e.cfg, errRead) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errRead) select { case out <- cliproxyexecutor.StreamChunk{Err: errRead}: case <-ctx.Done(): diff --git a/internal/runtime/executor/gemini_executor.go b/internal/runtime/executor/gemini_executor.go index c3f08010..e8fa2e40 100644 --- a/internal/runtime/executor/gemini_executor.go +++ b/internal/runtime/executor/gemini_executor.go @@ -341,7 +341,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/internal/runtime/executor/gemini_vertex_executor.go b/internal/runtime/executor/gemini_vertex_executor.go index ae0a718b..b899524c 100644 --- a/internal/runtime/executor/gemini_vertex_executor.go +++ b/internal/runtime/executor/gemini_vertex_executor.go @@ -679,7 +679,7 @@ func (e *GeminiVertexExecutor) executeStreamWithServiceAccount(ctx context.Conte } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): @@ -821,7 +821,7 @@ func (e *GeminiVertexExecutor) executeStreamWithAPIKey(ctx context.Context, auth } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/internal/runtime/executor/helps/usage_helpers.go b/internal/runtime/executor/helps/usage_helpers.go index c72b5c1a..bef0b4ea 100644 --- a/internal/runtime/executor/helps/usage_helpers.go +++ b/internal/runtime/executor/helps/usage_helpers.go @@ -3,6 +3,7 @@ package helps import ( "bytes" "context" + "errors" "fmt" "strings" "sync" @@ -51,7 +52,7 @@ func NewUsageReporter(ctx context.Context, provider, model string, auth *cliprox } func (r *UsageReporter) Publish(ctx context.Context, detail usage.Detail) { - r.publishWithOutcome(ctx, detail, false) + r.publishWithOutcome(ctx, detail, false, usage.Failure{}) } func (r *UsageReporter) PublishAdditionalModel(ctx context.Context, model string, detail usage.Detail) { @@ -74,11 +75,11 @@ func (r *UsageReporter) buildAdditionalModelRecord(model string, detail usage.De if !hasNonZeroTokenUsage(detail) { return usage.Record{}, false } - return r.buildRecordForModel(model, detail, false), true + return r.buildRecordForModel(model, detail, false, usage.Failure{}), true } -func (r *UsageReporter) PublishFailure(ctx context.Context) { - r.publishWithOutcome(ctx, usage.Detail{}, true) +func (r *UsageReporter) PublishFailure(ctx context.Context, errs ...error) { + r.publishWithOutcome(ctx, usage.Detail{}, true, failFromErrors(errs...)) } func (r *UsageReporter) TrackFailure(ctx context.Context, errPtr *error) { @@ -86,17 +87,17 @@ func (r *UsageReporter) TrackFailure(ctx context.Context, errPtr *error) { return } if *errPtr != nil { - r.PublishFailure(ctx) + r.PublishFailure(ctx, *errPtr) } } -func (r *UsageReporter) publishWithOutcome(ctx context.Context, detail usage.Detail, failed bool) { +func (r *UsageReporter) publishWithOutcome(ctx context.Context, detail usage.Detail, failed bool, fail usage.Failure) { if r == nil { return } detail = normalizeUsageDetailTotal(detail) r.once.Do(func() { - usage.PublishRecord(ctx, r.buildRecord(detail, failed)) + usage.PublishRecord(ctx, r.buildRecord(detail, failed, fail)) }) } @@ -127,20 +128,24 @@ func (r *UsageReporter) EnsurePublished(ctx context.Context) { return } r.once.Do(func() { - usage.PublishRecord(ctx, r.buildRecord(usage.Detail{}, false)) + usage.PublishRecord(ctx, r.buildRecord(usage.Detail{}, false, usage.Failure{})) }) } -func (r *UsageReporter) buildRecord(detail usage.Detail, failed bool) usage.Record { - if r == nil { - return usage.Record{Detail: detail, Failed: failed} +func (r *UsageReporter) buildRecord(detail usage.Detail, failed bool, failures ...usage.Failure) usage.Record { + var fail usage.Failure + if len(failures) > 0 { + fail = failures[0] } - return r.buildRecordForModel(r.model, detail, failed) + if r == nil { + return usage.Record{Detail: detail, Failed: failed, Fail: fail} + } + return r.buildRecordForModel(r.model, detail, failed, fail) } -func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, failed bool) usage.Record { +func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, failed bool, fail usage.Failure) usage.Record { if r == nil { - return usage.Record{Model: model, Detail: detail, Failed: failed} + return usage.Record{Model: model, Detail: detail, Failed: failed, Fail: fail} } return usage.Record{ Provider: r.provider, @@ -154,10 +159,28 @@ func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, f RequestedAt: r.requestedAt, Latency: r.latency(), Failed: failed, + Fail: fail, Detail: detail, } } +func failFromErrors(errs ...error) usage.Failure { + for _, err := range errs { + if err == nil { + continue + } + fail := usage.Failure{ + Body: strings.TrimSpace(err.Error()), + } + var se interface{ StatusCode() int } + if errors.As(err, &se) && se != nil { + fail.StatusCode = se.StatusCode() + } + return fail + } + return usage.Failure{} +} + func (r *UsageReporter) latency() time.Duration { if r == nil || r.requestedAt.IsZero() { return 0 diff --git a/internal/runtime/executor/kimi_executor.go b/internal/runtime/executor/kimi_executor.go index f330321f..6cfaec20 100644 --- a/internal/runtime/executor/kimi_executor.go +++ b/internal/runtime/executor/kimi_executor.go @@ -307,7 +307,7 @@ func (e *KimiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/internal/runtime/executor/openai_compat_executor.go b/internal/runtime/executor/openai_compat_executor.go index de12da37..82fc9e97 100644 --- a/internal/runtime/executor/openai_compat_executor.go +++ b/internal/runtime/executor/openai_compat_executor.go @@ -296,7 +296,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy if bytes.HasPrefix(trimmedLine, []byte("{")) || bytes.HasPrefix(trimmedLine, []byte("[")) { streamErr := statusErr{code: http.StatusBadGateway, msg: string(trimmedLine)} helps.RecordAPIResponseError(ctx, e.cfg, streamErr) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, streamErr) select { case out <- cliproxyexecutor.StreamChunk{Err: streamErr}: case <-ctx.Done(): @@ -318,7 +318,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy } if errScan := scanner.Err(); errScan != nil { helps.RecordAPIResponseError(ctx, e.cfg, errScan) - reporter.PublishFailure(ctx) + reporter.PublishFailure(ctx, errScan) select { case out <- cliproxyexecutor.StreamChunk{Err: errScan}: case <-ctx.Done(): diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index befdfe2c..d339f56b 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -51,6 +51,7 @@ type ExecutionSessionCloser interface { } const ( + homeAuthCountMetadataKey = "__cliproxy_home_auth_count" // CloseAllExecutionSessionsID asks an executor to release all active execution sessions. // Executors that do not support this marker may ignore it. CloseAllExecutionSessionsID = "__all_execution_sessions__" @@ -1316,19 +1317,25 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req } routeModel := req.Model opts = ensureRequestedModelMetadata(opts, routeModel) + homeMode := m.HomeEnabled() + homeAuthCount := 1 tried := make(map[string]struct{}) attempted := make(map[string]struct{}) var lastErr error for { - if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials { + if !homeMode && maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials { if lastErr != nil { return cliproxyexecutor.Response{}, lastErr } return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"} } - auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried) + pickOpts := opts + if homeMode { + pickOpts = withHomeAuthCount(opts, homeAuthCount) + } + auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried) if errPick != nil { - if lastErr != nil { + if lastErr != nil && !homeMode { return cliproxyexecutor.Response{}, lastErr } return cliproxyexecutor.Response{}, errPick @@ -1384,6 +1391,9 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req return cliproxyexecutor.Response{}, authErr } lastErr = authErr + if homeMode { + homeAuthCount++ + } continue } } @@ -1395,19 +1405,25 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string, } routeModel := req.Model opts = ensureRequestedModelMetadata(opts, routeModel) + homeMode := m.HomeEnabled() + homeAuthCount := 1 tried := make(map[string]struct{}) attempted := make(map[string]struct{}) var lastErr error for { - if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials { + if !homeMode && maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials { if lastErr != nil { return cliproxyexecutor.Response{}, lastErr } return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"} } - auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried) + pickOpts := opts + if homeMode { + pickOpts = withHomeAuthCount(opts, homeAuthCount) + } + auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried) if errPick != nil { - if lastErr != nil { + if lastErr != nil && !homeMode { return cliproxyexecutor.Response{}, lastErr } return cliproxyexecutor.Response{}, errPick @@ -1463,6 +1479,9 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string, return cliproxyexecutor.Response{}, authErr } lastErr = authErr + if homeMode { + homeAuthCount++ + } continue } } @@ -1474,19 +1493,25 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string } routeModel := req.Model opts = ensureRequestedModelMetadata(opts, routeModel) + homeMode := m.HomeEnabled() + homeAuthCount := 1 tried := make(map[string]struct{}) attempted := make(map[string]struct{}) var lastErr error for { - if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials { + if !homeMode && maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials { if lastErr != nil { return nil, lastErr } return nil, &Error{Code: "auth_not_found", Message: "no auth available"} } - auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried) + pickOpts := opts + if homeMode { + pickOpts = withHomeAuthCount(opts, homeAuthCount) + } + auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried) if errPick != nil { - if lastErr != nil { + if lastErr != nil && !homeMode { return nil, lastErr } return nil, errPick @@ -1516,6 +1541,9 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string return nil, errStream } lastErr = errStream + if homeMode { + homeAuthCount++ + } continue } return streamResult, nil @@ -1543,6 +1571,40 @@ func ensureRequestedModelMetadata(opts cliproxyexecutor.Options, requestedModel return opts } +func withHomeAuthCount(opts cliproxyexecutor.Options, count int) cliproxyexecutor.Options { + if count <= 0 { + count = 1 + } + meta := make(map[string]any, len(opts.Metadata)+1) + for k, v := range opts.Metadata { + meta[k] = v + } + meta[homeAuthCountMetadataKey] = count + opts.Metadata = meta + return opts +} + +func homeAuthCountFromMetadata(meta map[string]any) int { + if len(meta) == 0 { + return 1 + } + switch value := meta[homeAuthCountMetadataKey].(type) { + case int: + if value > 0 { + return value + } + case int64: + if value > 0 { + return int(value) + } + case float64: + if value > 0 { + return int(value) + } + } + return 1 +} + func hasRequestedModelMetadata(meta map[string]any) bool { if len(meta) == 0 { return false @@ -3099,8 +3161,9 @@ func (m *Manager) pickNextViaHome(ctx context.Context, model string, opts clipro requestedModel := requestedModelFromMetadata(opts.Metadata, model) sessionID := ExtractSessionID(opts.Headers, opts.OriginalRequest, opts.Metadata) + count := homeAuthCountFromMetadata(opts.Metadata) - raw, err := client.RPopAuth(ctx, requestedModel, sessionID, opts.Headers) + raw, err := client.RPopAuth(ctx, requestedModel, sessionID, opts.Headers, count) if err != nil { return nil, nil, "", &Error{Code: "auth_not_found", Message: err.Error(), HTTPStatus: http.StatusServiceUnavailable} } diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 6a94878d..89a480b5 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -561,6 +561,7 @@ func forceHomeRuntimeConfig(cfg *config.Config) { return } cfg.APIKeys = nil + cfg.UsageStatisticsEnabled = true cfg.DisableCooling = true cfg.WebsocketAuth = false cfg.EnableGeminiCLIEndpoint = false @@ -732,6 +733,7 @@ func (s *Service) Run(ctx context.Context) error { homeEnabled := s.cfg != nil && s.cfg.Home.Enabled if homeEnabled { forceHomeRuntimeConfig(s.cfg) + redisqueue.SetUsageStatisticsEnabled(true) } shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) diff --git a/sdk/cliproxy/service_stale_state_test.go b/sdk/cliproxy/service_stale_state_test.go index 8943d679..53849eb3 100644 --- a/sdk/cliproxy/service_stale_state_test.go +++ b/sdk/cliproxy/service_stale_state_test.go @@ -99,3 +99,32 @@ func TestServiceApplyCoreAuthAddOrUpdate_DeleteReAddDoesNotInheritStaleRuntimeSt t.Fatalf("expected re-added auth to re-register models in global registry") } } + +func TestForceHomeRuntimeConfigEnablesUsageStatistics(t *testing.T) { + cfg := &config.Config{ + UsageStatisticsEnabled: false, + } + + forceHomeRuntimeConfig(cfg) + + if !cfg.UsageStatisticsEnabled { + t.Fatal("expected home runtime config to force usage statistics enabled") + } +} + +func TestApplyHomeOverlayForcesUsageStatisticsEnabled(t *testing.T) { + baseCfg := &config.Config{} + baseCfg.Home.Enabled = true + service := &Service{cfg: baseCfg} + + service.applyHomeOverlay(&config.Config{ + UsageStatisticsEnabled: false, + }) + + if service.cfg == nil || !service.cfg.UsageStatisticsEnabled { + t.Fatal("expected home overlay to force usage statistics enabled") + } + if !service.cfg.Home.Enabled { + t.Fatal("expected home overlay to preserve local home settings") + } +} diff --git a/sdk/cliproxy/usage/manager.go b/sdk/cliproxy/usage/manager.go index 72405d75..2305d9a4 100644 --- a/sdk/cliproxy/usage/manager.go +++ b/sdk/cliproxy/usage/manager.go @@ -22,9 +22,16 @@ type Record struct { RequestedAt time.Time Latency time.Duration Failed bool + Fail Failure Detail Detail } +// Failure holds HTTP failure metadata for an upstream request attempt. +type Failure struct { + StatusCode int + Body string +} + // Detail holds the token usage breakdown. type Detail struct { InputTokens int64 From 67fb4eb98ed7a9b456e5d75e1e727d3c4c644e37 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 10 May 2026 02:09:53 +0800 Subject: [PATCH 10/10] feat(auth): add `shouldReturnLastErrorOnPickFailure` helper and improve error handling in home mode - Introduced `shouldReturnLastErrorOnPickFailure` to streamline error return logic during provider selection. - Added `isHomeRequestRetryExceededError` for better home-specific error classification. - Updated fallback conditions to enhance error handling clarity in `pickNextMixed`. --- sdk/cliproxy/auth/conductor.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index d339f56b..4e72d7c8 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -1335,7 +1335,7 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req } auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried) if errPick != nil { - if lastErr != nil && !homeMode { + if shouldReturnLastErrorOnPickFailure(homeMode, lastErr, errPick) { return cliproxyexecutor.Response{}, lastErr } return cliproxyexecutor.Response{}, errPick @@ -1423,7 +1423,7 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string, } auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried) if errPick != nil { - if lastErr != nil && !homeMode { + if shouldReturnLastErrorOnPickFailure(homeMode, lastErr, errPick) { return cliproxyexecutor.Response{}, lastErr } return cliproxyexecutor.Response{}, errPick @@ -1511,7 +1511,7 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string } auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried) if errPick != nil { - if lastErr != nil && !homeMode { + if shouldReturnLastErrorOnPickFailure(homeMode, lastErr, errPick) { return nil, lastErr } return nil, errPick @@ -3125,7 +3125,28 @@ type homeErrorDetail struct { Code string `json:"code,omitempty"` } -const homeUpstreamModelAttributeKey = "home_upstream_model" +const ( + homeUpstreamModelAttributeKey = "home_upstream_model" + homeRequestRetryExceededErrorCode = "request_retry_exceeded" +) + +func isHomeRequestRetryExceededError(err error) bool { + var authErr *Error + if !errors.As(err, &authErr) || authErr == nil { + return false + } + return strings.EqualFold(strings.TrimSpace(authErr.Code), homeRequestRetryExceededErrorCode) +} + +func shouldReturnLastErrorOnPickFailure(homeMode bool, lastErr error, errPick error) bool { + if lastErr == nil { + return false + } + if !homeMode { + return true + } + return isHomeRequestRetryExceededError(errPick) +} type homeAuthDispatchResponse struct { Model string `json:"model"`