@volter/supercode-interchange 0.1.1 → 0.1.3

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.
Files changed (44) hide show
  1. package/README.md +88 -0
  2. package/hermes-store.mjs +5 -2
  3. package/message.mjs +85 -0
  4. package/native-json.mjs +94 -7
  5. package/package.json +16 -3
  6. package/session-claude-append.mjs +107 -0
  7. package/session-claude-content.mjs +209 -0
  8. package/session-claude-files.mjs +78 -0
  9. package/session-claude-graph.mjs +285 -0
  10. package/session-claude-index.mjs +173 -0
  11. package/session-claude.mjs +73 -0
  12. package/session-codec-utils.mjs +115 -0
  13. package/session-codecs.d.ts +64 -0
  14. package/session-codecs.mjs +19 -0
  15. package/session-codex-context.mjs +68 -0
  16. package/session-codex-display-items.mjs +89 -0
  17. package/session-codex-display.mjs +81 -0
  18. package/session-codex-items.mjs +117 -0
  19. package/session-codex-user-history.mjs +62 -0
  20. package/session-codex.mjs +135 -0
  21. package/session-display.mjs +135 -0
  22. package/session-document.mjs +19 -0
  23. package/session-file.mjs +27 -0
  24. package/session-follower.mjs +181 -0
  25. package/session-gemini.mjs +93 -0
  26. package/session-goose.mjs +139 -0
  27. package/session-grok.mjs +72 -0
  28. package/session-hermes.mjs +116 -0
  29. package/session-lines.mjs +20 -0
  30. package/session-model.d.ts +114 -0
  31. package/session-model.mjs +70 -0
  32. package/session-native-residue.mjs +109 -0
  33. package/session-native.mjs +62 -0
  34. package/session-opencode-content.mjs +115 -0
  35. package/session-opencode-store.mjs +101 -0
  36. package/session-opencode.mjs +115 -0
  37. package/session-path.mjs +129 -0
  38. package/session-pi-content.mjs +129 -0
  39. package/session-pi.mjs +86 -0
  40. package/session-read.d.ts +12 -0
  41. package/session-read.mjs +104 -0
  42. package/session-source.mjs +30 -0
  43. package/session-time.mjs +46 -0
  44. package/session-views.mjs +171 -0
