mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 08:19:59 +00:00
chore(cliproxy): improve config and token management
This commit is contained in:
+175
@@ -0,0 +1,175 @@
|
||||
[2025-12-12 02:50:06] [info] [main.go:413] CLIProxyAPI Version: 6.6.5, Commit: fc054db, BuiltAt: 2025-12-12T00:54:34Z
|
||||
[2025-12-12 02:50:06] [info] [server.go:466] management routes registered after secret key configuration
|
||||
[2025-12-12 02:50:06] [info] [watcher.go:1000] full client load complete - 5 clients (5 auth files + 0 Gemini API keys + 0 Vertex API keys + 0 Claude API keys + 0 Codex keys + 0 OpenAI-compat)
|
||||
[2025-12-12 02:50:06] [info] [service.go:543] file watcher started for config and auth directory changes
|
||||
[2025-12-12 02:50:06] [info] [service.go:549] core auth auto-refresh started (interval=15m0s)
|
||||
[2025-12-12 02:50:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:18 | 200 | 0s | 127.0.0.1 | GET "/"
|
||||
[2025-12-12 02:50:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:18 | 200 | 0s | 127.0.0.1 | GET "/v1/models"
|
||||
[2025-12-12 02:50:19] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:19 | 200 | 0s | ::1 | GET "/"
|
||||
[2025-12-12 02:50:19] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:19 | 200 | 0s | ::1 | GET "/"
|
||||
[2025-12-12 02:50:19] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:19 | 200 | 49ms | ::1 | GET "/v0/management/debug"
|
||||
[2025-12-12 02:50:19] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:19 | 200 | 50ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:50:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:20 | 200 | 49ms | ::1 | GET "/v0/management/usage"
|
||||
[2025-12-12 02:50:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:20 | 200 | 0s | ::1 | GET "/v1/models"
|
||||
[2025-12-12 02:50:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:20 | 200 | 49ms | ::1 | GET "/v0/management/config.yaml"
|
||||
[2025-12-12 02:50:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:20 | 200 | 49ms | ::1 | GET "/v0/management/auth-files"
|
||||
[2025-12-12 02:50:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:20 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:50:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:20 | 200 | 49ms | ::1 | GET "/v0/management/oauth-excluded-models"
|
||||
[2025-12-12 02:50:32] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:32 | 200 | 0s | 127.0.0.1 | GET "/"
|
||||
[2025-12-12 02:50:32] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:32 | 200 | 0s | 127.0.0.1 | GET "/v1/models"
|
||||
[2025-12-12 02:50:42] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:42 | 200 | 2.115s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:43] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:50:43 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:50:43] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:50:43 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:50:43] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:50:43 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:50:43] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:50:43 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:50:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:44 | 200 | 773ms | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:50:45] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:45 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:50:45] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:45 | 200 | 1.568s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:45] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:50:45 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:50:45] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:50:45 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:50:47] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:47 | 200 | 1.16s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:47] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:47 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:50:48] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:48 | 200 | 6.774s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:49] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:49 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:50:50] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:50 | 200 | 7.951s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:50] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:50 | 200 | 0s | ::1 | GET "/"
|
||||
[2025-12-12 02:50:50] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:50 | 200 | 0s | ::1 | GET "/"
|
||||
[2025-12-12 02:50:50] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:50 | 200 | 50ms | ::1 | GET "/v0/management/debug"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 49ms | ::1 | GET "/v0/management/usage"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 0s | ::1 | GET "/v1/models"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 53ms | ::1 | GET "/v0/management/auth-files"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 53ms | ::1 | GET "/v0/management/config.yaml"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 49ms | ::1 | GET "/v0/management/oauth-excluded-models"
|
||||
[2025-12-12 02:50:51] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:51 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:50:52] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:52 | 200 | 2.247s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:55] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:55 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:50:55] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:55 | 200 | 13.83s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:55] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:55 | 200 | 3.023s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:50:55] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:50:55 | 200 | 10.921s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:00] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:51:00 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:51:00] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:51:00 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:51:00] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:51:00 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:51:00] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:51:00 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:51:02] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:02 | 200 | 1.16s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:02] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:02 | 200 | 1.254s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:02] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:51:02 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:51:02] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:02 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:02] [error] [gin_logger.go:60] [GIN] 2025/12/12 - 02:51:02 | 501 | 0s | 127.0.0.1 | POST "/api/provider/agy/v1/messages/count_tokens?beta=true"
|
||||
[2025-12-12 02:51:03] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:03 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:05] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:05 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:06] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:06 | 200 | 3.412s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:06] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:06 | 200 | 8.706s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:07] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:07 | 200 | 6.799s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:08] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:08 | 200 | 10.614s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:09] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:09 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:10] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:10 | 200 | 1.661s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:11] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:11 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:13 | 200 | 3.155s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:16] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:16 | 200 | 8.675s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:17] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:17 | 200 | 1.132s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:17] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:17 | 200 | 1.16s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:17] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:17 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:18 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:18 | 200 | 2.018s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:18 | 200 | 2.137s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:18 | 200 | 2.219s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:19] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:19 | 200 | 2.993s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:20 | 200 | 955ms | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:20] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:20 | 200 | 1.468s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:21] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:21 | 200 | 23.465s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:21] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:21 | 200 | 2.506s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:22] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:22 | 200 | 3.102s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 50ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 50ms | ::1 | GET "/v0/management/usage"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 0s | ::1 | GET "/v1/models"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 50ms | ::1 | GET "/v0/management/config.yaml"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 50ms | ::1 | GET "/v0/management/auth-files"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:51:23] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:23 | 200 | 49ms | ::1 | GET "/v0/management/oauth-excluded-models"
|
||||
[2025-12-12 02:51:29] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:29 | 200 | 6.533s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:30] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:30 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:30] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:30 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:31] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:31 | 200 | 1.337s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:34 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:35] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:35 | 200 | 5.291s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:36] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:36 | 200 | 1.482s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:38] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:38 | 200 | 2.867s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:40] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:40 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:40] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:40 | 200 | 5.468s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:43 | 200 | 7.802s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:45] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:45 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:47] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:47 | 200 | 4.344s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:48] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:48 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:48] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:48 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:52] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:52 | 200 | 4.022s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:52] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:52 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:51:54] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:54 | 200 | 5.541s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:57] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:57 | 200 | 3.579s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:58] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:58 | 200 | 4.434s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:51:59] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:51:59 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:01] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:01 | 200 | 6.851s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:03] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:03 | 200 | 2.477s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:04] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:04 | 200 | 2.796s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:06] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:06 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:08] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:08 | 200 | 3.953s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:09] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:09 | 200 | 741ms | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:11] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:11 | 200 | 1.983s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:12] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:12 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:12] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:12 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:13 | 200 | 3.488s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:52:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:18 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 49ms | ::1 | GET "/v0/management/usage"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 0s | ::1 | GET "/v1/models"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 49ms | ::1 | GET "/v0/management/config.yaml"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 49ms | ::1 | GET "/v0/management/auth-files"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:52:34] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:34 | 200 | 49ms | ::1 | GET "/v0/management/oauth-excluded-models"
|
||||
[2025-12-12 02:52:42] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:42 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:43 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:52:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:52:43 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:18] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:18 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:43 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:43 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:53:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:53:43 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:54:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:54:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:54:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:54:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:54:13] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:54:13 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:54:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:54:43 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:54:43] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:54:43 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:54:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:54:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:55:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:55:14 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:55:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:55:14 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:55:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:55:14 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:55:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:55:44 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:55:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:55:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:55:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:55:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:14 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:14 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:14 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:44] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:44 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:56:57] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:57 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:56:57] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:57 | 200 | 49ms | ::1 | GET "/v0/management/usage"
|
||||
[2025-12-12 02:56:57] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:57 | 200 | 0s | ::1 | GET "/v1/models"
|
||||
[2025-12-12 02:56:58] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:58 | 200 | 49ms | ::1 | GET "/v0/management/config.yaml"
|
||||
[2025-12-12 02:56:58] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:58 | 200 | 49ms | ::1 | GET "/v0/management/auth-files"
|
||||
[2025-12-12 02:56:58] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:58 | 200 | 49ms | ::1 | GET "/v0/management/config"
|
||||
[2025-12-12 02:56:58] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:56:58 | 200 | 49ms | ::1 | GET "/v0/management/oauth-excluded-models"
|
||||
[2025-12-12 02:57:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:57:14 | 200 | 1ms | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:57:14] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:57:14 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:57:15] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:57:15 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:57:16] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:57:16 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:57:22] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:57:22 | 200 | 0s | 127.0.0.1 | POST "/api/provider/agy/api/event_logging/batch"
|
||||
[2025-12-12 02:57:22] [info] [gin_logger.go:64] [GIN] 2025/12/12 - 02:57:22 | 200 | 5.549s | 127.0.0.1 | POST "/api/provider/agy/v1/messages?beta=true"
|
||||
[2025-12-12 02:57:26] [error] [service.go:613] error stopping API server: failed to shutdown HTTP server: context deadline exceeded
|
||||
[2025-12-12 02:57:26] [error] [service.go:425] service shutdown returned error: failed to shutdown HTTP server: context deadline exceeded
|
||||
@@ -130,11 +130,54 @@ export function saveAccountsRegistry(registry: AccountsRegistry): void {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync registry with actual token files
|
||||
* Removes stale entries where token file no longer exists
|
||||
* Called automatically when loading accounts
|
||||
*/
|
||||
function syncRegistryWithTokenFiles(registry: AccountsRegistry): boolean {
|
||||
const authDir = getAuthDir();
|
||||
let modified = false;
|
||||
|
||||
for (const [_providerName, providerAccounts] of Object.entries(registry.providers)) {
|
||||
if (!providerAccounts) continue;
|
||||
|
||||
const staleIds: string[] = [];
|
||||
|
||||
for (const [accountId, meta] of Object.entries(providerAccounts.accounts)) {
|
||||
const tokenPath = path.join(authDir, meta.tokenFile);
|
||||
if (!fs.existsSync(tokenPath)) {
|
||||
staleIds.push(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stale accounts
|
||||
for (const id of staleIds) {
|
||||
delete providerAccounts.accounts[id];
|
||||
modified = true;
|
||||
|
||||
// Update default if deleted
|
||||
if (providerAccounts.default === id) {
|
||||
const remainingIds = Object.keys(providerAccounts.accounts);
|
||||
providerAccounts.default = remainingIds[0] || 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all accounts for a provider
|
||||
*/
|
||||
export function getProviderAccounts(provider: CLIProxyProvider): AccountInfo[] {
|
||||
const registry = loadAccountsRegistry();
|
||||
|
||||
// Sync with actual token files (removes stale entries)
|
||||
if (syncRegistryWithTokenFiles(registry)) {
|
||||
saveAccountsRegistry(registry);
|
||||
}
|
||||
|
||||
const providerAccounts = registry.providers[provider];
|
||||
|
||||
if (!providerAccounts) {
|
||||
|
||||
+74
-13
@@ -11,7 +11,12 @@ import { getCcsDir, getConfigPath, loadConfig, loadSettings } from '../utils/con
|
||||
import { Config, Settings } from '../types/config';
|
||||
import { expandPath } from '../utils/helpers';
|
||||
import { runHealthChecks, fixHealthIssue } from './health-service';
|
||||
import { getAllAuthStatus, getOAuthConfig, initializeAccounts } from '../cliproxy/auth-handler';
|
||||
import {
|
||||
getAllAuthStatus,
|
||||
getOAuthConfig,
|
||||
initializeAccounts,
|
||||
triggerOAuth,
|
||||
} from '../cliproxy/auth-handler';
|
||||
import {
|
||||
fetchCliproxyStats,
|
||||
fetchCliproxyModels,
|
||||
@@ -33,6 +38,7 @@ import {
|
||||
removeAccount as removeAccountFn,
|
||||
} from '../cliproxy/account-manager';
|
||||
import type { CLIProxyProvider } from '../cliproxy/types';
|
||||
import { getClaudeEnvVars } from '../cliproxy/config-generator';
|
||||
// Unified config imports
|
||||
import {
|
||||
hasUnifiedConfig,
|
||||
@@ -200,12 +206,25 @@ function updateSettingsFile(
|
||||
|
||||
/**
|
||||
* Helper: Create cliproxy variant settings
|
||||
* Includes base URL and auth token for proper Claude CLI integration
|
||||
*/
|
||||
function createCliproxySettings(name: string, model?: string): string {
|
||||
function createCliproxySettings(name: string, provider: CLIProxyProvider, model?: string): string {
|
||||
const settingsPath = path.join(getCcsDir(), `${name}.settings.json`);
|
||||
|
||||
// Get base env vars from provider config (includes BASE_URL, AUTH_TOKEN)
|
||||
const baseEnv = getClaudeEnvVars(provider);
|
||||
|
||||
const settings: Settings = {
|
||||
env: model ? { ANTHROPIC_MODEL: model } : {},
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: baseEnv.ANTHROPIC_BASE_URL || '',
|
||||
ANTHROPIC_AUTH_TOKEN: baseEnv.ANTHROPIC_AUTH_TOKEN || '',
|
||||
ANTHROPIC_MODEL: model || (baseEnv.ANTHROPIC_MODEL as string) || '',
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: model || (baseEnv.ANTHROPIC_DEFAULT_OPUS_MODEL as string) || '',
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL:
|
||||
model || (baseEnv.ANTHROPIC_DEFAULT_SONNET_MODEL as string) || '',
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL:
|
||||
(baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL as string) || model || '',
|
||||
},
|
||||
};
|
||||
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
||||
@@ -356,7 +375,7 @@ apiRoutes.post('/cliproxy', (req: Request, res: Response): void => {
|
||||
}
|
||||
|
||||
// Create settings file for variant
|
||||
const settingsPath = createCliproxySettings(name, model);
|
||||
const settingsPath = createCliproxySettings(name, provider as CLIProxyProvider, model);
|
||||
|
||||
// Include account if specified (defaults to 'default' if not provided)
|
||||
config.cliproxy[name] = {
|
||||
@@ -534,10 +553,34 @@ apiRoutes.post('/cliproxy/accounts/:provider/default', (req: Request, res: Respo
|
||||
/**
|
||||
* DELETE /api/cliproxy/accounts/:provider/:accountId - Remove an account
|
||||
*/
|
||||
apiRoutes.delete(
|
||||
'/api/cliproxy/accounts/:provider/:accountId',
|
||||
(req: Request, res: Response): void => {
|
||||
const { provider, accountId } = req.params;
|
||||
apiRoutes.delete('/cliproxy/accounts/:provider/:accountId', (req: Request, res: Response): void => {
|
||||
const { provider, accountId } = req.params;
|
||||
|
||||
// Validate provider
|
||||
const validProviders: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
|
||||
if (!validProviders.includes(provider as CLIProxyProvider)) {
|
||||
res.status(400).json({ error: `Invalid provider: ${provider}` });
|
||||
return;
|
||||
}
|
||||
|
||||
const success = removeAccountFn(provider as CLIProxyProvider, accountId);
|
||||
|
||||
if (success) {
|
||||
res.json({ provider, accountId, deleted: true });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Account not found' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/cliproxy/auth/:provider/start - Start OAuth flow for a provider
|
||||
* Opens browser for authentication and returns account info when complete
|
||||
*/
|
||||
apiRoutes.post(
|
||||
'/cliproxy/auth/:provider/start',
|
||||
async (req: Request, res: Response): Promise<void> => {
|
||||
const { provider } = req.params;
|
||||
const { nickname } = req.body;
|
||||
|
||||
// Validate provider
|
||||
const validProviders: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
|
||||
@@ -546,12 +589,30 @@ apiRoutes.delete(
|
||||
return;
|
||||
}
|
||||
|
||||
const success = removeAccountFn(provider as CLIProxyProvider, accountId);
|
||||
try {
|
||||
// Trigger OAuth flow - this opens browser and waits for completion
|
||||
const account = await triggerOAuth(provider as CLIProxyProvider, {
|
||||
add: true, // Always add mode from UI
|
||||
headless: false, // Force interactive mode
|
||||
nickname: nickname || undefined,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
res.json({ provider, accountId, deleted: true });
|
||||
} else {
|
||||
res.status(404).json({ error: 'Account not found' });
|
||||
if (account) {
|
||||
res.json({
|
||||
success: true,
|
||||
account: {
|
||||
id: account.id,
|
||||
email: account.email,
|
||||
nickname: account.nickname,
|
||||
provider: account.provider,
|
||||
isDefault: account.isDefault,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({ error: 'Authentication failed or was cancelled' });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/**
|
||||
* Add Account Dialog Component
|
||||
* Simple dialog to add another OAuth account to a provider
|
||||
*
|
||||
* Shows auth command + refresh button (no variant creation)
|
||||
* Triggers OAuth flow server-side to add another account to a provider
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
@@ -14,9 +12,10 @@ import {
|
||||
DialogDescription,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Copy, Check, RefreshCw, Terminal } from 'lucide-react';
|
||||
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Loader2, ExternalLink, User } from 'lucide-react';
|
||||
import { useStartAuth } from '@/hooks/use-cliproxy';
|
||||
|
||||
interface AddAccountDialogProps {
|
||||
open: boolean;
|
||||
@@ -26,69 +25,82 @@ interface AddAccountDialogProps {
|
||||
}
|
||||
|
||||
export function AddAccountDialog({ open, onClose, provider, displayName }: AddAccountDialogProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const { refetch } = useCliproxyAuth();
|
||||
const [nickname, setNickname] = useState('');
|
||||
const startAuthMutation = useStartAuth();
|
||||
|
||||
const authCommand = `ccs ${provider} --auth --add`;
|
||||
|
||||
const copyCommand = async () => {
|
||||
await navigator.clipboard.writeText(authCommand);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
const handleStartAuth = () => {
|
||||
startAuthMutation.mutate(
|
||||
{ provider, nickname: nickname.trim() || undefined },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setNickname('');
|
||||
onClose();
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
await refetch();
|
||||
setIsRefreshing(false);
|
||||
onClose();
|
||||
const handleOpenChange = (isOpen: boolean) => {
|
||||
if (!isOpen && !startAuthMutation.isPending) {
|
||||
setNickname('');
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add {displayName} Account</DialogTitle>
|
||||
<DialogDescription>
|
||||
Run the command below in your terminal to authenticate a new account
|
||||
Click the button below to authenticate a new account. A browser window will open for
|
||||
OAuth.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<Card>
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Terminal className="w-4 h-4" />
|
||||
Run this command:
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 px-3 py-2 bg-muted rounded-md font-mono text-sm">
|
||||
{authCommand}
|
||||
</code>
|
||||
<Button variant="outline" size="icon" onClick={copyCommand}>
|
||||
{copied ? (
|
||||
<Check className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
This will open your browser to authenticate with {displayName}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nickname">Nickname (optional)</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="nickname"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
placeholder="e.g., work, personal"
|
||||
disabled={startAuthMutation.isPending}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
A friendly name to identify this account. Auto-generated from email if left empty.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" onClick={onClose}>
|
||||
<div className="flex items-center justify-end gap-2 pt-2">
|
||||
<Button variant="ghost" onClick={onClose} disabled={startAuthMutation.isPending}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleRefresh} disabled={isRefreshing}>
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? 'Checking...' : 'I ran the command'}
|
||||
<Button onClick={handleStartAuth} disabled={startAuthMutation.isPending}>
|
||||
{startAuthMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Authenticating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
Authenticate
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{startAuthMutation.isPending && (
|
||||
<p className="text-sm text-center text-muted-foreground">
|
||||
Complete the OAuth flow in your browser...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -17,6 +17,13 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import {
|
||||
Copy,
|
||||
Check,
|
||||
@@ -26,9 +33,12 @@ import {
|
||||
Terminal,
|
||||
User,
|
||||
Sparkles,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { useCliproxyAuth, useCreateVariant } from '@/hooks/use-cliproxy';
|
||||
import { useCliproxyAuth, useCreateVariant, useStartAuth } from '@/hooks/use-cliproxy';
|
||||
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
|
||||
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
||||
|
||||
interface QuickSetupWizardProps {
|
||||
open: boolean;
|
||||
@@ -53,9 +63,11 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
const [modelName, setModelName] = useState('');
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [isAddingNewAccount, setIsAddingNewAccount] = useState(false); // Track explicit "add account" action
|
||||
|
||||
const { data: authData, refetch } = useCliproxyAuth();
|
||||
const createMutation = useCreateVariant();
|
||||
const startAuthMutation = useStartAuth();
|
||||
|
||||
// Get auth status for selected provider
|
||||
const providerAuth = authData?.authStatus.find(
|
||||
@@ -73,26 +85,22 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
setVariantName('');
|
||||
setModelName('');
|
||||
setCopied(false);
|
||||
setIsAddingNewAccount(false);
|
||||
}, 0);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// Auto-advance from auth step when account detected
|
||||
// Use timeout to avoid synchronous setState in effect (React lint rule)
|
||||
// BUT only if user didn't explicitly click "Add new account"
|
||||
useEffect(() => {
|
||||
if (step === 'auth' && accounts.length > 0) {
|
||||
if (step === 'auth' && accounts.length > 0 && !isAddingNewAccount) {
|
||||
const timer = setTimeout(() => {
|
||||
if (accounts.length === 1) {
|
||||
setSelectedAccount(accounts[0]);
|
||||
setStep('variant');
|
||||
} else {
|
||||
setStep('account');
|
||||
}
|
||||
setStep('account');
|
||||
}, 0);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [step, accounts]);
|
||||
}, [step, accounts, isAddingNewAccount]);
|
||||
|
||||
const copyCommand = async (cmd: string) => {
|
||||
await navigator.clipboard.writeText(cmd);
|
||||
@@ -106,17 +114,32 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
setIsRefreshing(false);
|
||||
};
|
||||
|
||||
const handleStartAuth = () => {
|
||||
startAuthMutation.mutate(
|
||||
{ provider: selectedProvider },
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
// Account created, select it and advance to variant step
|
||||
if (data.account) {
|
||||
setSelectedAccount(data.account as OAuthAccount);
|
||||
setStep('variant');
|
||||
}
|
||||
refetch(); // Refresh auth status
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleProviderSelect = (providerId: string) => {
|
||||
setSelectedProvider(providerId);
|
||||
const auth = authData?.authStatus.find((s: AuthStatus) => s.provider === providerId);
|
||||
const provAccounts = auth?.accounts || [];
|
||||
|
||||
if (provAccounts.length === 0) {
|
||||
// No accounts - go to auth step to add first account
|
||||
setStep('auth');
|
||||
} else if (provAccounts.length === 1) {
|
||||
setSelectedAccount(provAccounts[0]);
|
||||
setStep('variant');
|
||||
} else {
|
||||
// Has accounts - always show account selection (includes "Add new account" option)
|
||||
setStep('account');
|
||||
}
|
||||
};
|
||||
@@ -152,9 +175,36 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
};
|
||||
const currentProgress = getStepProgress(step);
|
||||
|
||||
// Prevent accidental close when user has made progress
|
||||
const handleOpenChange = (isOpen: boolean) => {
|
||||
if (!isOpen) {
|
||||
// Allow closing from success step or provider step (no progress yet)
|
||||
if (step === 'success' || step === 'provider') {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
// For other steps, require explicit close via Cancel/Back
|
||||
// The X button still works, but clicking outside doesn't close
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent
|
||||
className="sm:max-w-lg"
|
||||
onPointerDownOutside={(e) => {
|
||||
// Prevent closing on outside click when user has made progress
|
||||
if (step !== 'success' && step !== 'provider') {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onEscapeKeyDown={(e) => {
|
||||
// Prevent ESC close during auth or variant creation
|
||||
if (startAuthMutation.isPending || createMutation.isPending) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-primary" />
|
||||
@@ -192,6 +242,48 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
{/* Step: Authentication */}
|
||||
{step === 'auth' && (
|
||||
<div className="space-y-4">
|
||||
{/* Primary: OAuth Button */}
|
||||
<div className="text-center space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Authenticate with {providers.find((p) => p.id === selectedProvider)?.name} to add
|
||||
an account
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleStartAuth}
|
||||
disabled={startAuthMutation.isPending}
|
||||
className="w-full gap-2"
|
||||
size="lg"
|
||||
>
|
||||
{startAuthMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Authenticating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
Authenticate in Browser
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{startAuthMutation.isPending && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Complete the OAuth flow in your browser...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">Or use terminal</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Secondary: CLI Command */}
|
||||
<Card>
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
@@ -210,21 +302,25 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
This will open your browser to authenticate with{' '}
|
||||
{providers.find((p) => p.id === selectedProvider)?.name}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="ghost" onClick={() => setStep('provider')}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setStep('provider')}
|
||||
disabled={startAuthMutation.isPending}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleRefresh} disabled={isRefreshing}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing || startAuthMutation.isPending}
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? 'Checking...' : 'I ran the command'}
|
||||
{isRefreshing ? 'Checking...' : 'Refresh Status'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,15 +329,23 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
{/* Step: Account Selection */}
|
||||
{step === 'account' && (
|
||||
<div className="space-y-4">
|
||||
{/* Existing accounts header */}
|
||||
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Select an account ({accounts.length})
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
{accounts.map((acc: OAuthAccount) => (
|
||||
<button
|
||||
key={acc.id}
|
||||
type="button"
|
||||
onClick={() => handleAccountSelect(acc)}
|
||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/50 transition-colors text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<User className="w-5 h-5 text-muted-foreground" />
|
||||
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center">
|
||||
<User className="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">{acc.email || acc.id}</div>
|
||||
{acc.isDefault && (
|
||||
@@ -253,10 +357,41 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Button variant="ghost" onClick={() => setStep('auth')}>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">Or</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add new account button - more prominent */}
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center gap-3 p-3 border-2 border-dashed border-primary/50 rounded-lg hover:border-primary hover:bg-primary/5 transition-colors text-left"
|
||||
onClick={() => {
|
||||
setIsAddingNewAccount(true);
|
||||
setStep('auth');
|
||||
}}
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
||||
<ExternalLink className="w-4 h-4 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-primary">Add new account</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Authenticate with a different account
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<Button variant="ghost" onClick={() => setStep('provider')}>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Add different account
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -286,30 +421,54 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="model-name">Model (optional)</Label>
|
||||
<Input
|
||||
id="model-name"
|
||||
value={modelName}
|
||||
onChange={(e) => setModelName(e.target.value)}
|
||||
placeholder="e.g., gemini-2.5-pro"
|
||||
/>
|
||||
<Label>Model</Label>
|
||||
<Select value={modelName} onValueChange={setModelName}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a model" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{MODEL_CATALOGS[selectedProvider]?.models.map((m) => (
|
||||
<SelectItem key={m.id} value={m.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{m.name}</span>
|
||||
{m.description && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
- {m.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Default: {MODEL_CATALOGS[selectedProvider]?.defaultModel || 'provider default'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => (accounts.length > 1 ? setStep('account') : setStep('auth'))}
|
||||
onClick={() => (accounts.length > 0 ? setStep('account') : setStep('provider'))}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateVariant}
|
||||
disabled={!variantName || createMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending ? 'Creating...' : 'Create Variant'}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" onClick={onClose}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateVariant}
|
||||
disabled={!variantName || createMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending ? 'Creating...' : 'Create Variant'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-center text-muted-foreground">
|
||||
Skip if you just wanted to add an account without creating a variant
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export function useCliproxy() {
|
||||
export function useCliproxyAuth() {
|
||||
return useQuery({
|
||||
queryKey: ['cliproxy-auth'],
|
||||
queryFn: () => api.cliproxy.auth(),
|
||||
queryFn: () => api.cliproxy.getAuthStatus(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,6 +118,24 @@ export function useRemoveAccount() {
|
||||
});
|
||||
}
|
||||
|
||||
// OAuth flow hook
|
||||
export function useStartAuth() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ provider, nickname }: { provider: string; nickname?: string }) =>
|
||||
api.cliproxy.auth.start(provider, nickname),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-accounts'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['cliproxy-auth'] });
|
||||
toast.success(`Account added for ${variables.provider}`);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Stats and models hooks for Overview tab
|
||||
export function useCliproxyStats() {
|
||||
return useQuery({
|
||||
|
||||
@@ -172,7 +172,7 @@ export const api = {
|
||||
},
|
||||
cliproxy: {
|
||||
list: () => request<{ variants: Variant[] }>('/cliproxy'),
|
||||
auth: () => request<{ authStatus: AuthStatus[] }>('/cliproxy/auth'),
|
||||
getAuthStatus: () => request<{ authStatus: AuthStatus[] }>('/cliproxy/auth'),
|
||||
create: (data: CreateVariant) =>
|
||||
request('/cliproxy', {
|
||||
method: 'POST',
|
||||
@@ -235,6 +235,15 @@ export const api = {
|
||||
remove: (provider: string, accountId: string) =>
|
||||
request(`/cliproxy/accounts/${provider}/${accountId}`, { method: 'DELETE' }),
|
||||
},
|
||||
// OAuth flow
|
||||
auth: {
|
||||
/** Start OAuth flow - opens browser for authentication */
|
||||
start: (provider: string, nickname?: string) =>
|
||||
request<{ success: boolean; account: OAuthAccount }>(`/cliproxy/auth/${provider}/start`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ nickname }),
|
||||
}),
|
||||
},
|
||||
},
|
||||
accounts: {
|
||||
list: () => request<{ accounts: Account[]; default: string | null }>('/accounts'),
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Model Catalogs for CLIProxy providers
|
||||
* Shared data for Quick Setup Wizard and Provider Editor
|
||||
*/
|
||||
|
||||
import type { ProviderCatalog } from '@/components/cliproxy/provider-model-selector';
|
||||
|
||||
/** Model catalog data - mirrors src/cliproxy/model-catalog.ts */
|
||||
export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
|
||||
agy: {
|
||||
provider: 'agy',
|
||||
displayName: 'Antigravity',
|
||||
defaultModel: 'gemini-claude-opus-4-5-thinking',
|
||||
models: [
|
||||
{
|
||||
id: 'gemini-claude-opus-4-5-thinking',
|
||||
name: 'Claude Opus 4.5 Thinking',
|
||||
description: 'Most capable, extended thinking',
|
||||
},
|
||||
{
|
||||
id: 'gemini-claude-sonnet-4-5-thinking',
|
||||
name: 'Claude Sonnet 4.5 Thinking',
|
||||
description: 'Balanced with extended thinking',
|
||||
},
|
||||
{
|
||||
id: 'gemini-claude-sonnet-4-5',
|
||||
name: 'Claude Sonnet 4.5',
|
||||
description: 'Fast and capable',
|
||||
},
|
||||
{
|
||||
id: 'gemini-3-pro-preview',
|
||||
name: 'Gemini 3 Pro',
|
||||
description: 'Google latest model via Antigravity',
|
||||
},
|
||||
],
|
||||
},
|
||||
gemini: {
|
||||
provider: 'gemini',
|
||||
displayName: 'Gemini',
|
||||
defaultModel: 'gemini-2.5-pro',
|
||||
models: [
|
||||
{
|
||||
id: 'gemini-3-pro-preview',
|
||||
name: 'Gemini 3 Pro',
|
||||
tier: 'paid',
|
||||
description: 'Latest model, requires paid Google account',
|
||||
},
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
name: 'Gemini 2.5 Pro',
|
||||
description: 'Stable, works with free Google account',
|
||||
},
|
||||
],
|
||||
},
|
||||
codex: {
|
||||
provider: 'codex',
|
||||
displayName: 'Codex',
|
||||
defaultModel: 'gpt-5.1-codex-max',
|
||||
models: [
|
||||
{
|
||||
id: 'gpt-5.1-codex-max',
|
||||
name: 'Codex Max (5.1)',
|
||||
description: 'Most capable Codex model',
|
||||
presetMapping: {
|
||||
default: 'gpt-5.1-codex-max',
|
||||
opus: 'gpt-5.1-codex-max-high',
|
||||
sonnet: 'gpt-5.1-codex-max',
|
||||
haiku: 'gpt-5.1-codex-mini-high',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.2',
|
||||
name: 'GPT 5.2',
|
||||
description: 'Latest GPT model',
|
||||
presetMapping: {
|
||||
default: 'gpt-5.2',
|
||||
opus: 'gpt-5.2',
|
||||
sonnet: 'gpt-5.2',
|
||||
haiku: 'gpt-5.2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.1-codex-mini',
|
||||
name: 'Codex Mini',
|
||||
description: 'Fast and efficient Codex model',
|
||||
},
|
||||
],
|
||||
},
|
||||
qwen: {
|
||||
provider: 'qwen',
|
||||
displayName: 'Qwen',
|
||||
defaultModel: 'qwen-coder-plus',
|
||||
models: [
|
||||
{
|
||||
id: 'qwen-coder-plus',
|
||||
name: 'Qwen Coder Plus',
|
||||
description: 'Alibaba code-focused model',
|
||||
},
|
||||
{
|
||||
id: 'qwen-max',
|
||||
name: 'Qwen Max',
|
||||
description: 'Most capable Qwen model',
|
||||
},
|
||||
],
|
||||
},
|
||||
iflow: {
|
||||
provider: 'iflow',
|
||||
displayName: 'iFlow',
|
||||
defaultModel: 'iflow-default',
|
||||
models: [
|
||||
{
|
||||
id: 'iflow-default',
|
||||
name: 'iFlow Default',
|
||||
description: 'Default iFlow model',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
+2
-114
@@ -15,7 +15,6 @@ import { QuickSetupWizard } from '@/components/quick-setup-wizard';
|
||||
import { AddAccountDialog } from '@/components/add-account-dialog';
|
||||
import { ProviderEditor } from '@/components/cliproxy/provider-editor';
|
||||
import { ProviderLogo } from '@/components/cliproxy/provider-logo';
|
||||
import type { ProviderCatalog } from '@/components/cliproxy/provider-model-selector';
|
||||
import {
|
||||
useCliproxy,
|
||||
useCliproxyAuth,
|
||||
@@ -24,120 +23,9 @@ import {
|
||||
useDeleteVariant,
|
||||
} from '@/hooks/use-cliproxy';
|
||||
import type { AuthStatus, Variant } from '@/lib/api-client';
|
||||
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Model catalog data - mirrors src/cliproxy/model-catalog.ts
|
||||
const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
|
||||
agy: {
|
||||
provider: 'agy',
|
||||
displayName: 'Antigravity',
|
||||
defaultModel: 'gemini-claude-opus-4-5-thinking',
|
||||
models: [
|
||||
{
|
||||
id: 'gemini-claude-opus-4-5-thinking',
|
||||
name: 'Claude Opus 4.5 Thinking',
|
||||
description: 'Most capable, extended thinking',
|
||||
},
|
||||
{
|
||||
id: 'gemini-claude-sonnet-4-5-thinking',
|
||||
name: 'Claude Sonnet 4.5 Thinking',
|
||||
description: 'Balanced with extended thinking',
|
||||
},
|
||||
{
|
||||
id: 'gemini-claude-sonnet-4-5',
|
||||
name: 'Claude Sonnet 4.5',
|
||||
description: 'Fast and capable',
|
||||
},
|
||||
{
|
||||
id: 'gemini-3-pro-preview',
|
||||
name: 'Gemini 3 Pro',
|
||||
description: 'Google latest model via Antigravity',
|
||||
},
|
||||
],
|
||||
},
|
||||
gemini: {
|
||||
provider: 'gemini',
|
||||
displayName: 'Gemini',
|
||||
defaultModel: 'gemini-2.5-pro',
|
||||
models: [
|
||||
{
|
||||
id: 'gemini-3-pro-preview',
|
||||
name: 'Gemini 3 Pro',
|
||||
tier: 'paid',
|
||||
description: 'Latest model, requires paid Google account',
|
||||
},
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
name: 'Gemini 2.5 Pro',
|
||||
description: 'Stable, works with free Google account',
|
||||
},
|
||||
],
|
||||
},
|
||||
codex: {
|
||||
provider: 'codex',
|
||||
displayName: 'Codex',
|
||||
defaultModel: 'gpt-5.1-codex-max',
|
||||
models: [
|
||||
{
|
||||
id: 'gpt-5.1-codex-max',
|
||||
name: 'Codex Max (5.1)',
|
||||
description: 'Most capable Codex model',
|
||||
presetMapping: {
|
||||
default: 'gpt-5.1-codex-max',
|
||||
opus: 'gpt-5.1-codex-max-high',
|
||||
sonnet: 'gpt-5.1-codex-max',
|
||||
haiku: 'gpt-5.1-codex-mini-high',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.2',
|
||||
name: 'GPT 5.2',
|
||||
description: 'Latest GPT model',
|
||||
presetMapping: {
|
||||
default: 'gpt-5.2',
|
||||
opus: 'gpt-5.2',
|
||||
sonnet: 'gpt-5.2',
|
||||
haiku: 'gpt-5.2',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gpt-5.1-codex-mini',
|
||||
name: 'Codex Mini',
|
||||
description: 'Fast and efficient Codex model',
|
||||
},
|
||||
],
|
||||
},
|
||||
qwen: {
|
||||
provider: 'qwen',
|
||||
displayName: 'Qwen',
|
||||
defaultModel: 'qwen-coder-plus',
|
||||
models: [
|
||||
{
|
||||
id: 'qwen-coder-plus',
|
||||
name: 'Qwen Coder Plus',
|
||||
description: 'Alibaba code-focused model',
|
||||
},
|
||||
{
|
||||
id: 'qwen-max',
|
||||
name: 'Qwen Max',
|
||||
description: 'Most capable Qwen model',
|
||||
},
|
||||
],
|
||||
},
|
||||
iflow: {
|
||||
provider: 'iflow',
|
||||
displayName: 'iFlow',
|
||||
defaultModel: 'iflow-default',
|
||||
models: [
|
||||
{
|
||||
id: 'iflow-default',
|
||||
name: 'iFlow Default',
|
||||
description: 'Default iFlow model',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// Sidebar provider item
|
||||
function ProviderSidebarItem({
|
||||
status,
|
||||
@@ -207,7 +95,7 @@ function VariantSidebarItem({
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
'w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors cursor-pointer text-left pl-6',
|
||||
'group w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors cursor-pointer text-left pl-6',
|
||||
isSelected
|
||||
? 'bg-primary/10 border border-primary/20'
|
||||
: 'hover:bg-muted border border-transparent'
|
||||
|
||||
Reference in New Issue
Block a user