ldrouter 1.16.4 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +17 -0
- package/dist/server/db/repositories/codex-accounts.js +15 -3
- package/dist/server/db/repositories/qoder-accounts.js +167 -0
- package/dist/server/db/schema.js +33 -1
- package/dist/server/gateway/runner.js +73 -4
- package/dist/server/providers/codex-refresh.js +5 -2
- package/dist/server/providers/codex.js +53 -19
- package/dist/server/providers/index.js +4 -0
- package/dist/server/providers/pooled.js +84 -0
- package/dist/server/providers/qoder/catalog.js +138 -0
- package/dist/server/providers/qoder/client.js +248 -0
- package/dist/server/providers/qoder/constants.js +34 -0
- package/dist/server/providers/qoder/cosy.js +81 -0
- package/dist/server/providers/qoder/credentials.js +125 -0
- package/dist/server/providers/qoder/encode.js +28 -0
- package/dist/server/providers/qoder/qoder-import.js +109 -0
- package/dist/server/providers/qoder/rsa.js +7 -0
- package/dist/server/providers/qoder/sse.js +211 -0
- package/dist/server/providers/qoder/tier.js +105 -0
- package/dist/server/routes/admin/backup.js +7 -2
- package/dist/server/routes/admin/models.js +14 -0
- package/dist/server/routes/admin/providers.js +53 -34
- package/dist/server/routes/admin/qoder.js +271 -0
- package/dist/server/routes/admin.js +8 -1
- package/dist/server/routing/capabilities.js +1 -0
- package/dist/server/routing/combo.js +22 -3
- package/dist/server/upstream/client.js +14 -0
- package/dist/web/assets/index-DdtyoPpS.js +391 -0
- package/dist/web/index.html +1 -1
- package/migrations/0007_qoder_accounts.sql +217 -0
- package/package.json +1 -1
- package/dist/web/assets/index-2L7raen4.js +0 -391
package/dist/web/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<meta name="color-scheme" content="light dark" />
|
|
8
8
|
<title>LateDev Router</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DdtyoPpS.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-Drmqi8MO.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
-- 0007_qoder_accounts.sql
|
|
2
|
+
-- Widen the provider type CHECK for 'qoder' and add the Qoder account pool.
|
|
3
|
+
-- Rebuilt in dependency order for the same reason as 0005: renaming `providers`
|
|
4
|
+
-- retargets child foreign keys to `providers_legacy`, so the whole dependent graph
|
|
5
|
+
-- is rebuilt rather than disabling enforcement.
|
|
6
|
+
-- `codex_accounts` is part of that graph: it holds REFERENCES providers(id), so the
|
|
7
|
+
-- rename would repoint it at providers_legacy (which this file then drops), leaving it
|
|
8
|
+
-- referencing a missing table. 0005 could leave it alone only because 0005 created it.
|
|
9
|
+
-- `request_attempts` too: it holds REFERENCES codex_accounts.
|
|
10
|
+
--
|
|
11
|
+
-- Statement order is deliberate. The legacy tables are dropped before any index is
|
|
12
|
+
-- (re)created: a rename carries the table's indexes along under their original names,
|
|
13
|
+
-- so `CREATE INDEX IF NOT EXISTS` would be a no-op and the DROP would then take the
|
|
14
|
+
-- index with the legacy table.
|
|
15
|
+
ALTER TABLE request_attempts RENAME TO request_attempts_legacy;
|
|
16
|
+
ALTER TABLE combo_members RENAME TO combo_members_legacy;
|
|
17
|
+
ALTER TABLE models RENAME TO models_legacy;
|
|
18
|
+
ALTER TABLE codex_accounts RENAME TO codex_accounts_legacy;
|
|
19
|
+
ALTER TABLE providers RENAME TO providers_legacy;
|
|
20
|
+
|
|
21
|
+
CREATE TABLE providers (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
name TEXT NOT NULL,
|
|
24
|
+
slug TEXT NOT NULL UNIQUE,
|
|
25
|
+
type TEXT NOT NULL CHECK (type IN ('openai','anthropic','codex','qoder')),
|
|
26
|
+
base_url TEXT NOT NULL,
|
|
27
|
+
encrypted_api_key TEXT,
|
|
28
|
+
api_key_nonce TEXT,
|
|
29
|
+
api_key_version INTEGER NOT NULL DEFAULT 1,
|
|
30
|
+
custom_headers_encrypted TEXT,
|
|
31
|
+
custom_headers_nonce TEXT,
|
|
32
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
33
|
+
connect_timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
34
|
+
first_token_timeout_ms INTEGER NOT NULL DEFAULT 30000,
|
|
35
|
+
stream_idle_timeout_ms INTEGER NOT NULL DEFAULT 60000,
|
|
36
|
+
total_timeout_ms INTEGER NOT NULL DEFAULT 180000,
|
|
37
|
+
max_retries INTEGER NOT NULL DEFAULT 2,
|
|
38
|
+
retry_base_ms INTEGER NOT NULL DEFAULT 500,
|
|
39
|
+
retry_max_ms INTEGER NOT NULL DEFAULT 8000,
|
|
40
|
+
cb_failure_threshold INTEGER NOT NULL DEFAULT 5,
|
|
41
|
+
cb_cooldown_seconds INTEGER NOT NULL DEFAULT 60,
|
|
42
|
+
health_state TEXT NOT NULL DEFAULT 'unknown' CHECK (health_state IN ('healthy','degraded','down','circuit_open','unknown')),
|
|
43
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
44
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
45
|
+
);
|
|
46
|
+
INSERT INTO providers SELECT * FROM providers_legacy;
|
|
47
|
+
|
|
48
|
+
CREATE TABLE models (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE RESTRICT,
|
|
51
|
+
upstream_model_id TEXT NOT NULL,
|
|
52
|
+
public_model_id TEXT NOT NULL UNIQUE,
|
|
53
|
+
display_name TEXT NOT NULL,
|
|
54
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
55
|
+
upstream_available INTEGER NOT NULL DEFAULT 1,
|
|
56
|
+
capabilities_json TEXT NOT NULL DEFAULT '{}',
|
|
57
|
+
max_context_tokens INTEGER,
|
|
58
|
+
max_output_tokens INTEGER,
|
|
59
|
+
discovered_metadata_json TEXT,
|
|
60
|
+
cache_override_enabled INTEGER,
|
|
61
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
62
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
63
|
+
last_seen_upstream_at TEXT
|
|
64
|
+
);
|
|
65
|
+
INSERT INTO models SELECT * FROM models_legacy;
|
|
66
|
+
|
|
67
|
+
CREATE TABLE combo_members (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
combo_id TEXT NOT NULL REFERENCES combos(id) ON DELETE CASCADE,
|
|
70
|
+
model_id TEXT NOT NULL REFERENCES models(id) ON DELETE RESTRICT,
|
|
71
|
+
position INTEGER NOT NULL,
|
|
72
|
+
weight INTEGER NOT NULL DEFAULT 1,
|
|
73
|
+
enabled INTEGER NOT NULL DEFAULT 1
|
|
74
|
+
);
|
|
75
|
+
INSERT INTO combo_members SELECT * FROM combo_members_legacy;
|
|
76
|
+
|
|
77
|
+
-- Column list is explicit here because 0006 appended the usage columns after
|
|
78
|
+
-- created_at/updated_at, so `SELECT *` cannot carry them across.
|
|
79
|
+
CREATE TABLE codex_accounts (
|
|
80
|
+
id TEXT PRIMARY KEY,
|
|
81
|
+
provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE RESTRICT,
|
|
82
|
+
email TEXT,
|
|
83
|
+
workspace_id TEXT,
|
|
84
|
+
chatgpt_account_id TEXT,
|
|
85
|
+
plan_type TEXT,
|
|
86
|
+
encrypted_access_token TEXT NOT NULL,
|
|
87
|
+
access_token_nonce TEXT NOT NULL,
|
|
88
|
+
access_token_version INTEGER NOT NULL DEFAULT 1,
|
|
89
|
+
encrypted_refresh_token TEXT NOT NULL,
|
|
90
|
+
refresh_token_nonce TEXT NOT NULL,
|
|
91
|
+
refresh_token_version INTEGER NOT NULL DEFAULT 1,
|
|
92
|
+
encrypted_id_token TEXT,
|
|
93
|
+
id_token_nonce TEXT,
|
|
94
|
+
id_token_version INTEGER NOT NULL DEFAULT 1,
|
|
95
|
+
token_expires_at TEXT NOT NULL,
|
|
96
|
+
last_refresh_at TEXT,
|
|
97
|
+
auth_method TEXT NOT NULL DEFAULT 'oauth' CHECK (auth_method IN ('oauth','access_token')),
|
|
98
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
99
|
+
health_state TEXT NOT NULL DEFAULT 'unknown' CHECK (health_state IN ('healthy','degraded','down','unknown')),
|
|
100
|
+
last_error TEXT,
|
|
101
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
102
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
103
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
104
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
105
|
+
codex_usage_json TEXT,
|
|
106
|
+
codex_usage_updated_at TEXT,
|
|
107
|
+
codex_usage_error TEXT,
|
|
108
|
+
codex_autostart_enabled INTEGER NOT NULL DEFAULT 0,
|
|
109
|
+
last_pinged_reset_at TEXT,
|
|
110
|
+
last_pinged_reset_key TEXT,
|
|
111
|
+
last_ping_at TEXT
|
|
112
|
+
);
|
|
113
|
+
INSERT INTO codex_accounts (
|
|
114
|
+
id, provider_id, email, workspace_id, chatgpt_account_id, plan_type,
|
|
115
|
+
encrypted_access_token, access_token_nonce, access_token_version,
|
|
116
|
+
encrypted_refresh_token, refresh_token_nonce, refresh_token_version,
|
|
117
|
+
encrypted_id_token, id_token_nonce, id_token_version, token_expires_at, last_refresh_at,
|
|
118
|
+
auth_method, enabled, health_state, last_error, consecutive_failures, priority,
|
|
119
|
+
created_at, updated_at, codex_usage_json, codex_usage_updated_at, codex_usage_error,
|
|
120
|
+
codex_autostart_enabled, last_pinged_reset_at, last_pinged_reset_key, last_ping_at
|
|
121
|
+
)
|
|
122
|
+
SELECT
|
|
123
|
+
id, provider_id, email, workspace_id, chatgpt_account_id, plan_type,
|
|
124
|
+
encrypted_access_token, access_token_nonce, access_token_version,
|
|
125
|
+
encrypted_refresh_token, refresh_token_nonce, refresh_token_version,
|
|
126
|
+
encrypted_id_token, id_token_nonce, id_token_version, token_expires_at, last_refresh_at,
|
|
127
|
+
auth_method, enabled, health_state, last_error, consecutive_failures, priority,
|
|
128
|
+
created_at, updated_at, codex_usage_json, codex_usage_updated_at, codex_usage_error,
|
|
129
|
+
codex_autostart_enabled, last_pinged_reset_at, last_pinged_reset_key, last_ping_at
|
|
130
|
+
FROM codex_accounts_legacy;
|
|
131
|
+
|
|
132
|
+
CREATE TABLE qoder_accounts (
|
|
133
|
+
id TEXT PRIMARY KEY,
|
|
134
|
+
provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE RESTRICT,
|
|
135
|
+
label TEXT,
|
|
136
|
+
email TEXT,
|
|
137
|
+
qoder_user_id TEXT NOT NULL,
|
|
138
|
+
machine_id TEXT NOT NULL,
|
|
139
|
+
encrypted_pat TEXT NOT NULL,
|
|
140
|
+
pat_nonce TEXT NOT NULL,
|
|
141
|
+
pat_version INTEGER NOT NULL DEFAULT 1,
|
|
142
|
+
encrypted_job_token TEXT NOT NULL,
|
|
143
|
+
job_token_nonce TEXT NOT NULL,
|
|
144
|
+
job_token_version INTEGER NOT NULL DEFAULT 1,
|
|
145
|
+
job_token_expires_at TEXT NOT NULL,
|
|
146
|
+
catalog_json TEXT,
|
|
147
|
+
catalog_fetched_at TEXT,
|
|
148
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
149
|
+
health_state TEXT NOT NULL DEFAULT 'unknown' CHECK (health_state IN ('healthy','degraded','down','unknown')),
|
|
150
|
+
last_error TEXT,
|
|
151
|
+
consecutive_failures INTEGER NOT NULL DEFAULT 0,
|
|
152
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
153
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
154
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
CREATE TABLE request_attempts (
|
|
158
|
+
id TEXT PRIMARY KEY,
|
|
159
|
+
request_id TEXT NOT NULL REFERENCES requests(id) ON DELETE CASCADE,
|
|
160
|
+
attempt_number INTEGER NOT NULL,
|
|
161
|
+
provider_id TEXT NOT NULL,
|
|
162
|
+
model_id TEXT NOT NULL,
|
|
163
|
+
started_at TEXT NOT NULL,
|
|
164
|
+
completed_at TEXT,
|
|
165
|
+
status_code INTEGER,
|
|
166
|
+
success INTEGER NOT NULL,
|
|
167
|
+
latency_ms INTEGER NOT NULL DEFAULT 0,
|
|
168
|
+
ttft_ms INTEGER,
|
|
169
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
170
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
171
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
172
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
173
|
+
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
|
174
|
+
stream_started INTEGER NOT NULL DEFAULT 0,
|
|
175
|
+
partial_response INTEGER NOT NULL DEFAULT 0,
|
|
176
|
+
selection_reason TEXT NOT NULL,
|
|
177
|
+
failure_reason TEXT,
|
|
178
|
+
error_message TEXT,
|
|
179
|
+
upstream_request_id TEXT,
|
|
180
|
+
codex_account_id TEXT REFERENCES codex_accounts(id) ON DELETE SET NULL,
|
|
181
|
+
qoder_account_id TEXT REFERENCES qoder_accounts(id) ON DELETE SET NULL
|
|
182
|
+
);
|
|
183
|
+
-- Column list is explicit: `qoder_account_id` is new, so `SELECT *` would be one
|
|
184
|
+
-- value short of the 24 target columns.
|
|
185
|
+
INSERT INTO request_attempts (
|
|
186
|
+
id, request_id, attempt_number, provider_id, model_id, started_at, completed_at, status_code,
|
|
187
|
+
success, latency_ms, ttft_ms, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
188
|
+
reasoning_tokens, stream_started, partial_response, selection_reason, failure_reason, error_message,
|
|
189
|
+
upstream_request_id, codex_account_id
|
|
190
|
+
)
|
|
191
|
+
SELECT
|
|
192
|
+
id, request_id, attempt_number, provider_id, model_id, started_at, completed_at, status_code,
|
|
193
|
+
success, latency_ms, ttft_ms, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
194
|
+
reasoning_tokens, stream_started, partial_response, selection_reason, failure_reason, error_message,
|
|
195
|
+
upstream_request_id, codex_account_id
|
|
196
|
+
FROM request_attempts_legacy;
|
|
197
|
+
|
|
198
|
+
DROP TABLE request_attempts_legacy;
|
|
199
|
+
DROP TABLE combo_members_legacy;
|
|
200
|
+
DROP TABLE models_legacy;
|
|
201
|
+
DROP TABLE codex_accounts_legacy;
|
|
202
|
+
DROP TABLE providers_legacy;
|
|
203
|
+
|
|
204
|
+
CREATE UNIQUE INDEX uniq_model_public ON models(public_model_id);
|
|
205
|
+
CREATE UNIQUE INDEX uniq_provider_upstream ON models(provider_id, upstream_model_id);
|
|
206
|
+
CREATE INDEX idx_model_enabled ON models(enabled);
|
|
207
|
+
CREATE UNIQUE INDEX uniq_combo_model ON combo_members(combo_id, model_id);
|
|
208
|
+
CREATE INDEX idx_combo_pos ON combo_members(combo_id, position);
|
|
209
|
+
CREATE INDEX idx_codex_account_provider_enabled_priority ON codex_accounts(provider_id, enabled, priority);
|
|
210
|
+
CREATE INDEX idx_codex_account_provider_email ON codex_accounts(provider_id, email);
|
|
211
|
+
CREATE INDEX idx_codex_account_provider_chatgpt ON codex_accounts(provider_id, chatgpt_account_id);
|
|
212
|
+
CREATE INDEX idx_qoder_account_provider_enabled_priority ON qoder_accounts(provider_id, enabled, priority);
|
|
213
|
+
CREATE INDEX idx_qoder_account_provider_user ON qoder_accounts(provider_id, qoder_user_id);
|
|
214
|
+
CREATE INDEX idx_attempt_request ON request_attempts(request_id, attempt_number);
|
|
215
|
+
CREATE INDEX idx_attempt_provider_model ON request_attempts(provider_id, model_id, started_at);
|
|
216
|
+
CREATE INDEX idx_attempt_codex_account ON request_attempts(codex_account_id, started_at);
|
|
217
|
+
CREATE INDEX idx_attempt_qoder_account ON request_attempts(qoder_account_id, started_at);
|