@openviking/dsh-memory-plugin 0.1.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.
@@ -0,0 +1,515 @@
1
+ // GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
2
+ const TEXT_BLOCK_TYPES = new Set(["text", "input_text", "output_text"]);
3
+ const TOOL_CALL_TYPES = new Set([
4
+ "tool_call",
5
+ "toolcall",
6
+ "tool_use",
7
+ "tooluse",
8
+ "function_call",
9
+ "functioncall",
10
+ ]);
11
+ const TOOL_RESULT_TYPES = new Set([
12
+ "tool_result",
13
+ "toolresult",
14
+ "tool_output",
15
+ "tooloutput",
16
+ "function_call_output",
17
+ "functioncalloutput",
18
+ ]);
19
+
20
+ // Tool output is reported verbatim; the server owns truncation via
21
+ // tool_output_externalization (threshold_chars, default 20000). This cap only
22
+ // guards against pathological payloads.
23
+ const DEFAULT_TOOL_MAX_CHARS = 1000000;
24
+
25
+ const ACK_RE = /^(?:ok|okay|k|yes|yep|no|nope|thanks|thank you|thx|done|收到|好的|好|嗯|可以|继续|不用|不需要|没了|好了)[.!?。!?\s]*$/i;
26
+ const SLASH_COMMAND_RE = /^\/[a-z0-9_-]{1,64}\b/i;
27
+ const METADATA_KEYS = [
28
+ "session_id",
29
+ "sessionid",
30
+ "sessionkey",
31
+ "conversation_id",
32
+ "conversationid",
33
+ "channel",
34
+ "sender",
35
+ "user_id",
36
+ "userid",
37
+ "agent_id",
38
+ "agentid",
39
+ "timestamp",
40
+ "timezone",
41
+ "cwd",
42
+ "model",
43
+ "permission_mode",
44
+ ];
45
+
46
+ function normalizeType(value) {
47
+ return String(value || "").toLowerCase().replace(/[-\s]/g, "_");
48
+ }
49
+
50
+ function isToolCallBlock(block) {
51
+ const type = normalizeType(block?.type || block?.kind || block?.role);
52
+ return TOOL_CALL_TYPES.has(type) || Boolean(block?.tool_calls) || Boolean(block?.function?.name);
53
+ }
54
+
55
+ function isToolResultBlock(block) {
56
+ const type = normalizeType(block?.type || block?.kind || block?.role);
57
+ return TOOL_RESULT_TYPES.has(type) || type === "tool" || type === "function";
58
+ }
59
+
60
+ function oneLine(text) {
61
+ return String(text || "").replace(/\s+/g, " ").trim();
62
+ }
63
+
64
+ export function truncateCaptureText(text, maxChars = 2000) {
65
+ const value = String(text || "").trim();
66
+ if (!Number.isFinite(maxChars) || maxChars <= 0 || value.length <= maxChars) return value;
67
+ return `${value.slice(0, Math.max(0, maxChars - 20)).trimEnd()}\n[truncated]`;
68
+ }
69
+
70
+ function stringifyCompact(value, maxChars) {
71
+ if (value == null) return "";
72
+ if (typeof value === "string") return truncateCaptureText(value, maxChars);
73
+ try {
74
+ return truncateCaptureText(JSON.stringify(value), maxChars);
75
+ } catch {
76
+ return truncateCaptureText(String(value), maxChars);
77
+ }
78
+ }
79
+
80
+ function parseMaybeJson(value) {
81
+ if (value == null || typeof value !== "string") return value;
82
+ const trimmed = value.trim();
83
+ if (!trimmed) return value;
84
+ try {
85
+ return JSON.parse(trimmed);
86
+ } catch {
87
+ return value;
88
+ }
89
+ }
90
+
91
+ function blockText(block) {
92
+ if (!block || typeof block !== "object") return "";
93
+ if (typeof block.text === "string") return block.text;
94
+ if (typeof block.output_text === "string") return block.output_text;
95
+ if (typeof block.input_text === "string") return block.input_text;
96
+ if (typeof block.content === "string") return block.content;
97
+ return "";
98
+ }
99
+
100
+ function toolName(block) {
101
+ return oneLine(
102
+ block?.name ||
103
+ block?.tool_name ||
104
+ block?.toolName ||
105
+ block?.tool ||
106
+ block?.function?.name ||
107
+ block?.call?.name ||
108
+ "",
109
+ );
110
+ }
111
+
112
+ function toolPayload(block, kind) {
113
+ if (!block || typeof block !== "object") return "";
114
+ if (kind === "call") {
115
+ return block.input ??
116
+ block.state?.input ??
117
+ block.arguments ??
118
+ block.args ??
119
+ block.params ??
120
+ block.function?.arguments ??
121
+ block.command ??
122
+ block.call?.input ??
123
+ block.call?.arguments ??
124
+ "";
125
+ }
126
+ return block.output ??
127
+ block.state?.output ??
128
+ block.result ??
129
+ block.error ??
130
+ block.state?.error ??
131
+ block.data ??
132
+ block.content ??
133
+ block.text ??
134
+ "";
135
+ }
136
+
137
+ function toolId(block) {
138
+ return oneLine(
139
+ block?.call_id ||
140
+ block?.callId ||
141
+ block?.callID ||
142
+ block?.tool_call_id ||
143
+ block?.toolCallId ||
144
+ block?.tool_use_id ||
145
+ block?.toolUseId ||
146
+ block?.function_call_id ||
147
+ block?.functionCallId ||
148
+ block?.id ||
149
+ "",
150
+ );
151
+ }
152
+
153
+ function toolStatus(block, kind) {
154
+ if (kind === "call") return "running";
155
+ if (block?.is_error || block?.error || block?.state?.error) return "error";
156
+ const status = oneLine(block?.status || block?.state?.status || "");
157
+ return status || "completed";
158
+ }
159
+
160
+ function setToolInput(part, payload) {
161
+ const input = parseMaybeJson(payload);
162
+ if (input === "" || input == null) return;
163
+ part.tool_input = typeof input === "object" && !Array.isArray(input)
164
+ ? input
165
+ : { value: input };
166
+ }
167
+
168
+ function buildToolPart(block, kind, { toolMaxChars = DEFAULT_TOOL_MAX_CHARS, toolNameById = {} } = {}) {
169
+ const id = toolId(block);
170
+ const name = toolName(block) || (id ? toolNameById[id] : "");
171
+ const payload = toolPayload(block, kind);
172
+ const part = {
173
+ type: "tool",
174
+ tool_id: id || undefined,
175
+ tool_name: name || undefined,
176
+ tool_status: toolStatus(block, kind),
177
+ };
178
+ if (kind === "call") {
179
+ setToolInput(part, payload);
180
+ } else {
181
+ if (block?.state?.input !== undefined) {
182
+ setToolInput(part, block.state.input);
183
+ }
184
+ part.tool_output = stringifyCompact(payload, toolMaxChars);
185
+ }
186
+ return part;
187
+ }
188
+
189
+ function formatToolBlock(block, kind, maxChars) {
190
+ const name = toolName(block);
191
+ const payload = toolPayload(block, kind);
192
+ const body = oneLine(stringifyCompact(payload, maxChars));
193
+ const label = kind === "call" ? "tool-call" : "tool-result";
194
+ return body
195
+ ? `[${label}${name ? ` ${name}` : ""}] ${body}`
196
+ : `[${label}${name ? ` ${name}` : ""}]`;
197
+ }
198
+
199
+ function blockToText(block, options) {
200
+ if (!block) return "";
201
+ if (typeof block === "string") return block;
202
+ if (Array.isArray(block)) return extractTextFromContent(block, options);
203
+ if (typeof block !== "object") return "";
204
+
205
+ if (block.item && typeof block.item === "object") {
206
+ const itemText = blockToText(block.item, options);
207
+ if (itemText) return itemText;
208
+ }
209
+
210
+ const type = normalizeType(block.type || block.kind || block.role);
211
+ if (TEXT_BLOCK_TYPES.has(type)) return blockText(block);
212
+ if (isToolCallBlock(block)) return formatToolBlock(block, "call", options.toolMaxChars);
213
+ if (isToolResultBlock(block)) return formatToolBlock(block, "result", options.toolMaxChars);
214
+ if (Array.isArray(block.content)) return extractTextFromContent(block.content, options);
215
+ if (!type) return blockText(block);
216
+ return "";
217
+ }
218
+
219
+ export function extractTextFromContent(content, options = {}) {
220
+ const opts = { toolMaxChars: DEFAULT_TOOL_MAX_CHARS, ...options };
221
+ if (!content) return "";
222
+ if (typeof content === "string") return content;
223
+ if (Array.isArray(content)) {
224
+ return content
225
+ .map((block) => blockToText(block, opts))
226
+ .filter(Boolean)
227
+ .join("\n\n");
228
+ }
229
+ if (typeof content === "object") {
230
+ return blockToText(content, opts) || stringifyCompact(content, opts.toolMaxChars);
231
+ }
232
+ return "";
233
+ }
234
+
235
+ export function extractTextFromPayload(payload, options = {}) {
236
+ if (!payload || typeof payload !== "object") return "";
237
+ const chunks = [];
238
+ const directType = normalizeType(payload.type || payload.kind || payload.role);
239
+ if (TOOL_RESULT_TYPES.has(directType) || directType === "tool" || TOOL_CALL_TYPES.has(directType)) {
240
+ const direct = blockToText(payload, { toolMaxChars: DEFAULT_TOOL_MAX_CHARS, ...options });
241
+ if (direct) return direct;
242
+ }
243
+
244
+ if (payload.message && typeof payload.message === "object") {
245
+ const messageText = extractTextFromPayload(payload.message, options);
246
+ if (messageText) chunks.push(messageText);
247
+ } else if (payload.content !== undefined) {
248
+ const contentText = extractTextFromContent(payload.content, options);
249
+ if (contentText) chunks.push(contentText);
250
+ }
251
+
252
+ for (const key of ["tool_calls", "toolCalls", "function_call", "functionCall", "tool_call", "toolCall"]) {
253
+ const value = payload[key];
254
+ if (!value) continue;
255
+ const toolText = extractTextFromContent(value, options);
256
+ if (toolText) chunks.push(toolText);
257
+ }
258
+
259
+ if (chunks.length === 0) {
260
+ const direct = blockToText(payload, { toolMaxChars: DEFAULT_TOOL_MAX_CHARS, ...options });
261
+ if (direct) chunks.push(direct);
262
+ }
263
+
264
+ return chunks.join("\n\n");
265
+ }
266
+
267
+ function collectToolNamesByIdFromEntries(entries) {
268
+ const map = {};
269
+ for (const entry of entries || []) {
270
+ const payload = entry?.payload && typeof entry.payload === "object" ? entry.payload : entry;
271
+ collectToolNamesByIdFromPayload(payload, map);
272
+ }
273
+ return map;
274
+ }
275
+
276
+ function collectToolNamesByIdFromPayload(payload, out) {
277
+ if (!payload || typeof payload !== "object") return;
278
+ if (payload.message && typeof payload.message === "object") {
279
+ collectToolNamesByIdFromPayload(payload.message, out);
280
+ }
281
+ const candidates = [];
282
+ if (Array.isArray(payload.content)) candidates.push(...payload.content);
283
+ for (const key of ["tool_calls", "toolCalls", "function_call", "functionCall", "tool_call", "toolCall"]) {
284
+ const value = payload[key];
285
+ if (!value) continue;
286
+ if (Array.isArray(value)) candidates.push(...value);
287
+ else candidates.push(value);
288
+ }
289
+ if (isToolCallBlock(payload)) candidates.push(payload);
290
+ for (const block of candidates) {
291
+ if (!block || typeof block !== "object") continue;
292
+ if (!isToolCallBlock(block)) continue;
293
+ const id = toolId(block);
294
+ const name = toolName(block);
295
+ if (id && name) out[id] = name;
296
+ }
297
+ }
298
+
299
+ function extractPartsFromContent(content, options = {}) {
300
+ const opts = { toolMaxChars: DEFAULT_TOOL_MAX_CHARS, toolNameById: {}, ...options };
301
+ const parts = [];
302
+ if (!content) return parts;
303
+ if (typeof content === "string") {
304
+ if (content.trim()) parts.push({ type: "text", text: content });
305
+ return parts;
306
+ }
307
+ if (!Array.isArray(content)) {
308
+ const text = blockToText(content, opts);
309
+ if (text.trim()) parts.push({ type: "text", text });
310
+ return parts;
311
+ }
312
+ for (const block of content) {
313
+ if (!block || typeof block !== "object") continue;
314
+ if (isToolCallBlock(block)) {
315
+ parts.push(buildToolPart(block, "call", opts));
316
+ } else if (isToolResultBlock(block)) {
317
+ parts.push(buildToolPart(block, "result", opts));
318
+ } else {
319
+ const text = blockToText(block, opts);
320
+ if (text.trim()) parts.push({ type: "text", text });
321
+ }
322
+ }
323
+ return parts;
324
+ }
325
+
326
+ export function extractPartsFromPayload(payload, options = {}) {
327
+ if (!payload || typeof payload !== "object") return [];
328
+ const opts = { toolMaxChars: DEFAULT_TOOL_MAX_CHARS, toolNameById: {}, ...options };
329
+ if (payload.message && typeof payload.message === "object") {
330
+ return extractPartsFromPayload(payload.message, opts);
331
+ }
332
+
333
+ const directType = normalizeType(payload.type || payload.kind || payload.role);
334
+ if (TOOL_CALL_TYPES.has(directType) || isToolCallBlock(payload)) {
335
+ return [buildToolPart(payload, "call", opts)];
336
+ }
337
+ if (TOOL_RESULT_TYPES.has(directType) || directType === "tool" || directType === "function") {
338
+ return [buildToolPart(payload, "result", opts)];
339
+ }
340
+
341
+ const parts = [];
342
+ if (payload.content !== undefined) {
343
+ parts.push(...extractPartsFromContent(payload.content, opts));
344
+ }
345
+ for (const key of ["tool_calls", "toolCalls", "function_call", "functionCall", "tool_call", "toolCall"]) {
346
+ const value = payload[key];
347
+ if (!value) continue;
348
+ if (Array.isArray(value)) {
349
+ for (const block of value) parts.push(...extractPartsFromPayload(block, opts));
350
+ } else {
351
+ parts.push(...extractPartsFromPayload(value, opts));
352
+ }
353
+ }
354
+ return parts;
355
+ }
356
+
357
+ export function extractCaptureTurns(rolloutEntries, cfg = {}) {
358
+ const toolNameById = collectToolNamesByIdFromEntries(rolloutEntries);
359
+ const turns = [];
360
+ for (const entry of rolloutEntries || []) {
361
+ if (!entry || typeof entry !== "object") continue;
362
+ const payload = entry.payload && typeof entry.payload === "object" ? entry.payload : entry;
363
+ const message = payload.message && typeof payload.message === "object" ? payload.message : null;
364
+ const rawRole = message?.role || payload.role || payload.type || payload.kind;
365
+ const role = normalizeCaptureRole(rawRole);
366
+ if (!role) continue;
367
+ if (isAssistantSideCaptureRole(rawRole) && !cfg.captureAssistantTurns) continue;
368
+
369
+ const rawText = extractTextFromPayload(payload, { toolMaxChars: cfg.captureToolMaxChars });
370
+ const parts = extractPartsFromPayload(payload, {
371
+ toolMaxChars: cfg.captureToolMaxChars,
372
+ toolNameById,
373
+ });
374
+ const decision = shouldCaptureText(rawText, role, cfg);
375
+ if (!decision.shouldCapture && parts.length === 0) continue;
376
+ const text = decision.shouldCapture ? decision.text : "";
377
+ turns.push({ role, text, parts });
378
+ }
379
+ return turns;
380
+ }
381
+
382
+ export function normalizeCaptureRole(role) {
383
+ const value = normalizeType(role);
384
+ if (value === "user") return "user";
385
+ if (value === "assistant") return "assistant";
386
+ if (value === "tool" || value === "tool_result" || value === "function" || value === "function_call_output") {
387
+ return "user";
388
+ }
389
+ if (value === "tool_call" || value === "function_call") return "assistant";
390
+ return null;
391
+ }
392
+
393
+ export function isAssistantSideCaptureRole(role) {
394
+ const value = normalizeType(role);
395
+ return value === "assistant" ||
396
+ value === "tool" ||
397
+ value === "tool_result" ||
398
+ value === "tool_call" ||
399
+ value === "function" ||
400
+ value === "function_call" ||
401
+ value === "function_call_output";
402
+ }
403
+
404
+ function stripMetadataFences(text) {
405
+ return String(text || "").replace(/```(?:json)?\s*([\s\S]*?)```/gi, (match, body) => {
406
+ const lower = body.toLowerCase();
407
+ let hits = 0;
408
+ for (const key of METADATA_KEYS) {
409
+ const re = new RegExp(`["']?${key}["']?\\s*:`, "i");
410
+ if (re.test(lower)) hits += 1;
411
+ }
412
+ return hits >= 3 ? "" : match;
413
+ });
414
+ }
415
+
416
+ function stripInjectedDigestBlocks(text) {
417
+ const lines = String(text || "").split(/\r?\n/);
418
+ const out = [];
419
+ let skipping = false;
420
+ let skipUntilMcpHint = false;
421
+
422
+ for (const line of lines) {
423
+ const trimmed = line.trim();
424
+ if (/^OpenViking session archive digest:/i.test(trimmed)) {
425
+ skipping = true;
426
+ skipUntilMcpHint = true;
427
+ continue;
428
+ }
429
+ if (/^OpenViking memory digest:/i.test(trimmed)) {
430
+ skipping = true;
431
+ skipUntilMcpHint = false;
432
+ continue;
433
+ }
434
+ if (skipping) {
435
+ if (skipUntilMcpHint) {
436
+ if (/^More detail: use the OpenViking MCP /i.test(trimmed)) {
437
+ skipping = false;
438
+ skipUntilMcpHint = false;
439
+ }
440
+ continue;
441
+ }
442
+ if (!trimmed) {
443
+ skipping = false;
444
+ continue;
445
+ }
446
+ if (
447
+ /^(?:[-*]\s+|#{1,6}\s+|More detail:|Use OpenViking MCP|Latest committed archive|Resume continuity|viking:\/\/)/i.test(trimmed) ||
448
+ /^\s{2,}\S/.test(line)
449
+ ) {
450
+ continue;
451
+ }
452
+ skipping = false;
453
+ }
454
+ out.push(line);
455
+ }
456
+
457
+ return out.join("\n");
458
+ }
459
+
460
+ export function sanitizeCapturedText(text) {
461
+ let value = String(text || "");
462
+ value = value
463
+ .replace(/\u0000/g, "")
464
+ .replace(/<openviking-context\b[^>]*>[\s\S]*?<\/openviking-context>/gi, " ")
465
+ .replace(/<relevant-memor(?:y|ies)\b[^>]*>[\s\S]*?<\/relevant-memor(?:y|ies)>/gi, " ")
466
+ .replace(/^\s*Sender\s*\([^)]+\)\s*```[\s\S]*?```\s*/gim, " ")
467
+ .replace(/^\s*Conversation (?:metadata|info):\s*```[\s\S]*?```\s*/gim, " ")
468
+ .replace(/^\s*\[?\d{4}-\d{2}-\d{2}[T ][^\]\n]{3,80}\]?\s*/gm, "")
469
+ .replace(/^\s*\d{10,13}\s+/gm, "");
470
+ value = stripMetadataFences(value);
471
+ value = stripInjectedDigestBlocks(value);
472
+ value = value.replace(/^\s*More detail: use the OpenViking MCP .*$/gim, " ");
473
+ return value
474
+ .replace(/[ \t]+\n/g, "\n")
475
+ .replace(/\n{3,}/g, "\n\n")
476
+ .trim();
477
+ }
478
+
479
+ function hasEnoughSignal(text) {
480
+ const cjk = text.match(/[\u3400-\u9fff]/g)?.length || 0;
481
+ const alnum = text.match(/[a-z0-9]/gi)?.length || 0;
482
+ return cjk >= 4 || alnum >= 6 || text.length >= 12;
483
+ }
484
+
485
+ function isPunctuationOnly(text) {
486
+ return !/[a-z0-9\u3400-\u9fff]/i.test(text);
487
+ }
488
+
489
+ export function shouldCaptureText(text, role, cfg = {}) {
490
+ const maxLength = cfg.captureMaxLength || 24000;
491
+ const sanitized = sanitizeCapturedText(text);
492
+ if (!sanitized) return { shouldCapture: false, reason: "empty", text: "" };
493
+
494
+ const capped = truncateCaptureText(sanitized, maxLength);
495
+ const compact = oneLine(capped);
496
+ const isToolSummary = /^\[tool-(?:call|result)\b/i.test(compact);
497
+
498
+ if (!isToolSummary && role === "user" && SLASH_COMMAND_RE.test(compact)) {
499
+ return { shouldCapture: false, reason: "slash_command", text: "" };
500
+ }
501
+ if (!isToolSummary && ACK_RE.test(compact)) {
502
+ return { shouldCapture: false, reason: "ack", text: "" };
503
+ }
504
+ if (!isToolSummary && isPunctuationOnly(compact)) {
505
+ return { shouldCapture: false, reason: "punctuation", text: "" };
506
+ }
507
+ if (!isToolSummary && !hasEnoughSignal(compact)) {
508
+ return { shouldCapture: false, reason: "too_short", text: "" };
509
+ }
510
+ if (/^\[openviking-memory\]/i.test(compact)) {
511
+ return { shouldCapture: false, reason: "plugin_status", text: "" };
512
+ }
513
+
514
+ return { shouldCapture: true, reason: "ok", text: capped };
515
+ }