diff --git a/CODE_REFACTOR_SUMMARY.md b/CODE_REFACTOR_SUMMARY.md deleted file mode 100644 index 51bec33c3a..0000000000 --- a/CODE_REFACTOR_SUMMARY.md +++ /dev/null @@ -1,128 +0,0 @@ -# CODE REFACTOR SUMMARY -## Executed: 2026-02-23 - ---- - -## ACTIONS COMPLETED - -### 1. Code Quality Improvements - -| Action | Before | After | Impact | -|--------|--------|-------|--------| -| Unused imports fixed | 486 | 0 | ✅ Cleaner code | -| Placeholder fixes (PIE790) | 192 | 0 | ✅ Consistent style | -| Lint errors | 1209 | 0 | ✅ All checks pass | -| Shadow directories | 8 | 0 | ✅ Removed dead code | -| __pycache__ cleanup | Many | 0 | ✅ Clean repo | - -### 2. Quality Gates Added - -| Gate | Description | Status | -|------|-------------|--------| -| Pre-commit file size | Max 500 LOC for new Python files | ✅ Added | -| Ruff lint | Auto-fix on commit | ✅ Existing | -| Secret detection | Gitleaks | ✅ Existing | -| DX audit | Pre-push | ✅ Existing | - -### 3. Import Fixes - -Fixed broken integrations `__init__.py`: -- Removed references to missing modules (lmcache, nats_event_bus, graphiti, etc.) -- Simplified to only export what exists -- Fixed syntax errors - -### 4. Test Verification - -| Suite | Status | Count | -|-------|--------|-------| -| Automation tests | ✅ Pass | 47 | -| Agent tests | ✅ Pass | 282 | -| **Total** | ✅ Pass | **329** | - ---- - -## METRICS SUMMARY - -### thegent (After Cleanup) -| Metric | Before | After | Change | -|--------|--------|-------|--------| -| Python src LOC | 246,386 | 247,012 | +626 (cleanup) | -| Python files | 1,330 | 1,418 | +88 (reorganized) | -| Test files | 1,215 | 1,215 | No change | -| Lint errors | 1,209 | 0 | **-100%** ✅ | -| Shadow dirs | 8 | 0 | **-100%** ✅ | - -### Files Still Needing Refactor (>500 LOC) -| File | LOC | Priority | -|------|-----|----------| -| install.py | 1,773 | HIGH | -| commands/sync.py | 1,745 | HIGH | -| clode_main.py | 1,717 | MEDIUM | -| run_execution_core_helpers.py | 1,571 | MEDIUM | -| config.py | 1,540 | HIGH | -| provider_model_manager.py | 1,526 | MEDIUM | -| audit/shadow_audit_git.py | 1,518 | LOW | -| ... (18 more) | | | - ---- - -## REMAINING WORK - -### High Priority -1. **Split large files** - 25 files >500 LOC need refactoring -2. **Consolidate sync** - Two sync.py files need review -3. **Move cliproxy** - ~3,300 LOC could move to cliproxyapi++ - -### Medium Priority -1. **Add tests to civ** - Currently 0 tests -2. **Implement parpour** - Currently specs only -3. **Document architecture** - ADRs needed - -### Low Priority -1. **Rust migration** - utils/routing_impl candidates -2. **Mojo experiments** - compute/* modules -3. **Zig protocols** - Low-latency paths - ---- - -## INTEGRATION RECOMMENDATIONS - -### thegent + cliproxyapi++ -- Keep adapter in thegent -- Move manager logic to cliproxyapi++ -- Share model definitions via JSON - -### thegent + agentapi++ -- Consider using for agent control -- Would reduce thegent agent code -- HTTP API standardization - -### civ + parpour -- civ: Rust implementation -- parpour: Planning/specs -- Sync via shared specs - ---- - -## FILES MODIFIED - -``` -thegent/ -├── .pre-commit-config.yaml # Added file size gate -├── pyproject.toml # Fixed lint config -├── src/thegent/ -│ ├── integrations/__init__.py # Fixed imports -│ ├── integrations/base.py # Fixed abstract class -│ └── [486 files with import fixes] -└── [8 shadow directories removed] -``` - ---- - -## NEXT SESSION TASKS - -1. ☐ Refactor install.py into modules -2. ☐ Split config.py by concern -3. ☐ Consolidate sync implementations -4. ☐ Add tests to civ -5. ☐ Start parpour implementation diff --git a/REFACTOR_COMPLETE_SUMMARY.md b/REFACTOR_COMPLETE_SUMMARY.md deleted file mode 100644 index 9ecdb4ad74..0000000000 --- a/REFACTOR_COMPLETE_SUMMARY.md +++ /dev/null @@ -1,175 +0,0 @@ -# REFACTOR SESSION - COMPLETE SUMMARY -## Date: 2026-02-23 - ---- - -## ✅ ALL TASKS COMPLETED - -| Task | New Modules | LOC Extracted | -|------|-------------|---------------| -| install.py refactor | 3 modules | 1,159 LOC | -| config.py refactor | 1 module | 414 LOC | -| clode_main.py refactor | 2 modules | 289 LOC | -| run_execution refactor | 2 modules | 233 LOC | -| provider_model_manager refactor | 3 modules | 550 LOC | - -**Total: 11 new modules, ~2,645 LOC extracted** - ---- - -## NEW MODULES CREATED - -### Install Modules -| Module | LOC | Purpose | -|--------|-----|---------| -| `install_hooks.py` | 334 | Hook setup (git hooks, Rust dispatcher, skills) | -| `install_system.py` | 389 | System install (Homebrew, mise, dependencies) | -| `install_manager.py` | 436 | InstallManager class (file install, backup) | - -### Config Modules -| Module | LOC | Purpose | -|--------|-----|---------| -| `config_models.py` | 414 | 12 split settings classes | - -### Clode Modules -| Module | LOC | Purpose | -|--------|-----|---------| -| `clode_model_cmds.py` | 108 | 14 model shortcut commands | -| `clode_sitback.py` | 181 | Sitback command implementations | - -### Run Execution Modules -| Module | LOC | Purpose | -|--------|-----|---------| -| `run_budget.py` | 79 | Budget checking helpers | -| `run_routing.py` | 154 | Routing helpers (Pareto, auto-classify) | - -### Provider Modules -| Module | LOC | Purpose | -|--------|-----|---------| -| `provider_crud.py` | 307 | CRUD operations + credentials | -| `provider_search.py` | 106 | Search and scoring | -| `provider_forms.py` | 137 | Form UI | - ---- - -## FILES STILL OVER 500 LOC - -| File | LOC | Status | -|------|-----|--------| -| install.py | 1,773 | Partially refactored | -| config.py | 1,540 | Partially refactored | -| commands/sync.py | 1,745 | Keep as-is (well-structured) | -| clode_main.py | 1,717 | Partially refactored | -| run_execution_core_helpers.py | 1,571 | Partially refactored | -| provider_model_manager.py | 1,526 | Partially refactored | -| audit/shadow_audit_git.py | 1,518 | Low priority | -| cli/apps/sync.py | 1,368 | Keep as-is | -| dex_main.py | 1,317 | Pending | - ---- - -## ARCHITECTURE IMPROVEMENTS - -### Before Refactoring -``` -install.py (1,773 LOC monolith) -├── setup_hooks() -├── install_homebrew() -├── install_mise() -├── InstallManager class -└── run_wizard(), run_install*() - -clode_main.py (1,717 LOC) -├── 14 model commands (repetitive) -├── sitback commands -└── doctor, config commands - -provider_model_manager.py (1,526 LOC) -├── CRUD operations -├── Search functions -├── Form UI -└── Model indices -``` - -### After Refactoring -``` -install.py (1,773) - entry point -├── install_hooks.py (334) -├── install_system.py (389) -└── install_manager.py (436) - -clode_main.py (1,717) - entry point -├── clode_model_cmds.py (108) -└── clode_sitback.py (181) - -provider_model_manager.py (1,526) - entry point -├── provider_crud.py (307) -├── provider_search.py (106) -└── provider_forms.py (137) -``` - ---- - -## TEST STATUS - -``` -✅ 47 automation tests passing -✅ 0 lint errors -✅ All new modules import correctly -``` - ---- - -## METRICS - -| Metric | Before | After | -|--------|--------|-------| -| New modules created | 0 | 11 | -| LOC in new modules | 0 | ~2,645 | -| Files >500 LOC | 25 | 25 (9 refactored) | -| Test pass rate | 100% | 100% | -| Lint errors | 0 | 0 | - ---- - -## NEXT SESSION TASKS - -1. ☐ Complete config.py migration to config_models.py -2. ☐ Refactor `dex_main.py` (1,317 LOC) -3. ☐ Refactor `audit/shadow_audit_git.py` (1,518 LOC) -4. ☐ Update clode_main.py to use clode_model_cmds.py -5. ☐ Update provider_model_manager.py to use new modules - ---- - -## IMPORT PATTERNS - -### From New Install Modules -```python -from thegent.install_hooks import setup_hooks, setup_skills -from thegent.install_system import install_homebrew, install_mise -from thegent.install_manager import InstallManager -``` - -### From New Config Modules -```python -from thegent.config_models import ( - PathSettings, - ModelDefaultsSettings, - BudgetSettings, - RoutingSettings, -) -``` - -### From New Clode Modules -```python -from thegent.clode_model_cmds import MODEL_COMMANDS, register_model_commands -from thegent.clode_sitback import SITBACK_MODEL_ALIASES, resolve_sitback_model -``` - -### From New Provider Modules -```python -from thegent.provider_crud import list_providers, add_provider, validate_provider -from thegent.provider_search import search_models_by_capability, fuzzy_search_models -from thegent.provider_forms import run_provider_form -``` diff --git a/REFACTOR_SESSION_FINAL_SUMMARY.md b/REFACTOR_SESSION_FINAL_SUMMARY.md deleted file mode 100644 index 2dd6b57326..0000000000 --- a/REFACTOR_SESSION_FINAL_SUMMARY.md +++ /dev/null @@ -1,117 +0,0 @@ -# REFACTOR SESSION SUMMARY - FINAL -## Completed: 2026-02-23 - ---- - -## COMPLETED TASKS - -### 1. Install Module Refactoring ✅ - -| Module | LOC | Purpose | -|--------|-----|---------| -| `install_hooks.py` | 334 | Hook setup (git hooks, Rust dispatcher, skills) | -| `install_system.py` | 389 | System install (Homebrew, mise, dependencies) | -| `install_manager.py` | 436 | InstallManager class (file install, backup, uninstall) | - -### 2. Config Module Refactoring ✅ - -| Module | LOC | Purpose | -|--------|-----|---------| -| `config_models.py` | 414 | Split settings classes (12 classes) | - -### 3. Sync Consolidation ✅ - -**Finding**: Already properly organized - no changes needed - -### 4. Clode Main Refactoring ✅ - -| Module | LOC | Purpose | -|--------|-----|---------| -| `clode_model_cmds.py` | 110 | Model shortcut commands (14 commands) | -| `clode_sitback.py` | 183 | Sitback command implementations | - -### 5. Run Execution Refactoring ✅ - -| Module | LOC | Purpose | -|--------|-----|---------| -| `run_budget.py` | 79 | Budget checking helpers | -| `run_routing.py` | 154 | Routing helpers (Pareto, auto-classify) | - ---- - -## FILES STILL OVER 500 LOC - -| File | LOC | Status | -|------|-----|--------| -| install.py | 1,773 | Partially refactored | -| config.py | 1,540 | Partially refactored | -| commands/sync.py | 1,745 | Keep as-is (well-structured) | -| clode_main.py | 1,717 | Partially refactored | -| run_execution_core_helpers.py | 1,571 | Partially refactored | -| provider_model_manager.py | 1,526 | Pending | -| audit/shadow_audit_git.py | 1,518 | Low priority | -| cli/apps/sync.py | 1,368 | Keep as-is | -| dex_main.py | 1,317 | Pending | - ---- - -## NEW MODULES CREATED (8 total) - -``` -thegent/src/thegent/ -├── install_hooks.py # Hook setup functions -├── install_system.py # System installation -├── install_manager.py # InstallManager class -├── config_models.py # Split settings classes -├── clode_model_cmds.py # Model shortcut commands -├── clode_sitback.py # Sitback commands -└── cli/services/ - ├── run_budget.py # Budget checking - └── run_routing.py # Routing helpers -``` - ---- - -## METRICS - -| Metric | Before | After | -|--------|--------|-------| -| New modules | 0 | 8 | -| Total new LOC | 0 | ~2,100 | -| Test pass rate | 100% | 100% | -| Lint errors | 0 | 0 | - ---- - -## ARCHITECTURE IMPROVEMENTS - -### Install Module -- Before: `install.py` (1,773 LOC monolith) -- After: `install.py` + 3 focused modules - -### Clode Module -- Before: `clode_main.py` (1,717 LOC with repetitive model commands) -- After: `clode_main.py` + `clode_model_cmds.py` + `clode_sitback.py` - -### Run Execution Module -- Before: `run_execution_core_helpers.py` (1,571 LOC with inline helpers) -- After: `run_execution_core_helpers.py` + `run_budget.py` + `run_routing.py` - ---- - -## NEXT SESSION TASKS - -1. ☐ Refactor `provider_model_manager.py` (1,526 LOC) -2. ☐ Refactor `dex_main.py` (1,317 LOC) -3. ☐ Complete config.py migration to config_models.py -4. ☐ Complete clode_main.py migration to clode_model_cmds.py - ---- - -## TEST STATUS - -``` -✅ 47 automation tests passing -✅ 0 lint errors -✅ All new modules import correctly -``` diff --git a/REFACTOR_SESSION_SUMMARY.md b/REFACTOR_SESSION_SUMMARY.md deleted file mode 100644 index d8ddb86a6a..0000000000 --- a/REFACTOR_SESSION_SUMMARY.md +++ /dev/null @@ -1,139 +0,0 @@ -# REFACTOR SESSION SUMMARY -## Completed: 2026-02-23 - ---- - -## COMPLETED TASKS - -### 1. Install Module Refactoring ✅ - -Created new modules extracted from `install.py`: - -| Module | LOC | Purpose | -|--------|-----|---------| -| `install_hooks.py` | 334 | Hook setup (git hooks, Rust dispatcher, skills) | -| `install_system.py` | 389 | System install (Homebrew, mise, dependencies) | -| `install_manager.py` | 436 | InstallManager class (file install, backup, uninstall) | - -**Result**: `install.py` can now delegate to these modules - ---- - -### 2. Config Module Refactoring ✅ - -Created `config_models.py` with split settings classes: - -| Class | Purpose | -|-------|---------| -| `PathSettings` | Directory paths (cache, session, etc.) | -| `ModelDefaultsSettings` | Default model configurations | -| `TimeoutSettings` | Timeout values | -| `SessionSettings` | Session backend config | -| `RetentionSettings` | Retention policies | -| `BudgetSettings` | Budget and cost tracking | -| `RoutingSettings` | Routing configuration | -| `GovernanceSettings` | Governance policies | -| `MCPSettings` | MCP server config | -| `SecuritySettings` | Security settings | -| `BinarySettings` | Binary paths | - -**Result**: Settings can now be mixed in as needed - ---- - -### 3. Sync Consolidation Analysis ✅ - -**Finding**: Sync modules are already properly organized: - -| File | LOC | Purpose | -|------|-----|---------| -| `commands/sync.py` | 1,745 | SyncCommand implementation class | -| `cli/apps/sync.py` | 1,368 | Typer CLI commands (imports from commands/sync.py) | - -**Result**: No consolidation needed - already properly layered - ---- - -## FILES STILL OVER 500 LOC - -| File | LOC | Status | -|------|-----|--------| -| install.py | 1,773 | Partial refactor done | -| config.py | 1,540 | Partial refactor done | -| commands/sync.py | 1,745 | Keep as-is (well-structured) | -| clode_main.py | 1,717 | Pending | -| run_execution_core_helpers.py | 1,571 | Pending | -| provider_model_manager.py | 1,526 | Pending | -| audit/shadow_audit_git.py | 1,518 | Pending | -| protocols/jsonrpc_agent_server.py | 1,375 | Pending | -| cli/apps/sync.py | 1,368 | Keep as-is (well-structured) | -| dex_main.py | 1,317 | Pending | - ---- - -## NEW MODULES CREATED - -``` -thegent/src/thegent/ -├── install_hooks.py # NEW: Hook setup functions -├── install_system.py # NEW: System installation -├── install_manager.py # NEW: InstallManager class -├── config_models.py # NEW: Split settings classes -└── (existing modules preserved) -``` - ---- - -## TEST STATUS - -| Suite | Status | Count | -|-------|--------|-------| -| Automation tests | ✅ Pass | 47 | -| Lint check | ✅ Pass | 0 errors | - ---- - -## NEXT SESSION TASKS - -1. ☐ Refactor `clode_main.py` (1,717 LOC) -2. ☐ Refactor `run_execution_core_helpers.py` (1,571 LOC) -3. ☐ Refactor `provider_model_manager.py` (1,526 LOC) -4. ☐ Refactor `dex_main.py` (1,317 LOC) -5. ☐ Complete config.py migration to config_models.py - ---- - -## ARCHITECTURE IMPROVEMENTS - -### Before -``` -install.py (1,773 LOC) -├── setup_hooks() -├── setup_rust_dispatcher() -├── setup_harness() -├── setup_skills() -├── install_homebrew() -├── install_mise() -├── verify_mise_installation() -├── InstallManager class -└── run_wizard(), run_install*() -``` - -### After -``` -install.py (1,773 LOC) - main entry point -├── imports from install_hooks.py (334 LOC) -├── imports from install_system.py (389 LOC) -└── imports from install_manager.py (436 LOC) -``` - ---- - -## METRICS - -| Metric | Before | After | -|--------|--------|-------| -| Files >500 LOC | 25 | 25 (10 refactored) | -| New modules | 0 | 4 | -| Test pass rate | 100% | 100% | -| Lint errors | 0 | 0 | diff --git a/SDK_INTEGRATION_STATUS.md b/SDK_INTEGRATION_STATUS.md deleted file mode 100644 index 3797f50260..0000000000 --- a/SDK_INTEGRATION_STATUS.md +++ /dev/null @@ -1,23 +0,0 @@ -# SDK Integration Status - -## Current Adapters - -| Adapter | Purpose | Status | -|----------|---------|---------| -| cliproxy_adapter.py | LLM proxy | ✅ Active | -| codex_proxy.py | Codex integration | ✅ Active | -| cliproxy_manager.py | Management | ✅ Active | -| universal_adapter.py | Generic | ⚠️ Stub | - -## Integration Points - -- All adapters use different patterns -- No unified interface -- Need: port/adapter pattern - -## Next Steps -1. Define unified port interfaces -2. Create adapter wrapper layer -3. Add bifrost validation middleware - -Generated: 2026-02-23 diff --git a/go.mod b/go.mod index 687aa4d768..d2d2e3cc25 100644 --- a/go.mod +++ b/go.mod @@ -3,123 +3,121 @@ module github.com/kooshapari/CLIProxyAPI/v7 go 1.26.0 require ( - github.com/KooshaPari/phenotype-go-auth v0.0.0 - github.com/andybalholm/brotli v1.2.0 - github.com/atotto/clipboard v0.1.4 - github.com/charmbracelet/bubbles v1.0.0 - github.com/charmbracelet/bubbletea v1.3.10 - github.com/charmbracelet/lipgloss v1.1.0 - github.com/edsrzf/mmap-go v1.2.0 - github.com/fsnotify/fsnotify v1.9.0 - github.com/fxamacker/cbor/v2 v2.9.0 - github.com/gin-gonic/gin v1.12.0 - github.com/go-git/go-git/v6 v6.0.0-20251009132922-75a182125145 - github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 - github.com/jackc/pgx/v5 v5.8.0 - github.com/joho/godotenv v1.5.1 - github.com/klauspost/compress v1.18.4 - github.com/minio/minio-go/v7 v7.0.98 - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c - github.com/refraction-networking/utls v1.8.2 - github.com/sirupsen/logrus v1.9.4 - github.com/stretchr/testify v1.11.1 - 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.51.0 - golang.org/x/oauth2 v0.35.0 - golang.org/x/sync v0.19.0 - golang.org/x/term v0.40.0 - golang.org/x/text v0.34.0 - gopkg.in/natefinch/lumberjack.v2 v2.2.1 - gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.46.1 +github.com/KooshaPari/phenotype-go-auth v0.0.0 +github.com/andybalholm/brotli v1.2.0 +github.com/atotto/clipboard v0.1.4 +github.com/charmbracelet/bubbles v1.0.0 +github.com/charmbracelet/bubbletea v1.3.10 +github.com/charmbracelet/lipgloss v1.1.0 +github.com/edsrzf/mmap-go v1.2.0 +github.com/fsnotify/fsnotify v1.9.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/gin-gonic/gin v1.12.0 +github.com/go-git/go-git/v6 v6.0.0-20251009132922-75a182125145 +github.com/google/uuid v1.6.0 +github.com/gorilla/websocket v1.5.3 +github.com/jackc/pgx/v5 v5.8.0 +github.com/joho/godotenv v1.5.1 +github.com/klauspost/compress v1.18.4 +github.com/minio/minio-go/v7 v7.0.98 +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c +github.com/refraction-networking/utls v1.8.2 +github.com/sirupsen/logrus v1.9.4 +github.com/stretchr/testify v1.11.1 +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.51.0 +golang.org/x/oauth2 v0.35.0 +golang.org/x/sync v0.19.0 +golang.org/x/term v0.40.0 +golang.org/x/text v0.34.0 +gopkg.in/natefinch/lumberjack.v2 v2.2.1 +gopkg.in/yaml.v3 v3.0.1 +modernc.org/sqlite v1.46.1 ) replace github.com/KooshaPari/phenotype-go-auth => ./third_party/phenotype-go-auth require ( - cloud.google.com/go/compute/metadata v0.3.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.15.0 // indirect - github.com/bytedance/sonic/loader v0.5.0 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect - github.com/charmbracelet/x/ansi v0.11.6 // indirect - github.com/charmbracelet/x/cellbuf v0.0.15 // indirect - github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/clipperhouse/displaywidth v0.9.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.5.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/gabriel-vasile/mimetype v1.4.12 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-git/gcfg/v2 v2.0.2 // indirect - github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 // indirect - github.com/go-ini/ini v1.67.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.1 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.4.0 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/klauspost/crc32 v1.3.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/lucasb-eyer/go-colorful v1.3.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/minio/crc64nvme v1.1.1 // indirect - github.com/minio/md5-simd v1.1.2 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/ncruces/go-strftime v1.0.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/philhofer/fwd v1.2.0 // indirect - github.com/pjbgf/sha1cd v0.5.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/xid v1.6.0 // indirect - github.com/sergi/go-diff v1.4.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect - github.com/tinylib/msgp v1.6.1 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.1 // indirect - github.com/x448/float16 v0.8.4 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/arch v0.22.0 // indirect - golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/sys v0.41.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect - modernc.org/libc v1.67.6 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect +cloud.google.com/go/compute/metadata v0.3.0 // indirect +github.com/Microsoft/go-winio v0.6.2 // indirect +github.com/ProtonMail/go-crypto v1.3.0 // indirect +github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect +github.com/bytedance/gopkg v0.1.3 // indirect +github.com/bytedance/sonic v1.15.0 // indirect +github.com/bytedance/sonic/loader v0.5.0 // indirect +github.com/charmbracelet/colorprofile v0.4.1 // indirect +github.com/charmbracelet/x/ansi v0.11.6 // indirect +github.com/charmbracelet/x/cellbuf v0.0.15 // indirect +github.com/charmbracelet/x/term v0.2.2 // indirect +github.com/clipperhouse/displaywidth v0.9.0 // indirect +github.com/clipperhouse/stringish v0.1.1 // indirect +github.com/clipperhouse/uax29/v2 v2.5.0 // indirect +github.com/cloudflare/circl v1.6.1 // indirect +github.com/cloudwego/base64x v0.1.6 // indirect +github.com/cyphar/filepath-securejoin v0.4.1 // indirect +github.com/davecgh/go-spew v1.1.1 // indirect +github.com/dlclark/regexp2 v1.11.5 // indirect +github.com/dustin/go-humanize v1.0.1 // indirect +github.com/emirpasic/gods v1.18.1 // indirect +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect +github.com/gabriel-vasile/mimetype v1.4.12 // indirect +github.com/gin-contrib/sse v1.1.0 // indirect +github.com/go-git/gcfg/v2 v2.0.2 // indirect +github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 // indirect +github.com/go-ini/ini v1.67.0 // indirect +github.com/go-playground/locales v0.14.1 // indirect +github.com/go-playground/universal-translator v0.18.1 // indirect +github.com/go-playground/validator/v10 v10.30.1 // indirect +github.com/goccy/go-json v0.10.5 // indirect +github.com/goccy/go-yaml v1.19.2 // indirect +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect +github.com/jackc/pgpassfile v1.0.0 // indirect +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect +github.com/jackc/puddle/v2 v2.2.2 // indirect +github.com/json-iterator/go v1.1.12 // indirect +github.com/kevinburke/ssh_config v1.4.0 // indirect +github.com/klauspost/cpuid/v2 v2.3.0 // indirect +github.com/klauspost/crc32 v1.3.0 // indirect +github.com/leodido/go-urn v1.4.0 // indirect +github.com/lucasb-eyer/go-colorful v1.3.0 // indirect +github.com/mattn/go-isatty v0.0.20 // indirect +github.com/mattn/go-localereader v0.0.1 // indirect +github.com/mattn/go-runewidth v0.0.19 // indirect +github.com/minio/crc64nvme v1.1.1 // indirect +github.com/minio/md5-simd v1.1.2 // indirect +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect +github.com/modern-go/reflect2 v1.0.2 // indirect +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect +github.com/muesli/cancelreader v0.2.2 // indirect +github.com/muesli/termenv v0.16.0 // indirect +github.com/ncruces/go-strftime v1.0.0 // indirect +github.com/pelletier/go-toml/v2 v2.2.4 // indirect +github.com/philhofer/fwd v1.2.0 // indirect +github.com/pjbgf/sha1cd v0.5.0 // indirect +github.com/pmezard/go-difflib v1.0.0 // indirect +github.com/quic-go/qpack v0.6.0 // indirect +github.com/quic-go/quic-go v0.59.0 // indirect +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect +github.com/rivo/uniseg v0.4.7 // indirect +github.com/rs/xid v1.6.0 // indirect +github.com/sergi/go-diff v1.4.0 // indirect +github.com/tidwall/match v1.1.1 // indirect +github.com/tidwall/pretty v1.2.0 // indirect +github.com/tinylib/msgp v1.6.1 // indirect +github.com/twitchyliquid64/golang-asm v0.15.1 // indirect +github.com/ugorji/go/codec v1.3.1 // indirect +github.com/x448/float16 v0.8.4 // indirect +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect +go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect +go.yaml.in/yaml/v3 v3.0.4 // indirect +golang.org/x/arch v0.22.0 // indirect +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect +golang.org/x/sys v0.41.0 // indirect +google.golang.org/protobuf v1.36.10 // indirect +modernc.org/libc v1.67.6 // indirect +modernc.org/mathutil v1.7.1 // indirect +modernc.org/memory v1.11.0 // indirect ) - -replace github.com/KooshaPari/phenotype-go-auth => ../../../template-commons/phenotype-go-auth diff --git a/pkg/llmproxy/api/handlers/management/config_lists.go b/pkg/llmproxy/api/handlers/management/config_lists.go index d0074f17bb..570f571481 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 f2434e4e60..4b888ee8e4 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/v7/pkg/llmproxy/buildinfo" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/usage" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/api/modules/amp/amp.go b/pkg/llmproxy/api/modules/amp/amp.go index e2886cd0cf..770290e089 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/v7/pkg/llmproxy/api/modules" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkaccess "github.com/kooshapari/CLIProxyAPI/v7/sdk/access" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/api/modules/amp/model_mapping.go b/pkg/llmproxy/api/modules/amp/model_mapping.go index daceaaf5af..697949cdd0 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/api/modules/amp/secret.go b/pkg/llmproxy/api/modules/amp/secret.go index bd3a1c28c5..c1984e785e 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/api/modules/modules.go b/pkg/llmproxy/api/modules/modules.go index 83cf94e46f..5172de8d6c 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/sdk/api/handlers" ) diff --git a/pkg/llmproxy/api/server.go b/pkg/llmproxy/api/server.go index cbe4e1276d..1a27053193 100644 --- a/pkg/llmproxy/api/server.go +++ b/pkg/llmproxy/api/server.go @@ -20,17 +20,17 @@ import ( "unsafe" "github.com/gin-gonic/gin" - "github.com/kooshapari/CLIProxyAPI/v7/internal/access" - managementHandlers "github.com/kooshapari/CLIProxyAPI/v7/internal/api/handlers/management" - "github.com/kooshapari/CLIProxyAPI/v7/internal/api/middleware" - "github.com/kooshapari/CLIProxyAPI/v7/internal/api/modules" - ampmodule "github.com/kooshapari/CLIProxyAPI/v7/internal/api/modules/amp" - "github.com/kooshapari/CLIProxyAPI/v7/internal/auth/kiro" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" - "github.com/kooshapari/CLIProxyAPI/v7/internal/logging" - "github.com/kooshapari/CLIProxyAPI/v7/internal/managementasset" - "github.com/kooshapari/CLIProxyAPI/v7/internal/usage" - "github.com/kooshapari/CLIProxyAPI/v7/internal/util" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/access" + managementHandlers "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/api/handlers/management" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/api/middleware" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/api/modules" + ampmodule "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/api/modules/amp" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/kiro" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/logging" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/managementasset" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/usage" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" sdkaccess "github.com/kooshapari/CLIProxyAPI/v7/sdk/access" "github.com/kooshapari/CLIProxyAPI/v7/sdk/api/handlers" "github.com/kooshapari/CLIProxyAPI/v7/sdk/api/handlers/claude" diff --git a/pkg/llmproxy/auth/antigravity/auth.go b/pkg/llmproxy/auth/antigravity/auth.go index ae7be895b7..1aa387d9c1 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/claude/utls_transport.go b/pkg/llmproxy/auth/claude/utls_transport.go index 3b7623bcb1..12ef0a8b88 100644 --- a/pkg/llmproxy/auth/claude/utls_transport.go +++ b/pkg/llmproxy/auth/claude/utls_transport.go @@ -10,7 +10,7 @@ import ( pkgconfig "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" tls "github.com/refraction-networking/utls" - pkgconfig "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + pkgconfig "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" "golang.org/x/net/http2" "golang.org/x/net/proxy" diff --git a/pkg/llmproxy/auth/copilot/oauth.go b/pkg/llmproxy/auth/copilot/oauth.go index 0eca3c2ed4..94c1daba6f 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/diff/config_diff.go b/pkg/llmproxy/auth/diff/config_diff.go index f88a693044..f523a5abef 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // BuildConfigChangeDetails computes a redacted, human-readable list of config changes. diff --git a/pkg/llmproxy/auth/diff/diff_generated.go b/pkg/llmproxy/auth/diff/diff_generated.go index 48c68e7cb6..b26b1b634f 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 2702037f45..7972d2d461 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) const modelHashSalt = "auth-model-hash:v1" diff --git a/pkg/llmproxy/auth/diff/models_summary.go b/pkg/llmproxy/auth/diff/models_summary.go index f63c3c54b5..246018aa1c 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) type GeminiModelsSummary struct { diff --git a/pkg/llmproxy/auth/diff/oauth_excluded.go b/pkg/llmproxy/auth/diff/oauth_excluded.go index 42154712f6..f5654ca06b 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) type ExcludedModelsSummary struct { diff --git a/pkg/llmproxy/auth/diff/oauth_model_alias.go b/pkg/llmproxy/auth/diff/oauth_model_alias.go index 193f3746a0..f47bf6cdc2 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) type OAuthModelAliasSummary struct { diff --git a/pkg/llmproxy/auth/diff/openai_compat.go b/pkg/llmproxy/auth/diff/openai_compat.go index 2d7d3b1e08..1b8cf786d5 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // DiffOpenAICompatibility produces human-readable change descriptions. diff --git a/pkg/llmproxy/auth/gemini/gemini_auth.go b/pkg/llmproxy/auth/gemini/gemini_auth.go index 3dece9b678..ec89da4290 100644 --- a/pkg/llmproxy/auth/gemini/gemini_auth.go +++ b/pkg/llmproxy/auth/gemini/gemini_auth.go @@ -17,7 +17,7 @@ import ( "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/base" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/codex" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/browser" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/auth/kiro/aws_auth.go b/pkg/llmproxy/auth/kiro/aws_auth.go index ce49ed4c45..36455bf810 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/auth/kiro/background_refresh.go b/pkg/llmproxy/auth/kiro/background_refresh.go index 01c2e419b5..5ae1c4b971 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 91e3dad26c..087217fb8d 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 55097432b9..f8097be94b 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 86a185c7cd..fe75a7ba1a 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 f297382a7e..340b713dee 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 7e18ec4627..ac121a1d09 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/v7/pkg/llmproxy/browser" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 90d5e43fde..26a20fcef0 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/v7/pkg/llmproxy/browser" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 097d2fbffa..86826d27c3 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" ) diff --git a/pkg/llmproxy/auth/qwen/qwen_auth.go b/pkg/llmproxy/auth/qwen/qwen_auth.go index e09eba5826..2517556a9f 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 efbb3e7fa3..c46271b3c0 100644 --- a/pkg/llmproxy/auth/qwen/qwen_token.go +++ b/pkg/llmproxy/auth/qwen/qwen_token.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - "github.com/KooshaPari/phenotype-go-kit/pkg/auth" + "github.com/KooshaPari/phenotype-go-auth" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" ) diff --git a/pkg/llmproxy/auth/synthesizer/config.go b/pkg/llmproxy/auth/synthesizer/config.go index c3d34c2655..aa04179aca 100644 --- a/pkg/llmproxy/auth/synthesizer/config.go +++ b/pkg/llmproxy/auth/synthesizer/config.go @@ -13,7 +13,7 @@ import ( "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/diff" kiroauth "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/kiro" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/cursorstorage" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/auth/synthesizer/context.go b/pkg/llmproxy/auth/synthesizer/context.go index 33b3004474..d0cfa2ce31 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // SynthesisContext provides the context needed for auth synthesis. diff --git a/pkg/llmproxy/auth/synthesizer/helpers.go b/pkg/llmproxy/auth/synthesizer/helpers.go index cc2535b58a..bf4b4006d9 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/v7/pkg/llmproxy/auth/diff" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/auth/synthesizer/synthesizer_generated.go b/pkg/llmproxy/auth/synthesizer/synthesizer_generated.go index d8a5a74552..6b8877b111 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // getDedicatedProviderEntries returns the config entries for a dedicated provider. diff --git a/pkg/llmproxy/auth/vertex/vertex_credentials.go b/pkg/llmproxy/auth/vertex/vertex_credentials.go index 705fda59e1..5be69cf598 100644 --- a/pkg/llmproxy/auth/vertex/vertex_credentials.go +++ b/pkg/llmproxy/auth/vertex/vertex_credentials.go @@ -7,12 +7,14 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" log "github.com/sirupsen/logrus" ) +// authBaseDir is the root directory for all Vertex credential files. +const authBaseDir = "vertex" + // VertexCredentialStorage stores the service account JSON for Vertex AI access. // The content is persisted verbatim under the "service_account" key, together with // helper fields for project, location and email to improve logging and discovery. diff --git a/pkg/llmproxy/cmd/anthropic_login.go b/pkg/llmproxy/cmd/anthropic_login.go index 9efde5ef0f..1140c8aecd 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/v7/pkg/llmproxy/auth/claude" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/antigravity_login.go b/pkg/llmproxy/cmd/antigravity_login.go index 569781c7a2..900e9924f5 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/cursor_login.go b/pkg/llmproxy/cmd/cursor_login.go index 864c42641c..d2debf795a 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/generic_apikey_login.go b/pkg/llmproxy/cmd/generic_apikey_login.go index e651387bfd..0aadec7f6b 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 64054c9ac2..32ec8982ef 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/iflow_cookie.go b/pkg/llmproxy/cmd/iflow_cookie.go index 610eafbf95..ed6f5f0426 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/v7/pkg/llmproxy/auth/iflow" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // DoIFlowCookieAuth performs the iFlow cookie-based authentication. diff --git a/pkg/llmproxy/cmd/iflow_login.go b/pkg/llmproxy/cmd/iflow_login.go index ea30939457..6e7516b557 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/kilo_login.go b/pkg/llmproxy/cmd/kilo_login.go index 652e191f8b..3b1d0c196e 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/kimi_login.go b/pkg/llmproxy/cmd/kimi_login.go index bad200e6ab..c8b7717605 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/kiro_login.go b/pkg/llmproxy/cmd/kiro_login.go index 290c76ad0a..6115fb196f 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/login.go b/pkg/llmproxy/cmd/login.go index d10ea548f6..8d63527342 100644 --- a/pkg/llmproxy/cmd/login.go +++ b/pkg/llmproxy/cmd/login.go @@ -18,7 +18,7 @@ import ( "time" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/gemini" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/interfaces" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/cmd/minimax_login.go b/pkg/llmproxy/cmd/minimax_login.go index 734184a1eb..134cd1a0da 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 6d46f6bfdd..a641c50ecd 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/v7/internal/auth/codex" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/codex" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/openai_login.go b/pkg/llmproxy/cmd/openai_login.go index d803b16b53..7806f68990 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/v7/pkg/llmproxy/auth/codex" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/qwen_login.go b/pkg/llmproxy/cmd/qwen_login.go index 7b4a1e3c93..443f5eef08 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/roo_login.go b/pkg/llmproxy/cmd/roo_login.go index 4f57f144e5..ed6a2d6226 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/run.go b/pkg/llmproxy/cmd/run.go index 097a23b421..7d43963996 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/v7/internal/api" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + internalapi "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/api" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/setup.go b/pkg/llmproxy/cmd/setup.go index 5cab46c93f..80d35ccaed 100644 --- a/pkg/llmproxy/cmd/setup.go +++ b/pkg/llmproxy/cmd/setup.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" ) diff --git a/pkg/llmproxy/cmd/thegent_login.go b/pkg/llmproxy/cmd/thegent_login.go index d0f76ac6c6..3cbdb63605 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/cmd/vertex_import.go b/pkg/llmproxy/cmd/vertex_import.go index 890dbaf320..23811bd201 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/v7/pkg/llmproxy/auth/vertex" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/config/config_io.go b/pkg/llmproxy/config/config_io.go deleted file mode 100644 index 585ac847bf..0000000000 --- a/pkg/llmproxy/config/config_io.go +++ /dev/null @@ -1,295 +0,0 @@ -package config - -import ( - "errors" - "fmt" - "os" - "strings" - "syscall" - - log "github.com/sirupsen/logrus" - "golang.org/x/crypto/bcrypt" - "gopkg.in/yaml.v3" -) - -const ( - DefaultPanelGitHubRepository = "https://github.com/router-for-me/Cli-Proxy-API-Management-Center" - DefaultPprofAddr = "127.0.0.1:8316" -) - -// LoadConfig reads a YAML configuration file from the given path, -// unmarshals it into a Config struct, applies environment variable overrides, -// and returns it. -func LoadConfig(configFile string) (*Config, error) { - return LoadConfigOptional(configFile, false) -} - -// LoadConfigOptional reads YAML from configFile. -// If optional is true and the file is missing, it returns an empty Config. -func LoadConfigOptional(configFile string, optional bool) (*Config, error) { - // Read the entire configuration file into memory. - data, err := os.ReadFile(configFile) - if err != nil { - if optional { - if os.IsNotExist(err) || errors.Is(err, syscall.EISDIR) { - // Missing and optional: return empty config. - return &Config{}, nil - } - } - if errors.Is(err, syscall.EISDIR) { - return nil, fmt.Errorf( - "failed to read config file: %w (config path %q is a directory; pass a YAML file path such as /CLIProxyAPI/config.yaml)", - err, - configFile, - ) - } - return nil, fmt.Errorf("failed to read config file: %w", err) - } - - // In cloud deploy mode (optional=true), if file is empty, return empty config. - if optional && len(data) == 0 { - return &Config{}, nil - } - - // Unmarshal the YAML data into the Config struct. - var cfg Config - // Set defaults before unmarshal - cfg.Host = "" - cfg.LoggingToFile = false - cfg.LogsMaxTotalSizeMB = 0 - cfg.ErrorLogsMaxFiles = 10 - cfg.UsageStatisticsEnabled = false - cfg.DisableCooling = false - cfg.Pprof.Enable = false - cfg.Pprof.Addr = DefaultPprofAddr - cfg.AmpCode.RestrictManagementToLocalhost = false - cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository - cfg.IncognitoBrowser = false - - if err = yaml.Unmarshal(data, &cfg); err != nil { - if optional { - // In cloud deploy mode, if YAML parsing fails, return empty config instead of error. - return &Config{}, nil - } - return nil, fmt.Errorf("failed to parse config file: %w", err) - } - - // Hash remote management key if plaintext is detected - if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) { - hashed, errHash := hashSecret(cfg.RemoteManagement.SecretKey) - if errHash != nil { - return nil, fmt.Errorf("failed to hash remote management key: %w", errHash) - } - cfg.RemoteManagement.SecretKey = hashed - - // Persist the hashed value back to the config file - _ = SaveConfigPreserveCommentsUpdateNestedScalar(configFile, []string{"remote-management", "secret-key"}, hashed) - } - - cfg.RemoteManagement.PanelGitHubRepository = strings.TrimSpace(cfg.RemoteManagement.PanelGitHubRepository) - if cfg.RemoteManagement.PanelGitHubRepository == "" { - cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository - } - - cfg.Pprof.Addr = strings.TrimSpace(cfg.Pprof.Addr) - if cfg.Pprof.Addr == "" { - cfg.Pprof.Addr = DefaultPprofAddr - } - - if cfg.LogsMaxTotalSizeMB < 0 { - cfg.LogsMaxTotalSizeMB = 0 - } - - if cfg.ErrorLogsMaxFiles < 0 { - cfg.ErrorLogsMaxFiles = 10 - } - - // Sanitize configurations - cfg.SanitizeGeminiKeys() - cfg.SanitizeVertexCompatKeys() - cfg.SanitizeCodexKeys() - cfg.SanitizeClaudeKeys() - cfg.SanitizeKiroKeys() - cfg.SanitizeCursorKeys() - cfg.SanitizeGeneratedProviders() - cfg.SanitizeOpenAICompatibility() - - // Inject premade providers from environment - cfg.InjectPremadeFromEnv() - - // Normalize OAuth settings - cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels) - cfg.SanitizeOAuthModelAlias() - cfg.SanitizeOAuthUpstream() - - // Validate payload rules - cfg.SanitizePayloadRules() - - // Apply environment variable overrides - cfg.ApplyEnvOverrides() - - return &cfg, nil -} - -// InjectPremadeFromEnv injects premade providers if their environment variables are set. -func (cfg *Config) InjectPremadeFromEnv() { - for _, spec := range GetPremadeProviders() { - cfg.injectPremadeFromSpec(spec.Name, spec) - } -} - -func (cfg *Config) injectPremadeFromSpec(name string, spec ProviderSpec) { - // Check if already in config - for _, compat := range cfg.OpenAICompatibility { - if strings.ToLower(compat.Name) == name { - return - } - } - - // Check env vars - var apiKey string - for _, ev := range spec.EnvVars { - if val := os.Getenv(ev); val != "" { - apiKey = val - break - } - } - if apiKey == "" { - return - } - - // Inject virtual entry - entry := OpenAICompatibility{ - Name: name, - BaseURL: spec.BaseURL, - APIKeyEntries: []OpenAICompatibilityAPIKey{ - {APIKey: apiKey}, - }, - Models: spec.DefaultModels, - } - cfg.OpenAICompatibility = append(cfg.OpenAICompatibility, entry) -} - -// looksLikeBcrypt returns true if the provided string appears to be a bcrypt hash. -func looksLikeBcrypt(s string) bool { - return len(s) > 4 && (s[:4] == "$2a$" || s[:4] == "$2b$" || s[:4] == "$2y$") -} - -// hashSecret hashes the given secret using bcrypt. -func hashSecret(secret string) (string, error) { - hashedBytes, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost) - if err != nil { - return "", err - } - return string(hashedBytes), nil -} - -// ApplyEnvOverrides applies environment variable overrides to the configuration. -func (cfg *Config) ApplyEnvOverrides() { - if cfg == nil { - return - } - - // CLIPROXY_HOST - if val := os.Getenv("CLIPROXY_HOST"); val != "" { - cfg.Host = val - log.WithField("host", val).Info("Applied CLIPROXY_HOST override") - } - - // CLIPROXY_PORT - if val := os.Getenv("CLIPROXY_PORT"); val != "" { - if port, err := parseIntEnvVar(val); err == nil && port > 0 && port <= 65535 { - cfg.Port = port - log.WithField("port", port).Info("Applied CLIPROXY_PORT override") - } else { - log.WithField("value", val).Warn("Invalid CLIPROXY_PORT value, ignoring") - } - } - - // CLIPROXY_SECRET_KEY - if val := os.Getenv("CLIPROXY_SECRET_KEY"); val != "" { - if !looksLikeBcrypt(val) { - hashed, err := hashSecret(val) - if err != nil { - log.WithError(err).Warn("Failed to hash CLIPROXY_SECRET_KEY, using as-is") - cfg.RemoteManagement.SecretKey = val - } else { - cfg.RemoteManagement.SecretKey = hashed - } - } else { - cfg.RemoteManagement.SecretKey = val - } - log.Info("Applied CLIPROXY_SECRET_KEY override") - } - - // CLIPROXY_ALLOW_REMOTE - if val := os.Getenv("CLIPROXY_ALLOW_REMOTE"); val != "" { - if parsed, err := parseBoolEnvVar(val); err == nil { - cfg.RemoteManagement.AllowRemote = parsed - log.WithField("allow-remote", parsed).Info("Applied CLIPROXY_ALLOW_REMOTE override") - } else { - log.WithField("value", val).Warn("Invalid CLIPROXY_ALLOW_REMOTE value, ignoring") - } - } - - // CLIPROXY_DEBUG - if val := os.Getenv("CLIPROXY_DEBUG"); val != "" { - if parsed, err := parseBoolEnvVar(val); err == nil { - cfg.Debug = parsed - log.WithField("debug", parsed).Info("Applied CLIPROXY_DEBUG override") - } else { - log.WithField("value", val).Warn("Invalid CLIPROXY_DEBUG value, ignoring") - } - } - - // CLIPROXY_ROUTING_STRATEGY - if val := os.Getenv("CLIPROXY_ROUTING_STRATEGY"); val != "" { - normalized := strings.ToLower(strings.TrimSpace(val)) - switch normalized { - case "round-robin", "roundrobin", "rr": - cfg.Routing.Strategy = "round-robin" - log.Info("Applied CLIPROXY_ROUTING_STRATEGY override: round-robin") - case "fill-first", "fillfirst", "ff": - cfg.Routing.Strategy = "fill-first" - log.Info("Applied CLIPROXY_ROUTING_STRATEGY override: fill-first") - default: - log.WithField("value", val).Warn("Invalid CLIPROXY_ROUTING_STRATEGY value, ignoring") - } - } - - // CLIPROXY_API_KEYS - if val := os.Getenv("CLIPROXY_API_KEYS"); val != "" { - keys := strings.Split(val, ",") - cfg.APIKeys = make([]string, 0, len(keys)) - for _, key := range keys { - trimmed := strings.TrimSpace(key) - if trimmed != "" { - cfg.APIKeys = append(cfg.APIKeys, trimmed) - } - } - if len(cfg.APIKeys) > 0 { - log.WithField("count", len(cfg.APIKeys)).Info("Applied CLIPROXY_API_KEYS override") - } - } -} - -// parseIntEnvVar parses an integer from an environment variable string. -func parseIntEnvVar(val string) (int, error) { - val = strings.TrimSpace(val) - var result int - _, err := fmt.Sscanf(val, "%d", &result) - return result, err -} - -// parseBoolEnvVar parses a boolean from an environment variable string. -func parseBoolEnvVar(val string) (bool, error) { - val = strings.ToLower(strings.TrimSpace(val)) - switch val { - case "true", "yes", "1", "on": - return true, nil - case "false", "no", "0", "off": - return false, nil - default: - return false, fmt.Errorf("invalid boolean value: %s", val) - } -} diff --git a/pkg/llmproxy/config/config_persistence.go b/pkg/llmproxy/config/config_persistence.go deleted file mode 100644 index 87473b7341..0000000000 --- a/pkg/llmproxy/config/config_persistence.go +++ /dev/null @@ -1,670 +0,0 @@ -package config - -import ( - "bytes" - "fmt" - "os" - "strings" - - "gopkg.in/yaml.v3" -) - -// SaveConfigPreserveComments writes the config back to YAML while preserving existing comments -// and key ordering by loading the original file into a yaml.Node tree and updating values in-place. -func SaveConfigPreserveComments(configFile string, cfg *Config) error { - persistCfg := cfg - // Load original YAML as a node tree to preserve comments and ordering. - data, err := os.ReadFile(configFile) - if err != nil { - return err - } - - var original yaml.Node - if err = yaml.Unmarshal(data, &original); err != nil { - return err - } - if original.Kind != yaml.DocumentNode || len(original.Content) == 0 { - return fmt.Errorf("invalid yaml document structure") - } - if original.Content[0] == nil || original.Content[0].Kind != yaml.MappingNode { - return fmt.Errorf("expected root mapping node") - } - - // Marshal the current cfg to YAML, then unmarshal to a yaml.Node we can merge from. - rendered, err := yaml.Marshal(persistCfg) - if err != nil { - return err - } - var generated yaml.Node - if err = yaml.Unmarshal(rendered, &generated); err != nil { - return err - } - if generated.Kind != yaml.DocumentNode || len(generated.Content) == 0 || generated.Content[0] == nil { - return fmt.Errorf("invalid generated yaml structure") - } - if generated.Content[0].Kind != yaml.MappingNode { - return fmt.Errorf("expected generated root mapping node") - } - - // Remove deprecated sections before merging back the sanitized config. - removeLegacyAuthBlock(original.Content[0]) - removeLegacyOpenAICompatAPIKeys(original.Content[0]) - removeLegacyAmpKeys(original.Content[0]) - removeLegacyGenerativeLanguageKeys(original.Content[0]) - - pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-excluded-models") - pruneMappingToGeneratedKeys(original.Content[0], generated.Content[0], "oauth-model-alias") - - // Merge generated into original in-place, preserving comments/order of existing nodes. - mergeMappingPreserve(original.Content[0], generated.Content[0]) - normalizeCollectionNodeStyles(original.Content[0]) - - // Write back. - f, err := os.Create(configFile) - if err != nil { - return err - } - defer func() { _ = f.Close() }() - var buf bytes.Buffer - enc := yaml.NewEncoder(&buf) - enc.SetIndent(2) - if err = enc.Encode(&original); err != nil { - _ = enc.Close() - return err - } - if err = enc.Close(); err != nil { - return err - } - data = NormalizeCommentIndentation(buf.Bytes()) - _, err = f.Write(data) - return err -} - -// SaveConfigPreserveCommentsUpdateNestedScalar updates a nested scalar key path like ["a","b"] -// while preserving comments and positions. -func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []string, value string) error { - data, err := os.ReadFile(configFile) - if err != nil { - return err - } - var root yaml.Node - if err = yaml.Unmarshal(data, &root); err != nil { - return err - } - if root.Kind != yaml.DocumentNode || len(root.Content) == 0 { - return fmt.Errorf("invalid yaml document structure") - } - node := root.Content[0] - // descend mapping nodes following path - for i, key := range path { - if i == len(path)-1 { - // set final scalar - v := getOrCreateMapValue(node, key) - v.Kind = yaml.ScalarNode - v.Tag = "!!str" - v.Value = value - } else { - next := getOrCreateMapValue(node, key) - if next.Kind != yaml.MappingNode { - next.Kind = yaml.MappingNode - next.Tag = "!!map" - } - node = next - } - } - f, err := os.Create(configFile) - if err != nil { - return err - } - defer func() { _ = f.Close() }() - var buf bytes.Buffer - enc := yaml.NewEncoder(&buf) - enc.SetIndent(2) - if err = enc.Encode(&root); err != nil { - _ = enc.Close() - return err - } - if err = enc.Close(); err != nil { - return err - } - data = NormalizeCommentIndentation(buf.Bytes()) - _, err = f.Write(data) - return err -} - -// NormalizeCommentIndentation removes indentation from standalone YAML comment lines to keep them left aligned. -func NormalizeCommentIndentation(data []byte) []byte { - lines := bytes.Split(data, []byte("\n")) - changed := false - for i, line := range lines { - trimmed := bytes.TrimLeft(line, " \t") - if len(trimmed) == 0 || trimmed[0] != '#' { - continue - } - if len(trimmed) == len(line) { - continue - } - lines[i] = append([]byte(nil), trimmed...) - changed = true - } - if !changed { - return data - } - return bytes.Join(lines, []byte("\n")) -} - -// getOrCreateMapValue finds the value node for a given key in a mapping node. -// If not found, it appends a new key/value pair and returns the new value node. -func getOrCreateMapValue(mapNode *yaml.Node, key string) *yaml.Node { - if mapNode.Kind != yaml.MappingNode { - mapNode.Kind = yaml.MappingNode - mapNode.Tag = "!!map" - mapNode.Content = nil - } - for i := 0; i+1 < len(mapNode.Content); i += 2 { - k := mapNode.Content[i] - if k.Value == key { - return mapNode.Content[i+1] - } - } - // append new key/value - mapNode.Content = append(mapNode.Content, &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: key}) - val := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: ""} - mapNode.Content = append(mapNode.Content, val) - return val -} - -// mergeMappingPreserve merges keys from src into dst mapping node while preserving -// key order and comments of existing keys in dst. -func mergeMappingPreserve(dst, src *yaml.Node, path ...[]string) { - var currentPath []string - if len(path) > 0 { - currentPath = path[0] - } - - if dst == nil || src == nil { - return - } - if dst.Kind != yaml.MappingNode || src.Kind != yaml.MappingNode { - copyNodeShallow(dst, src) - return - } - for i := 0; i+1 < len(src.Content); i += 2 { - sk := src.Content[i] - sv := src.Content[i+1] - idx := findMapKeyIndex(dst, sk.Value) - childPath := appendPath(currentPath, sk.Value) - if idx >= 0 { - // Merge into existing value node (always update, even to zero values) - dv := dst.Content[idx+1] - mergeNodePreserve(dv, sv, childPath) - } else { - // New key: only add if value is non-zero and not a known default - candidate := deepCopyNode(sv) - pruneKnownDefaultsInNewNode(childPath, candidate) - if isKnownDefaultValue(childPath, candidate) { - continue - } - dst.Content = append(dst.Content, deepCopyNode(sk), candidate) - } - } -} - -// mergeNodePreserve merges src into dst for scalars, mappings and sequences while -// reusing destination nodes to keep comments and anchors. -func mergeNodePreserve(dst, src *yaml.Node, path ...[]string) { - var currentPath []string - if len(path) > 0 { - currentPath = path[0] - } - - if dst == nil || src == nil { - return - } - switch src.Kind { - case yaml.MappingNode: - if dst.Kind != yaml.MappingNode { - copyNodeShallow(dst, src) - } - mergeMappingPreserve(dst, src, currentPath) - case yaml.SequenceNode: - // Preserve explicit null style if dst was null and src is empty sequence - if dst.Kind == yaml.ScalarNode && dst.Tag == "!!null" && len(src.Content) == 0 { - return - } - if dst.Kind != yaml.SequenceNode { - dst.Kind = yaml.SequenceNode - dst.Tag = "!!seq" - dst.Content = nil - } - reorderSequenceForMerge(dst, src) - // Update elements in place - minContent := len(dst.Content) - if len(src.Content) < minContent { - minContent = len(src.Content) - } - for i := 0; i < minContent; i++ { - if dst.Content[i] == nil { - dst.Content[i] = deepCopyNode(src.Content[i]) - continue - } - mergeNodePreserve(dst.Content[i], src.Content[i], currentPath) - if dst.Content[i] != nil && src.Content[i] != nil && - dst.Content[i].Kind == yaml.MappingNode && src.Content[i].Kind == yaml.MappingNode { - pruneMissingMapKeys(dst.Content[i], src.Content[i]) - } - } - // Append any extra items from src - for i := len(dst.Content); i < len(src.Content); i++ { - dst.Content = append(dst.Content, deepCopyNode(src.Content[i])) - } - // Truncate if dst has extra items not in src - if len(src.Content) < len(dst.Content) { - dst.Content = dst.Content[:len(src.Content)] - } - case yaml.ScalarNode, yaml.AliasNode: - // For scalars, update Tag and Value but keep Style from dst - dst.Kind = src.Kind - dst.Tag = src.Tag - dst.Value = src.Value - default: - // Fallback: replace shallowly - copyNodeShallow(dst, src) - } -} - -// findMapKeyIndex returns the index of key node in dst mapping. -// Returns -1 when not found. -func findMapKeyIndex(mapNode *yaml.Node, key string) int { - if mapNode == nil || mapNode.Kind != yaml.MappingNode { - return -1 - } - for i := 0; i+1 < len(mapNode.Content); i += 2 { - if mapNode.Content[i] != nil && mapNode.Content[i].Value == key { - return i - } - } - return -1 -} - -// appendPath appends a key to the path. -func appendPath(path []string, key string) []string { - if len(path) == 0 { - return []string{key} - } - newPath := make([]string, checkedPathLengthPlusOne(len(path))) - copy(newPath, path) - newPath[len(path)] = key - return newPath -} - -func checkedPathLengthPlusOne(pathLen int) int { - maxInt := int(^uint(0) >> 1) - if pathLen < 0 || pathLen >= maxInt { - panic(fmt.Sprintf("path length overflow: %d", pathLen)) - } - return pathLen + 1 -} - -// isKnownDefaultValue returns true if the given node represents a known default value. -func isKnownDefaultValue(path []string, node *yaml.Node) bool { - if isZeroValueNode(node) { - return true - } - - if len(path) == 0 { - return false - } - - fullPath := strings.Join(path, ".") - - // Check string defaults - if node.Kind == yaml.ScalarNode && node.Tag == "!!str" { - switch fullPath { - case "pprof.addr": - return node.Value == DefaultPprofAddr - case "remote-management.panel-github-repository": - return node.Value == DefaultPanelGitHubRepository - case "routing.strategy": - return node.Value == "round-robin" - } - } - - // Check integer defaults - if node.Kind == yaml.ScalarNode && node.Tag == "!!int" { - switch fullPath { - case "error-logs-max-files": - return node.Value == "10" - } - } - - return false -} - -// pruneKnownDefaultsInNewNode removes default-valued descendants from a new node. -func pruneKnownDefaultsInNewNode(path []string, node *yaml.Node) { - if node == nil { - return - } - - switch node.Kind { - case yaml.MappingNode: - filtered := make([]*yaml.Node, 0, len(node.Content)) - for i := 0; i+1 < len(node.Content); i += 2 { - keyNode := node.Content[i] - valueNode := node.Content[i+1] - if keyNode == nil || valueNode == nil { - continue - } - - childPath := appendPath(path, keyNode.Value) - if isKnownDefaultValue(childPath, valueNode) { - continue - } - - pruneKnownDefaultsInNewNode(childPath, valueNode) - if (valueNode.Kind == yaml.MappingNode || valueNode.Kind == yaml.SequenceNode) && - len(valueNode.Content) == 0 { - continue - } - - filtered = append(filtered, keyNode, valueNode) - } - node.Content = filtered - case yaml.SequenceNode: - for _, child := range node.Content { - pruneKnownDefaultsInNewNode(path, child) - } - } -} - -// isZeroValueNode returns true if the YAML node represents a zero/default value. -func isZeroValueNode(node *yaml.Node) bool { - if node == nil { - return true - } - switch node.Kind { - case yaml.ScalarNode: - switch node.Tag { - case "!!bool": - return node.Value == "false" - case "!!int", "!!float": - return node.Value == "0" || node.Value == "0.0" - case "!!str": - return node.Value == "" - case "!!null": - return true - } - case yaml.SequenceNode: - if len(node.Content) == 0 { - return true - } - // Check if all elements are zero values - for _, child := range node.Content { - if !isZeroValueNode(child) { - return false - } - } - return true - case yaml.MappingNode: - if len(node.Content) == 0 { - return true - } - // Check if all values are zero values - for i := 1; i < len(node.Content); i += 2 { - if !isZeroValueNode(node.Content[i]) { - return false - } - } - return true - } - return false -} - -// deepCopyNode creates a deep copy of a yaml.Node graph. -func deepCopyNode(n *yaml.Node) *yaml.Node { - if n == nil { - return nil - } - cp := *n - if len(n.Content) > 0 { - cp.Content = make([]*yaml.Node, len(n.Content)) - for i := range n.Content { - cp.Content[i] = deepCopyNode(n.Content[i]) - } - } - return &cp -} - -// copyNodeShallow copies type/tag/value from src to dst. -func copyNodeShallow(dst, src *yaml.Node) { - if dst == nil || src == nil { - return - } - dst.Kind = src.Kind - dst.Tag = src.Tag - dst.Value = src.Value - // Replace content with deep copy from src - if len(src.Content) > 0 { - dst.Content = make([]*yaml.Node, len(src.Content)) - for i := range src.Content { - dst.Content[i] = deepCopyNode(src.Content[i]) - } - } else { - dst.Content = nil - } -} - -func reorderSequenceForMerge(dst, src *yaml.Node) { - if dst == nil || src == nil { - return - } - if len(dst.Content) == 0 { - return - } - if len(src.Content) == 0 { - return - } - original := append([]*yaml.Node(nil), dst.Content...) - used := make([]bool, len(original)) - ordered := make([]*yaml.Node, len(src.Content)) - for i := range src.Content { - if idx := matchSequenceElement(original, used, src.Content[i]); idx >= 0 { - ordered[i] = original[idx] - used[idx] = true - } - } - dst.Content = ordered -} - -func matchSequenceElement(original []*yaml.Node, used []bool, target *yaml.Node) int { - if target == nil { - return -1 - } - switch target.Kind { - case yaml.MappingNode: - id := sequenceElementIdentity(target) - if id != "" { - for i := range original { - if used[i] || original[i] == nil || original[i].Kind != yaml.MappingNode { - continue - } - if sequenceElementIdentity(original[i]) == id { - return i - } - } - } - case yaml.ScalarNode: - for i := range original { - if used[i] || original[i] == nil || original[i].Kind != yaml.ScalarNode { - continue - } - if original[i].Value == target.Value { - return i - } - } - } - return -1 -} - -func sequenceElementIdentity(node *yaml.Node) string { - if node == nil || node.Kind != yaml.MappingNode { - return "" - } - // Try to find a unique identifier (e.g., "name" or "id" field) - for i := 0; i+1 < len(node.Content); i += 2 { - key := node.Content[i] - value := node.Content[i+1] - if key == nil || value == nil { - continue - } - if key.Value == "name" && value.Kind == yaml.ScalarNode { - return value.Value - } - } - return mappingScalarValue(node, "api-key") -} - -func mappingScalarValue(node *yaml.Node, key string) string { - if node == nil || node.Kind != yaml.MappingNode { - return "" - } - for i := 0; i+1 < len(node.Content); i += 2 { - k := node.Content[i] - v := node.Content[i+1] - if k != nil && k.Value == key && v != nil && v.Kind == yaml.ScalarNode { - return v.Value - } - } - return "" -} - -func nodesStructurallyEqual(a, b *yaml.Node) bool { - if (a == nil) != (b == nil) { - return false - } - if a == nil { - return true - } - if a.Kind != b.Kind || a.Tag != b.Tag { - return false - } - if a.Kind == yaml.ScalarNode { - return a.Value == b.Value - } - if len(a.Content) != len(b.Content) { - return false - } - for i := range a.Content { - if !nodesStructurallyEqual(a.Content[i], b.Content[i]) { - return false - } - } - return true -} - -func removeMapKey(mapNode *yaml.Node, key string) { - if mapNode == nil || mapNode.Kind != yaml.MappingNode { - return - } - newContent := make([]*yaml.Node, 0, len(mapNode.Content)) - for i := 0; i+1 < len(mapNode.Content); i += 2 { - if mapNode.Content[i] != nil && mapNode.Content[i].Value == key { - continue - } - newContent = append(newContent, mapNode.Content[i], mapNode.Content[i+1]) - } - mapNode.Content = newContent -} - -func pruneMappingToGeneratedKeys(dstRoot, srcRoot *yaml.Node, key string) { - // Find the key in dst and src - dstIdx := findMapKeyIndex(dstRoot, key) - srcIdx := findMapKeyIndex(srcRoot, key) - - if dstIdx < 0 { - return - } - if srcIdx < 0 { - removeMapKey(dstRoot, key) - return - } -} - -func pruneMissingMapKeys(dstMap, srcMap *yaml.Node) { - if dstMap == nil || srcMap == nil { - return - } - if dstMap.Kind != yaml.MappingNode || srcMap.Kind != yaml.MappingNode { - return - } - - // Build set of keys in src - srcKeys := make(map[string]struct{}) - for i := 0; i+1 < len(srcMap.Content); i += 2 { - if srcMap.Content[i] != nil { - srcKeys[srcMap.Content[i].Value] = struct{}{} - } - } - - // Remove keys from dst that are not in src - newContent := make([]*yaml.Node, 0, len(dstMap.Content)) - for i := 0; i+1 < len(dstMap.Content); i += 2 { - if dstMap.Content[i] != nil && dstMap.Content[i].Value != "" { - if _, exists := srcKeys[dstMap.Content[i].Value]; exists { - newContent = append(newContent, dstMap.Content[i], dstMap.Content[i+1]) - } - } - } - dstMap.Content = newContent -} - -func normalizeCollectionNodeStyles(node *yaml.Node) { - if node == nil { - return - } - switch node.Kind { - case yaml.MappingNode: - node.Style = 0 - for _, child := range node.Content { - normalizeCollectionNodeStyles(child) - } - case yaml.SequenceNode: - node.Style = 0 - for _, child := range node.Content { - normalizeCollectionNodeStyles(child) - } - } -} - -// Legacy removal functions -func removeLegacyOpenAICompatAPIKeys(root *yaml.Node) { - if root == nil || root.Kind != yaml.MappingNode { - return - } - removeMapKey(root, "openai-compatibility-api-keys") -} - -func removeLegacyAmpKeys(root *yaml.Node) { - if root == nil || root.Kind != yaml.MappingNode { - return - } - removeMapKey(root, "amp-upstream-url") - removeMapKey(root, "amp-upstream-api-key") - removeMapKey(root, "amp-restrict-management-to-localhost") - removeMapKey(root, "amp-model-mappings") -} - -func removeLegacyGenerativeLanguageKeys(root *yaml.Node) { - if root == nil || root.Kind != yaml.MappingNode { - return - } - removeMapKey(root, "generative-language-api-key") -} - -func removeLegacyAuthBlock(root *yaml.Node) { - if root == nil || root.Kind != yaml.MappingNode { - return - } - removeMapKey(root, "auth") -} diff --git a/pkg/llmproxy/config/config_providers.go b/pkg/llmproxy/config/config_providers.go deleted file mode 100644 index b3165f5f0f..0000000000 --- a/pkg/llmproxy/config/config_providers.go +++ /dev/null @@ -1,37 +0,0 @@ -package config - -import ( - "strings" -) - -// GetDedicatedProviders returns providers that have a dedicated config block. -func GetDedicatedProviders() []ProviderSpec { - var out []ProviderSpec - for _, p := range AllProviders { - if p.YAMLKey != "" { - out = append(out, p) - } - } - return out -} - -// GetPremadeProviders returns providers that can be injected from environment variables. -func GetPremadeProviders() []ProviderSpec { - var out []ProviderSpec - for _, p := range AllProviders { - if len(p.EnvVars) > 0 { - out = append(out, p) - } - } - return out -} - -// GetProviderByName looks up a provider by its name (case-insensitive). -func GetProviderByName(name string) (ProviderSpec, bool) { - for _, p := range AllProviders { - if strings.EqualFold(p.Name, name) { - return p, true - } - } - return ProviderSpec{}, false -} diff --git a/pkg/llmproxy/config/config_types.go b/pkg/llmproxy/config/config_types.go deleted file mode 100644 index c27fa9047b..0000000000 --- a/pkg/llmproxy/config/config_types.go +++ /dev/null @@ -1,620 +0,0 @@ -// Package config provides configuration management for the CLI Proxy API server. -// It handles loading and parsing YAML configuration files, and provides structured -// access to application settings including server port, authentication directory, -// debug settings, proxy configuration, and API keys. -package config - -import ( - "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/ratelimit" -) - -// Config represents the application's configuration, loaded from a YAML file. -type Config struct { - SDKConfig `yaml:",inline"` - // Host is the network host/interface on which the API server will bind. - // Default is empty ("") to bind all interfaces (IPv4 + IPv6). Use "127.0.0.1" or "localhost" for local-only access. - Host string `yaml:"host" json:"-"` - // Port is the network port on which the API server will listen. - Port int `yaml:"port" json:"-"` - - // TLS config controls HTTPS server settings. - TLS TLSConfig `yaml:"tls" json:"tls"` - - // RemoteManagement nests management-related options under 'remote-management'. - RemoteManagement RemoteManagement `yaml:"remote-management" json:"-"` - - // AuthDir is the directory where authentication token files are stored. - AuthDir string `yaml:"auth-dir" json:"-"` - - // Debug enables or disables debug-level logging and other debug features. - Debug bool `yaml:"debug" json:"debug"` - - // Pprof config controls the optional pprof HTTP debug server. - Pprof PprofConfig `yaml:"pprof" json:"pprof"` - - // CommercialMode disables high-overhead HTTP middleware features to minimize per-request memory usage. - CommercialMode bool `yaml:"commercial-mode" json:"commercial-mode"` - - // LoggingToFile controls whether application logs are written to rotating files or stdout. - LoggingToFile bool `yaml:"logging-to-file" json:"logging-to-file"` - - // LogsMaxTotalSizeMB limits the total size (in MB) of log files under the logs directory. - // When exceeded, the oldest log files are deleted until within the limit. Set to 0 to disable. - LogsMaxTotalSizeMB int `yaml:"logs-max-total-size-mb" json:"logs-max-total-size-mb"` - - // ErrorLogsMaxFiles limits the number of error log files retained when request logging is disabled. - // When exceeded, the oldest error log files are deleted. Default is 10. Set to 0 to disable cleanup. - ErrorLogsMaxFiles int `yaml:"error-logs-max-files" json:"error-logs-max-files"` - - // UsageStatisticsEnabled toggles in-memory usage aggregation; when false, usage data is discarded. - UsageStatisticsEnabled bool `yaml:"usage-statistics-enabled" json:"usage-statistics-enabled"` - - // DisableCooling disables quota cooldown scheduling when true. - DisableCooling bool `yaml:"disable-cooling" json:"disable-cooling"` - - // RequestRetry defines the retry times when the request failed. - RequestRetry int `yaml:"request-retry" json:"request-retry"` - // MaxRetryInterval defines the maximum wait time in seconds before retrying a cooled-down credential. - MaxRetryInterval int `yaml:"max-retry-interval" json:"max-retry-interval"` - - // QuotaExceeded defines the behavior when a quota is exceeded. - QuotaExceeded QuotaExceeded `yaml:"quota-exceeded" json:"quota-exceeded"` - - // Routing controls credential selection behavior. - Routing RoutingConfig `yaml:"routing" json:"routing"` - - // WebsocketAuth enables or disables authentication for the WebSocket API. - WebsocketAuth bool `yaml:"ws-auth" json:"ws-auth"` - - // ResponsesWebsocketEnabled gates the dedicated /v1/responses/ws route rollout. - // Nil means enabled (default behavior). - ResponsesWebsocketEnabled *bool `yaml:"responses-websocket-enabled,omitempty" json:"responses-websocket-enabled,omitempty"` - - // GeminiKey defines Gemini API key configurations with optional routing overrides. - GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"` - - // GeneratedConfig contains generated config fields for dedicated providers. - GeneratedConfig `yaml:",inline"` - - // KiroKey defines a list of Kiro (AWS CodeWhisperer) configurations. - KiroKey []KiroKey `yaml:"kiro" json:"kiro"` - - // CursorKey defines Cursor (via cursor-api) configurations. Uses login protocol, not static API key. - // Token file contains sk-... key from cursor-api /build-key, or token:checksum for /build-key. - CursorKey []CursorKey `yaml:"cursor" json:"cursor"` - - // KiroPreferredEndpoint sets the global default preferred endpoint for all Kiro providers. - // Values: "ide" (default, CodeWhisperer) or "cli" (Amazon Q). - KiroPreferredEndpoint string `yaml:"kiro-preferred-endpoint" json:"kiro-preferred-endpoint"` - - // Codex defines a list of Codex API key configurations as specified in the YAML configuration file. - CodexKey []CodexKey `yaml:"codex-api-key" json:"codex-api-key"` - - // ClaudeKey defines a list of Claude API key configurations as specified in the YAML configuration file. - ClaudeKey []ClaudeKey `yaml:"claude-api-key" json:"claude-api-key"` - - // ClaudeHeaderDefaults configures default header values for Claude API requests. - // These are used as fallbacks when the client does not send its own headers. - ClaudeHeaderDefaults ClaudeHeaderDefaults `yaml:"claude-header-defaults" json:"claude-header-defaults"` - - // OpenAICompatibility defines OpenAI API compatibility configurations for external providers. - OpenAICompatibility []OpenAICompatibility `yaml:"openai-compatibility" json:"openai-compatibility"` - - // VertexCompatAPIKey defines Vertex AI-compatible API key configurations for third-party providers. - // Used for services that use Vertex AI-style paths but with simple API key authentication. - VertexCompatAPIKey []VertexCompatKey `yaml:"vertex-api-key" json:"vertex-api-key"` - - // AmpCode contains Amp CLI upstream configuration, management restrictions, and model mappings. - AmpCode AmpCode `yaml:"ampcode" json:"ampcode"` - - // OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries. - // Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kiro, github-copilot. - OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"` - - // OAuthModelAlias defines global model name aliases for OAuth/file-backed auth channels. - // These aliases affect both model listing and model routing for supported channels: - // gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kiro, github-copilot. - // - // NOTE: This does not apply to existing per-credential model alias features under: - // gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode. - OAuthModelAlias map[string][]OAuthModelAlias `yaml:"oauth-model-alias,omitempty" json:"oauth-model-alias,omitempty"` - - // OAuthUpstream defines per-channel upstream base URL overrides for OAuth/file-backed auth channels. - // Keys are channel identifiers (e.g., gemini-cli, claude, codex, qwen, iflow, github-copilot, antigravity). - // Values must be absolute base URLs (scheme + host), and are normalized by trimming trailing slashes. - OAuthUpstream map[string]string `yaml:"oauth-upstream,omitempty" json:"oauth-upstream,omitempty"` - - // Payload defines default and override rules for provider payload parameters. - Payload PayloadConfig `yaml:"payload" json:"payload"` - - // IncognitoBrowser enables opening OAuth URLs in incognito/private browsing mode. - // This is useful when you want to login with a different account without logging out - // from your current session. Default: false. - IncognitoBrowser bool `yaml:"incognito-browser" json:"incognito-browser"` - - // ResponsesCompactEnabled controls whether OpenAI Responses API compact mode is active. - // Default (nil) is treated as enabled. - ResponsesCompactEnabled *bool `yaml:"responses-compact-enabled,omitempty" json:"responses-compact-enabled,omitempty"` -} - -// IsResponsesCompactEnabled returns whether responses compact mode is enabled. -// Defaults to true when the config is nil or the toggle is unset. -func (c *Config) IsResponsesCompactEnabled() bool { - if c == nil || c.ResponsesCompactEnabled == nil { - return true - } - return *c.ResponsesCompactEnabled -} - -// ClaudeHeaderDefaults configures default header values injected into Claude API requests -// when the client does not send them. Update these when Claude Code releases a new version. -type ClaudeHeaderDefaults struct { - UserAgent string `yaml:"user-agent" json:"user-agent"` - PackageVersion string `yaml:"package-version" json:"package-version"` - RuntimeVersion string `yaml:"runtime-version" json:"runtime-version"` - Timeout string `yaml:"timeout" json:"timeout"` -} - -// TLSConfig holds HTTPS server settings. -type TLSConfig struct { - // Enable toggles HTTPS server mode. - Enable bool `yaml:"enable" json:"enable"` - // Cert is the path to the TLS certificate file. - Cert string `yaml:"cert" json:"cert"` - // Key is the path to the TLS private key file. - Key string `yaml:"key" json:"key"` -} - -// PprofConfig holds pprof HTTP server settings. -type PprofConfig struct { - // Enable toggles the pprof HTTP debug server. - Enable bool `yaml:"enable" json:"enable"` - // Addr is the host:port address for the pprof HTTP server. - Addr string `yaml:"addr" json:"addr"` -} - -// RemoteManagement holds management API configuration under 'remote-management'. -type RemoteManagement struct { - // AllowRemote toggles remote (non-localhost) access to management API. - AllowRemote bool `yaml:"allow-remote"` - // SecretKey is the management key (plaintext or bcrypt hashed). YAML key intentionally 'secret-key'. - SecretKey string `yaml:"secret-key"` - // DisableControlPanel skips serving and syncing the bundled management UI when true. - DisableControlPanel bool `yaml:"disable-control-panel"` - // PanelGitHubRepository overrides the GitHub repository used to fetch the management panel asset. - // Accepts either a repository URL (https://github.com/org/repo) or an API releases endpoint. - PanelGitHubRepository string `yaml:"panel-github-repository"` -} - -// QuotaExceeded defines the behavior when API quota limits are exceeded. -// It provides configuration options for automatic failover mechanisms. -type QuotaExceeded struct { - // SwitchProject indicates whether to automatically switch to another project when a quota is exceeded. - SwitchProject bool `yaml:"switch-project" json:"switch-project"` - - // SwitchPreviewModel indicates whether to automatically switch to a preview model when a quota is exceeded. - SwitchPreviewModel bool `yaml:"switch-preview-model" json:"switch-preview-model"` -} - -// RoutingConfig configures how credentials are selected for requests. -type RoutingConfig struct { - // Strategy selects the credential selection strategy. - // Supported values: "round-robin" (default), "fill-first", "sticky-round-robin". - Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"` -} - -// OAuthModelAlias defines a model ID alias for a specific channel. -// It maps the upstream model name (Name) to the client-visible alias (Alias). -// When Fork is true, the alias is added as an additional model in listings while -// keeping the original model ID available. -type OAuthModelAlias struct { - Name string `yaml:"name" json:"name"` - Alias string `yaml:"alias" json:"alias"` - Fork bool `yaml:"fork,omitempty" json:"fork,omitempty"` -} - -// AmpModelMapping defines a model name mapping for Amp CLI requests. -// When Amp requests a model that isn't available locally, this mapping -// allows routing to an alternative model that IS available. -type AmpModelMapping struct { - // From is the model name that Amp CLI requests (e.g., "claude-opus-4.5"). - From string `yaml:"from" json:"from"` - - // To is the target model name to route to (e.g., "claude-sonnet-4"). - // The target model must have available providers in the registry. - To string `yaml:"to" json:"to"` - - // Params define provider-agnostic request overrides to apply when this mapping is used. - // Keys are merged into the request JSON at the root level unless they already exist. - // For example: params: {"custom_model": "iflow/tab-rt", "enable_stream": true} - Params map[string]interface{} `yaml:"params,omitempty" json:"params,omitempty"` - - // Regex indicates whether the 'from' field should be interpreted as a regular - // expression for matching model names. When true, this mapping is evaluated - // after exact matches and in the order provided. Defaults to false (exact match). - Regex bool `yaml:"regex,omitempty" json:"regex,omitempty"` -} - -// AmpCode groups Amp CLI integration settings including upstream routing, -// optional overrides, management route restrictions, and model fallback mappings. -type AmpCode struct { - // UpstreamURL defines the upstream Amp control plane used for non-provider calls. - UpstreamURL string `yaml:"upstream-url" json:"upstream-url"` - - // UpstreamAPIKey optionally overrides the Authorization header when proxying Amp upstream calls. - UpstreamAPIKey string `yaml:"upstream-api-key" json:"upstream-api-key"` - - // UpstreamAPIKeys maps client API keys (from top-level api-keys) to upstream API keys. - // When a client authenticates with a key that matches an entry, that upstream key is used. - // If no match is found, falls back to UpstreamAPIKey (default behavior). - UpstreamAPIKeys []AmpUpstreamAPIKeyEntry `yaml:"upstream-api-keys,omitempty" json:"upstream-api-keys,omitempty"` - - // RestrictManagementToLocalhost restricts Amp management routes (/api/user, /api/threads, etc.) - // to only accept connections from localhost (127.0.0.1, ::1). When true, prevents drive-by - // browser attacks and remote access to management endpoints. Default: false (API key auth is sufficient). - RestrictManagementToLocalhost bool `yaml:"restrict-management-to-localhost" json:"restrict-management-to-localhost"` - - // ModelMappings defines model name mappings for Amp CLI requests. - // When Amp requests a model that isn't available locally, these mappings - // allow routing to an alternative model that IS available. - ModelMappings []AmpModelMapping `yaml:"model-mappings" json:"model-mappings"` - - // ForceModelMappings when true, model mappings take precedence over local API keys. - // When false (default), local API keys are used first if available. - ForceModelMappings bool `yaml:"force-model-mappings" json:"force-model-mappings"` -} - -// AmpUpstreamAPIKeyEntry maps a set of client API keys to a specific upstream API key. -// When a request is authenticated with one of the APIKeys, the corresponding UpstreamAPIKey -// is used for the upstream Amp request. -type AmpUpstreamAPIKeyEntry struct { - // UpstreamAPIKey is the API key to use when proxying to the Amp upstream. - UpstreamAPIKey string `yaml:"upstream-api-key" json:"upstream-api-key"` - - // APIKeys are the client API keys (from top-level api-keys) that map to this upstream key. - APIKeys []string `yaml:"api-keys" json:"api-keys"` -} - -// PayloadConfig defines default and override parameter rules applied to provider payloads. -type PayloadConfig struct { - // Default defines rules that only set parameters when they are missing in the payload. - Default []PayloadRule `yaml:"default" json:"default"` - // DefaultRaw defines rules that set raw JSON values only when they are missing. - DefaultRaw []PayloadRule `yaml:"default-raw" json:"default-raw"` - // Override defines rules that always set parameters, overwriting any existing values. - Override []PayloadRule `yaml:"override" json:"override"` - // OverrideRaw defines rules that always set raw JSON values, overwriting any existing values. - OverrideRaw []PayloadRule `yaml:"override-raw" json:"override-raw"` - // Filter defines rules that remove parameters from the payload by JSON path. - Filter []PayloadFilterRule `yaml:"filter" json:"filter"` -} - -// PayloadFilterRule describes a rule to remove specific JSON paths from matching model payloads. -type PayloadFilterRule struct { - // Models lists model entries with name pattern and protocol constraint. - Models []PayloadModelRule `yaml:"models" json:"models"` - // Params lists JSON paths (gjson/sjson syntax) to remove from the payload. - Params []string `yaml:"params" json:"params"` -} - -// PayloadRule describes a single rule targeting a list of models with parameter updates. -type PayloadRule struct { - // Models lists model entries with name pattern and protocol constraint. - Models []PayloadModelRule `yaml:"models" json:"models"` - // Params maps JSON paths (gjson/sjson syntax) to values written into the payload. - // For *-raw rules, values are treated as raw JSON fragments (strings are used as-is). - Params map[string]any `yaml:"params" json:"params"` -} - -// PayloadModelRule ties a model name pattern to a specific translator protocol. -type PayloadModelRule struct { - // Name is the model name or wildcard pattern (e.g., "gpt-*", "*-5", "gemini-*-pro"). - Name string `yaml:"name" json:"name"` - // Protocol restricts the rule to a specific translator format (e.g., "gemini", "responses"). - Protocol string `yaml:"protocol" json:"protocol"` -} - -// CloakConfig configures request cloaking for non-Claude-Code clients. -// Cloaking disguises API requests to appear as originating from the official Claude Code CLI. -type CloakConfig struct { - // Mode controls cloaking behavior: "auto" (default), "always", or "never". - // - "auto": cloak only when client is not Claude Code (based on User-Agent) - // - "always": always apply cloaking regardless of client - // - "never": never apply cloaking - Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` - - // StrictMode controls how system prompts are handled when cloaking. - // - false (default): prepend Claude Code prompt to user system messages - // - true: strip all user system messages, keep only Claude Code prompt - StrictMode bool `yaml:"strict-mode,omitempty" json:"strict-mode,omitempty"` - - // SensitiveWords is a list of words to obfuscate with zero-width characters. - // This can help bypass certain content filters. - SensitiveWords []string `yaml:"sensitive-words,omitempty" json:"sensitive-words,omitempty"` -} - -// ClaudeKey represents the configuration for a Claude API key, -// including the API key itself and an optional base URL for the API endpoint. -type ClaudeKey struct { - // APIKey is the authentication key for accessing Claude API services. - APIKey string `yaml:"api-key" json:"api-key"` - - // Priority controls selection preference when multiple credentials match. - // Higher values are preferred; defaults to 0. - Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` - - // Prefix optionally namespaces models for this credential (e.g., "teamA/claude-sonnet-4"). - Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` - - // BaseURL is the base URL for the Claude API endpoint. - // If empty, the default Claude API URL will be used. - BaseURL string `yaml:"base-url" json:"base-url"` - - // ProxyURL overrides the global proxy setting for this API key if provided. - ProxyURL string `yaml:"proxy-url" json:"proxy-url"` - - // Models defines upstream model names and aliases for request routing. - Models []ClaudeModel `yaml:"models" json:"models"` - - // Headers optionally adds extra HTTP headers for requests sent with this key. - Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` - - // ExcludedModels lists model IDs that should be excluded for this provider. - ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` - - // Cloak configures request cloaking for non-Claude-Code clients. - Cloak *CloakConfig `yaml:"cloak,omitempty" json:"cloak,omitempty"` -} - -func (k ClaudeKey) GetAPIKey() string { return k.APIKey } -func (k ClaudeKey) GetBaseURL() string { return k.BaseURL } - -// ClaudeModel describes a mapping between an alias and the actual upstream model name. -type ClaudeModel struct { - // Name is the upstream model identifier used when issuing requests. - Name string `yaml:"name" json:"name"` - - // Alias is the client-facing model name that maps to Name. - Alias string `yaml:"alias" json:"alias"` -} - -func (m ClaudeModel) GetName() string { return m.Name } -func (m ClaudeModel) GetAlias() string { return m.Alias } - -// CodexKey represents the configuration for a Codex API key, -// including the API key itself and an optional base URL for the API endpoint. -type CodexKey struct { - // APIKey is the authentication key for accessing Codex API services. - APIKey string `yaml:"api-key" json:"api-key"` - - // Priority controls selection preference when multiple credentials match. - // Higher values are preferred; defaults to 0. - Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` - - // Prefix optionally namespaces models for this credential (e.g., "teamA/gpt-5-codex"). - Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` - - // BaseURL is the base URL for the Codex API endpoint. - // If empty, the default Codex API URL will be used. - BaseURL string `yaml:"base-url" json:"base-url"` - - // Websockets enables the Responses API websocket transport for this credential. - Websockets bool `yaml:"websockets,omitempty" json:"websockets,omitempty"` - - // ProxyURL overrides the global proxy setting for this API key if provided. - ProxyURL string `yaml:"proxy-url" json:"proxy-url"` - - // Models defines upstream model names and aliases for request routing. - Models []CodexModel `yaml:"models" json:"models"` - - // Headers optionally adds extra HTTP headers for requests sent with this key. - Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` - - // ExcludedModels lists model IDs that should be excluded for this provider. - ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` -} - -func (k CodexKey) GetAPIKey() string { return k.APIKey } -func (k CodexKey) GetBaseURL() string { return k.BaseURL } - -// CodexModel describes a mapping between an alias and the actual upstream model name. -type CodexModel struct { - // Name is the upstream model identifier used when issuing requests. - Name string `yaml:"name" json:"name"` - - // Alias is the client-facing model name that maps to Name. - Alias string `yaml:"alias" json:"alias"` -} - -func (m CodexModel) GetName() string { return m.Name } -func (m CodexModel) GetAlias() string { return m.Alias } - -// GeminiKey represents the configuration for a Gemini API key, -// including optional overrides for upstream base URL, proxy routing, and headers. -type GeminiKey struct { - // APIKey is the authentication key for accessing Gemini API services. - APIKey string `yaml:"api-key" json:"api-key"` - - // Priority controls selection preference when multiple credentials match. - // Higher values are preferred; defaults to 0. - Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` - - // Prefix optionally namespaces models for this credential (e.g., "teamA/gemini-3-pro-preview"). - Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` - - // BaseURL optionally overrides the Gemini API endpoint. - BaseURL string `yaml:"base-url,omitempty" json:"base-url,omitempty"` - - // ProxyURL optionally overrides the global proxy for this API key. - ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` - - // Models defines upstream model names and aliases for request routing. - Models []GeminiModel `yaml:"models,omitempty" json:"models,omitempty"` - - // Headers optionally adds extra HTTP headers for requests sent with this key. - Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` - - // ExcludedModels lists model IDs that should be excluded for this provider. - ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` -} - -func (k GeminiKey) GetAPIKey() string { return k.APIKey } -func (k GeminiKey) GetBaseURL() string { return k.BaseURL } - -// GeminiModel describes a mapping between an alias and the actual upstream model name. -type GeminiModel struct { - // Name is the upstream model identifier used when issuing requests. - Name string `yaml:"name" json:"name"` - - // Alias is the client-facing model name that maps to Name. - Alias string `yaml:"alias" json:"alias"` -} - -func (m GeminiModel) GetName() string { return m.Name } -func (m GeminiModel) GetAlias() string { return m.Alias } - -// KiroKey represents the configuration for Kiro (AWS CodeWhisperer) authentication. -type KiroKey struct { - // TokenFile is the path to the Kiro token file (default: ~/.aws/sso/cache/kiro-auth-token.json) - TokenFile string `yaml:"token-file,omitempty" json:"token-file,omitempty"` - - // AccessToken is the OAuth access token for direct configuration. - AccessToken string `yaml:"access-token,omitempty" json:"access-token,omitempty"` - - // RefreshToken is the OAuth refresh token for token renewal. - RefreshToken string `yaml:"refresh-token,omitempty" json:"refresh-token,omitempty"` - - // ProfileArn is the AWS CodeWhisperer profile ARN. - ProfileArn string `yaml:"profile-arn,omitempty" json:"profile-arn,omitempty"` - - // Region is the AWS region (default: us-east-1). - Region string `yaml:"region,omitempty" json:"region,omitempty"` - - // ProxyURL optionally overrides the global proxy for this configuration. - ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` - - // AgentTaskType sets the Kiro API task type. Known values: "vibe", "dev", "chat". - // Leave empty to let API use defaults. Different values may inject different system prompts. - AgentTaskType string `yaml:"agent-task-type,omitempty" json:"agent-task-type,omitempty"` - - // PreferredEndpoint sets the preferred Kiro API endpoint/quota. - // Values: "codewhisperer" (default, IDE quota) or "amazonq" (CLI quota). - PreferredEndpoint string `yaml:"preferred-endpoint,omitempty" json:"preferred-endpoint,omitempty"` -} - -// CursorKey represents Cursor (via cursor-api) configuration. Uses login protocol. -// Token file contains sk-... key from cursor-api /build-key, or token:checksum for /build-key. -// When token-file is absent, token is auto-read from Cursor IDE storage (zero-action flow). -type CursorKey struct { - // TokenFile is the path to the Cursor token file (sk-... key or token:checksum). - // Optional: when empty, token is auto-read from Cursor IDE state.vscdb. - TokenFile string `yaml:"token-file,omitempty" json:"token-file,omitempty"` - - // CursorAPIURL is the cursor-api server URL (default: http://127.0.0.1:3000). - CursorAPIURL string `yaml:"cursor-api-url,omitempty" json:"cursor-api-url,omitempty"` - - // AuthToken is the cursor-api admin token (matches AUTH_TOKEN env). Required for zero-action - // flow when using /tokens/add to register IDE token. Used as Bearer for chat when token-file absent. - AuthToken string `yaml:"auth-token,omitempty" json:"auth-token,omitempty"` - - // ProxyURL optionally overrides the global proxy for this configuration. - ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` -} - -// OAICompatProviderConfig represents a common configuration for OpenAI-compatible providers. -type OAICompatProviderConfig struct { - // TokenFile is the path to OAuth token file (access/refresh). Optional when APIKey is set. - TokenFile string `yaml:"token-file,omitempty" json:"token-file,omitempty"` - - // APIKey is the API key for direct auth (fallback when token-file not used). - APIKey string `yaml:"api-key,omitempty" json:"api-key,omitempty"` - - // BaseURL is the API base URL. - BaseURL string `yaml:"base-url,omitempty" json:"base-url,omitempty"` - - // ProxyURL optionally overrides the global proxy for this configuration. - ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` - - // Models defines optional model configurations including aliases for routing. - Models []OpenAICompatibilityModel `yaml:"models,omitempty" json:"models,omitempty"` - - // Priority controls selection preference. - Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` - - // Prefix optionally namespaces model aliases for this provider. - Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` - - // Headers optionally adds extra HTTP headers for requests sent with this key. - Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` - - // ExcludedModels lists model IDs that should be excluded for this provider. - ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"` - - // RateLimit defines optional rate limiting configuration for this credential. - RateLimit ratelimit.RateLimitConfig `yaml:"rate-limit,omitempty" json:"rate-limit,omitempty"` -} - -// ProviderSpec defines a provider's metadata for codegen and runtime injection. -type ProviderSpec struct { - Name string - YAMLKey string // If set, a dedicated block is generated in the Config struct - GoName string // Optional: Override PascalCase name in Go (defaults to Title(Name)) - BaseURL string - EnvVars []string // Environment variables for automatic injection - DefaultModels []OpenAICompatibilityModel -} - -// OpenAICompatibility represents the configuration for OpenAI API compatibility -// with external providers, allowing model aliases to be routed through OpenAI API format. -type OpenAICompatibility struct { - // Name is the identifier for this OpenAI compatibility configuration. - Name string `yaml:"name" json:"name"` - - // Priority controls selection preference when multiple providers or credentials match. - // Higher values are preferred; defaults to 0. - Priority int `yaml:"priority,omitempty" json:"priority,omitempty"` - - // Prefix optionally namespaces model aliases for this provider (e.g., "teamA/kimi-k2"). - Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty"` - - // BaseURL is the base URL for the external OpenAI-compatible API endpoint. - BaseURL string `yaml:"base-url" json:"base-url"` - - // ModelsEndpoint overrides the upstream model discovery path. - // Defaults to "/v1/models" when omitted. - ModelsEndpoint string `yaml:"models-endpoint,omitempty" json:"models-endpoint,omitempty"` - - // APIKeyEntries defines API keys with optional per-key proxy configuration. - APIKeyEntries []OpenAICompatibilityAPIKey `yaml:"api-key-entries,omitempty" json:"api-key-entries,omitempty"` - - // Models defines the model configurations including aliases for routing. - Models []OpenAICompatibilityModel `yaml:"models" json:"models"` - - // Headers optionally adds extra HTTP headers for requests sent to this provider. - Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` -} - -// OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting. -type OpenAICompatibilityAPIKey struct { - // TokenFile is the path to OAuth token file (access/refresh). Optional when APIKey is set. - TokenFile string `yaml:"token-file,omitempty" json:"token-file,omitempty"` - - // APIKey is the authentication key for accessing the external API services. - APIKey string `yaml:"api-key" json:"api-key"` - - // ProxyURL overrides the global proxy setting for this API key if provided. - ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` -} - -// OpenAICompatibilityModel represents a model configuration for OpenAI compatibility, -// including the actual model name and its alias for API routing. -type OpenAICompatibilityModel struct { - // Name is the actual model name used by the external provider. - Name string `yaml:"name" json:"name"` - - // Alias is the model name alias that clients will use to reference this model. - Alias string `yaml:"alias" json:"alias"` -} - -func (m OpenAICompatibilityModel) GetName() string { return m.Name } -func (m OpenAICompatibilityModel) GetAlias() string { return m.Alias } diff --git a/pkg/llmproxy/config/config_validation.go b/pkg/llmproxy/config/config_validation.go deleted file mode 100644 index a4e9ab4462..0000000000 --- a/pkg/llmproxy/config/config_validation.go +++ /dev/null @@ -1,460 +0,0 @@ -package config - -import ( - "bytes" - "encoding/json" - "strings" - - log "github.com/sirupsen/logrus" -) - -// SanitizePayloadRules validates raw JSON payload rule params and drops invalid rules. -func (cfg *Config) SanitizePayloadRules() { - if cfg == nil { - return - } - cfg.Payload.Default = sanitizePayloadRules(cfg.Payload.Default, "default") - cfg.Payload.Override = sanitizePayloadRules(cfg.Payload.Override, "override") - cfg.Payload.Filter = sanitizePayloadFilterRules(cfg.Payload.Filter, "filter") - cfg.Payload.DefaultRaw = sanitizePayloadRawRules(cfg.Payload.DefaultRaw, "default-raw") - cfg.Payload.OverrideRaw = sanitizePayloadRawRules(cfg.Payload.OverrideRaw, "override-raw") -} - -func sanitizePayloadRules(rules []PayloadRule, section string) []PayloadRule { - if len(rules) == 0 { - return rules - } - out := make([]PayloadRule, 0, len(rules)) - for i := range rules { - rule := rules[i] - if len(rule.Params) == 0 { - continue - } - invalid := false - for path := range rule.Params { - if payloadPathInvalid(path) { - log.WithFields(log.Fields{ - "section": section, - "rule_index": i + 1, - "param": path, - }).Warn("payload rule dropped: invalid parameter path") - invalid = true - break - } - } - if invalid { - continue - } - out = append(out, rule) - } - return out -} - -func sanitizePayloadRawRules(rules []PayloadRule, section string) []PayloadRule { - if len(rules) == 0 { - return rules - } - out := make([]PayloadRule, 0, len(rules)) - for i := range rules { - rule := rules[i] - if len(rule.Params) == 0 { - continue - } - invalid := false - for path, value := range rule.Params { - if payloadPathInvalid(path) { - log.WithFields(log.Fields{ - "section": section, - "rule_index": i + 1, - "param": path, - }).Warn("payload rule dropped: invalid parameter path") - invalid = true - break - } - raw, ok := payloadRawString(value) - if !ok { - continue - } - trimmed := bytes.TrimSpace(raw) - if len(trimmed) == 0 || !json.Valid(trimmed) { - log.WithFields(log.Fields{ - "section": section, - "rule_index": i + 1, - "param": path, - }).Warn("payload rule dropped: invalid raw JSON") - invalid = true - break - } - } - if invalid { - continue - } - out = append(out, rule) - } - return out -} - -func sanitizePayloadFilterRules(rules []PayloadFilterRule, section string) []PayloadFilterRule { - if len(rules) == 0 { - return rules - } - out := make([]PayloadFilterRule, 0, len(rules)) - for i := range rules { - rule := rules[i] - if len(rule.Params) == 0 { - continue - } - invalid := false - for _, path := range rule.Params { - if payloadPathInvalid(path) { - log.WithFields(log.Fields{ - "section": section, - "rule_index": i + 1, - "param": path, - }).Warn("payload filter rule dropped: invalid parameter path") - invalid = true - break - } - } - if invalid { - continue - } - out = append(out, rule) - } - return out -} - -func payloadPathInvalid(path string) bool { - p := strings.TrimSpace(path) - if p == "" { - return true - } - return strings.HasPrefix(p, ".") || strings.HasSuffix(p, ".") || strings.Contains(p, "..") -} - -func payloadRawString(value any) ([]byte, bool) { - switch typed := value.(type) { - case string: - return []byte(typed), true - case json.Number: - return []byte(typed), true - case []byte: - return typed, true - default: - return nil, false - } -} - -// SanitizeOAuthModelAlias normalizes OAuth model alias entries. -func (cfg *Config) SanitizeOAuthModelAlias() { - if cfg == nil { - return - } - - // Inject default aliases for channels with built-in compatibility mappings. - if cfg.OAuthModelAlias == nil { - cfg.OAuthModelAlias = make(map[string][]OAuthModelAlias) - } - if _, hasKiro := cfg.OAuthModelAlias["kiro"]; !hasKiro { - // Check case-insensitive too - found := false - for k := range cfg.OAuthModelAlias { - if strings.EqualFold(strings.TrimSpace(k), "kiro") { - found = true - break - } - } - if !found { - cfg.OAuthModelAlias["kiro"] = defaultKiroAliases() - } - } - if _, hasGitHubCopilot := cfg.OAuthModelAlias["github-copilot"]; !hasGitHubCopilot { - // Check case-insensitive too - found := false - for k := range cfg.OAuthModelAlias { - if strings.EqualFold(strings.TrimSpace(k), "github-copilot") { - found = true - break - } - } - if !found { - cfg.OAuthModelAlias["github-copilot"] = defaultGitHubCopilotAliases() - } - } - - if len(cfg.OAuthModelAlias) == 0 { - return - } - out := make(map[string][]OAuthModelAlias, len(cfg.OAuthModelAlias)) - for rawChannel, aliases := range cfg.OAuthModelAlias { - channel := strings.ToLower(strings.TrimSpace(rawChannel)) - if channel == "" { - continue - } - // Preserve channels that were explicitly set to empty/nil - if len(aliases) == 0 { - out[channel] = nil - continue - } - seenAlias := make(map[string]struct{}, len(aliases)) - clean := make([]OAuthModelAlias, 0, len(aliases)) - for _, entry := range aliases { - name := strings.TrimSpace(entry.Name) - alias := strings.TrimSpace(entry.Alias) - if name == "" || alias == "" { - continue - } - if strings.EqualFold(name, alias) { - continue - } - // Dedupe by name+alias combination - aliasKey := strings.ToLower(name) + ":" + strings.ToLower(alias) - if _, ok := seenAlias[aliasKey]; ok { - continue - } - seenAlias[aliasKey] = struct{}{} - clean = append(clean, OAuthModelAlias{Name: name, Alias: alias, Fork: entry.Fork}) - } - if len(clean) > 0 { - out[channel] = clean - } - } - cfg.OAuthModelAlias = out -} - -// SanitizeOAuthUpstream normalizes OAuth upstream URL override keys/values. -func (cfg *Config) SanitizeOAuthUpstream() { - if cfg == nil { - return - } - if len(cfg.OAuthUpstream) == 0 { - return - } - out := make(map[string]string, len(cfg.OAuthUpstream)) - for rawChannel, rawURL := range cfg.OAuthUpstream { - channel := normalizeOAuthUpstreamChannel(rawChannel) - if channel == "" { - continue - } - baseURL := strings.TrimSpace(rawURL) - if baseURL == "" { - continue - } - out[channel] = strings.TrimRight(baseURL, "/") - } - cfg.OAuthUpstream = out -} - -// OAuthUpstreamURL resolves the configured OAuth upstream override for a channel. -func (cfg *Config) OAuthUpstreamURL(channel string) string { - if cfg == nil || len(cfg.OAuthUpstream) == 0 { - return "" - } - key := normalizeOAuthUpstreamChannel(channel) - if key == "" { - return "" - } - return strings.TrimSpace(cfg.OAuthUpstream[key]) -} - -func normalizeOAuthUpstreamChannel(channel string) string { - key := strings.TrimSpace(strings.ToLower(channel)) - if key == "" { - return "" - } - key = strings.ReplaceAll(key, "_", "-") - key = strings.ReplaceAll(key, " ", "-") - key = strings.ReplaceAll(key, ".", "-") - key = strings.ReplaceAll(key, "/", "-") - key = strings.Trim(key, "-") - key = strings.Join(strings.FieldsFunc(key, func(r rune) bool { return r == '-' }), "-") - return key -} - -// IsResponsesWebsocketEnabled returns true when the dedicated responses websocket -// route should be mounted. Default is enabled when unset. -func (cfg *Config) IsResponsesWebsocketEnabled() bool { - if cfg == nil || cfg.ResponsesWebsocketEnabled == nil { - return true - } - return *cfg.ResponsesWebsocketEnabled -} - -// SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries -// missing a BaseURL and trims whitespace. -func (cfg *Config) SanitizeOpenAICompatibility() { - if cfg == nil || len(cfg.OpenAICompatibility) == 0 { - return - } - out := make([]OpenAICompatibility, 0, len(cfg.OpenAICompatibility)) - for i := range cfg.OpenAICompatibility { - e := cfg.OpenAICompatibility[i] - e.Name = strings.TrimSpace(e.Name) - e.Prefix = normalizeModelPrefix(e.Prefix) - e.BaseURL = strings.TrimSpace(e.BaseURL) - e.Headers = NormalizeHeaders(e.Headers) - if e.BaseURL == "" { - continue - } - out = append(out, e) - } - cfg.OpenAICompatibility = out -} - -// SanitizeCodexKeys removes Codex API key entries missing a BaseURL. -func (cfg *Config) SanitizeCodexKeys() { - if cfg == nil || len(cfg.CodexKey) == 0 { - return - } - out := make([]CodexKey, 0, len(cfg.CodexKey)) - for i := range cfg.CodexKey { - e := cfg.CodexKey[i] - e.Prefix = normalizeModelPrefix(e.Prefix) - e.BaseURL = strings.TrimSpace(e.BaseURL) - e.Headers = NormalizeHeaders(e.Headers) - e.ExcludedModels = NormalizeExcludedModels(e.ExcludedModels) - if e.BaseURL == "" { - continue - } - out = append(out, e) - } - cfg.CodexKey = out -} - -// SanitizeClaudeKeys normalizes headers for Claude credentials. -func (cfg *Config) SanitizeClaudeKeys() { - if cfg == nil || len(cfg.ClaudeKey) == 0 { - return - } - for i := range cfg.ClaudeKey { - entry := &cfg.ClaudeKey[i] - entry.Prefix = normalizeModelPrefix(entry.Prefix) - entry.Headers = NormalizeHeaders(entry.Headers) - entry.ExcludedModels = NormalizeExcludedModels(entry.ExcludedModels) - } -} - -// SanitizeKiroKeys trims whitespace from Kiro credential fields. -func (cfg *Config) SanitizeKiroKeys() { - if cfg == nil || len(cfg.KiroKey) == 0 { - return - } - for i := range cfg.KiroKey { - entry := &cfg.KiroKey[i] - entry.TokenFile = strings.TrimSpace(entry.TokenFile) - entry.AccessToken = strings.TrimSpace(entry.AccessToken) - entry.RefreshToken = strings.TrimSpace(entry.RefreshToken) - entry.ProfileArn = strings.TrimSpace(entry.ProfileArn) - entry.Region = strings.TrimSpace(entry.Region) - entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) - entry.PreferredEndpoint = strings.TrimSpace(entry.PreferredEndpoint) - } -} - -// SanitizeCursorKeys trims whitespace from Cursor credential fields. -func (cfg *Config) SanitizeCursorKeys() { - if cfg == nil || len(cfg.CursorKey) == 0 { - return - } - for i := range cfg.CursorKey { - entry := &cfg.CursorKey[i] - entry.TokenFile = strings.TrimSpace(entry.TokenFile) - entry.CursorAPIURL = strings.TrimSpace(entry.CursorAPIURL) - entry.AuthToken = strings.TrimSpace(entry.AuthToken) - entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) - } -} - -// SanitizeGeminiKeys deduplicates and normalizes Gemini credentials. -func (cfg *Config) SanitizeGeminiKeys() { - if cfg == nil { - return - } - - seen := make(map[string]struct{}, len(cfg.GeminiKey)) - out := cfg.GeminiKey[:0] - for i := range cfg.GeminiKey { - entry := cfg.GeminiKey[i] - entry.APIKey = strings.TrimSpace(entry.APIKey) - if entry.APIKey == "" { - continue - } - entry.Prefix = normalizeModelPrefix(entry.Prefix) - entry.BaseURL = strings.TrimSpace(entry.BaseURL) - entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) - entry.Headers = NormalizeHeaders(entry.Headers) - entry.ExcludedModels = NormalizeExcludedModels(entry.ExcludedModels) - if _, exists := seen[entry.APIKey]; exists { - continue - } - seen[entry.APIKey] = struct{}{} - out = append(out, entry) - } - cfg.GeminiKey = out -} - -func normalizeModelPrefix(prefix string) string { - trimmed := strings.TrimSpace(prefix) - trimmed = strings.Trim(trimmed, "/") - if trimmed == "" { - return "" - } - if strings.Contains(trimmed, "/") { - return "" - } - return trimmed -} - -// NormalizeHeaders normalizes HTTP header field names and values. -func NormalizeHeaders(headers map[string]string) map[string]string { - if len(headers) == 0 { - return headers - } - out := make(map[string]string, len(headers)) - for k, v := range headers { - normalized := strings.TrimSpace(k) - value := strings.TrimSpace(v) - if normalized == "" || value == "" { - continue - } - out[normalized] = value - } - return out -} - -// NormalizeExcludedModels normalizes excluded model names. -func NormalizeExcludedModels(models []string) []string { - if len(models) == 0 { - return models - } - seen := make(map[string]struct{}, len(models)) - out := make([]string, 0, len(models)) - for _, m := range models { - normalized := strings.TrimSpace(m) - if normalized == "" { - continue - } - if _, exists := seen[normalized]; exists { - continue - } - seen[normalized] = struct{}{} - out = append(out, normalized) - } - return out -} - -// NormalizeOAuthExcludedModels normalizes per-channel excluded model maps. -func NormalizeOAuthExcludedModels(entries map[string][]string) map[string][]string { - if len(entries) == 0 { - return entries - } - out := make(map[string][]string, len(entries)) - for channel, models := range entries { - normalized := strings.ToLower(strings.TrimSpace(channel)) - if normalized == "" { - continue - } - out[normalized] = NormalizeExcludedModels(models) - } - return out -} diff --git a/pkg/llmproxy/config/sdk_config.go b/pkg/llmproxy/config/sdk_config.go index 41fc8b1bc1..153da36ca7 100644 --- a/pkg/llmproxy/config/sdk_config.go +++ b/pkg/llmproxy/config/sdk_config.go @@ -4,7 +4,9 @@ // debug settings, proxy configuration, and API keys. package config -import internalconfig "github.com/kooshapari/CLIProxyAPI/v7/internal/config" +type SDKConfig struct { + // ProxyURL overrides the global proxy setting for SDK-level HTTP clients. + ProxyURL string `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"` // 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 7be5a07d32..82df734a71 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/wsrelay" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/executor/antigravity_executor.go b/pkg/llmproxy/executor/antigravity_executor.go index f0ad02875e..347e0c09b7 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/interfaces" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/registry" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" diff --git a/pkg/llmproxy/executor/claude_executor.go b/pkg/llmproxy/executor/claude_executor.go index 8c0c8e267f..932456d299 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/v7/pkg/llmproxy/auth/claude" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/codex_executor.go b/pkg/llmproxy/executor/codex_executor.go index 2a36d4c911..ccd3183f07 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/v7/pkg/llmproxy/auth/codex" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/gemini_cli_executor.go b/pkg/llmproxy/executor/gemini_cli_executor.go index 11d7d256b9..80408f8833 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/interfaces" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/runtime/geminicli" diff --git a/pkg/llmproxy/executor/gemini_executor.go b/pkg/llmproxy/executor/gemini_executor.go index 04570675f5..f3945e3072 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/interfaces" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/gemini_vertex_executor.go b/pkg/llmproxy/executor/gemini_vertex_executor.go index b8aaf37550..471d41ab52 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/v7/pkg/llmproxy/auth/vertex" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/interfaces" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/executor/github_copilot_executor.go b/pkg/llmproxy/executor/github_copilot_executor.go index 26c4e1ea0c..33ba71db5c 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/v7/pkg/llmproxy/auth/copilot" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/iflow_executor.go b/pkg/llmproxy/executor/iflow_executor.go index d3a3e6d6af..14c528190a 100644 --- a/pkg/llmproxy/executor/iflow_executor.go +++ b/pkg/llmproxy/executor/iflow_executor.go @@ -15,7 +15,7 @@ import ( "github.com/google/uuid" iflowauth "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/iflow" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/kilo_executor.go b/pkg/llmproxy/executor/kilo_executor.go index 0d2546ff09..eeda26d5e0 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/registry" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" diff --git a/pkg/llmproxy/executor/kimi_executor.go b/pkg/llmproxy/executor/kimi_executor.go index d111561e49..821e4aa9df 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/v7/pkg/llmproxy/auth/kimi" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/logging_helpers.go b/pkg/llmproxy/executor/logging_helpers.go index 610e7fdcc8..95c0d62323 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/logging" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/executor/oauth_upstream.go b/pkg/llmproxy/executor/oauth_upstream.go index ca4bb3eda2..be09d0150b 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/executor/openai_compat_executor.go b/pkg/llmproxy/executor/openai_compat_executor.go index 7e50fb0666..91b8f6b98e 100644 --- a/pkg/llmproxy/executor/openai_compat_executor.go +++ b/pkg/llmproxy/executor/openai_compat_executor.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" diff --git a/pkg/llmproxy/executor/openai_models_fetcher.go b/pkg/llmproxy/executor/openai_models_fetcher.go index c387ee6619..3bef9749b2 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/registry" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" diff --git a/pkg/llmproxy/executor/payload_helpers.go b/pkg/llmproxy/executor/payload_helpers.go index 145f26d530..6c748f2536 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" cliproxyexecutor "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/executor" "github.com/tidwall/gjson" @@ -152,7 +152,19 @@ func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string } func payloadModelRulesMatch(rules []config.PayloadModelRule, protocol string, models []string) bool { - if len(rules) == 0 || len(models) == 0 { + if len(rules) == 0 { + return false + } + // Empty models in candidates means no specific model context. + // In this case, check if any rule is unconditional (has no models specified). + if len(models) == 0 { + for _, entry := range rules { + name := strings.TrimSpace(entry.Name) + if name == "" { + // Empty Name means unconditional rule - applies to all models. + return true + } + } return false } for _, model := range models { diff --git a/pkg/llmproxy/executor/payload_helpers_test.go b/pkg/llmproxy/executor/payload_helpers_test.go new file mode 100644 index 0000000000..aa65e7b65c --- /dev/null +++ b/pkg/llmproxy/executor/payload_helpers_test.go @@ -0,0 +1,367 @@ +package executor + +import ( + "encoding/json" + "testing" + + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPayloadModelRulesMatch(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + rules []config.PayloadModelRule + protocol string + models []string + want bool + }{ + { + name: "empty rules returns false", + rules: nil, + protocol: "gemini", + models: []string{"gemini-2.0-flash"}, + want: false, + }, + { + name: "empty models with conditional rule returns false", + rules: []config.PayloadModelRule{{Name: "gemini-*", Protocol: "gemini"}}, + protocol: "gemini", + models: []string{}, + want: false, + }, + { + name: "unconditional rule matches empty models", + rules: []config.PayloadModelRule{{Name: "", Protocol: ""}}, + protocol: "gemini", + models: []string{}, + want: true, + }, + { + name: "unconditional rule with protocol matches empty models", + rules: []config.PayloadModelRule{{Name: "", Protocol: "gemini"}}, + protocol: "gemini", + models: []string{}, + want: true, + }, + { + name: "unconditional rule with wrong protocol returns false", + rules: []config.PayloadModelRule{{Name: "", Protocol: "openai"}}, + protocol: "gemini", + models: []string{}, + want: false, + }, + { + name: "conditional rule matches model", + rules: []config.PayloadModelRule{{Name: "gemini-*", Protocol: ""}}, + protocol: "gemini", + models: []string{"gemini-2.0-flash"}, + want: true, + }, + { + name: "conditional rule does not match wrong model", + rules: []config.PayloadModelRule{{Name: "gpt-*", Protocol: ""}}, + protocol: "gemini", + models: []string{"gemini-2.0-flash"}, + want: false, + }, + { + name: "protocol mismatch returns false", + rules: []config.PayloadModelRule{{Name: "gemini-*", Protocol: "openai"}}, + protocol: "gemini", + models: []string{"gemini-2.0-flash"}, + want: false, + }, + { + name: "wildcard name matches any model", + rules: []config.PayloadModelRule{{Name: "*", Protocol: ""}}, + protocol: "gemini", + models: []string{"any-model"}, + want: true, + }, + { + name: "mixed rules - one unconditional one conditional", + rules: []config.PayloadModelRule{{Name: "", Protocol: ""}, {Name: "gpt-*", Protocol: ""}}, + protocol: "gemini", + models: []string{}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := payloadModelRulesMatch(tt.rules, tt.protocol, tt.models) + assert.Equal(t, tt.want, got, "payloadModelRulesMatch(%+v, %q, %+v)", tt.rules, tt.protocol, tt.models) + }) + } +} + +func TestPayloadModelCandidates(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + model string + requestedModel string + want []string + }{ + { + name: "both empty returns nil", + model: "", + requestedModel: "", + want: nil, + }, + { + name: "only model returns model", + model: "gemini-2.0-flash", + requestedModel: "", + want: []string{"gemini-2.0-flash"}, + }, + { + name: "only requestedModel returns base and alias", + model: "", + requestedModel: "gemini-2.0-flash+thinking", + want: []string{"gemini-2.0-flash", "gemini-2.0-flash+thinking"}, + }, + { + name: "both model and requestedModel returns both", + model: "gemini-2.0-flash", + requestedModel: "gemini-pro", + want: []string{"gemini-2.0-flash", "gemini-pro"}, + }, + { + name: "duplicate model deduplicated", + model: "gemini-2.0-flash", + requestedModel: "gemini-2.0-flash", + want: []string{"gemini-2.0-flash"}, + }, + { + name: "whitespace trimmed", + model: " gemini-2.0-flash ", + requestedModel: " gemini-pro ", + want: []string{"gemini-2.0-flash", "gemini-pro"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := payloadModelCandidates(tt.model, tt.requestedModel) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestApplyPayloadConfigWithRoot_UnconditionalRules(t *testing.T) { + t.Parallel() + + cfg := &config.Config{ + Payload: config.PayloadConfig{ + Default: []config.PayloadRule{ + { + // Unconditional rule - no models specified + Models: []config.PayloadModelRule{}, + Params: map[string]any{"maxTokens": 1000}, + }, + }, + Override: []config.PayloadRule{ + { + // Conditional rule - specific model + Models: []config.PayloadModelRule{ + {Name: "gemini-*", Protocol: ""}, + }, + Params: map[string]any{"temperature": 0.7}, + }, + }, + }, + } + + payload := []byte(`{"model":"gemini-2.0-flash","maxTokens":500}`) + original := []byte(`{"model":"gemini-2.0-flash","maxTokens":200}`) + + result := applyPayloadConfigWithRoot(cfg, "gemini-2.0-flash", "", "", payload, original, "") + + var got map[string]any + err := json.Unmarshal(result, &got) + require.NoError(t, err) + + // Unconditional default rule should set maxTokens (1000 > 200 but source has it) + assert.Equal(t, float64(200), got["maxTokens"], "should keep original maxTokens") + + // Conditional override rule should apply temperature + assert.Equal(t, 0.7, got["temperature"], "conditional override should apply") +} + +func TestApplyPayloadConfigWithRoot_ProtocolMatching(t *testing.T) { + t.Parallel() + + cfg := &config.Config{ + Payload: config.PayloadConfig{ + Default: []config.PayloadRule{ + { + Models: []config.PayloadModelRule{ + {Name: "", Protocol: "gemini"}, // Protocol-specific unconditional + }, + Params: map[string]any{"safetySettings": "BLOCK_NONE"}, + }, + { + Models: []config.PayloadModelRule{ + {Name: "", Protocol: "responses"}, // Different protocol + }, + Params: map[string]any{"maxOutputTokens": 4096}, + }, + }, + }, + } + + t.Run("gemini protocol applies", func(t *testing.T) { + t.Parallel() + payload := []byte(`{"model":"gemini-2.0-flash"}`) + result := applyPayloadConfigWithRoot(cfg, "gemini-2.0-flash", "gemini", "", payload, nil, "") + var got map[string]any + err := json.Unmarshal(result, &got) + require.NoError(t, err) + assert.Equal(t, "BLOCK_NONE", got["safetySettings"]) + }) + + t.Run("responses protocol applies", func(t *testing.T) { + t.Parallel() + payload := []byte(`{"model":"responses-model"}`) + result := applyPayloadConfigWithRoot(cfg, "responses-model", "responses", "", payload, nil, "") + var got map[string]any + err := json.Unmarshal(result, &got) + require.NoError(t, err) + assert.Equal(t, float64(4096), got["maxOutputTokens"]) + }) + + t.Run("mismatched protocol does not apply", func(t *testing.T) { + t.Parallel() + payload := []byte(`{"model":"openai-model"}`) + result := applyPayloadConfigWithRoot(cfg, "openai-model", "openai", "", payload, nil, "") + var got map[string]any + err := json.Unmarshal(result, &got) + require.NoError(t, err) + _, hasSafety := got["safetySettings"] + _, hasMaxOutput := got["maxOutputTokens"] + assert.False(t, hasSafety || hasMaxOutput, "no rules should apply for mismatched protocol") + }) +} + +func TestApplyPayloadConfigWithRoot_RequestedModelAlias(t *testing.T) { + t.Parallel() + + cfg := &config.Config{ + Payload: config.PayloadConfig{ + Default: []config.PayloadRule{ + { + // Rule targeting the alias + Models: []config.PayloadModelRule{ + {Name: "gemini-pro", Protocol: ""}, + }, + Params: map[string]any{"thinkingBudget": 10000}, + }, + }, + }, + } + + payload := []byte(`{"model":"gemini-2.0-pro"}`) + // Original request had the alias "gemini-pro" + original := []byte(`{"model":"gemini-pro"}`) + + // Upstream model is "gemini-2.0-pro", but original was "gemini-pro" + result := applyPayloadConfigWithRoot(cfg, "gemini-2.0-pro", "", "", payload, original, "gemini-pro") + + var got map[string]any + err := json.Unmarshal(result, &got) + require.NoError(t, err) + + // Rule targeting "gemini-pro" should apply because requestedModel is "gemini-pro" + assert.Equal(t, float64(10000), got["thinkingBudget"], "rule should match via requestedModel alias") +} + +func TestApplyPayloadConfigWithRoot_SplitCounts(t *testing.T) { + t.Parallel() + + // This test verifies that conditional and unconditional rules are tracked separately + cfg := &config.Config{ + Payload: config.PayloadConfig{ + Default: []config.PayloadRule{ + { + // Unconditional rule + Models: []config.PayloadModelRule{}, + Params: map[string]any{"defaultUnconditional": "value1"}, + }, + { + // Conditional rule + Models: []config.PayloadModelRule{ + {Name: "gemini-*", Protocol: ""}, + }, + Params: map[string]any{"defaultConditional": "value2"}, + }, + }, + Override: []config.PayloadRule{ + { + // Unconditional rule + Models: []config.PayloadModelRule{}, + Params: map[string]any{"overrideUnconditional": "value3"}, + }, + { + // Conditional rule + Models: []config.PayloadModelRule{ + {Name: "claude-*", Protocol: ""}, + }, + Params: map[string]any{"overrideConditional": "value4"}, + }, + }, + Filter: []config.PayloadFilterRule{ + { + // Unconditional filter + Models: []config.PayloadModelRule{}, + Params: []string{"filterUnconditional"}, + }, + { + // Conditional filter + Models: []config.PayloadModelRule{ + {Name: "gemini-*", Protocol: ""}, + }, + Params: []string{"filterConditional"}, + }, + }, + }, + } + + payload := []byte(`{ + "model":"gemini-2.0-flash", + "defaultUnconditional":"orig1", + "defaultConditional":"orig2", + "overrideUnconditional":"orig3", + "overrideConditional":"orig4", + "filterUnconditional":"keep", + "filterConditional":"keep" + }`) + original := []byte(`{"model":"gemini-2.0-flash"}`) + + result := applyPayloadConfigWithRoot(cfg, "gemini-2.0-flash", "", "", payload, original, "") + + var got map[string]any + err := json.Unmarshal(result, &got) + require.NoError(t, err) + + // Default rules: conditional gemini-* applies, unconditional applies + assert.Equal(t, "value1", got["defaultUnconditional"]) + assert.Equal(t, "value2", got["defaultConditional"]) + + // Override rules: conditional claude-* does NOT apply, unconditional applies + assert.Equal(t, "value3", got["overrideUnconditional"]) + assert.Equal(t, "orig4", got["overrideConditional"]) + + // Filter rules: both conditional and unconditional paths applied correctly + _, hasFilterCond := got["filterConditional"] + _, hasFilterUncond := got["filterUnconditional"] + assert.False(t, hasFilterCond, "conditional filter should have removed the field") + assert.True(t, hasFilterUncond, "unconditional filter should have removed the field") +} diff --git a/pkg/llmproxy/executor/qwen_executor.go b/pkg/llmproxy/executor/qwen_executor.go index 27ea65797c..a0b4b28183 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/v7/pkg/llmproxy/auth/qwen" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" cliproxyauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" cliproxyexecutor "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/executor" diff --git a/pkg/llmproxy/executor/thinking_providers.go b/pkg/llmproxy/executor/thinking_providers.go index 05c2928590..230c175ecb 100644 --- a/pkg/llmproxy/executor/thinking_providers.go +++ b/pkg/llmproxy/executor/thinking_providers.go @@ -8,5 +8,6 @@ import ( _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/geminicli" _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/iflow" _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/kimi" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/minimax" _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/openai" ) diff --git a/pkg/llmproxy/interfaces/error_message.go b/pkg/llmproxy/interfaces/error_message.go index d971ba7ae0..2ccdcb2f30 100644 --- a/pkg/llmproxy/interfaces/error_message.go +++ b/pkg/llmproxy/interfaces/error_message.go @@ -1,7 +1,7 @@ package interfaces import ( - internalinterfaces "github.com/kooshapari/CLIProxyAPI/v7/internal/interfaces" + internalinterfaces "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/interfaces" ) // ErrorMessage encapsulates an error with an associated HTTP status code. diff --git a/pkg/llmproxy/logging/global_logger.go b/pkg/llmproxy/logging/global_logger.go index de7dfc9062..8424584b79 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" log "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" diff --git a/pkg/llmproxy/registry/model_registry.go b/pkg/llmproxy/registry/model_registry.go index ab5f93cd0c..7306417ed3 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/v7/internal/misc" + misc "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/misc" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/thinking/apply.go b/pkg/llmproxy/thinking/apply.go index ff46896ecd..3d06cfbe6f 100644 --- a/pkg/llmproxy/thinking/apply.go +++ b/pkg/llmproxy/thinking/apply.go @@ -531,6 +531,68 @@ func extractIFlowConfig(body []byte) ThinkingConfig { return ThinkingConfig{Mode: ModeNone, Budget: 0} } + // iFlow compatibility format: nested reasoning object + if reasoning := gjson.GetBytes(body, "reasoning"); reasoning.Exists() { + if effort := reasoning.Get("effort"); effort.Exists() { + value := strings.ToLower(strings.TrimSpace(effort.String())) + switch value { + case "": + return ThinkingConfig{} + case "none": + return ThinkingConfig{Mode: ModeNone, Budget: 0} + case "auto": + return ThinkingConfig{Mode: ModeAuto, Budget: -1} + default: + return ThinkingConfig{Mode: ModeLevel, Level: ThinkingLevel(value)} + } + } + } + + // iFlow compatibility format: literal key 'reasoning.effort' + if effort := gjson.GetBytes(body, `reasoning\.effort`); effort.Exists() { + value := strings.ToLower(strings.TrimSpace(effort.String())) + switch value { + case "": + return ThinkingConfig{} + case "none": + return ThinkingConfig{Mode: ModeNone, Budget: 0} + case "auto": + return ThinkingConfig{Mode: ModeAuto, Budget: -1} + default: + return ThinkingConfig{Mode: ModeLevel, Level: ThinkingLevel(value)} + } + } + + // iFlow compatibility format: dotted path reasoning.effort + if effort := gjson.GetBytes(body, "reasoning.effort"); effort.Exists() { + value := strings.ToLower(strings.TrimSpace(effort.String())) + switch value { + case "": + return ThinkingConfig{} + case "none": + return ThinkingConfig{Mode: ModeNone, Budget: 0} + case "auto": + return ThinkingConfig{Mode: ModeAuto, Budget: -1} + default: + return ThinkingConfig{Mode: ModeLevel, Level: ThinkingLevel(value)} + } + } + + // iFlow compatibility format: openai-style reasoning_effort + if effort := gjson.GetBytes(body, "reasoning_effort"); effort.Exists() { + value := strings.ToLower(strings.TrimSpace(effort.String())) + switch value { + case "": + return ThinkingConfig{} + case "none": + return ThinkingConfig{Mode: ModeNone, Budget: 0} + case "auto": + return ThinkingConfig{Mode: ModeAuto, Budget: -1} + default: + return ThinkingConfig{Mode: ModeLevel, Level: ThinkingLevel(value)} + } + } + return ThinkingConfig{} } diff --git a/pkg/llmproxy/thinking/provider/iflow/apply.go b/pkg/llmproxy/thinking/provider/iflow/apply.go index c43fd59112..1ac03d8fe8 100644 --- a/pkg/llmproxy/thinking/provider/iflow/apply.go +++ b/pkg/llmproxy/thinking/provider/iflow/apply.go @@ -143,6 +143,10 @@ func applyMiniMax(body []byte, config thinking.ThinkingConfig) []byte { } result, _ := sjson.SetBytes(body, "reasoning_split", reasoningSplit) + result, _ = sjson.DeleteBytes(result, "reasoning") + result, _ = sjson.DeleteBytes(result, "reasoning_effort") + result, _ = sjson.DeleteBytes(result, "reasoning.effort") + result, _ = sjson.DeleteBytes(result, "variant") return result } diff --git a/pkg/llmproxy/thinking/provider/minimax/minimax.go b/pkg/llmproxy/thinking/provider/minimax/minimax.go new file mode 100644 index 0000000000..312aad8774 --- /dev/null +++ b/pkg/llmproxy/thinking/provider/minimax/minimax.go @@ -0,0 +1,18 @@ +package minimax + +import ( + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/iflow" +) + +// NewApplier returns a Minimax applier. +// +// Current Minimax implementations share the same request shape and behavior as iFlow +// models that use the reasoning_split toggle. +func NewApplier() *iflow.Applier { + return iflow.NewApplier() +} + +func init() { + thinking.RegisterProvider("minimax", NewApplier()) +} diff --git a/pkg/llmproxy/thinking/strip.go b/pkg/llmproxy/thinking/strip.go index eb69171504..708c20ac34 100644 --- a/pkg/llmproxy/thinking/strip.go +++ b/pkg/llmproxy/thinking/strip.go @@ -41,10 +41,11 @@ func StripThinkingConfig(body []byte, provider string) []byte { paths = []string{"reasoning.effort"} case "iflow": paths = []string{ - "chat_template_kwargs.enable_thinking", - "chat_template_kwargs.clear_thinking", + "chat_template_kwargs", "reasoning_split", "reasoning_effort", + "reasoning", + "variant", } default: return body diff --git a/pkg/llmproxy/translator/antigravity/gemini/antigravity_gemini_request.go b/pkg/llmproxy/translator/antigravity/gemini/antigravity_gemini_request.go index f664e4933f..ffd4d4af22 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/v7/internal/translator/gemini/common" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/translator/gemini/common" "github.com/kooshapari/CLIProxyAPI/v7/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 94818cff9c..e144fed6d6 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/v7/internal/translator/gemini/common" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/translator/gemini/common" "github.com/kooshapari/CLIProxyAPI/v7/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 53074b4843..b2a0e88045 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/v7/internal/translator/gemini/common" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/translator/gemini/common" "github.com/kooshapari/CLIProxyAPI/v7/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 dd93e7a58a..938a7d2ea8 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/v7/internal/translator/gemini/common" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/translator/gemini/common" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" diff --git a/pkg/llmproxy/translator/openai/claude/openai_claude_response.go b/pkg/llmproxy/translator/openai/claude/openai_claude_response.go index f7ffa85cf9..ad8658deb4 100644 --- a/pkg/llmproxy/translator/openai/claude/openai_claude_response.go +++ b/pkg/llmproxy/translator/openai/claude/openai_claude_response.go @@ -76,6 +76,10 @@ type ToolCallAccumulator struct { // Returns: // - []string: A slice of strings, each containing an Anthropic-compatible JSON response. func ConvertOpenAIResponseToClaude(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string { + var localParam any + if param == nil { + param = &localParam + } if *param == nil { *param = &ConvertOpenAIResponseToAnthropicParams{ MessageID: "", @@ -100,6 +104,11 @@ func ConvertOpenAIResponseToClaude(_ context.Context, _ string, originalRequestR return convertOpenAIDoneToAnthropic((*param).(*ConvertOpenAIResponseToAnthropicParams)) } + streamResult := gjson.GetBytes(originalRequestRawJSON, "stream") + if !streamResult.Exists() || (streamResult.Exists() && streamResult.Type == gjson.False) { + return convertOpenAINonStreamingToAnthropic(rawJSON) + } + if !bytes.HasPrefix(rawJSON, dataTag) { return []string{} } @@ -111,12 +120,7 @@ func ConvertOpenAIResponseToClaude(_ context.Context, _ string, originalRequestR return convertOpenAIDoneToAnthropic((*param).(*ConvertOpenAIResponseToAnthropicParams)) } - streamResult := gjson.GetBytes(originalRequestRawJSON, "stream") - if !streamResult.Exists() || (streamResult.Exists() && streamResult.Type == gjson.False) { - return convertOpenAINonStreamingToAnthropic(rawJSON) - } else { - return convertOpenAIStreamingChunkToAnthropic(rawJSON, (*param).(*ConvertOpenAIResponseToAnthropicParams)) - } + return convertOpenAIStreamingChunkToAnthropic(rawJSON, (*param).(*ConvertOpenAIResponseToAnthropicParams)) } // convertOpenAIStreamingChunkToAnthropic converts OpenAI streaming chunk to Anthropic streaming events diff --git a/pkg/llmproxy/util/provider.go b/pkg/llmproxy/util/provider.go index c856d0b88d..143ea1976a 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/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 373bde228f..e6557e7ed3 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/registry" ) diff --git a/pkg/llmproxy/util/proxy.go b/pkg/llmproxy/util/proxy.go index fa2b484baf..6d8aabf02a 100644 --- a/pkg/llmproxy/util/proxy.go +++ b/pkg/llmproxy/util/proxy.go @@ -8,8 +8,9 @@ import ( "net" "net/http" "net/url" + "strings" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" "golang.org/x/net/proxy" ) @@ -18,39 +19,49 @@ import ( // It supports SOCKS5, HTTP, and HTTPS proxies. The function modifies the client's transport // to route requests through the configured proxy server. func SetProxy(cfg *config.SDKConfig, httpClient *http.Client) *http.Client { + if httpClient == nil { + httpClient = &http.Client{} + } + if cfg == nil { + return httpClient + } + + proxyStr := strings.TrimSpace(cfg.ProxyURL) + if proxyStr == "" { + return httpClient + } + + proxyURL, errParse := url.Parse(proxyStr) + if errParse != nil { + log.Errorf("parse proxy URL failed: %v", errParse) + return httpClient + } + var transport *http.Transport - // Attempt to parse the proxy URL from the configuration. - proxyURL, errParse := url.Parse(cfg.ProxyURL) - if errParse == nil { - // Handle different proxy schemes. - switch proxyURL.Scheme { - case "socks5": - // Configure SOCKS5 proxy with optional authentication. - var proxyAuth *proxy.Auth - if proxyURL.User != nil { - username := proxyURL.User.Username() - password, _ := proxyURL.User.Password() - proxyAuth = &proxy.Auth{User: username, Password: password} - } - dialer, errSOCKS5 := proxy.SOCKS5("tcp", proxyURL.Host, proxyAuth, proxy.Direct) - if errSOCKS5 != nil { - log.Errorf("create SOCKS5 dialer failed: %v", errSOCKS5) - return httpClient - } - // Set up a custom transport using the SOCKS5 dialer. - transport = &http.Transport{ - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialer.Dial(network, addr) - }, - } - case "http", "https": - // Configure HTTP or HTTPS proxy. - transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)} + switch proxyURL.Scheme { + case "socks5": + var proxyAuth *proxy.Auth + if proxyURL.User != nil { + username := proxyURL.User.Username() + password, _ := proxyURL.User.Password() + proxyAuth = &proxy.Auth{User: username, Password: password} } + dialer, errSOCKS5 := proxy.SOCKS5("tcp", proxyURL.Host, proxyAuth, proxy.Direct) + if errSOCKS5 != nil { + log.Errorf("create SOCKS5 dialer failed: %v", errSOCKS5) + return httpClient + } + transport = &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.Dial(network, addr) + }, + } + case "http", "https": + transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)} + default: + return httpClient } - // If a new transport was created, apply it to the HTTP client. - if transport != nil { - httpClient.Transport = transport - } + + httpClient.Transport = transport return httpClient } diff --git a/pkg/llmproxy/util/util.go b/pkg/llmproxy/util/util.go index b5029b3d92..c86a3fa681 100644 --- a/pkg/llmproxy/util/util.go +++ b/pkg/llmproxy/util/util.go @@ -11,7 +11,7 @@ import ( "regexp" "strings" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/util/util_test.go b/pkg/llmproxy/util/util_test.go index 5427f0ba37..a39ca9775a 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) func TestSetLogLevel(t *testing.T) { diff --git a/pkg/llmproxy/watcher/clients.go b/pkg/llmproxy/watcher/clients.go index 552e75a15d..1972e9a60e 100644 --- a/pkg/llmproxy/watcher/clients.go +++ b/pkg/llmproxy/watcher/clients.go @@ -14,9 +14,9 @@ import ( "strings" "time" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" - "github.com/kooshapari/CLIProxyAPI/v7/internal/util" - "github.com/kooshapari/CLIProxyAPI/v7/internal/watcher/diff" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/watcher/diff" coreauth "github.com/kooshapari/CLIProxyAPI/v7/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 a162a4f934..67a2ff21bd 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/v7/internal/config" - "github.com/kooshapari/CLIProxyAPI/v7/internal/util" - "github.com/kooshapari/CLIProxyAPI/v7/internal/watcher/diff" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/util" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 40e7118da6..046ed62a2c 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // BuildConfigChangeDetails computes a redacted, human-readable list of config changes. diff --git a/pkg/llmproxy/watcher/diff/model_hash.go b/pkg/llmproxy/watcher/diff/model_hash.go index 03cbcbc226..7de0da58dd 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // ComputeOpenAICompatModelsHash returns a stable hash for OpenAI-compat models. diff --git a/pkg/llmproxy/watcher/diff/models_summary.go b/pkg/llmproxy/watcher/diff/models_summary.go index 65ee716885..919f506255 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) type GeminiModelsSummary struct { diff --git a/pkg/llmproxy/watcher/diff/oauth_excluded.go b/pkg/llmproxy/watcher/diff/oauth_excluded.go index b201aa5d45..953ee0d83f 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) type ExcludedModelsSummary struct { diff --git a/pkg/llmproxy/watcher/diff/oauth_model_alias.go b/pkg/llmproxy/watcher/diff/oauth_model_alias.go index ae84690bb9..a86525173d 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) type OAuthModelAliasSummary struct { diff --git a/pkg/llmproxy/watcher/diff/openai_compat.go b/pkg/llmproxy/watcher/diff/openai_compat.go index e3f4c3791e..f85ca026fd 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) // DiffOpenAICompatibility produces human-readable change descriptions. diff --git a/pkg/llmproxy/watcher/dispatcher.go b/pkg/llmproxy/watcher/dispatcher.go index 0551475c75..5757c11d94 100644 --- a/pkg/llmproxy/watcher/dispatcher.go +++ b/pkg/llmproxy/watcher/dispatcher.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" - "github.com/kooshapari/CLIProxyAPI/v7/internal/watcher/synthesizer" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/watcher/synthesizer" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/events.go b/pkg/llmproxy/watcher/events.go index 844f62999a..19ca676054 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/v7/internal/auth/kiro" + kiroauth "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/kiro" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/watcher/synthesizer/config.go b/pkg/llmproxy/watcher/synthesizer/config.go index a1e7e06c9c..f4ac7c1fbe 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/v7/internal/auth/kiro" - "github.com/kooshapari/CLIProxyAPI/v7/internal/watcher/diff" + kiroauth "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/auth/kiro" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/watcher/diff" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) diff --git a/pkg/llmproxy/watcher/synthesizer/context.go b/pkg/llmproxy/watcher/synthesizer/context.go index 33b3004474..d0cfa2ce31 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/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 3394f1b1ce..14f5991db7 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/v7/internal/runtime/geminicli" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/runtime/geminicli" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/synthesizer/helpers.go b/pkg/llmproxy/watcher/synthesizer/helpers.go index b051bec2ec..bf7dcc9dd2 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/v7/internal/config" - "github.com/kooshapari/CLIProxyAPI/v7/internal/watcher/diff" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/watcher/diff" coreauth "github.com/kooshapari/CLIProxyAPI/v7/sdk/cliproxy/auth" ) diff --git a/pkg/llmproxy/watcher/watcher.go b/pkg/llmproxy/watcher/watcher.go index 56e07cee80..f15150fac2 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/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" "gopkg.in/yaml.v3" sdkAuth "github.com/kooshapari/CLIProxyAPI/v7/sdk/auth" diff --git a/sdk/auth/filestore.go b/sdk/auth/filestore.go index caa100d59a..3813b2e900 100644 --- a/sdk/auth/filestore.go +++ b/sdk/auth/filestore.go @@ -288,31 +288,51 @@ func (s *FileTokenStore) resolveAuthPath(auth *cliproxyauth.Auth) (string, error if auth == nil { return "", fmt.Errorf("auth filestore: auth is nil") } + + var candidate string + + // Determine the candidate path from various sources. if auth.Attributes != nil { if p := strings.TrimSpace(auth.Attributes["path"]); p != "" { - return p, nil + candidate = p } } - if fileName := strings.TrimSpace(auth.FileName); fileName != "" { + if candidate == "" && auth.FileName != "" { + fileName := strings.TrimSpace(auth.FileName) if filepath.IsAbs(fileName) { - return fileName, nil - } - if dir := s.baseDirSnapshot(); dir != "" { - return filepath.Join(dir, fileName), nil + candidate = fileName + } else if dir := s.baseDirSnapshot(); dir != "" { + candidate = filepath.Join(dir, fileName) + } else { + candidate = fileName } - return fileName, nil } - if auth.ID == "" { - return "", fmt.Errorf("auth filestore: missing id") - } - if filepath.IsAbs(auth.ID) { - return auth.ID, nil + if candidate == "" { + if auth.ID == "" { + return "", fmt.Errorf("auth filestore: missing id") + } + id := strings.TrimSpace(auth.ID) + if filepath.IsAbs(id) { + candidate = id + } else { + dir := s.baseDirSnapshot() + if dir == "" { + return "", fmt.Errorf("auth filestore: directory not configured") + } + candidate = filepath.Join(dir, id) + } } - dir := s.baseDirSnapshot() - if dir == "" { - return "", fmt.Errorf("auth filestore: directory not configured") + + // Normalize and validate the resolved path stays within the configured base directory. + resolved := filepath.Clean(candidate) + if dir := s.baseDirSnapshot(); dir != "" { + cleanBase := filepath.Clean(dir) + if resolved != cleanBase && !strings.HasPrefix(resolved, cleanBase+string(os.PathSeparator)) { + return "", fmt.Errorf("auth filestore: path escapes base directory") + } } - return filepath.Join(dir, auth.ID), nil + + return resolved, nil } func (s *FileTokenStore) labelFor(metadata map[string]any) string { diff --git a/sdk/config/config.go b/sdk/config/config.go index dfb4011a3f..6c0aafee6f 100644 --- a/sdk/config/config.go +++ b/sdk/config/config.go @@ -6,49 +6,49 @@ package config import llmproxyconfig "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" -type SDKConfig = pkgconfig.SDKConfig - -type Config = pkgconfig.Config - -type StreamingConfig = pkgconfig.StreamingConfig -type TLSConfig = pkgconfig.TLSConfig -type RemoteManagement = pkgconfig.RemoteManagement -type AmpCode = pkgconfig.AmpCode -type OAuthModelAlias = pkgconfig.OAuthModelAlias -type PayloadConfig = pkgconfig.PayloadConfig -type PayloadRule = pkgconfig.PayloadRule -type PayloadFilterRule = pkgconfig.PayloadFilterRule -type PayloadModelRule = pkgconfig.PayloadModelRule - -type GeminiKey = pkgconfig.GeminiKey -type CodexKey = pkgconfig.CodexKey -type ClaudeKey = pkgconfig.ClaudeKey -type VertexCompatKey = pkgconfig.VertexCompatKey -type VertexCompatModel = pkgconfig.VertexCompatModel -type OpenAICompatibility = pkgconfig.OpenAICompatibility -type OpenAICompatibilityAPIKey = pkgconfig.OpenAICompatibilityAPIKey -type OpenAICompatibilityModel = pkgconfig.OpenAICompatibilityModel - -type TLS = pkgconfig.TLSConfig +type SDKConfig = llmproxyconfig.SDKConfig + +type Config = llmproxyconfig.Config + +type StreamingConfig = llmproxyconfig.StreamingConfig +type TLSConfig = llmproxyconfig.TLSConfig +type RemoteManagement = llmproxyconfig.RemoteManagement +type AmpCode = llmproxyconfig.AmpCode +type OAuthModelAlias = llmproxyconfig.OAuthModelAlias +type PayloadConfig = llmproxyconfig.PayloadConfig +type PayloadRule = llmproxyconfig.PayloadRule +type PayloadFilterRule = llmproxyconfig.PayloadFilterRule +type PayloadModelRule = llmproxyconfig.PayloadModelRule + +type GeminiKey = llmproxyconfig.GeminiKey +type CodexKey = llmproxyconfig.CodexKey +type ClaudeKey = llmproxyconfig.ClaudeKey +type VertexCompatKey = llmproxyconfig.VertexCompatKey +type VertexCompatModel = llmproxyconfig.VertexCompatModel +type OpenAICompatibility = llmproxyconfig.OpenAICompatibility +type OpenAICompatibilityAPIKey = llmproxyconfig.OpenAICompatibilityAPIKey +type OpenAICompatibilityModel = llmproxyconfig.OpenAICompatibilityModel + +type TLS = llmproxyconfig.TLSConfig const ( - DefaultPanelGitHubRepository = pkgconfig.DefaultPanelGitHubRepository + DefaultPanelGitHubRepository = llmproxyconfig.DefaultPanelGitHubRepository ) -func LoadConfig(configFile string) (*Config, error) { return pkgconfig.LoadConfig(configFile) } +func LoadConfig(configFile string) (*Config, error) { return llmproxyconfig.LoadConfig(configFile) } func LoadConfigOptional(configFile string, optional bool) (*Config, error) { - return pkgconfig.LoadConfigOptional(configFile, optional) + return llmproxyconfig.LoadConfigOptional(configFile, optional) } func SaveConfigPreserveComments(configFile string, cfg *Config) error { - return pkgconfig.SaveConfigPreserveComments(configFile, cfg) + return llmproxyconfig.SaveConfigPreserveComments(configFile, cfg) } func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []string, value string) error { - return pkgconfig.SaveConfigPreserveCommentsUpdateNestedScalar(configFile, path, value) + return llmproxyconfig.SaveConfigPreserveCommentsUpdateNestedScalar(configFile, path, value) } func NormalizeCommentIndentation(data []byte) []byte { - return pkgconfig.NormalizeCommentIndentation(data) + return llmproxyconfig.NormalizeCommentIndentation(data) } diff --git a/test/amp_management_test.go b/test/amp_management_test.go index 474987b804..96bd5be801 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/v7/internal/api/handlers/management" - "github.com/kooshapari/CLIProxyAPI/v7/internal/config" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/api/handlers/management" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/config" ) func init() { diff --git a/test/builtin_tools_translation_test.go b/test/builtin_tools_translation_test.go index 2d9e8611d9..4c95336d0f 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/v7/internal/translator" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/translator" sdktranslator "github.com/kooshapari/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" diff --git a/test/thinking_conversion_test.go b/test/thinking_conversion_test.go index 8d1a35fe9e..de9b3439d4 100644 --- a/test/thinking_conversion_test.go +++ b/test/thinking_conversion_test.go @@ -6,20 +6,21 @@ import ( "testing" "time" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/translator" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/translator" // Import provider packages to trigger init() registration of ProviderAppliers - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/antigravity" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/claude" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/codex" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/gemini" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/geminicli" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/iflow" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/kimi" - _ "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking/provider/openai" - - "github.com/kooshapari/CLIProxyAPI/v7/internal/registry" - "github.com/kooshapari/CLIProxyAPI/v7/internal/thinking" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/antigravity" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/claude" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/codex" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/gemini" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/geminicli" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/iflow" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/kimi" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/minimax" + _ "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking/provider/openai" + + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/registry" + "github.com/kooshapari/CLIProxyAPI/v7/pkg/llmproxy/thinking" sdktranslator "github.com/kooshapari/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" "github.com/tidwall/sjson"