@@ -0,0 +1,46 @@
1
+ const min = -(1n << 63n), max = (1n << 63n) - 1n;
2
+ const floorDiv = (value, divisor) => value < 0n ? (value - divisor + 1n) / divisor : value / divisor;
3
+
4
+ /** The native f64-to-i64 cast saturates, including infinities and NaN. */
5
+ function milliseconds(seconds, rounded) {
6
+ let value = seconds * 1000;
7
+ if (Number.isNaN(value)) return 0n;
8
+ if (rounded && Number.isFinite(value)) {
9
+ const whole = Math.trunc(value);
10
+ value = Math.abs(value - whole) >= 0.5 ? whole + Math.sign(value) : whole;
11
+ }
12
+ if (value >= Number(max)) return max;
13
+ if (value <= Number(min)) return min;
14
+ return BigInt(Math.trunc(value));
15
+ }
16
+
17
+ function civil(seconds, sub) {
18
+ const days = floorDiv(seconds, 86400n), sod = seconds - days * 86400n;
19
+ const z = days + 719468n, era = floorDiv(z, 146097n), doe = z - era * 146097n;
20
+ const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
21
+ let year = yoe + era * 400n;
22
+ const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n), mp = (5n * doy + 2n) / 153n;
23
+ const day = doy - (153n * mp + 2n) / 5n + 1n, month = mp < 10n ? mp + 3n : mp - 9n;
24
+ if (month <= 2n) year++;
25
+ const yearText = year < 0n ? `-${String(-year).padStart(3, '0')}` : String(year).padStart(4, '0');
26
+ const pad = (value, width = 2) => String(value).padStart(width, '0');
27
+ return `${yearText}-${pad(month)}-${pad(day)}T${pad(sod / 3600n)}:${pad(sod % 3600n / 60n)}:${pad(sod % 60n)}.${pad(sub, 3)}Z`;
28
+ }
29
+
30
+ /** Binding lifecycle epochs round milliseconds and retain pre-epoch dates. */
31
+ export function bindingEpoch(seconds) {
32
+ const ms = milliseconds(seconds, true), secs = floorDiv(ms, 1000n);
33
+ return civil(secs, ms - secs * 1000n);
34
+ }
35
+
36
+ /** Message sidecar epochs truncate milliseconds and clamp negative seconds,
37
+ * retaining the native helper's Euclidean sub-second remainder. */
38
+ export function messageEpoch(seconds) {
39
+ return messageMilliseconds(milliseconds(seconds, false));
40
+ }
41
+
42
+ /** Exact signed native millisecond clock, without a floating-point round trip. */
43
+ export function messageMilliseconds(ms) {
44
+ const secs = floorDiv(ms, 1000n);
45
+ return civil(secs < 0n ? 0n : secs, ms - secs * 1000n);
46
+ }
@@ -0,0 +1,171 @@
1
+ // Read projections preserve the internal transcript and its source records.
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { messageJson } from './message.mjs';
4
+ import { cloneNativeJson, nativeObjectKeys, parseNativeJson } from './native-json.mjs';
5
+ import { nativeTrim, truncateScalars } from './text.mjs';
6
+
7
+ function truncate(text, count) {
8
+ const prefix = truncateScalars(text, count);
9
+ return prefix.length < text.length ? `${prefix}\n…` : text;
10
+ }
11
+
12
+ function boundStrings(value, count) {
13
+ if (typeof value === 'string') return truncate(value, count);
14
+ if (value != null && typeof value === 'object') {
15
+ for (const key of nativeObjectKeys(value)) value[key] = boundStrings(value[key], count);
16
+ }
17
+ return value;
18
+ }
19
+
20
+ /** Mutates this display view only, matching bound_session_view. Raw/source
21
+ * records and total imported count are untouched; subagents are not bounded. */
22
+ export function boundSessionView(session, messageLimit = null, maxMessageChars = null) {
23
+ if (messageLimit != null && session.messages.length > messageLimit) session.messages.splice(0, session.messages.length - messageLimit);
24
+ if (maxMessageChars == null) return;
25
+ for (const message of session.messages) {
26
+ if (message.content != null) message.content = truncate(message.content, maxMessageChars);
27
+ if (message.content_parts != null) boundStrings(message.content_parts, maxMessageChars);
28
+ for (const call of message.tool_calls ?? []) call.function.arguments = truncate(call.function.arguments, maxMessageChars);
29
+ for (const key of Object.keys(message.metadata)) message.metadata[key] = truncate(message.metadata[key], maxMessageChars);
30
+ }
31
+ }
32
+
33
+ export function messageHasContent(message) {
34
+ return (message.content != null && nativeTrim(message.content) !== '') || (message.content_parts?.length ?? 0) > 0;
35
+ }
36
+
37
+ export function messageText(message) {
38
+ if (message.content != null) return message.content;
39
+ return (message.content_parts ?? []).filter((part) => typeof part?.text === 'string').map((part) => part.text).join('\n');
40
+ }
41
+
42
+ export function normalizedItemCount(messages) {
43
+ let count = 0;
44
+ for (const message of messages) {
45
+ if (['assistant', 'user', 'tool'].includes(message.role) && messageHasContent(message)) count++;
46
+ count += message.tool_calls?.length ?? 0;
47
+ }
48
+ return count;
49
+ }
50
+
51
+ export function projectedMessageWindow(total, options) {
52
+ if (options.message_tail != null) return [Math.max(0, total - options.message_tail), total];
53
+ const offset = Math.min(options.message_offset ?? 0, total);
54
+ return [offset, options.message_limit == null ? total : Math.min(offset + options.message_limit, total)];
55
+ }
56
+
57
+ export function projectInlineMedia(message, options) {
58
+ const result = cloneNativeJson(message);
59
+ if (!Array.isArray(result.content)) return result;
60
+ for (let at = 0; at < result.content.length; at++) {
61
+ const part = result.content[at];
62
+ const url = part?.image_url?.url;
63
+ if (typeof url !== 'string' || !url.startsWith('data:')) continue;
64
+ const rest = url.slice(5), separator = rest.indexOf(';base64,');
65
+ if (separator < 0) continue;
66
+ const mediaType = rest.slice(0, separator), encoded = rest.slice(separator + 8);
67
+ const encodedBytes = Buffer.byteLength(encoded), padding = Number(encoded.endsWith('=')) + Number(encoded.endsWith('=='));
68
+ const decodedBytes = Math.max(0, Math.floor(encodedBytes * 3 / 4) - padding);
69
+ if (options.inline_media !== 'metadata' && (options.max_inline_media_bytes == null || decodedBytes <= options.max_inline_media_bytes)) continue;
70
+ result.content[at] = { type: 'media_reference', media_type: mediaType, encoding: 'base64', encoded_bytes: encodedBytes, decoded_bytes: decodedBytes, omitted: true };
71
+ }
72
+ return result;
73
+ }
74
+
75
+ export function projectedSessionSummary(session, options) {
76
+ let first = null, last = null, firstAssistant = null, lastAssistant = null, final = null;
77
+ for (const message of session.messages) {
78
+ if (message.role !== 'system') final = message;
79
+ if (!['assistant', 'user'].includes(message.role) || !messageHasContent(message)) continue;
80
+ first ??= message; last = message;
81
+ if (message.role === 'assistant') { firstAssistant ??= message; lastAssistant = message; }
82
+ }
83
+ const project = (message) => message == null ? null : projectInlineMedia(messageJson(message), options);
84
+ return {
85
+ end_of_turn: final != null && final.role === 'assistant' && messageHasContent(final) && (final.tool_calls?.length ?? 0) === 0 && final.metadata.phase !== 'commentary',
86
+ first_assistant_message: project(firstAssistant), first_message: project(first),
87
+ last_assistant_message: project(lastAssistant), last_assistant_text: lastAssistant == null ? '' : messageText(lastAssistant), last_message: project(last),
88
+ };
89
+ }
90
+
91
+ const string = (value) => typeof value === 'string' ? value : null;
92
+ const has = (value, key) => value != null && typeof value === 'object' && !Array.isArray(value) && Object.hasOwn(value, key);
93
+ const payloadFor = (record) => has(record, 'payload') ? record.payload : record;
94
+ function parsedRecord(line) { try { return parseNativeJson(line); } catch { return null; } }
95
+
96
+ /** Only recorded facts. Current configuration and the reader's executable
97
+ * never fill unknown historical fields. */
98
+ export function recordedSessionProfile(session) {
99
+ const { meta } = session;
100
+ let model = session.messages.findLast((message) => has(message.metadata, 'model'))?.metadata.model ?? meta.model;
101
+ let cwd = meta.cwd, version = null, approval = null, sandbox = null;
102
+ for (const header of meta.codex_headers) {
103
+ const payload = payloadFor(header);
104
+ version = string(payload?.cli_version) ?? version;
105
+ model = string(payload?.model) ?? model;
106
+ cwd = string(payload?.cwd) ?? cwd;
107
+ if (has(payload, 'approval_policy')) approval = cloneNativeJson(payload.approval_policy);
108
+ if (has(payload, 'sandbox_policy')) sandbox = cloneNativeJson(payload.sandbox_policy);
109
+ }
110
+ if (meta.source === 'claude_code') {
111
+ for (let at = session.raw.length - 1; at >= 0; at--) {
112
+ const recorded = string(parsedRecord(session.raw[at])?.version);
113
+ if (recorded != null) { version = recorded; break; }
114
+ }
115
+ }
116
+ return {
117
+ worker: { harness: meta.source === 'claude_code' ? 'claude-code' : meta.source, model, cwd, binary: { version } },
118
+ permissions: { native: { approval, sandbox } }, source: 'native-session',
119
+ };
120
+ }
121
+
122
+ export function recordedSessionProfileHistory(session) {
123
+ if (!['codex', 'claude_code'].includes(session.meta.source)) return [];
124
+ const profile = {
125
+ worker: { harness: session.meta.source === 'codex' ? 'codex' : 'claude-code', model: null, cwd: null, binary: { version: null } },
126
+ permissions: { native: { approval: null, sandbox: null } }, source: 'native-session',
127
+ };
128
+ const records = session.meta.source === 'codex' ? session.meta.codex_headers : session.raw;
129
+ const history = [];
130
+ for (let index = 0; index < records.length; index++) {
131
+ const record = session.meta.source === 'codex' ? records[index] : parsedRecord(records[index]);
132
+ if (record == null) continue;
133
+ const before = cloneNativeJson(profile), payload = payloadFor(record);
134
+ for (const key of ['cwd', 'model']) {
135
+ if (typeof payload?.[key] === 'string') profile.worker[key] = payload[key];
136
+ }
137
+ const version = string(has(payload, 'cli_version') ? payload.cli_version : record?.version);
138
+ if (version != null) profile.worker.binary.version = version;
139
+ if (typeof record?.message?.model === 'string') profile.worker.model = record.message.model;
140
+ for (const [key, target] of [['approval_policy', 'approval'], ['sandbox_policy', 'sandbox']]) {
141
+ if (has(payload, key)) profile.permissions.native[target] = cloneNativeJson(payload[key]);
142
+ }
143
+ if (!isDeepStrictEqual(before, profile)) history.push({ native_key: `profile-record:${index}`, at: string(record?.timestamp), profile: cloneNativeJson(profile) });
144
+ }
145
+ return history;
146
+ }
147
+
148
+ export function projectedSessionJson(session, options) {
149
+ const { meta } = session, [offset, end] = projectedMessageWindow(session.messages.length, options);
150
+ const childOptions = { ...options, message_limit: null, message_offset: null, message_tail: null };
151
+ return {
152
+ source: meta.source, session_id: meta.session_id, ended_at: meta.ended_at, end_reason: meta.end_reason, model: meta.model,
153
+ resolved_profile: recordedSessionProfile(session), resolved_profile_history: recordedSessionProfileHistory(session),
154
+ cwd: meta.cwd, system_prompt: meta.system_prompt, agent_id: meta.agent_id, parent_tool_use_id: meta.parent_tool_use_id,
155
+ lineage: cloneNativeJson(meta.lineage), messages: session.messages.slice(offset, end).map((message) => projectInlineMedia(messageJson(message), options)),
156
+ subagents: options.include_subagents === false ? [] : session.subagents.map((child) => projectedSessionJson(child, childOptions)),
157
+ raw_record_count: session.raw.length, parse_error_lines: session.parse_error_lines,
158
+ };
159
+ }
160
+
161
+ export function projectedSessionResult(session, options) {
162
+ const total = session.messages.length, [offset, end] = projectedMessageWindow(total, options);
163
+ return {
164
+ session: projectedSessionJson(session, options), summary: projectedSessionSummary(session, options),
165
+ window: {
166
+ has_more: offset > 0 || end < total, has_newer: end < total, has_older: offset > 0,
167
+ newer_items: normalizedItemCount(session.messages.slice(end)), offset,
168
+ older_items: normalizedItemCount(session.messages.slice(0, offset)), returned: end - offset, total_messages: total,
169
+ },
170
+ };
171
+ }