@volter/supercode-interchange 0.1.22 → 0.1.24
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/LICENSE-diffy-MIT +23 -0
- package/README.md +422 -2
- package/channel-records.mjs +71 -0
- package/channel-session-counts.mjs +81 -0
- package/channels-read.mjs +90 -0
- package/corpus-audit-records.mjs +192 -0
- package/corpus-audit-render.mjs +47 -0
- package/corpus-audit-schema.mjs +89 -0
- package/corpus-audit.d.ts +16 -0
- package/corpus-audit.mjs +132 -0
- package/file-io.mjs +21 -2
- package/harness-settings.mjs +19 -0
- package/hermes-fire-links.mjs +135 -0
- package/hermes-fires.mjs +106 -0
- package/hermes-read-home.d.ts +3 -0
- package/hermes-read-home.mjs +235 -0
- package/hermes-read-store.mjs +49 -0
- package/hermes-read-validation.mjs +170 -0
- package/hermes-read-workflow.mjs +164 -0
- package/hermes-read-yaml.mjs +68 -0
- package/hermes-store.mjs +1 -1
- package/homes.mjs +2 -2
- package/index.d.ts +14 -1
- package/index.mjs +1 -1
- package/inspection-builtin-tools.json +261 -0
- package/inspection-config-fields.json +765 -0
- package/inspection-schemas.d.ts +4 -0
- package/inspection-schemas.mjs +117 -0
- package/job-notepad-read.mjs +97 -0
- package/jobs-control.mjs +250 -0
- package/jobs-read.mjs +198 -0
- package/jsonl.mjs +4 -3
- package/memory-show.mjs +187 -0
- package/metadata.mjs +45 -3
- package/native-json.mjs +46 -3
- package/ontology-contracts.d.ts +5 -0
- package/ontology-contracts.mjs +29 -0
- package/ontology-operations.json +198 -0
- package/ontology-schemas.json +7946 -0
- package/openclaw-jobs-read.mjs +117 -0
- package/openclaw-profile-home.mjs +142 -0
- package/orchestrator-read-records.mjs +112 -0
- package/orchestrator-store.mjs +21 -9
- package/package.json +59 -4
- package/parity-ledger.json +6984 -0
- package/parity-orchestration.json +5538 -0
- package/parity-report.d.ts +7 -0
- package/parity-report.mjs +46 -0
- package/preset-profiles.mjs +27 -0
- package/profile-command.mjs +32 -0
- package/profile-orchestrator-door.mjs +104 -0
- package/profile-paths.mjs +25 -0
- package/profiles-control.mjs +98 -0
- package/profiles-read.mjs +119 -0
- package/reduction-attribution.mjs +54 -0
- package/reduction-diff.mjs +165 -0
- package/reduction-log.mjs +177 -0
- package/reduction-view.mjs +145 -0
- package/retained-projection.mjs +9 -0
- package/routes-read.mjs +103 -0
- package/runs-read.mjs +224 -0
- package/session-activity.d.ts +23 -0
- package/session-activity.mjs +124 -0
- package/session-append-scan.mjs +27 -17
- package/session-branch-handoff.mjs +115 -0
- package/session-claude-graph.mjs +29 -7
- package/session-claude-index.mjs +732 -114
- package/session-claude-runtime.d.ts +28 -0
- package/session-claude-runtime.mjs +171 -6
- package/session-claude-scan-store.mjs +164 -0
- package/session-claude.mjs +9 -0
- package/session-codex-activity.d.ts +6 -0
- package/session-codex-activity.mjs +120 -0
- package/session-codex-window.mjs +24 -4
- package/session-config-scan.mjs +141 -0
- package/session-convert.d.ts +33 -0
- package/session-convert.mjs +179 -0
- package/session-emission-version.json +1 -0
- package/session-file.mjs +11 -1
- package/session-follower.mjs +17 -5
- package/session-fresh-id.mjs +15 -0
- package/session-hermes-import.mjs +61 -0
- package/session-hermes.mjs +1 -1
- package/session-import.mjs +37 -0
- package/session-inspect.d.ts +18 -0
- package/session-inspect.mjs +122 -0
- package/session-machine-tree.d.ts +33 -0
- package/session-machine-tree.mjs +274 -0
- package/session-materialize-files.mjs +78 -0
- package/session-materialize-opencode.mjs +137 -0
- package/session-materialize.mjs +186 -0
- package/session-message.mjs +85 -0
- package/session-model.d.ts +9 -0
- package/session-model.mjs +1 -0
- package/session-opencode-store.mjs +39 -3
- package/session-path.mjs +6 -3
- package/session-pi-content.mjs +7 -2
- package/session-read.d.ts +134 -2
- package/session-read.mjs +86 -9
- package/session-recorded-config.mjs +3 -2
- package/session-records.mjs +12 -6
- package/session-residue-store.mjs +90 -0
- package/session-residue.mjs +150 -0
- package/session-resume-instructions.mjs +1 -1
- package/session-saved-read.mjs +105 -0
- package/session-transfer.mjs +141 -0
- package/session-views.mjs +1 -0
- package/session-write-claude.mjs +135 -0
- package/session-write-codex.mjs +146 -0
- package/session-write-gemini.mjs +77 -0
- package/session-write-goose.mjs +99 -0
- package/session-write-grok.mjs +70 -0
- package/session-write-native.mjs +41 -0
- package/session-write-opencode.mjs +197 -0
- package/session-write-pi.mjs +130 -0
- package/session-write-residue.mjs +19 -0
- package/session-write-utils.mjs +77 -0
- package/session-write.d.ts +10 -0
- package/session-write.mjs +37 -0
- package/skill-toml.mjs +80 -0
- package/skills-control.mjs +193 -0
- package/skills-list.mjs +237 -0
- package/source-files.mjs +12 -2
- package/sqlite-reader.mjs +134 -18
- package/support-registry.json +402 -0
- package/support-report.mjs +69 -0
- package/trigger-records.mjs +77 -0
- package/triggers-read.mjs +67 -0
- package/workflow-read.mjs +165 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Artifacts retain native source recovery and portable source segments. Only
|
|
2
|
+
// export-to-Hermes invokes a vendor writer, through its own import command.
|
|
3
|
+
import { readFile, lstat } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, extname, resolve } from 'node:path';
|
|
5
|
+
import { cloneNativeJson } from './native-json.mjs';
|
|
6
|
+
import { decodeSessionUtf8 } from './session-file.mjs';
|
|
7
|
+
import { loadSessionLocator } from './session-path.mjs';
|
|
8
|
+
import { toSessionJsonl, toSessionJsonlSpliced, toNativeJsonl } from './session-write.mjs';
|
|
9
|
+
import { createResidueStore } from './session-residue-store.mjs';
|
|
10
|
+
import { restoreResidue } from './session-residue.mjs';
|
|
11
|
+
import { importIntoHermes } from './session-hermes-import.mjs';
|
|
12
|
+
|
|
13
|
+
const targetFormats = Object.freeze({ 'claude-code': 'claude_code', codex: 'codex', opencode: 'opencode', pi: 'pi',
|
|
14
|
+
grok: 'grok', gemini: 'gemini', goose: 'goose', hermes: 'codex' });
|
|
15
|
+
const invalid = message => { throw Object.assign(new Error(message), { rpcCode: -32602 }); };
|
|
16
|
+
const object = (value, name) => {
|
|
17
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) invalid(`${name} must be an object`);
|
|
18
|
+
return value;
|
|
19
|
+
};
|
|
20
|
+
const string = (value, name) => { if (typeof value !== 'string') invalid(`${name} must be a string`); return value; };
|
|
21
|
+
function struct(value, fields, name) {
|
|
22
|
+
if (!Array.isArray(value)) return object(value, name);
|
|
23
|
+
if (value.length !== fields.length) invalid(`${name} positional params require ${fields.join(', ')}`);
|
|
24
|
+
return Object.fromEntries(fields.map((field, index) => [field, value[index]]));
|
|
25
|
+
}
|
|
26
|
+
export function transferTarget(value) {
|
|
27
|
+
// Externally tagged unit variants also admit {"target-name": null}.
|
|
28
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
29
|
+
const keys = Object.keys(value);
|
|
30
|
+
if (keys.length !== 1 || value[keys[0]] !== null) invalid('target_harness must be a transfer format');
|
|
31
|
+
value = keys[0];
|
|
32
|
+
}
|
|
33
|
+
const requested = string(value, 'target_harness'), target = requested === 'open-code' ? 'opencode' : requested;
|
|
34
|
+
if (!Object.hasOwn(targetFormats, target)) invalid(`unknown transfer target: ${requested}`);
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseTransferLocator(value) {
|
|
39
|
+
const supplied = struct(value, ['harness', 'session_id', 'storage'], 'locator'), storage = object(supplied.storage, 'locator.storage');
|
|
40
|
+
const locator = { harness: string(supplied.harness, 'locator.harness'), session_id: string(supplied.session_id, 'locator.session_id'),
|
|
41
|
+
storage: { kind: storage.kind, path: string(storage.path, 'locator.storage.path') } };
|
|
42
|
+
if (!['file', 'sqlite'].includes(storage.kind)) invalid('locator.storage.kind must be file or sqlite');
|
|
43
|
+
if (storage.kind === 'sqlite') locator.storage.selector = string(storage.selector, 'locator.storage.selector');
|
|
44
|
+
// Native service admission decodes a JSON Value, so unknown fields are
|
|
45
|
+
// ignored and repeated JSON members have already settled to their last value.
|
|
46
|
+
return locator;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseSessionTransferParams(params) {
|
|
50
|
+
params = struct(params, ['locator', 'target_harness'], 'session transfer params');
|
|
51
|
+
return { locator: parseTransferLocator(params.locator), target: transferTarget(params.target_harness) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function sanitizeArtifactFilename(value) {
|
|
55
|
+
return [...value].map(char => /^[A-Za-z0-9_-]$/u.test(char) ? char : '-').join('').replace(/^-+|-+$/gu, '').slice(0, 100) || 'session';
|
|
56
|
+
}
|
|
57
|
+
const appended = session => session.imported_message_count != null && session.imported_message_count < session.messages.length;
|
|
58
|
+
const verbatim = session => session.raw.join('\n') + (session.raw_trailing_newline ? '\n' : '');
|
|
59
|
+
|
|
60
|
+
async function appendGrokBundle(locator, prefix, role, files, { cwd = process.cwd() } = {}) {
|
|
61
|
+
const primary = locator.storage.path;
|
|
62
|
+
if (basename(primary) !== 'chat_history.jsonl') throw new Error(`Grok bundle locator must name chat_history.jsonl, got ${primary}`);
|
|
63
|
+
for (const name of ['summary.json', 'updates.jsonl']) {
|
|
64
|
+
const path = resolve(cwd, dirname(primary), name); let metadata;
|
|
65
|
+
try { metadata = await lstat(path); } catch (error) { if (error.code === 'ENOENT') continue; throw error; }
|
|
66
|
+
if (metadata.isSymbolicLink() || !metadata.isFile()) throw new Error(`refusing non-regular Grok bundle member ${path}`);
|
|
67
|
+
let content;
|
|
68
|
+
try { content = decodeSessionUtf8(await readFile(path)); }
|
|
69
|
+
catch (error) { throw new Error(`Grok bundle member ${path} is not representable as UTF-8: ${error.message}`, { cause: error }); }
|
|
70
|
+
files.push({ path: prefix + name, content, role });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Construct the complete artifact; targetSessionId is the native handoff/fork hook. */
|
|
75
|
+
export async function sessionArtifact(locator, session, target, context = {}, targetSessionId = null) {
|
|
76
|
+
const format = targetFormats[target]; if (format == null) throw new TypeError(`unknown transfer target: ${target}`);
|
|
77
|
+
const diagonal = format === session.meta.source, hasAppended = appended(session), store = createResidueStore(context);
|
|
78
|
+
await store.store(session);
|
|
79
|
+
let content, restoration = null;
|
|
80
|
+
if (targetSessionId != null) {
|
|
81
|
+
if (diagonal && format !== 'opencode') content = toSessionJsonlSpliced(session, format, targetSessionId);
|
|
82
|
+
else { const rewritten = cloneNativeJson(session); rewritten.meta.session_id = targetSessionId; content = toSessionJsonl(rewritten, format); }
|
|
83
|
+
} else if (diagonal && session.raw_is_verbatim && !hasAppended) content = verbatim(session);
|
|
84
|
+
else if (diagonal) content = toSessionJsonlSpliced(session, format);
|
|
85
|
+
else {
|
|
86
|
+
const restored = await restoreResidue(session, format, store.lookup);
|
|
87
|
+
if (restored == null) content = toSessionJsonl(session, format);
|
|
88
|
+
else { content = restored.content; restoration = restored.report; }
|
|
89
|
+
}
|
|
90
|
+
const stem = sanitizeArtifactFilename(targetSessionId ?? session.meta.session_id ?? locator.session_id);
|
|
91
|
+
const filename = diagonal && target === 'grok' ? 'chat_history.jsonl' : target === 'goose' ? `${stem}.goose.json` : `${stem}.${target}.jsonl`;
|
|
92
|
+
const files = [{ path: filename, content, role: 'primary' }];
|
|
93
|
+
if (target === 'claude-code') {
|
|
94
|
+
const bundleStem = basename(filename, extname(filename)), paths = new Set();
|
|
95
|
+
for (const [index, child] of session.subagents.entries()) {
|
|
96
|
+
const agentId = child.meta.agent_id == null ? `subagent-${index + 1}` : sanitizeArtifactFilename(child.meta.agent_id.replace(/^agent-/u, ''));
|
|
97
|
+
let childContent;
|
|
98
|
+
if (targetSessionId == null && child.meta.source === 'claude_code' && child.raw_is_verbatim && !appended(child)) childContent = verbatim(child);
|
|
99
|
+
else if (child.meta.source === 'claude_code') childContent = toSessionJsonlSpliced(child, 'claude_code', targetSessionId);
|
|
100
|
+
else {
|
|
101
|
+
const rewritten = cloneNativeJson(child); if (targetSessionId != null) rewritten.meta.session_id = targetSessionId;
|
|
102
|
+
childContent = toSessionJsonl(rewritten, 'claude_code');
|
|
103
|
+
}
|
|
104
|
+
const path = `${bundleStem}/subagents/agent-${agentId}.jsonl`;
|
|
105
|
+
if (paths.has(path)) throw new Error(`Claude subagent ids collide at artifact path \`${path}\``);
|
|
106
|
+
paths.add(path); files.push({ path, content: childContent, role: 'subagent' });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (diagonal && target === 'grok') await appendGrokBundle(locator, '', 'bundle', files, context);
|
|
110
|
+
if (!diagonal || !session.raw_is_verbatim) {
|
|
111
|
+
files.push({ path: 'recovery/source.supercode.jsonl', content: toNativeJsonl(session), role: 'source_recovery' });
|
|
112
|
+
for (const [index, child] of session.subagents.entries()) {
|
|
113
|
+
const id = child.meta.agent_id == null ? `subagent-${index + 1}` : sanitizeArtifactFilename(child.meta.agent_id);
|
|
114
|
+
files.push({ path: `recovery/subagents/${id}.supercode.jsonl`, content: toNativeJsonl(child), role: 'source_recovery' });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!diagonal && session.meta.source === 'grok') await appendGrokBundle(locator, 'recovery/grok/', 'source_recovery', files, context);
|
|
118
|
+
let fidelity, residue;
|
|
119
|
+
if (diagonal && targetSessionId == null && session.raw_is_verbatim && !hasAppended) { fidelity = 'byte_lossless'; residue = []; }
|
|
120
|
+
else if (diagonal && !(targetSessionId != null && target === 'opencode')) {
|
|
121
|
+
fidelity = 'value_lossless'; residue = [targetSessionId != null
|
|
122
|
+
? 'target identity was rewritten, so the artifact intentionally differs from source bytes'
|
|
123
|
+
: 'source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured'];
|
|
124
|
+
} else if (restoration?.rendered_messages === 0) {
|
|
125
|
+
fidelity = 'byte_lossless'; residue = [`restored verbatim from this conversation's ${target} source records in the residue store`];
|
|
126
|
+
} else if (restoration != null) {
|
|
127
|
+
fidelity = 'semantic'; residue = [`${restoration.restored_messages} of ${restoration.restored_messages + restoration.rendered_messages} messages restored verbatim from the residue store; the other ${restoration.rendered_messages} written by the ${target} writer`];
|
|
128
|
+
} else { fidelity = 'semantic'; residue = ['target schema has no portable slot for every source-native record and metadata field']; }
|
|
129
|
+
return { source_harness: locator.harness, target_harness: target, session_id: targetSessionId ?? session.meta.session_id,
|
|
130
|
+
content, suggested_filename: filename, files, fidelity, residue };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function transferSession(params, { exporting = false, ...context } = {}) {
|
|
134
|
+
context.signal?.throwIfAborted();
|
|
135
|
+
const { locator, target } = parseSessionTransferParams(params);
|
|
136
|
+
const loaded = { ...locator, storage: { ...locator.storage, path: resolve(context.cwd ?? process.cwd(), locator.storage.path) } };
|
|
137
|
+
const session = await loadSessionLocator(loaded, { ...context, fidelity: 'byte_lossless', includeSubagents: true });
|
|
138
|
+
context.signal?.throwIfAborted();
|
|
139
|
+
const artifact = await sessionArtifact(locator, session, target, context);
|
|
140
|
+
return exporting && target === 'hermes' ? { artifact, imported: await importIntoHermes(session, context) } : { artifact };
|
|
141
|
+
}
|
package/session-views.mjs
CHANGED
|
@@ -160,6 +160,7 @@ export function projectedSessionJson(session, options) {
|
|
|
160
160
|
const childOptions = { ...options, message_limit: null, message_offset: null, message_tail: null };
|
|
161
161
|
return {
|
|
162
162
|
source: meta.source, session_id: meta.session_id, ended_at: meta.ended_at, end_reason: meta.end_reason, model: meta.model,
|
|
163
|
+
...(meta.first_response ? { first_response: cloneNativeJson(meta.first_response) } : {}),
|
|
163
164
|
resolved_profile: recordedSessionProfile(session), resolved_profile_history: recordedSessionProfileHistory(session),
|
|
164
165
|
cwd: meta.cwd, system_prompt: meta.system_prompt, agent_id: meta.agent_id, parent_tool_use_id: meta.parent_tool_use_id,
|
|
165
166
|
lineage: cloneNativeJson(meta.lineage), messages: session.messages.slice(offset, end).map((message) => projectInlineMedia(messageJson(message), options)),
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { nativeInteger } from './native-json.mjs';
|
|
2
|
+
import { toolOutcome } from './message.mjs';
|
|
3
|
+
import { nativeTrim } from './text.mjs';
|
|
4
|
+
import { NATIVE_RESIDUE_KEY, NATIVE_RESIDUE_SUMMARY_KEY } from './session-native-residue.mjs';
|
|
5
|
+
import { nativeResidueEnvelope, nativeResidueSummary } from './session-write-residue.mjs';
|
|
6
|
+
import { SYNTH_TS, replayExcluded, sessionCwd, tryNativeJson, sourceMessageExtension, splicedPrefixLengths,
|
|
7
|
+
jsonLine, injectFirstJsonLine, synthUuid, messageTimestamp, parseDataUri, parsedJsonField } from './session-write-utils.mjs';
|
|
8
|
+
|
|
9
|
+
function imageBlock(url) {
|
|
10
|
+
const data = parseDataUri(url);
|
|
11
|
+
return { type: 'image', source: data == null ? { type: 'url', url }
|
|
12
|
+
: { type: 'base64', media_type: data.mimeType, data: data.data } };
|
|
13
|
+
}
|
|
14
|
+
function imageBlocks(parts) {
|
|
15
|
+
return (parts ?? []).filter(part => part?.type === 'image_url' && typeof part?.image_url?.url === 'string')
|
|
16
|
+
.map(part => imageBlock(part.image_url.url));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function userContent(message) {
|
|
20
|
+
if (message.content_parts == null) return message.content ?? '';
|
|
21
|
+
const blocks = [];
|
|
22
|
+
for (const part of message.content_parts) {
|
|
23
|
+
if (part?.type === 'text' && typeof part.text === 'string' && part.text !== '') blocks.push({ type: 'text', text: part.text });
|
|
24
|
+
else if (part?.type === 'image_url' && typeof part?.image_url?.url === 'string') blocks.push(imageBlock(part.image_url.url));
|
|
25
|
+
}
|
|
26
|
+
return blocks;
|
|
27
|
+
}
|
|
28
|
+
function toolResultContent(message) {
|
|
29
|
+
if (!message.content_parts?.length) return message.content ?? '';
|
|
30
|
+
return [...(message.content ? [{ type: 'text', text: message.content }] : []), ...imageBlocks(message.content_parts)];
|
|
31
|
+
}
|
|
32
|
+
function assistantContent(message) {
|
|
33
|
+
const blocks = [], metadata = message.metadata, thinking = tryNativeJson(metadata.thinking_blocks);
|
|
34
|
+
if (Array.isArray(thinking)) blocks.push(...thinking);
|
|
35
|
+
else {
|
|
36
|
+
if (metadata.thinking != null) {
|
|
37
|
+
const block = { type: 'thinking', thinking: metadata.thinking };
|
|
38
|
+
if (metadata.thinking_signature != null) block.signature = metadata.thinking_signature;
|
|
39
|
+
blocks.push(block);
|
|
40
|
+
}
|
|
41
|
+
if (metadata.redacted_thinking != null) blocks.push({ type: 'redacted_thinking', data: metadata.redacted_thinking });
|
|
42
|
+
}
|
|
43
|
+
if (message.content) blocks.push({ type: 'text', text: message.content });
|
|
44
|
+
blocks.push(...imageBlocks(message.content_parts));
|
|
45
|
+
for (const call of message.tool_calls ?? []) {
|
|
46
|
+
const block = { type: 'tool_use', id: call.id, name: call.function.name }, args = nativeTrim(call.function.arguments);
|
|
47
|
+
if (args === '') block.input = {}; else parsedJsonField(block, 'input', args, {});
|
|
48
|
+
blocks.push(block);
|
|
49
|
+
}
|
|
50
|
+
return blocks;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function claudeRecords(session, messages, sessionId, cwd, parent = null, seedIds = new Set()) {
|
|
54
|
+
const used = new Set(seedIds); let counter = 1n, text = '';
|
|
55
|
+
const messageUuid = message => {
|
|
56
|
+
for (const key of ['claude_uuid', 'supercode_native_uuid']) {
|
|
57
|
+
const candidate = message.metadata[key];
|
|
58
|
+
if (candidate && !used.has(candidate)) { used.add(candidate); return candidate; }
|
|
59
|
+
}
|
|
60
|
+
for (;;) {
|
|
61
|
+
const candidate = synthUuid(counter++);
|
|
62
|
+
if (!used.has(candidate)) { used.add(candidate); return candidate; }
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
for (const message of messages) {
|
|
66
|
+
if (replayExcluded(message)) continue;
|
|
67
|
+
let value, uuid;
|
|
68
|
+
if (message.role === 'system') {
|
|
69
|
+
const content = message.content ?? ''; if (!nativeTrim(content)) continue;
|
|
70
|
+
uuid = messageUuid(message);
|
|
71
|
+
value = { parentUuid: parent, type: 'system', subtype: message.metadata.systemSubtype ?? 'local_command', content,
|
|
72
|
+
uuid, sessionId, cwd, timestamp: messageTimestamp(message) };
|
|
73
|
+
} else {
|
|
74
|
+
uuid = messageUuid(message);
|
|
75
|
+
let native;
|
|
76
|
+
switch (message.role) {
|
|
77
|
+
case 'user': native = { role: 'user', content: userContent(message) }; break;
|
|
78
|
+
case 'tool': native = { role: 'user', content: [{ type: 'tool_result', tool_use_id: message.tool_call_id ?? '',
|
|
79
|
+
content: toolResultContent(message) }] }; break;
|
|
80
|
+
case 'assistant':
|
|
81
|
+
native = { role: 'assistant', content: assistantContent(message) };
|
|
82
|
+
if (message.metadata.model != null || session.meta.model != null) native.model = message.metadata.model ?? session.meta.model;
|
|
83
|
+
break;
|
|
84
|
+
default: throw new TypeError(`unknown message role: ${message.role}`);
|
|
85
|
+
}
|
|
86
|
+
value = { parentUuid: parent, type: message.role === 'assistant' ? 'assistant' : 'user', message: native,
|
|
87
|
+
uuid, sessionId, cwd, timestamp: messageTimestamp(message) };
|
|
88
|
+
if (message.role === 'tool' && toolOutcome(message) === 'unknown') value._supercode_tool_outcome = 'unknown';
|
|
89
|
+
}
|
|
90
|
+
sourceMessageExtension(value, session.meta.source, message); text += jsonLine(value); parent = uuid;
|
|
91
|
+
}
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function residueLine(raw, sessionId, cwd) {
|
|
96
|
+
const value = tryNativeJson(raw);
|
|
97
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) return raw;
|
|
98
|
+
let changed = false;
|
|
99
|
+
for (const [key, current] of [['sessionId', sessionId], ['cwd', cwd]]) {
|
|
100
|
+
if (typeof value[key] === 'string' && value[key] !== current) { value[key] = current; changed = true; }
|
|
101
|
+
}
|
|
102
|
+
return changed ? jsonLine(value).slice(0, -1) : raw;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function toClaudeCodeJsonl(session) {
|
|
106
|
+
const sessionId = session.meta.session_id ?? synthUuid(0), cwd = sessionCwd(session);
|
|
107
|
+
let text = session.meta.lineage.claude_fork_context_ref_raw == null ? '' : session.meta.lineage.claude_fork_context_ref_raw + '\n';
|
|
108
|
+
text += claudeRecords(session, session.messages, sessionId, cwd);
|
|
109
|
+
if (session.meta.native_residue_source === 'claude_code') {
|
|
110
|
+
const at = entry => nativeInteger(entry, 'record_index') ?? ((1n << 64n) - 1n);
|
|
111
|
+
const records = [...session.meta.native_residue].sort((left, right) => at(left) < at(right) ? -1 : at(left) > at(right) ? 1 : 0);
|
|
112
|
+
for (const entry of records) if (typeof entry?.raw === 'string') text += residueLine(entry.raw, sessionId, cwd) + '\n';
|
|
113
|
+
} else {
|
|
114
|
+
const envelope = nativeResidueEnvelope(session.meta);
|
|
115
|
+
if (envelope != null) {
|
|
116
|
+
if (!text) text = jsonLine({ type: 'file-history-snapshot', messageId: synthUuid(1), snapshot: {}, sessionId, cwd, timestamp: SYNTH_TS });
|
|
117
|
+
text = injectFirstJsonLine(text, NATIVE_RESIDUE_SUMMARY_KEY, nativeResidueSummary(envelope));
|
|
118
|
+
text = injectFirstJsonLine(text, NATIVE_RESIDUE_KEY, envelope);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return text;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function toClaudeCodeJsonlSpliced(session, sessionId = null) {
|
|
125
|
+
const prefix = splicedPrefixLengths(session), used = new Set(); let text = '', parent = null;
|
|
126
|
+
for (const line of session.raw.slice(0, prefix.raw)) {
|
|
127
|
+
const value = tryNativeJson(line);
|
|
128
|
+
if (sessionId != null && value != null && typeof value === 'object' && !Array.isArray(value) && Object.hasOwn(value, 'sessionId')) {
|
|
129
|
+
value.sessionId = sessionId; text += jsonLine(value);
|
|
130
|
+
} else text += line + '\n';
|
|
131
|
+
if (typeof value?.uuid === 'string') { parent = value.uuid; used.add(value.uuid); }
|
|
132
|
+
}
|
|
133
|
+
return text + claudeRecords(session, session.messages.slice(prefix.messages), sessionId ?? session.meta.session_id ?? synthUuid(0),
|
|
134
|
+
sessionCwd(session), parent, used);
|
|
135
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { cloneNativeJson, nativeInteger, setNativeInteger } from './native-json.mjs';
|
|
2
|
+
import emitterVersion from './session-emission-version.json' with { type: 'json' };
|
|
3
|
+
import { nativeTrim } from './text.mjs';
|
|
4
|
+
import { NATIVE_RESIDUE_KEY, NATIVE_RESIDUE_SUMMARY_KEY } from './session-native-residue.mjs';
|
|
5
|
+
import { nativeResidueEnvelope, nativeResidueSummary } from './session-write-residue.mjs';
|
|
6
|
+
import { SYNTH_TS, replayExcluded, sessionCwd, tryNativeJson, sourceMessageExtension, splicedPrefixLengths,
|
|
7
|
+
jsonLine, synthUuid, messageTimestamp, parsedJsonField } from './session-write-utils.mjs';
|
|
8
|
+
|
|
9
|
+
function synthesizedHeader(session) {
|
|
10
|
+
const payload = { id: session.meta.session_id ?? synthUuid(0), timestamp: SYNTH_TS, cwd: sessionCwd(session),
|
|
11
|
+
originator: 'supercode', cli_version: emitterVersion, source: 'exec', thread_source: 'user', model_provider: 'openai' };
|
|
12
|
+
if (session.meta.system_prompt != null) payload.base_instructions = { text: session.meta.system_prompt };
|
|
13
|
+
if (session.meta.lineage.claude_fork_context_ref_raw != null) parsedJsonField(payload, 'claude_fork_context_ref', session.meta.lineage.claude_fork_context_ref_raw);
|
|
14
|
+
let text = jsonLine({ timestamp: SYNTH_TS, type: 'session_meta', payload });
|
|
15
|
+
if (session.meta.model != null) text += jsonLine({ timestamp: SYNTH_TS, type: 'turn_context',
|
|
16
|
+
payload: { model: session.meta.model, cwd: sessionCwd(session) } });
|
|
17
|
+
return text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function messageContent(textType, message) {
|
|
21
|
+
if (message.content_parts == null) return [{ type: textType, text: message.content ?? '' }];
|
|
22
|
+
const content = [];
|
|
23
|
+
for (const part of message.content_parts) {
|
|
24
|
+
if (part?.type === 'text' && typeof part.text === 'string' && part.text !== '') content.push({ type: textType, text: part.text });
|
|
25
|
+
else if (part?.type === 'image_url' && typeof part?.image_url?.url === 'string') content.push({ type: 'input_image', image_url: part.image_url.url });
|
|
26
|
+
}
|
|
27
|
+
return content;
|
|
28
|
+
}
|
|
29
|
+
function toolOutput(message) {
|
|
30
|
+
const content = message.content ?? '';
|
|
31
|
+
const images = (message.content_parts ?? []).filter(part => part?.type === 'image_url')
|
|
32
|
+
.map(part => part?.image_url?.url).filter(url => typeof url === 'string');
|
|
33
|
+
return !images.length ? content : [...(content ? [{ type: 'input_text', text: content }] : []),
|
|
34
|
+
...images.map(image_url => ({ type: 'input_image', image_url }))];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function codexRecords(session, messages, seedIds = new Set()) {
|
|
38
|
+
const used = new Set(seedIds), searchCalls = new Set(); let nextGroup = 0n, text = '';
|
|
39
|
+
const emit = (payload, message) => {
|
|
40
|
+
sourceMessageExtension(payload, session.meta.source, message);
|
|
41
|
+
text += jsonLine({ timestamp: messageTimestamp(message), type: 'response_item', payload });
|
|
42
|
+
};
|
|
43
|
+
const withTurn = (payload, turnId) => { if (turnId != null) payload.metadata = { turn_id: turnId }; return payload; };
|
|
44
|
+
const emitMessage = (message, role, textType, turnId, subtype = null) => {
|
|
45
|
+
const payload = withTurn({ type: 'message', role, content: messageContent(textType, message) }, turnId);
|
|
46
|
+
if (subtype != null) { payload.metadata ??= {}; payload.metadata.claude_system_subtype = subtype; }
|
|
47
|
+
emit(payload, message);
|
|
48
|
+
};
|
|
49
|
+
for (const message of messages) {
|
|
50
|
+
if (replayExcluded(message)) continue;
|
|
51
|
+
const realTurn = message.metadata.turn_id;
|
|
52
|
+
switch (message.role) {
|
|
53
|
+
case 'system': emitMessage(message, 'developer', 'input_text', realTurn, message.metadata.systemSubtype); break;
|
|
54
|
+
case 'user': emitMessage(message, 'user', 'input_text', realTurn); break;
|
|
55
|
+
case 'assistant': {
|
|
56
|
+
const calls = message.tool_calls ?? [];
|
|
57
|
+
let group = realTurn ?? null;
|
|
58
|
+
if (group != null && used.has(group)) {
|
|
59
|
+
let duplicate = 1n; while (used.has(`${realTurn}~dup${duplicate}`)) duplicate++;
|
|
60
|
+
group = `${realTurn}~dup${duplicate}`;
|
|
61
|
+
} else if (group == null && calls.length) {
|
|
62
|
+
do { group = `sc-grp-${nextGroup++}`; } while (used.has(group));
|
|
63
|
+
}
|
|
64
|
+
if (group != null) used.add(group);
|
|
65
|
+
if (message.content || message.content_parts != null || Object.hasOwn(message.metadata, 'empty_assistant_record')) {
|
|
66
|
+
emitMessage(message, 'assistant', 'output_text', group);
|
|
67
|
+
}
|
|
68
|
+
const custom = tryNativeJson(message.metadata.codex_custom_tool_call_ids);
|
|
69
|
+
const customIds = Array.isArray(custom) && custom.every(id => typeof id === 'string') ? custom : [];
|
|
70
|
+
for (const call of calls) {
|
|
71
|
+
let payload;
|
|
72
|
+
if (customIds.includes(call.id)) {
|
|
73
|
+
payload = { type: 'custom_tool_call', name: call.function.name };
|
|
74
|
+
const args = nativeTrim(call.function.arguments);
|
|
75
|
+
if (args === '') payload.input = {}; else parsedJsonField(payload, 'input', args, call.function.arguments);
|
|
76
|
+
payload.call_id = call.id;
|
|
77
|
+
} else if (call.function.name === 'tool_search') {
|
|
78
|
+
searchCalls.add(call.id);
|
|
79
|
+
payload = { type: 'tool_search_call', arguments: call.function.arguments, call_id: call.id };
|
|
80
|
+
} else payload = { type: 'function_call', name: call.function.name, arguments: call.function.arguments, call_id: call.id };
|
|
81
|
+
emit(withTurn(payload, group), message);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'tool': {
|
|
86
|
+
let payload;
|
|
87
|
+
if (message.tool_call_id != null && searchCalls.has(message.tool_call_id)) {
|
|
88
|
+
payload = { type: 'tool_search_output', call_id: message.tool_call_id };
|
|
89
|
+
parsedJsonField(payload, 'tools', message.content ?? '');
|
|
90
|
+
} else payload = { type: 'function_call_output', call_id: message.tool_call_id ?? '', output: toolOutput(message) };
|
|
91
|
+
emit(payload, message); break;
|
|
92
|
+
}
|
|
93
|
+
default: throw new TypeError(`unknown message role: ${message.role}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return text;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function patchSessionMeta(value, id) {
|
|
100
|
+
if (value?.type !== 'session_meta' || !Object.hasOwn(value, 'payload')) return;
|
|
101
|
+
if (value.payload === null) value.payload = {};
|
|
102
|
+
if (typeof value.payload !== 'object' || Array.isArray(value.payload)) throw new Error('Codex session_meta payload is not an object');
|
|
103
|
+
value.payload.id = id;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function toCodexJsonl(session) {
|
|
107
|
+
let text;
|
|
108
|
+
if (!session.meta.codex_headers.length) text = synthesizedHeader(session);
|
|
109
|
+
else text = session.meta.codex_headers.map(original => {
|
|
110
|
+
const header = cloneNativeJson(original);
|
|
111
|
+
if (session.meta.session_id != null) patchSessionMeta(header, session.meta.session_id);
|
|
112
|
+
return jsonLine(header);
|
|
113
|
+
}).join('');
|
|
114
|
+
text += codexRecords(session, session.messages);
|
|
115
|
+
const envelope = nativeResidueEnvelope(session.meta), end = text.indexOf('\n');
|
|
116
|
+
if (envelope != null && end >= 0) {
|
|
117
|
+
const record = tryNativeJson(text.slice(0, end));
|
|
118
|
+
if (record?.type === 'session_meta' && record.payload != null && typeof record.payload === 'object' && !Array.isArray(record.payload)) {
|
|
119
|
+
record.payload[NATIVE_RESIDUE_SUMMARY_KEY] = nativeResidueSummary(envelope); record.payload[NATIVE_RESIDUE_KEY] = envelope;
|
|
120
|
+
text = jsonLine(record) + text.slice(end + 1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return text;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function toCodexJsonlSpliced(session, sessionId = null) {
|
|
127
|
+
const prefix = splicedPrefixLengths(session), used = new Set(); let text = '', ordinal = null;
|
|
128
|
+
for (const line of session.raw.slice(0, prefix.raw)) {
|
|
129
|
+
const value = tryNativeJson(line);
|
|
130
|
+
if (sessionId != null && value?.type === 'session_meta') { patchSessionMeta(value, sessionId); text += jsonLine(value); }
|
|
131
|
+
else text += line + '\n';
|
|
132
|
+
const turnId = value?.payload?.metadata?.turn_id;
|
|
133
|
+
if (typeof turnId === 'string') used.add(turnId);
|
|
134
|
+
const recordedOrdinal = nativeInteger(value, 'ordinal'); if (recordedOrdinal != null) ordinal = recordedOrdinal;
|
|
135
|
+
}
|
|
136
|
+
for (const message of session.messages.slice(0, prefix.messages)) if (message.metadata.turn_id != null) used.add(message.metadata.turn_id);
|
|
137
|
+
const tail = codexRecords(session, session.messages.slice(prefix.messages), used);
|
|
138
|
+
if (ordinal == null) return text + tail;
|
|
139
|
+
for (const line of tail.split('\n').slice(0, -1)) {
|
|
140
|
+
const record = tryNativeJson(line);
|
|
141
|
+
if (record != null && typeof record === 'object' && !Array.isArray(record)) {
|
|
142
|
+
ordinal = BigInt.asUintN(64, ordinal + 1n); setNativeInteger(record, 'ordinal', ordinal); text += jsonLine(record);
|
|
143
|
+
} else text += line + '\n';
|
|
144
|
+
}
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { stringifyNativeJson } from './native-json.mjs';
|
|
2
|
+
import { SYNTH_TS, replayExcluded, tryNativeJson, parsedJsonField, messageEnvelope, splicedPrefixLengths, jsonLine, inlineDataUrl } from './session-write-utils.mjs';
|
|
3
|
+
|
|
4
|
+
function header(session, sessionId) {
|
|
5
|
+
return jsonLine({ sessionId: sessionId ?? 'supercode-gemini-session',
|
|
6
|
+
projectHash: session.meta.lineage.gemini_project_hash ?? 'supercode',
|
|
7
|
+
startTime: session.meta.lineage.created_at ?? SYNTH_TS, lastUpdated: session.meta.lineage.updated_at ?? SYNTH_TS,
|
|
8
|
+
kind: session.meta.lineage.gemini_session_kind ?? 'main' });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function geminiRecords(session, messages) {
|
|
12
|
+
let text = ''; const names = new Map();
|
|
13
|
+
for (const [index, message] of messages.entries()) {
|
|
14
|
+
if (replayExcluded(message)) continue;
|
|
15
|
+
const timestamp = message.metadata.timestamp ?? SYNTH_TS;
|
|
16
|
+
let value;
|
|
17
|
+
switch (message.role) {
|
|
18
|
+
case 'system': case 'user': {
|
|
19
|
+
const parts = [];
|
|
20
|
+
const collected = (message.content_parts ?? []).map(part => part?.text).filter(part => typeof part === 'string').join(' ');
|
|
21
|
+
const content = message.content ?? (collected ? collected : null);
|
|
22
|
+
if (content != null) parts.push({ text: message.role === 'system' ? `[System] ${content}` : content });
|
|
23
|
+
for (const part of message.content_parts ?? []) {
|
|
24
|
+
const data = inlineDataUrl(part); if (data != null) parts.push({ inlineData: data });
|
|
25
|
+
}
|
|
26
|
+
if (!parts.length) continue;
|
|
27
|
+
value = { id: `supercode-user-${index}`, timestamp, type: 'user', content: parts };
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
case 'assistant': {
|
|
31
|
+
const calls = (message.tool_calls ?? []).map(call => {
|
|
32
|
+
names.set(call.id, call.function.name);
|
|
33
|
+
const value = { id: call.id, name: call.function.name };
|
|
34
|
+
parsedJsonField(value, 'args', call.function.arguments); return value;
|
|
35
|
+
});
|
|
36
|
+
value = { id: `supercode-gemini-${index}`, timestamp, type: 'gemini', content: message.content ?? '',
|
|
37
|
+
model: message.metadata.gemini_model ?? session.meta.model ?? 'unknown' };
|
|
38
|
+
if (calls.length) value.toolCalls = calls;
|
|
39
|
+
const thoughts = message.metadata.gemini_thoughts;
|
|
40
|
+
if (thoughts != null) parsedJsonField(value, 'thoughts', thoughts);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'tool': {
|
|
44
|
+
const id = message.tool_call_id ?? '', name = message.name ?? names.get(id) ?? 'tool';
|
|
45
|
+
const output = message.content ?? (message.content_parts == null ? '' : stringifyNativeJson(message.content_parts));
|
|
46
|
+
value = { id: `supercode-tool-${index}`, timestamp, type: 'user', content: [{
|
|
47
|
+
functionResponse: { id, name, response: { output } },
|
|
48
|
+
}] };
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
default: throw new TypeError(`unknown message role: ${message.role}`);
|
|
52
|
+
}
|
|
53
|
+
value._supercode_gemini_message = messageEnvelope(message, 1); text += jsonLine(value);
|
|
54
|
+
}
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function toGeminiJsonl(session) {
|
|
59
|
+
return header(session, session.meta.session_id) + geminiRecords(session, session.messages)
|
|
60
|
+
+ jsonLine({ $set: { lastUpdated: SYNTH_TS } });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function toGeminiJsonlSpliced(session, sessionId = null) {
|
|
64
|
+
const prefix = splicedPrefixLengths(session);
|
|
65
|
+
if (!prefix.raw) return header(session, sessionId ?? session.meta.session_id) + geminiRecords(session, session.messages);
|
|
66
|
+
let text = '';
|
|
67
|
+
for (const [index, line] of session.raw.slice(0, prefix.raw).entries()) {
|
|
68
|
+
if (!index && sessionId != null) {
|
|
69
|
+
const value = tryNativeJson(line);
|
|
70
|
+
if (value != null && typeof value === 'object' && !Array.isArray(value) && !Object.hasOwn(value, 'type') && Object.hasOwn(value, 'sessionId')) {
|
|
71
|
+
value.sessionId = sessionId; text += jsonLine(value); continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
text += line + '\n';
|
|
75
|
+
}
|
|
76
|
+
return text + geminiRecords(session, session.messages.slice(prefix.messages));
|
|
77
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { cloneNativeJson, setNativeInteger, stringifyNativeJson, stringifyNativeJsonPretty } from './native-json.mjs';
|
|
2
|
+
import { rawVerbatim } from './session-model.mjs';
|
|
3
|
+
import { isToolError } from './message.mjs';
|
|
4
|
+
import { SYNTH_TS, SYNTH_TS_MS, replayExcluded, sessionCwd, tryNativeJson, metadataI64, targetMessageExtension, parsedJsonField } from './session-write-utils.mjs';
|
|
5
|
+
|
|
6
|
+
function gooseConversation(messages) {
|
|
7
|
+
const result = [], names = new Map(); let lastNativeIndex = null;
|
|
8
|
+
for (const [index, message] of messages.entries()) {
|
|
9
|
+
if (replayExcluded(message)) continue;
|
|
10
|
+
const nativeIndex = message.metadata.goose_native_index;
|
|
11
|
+
if (nativeIndex != null) {
|
|
12
|
+
if (lastNativeIndex === nativeIndex) continue;
|
|
13
|
+
lastNativeIndex = nativeIndex;
|
|
14
|
+
const native = tryNativeJson(message.metadata.goose_native_message);
|
|
15
|
+
if (native !== undefined) { result.push(native); continue; }
|
|
16
|
+
} else lastNativeIndex = null;
|
|
17
|
+
for (const call of message.tool_calls ?? []) names.set(call.id, call.function.name);
|
|
18
|
+
const created = metadataI64(message.metadata.goose_created) ?? SYNTH_TS_MS / 1000n + BigInt(index);
|
|
19
|
+
const role = message.role === 'assistant' ? 'assistant' : 'user', content = [];
|
|
20
|
+
if (message.role !== 'tool') {
|
|
21
|
+
if (message.content != null) content.push({ type: 'text', text: message.role === 'system' ? `[System] ${message.content}` : message.content });
|
|
22
|
+
for (const part of message.content_parts ?? []) {
|
|
23
|
+
if (typeof part?.text === 'string' && message.content == null) content.push({ type: 'text', text: part.text });
|
|
24
|
+
const url = part?.image_url?.url;
|
|
25
|
+
if (typeof url !== 'string' || !url.startsWith('data:')) continue;
|
|
26
|
+
const separator = url.indexOf(';base64,', 5);
|
|
27
|
+
if (separator < 0) continue;
|
|
28
|
+
content.push({ type: 'image', data: url.slice(separator + 8), mimeType: url.slice(5, separator) });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const call of message.tool_calls ?? []) {
|
|
32
|
+
const value = { name: call.function.name };
|
|
33
|
+
parsedJsonField(value, 'arguments', call.function.arguments);
|
|
34
|
+
content.push({ type: 'toolRequest', id: call.id, toolCall: { status: 'success',
|
|
35
|
+
value } });
|
|
36
|
+
}
|
|
37
|
+
if (message.role === 'tool') {
|
|
38
|
+
const id = message.tool_call_id ?? '';
|
|
39
|
+
const output = message.content ?? (message.content_parts == null ? '' : stringifyNativeJson(message.content_parts));
|
|
40
|
+
const toolResult = isToolError(message) ? { status: 'error', error: output }
|
|
41
|
+
: { status: 'success', value: { content: [{ type: 'text', text: output }], isError: false } };
|
|
42
|
+
content.push({ type: 'toolResponse', id, toolResult, metadata: { toolName: message.name ?? names.get(id) ?? null } });
|
|
43
|
+
}
|
|
44
|
+
if (!content.length) continue;
|
|
45
|
+
const native = { id: message.metadata.goose_message_id ?? `supercode-goose-${index}`, role, created: 0,
|
|
46
|
+
content };
|
|
47
|
+
parsedJsonField(native, 'metadata', message.metadata.goose_metadata, { userVisible: true, agentVisible: true });
|
|
48
|
+
setNativeInteger(native, 'created', created); targetMessageExtension(native, message); result.push(native);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function synthesizedGooseDocument(session, sessionId, messages) {
|
|
54
|
+
let document = session.meta.goose_header == null ? undefined : cloneNativeJson(session.meta.goose_header);
|
|
55
|
+
if (document === undefined) for (const message of session.messages) {
|
|
56
|
+
const header = tryNativeJson(message.metadata.goose_session_header);
|
|
57
|
+
if (header !== undefined) { document = header; break; }
|
|
58
|
+
}
|
|
59
|
+
if (document === undefined) document = {
|
|
60
|
+
id: 'supercode-goose-session', working_dir: sessionCwd(session), name: 'supercode export', user_set_name: false,
|
|
61
|
+
session_type: 'user', created_at: SYNTH_TS, updated_at: SYNTH_TS, extension_data: {}, usage: {}, accumulated_usage: {},
|
|
62
|
+
accumulated_cost: null, schedule_id: null, recipe: null, user_recipe_values: null, message_count: 0,
|
|
63
|
+
last_message_at: null, provider_name: null, model_config: null, goose_mode: 'auto', archived_at: null,
|
|
64
|
+
project_id: null, parent_session_id: null, last_message_snippet: null,
|
|
65
|
+
};
|
|
66
|
+
if (document === null) document = {};
|
|
67
|
+
if (typeof document !== 'object' || Array.isArray(document)) throw new Error('Goose session header is not an object');
|
|
68
|
+
document.id = sessionId ?? session.meta.session_id ?? 'supercode-goose-session'; document.working_dir = sessionCwd(session);
|
|
69
|
+
const conversation = gooseConversation(messages);
|
|
70
|
+
setNativeInteger(document, 'message_count', BigInt(conversation.length)); document.conversation = conversation;
|
|
71
|
+
return stringifyNativeJsonPretty(document);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Unchanged native Goose documents retain every original byte. */
|
|
75
|
+
export function toGooseJson(session) {
|
|
76
|
+
if (session.meta.source === 'goose' && session.raw.length && session.imported_message_count === session.messages.length) return rawVerbatim(session);
|
|
77
|
+
return synthesizedGooseDocument(session, null, session.messages);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function toGooseJsonSpliced(session, sessionId = null) {
|
|
81
|
+
const prefix = Math.min(session.imported_message_count ?? session.messages.length, session.messages.length);
|
|
82
|
+
if (session.meta.source === 'goose' && session.raw.length) {
|
|
83
|
+
if (sessionId == null && prefix === session.messages.length) return rawVerbatim(session);
|
|
84
|
+
let document = tryNativeJson(rawVerbatim(session));
|
|
85
|
+
if (document !== undefined) {
|
|
86
|
+
if (sessionId != null) {
|
|
87
|
+
if (document === null) document = {};
|
|
88
|
+
if (typeof document !== 'object' || Array.isArray(document)) throw new Error('Goose session document is not an object');
|
|
89
|
+
document.id = sessionId;
|
|
90
|
+
}
|
|
91
|
+
const appended = gooseConversation(session.messages.slice(prefix));
|
|
92
|
+
if (Array.isArray(document?.conversation)) {
|
|
93
|
+
document.conversation.push(...appended); setNativeInteger(document, 'message_count', BigInt(document.conversation.length));
|
|
94
|
+
}
|
|
95
|
+
return stringifyNativeJsonPretty(document);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return synthesizedGooseDocument(session, sessionId, session.messages);
|
|
99
|
+
}
|