diff --git a/.gitignore b/.gitignore index 21205b017d..6106ace497 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ cliproxyapi++ # Hot-reload artifacts .air/ - # Configuration config.yaml .env @@ -56,22 +55,23 @@ _bmad-output/* .DS_Store ._* *.bak -server -<<<<<<< HEAD -======= -server -cli-proxy-api-plus-integration-test +# Local worktree shelves (canonical checkout must stay clean) +PROJECT-wtrees/ boardsync releasebatch .cache >>>>>>> a4e4c2b8 (chore: add build artifacts to .gitignore) -# Build artifacts (cherry-picked from fix/test-cleanups) -cliproxyapi++ -.air/ -boardsync -releasebatch -.cache -logs/ -!.gemini/config.yaml + +# AI tool artifacts +.claude/ +.codex/ +.cursor/ +.gemini/ +.kittify/ +.kilocode/ +.github/prompts/ +.github/copilot-instructions.md +.claudeignore +.llmignore diff --git a/README.md b/README.md index 12eaec1040..e6c9b2d3f2 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,68 @@ -# CLIProxyAPI++ (KooshaPari Fork) +# CLIProxyAPI++ -This repository works with Claude and other AI agents as autonomous software engineers. +Agent-native, multi-provider OpenAI-compatible proxy for production and local model routing. -## Quick Start +## Table of Contents -```bash -# Docker -docker run -p 8317:8317 eceasy/cli-proxy-api-plus:latest +- [Key Features](#key-features) +- [Architecture](#architecture) +- [Getting Started](#getting-started) +- [Operations and Security](#operations-and-security) +- [Testing and Quality](#testing-and-quality) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [License](#license) -# Or build from source -go build -o cliproxy ./cmd/cliproxy -./cliproxy --config config.yaml +## Key Features -# Health check -curl http://localhost:8317/health -``` +- OpenAI-compatible request surface across heterogeneous providers. +- Unified auth and token handling for OpenAI, Anthropic, Gemini, Kiro, Copilot, and more. +- Provider-aware routing and model conversion. +- Built-in operational tooling for management APIs and diagnostics. + +## Architecture + +- `cmd/server`: primary API server entrypoint. +- `cmd/cliproxyctl`: operational CLI. +- `internal/`: runtime/auth/translator internals. +- `pkg/llmproxy/`: reusable proxy modules. +- `sdk/`: SDK-facing interfaces. + +## Getting Started + +### Prerequisites -## Multi-Provider Routing +- Go 1.24+ +- Docker (optional) +- Provider credentials for target upstreams -Route OpenAI-compatible requests to any provider: +### Quick Start ```bash -# List models -curl http://localhost:8317/v1/models - -# Chat completion (OpenAI) -curl -X POST http://localhost:8317/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}' - -# Chat completion (Anthropic) -curl -X POST http://localhost:8317/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{"model": "claude-3-5-sonnet", "messages": [{"role": "user", "content": "Hello"}]}' +go build -o cliproxy ./cmd/server +./cliproxy --config config.yaml ``` -### Provider Configuration - -```yaml -providers: - openai: - api_key: ${OPENAI_API_KEY} - anthropic: - api_key: ${ANTHROPIC_API_KEY} - kiro: - enabled: true - github_copilot: - enabled: true - ollama: - enabled: true - base_url: http://localhost:11434 +### Docker Quick Start + +```bash +docker run -p 8317:8317 eceasy/cli-proxy-api-plus:latest ``` -## Supported Providers +## Operations and Security + +- Rate limiting and quota/cooldown controls. +- Auth flows for provider-specific OAuth/API keys. +- CI policy checks and path guards. +- Governance and security docs under `docs/operations/` and `docs/reference/`. + +## Testing and Quality + +```bash +go test ./... +``` -| Provider | Auth | Status | -|----------|------|--------| -| OpenAI | API Key | ✅ | -| Anthropic | API Key | ✅ | -| Azure OpenAI | API Key/OAuth | ✅ | -| Google Gemini | API Key | ✅ | -| AWS Bedrock | IAM | ✅ | -| Kiro (CodeWhisperer) | OAuth | ✅ | -| GitHub Copilot | OAuth | ✅ | -| Ollama | Local | ✅ | -| LM Studio | Local | ✅ | +Quality gates are enforced via repo CI workflows (build/lint/path guards). ## Documentation @@ -77,126 +75,18 @@ providers: ## Environment ```bash -export OPENAI_API_KEY="sk-..." -export ANTHROPIC_API_KEY="sk-..." -export CLIPROXY_PORT=8317 +cd docs +npm install +npm run docs:dev +npm run docs:build ``` --- -## Development Philosophy - -### Extend, Never Duplicate - -- NEVER create a v2 file. Refactor the original. -- NEVER create a new class if an existing one can be made generic. -- NEVER create custom implementations when an OSS library exists. -- Before writing ANY new code: search the codebase for existing patterns. - -### Primitives First - -- Build generic building blocks before application logic. -- A provider interface + registry is better than N isolated classes. -- Template strings > hardcoded messages. Config-driven > code-driven. - -### Research Before Implementing - -- Check pkg.go.dev for existing libraries. -- Search GitHub for 80%+ implementations to fork/adapt. - ---- - -## Library Preferences (DO NOT REINVENT) - -| Need | Use | NOT | -|------|-----|-----| -| HTTP router | chi | custom router | -| Logging | zerolog | fmt.Print | -| Config | viper | manual env parsing | -| Validation | go-playground/validator | manual if/else | -| Rate limiting | golang.org/x/time/rate | custom limiter | - ---- - -## Code Quality Non-Negotiables - -- Zero new lint suppressions without inline justification -- All new code must pass: go fmt, go vet, golint -- Max function: 40 lines -- No placeholder TODOs in committed code - -### Go-Specific Rules - -- Use `go fmt` for formatting -- Use `go vet` for linting -- Use `golangci-lint` for comprehensive linting -- All public APIs must have godoc comments - ---- - -## Verifiable Constraints - -| Metric | Threshold | Enforcement | -|--------|-----------|-------------| -| Tests | 80% coverage | CI gate | -| Lint | 0 errors | golangci-lint | -| Security | 0 critical | trivy scan | - ---- - -## Domain-Specific Patterns - -### What CLIProxyAPI++ Is - -CLIProxyAPI++ is an **OpenAI-compatible API gateway** that translates client requests to multiple upstream LLM providers. The core domain is: provide a single API surface that routes to heterogeneous providers with auth, rate limiting, and metrics. - -### Key Interfaces - -| Interface | Responsibility | Location | -|-----------|---------------|----------| -| **Router** | Request routing to providers | `pkg/llmproxy/router/` | -| **Provider** | Provider abstraction | `pkg/llmproxy/providers/` | -| **Auth** | Credential management | `pkg/llmproxy/auth/` | -| **Rate Limiter** | Throttling | `pkg/llmproxy/ratelimit/` | - -### Request Flow - -``` -1. Client Request → Router -2. Router → Auth Validation -3. Auth → Provider Selection -4. Provider → Upstream API -5. Response ← Provider -6. Metrics → Response -``` - -### Common Anti-Patterns to Avoid - -- **Hardcoded provider URLs** -- Use configuration -- **Blocking on upstream** -- Use timeouts and circuit breakers -- **No fallbacks** -- Implement provider failover -- **Missing metrics** -- Always track latency/cost - ---- - -## Kush Ecosystem - -This project is part of the Kush multi-repo system: - -``` -kush/ -├── thegent/ # Agent orchestration -├── agentapi++/ # HTTP API for coding agents -├── cliproxy++/ # LLM proxy (this repo) -├── tokenledger/ # Token and cost tracking -├── 4sgm/ # Python tooling workspace -├── civ/ # Deterministic simulation -├── parpour/ # Spec-first planning -└── pheno-sdk/ # Python SDK -``` - ---- +1. Create a worktree branch. +2. Implement and validate changes. +3. Open a PR with clear scope and migration notes. ## License -MIT License - see LICENSE file +MIT License. See `LICENSE`. diff --git a/cliproxyctl/main.go b/cliproxyctl/main.go index 3c72d1b18c..d18a9bb1cf 100644 --- a/cliproxyctl/main.go +++ b/cliproxyctl/main.go @@ -13,7 +13,7 @@ import ( "time" cliproxycmd "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cmd" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) const responseSchemaVersion = "cliproxyctl.response.v1" diff --git a/cliproxyctl/main_test.go b/cliproxyctl/main_test.go index 9fe3fef559..13f8cf9ce5 100644 --- a/cliproxyctl/main_test.go +++ b/cliproxyctl/main_test.go @@ -8,7 +8,7 @@ import ( "time" cliproxycmd "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cmd" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestRunSetupJSONResponseShape(t *testing.T) { diff --git a/cmd/cliproxyctl/main.go b/cmd/cliproxyctl/main.go index ee3a10e7f4..1b3ccb065c 100644 --- a/cmd/cliproxyctl/main.go +++ b/cmd/cliproxyctl/main.go @@ -17,7 +17,6 @@ import ( cliproxycmd "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cmd" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" ) const responseSchemaVersion = "cliproxyctl.response.v1" diff --git a/cmd/cliproxyctl/main_test.go b/cmd/cliproxyctl/main_test.go index 6e6c24faa1..15c2418930 100644 --- a/cmd/cliproxyctl/main_test.go +++ b/cmd/cliproxyctl/main_test.go @@ -12,8 +12,7 @@ import ( "time" cliproxycmd "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cmd" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestRunSetupJSONResponseShape(t *testing.T) { diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index 93373a5e19..6db253b724 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -72,7 +72,7 @@ const synthTemplate = `// Code generated by github.com/kooshapari/cliproxyapi-pl package synthesizer import ( - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // getDedicatedProviderEntries returns the config entries for a dedicated provider. @@ -123,7 +123,7 @@ package diff import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // BuildConfigChangeDetailsGeneratedProviders computes changes for generated dedicated providers. diff --git a/cmd/server/config_validate.go b/cmd/server/config_validate.go index 18bf59b78b..3da26a02c5 100644 --- a/cmd/server/config_validate.go +++ b/cmd/server/config_validate.go @@ -6,7 +6,7 @@ import ( "io" "os" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "gopkg.in/yaml.v3" ) diff --git a/cmd/server/main.go b/cmd/server/main.go index 7a6e10ecc7..300abc19e4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -17,19 +17,19 @@ import ( "time" "github.com/joho/godotenv" - configaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/access/config_access" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/buildinfo" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cmd" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/logging" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/managementasset" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/store" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/tui" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/usage" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + configaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/access/config_access" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/buildinfo" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/cmd" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/logging" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/managementasset" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/store" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/tui" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/usage" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/util" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index 4b767532c7..0000000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,2565 +0,0 @@ -{ - "name": "cliproxyapi-plusplus-docs", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "cliproxyapi-plusplus-docs", - "devDependencies": { - "vitepress": "^1.6.4" - } - }, - "node_modules/@algolia/abtesting": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.15.0.tgz", - "integrity": "sha512-D1QZ8dQx5zC9yrxNao9ER9bojmmzUdL1i2P9waIRiwnZ5fI26YswcCd6VHR/Q4W3PASfVf2My4YQ2FhGGDewTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", - "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", - "@algolia/autocomplete-shared": "1.17.7" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", - "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", - "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", - "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.49.0.tgz", - "integrity": "sha512-Q1MSRhh4Du9WeLIl1S9O+BDUMaL01uuQtmzCyEzOBtu1xBDr3wvqrTJtfEceEkA5/Nw1BdGSHa6sDT3xTAF90A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.49.0.tgz", - "integrity": "sha512-v50elhC80oyQw+8o8BwM+VvPuOo36+3W8VCfR4hsHoafQtGbMtP63U5eNcUydbVsM0py3JLoBaL1yKBK4L01sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.49.0.tgz", - "integrity": "sha512-BDmVDtpDvymfLE5YQ2cPnfWJUVTDJqwpJa03Fsb7yJFJmbeKsUOGsnRkYsTbdzf0FfcvyvBB5zdcbrAIL249bg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.49.0.tgz", - "integrity": "sha512-lDCXsnZDx7zQ5GzSi1EL3l07EbksjrdpMgixFRCdi2QqeBe42HIQJfPPqdWtwrAXjORRopsPx2z+gGYJP/79Uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.49.0.tgz", - "integrity": "sha512-5k/KB+DsnesNKvMUEwTKSzExOf5zYbiPg7DVO7g1Y/+bhMb3wmxp9RFwfqwPfmoRTjptqvwhR6a0593tWVkmAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.49.0.tgz", - "integrity": "sha512-pjHNcrdjn7p3RQ5Ql1Baiwfdn9bkS+z4gqONJJP8kuZFqYP8Olthy4G7fl5bCB29UjdUj5EWlaElQKCtPluCtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.0.tgz", - "integrity": "sha512-uGv2P3lcviuaZy8ZOAyN60cZdhOVyjXwaDC27a1qdp3Pb5Azn+lLSJwkHU4TNRpphHmIei9HZuUxwQroujdPjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/ingestion": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.49.0.tgz", - "integrity": "sha512-sH10mftYlmvfGbvAgTtHYbCIstmNUdiAkX//0NAyBcJRB6NnZmNsdLxdFGbE8ZqlGXzoe0zcUIau+DxKpXtqCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.49.0.tgz", - "integrity": "sha512-RqhGcVVxLpK+lA0GZKywlQIXsI704flc12nv/hOdrwiuk/Uyhxs46KLM4ngip7wutU+7t0PYZWiVayrqBPN/ZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.49.0.tgz", - "integrity": "sha512-kg8omGRvmIPhhqtUqSIpS3regFKWuoWh3WqyUhGk27N4T7q8I++8TsDYsV8vK7oBEzw706m2vUBtN5fw2fDjmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.49.0.tgz", - "integrity": "sha512-BaZ6NTI9VdSbDcsMucdKhTuFFxv6B+3dAZZBozX12fKopYsELh7dBLfZwm8evDCIicmNjIjobi4VNnNshrCSuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.49.0.tgz", - "integrity": "sha512-2nxISxS5xO5DLAj6QzMImgJv6CqpZhJVkhcTFULESR/k4IpbkJTEHmViVTxw9MlrU8B5GfwHevFd7vKL3a7MXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.49.0.tgz", - "integrity": "sha512-S/B94C6piEUXGpN3y5ysmNKMEqdfNVAXYY+FxivEAV5IGJjbEuLZfT8zPPZUWGw9vh6lgP80Hye2G5aVBNIa8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", - "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@docsearch/js": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", - "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docsearch/react": "3.8.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", - "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-core": "1.17.7", - "@algolia/autocomplete-preset-algolia": "1.17.7", - "@docsearch/css": "3.8.2", - "algoliasearch": "^5.14.2" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@iconify-json/simple-icons": { - "version": "1.2.71", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.71.tgz", - "integrity": "sha512-rNoDFbq1fAYiEexBvrw613/xiUOPEu5MKVV/X8lI64AgdTzLQUUemr9f9fplxUMPoxCBP2rWzlhOEeTHk/Sf0Q==", - "dev": true, - "license": "CC0-1.0", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@shikijs/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", - "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", - "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^3.1.0" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", - "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", - "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", - "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/transformers": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", - "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/types": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", - "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", - "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/shared": "3.5.28", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", - "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.28", - "@vue/shared": "3.5.28" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", - "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/compiler-core": "3.5.28", - "@vue/compiler-dom": "3.5.28", - "@vue/compiler-ssr": "3.5.28", - "@vue/shared": "3.5.28", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", - "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.28", - "@vue/shared": "3.5.28" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.9" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", - "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.28" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", - "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.28", - "@vue/shared": "3.5.28" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", - "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.28", - "@vue/runtime-core": "3.5.28", - "@vue/shared": "3.5.28", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", - "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.28", - "@vue/shared": "3.5.28" - }, - "peerDependencies": { - "vue": "3.5.28" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", - "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", - "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", - "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vueuse/core": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "^4", - "axios": "^1", - "change-case": "^5", - "drauu": "^0.4", - "focus-trap": "^7", - "fuse.js": "^7", - "idb-keyval": "^6", - "jwt-decode": "^4", - "nprogress": "^0.2", - "qrcode": "^1.5", - "sortablejs": "^1", - "universal-cookie": "^7" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", - "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", - "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/algoliasearch": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.0.tgz", - "integrity": "sha512-Tse7vx7WOvbU+kpq/L3BrBhSWTPbtMa59zIEhMn+Z2NoxZlpcCRUDCRxQ7kDFs1T3CHxDgvb+mDuILiBBpBaAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.15.0", - "@algolia/client-abtesting": "5.49.0", - "@algolia/client-analytics": "5.49.0", - "@algolia/client-common": "5.49.0", - "@algolia/client-insights": "5.49.0", - "@algolia/client-personalization": "5.49.0", - "@algolia/client-query-suggestions": "5.49.0", - "@algolia/client-search": "5.49.0", - "@algolia/ingestion": "1.49.0", - "@algolia/monitoring": "1.49.0", - "@algolia/recommend": "5.49.0", - "@algolia/requester-browser-xhr": "5.49.0", - "@algolia/requester-fetch": "5.49.0", - "@algolia/requester-node-http": "5.49.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "dev": true, - "license": "MIT" - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/focus-trap": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", - "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tabbable": "^6.4.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/minisearch": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", - "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", - "dev": true, - "license": "MIT" - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/oniguruma-to-es": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", - "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.28.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", - "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true, - "license": "MIT" - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/shiki": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", - "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/langs": "2.5.0", - "@shikijs/themes": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", - "dev": true, - "license": "MIT" - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vitepress": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", - "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docsearch/css": "3.8.2", - "@docsearch/js": "3.8.2", - "@iconify-json/simple-icons": "^1.2.21", - "@shikijs/core": "^2.1.0", - "@shikijs/transformers": "^2.1.0", - "@shikijs/types": "^2.1.0", - "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/devtools-api": "^7.7.0", - "@vue/shared": "^3.5.13", - "@vueuse/core": "^12.4.0", - "@vueuse/integrations": "^12.4.0", - "focus-trap": "^7.6.4", - "mark.js": "8.11.1", - "minisearch": "^7.1.1", - "shiki": "^2.1.0", - "vite": "^5.4.14", - "vue": "^3.5.13" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4", - "postcss": "^8" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", - "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.28", - "@vue/compiler-sfc": "3.5.28", - "@vue/runtime-dom": "3.5.28", - "@vue/server-renderer": "3.5.28", - "@vue/shared": "3.5.28" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/examples/custom-provider/main.go b/examples/custom-provider/main.go index 20163bc480..d6bc796ebd 100644 --- a/examples/custom-provider/main.go +++ b/examples/custom-provider/main.go @@ -29,8 +29,8 @@ import ( "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" clipexec "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/logging" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/logging" sdktr "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" ) diff --git a/go.mod b/go.mod index 2f4a4946a0..fd30f86747 100644 --- a/go.mod +++ b/go.mod @@ -27,12 +27,12 @@ require ( github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/tiktoken-go/tokenizer v0.7.0 - golang.org/x/crypto v0.48.0 - golang.org/x/net v0.49.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.19.0 - golang.org/x/term v0.40.0 - golang.org/x/text v0.34.0 + golang.org/x/crypto v0.49.0 + golang.org/x/net v0.51.0 + golang.org/x/oauth2 v0.36.0 + golang.org/x/sync v0.20.0 + golang.org/x/term v0.41.0 + golang.org/x/text v0.35.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.46.1 @@ -112,7 +112,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.42.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect modernc.org/libc v1.67.6 // indirect @@ -120,3 +120,4 @@ require ( modernc.org/memory v1.11.0 // indirect ) +replace github.com/KooshaPari/phenotype-go-auth => ../../../template-commons/phenotype-go-auth diff --git a/go.sum b/go.sum index 95dd4c3613..e1a1e5cbf4 100644 --- a/go.sum +++ b/go.sum @@ -259,6 +259,7 @@ golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/auth/copilot/token.go b/internal/auth/copilot/token.go index 419c5d8cb0..76ffe8e609 100644 --- a/internal/auth/copilot/token.go +++ b/internal/auth/copilot/token.go @@ -4,7 +4,11 @@ package copilot import ( - "github.com/KooshaPari/phenotype-go-auth" + "encoding/json" + "fmt" + "os" + "path/filepath" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/misc" ) diff --git a/internal/auth/gemini/gemini_token.go b/internal/auth/gemini/gemini_token.go index c0a951b191..8845923bc0 100644 --- a/internal/auth/gemini/gemini_token.go +++ b/internal/auth/gemini/gemini_token.go @@ -7,8 +7,8 @@ import ( "fmt" "strings" - "github.com/KooshaPari/phenotype-go-auth" "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/misc" + log "github.com/sirupsen/logrus" ) // GeminiTokenStorage stores OAuth2 token information for Google Gemini API authentication. diff --git a/pkg/llmproxy/access/reconcile.go b/pkg/llmproxy/access/reconcile.go index e0c103fd0e..f636f2abd6 100644 --- a/pkg/llmproxy/access/reconcile.go +++ b/pkg/llmproxy/access/reconcile.go @@ -6,10 +6,9 @@ import ( "sort" "strings" - configaccess "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/access/config_access" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + configaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/access/config_access" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + sdkaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/access" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/api/handlers/management/api_tools_test.go b/pkg/llmproxy/api/handlers/management/api_tools_test.go index af053af69f..ec966d3a26 100644 --- a/pkg/llmproxy/api/handlers/management/api_tools_test.go +++ b/pkg/llmproxy/api/handlers/management/api_tools_test.go @@ -14,8 +14,9 @@ import ( "time" "github.com/gin-gonic/gin" - kiroauth "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/kiro" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) func TestAPICall_RejectsUnsafeHost(t *testing.T) { diff --git a/pkg/llmproxy/api/handlers/management/auth_files.go b/pkg/llmproxy/api/handlers/management/auth_files.go index 7e3aff6a7d..2d92746a0c 100644 --- a/pkg/llmproxy/api/handlers/management/auth_files.go +++ b/pkg/llmproxy/api/handlers/management/auth_files.go @@ -1,21 +1,3026 @@ package management -// NOTE: This file has been decomposed into smaller, logically-grouped files: -// - auth_helpers.go: Callback forwarders, timestamp parsing, auth helpers -// - auth_file_mgmt.go: Auth file listing, building entries, metadata extraction -// - auth_file_crud.go: Download, upload, delete auth files -// - auth_file_patch.go: Patch status/fields, token record management -// - auth_anthropic.go: Anthropic token requests -// - auth_gemini.go: Gemini CLI token, setup, helpers -// - auth_codex.go: Codex token requests -// - auth_antigravity.go: Antigravity token requests -// - auth_qwen.go: Qwen token requests -// - auth_kimi.go: Kimi token requests -// - auth_iflow.go: IFlow and IFlow Cookie token requests -// - auth_github.go: GitHub token requests -// - auth_kiro.go: Kiro token requests and PKCE helpers -// - auth_kilo.go: Kilo token requests -// - auth_status.go: Auth status endpoint -// -// All functions remain in the 'management' package. This is purely a file organization change. -// No function signatures or behavior have been modified. +import ( + "bytes" + "context" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/antigravity" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/claude" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/codex" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/copilot" + geminiAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/gemini" + iflowauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/iflow" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kilo" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kimi" + kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/qwen" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" + log "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +var lastRefreshKeys = []string{"last_refresh", "lastRefresh", "last_refreshed_at", "lastRefreshedAt"} + +const ( + anthropicCallbackPort = 54545 + geminiCallbackPort = 8085 + codexCallbackPort = 1455 + geminiCLIEndpoint = "https://cloudcode-pa.googleapis.com" + geminiCLIVersion = "v1internal" + geminiCLIUserAgent = "google-api-nodejs-client/9.15.1" + geminiCLIApiClient = "gl-node/22.17.0" + geminiCLIClientMetadata = "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI" +) + +type callbackForwarder struct { + provider string + server *http.Server + done chan struct{} +} + +var ( + callbackForwardersMu sync.Mutex + callbackForwarders = make(map[int]*callbackForwarder) +) + +func extractLastRefreshTimestamp(meta map[string]any) (time.Time, bool) { + if len(meta) == 0 { + return time.Time{}, false + } + for _, key := range lastRefreshKeys { + if val, ok := meta[key]; ok { + if ts, ok1 := parseLastRefreshValue(val); ok1 { + return ts, true + } + } + } + return time.Time{}, false +} + +func parseLastRefreshValue(v any) (time.Time, bool) { + switch val := v.(type) { + case string: + s := strings.TrimSpace(val) + if s == "" { + return time.Time{}, false + } + layouts := []string{time.RFC3339, time.RFC3339Nano, "2006-01-02 15:04:05", "2006-01-02T15:04:05Z07:00"} + for _, layout := range layouts { + if ts, err := time.Parse(layout, s); err == nil { + return ts.UTC(), true + } + } + if unix, err := strconv.ParseInt(s, 10, 64); err == nil && unix > 0 { + return time.Unix(unix, 0).UTC(), true + } + case float64: + if val <= 0 { + return time.Time{}, false + } + return time.Unix(int64(val), 0).UTC(), true + case int64: + if val <= 0 { + return time.Time{}, false + } + return time.Unix(val, 0).UTC(), true + case int: + if val <= 0 { + return time.Time{}, false + } + return time.Unix(int64(val), 0).UTC(), true + case json.Number: + if i, err := val.Int64(); err == nil && i > 0 { + return time.Unix(i, 0).UTC(), true + } + } + return time.Time{}, false +} + +func isWebUIRequest(c *gin.Context) bool { + raw := strings.TrimSpace(c.Query("is_webui")) + if raw == "" { + return false + } + switch strings.ToLower(raw) { + case "1", "true", "yes", "on": + return true + default: + return false + } +} + +func startCallbackForwarder(port int, provider, targetBase string) (*callbackForwarder, error) { + targetURL, errTarget := validateCallbackForwarderTarget(targetBase) + if errTarget != nil { + return nil, fmt.Errorf("invalid callback target: %w", errTarget) + } + + callbackForwardersMu.Lock() + prev := callbackForwarders[port] + if prev != nil { + delete(callbackForwarders, port) + } + callbackForwardersMu.Unlock() + + if prev != nil { + stopForwarderInstance(port, prev) + } + + addr := fmt.Sprintf("127.0.0.1:%d", port) + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, fmt.Errorf("failed to listen on %s: %w", addr, err) + } + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + target := *targetURL + if raw := r.URL.RawQuery; raw != "" { + if target.RawQuery != "" { + target.RawQuery = target.RawQuery + "&" + raw + } else { + target.RawQuery = raw + } + } + w.Header().Set("Cache-Control", "no-store") + http.Redirect(w, r, target.String(), http.StatusFound) + }) + + srv := &http.Server{ + Handler: handler, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + } + done := make(chan struct{}) + + go func() { + if errServe := srv.Serve(ln); errServe != nil && !errors.Is(errServe, http.ErrServerClosed) { + log.WithError(errServe).Warnf("callback forwarder for %s stopped unexpectedly", provider) + } + close(done) + }() + + forwarder := &callbackForwarder{ + provider: provider, + server: srv, + done: done, + } + + callbackForwardersMu.Lock() + callbackForwarders[port] = forwarder + callbackForwardersMu.Unlock() + + log.Infof("callback forwarder for %s listening on %s", provider, addr) + + return forwarder, nil +} + +func validateCallbackForwarderTarget(targetBase string) (*url.URL, error) { + trimmed := strings.TrimSpace(targetBase) + if trimmed == "" { + return nil, fmt.Errorf("target cannot be empty") + } + parsed, err := url.Parse(trimmed) + if err != nil { + return nil, fmt.Errorf("parse target: %w", err) + } + if !parsed.IsAbs() { + return nil, fmt.Errorf("target must be absolute") + } + scheme := strings.ToLower(parsed.Scheme) + if scheme != "http" && scheme != "https" { + return nil, fmt.Errorf("target scheme %q is not allowed", parsed.Scheme) + } + host := strings.ToLower(strings.TrimSpace(parsed.Hostname())) + if host == "" { + return nil, fmt.Errorf("target host is required") + } + if ip := net.ParseIP(host); ip != nil { + if !ip.IsLoopback() { + return nil, fmt.Errorf("target host must be loopback") + } + return parsed, nil + } + if host != "localhost" { + return nil, fmt.Errorf("target host must be localhost or loopback") + } + return parsed, nil +} + +func stopCallbackForwarder(port int) { + callbackForwardersMu.Lock() + forwarder := callbackForwarders[port] + if forwarder != nil { + delete(callbackForwarders, port) + } + callbackForwardersMu.Unlock() + + stopForwarderInstance(port, forwarder) +} + +func stopCallbackForwarderInstance(port int, forwarder *callbackForwarder) { + if forwarder == nil { + return + } + callbackForwardersMu.Lock() + if current := callbackForwarders[port]; current == forwarder { + delete(callbackForwarders, port) + } + callbackForwardersMu.Unlock() + + stopForwarderInstance(port, forwarder) +} + +func stopForwarderInstance(port int, forwarder *callbackForwarder) { + if forwarder == nil || forwarder.server == nil { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + if err := forwarder.server.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.WithError(err).Warnf("failed to shut down callback forwarder on port %d", port) + } + + select { + case <-forwarder.done: + case <-time.After(2 * time.Second): + } + + log.Infof("callback forwarder on port %d stopped", port) +} + +func (h *Handler) managementCallbackURL(path string) (string, error) { + if h == nil || h.cfg == nil || h.cfg.Port <= 0 { + return "", fmt.Errorf("server port is not configured") + } + path = normalizeManagementCallbackPath(path) + scheme := "http" + if h.cfg.TLS.Enable { + scheme = "https" + } + return fmt.Sprintf("%s://127.0.0.1:%d%s", scheme, h.cfg.Port, path), nil +} + +func normalizeManagementCallbackPath(rawPath string) string { + normalized := strings.TrimSpace(rawPath) + normalized = strings.ReplaceAll(normalized, "\\", "/") + if idx := strings.IndexAny(normalized, "?#"); idx >= 0 { + normalized = normalized[:idx] + } + if normalized == "" { + return "/" + } + if !strings.HasPrefix(normalized, "/") { + normalized = "/" + normalized + } + normalized = path.Clean(normalized) + // Security: Verify cleaned path is safe (no open redirect) + if normalized == "." || normalized == "" { + return "/" + } + // Prevent open redirect attacks (e.g., //evil.com or http://...) + if strings.Contains(normalized, "//") || strings.Contains(normalized, ":/") { + return "/" + } + // Security: Ensure path doesn't start with // or \ (could be interpreted as URL) + if len(normalized) >= 2 && (normalized[1] == '/' || normalized[1] == '\\') { + return "/" + } + if !strings.HasPrefix(normalized, "/") { + return "/" + normalized + } + return normalized +} + +func (h *Handler) ListAuthFiles(c *gin.Context) { + if h == nil { + c.JSON(500, gin.H{"error": "handler not initialized"}) + return + } + if h.authManager == nil { + h.listAuthFilesFromDisk(c) + return + } + auths := h.authManager.List() + files := make([]gin.H, 0, len(auths)) + for _, auth := range auths { + if entry := h.buildAuthFileEntry(auth); entry != nil { + files = append(files, entry) + } + } + sort.Slice(files, func(i, j int) bool { + nameI, _ := files[i]["name"].(string) + nameJ, _ := files[j]["name"].(string) + return strings.ToLower(nameI) < strings.ToLower(nameJ) + }) + c.JSON(200, gin.H{"files": files}) +} + +// GetAuthFileModels returns the models supported by a specific auth file +func (h *Handler) GetAuthFileModels(c *gin.Context) { + name := c.Query("name") + if name == "" { + c.JSON(400, gin.H{"error": "name is required"}) + return + } + + // Try to find auth ID via authManager + var authID string + if h.authManager != nil { + auths := h.authManager.List() + for _, auth := range auths { + if auth.FileName == name || auth.ID == name { + authID = auth.ID + break + } + } + } + + if authID == "" { + authID = name // fallback to filename as ID + } + + // Get models from registry + reg := registry.GetGlobalRegistry() + models := reg.GetModelsForClient(authID) + + result := make([]gin.H, 0, len(models)) + for _, m := range models { + entry := gin.H{ + "id": m.ID, + } + if m.DisplayName != "" { + entry["display_name"] = m.DisplayName + } + if m.Type != "" { + entry["type"] = m.Type + } + if m.OwnedBy != "" { + entry["owned_by"] = m.OwnedBy + } + result = append(result, entry) + } + + c.JSON(200, gin.H{"models": result}) +} + +// List auth files from disk when the auth manager is unavailable. +func (h *Handler) listAuthFilesFromDisk(c *gin.Context) { + entries, err := os.ReadDir(h.cfg.AuthDir) + if err != nil { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to read auth dir: %v", err)}) + return + } + files := make([]gin.H, 0) + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasSuffix(strings.ToLower(name), ".json") { + continue + } + if info, errInfo := e.Info(); errInfo == nil { + fileData := gin.H{"name": name, "size": info.Size(), "modtime": info.ModTime()} + + // Read file to get type field + full := filepath.Join(h.cfg.AuthDir, name) + if data, errRead := os.ReadFile(full); errRead == nil { + typeValue := gjson.GetBytes(data, "type").String() + emailValue := gjson.GetBytes(data, "email").String() + fileData["type"] = typeValue + fileData["email"] = emailValue + } + + files = append(files, fileData) + } + } + c.JSON(200, gin.H{"files": files}) +} + +func (h *Handler) buildAuthFileEntry(auth *coreauth.Auth) gin.H { + if auth == nil { + return nil + } + auth.EnsureIndex() + runtimeOnly := isRuntimeOnlyAuth(auth) + if runtimeOnly && (auth.Disabled || auth.Status == coreauth.StatusDisabled) { + return nil + } + path := strings.TrimSpace(authAttribute(auth, "path")) + if path == "" && !runtimeOnly { + return nil + } + name := strings.TrimSpace(auth.FileName) + if name == "" { + name = auth.ID + } + entry := gin.H{ + "id": auth.ID, + "auth_index": auth.Index, + "name": name, + "type": strings.TrimSpace(auth.Provider), + "provider": strings.TrimSpace(auth.Provider), + "label": auth.Label, + "status": auth.Status, + "status_message": auth.StatusMessage, + "disabled": auth.Disabled, + "unavailable": auth.Unavailable, + "runtime_only": runtimeOnly, + "source": "memory", + "size": int64(0), + } + if email := authEmail(auth); email != "" { + entry["email"] = email + } + if accountType, account := auth.AccountInfo(); accountType != "" || account != "" { + if accountType != "" { + entry["account_type"] = accountType + } + if account != "" { + entry["account"] = account + } + } + if !auth.CreatedAt.IsZero() { + entry["created_at"] = auth.CreatedAt + } + if !auth.UpdatedAt.IsZero() { + entry["modtime"] = auth.UpdatedAt + entry["updated_at"] = auth.UpdatedAt + } + if !auth.LastRefreshedAt.IsZero() { + entry["last_refresh"] = auth.LastRefreshedAt + } + if path != "" { + entry["path"] = path + entry["source"] = "file" + if info, err := os.Stat(path); err == nil { + entry["size"] = info.Size() + entry["modtime"] = info.ModTime() + } else if os.IsNotExist(err) { + // Hide credentials removed from disk but still lingering in memory. + if !runtimeOnly && (auth.Disabled || auth.Status == coreauth.StatusDisabled || strings.EqualFold(strings.TrimSpace(auth.StatusMessage), "removed via management api")) { + return nil + } + entry["source"] = "memory" + } else { + log.WithError(err).Warnf("failed to stat auth file %s", path) + } + } + if claims := extractCodexIDTokenClaims(auth); claims != nil { + entry["id_token"] = claims + } + return entry +} + +func extractCodexIDTokenClaims(auth *coreauth.Auth) gin.H { + if auth == nil || auth.Metadata == nil { + return nil + } + if !strings.EqualFold(strings.TrimSpace(auth.Provider), "codex") { + return nil + } + idTokenRaw, ok := auth.Metadata["id_token"].(string) + if !ok { + return nil + } + idToken := strings.TrimSpace(idTokenRaw) + if idToken == "" { + return nil + } + claims, err := codex.ParseJWTToken(idToken) + if err != nil || claims == nil { + return nil + } + + result := gin.H{} + if v := strings.TrimSpace(claims.CodexAuthInfo.ChatgptAccountID); v != "" { + result["chatgpt_account_id"] = v + } + if v := strings.TrimSpace(claims.CodexAuthInfo.ChatgptPlanType); v != "" { + result["plan_type"] = v + } + if v := claims.CodexAuthInfo.ChatgptSubscriptionActiveStart; v != nil { + result["chatgpt_subscription_active_start"] = v + } + if v := claims.CodexAuthInfo.ChatgptSubscriptionActiveUntil; v != nil { + result["chatgpt_subscription_active_until"] = v + } + + if len(result) == 0 { + return nil + } + return result +} + +func authEmail(auth *coreauth.Auth) string { + if auth == nil { + return "" + } + if auth.Metadata != nil { + if v, ok := auth.Metadata["email"].(string); ok { + return strings.TrimSpace(v) + } + } + if auth.Attributes != nil { + if v := strings.TrimSpace(auth.Attributes["email"]); v != "" { + return v + } + if v := strings.TrimSpace(auth.Attributes["account_email"]); v != "" { + return v + } + } + return "" +} + +func authAttribute(auth *coreauth.Auth, key string) string { + if auth == nil || len(auth.Attributes) == 0 { + return "" + } + return auth.Attributes[key] +} + +func isRuntimeOnlyAuth(auth *coreauth.Auth) bool { + if auth == nil || len(auth.Attributes) == 0 { + return false + } + return strings.EqualFold(strings.TrimSpace(auth.Attributes["runtime_only"]), "true") +} + +// Download single auth file by name +func (h *Handler) DownloadAuthFile(c *gin.Context) { + name := strings.TrimSpace(c.Query("name")) + if name == "" { + c.JSON(400, gin.H{"error": "invalid name"}) + return + } + if !strings.HasSuffix(strings.ToLower(name), ".json") { + c.JSON(400, gin.H{"error": "name must end with .json"}) + return + } + full, err := misc.ResolveSafeFilePathInDir(h.cfg.AuthDir, name) + if err != nil { + c.JSON(400, gin.H{"error": "invalid name"}) + return + } + data, err := os.ReadFile(full) + if err != nil { + if os.IsNotExist(err) { + c.JSON(404, gin.H{"error": "file not found"}) + } else { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to read file: %v", err)}) + } + return + } + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", name)) + c.Data(200, "application/json", data) +} + +// Upload auth file: multipart or raw JSON with ?name= +func (h *Handler) UploadAuthFile(c *gin.Context) { + if h.authManager == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": "core auth manager unavailable"}) + return + } + ctx := c.Request.Context() + if file, err := c.FormFile("file"); err == nil && file != nil { + name := strings.TrimSpace(file.Filename) + dst, err := misc.ResolveSafeFilePathInDir(h.cfg.AuthDir, name) + if err != nil { + c.JSON(400, gin.H{"error": "invalid auth file name"}) + return + } + if !strings.HasSuffix(strings.ToLower(filepath.Base(dst)), ".json") { + c.JSON(400, gin.H{"error": "file must be .json"}) + return + } + if errSave := c.SaveUploadedFile(file, dst); errSave != nil { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to save file: %v", errSave)}) + return + } + data, errRead := os.ReadFile(dst) + if errRead != nil { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to read saved file: %v", errRead)}) + return + } + if errReg := h.registerAuthFromFile(ctx, dst, data); errReg != nil { + // Path traversal or other validation errors should return 400 + if strings.Contains(errReg.Error(), "escapes") || strings.Contains(errReg.Error(), "traversal") { + c.JSON(400, gin.H{"error": "invalid auth file path"}) + } else { + c.JSON(500, gin.H{"error": errReg.Error()}) + } + return + } + c.JSON(200, gin.H{"status": "ok"}) + return + } + name := c.Query("name") + name = strings.TrimSpace(name) + if name == "" { + c.JSON(400, gin.H{"error": "invalid name"}) + return + } + if !strings.HasSuffix(strings.ToLower(name), ".json") { + c.JSON(400, gin.H{"error": "name must end with .json"}) + return + } + data, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(400, gin.H{"error": "failed to read body"}) + return + } + dst, err := misc.ResolveSafeFilePathInDir(h.cfg.AuthDir, name) + if err != nil { + c.JSON(400, gin.H{"error": "invalid name"}) + return + } + if errWrite := os.WriteFile(dst, data, 0o600); errWrite != nil { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to write file: %v", errWrite)}) + return + } + if err = h.registerAuthFromFile(ctx, dst, data); err != nil { + // Path traversal or other validation errors should return 400 + if strings.Contains(err.Error(), "escapes") || strings.Contains(err.Error(), "traversal") { + c.JSON(400, gin.H{"error": "invalid auth file path"}) + } else { + c.JSON(500, gin.H{"error": err.Error()}) + } + return + } + c.JSON(200, gin.H{"status": "ok"}) +} + +// Delete auth files: single by name or all +func (h *Handler) DeleteAuthFile(c *gin.Context) { + if h.authManager == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": "core auth manager unavailable"}) + return + } + ctx := c.Request.Context() + if all := c.Query("all"); all == "true" || all == "1" || all == "*" { + entries, err := os.ReadDir(h.cfg.AuthDir) + if err != nil { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to read auth dir: %v", err)}) + return + } + deleted := 0 + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasSuffix(strings.ToLower(name), ".json") { + continue + } + full, err := misc.ResolveSafeFilePathInDir(h.cfg.AuthDir, name) + if err != nil { + c.JSON(500, gin.H{"error": fmt.Sprintf("invalid auth file path: %v", err)}) + return + } + if err = os.Remove(full); err == nil { + if errDel := h.deleteTokenRecord(ctx, full); errDel != nil { + c.JSON(500, gin.H{"error": errDel.Error()}) + return + } + deleted++ + h.disableAuth(ctx, full) + } + } + c.JSON(200, gin.H{"status": "ok", "deleted": deleted}) + return + } + name := strings.TrimSpace(c.Query("name")) + if name == "" { + c.JSON(400, gin.H{"error": "invalid name"}) + return + } + full, err := misc.ResolveSafeFilePathInDir(h.cfg.AuthDir, name) + if err != nil { + c.JSON(400, gin.H{"error": "invalid name"}) + return + } + if err := os.Remove(full); err != nil { + if os.IsNotExist(err) { + c.JSON(404, gin.H{"error": "file not found"}) + } else { + c.JSON(500, gin.H{"error": fmt.Sprintf("failed to remove file: %v", err)}) + } + return + } + if err := h.deleteTokenRecord(ctx, full); err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + h.disableAuth(ctx, full) + c.JSON(200, gin.H{"status": "ok"}) +} + +func (h *Handler) authIDForPath(path string) string { + path = strings.TrimSpace(path) + if path == "" { + return "" + } + if h == nil || h.cfg == nil { + return path + } + authDir := strings.TrimSpace(h.cfg.AuthDir) + if authDir == "" { + return path + } + if rel, err := filepath.Rel(authDir, path); err == nil && rel != "" { + return rel + } + return path +} + +func (h *Handler) resolveAuthPath(path string) (string, error) { + path = strings.TrimSpace(path) + if path == "" { + return "", fmt.Errorf("auth path is empty") + } + if h == nil || h.cfg == nil { + return "", fmt.Errorf("handler configuration unavailable") + } + authDir := strings.TrimSpace(h.cfg.AuthDir) + if authDir == "" { + return "", fmt.Errorf("auth directory not configured") + } + cleanAuthDir, err := filepath.Abs(filepath.Clean(authDir)) + if err != nil { + return "", fmt.Errorf("resolve auth dir: %w", err) + } + if resolvedDir, err := filepath.EvalSymlinks(cleanAuthDir); err == nil { + cleanAuthDir = resolvedDir + } + cleanPath := filepath.Clean(path) + absPath := cleanPath + if !filepath.IsAbs(absPath) { + absPath = filepath.Join(cleanAuthDir, cleanPath) + } + absPath, err = filepath.Abs(absPath) + if err != nil { + return "", fmt.Errorf("resolve auth path: %w", err) + } + relPath, err := filepath.Rel(cleanAuthDir, absPath) + if err != nil { + return "", fmt.Errorf("resolve relative auth path: %w", err) + } + if relPath == ".." || strings.HasPrefix(relPath, ".."+string(os.PathSeparator)) { + return "", fmt.Errorf("auth path escapes auth directory") + } + return absPath, nil +} + +func (h *Handler) registerAuthFromFile(ctx context.Context, path string, data []byte) error { + if h.authManager == nil { + return nil + } + safePath, err := h.resolveAuthPath(path) + if err != nil { + return err + } + if data == nil { + data, err = os.ReadFile(safePath) + if err != nil { + return fmt.Errorf("failed to read auth file: %w", err) + } + } + metadata := make(map[string]any) + if err := json.Unmarshal(data, &metadata); err != nil { + return fmt.Errorf("invalid auth file: %w", err) + } + provider, _ := metadata["type"].(string) + if provider == "" { + provider = "unknown" + } + label := provider + if email, ok := metadata["email"].(string); ok && email != "" { + label = email + } + lastRefresh, hasLastRefresh := extractLastRefreshTimestamp(metadata) + + authID := h.authIDForPath(safePath) + if authID == "" { + authID = safePath + } + attr := map[string]string{ + "path": safePath, + "source": safePath, + } + auth := &coreauth.Auth{ + ID: authID, + Provider: provider, + FileName: filepath.Base(safePath), + Label: label, + Status: coreauth.StatusActive, + Attributes: attr, + Metadata: metadata, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if hasLastRefresh { + auth.LastRefreshedAt = lastRefresh + } + if existing, ok := h.authManager.GetByID(authID); ok { + auth.CreatedAt = existing.CreatedAt + if !hasLastRefresh { + auth.LastRefreshedAt = existing.LastRefreshedAt + } + auth.NextRefreshAfter = existing.NextRefreshAfter + if len(auth.ModelStates) == 0 && len(existing.ModelStates) > 0 { + auth.ModelStates = existing.ModelStates + } + auth.Runtime = existing.Runtime + _, err = h.authManager.Update(ctx, auth) + return err + } + _, err = h.authManager.Register(ctx, auth) + return err +} + +// PatchAuthFileStatus toggles the disabled state of an auth file +func (h *Handler) PatchAuthFileStatus(c *gin.Context) { + if h.authManager == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": "core auth manager unavailable"}) + return + } + + var req struct { + Name string `json:"name"` + Disabled *bool `json:"disabled"` + Enabled *bool `json:"enabled"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + return + } + + name := strings.TrimSpace(req.Name) + if name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + return + } + if req.Disabled == nil && req.Enabled == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "disabled or enabled is required"}) + return + } + desiredDisabled := false + if req.Disabled != nil { + desiredDisabled = *req.Disabled + } else { + desiredDisabled = !*req.Enabled + } + + ctx := c.Request.Context() + + targetAuth := h.findAuthByIdentifier(name) + + if targetAuth == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "auth file not found"}) + return + } + + // Update disabled state + targetAuth.Disabled = desiredDisabled + if desiredDisabled { + targetAuth.Status = coreauth.StatusDisabled + targetAuth.StatusMessage = "disabled via management API" + } else { + targetAuth.Status = coreauth.StatusActive + targetAuth.StatusMessage = "" + } + targetAuth.UpdatedAt = time.Now() + + if _, err := h.authManager.Update(ctx, targetAuth); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to update auth: %v", err)}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "ok", "disabled": desiredDisabled}) +} + +func (h *Handler) findAuthByIdentifier(name string) *coreauth.Auth { + name = strings.TrimSpace(name) + if name == "" || h.authManager == nil { + return nil + } + if auth, ok := h.authManager.GetByID(name); ok { + return auth + } + for _, auth := range h.authManager.List() { + if auth.FileName == name || filepath.Base(auth.FileName) == name { + return auth + } + if pathVal, ok := auth.Attributes["path"]; ok && (pathVal == name || filepath.Base(pathVal) == name) { + return auth + } + if sourceVal, ok := auth.Attributes["source"]; ok && (sourceVal == name || filepath.Base(sourceVal) == name) { + return auth + } + } + return nil +} + +// PatchAuthFileFields updates editable fields (prefix, proxy_url, priority) of an auth file. +func (h *Handler) PatchAuthFileFields(c *gin.Context) { + if h.authManager == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": "core auth manager unavailable"}) + return + } + + var req struct { + Name string `json:"name"` + Prefix *string `json:"prefix"` + ProxyURL *string `json:"proxy_url"` + Priority *int `json:"priority"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) + return + } + + name := strings.TrimSpace(req.Name) + if name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + return + } + + ctx := c.Request.Context() + + targetAuth := h.findAuthByIdentifier(name) + + if targetAuth == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "auth file not found"}) + return + } + + changed := false + if req.Prefix != nil { + targetAuth.Prefix = *req.Prefix + changed = true + } + if req.ProxyURL != nil { + targetAuth.ProxyURL = *req.ProxyURL + changed = true + } + if req.Priority != nil { + if targetAuth.Metadata == nil { + targetAuth.Metadata = make(map[string]any) + } + if *req.Priority == 0 { + delete(targetAuth.Metadata, "priority") + } else { + targetAuth.Metadata["priority"] = *req.Priority + } + changed = true + } + + if !changed { + c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"}) + return + } + + targetAuth.UpdatedAt = time.Now() + + if _, err := h.authManager.Update(ctx, targetAuth); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to update auth: %v", err)}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "ok"}) +} + +func (h *Handler) disableAuth(ctx context.Context, id string) { + if h == nil || h.authManager == nil { + return + } + authID := h.authIDForPath(id) + if authID == "" { + authID = strings.TrimSpace(id) + } + if authID == "" { + return + } + if auth, ok := h.authManager.GetByID(authID); ok { + auth.Disabled = true + auth.Status = coreauth.StatusDisabled + auth.StatusMessage = "removed via management API" + auth.UpdatedAt = time.Now() + _, _ = h.authManager.Update(ctx, auth) + } +} + +func (h *Handler) deleteTokenRecord(ctx context.Context, path string) error { + if strings.TrimSpace(path) == "" { + return fmt.Errorf("auth path is empty") + } + store := h.tokenStoreWithBaseDir() + if store == nil { + return fmt.Errorf("token store unavailable") + } + return store.Delete(ctx, path) +} + +func (h *Handler) tokenStoreWithBaseDir() coreauth.Store { + if h == nil { + return nil + } + store := h.tokenStore + if store == nil { + store = sdkAuth.GetTokenStore() + h.tokenStore = store + } + if h.cfg != nil { + if dirSetter, ok := store.(interface{ SetBaseDir(string) }); ok { + dirSetter.SetBaseDir(h.cfg.AuthDir) + } + } + return store +} + +func (h *Handler) saveTokenRecord(ctx context.Context, record *coreauth.Auth) (string, error) { + if record == nil { + return "", fmt.Errorf("token record is nil") + } + store := h.tokenStoreWithBaseDir() + if store == nil { + return "", fmt.Errorf("token store unavailable") + } + return store.Save(ctx, record) +} + +func (h *Handler) RequestAnthropicToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing Claude authentication...") + + // Generate PKCE codes + pkceCodes, err := claude.GeneratePKCECodes() + if err != nil { + log.Errorf("Failed to generate PKCE codes: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate PKCE codes"}) + return + } + + // Generate random state parameter + state, err := misc.GenerateRandomState() + if err != nil { + log.Errorf("Failed to generate state parameter: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate state parameter"}) + return + } + + // Initialize Claude auth service + anthropicAuth := claude.NewClaudeAuth(h.cfg, http.DefaultClient) + + // Generate authorization URL (then override redirect_uri to reuse server port) + authURL, state, err := anthropicAuth.GenerateAuthURL(state, pkceCodes) + if err != nil { + log.Errorf("Failed to generate authorization URL: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) + return + } + + RegisterOAuthSession(state, "anthropic") + + isWebUI := isWebUIRequest(c) + var forwarder *callbackForwarder + if isWebUI { + targetURL, errTarget := h.managementCallbackURL("/anthropic/callback") + if errTarget != nil { + log.WithError(errTarget).Error("failed to compute anthropic callback target") + c.JSON(http.StatusInternalServerError, gin.H{"error": "callback server unavailable"}) + return + } + var errStart error + if forwarder, errStart = startCallbackForwarder(anthropicCallbackPort, "anthropic", targetURL); errStart != nil { + log.WithError(errStart).Error("failed to start anthropic callback forwarder") + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to start callback server"}) + return + } + } + + go func() { + if isWebUI { + defer stopCallbackForwarderInstance(anthropicCallbackPort, forwarder) + } + + // Helper: wait for callback file + waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-anthropic-%s.oauth", state)) + waitForFile := func(path string, timeout time.Duration) (map[string]string, error) { + deadline := time.Now().Add(timeout) + for { + if !IsOAuthSessionPending(state, "anthropic") { + return nil, errOAuthSessionNotPending + } + if time.Now().After(deadline) { + SetOAuthSessionError(state, "Timeout waiting for OAuth callback") + return nil, fmt.Errorf("timeout waiting for OAuth callback") + } + data, errRead := os.ReadFile(path) + if errRead == nil { + var m map[string]string + _ = json.Unmarshal(data, &m) + _ = os.Remove(path) + return m, nil + } + time.Sleep(500 * time.Millisecond) + } + } + + fmt.Println("Waiting for authentication callback...") + // Wait up to 5 minutes + resultMap, errWait := waitForFile(waitFile, 5*time.Minute) + if errWait != nil { + if errors.Is(errWait, errOAuthSessionNotPending) { + return + } + authErr := claude.NewAuthenticationError(claude.ErrCallbackTimeout, errWait) + log.Error(claude.GetUserFriendlyMessage(authErr)) + return + } + if errStr := resultMap["error"]; errStr != "" { + oauthErr := claude.NewOAuthError(errStr, "", http.StatusBadRequest) + log.Error(claude.GetUserFriendlyMessage(oauthErr)) + SetOAuthSessionError(state, "Bad request") + return + } + if resultMap["state"] != state { + authErr := claude.NewAuthenticationError(claude.ErrInvalidState, fmt.Errorf("expected %s, got %s", state, resultMap["state"])) + log.Error(claude.GetUserFriendlyMessage(authErr)) + SetOAuthSessionError(state, "State code error") + return + } + + // Parse code (Claude may append state after '#') + rawCode := resultMap["code"] + code := strings.Split(rawCode, "#")[0] + + // Exchange code for tokens using internal auth service + bundle, errExchange := anthropicAuth.ExchangeCodeForTokens(ctx, code, state, pkceCodes) + if errExchange != nil { + authErr := claude.NewAuthenticationError(claude.ErrCodeExchangeFailed, errExchange) + log.Errorf("Failed to exchange authorization code for tokens: %v", authErr) + SetOAuthSessionError(state, "Failed to exchange authorization code for tokens") + return + } + + // Create token storage + tokenStorage := anthropicAuth.CreateTokenStorage(bundle) + record := &coreauth.Auth{ + ID: fmt.Sprintf("claude-%s.json", tokenStorage.Email), + Provider: "claude", + FileName: fmt.Sprintf("claude-%s.json", tokenStorage.Email), + Storage: tokenStorage, + Metadata: map[string]any{"email": tokenStorage.Email}, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + if bundle.APIKey != "" { + fmt.Println("API key obtained and saved") + } + fmt.Println("You can now use Claude services through this CLI") + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("anthropic") + }() + + c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { + ctx := context.Background() + proxyHTTPClient := util.SetProxy(&h.cfg.SDKConfig, &http.Client{}) + ctx = context.WithValue(ctx, oauth2.HTTPClient, proxyHTTPClient) + + // Optional project ID from query + projectID := c.Query("project_id") + + fmt.Println("Initializing Google authentication...") + + // OAuth2 configuration using exported constants from pkg/llmproxy/auth/gemini + conf := &oauth2.Config{ + ClientID: geminiAuth.ClientID, + ClientSecret: geminiAuth.ClientSecret, + RedirectURL: fmt.Sprintf("http://localhost:%d/oauth2callback", geminiAuth.DefaultCallbackPort), + Scopes: geminiAuth.Scopes, + Endpoint: google.Endpoint, + } + + // Build authorization URL and return it immediately + state := fmt.Sprintf("gem-%d", time.Now().UnixNano()) + authURL := conf.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent")) + + RegisterOAuthSession(state, "gemini") + + isWebUI := isWebUIRequest(c) + var forwarder *callbackForwarder + if isWebUI { + targetURL, errTarget := h.managementCallbackURL("/google/callback") + if errTarget != nil { + log.WithError(errTarget).Error("failed to compute gemini callback target") + c.JSON(http.StatusInternalServerError, gin.H{"error": "callback server unavailable"}) + return + } + var errStart error + if forwarder, errStart = startCallbackForwarder(geminiCallbackPort, "gemini", targetURL); errStart != nil { + log.WithError(errStart).Error("failed to start gemini callback forwarder") + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to start callback server"}) + return + } + } + + go func() { + if isWebUI { + defer stopCallbackForwarderInstance(geminiCallbackPort, forwarder) + } + + // Wait for callback file written by server route + waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-gemini-%s.oauth", state)) + fmt.Println("Waiting for authentication callback...") + deadline := time.Now().Add(5 * time.Minute) + var authCode string + for { + if !IsOAuthSessionPending(state, "gemini") { + return + } + if time.Now().After(deadline) { + log.Error("oauth flow timed out") + SetOAuthSessionError(state, "OAuth flow timed out") + return + } + if data, errR := os.ReadFile(waitFile); errR == nil { + var m map[string]string + _ = json.Unmarshal(data, &m) + _ = os.Remove(waitFile) + if errStr := m["error"]; errStr != "" { + log.Errorf("Authentication failed: %s", errStr) + SetOAuthSessionError(state, "Authentication failed") + return + } + authCode = m["code"] + if authCode == "" { + log.Errorf("Authentication failed: code not found") + SetOAuthSessionError(state, "Authentication failed: code not found") + return + } + break + } + time.Sleep(500 * time.Millisecond) + } + + // Exchange authorization code for token + token, err := conf.Exchange(ctx, authCode) + if err != nil { + log.Errorf("Failed to exchange token: %v", err) + SetOAuthSessionError(state, "Failed to exchange token") + return + } + + requestedProjectID := strings.TrimSpace(projectID) + + // Create token storage (mirrors pkg/llmproxy/auth/gemini createTokenStorage) + authHTTPClient := conf.Client(ctx, token) + req, errNewRequest := http.NewRequestWithContext(ctx, "GET", "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", nil) + if errNewRequest != nil { + log.Errorf("Could not get user info: %v", errNewRequest) + SetOAuthSessionError(state, "Could not get user info") + return + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken)) + + resp, errDo := authHTTPClient.Do(req) + if errDo != nil { + log.Errorf("Failed to execute request: %v", errDo) + SetOAuthSessionError(state, "Failed to execute request") + return + } + defer func() { + if errClose := resp.Body.Close(); errClose != nil { + log.Printf("warn: failed to close response body: %v", errClose) + } + }() + + bodyBytes, _ := io.ReadAll(resp.Body) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + log.Errorf("Get user info request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + SetOAuthSessionError(state, fmt.Sprintf("Get user info request failed with status %d", resp.StatusCode)) + return + } + + email := gjson.GetBytes(bodyBytes, "email").String() + if email != "" { + fmt.Printf("Authenticated user email: %s\n", email) + } else { + fmt.Println("Failed to get user email from token") + } + + // Marshal/unmarshal oauth2.Token to generic map and enrich fields + var ifToken map[string]any + jsonData, _ := json.Marshal(token) + if errUnmarshal := json.Unmarshal(jsonData, &ifToken); errUnmarshal != nil { + log.Errorf("Failed to unmarshal token: %v", errUnmarshal) + SetOAuthSessionError(state, "Failed to unmarshal token") + return + } + + ifToken["token_uri"] = "https://oauth2.googleapis.com/token" + ifToken["client_id"] = geminiAuth.ClientID + ifToken["client_secret"] = geminiAuth.ClientSecret + ifToken["scopes"] = geminiAuth.Scopes + ifToken["universe_domain"] = "googleapis.com" + + ts := geminiAuth.GeminiTokenStorage{ + Token: ifToken, + ProjectID: requestedProjectID, + Email: email, + Auto: requestedProjectID == "", + } + + // Initialize authenticated HTTP client via GeminiAuth to honor proxy settings + gemAuth := geminiAuth.NewGeminiAuth() + gemClient, errGetClient := gemAuth.GetAuthenticatedClient(ctx, &ts, h.cfg, &geminiAuth.WebLoginOptions{ + NoBrowser: true, + }) + if errGetClient != nil { + log.Errorf("failed to get authenticated client: %v", errGetClient) + SetOAuthSessionError(state, "Failed to get authenticated client") + return + } + fmt.Println("Authentication successful.") + + if strings.EqualFold(requestedProjectID, "ALL") { + ts.Auto = false + projects, errAll := onboardAllGeminiProjects(ctx, gemClient, &ts) + if errAll != nil { + log.Errorf("Failed to complete Gemini CLI onboarding: %v", errAll) + SetOAuthSessionError(state, "Failed to complete Gemini CLI onboarding") + return + } + if errVerify := ensureGeminiProjectsEnabled(ctx, gemClient, projects); errVerify != nil { + log.Errorf("Failed to verify Cloud AI API status: %v", errVerify) + SetOAuthSessionError(state, "Failed to verify Cloud AI API status") + return + } + ts.ProjectID = strings.Join(projects, ",") + ts.Checked = true + } else if strings.EqualFold(requestedProjectID, "GOOGLE_ONE") { + ts.Auto = false + if errSetup := performGeminiCLISetup(ctx, gemClient, &ts, ""); errSetup != nil { + log.Errorf("Google One auto-discovery failed: %v", errSetup) + SetOAuthSessionError(state, "Google One auto-discovery failed") + return + } + if strings.TrimSpace(ts.ProjectID) == "" { + log.Error("Google One auto-discovery returned empty project ID") + SetOAuthSessionError(state, "Google One auto-discovery returned empty project ID") + return + } + isChecked, errCheck := checkCloudAPIIsEnabled(ctx, gemClient, ts.ProjectID) + if errCheck != nil { + log.Errorf("Failed to verify Cloud AI API status: %v", errCheck) + SetOAuthSessionError(state, "Failed to verify Cloud AI API status") + return + } + ts.Checked = isChecked + if !isChecked { + log.Error("Cloud AI API is not enabled for the auto-discovered project") + SetOAuthSessionError(state, "Cloud AI API not enabled") + return + } + } else { + if errEnsure := ensureGeminiProjectAndOnboard(ctx, gemClient, &ts, requestedProjectID); errEnsure != nil { + log.Errorf("Failed to complete Gemini CLI onboarding: %v", errEnsure) + SetOAuthSessionError(state, "Failed to complete Gemini CLI onboarding") + return + } + + if strings.TrimSpace(ts.ProjectID) == "" { + log.Error("Onboarding did not return a project ID") + SetOAuthSessionError(state, "Failed to resolve project ID") + return + } + + isChecked, errCheck := checkCloudAPIIsEnabled(ctx, gemClient, ts.ProjectID) + if errCheck != nil { + log.Errorf("Failed to verify Cloud AI API status: %v", errCheck) + SetOAuthSessionError(state, "Failed to verify Cloud AI API status") + return + } + ts.Checked = isChecked + if !isChecked { + log.Error("Cloud AI API is not enabled for the selected project") + SetOAuthSessionError(state, "Cloud AI API not enabled") + return + } + } + + recordMetadata := map[string]any{ + "email": ts.Email, + "project_id": ts.ProjectID, + "auto": ts.Auto, + "checked": ts.Checked, + } + + fileName := geminiAuth.CredentialFileName(ts.Email, ts.ProjectID, true) + record := &coreauth.Auth{ + ID: fileName, + Provider: "gemini", + FileName: fileName, + Storage: &ts, + Metadata: recordMetadata, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save token to file: %v", errSave) + SetOAuthSessionError(state, "Failed to save token to file") + return + } + + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("gemini") + fmt.Printf("You can now use Gemini CLI services through this CLI; token saved to %s\n", savedPath) + }() + + c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestCodexToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing Codex authentication...") + + // Generate PKCE codes + pkceCodes, err := codex.GeneratePKCECodes() + if err != nil { + log.Errorf("Failed to generate PKCE codes: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate PKCE codes"}) + return + } + + // Generate random state parameter + state, err := misc.GenerateRandomState() + if err != nil { + log.Errorf("Failed to generate state parameter: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate state parameter"}) + return + } + + // Initialize Codex auth service + openaiAuth := codex.NewCodexAuth(h.cfg) + + // Generate authorization URL + authURL, err := openaiAuth.GenerateAuthURL(state, pkceCodes) + if err != nil { + log.Errorf("Failed to generate authorization URL: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) + return + } + + RegisterOAuthSession(state, "codex") + + isWebUI := isWebUIRequest(c) + var forwarder *callbackForwarder + if isWebUI { + targetURL, errTarget := h.managementCallbackURL("/codex/callback") + if errTarget != nil { + log.WithError(errTarget).Error("failed to compute codex callback target") + c.JSON(http.StatusInternalServerError, gin.H{"error": "callback server unavailable"}) + return + } + var errStart error + if forwarder, errStart = startCallbackForwarder(codexCallbackPort, "codex", targetURL); errStart != nil { + log.WithError(errStart).Error("failed to start codex callback forwarder") + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to start callback server"}) + return + } + } + + go func() { + if isWebUI { + defer stopCallbackForwarderInstance(codexCallbackPort, forwarder) + } + + // Wait for callback file + waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-codex-%s.oauth", state)) + deadline := time.Now().Add(5 * time.Minute) + var code string + for { + if !IsOAuthSessionPending(state, "codex") { + return + } + if time.Now().After(deadline) { + authErr := codex.NewAuthenticationError(codex.ErrCallbackTimeout, fmt.Errorf("timeout waiting for OAuth callback")) + log.Error(codex.GetUserFriendlyMessage(authErr)) + SetOAuthSessionError(state, "Timeout waiting for OAuth callback") + return + } + if data, errR := os.ReadFile(waitFile); errR == nil { + var m map[string]string + _ = json.Unmarshal(data, &m) + _ = os.Remove(waitFile) + if errStr := m["error"]; errStr != "" { + oauthErr := codex.NewOAuthError(errStr, "", http.StatusBadRequest) + log.Error(codex.GetUserFriendlyMessage(oauthErr)) + SetOAuthSessionError(state, "Bad Request") + return + } + if m["state"] != state { + authErr := codex.NewAuthenticationError(codex.ErrInvalidState, fmt.Errorf("expected %s, got %s", state, m["state"])) + SetOAuthSessionError(state, "State code error") + log.Error(codex.GetUserFriendlyMessage(authErr)) + return + } + code = m["code"] + break + } + time.Sleep(500 * time.Millisecond) + } + + log.Debug("Authorization code received, exchanging for tokens...") + // Exchange code for tokens using internal auth service + bundle, errExchange := openaiAuth.ExchangeCodeForTokens(ctx, code, pkceCodes) + if errExchange != nil { + authErr := codex.NewAuthenticationError(codex.ErrCodeExchangeFailed, errExchange) + SetOAuthSessionError(state, "Failed to exchange authorization code for tokens") + log.Errorf("Failed to exchange authorization code for tokens: %v", authErr) + return + } + + // Extract additional info for filename generation + claims, _ := codex.ParseJWTToken(bundle.TokenData.IDToken) + planType := "" + hashAccountID := "" + if claims != nil { + planType = strings.TrimSpace(claims.CodexAuthInfo.ChatgptPlanType) + if accountID := claims.GetAccountID(); accountID != "" { + digest := sha256.Sum256([]byte(accountID)) + hashAccountID = hex.EncodeToString(digest[:])[:8] + } + } + + // Create token storage and persist + tokenStorage := openaiAuth.CreateTokenStorage(bundle) + fileName := codex.CredentialFileName(tokenStorage.Email, planType, hashAccountID, true) + record := &coreauth.Auth{ + ID: fileName, + Provider: "codex", + FileName: fileName, + Storage: tokenStorage, + Metadata: map[string]any{ + "email": tokenStorage.Email, + "account_id": tokenStorage.AccountID, + }, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + SetOAuthSessionError(state, "Failed to save authentication tokens") + log.Errorf("Failed to save authentication tokens: %v", errSave) + return + } + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + if bundle.APIKey != "" { + fmt.Println("API key obtained and saved") + } + fmt.Println("You can now use Codex services through this CLI") + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("codex") + }() + + c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestAntigravityToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing Antigravity authentication...") + + authSvc := antigravity.NewAntigravityAuth(h.cfg, nil) + + state, errState := misc.GenerateRandomState() + if errState != nil { + log.Errorf("Failed to generate state parameter: %v", errState) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate state parameter"}) + return + } + + redirectURI := fmt.Sprintf("http://localhost:%d/oauth-callback", antigravity.CallbackPort) + authURL := authSvc.BuildAuthURL(state, redirectURI) + + RegisterOAuthSession(state, "antigravity") + + isWebUI := isWebUIRequest(c) + var forwarder *callbackForwarder + if isWebUI { + targetURL, errTarget := h.managementCallbackURL("/antigravity/callback") + if errTarget != nil { + log.WithError(errTarget).Error("failed to compute antigravity callback target") + c.JSON(http.StatusInternalServerError, gin.H{"error": "callback server unavailable"}) + return + } + var errStart error + if forwarder, errStart = startCallbackForwarder(antigravity.CallbackPort, "antigravity", targetURL); errStart != nil { + log.WithError(errStart).Error("failed to start antigravity callback forwarder") + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to start callback server"}) + return + } + } + + go func() { + if isWebUI { + defer stopCallbackForwarderInstance(antigravity.CallbackPort, forwarder) + } + + waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-antigravity-%s.oauth", state)) + deadline := time.Now().Add(5 * time.Minute) + var authCode string + for { + if !IsOAuthSessionPending(state, "antigravity") { + return + } + if time.Now().After(deadline) { + log.Error("oauth flow timed out") + SetOAuthSessionError(state, "OAuth flow timed out") + return + } + if data, errReadFile := os.ReadFile(waitFile); errReadFile == nil { + var payload map[string]string + _ = json.Unmarshal(data, &payload) + _ = os.Remove(waitFile) + if errStr := strings.TrimSpace(payload["error"]); errStr != "" { + log.Errorf("Authentication failed: %s", errStr) + SetOAuthSessionError(state, "Authentication failed") + return + } + if payloadState := strings.TrimSpace(payload["state"]); payloadState != "" && payloadState != state { + log.Errorf("Authentication failed: state mismatch") + SetOAuthSessionError(state, "Authentication failed: state mismatch") + return + } + authCode = strings.TrimSpace(payload["code"]) + if authCode == "" { + log.Error("Authentication failed: code not found") + SetOAuthSessionError(state, "Authentication failed: code not found") + return + } + break + } + time.Sleep(500 * time.Millisecond) + } + + tokenResp, errToken := authSvc.ExchangeCodeForTokens(ctx, authCode, redirectURI) + if errToken != nil { + log.Errorf("Failed to exchange token: %v", errToken) + SetOAuthSessionError(state, "Failed to exchange token") + return + } + + accessToken := strings.TrimSpace(tokenResp.AccessToken) + if accessToken == "" { + log.Error("antigravity: token exchange returned empty access token") + SetOAuthSessionError(state, "Failed to exchange token") + return + } + + email, errInfo := authSvc.FetchUserInfo(ctx, accessToken) + if errInfo != nil { + log.Errorf("Failed to fetch user info: %v", errInfo) + SetOAuthSessionError(state, "Failed to fetch user info") + return + } + email = strings.TrimSpace(email) + if email == "" { + log.Error("antigravity: user info returned empty email") + SetOAuthSessionError(state, "Failed to fetch user info") + return + } + + projectID := "" + if accessToken != "" { + fetchedProjectID, errProject := authSvc.FetchProjectID(ctx, accessToken) + if errProject != nil { + log.Warnf("antigravity: failed to fetch project ID: %v", errProject) + } else { + projectID = fetchedProjectID + log.Infof("antigravity: obtained project ID %s", projectID) + } + } + + now := time.Now() + metadata := map[string]any{ + "type": "antigravity", + "access_token": tokenResp.AccessToken, + "refresh_token": tokenResp.RefreshToken, + "expires_in": tokenResp.ExpiresIn, + "timestamp": now.UnixMilli(), + "expired": now.Add(time.Duration(tokenResp.ExpiresIn) * time.Second).Format(time.RFC3339), + } + if email != "" { + metadata["email"] = email + } + if projectID != "" { + metadata["project_id"] = projectID + } + + fileName := antigravity.CredentialFileName(email) + label := strings.TrimSpace(email) + if label == "" { + label = "antigravity" + } + + record := &coreauth.Auth{ + ID: fileName, + Provider: "antigravity", + FileName: fileName, + Label: label, + Metadata: metadata, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save token to file: %v", errSave) + SetOAuthSessionError(state, "Failed to save token to file") + return + } + + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("antigravity") + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + if projectID != "" { + fmt.Printf("Using GCP project: %s\n", projectID) + } + fmt.Println("You can now use Antigravity services through this CLI") + }() + + c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestQwenToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing Qwen authentication...") + + state := fmt.Sprintf("gem-%d", time.Now().UnixNano()) + // Initialize Qwen auth service + qwenAuth := qwen.NewQwenAuth(h.cfg, http.DefaultClient) + + // Generate authorization URL + deviceFlow, err := qwenAuth.InitiateDeviceFlow(ctx) + if err != nil { + log.Errorf("Failed to generate authorization URL: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) + return + } + authURL := deviceFlow.VerificationURIComplete + + RegisterOAuthSession(state, "qwen") + + go func() { + fmt.Println("Waiting for authentication...") + tokenData, errPollForToken := qwenAuth.PollForToken(deviceFlow.DeviceCode, deviceFlow.CodeVerifier) + if errPollForToken != nil { + SetOAuthSessionError(state, "Authentication failed") + fmt.Printf("Authentication failed: %v\n", errPollForToken) + return + } + + // Create token storage + tokenStorage := qwenAuth.CreateTokenStorage(tokenData) + + tokenStorage.Email = fmt.Sprintf("%d", time.Now().UnixMilli()) + record := &coreauth.Auth{ + ID: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), + Provider: "qwen", + FileName: fmt.Sprintf("qwen-%s.json", tokenStorage.Email), + Storage: tokenStorage, + Metadata: map[string]any{"email": tokenStorage.Email}, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + fmt.Println("You can now use Qwen services through this CLI") + CompleteOAuthSession(state) + }() + + c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestKimiToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing Kimi authentication...") + + state := fmt.Sprintf("kmi-%d", time.Now().UnixNano()) + // Initialize Kimi auth service + kimiAuth := kimi.NewKimiAuth(h.cfg) + + // Generate authorization URL + deviceFlow, errStartDeviceFlow := kimiAuth.StartDeviceFlow(ctx) + if errStartDeviceFlow != nil { + log.Errorf("Failed to generate authorization URL: %v", errStartDeviceFlow) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) + return + } + authURL := deviceFlow.VerificationURIComplete + if authURL == "" { + authURL = deviceFlow.VerificationURI + } + + RegisterOAuthSession(state, "kimi") + + go func() { + fmt.Println("Waiting for authentication...") + authBundle, errWaitForAuthorization := kimiAuth.WaitForAuthorization(ctx, deviceFlow) + if errWaitForAuthorization != nil { + SetOAuthSessionError(state, "Authentication failed") + fmt.Printf("Authentication failed: %v\n", errWaitForAuthorization) + return + } + + // Create token storage + tokenStorage := kimiAuth.CreateTokenStorage(authBundle) + + metadata := map[string]any{ + "type": "kimi", + "access_token": authBundle.TokenData.AccessToken, + "refresh_token": authBundle.TokenData.RefreshToken, + "token_type": authBundle.TokenData.TokenType, + "scope": authBundle.TokenData.Scope, + "timestamp": time.Now().UnixMilli(), + } + if authBundle.TokenData.ExpiresAt > 0 { + expired := time.Unix(authBundle.TokenData.ExpiresAt, 0).UTC().Format(time.RFC3339) + metadata["expired"] = expired + } + if strings.TrimSpace(authBundle.DeviceID) != "" { + metadata["device_id"] = strings.TrimSpace(authBundle.DeviceID) + } + + fileName := fmt.Sprintf("kimi-%d.json", time.Now().UnixMilli()) + record := &coreauth.Auth{ + ID: fileName, + Provider: "kimi", + FileName: fileName, + Label: "Kimi User", + Storage: tokenStorage, + Metadata: metadata, + } + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + fmt.Println("You can now use Kimi services through this CLI") + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("kimi") + }() + + c.JSON(200, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestIFlowToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing iFlow authentication...") + + state := fmt.Sprintf("ifl-%d", time.Now().UnixNano()) + authSvc := iflowauth.NewIFlowAuth(h.cfg, http.DefaultClient) + authURL, redirectURI := authSvc.AuthorizationURL(state, iflowauth.CallbackPort) + + RegisterOAuthSession(state, "iflow") + + isWebUI := isWebUIRequest(c) + var forwarder *callbackForwarder + if isWebUI { + targetURL, errTarget := h.managementCallbackURL("/iflow/callback") + if errTarget != nil { + log.WithError(errTarget).Error("failed to compute iflow callback target") + c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "callback server unavailable"}) + return + } + var errStart error + if forwarder, errStart = startCallbackForwarder(iflowauth.CallbackPort, "iflow", targetURL); errStart != nil { + log.WithError(errStart).Error("failed to start iflow callback forwarder") + c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "failed to start callback server"}) + return + } + } + + go func() { + if isWebUI { + defer stopCallbackForwarderInstance(iflowauth.CallbackPort, forwarder) + } + fmt.Println("Waiting for authentication...") + + waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-iflow-%s.oauth", state)) + deadline := time.Now().Add(5 * time.Minute) + var resultMap map[string]string + for { + if !IsOAuthSessionPending(state, "iflow") { + return + } + if time.Now().After(deadline) { + SetOAuthSessionError(state, "Authentication failed") + fmt.Println("Authentication failed: timeout waiting for callback") + return + } + if data, errR := os.ReadFile(waitFile); errR == nil { + _ = os.Remove(waitFile) + _ = json.Unmarshal(data, &resultMap) + break + } + time.Sleep(500 * time.Millisecond) + } + + if errStr := strings.TrimSpace(resultMap["error"]); errStr != "" { + SetOAuthSessionError(state, "Authentication failed") + fmt.Printf("Authentication failed: %s\n", errStr) + return + } + if resultState := strings.TrimSpace(resultMap["state"]); resultState != state { + SetOAuthSessionError(state, "Authentication failed") + fmt.Println("Authentication failed: state mismatch") + return + } + + code := strings.TrimSpace(resultMap["code"]) + if code == "" { + SetOAuthSessionError(state, "Authentication failed") + fmt.Println("Authentication failed: code missing") + return + } + + tokenData, errExchange := authSvc.ExchangeCodeForTokens(ctx, code, redirectURI) + if errExchange != nil { + SetOAuthSessionError(state, "Authentication failed") + fmt.Printf("Authentication failed: %v\n", errExchange) + return + } + + tokenStorage := authSvc.CreateTokenStorage(tokenData) + identifier := strings.TrimSpace(tokenStorage.Email) + if identifier == "" { + identifier = fmt.Sprintf("%d", time.Now().UnixMilli()) + tokenStorage.Email = identifier + } + record := &coreauth.Auth{ + ID: fmt.Sprintf("iflow-%s.json", identifier), + Provider: "iflow", + FileName: fmt.Sprintf("iflow-%s.json", identifier), + Storage: tokenStorage, + Metadata: map[string]any{"email": identifier, "api_key": tokenStorage.APIKey}, + Attributes: map[string]string{"api_key": tokenStorage.APIKey}, + } + + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + SetOAuthSessionError(state, "Failed to save authentication tokens") + log.Errorf("Failed to save authentication tokens: %v", errSave) + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + if tokenStorage.APIKey != "" { + fmt.Println("API key obtained and saved") + } + fmt.Println("You can now use iFlow services through this CLI") + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("iflow") + }() + + c.JSON(http.StatusOK, gin.H{"status": "ok", "url": authURL, "state": state}) +} + +func (h *Handler) RequestGitHubToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing GitHub Copilot authentication...") + + state := fmt.Sprintf("gh-%d", time.Now().UnixNano()) + + // Initialize Copilot auth service + // We need to import "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/copilot" first if not present + // Assuming copilot package is imported as "copilot" + deviceClient := copilot.NewDeviceFlowClient(h.cfg) + + // Initiate device flow + deviceCode, err := deviceClient.RequestDeviceCode(ctx) + if err != nil { + log.Errorf("Failed to initiate device flow: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initiate device flow"}) + return + } + + authURL := deviceCode.VerificationURI + userCode := deviceCode.UserCode + + RegisterOAuthSession(state, "github") + + go func() { + fmt.Printf("Please visit %s and enter code: %s\n", authURL, userCode) + + tokenData, errPoll := deviceClient.PollForToken(ctx, deviceCode) + if errPoll != nil { + SetOAuthSessionError(state, "Authentication failed") + fmt.Printf("Authentication failed: %v\n", errPoll) + return + } + + username, errUser := deviceClient.FetchUserInfo(ctx, tokenData.AccessToken) + if errUser != nil { + log.Warnf("Failed to fetch user info: %v", errUser) + username = "github-user" + } + + tokenStorage := &copilot.CopilotTokenStorage{ + AccessToken: tokenData.AccessToken, + TokenType: tokenData.TokenType, + Scope: tokenData.Scope, + Username: username, + Type: "github-copilot", + } + + fileName := fmt.Sprintf("github-%s.json", username) + record := &coreauth.Auth{ + ID: fileName, + Provider: "github", + FileName: fileName, + Storage: tokenStorage, + Metadata: map[string]any{ + "email": username, + "username": username, + }, + } + + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + fmt.Println("You can now use GitHub Copilot services through this CLI") + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("github") + }() + + c.JSON(200, gin.H{ + "status": "ok", + "url": authURL, + "state": state, + "user_code": userCode, + "verification_uri": authURL, + }) +} + +func (h *Handler) RequestIFlowCookieToken(c *gin.Context) { + ctx := context.Background() + + var payload struct { + Cookie string `json:"cookie"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "cookie is required"}) + return + } + + cookieValue := strings.TrimSpace(payload.Cookie) + + if cookieValue == "" { + c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "cookie is required"}) + return + } + + cookieValue, errNormalize := iflowauth.NormalizeCookie(cookieValue) + if errNormalize != nil { + c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": errNormalize.Error()}) + return + } + + // Check for duplicate BXAuth before authentication + bxAuth := iflowauth.ExtractBXAuth(cookieValue) + if existingFile, err := iflowauth.CheckDuplicateBXAuth(h.cfg.AuthDir, bxAuth); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "failed to check duplicate"}) + return + } else if existingFile != "" { + existingFileName := filepath.Base(existingFile) + c.JSON(http.StatusConflict, gin.H{"status": "error", "error": "duplicate BXAuth found", "existing_file": existingFileName}) + return + } + + authSvc := iflowauth.NewIFlowAuth(h.cfg, http.DefaultClient) + tokenData, errAuth := authSvc.AuthenticateWithCookie(ctx, cookieValue) + if errAuth != nil { + c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": errAuth.Error()}) + return + } + + tokenData.Cookie = cookieValue + + tokenStorage := authSvc.CreateCookieTokenStorage(tokenData) + email := strings.TrimSpace(tokenStorage.Email) + if email == "" { + c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "failed to extract email from token"}) + return + } + + fileName := iflowauth.SanitizeIFlowFileName(email) + if fileName == "" { + fileName = fmt.Sprintf("iflow-%d", time.Now().UnixMilli()) + } else { + fileName = fmt.Sprintf("iflow-%s", fileName) + } + + tokenStorage.Email = email + timestamp := time.Now().Unix() + + record := &coreauth.Auth{ + ID: fmt.Sprintf("%s-%d.json", fileName, timestamp), + Provider: "iflow", + FileName: fmt.Sprintf("%s-%d.json", fileName, timestamp), + Storage: tokenStorage, + Metadata: map[string]any{ + "email": email, + "api_key": tokenStorage.APIKey, + "expires_at": tokenStorage.Expire, + "cookie": tokenStorage.Cookie, + "type": tokenStorage.Type, + "last_refresh": tokenStorage.LastRefresh, + }, + Attributes: map[string]string{ + "api_key": tokenStorage.APIKey, + }, + } + + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "failed to save authentication tokens"}) + return + } + + fmt.Printf("iFlow cookie authentication successful. Token saved to %s\n", savedPath) + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "saved_path": savedPath, + "email": email, + "expires_at": tokenStorage.Expire, + "type": tokenStorage.Type, + }) +} + +type projectSelectionRequiredError struct{} + +func (e *projectSelectionRequiredError) Error() string { + return "gemini cli: project selection required" +} + +func ensureGeminiProjectAndOnboard(ctx context.Context, httpClient *http.Client, storage *geminiAuth.GeminiTokenStorage, requestedProject string) error { + if storage == nil { + return fmt.Errorf("gemini storage is nil") + } + + trimmedRequest := strings.TrimSpace(requestedProject) + if trimmedRequest == "" { + projects, errProjects := fetchGCPProjects(ctx, httpClient) + if errProjects != nil { + return fmt.Errorf("fetch project list: %w", errProjects) + } + if len(projects) == 0 { + return fmt.Errorf("no Google Cloud projects available for this account") + } + trimmedRequest = strings.TrimSpace(projects[0].ProjectID) + if trimmedRequest == "" { + return fmt.Errorf("resolved project id is empty") + } + storage.Auto = true + } else { + storage.Auto = false + } + + if err := performGeminiCLISetup(ctx, httpClient, storage, trimmedRequest); err != nil { + return err + } + + if strings.TrimSpace(storage.ProjectID) == "" { + storage.ProjectID = trimmedRequest + } + + return nil +} + +func onboardAllGeminiProjects(ctx context.Context, httpClient *http.Client, storage *geminiAuth.GeminiTokenStorage) ([]string, error) { + projects, errProjects := fetchGCPProjects(ctx, httpClient) + if errProjects != nil { + return nil, fmt.Errorf("fetch project list: %w", errProjects) + } + if len(projects) == 0 { + return nil, fmt.Errorf("no Google Cloud projects available for this account") + } + activated := make([]string, 0, len(projects)) + seen := make(map[string]struct{}, len(projects)) + for _, project := range projects { + candidate := strings.TrimSpace(project.ProjectID) + if candidate == "" { + continue + } + if _, dup := seen[candidate]; dup { + continue + } + if err := performGeminiCLISetup(ctx, httpClient, storage, candidate); err != nil { + return nil, fmt.Errorf("onboard project %s: %w", candidate, err) + } + finalID := strings.TrimSpace(storage.ProjectID) + if finalID == "" { + finalID = candidate + } + activated = append(activated, finalID) + seen[candidate] = struct{}{} + } + if len(activated) == 0 { + return nil, fmt.Errorf("no Google Cloud projects available for this account") + } + return activated, nil +} + +func ensureGeminiProjectsEnabled(ctx context.Context, httpClient *http.Client, projectIDs []string) error { + for _, pid := range projectIDs { + trimmed := strings.TrimSpace(pid) + if trimmed == "" { + continue + } + isChecked, errCheck := checkCloudAPIIsEnabled(ctx, httpClient, trimmed) + if errCheck != nil { + return fmt.Errorf("project %s: %w", trimmed, errCheck) + } + if !isChecked { + return fmt.Errorf("project %s: Cloud AI API not enabled", trimmed) + } + } + return nil +} + +func performGeminiCLISetup(ctx context.Context, httpClient *http.Client, storage *geminiAuth.GeminiTokenStorage, requestedProject string) error { + metadata := map[string]string{ + "ideType": "IDE_UNSPECIFIED", + "platform": "PLATFORM_UNSPECIFIED", + "pluginType": "GEMINI", + } + + trimmedRequest := strings.TrimSpace(requestedProject) + explicitProject := trimmedRequest != "" + + loadReqBody := map[string]any{ + "metadata": metadata, + } + if explicitProject { + loadReqBody["cloudaicompanionProject"] = trimmedRequest + } + + var loadResp map[string]any + if errLoad := callGeminiCLI(ctx, httpClient, "loadCodeAssist", loadReqBody, &loadResp); errLoad != nil { + return fmt.Errorf("load code assist: %w", errLoad) + } + + tierID := "legacy-tier" + if tiers, okTiers := loadResp["allowedTiers"].([]any); okTiers { + for _, rawTier := range tiers { + tier, okTier := rawTier.(map[string]any) + if !okTier { + continue + } + if isDefault, okDefault := tier["isDefault"].(bool); okDefault && isDefault { + if id, okID := tier["id"].(string); okID && strings.TrimSpace(id) != "" { + tierID = strings.TrimSpace(id) + break + } + } + } + } + + projectID := trimmedRequest + if projectID == "" { + if id, okProject := loadResp["cloudaicompanionProject"].(string); okProject { + projectID = strings.TrimSpace(id) + } + if projectID == "" { + if projectMap, okProject := loadResp["cloudaicompanionProject"].(map[string]any); okProject { + if id, okID := projectMap["id"].(string); okID { + projectID = strings.TrimSpace(id) + } + } + } + } + if projectID == "" { + // Auto-discovery: try onboardUser without specifying a project + // to let Google auto-provision one (matches Gemini CLI headless behavior + // and Antigravity's FetchProjectID pattern). + autoOnboardReq := map[string]any{ + "tierId": tierID, + "metadata": metadata, + } + + autoCtx, autoCancel := context.WithTimeout(ctx, 30*time.Second) + defer autoCancel() + for attempt := 1; ; attempt++ { + var onboardResp map[string]any + if errOnboard := callGeminiCLI(autoCtx, httpClient, "onboardUser", autoOnboardReq, &onboardResp); errOnboard != nil { + return fmt.Errorf("auto-discovery onboardUser: %w", errOnboard) + } + + if done, okDone := onboardResp["done"].(bool); okDone && done { + if resp, okResp := onboardResp["response"].(map[string]any); okResp { + switch v := resp["cloudaicompanionProject"].(type) { + case string: + projectID = strings.TrimSpace(v) + case map[string]any: + if id, okID := v["id"].(string); okID { + projectID = strings.TrimSpace(id) + } + } + } + break + } + + log.Debugf("Auto-discovery: onboarding in progress, attempt %d...", attempt) + select { + case <-autoCtx.Done(): + return &projectSelectionRequiredError{} + case <-time.After(2 * time.Second): + } + } + + if projectID == "" { + return &projectSelectionRequiredError{} + } + log.Infof("Auto-discovered project ID via onboarding: %s", projectID) + } + + onboardReqBody := map[string]any{ + "tierId": tierID, + "metadata": metadata, + "cloudaicompanionProject": projectID, + } + + storage.ProjectID = projectID + + for { + var onboardResp map[string]any + if errOnboard := callGeminiCLI(ctx, httpClient, "onboardUser", onboardReqBody, &onboardResp); errOnboard != nil { + return fmt.Errorf("onboard user: %w", errOnboard) + } + + if done, okDone := onboardResp["done"].(bool); okDone && done { + responseProjectID := "" + if resp, okResp := onboardResp["response"].(map[string]any); okResp { + switch projectValue := resp["cloudaicompanionProject"].(type) { + case map[string]any: + if id, okID := projectValue["id"].(string); okID { + responseProjectID = strings.TrimSpace(id) + } + case string: + responseProjectID = strings.TrimSpace(projectValue) + } + } + + finalProjectID := projectID + if responseProjectID != "" { + if explicitProject && !strings.EqualFold(responseProjectID, projectID) { + // Check if this is a free user (gen-lang-client projects or free/legacy tier) + isFreeUser := strings.HasPrefix(projectID, "gen-lang-client-") || + strings.EqualFold(tierID, "FREE") || + strings.EqualFold(tierID, "LEGACY") + + if isFreeUser { + // For free users, use backend project ID for preview model access + log.Infof("Gemini onboarding: frontend project %s maps to backend project %s", projectID, responseProjectID) + log.Infof("Using backend project ID: %s (recommended for preview model access)", responseProjectID) + finalProjectID = responseProjectID + } else { + // Pro users: keep requested project ID (original behavior) + log.Warnf("Gemini onboarding returned project %s instead of requested %s; keeping requested project ID.", responseProjectID, projectID) + } + } else { + finalProjectID = responseProjectID + } + } + + storage.ProjectID = strings.TrimSpace(finalProjectID) + if storage.ProjectID == "" { + storage.ProjectID = strings.TrimSpace(projectID) + } + if storage.ProjectID == "" { + return fmt.Errorf("onboard user completed without project id") + } + log.Infof("Onboarding complete. Using Project ID: %s", storage.ProjectID) + return nil + } + + log.Println("Onboarding in progress, waiting 5 seconds...") + time.Sleep(5 * time.Second) + } +} + +func callGeminiCLI(ctx context.Context, httpClient *http.Client, endpoint string, body any, result any) error { + endPointURL := fmt.Sprintf("%s/%s:%s", geminiCLIEndpoint, geminiCLIVersion, endpoint) + if strings.HasPrefix(endpoint, "operations/") { + endPointURL = fmt.Sprintf("%s/%s", geminiCLIEndpoint, endpoint) + } + + var reader io.Reader + if body != nil { + rawBody, errMarshal := json.Marshal(body) + if errMarshal != nil { + return fmt.Errorf("marshal request body: %w", errMarshal) + } + reader = bytes.NewReader(rawBody) + } + + req, errRequest := http.NewRequestWithContext(ctx, http.MethodPost, endPointURL, reader) + if errRequest != nil { + return fmt.Errorf("create request: %w", errRequest) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", geminiCLIUserAgent) + req.Header.Set("X-Goog-Api-Client", geminiCLIApiClient) + req.Header.Set("Client-Metadata", geminiCLIClientMetadata) + + resp, errDo := httpClient.Do(req) + if errDo != nil { + return fmt.Errorf("execute request: %w", errDo) + } + defer func() { + if errClose := resp.Body.Close(); errClose != nil { + log.Errorf("response body close error: %v", errClose) + } + }() + + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + bodyBytes, _ := io.ReadAll(resp.Body) + return fmt.Errorf("api request failed with status %d: %s", resp.StatusCode, strings.TrimSpace(string(bodyBytes))) + } + + if result == nil { + _, _ = io.Copy(io.Discard, resp.Body) + return nil + } + + if errDecode := json.NewDecoder(resp.Body).Decode(result); errDecode != nil { + return fmt.Errorf("decode response body: %w", errDecode) + } + + return nil +} + +func fetchGCPProjects(ctx context.Context, httpClient *http.Client) ([]interfaces.GCPProjectProjects, error) { + req, errRequest := http.NewRequestWithContext(ctx, http.MethodGet, "https://cloudresourcemanager.googleapis.com/v1/projects", nil) + if errRequest != nil { + return nil, fmt.Errorf("could not create project list request: %w", errRequest) + } + + resp, errDo := httpClient.Do(req) + if errDo != nil { + return nil, fmt.Errorf("failed to execute project list request: %w", errDo) + } + defer func() { + if errClose := resp.Body.Close(); errClose != nil { + log.Errorf("response body close error: %v", errClose) + } + }() + + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("project list request failed with status %d: %s", resp.StatusCode, strings.TrimSpace(string(bodyBytes))) + } + + var projects interfaces.GCPProject + if errDecode := json.NewDecoder(resp.Body).Decode(&projects); errDecode != nil { + return nil, fmt.Errorf("failed to unmarshal project list: %w", errDecode) + } + + return projects.Projects, nil +} + +func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projectID string) (bool, error) { + serviceUsageURL := "https://serviceusage.googleapis.com" + requiredServices := []string{ + "cloudaicompanion.googleapis.com", + } + for _, service := range requiredServices { + checkURL := fmt.Sprintf("%s/v1/projects/%s/services/%s", serviceUsageURL, projectID, service) + req, errRequest := http.NewRequestWithContext(ctx, http.MethodGet, checkURL, nil) + if errRequest != nil { + return false, fmt.Errorf("failed to create request: %w", errRequest) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", geminiCLIUserAgent) + resp, errDo := httpClient.Do(req) + if errDo != nil { + return false, fmt.Errorf("failed to execute request: %w", errDo) + } + + if resp.StatusCode == http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + if gjson.GetBytes(bodyBytes, "state").String() == "ENABLED" { + _ = resp.Body.Close() + continue + } + } + _ = resp.Body.Close() + + enableURL := fmt.Sprintf("%s/v1/projects/%s/services/%s:enable", serviceUsageURL, projectID, service) + req, errRequest = http.NewRequestWithContext(ctx, http.MethodPost, enableURL, strings.NewReader("{}")) + if errRequest != nil { + return false, fmt.Errorf("failed to create request: %w", errRequest) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", geminiCLIUserAgent) + resp, errDo = httpClient.Do(req) + if errDo != nil { + return false, fmt.Errorf("failed to execute request: %w", errDo) + } + + bodyBytes, _ := io.ReadAll(resp.Body) + errMessage := string(bodyBytes) + errMessageResult := gjson.GetBytes(bodyBytes, "error.message") + if errMessageResult.Exists() { + errMessage = errMessageResult.String() + } + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated { + _ = resp.Body.Close() + continue + } else if resp.StatusCode == http.StatusBadRequest { + _ = resp.Body.Close() + if strings.Contains(strings.ToLower(errMessage), "already enabled") { + continue + } + } + _ = resp.Body.Close() + return false, fmt.Errorf("project activation required: %s", errMessage) + } + return true, nil +} + +func (h *Handler) GetAuthStatus(c *gin.Context) { + state := strings.TrimSpace(c.Query("state")) + if state == "" { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + return + } + if err := ValidateOAuthState(state); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error": "invalid state"}) + return + } + + _, status, ok := GetOAuthSession(state) + if !ok { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + return + } + if status != "" { + if strings.HasPrefix(status, "device_code|") { + parts := strings.SplitN(status, "|", 3) + if len(parts) == 3 { + c.JSON(http.StatusOK, gin.H{ + "status": "device_code", + "verification_url": parts[1], + "user_code": parts[2], + }) + return + } + } + if strings.HasPrefix(status, "auth_url|") { + authURL := strings.TrimPrefix(status, "auth_url|") + c.JSON(http.StatusOK, gin.H{ + "status": "auth_url", + "url": authURL, + }) + return + } + c.JSON(http.StatusOK, gin.H{"status": "error", "error": status}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "wait"}) +} + +const kiroCallbackPort = 9876 + +func (h *Handler) RequestKiroToken(c *gin.Context) { + ctx := context.Background() + + // Get the login method from query parameter (default: aws for device code flow) + method := strings.ToLower(strings.TrimSpace(c.Query("method"))) + if method == "" { + method = "aws" + } + + fmt.Println("Initializing Kiro authentication...") + + state := fmt.Sprintf("kiro-%d", time.Now().UnixNano()) + + switch method { + case "aws", "builder-id": + RegisterOAuthSession(state, "kiro") + + // AWS Builder ID uses device code flow (no callback needed) + go func() { + ssoClient := kiroauth.NewSSOOIDCClient(h.cfg) + + // Step 1: Register client + fmt.Println("Registering client...") + regResp, errRegister := ssoClient.RegisterClient(ctx) + if errRegister != nil { + log.Errorf("Failed to register client: %v", errRegister) + SetOAuthSessionError(state, "Failed to register client") + return + } + + // Step 2: Start device authorization + fmt.Println("Starting device authorization...") + authResp, errAuth := ssoClient.StartDeviceAuthorization(ctx, regResp.ClientID, regResp.ClientSecret) + if errAuth != nil { + log.Errorf("Failed to start device auth: %v", errAuth) + SetOAuthSessionError(state, "Failed to start device authorization") + return + } + + // Store the verification URL for the frontend to display. + // Using "|" as separator because URLs contain ":". + SetOAuthSessionError(state, "device_code|"+authResp.VerificationURIComplete+"|"+authResp.UserCode) + + // Step 3: Poll for token + fmt.Println("Waiting for authorization...") + interval := 5 * time.Second + if authResp.Interval > 0 { + interval = time.Duration(authResp.Interval) * time.Second + } + deadline := time.Now().Add(time.Duration(authResp.ExpiresIn) * time.Second) + + for time.Now().Before(deadline) { + select { + case <-ctx.Done(): + SetOAuthSessionError(state, "Authorization cancelled") + return + case <-time.After(interval): + tokenResp, errToken := ssoClient.CreateToken(ctx, regResp.ClientID, regResp.ClientSecret, authResp.DeviceCode) + if errToken != nil { + errStr := errToken.Error() + if strings.Contains(errStr, "authorization_pending") { + continue + } + if strings.Contains(errStr, "slow_down") { + interval += 5 * time.Second + continue + } + log.Errorf("Token creation failed: %v", errToken) + SetOAuthSessionError(state, "Token creation failed") + return + } + + // Success! Save the token + expiresAt := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second) + email := kiroauth.ExtractEmailFromJWT(tokenResp.AccessToken) + + idPart := kiroauth.SanitizeEmailForFilename(email) + if idPart == "" { + idPart = fmt.Sprintf("%d", time.Now().UnixNano()%100000) + } + + now := time.Now() + fileName := fmt.Sprintf("kiro-aws-%s.json", idPart) + + record := &coreauth.Auth{ + ID: fileName, + Provider: "kiro", + FileName: fileName, + Metadata: map[string]any{ + "type": "kiro", + "access_token": tokenResp.AccessToken, + "refresh_token": tokenResp.RefreshToken, + "expires_at": expiresAt.Format(time.RFC3339), + "auth_method": "builder-id", + "provider": "AWS", + "client_id": regResp.ClientID, + "client_secret": regResp.ClientSecret, + "email": email, + "last_refresh": now.Format(time.RFC3339), + }, + } + + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + if email != "" { + fmt.Printf("Authenticated as: %s\n", email) + } + CompleteOAuthSession(state) + return + } + } + + SetOAuthSessionError(state, "Authorization timed out") + }() + + // Return immediately with the state for polling + c.JSON(http.StatusOK, gin.H{"status": "ok", "state": state, "method": "device_code"}) + + case "google", "github": + RegisterOAuthSession(state, "kiro") + + // Social auth uses protocol handler - for WEB UI we use a callback forwarder + provider := "Google" + if method == "github" { + provider = "Github" + } + + isWebUI := isWebUIRequest(c) + if isWebUI { + targetURL, errTarget := h.managementCallbackURL("/kiro/callback") + if errTarget != nil { + log.WithError(errTarget).Error("failed to compute kiro callback target") + c.JSON(http.StatusInternalServerError, gin.H{"error": "callback server unavailable"}) + return + } + if _, errStart := startCallbackForwarder(kiroCallbackPort, "kiro", targetURL); errStart != nil { + log.WithError(errStart).Error("failed to start kiro callback forwarder") + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to start callback server"}) + return + } + } + + go func() { + if isWebUI { + defer stopCallbackForwarder(kiroCallbackPort) + } + + socialClient := kiroauth.NewSocialAuthClient(h.cfg) + + // Generate PKCE codes + codeVerifier, codeChallenge, errPKCE := generateKiroPKCE() + if errPKCE != nil { + log.Errorf("Failed to generate PKCE: %v", errPKCE) + SetOAuthSessionError(state, "Failed to generate PKCE") + return + } + + // Build login URL + authURL := fmt.Sprintf("%s/login?idp=%s&redirect_uri=%s&code_challenge=%s&code_challenge_method=S256&state=%s&prompt=select_account", + "https://prod.us-east-1.auth.desktop.kiro.dev", + provider, + url.QueryEscape(kiroauth.KiroRedirectURI), + codeChallenge, + state, + ) + + // Store auth URL for frontend. + // Using "|" as separator because URLs contain ":". + SetOAuthSessionError(state, "auth_url|"+authURL) + + // Wait for callback file + waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-kiro-%s.oauth", state)) + deadline := time.Now().Add(5 * time.Minute) + + for { + if time.Now().After(deadline) { + log.Error("oauth flow timed out") + SetOAuthSessionError(state, "OAuth flow timed out") + return + } + if data, errRead := os.ReadFile(waitFile); errRead == nil { + var m map[string]string + _ = json.Unmarshal(data, &m) + _ = os.Remove(waitFile) + if errStr := m["error"]; errStr != "" { + log.Errorf("Authentication failed: %s", errStr) + SetOAuthSessionError(state, "Authentication failed") + return + } + if m["state"] != state { + log.Errorf("State mismatch") + SetOAuthSessionError(state, "State mismatch") + return + } + code := m["code"] + if code == "" { + log.Error("No authorization code received") + SetOAuthSessionError(state, "No authorization code received") + return + } + + // Exchange code for tokens + tokenReq := &kiroauth.CreateTokenRequest{ + Code: code, + CodeVerifier: codeVerifier, + RedirectURI: kiroauth.KiroRedirectURI, + } + + tokenResp, errToken := socialClient.CreateToken(ctx, tokenReq) + if errToken != nil { + log.Errorf("Failed to exchange code for tokens: %v", errToken) + SetOAuthSessionError(state, "Failed to exchange code for tokens") + return + } + + // Save the token + expiresIn := tokenResp.ExpiresIn + if expiresIn <= 0 { + expiresIn = 3600 + } + expiresAt := time.Now().Add(time.Duration(expiresIn) * time.Second) + email := kiroauth.ExtractEmailFromJWT(tokenResp.AccessToken) + + idPart := kiroauth.SanitizeEmailForFilename(email) + if idPart == "" { + idPart = fmt.Sprintf("%d", time.Now().UnixNano()%100000) + } + + now := time.Now() + fileName := fmt.Sprintf("kiro-%s-%s.json", strings.ToLower(provider), idPart) + + record := &coreauth.Auth{ + ID: fileName, + Provider: "kiro", + FileName: fileName, + Metadata: map[string]any{ + "type": "kiro", + "access_token": tokenResp.AccessToken, + "refresh_token": tokenResp.RefreshToken, + "profile_arn": tokenResp.ProfileArn, + "expires_at": expiresAt.Format(time.RFC3339), + "auth_method": "social", + "provider": provider, + "email": email, + "last_refresh": now.Format(time.RFC3339), + }, + } + + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + if email != "" { + fmt.Printf("Authenticated as: %s\n", email) + } + CompleteOAuthSession(state) + return + } + time.Sleep(500 * time.Millisecond) + } + }() + + c.JSON(http.StatusOK, gin.H{"status": "ok", "state": state, "method": "social"}) + + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid method, use 'aws', 'google', or 'github'"}) + } +} + +// generateKiroPKCE generates PKCE code verifier and challenge for Kiro OAuth. +func generateKiroPKCE() (verifier, challenge string, err error) { + b := make([]byte, 32) + if _, errRead := io.ReadFull(rand.Reader, b); errRead != nil { + return "", "", fmt.Errorf("failed to generate random bytes: %w", errRead) + } + verifier = base64.RawURLEncoding.EncodeToString(b) + + h := sha256.Sum256([]byte(verifier)) + challenge = base64.RawURLEncoding.EncodeToString(h[:]) + + return verifier, challenge, nil +} + +func (h *Handler) RequestKiloToken(c *gin.Context) { + ctx := context.Background() + + fmt.Println("Initializing Kilo authentication...") + + state := fmt.Sprintf("kil-%d", time.Now().UnixNano()) + kilocodeAuth := kilo.NewKiloAuth() + + resp, err := kilocodeAuth.InitiateDeviceFlow(ctx) + if err != nil { + log.Errorf("Failed to initiate device flow: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to initiate device flow"}) + return + } + + RegisterOAuthSession(state, "kilo") + + go func() { + fmt.Printf("Please visit %s and enter code: %s\n", resp.VerificationURL, resp.Code) + + status, err := kilocodeAuth.PollForToken(ctx, resp.Code) + if err != nil { + SetOAuthSessionError(state, "Authentication failed") + fmt.Printf("Authentication failed: %v\n", err) + return + } + + profile, err := kilocodeAuth.GetProfile(ctx, status.Token) + if err != nil { + log.Warnf("Failed to fetch profile: %v", err) + profile = &kilo.Profile{Email: status.UserEmail} + } + + var orgID string + if len(profile.Orgs) > 0 { + orgID = profile.Orgs[0].ID + } + + defaults, err := kilocodeAuth.GetDefaults(ctx, status.Token, orgID) + if err != nil { + defaults = &kilo.Defaults{} + } + + ts := &kilo.KiloTokenStorage{ + Token: status.Token, + OrganizationID: orgID, + Model: defaults.Model, + Email: status.UserEmail, + Type: "kilo", + } + + fileName := kilo.CredentialFileName(status.UserEmail) + record := &coreauth.Auth{ + ID: fileName, + Provider: "kilo", + FileName: fileName, + Storage: ts, + Metadata: map[string]any{ + "email": status.UserEmail, + "organization_id": orgID, + "model": defaults.Model, + }, + } + + savedPath, errSave := h.saveTokenRecord(ctx, record) + if errSave != nil { + log.Errorf("Failed to save authentication tokens: %v", errSave) + SetOAuthSessionError(state, "Failed to save authentication tokens") + return + } + + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + CompleteOAuthSession(state) + CompleteOAuthSessionsByProvider("kilo") + }() + + c.JSON(200, gin.H{ + "status": "ok", + "url": resp.VerificationURL, + "state": state, + "user_code": resp.Code, + "verification_uri": resp.VerificationURL, + }) +} diff --git a/pkg/llmproxy/api/handlers/management/config_basic.go b/pkg/llmproxy/api/handlers/management/config_basic.go index 8c0361352c..3982c156ee 100644 --- a/pkg/llmproxy/api/handlers/management/config_basic.go +++ b/pkg/llmproxy/api/handlers/management/config_basic.go @@ -10,8 +10,9 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + sdkconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/config" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) diff --git a/pkg/llmproxy/api/handlers/management/config_lists.go b/pkg/llmproxy/api/handlers/management/config_lists.go index 787755cc11..31a72f3b9e 100644 --- a/pkg/llmproxy/api/handlers/management/config_lists.go +++ b/pkg/llmproxy/api/handlers/management/config_lists.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // Generic helpers for list[string] diff --git a/pkg/llmproxy/api/handlers/management/handler.go b/pkg/llmproxy/api/handlers/management/handler.go index e4689199bd..868103719a 100644 --- a/pkg/llmproxy/api/handlers/management/handler.go +++ b/pkg/llmproxy/api/handlers/management/handler.go @@ -16,7 +16,7 @@ import ( "github.com/gin-gonic/gin" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/buildinfo" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/usage" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/api/handlers/management/management_auth_test.go b/pkg/llmproxy/api/handlers/management/management_auth_test.go index 8b4bf2ca1b..f9015791cb 100644 --- a/pkg/llmproxy/api/handlers/management/management_auth_test.go +++ b/pkg/llmproxy/api/handlers/management/management_auth_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestListAuthFiles(t *testing.T) { diff --git a/pkg/llmproxy/api/handlers/management/management_basic_test.go b/pkg/llmproxy/api/handlers/management/management_basic_test.go index 78b15f501d..f748d038a8 100644 --- a/pkg/llmproxy/api/handlers/management/management_basic_test.go +++ b/pkg/llmproxy/api/handlers/management/management_basic_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestGetConfig(t *testing.T) { diff --git a/pkg/llmproxy/api/handlers/management/management_extra_test.go b/pkg/llmproxy/api/handlers/management/management_extra_test.go index 677c1c0f2c..8a24fd9af0 100644 --- a/pkg/llmproxy/api/handlers/management/management_extra_test.go +++ b/pkg/llmproxy/api/handlers/management/management_extra_test.go @@ -14,7 +14,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/usage" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/api/handlers/management/management_fields_test.go b/pkg/llmproxy/api/handlers/management/management_fields_test.go index 0cfe0e5b3a..7254281200 100644 --- a/pkg/llmproxy/api/handlers/management/management_fields_test.go +++ b/pkg/llmproxy/api/handlers/management/management_fields_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func setupTestHandler(cfg *config.Config) (*Handler, string, func()) { diff --git a/pkg/llmproxy/api/handlers/management/management_modelstates_test.go b/pkg/llmproxy/api/handlers/management/management_modelstates_test.go index 87673262ec..9e13a28778 100644 --- a/pkg/llmproxy/api/handlers/management/management_modelstates_test.go +++ b/pkg/llmproxy/api/handlers/management/management_modelstates_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/api/modules/amp/amp.go b/pkg/llmproxy/api/modules/amp/amp.go index 4b5d891f10..48413dbb7d 100644 --- a/pkg/llmproxy/api/modules/amp/amp.go +++ b/pkg/llmproxy/api/modules/amp/amp.go @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/api/modules" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/access" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/api/modules/amp/amp_test.go b/pkg/llmproxy/api/modules/amp/amp_test.go index 3de1de1a48..805a344ca2 100644 --- a/pkg/llmproxy/api/modules/amp/amp_test.go +++ b/pkg/llmproxy/api/modules/amp/amp_test.go @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/api/modules" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/access" "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers" ) diff --git a/pkg/llmproxy/api/modules/amp/fallback_handlers_test.go b/pkg/llmproxy/api/modules/amp/fallback_handlers_test.go index 3433f082ed..7289e46dca 100644 --- a/pkg/llmproxy/api/modules/amp/fallback_handlers_test.go +++ b/pkg/llmproxy/api/modules/amp/fallback_handlers_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" ) diff --git a/pkg/llmproxy/api/modules/amp/model_mapping.go b/pkg/llmproxy/api/modules/amp/model_mapping.go index eb5b6f7ec6..40a03a962e 100644 --- a/pkg/llmproxy/api/modules/amp/model_mapping.go +++ b/pkg/llmproxy/api/modules/amp/model_mapping.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/api/modules/amp/model_mapping_test.go b/pkg/llmproxy/api/modules/amp/model_mapping_test.go index 1d8c9d312f..021246ca4d 100644 --- a/pkg/llmproxy/api/modules/amp/model_mapping_test.go +++ b/pkg/llmproxy/api/modules/amp/model_mapping_test.go @@ -3,7 +3,7 @@ package amp import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" ) diff --git a/pkg/llmproxy/api/modules/amp/proxy_test.go b/pkg/llmproxy/api/modules/amp/proxy_test.go index 0b0cbbc5db..676a9849bb 100644 --- a/pkg/llmproxy/api/modules/amp/proxy_test.go +++ b/pkg/llmproxy/api/modules/amp/proxy_test.go @@ -11,7 +11,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // Helper: compress data with gzip diff --git a/pkg/llmproxy/api/modules/amp/secret.go b/pkg/llmproxy/api/modules/amp/secret.go index 1f0ec01274..2dac646c19 100644 --- a/pkg/llmproxy/api/modules/amp/secret.go +++ b/pkg/llmproxy/api/modules/amp/secret.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/api/modules/amp/secret_test.go b/pkg/llmproxy/api/modules/amp/secret_test.go index dd014084cf..80c843b2ac 100644 --- a/pkg/llmproxy/api/modules/amp/secret_test.go +++ b/pkg/llmproxy/api/modules/amp/secret_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" ) diff --git a/pkg/llmproxy/api/modules/modules.go b/pkg/llmproxy/api/modules/modules.go index 7289512042..f5163b7a30 100644 --- a/pkg/llmproxy/api/modules/modules.go +++ b/pkg/llmproxy/api/modules/modules.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers" ) diff --git a/pkg/llmproxy/api/server.go b/pkg/llmproxy/api/server.go index 499cb4dfc9..f622cae5f8 100644 --- a/pkg/llmproxy/api/server.go +++ b/pkg/llmproxy/api/server.go @@ -20,25 +20,24 @@ import ( "unsafe" "github.com/gin-gonic/gin" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/access" - managementHandlers "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/api/handlers/management" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/api/middleware" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/api/modules" - ampmodule "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/api/modules/amp" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/kiro" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/logging" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/managementasset" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/usage" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/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" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/access" + managementHandlers "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api/handlers/management" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api/middleware" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api/modules" + ampmodule "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api/modules/amp" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/logging" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/managementasset" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/usage" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/util" + sdkaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/access" + "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers" + "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers/claude" + "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers/gemini" + "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers/openai" + sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) diff --git a/pkg/llmproxy/api/server_test.go b/pkg/llmproxy/api/server_test.go index 44b968c317..e8f7894757 100644 --- a/pkg/llmproxy/api/server_test.go +++ b/pkg/llmproxy/api/server_test.go @@ -9,7 +9,7 @@ import ( "testing" gin "github.com/gin-gonic/gin" - proxyconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + proxyconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkaccess "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/access" "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" sdkconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/config" diff --git a/pkg/llmproxy/auth/antigravity/auth.go b/pkg/llmproxy/auth/antigravity/auth.go index 8e18dec440..da2e9bae2f 100644 --- a/pkg/llmproxy/auth/antigravity/auth.go +++ b/pkg/llmproxy/auth/antigravity/auth.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/claude/anthropic_auth.go b/pkg/llmproxy/auth/claude/anthropic_auth.go index ec06454aa1..953627a168 100644 --- a/pkg/llmproxy/auth/claude/anthropic_auth.go +++ b/pkg/llmproxy/auth/claude/anthropic_auth.go @@ -14,7 +14,6 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/claude/token.go b/pkg/llmproxy/auth/claude/token.go index 22bf50cbda..5c1adf5d4b 100644 --- a/pkg/llmproxy/auth/claude/token.go +++ b/pkg/llmproxy/auth/claude/token.go @@ -6,7 +6,7 @@ package claude import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" ) // ClaudeTokenStorage stores OAuth2 token information for Anthropic Claude API authentication. diff --git a/pkg/llmproxy/auth/claude/utls_transport.go b/pkg/llmproxy/auth/claude/utls_transport.go index 34794e11d5..dc71a48708 100644 --- a/pkg/llmproxy/auth/claude/utls_transport.go +++ b/pkg/llmproxy/auth/claude/utls_transport.go @@ -10,6 +10,7 @@ import ( pkgconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" tls "github.com/refraction-networking/utls" + pkgconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" "golang.org/x/net/http2" "golang.org/x/net/proxy" diff --git a/pkg/llmproxy/auth/codex/openai_auth.go b/pkg/llmproxy/auth/codex/openai_auth.go index 3adc4e469e..46ca4252e6 100644 --- a/pkg/llmproxy/auth/codex/openai_auth.go +++ b/pkg/llmproxy/auth/codex/openai_auth.go @@ -15,7 +15,6 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/codex/openai_auth_test.go b/pkg/llmproxy/auth/codex/openai_auth_test.go index 0dc71b4a2a..3a532037a5 100644 --- a/pkg/llmproxy/auth/codex/openai_auth_test.go +++ b/pkg/llmproxy/auth/codex/openai_auth_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestNewCodexAuth(t *testing.T) { diff --git a/pkg/llmproxy/auth/codex/token.go b/pkg/llmproxy/auth/codex/token.go index 297ffdd003..9b2d492419 100644 --- a/pkg/llmproxy/auth/codex/token.go +++ b/pkg/llmproxy/auth/codex/token.go @@ -6,7 +6,7 @@ package codex import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" ) // CodexTokenStorage stores OAuth2 token information for OpenAI Codex API authentication. diff --git a/pkg/llmproxy/auth/copilot/copilot_auth.go b/pkg/llmproxy/auth/copilot/copilot_auth.go index bff26bece4..a942cc422b 100644 --- a/pkg/llmproxy/auth/copilot/copilot_auth.go +++ b/pkg/llmproxy/auth/copilot/copilot_auth.go @@ -11,7 +11,6 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/copilot/copilot_auth_test.go b/pkg/llmproxy/auth/copilot/copilot_auth_test.go index 8b06a19531..8e16faf00a 100644 --- a/pkg/llmproxy/auth/copilot/copilot_auth_test.go +++ b/pkg/llmproxy/auth/copilot/copilot_auth_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type rewriteTransport struct { diff --git a/pkg/llmproxy/auth/copilot/copilot_extra_test.go b/pkg/llmproxy/auth/copilot/copilot_extra_test.go index 425a5eacc0..4a42733cf9 100644 --- a/pkg/llmproxy/auth/copilot/copilot_extra_test.go +++ b/pkg/llmproxy/auth/copilot/copilot_extra_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestNewCopilotAuth(t *testing.T) { diff --git a/pkg/llmproxy/auth/copilot/oauth.go b/pkg/llmproxy/auth/copilot/oauth.go index 9994cf7257..e28728bd47 100644 --- a/pkg/llmproxy/auth/copilot/oauth.go +++ b/pkg/llmproxy/auth/copilot/oauth.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/copilot/token.go b/pkg/llmproxy/auth/copilot/token.go index cecb2e4441..d529538139 100644 --- a/pkg/llmproxy/auth/copilot/token.go +++ b/pkg/llmproxy/auth/copilot/token.go @@ -6,7 +6,7 @@ package copilot import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" ) // CopilotTokenStorage stores OAuth2 token information for GitHub Copilot API authentication. diff --git a/pkg/llmproxy/auth/diff/config_diff.go b/pkg/llmproxy/auth/diff/config_diff.go index 2a8d73eca6..7315089122 100644 --- a/pkg/llmproxy/auth/diff/config_diff.go +++ b/pkg/llmproxy/auth/diff/config_diff.go @@ -6,7 +6,7 @@ import ( "reflect" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // BuildConfigChangeDetails computes a redacted, human-readable list of config changes. diff --git a/pkg/llmproxy/auth/diff/config_diff_test.go b/pkg/llmproxy/auth/diff/config_diff_test.go index 41cbe13610..cce7e5dd25 100644 --- a/pkg/llmproxy/auth/diff/config_diff_test.go +++ b/pkg/llmproxy/auth/diff/config_diff_test.go @@ -1,7 +1,7 @@ package diff import ( - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "testing" ) diff --git a/pkg/llmproxy/auth/diff/diff_generated.go b/pkg/llmproxy/auth/diff/diff_generated.go index f564fd2e30..fd9767061c 100644 --- a/pkg/llmproxy/auth/diff/diff_generated.go +++ b/pkg/llmproxy/auth/diff/diff_generated.go @@ -3,7 +3,7 @@ package diff import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // BuildConfigChangeDetailsGeneratedProviders computes changes for generated dedicated providers. diff --git a/pkg/llmproxy/auth/diff/model_hash.go b/pkg/llmproxy/auth/diff/model_hash.go index 63ebf69aa4..0f4d5517fb 100644 --- a/pkg/llmproxy/auth/diff/model_hash.go +++ b/pkg/llmproxy/auth/diff/model_hash.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) const modelHashSalt = "auth-model-hash:v1" diff --git a/pkg/llmproxy/auth/diff/model_hash_test.go b/pkg/llmproxy/auth/diff/model_hash_test.go index 4e4ada991e..07c62116fa 100644 --- a/pkg/llmproxy/auth/diff/model_hash_test.go +++ b/pkg/llmproxy/auth/diff/model_hash_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestComputeOpenAICompatModelsHash_Deterministic(t *testing.T) { diff --git a/pkg/llmproxy/auth/diff/models_summary.go b/pkg/llmproxy/auth/diff/models_summary.go index cc107c368b..084f7c1fea 100644 --- a/pkg/llmproxy/auth/diff/models_summary.go +++ b/pkg/llmproxy/auth/diff/models_summary.go @@ -4,7 +4,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type GeminiModelsSummary struct { diff --git a/pkg/llmproxy/auth/diff/oauth_excluded.go b/pkg/llmproxy/auth/diff/oauth_excluded.go index 75f2f61594..1ef85e3d4d 100644 --- a/pkg/llmproxy/auth/diff/oauth_excluded.go +++ b/pkg/llmproxy/auth/diff/oauth_excluded.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type ExcludedModelsSummary struct { diff --git a/pkg/llmproxy/auth/diff/oauth_excluded_test.go b/pkg/llmproxy/auth/diff/oauth_excluded_test.go index f89366df79..3b56388e1a 100644 --- a/pkg/llmproxy/auth/diff/oauth_excluded_test.go +++ b/pkg/llmproxy/auth/diff/oauth_excluded_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestSummarizeExcludedModels_NormalizesAndDedupes(t *testing.T) { diff --git a/pkg/llmproxy/auth/diff/oauth_model_alias.go b/pkg/llmproxy/auth/diff/oauth_model_alias.go index f1f61eb294..a0e0833335 100644 --- a/pkg/llmproxy/auth/diff/oauth_model_alias.go +++ b/pkg/llmproxy/auth/diff/oauth_model_alias.go @@ -5,7 +5,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type OAuthModelAliasSummary struct { diff --git a/pkg/llmproxy/auth/diff/openai_compat.go b/pkg/llmproxy/auth/diff/openai_compat.go index e149f5d822..a3c33d9ca0 100644 --- a/pkg/llmproxy/auth/diff/openai_compat.go +++ b/pkg/llmproxy/auth/diff/openai_compat.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // DiffOpenAICompatibility produces human-readable change descriptions. diff --git a/pkg/llmproxy/auth/diff/openai_compat_test.go b/pkg/llmproxy/auth/diff/openai_compat_test.go index 934ca76380..73f7547a08 100644 --- a/pkg/llmproxy/auth/diff/openai_compat_test.go +++ b/pkg/llmproxy/auth/diff/openai_compat_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestDiffOpenAICompatibility(t *testing.T) { diff --git a/pkg/llmproxy/auth/gemini/gemini_auth.go b/pkg/llmproxy/auth/gemini/gemini_auth.go index 08badb1283..f2ab015ed5 100644 --- a/pkg/llmproxy/auth/gemini/gemini_auth.go +++ b/pkg/llmproxy/auth/gemini/gemini_auth.go @@ -14,10 +14,9 @@ import ( "net/url" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/codex" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/auth/gemini/gemini_auth_test.go b/pkg/llmproxy/auth/gemini/gemini_auth_test.go index c091a5912e..be5eec1a8f 100644 --- a/pkg/llmproxy/auth/gemini/gemini_auth_test.go +++ b/pkg/llmproxy/auth/gemini/gemini_auth_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "golang.org/x/oauth2" ) diff --git a/pkg/llmproxy/auth/gemini/gemini_token.go b/pkg/llmproxy/auth/gemini/gemini_token.go index 32b05729a1..4fd0ebf146 100644 --- a/pkg/llmproxy/auth/gemini/gemini_token.go +++ b/pkg/llmproxy/auth/gemini/gemini_token.go @@ -7,7 +7,8 @@ import ( "fmt" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + log "github.com/sirupsen/logrus" ) // GeminiTokenStorage stores OAuth2 token information for Google Gemini API authentication. diff --git a/pkg/llmproxy/auth/iflow/iflow_auth.go b/pkg/llmproxy/auth/iflow/iflow_auth.go index a4ead0e04c..3a3370d383 100644 --- a/pkg/llmproxy/auth/iflow/iflow_auth.go +++ b/pkg/llmproxy/auth/iflow/iflow_auth.go @@ -14,7 +14,6 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/iflow/iflow_token.go b/pkg/llmproxy/auth/iflow/iflow_token.go index fb925a2f1a..cd31452af5 100644 --- a/pkg/llmproxy/auth/iflow/iflow_token.go +++ b/pkg/llmproxy/auth/iflow/iflow_token.go @@ -3,7 +3,7 @@ package iflow import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" ) // IFlowTokenStorage persists iFlow OAuth credentials alongside the derived API key. diff --git a/pkg/llmproxy/auth/kilo/kilo_token.go b/pkg/llmproxy/auth/kilo/kilo_token.go index 71c17e1dc4..e8e3026ed0 100644 --- a/pkg/llmproxy/auth/kilo/kilo_token.go +++ b/pkg/llmproxy/auth/kilo/kilo_token.go @@ -5,7 +5,8 @@ package kilo import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + log "github.com/sirupsen/logrus" ) // KiloTokenStorage stores token information for Kilo AI authentication. diff --git a/pkg/llmproxy/auth/kimi/kimi.go b/pkg/llmproxy/auth/kimi/kimi.go index 2a5ebb6716..396e14a7c0 100644 --- a/pkg/llmproxy/auth/kimi/kimi.go +++ b/pkg/llmproxy/auth/kimi/kimi.go @@ -16,7 +16,6 @@ import ( "github.com/google/uuid" "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kimi/token.go b/pkg/llmproxy/auth/kimi/token.go index 61e410c40e..7ae172e630 100644 --- a/pkg/llmproxy/auth/kimi/token.go +++ b/pkg/llmproxy/auth/kimi/token.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/base" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" ) // KimiTokenStorage stores OAuth2 token information for Kimi API authentication. diff --git a/pkg/llmproxy/auth/kiro/aws_auth.go b/pkg/llmproxy/auth/kiro/aws_auth.go index e798a85b79..d778af5bb2 100644 --- a/pkg/llmproxy/auth/kiro/aws_auth.go +++ b/pkg/llmproxy/auth/kiro/aws_auth.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/aws_extra_test.go b/pkg/llmproxy/auth/kiro/aws_extra_test.go index 434b91edf4..417e1a345c 100644 --- a/pkg/llmproxy/auth/kiro/aws_extra_test.go +++ b/pkg/llmproxy/auth/kiro/aws_extra_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestNewKiroAuth(t *testing.T) { diff --git a/pkg/llmproxy/auth/kiro/background_refresh.go b/pkg/llmproxy/auth/kiro/background_refresh.go index 2f52798786..7638336b89 100644 --- a/pkg/llmproxy/auth/kiro/background_refresh.go +++ b/pkg/llmproxy/auth/kiro/background_refresh.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "golang.org/x/sync/semaphore" ) diff --git a/pkg/llmproxy/auth/kiro/codewhisperer_client.go b/pkg/llmproxy/auth/kiro/codewhisperer_client.go index 8ba06d266a..827d38c135 100644 --- a/pkg/llmproxy/auth/kiro/codewhisperer_client.go +++ b/pkg/llmproxy/auth/kiro/codewhisperer_client.go @@ -10,7 +10,7 @@ import ( "time" "github.com/google/uuid" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/oauth.go b/pkg/llmproxy/auth/kiro/oauth.go index 7b0921169a..9789d60489 100644 --- a/pkg/llmproxy/auth/kiro/oauth.go +++ b/pkg/llmproxy/auth/kiro/oauth.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/oauth_web.go b/pkg/llmproxy/auth/kiro/oauth_web.go index f02621fc6c..1dac8fd214 100644 --- a/pkg/llmproxy/auth/kiro/oauth_web.go +++ b/pkg/llmproxy/auth/kiro/oauth_web.go @@ -16,7 +16,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/refresh_manager.go b/pkg/llmproxy/auth/kiro/refresh_manager.go index ef58852e08..dc77cf99bc 100644 --- a/pkg/llmproxy/auth/kiro/refresh_manager.go +++ b/pkg/llmproxy/auth/kiro/refresh_manager.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/social_auth.go b/pkg/llmproxy/auth/kiro/social_auth.go index aab71c6867..a7b08f9519 100644 --- a/pkg/llmproxy/auth/kiro/social_auth.go +++ b/pkg/llmproxy/auth/kiro/social_auth.go @@ -19,7 +19,7 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" "golang.org/x/term" diff --git a/pkg/llmproxy/auth/kiro/sso_oidc.go b/pkg/llmproxy/auth/kiro/sso_oidc.go index 3eed67fc49..bc52c0f436 100644 --- a/pkg/llmproxy/auth/kiro/sso_oidc.go +++ b/pkg/llmproxy/auth/kiro/sso_oidc.go @@ -21,7 +21,7 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/usage_checker.go b/pkg/llmproxy/auth/kiro/usage_checker.go index ebdd0a1587..d2e90eed4b 100644 --- a/pkg/llmproxy/auth/kiro/usage_checker.go +++ b/pkg/llmproxy/auth/kiro/usage_checker.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" ) diff --git a/pkg/llmproxy/auth/qwen/qwen_auth.go b/pkg/llmproxy/auth/qwen/qwen_auth.go index 398f0bacc9..e2025f39c7 100644 --- a/pkg/llmproxy/auth/qwen/qwen_auth.go +++ b/pkg/llmproxy/auth/qwen/qwen_auth.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/qwen/qwen_token.go b/pkg/llmproxy/auth/qwen/qwen_token.go index 1163895146..f42e42b4a0 100644 --- a/pkg/llmproxy/auth/qwen/qwen_token.go +++ b/pkg/llmproxy/auth/qwen/qwen_token.go @@ -9,7 +9,6 @@ import ( "path/filepath" "strings" - "github.com/KooshaPari/phenotype-go-kit/pkg/auth" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" ) diff --git a/pkg/llmproxy/auth/synthesizer/config.go b/pkg/llmproxy/auth/synthesizer/config.go index 84ccae4707..667b60c817 100644 --- a/pkg/llmproxy/auth/synthesizer/config.go +++ b/pkg/llmproxy/auth/synthesizer/config.go @@ -13,7 +13,7 @@ import ( "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/diff" kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cursorstorage" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/auth/synthesizer/config_test.go b/pkg/llmproxy/auth/synthesizer/config_test.go index 118b71353e..90f0a669a8 100644 --- a/pkg/llmproxy/auth/synthesizer/config_test.go +++ b/pkg/llmproxy/auth/synthesizer/config_test.go @@ -1,7 +1,7 @@ package synthesizer import ( - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "os" "path/filepath" "testing" diff --git a/pkg/llmproxy/auth/synthesizer/context.go b/pkg/llmproxy/auth/synthesizer/context.go index 79d1cf1d08..99e8df7ad0 100644 --- a/pkg/llmproxy/auth/synthesizer/context.go +++ b/pkg/llmproxy/auth/synthesizer/context.go @@ -3,7 +3,7 @@ package synthesizer import ( "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // SynthesisContext provides the context needed for auth synthesis. diff --git a/pkg/llmproxy/auth/synthesizer/file_test.go b/pkg/llmproxy/auth/synthesizer/file_test.go index e549822a9a..c169ab4817 100644 --- a/pkg/llmproxy/auth/synthesizer/file_test.go +++ b/pkg/llmproxy/auth/synthesizer/file_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/auth/synthesizer/helpers.go b/pkg/llmproxy/auth/synthesizer/helpers.go index 1fdceeeae1..553ff44d3e 100644 --- a/pkg/llmproxy/auth/synthesizer/helpers.go +++ b/pkg/llmproxy/auth/synthesizer/helpers.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/diff" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/auth/synthesizer/helpers_test.go b/pkg/llmproxy/auth/synthesizer/helpers_test.go index b96620d0e9..5a56e83314 100644 --- a/pkg/llmproxy/auth/synthesizer/helpers_test.go +++ b/pkg/llmproxy/auth/synthesizer/helpers_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/diff" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/auth/synthesizer/synthesizer_generated.go b/pkg/llmproxy/auth/synthesizer/synthesizer_generated.go index 03c29d8fd8..eddb8a3a17 100644 --- a/pkg/llmproxy/auth/synthesizer/synthesizer_generated.go +++ b/pkg/llmproxy/auth/synthesizer/synthesizer_generated.go @@ -2,7 +2,7 @@ package synthesizer import ( - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // getDedicatedProviderEntries returns the config entries for a dedicated provider. diff --git a/pkg/llmproxy/cmd/anthropic_login.go b/pkg/llmproxy/cmd/anthropic_login.go index d70f135afd..de64e1433d 100644 --- a/pkg/llmproxy/cmd/anthropic_login.go +++ b/pkg/llmproxy/cmd/anthropic_login.go @@ -7,7 +7,7 @@ import ( "os" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/claude" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/antigravity_login.go b/pkg/llmproxy/cmd/antigravity_login.go index 8718224ac9..7693f42fa7 100644 --- a/pkg/llmproxy/cmd/antigravity_login.go +++ b/pkg/llmproxy/cmd/antigravity_login.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/config_cast.go b/pkg/llmproxy/cmd/config_cast.go index d738501f73..a101ee8f29 100644 --- a/pkg/llmproxy/cmd/config_cast.go +++ b/pkg/llmproxy/cmd/config_cast.go @@ -3,9 +3,9 @@ package cmd import ( "unsafe" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + internalconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + sdkconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // castToInternalConfig converts a pkg/llmproxy/config.Config pointer to an internal/config.Config pointer. diff --git a/pkg/llmproxy/cmd/cursor_login.go b/pkg/llmproxy/cmd/cursor_login.go index 6921a94771..342054bad9 100644 --- a/pkg/llmproxy/cmd/cursor_login.go +++ b/pkg/llmproxy/cmd/cursor_login.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/cursor_login_test.go b/pkg/llmproxy/cmd/cursor_login_test.go index a3b0950441..194b0964c0 100644 --- a/pkg/llmproxy/cmd/cursor_login_test.go +++ b/pkg/llmproxy/cmd/cursor_login_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestDoCursorLogin_TokenFileMode_WritesTokenAndConfig(t *testing.T) { diff --git a/pkg/llmproxy/cmd/generic_apikey_login.go b/pkg/llmproxy/cmd/generic_apikey_login.go index 9c2a0b2593..0b53a4722a 100644 --- a/pkg/llmproxy/cmd/generic_apikey_login.go +++ b/pkg/llmproxy/cmd/generic_apikey_login.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/github_copilot_login.go b/pkg/llmproxy/cmd/github_copilot_login.go index de049f940c..d29c2dbe16 100644 --- a/pkg/llmproxy/cmd/github_copilot_login.go +++ b/pkg/llmproxy/cmd/github_copilot_login.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/iflow_cookie.go b/pkg/llmproxy/cmd/iflow_cookie.go index 4e6ddfe694..c8e8544248 100644 --- a/pkg/llmproxy/cmd/iflow_cookie.go +++ b/pkg/llmproxy/cmd/iflow_cookie.go @@ -10,7 +10,7 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/iflow" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // DoIFlowCookieAuth performs the iFlow cookie-based authentication. diff --git a/pkg/llmproxy/cmd/iflow_cookie_test.go b/pkg/llmproxy/cmd/iflow_cookie_test.go index 4b84e06f54..8f971cb1f2 100644 --- a/pkg/llmproxy/cmd/iflow_cookie_test.go +++ b/pkg/llmproxy/cmd/iflow_cookie_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestGetAuthFilePath_UsesDefaultAuthDirAndFallbackName(t *testing.T) { diff --git a/pkg/llmproxy/cmd/iflow_login.go b/pkg/llmproxy/cmd/iflow_login.go index ea1aec6aa2..c6aa14b973 100644 --- a/pkg/llmproxy/cmd/iflow_login.go +++ b/pkg/llmproxy/cmd/iflow_login.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/kilo_login.go b/pkg/llmproxy/cmd/kilo_login.go index f93cba66cb..a1f34f3407 100644 --- a/pkg/llmproxy/cmd/kilo_login.go +++ b/pkg/llmproxy/cmd/kilo_login.go @@ -5,7 +5,7 @@ import ( "io" "os" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/kimi_login.go b/pkg/llmproxy/cmd/kimi_login.go index 0e65cf6a05..cb0bd32290 100644 --- a/pkg/llmproxy/cmd/kimi_login.go +++ b/pkg/llmproxy/cmd/kimi_login.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/kiro_login.go b/pkg/llmproxy/cmd/kiro_login.go index 2467ab563f..715cce6737 100644 --- a/pkg/llmproxy/cmd/kiro_login.go +++ b/pkg/llmproxy/cmd/kiro_login.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/login.go b/pkg/llmproxy/cmd/login.go index 9c9a3dbf34..b1af47e6b8 100644 --- a/pkg/llmproxy/cmd/login.go +++ b/pkg/llmproxy/cmd/login.go @@ -18,7 +18,7 @@ import ( "time" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/gemini" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/cmd/minimax_login.go b/pkg/llmproxy/cmd/minimax_login.go index 5b316bd2f7..48a2677033 100644 --- a/pkg/llmproxy/cmd/minimax_login.go +++ b/pkg/llmproxy/cmd/minimax_login.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/openai_device_login.go b/pkg/llmproxy/cmd/openai_device_login.go index 6522e7952f..4b9e4787c5 100644 --- a/pkg/llmproxy/cmd/openai_device_login.go +++ b/pkg/llmproxy/cmd/openai_device_login.go @@ -6,8 +6,8 @@ import ( "fmt" "os" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/codex" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/auth/codex" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/openai_login.go b/pkg/llmproxy/cmd/openai_login.go index 4ad06a71d7..2a6a5445d6 100644 --- a/pkg/llmproxy/cmd/openai_login.go +++ b/pkg/llmproxy/cmd/openai_login.go @@ -7,7 +7,7 @@ import ( "os" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/codex" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/qwen_login.go b/pkg/llmproxy/cmd/qwen_login.go index 8980c6cc08..ebcd3b60e9 100644 --- a/pkg/llmproxy/cmd/qwen_login.go +++ b/pkg/llmproxy/cmd/qwen_login.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/roo_kilo_login_test.go b/pkg/llmproxy/cmd/roo_kilo_login_test.go index 5a8b794b24..c378183edd 100644 --- a/pkg/llmproxy/cmd/roo_kilo_login_test.go +++ b/pkg/llmproxy/cmd/roo_kilo_login_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestRunRooLoginWithRunner_Success(t *testing.T) { diff --git a/pkg/llmproxy/cmd/roo_login.go b/pkg/llmproxy/cmd/roo_login.go index ce0c3127ad..dae422b85c 100644 --- a/pkg/llmproxy/cmd/roo_login.go +++ b/pkg/llmproxy/cmd/roo_login.go @@ -5,7 +5,7 @@ import ( "io" "os" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/run.go b/pkg/llmproxy/cmd/run.go index 523051077d..60438b49c3 100644 --- a/pkg/llmproxy/cmd/run.go +++ b/pkg/llmproxy/cmd/run.go @@ -10,8 +10,8 @@ import ( "syscall" "time" - internalapi "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/api" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + internalapi "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/setup.go b/pkg/llmproxy/cmd/setup.go index fe30592e78..a93166a498 100644 --- a/pkg/llmproxy/cmd/setup.go +++ b/pkg/llmproxy/cmd/setup.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" ) diff --git a/pkg/llmproxy/cmd/setup_test.go b/pkg/llmproxy/cmd/setup_test.go index fb749d151e..3f59833671 100644 --- a/pkg/llmproxy/cmd/setup_test.go +++ b/pkg/llmproxy/cmd/setup_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestSetupOptions_ContainsCursorLogin(t *testing.T) { diff --git a/pkg/llmproxy/cmd/thegent_login.go b/pkg/llmproxy/cmd/thegent_login.go index 5aee5ae10b..a80d3a67ff 100644 --- a/pkg/llmproxy/cmd/thegent_login.go +++ b/pkg/llmproxy/cmd/thegent_login.go @@ -6,7 +6,7 @@ import ( "os" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/vertex_import.go b/pkg/llmproxy/cmd/vertex_import.go index 29f9f4f8d3..af744039c3 100644 --- a/pkg/llmproxy/cmd/vertex_import.go +++ b/pkg/llmproxy/cmd/vertex_import.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/vertex" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/config/sdk_config.go b/pkg/llmproxy/config/sdk_config.go index 9d99c92423..38c419f515 100644 --- a/pkg/llmproxy/config/sdk_config.go +++ b/pkg/llmproxy/config/sdk_config.go @@ -4,10 +4,7 @@ // debug settings, proxy configuration, and API keys. package config -// SDKConfig represents the application's configuration, loaded from a YAML file. -type SDKConfig struct { - // ProxyURL is the URL of an optional proxy server to use for outbound requests. - ProxyURL string `yaml:"proxy-url" json:"proxy-url"` +import internalconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" // ForceModelPrefix requires explicit model prefixes (e.g., "teamA/gemini-3-pro-preview") // to target prefixed credentials. When false, unprefixed model requests may use prefixed diff --git a/pkg/llmproxy/executor/aistudio_executor.go b/pkg/llmproxy/executor/aistudio_executor.go index 98d476bbec..8b862c3fb4 100644 --- a/pkg/llmproxy/executor/aistudio_executor.go +++ b/pkg/llmproxy/executor/aistudio_executor.go @@ -13,7 +13,7 @@ import ( "net/url" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/wsrelay" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/executor/antigravity_executor.go b/pkg/llmproxy/executor/antigravity_executor.go index 97c9ced34e..77b427c735 100644 --- a/pkg/llmproxy/executor/antigravity_executor.go +++ b/pkg/llmproxy/executor/antigravity_executor.go @@ -24,7 +24,7 @@ import ( "time" "github.com/google/uuid" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" diff --git a/pkg/llmproxy/executor/claude_executor.go b/pkg/llmproxy/executor/claude_executor.go index f85f0d07a3..df1f1956ba 100644 --- a/pkg/llmproxy/executor/claude_executor.go +++ b/pkg/llmproxy/executor/claude_executor.go @@ -15,7 +15,7 @@ import ( "github.com/andybalholm/brotli" "github.com/klauspost/compress/zstd" claudeauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/claude" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/claude_executor_test.go b/pkg/llmproxy/executor/claude_executor_test.go index 79a6fc9424..3c1f57d163 100644 --- a/pkg/llmproxy/executor/claude_executor_test.go +++ b/pkg/llmproxy/executor/claude_executor_test.go @@ -8,7 +8,7 @@ import ( "net/http/httptest" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" diff --git a/pkg/llmproxy/executor/codex_executor.go b/pkg/llmproxy/executor/codex_executor.go index 9f6faacc3a..f0698e8c81 100644 --- a/pkg/llmproxy/executor/codex_executor.go +++ b/pkg/llmproxy/executor/codex_executor.go @@ -12,7 +12,7 @@ import ( "time" codexauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/codex" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/codex_executor_compact_test.go b/pkg/llmproxy/executor/codex_executor_compact_test.go index b6a7c6e52b..6e279c527f 100644 --- a/pkg/llmproxy/executor/codex_executor_compact_test.go +++ b/pkg/llmproxy/executor/codex_executor_compact_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" diff --git a/pkg/llmproxy/executor/codex_executor_cpb0106_test.go b/pkg/llmproxy/executor/codex_executor_cpb0106_test.go index da3bae4d4a..aabed3d178 100644 --- a/pkg/llmproxy/executor/codex_executor_cpb0106_test.go +++ b/pkg/llmproxy/executor/codex_executor_cpb0106_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" diff --git a/pkg/llmproxy/executor/codex_executor_cpb0227_test.go b/pkg/llmproxy/executor/codex_executor_cpb0227_test.go index 19973b216c..a170da7e83 100644 --- a/pkg/llmproxy/executor/codex_executor_cpb0227_test.go +++ b/pkg/llmproxy/executor/codex_executor_cpb0227_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" diff --git a/pkg/llmproxy/executor/codex_websockets_executor.go b/pkg/llmproxy/executor/codex_websockets_executor.go index b29fad507e..c95591228c 100644 --- a/pkg/llmproxy/executor/codex_websockets_executor.go +++ b/pkg/llmproxy/executor/codex_websockets_executor.go @@ -17,13 +17,13 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/misc" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/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/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" + cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" + sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/pkg/llmproxy/executor/gemini_cli_executor.go b/pkg/llmproxy/executor/gemini_cli_executor.go index 5d7396a3fb..f4e030176a 100644 --- a/pkg/llmproxy/executor/gemini_cli_executor.go +++ b/pkg/llmproxy/executor/gemini_cli_executor.go @@ -17,7 +17,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/runtime/geminicli" diff --git a/pkg/llmproxy/executor/gemini_executor.go b/pkg/llmproxy/executor/gemini_executor.go index d01ea5b8cd..00e186ab74 100644 --- a/pkg/llmproxy/executor/gemini_executor.go +++ b/pkg/llmproxy/executor/gemini_executor.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/gemini_vertex_executor.go b/pkg/llmproxy/executor/gemini_vertex_executor.go index dcf4230c4a..0a404d4fc7 100644 --- a/pkg/llmproxy/executor/gemini_vertex_executor.go +++ b/pkg/llmproxy/executor/gemini_vertex_executor.go @@ -15,7 +15,7 @@ import ( "time" vertexauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/vertex" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/executor/github_copilot_executor.go b/pkg/llmproxy/executor/github_copilot_executor.go index 5be4132c6e..55fbf4ef57 100644 --- a/pkg/llmproxy/executor/github_copilot_executor.go +++ b/pkg/llmproxy/executor/github_copilot_executor.go @@ -13,7 +13,7 @@ import ( "github.com/google/uuid" copilotauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/copilot" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/iflow_executor.go b/pkg/llmproxy/executor/iflow_executor.go index f10662bc4c..ec6dff4f5b 100644 --- a/pkg/llmproxy/executor/iflow_executor.go +++ b/pkg/llmproxy/executor/iflow_executor.go @@ -14,7 +14,7 @@ import ( "github.com/google/uuid" iflowauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/iflow" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/kilo_executor.go b/pkg/llmproxy/executor/kilo_executor.go index 35e0a70921..2a3c25078d 100644 --- a/pkg/llmproxy/executor/kilo_executor.go +++ b/pkg/llmproxy/executor/kilo_executor.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/kimi_executor.go b/pkg/llmproxy/executor/kimi_executor.go index 4fd1da05d6..e70bde93cb 100644 --- a/pkg/llmproxy/executor/kimi_executor.go +++ b/pkg/llmproxy/executor/kimi_executor.go @@ -14,7 +14,7 @@ import ( "time" kimiauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kimi" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/kiro_executor.go b/pkg/llmproxy/executor/kiro_executor.go index 0a25f4e99c..8e0f5041e4 100644 --- a/pkg/llmproxy/executor/kiro_executor.go +++ b/pkg/llmproxy/executor/kiro_executor.go @@ -23,11 +23,11 @@ import ( "time" "github.com/google/uuid" + kiroclaude "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/kiro/claude" + kirocommon "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/kiro/common" + kiroopenai "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/kiro/openai" kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - kiroclaude "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/kiro/claude" - kirocommon "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/kiro/common" - kiroopenai "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/kiro/openai" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/kiro_streaming.go b/pkg/llmproxy/executor/kiro_streaming.go index cd09d97371..60abad576b 100644 --- a/pkg/llmproxy/executor/kiro_streaming.go +++ b/pkg/llmproxy/executor/kiro_streaming.go @@ -15,12 +15,14 @@ import ( "time" "github.com/google/uuid" - kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" - kiroclaude "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/kiro/claude" - kirocommon "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/kiro/common" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" - clipproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" - clipproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" + kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + kiroclaude "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/kiro/claude" + kirocommon "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/kiro/common" + kiroopenai "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/kiro/openai" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/util" + cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" + cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/usage" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/executor/logging_helpers.go b/pkg/llmproxy/executor/logging_helpers.go index 11f2b68787..ea085a1704 100644 --- a/pkg/llmproxy/executor/logging_helpers.go +++ b/pkg/llmproxy/executor/logging_helpers.go @@ -11,7 +11,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/logging" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/executor/logging_helpers_test.go b/pkg/llmproxy/executor/logging_helpers_test.go index af0608c767..8d0c0aab76 100644 --- a/pkg/llmproxy/executor/logging_helpers_test.go +++ b/pkg/llmproxy/executor/logging_helpers_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestRecordAPIResponseMetadataRecordsTimestamp(t *testing.T) { diff --git a/pkg/llmproxy/executor/oauth_upstream.go b/pkg/llmproxy/executor/oauth_upstream.go index e0245e4950..b7ed6dce7e 100644 --- a/pkg/llmproxy/executor/oauth_upstream.go +++ b/pkg/llmproxy/executor/oauth_upstream.go @@ -3,7 +3,7 @@ package executor import ( "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/executor/oauth_upstream_test.go b/pkg/llmproxy/executor/oauth_upstream_test.go index d75be44e5f..61dcf8f0fb 100644 --- a/pkg/llmproxy/executor/oauth_upstream_test.go +++ b/pkg/llmproxy/executor/oauth_upstream_test.go @@ -3,7 +3,7 @@ package executor import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestResolveOAuthBaseURLWithOverride_PreferenceOrder(t *testing.T) { diff --git a/pkg/llmproxy/executor/openai_compat_executor.go b/pkg/llmproxy/executor/openai_compat_executor.go index 3f2b6767e0..5592e34485 100644 --- a/pkg/llmproxy/executor/openai_compat_executor.go +++ b/pkg/llmproxy/executor/openai_compat_executor.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/executor/openai_compat_executor_compact_test.go b/pkg/llmproxy/executor/openai_compat_executor_compact_test.go index 9db170edd8..25fa1ca99a 100644 --- a/pkg/llmproxy/executor/openai_compat_executor_compact_test.go +++ b/pkg/llmproxy/executor/openai_compat_executor_compact_test.go @@ -7,7 +7,7 @@ import ( "net/http/httptest" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" diff --git a/pkg/llmproxy/executor/openai_models_fetcher.go b/pkg/llmproxy/executor/openai_models_fetcher.go index 844e97c5fb..2d96e9b54c 100644 --- a/pkg/llmproxy/executor/openai_models_fetcher.go +++ b/pkg/llmproxy/executor/openai_models_fetcher.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/executor/openai_models_fetcher_test.go b/pkg/llmproxy/executor/openai_models_fetcher_test.go index bb52872e2d..e065319932 100644 --- a/pkg/llmproxy/executor/openai_models_fetcher_test.go +++ b/pkg/llmproxy/executor/openai_models_fetcher_test.go @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/executor/payload_helpers.go b/pkg/llmproxy/executor/payload_helpers.go index 67e3cf99cd..a6b86bf1c7 100644 --- a/pkg/llmproxy/executor/payload_helpers.go +++ b/pkg/llmproxy/executor/payload_helpers.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" "github.com/tidwall/gjson" diff --git a/pkg/llmproxy/executor/proxy_helpers.go b/pkg/llmproxy/executor/proxy_helpers.go index 442cd406c2..6415ec669f 100644 --- a/pkg/llmproxy/executor/proxy_helpers.go +++ b/pkg/llmproxy/executor/proxy_helpers.go @@ -11,7 +11,7 @@ import ( "sync" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" "golang.org/x/net/proxy" diff --git a/pkg/llmproxy/executor/qwen_executor.go b/pkg/llmproxy/executor/qwen_executor.go index b983b607f2..528675995c 100644 --- a/pkg/llmproxy/executor/qwen_executor.go +++ b/pkg/llmproxy/executor/qwen_executor.go @@ -10,7 +10,7 @@ import ( "time" qwenauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/qwen" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/interfaces/error_message.go b/pkg/llmproxy/interfaces/error_message.go index 7e1a14dc70..b345c631b0 100644 --- a/pkg/llmproxy/interfaces/error_message.go +++ b/pkg/llmproxy/interfaces/error_message.go @@ -1,6 +1,8 @@ package interfaces -import "net/http" +import ( + internalinterfaces "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/interfaces" +) // ErrorMessage encapsulates an error with an associated HTTP status code. type ErrorMessage struct { diff --git a/pkg/llmproxy/logging/global_logger.go b/pkg/llmproxy/logging/global_logger.go index 4db7ffe3fb..8f5fb240e6 100644 --- a/pkg/llmproxy/logging/global_logger.go +++ b/pkg/llmproxy/logging/global_logger.go @@ -10,7 +10,7 @@ import ( "sync" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" diff --git a/pkg/llmproxy/managementasset/updater.go b/pkg/llmproxy/managementasset/updater.go index a2553c49cf..b4287fb6b2 100644 --- a/pkg/llmproxy/managementasset/updater.go +++ b/pkg/llmproxy/managementasset/updater.go @@ -17,9 +17,9 @@ import ( "sync/atomic" "time" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + sdkconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" "golang.org/x/sync/singleflight" ) diff --git a/pkg/llmproxy/registry/model_registry.go b/pkg/llmproxy/registry/model_registry.go index 602725cf86..234d263883 100644 --- a/pkg/llmproxy/registry/model_registry.go +++ b/pkg/llmproxy/registry/model_registry.go @@ -11,7 +11,7 @@ import ( "sync" "time" - misc "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + misc "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/misc" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/runtime/executor/oauth_upstream_test.go b/pkg/llmproxy/runtime/executor/oauth_upstream_test.go index 2856cf28d5..0449bb25f3 100644 --- a/pkg/llmproxy/runtime/executor/oauth_upstream_test.go +++ b/pkg/llmproxy/runtime/executor/oauth_upstream_test.go @@ -3,7 +3,7 @@ package executor import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func resolveOAuthBaseURLWithOverride(cfg *config.Config, provider, defaultURL, authURL string) string { diff --git a/pkg/llmproxy/translator/antigravity/claude/antigravity_claude_request.go b/pkg/llmproxy/translator/antigravity/claude/antigravity_claude_request.go index bcee589929..edcac3b181 100644 --- a/pkg/llmproxy/translator/antigravity/claude/antigravity_claude_request.go +++ b/pkg/llmproxy/translator/antigravity/claude/antigravity_claude_request.go @@ -8,11 +8,11 @@ package claude import ( "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/cache" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/registry" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/gemini/common" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/cache" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) diff --git a/pkg/llmproxy/translator/antigravity/gemini/antigravity_gemini_request.go b/pkg/llmproxy/translator/antigravity/gemini/antigravity_gemini_request.go index 9d3563de03..27ec62b2b5 100644 --- a/pkg/llmproxy/translator/antigravity/gemini/antigravity_gemini_request.go +++ b/pkg/llmproxy/translator/antigravity/gemini/antigravity_gemini_request.go @@ -9,7 +9,7 @@ import ( "fmt" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/gemini/common" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/gemini/common" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" diff --git a/pkg/llmproxy/translator/gemini-cli/gemini/gemini-cli_gemini_request.go b/pkg/llmproxy/translator/gemini-cli/gemini/gemini-cli_gemini_request.go index 1e064c46a1..148e5b879a 100644 --- a/pkg/llmproxy/translator/gemini-cli/gemini/gemini-cli_gemini_request.go +++ b/pkg/llmproxy/translator/gemini-cli/gemini/gemini-cli_gemini_request.go @@ -8,7 +8,7 @@ package gemini import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/gemini/common" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/gemini/common" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" diff --git a/pkg/llmproxy/translator/gemini/gemini-cli/gemini_gemini-cli_request.go b/pkg/llmproxy/translator/gemini/gemini-cli/gemini_gemini-cli_request.go index bce983f44f..129e0bb499 100644 --- a/pkg/llmproxy/translator/gemini/gemini-cli/gemini_gemini-cli_request.go +++ b/pkg/llmproxy/translator/gemini/gemini-cli/gemini_gemini-cli_request.go @@ -8,7 +8,7 @@ package geminiCLI import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/gemini/common" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/gemini/common" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/pkg/llmproxy/translator/gemini/gemini/gemini_gemini_request.go b/pkg/llmproxy/translator/gemini/gemini/gemini_gemini_request.go index f9aafabcb6..b7585313ab 100644 --- a/pkg/llmproxy/translator/gemini/gemini/gemini_gemini_request.go +++ b/pkg/llmproxy/translator/gemini/gemini/gemini_gemini_request.go @@ -6,7 +6,7 @@ package gemini import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/gemini/common" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator/gemini/common" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/pkg/llmproxy/translator/init.go b/pkg/llmproxy/translator/init.go index bbb8ba5d7a..e10babb272 100644 --- a/pkg/llmproxy/translator/init.go +++ b/pkg/llmproxy/translator/init.go @@ -1,9 +1,6 @@ package translator import ( - // Import translator/translator to trigger centralized registration init. - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/translator" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/claude/gemini" _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/claude/gemini-cli" _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator/claude/openai/chat-completions" diff --git a/pkg/llmproxy/util/provider.go b/pkg/llmproxy/util/provider.go index 75a1d591f8..b93f26d5da 100644 --- a/pkg/llmproxy/util/provider.go +++ b/pkg/llmproxy/util/provider.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/util/provider_test.go b/pkg/llmproxy/util/provider_test.go index a003b0f029..319b28114b 100644 --- a/pkg/llmproxy/util/provider_test.go +++ b/pkg/llmproxy/util/provider_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" ) diff --git a/pkg/llmproxy/util/proxy.go b/pkg/llmproxy/util/proxy.go index c02b13d103..830d269cc1 100644 --- a/pkg/llmproxy/util/proxy.go +++ b/pkg/llmproxy/util/proxy.go @@ -9,7 +9,7 @@ import ( "net/http" "net/url" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" "golang.org/x/net/proxy" ) diff --git a/pkg/llmproxy/util/util.go b/pkg/llmproxy/util/util.go index b4b48fd120..167939a673 100644 --- a/pkg/llmproxy/util/util.go +++ b/pkg/llmproxy/util/util.go @@ -11,7 +11,7 @@ import ( "regexp" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/util/util_test.go b/pkg/llmproxy/util/util_test.go index 789fd4b612..de8b8d7ed4 100644 --- a/pkg/llmproxy/util/util_test.go +++ b/pkg/llmproxy/util/util_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestSetLogLevel(t *testing.T) { diff --git a/pkg/llmproxy/watcher/clients.go b/pkg/llmproxy/watcher/clients.go index 1aed827156..7c4171d1fa 100644 --- a/pkg/llmproxy/watcher/clients.go +++ b/pkg/llmproxy/watcher/clients.go @@ -14,9 +14,9 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/diff" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/util" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/diff" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/watcher/config_reload.go b/pkg/llmproxy/watcher/config_reload.go index e84feb317c..ba5d0d4d69 100644 --- a/pkg/llmproxy/watcher/config_reload.go +++ b/pkg/llmproxy/watcher/config_reload.go @@ -9,9 +9,9 @@ import ( "reflect" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/diff" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/util" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/diff" "gopkg.in/yaml.v3" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/watcher/diff/config_diff.go b/pkg/llmproxy/watcher/diff/config_diff.go index 582162ef51..ec9949c09b 100644 --- a/pkg/llmproxy/watcher/diff/config_diff.go +++ b/pkg/llmproxy/watcher/diff/config_diff.go @@ -6,7 +6,7 @@ import ( "reflect" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // BuildConfigChangeDetails computes a redacted, human-readable list of config changes. diff --git a/pkg/llmproxy/watcher/diff/config_diff_test.go b/pkg/llmproxy/watcher/diff/config_diff_test.go index 6c46c3a773..778eb96790 100644 --- a/pkg/llmproxy/watcher/diff/config_diff_test.go +++ b/pkg/llmproxy/watcher/diff/config_diff_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" sdkconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/config" ) diff --git a/pkg/llmproxy/watcher/diff/model_hash.go b/pkg/llmproxy/watcher/diff/model_hash.go index c7ec5056b6..4b328150e9 100644 --- a/pkg/llmproxy/watcher/diff/model_hash.go +++ b/pkg/llmproxy/watcher/diff/model_hash.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // ComputeOpenAICompatModelsHash returns a stable hash for OpenAI-compat models. diff --git a/pkg/llmproxy/watcher/diff/model_hash_test.go b/pkg/llmproxy/watcher/diff/model_hash_test.go index 4e4ada991e..07c62116fa 100644 --- a/pkg/llmproxy/watcher/diff/model_hash_test.go +++ b/pkg/llmproxy/watcher/diff/model_hash_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestComputeOpenAICompatModelsHash_Deterministic(t *testing.T) { diff --git a/pkg/llmproxy/watcher/diff/models_summary.go b/pkg/llmproxy/watcher/diff/models_summary.go index aa83f6e413..97d1e6b099 100644 --- a/pkg/llmproxy/watcher/diff/models_summary.go +++ b/pkg/llmproxy/watcher/diff/models_summary.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type GeminiModelsSummary struct { diff --git a/pkg/llmproxy/watcher/diff/oauth_excluded.go b/pkg/llmproxy/watcher/diff/oauth_excluded.go index 64c7e75452..7a9cd30fa4 100644 --- a/pkg/llmproxy/watcher/diff/oauth_excluded.go +++ b/pkg/llmproxy/watcher/diff/oauth_excluded.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type ExcludedModelsSummary struct { diff --git a/pkg/llmproxy/watcher/diff/oauth_excluded_test.go b/pkg/llmproxy/watcher/diff/oauth_excluded_test.go index 26066a9e8b..fec7ec0d1b 100644 --- a/pkg/llmproxy/watcher/diff/oauth_excluded_test.go +++ b/pkg/llmproxy/watcher/diff/oauth_excluded_test.go @@ -3,7 +3,7 @@ package diff import ( "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestSummarizeExcludedModels_NormalizesAndDedupes(t *testing.T) { diff --git a/pkg/llmproxy/watcher/diff/oauth_model_alias.go b/pkg/llmproxy/watcher/diff/oauth_model_alias.go index 3f2d0190ae..f1fdaf74e3 100644 --- a/pkg/llmproxy/watcher/diff/oauth_model_alias.go +++ b/pkg/llmproxy/watcher/diff/oauth_model_alias.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) type OAuthModelAliasSummary struct { diff --git a/pkg/llmproxy/watcher/diff/openai_compat.go b/pkg/llmproxy/watcher/diff/openai_compat.go index 37740d17fd..1017a7d4ce 100644 --- a/pkg/llmproxy/watcher/diff/openai_compat.go +++ b/pkg/llmproxy/watcher/diff/openai_compat.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // DiffOpenAICompatibility produces human-readable change descriptions. diff --git a/pkg/llmproxy/watcher/diff/openai_compat_test.go b/pkg/llmproxy/watcher/diff/openai_compat_test.go index 9dd9813a77..d0a5454ae1 100644 --- a/pkg/llmproxy/watcher/diff/openai_compat_test.go +++ b/pkg/llmproxy/watcher/diff/openai_compat_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func TestDiffOpenAICompatibility(t *testing.T) { diff --git a/pkg/llmproxy/watcher/dispatcher.go b/pkg/llmproxy/watcher/dispatcher.go index e3cd27e7d9..d28c71a386 100644 --- a/pkg/llmproxy/watcher/dispatcher.go +++ b/pkg/llmproxy/watcher/dispatcher.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/synthesizer" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/synthesizer" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/events.go b/pkg/llmproxy/watcher/events.go index 700867f7b1..f3e547a986 100644 --- a/pkg/llmproxy/watcher/events.go +++ b/pkg/llmproxy/watcher/events.go @@ -13,7 +13,7 @@ import ( "time" "github.com/fsnotify/fsnotify" - kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" + kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/auth/kiro" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/watcher/synthesizer/config.go b/pkg/llmproxy/watcher/synthesizer/config.go index 98edd1996c..9bd9b8c2b6 100644 --- a/pkg/llmproxy/watcher/synthesizer/config.go +++ b/pkg/llmproxy/watcher/synthesizer/config.go @@ -5,8 +5,8 @@ import ( "strconv" "strings" - kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/diff" + kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/diff" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/watcher/synthesizer/config_test.go b/pkg/llmproxy/watcher/synthesizer/config_test.go index e4ff622335..2afbb175dd 100644 --- a/pkg/llmproxy/watcher/synthesizer/config_test.go +++ b/pkg/llmproxy/watcher/synthesizer/config_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/synthesizer/context.go b/pkg/llmproxy/watcher/synthesizer/context.go index 79d1cf1d08..99e8df7ad0 100644 --- a/pkg/llmproxy/watcher/synthesizer/context.go +++ b/pkg/llmproxy/watcher/synthesizer/context.go @@ -3,7 +3,7 @@ package synthesizer import ( "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) // SynthesisContext provides the context needed for auth synthesis. diff --git a/pkg/llmproxy/watcher/synthesizer/file.go b/pkg/llmproxy/watcher/synthesizer/file.go index 30a5aa467b..4413f22050 100644 --- a/pkg/llmproxy/watcher/synthesizer/file.go +++ b/pkg/llmproxy/watcher/synthesizer/file.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/runtime/geminicli" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/runtime/geminicli" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/synthesizer/file_test.go b/pkg/llmproxy/watcher/synthesizer/file_test.go index e549822a9a..c169ab4817 100644 --- a/pkg/llmproxy/watcher/synthesizer/file_test.go +++ b/pkg/llmproxy/watcher/synthesizer/file_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/synthesizer/helpers.go b/pkg/llmproxy/watcher/synthesizer/helpers.go index b0883951be..17d6a17f7f 100644 --- a/pkg/llmproxy/watcher/synthesizer/helpers.go +++ b/pkg/llmproxy/watcher/synthesizer/helpers.go @@ -7,8 +7,8 @@ import ( "sort" "strings" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/diff" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/diff" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/synthesizer/helpers_test.go b/pkg/llmproxy/watcher/synthesizer/helpers_test.go index 2d9a32e310..7c6778245c 100644 --- a/pkg/llmproxy/watcher/synthesizer/helpers_test.go +++ b/pkg/llmproxy/watcher/synthesizer/helpers_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/diff" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/diff" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/watcher.go b/pkg/llmproxy/watcher/watcher.go index 1c352d4974..6fbae5fde0 100644 --- a/pkg/llmproxy/watcher/watcher.go +++ b/pkg/llmproxy/watcher/watcher.go @@ -9,7 +9,7 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" "gopkg.in/yaml.v3" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" diff --git a/pkg/llmproxy/watcher/watcher_test.go b/pkg/llmproxy/watcher/watcher_test.go index c6e83ce611..0d203efa3f 100644 --- a/pkg/llmproxy/watcher/watcher_test.go +++ b/pkg/llmproxy/watcher/watcher_test.go @@ -14,9 +14,9 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/diff" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher/synthesizer" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/diff" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/watcher/synthesizer" sdkAuth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/auth" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" "gopkg.in/yaml.v3" diff --git a/sdk/api/handlers/handlers.go b/sdk/api/handlers/handlers.go index 61219f1cb7..521b04ab3a 100644 --- a/sdk/api/handlers/handlers.go +++ b/sdk/api/handlers/handlers.go @@ -14,14 +14,14 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - "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" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/interfaces" - 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/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/logging" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" + coreexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" "golang.org/x/net/context" ) 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 49ce37ec03..46bb8bcfa0 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,7 +8,7 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/interfaces" "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers" sdkconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/config" ) diff --git a/sdk/api/management.go b/sdk/api/management.go index 9b658a74c8..84be0b035e 100644 --- a/sdk/api/management.go +++ b/sdk/api/management.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" internalmanagement "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/api/handlers/management" coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/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 1a329a4036..08234212fc 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/pkg/llmproxy/api" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" + internalapi "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api" + "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/api/handlers" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/logging" ) // ServerOption customises HTTP server construction. diff --git a/sdk/auth/antigravity.go b/sdk/auth/antigravity.go index c8263e705f..72a025f38f 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/pkg/llmproxy/auth/antigravity" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/browser" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/misc" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/antigravity" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/claude.go b/sdk/auth/claude.go index 3f311ba63b..7fc46c0383 100644 --- a/sdk/auth/claude.go +++ b/sdk/auth/claude.go @@ -10,10 +10,10 @@ import ( "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/claude" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" // legacy client removed - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/misc" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/codex.go b/sdk/auth/codex.go index 36c37ccc3e..3ba8988a94 100644 --- a/sdk/auth/codex.go +++ b/sdk/auth/codex.go @@ -10,10 +10,10 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/codex" "github.com/router-for-me/CLIProxyAPI/v6/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/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/codex_device.go b/sdk/auth/codex_device.go index 47798dc51a..ebaf0f5e0b 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/pkg/llmproxy/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/kooshapari/cliproxyapi-plusplus/v6/internal/auth/codex" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/browser" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/util" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/errors.go b/sdk/auth/errors.go index fbaf771ae7..aa4fa32e41 100644 --- a/sdk/auth/errors.go +++ b/sdk/auth/errors.go @@ -3,7 +3,7 @@ package auth import ( "fmt" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/interfaces" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/interfaces" ) // ProjectSelectionError indicates that the user must choose a specific project ID. diff --git a/sdk/auth/gemini.go b/sdk/auth/gemini.go index 27daacd4c0..5a94e1bc6e 100644 --- a/sdk/auth/gemini.go +++ b/sdk/auth/gemini.go @@ -7,8 +7,8 @@ import ( "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/gemini" // legacy client removed - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts. diff --git a/sdk/auth/github_copilot.go b/sdk/auth/github_copilot.go index 339d5fdd54..61d5249a24 100644 --- a/sdk/auth/github_copilot.go +++ b/sdk/auth/github_copilot.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/copilot" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/browser" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/copilot" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/iflow.go b/sdk/auth/iflow.go index ea11fd46cd..206c2a7af9 100644 --- a/sdk/auth/iflow.go +++ b/sdk/auth/iflow.go @@ -6,12 +6,12 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/iflow" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/browser" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/misc" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/iflow" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/misc" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/util" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/interfaces.go b/sdk/auth/interfaces.go index d71c5ca6ab..c98e608c5d 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/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported") diff --git a/sdk/auth/kilo.go b/sdk/auth/kilo.go index 6a9d3e4b79..b4880f3768 100644 --- a/sdk/auth/kilo.go +++ b/sdk/auth/kilo.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/kilo" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kilo" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) // KiloAuthenticator implements the login flow for Kilo AI accounts. diff --git a/sdk/auth/kimi.go b/sdk/auth/kimi.go index b26501ec1a..7d925b203b 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/pkg/llmproxy/auth/kimi" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/browser" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kimi" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/auth/kiro.go b/sdk/auth/kiro.go index 289985921a..df734c9afa 100644 --- a/sdk/auth/kiro.go +++ b/sdk/auth/kiro.go @@ -9,9 +9,9 @@ import ( "strings" "time" - kiroauth "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/kiro" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + kiroauth "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/kiro" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) // extractKiroIdentifier extracts a meaningful identifier for file naming. diff --git a/sdk/auth/manager.go b/sdk/auth/manager.go index 93fdc5f463..b0965ab3eb 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/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" ) // Manager aggregates authenticators and coordinates persistence via a token store. diff --git a/sdk/auth/qwen.go b/sdk/auth/qwen.go index 27e5e08371..5cdfe155bd 100644 --- a/sdk/auth/qwen.go +++ b/sdk/auth/qwen.go @@ -9,8 +9,8 @@ import ( "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/auth/qwen" "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/browser" // legacy client removed - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" + coreauth "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/sdk/cliproxy/auth/api_key_model_alias_test.go b/sdk/cliproxy/auth/api_key_model_alias_test.go index 6fc0ce4afa..8de07bb7fa 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/pkg/llmproxy/config" + internalconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" ) func TestLookupAPIKeyUpstreamModel(t *testing.T) { diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index 6cf1d10938..cdc4dc4b6f 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -2,24 +2,13 @@ package auth import ( "context" - "crypto/sha256" - "encoding/json" - "encoding/hex" - "errors" - "io" "net/http" "sync" "sync/atomic" "time" - "github.com/google/uuid" - internalconfig "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/logging" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/registry" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/thinking" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" - cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" - log "github.com/sirupsen/logrus" + internalconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + cliproxyexecutor "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/cliproxy/executor" ) // ProviderExecutor defines the contract required by Manager to execute provider calls. @@ -216,2174 +205,3 @@ func (m *Manager) SetConfig(cfg *internalconfig.Config) { m.runtimeConfig.Store(cfg) m.rebuildAPIKeyModelAliasFromRuntimeConfig() } - -func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) string { - if m == nil { - return "" - } - authID = strings.TrimSpace(authID) - if authID == "" { - return "" - } - requestedModel = strings.TrimSpace(requestedModel) - if requestedModel == "" { - return "" - } - table, _ := m.apiKeyModelAlias.Load().(apiKeyModelAliasTable) - if table == nil { - return "" - } - byAlias := table[authID] - if len(byAlias) == 0 { - return "" - } - key := strings.ToLower(thinking.ParseSuffix(requestedModel).ModelName) - if key == "" { - key = strings.ToLower(requestedModel) - } - resolved := strings.TrimSpace(byAlias[key]) - if resolved == "" { - return "" - } - // Preserve thinking suffix from the client's requested model unless config already has one. - requestResult := thinking.ParseSuffix(requestedModel) - if thinking.ParseSuffix(resolved).HasSuffix { - return resolved - } - if requestResult.HasSuffix && requestResult.RawSuffix != "" { - return resolved + "(" + requestResult.RawSuffix + ")" - } - return resolved - -} - -func (m *Manager) rebuildAPIKeyModelAliasFromRuntimeConfig() { - if m == nil { - return - } - cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config) - if cfg == nil { - cfg = &internalconfig.Config{} - } - m.mu.Lock() - defer m.mu.Unlock() - m.rebuildAPIKeyModelAliasLocked(cfg) -} - -func (m *Manager) rebuildAPIKeyModelAliasLocked(cfg *internalconfig.Config) { - if m == nil { - return - } - if cfg == nil { - cfg = &internalconfig.Config{} - } - - out := make(apiKeyModelAliasTable) - for _, auth := range m.auths { - if auth == nil { - continue - } - if strings.TrimSpace(auth.ID) == "" { - continue - } - kind, _ := auth.AccountInfo() - if !strings.EqualFold(strings.TrimSpace(kind), "api_key") { - continue - } - - byAlias := make(map[string]string) - provider := strings.ToLower(strings.TrimSpace(auth.Provider)) - switch provider { - case "gemini": - if entry := resolveGeminiAPIKeyConfig(cfg, auth); entry != nil { - compileAPIKeyModelAliasForModels(byAlias, entry.Models) - } - case "claude": - if entry := resolveClaudeAPIKeyConfig(cfg, auth); entry != nil { - compileAPIKeyModelAliasForModels(byAlias, entry.Models) - } - case "codex": - if entry := resolveCodexAPIKeyConfig(cfg, auth); entry != nil { - compileAPIKeyModelAliasForModels(byAlias, entry.Models) - } - case "vertex": - if entry := resolveVertexAPIKeyConfig(cfg, auth); entry != nil { - compileAPIKeyModelAliasForModels(byAlias, entry.Models) - } - default: - // OpenAI-compat uses config selection from auth.Attributes. - providerKey := "" - compatName := "" - if auth.Attributes != nil { - providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) - compatName = strings.TrimSpace(auth.Attributes["compat_name"]) - } - if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { - if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil { - compileAPIKeyModelAliasForModels(byAlias, entry.Models) - } - } - } - - if len(byAlias) > 0 { - out[auth.ID] = byAlias - } - } - - m.apiKeyModelAlias.Store(out) -} - -func compileAPIKeyModelAliasForModels[T interface { - GetName() string - GetAlias() string -}](out map[string]string, models []T) { - if out == nil { - return - } - for i := range models { - alias := strings.TrimSpace(models[i].GetAlias()) - name := strings.TrimSpace(models[i].GetName()) - if alias == "" || name == "" { - continue - } - aliasKey := strings.ToLower(thinking.ParseSuffix(alias).ModelName) - if aliasKey == "" { - aliasKey = strings.ToLower(alias) - } - // Config priority: first alias wins. - if _, exists := out[aliasKey]; exists { - continue - } - out[aliasKey] = name - // Also allow direct lookup by upstream name (case-insensitive), so lookups on already-upstream - // models remain a cheap no-op. - nameKey := strings.ToLower(thinking.ParseSuffix(name).ModelName) - if nameKey == "" { - nameKey = strings.ToLower(name) - } - if nameKey != "" { - if _, exists := out[nameKey]; !exists { - out[nameKey] = name - } - } - // Preserve config suffix priority by seeding a base-name lookup when name already has suffix. - nameResult := thinking.ParseSuffix(name) - if nameResult.HasSuffix { - baseKey := strings.ToLower(strings.TrimSpace(nameResult.ModelName)) - if baseKey != "" { - if _, exists := out[baseKey]; !exists { - out[baseKey] = name - } - } - } - } -} - -// SetRetryConfig updates retry attempts and cooldown wait interval. -func (m *Manager) SetRetryConfig(retry int, maxRetryInterval time.Duration) { - if m == nil { - return - } - if retry < 0 { - retry = 0 - } - if maxRetryInterval < 0 { - maxRetryInterval = 0 - } - m.requestRetry.Store(int32(retry)) - m.maxRetryInterval.Store(maxRetryInterval.Nanoseconds()) -} - -// RegisterExecutor registers a provider executor with the manager. -func (m *Manager) RegisterExecutor(executor ProviderExecutor) { - if executor == nil { - return - } - provider := strings.TrimSpace(executor.Identifier()) - if provider == "" { - return - } - - var replaced ProviderExecutor - m.mu.Lock() - replaced = m.executors[provider] - m.executors[provider] = executor - m.mu.Unlock() - - if replaced == nil || replaced == executor { - return - } - if closer, ok := replaced.(ExecutionSessionCloser); ok && closer != nil { - closer.CloseExecutionSession(CloseAllExecutionSessionsID) - } -} - -// UnregisterExecutor removes the executor associated with the provider key. -func (m *Manager) UnregisterExecutor(provider string) { - provider = strings.ToLower(strings.TrimSpace(provider)) - if provider == "" { - return - } - m.mu.Lock() - delete(m.executors, provider) - m.mu.Unlock() -} - -// Register inserts a new auth entry into the manager. -func (m *Manager) Register(ctx context.Context, auth *Auth) (*Auth, error) { - if auth == nil { - return nil, nil - } - if auth.ID == "" { - auth.ID = uuid.NewString() - } - auth.EnsureIndex() - m.mu.Lock() - m.auths[auth.ID] = auth.Clone() - m.mu.Unlock() - m.rebuildAPIKeyModelAliasFromRuntimeConfig() - _ = m.persist(ctx, auth) - m.hook.OnAuthRegistered(ctx, auth.Clone()) - return auth.Clone(), nil -} - -// Update replaces an existing auth entry and notifies hooks. -func (m *Manager) Update(ctx context.Context, auth *Auth) (*Auth, error) { - if auth == nil || auth.ID == "" { - return nil, nil - } - m.mu.Lock() - if existing, ok := m.auths[auth.ID]; ok && existing != nil && !auth.indexAssigned && auth.Index == "" { - auth.Index = existing.Index - auth.indexAssigned = existing.indexAssigned - } - auth.EnsureIndex() - m.auths[auth.ID] = auth.Clone() - m.mu.Unlock() - m.rebuildAPIKeyModelAliasFromRuntimeConfig() - _ = m.persist(ctx, auth) - m.hook.OnAuthUpdated(ctx, auth.Clone()) - return auth.Clone(), nil -} - -// Load resets manager state from the backing store. -func (m *Manager) Load(ctx context.Context) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.store == nil { - return nil - } - items, err := m.store.List(ctx) - if err != nil { - return err - } - m.auths = make(map[string]*Auth, len(items)) - for _, auth := range items { - if auth == nil || auth.ID == "" { - continue - } - auth.EnsureIndex() - m.auths[auth.ID] = auth.Clone() - } - cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config) - if cfg == nil { - cfg = &internalconfig.Config{} - } - m.rebuildAPIKeyModelAliasLocked(cfg) - return nil -} - -// Execute performs a non-streaming execution using the configured selector and executor. -// It supports multiple providers for the same model and round-robins the starting provider per model. -func (m *Manager) Execute(ctx context.Context, providers []string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) { - normalized := m.normalizeProviders(providers) - if len(normalized) == 0 { - return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - - _, maxWait := m.retrySettings() - - var lastErr error - for attempt := 0; ; attempt++ { - resp, errExec := m.executeMixedOnce(ctx, normalized, req, opts) - if errExec == nil { - return resp, nil - } - lastErr = errExec - wait, shouldRetry := m.shouldRetryAfterError(errExec, attempt, normalized, req.Model, maxWait) - if !shouldRetry { - break - } - if errWait := waitForCooldown(ctx, wait); errWait != nil { - return cliproxyexecutor.Response{}, errWait - } - } - if lastErr != nil { - return cliproxyexecutor.Response{}, lastErr - } - return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"} -} - -// ExecuteCount performs a non-streaming execution using the configured selector and executor. -// It supports multiple providers for the same model and round-robins the starting provider per model. -func (m *Manager) ExecuteCount(ctx context.Context, providers []string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) { - normalized := m.normalizeProviders(providers) - if len(normalized) == 0 { - return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - - _, maxWait := m.retrySettings() - - var lastErr error - for attempt := 0; ; attempt++ { - resp, errExec := m.executeCountMixedOnce(ctx, normalized, req, opts) - if errExec == nil { - return resp, nil - } - lastErr = errExec - wait, shouldRetry := m.shouldRetryAfterError(errExec, attempt, normalized, req.Model, maxWait) - if !shouldRetry { - break - } - if errWait := waitForCooldown(ctx, wait); errWait != nil { - return cliproxyexecutor.Response{}, errWait - } - } - if lastErr != nil { - return cliproxyexecutor.Response{}, lastErr - } - return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"} -} - -// ExecuteStream performs a streaming execution using the configured selector and executor. -// It supports multiple providers for the same model and round-robins the starting provider per model. -func (m *Manager) ExecuteStream(ctx context.Context, providers []string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (*cliproxyexecutor.StreamResult, error) { - normalized := m.normalizeProviders(providers) - if len(normalized) == 0 { - return nil, &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - - _, maxWait := m.retrySettings() - - var lastErr error - for attempt := 0; ; attempt++ { - result, errStream := m.executeStreamMixedOnce(ctx, normalized, req, opts) - if errStream == nil { - return result, nil - } - lastErr = errStream - wait, shouldRetry := m.shouldRetryAfterError(errStream, attempt, normalized, req.Model, maxWait) - if !shouldRetry { - break - } - if errWait := waitForCooldown(ctx, wait); errWait != nil { - return nil, errWait - } - } - if lastErr != nil { - return nil, lastErr - } - return nil, &Error{Code: "auth_not_found", Message: "no auth available"} -} - -func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) { - if len(providers) == 0 { - return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - routeModel := req.Model - opts = ensureRequestedModelMetadata(opts, routeModel) - tried := make(map[string]struct{}) - var lastErr error - for { - auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried) - if errPick != nil { - if lastErr != nil { - return cliproxyexecutor.Response{}, lastErr - } - return cliproxyexecutor.Response{}, errPick - } - - entry := logEntryWithRequestID(ctx) - debugLogAuthSelection(entry, auth, provider, req.Model) - publishSelectedAuthMetadata(opts.Metadata, auth.ID) - - tried[auth.ID] = struct{}{} - execCtx := ctx - if rt := m.roundTripperFor(auth); rt != nil { - execCtx = context.WithValue(execCtx, roundTripperContextKey{}, rt) - execCtx = context.WithValue(execCtx, "cliproxy.roundtripper", rt) - } - execReq := req - execReq.Model = rewriteModelForAuth(routeModel, auth) - execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model) - execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model) - resp, errExec := executor.Execute(execCtx, auth, execReq, opts) - result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} - if errExec != nil { - if errCtx := execCtx.Err(); errCtx != nil { - return cliproxyexecutor.Response{}, errCtx - } - result.Error = &Error{Message: errExec.Error()} - if se, ok := errors.AsType[cliproxyexecutor.StatusError](errExec); ok && se != nil { - result.Error.HTTPStatus = se.StatusCode() - } - if ra := retryAfterFromError(errExec); ra != nil { - result.RetryAfter = ra - } - m.MarkResult(execCtx, result) - if isRequestInvalidError(errExec) { - return cliproxyexecutor.Response{}, errExec - } - lastErr = errExec - continue - } - m.MarkResult(execCtx, result) - return resp, nil - } -} - -func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) { - if len(providers) == 0 { - return cliproxyexecutor.Response{}, &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - routeModel := req.Model - opts = ensureRequestedModelMetadata(opts, routeModel) - tried := make(map[string]struct{}) - var lastErr error - for { - auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried) - if errPick != nil { - if lastErr != nil { - return cliproxyexecutor.Response{}, lastErr - } - return cliproxyexecutor.Response{}, errPick - } - - entry := logEntryWithRequestID(ctx) - debugLogAuthSelection(entry, auth, provider, req.Model) - publishSelectedAuthMetadata(opts.Metadata, auth.ID) - - tried[auth.ID] = struct{}{} - execCtx := ctx - if rt := m.roundTripperFor(auth); rt != nil { - execCtx = context.WithValue(execCtx, roundTripperContextKey{}, rt) - execCtx = context.WithValue(execCtx, "cliproxy.roundtripper", rt) - } - execReq := req - execReq.Model = rewriteModelForAuth(routeModel, auth) - execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model) - execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model) - resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts) - result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} - if errExec != nil { - if errCtx := execCtx.Err(); errCtx != nil { - return cliproxyexecutor.Response{}, errCtx - } - result.Error = &Error{Message: errExec.Error()} - if se, ok := errors.AsType[cliproxyexecutor.StatusError](errExec); ok && se != nil { - result.Error.HTTPStatus = se.StatusCode() - } - if ra := retryAfterFromError(errExec); ra != nil { - result.RetryAfter = ra - } - m.MarkResult(execCtx, result) - if isRequestInvalidError(errExec) { - return cliproxyexecutor.Response{}, errExec - } - lastErr = errExec - continue - } - m.MarkResult(execCtx, result) - return resp, nil - } -} - -func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (*cliproxyexecutor.StreamResult, error) { - if len(providers) == 0 { - return nil, &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - routeModel := req.Model - opts = ensureRequestedModelMetadata(opts, routeModel) - tried := make(map[string]struct{}) - var lastErr error - for { - auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried) - if errPick != nil { - if lastErr != nil { - return nil, lastErr - } - return nil, errPick - } - - entry := logEntryWithRequestID(ctx) - debugLogAuthSelection(entry, auth, provider, req.Model) - publishSelectedAuthMetadata(opts.Metadata, auth.ID) - - tried[auth.ID] = struct{}{} - execCtx := ctx - if rt := m.roundTripperFor(auth); rt != nil { - execCtx = context.WithValue(execCtx, roundTripperContextKey{}, rt) - execCtx = context.WithValue(execCtx, "cliproxy.roundtripper", rt) - } - execReq := req - execReq.Model = rewriteModelForAuth(routeModel, auth) - execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model) - execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model) - streamResult, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts) - if errStream != nil { - if errCtx := execCtx.Err(); errCtx != nil { - return nil, errCtx - } - rerr := &Error{Message: errStream.Error()} - if se, ok := errors.AsType[cliproxyexecutor.StatusError](errStream); ok && se != nil { - rerr.HTTPStatus = se.StatusCode() - } - result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: false, Error: rerr} - result.RetryAfter = retryAfterFromError(errStream) - m.MarkResult(execCtx, result) - if isRequestInvalidError(errStream) { - return nil, errStream - } - lastErr = errStream - continue - } - out := make(chan cliproxyexecutor.StreamChunk) - go func(streamCtx context.Context, streamAuth *Auth, streamProvider string, streamChunks <-chan cliproxyexecutor.StreamChunk) { - defer close(out) - var failed bool - forward := true - for chunk := range streamChunks { - if chunk.Err != nil && !failed { - failed = true - rerr := &Error{Message: chunk.Err.Error()} - if se, ok := errors.AsType[cliproxyexecutor.StatusError](chunk.Err); ok && se != nil { - rerr.HTTPStatus = se.StatusCode() - } - m.MarkResult(streamCtx, Result{AuthID: streamAuth.ID, Provider: streamProvider, Model: routeModel, Success: false, Error: rerr}) - } - if !forward { - continue - } - if streamCtx == nil { - out <- chunk - continue - } - select { - case <-streamCtx.Done(): - forward = false - case out <- chunk: - } - } - if !failed { - m.MarkResult(streamCtx, Result{AuthID: streamAuth.ID, Provider: streamProvider, Model: routeModel, Success: true}) - } - }(execCtx, auth.Clone(), provider, streamResult.Chunks) - return &cliproxyexecutor.StreamResult{ - Headers: streamResult.Headers, - Chunks: out, - }, nil - } -} - -func ensureRequestedModelMetadata(opts cliproxyexecutor.Options, requestedModel string) cliproxyexecutor.Options { - requestedModel = strings.TrimSpace(requestedModel) - if requestedModel == "" { - return opts - } - if hasRequestedModelMetadata(opts.Metadata) { - return opts - } - if len(opts.Metadata) == 0 { - opts.Metadata = map[string]any{cliproxyexecutor.RequestedModelMetadataKey: requestedModel} - return opts - } - meta := make(map[string]any, len(opts.Metadata)+1) - for k, v := range opts.Metadata { - meta[k] = v - } - meta[cliproxyexecutor.RequestedModelMetadataKey] = requestedModel - opts.Metadata = meta - return opts -} - -func hasRequestedModelMetadata(meta map[string]any) bool { - if len(meta) == 0 { - return false - } - raw, ok := meta[cliproxyexecutor.RequestedModelMetadataKey] - if !ok || raw == nil { - return false - } - switch v := raw.(type) { - case string: - return strings.TrimSpace(v) != "" - case []byte: - return strings.TrimSpace(string(v)) != "" - default: - return false - } -} - -func pinnedAuthIDFromMetadata(meta map[string]any) string { - if len(meta) == 0 { - return "" - } - raw, ok := meta[cliproxyexecutor.PinnedAuthMetadataKey] - if !ok || raw == nil { - return "" - } - switch val := raw.(type) { - case string: - return strings.TrimSpace(val) - case []byte: - return strings.TrimSpace(string(val)) - default: - return "" - } -} - -func publishSelectedAuthMetadata(meta map[string]any, authID string) { - if len(meta) == 0 { - return - } - authID = strings.TrimSpace(authID) - if authID == "" { - return - } - meta[cliproxyexecutor.SelectedAuthMetadataKey] = authID - if callback, ok := meta[cliproxyexecutor.SelectedAuthCallbackMetadataKey].(func(string)); ok && callback != nil { - callback(authID) - } -} - -func rewriteModelForAuth(model string, auth *Auth) string { - if auth == nil || model == "" { - return model - } - prefix := strings.TrimSpace(auth.Prefix) - if prefix == "" { - return model - } - needle := prefix + "/" - if !strings.HasPrefix(model, needle) { - return model - } - return strings.TrimPrefix(model, needle) -} - -func (m *Manager) applyAPIKeyModelAlias(auth *Auth, requestedModel string) string { - if m == nil || auth == nil { - return requestedModel - } - - kind, _ := auth.AccountInfo() - if !strings.EqualFold(strings.TrimSpace(kind), "api_key") { - return requestedModel - } - - requestedModel = strings.TrimSpace(requestedModel) - if requestedModel == "" { - return requestedModel - } - - // Fast path: lookup per-auth mapping table (keyed by auth.ID). - if resolved := m.lookupAPIKeyUpstreamModel(auth.ID, requestedModel); resolved != "" { - return resolved - } - - // Slow path: scan config for the matching credential entry and resolve alias. - // This acts as a safety net if mappings are stale or auth.ID is missing. - cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config) - if cfg == nil { - cfg = &internalconfig.Config{} - } - - provider := strings.ToLower(strings.TrimSpace(auth.Provider)) - upstreamModel := "" - switch provider { - case "gemini": - upstreamModel = resolveUpstreamModelForGeminiAPIKey(cfg, auth, requestedModel) - case "claude": - upstreamModel = resolveUpstreamModelForClaudeAPIKey(cfg, auth, requestedModel) - case "codex": - upstreamModel = resolveUpstreamModelForCodexAPIKey(cfg, auth, requestedModel) - case "vertex": - upstreamModel = resolveUpstreamModelForVertexAPIKey(cfg, auth, requestedModel) - default: - upstreamModel = resolveUpstreamModelForOpenAICompatAPIKey(cfg, auth, requestedModel) - } - - // Return upstream model if found, otherwise return requested model. - if upstreamModel != "" { - return upstreamModel - } - return requestedModel -} - -// APIKeyConfigEntry is a generic interface for API key configurations. -type APIKeyConfigEntry interface { - GetAPIKey() string - GetBaseURL() string -} - -func resolveAPIKeyConfig[T APIKeyConfigEntry](entries []T, auth *Auth) *T { - if auth == nil || len(entries) == 0 { - return nil - } - attrKey, attrBase := "", "" - if auth.Attributes != nil { - attrKey = strings.TrimSpace(auth.Attributes["api_key"]) - attrBase = strings.TrimSpace(auth.Attributes["base_url"]) - } - for i := range entries { - entry := &entries[i] - cfgKey := strings.TrimSpace((*entry).GetAPIKey()) - cfgBase := strings.TrimSpace((*entry).GetBaseURL()) - if attrKey != "" && attrBase != "" { - if strings.EqualFold(cfgKey, attrKey) && strings.EqualFold(cfgBase, attrBase) { - return entry - } - continue - } - if attrKey != "" && strings.EqualFold(cfgKey, attrKey) { - if cfgBase == "" || strings.EqualFold(cfgBase, attrBase) { - return entry - } - } - if attrKey == "" && attrBase != "" && strings.EqualFold(cfgBase, attrBase) { - return entry - } - } - if attrKey != "" { - for i := range entries { - entry := &entries[i] - if strings.EqualFold(strings.TrimSpace((*entry).GetAPIKey()), attrKey) { - return entry - } - } - } - return nil -} - -func resolveGeminiAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.GeminiKey { - if cfg == nil { - return nil - } - return resolveAPIKeyConfig(cfg.GeminiKey, auth) -} - -func resolveClaudeAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.ClaudeKey { - if cfg == nil { - return nil - } - return resolveAPIKeyConfig(cfg.ClaudeKey, auth) -} - -func resolveCodexAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.CodexKey { - if cfg == nil { - return nil - } - return resolveAPIKeyConfig(cfg.CodexKey, auth) -} - -func resolveVertexAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.VertexCompatKey { - if cfg == nil { - return nil - } - return resolveAPIKeyConfig(cfg.VertexCompatAPIKey, auth) -} - -func resolveUpstreamModelForGeminiAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { - entry := resolveGeminiAPIKeyConfig(cfg, auth) - if entry == nil { - return "" - } - return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) -} - -func resolveUpstreamModelForClaudeAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { - entry := resolveClaudeAPIKeyConfig(cfg, auth) - if entry == nil { - return "" - } - return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) -} - -func resolveUpstreamModelForCodexAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { - entry := resolveCodexAPIKeyConfig(cfg, auth) - if entry == nil { - return "" - } - return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) -} - -func resolveUpstreamModelForVertexAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { - entry := resolveVertexAPIKeyConfig(cfg, auth) - if entry == nil { - return "" - } - return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) -} - -func resolveUpstreamModelForOpenAICompatAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { - providerKey := "" - compatName := "" - if auth != nil && len(auth.Attributes) > 0 { - providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) - compatName = strings.TrimSpace(auth.Attributes["compat_name"]) - } - if compatName == "" && !strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { - return "" - } - entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider) - if entry == nil { - return "" - } - return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) -} - -type apiKeyModelAliasTable map[string]map[string]string - -func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatName, authProvider string) *internalconfig.OpenAICompatibility { - if cfg == nil { - return nil - } - candidates := make([]string, 0, 3) - if v := strings.TrimSpace(compatName); v != "" { - candidates = append(candidates, v) - } - if v := strings.TrimSpace(providerKey); v != "" { - candidates = append(candidates, v) - } - if v := strings.TrimSpace(authProvider); v != "" { - candidates = append(candidates, v) - } - for i := range cfg.OpenAICompatibility { - compat := &cfg.OpenAICompatibility[i] - for _, candidate := range candidates { - if candidate != "" && strings.EqualFold(strings.TrimSpace(candidate), compat.Name) { - return compat - } - } - } - return nil -} - -func asModelAliasEntries[T interface { - GetName() string - GetAlias() string -}](models []T) []modelAliasEntry { - if len(models) == 0 { - return nil - } - out := make([]modelAliasEntry, 0, len(models)) - for i := range models { - out = append(out, models[i]) - } - return out -} - -func (m *Manager) normalizeProviders(providers []string) []string { - if len(providers) == 0 { - return nil - } - result := make([]string, 0, len(providers)) - seen := make(map[string]struct{}, len(providers)) - for _, provider := range providers { - p := strings.TrimSpace(strings.ToLower(provider)) - if p == "" { - continue - } - if _, ok := seen[p]; ok { - continue - } - seen[p] = struct{}{} - result = append(result, p) - } - return result -} - -func (m *Manager) retrySettings() (int, time.Duration) { - if m == nil { - return 0, 0 - } - return int(m.requestRetry.Load()), time.Duration(m.maxRetryInterval.Load()) -} - -func (m *Manager) closestCooldownWait(providers []string, model string, attempt int) (time.Duration, bool) { - if m == nil || len(providers) == 0 { - return 0, false - } - now := time.Now() - defaultRetry := int(m.requestRetry.Load()) - if defaultRetry < 0 { - defaultRetry = 0 - } - providerSet := make(map[string]struct{}, len(providers)) - for i := range providers { - key := strings.TrimSpace(strings.ToLower(providers[i])) - if key == "" { - continue - } - providerSet[key] = struct{}{} - } - m.mu.RLock() - defer m.mu.RUnlock() - var ( - found bool - minWait time.Duration - ) - for _, auth := range m.auths { - if auth == nil { - continue - } - providerKey := strings.TrimSpace(strings.ToLower(auth.Provider)) - if _, ok := providerSet[providerKey]; !ok { - continue - } - effectiveRetry := defaultRetry - if override, ok := auth.RequestRetryOverride(); ok { - effectiveRetry = override - } - if effectiveRetry < 0 { - effectiveRetry = 0 - } - if attempt >= effectiveRetry { - continue - } - blocked, reason, next := isAuthBlockedForModel(auth, model, now) - if !blocked || next.IsZero() || reason == blockReasonDisabled { - continue - } - wait := next.Sub(now) - if wait < 0 { - continue - } - if !found || wait < minWait { - minWait = wait - found = true - } - } - return minWait, found -} - -func (m *Manager) shouldRetryAfterError(err error, attempt int, providers []string, model string, maxWait time.Duration) (time.Duration, bool) { - if err == nil { - return 0, false - } - if maxWait <= 0 { - return 0, false - } - if status := statusCodeFromError(err); status == http.StatusOK { - return 0, false - } - if isRequestInvalidError(err) { - return 0, false - } - wait, found := m.closestCooldownWait(providers, model, attempt) - if !found || wait > maxWait { - return 0, false - } - return wait, true -} - -func waitForCooldown(ctx context.Context, wait time.Duration) error { - if wait <= 0 { - return nil - } - timer := time.NewTimer(wait) - defer timer.Stop() - select { - case <-ctx.Done(): - return ctx.Err() - case <-timer.C: - return nil - } -} - -// MarkResult records an execution result and notifies hooks. -func (m *Manager) MarkResult(ctx context.Context, result Result) { - if result.AuthID == "" { - return - } - - shouldResumeModel := false - shouldSuspendModel := false - suspendReason := "" - clearModelQuota := false - setModelQuota := false - - m.mu.Lock() - if auth, ok := m.auths[result.AuthID]; ok && auth != nil { - now := time.Now() - - if result.Success { - if result.Model != "" { - state := ensureModelState(auth, result.Model) - resetModelState(state, now) - updateAggregatedAvailability(auth, now) - if !hasModelError(auth, now) { - auth.LastError = nil - auth.StatusMessage = "" - auth.Status = StatusActive - } - auth.UpdatedAt = now - shouldResumeModel = true - clearModelQuota = true - } else { - clearAuthStateOnSuccess(auth, now) - } - } else { - if result.Model != "" { - state := ensureModelState(auth, result.Model) - state.Unavailable = true - state.Status = StatusError - state.UpdatedAt = now - if result.Error != nil { - state.LastError = cloneError(result.Error) - state.StatusMessage = result.Error.Message - auth.LastError = cloneError(result.Error) - auth.StatusMessage = result.Error.Message - } - - statusCode := statusCodeFromResult(result.Error) - switch statusCode { - case 401: - next := now.Add(30 * time.Minute) - state.NextRetryAfter = next - suspendReason = "unauthorized" - shouldSuspendModel = true - case 402, 403: - next := now.Add(30 * time.Minute) - state.NextRetryAfter = next - suspendReason = "payment_required" - shouldSuspendModel = true - case 404: - next := now.Add(12 * time.Hour) - state.NextRetryAfter = next - suspendReason = "not_found" - shouldSuspendModel = true - case 429: - var next time.Time - backoffLevel := state.Quota.BackoffLevel - if result.RetryAfter != nil { - next = now.Add(*result.RetryAfter) - } else { - cooldown, nextLevel := nextQuotaCooldown(backoffLevel, quotaCooldownDisabledForAuth(auth)) - if cooldown > 0 { - next = now.Add(cooldown) - } - backoffLevel = nextLevel - } - state.NextRetryAfter = next - state.Quota = QuotaState{ - Exceeded: true, - Reason: "quota", - NextRecoverAt: next, - BackoffLevel: backoffLevel, - } - suspendReason = "quota" - shouldSuspendModel = true - setModelQuota = true - case 408, 500, 502, 503, 504: - if quotaCooldownDisabledForAuth(auth) { - state.NextRetryAfter = time.Time{} - } else { - next := now.Add(1 * time.Minute) - state.NextRetryAfter = next - } - default: - state.NextRetryAfter = time.Time{} - } - - auth.Status = StatusError - auth.UpdatedAt = now - updateAggregatedAvailability(auth, now) - } else { - applyAuthFailureState(auth, result.Error, result.RetryAfter, now) - } - } - - _ = m.persist(ctx, auth) - } - m.mu.Unlock() - - if clearModelQuota && result.Model != "" { - registry.GetGlobalRegistry().ClearModelQuotaExceeded(result.AuthID, result.Model) - } - if setModelQuota && result.Model != "" { - registry.GetGlobalRegistry().SetModelQuotaExceeded(result.AuthID, result.Model) - } - if shouldResumeModel { - registry.GetGlobalRegistry().ResumeClientModel(result.AuthID, result.Model) - } else if shouldSuspendModel { - registry.GetGlobalRegistry().SuspendClientModel(result.AuthID, result.Model, suspendReason) - } - - m.hook.OnResult(ctx, result) -} - -func ensureModelState(auth *Auth, model string) *ModelState { - if auth == nil || model == "" { - return nil - } - if auth.ModelStates == nil { - auth.ModelStates = make(map[string]*ModelState) - } - if state, ok := auth.ModelStates[model]; ok && state != nil { - return state - } - state := &ModelState{Status: StatusActive} - auth.ModelStates[model] = state - return state -} - -func resetModelState(state *ModelState, now time.Time) { - if state == nil { - return - } - state.Unavailable = false - state.Status = StatusActive - state.StatusMessage = "" - state.NextRetryAfter = time.Time{} - state.LastError = nil - state.Quota = QuotaState{} - state.UpdatedAt = now -} - -func updateAggregatedAvailability(auth *Auth, now time.Time) { - if auth == nil || len(auth.ModelStates) == 0 { - return - } - allUnavailable := true - earliestRetry := time.Time{} - quotaExceeded := false - quotaRecover := time.Time{} - maxBackoffLevel := 0 - for _, state := range auth.ModelStates { - if state == nil { - continue - } - stateUnavailable := false - if state.Status == StatusDisabled { - stateUnavailable = true - } else if state.Unavailable { - if state.NextRetryAfter.IsZero() { - stateUnavailable = false - } else if state.NextRetryAfter.After(now) { - stateUnavailable = true - if earliestRetry.IsZero() || state.NextRetryAfter.Before(earliestRetry) { - earliestRetry = state.NextRetryAfter - } - } else { - state.Unavailable = false - state.NextRetryAfter = time.Time{} - } - } - if !stateUnavailable { - allUnavailable = false - } - if state.Quota.Exceeded { - quotaExceeded = true - if quotaRecover.IsZero() || (!state.Quota.NextRecoverAt.IsZero() && state.Quota.NextRecoverAt.Before(quotaRecover)) { - quotaRecover = state.Quota.NextRecoverAt - } - if state.Quota.BackoffLevel > maxBackoffLevel { - maxBackoffLevel = state.Quota.BackoffLevel - } - } - } - auth.Unavailable = allUnavailable - if allUnavailable { - auth.NextRetryAfter = earliestRetry - } else { - auth.NextRetryAfter = time.Time{} - } - if quotaExceeded { - auth.Quota.Exceeded = true - auth.Quota.Reason = "quota" - auth.Quota.NextRecoverAt = quotaRecover - auth.Quota.BackoffLevel = maxBackoffLevel - } else { - auth.Quota.Exceeded = false - auth.Quota.Reason = "" - auth.Quota.NextRecoverAt = time.Time{} - auth.Quota.BackoffLevel = 0 - } -} - -func hasModelError(auth *Auth, now time.Time) bool { - if auth == nil || len(auth.ModelStates) == 0 { - return false - } - for _, state := range auth.ModelStates { - if state == nil { - continue - } - if state.LastError != nil { - return true - } - if state.Status == StatusError { - if state.Unavailable && (state.NextRetryAfter.IsZero() || state.NextRetryAfter.After(now)) { - return true - } - } - } - return false -} - -func clearAuthStateOnSuccess(auth *Auth, now time.Time) { - if auth == nil { - return - } - auth.Unavailable = false - auth.Status = StatusActive - auth.StatusMessage = "" - auth.Quota.Exceeded = false - auth.Quota.Reason = "" - auth.Quota.NextRecoverAt = time.Time{} - auth.Quota.BackoffLevel = 0 - auth.LastError = nil - auth.NextRetryAfter = time.Time{} - auth.UpdatedAt = now -} - -func cloneError(err *Error) *Error { - if err == nil { - return nil - } - return &Error{ - Code: err.Code, - Message: err.Message, - Retryable: err.Retryable, - HTTPStatus: err.HTTPStatus, - } -} - -func statusCodeFromError(err error) int { - if err == nil { - return 0 - } - type statusCoder interface { - StatusCode() int - } - var sc statusCoder - if errors.As(err, &sc) && sc != nil { - return sc.StatusCode() - } - return 0 -} - -func retryAfterFromError(err error) *time.Duration { - if err == nil { - return nil - } - type retryAfterProvider interface { - RetryAfter() *time.Duration - } - rap, ok := err.(retryAfterProvider) - if !ok || rap == nil { - return nil - } - retryAfter := rap.RetryAfter() - if retryAfter == nil { - return nil - } - return new(*retryAfter) -} - -func statusCodeFromResult(err *Error) int { - if err == nil { - return 0 - } - return err.StatusCode() -} - -// isRequestInvalidError returns true if the error represents a client request -// error that should not be retried. Specifically, it checks for 400 Bad Request -// with "invalid_request_error" in the message, indicating the request itself is -// malformed and switching to a different auth will not help. -func isRequestInvalidError(err error) bool { - if err == nil { - return false - } - status := statusCodeFromError(err) - if status != http.StatusBadRequest { - return false - } - return strings.Contains(err.Error(), "invalid_request_error") -} - -func applyAuthFailureState(auth *Auth, resultErr *Error, retryAfter *time.Duration, now time.Time) { - if auth == nil { - return - } - auth.Unavailable = true - auth.Status = StatusError - auth.UpdatedAt = now - if resultErr != nil { - auth.LastError = cloneError(resultErr) - if resultErr.Message != "" { - auth.StatusMessage = resultErr.Message - } - } - statusCode := statusCodeFromResult(resultErr) - switch statusCode { - case 401: - auth.StatusMessage = "unauthorized" - auth.NextRetryAfter = now.Add(30 * time.Minute) - case 402, 403: - auth.StatusMessage = "payment_required" - auth.NextRetryAfter = now.Add(30 * time.Minute) - case 404: - auth.StatusMessage = "not_found" - auth.NextRetryAfter = now.Add(12 * time.Hour) - case 429: - auth.StatusMessage = "quota exhausted" - auth.Quota.Exceeded = true - auth.Quota.Reason = "quota" - var next time.Time - if retryAfter != nil { - next = now.Add(*retryAfter) - } else { - cooldown, nextLevel := nextQuotaCooldown(auth.Quota.BackoffLevel, quotaCooldownDisabledForAuth(auth)) - if cooldown > 0 { - next = now.Add(cooldown) - } - auth.Quota.BackoffLevel = nextLevel - } - auth.Quota.NextRecoverAt = next - auth.NextRetryAfter = next - case 408, 500, 502, 503, 504: - auth.StatusMessage = "transient upstream error" - if quotaCooldownDisabledForAuth(auth) { - auth.NextRetryAfter = time.Time{} - } else { - auth.NextRetryAfter = now.Add(1 * time.Minute) - } - default: - if auth.StatusMessage == "" { - auth.StatusMessage = "request failed" - } - } -} - -// nextQuotaCooldown returns the next cooldown duration and updated backoff level for repeated quota errors. -func nextQuotaCooldown(prevLevel int, disableCooling bool) (time.Duration, int) { - if prevLevel < 0 { - prevLevel = 0 - } - if disableCooling { - return 0, prevLevel - } - cooldown := quotaBackoffBase * time.Duration(1<= quotaBackoffMax { - return quotaBackoffMax, prevLevel - } - return cooldown, prevLevel + 1 -} - -// List returns all auth entries currently known by the manager. -func (m *Manager) List() []*Auth { - m.mu.RLock() - defer m.mu.RUnlock() - list := make([]*Auth, 0, len(m.auths)) - for _, auth := range m.auths { - list = append(list, auth.Clone()) - } - return list -} - -// GetByID retrieves an auth entry by its ID. - -func (m *Manager) GetByID(id string) (*Auth, bool) { - if id == "" { - return nil, false - } - m.mu.RLock() - defer m.mu.RUnlock() - auth, ok := m.auths[id] - if !ok { - return nil, false - } - return auth.Clone(), true -} - -// Executor returns the registered provider executor for a provider key. -func (m *Manager) Executor(provider string) (ProviderExecutor, bool) { - if m == nil { - return nil, false - } - provider = strings.TrimSpace(provider) - if provider == "" { - return nil, false - } - - m.mu.RLock() - executor, okExecutor := m.executors[provider] - if !okExecutor { - lowerProvider := strings.ToLower(provider) - if lowerProvider != provider { - executor, okExecutor = m.executors[lowerProvider] - } - } - m.mu.RUnlock() - - if !okExecutor || executor == nil { - return nil, false - } - return executor, true -} - -// CloseExecutionSession asks all registered executors to release the supplied execution session. -func (m *Manager) CloseExecutionSession(sessionID string) { - sessionID = strings.TrimSpace(sessionID) - if m == nil || sessionID == "" { - return - } - - m.mu.RLock() - executors := make([]ProviderExecutor, 0, len(m.executors)) - for _, exec := range m.executors { - executors = append(executors, exec) - } - m.mu.RUnlock() - - for i := range executors { - if closer, ok := executors[i].(ExecutionSessionCloser); ok && closer != nil { - closer.CloseExecutionSession(sessionID) - } - } -} - -func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, error) { - pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata) - - m.mu.RLock() - executor, okExecutor := m.executors[provider] - if !okExecutor { - m.mu.RUnlock() - return nil, nil, &Error{Code: "executor_not_found", Message: "executor not registered"} - } - candidates := make([]*Auth, 0, len(m.auths)) - modelKey := strings.TrimSpace(model) - // Always use base model name (without thinking suffix) for auth matching. - if modelKey != "" { - parsed := thinking.ParseSuffix(modelKey) - if parsed.ModelName != "" { - modelKey = strings.TrimSpace(parsed.ModelName) - } - } - registryRef := registry.GetGlobalRegistry() - for _, candidate := range m.auths { - if candidate.Provider != provider || candidate.Disabled { - continue - } - if pinnedAuthID != "" && candidate.ID != pinnedAuthID { - continue - } - if _, used := tried[candidate.ID]; used { - continue - } - if modelKey != "" && registryRef != nil && !registryRef.ClientSupportsModel(candidate.ID, modelKey) { - continue - } - candidates = append(candidates, candidate) - } - if len(candidates) == 0 { - m.mu.RUnlock() - return nil, nil, &Error{Code: "auth_not_found", Message: "no auth available"} - } - selected, errPick := m.selector.Pick(ctx, provider, model, opts, candidates) - if errPick != nil { - m.mu.RUnlock() - return nil, nil, errPick - } - if selected == nil { - m.mu.RUnlock() - return nil, nil, &Error{Code: "auth_not_found", Message: "selector returned no auth"} - } - authCopy := selected.Clone() - m.mu.RUnlock() - if !selected.indexAssigned { - m.mu.Lock() - if current := m.auths[authCopy.ID]; current != nil && !current.indexAssigned { - current.EnsureIndex() - authCopy = current.Clone() - } - m.mu.Unlock() - } - return authCopy, executor, nil -} - -func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) { - pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata) - - providerSet := make(map[string]struct{}, len(providers)) - for _, provider := range providers { - p := strings.TrimSpace(strings.ToLower(provider)) - if p == "" { - continue - } - providerSet[p] = struct{}{} - } - if len(providerSet) == 0 { - return nil, nil, "", &Error{Code: "provider_not_found", Message: "no provider supplied"} - } - - m.mu.RLock() - candidates := make([]*Auth, 0, len(m.auths)) - modelKey := strings.TrimSpace(model) - // Always use base model name (without thinking suffix) for auth matching. - if modelKey != "" { - parsed := thinking.ParseSuffix(modelKey) - if parsed.ModelName != "" { - modelKey = strings.TrimSpace(parsed.ModelName) - } - } - registryRef := registry.GetGlobalRegistry() - for _, candidate := range m.auths { - if candidate == nil || candidate.Disabled { - continue - } - if pinnedAuthID != "" && candidate.ID != pinnedAuthID { - continue - } - providerKey := strings.TrimSpace(strings.ToLower(candidate.Provider)) - if providerKey == "" { - continue - } - if _, ok := providerSet[providerKey]; !ok { - continue - } - if _, used := tried[candidate.ID]; used { - continue - } - if _, ok := m.executors[providerKey]; !ok { - continue - } - if modelKey != "" && registryRef != nil && !registryRef.ClientSupportsModel(candidate.ID, modelKey) { - continue - } - candidates = append(candidates, candidate) - } - if len(candidates) == 0 { - m.mu.RUnlock() - return nil, nil, "", &Error{Code: "auth_not_found", Message: "no auth available"} - } - selected, errPick := m.selector.Pick(ctx, "mixed", model, opts, candidates) - if errPick != nil { - m.mu.RUnlock() - return nil, nil, "", errPick - } - if selected == nil { - m.mu.RUnlock() - return nil, nil, "", &Error{Code: "auth_not_found", Message: "selector returned no auth"} - } - providerKey := strings.TrimSpace(strings.ToLower(selected.Provider)) - executor, okExecutor := m.executors[providerKey] - if !okExecutor { - m.mu.RUnlock() - return nil, nil, "", &Error{Code: "executor_not_found", Message: "executor not registered"} - } - authCopy := selected.Clone() - m.mu.RUnlock() - if !selected.indexAssigned { - m.mu.Lock() - if current := m.auths[authCopy.ID]; current != nil && !current.indexAssigned { - current.EnsureIndex() - authCopy = current.Clone() - } - m.mu.Unlock() - } - return authCopy, executor, providerKey, nil -} - -func (m *Manager) persist(ctx context.Context, auth *Auth) error { - if m.store == nil || auth == nil { - return nil - } - if shouldSkipPersist(ctx) { - return nil - } - if auth.Attributes != nil { - if v := strings.ToLower(strings.TrimSpace(auth.Attributes["runtime_only"])); v == "true" { - return nil - } - } - // Skip persistence when metadata is absent (e.g., runtime-only auths). - if auth.Metadata == nil { - return nil - } - _, err := m.store.Save(ctx, auth) - return err -} - -// StartAutoRefresh launches a background loop that evaluates auth freshness -// every few seconds and triggers refresh operations when required. -// Only one loop is kept alive; starting a new one cancels the previous run. -func (m *Manager) StartAutoRefresh(parent context.Context, interval time.Duration) { - if interval <= 0 || interval > refreshCheckInterval { - interval = refreshCheckInterval - } else { - interval = refreshCheckInterval - } - if m.refreshCancel != nil { - m.refreshCancel() - m.refreshCancel = nil - } - ctx, cancel := context.WithCancel(parent) - m.refreshCancel = cancel - go func() { - ticker := time.NewTicker(interval) - defer ticker.Stop() - m.checkRefreshes(ctx) - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - m.checkRefreshes(ctx) - } - } - }() -} - -// StopAutoRefresh cancels the background refresh loop, if running. -func (m *Manager) StopAutoRefresh() { - if m.refreshCancel != nil { - m.refreshCancel() - m.refreshCancel = nil - } -} - -func (m *Manager) checkRefreshes(ctx context.Context) { - // log.Debugf("checking refreshes") - now := time.Now() - snapshot := m.snapshotAuths() - for _, a := range snapshot { - typ, _ := a.AccountInfo() - if typ != "api_key" { - if !m.shouldRefresh(a, now) { - continue - } - log.Debugf("checking refresh for %s, %s, %s", a.Provider, a.ID, typ) - - if exec := m.executorFor(a.Provider); exec == nil { - continue - } - if !m.markRefreshPending(a.ID, now) { - continue - } - go m.refreshAuth(ctx, a.ID) - } - } -} - -func (m *Manager) snapshotAuths() []*Auth { - m.mu.RLock() - defer m.mu.RUnlock() - out := make([]*Auth, 0, len(m.auths)) - for _, a := range m.auths { - out = append(out, a.Clone()) - } - return out -} - -func (m *Manager) shouldRefresh(a *Auth, now time.Time) bool { - if a == nil || a.Disabled { - return false - } - if !a.NextRefreshAfter.IsZero() && now.Before(a.NextRefreshAfter) { - return false - } - if evaluator, ok := a.Runtime.(RefreshEvaluator); ok && evaluator != nil { - return evaluator.ShouldRefresh(now, a) - } - - lastRefresh := a.LastRefreshedAt - if lastRefresh.IsZero() { - if ts, ok := authLastRefreshTimestamp(a); ok { - lastRefresh = ts - } - } - - expiry, hasExpiry := a.ExpirationTime() - - if interval := authPreferredInterval(a); interval > 0 { - if hasExpiry && !expiry.IsZero() { - if !expiry.After(now) { - return true - } - if expiry.Sub(now) <= interval { - return true - } - } - if lastRefresh.IsZero() { - return true - } - return now.Sub(lastRefresh) >= interval - } - - provider := strings.ToLower(a.Provider) - lead := ProviderRefreshLead(provider, a.Runtime) - if lead == nil { - return false - } - if *lead <= 0 { - if hasExpiry && !expiry.IsZero() { - return now.After(expiry) - } - return false - } - if hasExpiry && !expiry.IsZero() { - return time.Until(expiry) <= *lead - } - if !lastRefresh.IsZero() { - return now.Sub(lastRefresh) >= *lead - } - return true -} - -func authPreferredInterval(a *Auth) time.Duration { - if a == nil { - return 0 - } - if d := durationFromMetadata(a.Metadata, "refresh_interval_seconds", "refreshIntervalSeconds", "refresh_interval", "refreshInterval"); d > 0 { - return d - } - if d := durationFromAttributes(a.Attributes, "refresh_interval_seconds", "refreshIntervalSeconds", "refresh_interval", "refreshInterval"); d > 0 { - return d - } - return 0 -} - -func durationFromMetadata(meta map[string]any, keys ...string) time.Duration { - if len(meta) == 0 { - return 0 - } - for _, key := range keys { - if val, ok := meta[key]; ok { - if dur := parseDurationValue(val); dur > 0 { - return dur - } - } - } - return 0 -} - -func durationFromAttributes(attrs map[string]string, keys ...string) time.Duration { - if len(attrs) == 0 { - return 0 - } - for _, key := range keys { - if val, ok := attrs[key]; ok { - if dur := parseDurationString(val); dur > 0 { - return dur - } - } - } - return 0 -} - -func parseDurationValue(val any) time.Duration { - switch v := val.(type) { - case time.Duration: - if v <= 0 { - return 0 - } - return v - case int: - if v <= 0 { - return 0 - } - return time.Duration(v) * time.Second - case int32: - if v <= 0 { - return 0 - } - return time.Duration(v) * time.Second - case int64: - if v <= 0 { - return 0 - } - return time.Duration(v) * time.Second - case uint: - if v == 0 { - return 0 - } - return time.Duration(v) * time.Second - case uint32: - if v == 0 { - return 0 - } - return time.Duration(v) * time.Second - case uint64: - if v == 0 { - return 0 - } - return time.Duration(v) * time.Second - case float32: - if v <= 0 { - return 0 - } - return time.Duration(float64(v) * float64(time.Second)) - case float64: - if v <= 0 { - return 0 - } - return time.Duration(v * float64(time.Second)) - case json.Number: - if i, err := v.Int64(); err == nil { - if i <= 0 { - return 0 - } - return time.Duration(i) * time.Second - } - if f, err := v.Float64(); err == nil && f > 0 { - return time.Duration(f * float64(time.Second)) - } - case string: - return parseDurationString(v) - } - return 0 -} - -func parseDurationString(raw string) time.Duration { - s := strings.TrimSpace(raw) - if s == "" { - return 0 - } - if dur, err := time.ParseDuration(s); err == nil && dur > 0 { - return dur - } - if secs, err := strconv.ParseFloat(s, 64); err == nil && secs > 0 { - return time.Duration(secs * float64(time.Second)) - } - return 0 -} - -func authLastRefreshTimestamp(a *Auth) (time.Time, bool) { - if a == nil { - return time.Time{}, false - } - if a.Metadata != nil { - if ts, ok := lookupMetadataTime(a.Metadata, "last_refresh", "lastRefresh", "last_refreshed_at", "lastRefreshedAt"); ok { - return ts, true - } - } - if a.Attributes != nil { - for _, key := range []string{"last_refresh", "lastRefresh", "last_refreshed_at", "lastRefreshedAt"} { - if val := strings.TrimSpace(a.Attributes[key]); val != "" { - if ts, ok := parseTimeValue(val); ok { - return ts, true - } - } - } - } - return time.Time{}, false -} - -func lookupMetadataTime(meta map[string]any, keys ...string) (time.Time, bool) { - for _, key := range keys { - if val, ok := meta[key]; ok { - if ts, ok1 := parseTimeValue(val); ok1 { - return ts, true - } - } - } - return time.Time{}, false -} - -func (m *Manager) markRefreshPending(id string, now time.Time) bool { - m.mu.Lock() - defer m.mu.Unlock() - auth, ok := m.auths[id] - if !ok || auth == nil || auth.Disabled { - return false - } - if !auth.NextRefreshAfter.IsZero() && now.Before(auth.NextRefreshAfter) { - return false - } - auth.NextRefreshAfter = now.Add(refreshPendingBackoff) - m.auths[id] = auth - return true -} - -func (m *Manager) refreshAuth(ctx context.Context, id string) { - if ctx == nil { - ctx = context.Background() - } - m.mu.RLock() - auth := m.auths[id] - var exec ProviderExecutor - if auth != nil { - exec = m.executors[auth.Provider] - } - m.mu.RUnlock() - if auth == nil || exec == nil { - return - } - cloned := auth.Clone() - updated, err := exec.Refresh(ctx, cloned) - if err != nil && errors.Is(err, context.Canceled) { - log.Debugf("refresh canceled for %s, %s", auth.Provider, auth.ID) - return - } - log.Debugf("refreshed %s, %s, %v", auth.Provider, auth.ID, err) - now := time.Now() - if err != nil { - m.mu.Lock() - if current := m.auths[id]; current != nil { - current.NextRefreshAfter = now.Add(refreshFailureBackoff) - current.LastError = &Error{Message: err.Error()} - m.auths[id] = current - } - m.mu.Unlock() - return - } - if updated == nil { - updated = cloned - } - // Preserve runtime created by the executor during Refresh. - // If executor didn't set one, fall back to the previous runtime. - if updated.Runtime == nil { - updated.Runtime = auth.Runtime - } - updated.LastRefreshedAt = now - // Preserve NextRefreshAfter set by the Authenticator - // If the Authenticator set a reasonable refresh time, it should not be overwritten - // If the Authenticator did not set it (zero value), shouldRefresh will use default logic - updated.LastError = nil - updated.UpdatedAt = now - _, _ = m.Update(ctx, updated) -} - -func (m *Manager) executorFor(provider string) ProviderExecutor { - m.mu.RLock() - defer m.mu.RUnlock() - return m.executors[provider] -} - -// roundTripperContextKey is an unexported context key type to avoid collisions. -type roundTripperContextKey struct{} - -// roundTripperFor retrieves an HTTP RoundTripper for the given auth if a provider is registered. -func (m *Manager) roundTripperFor(auth *Auth) http.RoundTripper { - m.mu.RLock() - p := m.rtProvider - m.mu.RUnlock() - if p == nil || auth == nil { - return nil - } - return p.RoundTripperFor(auth) -} - -// RoundTripperProvider defines a minimal provider of per-auth HTTP transports. -type RoundTripperProvider interface { - RoundTripperFor(auth *Auth) http.RoundTripper -} - -// RequestPreparer is an optional interface that provider executors can implement -// to mutate outbound HTTP requests with provider credentials. -type RequestPreparer interface { - PrepareRequest(req *http.Request, auth *Auth) error -} - -func executorKeyFromAuth(auth *Auth) string { - if auth == nil { - return "" - } - if auth.Attributes != nil { - providerKey := strings.TrimSpace(auth.Attributes["provider_key"]) - compatName := strings.TrimSpace(auth.Attributes["compat_name"]) - if compatName != "" { - if providerKey == "" { - providerKey = compatName - } - return strings.ToLower(providerKey) - } - } - return strings.ToLower(strings.TrimSpace(auth.Provider)) -} - -// logEntryWithRequestID returns a logrus entry with request_id field if available in context. -func logEntryWithRequestID(ctx context.Context) *log.Entry { - if ctx == nil { - return log.NewEntry(log.StandardLogger()) - } - if reqID := logging.GetRequestID(ctx); reqID != "" { - return log.WithField("request_id", reqID) - } - return log.NewEntry(log.StandardLogger()) -} - -func debugLogAuthSelection(entry *log.Entry, auth *Auth, provider string, model string) { - if !log.IsLevelEnabled(log.DebugLevel) { - return - } - if entry == nil || auth == nil { - return - } - accountType, accountInfo := auth.AccountInfo() - proxyInfo := auth.ProxyInfo() - suffix := "" - if proxyInfo != "" { - suffix = " " + proxyInfo - } - switch accountType { - case "api_key": - // nolint:gosec // false positive: model alias, not actual API key - entry.Debugf("Use API key %s for model %s%s", util.HideAPIKey(accountInfo), model, suffix) - case "oauth": - ident := formatOauthIdentity(auth, provider, accountInfo) - entry.Debugf("Use OAuth %s for model %s%s", ident, model, suffix) - } -} - -func formatOauthIdentity(auth *Auth, provider string, accountInfo string) string { - if auth == nil { - return "" - } - // Prefer the auth's provider when available. - providerName := strings.TrimSpace(auth.Provider) - if providerName == "" { - providerName = strings.TrimSpace(provider) - } - // Only log the basename to avoid leaking host paths. - // FileName may be unset for some auth backends; fall back to ID. - authFile := strings.TrimSpace(auth.FileName) - if authFile == "" { - authFile = strings.TrimSpace(auth.ID) - } - if authFile != "" { - authFile = filepath.Base(authFile) - } - parts := make([]string, 0, 3) - if providerName != "" { - parts = append(parts, "provider="+providerName) - } - if authFile != "" { - parts = append(parts, "auth_file="+authFile) - } - if len(parts) == 0 { - return accountInfo - } - return strings.Join(parts, " ") -} - -func authLogRef(auth *Auth) string { - if auth == nil { - return "provider=unknown auth_id_hash=" - } - provider := strings.TrimSpace(auth.Provider) - if provider == "" { - provider = "unknown" - } - sum := sha256.Sum256([]byte(strings.TrimSpace(auth.ID))) - hash := hex.EncodeToString(sum[:8]) - return "provider=" + provider + " auth_id_hash=" + hash -} - -// InjectCredentials delegates per-provider HTTP request preparation when supported. -// If the registered executor for the auth provider implements RequestPreparer, -// it will be invoked to modify the request (e.g., add headers). -func (m *Manager) InjectCredentials(req *http.Request, authID string) error { - if req == nil || authID == "" { - return nil - } - m.mu.RLock() - a := m.auths[authID] - var exec ProviderExecutor - if a != nil { - exec = m.executors[executorKeyFromAuth(a)] - } - m.mu.RUnlock() - if a == nil || exec == nil { - return nil - } - if p, ok := exec.(RequestPreparer); ok && p != nil { - return p.PrepareRequest(req, a) - } - return nil -} - -// PrepareHttpRequest injects provider credentials into the supplied HTTP request. -func (m *Manager) PrepareHttpRequest(ctx context.Context, auth *Auth, req *http.Request) error { - if m == nil { - return &Error{Code: "provider_not_found", Message: "manager is nil"} - } - if auth == nil { - return &Error{Code: "auth_not_found", Message: "auth is nil"} - } - if req == nil { - return &Error{Code: "invalid_request", Message: "http request is nil"} - } - if ctx != nil { - *req = *req.WithContext(ctx) - } - providerKey := executorKeyFromAuth(auth) - if providerKey == "" { - return &Error{Code: "provider_not_found", Message: "auth provider is empty"} - } - exec := m.executorFor(providerKey) - if exec == nil { - return &Error{Code: "provider_not_found", Message: "executor not registered for provider: " + providerKey} - } - preparer, ok := exec.(RequestPreparer) - if !ok || preparer == nil { - return &Error{Code: "not_supported", Message: "executor does not support http request preparation"} - } - return preparer.PrepareRequest(req, auth) -} - -// NewHttpRequest constructs a new HTTP request and injects provider credentials into it. -func (m *Manager) NewHttpRequest(ctx context.Context, auth *Auth, method, targetURL string, body []byte, headers http.Header) (*http.Request, error) { - if ctx == nil { - ctx = context.Background() - } - method = strings.TrimSpace(method) - if method == "" { - method = http.MethodGet - } - var reader io.Reader - if body != nil { - reader = bytes.NewReader(body) - } - httpReq, err := http.NewRequestWithContext(ctx, method, targetURL, reader) - if err != nil { - return nil, err - } - if headers != nil { - httpReq.Header = headers.Clone() - } - if errPrepare := m.PrepareHttpRequest(ctx, auth, httpReq); errPrepare != nil { - return nil, errPrepare - } - return httpReq, nil -} - -// HttpRequest injects provider credentials into the supplied HTTP request and executes it. -func (m *Manager) HttpRequest(ctx context.Context, auth *Auth, req *http.Request) (*http.Response, error) { - if m == nil { - return nil, &Error{Code: "provider_not_found", Message: "manager is nil"} - } - if auth == nil { - return nil, &Error{Code: "auth_not_found", Message: "auth is nil"} - } - if req == nil { - return nil, &Error{Code: "invalid_request", Message: "http request is nil"} - } - providerKey := executorKeyFromAuth(auth) - if providerKey == "" { - return nil, &Error{Code: "provider_not_found", Message: "auth provider is empty"} - } - exec := m.executorFor(providerKey) - if exec == nil { - return nil, &Error{Code: "provider_not_found", Message: "executor not registered for provider: " + providerKey} - } - return exec.HttpRequest(ctx, auth, req) -} diff --git a/sdk/cliproxy/auth/oauth_model_alias.go b/sdk/cliproxy/auth/oauth_model_alias.go index eda2e71277..4277551ed9 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/pkg/llmproxy/config" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/thinking" + internalconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/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 4a6dc7ff6b..426fafc1b6 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/pkg/llmproxy/config" + internalconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" ) func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) { diff --git a/sdk/cliproxy/pprof_server.go b/sdk/cliproxy/pprof_server.go index f7a599cb86..043f6fb27b 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/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/sdk/cliproxy/providers.go b/sdk/cliproxy/providers.go index f623bc3247..0c350c29f3 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/pkg/llmproxy/watcher" - "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/watcher" + "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" ) // NewFileTokenClientProvider returns the default token-backed client loader. diff --git a/sdk/config/config.go b/sdk/config/config.go index ae61090077..6d12581963 100644 --- a/sdk/config/config.go +++ b/sdk/config/config.go @@ -4,7 +4,7 @@ // embed CLIProxyAPI without importing internal packages. package config -import pkgconfig "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/config" +import llmproxyconfig "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" type SDKConfig = pkgconfig.SDKConfig diff --git a/test/amp_management_test.go b/test/amp_management_test.go index c4c438b476..82d70b8cb3 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/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/api/handlers/management" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/config" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/api/handlers/management" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/config" ) func init() { diff --git a/test/builtin_tools_translation_test.go b/test/builtin_tools_translation_test.go index f641a471d2..c535b0c276 100644 --- a/test/builtin_tools_translation_test.go +++ b/test/builtin_tools_translation_test.go @@ -3,7 +3,7 @@ package test import ( "testing" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" "github.com/tidwall/gjson" diff --git a/test/thinking_conversion_test.go b/test/thinking_conversion_test.go index 77e944fbf4..5734cce016 100644 --- a/test/thinking_conversion_test.go +++ b/test/thinking_conversion_test.go @@ -6,20 +6,20 @@ import ( "testing" "time" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/translator" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/translator" // Import provider packages to trigger init() registration of ProviderAppliers - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/antigravity" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/claude" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/codex" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/gemini" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/geminicli" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/iflow" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/kimi" - _ "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking/provider/openai" - - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/registry" - "github.com/kooshapari/cliproxyapi-plusplus/v6/pkg/llmproxy/thinking" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/antigravity" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/claude" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/codex" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/gemini" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/geminicli" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/iflow" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/kimi" + _ "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking/provider/openai" + + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/registry" + "github.com/kooshapari/cliproxyapi-plusplus/v6/internal/thinking" sdktranslator "github.com/kooshapari/cliproxyapi-plusplus/v6/sdk/translator" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/third_party/phenotype-go-auth/oauth.go b/third_party/phenotype-go-auth/oauth.go deleted file mode 100644 index 88f01b5624..0000000000 --- a/third_party/phenotype-go-auth/oauth.go +++ /dev/null @@ -1,337 +0,0 @@ -// Package auth provides shared OAuth utilities for Phenotype services. -package auth - -import ( - "context" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "errors" - "fmt" - "net" - "net/http" - "sync" - "time" - - log "github.com/sirupsen/logrus" -) - -// PKCECodes holds the PKCE code verifier and challenge pair. -type PKCECodes struct { - // CodeVerifier is the cryptographically random string sent in the token request. - CodeVerifier string - - // CodeChallenge is the SHA256 hash of the code verifier, sent in the authorization request. - CodeChallenge string -} - -// GeneratePKCECodes generates a PKCE code verifier and challenge pair -// following RFC 7636 specifications for OAuth 2.0 PKCE extension. -// This provides additional security for the OAuth flow by ensuring that -// only the client that initiated the request can exchange the authorization code. -// -// Returns: -// - *PKCECodes: A struct containing the code verifier and challenge -// - error: An error if the generation fails, nil otherwise -func GeneratePKCECodes() (*PKCECodes, error) { - // Generate code verifier: 43-128 characters, URL-safe - codeVerifier, err := generateCodeVerifier() - if err != nil { - return nil, fmt.Errorf("failed to generate code verifier: %w", err) - } - - // Generate code challenge using S256 method - codeChallenge := generateCodeChallenge(codeVerifier) - - return &PKCECodes{ - CodeVerifier: codeVerifier, - CodeChallenge: codeChallenge, - }, nil -} - -// generateCodeVerifier creates a cryptographically random string -// of 128 characters using URL-safe base64 encoding -func generateCodeVerifier() (string, error) { - // Generate 96 random bytes (will result in 128 base64 characters) - bytes := make([]byte, 96) - _, err := rand.Read(bytes) - if err != nil { - return "", fmt.Errorf("failed to generate random bytes: %w", err) - } - - // Encode to URL-safe base64 without padding - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(bytes), nil -} - -// generateCodeChallenge creates a SHA256 hash of the code verifier -// and encodes it using URL-safe base64 encoding without padding -func generateCodeChallenge(codeVerifier string) string { - hash := sha256.Sum256([]byte(codeVerifier)) - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(hash[:]) -} - -// OAuthServer handles the local HTTP server for OAuth callbacks. -// It listens for the authorization code response from the OAuth provider -// and captures the necessary parameters to complete the authentication flow. -type OAuthServer struct { - // server is the underlying HTTP server instance - server *http.Server - // port is the port number on which the server listens - port int - // resultChan is a channel for sending OAuth results - resultChan chan *OAuthResult - // errorChan is a channel for sending OAuth errors - errorChan chan error - // mu is a mutex for protecting server state - mu sync.Mutex - // running indicates whether the server is currently running - running bool -} - -// OAuthResult contains the result of the OAuth callback. -// It holds either the authorization code and state for successful authentication -// or an error message if the authentication failed. -type OAuthResult struct { - // Code is the authorization code received from the OAuth provider - Code string - // State is the state parameter used to prevent CSRF attacks - State string - // Error contains any error message if the OAuth flow failed - Error string -} - -// NewOAuthServer creates a new OAuth callback server. -// It initializes the server with the specified port and creates channels -// for handling OAuth results and errors. -// -// Parameters: -// - port: The port number on which the server should listen -// -// Returns: -// - *OAuthServer: A new OAuthServer instance -func NewOAuthServer(port int) *OAuthServer { - return &OAuthServer{ - port: port, - resultChan: make(chan *OAuthResult, 1), - errorChan: make(chan error, 1), - } -} - -// Start starts the OAuth callback server. -// It sets up the HTTP handlers for the callback and success endpoints, -// and begins listening on the specified port. -// -// Returns: -// - error: An error if the server fails to start -func (s *OAuthServer) Start() error { - s.mu.Lock() - defer s.mu.Unlock() - - if s.running { - return fmt.Errorf("server is already running") - } - - // Check if port is available - if !s.isPortAvailable() { - return fmt.Errorf("port %d is already in use", s.port) - } - - mux := http.NewServeMux() - mux.HandleFunc("/callback", s.handleCallback) - mux.HandleFunc("/success", s.handleSuccess) - - s.server = &http.Server{ - Addr: fmt.Sprintf(":%d", s.port), - Handler: mux, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - } - - s.running = true - - // Start server in goroutine - go func() { - if err := s.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - s.errorChan <- fmt.Errorf("server failed to start: %w", err) - } - }() - - // Give server a moment to start - time.Sleep(100 * time.Millisecond) - - return nil -} - -// Stop gracefully stops the OAuth callback server. -// It performs a graceful shutdown of the HTTP server with a timeout. -// -// Parameters: -// - ctx: The context for controlling the shutdown process -// -// Returns: -// - error: An error if the server fails to stop gracefully -func (s *OAuthServer) Stop(ctx context.Context) error { - s.mu.Lock() - defer s.mu.Unlock() - - if !s.running || s.server == nil { - return nil - } - - log.Debug("Stopping OAuth callback server") - - // Create a context with timeout for shutdown - shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() - - err := s.server.Shutdown(shutdownCtx) - s.running = false - s.server = nil - - return err -} - -// WaitForCallback waits for the OAuth callback with a timeout. -// It blocks until either an OAuth result is received, an error occurs, -// or the specified timeout is reached. -// -// Parameters: -// - timeout: The maximum time to wait for the callback -// -// Returns: -// - *OAuthResult: The OAuth result if successful -// - error: An error if the callback times out or an error occurs -func (s *OAuthServer) WaitForCallback(timeout time.Duration) (*OAuthResult, error) { - select { - case result := <-s.resultChan: - return result, nil - case err := <-s.errorChan: - return nil, err - case <-time.After(timeout): - return nil, fmt.Errorf("timeout waiting for OAuth callback") - } -} - -// handleCallback handles the OAuth callback endpoint. -// It extracts the authorization code and state from the callback URL, -// validates the parameters, and sends the result to the waiting channel. -// -// Parameters: -// - w: The HTTP response writer -// - r: The HTTP request -func (s *OAuthServer) handleCallback(w http.ResponseWriter, r *http.Request) { - log.Debug("Received OAuth callback") - - // Validate request method - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - // Extract parameters - query := r.URL.Query() - code := query.Get("code") - state := query.Get("state") - errorParam := query.Get("error") - - // Validate required parameters - if errorParam != "" { - log.Errorf("OAuth error received: %s", errorParam) - result := &OAuthResult{ - Error: errorParam, - } - s.sendResult(result) - http.Error(w, fmt.Sprintf("OAuth error: %s", errorParam), http.StatusBadRequest) - return - } - - if code == "" { - log.Error("No authorization code received") - result := &OAuthResult{ - Error: "no_code", - } - s.sendResult(result) - http.Error(w, "No authorization code received", http.StatusBadRequest) - return - } - - if state == "" { - log.Error("No state parameter received") - result := &OAuthResult{ - Error: "no_state", - } - s.sendResult(result) - http.Error(w, "No state parameter received", http.StatusBadRequest) - return - } - - // Send successful result - result := &OAuthResult{ - Code: code, - State: state, - } - s.sendResult(result) - - // Redirect to success page - http.Redirect(w, r, "/success", http.StatusFound) -} - -// handleSuccess handles the success page endpoint. -// It serves a user-friendly response indicating that authentication was successful. -// -// Parameters: -// - w: The HTTP response writer -// - r: The HTTP request -func (s *OAuthServer) handleSuccess(w http.ResponseWriter, r *http.Request) { - log.Debug("Serving success page") - - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - - successMsg := "Authentication successful! You can now close this window and return to your CLI." - _, err := w.Write([]byte(successMsg)) - if err != nil { - log.Errorf("Failed to write success page: %v", err) - } -} - -// sendResult sends the OAuth result to the waiting channel. -// It ensures that the result is sent without blocking the handler. -// -// Parameters: -// - result: The OAuth result to send -func (s *OAuthServer) sendResult(result *OAuthResult) { - select { - case s.resultChan <- result: - log.Debug("OAuth result sent to channel") - default: - log.Warn("OAuth result channel is full, result dropped") - } -} - -// isPortAvailable checks if the specified port is available. -// It attempts to listen on the port to determine availability. -// -// Returns: -// - bool: True if the port is available, false otherwise -func (s *OAuthServer) isPortAvailable() bool { - addr := fmt.Sprintf(":%d", s.port) - listener, err := net.Listen("tcp", addr) - if err != nil { - return false - } - defer func() { - _ = listener.Close() - }() - return true -} - -// IsRunning returns whether the server is currently running. -// -// Returns: -// - bool: True if the server is running, false otherwise -func (s *OAuthServer) IsRunning() bool { - s.mu.Lock() - defer s.mu.Unlock() - return s.running -}