llm-simple-router 0.4.2 → 0.5.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/README.md +67 -18
- package/dist/admin/monitor.js +8 -0
- package/dist/admin/routes.js +2 -0
- package/dist/admin/settings-import-export.d.ts +11 -0
- package/dist/admin/settings-import-export.js +144 -0
- package/dist/admin/settings.js +40 -2
- package/dist/db/db-size-monitor.d.ts +22 -0
- package/dist/db/db-size-monitor.js +81 -0
- package/dist/db/index.d.ts +4 -1
- package/dist/db/index.js +4 -1
- package/dist/db/log-cleaner.js +16 -7
- package/dist/db/logs.d.ts +9 -1
- package/dist/db/logs.js +55 -5
- package/dist/db/migrations/022_add_session_id_and_incremental_vacuum.sql +5 -0
- package/dist/db/settings.d.ts +4 -0
- package/dist/db/settings.js +18 -1
- package/dist/index.js +5 -0
- package/dist/metrics/metrics-extractor.js +4 -0
- package/dist/monitor/request-tracker.d.ts +2 -0
- package/dist/monitor/request-tracker.js +20 -1
- package/dist/monitor/types.d.ts +1 -0
- package/dist/proxy/log-helpers.d.ts +2 -0
- package/dist/proxy/log-helpers.js +4 -2
- package/dist/proxy/model-state.d.ts +2 -0
- package/dist/proxy/model-state.js +4 -0
- package/dist/proxy/orchestrator.d.ts +2 -0
- package/dist/proxy/orchestrator.js +1 -0
- package/dist/proxy/proxy-handler.js +66 -34
- package/dist/proxy/proxy-logging.d.ts +2 -1
- package/dist/proxy/proxy-logging.js +5 -1
- package/dist/proxy/semaphore.d.ts +2 -0
- package/dist/proxy/semaphore.js +11 -0
- package/frontend-dist/assets/{CardContent-3ytnac7B.js → CardContent-CIO85eT6.js} +1 -1
- package/frontend-dist/assets/{CardTitle-BHZE8Rty.js → CardTitle-DiqIReMT.js} +1 -1
- package/frontend-dist/assets/Checkbox-C2u5pIp4.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-RKFL41om.js +1 -0
- package/frontend-dist/assets/Collection-iiNnuTQj.js +1 -0
- package/frontend-dist/assets/{Dashboard-BJslVTg8.js → Dashboard-DOEqP6gF.js} +1 -1
- package/frontend-dist/assets/DialogTitle-CEqndrf6.js +1 -0
- package/frontend-dist/assets/{Input-JApdUstN.js → Input-l5ZurXX5.js} +1 -1
- package/frontend-dist/assets/{Label-IbQFgxLe.js → Label-PgGtS8v2.js} +1 -1
- package/frontend-dist/assets/{Login-BjuVvrPV.js → Login-DaN6ZcCx.js} +1 -1
- package/frontend-dist/assets/Logs-CleRQ7Xk.js +1 -0
- package/frontend-dist/assets/{ModelMappings-DWVmxMy6.js → ModelMappings-CacA_ua_.js} +1 -1
- package/frontend-dist/assets/{Monitor-BTEW0evp.js → Monitor-LSMFOBN2.js} +1 -1
- package/frontend-dist/assets/PopperContent-zLFHqQP0.js +1 -0
- package/frontend-dist/assets/{Providers-BqLSKXuv.js → Providers-NT5MUDU0.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-TAHOKnxW.js → ProxyEnhancement-DhOy8nNy.js} +1 -1
- package/frontend-dist/assets/{RetryRules-Cn6KHzgB.js → RetryRules-7arWa3jB.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-CBgWAJ6-.js → RouterKeys-CdaZunRg.js} +1 -1
- package/frontend-dist/assets/SelectValue-CSg-MKW_.js +1 -0
- package/frontend-dist/assets/Settings-1ntV9XE3.js +6 -0
- package/frontend-dist/assets/{Setup-QKmeMDtB.js → Setup-CXLTDhYJ.js} +1 -1
- package/frontend-dist/assets/Switch-DivrIFE3.js +1 -0
- package/frontend-dist/assets/TableHeader-Bn0bodWx.js +1 -0
- package/frontend-dist/assets/TabsContent-MWvOH_LJ.js +1 -0
- package/frontend-dist/assets/TabsTrigger-WKkUfO2M.js +1 -0
- package/frontend-dist/assets/Teleport-B0PNXZbP.js +3 -0
- package/frontend-dist/assets/UnifiedRequestDialog-B2nt8nLl.css +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-Ba2e7YuJ.js +3 -0
- package/frontend-dist/assets/{VisuallyHidden-DPKPka_x.js → VisuallyHidden-BwwTtzb9.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-Bnglr6yR.js → VisuallyHiddenInput-EGZSP7s8.js} +1 -1
- package/frontend-dist/assets/alert-dialog-CS1yFhdV.js +1 -0
- package/frontend-dist/assets/{badge-BTjuxlp4.js → badge-C-QcC5n2.js} +1 -1
- package/frontend-dist/assets/{button-BKJB3nEQ.js → button-Dbz2Be22.js} +2 -2
- package/frontend-dist/assets/{createLucideIcon-igIAnu_Y.js → createLucideIcon-Biq59l_W.js} +1 -1
- package/frontend-dist/assets/dialog-Cr0YQlLW.js +1 -0
- package/frontend-dist/assets/{file-text-Ci7Mgh3F.js → file-text-DoRW0hQW.js} +1 -1
- package/frontend-dist/assets/index-0H2uCGbx.js +1 -0
- package/frontend-dist/assets/index-D-cdVNCb.css +1 -0
- package/frontend-dist/assets/{lib-BGW4QyKP.js → lib-B0lieqgg.js} +1 -1
- package/frontend-dist/assets/{ohash.D__AXeF1-CsY_LBk-.js → ohash.D__AXeF1-BGxYMs6k.js} +1 -1
- package/frontend-dist/assets/{useClipboard-wnGQAe3I.js → useClipboard-vaHkvJHw.js} +1 -1
- package/frontend-dist/assets/{useForwardExpose-bqtcPo63.js → useForwardExpose-C2_ks3sW.js} +1 -1
- package/frontend-dist/assets/useLogRetention-Cs_fiKql.js +1 -0
- package/frontend-dist/assets/useNonce-C9do0jOI.js +1 -0
- package/frontend-dist/assets/x-BlTnH_0_.js +1 -0
- package/frontend-dist/index.html +8 -8
- package/package.json +1 -1
- package/frontend-dist/assets/Checkbox-CMYgDuxw.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-DooxvEnx.js +0 -1
- package/frontend-dist/assets/Collection-GDvpW_uY.js +0 -3
- package/frontend-dist/assets/DialogTitle-lj6NAA5R.js +0 -1
- package/frontend-dist/assets/Logs-J08HyZWA.js +0 -1
- package/frontend-dist/assets/PopperContent-ZhhkKJo0.js +0 -1
- package/frontend-dist/assets/SelectValue-DS4Z8y0u.js +0 -1
- package/frontend-dist/assets/Switch-BYebebrY.js +0 -1
- package/frontend-dist/assets/TableHeader-B2A48qgy.js +0 -1
- package/frontend-dist/assets/TabsContent-BcNBY5CB.js +0 -1
- package/frontend-dist/assets/TabsTrigger-8W_mNsGI.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BmEamR1L.js +0 -3
- package/frontend-dist/assets/UnifiedRequestDialog-Dk3IIDDx.css +0 -1
- package/frontend-dist/assets/alert-dialog-BzyDZnoE.js +0 -1
- package/frontend-dist/assets/dialog-C0B-Xn-S.js +0 -1
- package/frontend-dist/assets/index-BrDOp_gc.js +0 -1
- package/frontend-dist/assets/index-DMdVJThL.css +0 -1
- package/frontend-dist/assets/useNonce-DN0Hrw3l.js +0 -1
- package/frontend-dist/assets/x-Cy_v5hrA.js +0 -1
- /package/frontend-dist/assets/{format-CPdJtjZ5.js → format-DOVIVsQC.js} +0 -0
package/dist/db/logs.js
CHANGED
|
@@ -4,14 +4,14 @@ const LOG_LIST_SELECT = `rl.id, rl.api_type, rl.model, rl.provider_id, rl.status
|
|
|
4
4
|
rl.is_stream, rl.error_message, rl.created_at, rl.is_retry, rl.is_failover, rl.original_request_id, rl.original_model,
|
|
5
5
|
CASE WHEN rl.provider_id = 'router' THEN rl.upstream_request ELSE NULL END AS upstream_request,
|
|
6
6
|
rl.input_tokens, rl.output_tokens, rl.cache_read_tokens, rl.ttft_ms, rl.tokens_per_second, rl.stop_reason,
|
|
7
|
-
rl.backend_model, rl.metrics_complete,
|
|
7
|
+
rl.backend_model, rl.metrics_complete, rl.session_id,
|
|
8
8
|
COALESCE(p.name, rl.provider_id) AS provider_name`;
|
|
9
9
|
const LOG_LIST_JOIN = `LEFT JOIN providers p ON p.id = rl.provider_id`;
|
|
10
10
|
export function insertRequestLog(db, log) {
|
|
11
11
|
db.prepare(`INSERT INTO request_logs (id, api_type, model, provider_id, status_code, latency_ms,
|
|
12
12
|
is_stream, error_message, created_at, client_request, upstream_request, upstream_response,
|
|
13
|
-
is_retry, is_failover, original_request_id, router_key_id, original_model)
|
|
14
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(log.id, log.api_type, log.model, log.provider_id, log.status_code, log.latency_ms, log.is_stream, log.error_message, log.created_at, log.client_request ?? null, log.upstream_request ?? null, log.upstream_response ?? null, log.is_retry ?? 0, log.is_failover ?? 0, log.original_request_id ?? null, log.router_key_id ?? null, log.original_model ?? null);
|
|
13
|
+
is_retry, is_failover, original_request_id, router_key_id, original_model, session_id)
|
|
14
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(log.id, log.api_type, log.model, log.provider_id, log.status_code, log.latency_ms, log.is_stream, log.error_message, log.created_at, log.client_request ?? null, log.upstream_request ?? null, log.upstream_response ?? null, log.is_retry ?? 0, log.is_failover ?? 0, log.original_request_id ?? null, log.router_key_id ?? null, log.original_model ?? null, log.session_id ?? null);
|
|
15
15
|
}
|
|
16
16
|
function buildLogWhereClause(options, baseCondition) {
|
|
17
17
|
let where = baseCondition;
|
|
@@ -55,7 +55,9 @@ export function getRequestLogs(db, options) {
|
|
|
55
55
|
return { data, total };
|
|
56
56
|
}
|
|
57
57
|
export function getRequestLogById(db, id) {
|
|
58
|
-
return db.prepare(
|
|
58
|
+
return db.prepare(`SELECT rl.*, COALESCE(p.name, rl.provider_id) AS provider_name
|
|
59
|
+
FROM request_logs rl LEFT JOIN providers p ON p.id = rl.provider_id
|
|
60
|
+
WHERE rl.id = ?`).get(id);
|
|
59
61
|
}
|
|
60
62
|
/** 双写:collectTransportMetrics 写 request_metrics 的同时,更新 request_logs 的冗余列 */
|
|
61
63
|
export function updateLogMetrics(db, logId, m) {
|
|
@@ -89,7 +91,55 @@ export function backfillMetricsFromRequestMetrics(db) {
|
|
|
89
91
|
`).run().changes;
|
|
90
92
|
}
|
|
91
93
|
export function deleteLogsBefore(db, beforeDate) {
|
|
92
|
-
|
|
94
|
+
const changes = db.prepare("DELETE FROM request_logs WHERE created_at < ?").run(beforeDate).changes;
|
|
95
|
+
if (changes > 0) {
|
|
96
|
+
db.pragma("incremental_vacuum");
|
|
97
|
+
}
|
|
98
|
+
return changes;
|
|
99
|
+
}
|
|
100
|
+
/** 每行元数据(数字列+索引)的估算字节数 */
|
|
101
|
+
const ROW_METADATA_BYTES = 500;
|
|
102
|
+
/** 估算 request_logs 表占用字节数 */
|
|
103
|
+
export function estimateLogTableSize(db) {
|
|
104
|
+
const row = db.prepare(`
|
|
105
|
+
SELECT COALESCE(SUM(
|
|
106
|
+
COALESCE(length(client_request), 0) + COALESCE(length(upstream_request), 0) +
|
|
107
|
+
COALESCE(length(upstream_response), 0) + COALESCE(length(stream_text_content), 0) +
|
|
108
|
+
COALESCE(length(error_message), 0) + ?
|
|
109
|
+
), 0) as size
|
|
110
|
+
FROM request_logs
|
|
111
|
+
`).get(ROW_METADATA_BYTES);
|
|
112
|
+
return row.size;
|
|
113
|
+
}
|
|
114
|
+
const DELETE_BATCH_SIZE = 1000;
|
|
115
|
+
/** 删除最旧的日志,保留 keepCount 条,返回实际删除条数。分批删除避免长时间锁表 */
|
|
116
|
+
export function deleteOldestLogs(db, keepCount) {
|
|
117
|
+
const total = db.prepare("SELECT count(*) as c FROM request_logs").get().c;
|
|
118
|
+
const toDelete = Math.max(0, total - keepCount);
|
|
119
|
+
if (toDelete === 0)
|
|
120
|
+
return 0;
|
|
121
|
+
let totalDeleted = 0;
|
|
122
|
+
const stmt = db.prepare(`
|
|
123
|
+
DELETE FROM request_logs
|
|
124
|
+
WHERE rowid IN (
|
|
125
|
+
SELECT rowid FROM request_logs ORDER BY created_at ASC LIMIT ?
|
|
126
|
+
)
|
|
127
|
+
`);
|
|
128
|
+
while (totalDeleted < toDelete) {
|
|
129
|
+
const batchSize = Math.min(DELETE_BATCH_SIZE, toDelete - totalDeleted);
|
|
130
|
+
const result = stmt.run(batchSize);
|
|
131
|
+
totalDeleted += result.changes;
|
|
132
|
+
if (result.changes < batchSize)
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
if (totalDeleted > 0) {
|
|
136
|
+
db.pragma("incremental_vacuum");
|
|
137
|
+
}
|
|
138
|
+
return totalDeleted;
|
|
139
|
+
}
|
|
140
|
+
/** 获取 request_logs 总行数 */
|
|
141
|
+
export function getLogCount(db) {
|
|
142
|
+
return db.prepare("SELECT count(*) as c FROM request_logs").get().c;
|
|
93
143
|
}
|
|
94
144
|
/** 查询某条日志的子请求(retry/failover 关联),上限 100 条 */
|
|
95
145
|
export function getRequestLogChildren(db, parentId) {
|
package/dist/db/settings.d.ts
CHANGED
|
@@ -4,3 +4,7 @@ export declare function setSetting(db: Database.Database, key: string, value: st
|
|
|
4
4
|
export declare function isInitialized(db: Database.Database): boolean;
|
|
5
5
|
export declare function getLogRetentionDays(db: Database.Database): number;
|
|
6
6
|
export declare function setLogRetentionDays(db: Database.Database, days: number): void;
|
|
7
|
+
export declare function getDbMaxSizeMb(db: Database.Database): number;
|
|
8
|
+
export declare function setDbMaxSizeMb(db: Database.Database, mb: number): void;
|
|
9
|
+
export declare function getLogTableMaxSizeMb(db: Database.Database): number;
|
|
10
|
+
export declare function setLogTableMaxSizeMb(db: Database.Database, mb: number): void;
|
package/dist/db/settings.js
CHANGED
|
@@ -10,8 +10,25 @@ export function isInitialized(db) {
|
|
|
10
10
|
}
|
|
11
11
|
export function getLogRetentionDays(db) {
|
|
12
12
|
const val = getSetting(db, "log_retention_days");
|
|
13
|
-
|
|
13
|
+
const DEFAULT_LOG_RETENTION_DAYS = 3;
|
|
14
|
+
return val ? parseInt(val, 10) : DEFAULT_LOG_RETENTION_DAYS;
|
|
14
15
|
}
|
|
15
16
|
export function setLogRetentionDays(db, days) {
|
|
16
17
|
setSetting(db, "log_retention_days", String(days));
|
|
17
18
|
}
|
|
19
|
+
const DEFAULT_DB_MAX_SIZE_MB = 1024;
|
|
20
|
+
const DEFAULT_LOG_TABLE_MAX_SIZE_MB = 800;
|
|
21
|
+
export function getDbMaxSizeMb(db) {
|
|
22
|
+
const val = getSetting(db, "db_max_size_mb");
|
|
23
|
+
return val ? parseInt(val, 10) : DEFAULT_DB_MAX_SIZE_MB;
|
|
24
|
+
}
|
|
25
|
+
export function setDbMaxSizeMb(db, mb) {
|
|
26
|
+
setSetting(db, "db_max_size_mb", String(mb));
|
|
27
|
+
}
|
|
28
|
+
export function getLogTableMaxSizeMb(db) {
|
|
29
|
+
const val = getSetting(db, "log_table_max_size_mb");
|
|
30
|
+
return val ? parseInt(val, 10) : DEFAULT_LOG_TABLE_MAX_SIZE_MB;
|
|
31
|
+
}
|
|
32
|
+
export function setLogTableMaxSizeMb(db, mb) {
|
|
33
|
+
setSetting(db, "log_table_max_size_mb", String(mb));
|
|
34
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import { RequestTracker } from "./monitor/request-tracker.js";
|
|
|
33
33
|
import { modelState } from "./proxy/model-state.js";
|
|
34
34
|
import { UsageWindowTracker } from "./proxy/usage-window-tracker.js";
|
|
35
35
|
import { scheduleLogCleanup } from "./db/log-cleaner.js";
|
|
36
|
+
import { scheduleDbSizeMonitor } from "./db/db-size-monitor.js";
|
|
36
37
|
import fastifyStatic from "@fastify/static";
|
|
37
38
|
export async function buildApp(options) {
|
|
38
39
|
const config = options?.config ?? getBaseConfig();
|
|
@@ -184,12 +185,16 @@ export async function buildApp(options) {
|
|
|
184
185
|
return { status: "ok" };
|
|
185
186
|
});
|
|
186
187
|
const logCleanup = scheduleLogCleanup(db, app.log);
|
|
188
|
+
const dbSizeMonitor = scheduleDbSizeMonitor(db, config.DB_PATH, {
|
|
189
|
+
log: app.log,
|
|
190
|
+
});
|
|
187
191
|
return {
|
|
188
192
|
app,
|
|
189
193
|
db,
|
|
190
194
|
usageWindowTracker,
|
|
191
195
|
close: async () => {
|
|
192
196
|
logCleanup.stop();
|
|
197
|
+
dbSizeMonitor.stop();
|
|
193
198
|
tracker.stopPushInterval();
|
|
194
199
|
await app.close();
|
|
195
200
|
db.close();
|
|
@@ -92,6 +92,10 @@ export class MetricsExtractor {
|
|
|
92
92
|
else if (type === "message_delta") {
|
|
93
93
|
const msg = parsed;
|
|
94
94
|
this.outputTokens = msg.usage?.output_tokens ?? null;
|
|
95
|
+
// 第三方 Anthropic 兼容 API(如 OpenRouter、智谱)可能将 input_tokens 放在 message_delta 而非 message_start
|
|
96
|
+
if (this.inputTokens === null && msg.usage?.input_tokens) {
|
|
97
|
+
this.inputTokens = msg.usage.input_tokens;
|
|
98
|
+
}
|
|
95
99
|
this.stopReason = msg.delta?.stop_reason ?? null;
|
|
96
100
|
this.streamEndTime = Date.now();
|
|
97
101
|
}
|
|
@@ -34,6 +34,8 @@ export declare class RequestTracker {
|
|
|
34
34
|
getActive(): ActiveRequest[];
|
|
35
35
|
getRecent(limit?: number): ActiveRequest[];
|
|
36
36
|
get(id: string): ActiveRequest | undefined;
|
|
37
|
+
/** Public alias for API endpoint use — returns full request data including clientRequest */
|
|
38
|
+
getRequestById(id: string): ActiveRequest | undefined;
|
|
37
39
|
getStats(): StatsSnapshot;
|
|
38
40
|
getConcurrency(): ProviderConcurrencySnapshot[];
|
|
39
41
|
getRuntime(): RuntimeMetrics;
|
|
@@ -135,6 +135,10 @@ export class RequestTracker {
|
|
|
135
135
|
get(id) {
|
|
136
136
|
return this.activeMap.get(id) ?? this.recentCompleted.find((r) => r.id === id);
|
|
137
137
|
}
|
|
138
|
+
/** Public alias for API endpoint use — returns full request data including clientRequest */
|
|
139
|
+
getRequestById(id) {
|
|
140
|
+
return this.get(id);
|
|
141
|
+
}
|
|
138
142
|
// --- Stats / monitoring ---
|
|
139
143
|
getStats() {
|
|
140
144
|
return this.statsAggregator.getStats();
|
|
@@ -195,7 +199,22 @@ export class RequestTracker {
|
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
201
|
broadcast(event, data) {
|
|
198
|
-
|
|
202
|
+
// Strip clientRequest from broadcasts to reduce bandwidth;
|
|
203
|
+
// full data available on-demand via API endpoint
|
|
204
|
+
let payload = data;
|
|
205
|
+
if (event === "request_update" && Array.isArray(data)) {
|
|
206
|
+
payload = data.map((req) => {
|
|
207
|
+
const copy = { ...req };
|
|
208
|
+
delete copy.clientRequest;
|
|
209
|
+
return copy;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else if (event === "request_complete" && data && typeof data === "object") {
|
|
213
|
+
const copy = { ...data };
|
|
214
|
+
delete copy.clientRequest;
|
|
215
|
+
payload = copy;
|
|
216
|
+
}
|
|
217
|
+
const msg = `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
|
|
199
218
|
const clientCount = this.clients.size;
|
|
200
219
|
let sentCount = 0;
|
|
201
220
|
for (const client of this.clients) {
|
package/dist/monitor/types.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface RequestLogParams extends LogRetryMeta {
|
|
|
24
24
|
upHdrs: Record<string, string>;
|
|
25
25
|
routerKeyId?: string | null;
|
|
26
26
|
originalModel?: string | null;
|
|
27
|
+
sessionId?: string | null;
|
|
27
28
|
}
|
|
28
29
|
/** 插入成功请求日志,供 openai/anthropic 插件共享 */
|
|
29
30
|
export declare function insertSuccessLog(db: Database.Database, params: RequestLogParams): void;
|
|
@@ -41,6 +42,7 @@ export interface RejectedLogParams extends LogRetryMeta {
|
|
|
41
42
|
clientHeaders: RawHeaders;
|
|
42
43
|
providerId?: string | null;
|
|
43
44
|
originalModel?: string | null;
|
|
45
|
+
sessionId?: string | null;
|
|
44
46
|
}
|
|
45
47
|
/** Log a request rejected before reaching upstream */
|
|
46
48
|
export declare function insertRejectedLog(params: RejectedLogParams): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { insertRequestLog } from "../db/index.js";
|
|
2
2
|
/** 插入成功请求日志,供 openai/anthropic 插件共享 */
|
|
3
3
|
export function insertSuccessLog(db, params) {
|
|
4
|
-
const { id: logId, apiType, model, provider, isStream, startTime, clientReq, upstreamReq, status, respBody, upHdrs, isRetry = false, isFailover = false, originalRequestId = null, routerKeyId = null, originalModel = null } = params;
|
|
4
|
+
const { id: logId, apiType, model, provider, isStream, startTime, clientReq, upstreamReq, status, respBody, upHdrs, isRetry = false, isFailover = false, originalRequestId = null, routerKeyId = null, originalModel = null, sessionId = null } = params;
|
|
5
5
|
insertRequestLog(db, {
|
|
6
6
|
id: logId, api_type: apiType, model, provider_id: provider.id,
|
|
7
7
|
status_code: status, latency_ms: Date.now() - startTime,
|
|
@@ -11,11 +11,12 @@ export function insertSuccessLog(db, params) {
|
|
|
11
11
|
upstream_response: JSON.stringify({ statusCode: status, headers: upHdrs, body: respBody }),
|
|
12
12
|
is_retry: isRetry ? 1 : 0, is_failover: isFailover ? 1 : 0, original_request_id: originalRequestId,
|
|
13
13
|
router_key_id: routerKeyId, original_model: originalModel,
|
|
14
|
+
session_id: sessionId,
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
/** Log a request rejected before reaching upstream */
|
|
17
18
|
export function insertRejectedLog(params) {
|
|
18
|
-
const { db, logId, apiType, model, statusCode, errorMessage, startTime, isStream, routerKeyId, originalBody, clientHeaders, providerId = null, isFailover = false, originalRequestId = null, originalModel = null } = params;
|
|
19
|
+
const { db, logId, apiType, model, statusCode, errorMessage, startTime, isStream, routerKeyId, originalBody, clientHeaders, providerId = null, isFailover = false, originalRequestId = null, originalModel = null, sessionId = null } = params;
|
|
19
20
|
insertRequestLog(db, {
|
|
20
21
|
id: logId,
|
|
21
22
|
api_type: apiType,
|
|
@@ -31,5 +32,6 @@ export function insertRejectedLog(params) {
|
|
|
31
32
|
original_request_id: originalRequestId,
|
|
32
33
|
router_key_id: routerKeyId,
|
|
33
34
|
original_model: originalModel,
|
|
35
|
+
session_id: sessionId,
|
|
34
36
|
});
|
|
35
37
|
}
|
|
@@ -4,6 +4,8 @@ export declare class ModelStateManager {
|
|
|
4
4
|
private db;
|
|
5
5
|
/** 单例注入 DB 实例,启动时调用一次 */
|
|
6
6
|
init(db: Database.Database): void;
|
|
7
|
+
/** 清空所有内存缓存(导入配置后调用) */
|
|
8
|
+
clearAll(): void;
|
|
7
9
|
/** 构造内存 Map 的 key:有 sessionId 时用复合键 */
|
|
8
10
|
buildKey(routerKeyId: string | null, sessionId?: string): string;
|
|
9
11
|
/**
|
|
@@ -23,6 +23,8 @@ export interface OrchestratorConfig {
|
|
|
23
23
|
trackerId?: string;
|
|
24
24
|
/** Claude Code 的 session ID,从 x-claude-code-session-id 请求头获取 */
|
|
25
25
|
sessionId?: string;
|
|
26
|
+
/** 客户端请求的 JSON 字符串(headers + body),用于 Monitor 实时查看 */
|
|
27
|
+
clientRequest?: string;
|
|
26
28
|
}
|
|
27
29
|
export interface HandleContext {
|
|
28
30
|
streamTimeoutMs?: number;
|
|
@@ -14,15 +14,49 @@ import { updateLogStreamContent } from "../db/index.js";
|
|
|
14
14
|
import { callNonStream, callStream } from "./transport.js";
|
|
15
15
|
import { insertRejectedLog } from "./log-helpers.js";
|
|
16
16
|
const HTTP_ERROR_THRESHOLD = 400;
|
|
17
|
-
const STREAM_CONTENT_MAX_RAW =
|
|
18
|
-
const STREAM_CONTENT_MAX_TEXT =
|
|
17
|
+
const STREAM_CONTENT_MAX_RAW = 131072;
|
|
18
|
+
const STREAM_CONTENT_MAX_TEXT = 65536;
|
|
19
|
+
/** 将 tracker blocks 序列化为前端 tryDirectParse 可解析的 JSON */
|
|
20
|
+
function serializeBlocksForStorage(blocks, apiType) {
|
|
21
|
+
if (!blocks || blocks.length === 0)
|
|
22
|
+
return "";
|
|
23
|
+
if (apiType === "anthropic") {
|
|
24
|
+
const content = blocks.map(b => {
|
|
25
|
+
if (b.type === "thinking")
|
|
26
|
+
return { type: "thinking", thinking: b.content };
|
|
27
|
+
if (b.type === "tool_use") {
|
|
28
|
+
let input = {};
|
|
29
|
+
try {
|
|
30
|
+
input = JSON.parse(b.content || "{}");
|
|
31
|
+
}
|
|
32
|
+
catch { /* eslint-disable-line taste/no-silent-catch -- tool_use content 非合法 JSON 时保留空对象 */ }
|
|
33
|
+
return { type: "tool_use", name: b.name ?? "", input };
|
|
34
|
+
}
|
|
35
|
+
return { type: "text", text: b.content };
|
|
36
|
+
});
|
|
37
|
+
return JSON.stringify({ content });
|
|
38
|
+
}
|
|
39
|
+
const text = blocks.filter(b => b.type === "text").map(b => b.content).join("");
|
|
40
|
+
return JSON.stringify({ choices: [{ message: { content: text } }] });
|
|
41
|
+
}
|
|
42
|
+
function toStreamMetrics(m) {
|
|
43
|
+
return {
|
|
44
|
+
inputTokens: m.input_tokens,
|
|
45
|
+
outputTokens: m.output_tokens,
|
|
46
|
+
cacheReadTokens: m.cache_read_tokens,
|
|
47
|
+
ttftMs: m.ttft_ms,
|
|
48
|
+
tokensPerSecond: m.tokens_per_second,
|
|
49
|
+
stopReason: m.stop_reason,
|
|
50
|
+
isComplete: m.is_complete === 1,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
19
53
|
export async function handleProxyRequest(request, reply, apiType, upstreamPath, errors, deps, options) {
|
|
20
54
|
request.raw.socket.on("error", (err) => request.log.debug({ err }, "client socket error"));
|
|
21
55
|
const clientModel = request.body.model || "unknown";
|
|
22
56
|
const sessionId = request.headers["x-claude-code-session-id"];
|
|
23
57
|
const { effectiveModel, originalModel, interceptResponse } = applyEnhancement(deps.db, request, clientModel, sessionId);
|
|
24
58
|
if (interceptResponse)
|
|
25
|
-
return handleIntercept(deps.db, apiType, request, reply, interceptResponse, clientModel);
|
|
59
|
+
return handleIntercept(deps.db, apiType, request, reply, interceptResponse, clientModel, sessionId);
|
|
26
60
|
const group = getMappingGroup(deps.db, effectiveModel);
|
|
27
61
|
const isFailover = group?.strategy === "failover";
|
|
28
62
|
const excludeTargets = [];
|
|
@@ -47,7 +81,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
47
81
|
db: deps.db, logId, apiType, model: effectiveModel, statusCode: e.statusCode,
|
|
48
82
|
errorMessage: `All failover targets exhausted (${excludeTargets.length} attempted)`,
|
|
49
83
|
startTime, isStream, routerKeyId, originalBody, clientHeaders: cliHdrs, originalModel,
|
|
50
|
-
isFailover: true, originalRequestId: rootLogId,
|
|
84
|
+
isFailover: true, originalRequestId: rootLogId, sessionId,
|
|
51
85
|
});
|
|
52
86
|
return reply.status(e.statusCode).send(e.body);
|
|
53
87
|
}
|
|
@@ -56,7 +90,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
56
90
|
db: deps.db, logId, apiType, model: effectiveModel, statusCode: e.statusCode,
|
|
57
91
|
errorMessage: `No mapping found for model '${effectiveModel}'`, startTime, isStream,
|
|
58
92
|
routerKeyId, originalBody, clientHeaders: cliHdrs, originalModel,
|
|
59
|
-
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
93
|
+
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
60
94
|
});
|
|
61
95
|
return reply.status(e.statusCode).send(e.body);
|
|
62
96
|
}
|
|
@@ -71,7 +105,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
71
105
|
db: deps.db, logId, apiType, model: effectiveModel, statusCode: e.statusCode,
|
|
72
106
|
errorMessage: `Model '${resolved.backend_model}' not allowed`, startTime, isStream, routerKeyId,
|
|
73
107
|
originalBody, clientHeaders: cliHdrs, providerId: resolved.provider_id, originalModel,
|
|
74
|
-
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
108
|
+
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
75
109
|
});
|
|
76
110
|
return reply.status(e.statusCode).send(e.body);
|
|
77
111
|
}
|
|
@@ -88,7 +122,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
88
122
|
db: deps.db, logId, apiType, model: effectiveModel, statusCode: e.statusCode,
|
|
89
123
|
errorMessage: `Provider '${resolved.provider_id}' unavailable`, startTime, isStream, routerKeyId,
|
|
90
124
|
originalBody, clientHeaders: cliHdrs, providerId: resolved.provider_id, originalModel,
|
|
91
|
-
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
125
|
+
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
92
126
|
});
|
|
93
127
|
return reply.status(e.statusCode).send(e.body);
|
|
94
128
|
}
|
|
@@ -98,7 +132,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
98
132
|
db: deps.db, logId, apiType, model: effectiveModel, statusCode: e.statusCode,
|
|
99
133
|
errorMessage: `API type mismatch: expected '${apiType}'`, startTime, isStream, routerKeyId,
|
|
100
134
|
originalBody, clientHeaders: cliHdrs, providerId: resolved.provider_id, originalModel,
|
|
101
|
-
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
135
|
+
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
102
136
|
});
|
|
103
137
|
return reply.status(e.statusCode).send(e.body);
|
|
104
138
|
}
|
|
@@ -119,14 +153,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
119
153
|
if (isStream) {
|
|
120
154
|
const metricsTransform = new SSEMetricsTransform(apiType, startTime, {
|
|
121
155
|
onMetrics: (m) => {
|
|
122
|
-
deps.tracker?.update(logId, {
|
|
123
|
-
streamMetrics: {
|
|
124
|
-
inputTokens: m.input_tokens, outputTokens: m.output_tokens,
|
|
125
|
-
cacheReadTokens: m.cache_read_tokens,
|
|
126
|
-
ttftMs: m.ttft_ms, tokensPerSecond: m.tokens_per_second,
|
|
127
|
-
stopReason: m.stop_reason, isComplete: m.is_complete === 1,
|
|
128
|
-
},
|
|
129
|
-
});
|
|
156
|
+
deps.tracker?.update(logId, { streamMetrics: toStreamMetrics(m) });
|
|
130
157
|
},
|
|
131
158
|
onChunk: (rawLine) => {
|
|
132
159
|
deps.tracker?.appendStreamChunk(logId, rawLine, apiType, STREAM_CONTENT_MAX_RAW, STREAM_CONTENT_MAX_TEXT);
|
|
@@ -135,21 +162,19 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
135
162
|
const checkEarlyError = deps.matcher
|
|
136
163
|
? (data) => deps.matcher.test(UPSTREAM_SUCCESS, data)
|
|
137
164
|
: undefined;
|
|
138
|
-
|
|
165
|
+
const streamResult = await callStream(provider, apiKey, body, cliHdrs, reply, deps.streamTimeoutMs, upstreamPath, buildUpstreamHeaders, metricsTransform, checkEarlyError);
|
|
166
|
+
const m = (streamResult.kind === "stream_success" || streamResult.kind === "stream_abort")
|
|
167
|
+
? streamResult.metrics : undefined;
|
|
168
|
+
if (m)
|
|
169
|
+
deps.tracker?.update(logId, { streamMetrics: toStreamMetrics(m) });
|
|
170
|
+
return streamResult;
|
|
139
171
|
}
|
|
140
172
|
const result = await callNonStream(provider, apiKey, body, cliHdrs, upstreamPath, buildUpstreamHeaders);
|
|
141
173
|
// 非流式请求:从响应体提取指标并更新 tracker
|
|
142
174
|
if (result.kind === "success") {
|
|
143
175
|
const mr = MetricsExtractor.fromNonStreamResponse(apiType, result.body);
|
|
144
176
|
if (mr) {
|
|
145
|
-
deps.tracker?.update(logId, {
|
|
146
|
-
streamMetrics: {
|
|
147
|
-
inputTokens: mr.input_tokens, outputTokens: mr.output_tokens,
|
|
148
|
-
cacheReadTokens: mr.cache_read_tokens,
|
|
149
|
-
ttftMs: mr.ttft_ms, tokensPerSecond: mr.tokens_per_second,
|
|
150
|
-
stopReason: mr.stop_reason, isComplete: mr.is_complete === 1,
|
|
151
|
-
},
|
|
152
|
-
});
|
|
177
|
+
deps.tracker?.update(logId, { streamMetrics: toStreamMetrics(mr) });
|
|
153
178
|
}
|
|
154
179
|
}
|
|
155
180
|
// 非流式响应注入模型信息标签(模型映射场景)
|
|
@@ -168,19 +193,25 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
168
193
|
return result;
|
|
169
194
|
};
|
|
170
195
|
try {
|
|
171
|
-
const resilienceResult = await deps.orchestrator.handle(request, reply, apiType, { resolved, provider, clientModel: effectiveModel, isStream, trackerId: logId, sessionId }, { retryMaxAttempts: deps.retryMaxAttempts, retryBaseDelayMs: deps.retryBaseDelayMs, isFailover, ruleMatcher: deps.matcher, transportFn });
|
|
196
|
+
const resilienceResult = await deps.orchestrator.handle(request, reply, apiType, { resolved, provider, clientModel: effectiveModel, isStream, trackerId: logId, sessionId, clientRequest: clientReq }, { retryMaxAttempts: deps.retryMaxAttempts, retryBaseDelayMs: deps.retryBaseDelayMs, isFailover, ruleMatcher: deps.matcher, transportFn });
|
|
172
197
|
const lastLogId = logResilienceResult(deps.db, {
|
|
173
198
|
apiType, model: effectiveModel, providerId: provider.id, isStream,
|
|
174
|
-
clientReq, upstreamReqBase, logId, routerKeyId, originalModel,
|
|
199
|
+
clientReq, upstreamReqBase, logId, routerKeyId, originalModel, sessionId,
|
|
175
200
|
failover: { isFailoverIteration, rootLogId: rootLogId },
|
|
176
201
|
}, resilienceResult.attempts, resilienceResult.result, startTime);
|
|
177
202
|
collectTransportMetrics(deps.db, apiType, resilienceResult.result, isStream, lastLogId, provider.id, resolved.backend_model, request);
|
|
178
|
-
// 流式请求:将 tracker
|
|
203
|
+
// 流式请求:将 tracker 中累积的内容持久化到日志
|
|
204
|
+
// blocks 含非 text 类型时(thinking/tool_use)必须序列化为 JSON 以保留结构
|
|
205
|
+
// 注意:tracker 在原 logId 下累积内容,lastLogId 可能因 resilience 重试而指向不同记录
|
|
179
206
|
if (isStream && deps.tracker) {
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
207
|
+
const sc = deps.tracker.get(logId)?.streamContent;
|
|
208
|
+
const blocks = sc?.blocks;
|
|
209
|
+
const hasStructured = blocks && blocks.length > 0 && blocks.some(b => b.type !== "text");
|
|
210
|
+
const content = hasStructured
|
|
211
|
+
? serializeBlocksForStorage(blocks, apiType)
|
|
212
|
+
: (sc?.textContent || "");
|
|
213
|
+
if (content)
|
|
214
|
+
updateLogStreamContent(deps.db, lastLogId, content);
|
|
184
215
|
}
|
|
185
216
|
// Failover: 单 provider 内重试已耗尽但仍失败,尝试下一个 target
|
|
186
217
|
if (isFailover && !reply.raw.headersSent) {
|
|
@@ -216,7 +247,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
216
247
|
errorMessage: `Concurrency queue full for provider '${provider.id}'`,
|
|
217
248
|
startTime, isStream, routerKeyId, originalBody, clientHeaders: cliHdrs,
|
|
218
249
|
providerId: provider.id, originalModel,
|
|
219
|
-
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
250
|
+
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
220
251
|
});
|
|
221
252
|
return reply.status(err.statusCode).send(err.body);
|
|
222
253
|
}
|
|
@@ -227,7 +258,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
227
258
|
errorMessage: `Concurrency wait timeout for provider '${provider.id}' (${e.timeoutMs}ms)`,
|
|
228
259
|
startTime, isStream, routerKeyId, originalBody, clientHeaders: cliHdrs,
|
|
229
260
|
providerId: provider.id, originalModel,
|
|
230
|
-
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null,
|
|
261
|
+
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
231
262
|
});
|
|
232
263
|
return reply.status(err.statusCode).send(err.body);
|
|
233
264
|
}
|
|
@@ -240,6 +271,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
240
271
|
client_request: clientReq, upstream_request: upstreamReqBase,
|
|
241
272
|
is_failover: isFailoverIteration ? 1 : 0, original_request_id: isFailoverIteration ? rootLogId : null,
|
|
242
273
|
router_key_id: routerKeyId, original_model: originalModel,
|
|
274
|
+
session_id: sessionId,
|
|
243
275
|
});
|
|
244
276
|
const err = errors.upstreamConnectionFailed();
|
|
245
277
|
return reply.status(err.statusCode).send(err.body);
|
|
@@ -11,7 +11,7 @@ export declare function handleIntercept(db: Database.Database, apiType: "openai"
|
|
|
11
11
|
statusCode: number;
|
|
12
12
|
body: unknown;
|
|
13
13
|
meta?: unknown;
|
|
14
|
-
}, clientModel: string): import("fastify").FastifyReply;
|
|
14
|
+
}, clientModel: string, sessionId?: string): import("fastify").FastifyReply;
|
|
15
15
|
export declare function logResilienceResult(db: Database.Database, params: {
|
|
16
16
|
apiType: "openai" | "anthropic";
|
|
17
17
|
model: string;
|
|
@@ -22,6 +22,7 @@ export declare function logResilienceResult(db: Database.Database, params: {
|
|
|
22
22
|
logId: string;
|
|
23
23
|
routerKeyId: string | null;
|
|
24
24
|
originalModel: string | null;
|
|
25
|
+
sessionId?: string | null;
|
|
25
26
|
failover?: FailoverContext;
|
|
26
27
|
}, attempts: ResilienceAttempt[], result: TransportResult, startTime: number): string;
|
|
27
28
|
export declare function collectTransportMetrics(db: Database.Database, apiType: "openai" | "anthropic", result: TransportResult, isStream: boolean, lastSuccessLogId: string, providerId: string, backendModel: string, request: FastifyRequest): void;
|
|
@@ -18,7 +18,7 @@ export function sanitizeHeadersForLog(headers) {
|
|
|
18
18
|
return sanitized;
|
|
19
19
|
}
|
|
20
20
|
// ---------- Logging helpers (extracted from proxy-core) ----------
|
|
21
|
-
export function handleIntercept(db, apiType, request, reply, interceptResponse, clientModel) {
|
|
21
|
+
export function handleIntercept(db, apiType, request, reply, interceptResponse, clientModel, sessionId) {
|
|
22
22
|
const logId = randomUUID();
|
|
23
23
|
const isStream = request.body.stream === true;
|
|
24
24
|
const respBody = JSON.stringify(interceptResponse.body);
|
|
@@ -32,6 +32,7 @@ export function handleIntercept(db, apiType, request, reply, interceptResponse,
|
|
|
32
32
|
upstream_response: JSON.stringify({ statusCode: interceptResponse.statusCode, body: respBody }),
|
|
33
33
|
is_retry: 0, is_failover: 0, original_request_id: null,
|
|
34
34
|
router_key_id: request.routerKey?.id ?? null, original_model: null,
|
|
35
|
+
session_id: sessionId,
|
|
35
36
|
});
|
|
36
37
|
return reply.status(interceptResponse.statusCode).send(interceptResponse.body);
|
|
37
38
|
}
|
|
@@ -56,6 +57,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
56
57
|
is_retry: isOriginal ? 0 : 1, is_failover: isFailoverLog ? 1 : 0,
|
|
57
58
|
original_request_id: parentId,
|
|
58
59
|
router_key_id: params.routerKeyId, original_model: params.originalModel,
|
|
60
|
+
session_id: params.sessionId,
|
|
59
61
|
});
|
|
60
62
|
}
|
|
61
63
|
else if (attempt.statusCode !== UPSTREAM_SUCCESS) {
|
|
@@ -70,6 +72,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
70
72
|
is_retry: isOriginal ? 0 : 1, is_failover: isFailoverLog ? 1 : 0,
|
|
71
73
|
original_request_id: parentId,
|
|
72
74
|
router_key_id: params.routerKeyId, original_model: params.originalModel,
|
|
75
|
+
session_id: params.sessionId,
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
else {
|
|
@@ -87,6 +90,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
87
90
|
isRetry: !isOriginal, isFailover: isFailoverLog,
|
|
88
91
|
originalRequestId: parentId,
|
|
89
92
|
routerKeyId: params.routerKeyId, originalModel: params.originalModel,
|
|
93
|
+
sessionId: params.sessionId,
|
|
90
94
|
});
|
|
91
95
|
lastSuccessLogId = attemptLogId;
|
|
92
96
|
}
|
package/dist/proxy/semaphore.js
CHANGED
|
@@ -149,4 +149,15 @@ export class ProviderSemaphoreManager {
|
|
|
149
149
|
}
|
|
150
150
|
this.entries.delete(providerId);
|
|
151
151
|
}
|
|
152
|
+
/** 清除所有 provider 的信号量配置(导入配置后调用) */
|
|
153
|
+
removeAll() {
|
|
154
|
+
for (const [, entry] of this.entries) {
|
|
155
|
+
for (const e of entry.queue) {
|
|
156
|
+
if (e.timer)
|
|
157
|
+
clearTimeout(e.timer);
|
|
158
|
+
e.reject(new Error("Provider removed"));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
this.entries.clear();
|
|
162
|
+
}
|
|
152
163
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{It as e,K as t,Pt as n,U as r,at as i,ct as a,r as o}from"./button-
|
|
1
|
+
import{It as e,K as t,Pt as n,U as r,at as i,ct as a,r as o}from"./button-Dbz2Be22.js";var s=[`data-size`],c=t({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(t){let c=t;return(l,u)=>(i(),r(`div`,{"data-slot":`card`,"data-size":t.size,class:e(n(o)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[a(l.$slots,`default`)],10,s))}}),l=t({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(t){let s=t;return(t,c)=>(i(),r(`div`,{"data-slot":`card-content`,class:e(n(o)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[a(t.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{It as e,K as t,Pt as n,U as r,at as i,ct as a,r as o}from"./button-
|
|
1
|
+
import{It as e,K as t,Pt as n,U as r,at as i,ct as a,r as o}from"./button-Dbz2Be22.js";var s=t({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(t){let s=t;return(t,c)=>(i(),r(`div`,{"data-slot":`card-header`,class:e(n(o)(`gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]`,s.class))},[a(t.$slots,`default`)],2))}}),c=t({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(t){let s=t;return(t,c)=>(i(),r(`div`,{"data-slot":`card-title`,class:e(n(o)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[a(t.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,F as t,G as n,H as r,I as i,J as a,K as o,Lt as s,Pt as c,V as l,at as u,b as d,ct as f,i as p,p as m,r as h,ut as g,vt as _,z as v}from"./button-Dbz2Be22.js";import{n as y,t as b}from"./ohash.D__AXeF1-BGxYMs6k.js";import{g as x,o as S,u as C,y as w}from"./Teleport-B0PNXZbP.js";import{n as T}from"./VisuallyHidden-BwwTtzb9.js";import{t as E}from"./useForwardExpose-C2_ks3sW.js";import{t as D}from"./VisuallyHiddenInput-EGZSP7s8.js";import{s as O}from"./TableHeader-Bn0bodWx.js";function k(e,t){return x(e)?!1:Array.isArray(e)?e.some(e=>b(e,t)):b(e,t)}var[A,j]=w(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=w(`CheckboxRoot`),I=o({inheritAttrs:!1,__name:`CheckboxRoot`,props:{defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},disabled:{type:Boolean,required:!1},value:{type:null,required:!1,default:`on`},id:{type:String,required:!1},trueValue:{type:null,required:!1,default:()=>!0},falseValue:{type:null,required:!1,default:()=>!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`],setup(n,{emit:a}){let o=n,s=a,{forwardRef:d,currentElement:h}=E(),y=A(null),S=m(o,`modelValue`,s,{defaultValue:o.defaultValue??o.falseValue,passive:o.modelValue===void 0}),C=v(()=>y?.disabled.value||o.disabled),w=v(()=>b(S.value,o.trueValue)),j=v(()=>x(y?.modelValue.value)?S.value===`indeterminate`?`indeterminate`:w.value:k(y.modelValue.value,o.value));function P(){if(x(y?.modelValue.value))S.value===`indeterminate`?S.value=o.trueValue:S.value=w.value?o.falseValue:o.trueValue;else{let e=[...y.modelValue.value||[]];if(k(e,o.value)){let t=e.findIndex(e=>b(e,o.value));e.splice(t,1)}else e.push(o.value);y.modelValue.value=e}}let I=T(h),L=v(()=>o.id&&h.value?document.querySelector(`[for="${o.id}"]`)?.innerText:void 0);return F({disabled:C,state:j}),(n,a)=>(u(),l(g(c(y)?.rovingFocus.value?c(O):c(p)),e(n.$attrs,{id:n.id,ref:c(d),role:`checkbox`,"as-child":n.asChild,as:n.as,type:n.as===`button`?`button`:void 0,"aria-checked":c(M)(j.value)?`mixed`:j.value,"aria-required":n.required,"aria-label":n.$attrs[`aria-label`]||L.value,"data-state":c(N)(j.value),"data-disabled":C.value?``:void 0,disabled:C.value,focusable:c(y)?.rovingFocus.value?!C.value:void 0,onKeydown:t(i(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:_(()=>[f(n.$slots,`default`,{modelValue:c(S),state:j.value}),c(I)&&n.name&&!c(y)?(u(),l(c(D),{key:0,type:`checkbox`,checked:!!j.value,name:n.name,value:n.value,disabled:C.value,required:n.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):r(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=o({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(t){let{forwardRef:r}=E(),i=P();return(t,a)=>(u(),l(c(S),{present:t.forceMount||c(M)(c(i).state.value)||c(i).state.value===!0},{default:_(()=>[n(c(p),e({ref:c(r),"data-state":c(N)(c(i).state.value),"data-disabled":c(i).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":t.asChild,as:t.as},t.$attrs),{default:_(()=>[f(t.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=o({__name:`Checkbox`,props:{defaultValue:{},modelValue:{},disabled:{type:Boolean},value:{},id:{},trueValue:{},falseValue:{},asChild:{type:Boolean},as:{},name:{},required:{type:Boolean},class:{type:[Boolean,null,String,Object,Array]}},emits:[`update:modelValue`],setup(t,{emit:r}){let i=t,o=r,p=C(d(i,`class`),o);return(t,r)=>(u(),l(c(I),e({"data-slot":`checkbox`},c(p),{class:c(h)(`border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-md border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50`,i.class)}),{default:_(e=>[n(c(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:_(()=>[f(t.$slots,`default`,s(a(e)),()=>[n(c(y))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Et as t,G as n,H as r,J as i,K as a,Lt as o,Pt as s,V as c,at as l,ct as u,et as d,ht as f,i as p,jt as m,nt as h,p as g,u as _,vt as v,z as y}from"./button-Dbz2Be22.js";import{c as b,o as x,u as S,y as C}from"./Teleport-B0PNXZbP.js";import{t as w}from"./useForwardExpose-C2_ks3sW.js";var[T,E]=C(`CollapsibleRoot`),D=a({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:n}){let r=e,i=g(r,`open`,n,{defaultValue:r.defaultOpen,passive:r.open===void 0}),{disabled:a,unmountOnHide:o}=m(r);return E({contentId:``,disabled:a,open:i,unmountOnHide:o,onOpenToggle:()=>{a.value||(i.value=!i.value)}}),t({open:i}),w(),(e,t)=>(l(),c(s(p),{as:e.as,"as-child":r.asChild,"data-state":s(i)?`open`:`closed`,"data-disabled":s(a)?``:void 0},{default:v(()=>[u(e.$slots,`default`,{open:s(i)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=a({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(i,{emit:a}){let o=i,m=a,g=T();g.contentId||=b(void 0,`reka-collapsible-content`);let S=t(),{forwardRef:C,currentElement:E}=w(),D=t(0),O=t(0),k=y(()=>g.open.value),A=t(k.value),j=t();f(()=>[k.value,S.value?.present],async()=>{await d();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=y(()=>A.value&&g.open.value);return h(()=>{requestAnimationFrame(()=>{A.value=!1})}),_(E,`beforematch`,e=>{requestAnimationFrame(()=>{g.onOpenToggle(),m(`contentFound`)})}),(t,i)=>(l(),c(s(x),{ref_key:`presentRef`,ref:S,present:t.forceMount||s(g).open.value,"force-mount":!0},{default:v(({present:i})=>[n(s(p),e(t.$attrs,{id:s(g).contentId,ref:s(C),"as-child":o.asChild,as:t.as,hidden:i?void 0:s(g).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:s(g).open.value?`open`:`closed`,"data-disabled":s(g).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:v(()=>[!s(g).unmountOnHide.value||i?u(t.$slots,`default`,{key:0}):r(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=a({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let t=e;w();let n=T();return(e,r)=>(l(),c(s(p),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":t.asChild,"aria-controls":s(n).contentId,"aria-expanded":s(n).open.value,"data-state":s(n).open.value?`open`:`closed`,"data-disabled":s(n).disabled?.value?``:void 0,disabled:s(n).disabled?.value,onClick:s(n).onOpenToggle},{default:v(()=>[u(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),A=a({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(t,{emit:n}){let r=S(t,n);return(t,n)=>(l(),c(s(D),e({"data-slot":`collapsible`},s(r)),{default:v(e=>[u(t.$slots,`default`,o(i(e)))]),_:3},16))}}),j=a({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(t){let n=t;return(t,r)=>(l(),c(s(O),e({"data-slot":`collapsible-content`},n),{default:v(()=>[u(t.$slots,`default`)]),_:3},16))}}),M=a({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(t){let n=t;return(t,r)=>(l(),c(s(k),e({"data-slot":`collapsible-trigger`},n),{default:v(()=>[u(t.$slots,`default`)]),_:3},16))}});export{j as n,A as r,M as t};
|