@beeuni/sdk 0.1.0 → 0.2.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.
- package/README.md +35 -83
- package/dist/beeuni.esm.min.js +35 -35
- package/dist/beeuni.global.js +35 -35
- package/dist/browser/stores/sync-store.d.ts +6 -0
- package/dist/browser/stores/sync-store.js +52 -21
- package/dist/core/client/beeuni.d.ts +7 -29
- package/dist/core/client/beeuni.js +3 -11
- package/dist/core/client/facade-catalog.d.ts +0 -4
- package/dist/core/client/facade-catalog.js +1 -5
- package/dist/core/client/project-connections.d.ts +2 -2
- package/dist/core/client/project-connections.js +2 -2
- package/dist/core/client/project-operations.d.ts +4 -15
- package/dist/core/client/project-operations.js +2 -6
- package/dist/core/client/project-platform-security.d.ts +1 -1
- package/dist/core/client/project-platform-settings.d.ts +1 -1
- package/dist/core/http/api-client.d.ts +0 -7
- package/dist/core/http/api-client.js +1 -37
- package/dist/core/http/auth.d.ts +2 -4
- package/dist/core/http/auth.js +2 -8
- package/dist/core/rest/platform-client/client-policy.d.ts +8 -0
- package/dist/core/rest/platform-client/client-policy.js +30 -0
- package/dist/core/rest/platform-client/index.d.ts +3 -4
- package/dist/core/rest/platform-client/index.js +3 -4
- package/dist/core/rest/platform-client/types.d.ts +1 -1
- package/dist/core/rest/projects-client/apps.d.ts +8 -1
- package/dist/core/rest/projects-client/connectors.d.ts +6 -55
- package/dist/core/rest/projects-client/connectors.js +2 -29
- package/dist/core/rest/projects-client/git-backend.d.ts +1 -10
- package/dist/core/rest/projects-client/git-backend.js +0 -10
- package/dist/core/rest/projects-client/iam-sso.d.ts +0 -10
- package/dist/core/rest/projects-client/iam-sso.js +0 -3
- package/dist/core/rest/projects-client/index.d.ts +1 -4
- package/dist/core/rest/projects-client/index.js +1 -4
- package/dist/core/rest/projects-client/projects.d.ts +2 -1
- package/dist/core/rest/projects-client/projects.js +3 -0
- package/dist/core/rest/projects-client/sandbox.d.ts +8 -9
- package/dist/core/rest/projects-client/session-sandbox.d.ts +1 -1
- package/dist/core/rest/projects-client/sessions.d.ts +2 -2
- package/dist/core/session/control-stream.d.ts +1 -0
- package/dist/core/session/control-stream.js +18 -1
- package/dist/core/session-log/guard.d.ts +6 -0
- package/dist/core/session-log/guard.js +109 -0
- package/dist/core/session-log/index.d.ts +3 -0
- package/dist/core/session-log/index.js +2 -0
- package/dist/core/session-log/saved-log.d.ts +10 -0
- package/dist/core/session-log/saved-log.js +25 -0
- package/dist/core/session-log/source.d.ts +25 -0
- package/dist/core/session-log/source.js +30 -0
- package/dist/core/session-log/store.d.ts +21 -0
- package/dist/core/session-log/store.js +126 -0
- package/dist/core/session-log/tool-call.d.ts +6 -0
- package/dist/core/session-log/tool-call.js +46 -0
- package/dist/core/session-log/types.d.ts +264 -0
- package/dist/core/session-log/types.js +6 -0
- package/dist/core/session-log/v1-view.d.ts +10 -0
- package/dist/core/session-log/v1-view.js +151 -0
- package/dist/core/session-log/v1.d.ts +31 -0
- package/dist/core/session-log/v1.js +466 -0
- package/dist/core/session-sync/copy-store-types.d.ts +19 -0
- package/dist/core/session-sync/copy-store-types.js +1 -0
- package/dist/core/session-sync/saved-copy-store.js +25 -4
- package/dist/core/stream/event-stream.js +2 -2
- package/dist/core/turns/classify.d.ts +2 -1
- package/dist/core/turns/classify.js +3 -1
- package/dist/core/turns/parts.d.ts +3 -2
- package/dist/core/turns/parts.js +4 -2
- package/dist/core/turns/segments/index.d.ts +1 -0
- package/dist/core/turns/segments/index.js +1 -0
- package/dist/core/turns/segments/work-split.d.ts +10 -0
- package/dist/core/turns/segments/work-split.js +40 -0
- package/dist/core/turns/state.d.ts +2 -7
- package/dist/core/turns/state.js +28 -134
- package/dist/core/turns/tools/tool-meta.d.ts +2 -1
- package/dist/core/turns/tools/tool-meta.js +3 -1
- package/dist/core/turns/tools/tool-outcome.d.ts +2 -1
- package/dist/core/turns/tools/tool-outcome.js +3 -1
- package/dist/core/turns/types.d.ts +1 -1
- package/dist/core/warm-session-pool.d.ts +29 -0
- package/dist/core/warm-session-pool.js +127 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/internal/connector-catalog.d.ts +47 -0
- package/dist/internal/connector-catalog.js +27 -0
- package/dist/internal/host-configuration.d.ts +22 -0
- package/dist/internal/host-configuration.js +28 -0
- package/dist/internal/platform-admin-react.d.ts +12 -0
- package/dist/internal/platform-admin-react.js +13 -0
- package/dist/internal/platform-admin.d.ts +9 -0
- package/dist/internal/platform-admin.js +9 -0
- package/dist/react/index.d.ts +0 -13
- package/dist/react/index.js +0 -13
- package/dist/react/opencode.d.ts +1 -0
- package/dist/react/opencode.js +1 -0
- package/dist/react/query-keys.d.ts +0 -1
- package/dist/react/query-keys.js +0 -1
- package/dist/react/session-transcript-subscription.js +3 -1
- package/dist/react/sign-in-with-beeuni.d.ts +1 -1
- package/dist/react/use-admin-providers.d.ts +5 -5
- package/dist/react/use-opencode-events/handle-event.d.ts +1 -0
- package/dist/react/use-opencode-events/handle-event.js +5 -0
- package/dist/react/use-opencode-events/index.js +1 -0
- package/dist/react/use-runtime-reconnect.js +2 -2
- package/dist/react/use-session-log.d.ts +15 -0
- package/dist/react/use-session-log.js +134 -0
- package/dist/react/use-session.js +2 -0
- package/package.json +15 -7
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { SESSION_LOG_MINOR, SESSION_LOG_SCHEMA } from './types.js';
|
|
2
|
+
import { BEEUNI_TRANSCRIPT_SCHEMA } from '../runtime/transcript-types.js';
|
|
3
|
+
import { fromV1, toV1 } from './v1.js';
|
|
4
|
+
const SHELL = {
|
|
5
|
+
schema: SESSION_LOG_SCHEMA,
|
|
6
|
+
v: SESSION_LOG_MINOR,
|
|
7
|
+
session_id: '',
|
|
8
|
+
title: null,
|
|
9
|
+
created_at: '',
|
|
10
|
+
restore_grade: 'native',
|
|
11
|
+
harness: { current: '', history: [] },
|
|
12
|
+
selection: { agent: null, model: null },
|
|
13
|
+
todos: [],
|
|
14
|
+
pending: { questions: [], permissions: [] },
|
|
15
|
+
};
|
|
16
|
+
const NO_MESSAGES = [];
|
|
17
|
+
const v1Ext = (head) => Object.values(head.ext ?? {}).find((e) => e.version === BEEUNI_TRANSCRIPT_SCHEMA);
|
|
18
|
+
export const isV1Thread = (head) => !!head && v1Ext(head) !== undefined;
|
|
19
|
+
const nativeOf = (head) => {
|
|
20
|
+
const e = v1Ext(head);
|
|
21
|
+
return e ? (e.native_id ?? null) : head.thread_id;
|
|
22
|
+
};
|
|
23
|
+
const messageRows = new WeakMap();
|
|
24
|
+
function rowsOf(head, message) {
|
|
25
|
+
const native = nativeOf(head);
|
|
26
|
+
const cached = messageRows.get(message);
|
|
27
|
+
if (cached && cached.native === native)
|
|
28
|
+
return cached.rows;
|
|
29
|
+
const { rows } = toV1({ ...SHELL, threads: [{ ...head, messages: [message] }] });
|
|
30
|
+
const out = rows.map((r) => ({ info: r.info, parts: r.parts }));
|
|
31
|
+
messageRows.set(message, { native, rows: out });
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
const indexes = new WeakMap();
|
|
35
|
+
function indexOf(state) {
|
|
36
|
+
let index = indexes.get(state);
|
|
37
|
+
if (!index) {
|
|
38
|
+
index = new Map();
|
|
39
|
+
for (const head of Object.values(state.threads)) {
|
|
40
|
+
const native = v1Ext(head) ? nativeOf(head) : null;
|
|
41
|
+
if (native !== null && !index.has(native))
|
|
42
|
+
index.set(native, head.thread_id);
|
|
43
|
+
}
|
|
44
|
+
indexes.set(state, index);
|
|
45
|
+
}
|
|
46
|
+
return index;
|
|
47
|
+
}
|
|
48
|
+
function findThread(logs, runtimeSessionId) {
|
|
49
|
+
for (const state of Object.values(logs)) {
|
|
50
|
+
const threadId = indexOf(state).get(runtimeSessionId);
|
|
51
|
+
if (threadId !== undefined)
|
|
52
|
+
return { state, head: state.threads[threadId], list: state.messages[threadId] ?? NO_MESSAGES };
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const createdOf = (row) => row.info.time?.created;
|
|
57
|
+
function insertAt(rows, row) {
|
|
58
|
+
const created = createdOf(row);
|
|
59
|
+
if (created === undefined)
|
|
60
|
+
return rows.length;
|
|
61
|
+
const at = rows.findIndex((other) => {
|
|
62
|
+
const t = createdOf(other);
|
|
63
|
+
return t !== undefined && (t > created || (t === created && other.info.id > row.info.id));
|
|
64
|
+
});
|
|
65
|
+
return at < 0 ? rows.length : at;
|
|
66
|
+
}
|
|
67
|
+
const v1Order = (a, b) => (createdOf(a) ?? -Infinity) - (createdOf(b) ?? -Infinity) || (a.info.id < b.info.id ? -1 : a.info.id > b.info.id ? 1 : 0);
|
|
68
|
+
const merged = new WeakMap();
|
|
69
|
+
export function sessionLogRowsFor(logs, runtimeSessionId, v1Rows) {
|
|
70
|
+
const found = findThread(logs, runtimeSessionId);
|
|
71
|
+
if (!found)
|
|
72
|
+
return v1Rows;
|
|
73
|
+
const { state, head, list } = found;
|
|
74
|
+
const cached = merged.get(list);
|
|
75
|
+
const native = nativeOf(head);
|
|
76
|
+
if (cached && cached.v1Rows === v1Rows && cached.native === native && cached.revs === state.revs)
|
|
77
|
+
return cached.result;
|
|
78
|
+
const result = list.flatMap((m) => rowsOf(head, m)).sort(v1Order);
|
|
79
|
+
const shown = new Set(result.map((r) => r.info.id));
|
|
80
|
+
for (const row of v1Rows) {
|
|
81
|
+
if (shown.has(row.info.id) || state.revs[row.info.id] !== undefined)
|
|
82
|
+
continue;
|
|
83
|
+
result.splice(insertAt(result, row), 0, row);
|
|
84
|
+
}
|
|
85
|
+
merged.set(list, { v1Rows, native, revs: state.revs, result });
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
const converted = new WeakMap();
|
|
89
|
+
const views = new WeakMap();
|
|
90
|
+
const isObj = (v) => typeof v === 'object' && v !== null;
|
|
91
|
+
const iso = (ms) => (ms !== undefined && Number.isFinite(ms) && Math.abs(ms) <= 8.64e15 ? new Date(ms).toISOString() : null);
|
|
92
|
+
const isMarker = (row) => row.info.role === 'user' && row.parts.some((p) => isObj(p) && p.type === 'compaction');
|
|
93
|
+
const summaryOf = (row, marker) => {
|
|
94
|
+
const info = row.info;
|
|
95
|
+
return info.role === 'assistant' && info.summary === true && info.parentID === marker.info.id;
|
|
96
|
+
};
|
|
97
|
+
function convert(sessionId, native, row, sum, seq) {
|
|
98
|
+
const cached = converted.get(row);
|
|
99
|
+
if (cached && cached.sessionId === sessionId && cached.native === native && cached.sum === sum && cached.seq === seq)
|
|
100
|
+
return cached;
|
|
101
|
+
const unit = sum ? [row, sum] : [row];
|
|
102
|
+
const log = fromV1({ session_id: sessionId, root_id: native, rows: unit.map((r) => ({ runtime_session_id: native, info: r.info, parts: r.parts })) }, { live: true });
|
|
103
|
+
const entry = { sessionId, native, sum, seq, message: { ...log.threads[0].messages[0], seq }, lost: log.grade_counts };
|
|
104
|
+
converted.set(row, entry);
|
|
105
|
+
return entry;
|
|
106
|
+
}
|
|
107
|
+
export function sessionLogOfV1Rows(sessionId, runtimeSessionId, rows) {
|
|
108
|
+
const cached = views.get(rows);
|
|
109
|
+
if (cached && cached.sessionId === sessionId && cached.native === runtimeSessionId)
|
|
110
|
+
return cached.log;
|
|
111
|
+
const sorted = [...rows].sort(v1Order);
|
|
112
|
+
const paired = new Map();
|
|
113
|
+
const used = new Set();
|
|
114
|
+
for (const row of sorted) {
|
|
115
|
+
if (!isMarker(row))
|
|
116
|
+
continue;
|
|
117
|
+
const sum = sorted.find((r) => !used.has(r) && summaryOf(r, row));
|
|
118
|
+
if (!sum)
|
|
119
|
+
continue;
|
|
120
|
+
paired.set(row, sum);
|
|
121
|
+
used.add(sum);
|
|
122
|
+
}
|
|
123
|
+
const counts = { tool_input: 0, cut_point: 0, attachment: 0 };
|
|
124
|
+
const messages = [];
|
|
125
|
+
for (const row of sorted) {
|
|
126
|
+
if (used.has(row))
|
|
127
|
+
continue;
|
|
128
|
+
const entry = convert(sessionId, runtimeSessionId, row, paired.get(row), messages.length + 1);
|
|
129
|
+
for (const k of ['tool_input', 'cut_point', 'attachment'])
|
|
130
|
+
counts[k] += entry.lost[k];
|
|
131
|
+
messages.push(entry.message);
|
|
132
|
+
}
|
|
133
|
+
const created = iso(sorted.map(createdOf).find((t) => typeof t === 'number' && Number.isFinite(t))) ?? '';
|
|
134
|
+
const users = messages.filter((m) => m.role === 'user' && m.kind === 'turn');
|
|
135
|
+
const log = {
|
|
136
|
+
...SHELL,
|
|
137
|
+
session_id: sessionId,
|
|
138
|
+
created_at: created,
|
|
139
|
+
restore_grade: counts.tool_input + counts.cut_point + counts.attachment > 0 ? 'partial' : 'converted',
|
|
140
|
+
grade_counts: counts,
|
|
141
|
+
harness: { current: 'opencode', history: [] },
|
|
142
|
+
selection: { agent: users.at(-1)?.agent ?? null, model: users.at(-1)?.model ?? null },
|
|
143
|
+
threads: [{
|
|
144
|
+
schema: SESSION_LOG_SCHEMA, v: SESSION_LOG_MINOR, thread_id: runtimeSessionId, parent_thread_id: null, spawned_by: null,
|
|
145
|
+
agent: users[0]?.agent ?? null, title: null, created_at: messages[0]?.created_at || created, messages,
|
|
146
|
+
ext: { opencode: { version: BEEUNI_TRANSCRIPT_SCHEMA, native_id: runtimeSessionId } },
|
|
147
|
+
}],
|
|
148
|
+
};
|
|
149
|
+
views.set(rows, { sessionId, native: runtimeSessionId, log });
|
|
150
|
+
return log;
|
|
151
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BeeuniFilePart, BeeuniMessageInfo, BeeuniPart } from '../runtime/transcript-types.js';
|
|
2
|
+
import { type SessionLog } from './types.js';
|
|
3
|
+
export type V1Row = {
|
|
4
|
+
runtime_session_id: string | null;
|
|
5
|
+
info: BeeuniMessageInfo;
|
|
6
|
+
parts: BeeuniPart[];
|
|
7
|
+
};
|
|
8
|
+
export type V1Transcript = {
|
|
9
|
+
session_id: string;
|
|
10
|
+
root_id: string | null;
|
|
11
|
+
rows: V1Row[];
|
|
12
|
+
};
|
|
13
|
+
export type V1Attachment = {
|
|
14
|
+
ref: string;
|
|
15
|
+
bytes: number;
|
|
16
|
+
sha256?: string;
|
|
17
|
+
};
|
|
18
|
+
export type FromV1Options = {
|
|
19
|
+
harness?: 'opencode' | 'pi';
|
|
20
|
+
resolveAttachment?: (part: BeeuniFilePart) => V1Attachment | null;
|
|
21
|
+
live?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type V1Change = {
|
|
24
|
+
set: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
del: true;
|
|
27
|
+
} | {
|
|
28
|
+
sub: Record<string, V1Change>;
|
|
29
|
+
};
|
|
30
|
+
export declare function toV1(input: SessionLog): V1Transcript;
|
|
31
|
+
export declare function fromV1(v1: V1Transcript, options?: FromV1Options): SessionLog;
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { BEEUNI_TRANSCRIPT_SCHEMA, isTurnErrorCode } from '../runtime/transcript-types.js';
|
|
2
|
+
import { SESSION_LOG_MINOR, SESSION_LOG_SCHEMA, } from './types.js';
|
|
3
|
+
const IMPORT = 'beeuni-v1-import';
|
|
4
|
+
const PRODUCER = { harness: IMPORT, harness_version: BEEUNI_TRANSCRIPT_SCHEMA, adapter_version: '1' };
|
|
5
|
+
const V = { schema: SESSION_LOG_SCHEMA, v: SESSION_LOG_MINOR };
|
|
6
|
+
const INTERRUPTED = { opencode: '[Tool execution was interrupted]', pi: 'No result provided' };
|
|
7
|
+
const CLEARED = '[Old tool result content cleared]';
|
|
8
|
+
const SUBTASK_TEXT = 'The following tool was executed by the user';
|
|
9
|
+
const KIND = new Map(Object.entries({
|
|
10
|
+
bash: 'shell', powershell: 'shell', read: 'read', write: 'write', edit: 'edit', multiedit: 'edit', patch: 'patch', apply_patch: 'patch',
|
|
11
|
+
list: 'list', ls: 'list', glob: 'glob', find: 'glob', grep: 'grep', webfetch: 'web_fetch', websearch: 'web_search', codesearch: 'web_search',
|
|
12
|
+
todowrite: 'todo', todoread: 'todo', task: 'task', question: 'question', plan_enter: 'plan', plan_exit: 'plan',
|
|
13
|
+
}));
|
|
14
|
+
const kindOf = (name) => KIND.get(name) ?? (name.includes('_') ? 'mcp' : 'other');
|
|
15
|
+
const FINISH_V2 = new Map([['stop', 'stop'], ['tool-calls', 'tool_calls'], ['length', 'length'], ['error', 'error']]);
|
|
16
|
+
const FINISH_V1 = new Map([['stop', 'stop'], ['tool_calls', 'tool-calls'], ['length', 'length'], ['error', 'error']]);
|
|
17
|
+
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
18
|
+
const str = (v) => (typeof v === 'string' ? v : undefined);
|
|
19
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);
|
|
20
|
+
const own = (o, k) => (Object.hasOwn(o, k) ? o[k] : undefined);
|
|
21
|
+
const define = (o, k, v) => Object.defineProperty(o, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
22
|
+
const iso = (ms) => (num(ms) !== undefined && Math.abs(ms) <= 8.64e15 ? new Date(ms).toISOString() : null);
|
|
23
|
+
const msOf = (s) => {
|
|
24
|
+
const t = s ? Date.parse(s) : NaN;
|
|
25
|
+
return Number.isNaN(t) ? undefined : t;
|
|
26
|
+
};
|
|
27
|
+
function same(a, b) {
|
|
28
|
+
if (a === b)
|
|
29
|
+
return true;
|
|
30
|
+
if (Array.isArray(a) || Array.isArray(b))
|
|
31
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((x, i) => same(x, b[i]));
|
|
32
|
+
if (!isObj(a) || !isObj(b))
|
|
33
|
+
return false;
|
|
34
|
+
const ka = Object.keys(a).filter((k) => a[k] !== undefined);
|
|
35
|
+
return ka.length === Object.keys(b).filter((k) => b[k] !== undefined).length && ka.every((k) => same(a[k], own(b, k)));
|
|
36
|
+
}
|
|
37
|
+
function diff(stored, built) {
|
|
38
|
+
if (same(stored, built))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (stored === undefined)
|
|
41
|
+
return { del: true };
|
|
42
|
+
if (!isObj(stored) || !isObj(built))
|
|
43
|
+
return { set: stored };
|
|
44
|
+
const sub = {};
|
|
45
|
+
for (const k of new Set([...Object.keys(stored), ...Object.keys(built)])) {
|
|
46
|
+
const change = diff(own(stored, k), own(built, k));
|
|
47
|
+
if (change)
|
|
48
|
+
define(sub, k, change);
|
|
49
|
+
}
|
|
50
|
+
return { sub };
|
|
51
|
+
}
|
|
52
|
+
function apply(built, change) {
|
|
53
|
+
if (!change)
|
|
54
|
+
return built;
|
|
55
|
+
if ('set' in change)
|
|
56
|
+
return change.set;
|
|
57
|
+
if ('del' in change)
|
|
58
|
+
return undefined;
|
|
59
|
+
const out = isObj(built) ? { ...built } : {};
|
|
60
|
+
for (const [k, c] of Object.entries(change.sub)) {
|
|
61
|
+
const v = apply(own(out, k), c);
|
|
62
|
+
if (v === undefined)
|
|
63
|
+
delete out[k];
|
|
64
|
+
else
|
|
65
|
+
define(out, k, v);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
const v1Ext = (ext) => Object.values(ext ?? {}).find((e) => e.version === BEEUNI_TRANSCRIPT_SCHEMA);
|
|
70
|
+
const extOf = (h, entry) => ({ [h]: { version: BEEUNI_TRANSCRIPT_SCHEMA, ...entry } });
|
|
71
|
+
const urlOf = (a, c) => a.url !== undefined ? { url: a.url } : a.ref !== undefined && !c.fromV1 ? { url: a.ref } : {};
|
|
72
|
+
const textOf = (content) => (content ?? []).flatMap((c) => (c.type === 'text' ? [c.text] : [])).join('');
|
|
73
|
+
const head = (b, c) => ({ id: b.id, ...(c.sessionID !== null && { sessionID: c.sessionID }), messageID: c.messageID });
|
|
74
|
+
function toolState(b, c, base) {
|
|
75
|
+
const r = b.result;
|
|
76
|
+
const input = b.input === null || b.input === undefined ? {} : { input: b.input };
|
|
77
|
+
const start = msOf(b.started_at) ?? c.created;
|
|
78
|
+
const end = msOf(b.ended_at) ?? start;
|
|
79
|
+
const meta = r?.exit_code !== undefined ? { exit: r.exit_code } : {};
|
|
80
|
+
if (b.status === 'pending')
|
|
81
|
+
return { status: 'pending', input: b.input ?? {}, raw: '' };
|
|
82
|
+
if (b.status === 'running')
|
|
83
|
+
return { status: 'running', input: b.input ?? {}, ...(b.title && { title: b.title }), time: { start } };
|
|
84
|
+
if (b.status === 'error')
|
|
85
|
+
return { status: 'error', ...input, error: textOf(r?.content), ...(r?.exit_code !== undefined && { metadata: meta }), time: { start, end } };
|
|
86
|
+
const atts = (r?.content ?? []).flatMap((x, i) => x.type === 'attachment' ? [{ ...base, id: `${b.id}-a${i}`, type: 'file', mime: x.mime, ...urlOf(x, c) }] : []);
|
|
87
|
+
const compacted = msOf(r?.cleared_at);
|
|
88
|
+
return {
|
|
89
|
+
status: 'completed', ...input, output: textOf(r?.content), title: b.title ?? '', metadata: meta,
|
|
90
|
+
time: { start, end, ...(compacted !== undefined && { compacted }) },
|
|
91
|
+
...(atts.length && { attachments: atts }),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function partOf(b, c) {
|
|
95
|
+
const base = head(b, c);
|
|
96
|
+
switch (b.type) {
|
|
97
|
+
case 'text': return { ...base, type: 'text', text: b.text, ...(b.synthetic !== undefined && { synthetic: b.synthetic }) };
|
|
98
|
+
case 'reasoning': return { ...base, type: 'reasoning', text: b.text, time: { start: c.created } };
|
|
99
|
+
case 'attachment': return { ...base, type: 'file', mime: b.mime, ...(b.name !== undefined && { filename: b.name }), ...urlOf(b, c) };
|
|
100
|
+
case 'tool_call': return { ...base, type: 'tool', callID: b.call_id, tool: b.name, state: toolState(b, c, { ...(c.sessionID !== null && { sessionID: c.sessionID }), messageID: c.messageID }) };
|
|
101
|
+
case 'step': return b.phase === 'start'
|
|
102
|
+
? { ...base, type: 'step-start' }
|
|
103
|
+
: { ...base, type: 'step-finish', reason: 'stop', cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } };
|
|
104
|
+
case 'compaction': return {
|
|
105
|
+
...base, type: 'compaction', auto: b.trigger === 'auto' || b.trigger === 'overflow',
|
|
106
|
+
...(b.trigger === 'overflow' && { overflow: true }), ...(b.first_kept_message_id !== null && { tail_start_id: b.first_kept_message_id }),
|
|
107
|
+
};
|
|
108
|
+
case 'harness':
|
|
109
|
+
if (c.fromV1 && (b.harness === 'opencode' || b.harness === 'pi'))
|
|
110
|
+
return b.data;
|
|
111
|
+
return b.model_visible && b.fallback_text !== undefined ? { ...base, type: 'text', text: b.fallback_text, synthetic: true } : undefined;
|
|
112
|
+
case 'subtask': return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function errorV1(e) {
|
|
116
|
+
if (!isTurnErrorCode(e.code))
|
|
117
|
+
return { name: e.code, data: { message: e.message } };
|
|
118
|
+
return { name: e.code === 'aborted' ? 'MessageAbortedError' : 'UnknownError', code: e.code, data: { message: e.message } };
|
|
119
|
+
}
|
|
120
|
+
function assistantInfo(m, c, over = {}) {
|
|
121
|
+
const u = m.usage;
|
|
122
|
+
const finish = m.finish === null ? undefined : FINISH_V1.get(m.finish);
|
|
123
|
+
return {
|
|
124
|
+
id: m.message_id, ...(c.sessionID !== null && { sessionID: c.sessionID }), role: 'assistant',
|
|
125
|
+
time: { created: c.created, ...(m.completed_at !== null && { completed: msOf(m.completed_at) }) },
|
|
126
|
+
...(m.reply_to && { parentID: m.reply_to }),
|
|
127
|
+
modelID: m.model?.model ?? '', providerID: m.model?.provider ?? '', ...(m.model?.variant !== undefined && { variant: m.model.variant }),
|
|
128
|
+
mode: m.agent ?? '', agent: m.agent ?? '', path: { cwd: '', root: '' },
|
|
129
|
+
cost: u?.cost ?? 0, tokens: { input: u?.input ?? 0, output: u?.output ?? 0, reasoning: u?.reasoning ?? 0, cache: { read: u?.cache_read ?? 0, write: u?.cache_write ?? 0 } },
|
|
130
|
+
...(finish && { finish }), ...(m.error && { error: errorV1(m.error) }),
|
|
131
|
+
...over,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function infoOf(m, c) {
|
|
135
|
+
if (m.role === 'assistant')
|
|
136
|
+
return assistantInfo(m, c);
|
|
137
|
+
return {
|
|
138
|
+
id: m.message_id, ...(c.sessionID !== null && { sessionID: c.sessionID }), role: 'user', time: { created: c.created },
|
|
139
|
+
agent: m.agent ?? '', model: { providerID: m.model?.provider ?? '', modelID: m.model?.model ?? '', ...(m.model?.variant !== undefined && { variant: m.model.variant }) },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function summaryOf(m, cb, c) {
|
|
143
|
+
const id = `${m.message_id}-summary`;
|
|
144
|
+
const info = assistantInfo({ ...m, reply_to: m.message_id, agent: 'compaction', finish: m.status === 'complete' ? 'stop' : null }, c, { id, summary: true });
|
|
145
|
+
return { info, parts: [{ id: `${id}-p0`, ...(c.sessionID !== null && { sessionID: c.sessionID }), messageID: id, type: 'text', text: cb.summary }] };
|
|
146
|
+
}
|
|
147
|
+
function rowsOf(m, threadNative, v1Thread) {
|
|
148
|
+
const v1 = v1Thread || m.producer.harness === IMPORT;
|
|
149
|
+
if (!v1 && (m.role === 'system' || m.kind === 'context'))
|
|
150
|
+
return [];
|
|
151
|
+
const d = v1Ext(m.ext)?.data;
|
|
152
|
+
const sessionID = d && 'runtime_session_id' in d ? d.runtime_session_id ?? null : threadNative;
|
|
153
|
+
const c = { sessionID, messageID: m.message_id, created: msOf(m.created_at), fromV1: v1 };
|
|
154
|
+
const parts = d && 'parts' in d
|
|
155
|
+
? d.parts
|
|
156
|
+
: m.blocks.flatMap((b) => {
|
|
157
|
+
const p = partOf(b, c);
|
|
158
|
+
return p === undefined ? [] : [apply(p, v1Ext(b.ext)?.data)];
|
|
159
|
+
});
|
|
160
|
+
const rows = [{ runtime_session_id: sessionID, info: apply(infoOf(m, c), d?.info), parts }];
|
|
161
|
+
const cb = m.blocks.find((b) => b.type === 'compaction');
|
|
162
|
+
if (m.kind === 'compaction' && cb && cb.summary !== null) {
|
|
163
|
+
const s = apply(summaryOf(m, cb, c), d?.summary);
|
|
164
|
+
rows.push({ runtime_session_id: sessionID, info: s.info, parts: s.parts });
|
|
165
|
+
}
|
|
166
|
+
return rows;
|
|
167
|
+
}
|
|
168
|
+
const created = (r) => num(isObj(r.info) && isObj(r.info.time) ? r.info.time.created : undefined) ?? -Infinity;
|
|
169
|
+
const idOf = (r) => String(isObj(r.info) ? r.info.id : '');
|
|
170
|
+
const rowOrder = (a, b) => created(a) - created(b) || (idOf(a) < idOf(b) ? -1 : idOf(a) > idOf(b) ? 1 : 0);
|
|
171
|
+
const threadNativeOf = (t) => {
|
|
172
|
+
const e = v1Ext(t.ext);
|
|
173
|
+
return e ? e.native_id ?? null : t.thread_id;
|
|
174
|
+
};
|
|
175
|
+
export function toV1(input) {
|
|
176
|
+
const log = structuredClone(input);
|
|
177
|
+
const root = log.threads.find((t) => t.parent_thread_id === null) ?? log.threads[0];
|
|
178
|
+
const rows = log.threads.flatMap((t) => t.messages.flatMap((m) => rowsOf(m, threadNativeOf(t), v1Ext(t.ext) !== undefined)));
|
|
179
|
+
const stated = v1Ext(log.ext)?.data;
|
|
180
|
+
const rootId = stated && 'root_id' in stated ? stated.root_id ?? null : root ? threadNativeOf(root) : null;
|
|
181
|
+
return { session_id: log.session_id, root_id: rootId, rows: rows.sort(rowOrder) };
|
|
182
|
+
}
|
|
183
|
+
function modelOf(info) {
|
|
184
|
+
const src = info.role === 'assistant' ? { p: info.providerID, m: info.modelID, v: info.variant } : isObj(info.model) ? { p: info.model.providerID, m: info.model.modelID, v: info.model.variant } : {};
|
|
185
|
+
const provider = str(src.p);
|
|
186
|
+
const model = str(src.m);
|
|
187
|
+
return provider === undefined || model === undefined ? null : { provider, model, ...(str(src.v) !== undefined && { variant: src.v }) };
|
|
188
|
+
}
|
|
189
|
+
function usageOf(info) {
|
|
190
|
+
if (!isObj(info.tokens))
|
|
191
|
+
return null;
|
|
192
|
+
const t = info.tokens;
|
|
193
|
+
const cache = isObj(t.cache) ? t.cache : {};
|
|
194
|
+
return {
|
|
195
|
+
input: num(t.input) ?? 0, output: num(t.output) ?? 0, cache_read: num(cache.read) ?? 0, cache_write: num(cache.write) ?? 0,
|
|
196
|
+
...(num(t.reasoning) !== undefined && { reasoning: t.reasoning }), ...(num(info.cost) !== undefined && { cost: info.cost }),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const errorOf = (e) => ({ code: str(e.code) || str(e.name) || 'UnknownError', message: str(isObj(e.data) ? e.data.message : undefined) ?? '' });
|
|
200
|
+
function attachment(p, c, lost) {
|
|
201
|
+
const r = c.resolve(p);
|
|
202
|
+
if (!r)
|
|
203
|
+
lost.attachment++;
|
|
204
|
+
return {
|
|
205
|
+
mime: str(p.mime) ?? 'application/octet-stream',
|
|
206
|
+
...(r && { ref: r.ref, bytes: r.bytes, ...(r.sha256 !== undefined && { sha256: r.sha256 }) }),
|
|
207
|
+
...(str(p.url) !== undefined && { url: p.url }),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function toolBlock(p, id, c, lost) {
|
|
211
|
+
const st = p.state;
|
|
212
|
+
const time = isObj(st.time) ? st.time : {};
|
|
213
|
+
if (!isObj(st.input))
|
|
214
|
+
lost.tool_input++;
|
|
215
|
+
const exit = isObj(st.metadata) && num(st.metadata.exit) !== undefined ? { exit_code: st.metadata.exit } : {};
|
|
216
|
+
let status;
|
|
217
|
+
let result;
|
|
218
|
+
let input = st.input ?? null;
|
|
219
|
+
if (st.status === 'completed') {
|
|
220
|
+
const atts = (Array.isArray(st.attachments) ? st.attachments : []).filter(isObj).map((a) => {
|
|
221
|
+
const { bytes: _bytes, ...content } = attachment(a, c, lost);
|
|
222
|
+
return { type: 'attachment', ...content };
|
|
223
|
+
});
|
|
224
|
+
const cleared = iso(time.compacted);
|
|
225
|
+
status = 'complete';
|
|
226
|
+
result = {
|
|
227
|
+
content: [...(str(st.output) !== undefined ? [{ type: 'text', text: st.output }] : []), ...atts], is_error: false, ...exit,
|
|
228
|
+
...(cleared && { model_content: [{ type: 'text', text: CLEARED }], cleared_at: cleared }),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
else if (st.status === 'error') {
|
|
232
|
+
const error = str(st.error);
|
|
233
|
+
status = 'error';
|
|
234
|
+
result = { content: error !== undefined ? [{ type: 'text', text: error }] : [], is_error: true, ...(error !== undefined && { error: { message: error } }), ...exit };
|
|
235
|
+
}
|
|
236
|
+
else if (c.live) {
|
|
237
|
+
status = st.status === 'pending' ? 'pending' : 'running';
|
|
238
|
+
if (status === 'pending' && str(st.raw) && !(isObj(st.input) && Object.keys(st.input).length))
|
|
239
|
+
input = st.raw;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const text = INTERRUPTED[c.h];
|
|
243
|
+
status = 'error';
|
|
244
|
+
result = { content: [{ type: 'text', text }], is_error: true, synthetic: true, error: { code: 'interrupted', message: text } };
|
|
245
|
+
}
|
|
246
|
+
const started = iso(time.start);
|
|
247
|
+
const ended = iso(time.end);
|
|
248
|
+
return {
|
|
249
|
+
type: 'tool_call', id, call_id: p.callID, name: p.tool, kind: kindOf(p.tool), input, status, ...(result && { result }),
|
|
250
|
+
...(str(st.title) && { title: st.title }), ...(started && { started_at: started }), ...(ended && { ended_at: ended }),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function blockOf(p, id, marker, summary, c, lost) {
|
|
254
|
+
const harness = (kind, model_visible = false) => ({
|
|
255
|
+
type: 'harness', id, harness: c.h, kind, data: p, model_visible, ...(model_visible && { fallback_text: SUBTASK_TEXT }),
|
|
256
|
+
});
|
|
257
|
+
if (!isObj(p))
|
|
258
|
+
return harness('unknown');
|
|
259
|
+
switch (p.type) {
|
|
260
|
+
case 'text':
|
|
261
|
+
return typeof p.text !== 'string' || p.ignored === true ? harness('text') : { type: 'text', id, text: p.text, ...(typeof p.synthetic === 'boolean' && { synthetic: p.synthetic }) };
|
|
262
|
+
case 'reasoning':
|
|
263
|
+
return typeof p.text !== 'string' ? harness('reasoning') : { type: 'reasoning', id, text: p.text };
|
|
264
|
+
case 'file': {
|
|
265
|
+
if (p.mime === 'text/plain' || p.mime === 'application/x-directory')
|
|
266
|
+
return harness('file');
|
|
267
|
+
return { type: 'attachment', id, ...attachment(p, c, lost), ...(str(p.filename) !== undefined && { name: p.filename }) };
|
|
268
|
+
}
|
|
269
|
+
case 'tool':
|
|
270
|
+
return typeof p.callID === 'string' && typeof p.tool === 'string' && isObj(p.state) ? toolBlock(p, id, c, lost) : harness('tool');
|
|
271
|
+
case 'step-start':
|
|
272
|
+
case 'step-finish':
|
|
273
|
+
return { type: 'step', id, phase: p.type === 'step-start' ? 'start' : 'finish' };
|
|
274
|
+
case 'compaction': {
|
|
275
|
+
if (p !== marker)
|
|
276
|
+
return harness('compaction');
|
|
277
|
+
const trigger = p.overflow === true ? 'overflow' : p.auto === true ? 'auto' : 'manual';
|
|
278
|
+
return { type: 'compaction', id, summary, first_kept_message_id: str(p.tail_start_id) ?? null, trigger };
|
|
279
|
+
}
|
|
280
|
+
case 'subtask':
|
|
281
|
+
return harness('subtask', true);
|
|
282
|
+
default:
|
|
283
|
+
return harness(str(p.type) || 'unknown');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function blockIds(parts) {
|
|
287
|
+
const given = parts.map((p, i) => (isObj(p) && typeof p.id === 'string' && p.id ? p.id : `#${i}`));
|
|
288
|
+
const taken = new Set(given);
|
|
289
|
+
const seen = new Set();
|
|
290
|
+
return given.map((id) => {
|
|
291
|
+
if (!seen.has(id)) {
|
|
292
|
+
seen.add(id);
|
|
293
|
+
return id;
|
|
294
|
+
}
|
|
295
|
+
let n = 2;
|
|
296
|
+
while (taken.has(`${id}#${n}`))
|
|
297
|
+
n++;
|
|
298
|
+
taken.add(`${id}#${n}`);
|
|
299
|
+
return `${id}#${n}`;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function toMessage(row, sum, threadId, seq, superseded, c) {
|
|
303
|
+
const info = isObj(row.info) ? row.info : {};
|
|
304
|
+
const parts = Array.isArray(row.parts) ? row.parts : [];
|
|
305
|
+
const marker = info.role === 'user' ? parts.find((p) => isObj(p) && p.type === 'compaction') : undefined;
|
|
306
|
+
const sInfo = sum && isObj(sum.info) ? sum.info : undefined;
|
|
307
|
+
const role = info.role === 'assistant' || info.role === 'user' ? info.role : 'system';
|
|
308
|
+
const src = marker ? sInfo : role === 'assistant' ? info : undefined;
|
|
309
|
+
const err = src && isObj(src.error) ? src.error : undefined;
|
|
310
|
+
const aborted = err?.name === 'MessageAbortedError';
|
|
311
|
+
const completedAt = src && isObj(src.time) ? iso(src.time.completed) : null;
|
|
312
|
+
const open = c.live ? 'streaming' : 'interrupted';
|
|
313
|
+
const status = !src ? (marker ? open : 'complete') : err ? (aborted ? 'aborted' : 'error') : completedAt ? 'complete' : open;
|
|
314
|
+
const createdAt = iso(isObj(info.time) ? info.time.created : undefined) ?? '';
|
|
315
|
+
const summary = sum ? (Array.isArray(sum.parts) ? sum.parts : []).flatMap((p) => (isObj(p) && p.type === 'text' && typeof p.text === 'string' ? [p.text] : [])).join('') : null;
|
|
316
|
+
const lost = { tool_input: 0, cut_point: 0, attachment: 0 };
|
|
317
|
+
const ids = blockIds(parts);
|
|
318
|
+
const blocks = parts.map((p, i) => blockOf(p, ids[i], marker, summary, c, lost));
|
|
319
|
+
const visible = blocks.some((b) => b.type === 'text' || b.type === 'attachment' || b.type === 'tool_call' || (b.type === 'harness' && b.model_visible));
|
|
320
|
+
const inContext = !superseded && role !== 'system' && (marker ? status === 'complete' : !err || (aborted && visible));
|
|
321
|
+
const hidden = superseded ? 'superseded' : inContext || !err ? undefined : aborted ? 'aborted' : 'failed_attempt';
|
|
322
|
+
if (marker && isObj(marker) && str(marker.tail_start_id) === undefined)
|
|
323
|
+
lost.cut_point++;
|
|
324
|
+
const m = {
|
|
325
|
+
...V, message_id: str(info.id) ?? '', thread_id: threadId, seq, role, kind: marker ? 'compaction' : 'turn', status, in_context: inContext,
|
|
326
|
+
...(hidden && { hidden_reason: hidden }),
|
|
327
|
+
agent: str(info.agent) ?? null,
|
|
328
|
+
...(role === 'assistant' && { reply_to: str(info.parentID) ?? null }),
|
|
329
|
+
model: modelOf(info), usage: src ? usageOf(src) : null,
|
|
330
|
+
finish: !src || marker ? null : aborted ? 'aborted' : err ? 'error' : FINISH_V2.get(info.finish) ?? null,
|
|
331
|
+
error: err ? errorOf(err) : null,
|
|
332
|
+
created_at: createdAt, completed_at: src || marker ? completedAt : createdAt || null,
|
|
333
|
+
producer: PRODUCER, blocks,
|
|
334
|
+
};
|
|
335
|
+
return { m, lost };
|
|
336
|
+
}
|
|
337
|
+
function keepResidual(m, row, sum, threadNative, h) {
|
|
338
|
+
const data = {};
|
|
339
|
+
if (row.runtime_session_id !== threadNative)
|
|
340
|
+
data.runtime_session_id = row.runtime_session_id;
|
|
341
|
+
const c = { sessionID: row.runtime_session_id, messageID: m.message_id, created: msOf(m.created_at), fromV1: true };
|
|
342
|
+
if (Array.isArray(row.parts)) {
|
|
343
|
+
m.blocks.forEach((b, i) => {
|
|
344
|
+
if (b.type === 'harness' || b.type === 'subtask')
|
|
345
|
+
return;
|
|
346
|
+
const change = diff(row.parts[i], partOf(b, c));
|
|
347
|
+
if (change)
|
|
348
|
+
b.ext = extOf(h, { data: change });
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else
|
|
352
|
+
data.parts = row.parts;
|
|
353
|
+
const info = diff(row.info, infoOf(m, c));
|
|
354
|
+
if (info)
|
|
355
|
+
data.info = info;
|
|
356
|
+
const cb = m.blocks.find((b) => b.type === 'compaction');
|
|
357
|
+
if (sum && cb) {
|
|
358
|
+
const change = diff({ info: sum.info, parts: sum.parts }, summaryOf(m, cb, c));
|
|
359
|
+
if (change)
|
|
360
|
+
data.summary = change;
|
|
361
|
+
}
|
|
362
|
+
if (Object.keys(data).length)
|
|
363
|
+
m.ext = extOf(h, { data });
|
|
364
|
+
}
|
|
365
|
+
function spawnerOf(rows, id, text) {
|
|
366
|
+
if (!id)
|
|
367
|
+
return undefined;
|
|
368
|
+
for (const exact of [true, false]) {
|
|
369
|
+
for (const row of rows) {
|
|
370
|
+
if (row.runtime_session_id === id || !Array.isArray(row.parts))
|
|
371
|
+
continue;
|
|
372
|
+
for (const p of row.parts) {
|
|
373
|
+
if (!isObj(p) || p.type !== 'tool' || !isObj(p.state))
|
|
374
|
+
continue;
|
|
375
|
+
if (exact ? isObj(p.state.metadata) && p.state.metadata.sessionId === id : text(p.state).includes(id))
|
|
376
|
+
return { row, part: p };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
export function fromV1(v1, options = {}) {
|
|
383
|
+
const c = { h: options.harness ?? 'opencode', resolve: options.resolveAttachment ?? (() => null), counts: { tool_input: 0, cut_point: 0, attachment: 0 }, live: options.live === true };
|
|
384
|
+
const rows = structuredClone(v1.rows).sort(rowOrder);
|
|
385
|
+
const groups = new Map();
|
|
386
|
+
for (const r of rows) {
|
|
387
|
+
const list = groups.get(r.runtime_session_id);
|
|
388
|
+
if (list)
|
|
389
|
+
list.push(r);
|
|
390
|
+
else
|
|
391
|
+
groups.set(r.runtime_session_id, [r]);
|
|
392
|
+
}
|
|
393
|
+
const stated = v1.root_id;
|
|
394
|
+
const usable = groups.has(stated);
|
|
395
|
+
const texts = new Map();
|
|
396
|
+
const text = (state) => texts.get(state) ?? (texts.set(state, JSON.stringify(state)), texts.get(state));
|
|
397
|
+
const spawners = new Map();
|
|
398
|
+
for (const id of groups.keys()) {
|
|
399
|
+
const s = usable && id === stated ? undefined : spawnerOf(rows, id, text);
|
|
400
|
+
if (s)
|
|
401
|
+
spawners.set(id, s);
|
|
402
|
+
}
|
|
403
|
+
const latest = [...rows].reverse().find((r) => r.runtime_session_id === null || !spawners.has(r.runtime_session_id));
|
|
404
|
+
const root = usable || !latest ? stated : latest.runtime_session_id;
|
|
405
|
+
if (!groups.has(root))
|
|
406
|
+
groups.set(root, []);
|
|
407
|
+
const rootThread = root || v1.session_id;
|
|
408
|
+
const threadOf = (id) => (id !== null && spawners.has(id) ? id : rootThread);
|
|
409
|
+
const sessionCreated = iso(rows.map(created).find(Number.isFinite)) ?? '';
|
|
410
|
+
const threads = [];
|
|
411
|
+
for (const [id, own] of groups) {
|
|
412
|
+
if (id !== root && (id === null || !spawners.has(id)))
|
|
413
|
+
continue;
|
|
414
|
+
const isRoot = id === root;
|
|
415
|
+
const superseded = isRoot ? rows.filter((r) => r.runtime_session_id !== root && threadOf(r.runtime_session_id) === rootThread) : [];
|
|
416
|
+
const list = [...superseded, ...own];
|
|
417
|
+
const old = new Set(superseded);
|
|
418
|
+
const summaries = new Map();
|
|
419
|
+
const paired = new Set();
|
|
420
|
+
for (const row of list) {
|
|
421
|
+
const info = isObj(row.info) ? row.info : {};
|
|
422
|
+
if (info.role !== 'user' || !Array.isArray(row.parts) || !row.parts.some((p) => isObj(p) && p.type === 'compaction'))
|
|
423
|
+
continue;
|
|
424
|
+
const sum = list.find((r) => !paired.has(r) && r.runtime_session_id === row.runtime_session_id && isObj(r.info) && r.info.role === 'assistant' && r.info.summary === true && r.info.parentID === info.id);
|
|
425
|
+
if (!sum)
|
|
426
|
+
continue;
|
|
427
|
+
summaries.set(row, sum);
|
|
428
|
+
paired.add(sum);
|
|
429
|
+
}
|
|
430
|
+
const messages = [];
|
|
431
|
+
for (const row of list) {
|
|
432
|
+
if (paired.has(row))
|
|
433
|
+
continue;
|
|
434
|
+
const sum = summaries.get(row);
|
|
435
|
+
const { m, lost } = toMessage(row, sum, isRoot ? rootThread : id, messages.length + 1, old.has(row), c);
|
|
436
|
+
keepResidual(m, row, sum, id, c.h);
|
|
437
|
+
if (m.in_context)
|
|
438
|
+
for (const k of ['tool_input', 'cut_point', 'attachment'])
|
|
439
|
+
c.counts[k] += lost[k];
|
|
440
|
+
messages.push(m);
|
|
441
|
+
}
|
|
442
|
+
const spawn = id === null ? undefined : spawners.get(id);
|
|
443
|
+
const firstUser = messages.find((m) => m.role === 'user' && m.kind === 'turn');
|
|
444
|
+
threads.push({
|
|
445
|
+
...V, thread_id: isRoot ? rootThread : id,
|
|
446
|
+
parent_thread_id: spawn ? threadOf(spawn.row.runtime_session_id) : null,
|
|
447
|
+
spawned_by: spawn && typeof spawn.part.callID === 'string' ? { message_id: str(spawn.row.info?.id) ?? '', call_id: spawn.part.callID } : null,
|
|
448
|
+
agent: firstUser?.agent ?? null, title: null, created_at: messages[0]?.created_at || sessionCreated, messages,
|
|
449
|
+
ext: extOf(c.h, id === null ? {} : { native_id: id }),
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
threads.sort((a, b) => (a.parent_thread_id === null ? -1 : b.parent_thread_id === null ? 1 : rowsIndex(a) - rowsIndex(b)));
|
|
453
|
+
const lastUser = threads[0]?.messages.filter((m) => m.role === 'user' && m.kind === 'turn').at(-1);
|
|
454
|
+
const partial = c.counts.tool_input + c.counts.cut_point + c.counts.attachment > 0;
|
|
455
|
+
return {
|
|
456
|
+
...V, session_id: v1.session_id, title: null, created_at: sessionCreated,
|
|
457
|
+
restore_grade: partial ? 'partial' : 'converted', grade_counts: c.counts,
|
|
458
|
+
harness: { current: c.h, history: [] },
|
|
459
|
+
selection: { agent: lastUser?.agent ?? null, model: lastUser?.model ?? null },
|
|
460
|
+
todos: [], pending: { questions: [], permissions: [] }, threads,
|
|
461
|
+
...(root !== stated && { ext: extOf(c.h, { data: { root_id: stated } }) }),
|
|
462
|
+
};
|
|
463
|
+
function rowsIndex(t) {
|
|
464
|
+
return rows.findIndex((r) => r.runtime_session_id === t.thread_id);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SessionTranscriptSyncEnvelope } from '../rest/projects-client/sessions.js';
|
|
2
|
+
export interface StoredCopy<T = SessionTranscriptSyncEnvelope> {
|
|
3
|
+
v: number;
|
|
4
|
+
user: string;
|
|
5
|
+
at: number;
|
|
6
|
+
e: T;
|
|
7
|
+
}
|
|
8
|
+
export interface CopyCodec<T> {
|
|
9
|
+
paintable(value: T | null | undefined): value is T;
|
|
10
|
+
fit(stored: StoredCopy<T>, maxChars: number): string | null;
|
|
11
|
+
capturedAt(value: T): number;
|
|
12
|
+
}
|
|
13
|
+
export interface CopyStore<T> {
|
|
14
|
+
keyFor(projectId: string, sessionId: string): string;
|
|
15
|
+
read(projectId: string, sessionId: string): T | null | Promise<T | null>;
|
|
16
|
+
write(projectId: string, sessionId: string, value: T): Promise<void>;
|
|
17
|
+
remove(projectId: string, sessionId: string): Promise<void>;
|
|
18
|
+
clear(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|