@morlay/session-rdb 0.0.21-alpha.7 → 0.0.21-alpha.9
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 +31 -12
- package/dist/artifact.d.mts +2 -101
- package/dist/artifact.mjs +2 -2
- package/dist/{branch-BjUR6wfe.mjs → branch-D4nVzQQM.mjs} +3 -3
- package/dist/deletion.d.mts +1 -1
- package/dist/deletion.mjs +1 -1
- package/dist/{dist-vIVO6bA-.mjs → dist-DHer1cql.mjs} +14 -7
- package/dist/export.d.mts +8 -0
- package/dist/export.mjs +76 -0
- package/dist/gc.d.mts +8 -0
- package/dist/gc.mjs +51 -0
- package/dist/import.d.mts +1 -1
- package/dist/import.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/{log-qCNkDjh5.mjs → log-BWVFYAh5.mjs} +39 -12
- package/dist/{backend-E0bmTXpz.d.mts → log-CUjKQbOR.d.mts} +222 -39
- package/dist/{sqlite-zcXVyMxr.mjs → sqlite-SCiSnwaz.mjs} +321 -259
- package/dist/{src-B9X8BKyh.mjs → src-BZ1tffd5.mjs} +342 -310
- package/dist/storage.d.mts +20 -7
- package/dist/storage.mjs +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +48 -29
- package/dist/usage.d.mts +2 -0
- package/dist/usage.mjs +152 -0
- package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +48 -0
- package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +48 -0
- package/package.json +25 -30
- package/src/backend.ts +41 -17
- package/src/branch.ts +4 -4
- package/src/drizzle/postgres-v3.ts +2 -0
- package/src/drizzle/sqlite-v3.ts +2 -0
- package/src/entities/v3/event-usage.ts +8 -1
- package/src/entities/v3/index.ts +8 -0
- package/src/entities/v3/session-counts.ts +23 -0
- package/src/entities/v3/session-usage.ts +29 -0
- package/src/index.ts +230 -88
- package/src/log.ts +58 -17
- package/src/postgres.ts +248 -149
- package/src/rows.ts +2 -1
- package/src/schema.ts +4 -0
- package/src/sqlite.ts +263 -124
- package/src/storage-takeover/repository.ts +1 -4
- package/src/usage.ts +10 -6
- package/cordis.patch.yml +0 -13
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { COUNTED_EVENT_TYPES, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey } from "./usage.mjs";
|
|
2
|
+
import { _ as sessionConflictRow, b as titleOfEventData, v as sessionInsertRow } from "./log-BWVFYAh5.mjs";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
4
5
|
import { and, desc, eq, gte, inArray, isNotNull, lt, notInArray, sql } from "drizzle-orm";
|
|
@@ -453,7 +454,9 @@ const sessionProjcacheRows = {
|
|
|
453
454
|
//#region src/entities/v3/event-usage.ts
|
|
454
455
|
/**
|
|
455
456
|
* 用量日志:一条 `assistant/message` 事件一行(按事件行唯一,fork 共享行不重复)。
|
|
456
|
-
*
|
|
457
|
+
* 读侧维度全部物化在行上——本地日、是否被会话引用、是否被子代理会话引用——统计因此不做
|
|
458
|
+
* `EXISTS` 判定、也不回连事件表;事件行本身仍留在 `t_events`。
|
|
459
|
+
* 取舍见 ADR-统计衍生表物化归属与汇总。
|
|
457
460
|
*/
|
|
458
461
|
const eventUsage = {
|
|
459
462
|
name: "t_event_usage",
|
|
@@ -471,8 +474,22 @@ const eventUsage = {
|
|
|
471
474
|
type: "bigint",
|
|
472
475
|
notNull: true
|
|
473
476
|
},
|
|
477
|
+
f_day: {
|
|
478
|
+
type: "text",
|
|
479
|
+
notNull: true
|
|
480
|
+
},
|
|
474
481
|
f_provider: { type: "text" },
|
|
475
482
|
f_model: { type: "text" },
|
|
483
|
+
f_referenced: {
|
|
484
|
+
type: "integer",
|
|
485
|
+
notNull: true,
|
|
486
|
+
default: 0
|
|
487
|
+
},
|
|
488
|
+
f_subagent: {
|
|
489
|
+
type: "integer",
|
|
490
|
+
notNull: true,
|
|
491
|
+
default: 0
|
|
492
|
+
},
|
|
476
493
|
f_input_tokens: {
|
|
477
494
|
type: "integer",
|
|
478
495
|
notNull: true,
|
|
@@ -502,6 +519,118 @@ const eventUsage = {
|
|
|
502
519
|
indexes: { idx_event_usage_created_at: { columns: ["f_created_at"] } }
|
|
503
520
|
};
|
|
504
521
|
//#endregion
|
|
522
|
+
//#region src/entities/v3/session-usage.ts
|
|
523
|
+
/**
|
|
524
|
+
* 会话 × 本地日 × 模型的 token 汇总(派生统计表):**按会话的行**读它,因此保住
|
|
525
|
+
* 「fork 子会话含继承前缀」的口径(各会话行之和 ≥ 总量)而不回连事件表。
|
|
526
|
+
* 旁路累加、可按会话重建,读侧只读;取舍见 ADR-统计衍生表物化归属与汇总。
|
|
527
|
+
*/
|
|
528
|
+
const sessionUsage = {
|
|
529
|
+
name: "t_session_usage",
|
|
530
|
+
columns: {
|
|
531
|
+
f_session_id: {
|
|
532
|
+
type: "text",
|
|
533
|
+
notNull: true,
|
|
534
|
+
references: {
|
|
535
|
+
table: "t_sessions",
|
|
536
|
+
column: "f_session_id",
|
|
537
|
+
onDelete: "cascade"
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
f_day: {
|
|
541
|
+
type: "text",
|
|
542
|
+
notNull: true
|
|
543
|
+
},
|
|
544
|
+
f_provider: {
|
|
545
|
+
type: "text",
|
|
546
|
+
notNull: true,
|
|
547
|
+
default: ""
|
|
548
|
+
},
|
|
549
|
+
f_model: {
|
|
550
|
+
type: "text",
|
|
551
|
+
notNull: true,
|
|
552
|
+
default: ""
|
|
553
|
+
},
|
|
554
|
+
f_input_tokens: {
|
|
555
|
+
type: "integer",
|
|
556
|
+
notNull: true,
|
|
557
|
+
default: 0
|
|
558
|
+
},
|
|
559
|
+
f_output_tokens: {
|
|
560
|
+
type: "integer",
|
|
561
|
+
notNull: true,
|
|
562
|
+
default: 0
|
|
563
|
+
},
|
|
564
|
+
f_cache_read_tokens: {
|
|
565
|
+
type: "integer",
|
|
566
|
+
notNull: true,
|
|
567
|
+
default: 0
|
|
568
|
+
},
|
|
569
|
+
f_reasoning_tokens: {
|
|
570
|
+
type: "integer",
|
|
571
|
+
notNull: true,
|
|
572
|
+
default: 0
|
|
573
|
+
},
|
|
574
|
+
f_total_tokens: {
|
|
575
|
+
type: "integer",
|
|
576
|
+
notNull: true,
|
|
577
|
+
default: 0
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
uniques: { uq_session_usage_key: { columns: [
|
|
581
|
+
"f_session_id",
|
|
582
|
+
"f_day",
|
|
583
|
+
"f_provider",
|
|
584
|
+
"f_model"
|
|
585
|
+
] } }
|
|
586
|
+
};
|
|
587
|
+
//#endregion
|
|
588
|
+
//#region src/entities/v3/session-counts.ts
|
|
589
|
+
/**
|
|
590
|
+
* 会话 × 本地日的活动计数(派生统计表):轮次 / 步骤 / 用户输入 / 工具调用四列直接读,
|
|
591
|
+
* 不再存事件类型、也不在读取时折。旁路累加、可按会话重建,读侧只读;
|
|
592
|
+
* 取舍见 ADR-统计衍生表物化归属与汇总。
|
|
593
|
+
*/
|
|
594
|
+
const sessionCounts = {
|
|
595
|
+
name: "t_session_counts",
|
|
596
|
+
columns: {
|
|
597
|
+
f_session_id: {
|
|
598
|
+
type: "text",
|
|
599
|
+
notNull: true,
|
|
600
|
+
references: {
|
|
601
|
+
table: "t_sessions",
|
|
602
|
+
column: "f_session_id",
|
|
603
|
+
onDelete: "cascade"
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
f_day: {
|
|
607
|
+
type: "text",
|
|
608
|
+
notNull: true
|
|
609
|
+
},
|
|
610
|
+
f_turns: {
|
|
611
|
+
type: "integer",
|
|
612
|
+
notNull: true,
|
|
613
|
+
default: 0
|
|
614
|
+
},
|
|
615
|
+
f_steps: {
|
|
616
|
+
type: "integer",
|
|
617
|
+
notNull: true,
|
|
618
|
+
default: 0
|
|
619
|
+
},
|
|
620
|
+
f_user_inputs: {
|
|
621
|
+
type: "integer",
|
|
622
|
+
notNull: true,
|
|
623
|
+
default: 0
|
|
624
|
+
},
|
|
625
|
+
f_tool_calls: {
|
|
626
|
+
type: "integer",
|
|
627
|
+
notNull: true,
|
|
628
|
+
default: 0
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
uniques: { uq_session_counts_key: { columns: ["f_session_id", "f_day"] } }
|
|
632
|
+
};
|
|
633
|
+
//#endregion
|
|
505
634
|
//#region src/entities/v3/index.ts
|
|
506
635
|
const sqliteTableDefs = [
|
|
507
636
|
persistenceState,
|
|
@@ -514,7 +643,9 @@ const sqliteTableDefs = [
|
|
|
514
643
|
workspaceSessions,
|
|
515
644
|
workspaceState,
|
|
516
645
|
sessionProjcacheRows,
|
|
517
|
-
eventUsage
|
|
646
|
+
eventUsage,
|
|
647
|
+
sessionUsage,
|
|
648
|
+
sessionCounts
|
|
518
649
|
];
|
|
519
650
|
const postgresTableDefs = [
|
|
520
651
|
persistenceState,
|
|
@@ -527,7 +658,9 @@ const postgresTableDefs = [
|
|
|
527
658
|
workspaceSessions,
|
|
528
659
|
workspaceState,
|
|
529
660
|
sessionProjcacheRows,
|
|
530
|
-
eventUsage
|
|
661
|
+
eventUsage,
|
|
662
|
+
sessionUsage,
|
|
663
|
+
sessionCounts
|
|
531
664
|
];
|
|
532
665
|
({ ...sessionEvents$1 }), { ...sessionEvents$1.columns };
|
|
533
666
|
//#endregion
|
|
@@ -547,6 +680,8 @@ const tWorkspaceSessions = sqliteTables["t_workspace_sessions"];
|
|
|
547
680
|
const tWorkspaceState = sqliteTables["t_workspace_state"];
|
|
548
681
|
const tSessionProjcacheRows = sqliteTables["t_session_projcache_row"];
|
|
549
682
|
const tEventUsage = sqliteTables["t_event_usage"];
|
|
683
|
+
const tSessionUsage = sqliteTables["t_session_usage"];
|
|
684
|
+
const tSessionCounts = sqliteTables["t_session_counts"];
|
|
550
685
|
const DEFAULT_BUSY_TIMEOUT_MS = 5e3;
|
|
551
686
|
function eventKind(event) {
|
|
552
687
|
switch (event.type) {
|
|
@@ -703,152 +838,6 @@ function eventDimensions(event) {
|
|
|
703
838
|
}
|
|
704
839
|
}
|
|
705
840
|
//#endregion
|
|
706
|
-
//#region src/usage.ts
|
|
707
|
-
const SESSION_USAGE_PATH = "/api/session.usage";
|
|
708
|
-
const DAY_MS = 864e5;
|
|
709
|
-
const ROLLING_DAYS = {
|
|
710
|
-
"7d": 7,
|
|
711
|
-
"30d": 30,
|
|
712
|
-
"90d": 90
|
|
713
|
-
};
|
|
714
|
-
function localDayStart(now) {
|
|
715
|
-
const start = new Date(now);
|
|
716
|
-
start.setHours(0, 0, 0, 0);
|
|
717
|
-
return start;
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* 范围起点(含),不限时为 undefined。自然日 / 自然周按 host 的本地时区算,周边界是周一 00:00。
|
|
721
|
-
* @param range - 语义键。
|
|
722
|
-
* @param now - 当前时刻。
|
|
723
|
-
* @returns 起点毫秒时间戳,或 undefined。
|
|
724
|
-
*/
|
|
725
|
-
function resolveUsageSince(range, now) {
|
|
726
|
-
switch (range) {
|
|
727
|
-
case "all": return;
|
|
728
|
-
case "day": return localDayStart(now).getTime();
|
|
729
|
-
case "week": {
|
|
730
|
-
const start = localDayStart(now);
|
|
731
|
-
start.setDate(start.getDate() - (start.getDay() + 6) % 7);
|
|
732
|
-
return start.getTime();
|
|
733
|
-
}
|
|
734
|
-
default: return now - ROLLING_DAYS[range] * DAY_MS;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
function parseUsageRange(value) {
|
|
738
|
-
return value === "day" || value === "week" || value === "7d" || value === "30d" || value === "90d" ? value : "all";
|
|
739
|
-
}
|
|
740
|
-
/** 计入活动计数的事件类型(轮次 / 步骤 / 用户输入 / 工具调用)。 */
|
|
741
|
-
const COUNTED_EVENT_TYPES = [
|
|
742
|
-
"turn/start",
|
|
743
|
-
"step/start",
|
|
744
|
-
"user/message",
|
|
745
|
-
"tool/call"
|
|
746
|
-
];
|
|
747
|
-
/** 按事件类型把计数累加到活动指标上(表里按类型存,读的时候折成四项)。 */
|
|
748
|
-
function addActivityCount(target, type, count) {
|
|
749
|
-
switch (type) {
|
|
750
|
-
case "turn/start":
|
|
751
|
-
target.turns += count;
|
|
752
|
-
return target;
|
|
753
|
-
case "step/start":
|
|
754
|
-
target.steps += count;
|
|
755
|
-
return target;
|
|
756
|
-
case "user/message":
|
|
757
|
-
target.userInputs += count;
|
|
758
|
-
return target;
|
|
759
|
-
case "tool/call":
|
|
760
|
-
target.toolCalls += count;
|
|
761
|
-
return target;
|
|
762
|
-
default: return target;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
/** 毫秒时间戳 → host 本地日(`YYYY-MM-DD`,与 SQL 的 localtime 口径一致)。 */
|
|
766
|
-
function localDayKey(ms) {
|
|
767
|
-
const date = new Date(ms);
|
|
768
|
-
const pad = (value) => String(value).padStart(2, "0");
|
|
769
|
-
return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
770
|
-
}
|
|
771
|
-
function emptyTotals() {
|
|
772
|
-
return {
|
|
773
|
-
turns: 0,
|
|
774
|
-
steps: 0,
|
|
775
|
-
userInputs: 0,
|
|
776
|
-
toolCalls: 0,
|
|
777
|
-
inputTokens: 0,
|
|
778
|
-
outputTokens: 0,
|
|
779
|
-
cacheReadTokens: 0,
|
|
780
|
-
reasoningTokens: 0,
|
|
781
|
-
totalTokens: 0
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
function addTokenTotals(target, row) {
|
|
785
|
-
target.inputTokens += row.inputTokens;
|
|
786
|
-
target.outputTokens += row.outputTokens;
|
|
787
|
-
target.cacheReadTokens += row.cacheReadTokens;
|
|
788
|
-
target.reasoningTokens += row.reasoningTokens;
|
|
789
|
-
target.totalTokens += row.totalTokens;
|
|
790
|
-
return target;
|
|
791
|
-
}
|
|
792
|
-
function addActivityTotals(target, row) {
|
|
793
|
-
target.turns += row.turns;
|
|
794
|
-
target.steps += row.steps;
|
|
795
|
-
target.userInputs += row.userInputs;
|
|
796
|
-
target.toolCalls += row.toolCalls;
|
|
797
|
-
return target;
|
|
798
|
-
}
|
|
799
|
-
/** 把一行的整套指标累加进目标(后端合并「是否子代理」两组时用)。 */
|
|
800
|
-
function addTotals(target, row) {
|
|
801
|
-
addActivityTotals(target, row);
|
|
802
|
-
addTokenTotals(target, row);
|
|
803
|
-
return target;
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* 用量统计通道:一次请求回报总览(含 subagent 拆分)、按天 × 模型的桶与按会话的行。
|
|
807
|
-
* 聚合只算被会话引用的事件行——fork 共享行因此只计一次,已删会话留下的孤儿行不计。
|
|
808
|
-
*/
|
|
809
|
-
function registerSessionUsage(ctx, persistence) {
|
|
810
|
-
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
811
|
-
const webServer = webCtx.webServer;
|
|
812
|
-
const connection = webCtx.get("connection");
|
|
813
|
-
return webCtx.effect(() => webServer.register({
|
|
814
|
-
kind: "exact",
|
|
815
|
-
path: SESSION_USAGE_PATH,
|
|
816
|
-
handler: async (req, res) => {
|
|
817
|
-
const rejection = connection.requestRejection(req);
|
|
818
|
-
if (rejection !== void 0) {
|
|
819
|
-
res.writeHead(rejection);
|
|
820
|
-
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
if (req.method !== "POST") {
|
|
824
|
-
res.writeHead(405, { "content-type": "application/json" });
|
|
825
|
-
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
const chunks = [];
|
|
829
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
830
|
-
const raw = Buffer.concat(chunks).toString("utf8");
|
|
831
|
-
let envelope = {};
|
|
832
|
-
if (raw !== "") try {
|
|
833
|
-
envelope = JSON.parse(raw);
|
|
834
|
-
} catch {
|
|
835
|
-
res.writeHead(400, { "content-type": "application/json" });
|
|
836
|
-
res.end(JSON.stringify({ error: "request body is not JSON" }));
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
try {
|
|
840
|
-
const report = await persistence.usageReport(resolveUsageSince(parseUsageRange(envelope.range), Date.now()));
|
|
841
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
842
|
-
res.end(JSON.stringify(report));
|
|
843
|
-
} catch (error) {
|
|
844
|
-
res.writeHead(500, { "content-type": "application/json" });
|
|
845
|
-
res.end(JSON.stringify({ error: error instanceof Error ? error.message : "usage report failed" }));
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
}), `session-rdb: ${SESSION_USAGE_PATH} route`);
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
//#endregion
|
|
852
841
|
//#region src/storage-takeover/repository.ts
|
|
853
842
|
async function allRows(query) {
|
|
854
843
|
const q = query;
|
|
@@ -1107,45 +1096,44 @@ function createStorageRepository(host) {
|
|
|
1107
1096
|
}
|
|
1108
1097
|
//#endregion
|
|
1109
1098
|
//#region src/sqlite.ts
|
|
1110
|
-
/**
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
CREATE INDEX IF NOT EXISTS idx_event_counts_day ON t_event_counts(f_day);`);
|
|
1122
|
-
}
|
|
1123
|
-
/** 一次性回填活动计数(表为空时从事件表重算;幂等)。 */
|
|
1124
|
-
function backfillEventCounts(db) {
|
|
1125
|
-
if (db.prepare("SELECT count(*) AS n FROM t_event_counts").get().n > 0) return;
|
|
1126
|
-
db.exec(`INSERT OR IGNORE INTO t_event_counts (f_session_id, f_day, f_type, f_count)
|
|
1127
|
-
SELECT b.f_session_id,
|
|
1128
|
-
date(e.f_created_at / 1000, 'unixepoch', 'localtime'),
|
|
1129
|
-
e.f_type,
|
|
1130
|
-
count(*)
|
|
1131
|
-
FROM t_session_events b
|
|
1132
|
-
JOIN t_events e ON e.f_event_id = b.f_event_id
|
|
1133
|
-
WHERE e.f_type IN (${COUNTED_EVENT_TYPE_SQL})
|
|
1134
|
-
GROUP BY 1, 2, 3`);
|
|
1099
|
+
/** 用量行的「被引用」物化表达式(`%EVENT%` 换成事件 id 列)。 */
|
|
1100
|
+
const EVENT_REFERENCED_SQL = `EXISTS (SELECT 1 FROM t_session_events rb
|
|
1101
|
+
WHERE rb.f_event_id = %EVENT%)`;
|
|
1102
|
+
/** 用量行的「被 subagent 会话引用」物化表达式(`%EVENT%` 换成事件 id 列)。 */
|
|
1103
|
+
const EVENT_SUBAGENT_SQL = `EXISTS (SELECT 1 FROM t_session_events sb
|
|
1104
|
+
JOIN t_sessions ss ON ss.f_session_id = sb.f_session_id
|
|
1105
|
+
WHERE sb.f_event_id = %EVENT% AND ss.f_origin = 'subagent')`;
|
|
1106
|
+
/** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
|
|
1107
|
+
const COUNTED_EVENT_TYPE_SQL = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
|
|
1108
|
+
function withEvent(expression, column) {
|
|
1109
|
+
return expression.replace("%EVENT%", column);
|
|
1135
1110
|
}
|
|
1136
1111
|
/**
|
|
1137
|
-
*
|
|
1112
|
+
* 派生统计表的回填:任一表为空即按事件表全量重算(幂等;迁移删表重建后、手动清空后都靠它自愈)。
|
|
1113
|
+
* 顺序有依赖:用量行(带物化列)先落,两张会话汇总表再从它 / 事件表重算。
|
|
1138
1114
|
*/
|
|
1115
|
+
function backfillUsageTables(db) {
|
|
1116
|
+
backfillEventUsage(db);
|
|
1117
|
+
backfillSessionUsage(db);
|
|
1118
|
+
backfillSessionCounts(db);
|
|
1119
|
+
}
|
|
1120
|
+
function tableIsEmpty(db, table) {
|
|
1121
|
+
return db.prepare(`SELECT count(*) AS n FROM ${table}`).get().n === 0;
|
|
1122
|
+
}
|
|
1123
|
+
/** 用量行:一条 `assistant/message` 事件行一行,物化本地日与两个归属标记。 */
|
|
1139
1124
|
function backfillEventUsage(db) {
|
|
1140
|
-
if (db
|
|
1125
|
+
if (!tableIsEmpty(db, "t_event_usage")) return;
|
|
1141
1126
|
db.exec(`INSERT OR IGNORE INTO t_event_usage (
|
|
1142
|
-
f_event_id, f_created_at, f_provider, f_model,
|
|
1127
|
+
f_event_id, f_created_at, f_day, f_provider, f_model, f_referenced, f_subagent,
|
|
1143
1128
|
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
1144
1129
|
SELECT e.f_event_id, e.f_created_at,
|
|
1130
|
+
date(e.f_created_at / 1000, 'unixepoch', 'localtime'),
|
|
1145
1131
|
coalesce(json_extract(e.f_data, '$.data.message.source.provider'),
|
|
1146
1132
|
json_extract(e.f_data, '$.message.source.provider')),
|
|
1147
1133
|
coalesce(json_extract(e.f_data, '$.data.message.source.model'),
|
|
1148
1134
|
json_extract(e.f_data, '$.message.source.model')),
|
|
1135
|
+
${withEvent(EVENT_REFERENCED_SQL, "e.f_event_id")},
|
|
1136
|
+
${withEvent(EVENT_SUBAGENT_SQL, "e.f_event_id")},
|
|
1149
1137
|
coalesce(json_extract(e.f_data, '$.data.usage.inputTokens'),
|
|
1150
1138
|
json_extract(e.f_data, '$.usage.inputTokens'), 0),
|
|
1151
1139
|
coalesce(json_extract(e.f_data, '$.data.usage.outputTokens'),
|
|
@@ -1161,6 +1149,35 @@ function backfillEventUsage(db) {
|
|
|
1161
1149
|
AND (json_extract(e.f_data, '$.data.usage') IS NOT NULL
|
|
1162
1150
|
OR json_extract(e.f_data, '$.usage') IS NOT NULL)`);
|
|
1163
1151
|
}
|
|
1152
|
+
/** 会话 × 本地日 × 模型的 token 汇总:从桥接行 × 用量行重算(含 fork 继承前缀)。 */
|
|
1153
|
+
function backfillSessionUsage(db) {
|
|
1154
|
+
if (!tableIsEmpty(db, "t_session_usage")) return;
|
|
1155
|
+
db.exec(`INSERT OR IGNORE INTO t_session_usage (
|
|
1156
|
+
f_session_id, f_day, f_provider, f_model,
|
|
1157
|
+
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
1158
|
+
SELECT b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, ''),
|
|
1159
|
+
sum(u.f_input_tokens), sum(u.f_output_tokens), sum(u.f_cache_read_tokens),
|
|
1160
|
+
sum(u.f_reasoning_tokens), sum(u.f_total_tokens)
|
|
1161
|
+
FROM t_session_events b
|
|
1162
|
+
JOIN t_event_usage u ON u.f_event_id = b.f_event_id
|
|
1163
|
+
GROUP BY b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, '')`);
|
|
1164
|
+
}
|
|
1165
|
+
/** 会话 × 本地日的活动计数:从桥接行 × 事件类型重算。 */
|
|
1166
|
+
function backfillSessionCounts(db) {
|
|
1167
|
+
if (!tableIsEmpty(db, "t_session_counts")) return;
|
|
1168
|
+
db.exec(`INSERT OR IGNORE INTO t_session_counts (
|
|
1169
|
+
f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
1170
|
+
SELECT b.f_session_id,
|
|
1171
|
+
date(e.f_created_at / 1000, 'unixepoch', 'localtime'),
|
|
1172
|
+
sum(CASE WHEN e.f_type = 'turn/start' THEN 1 ELSE 0 END),
|
|
1173
|
+
sum(CASE WHEN e.f_type = 'step/start' THEN 1 ELSE 0 END),
|
|
1174
|
+
sum(CASE WHEN e.f_type = 'user/message' THEN 1 ELSE 0 END),
|
|
1175
|
+
sum(CASE WHEN e.f_type = 'tool/call' THEN 1 ELSE 0 END)
|
|
1176
|
+
FROM t_session_events b
|
|
1177
|
+
JOIN t_events e ON e.f_event_id = b.f_event_id
|
|
1178
|
+
WHERE e.f_type IN (${COUNTED_EVENT_TYPE_SQL})
|
|
1179
|
+
GROUP BY b.f_session_id, 2`);
|
|
1180
|
+
}
|
|
1164
1181
|
function sessionListRowOf(row) {
|
|
1165
1182
|
return {
|
|
1166
1183
|
sessionId: typeof row["session_id"] === "string" ? row["session_id"] : String(row["session_id"]),
|
|
@@ -1184,6 +1201,15 @@ function tokenTotalsOf(row) {
|
|
|
1184
1201
|
totalTokens: row.total_tokens ?? 0
|
|
1185
1202
|
};
|
|
1186
1203
|
}
|
|
1204
|
+
/** `t_session_counts` 的原始行(列名是 SQL 别名)折成四项活动计数。 */
|
|
1205
|
+
function activityTotalsOf(row) {
|
|
1206
|
+
return {
|
|
1207
|
+
turns: row.turns ?? 0,
|
|
1208
|
+
steps: row.steps ?? 0,
|
|
1209
|
+
userInputs: row.user_inputs ?? 0,
|
|
1210
|
+
toolCalls: row.tool_calls ?? 0
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1187
1213
|
/** 一行的整套指标:token 用量 + 活动计数(轮次 / 步骤 / 用户输入 / 工具调用)。 */
|
|
1188
1214
|
function emptyActivity() {
|
|
1189
1215
|
return {
|
|
@@ -1193,17 +1219,10 @@ function emptyActivity() {
|
|
|
1193
1219
|
toolCalls: 0
|
|
1194
1220
|
};
|
|
1195
1221
|
}
|
|
1196
|
-
/** 有 token
|
|
1222
|
+
/** 有 token 用量的会话:活动计数与列表都限定在这一批会话里,口径一致(读汇总表,几百行)。 */
|
|
1197
1223
|
function hasUsageSession(column) {
|
|
1198
|
-
return `EXISTS (SELECT 1 FROM
|
|
1199
|
-
JOIN t_event_usage uu ON uu.f_event_id = ub.f_event_id
|
|
1200
|
-
WHERE ub.f_session_id = ${column})`;
|
|
1224
|
+
return `EXISTS (SELECT 1 FROM t_session_usage ub WHERE ub.f_session_id = ${column})`;
|
|
1201
1225
|
}
|
|
1202
|
-
const SUBAGENT_OF_EVENT = `EXISTS (SELECT 1 FROM t_session_events sb
|
|
1203
|
-
JOIN t_sessions ss ON ss.f_session_id = sb.f_session_id
|
|
1204
|
-
WHERE sb.f_event_id = %EVENT% AND ss.f_origin = 'subagent')`;
|
|
1205
|
-
/** `IN (...)` 用的类型字面量(与 `COUNTED_EVENT_TYPES` 同源)。 */
|
|
1206
|
-
const COUNTED_EVENT_TYPE_SQL = COUNTED_EVENT_TYPES.map((type) => `'${type}'`).join(", ");
|
|
1207
1226
|
const sqliteMigrationsDir = fileURLToPath(new URL("../drizzle/sqlite/", import.meta.url));
|
|
1208
1227
|
const sqliteTxQueues = /* @__PURE__ */ new Map();
|
|
1209
1228
|
function enqueueSqliteTx(path, fn) {
|
|
@@ -1226,9 +1245,7 @@ async function openDatabase(path, journalMode, busyTimeout = DEFAULT_BUSY_TIMEOU
|
|
|
1226
1245
|
const { user_version: onDisk } = db.prepare("PRAGMA user_version").get();
|
|
1227
1246
|
if (onDisk === 2 && !hasMigrationsTable(db)) await baselineV2(db);
|
|
1228
1247
|
migrate(dbx, { migrationsFolder: sqliteMigrationsDir });
|
|
1229
|
-
|
|
1230
|
-
backfillEventCounts(db);
|
|
1231
|
-
backfillEventUsage(db);
|
|
1248
|
+
backfillUsageTables(db);
|
|
1232
1249
|
dbx.insert(tPersistenceState).values({
|
|
1233
1250
|
fSingleton: 1,
|
|
1234
1251
|
fStoreId: randomUUID()
|
|
@@ -1399,7 +1416,7 @@ var SqliteBackend = class SqliteBackend {
|
|
|
1399
1416
|
getHead: (id) => this.getHead(id),
|
|
1400
1417
|
getSeedLength: (id) => this.getSeedLength(id),
|
|
1401
1418
|
updateSeedLength: (id, seedLength) => this.updateSeedLength(id, seedLength),
|
|
1402
|
-
insertEvents: (events) => this.insertEvents(events),
|
|
1419
|
+
insertEvents: (events, usageRows) => this.insertEvents(events, usageRows),
|
|
1403
1420
|
insertBridges: (rows) => this.insertBridges(rows),
|
|
1404
1421
|
updateHead: (id, headEventId, headSequence) => this.updateHead(id, headEventId, headSequence),
|
|
1405
1422
|
bumpRevision: (id, lastEventAt) => this.bumpRevision(id, lastEventAt),
|
|
@@ -1432,14 +1449,10 @@ var SqliteBackend = class SqliteBackend {
|
|
|
1432
1449
|
this.db.update(tSessions).set({ fSeedLength: seedLength }).where(eq(tSessions.fSessionId, id)).run();
|
|
1433
1450
|
}
|
|
1434
1451
|
static INSERT_BATCH_ROWS = 1e3;
|
|
1435
|
-
async insertEvents(events) {
|
|
1452
|
+
async insertEvents(events, usageRows) {
|
|
1436
1453
|
if (events.length === 0) return;
|
|
1437
1454
|
for (let i = 0; i < events.length; i += SqliteBackend.INSERT_BATCH_ROWS) this.db.insert(tEvents).values(events.slice(i, i + SqliteBackend.INSERT_BATCH_ROWS).map((event) => ({ ...event }))).run();
|
|
1438
|
-
|
|
1439
|
-
const row = usageRowOf(event);
|
|
1440
|
-
return row === void 0 ? [] : [row];
|
|
1441
|
-
});
|
|
1442
|
-
if (usage.length > 0) this.db.insert(tEventUsage).values(usage).onConflictDoNothing().run();
|
|
1455
|
+
if (usageRows.length > 0) this.db.insert(tEventUsage).values(usageRows.map((row) => ({ ...row }))).onConflictDoNothing().run();
|
|
1443
1456
|
}
|
|
1444
1457
|
async insertBridges(rows) {
|
|
1445
1458
|
if (rows.length === 0) return;
|
|
@@ -1549,41 +1562,49 @@ var SqliteBackend = class SqliteBackend {
|
|
|
1549
1562
|
total: Number(counted.n)
|
|
1550
1563
|
};
|
|
1551
1564
|
}
|
|
1565
|
+
/**
|
|
1566
|
+
* 用量聚合:只读三张派生统计表——事件级用量行给去重口径的总量与「天 × 模型」桶,
|
|
1567
|
+
* `t_session_usage` / `t_session_counts` 给按会话的行(含 fork 继承前缀)。
|
|
1568
|
+
* 归属、本地日与「是否被引用」都是物化列,这里不再现算、也不回连事件表。
|
|
1569
|
+
* 事件级按毫秒时间戳过滤、汇总表按本地日过滤——`sinceMs` 对齐到本地零点时两者等价。
|
|
1570
|
+
*/
|
|
1552
1571
|
async usageReport(sinceMs) {
|
|
1553
|
-
const usageSince = sinceMs === void 0 ? "" : " AND
|
|
1572
|
+
const usageSince = sinceMs === void 0 ? "" : " AND f_created_at >= ?";
|
|
1554
1573
|
const params = sinceMs === void 0 ? [] : [sinceMs];
|
|
1555
|
-
const daySince = sinceMs === void 0 ? "" : " AND
|
|
1574
|
+
const daySince = sinceMs === void 0 ? "" : " AND %COL% >= ?";
|
|
1556
1575
|
const dayParams = sinceMs === void 0 ? [] : [localDayKey(sinceMs)];
|
|
1557
|
-
const
|
|
1558
|
-
const buckets = this.db.$client.prepare(`SELECT
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
sum(
|
|
1563
|
-
sum(
|
|
1564
|
-
sum(
|
|
1565
|
-
sum(
|
|
1566
|
-
sum(
|
|
1567
|
-
FROM t_event_usage
|
|
1568
|
-
WHERE
|
|
1569
|
-
GROUP BY
|
|
1570
|
-
const tokenScope = this.db.$client.prepare(`SELECT
|
|
1571
|
-
sum(
|
|
1572
|
-
sum(
|
|
1573
|
-
sum(
|
|
1574
|
-
sum(
|
|
1575
|
-
sum(
|
|
1576
|
-
FROM t_event_usage
|
|
1577
|
-
WHERE
|
|
1578
|
-
GROUP BY
|
|
1576
|
+
const dayFilter = (column) => daySince.replace("%COL%", column);
|
|
1577
|
+
const buckets = this.db.$client.prepare(`SELECT f_day AS day,
|
|
1578
|
+
f_provider AS provider,
|
|
1579
|
+
f_model AS model,
|
|
1580
|
+
f_subagent AS subagent,
|
|
1581
|
+
sum(f_input_tokens) AS input_tokens,
|
|
1582
|
+
sum(f_output_tokens) AS output_tokens,
|
|
1583
|
+
sum(f_cache_read_tokens) AS cache_read_tokens,
|
|
1584
|
+
sum(f_reasoning_tokens) AS reasoning_tokens,
|
|
1585
|
+
sum(f_total_tokens) AS total_tokens
|
|
1586
|
+
FROM t_event_usage
|
|
1587
|
+
WHERE f_referenced = 1${usageSince}
|
|
1588
|
+
GROUP BY f_day, f_provider, f_model, f_subagent`).all(...params);
|
|
1589
|
+
const tokenScope = this.db.$client.prepare(`SELECT f_subagent AS subagent,
|
|
1590
|
+
sum(f_input_tokens) AS input_tokens,
|
|
1591
|
+
sum(f_output_tokens) AS output_tokens,
|
|
1592
|
+
sum(f_cache_read_tokens) AS cache_read_tokens,
|
|
1593
|
+
sum(f_reasoning_tokens) AS reasoning_tokens,
|
|
1594
|
+
sum(f_total_tokens) AS total_tokens
|
|
1595
|
+
FROM t_event_usage
|
|
1596
|
+
WHERE f_referenced = 1${usageSince}
|
|
1597
|
+
GROUP BY f_subagent`).all(...params);
|
|
1579
1598
|
const activityScope = this.db.$client.prepare(`SELECT (s.f_origin = 'subagent') AS subagent,
|
|
1580
|
-
c.
|
|
1581
|
-
sum(c.
|
|
1582
|
-
|
|
1599
|
+
sum(c.f_turns) AS turns,
|
|
1600
|
+
sum(c.f_steps) AS steps,
|
|
1601
|
+
sum(c.f_user_inputs) AS user_inputs,
|
|
1602
|
+
sum(c.f_tool_calls) AS tool_calls
|
|
1603
|
+
FROM t_session_counts c
|
|
1583
1604
|
JOIN t_sessions s ON s.f_session_id = c.f_session_id
|
|
1584
|
-
WHERE ${hasUsageSession("c.f_session_id")}${
|
|
1585
|
-
GROUP BY subagent
|
|
1586
|
-
const tokenSessions = this.db.$client.prepare(`SELECT
|
|
1605
|
+
WHERE ${hasUsageSession("c.f_session_id")}${dayFilter("c.f_day")}
|
|
1606
|
+
GROUP BY subagent`).all(...dayParams);
|
|
1607
|
+
const tokenSessions = this.db.$client.prepare(`SELECT u.f_session_id AS session_id,
|
|
1587
1608
|
s.f_title AS title,
|
|
1588
1609
|
(s.f_origin = 'subagent') AS subagent,
|
|
1589
1610
|
(s.f_archived_at IS NOT NULL) AS archived,
|
|
@@ -1592,28 +1613,25 @@ var SqliteBackend = class SqliteBackend {
|
|
|
1592
1613
|
sum(u.f_cache_read_tokens) AS cache_read_tokens,
|
|
1593
1614
|
sum(u.f_reasoning_tokens) AS reasoning_tokens,
|
|
1594
1615
|
sum(u.f_total_tokens) AS total_tokens
|
|
1595
|
-
FROM
|
|
1596
|
-
JOIN
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
sum(
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1616
|
+
FROM t_session_usage u
|
|
1617
|
+
JOIN t_sessions s ON s.f_session_id = u.f_session_id
|
|
1618
|
+
WHERE 1 = 1${dayFilter("u.f_day")}
|
|
1619
|
+
GROUP BY u.f_session_id, s.f_title, s.f_origin, s.f_archived_at`).all(...dayParams);
|
|
1620
|
+
const activitySessions = this.db.$client.prepare(`SELECT f_session_id AS session_id,
|
|
1621
|
+
sum(f_turns) AS turns,
|
|
1622
|
+
sum(f_steps) AS steps,
|
|
1623
|
+
sum(f_user_inputs) AS user_inputs,
|
|
1624
|
+
sum(f_tool_calls) AS tool_calls
|
|
1625
|
+
FROM t_session_counts
|
|
1626
|
+
WHERE 1 = 1${dayFilter("f_day")}
|
|
1627
|
+
GROUP BY f_session_id`).all(...dayParams);
|
|
1606
1628
|
const human = emptyTotals();
|
|
1607
1629
|
const subagent = emptyTotals();
|
|
1608
1630
|
for (const row of tokenScope) addTokenTotals(row.subagent === 1 ? subagent : human, tokenTotalsOf(row));
|
|
1609
|
-
for (const row of activityScope)
|
|
1631
|
+
for (const row of activityScope) addActivityTotals(row.subagent === 1 ? subagent : human, activityTotalsOf(row));
|
|
1610
1632
|
const totals = addTotals(addTotals(emptyTotals(), human), subagent);
|
|
1611
1633
|
const activityBySession = /* @__PURE__ */ new Map();
|
|
1612
|
-
for (const row of activitySessions)
|
|
1613
|
-
const target = activityBySession.get(row.session_id) ?? emptyActivity();
|
|
1614
|
-
addActivityCount(target, row.f_type, row.n ?? 0);
|
|
1615
|
-
activityBySession.set(row.session_id, target);
|
|
1616
|
-
}
|
|
1634
|
+
for (const row of activitySessions) activityBySession.set(row.session_id, activityTotalsOf(row));
|
|
1617
1635
|
return {
|
|
1618
1636
|
totals,
|
|
1619
1637
|
subagent,
|
|
@@ -1635,28 +1653,72 @@ var SqliteBackend = class SqliteBackend {
|
|
|
1635
1653
|
}))
|
|
1636
1654
|
};
|
|
1637
1655
|
}
|
|
1638
|
-
/**
|
|
1639
|
-
async
|
|
1656
|
+
/** token 用量旁路累加:派生表 `t_session_usage`,不在写事务里(失败可丢,表可销毁重建)。 */
|
|
1657
|
+
async incrementSessionUsage(id, buckets) {
|
|
1658
|
+
if (buckets.length === 0) return;
|
|
1659
|
+
const statement = this.db.$client.prepare(`INSERT INTO t_session_usage (f_session_id, f_day, f_provider, f_model,
|
|
1660
|
+
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
1661
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1662
|
+
ON CONFLICT(f_session_id, f_day, f_provider, f_model) DO UPDATE SET
|
|
1663
|
+
f_input_tokens = f_input_tokens + excluded.f_input_tokens,
|
|
1664
|
+
f_output_tokens = f_output_tokens + excluded.f_output_tokens,
|
|
1665
|
+
f_cache_read_tokens = f_cache_read_tokens + excluded.f_cache_read_tokens,
|
|
1666
|
+
f_reasoning_tokens = f_reasoning_tokens + excluded.f_reasoning_tokens,
|
|
1667
|
+
f_total_tokens = f_total_tokens + excluded.f_total_tokens`);
|
|
1668
|
+
for (const bucket of buckets) statement.run(id, bucket.day, bucket.provider ?? "", bucket.model ?? "", bucket.inputTokens, bucket.outputTokens, bucket.cacheReadTokens, bucket.reasoningTokens, bucket.totalTokens);
|
|
1669
|
+
}
|
|
1670
|
+
/** 活动计数旁路累加:派生表 `t_session_counts`,同上(四项计数一次落)。 */
|
|
1671
|
+
async incrementSessionCounts(id, buckets) {
|
|
1640
1672
|
if (buckets.length === 0) return;
|
|
1641
|
-
const statement = this.db.$client.prepare(`INSERT INTO
|
|
1642
|
-
|
|
1643
|
-
|
|
1673
|
+
const statement = this.db.$client.prepare(`INSERT INTO t_session_counts (f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
1674
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1675
|
+
ON CONFLICT(f_session_id, f_day) DO UPDATE SET
|
|
1676
|
+
f_turns = f_turns + excluded.f_turns,
|
|
1677
|
+
f_steps = f_steps + excluded.f_steps,
|
|
1678
|
+
f_user_inputs = f_user_inputs + excluded.f_user_inputs,
|
|
1679
|
+
f_tool_calls = f_tool_calls + excluded.f_tool_calls`);
|
|
1680
|
+
for (const bucket of buckets) statement.run(id, bucket.day, bucket.turns, bucket.steps, bucket.userInputs, bucket.toolCalls);
|
|
1644
1681
|
}
|
|
1645
|
-
/**
|
|
1646
|
-
async
|
|
1647
|
-
this.db.$client.prepare("DELETE FROM
|
|
1648
|
-
this.db.$client.prepare(`INSERT INTO
|
|
1682
|
+
/** 按会话重算两张汇总表(rewind / fork 之后):先删该会话的行,再从事件表重算。 */
|
|
1683
|
+
async rebuildSessionStats(id) {
|
|
1684
|
+
this.db.$client.prepare("DELETE FROM t_session_usage WHERE f_session_id = ?").run(id);
|
|
1685
|
+
this.db.$client.prepare(`INSERT INTO t_session_usage (
|
|
1686
|
+
f_session_id, f_day, f_provider, f_model,
|
|
1687
|
+
f_input_tokens, f_output_tokens, f_cache_read_tokens, f_reasoning_tokens, f_total_tokens)
|
|
1688
|
+
SELECT b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, ''),
|
|
1689
|
+
sum(u.f_input_tokens), sum(u.f_output_tokens), sum(u.f_cache_read_tokens),
|
|
1690
|
+
sum(u.f_reasoning_tokens), sum(u.f_total_tokens)
|
|
1691
|
+
FROM t_session_events b
|
|
1692
|
+
JOIN t_event_usage u ON u.f_event_id = b.f_event_id
|
|
1693
|
+
WHERE b.f_session_id = ?
|
|
1694
|
+
GROUP BY b.f_session_id, u.f_day, coalesce(u.f_provider, ''), coalesce(u.f_model, '')`).run(id);
|
|
1695
|
+
this.db.$client.prepare("DELETE FROM t_session_counts WHERE f_session_id = ?").run(id);
|
|
1696
|
+
this.db.$client.prepare(`INSERT INTO t_session_counts (
|
|
1697
|
+
f_session_id, f_day, f_turns, f_steps, f_user_inputs, f_tool_calls)
|
|
1649
1698
|
SELECT b.f_session_id,
|
|
1650
1699
|
date(e.f_created_at / 1000, 'unixepoch', 'localtime'),
|
|
1651
|
-
e.f_type,
|
|
1652
|
-
|
|
1700
|
+
sum(CASE WHEN e.f_type = 'turn/start' THEN 1 ELSE 0 END),
|
|
1701
|
+
sum(CASE WHEN e.f_type = 'step/start' THEN 1 ELSE 0 END),
|
|
1702
|
+
sum(CASE WHEN e.f_type = 'user/message' THEN 1 ELSE 0 END),
|
|
1703
|
+
sum(CASE WHEN e.f_type = 'tool/call' THEN 1 ELSE 0 END)
|
|
1653
1704
|
FROM t_session_events b
|
|
1654
1705
|
JOIN t_events e ON e.f_event_id = b.f_event_id
|
|
1655
1706
|
WHERE b.f_session_id = ? AND e.f_type IN (${COUNTED_EVENT_TYPE_SQL})
|
|
1656
|
-
GROUP BY
|
|
1707
|
+
GROUP BY b.f_session_id, 2`).run(id);
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* 全量重算用量行的引用标记:rewind 截断 / fork 复用 / 会话删除都会让「引用」跨会话变化,
|
|
1711
|
+
* 只按本会话判定不够。低频路径,一次全表 UPDATE(3 万行量级)。
|
|
1712
|
+
*/
|
|
1713
|
+
async refreshEventUsageFlags() {
|
|
1714
|
+
const eventId = "t_event_usage.f_event_id";
|
|
1715
|
+
this.db.$client.exec(`UPDATE t_event_usage SET
|
|
1716
|
+
f_referenced = ${withEvent(EVENT_REFERENCED_SQL, eventId)},
|
|
1717
|
+
f_subagent = ${withEvent(EVENT_SUBAGENT_SQL, eventId)}`);
|
|
1657
1718
|
}
|
|
1658
|
-
async
|
|
1659
|
-
this.db.$client.prepare("DELETE FROM
|
|
1719
|
+
async deleteSessionStats(id) {
|
|
1720
|
+
this.db.$client.prepare("DELETE FROM t_session_usage WHERE f_session_id = ?").run(id);
|
|
1721
|
+
this.db.$client.prepare("DELETE FROM t_session_counts WHERE f_session_id = ?").run(id);
|
|
1660
1722
|
}
|
|
1661
1723
|
async vacuum() {
|
|
1662
1724
|
await enqueueSqliteTx(this.dbPath, async () => {
|
|
@@ -1679,4 +1741,4 @@ var SqliteBackend = class SqliteBackend {
|
|
|
1679
1741
|
}
|
|
1680
1742
|
};
|
|
1681
1743
|
//#endregion
|
|
1682
|
-
export {
|
|
1744
|
+
export { postgresTableDefs as C, tWorkspaces as S, WriteGuard as T, tSessionUsage as _, EVENT_ENCODING as a, tWorkspaceSessions as b, eventDimensions as c, tEvents as d, tPersistenceState as f, tSessionProjcacheRows as g, tSessionEvents as h, DEFAULT_BUSY_TIMEOUT_MS as i, eventKind as l, tSessionCounts as m, openDatabase as n, SCHEMA_VERSION as o, tSchemaMeta as p, createStorageRepository as r, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID as s, SqliteBackend as t, tEventUsage as u, tSessions as v, toPostgresSchema as w, tWorkspaceState as x, tStorageUnits as y };
|