@remnic/core 9.3.641 → 9.3.643
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/dist/access-http.js +3 -3
- package/dist/access-mcp.js +2 -2
- package/dist/{chunk-ACNU2UBF.js → chunk-2GCNTHRA.js} +3 -3
- package/dist/{chunk-5IDVOWBE.js → chunk-GJSNDJHR.js} +14 -2
- package/dist/chunk-GJSNDJHR.js.map +1 -0
- package/dist/{chunk-6DDBW2V4.js → chunk-S2SEPLLA.js} +2 -2
- package/dist/{chunk-6L46YAEZ.js → chunk-T4WDJPEZ.js} +45 -14
- package/dist/chunk-T4WDJPEZ.js.map +1 -0
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +128 -1
- package/dist/index.js +916 -68
- package/dist/index.js.map +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +1 -0
- package/dist/mcp-memory-inspector-app.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +1 -1
- package/src/access-mcp.ts +16 -0
- package/src/index.ts +7 -4
- package/src/mcp-memory-inspector-app.ts +79 -17
- package/src/session-summaries/adapters.ts +438 -0
- package/src/session-summaries/index.ts +401 -0
- package/src/session-summaries/redaction.ts +160 -0
- package/src/session-summaries/session-summaries.test.ts +1748 -0
- package/src/session-summaries/types.ts +130 -0
- package/dist/chunk-5IDVOWBE.js.map +0 -1
- package/dist/chunk-6L46YAEZ.js.map +0 -1
- /package/dist/{chunk-ACNU2UBF.js.map → chunk-2GCNTHRA.js.map} +0 -0
- /package/dist/{chunk-6DDBW2V4.js.map → chunk-S2SEPLLA.js.map} +0 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import type { LocalSessionParsedFile, LocalSessionRole, LocalSessionSourceAdapter, LocalSessionTurn } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const VALID_ROLES = new Set<LocalSessionRole>(["user", "assistant", "tool", "system", "other"]);
|
|
4
|
+
|
|
5
|
+
function normalizeRole(value: unknown): LocalSessionRole {
|
|
6
|
+
if (typeof value !== "string") return "other";
|
|
7
|
+
const role = value.trim().toLowerCase();
|
|
8
|
+
if (VALID_ROLES.has(role as LocalSessionRole)) {
|
|
9
|
+
return role as LocalSessionRole;
|
|
10
|
+
}
|
|
11
|
+
if (role === "human") return "user";
|
|
12
|
+
if (role === "ai" || role === "model" || role === "bot") return "assistant";
|
|
13
|
+
if (role === "function") return "tool";
|
|
14
|
+
return "other";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeTimestamp(value: unknown): string | undefined {
|
|
18
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
19
|
+
const millis = value > 1e12 ? value : value * 1000;
|
|
20
|
+
const date = new Date(millis);
|
|
21
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== "string" || value.trim().length === 0) return undefined;
|
|
24
|
+
const date = new Date(value);
|
|
25
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeContent(value: unknown): string | undefined {
|
|
29
|
+
if (typeof value === "string") {
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
const parts = value
|
|
35
|
+
.map((part) => normalizeContent(part))
|
|
36
|
+
.filter((part): part is string => typeof part === "string")
|
|
37
|
+
.join("\n")
|
|
38
|
+
.trim();
|
|
39
|
+
return parts.length > 0 ? parts : undefined;
|
|
40
|
+
}
|
|
41
|
+
if (!value || typeof value !== "object") return undefined;
|
|
42
|
+
const obj = value as Record<string, unknown>;
|
|
43
|
+
if (firstString(obj.type) === "tool_result") return undefined;
|
|
44
|
+
return normalizeContent(obj.text ?? obj.content ?? obj.message ?? obj.parts ?? obj.value ?? obj.input);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function firstString(...values: unknown[]): string | undefined {
|
|
48
|
+
for (const value of values) {
|
|
49
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
50
|
+
return value.trim();
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function firstContent(...values: unknown[]): string | undefined {
|
|
60
|
+
for (const value of values) {
|
|
61
|
+
const content = normalizeContent(value);
|
|
62
|
+
if (content) return content;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function objectField(value: unknown): Record<string, unknown> | undefined {
|
|
68
|
+
return value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const SESSION_KEY_FIELDS = [
|
|
72
|
+
"sessionKey",
|
|
73
|
+
"session_key",
|
|
74
|
+
"sessionId",
|
|
75
|
+
"session_id",
|
|
76
|
+
"conversationId",
|
|
77
|
+
"conversation_id",
|
|
78
|
+
"threadId",
|
|
79
|
+
"thread_id",
|
|
80
|
+
"transcriptId",
|
|
81
|
+
"transcript_id",
|
|
82
|
+
] as const;
|
|
83
|
+
|
|
84
|
+
function hasUsableSessionKeyField(value: unknown): boolean {
|
|
85
|
+
return (
|
|
86
|
+
(typeof value === "string" && value.trim().length > 0) ||
|
|
87
|
+
(typeof value === "number" && Number.isFinite(value))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function inheritEnvelopeSessionFields(row: unknown, envelope: Record<string, unknown>): unknown {
|
|
92
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return row;
|
|
93
|
+
const child = row as Record<string, unknown>;
|
|
94
|
+
const inherited: Record<string, unknown> = {};
|
|
95
|
+
for (const key of SESSION_KEY_FIELDS) {
|
|
96
|
+
if (!hasUsableSessionKeyField(child[key]) && hasUsableSessionKeyField(envelope[key])) {
|
|
97
|
+
inherited[key] = envelope[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return Object.keys(inherited).length > 0 ? { ...child, ...inherited } : row;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function codexRoleFromTypes(
|
|
104
|
+
rowType: string | undefined,
|
|
105
|
+
payloadType: string | undefined
|
|
106
|
+
): LocalSessionRole | undefined {
|
|
107
|
+
if (payloadType === "agent_message") return "assistant";
|
|
108
|
+
if (payloadType === "user_message") return "user";
|
|
109
|
+
if (rowType === "event_msg") return "user";
|
|
110
|
+
if (rowType === "response_item") return "assistant";
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function sessionKeyFromRow(row: unknown): string | undefined {
|
|
115
|
+
if (!row || typeof row !== "object") return undefined;
|
|
116
|
+
const obj = row as Record<string, unknown>;
|
|
117
|
+
const nestedMessage = objectField(obj.message);
|
|
118
|
+
const payload = objectField(obj.payload);
|
|
119
|
+
const payloadMessage = objectField(payload?.message);
|
|
120
|
+
const rowType = firstString(obj.type, obj.kind, obj.event)?.toLowerCase();
|
|
121
|
+
return firstString(
|
|
122
|
+
obj.sessionKey,
|
|
123
|
+
obj.session_key,
|
|
124
|
+
obj.sessionId,
|
|
125
|
+
obj.session_id,
|
|
126
|
+
obj.conversationId,
|
|
127
|
+
obj.conversation_id,
|
|
128
|
+
obj.threadId,
|
|
129
|
+
obj.thread_id,
|
|
130
|
+
obj.transcriptId,
|
|
131
|
+
obj.transcript_id,
|
|
132
|
+
nestedMessage?.sessionKey,
|
|
133
|
+
nestedMessage?.session_key,
|
|
134
|
+
nestedMessage?.sessionId,
|
|
135
|
+
nestedMessage?.session_id,
|
|
136
|
+
nestedMessage?.conversationId,
|
|
137
|
+
nestedMessage?.conversation_id,
|
|
138
|
+
nestedMessage?.threadId,
|
|
139
|
+
nestedMessage?.thread_id,
|
|
140
|
+
nestedMessage?.transcriptId,
|
|
141
|
+
nestedMessage?.transcript_id,
|
|
142
|
+
rowType === "session_meta" ? payload?.id : undefined,
|
|
143
|
+
payload?.sessionKey,
|
|
144
|
+
payload?.session_key,
|
|
145
|
+
payload?.sessionId,
|
|
146
|
+
payload?.session_id,
|
|
147
|
+
payload?.conversationId,
|
|
148
|
+
payload?.conversation_id,
|
|
149
|
+
payload?.threadId,
|
|
150
|
+
payload?.thread_id,
|
|
151
|
+
payload?.transcriptId,
|
|
152
|
+
payload?.transcript_id,
|
|
153
|
+
payloadMessage?.sessionKey,
|
|
154
|
+
payloadMessage?.session_key,
|
|
155
|
+
payloadMessage?.sessionId,
|
|
156
|
+
payloadMessage?.session_id,
|
|
157
|
+
payloadMessage?.conversationId,
|
|
158
|
+
payloadMessage?.conversation_id,
|
|
159
|
+
payloadMessage?.threadId,
|
|
160
|
+
payloadMessage?.thread_id,
|
|
161
|
+
payloadMessage?.transcriptId,
|
|
162
|
+
payloadMessage?.transcript_id
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function extractRowsFromJson(value: unknown): unknown[] {
|
|
167
|
+
if (Array.isArray(value)) return value;
|
|
168
|
+
if (!value || typeof value !== "object") return [];
|
|
169
|
+
const obj = value as Record<string, unknown>;
|
|
170
|
+
for (const key of ["turns", "messages", "events", "entries", "items"]) {
|
|
171
|
+
const rows = obj[key];
|
|
172
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
173
|
+
return rows.map((row) => inheritEnvelopeSessionFields(row, obj));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return [obj];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function parseJsonlRows(
|
|
180
|
+
content: string,
|
|
181
|
+
adapterId: string,
|
|
182
|
+
strict: boolean | undefined,
|
|
183
|
+
fileRef: string
|
|
184
|
+
): { rows: unknown[]; warnings: LocalSessionParsedFile["warnings"] } {
|
|
185
|
+
const rows: unknown[] = [];
|
|
186
|
+
const warnings: LocalSessionParsedFile["warnings"] = [];
|
|
187
|
+
for (const [index, line] of content.split("\n").entries()) {
|
|
188
|
+
const trimmed = line.trim();
|
|
189
|
+
if (trimmed.length === 0) continue;
|
|
190
|
+
try {
|
|
191
|
+
rows.push(JSON.parse(trimmed));
|
|
192
|
+
} catch {
|
|
193
|
+
const warning = {
|
|
194
|
+
code: `${adapterId}.jsonl.invalid_line`,
|
|
195
|
+
message: `Skipping invalid JSONL line ${index + 1} in fileRef ${fileRef}.`,
|
|
196
|
+
fileRef,
|
|
197
|
+
line: index + 1,
|
|
198
|
+
};
|
|
199
|
+
if (strict) throw new Error(warning.message);
|
|
200
|
+
warnings.push(warning);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { rows, warnings };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parseRows(
|
|
207
|
+
content: string,
|
|
208
|
+
extension: string,
|
|
209
|
+
adapterId: string,
|
|
210
|
+
strict: boolean | undefined,
|
|
211
|
+
fileRef: string
|
|
212
|
+
): { rows: unknown[]; warnings: LocalSessionParsedFile["warnings"] } {
|
|
213
|
+
if (extension === ".jsonl") {
|
|
214
|
+
return parseJsonlRows(content, adapterId, strict, fileRef);
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return { rows: extractRowsFromJson(JSON.parse(content)), warnings: [] };
|
|
218
|
+
} catch {
|
|
219
|
+
const message = `Invalid ${adapterId} JSON in fileRef ${fileRef}; skipping file.`;
|
|
220
|
+
if (strict) throw new Error(message);
|
|
221
|
+
return {
|
|
222
|
+
rows: [],
|
|
223
|
+
warnings: [{ code: `${adapterId}.json.invalid`, message, fileRef }],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function turnFromRow(row: unknown, fallbackSessionKey: string): LocalSessionTurn | null {
|
|
229
|
+
if (!row || typeof row !== "object") return null;
|
|
230
|
+
const obj = row as Record<string, unknown>;
|
|
231
|
+
const nestedMessage = objectField(obj.message);
|
|
232
|
+
const payload = objectField(obj.payload);
|
|
233
|
+
const payloadItem = objectField(payload?.item);
|
|
234
|
+
const payloadMessage = objectField(payload?.message);
|
|
235
|
+
const author = objectField(obj.author);
|
|
236
|
+
const rowType = firstString(obj.type, obj.kind, obj.event);
|
|
237
|
+
const payloadType = firstString(payload?.type, payloadItem?.type);
|
|
238
|
+
const codexRole = codexRoleFromTypes(rowType, payloadType);
|
|
239
|
+
|
|
240
|
+
const content = firstContent(
|
|
241
|
+
obj.content,
|
|
242
|
+
obj.text,
|
|
243
|
+
obj.message,
|
|
244
|
+
obj.body,
|
|
245
|
+
obj.parts,
|
|
246
|
+
obj.input,
|
|
247
|
+
payload?.message,
|
|
248
|
+
payload?.content,
|
|
249
|
+
payload?.text,
|
|
250
|
+
payload?.body,
|
|
251
|
+
payload?.parts,
|
|
252
|
+
payload?.input,
|
|
253
|
+
payloadMessage?.content,
|
|
254
|
+
payloadMessage?.text,
|
|
255
|
+
payloadMessage?.parts,
|
|
256
|
+
payloadMessage?.input,
|
|
257
|
+
payloadItem?.content,
|
|
258
|
+
payloadItem?.text,
|
|
259
|
+
payloadItem?.message,
|
|
260
|
+
payloadItem?.parts,
|
|
261
|
+
payloadItem?.input,
|
|
262
|
+
nestedMessage?.content,
|
|
263
|
+
nestedMessage?.text,
|
|
264
|
+
nestedMessage?.parts,
|
|
265
|
+
nestedMessage?.input
|
|
266
|
+
);
|
|
267
|
+
if (!content) return null;
|
|
268
|
+
|
|
269
|
+
const role = normalizeRole(
|
|
270
|
+
obj.role ??
|
|
271
|
+
obj.sender ??
|
|
272
|
+
obj.actor ??
|
|
273
|
+
payload?.role ??
|
|
274
|
+
payload?.sender ??
|
|
275
|
+
payloadMessage?.role ??
|
|
276
|
+
payloadMessage?.sender ??
|
|
277
|
+
payloadItem?.role ??
|
|
278
|
+
nestedMessage?.role ??
|
|
279
|
+
nestedMessage?.sender ??
|
|
280
|
+
author?.role ??
|
|
281
|
+
codexRole ??
|
|
282
|
+
obj.type
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
role,
|
|
287
|
+
content,
|
|
288
|
+
timestamp: normalizeTimestamp(
|
|
289
|
+
obj.timestamp ??
|
|
290
|
+
obj.createdAt ??
|
|
291
|
+
obj.created_at ??
|
|
292
|
+
obj.time ??
|
|
293
|
+
obj.date ??
|
|
294
|
+
payload?.timestamp ??
|
|
295
|
+
payload?.createdAt ??
|
|
296
|
+
payload?.created_at ??
|
|
297
|
+
payloadMessage?.timestamp ??
|
|
298
|
+
payloadMessage?.createdAt ??
|
|
299
|
+
payloadMessage?.created_at ??
|
|
300
|
+
payloadItem?.timestamp ??
|
|
301
|
+
payloadItem?.created_at ??
|
|
302
|
+
nestedMessage?.timestamp ??
|
|
303
|
+
nestedMessage?.created_at
|
|
304
|
+
),
|
|
305
|
+
sessionKey: sessionKeyFromRow(row) ?? fallbackSessionKey,
|
|
306
|
+
sourceId: firstString(obj.id, obj.uuid, obj.turnId, obj.turn_id, payload?.id, payloadItem?.id),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function shouldSkipRowForAdapter(adapterId: string, row: unknown): boolean {
|
|
311
|
+
if (adapterId !== "codex-jsonl" || !row || typeof row !== "object") return false;
|
|
312
|
+
const obj = row as Record<string, unknown>;
|
|
313
|
+
const payload = objectField(obj.payload);
|
|
314
|
+
const rowType = firstString(obj.type, obj.kind, obj.event)?.toLowerCase();
|
|
315
|
+
const payloadType = firstString(payload?.type)?.toLowerCase();
|
|
316
|
+
return rowType === "compacted" || payloadType === "compacted";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
type CodexMirrorSurface = "event-agent-message" | "response-item";
|
|
320
|
+
|
|
321
|
+
const CODEX_MIRROR_SURFACE_PRIORITY: Record<CodexMirrorSurface, number> = {
|
|
322
|
+
"event-agent-message": 1,
|
|
323
|
+
"response-item": 2,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
function codexMirrorSurface(row: unknown): CodexMirrorSurface | undefined {
|
|
327
|
+
if (!row || typeof row !== "object") return undefined;
|
|
328
|
+
const obj = row as Record<string, unknown>;
|
|
329
|
+
const payload = objectField(obj.payload);
|
|
330
|
+
const payloadItem = objectField(payload?.item);
|
|
331
|
+
const rowType = firstString(obj.type, obj.kind, obj.event)?.toLowerCase();
|
|
332
|
+
const payloadType = firstString(payload?.type, payloadItem?.type)?.toLowerCase();
|
|
333
|
+
if (rowType === "event_msg" && payloadType === "agent_message") return "event-agent-message";
|
|
334
|
+
if (rowType === "response_item") return "response-item";
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function codexMirrorKey(adapterId: string, row: unknown, turn: LocalSessionTurn): string | undefined {
|
|
339
|
+
if (adapterId !== "codex-jsonl" || turn.role !== "assistant") return undefined;
|
|
340
|
+
const surface = codexMirrorSurface(row);
|
|
341
|
+
if (!surface) return undefined;
|
|
342
|
+
const sessionKey = turn.sessionKey?.trim();
|
|
343
|
+
const content = turn.content.replace(/\s+/g, " ").trim();
|
|
344
|
+
if (!sessionKey || content.length === 0) return undefined;
|
|
345
|
+
return `${sessionKey}\0${turn.role}\0${content}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function pushTurnWithCodexMirrorDedupe(
|
|
349
|
+
adapterId: string,
|
|
350
|
+
row: unknown,
|
|
351
|
+
turn: LocalSessionTurn,
|
|
352
|
+
turns: LocalSessionTurn[],
|
|
353
|
+
codexMirrorTurns: Map<string, { index: number; surface: CodexMirrorSurface; rowSignal?: string }[]>
|
|
354
|
+
): void {
|
|
355
|
+
const key = codexMirrorKey(adapterId, row, turn);
|
|
356
|
+
const surface = key ? codexMirrorSurface(row) : undefined;
|
|
357
|
+
if (!key || !surface) {
|
|
358
|
+
turns.push(turn);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const candidates = codexMirrorTurns.get(key) ?? [];
|
|
363
|
+
const rowSignal = turn.sourceId ?? turn.timestamp;
|
|
364
|
+
const matchingIndex = candidates.findIndex((candidate) => {
|
|
365
|
+
if (candidate.surface === surface) {
|
|
366
|
+
return Boolean(candidate.rowSignal && rowSignal && candidate.rowSignal === rowSignal);
|
|
367
|
+
}
|
|
368
|
+
return Boolean(candidate.rowSignal && rowSignal && candidate.rowSignal === rowSignal);
|
|
369
|
+
});
|
|
370
|
+
const existing = matchingIndex >= 0 ? candidates[matchingIndex] : undefined;
|
|
371
|
+
if (existing) {
|
|
372
|
+
if (CODEX_MIRROR_SURFACE_PRIORITY[surface] > CODEX_MIRROR_SURFACE_PRIORITY[existing.surface]) {
|
|
373
|
+
turns[existing.index] = turn;
|
|
374
|
+
candidates[matchingIndex] = { index: existing.index, surface, ...(rowSignal ? { rowSignal } : {}) };
|
|
375
|
+
codexMirrorTurns.set(key, candidates);
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
candidates.push({ index: turns.length, surface, ...(rowSignal ? { rowSignal } : {}) });
|
|
381
|
+
codexMirrorTurns.set(key, candidates);
|
|
382
|
+
turns.push(turn);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function makeJsonTranscriptAdapter(id: string): LocalSessionSourceAdapter {
|
|
386
|
+
return {
|
|
387
|
+
id,
|
|
388
|
+
parseFile(input, options = {}) {
|
|
389
|
+
const parsed = parseRows(input.content, input.fileExtension, id, options.strict, input.fileRef);
|
|
390
|
+
const fallbackSessionKey = `${id}:${input.fileRef}`;
|
|
391
|
+
let currentSessionKey = fallbackSessionKey;
|
|
392
|
+
const turns: LocalSessionTurn[] = [];
|
|
393
|
+
const codexMirrorTurns = new Map<string, { index: number; surface: CodexMirrorSurface; rowSignal?: string }[]>();
|
|
394
|
+
for (const row of parsed.rows) {
|
|
395
|
+
if (shouldSkipRowForAdapter(id, row)) continue;
|
|
396
|
+
currentSessionKey = sessionKeyFromRow(row) ?? currentSessionKey;
|
|
397
|
+
const turn = turnFromRow(row, currentSessionKey);
|
|
398
|
+
if (turn) {
|
|
399
|
+
currentSessionKey = turn.sessionKey ?? currentSessionKey;
|
|
400
|
+
pushTurnWithCodexMirrorDedupe(id, row, turn, turns, codexMirrorTurns);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return { turns, warnings: parsed.warnings };
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const BUILT_IN_ADAPTERS = new Map<string, LocalSessionSourceAdapter>();
|
|
409
|
+
|
|
410
|
+
export function registerLocalSessionSourceAdapter(adapter: LocalSessionSourceAdapter): void {
|
|
411
|
+
if (!adapter || typeof adapter !== "object") {
|
|
412
|
+
throw new Error("local session source adapter must be an object");
|
|
413
|
+
}
|
|
414
|
+
if (typeof adapter.id !== "string" || adapter.id.trim().length === 0) {
|
|
415
|
+
throw new Error("local session source adapter id must be a non-empty string");
|
|
416
|
+
}
|
|
417
|
+
if (typeof adapter.parseFile !== "function") {
|
|
418
|
+
throw new Error(`local session adapter '${adapter.id}' must define parseFile`);
|
|
419
|
+
}
|
|
420
|
+
const id = adapter.id.trim();
|
|
421
|
+
if (BUILT_IN_ADAPTERS.has(id)) {
|
|
422
|
+
throw new Error(`local session adapter '${id}' is already registered`);
|
|
423
|
+
}
|
|
424
|
+
BUILT_IN_ADAPTERS.set(id, adapter.id === id ? adapter : { ...adapter, id });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function getLocalSessionSourceAdapter(id: string | undefined): LocalSessionSourceAdapter | undefined {
|
|
428
|
+
const key = typeof id === "string" && id.trim().length > 0 ? id.trim() : "generic-jsonl";
|
|
429
|
+
return BUILT_IN_ADAPTERS.get(key);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function listLocalSessionSourceAdapters(): string[] {
|
|
433
|
+
return [...BUILT_IN_ADAPTERS.keys()].sort();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
for (const adapterId of ["generic-jsonl", "codex-jsonl", "claude-jsonl"]) {
|
|
437
|
+
registerLocalSessionSourceAdapter(makeJsonTranscriptAdapter(adapterId));
|
|
438
|
+
}
|