ccproxy-api 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.github/workflows/release.yml +3 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/CHANGELOG.md +28 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/PKG-INFO +62 -7
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/README.md +61 -6
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/_version.py +2 -2
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/app.py +70 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/routes/claude.py +0 -6
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/routes/proxy.py +13 -6
- ccproxy_api-0.1.1/ccproxy/auth/conditional.py +84 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/commands/auth.py +59 -43
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/commands/config/commands.py +2 -2
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/commands/serve.py +304 -86
- ccproxy_api-0.1.1/ccproxy/cli/main.py +115 -0
- ccproxy_api-0.1.1/ccproxy/cli/options/claude_options.py +102 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/options/core_options.py +1 -13
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/options/security_options.py +1 -9
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/options/server_options.py +1 -52
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/docker_settings.py +1 -1
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/pricing.py +5 -6
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/scheduler.py +5 -6
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/settings.py +0 -2
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/async_utils.py +1 -1
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/types.py +3 -9
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/models/messages.py +1 -2
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/models/responses.py +0 -36
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/pricing/models.py +5 -6
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/proxy_service.py +6 -7
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/getting-started/configuration.md +5 -5
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/getting-started/quickstart.md +64 -16
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/systemd-setup.md +1 -1
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/authentication.md +5 -5
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/claude-code-options.md +3 -18
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/pyproject.toml +1 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_api.py +7 -28
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_auth.py +0 -34
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_metrics_api.py +0 -11
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_observability.py +0 -7
- ccproxy_api-0.1.0/ccproxy/cli/main.py +0 -193
- ccproxy_api-0.1.0/ccproxy/cli/options/claude_options.py +0 -216
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.env.example +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.github/workflows/backend.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.github/workflows/build.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.github/workflows/ci.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.github/workflows/docs.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.github/workflows/frontend.yml.disabled +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.gitignore +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.pre-commit-config.yaml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/.python-version +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/CONTRIBUTING.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/CONVENTIONS.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/Dockerfile +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/LICENSE +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/Makefile +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/TESTING.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/__main__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/adapters/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/adapters/base.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/adapters/openai/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/adapters/openai/adapter.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/adapters/openai/models.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/adapters/openai/streaming.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/dependencies.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/auth.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/cors.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/errors.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/headers.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/logging.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/request_id.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/middleware/server_header.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/responses.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/routes/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/routes/health.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/api/routes/metrics.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/bearer.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/credentials_adapter.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/dependencies.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/exceptions.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/manager.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/models.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/oauth/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/oauth/models.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/oauth/routes.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/oauth/storage.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/storage/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/storage/base.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/storage/json_file.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/auth/storage/keyring.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/claude_sdk/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/claude_sdk/client.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/claude_sdk/converter.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/claude_sdk/options.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/commands/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/commands/config/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/commands/config/schema_commands.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/docker/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/docker/adapter_factory.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/docker/params.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/helpers.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/cli/options/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/auth.py +2 -2
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/claude.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/cors.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/discovery.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/loader.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/observability.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/reverse_proxy.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/security.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/server.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/config/validators.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/constants.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/errors.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/http.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/http_transformers.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/interfaces.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/logging.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/middleware.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/proxy.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/system.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/transformers.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/core/validators.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/adapter.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/docker_path.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/middleware.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/models.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/protocol.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/stream_process.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/docker/validators.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/models/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/models/errors.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/models/requests.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/models/types.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/access_logger.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/context.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/metrics.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/pushgateway.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/sse_events.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/stats_printer.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/storage/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/storage/duckdb_simple.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/observability/storage/models.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/pricing/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/pricing/cache.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/pricing/loader.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/pricing/updater.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/scheduler/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/scheduler/core.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/scheduler/exceptions.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/scheduler/manager.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/scheduler/registry.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/scheduler/tasks.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/claude_sdk_service.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/credentials/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/credentials/config.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/credentials/manager.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/services/credentials/oauth_client.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/static/.keep +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/testing/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/testing/config.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/testing/content_generation.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/testing/mock_responses.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/testing/response_handlers.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/testing/scenarios.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/utils/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/utils/cost_calculator.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/ccproxy/utils/streaming_metrics.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/data/metrics.db +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/devenv.lock +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/devenv.nix +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docker-compose.monitoring.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docker-compose.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/README.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/api-reference.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/assets/extra.css +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/assets/extra.js +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/contributing.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/development/debugging-with-proxy.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/examples.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/gen_ref_pages.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/getting-started/installation.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/index.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/metrics-api.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/observability.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/api-usage.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/claude-sdk-compatibility.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/mcp-integration.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/pool-configuration.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/docs/user-guide/understanding-pool-logs.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/README.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/README_chat_agent.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/ai_code_discussion_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/anthropic_streaming_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/anthropic_tools_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/client.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/common_utils.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/console_utils.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_anthropic_conversation_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_claude_code_options_example.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_json_object_example.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_json_schema_example.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_streaming_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_structured_response_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_thinking_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/openai_tools_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/project_code_access_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/simple_thinking_demo.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/examples/textual_chat_agent.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/mkdocs.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/monitoring/grafana/dashboards/ccproxy-dashboard.json +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/monitoring/grafana/provisioning/dashboards/dashboard.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/monitoring/grafana/provisioning/datasources/victoria-metrics.yml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/act-run.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/benchmark_startup_node.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/build-docs.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/entrypoint.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/format_version.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/serve-docs.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/setup-systemd.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/setup.sh +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/scripts/traffic_generator.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/systemd/ccproxy.service.template +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/test_config/test-config.toml +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/conftest.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/factories/MIGRATION_GUIDE.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/factories/README.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/factories/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/factories/fastapi_factory.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/MIGRATION_GUIDE.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/README.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/auth/MIGRATION_GUIDE.md +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/auth/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/auth/example_usage.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/claude_sdk/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/claude_sdk/internal_mocks.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/claude_sdk/responses.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/credentials.json +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/external_apis/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/external_apis/anthropic_api.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/proxy_service/__init__.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/proxy_service/oauth_mocks.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/fixtures/responses.json +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_access_logger_integration.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_adapters.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_cli_auth_commands.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_cli_config.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_cli_serve.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_docker.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_duckdb_lifecycle.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_duckdb_settings_integration.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_fastapi_factory.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_http_transformers.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_pricing.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_pushgateway_error_handling.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_queue_duckdb_storage.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_reset_endpoint.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_scheduler.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_scheduler_tasks.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_sse_events.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_sse_stream_filtering.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_stats_printer.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/tests/test_streaming.py +0 -0
- {ccproxy_api-0.1.0 → ccproxy_api-0.1.1}/uv.lock +0 -0
|
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.1] - 2025-07-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Conditional Authentication**: API endpoints now support optional authentication - when `SECURITY__AUTH_TOKEN` is configured, authentication is enforced; when not configured, the proxy runs in open mode.
|
|
12
|
+
- **Startup Validation**: Added comprehensive validation checks during application startup:
|
|
13
|
+
- Validates OAuth credentials and warns about expired tokens
|
|
14
|
+
- Checks for Claude CLI binary availability with installation instructions
|
|
15
|
+
- Logs token expiration time and subscription type when valid
|
|
16
|
+
- **Default Command**: The `serve` command is now the default - running `ccproxy` without subcommands automatically starts the server.
|
|
17
|
+
- **Alternative Entry Point**: Added `ccproxy-api` as an alternative command-line entry point.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Authentication Variable**: Renamed environment variable from `AUTH_TOKEN` to `SECURITY__AUTH_TOKEN` for better namespace organization and clarity.
|
|
21
|
+
- **Credential Priority**: Reordered credential search paths to prioritize ccproxy-specific credentials before Claude CLI paths.
|
|
22
|
+
- **CLI Syntax**: Migrated all CLI parameters to modern Annotated syntax for better type safety and IDE support.
|
|
23
|
+
- **Pydantic v2**: Updated all models to use Pydantic v2 configuration syntax (`model_config` instead of `Config` class).
|
|
24
|
+
- **Documentation**: Improved Aider integration docs with correct API endpoint URLs and added installation options (uv, pipx).
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Authentication Separation**: Fixed critical issue where auth token was incorrectly used for both client and upstream authentication - now client auth token is separate from OAuth credentials.
|
|
28
|
+
- **URL Paths**: Fixed documentation to use `/api` endpoints for Aider compatibility instead of SDK mode paths.
|
|
29
|
+
- **Default Values**: Fixed default values for list parameters in CLI (docker_env, docker_volume, docker_arg).
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
- **Status Endpoints**: Removed redundant `/status` endpoints from both Claude SDK and proxy routes.
|
|
33
|
+
- **Permission Tool**: Removed Claude permission tool functionality and related CLI options (`--permission-mode`, `--permission-prompt-tool-name`) that are no longer needed.
|
|
34
|
+
- **Deprecated Options**: Removed references to deprecated permission_mode and permission_prompt_tool_name from documentation.
|
|
35
|
+
|
|
8
36
|
## [0.1.0] - 2025-07-21
|
|
9
37
|
|
|
10
38
|
This is the initial public release of the CCProxy API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ccproxy-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: API server that provides an Anthropic and OpenAI compatible interface over Claude Code, allowing to use your Claude OAuth account or over the API.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -43,14 +43,26 @@ It includes a translation layer to support both Anthropic and OpenAI-compatible
|
|
|
43
43
|
# The official claude-code CLI is required for SDK mode
|
|
44
44
|
npm install -g @anthropic-ai/claude-code
|
|
45
45
|
|
|
46
|
-
#
|
|
47
|
-
|
|
46
|
+
# run it with uv
|
|
47
|
+
uvx ccproxy-api
|
|
48
|
+
|
|
49
|
+
# run it with pipx
|
|
50
|
+
pipx run ccproxy-api
|
|
51
|
+
|
|
52
|
+
# install with uv
|
|
53
|
+
uv tool install ccproxy-api
|
|
54
|
+
|
|
55
|
+
# Install ccproxy with pip
|
|
56
|
+
pipx install ccproxy-api
|
|
48
57
|
|
|
49
58
|
# Optional: Enable shell completion
|
|
50
59
|
eval "$(ccproxy --show-completion zsh)" # For zsh
|
|
51
60
|
eval "$(ccproxy --show-completion bash)" # For bash
|
|
52
61
|
```
|
|
53
62
|
|
|
63
|
+
|
|
64
|
+
For dev version replace `ccproxy-api` with `git+https://github.com/caddyglow/ccproxy-api.git@dev`
|
|
65
|
+
|
|
54
66
|
## Authentication
|
|
55
67
|
|
|
56
68
|
The proxy uses two different authentication mechanisms depending on the mode.
|
|
@@ -61,12 +73,23 @@ The proxy uses two different authentication mechanisms depending on the mode.
|
|
|
61
73
|
claude /login
|
|
62
74
|
```
|
|
63
75
|
|
|
76
|
+
It's also possible now to get a long live token to avoid renewing issues
|
|
77
|
+
using
|
|
78
|
+
```sh
|
|
79
|
+
```bash
|
|
80
|
+
claude setup-token`
|
|
81
|
+
|
|
64
82
|
2. **ccproxy (`api` mode):**
|
|
65
83
|
This mode uses its own OAuth2 flow to obtain credentials for direct API access.
|
|
66
84
|
```bash
|
|
67
85
|
ccproxy auth login
|
|
68
86
|
```
|
|
69
|
-
|
|
87
|
+
|
|
88
|
+
If you are already connected with Claude CLI the credentials should be found automatically
|
|
89
|
+
|
|
90
|
+
You can check the status of these credentials with `ccproxy auth validate` and `ccproxy auth info`.
|
|
91
|
+
|
|
92
|
+
Warning is show on start up if no credentials are setup.
|
|
70
93
|
|
|
71
94
|
## Usage
|
|
72
95
|
|
|
@@ -102,6 +125,38 @@ export ANTHROPIC_BASE_URL="http://localhost:8000/api"
|
|
|
102
125
|
export ANTHROPIC_API_KEY="dummy-key"
|
|
103
126
|
```
|
|
104
127
|
|
|
128
|
+
|
|
129
|
+
## Using with Aider
|
|
130
|
+
|
|
131
|
+
CCProxy works seamlessly with Aider and other AI coding assistants:
|
|
132
|
+
|
|
133
|
+
### Anthropic Mode
|
|
134
|
+
```bash
|
|
135
|
+
export ANTHROPIC_API_KEY=dummy
|
|
136
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8000/api
|
|
137
|
+
aider --model claude-sonnet-4-20250514
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### OpenAI Mode with Model Mapping
|
|
141
|
+
|
|
142
|
+
If your tool only supports OpenAI settings, ccproxy automatically maps OpenAI models to Claude:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
export OPENAI_API_KEY=dummy
|
|
146
|
+
export OPENAI_BASE_URL=http://127.0.0.1:8000/api/v1
|
|
147
|
+
aider --model o3-mini
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### API Mode (Direct Proxy)
|
|
151
|
+
|
|
152
|
+
For minimal interference and direct API access:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
export OPENAI_API_KEY=dummy
|
|
156
|
+
export OPENAI_BASE_URL=http://127.0.0.1:8000/api/v1
|
|
157
|
+
aider --model o3-mini
|
|
158
|
+
```
|
|
159
|
+
|
|
105
160
|
### `curl` Example
|
|
106
161
|
|
|
107
162
|
```bash
|
|
@@ -185,16 +240,16 @@ You can enable token authentication for the proxy. This supports multiple header
|
|
|
185
240
|
**1. Generate a Token:**
|
|
186
241
|
```bash
|
|
187
242
|
ccproxy generate-token
|
|
188
|
-
# Output:
|
|
243
|
+
# Output: SECURITY__AUTH_TOKEN=abc123xyz789...
|
|
189
244
|
```
|
|
190
245
|
|
|
191
246
|
**2. Configure the Token:**
|
|
192
247
|
```bash
|
|
193
248
|
# Set environment variable
|
|
194
|
-
export
|
|
249
|
+
export SECURITY__AUTH_TOKEN=abc123xyz789...
|
|
195
250
|
|
|
196
251
|
# Or add to .env file
|
|
197
|
-
echo "
|
|
252
|
+
echo "SECURITY__AUTH_TOKEN=abc123xyz789..." >> .env
|
|
198
253
|
```
|
|
199
254
|
|
|
200
255
|
**3. Use in Requests:**
|
|
@@ -14,14 +14,26 @@ It includes a translation layer to support both Anthropic and OpenAI-compatible
|
|
|
14
14
|
# The official claude-code CLI is required for SDK mode
|
|
15
15
|
npm install -g @anthropic-ai/claude-code
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
17
|
+
# run it with uv
|
|
18
|
+
uvx ccproxy-api
|
|
19
|
+
|
|
20
|
+
# run it with pipx
|
|
21
|
+
pipx run ccproxy-api
|
|
22
|
+
|
|
23
|
+
# install with uv
|
|
24
|
+
uv tool install ccproxy-api
|
|
25
|
+
|
|
26
|
+
# Install ccproxy with pip
|
|
27
|
+
pipx install ccproxy-api
|
|
19
28
|
|
|
20
29
|
# Optional: Enable shell completion
|
|
21
30
|
eval "$(ccproxy --show-completion zsh)" # For zsh
|
|
22
31
|
eval "$(ccproxy --show-completion bash)" # For bash
|
|
23
32
|
```
|
|
24
33
|
|
|
34
|
+
|
|
35
|
+
For dev version replace `ccproxy-api` with `git+https://github.com/caddyglow/ccproxy-api.git@dev`
|
|
36
|
+
|
|
25
37
|
## Authentication
|
|
26
38
|
|
|
27
39
|
The proxy uses two different authentication mechanisms depending on the mode.
|
|
@@ -32,12 +44,23 @@ The proxy uses two different authentication mechanisms depending on the mode.
|
|
|
32
44
|
claude /login
|
|
33
45
|
```
|
|
34
46
|
|
|
47
|
+
It's also possible now to get a long live token to avoid renewing issues
|
|
48
|
+
using
|
|
49
|
+
```sh
|
|
50
|
+
```bash
|
|
51
|
+
claude setup-token`
|
|
52
|
+
|
|
35
53
|
2. **ccproxy (`api` mode):**
|
|
36
54
|
This mode uses its own OAuth2 flow to obtain credentials for direct API access.
|
|
37
55
|
```bash
|
|
38
56
|
ccproxy auth login
|
|
39
57
|
```
|
|
40
|
-
|
|
58
|
+
|
|
59
|
+
If you are already connected with Claude CLI the credentials should be found automatically
|
|
60
|
+
|
|
61
|
+
You can check the status of these credentials with `ccproxy auth validate` and `ccproxy auth info`.
|
|
62
|
+
|
|
63
|
+
Warning is show on start up if no credentials are setup.
|
|
41
64
|
|
|
42
65
|
## Usage
|
|
43
66
|
|
|
@@ -73,6 +96,38 @@ export ANTHROPIC_BASE_URL="http://localhost:8000/api"
|
|
|
73
96
|
export ANTHROPIC_API_KEY="dummy-key"
|
|
74
97
|
```
|
|
75
98
|
|
|
99
|
+
|
|
100
|
+
## Using with Aider
|
|
101
|
+
|
|
102
|
+
CCProxy works seamlessly with Aider and other AI coding assistants:
|
|
103
|
+
|
|
104
|
+
### Anthropic Mode
|
|
105
|
+
```bash
|
|
106
|
+
export ANTHROPIC_API_KEY=dummy
|
|
107
|
+
export ANTHROPIC_BASE_URL=http://127.0.0.1:8000/api
|
|
108
|
+
aider --model claude-sonnet-4-20250514
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### OpenAI Mode with Model Mapping
|
|
112
|
+
|
|
113
|
+
If your tool only supports OpenAI settings, ccproxy automatically maps OpenAI models to Claude:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
export OPENAI_API_KEY=dummy
|
|
117
|
+
export OPENAI_BASE_URL=http://127.0.0.1:8000/api/v1
|
|
118
|
+
aider --model o3-mini
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### API Mode (Direct Proxy)
|
|
122
|
+
|
|
123
|
+
For minimal interference and direct API access:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
export OPENAI_API_KEY=dummy
|
|
127
|
+
export OPENAI_BASE_URL=http://127.0.0.1:8000/api/v1
|
|
128
|
+
aider --model o3-mini
|
|
129
|
+
```
|
|
130
|
+
|
|
76
131
|
### `curl` Example
|
|
77
132
|
|
|
78
133
|
```bash
|
|
@@ -156,16 +211,16 @@ You can enable token authentication for the proxy. This supports multiple header
|
|
|
156
211
|
**1. Generate a Token:**
|
|
157
212
|
```bash
|
|
158
213
|
ccproxy generate-token
|
|
159
|
-
# Output:
|
|
214
|
+
# Output: SECURITY__AUTH_TOKEN=abc123xyz789...
|
|
160
215
|
```
|
|
161
216
|
|
|
162
217
|
**2. Configure the Token:**
|
|
163
218
|
```bash
|
|
164
219
|
# Set environment variable
|
|
165
|
-
export
|
|
220
|
+
export SECURITY__AUTH_TOKEN=abc123xyz789...
|
|
166
221
|
|
|
167
222
|
# Or add to .env file
|
|
168
|
-
echo "
|
|
223
|
+
echo "SECURITY__AUTH_TOKEN=abc123xyz789..." >> .env
|
|
169
224
|
```
|
|
170
225
|
|
|
171
226
|
**3. Use in Requests:**
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import AsyncGenerator
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
|
+
from datetime import UTC, datetime
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from fastapi import FastAPI, HTTPException
|
|
@@ -23,11 +24,13 @@ from ccproxy.api.routes.metrics import (
|
|
|
23
24
|
prometheus_router,
|
|
24
25
|
)
|
|
25
26
|
from ccproxy.api.routes.proxy import router as proxy_router
|
|
27
|
+
from ccproxy.auth.exceptions import CredentialsNotFoundError
|
|
26
28
|
from ccproxy.auth.oauth.routes import router as oauth_router
|
|
27
29
|
from ccproxy.config.settings import Settings, get_settings
|
|
28
30
|
from ccproxy.core.logging import setup_logging
|
|
29
31
|
from ccproxy.observability.storage.duckdb_simple import SimpleDuckDBStorage
|
|
30
32
|
from ccproxy.scheduler.manager import start_scheduler, stop_scheduler
|
|
33
|
+
from ccproxy.services.credentials import CredentialsManager
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
logger = get_logger(__name__)
|
|
@@ -58,6 +61,73 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
|
58
61
|
"claude_cli_search_paths", paths=settings.claude.get_searched_paths()
|
|
59
62
|
)
|
|
60
63
|
|
|
64
|
+
# Validate authentication token at startup
|
|
65
|
+
try:
|
|
66
|
+
credentials_manager = CredentialsManager()
|
|
67
|
+
validation = await credentials_manager.validate()
|
|
68
|
+
|
|
69
|
+
if validation.valid and not validation.expired:
|
|
70
|
+
credentials = validation.credentials
|
|
71
|
+
oauth_token = credentials.claude_ai_oauth if credentials else None
|
|
72
|
+
|
|
73
|
+
if oauth_token and oauth_token.expires_at_datetime:
|
|
74
|
+
hours_until_expiry = int(
|
|
75
|
+
(
|
|
76
|
+
oauth_token.expires_at_datetime - datetime.now(UTC)
|
|
77
|
+
).total_seconds()
|
|
78
|
+
/ 3600
|
|
79
|
+
)
|
|
80
|
+
logger.info(
|
|
81
|
+
"auth_token_valid",
|
|
82
|
+
expires_in_hours=hours_until_expiry,
|
|
83
|
+
subscription_type=oauth_token.subscription_type,
|
|
84
|
+
credentials_path=str(validation.path) if validation.path else None,
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
logger.info("auth_token_valid", credentials_path=str(validation.path))
|
|
88
|
+
elif validation.expired:
|
|
89
|
+
logger.warning(
|
|
90
|
+
"auth_token_expired",
|
|
91
|
+
message="Authentication token has expired. Please run 'ccproxy auth login' to refresh.",
|
|
92
|
+
credentials_path=str(validation.path) if validation.path else None,
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
logger.warning(
|
|
96
|
+
"auth_token_invalid",
|
|
97
|
+
message="Authentication token is invalid. Please run 'ccproxy auth login'.",
|
|
98
|
+
credentials_path=str(validation.path) if validation.path else None,
|
|
99
|
+
)
|
|
100
|
+
except CredentialsNotFoundError:
|
|
101
|
+
logger.warning(
|
|
102
|
+
"auth_token_not_found",
|
|
103
|
+
message="No authentication credentials found. Please run 'ccproxy auth login' to authenticate.",
|
|
104
|
+
searched_paths=settings.auth.storage.storage_paths,
|
|
105
|
+
)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.error(
|
|
108
|
+
"auth_token_validation_error",
|
|
109
|
+
error=str(e),
|
|
110
|
+
message="Failed to validate authentication token. The server will continue without authentication.",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Validate Claude binary at startup
|
|
114
|
+
claude_path, found_in_path = settings.claude.find_claude_cli()
|
|
115
|
+
if claude_path:
|
|
116
|
+
logger.info(
|
|
117
|
+
"claude_binary_found",
|
|
118
|
+
path=claude_path,
|
|
119
|
+
found_in_path=found_in_path,
|
|
120
|
+
message=f"Claude CLI binary found at: {claude_path}",
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
searched_paths = settings.claude.get_searched_paths()
|
|
124
|
+
logger.warning(
|
|
125
|
+
"claude_binary_not_found",
|
|
126
|
+
message="Claude CLI binary not found. Please install Claude CLI to use SDK features.",
|
|
127
|
+
searched_paths=searched_paths,
|
|
128
|
+
install_command="npm install -g @anthropic-ai/claude-code",
|
|
129
|
+
)
|
|
130
|
+
|
|
61
131
|
# Start scheduler system
|
|
62
132
|
try:
|
|
63
133
|
scheduler = await start_scheduler(settings)
|
|
@@ -173,9 +173,3 @@ async def list_models(
|
|
|
173
173
|
raise HTTPException(
|
|
174
174
|
status_code=500, detail=f"Internal server error: {str(e)}"
|
|
175
175
|
) from e
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@router.get("/status")
|
|
179
|
-
async def claude_sdk_status() -> dict[str, str]:
|
|
180
|
-
"""Get Claude SDK status."""
|
|
181
|
-
return {"status": "claude sdk endpoint available", "service": "direct"}
|
|
@@ -11,6 +11,7 @@ from starlette.background import BackgroundTask
|
|
|
11
11
|
from ccproxy.adapters.openai.adapter import OpenAIAdapter
|
|
12
12
|
from ccproxy.api.dependencies import ProxyServiceDep
|
|
13
13
|
from ccproxy.api.responses import ProxyResponse
|
|
14
|
+
from ccproxy.auth.conditional import ConditionalAuthDep
|
|
14
15
|
from ccproxy.core.errors import ProxyHTTPException
|
|
15
16
|
|
|
16
17
|
|
|
@@ -22,6 +23,7 @@ router = APIRouter(tags=["proxy"])
|
|
|
22
23
|
async def create_openai_chat_completion(
|
|
23
24
|
request: Request,
|
|
24
25
|
proxy_service: ProxyServiceDep,
|
|
26
|
+
auth: ConditionalAuthDep,
|
|
25
27
|
) -> StreamingResponse | Response:
|
|
26
28
|
"""Create a chat completion using Claude AI with OpenAI-compatible format.
|
|
27
29
|
|
|
@@ -98,6 +100,9 @@ async def create_openai_chat_completion(
|
|
|
98
100
|
media_type=response_headers.get("content-type", "application/json"),
|
|
99
101
|
)
|
|
100
102
|
|
|
103
|
+
except HTTPException:
|
|
104
|
+
# Re-raise HTTPException as-is (including 401 auth errors)
|
|
105
|
+
raise
|
|
101
106
|
except Exception as e:
|
|
102
107
|
raise HTTPException(
|
|
103
108
|
status_code=500, detail=f"Internal server error: {str(e)}"
|
|
@@ -108,6 +113,7 @@ async def create_openai_chat_completion(
|
|
|
108
113
|
async def create_anthropic_message(
|
|
109
114
|
request: Request,
|
|
110
115
|
proxy_service: ProxyServiceDep,
|
|
116
|
+
auth: ConditionalAuthDep,
|
|
111
117
|
) -> StreamingResponse | Response:
|
|
112
118
|
"""Create a message using Claude AI with Anthropic format.
|
|
113
119
|
|
|
@@ -180,6 +186,9 @@ async def create_anthropic_message(
|
|
|
180
186
|
media_type=response_headers.get("content-type", "application/json"),
|
|
181
187
|
)
|
|
182
188
|
|
|
189
|
+
except HTTPException:
|
|
190
|
+
# Re-raise HTTPException as-is (including 401 auth errors)
|
|
191
|
+
raise
|
|
183
192
|
except Exception as e:
|
|
184
193
|
raise HTTPException(
|
|
185
194
|
status_code=500, detail=f"Internal server error: {str(e)}"
|
|
@@ -190,6 +199,7 @@ async def create_anthropic_message(
|
|
|
190
199
|
async def list_models(
|
|
191
200
|
request: Request,
|
|
192
201
|
proxy_service: ProxyServiceDep,
|
|
202
|
+
auth: ConditionalAuthDep,
|
|
193
203
|
) -> Response:
|
|
194
204
|
"""List available models using the proxy service.
|
|
195
205
|
|
|
@@ -226,13 +236,10 @@ async def list_models(
|
|
|
226
236
|
media_type=response_headers.get("content-type", "application/json"),
|
|
227
237
|
)
|
|
228
238
|
|
|
239
|
+
except HTTPException:
|
|
240
|
+
# Re-raise HTTPException as-is (including 401 auth errors)
|
|
241
|
+
raise
|
|
229
242
|
except Exception as e:
|
|
230
243
|
raise HTTPException(
|
|
231
244
|
status_code=500, detail=f"Internal server error: {str(e)}"
|
|
232
245
|
) from e
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
@router.get("/status")
|
|
236
|
-
async def proxy_status() -> dict[str, str]:
|
|
237
|
-
"""Get proxy status."""
|
|
238
|
-
return {"status": "proxy API available", "version": "1.0.0"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Conditional authentication dependencies."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends, HTTPException, Request, status
|
|
6
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
7
|
+
|
|
8
|
+
from ccproxy.auth.bearer import BearerTokenAuthManager
|
|
9
|
+
from ccproxy.auth.exceptions import AuthenticationError
|
|
10
|
+
from ccproxy.auth.manager import AuthManager
|
|
11
|
+
from ccproxy.config.settings import Settings, get_settings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# FastAPI security scheme for bearer tokens
|
|
15
|
+
bearer_scheme = HTTPBearer(auto_error=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def get_conditional_auth_manager(
|
|
19
|
+
request: Request,
|
|
20
|
+
credentials: Annotated[
|
|
21
|
+
HTTPAuthorizationCredentials | None, Depends(bearer_scheme)
|
|
22
|
+
] = None,
|
|
23
|
+
settings: Annotated[Settings | None, Depends(get_settings)] = None,
|
|
24
|
+
) -> AuthManager | None:
|
|
25
|
+
"""Get authentication manager only if auth is required.
|
|
26
|
+
|
|
27
|
+
This dependency checks if authentication is configured and validates
|
|
28
|
+
the token if required. If no auth is configured, returns None.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
request: The FastAPI request object
|
|
32
|
+
credentials: HTTP authorization credentials
|
|
33
|
+
settings: Application settings
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
AuthManager instance if authenticated, None if no auth required
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
HTTPException: If auth is required but credentials are invalid
|
|
40
|
+
"""
|
|
41
|
+
# Check if auth is required for this configuration
|
|
42
|
+
if settings is None or not settings.security.auth_token:
|
|
43
|
+
# No auth configured, return None
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
# Auth is required, validate credentials
|
|
47
|
+
if not credentials or not credentials.credentials:
|
|
48
|
+
raise HTTPException(
|
|
49
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
50
|
+
detail="Authentication required",
|
|
51
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Validate the token
|
|
55
|
+
if credentials.credentials != settings.security.auth_token:
|
|
56
|
+
raise HTTPException(
|
|
57
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
58
|
+
detail="Invalid authentication credentials",
|
|
59
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Create and return auth manager
|
|
63
|
+
try:
|
|
64
|
+
bearer_auth = BearerTokenAuthManager(credentials.credentials)
|
|
65
|
+
if await bearer_auth.is_authenticated():
|
|
66
|
+
return bearer_auth
|
|
67
|
+
else:
|
|
68
|
+
raise HTTPException(
|
|
69
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
70
|
+
detail="Authentication failed",
|
|
71
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
72
|
+
)
|
|
73
|
+
except (AuthenticationError, ValueError) as e:
|
|
74
|
+
raise HTTPException(
|
|
75
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
76
|
+
detail=str(e),
|
|
77
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
78
|
+
) from e
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Type alias for conditional auth dependency
|
|
82
|
+
ConditionalAuthDep = Annotated[
|
|
83
|
+
AuthManager | None, Depends(get_conditional_auth_manager)
|
|
84
|
+
]
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
from datetime import UTC, datetime, timezone
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
7
|
+
from typing import Annotated, Optional
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
from rich import box
|
|
@@ -52,16 +52,20 @@ def get_docker_credential_paths() -> list[Path]:
|
|
|
52
52
|
|
|
53
53
|
@app.command(name="validate")
|
|
54
54
|
def validate_credentials(
|
|
55
|
-
docker:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
docker: Annotated[
|
|
56
|
+
bool,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--docker",
|
|
59
|
+
help="Use Docker credential paths (from get_claude_docker_home_dir())",
|
|
60
|
+
),
|
|
61
|
+
] = False,
|
|
62
|
+
credential_file: Annotated[
|
|
63
|
+
str | None,
|
|
64
|
+
typer.Option(
|
|
65
|
+
"--credential-file",
|
|
66
|
+
help="Path to specific credential file to validate",
|
|
67
|
+
),
|
|
68
|
+
] = None,
|
|
65
69
|
) -> None:
|
|
66
70
|
"""Validate Claude CLI credentials.
|
|
67
71
|
|
|
@@ -176,16 +180,20 @@ def validate_credentials(
|
|
|
176
180
|
|
|
177
181
|
@app.command(name="info")
|
|
178
182
|
def credential_info(
|
|
179
|
-
docker:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
docker: Annotated[
|
|
184
|
+
bool,
|
|
185
|
+
typer.Option(
|
|
186
|
+
"--docker",
|
|
187
|
+
help="Use Docker credential paths (from get_claude_docker_home_dir())",
|
|
188
|
+
),
|
|
189
|
+
] = False,
|
|
190
|
+
credential_file: Annotated[
|
|
191
|
+
str | None,
|
|
192
|
+
typer.Option(
|
|
193
|
+
"--credential-file",
|
|
194
|
+
help="Path to specific credential file to display info for",
|
|
195
|
+
),
|
|
196
|
+
] = None,
|
|
189
197
|
) -> None:
|
|
190
198
|
"""Display detailed credential information.
|
|
191
199
|
|
|
@@ -376,16 +384,20 @@ def credential_info(
|
|
|
376
384
|
|
|
377
385
|
@app.command(name="login")
|
|
378
386
|
def login_command(
|
|
379
|
-
docker:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
docker: Annotated[
|
|
388
|
+
bool,
|
|
389
|
+
typer.Option(
|
|
390
|
+
"--docker",
|
|
391
|
+
help="Use Docker credential paths (from get_claude_docker_home_dir())",
|
|
392
|
+
),
|
|
393
|
+
] = False,
|
|
394
|
+
credential_file: Annotated[
|
|
395
|
+
str | None,
|
|
396
|
+
typer.Option(
|
|
397
|
+
"--credential-file",
|
|
398
|
+
help="Path to specific credential file to save to",
|
|
399
|
+
),
|
|
400
|
+
] = None,
|
|
389
401
|
) -> None:
|
|
390
402
|
"""Login to Claude using OAuth authentication.
|
|
391
403
|
|
|
@@ -470,18 +482,22 @@ def login_command(
|
|
|
470
482
|
|
|
471
483
|
@app.command()
|
|
472
484
|
def renew(
|
|
473
|
-
docker:
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
+
docker: Annotated[
|
|
486
|
+
bool,
|
|
487
|
+
typer.Option(
|
|
488
|
+
"--docker",
|
|
489
|
+
"-d",
|
|
490
|
+
help="Renew credentials for Docker environment",
|
|
491
|
+
),
|
|
492
|
+
] = False,
|
|
493
|
+
credential_file: Annotated[
|
|
494
|
+
Path | None,
|
|
495
|
+
typer.Option(
|
|
496
|
+
"--credential-file",
|
|
497
|
+
"-f",
|
|
498
|
+
help="Path to custom credential file",
|
|
499
|
+
),
|
|
500
|
+
] = None,
|
|
485
501
|
) -> None:
|
|
486
502
|
"""Force renew Claude credentials without checking expiration.
|
|
487
503
|
|