@morlay/session-rdb 0.1.9 → 0.1.10-alpha.2
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 +24 -3
- package/dist/artifact.d.mts +118 -3
- package/dist/artifact.mjs +5 -3
- package/dist/{log-C0q5svm7.d.mts → backend-BKtJ5sO5.d.mts} +189 -291
- package/dist/backend-rdb-BDCntVDy.mjs +1341 -0
- package/dist/branch-CG1PUlO6.mjs +833 -0
- package/dist/deletion-SSX7IqU8.mjs +35 -0
- package/dist/deletion.d.mts +1 -3
- package/dist/deletion.mjs +2 -2
- package/dist/export-CfJQjQfr.mjs +123 -0
- package/dist/export.d.mts +1 -3
- package/dist/export.mjs +32 -57
- package/dist/import.d.mts +1 -3
- package/dist/import.mjs +32 -78
- package/dist/index-CUD2ZpTA.d.mts +71 -0
- package/dist/index.d.mts +20 -2
- package/dist/index.mjs +1562 -5
- package/dist/open-YoyD_kah.mjs +561 -0
- package/dist/organize.d.mts +9 -0
- package/dist/organize.mjs +30 -0
- package/dist/route-DpfY0zq-.mjs +114 -0
- package/dist/storage.d.mts +41 -50
- package/dist/storage.mjs +3 -1
- package/dist/testing.d.mts +9 -5
- package/dist/testing.mjs +27 -8
- package/dist/usage-row-BQQM-_t6.mjs +36 -0
- package/dist/usage.d.mts +1 -1
- package/dist/usage.mjs +8 -42
- package/dist/write-guard-CYrY58GM.mjs +174 -0
- package/package.json +32 -36
- package/src/artifact.ts +4 -1
- package/src/backend-rdb.ts +331 -0
- package/src/backend.ts +32 -15
- package/src/{branch.ts → branch/index.ts} +125 -112
- package/src/branch/port.ts +62 -0
- package/src/branch/semantics.ts +69 -0
- package/src/db/open.ts +180 -0
- package/src/db/stats.ts +449 -0
- package/src/deletion-error.ts +17 -0
- package/src/deletion.ts +19 -72
- package/src/entities/index.ts +16 -1
- package/src/entities/v3/event-usage.ts +3 -3
- package/src/entities/v3/events.ts +3 -3
- package/src/entities/v3/index.ts +4 -16
- package/src/entities/v3/persistence-state.ts +3 -3
- package/src/entities/v3/schema-meta.ts +3 -3
- package/src/entities/v3/session-counts.ts +3 -3
- package/src/entities/v3/session-events.ts +3 -3
- package/src/entities/v3/session-projcache-rows.ts +3 -3
- package/src/entities/v3/session-usage.ts +3 -3
- package/src/entities/v3/sessions.ts +3 -3
- package/src/entities/v3/storage-records.ts +17 -0
- package/src/entities/v3/storage-units.ts +3 -3
- package/src/{schema.ts → event-shape.ts} +0 -46
- package/src/export.ts +32 -78
- package/src/handles.ts +349 -0
- package/src/http/route.ts +178 -0
- package/src/import-storages.ts +5 -2
- package/src/import.ts +63 -125
- package/src/index.ts +15 -1630
- package/src/legacy.ts +3 -1
- package/src/live-routing.ts +205 -0
- package/src/log/export.ts +84 -0
- package/src/{log.ts → log/repair.ts} +1 -272
- package/src/log/rows.ts +107 -0
- package/src/log/usage-row.ts +64 -0
- package/src/migrations.ts +196 -0
- package/src/organize.ts +38 -0
- package/src/read-path.ts +190 -0
- package/src/routes.ts +2 -1
- package/src/rows.ts +25 -77
- package/src/session-ops.ts +444 -0
- package/src/session-query.ts +17 -4
- package/src/session-store.ts +675 -0
- package/src/stats-fold.ts +44 -0
- package/src/storage-takeover/index.ts +2 -5
- package/src/storage-takeover/legacy-domain-migration.ts +118 -0
- package/src/storage-takeover/projection-cache.ts +139 -77
- package/src/storage-takeover/read-model.ts +189 -0
- package/src/storage-takeover/repository.ts +238 -425
- package/src/storage-takeover/storage-backend.ts +47 -149
- package/src/storage-takeover/types.ts +45 -14
- package/src/storage-takeover/workspace-documents.ts +65 -0
- package/src/storage.ts +4 -2
- package/src/testing.ts +21 -1
- package/src/usage.ts +11 -65
- package/src/version.ts +13 -0
- package/src/write-path.ts +286 -0
- package/dist/branch-DbFW3rJX.mjs +0 -391
- package/dist/deletion-ODZFcbZm.mjs +0 -2218
- package/dist/gc.d.mts +0 -6
- package/dist/gc.mjs +0 -49
- package/dist/log-DYxdjJ-l.mjs +0 -562
- package/dist/routes-DGBlBE_I.mjs +0 -10
- package/dist/sqlite-DjOnMRh2.mjs +0 -1777
- package/drizzle/postgres/20260908072805_v2_initial/migration.sql +0 -58
- package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +0 -686
- package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +0 -1
- package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +0 -673
- package/drizzle/postgres/20260910120001_v3_storage_tables/migration.sql +0 -65
- package/drizzle/postgres/20260910120001_v3_storage_tables/snapshot.json +0 -1183
- package/drizzle/postgres/20260910130001_v3_session_title_backfill/migration.sql +0 -10
- package/drizzle/postgres/20260910130001_v3_session_title_backfill/snapshot.json +0 -1183
- package/drizzle/postgres/20260918120000_v3_event_usage/migration.sql +0 -14
- package/drizzle/postgres/20260918120000_v3_event_usage/snapshot.json +0 -1309
- package/drizzle/postgres/20260921075900_v3_agent_preset/migration.sql +0 -1
- package/drizzle/postgres/20260922090001_v3_pinned_seq/migration.sql +0 -2
- package/drizzle/postgres/20260922093001_v3_last_event_at/migration.sql +0 -10
- package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +0 -48
- package/drizzle/postgres/20261008103001_v3_schedule_tasks/migration.sql +0 -11
- package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +0 -53
- package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +0 -492
- package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +0 -6
- package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +0 -513
- package/drizzle/sqlite/20260910120000_v3_storage_tables/migration.sql +0 -66
- package/drizzle/sqlite/20260910120000_v3_storage_tables/snapshot.json +0 -905
- package/drizzle/sqlite/20260910130000_v3_session_title_backfill/migration.sql +0 -13
- package/drizzle/sqlite/20260910130000_v3_session_title_backfill/snapshot.json +0 -905
- package/drizzle/sqlite/20260918120000_v3_event_usage/migration.sql +0 -14
- package/drizzle/sqlite/20260918120000_v3_event_usage/snapshot.json +0 -1031
- package/drizzle/sqlite/20260921075855_v3_agent_preset/migration.sql +0 -7
- package/drizzle/sqlite/20260921075855_v3_agent_preset/snapshot.json +0 -1041
- package/drizzle/sqlite/20260922090000_v3_pinned_seq/migration.sql +0 -4
- package/drizzle/sqlite/20260922093000_v3_last_event_at/migration.sql +0 -13
- package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +0 -48
- package/drizzle/sqlite/20261008103000_v3_schedule_tasks/migration.sql +0 -14
- package/src/adapters/index.ts +0 -12
- package/src/adapters/to-postgres.ts +0 -93
- package/src/adapters/to-sqlite.ts +0 -83
- package/src/adapters/types.ts +0 -44
- package/src/drizzle/postgres-v2.ts +0 -10
- package/src/drizzle/postgres-v3.ts +0 -18
- package/src/drizzle/sqlite-v2.ts +0 -9
- package/src/drizzle/sqlite-v3.ts +0 -18
- package/src/entities/v2/index.ts +0 -20
- package/src/entities/v2/session-events.ts +0 -10
- package/src/entities/v3/schedule-tasks.ts +0 -16
- package/src/entities/v3/workspace-sessions.ts +0 -19
- package/src/entities/v3/workspace-state.ts +0 -12
- package/src/entities/v3/workspaces.ts +0 -14
- package/src/gc.ts +0 -77
- package/src/postgres.ts +0 -1015
- package/src/sqlite.ts +0 -1086
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/routes.ts
|
|
2
|
+
const MORLAY_SESSION_API_PREFIX = "/api/morlay/v1/session";
|
|
3
|
+
const SESSION_ROWS_PATH = `${MORLAY_SESSION_API_PREFIX}/rows`;
|
|
4
|
+
const SESSION_EXPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/export`;
|
|
5
|
+
const SESSION_IMPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/import`;
|
|
6
|
+
const SESSION_DELETE_PATH = `${MORLAY_SESSION_API_PREFIX}/delete`;
|
|
7
|
+
const SESSION_ORGANIZE_PATH = `${MORLAY_SESSION_API_PREFIX}/organize`;
|
|
8
|
+
const SESSION_USAGE_PATH = `${MORLAY_SESSION_API_PREFIX}/usage`;
|
|
9
|
+
// src/http/route.ts
|
|
10
|
+
/** 一条路由自己决定的状态码(鉴权 / 方法 / 解析之外的那些校验)。 */
|
|
11
|
+
var HttpError = class extends Error {
|
|
12
|
+
status;
|
|
13
|
+
payload;
|
|
14
|
+
constructor(status, message, payload = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.payload = payload;
|
|
18
|
+
this.name = "HttpError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function writeJson(res, status, body) {
|
|
22
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
23
|
+
res.end(JSON.stringify(body));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 挂一条路由:鉴权与方法检查这些每处都一样的部分收在这里。
|
|
27
|
+
*
|
|
28
|
+
* 二进制响应(导出 zip)自己写响应体,所以只共用鉴权 / 405 / 错误兜底。
|
|
29
|
+
*/
|
|
30
|
+
function mountRoute(ctx, path, handler) {
|
|
31
|
+
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
32
|
+
const webServer = webCtx.webServer;
|
|
33
|
+
const connection = webCtx.get("connection");
|
|
34
|
+
return webCtx.effect(() => webServer.register({
|
|
35
|
+
kind: "exact",
|
|
36
|
+
path,
|
|
37
|
+
handler: async (req, res) => {
|
|
38
|
+
const rejection = connection.requestRejection(req);
|
|
39
|
+
if (rejection !== void 0) {
|
|
40
|
+
res.writeHead(rejection);
|
|
41
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (req.method !== "POST") {
|
|
45
|
+
writeJson(res, 405, { error: "method not allowed" });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await handler(req, res);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (res.headersSent) return;
|
|
52
|
+
writeJson(res, 500, { error: error instanceof Error ? error.message : "request failed" });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}), `session-rdb: ${path}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 挂一条 JSON 路由:鉴权、方法、body 解析、错误码这些每处都一样的部分收在这里。
|
|
60
|
+
*
|
|
61
|
+
* 5 个 JSON 路由(rows / usage / gc / deletion / import)共用 `mountJsonRoute`;`export` 的响应体是 zip,走
|
|
62
|
+
* `mountRoute`,只复用鉴权 / 405 / 500。之前每个文件各抄一遍(约 270 行样板,而且 `import` 那条漏了 405)。
|
|
63
|
+
*/
|
|
64
|
+
function mountJsonRoute(ctx, spec) {
|
|
65
|
+
ctx.inject(["webServer", "connection"], (webCtx) => {
|
|
66
|
+
const webServer = webCtx.webServer;
|
|
67
|
+
const connection = webCtx.get("connection");
|
|
68
|
+
return webCtx.effect(() => webServer.register({
|
|
69
|
+
kind: "exact",
|
|
70
|
+
path: spec.path,
|
|
71
|
+
handler: async (req, res) => {
|
|
72
|
+
const rejection = connection.requestRejection(req);
|
|
73
|
+
if (rejection !== void 0) {
|
|
74
|
+
res.writeHead(rejection);
|
|
75
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (req.method !== "POST") {
|
|
79
|
+
writeJson(res, 405, { error: "method not allowed" });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const chunks = [];
|
|
83
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
84
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
85
|
+
let body = {};
|
|
86
|
+
if (raw !== "") try {
|
|
87
|
+
body = JSON.parse(raw);
|
|
88
|
+
} catch {
|
|
89
|
+
writeJson(res, 400, { error: "request body is not JSON" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const reply = await spec.handle(body, webCtx);
|
|
94
|
+
writeJson(res, reply.status ?? 200, reply.body);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof HttpError) {
|
|
97
|
+
writeJson(res, error.status, {
|
|
98
|
+
error: error.message,
|
|
99
|
+
...error.payload
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const mapped = spec.statusOfError?.(error);
|
|
104
|
+
if (mapped !== void 0) {
|
|
105
|
+
writeJson(res, mapped.status, mapped.body);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
writeJson(res, 500, { error: error instanceof Error ? error.message : "request failed" });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}), `session-rdb: ${spec.path}`);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export { SESSION_EXPORT_PATH as a, SESSION_ROWS_PATH as c, SESSION_DELETE_PATH as i, SESSION_USAGE_PATH as l, mountJsonRoute as n, SESSION_IMPORT_PATH as o, mountRoute as r, SESSION_ORGANIZE_PATH as s, HttpError as t };
|
package/dist/storage.d.mts
CHANGED
|
@@ -1,59 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { F as JournalMode, G as StorageRepository, I as SCHEMA_VERSION, L as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, N as DEFAULT_BUSY_TIMEOUT_MS, P as EVENT_ENCODING, R as WriteGuard, a as SessionCountBucket, c as SessionRow, i as ReadModel, l as SessionUsageBucket, n as BackendTx, o as SessionListRowsPage, p as UsageAggregate, r as EventRow, s as SessionListRowsQuery, t as Backend } from "./backend-BKtJ5sO5.mjs";
|
|
2
|
+
import { Database } from "@morlay/rdbkit";
|
|
3
|
+
import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
4
|
+
export type EventKind = "message" | "thinking" | "turn" | "tool" | "request" | "config" | "audit" | "lifecycle" | "inbox" | "compaction" | "llm" | "subagent" | "team" | "workflow" | "goal" | "schedule" | "todo" | "web";
|
|
5
|
+
export type EventRole = "user" | "assistant" | "tool";
|
|
6
|
+
export declare function eventKind(event: {
|
|
7
|
+
type: string;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
}): EventKind | "";
|
|
10
|
+
export declare function eventDimensions(event: SessionEvent): {
|
|
11
|
+
kind: string;
|
|
12
|
+
role: string;
|
|
13
|
+
name: string;
|
|
14
|
+
actionId: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 唯一的 backend 实现:读写方法收敛在这里(读模型 + `session-ops` + 表句柄);打开流程在 `db/open.ts`,
|
|
18
|
+
* 统计面在 `db/stats.ts`。方言差异全部由 rdbkit 的表达式承担,这里不出现任何 SQL 文本。
|
|
19
|
+
*/
|
|
20
|
+
export declare class RdbBackend implements Backend {
|
|
21
|
+
#private;
|
|
15
22
|
readonly storage: StorageRepository;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
readonly storeIdentity: string;
|
|
24
|
+
readonly reads: ReadModel;
|
|
25
|
+
constructor(options: {
|
|
26
|
+
kind: "sqlite" | "postgres";
|
|
27
|
+
database: Database;
|
|
28
|
+
storage: StorageRepository;
|
|
29
|
+
readModel: ReadModel;
|
|
30
|
+
storeIdentity: string;
|
|
31
|
+
});
|
|
32
|
+
readonly kind: "sqlite" | "postgres";
|
|
22
33
|
open(): Promise<void>;
|
|
23
|
-
private doOpen;
|
|
24
34
|
close(): Promise<void>;
|
|
25
|
-
getSession(id: SessionId): Promise<SessionRow | undefined>;
|
|
26
|
-
getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]>;
|
|
27
|
-
getEventTypeAt(id: SessionId, sequence: number): Promise<string | undefined>;
|
|
28
|
-
getEventTypesBefore(id: SessionId, beforeSequence: number, limit: number): Promise<Array<Pick<EventRow, "fSequence" | "fType">>>;
|
|
29
|
-
listSessions(): Promise<SessionRow[]>;
|
|
30
35
|
transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T>;
|
|
31
|
-
private readonly tx;
|
|
32
|
-
private upsertSession;
|
|
33
|
-
private getHead;
|
|
34
|
-
private getSeedLength;
|
|
35
|
-
private updateSeedLength;
|
|
36
|
-
private static readonly INSERT_BATCH_ROWS;
|
|
37
|
-
private insertEvents;
|
|
38
|
-
private insertBridges;
|
|
39
|
-
private updateHead;
|
|
40
|
-
private bumpRevision;
|
|
41
|
-
private refreshTitle;
|
|
42
|
-
private deleteBridgeTail;
|
|
43
|
-
private getPrevBridge;
|
|
44
|
-
private deleteSession;
|
|
45
|
-
collectOrphans(): Promise<number>;
|
|
46
36
|
listOrphanSubagentSessions(): Promise<SessionId[]>;
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
/** 当前 head 指针(分支 / 测试读路径用;写路径在 `BackendTx` 里)。 */
|
|
38
|
+
getHead(id: SessionId): Promise<Pick<SessionRow, "fHeadEventId" | "fHeadSequence">>;
|
|
39
|
+
collectOrphans(): Promise<number>;
|
|
49
40
|
usageReport(sinceMs?: number): Promise<UsageAggregate>;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
listSessionRows(query?: SessionListRowsQuery): Promise<SessionListRowsPage>;
|
|
42
|
+
incrementSessionUsage(id: string, buckets: readonly SessionUsageBucket[]): Promise<void>;
|
|
43
|
+
incrementSessionCounts(id: string, buckets: readonly SessionCountBucket[]): Promise<void>;
|
|
44
|
+
deleteSessionStats(id: string): Promise<void>;
|
|
45
|
+
rebuildSessionStats(id: string): Promise<void>;
|
|
46
|
+
rebuildAllStats(): Promise<number>;
|
|
53
47
|
refreshEventUsageFlags(eventIds: readonly string[]): Promise<void>;
|
|
54
|
-
deleteSessionStats(id: SessionId): Promise<void>;
|
|
55
48
|
vacuum(): Promise<void>;
|
|
56
|
-
private eventRows;
|
|
57
49
|
}
|
|
58
|
-
|
|
59
|
-
export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, EventKind, EventRole, type EventRow, JournalMode, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, type SessionRow, WriteGuard, eventDimensions, eventKind, tEventUsage, tEvents, tPersistenceState, tScheduleTasks, tSchemaMeta, tSessionCounts, tSessionEvents, tSessionProjcacheRows, tSessionUsage, tSessions, tStorageUnits, tWorkspaceSessions, tWorkspaceState, tWorkspaces };
|
|
50
|
+
export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, type EventRow, JournalMode, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, type SessionRow, WriteGuard };
|
package/dist/storage.mjs
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import { d as EVENT_ENCODING, f as SCHEMA_VERSION, p as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, t as RdbBackend, u as DEFAULT_BUSY_TIMEOUT_MS } from "./backend-rdb-BDCntVDy.mjs";
|
|
2
|
+
import { n as eventDimensions, r as eventKind, t as WriteGuard } from "./write-guard-CYrY58GM.mjs";
|
|
3
|
+
export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, RdbBackend, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, WriteGuard, eventDimensions, eventKind };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as JournalMode } from "./backend-BKtJ5sO5.mjs";
|
|
2
|
+
import { i as truncateLiveSession } from "./index-CUD2ZpTA.mjs";
|
|
2
3
|
import { Session, SessionEvent, SessionHeader, SessionId } from "@deepseek-ai/dsh-session";
|
|
3
4
|
import { SessionPersistence } from "@deepseek-ai/dsh-session-persistence";
|
|
4
5
|
import { Context, Fiber } from "@deepseek-ai/cordis";
|
|
5
|
-
|
|
6
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
7
|
interface ContractBackendInstance {
|
|
7
8
|
persistence: SessionPersistence;
|
|
8
9
|
dispose: () => Promise<void>;
|
|
@@ -15,13 +16,16 @@ export declare function meta(id: string, cwd?: string): SessionHeader;
|
|
|
15
16
|
export declare function oneTurnLog(): SessionEvent[];
|
|
16
17
|
export declare function appendLog(session: Session, events: readonly SessionEvent[]): void;
|
|
17
18
|
export declare function runPersistenceContract(name: string, make: () => Promise<ContractBackend>): void;
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/testing/coordinator-contract.d.ts
|
|
20
19
|
interface CoordinatorFixture {
|
|
21
20
|
mount: (ctx: Context) => Promise<Fiber>;
|
|
22
21
|
corruptTail?: (id: SessionId, cwd: string | undefined) => Promise<void>;
|
|
23
22
|
cleanup: () => Promise<void>;
|
|
24
23
|
}
|
|
25
24
|
export declare function runCoordinatorContract(name: string, makeFixture: () => Promise<CoordinatorFixture>): void;
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* 测试辅助:先把库初始化(建表 / 迁移 / 身份),再给一个同步句柄——造损坏数据、读 `PRAGMA` 用。
|
|
27
|
+
*
|
|
28
|
+
* 同步口只在测试里存在:生产路径拿到的永远是 `Database`。
|
|
29
|
+
*/
|
|
30
|
+
export declare function openInitializedSqlite(path: string, journalMode?: JournalMode, busyTimeout?: number): Promise<DatabaseSync>;
|
|
27
31
|
export { type ContractBackend, type CoordinatorFixture, truncateLiveSession };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { a as truncateLiveSession } from "./branch-
|
|
1
|
+
import { a as truncateLiveSession } from "./branch-CG1PUlO6.mjs";
|
|
2
|
+
import { u as DEFAULT_BUSY_TIMEOUT_MS } from "./backend-rdb-BDCntVDy.mjs";
|
|
3
|
+
import { t as openRdbBackend } from "./open-YoyD_kah.mjs";
|
|
2
4
|
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
3
|
-
import {
|
|
4
|
-
import { Context } from "@deepseek-ai/cordis";
|
|
5
|
+
import { openSqliteHandle } from "@morlay/rdbkit/testing";
|
|
5
6
|
import { describe, expect, it } from "vitest";
|
|
6
7
|
import { MessageId, createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
|
|
7
|
-
|
|
8
|
+
import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistenceNotFoundError, SessionReadOnlyError } from "@deepseek-ai/dsh-session-persistence";
|
|
9
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
10
|
+
// src/testing/contract.ts
|
|
8
11
|
function meta(id, cwd) {
|
|
9
12
|
return {
|
|
10
13
|
version: SESSION_FORMAT_VERSION,
|
|
@@ -636,8 +639,7 @@ function runPersistenceContract(name, make) {
|
|
|
636
639
|
});
|
|
637
640
|
});
|
|
638
641
|
}
|
|
639
|
-
|
|
640
|
-
//#region src/testing/coordinator-contract.ts
|
|
642
|
+
// src/testing/coordinator-contract.ts
|
|
641
643
|
const WORK = "/w";
|
|
642
644
|
function send(session, events) {
|
|
643
645
|
appendLog(session, events);
|
|
@@ -1048,5 +1050,22 @@ function runCoordinatorContract(name, makeFixture) {
|
|
|
1048
1050
|
});
|
|
1049
1051
|
});
|
|
1050
1052
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
+
// src/testing.ts
|
|
1054
|
+
/**
|
|
1055
|
+
* 测试辅助:先把库初始化(建表 / 迁移 / 身份),再给一个同步句柄——造损坏数据、读 `PRAGMA` 用。
|
|
1056
|
+
*
|
|
1057
|
+
* 同步口只在测试里存在:生产路径拿到的永远是 `Database`。
|
|
1058
|
+
*/
|
|
1059
|
+
async function openInitializedSqlite(path, journalMode = "wal", busyTimeout = DEFAULT_BUSY_TIMEOUT_MS) {
|
|
1060
|
+
await (await openRdbBackend({
|
|
1061
|
+
type: "sqlite",
|
|
1062
|
+
path,
|
|
1063
|
+
journalMode,
|
|
1064
|
+
busyTimeout
|
|
1065
|
+
})).close();
|
|
1066
|
+
return openSqliteHandle(path, {
|
|
1067
|
+
journalMode,
|
|
1068
|
+
busyTimeout
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
export { appendLog, meta, oneTurnLog, openInitializedSqlite, runCoordinatorContract, runPersistenceContract, truncateLiveSession };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { localDayKey } from "./usage.mjs";
|
|
2
|
+
// src/log/usage-row.ts
|
|
3
|
+
function numericField(value) {
|
|
4
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
|
|
5
|
+
}
|
|
6
|
+
function textField(value) {
|
|
7
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
8
|
+
}
|
|
9
|
+
function usageRowOf(event, subagent) {
|
|
10
|
+
if (event.fType !== "assistant/message") return void 0;
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(event.fData);
|
|
14
|
+
} catch {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const envelope = parsed["data"] ?? parsed;
|
|
18
|
+
const usage = envelope["usage"];
|
|
19
|
+
if (typeof usage !== "object" || usage === null) return void 0;
|
|
20
|
+
const source = envelope["message"]?.source;
|
|
21
|
+
return {
|
|
22
|
+
fEventId: event.fEventId,
|
|
23
|
+
fCreatedAt: event.fCreatedAt,
|
|
24
|
+
fDay: localDayKey(event.fCreatedAt),
|
|
25
|
+
fProvider: textField(source?.["provider"]),
|
|
26
|
+
fModel: textField(source?.["model"]),
|
|
27
|
+
fReferenced: 1,
|
|
28
|
+
fSubagent: subagent ? 1 : 0,
|
|
29
|
+
fInputTokens: numericField(usage["inputTokens"]),
|
|
30
|
+
fOutputTokens: numericField(usage["outputTokens"]),
|
|
31
|
+
fCacheReadTokens: numericField(usage["cacheReadTokens"]),
|
|
32
|
+
fReasoningTokens: numericField(usage["reasoningTokens"]),
|
|
33
|
+
fTotalTokens: numericField(usage["totalTokens"])
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export { usageRowOf as t };
|
package/dist/usage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as emptyTotals, E as resolveUsageSince, S as addTotals, T as registerSessionUsage, W as SESSION_USAGE_PATH, _ as UsageTokenTotals, b as addActivityTotals, d as SessionUsageReport, f as UsageActivityTotals, g as UsageSessionRow, h as UsageRangeKey, m as UsageBucket, p as UsageAggregate, u as COUNTED_EVENT_TYPES, v as UsageTotals, w as localDayKey, x as addTokenTotals, y as addActivityCount } from "./backend-BKtJ5sO5.mjs";
|
|
2
2
|
export { COUNTED_EVENT_TYPES, SESSION_USAGE_PATH, SessionUsageReport, UsageActivityTotals, UsageAggregate, UsageBucket, UsageRangeKey, UsageSessionRow, UsageTokenTotals, UsageTotals, addActivityCount, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey, registerSessionUsage, resolveUsageSince };
|
package/dist/usage.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { l as SESSION_USAGE_PATH, n as mountJsonRoute } from "./route-DpfY0zq-.mjs";
|
|
2
|
+
// src/usage.ts
|
|
3
3
|
const ROLLING_DAYS = {
|
|
4
4
|
"7d": 7,
|
|
5
5
|
"30d": 30,
|
|
@@ -91,46 +91,12 @@ function addTotals(target, row) {
|
|
|
91
91
|
return target;
|
|
92
92
|
}
|
|
93
93
|
function registerSessionUsage(ctx, persistence) {
|
|
94
|
-
ctx
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
handler: async (req, res) => {
|
|
101
|
-
const rejection = connection.requestRejection(req);
|
|
102
|
-
if (rejection !== void 0) {
|
|
103
|
-
res.writeHead(rejection);
|
|
104
|
-
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (req.method !== "POST") {
|
|
108
|
-
res.writeHead(405, { "content-type": "application/json" });
|
|
109
|
-
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const chunks = [];
|
|
113
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
114
|
-
const raw = Buffer.concat(chunks).toString("utf8");
|
|
115
|
-
let envelope = {};
|
|
116
|
-
if (raw !== "") try {
|
|
117
|
-
envelope = JSON.parse(raw);
|
|
118
|
-
} catch {
|
|
119
|
-
res.writeHead(400, { "content-type": "application/json" });
|
|
120
|
-
res.end(JSON.stringify({ error: "request body is not JSON" }));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const report = await persistence.usageReport(resolveUsageSince(parseUsageRange(envelope.range), Date.now()));
|
|
125
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
126
|
-
res.end(JSON.stringify(report));
|
|
127
|
-
} catch (error) {
|
|
128
|
-
res.writeHead(500, { "content-type": "application/json" });
|
|
129
|
-
res.end(JSON.stringify({ error: error instanceof Error ? error.message : "usage report failed" }));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}), `session-rdb: ${SESSION_USAGE_PATH} route`);
|
|
94
|
+
mountJsonRoute(ctx, {
|
|
95
|
+
path: SESSION_USAGE_PATH,
|
|
96
|
+
handle: async (body) => {
|
|
97
|
+
const { range } = body;
|
|
98
|
+
return { body: await persistence.usageReport(resolveUsageSince(parseUsageRange(range), Date.now())) };
|
|
99
|
+
}
|
|
133
100
|
});
|
|
134
101
|
}
|
|
135
|
-
//#endregion
|
|
136
102
|
export { COUNTED_EVENT_TYPES, SESSION_USAGE_PATH, addActivityCount, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey, registerSessionUsage, resolveUsageSince };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/event-shape.ts
|
|
2
|
+
function eventKind(event) {
|
|
3
|
+
switch (event.type) {
|
|
4
|
+
case "user/message": return "message";
|
|
5
|
+
case "assistant/message":
|
|
6
|
+
if ((event.data.message?.content)?.some((block) => block?.type === "reasoning")) return "thinking";
|
|
7
|
+
return "message";
|
|
8
|
+
case "turn/start":
|
|
9
|
+
case "turn/end":
|
|
10
|
+
case "step/start":
|
|
11
|
+
case "step/end":
|
|
12
|
+
case "session/end-seed": return "turn";
|
|
13
|
+
case "tool/call":
|
|
14
|
+
case "tool/result":
|
|
15
|
+
case "tool/ptc-dispatch-start":
|
|
16
|
+
case "tool/ptc-dispatch":
|
|
17
|
+
case "tool/code-dispatch-start":
|
|
18
|
+
case "tool/code-dispatch": return "tool";
|
|
19
|
+
case "request/header":
|
|
20
|
+
case "request/context": return "request";
|
|
21
|
+
case "model/selection":
|
|
22
|
+
case "permission/preset":
|
|
23
|
+
case "approval/policy":
|
|
24
|
+
case "sandbox/mode":
|
|
25
|
+
case "plan/mode":
|
|
26
|
+
case "agent-preset/selected": return "config";
|
|
27
|
+
case "approval/asked":
|
|
28
|
+
case "approval/decided":
|
|
29
|
+
case "command/run":
|
|
30
|
+
case "command/done":
|
|
31
|
+
case "hook/invoked":
|
|
32
|
+
case "hook/result":
|
|
33
|
+
case "feedback/record": return "audit";
|
|
34
|
+
case "session/title":
|
|
35
|
+
case "session/title-llm-request":
|
|
36
|
+
case "session-log-deepseek/delivery-accepted": return "lifecycle";
|
|
37
|
+
case "agent/inbox/spliced": return "inbox";
|
|
38
|
+
case "compaction/start":
|
|
39
|
+
case "compaction/end":
|
|
40
|
+
case "compaction/summary":
|
|
41
|
+
case "compaction/prune": return "compaction";
|
|
42
|
+
case "llm/retry":
|
|
43
|
+
case "llm/retry-started": return "llm";
|
|
44
|
+
case "subagent/descriptor":
|
|
45
|
+
case "subagent/model-selection-policy": return "subagent";
|
|
46
|
+
case "team/member":
|
|
47
|
+
case "team/task":
|
|
48
|
+
case "team/message/queued":
|
|
49
|
+
case "team/message/delivered": return "team";
|
|
50
|
+
case "tool-workflow/run-start":
|
|
51
|
+
case "tool-workflow/run-end":
|
|
52
|
+
case "tool-workflow/agent-start":
|
|
53
|
+
case "tool-workflow/agent-end": return "workflow";
|
|
54
|
+
case "goal/change": return "goal";
|
|
55
|
+
case "schedule/change": return "schedule";
|
|
56
|
+
case "todo/write": return "todo";
|
|
57
|
+
case "web/deepseek-search-llm-request": return "web";
|
|
58
|
+
default: return "";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function eventDimensions(event) {
|
|
62
|
+
const kind = eventKind(event);
|
|
63
|
+
const type = event.type;
|
|
64
|
+
const data = event.data;
|
|
65
|
+
switch (type) {
|
|
66
|
+
case "user/message": return {
|
|
67
|
+
kind,
|
|
68
|
+
role: "user",
|
|
69
|
+
name: "",
|
|
70
|
+
actionId: ""
|
|
71
|
+
};
|
|
72
|
+
case "assistant/message": return {
|
|
73
|
+
kind,
|
|
74
|
+
role: "assistant",
|
|
75
|
+
name: "",
|
|
76
|
+
actionId: ""
|
|
77
|
+
};
|
|
78
|
+
case "tool/result": return {
|
|
79
|
+
kind,
|
|
80
|
+
role: "tool",
|
|
81
|
+
name: "",
|
|
82
|
+
actionId: data["message"]?.toolCallId ?? ""
|
|
83
|
+
};
|
|
84
|
+
case "tool/call": return {
|
|
85
|
+
kind,
|
|
86
|
+
role: "",
|
|
87
|
+
name: typeof data["name"] === "string" ? data["name"] : "",
|
|
88
|
+
actionId: typeof data["callId"] === "string" ? data["callId"] : ""
|
|
89
|
+
};
|
|
90
|
+
case "tool/ptc-dispatch-start":
|
|
91
|
+
case "tool/ptc-dispatch":
|
|
92
|
+
case "tool/code-dispatch-start":
|
|
93
|
+
case "tool/code-dispatch": return {
|
|
94
|
+
kind,
|
|
95
|
+
role: "",
|
|
96
|
+
name: "",
|
|
97
|
+
actionId: typeof data["subCallId"] === "string" ? data["subCallId"] : ""
|
|
98
|
+
};
|
|
99
|
+
case "command/run":
|
|
100
|
+
case "command/done": return {
|
|
101
|
+
kind,
|
|
102
|
+
role: "",
|
|
103
|
+
name: type === "command/run" && typeof data["name"] === "string" ? data["name"] : "",
|
|
104
|
+
actionId: typeof data["commandId"] === "string" ? data["commandId"] : ""
|
|
105
|
+
};
|
|
106
|
+
case "approval/asked":
|
|
107
|
+
case "approval/decided": return {
|
|
108
|
+
kind,
|
|
109
|
+
role: "",
|
|
110
|
+
name: "",
|
|
111
|
+
actionId: typeof data["id"] === "string" ? data["id"] : ""
|
|
112
|
+
};
|
|
113
|
+
case "hook/invoked":
|
|
114
|
+
case "hook/result": return {
|
|
115
|
+
kind,
|
|
116
|
+
role: "",
|
|
117
|
+
name: "",
|
|
118
|
+
actionId: typeof data["handlerId"] === "string" ? data["handlerId"] : ""
|
|
119
|
+
};
|
|
120
|
+
case "llm/retry":
|
|
121
|
+
case "llm/retry-started": return {
|
|
122
|
+
kind,
|
|
123
|
+
role: "",
|
|
124
|
+
name: "",
|
|
125
|
+
actionId: typeof data["retryId"] === "string" ? data["retryId"] : ""
|
|
126
|
+
};
|
|
127
|
+
case "tool-workflow/run-start":
|
|
128
|
+
case "tool-workflow/run-end":
|
|
129
|
+
case "tool-workflow/agent-start":
|
|
130
|
+
case "tool-workflow/agent-end": return {
|
|
131
|
+
kind,
|
|
132
|
+
role: "",
|
|
133
|
+
name: "",
|
|
134
|
+
actionId: typeof data["runId"] === "string" ? data["runId"] : ""
|
|
135
|
+
};
|
|
136
|
+
case "todo/write": return {
|
|
137
|
+
kind,
|
|
138
|
+
role: "",
|
|
139
|
+
name: "todos",
|
|
140
|
+
actionId: ""
|
|
141
|
+
};
|
|
142
|
+
case "subagent/descriptor": return {
|
|
143
|
+
kind,
|
|
144
|
+
role: "",
|
|
145
|
+
name: typeof data["label"] === "string" ? data["label"] : "",
|
|
146
|
+
actionId: ""
|
|
147
|
+
};
|
|
148
|
+
default: return {
|
|
149
|
+
kind,
|
|
150
|
+
role: "",
|
|
151
|
+
name: "",
|
|
152
|
+
actionId: ""
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// src/write-guard.ts
|
|
157
|
+
var WriteGuard = class {
|
|
158
|
+
headSeqs = /* @__PURE__ */ new Map();
|
|
159
|
+
confirmHead(id, head) {
|
|
160
|
+
this.headSeqs.set(id, head);
|
|
161
|
+
}
|
|
162
|
+
has(id) {
|
|
163
|
+
return this.headSeqs.has(id);
|
|
164
|
+
}
|
|
165
|
+
assertNoConcurrentWriter(id, storedHead) {
|
|
166
|
+
const known = this.headSeqs.get(id);
|
|
167
|
+
if (known === void 0) {
|
|
168
|
+
if (storedHead !== -1) throw new Error(`session "${id}" has a persisted log this instance has not read; another writer may own it — load the session first`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (known !== storedHead) throw new Error(`session "${id}" was modified by another writer (stored head ${storedHead}, this instance last confirmed head ${known}); concurrent writers on one session are not supported`);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
export { eventDimensions as n, eventKind as r, WriteGuard as t };
|