@openpond/evals 0.11.17 → 0.11.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connected-evidence/contracts.js +14 -4
- package/dist/connected-evidence/imports.js +77 -17
- package/dist/connected-evidence/normalize.js +4 -3
- package/dist/connected-evidence/sources/claude-branches.js +52 -0
- package/dist/connected-evidence/sources/claude-code.js +7 -29
- package/dist/connected-evidence/sources/codex-authorization.js +40 -0
- package/dist/connected-evidence/sources/codex.js +7 -3
- package/dist/connected-evidence/sources/grok-build.js +137 -0
- package/dist/connected-evidence/sources/opencode.js +101 -0
- package/dist/connected-evidence/sources/pi.js +187 -0
- package/dist/graders.js +11 -1
- package/dist/learning/budgeted-reward-fixtures.js +1 -0
- package/dist/learning/contracts.js +10 -0
- package/dist/learning/index.js +1 -0
- package/dist/learning/model-judge.js +22 -5
- package/dist/learning/reward-calibration-closure.js +110 -0
- package/dist/learning/reward-calibration.js +20 -10
- package/dist/learning/reward-checks.js +1 -0
- package/dist/native-conversations/appimage-runtime.js +131 -0
- package/dist/native-conversations/collector-branches.js +79 -0
- package/dist/native-conversations/collector-contracts.js +9 -0
- package/dist/native-conversations/collector-destinations.js +27 -0
- package/dist/native-conversations/collector-errors.js +42 -0
- package/dist/native-conversations/collector-progress.js +114 -0
- package/dist/native-conversations/collector-store.js +247 -0
- package/dist/native-conversations/collector-sync-requests.js +52 -0
- package/dist/native-conversations/collector.js +322 -0
- package/dist/native-conversations/contracts.js +11 -0
- package/dist/native-conversations/database.js +149 -0
- package/dist/native-conversations/discovery.js +135 -0
- package/dist/native-conversations/history.js +298 -0
- package/dist/native-conversations/index.js +11 -0
- package/dist/native-conversations/location.js +29 -0
- package/dist/native-conversations/openclaw-cold-archive.js +91 -0
- package/dist/native-conversations/openclaw-database.js +130 -0
- package/dist/native-conversations/source-locations.js +112 -0
- package/dist/native-conversations/supervisor.js +208 -0
- package/dist/rewards.js +1 -1
- package/dist/types/connected-evidence/contracts.d.ts +15 -0
- package/dist/types/connected-evidence/contracts.d.ts.map +1 -1
- package/dist/types/connected-evidence/imports.d.ts +6 -0
- package/dist/types/connected-evidence/imports.d.ts.map +1 -1
- package/dist/types/connected-evidence/normalize.d.ts +4 -0
- package/dist/types/connected-evidence/normalize.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/claude-branches.d.ts +19 -0
- package/dist/types/connected-evidence/sources/claude-branches.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/claude-code.d.ts +5 -1
- package/dist/types/connected-evidence/sources/claude-code.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/codex-authorization.d.ts +18 -0
- package/dist/types/connected-evidence/sources/codex-authorization.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/codex.d.ts +5 -1
- package/dist/types/connected-evidence/sources/codex.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/grok-build.d.ts +79 -0
- package/dist/types/connected-evidence/sources/grok-build.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/history.d.ts +10 -2
- package/dist/types/connected-evidence/sources/history.d.ts.map +1 -1
- package/dist/types/connected-evidence/sources/opencode.d.ts +78 -0
- package/dist/types/connected-evidence/sources/opencode.d.ts.map +1 -0
- package/dist/types/connected-evidence/sources/pi.d.ts +79 -0
- package/dist/types/connected-evidence/sources/pi.d.ts.map +1 -0
- package/dist/types/graders.d.ts +81 -0
- package/dist/types/graders.d.ts.map +1 -1
- package/dist/types/learning/budgeted-reward-fixtures.d.ts +2 -1
- package/dist/types/learning/budgeted-reward-fixtures.d.ts.map +1 -1
- package/dist/types/learning/contracts.d.ts +167 -0
- package/dist/types/learning/contracts.d.ts.map +1 -1
- package/dist/types/learning/index.d.ts +1 -0
- package/dist/types/learning/index.d.ts.map +1 -1
- package/dist/types/learning/judge-budget.d.ts +3 -0
- package/dist/types/learning/judge-budget.d.ts.map +1 -1
- package/dist/types/learning/model-judge.d.ts +6 -1
- package/dist/types/learning/model-judge.d.ts.map +1 -1
- package/dist/types/learning/operations.d.ts +44 -0
- package/dist/types/learning/operations.d.ts.map +1 -1
- package/dist/types/learning/repository.d.ts +81 -0
- package/dist/types/learning/repository.d.ts.map +1 -1
- package/dist/types/learning/reward-calibration-closure.d.ts +2323 -0
- package/dist/types/learning/reward-calibration-closure.d.ts.map +1 -0
- package/dist/types/learning/reward-calibration.d.ts +1 -1
- package/dist/types/learning/reward-calibration.d.ts.map +1 -1
- package/dist/types/learning/reward-checks.d.ts +35 -0
- package/dist/types/learning/reward-checks.d.ts.map +1 -1
- package/dist/types/learning/transport.d.ts +103 -0
- package/dist/types/learning/transport.d.ts.map +1 -1
- package/dist/types/native-conversations/appimage-runtime.d.ts +12 -0
- package/dist/types/native-conversations/appimage-runtime.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-branches.d.ts +22 -0
- package/dist/types/native-conversations/collector-branches.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-contracts.d.ts +88 -0
- package/dist/types/native-conversations/collector-contracts.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-destinations.d.ts +18 -0
- package/dist/types/native-conversations/collector-destinations.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-errors.d.ts +13 -0
- package/dist/types/native-conversations/collector-errors.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-progress.d.ts +24 -0
- package/dist/types/native-conversations/collector-progress.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-store.d.ts +31 -0
- package/dist/types/native-conversations/collector-store.d.ts.map +1 -0
- package/dist/types/native-conversations/collector-sync-requests.d.ts +25 -0
- package/dist/types/native-conversations/collector-sync-requests.d.ts.map +1 -0
- package/dist/types/native-conversations/collector.d.ts +8 -0
- package/dist/types/native-conversations/collector.d.ts.map +1 -0
- package/dist/types/native-conversations/contracts.d.ts +47 -0
- package/dist/types/native-conversations/contracts.d.ts.map +1 -0
- package/dist/types/native-conversations/database.d.ts +14 -0
- package/dist/types/native-conversations/database.d.ts.map +1 -0
- package/dist/types/native-conversations/discovery.d.ts +9 -0
- package/dist/types/native-conversations/discovery.d.ts.map +1 -0
- package/dist/types/native-conversations/history.d.ts +23 -0
- package/dist/types/native-conversations/history.d.ts.map +1 -0
- package/dist/types/native-conversations/index.d.ts +12 -0
- package/dist/types/native-conversations/index.d.ts.map +1 -0
- package/dist/types/native-conversations/location.d.ts +3 -0
- package/dist/types/native-conversations/location.d.ts.map +1 -0
- package/dist/types/native-conversations/openclaw-cold-archive.d.ts +8 -0
- package/dist/types/native-conversations/openclaw-cold-archive.d.ts.map +1 -0
- package/dist/types/native-conversations/openclaw-database.d.ts +14 -0
- package/dist/types/native-conversations/openclaw-database.d.ts.map +1 -0
- package/dist/types/native-conversations/source-locations.d.ts +8 -0
- package/dist/types/native-conversations/source-locations.d.ts.map +1 -0
- package/dist/types/native-conversations/supervisor.d.ts +21 -0
- package/dist/types/native-conversations/supervisor.d.ts.map +1 -0
- package/dist/types/rewards.d.ts +50 -0
- package/dist/types/rewards.d.ts.map +1 -1
- package/dist/types/telemetry-analysis.d.ts +1 -1
- package/dist/types/telemetry-bundle.d.ts +2 -2
- package/dist/types/telemetry-catalog.d.ts +21 -21
- package/dist/types/telemetry.d.ts +2 -2
- package/package.json +15 -3
- package/schemas/learning/v1/command-request.schema.json +122 -91
- package/schemas/learning/v1/decision.schema.json +63 -0
- package/schemas/learning/v1/grade.schema.json +91 -0
- package/schemas/learning/v1/reward_check.schema.json +91 -0
- package/schemas/learning/v1/source.schema.json +28 -0
|
@@ -8,7 +8,7 @@ const Id = z.string().min(1).max(500);
|
|
|
8
8
|
const Hash = z.string().regex(/^[a-f0-9]{64}$/);
|
|
9
9
|
const Time = z.string().datetime().nullable();
|
|
10
10
|
const Tokens = z.number().int().nonnegative().nullable();
|
|
11
|
-
export const ConnectedSourceKindSchema = z.enum(["native_chat", "native_work", "codex", "claude_code", "hermes", "openclaw"]);
|
|
11
|
+
export const ConnectedSourceKindSchema = z.enum(["native_chat", "native_work", "codex", "claude_code", "hermes", "openclaw", "opencode", "grok_build", "pi", "oh_my_pi"]);
|
|
12
12
|
export const ConnectedUsageSchema = z.object({
|
|
13
13
|
invocationId: Id, inputTokens: Tokens, outputTokens: Tokens, cachedInputTokens: Tokens,
|
|
14
14
|
totalTokens: Tokens, provenance: z.enum(["billed_invocation", "reported_invocation", "reported_cumulative"]),
|
|
@@ -33,6 +33,7 @@ export const ConnectedSessionSchema = z.object({
|
|
|
33
33
|
schemaVersion: z.literal(CONNECTED_EVIDENCE_VERSION), normalizerVersion: z.literal(CONNECTED_NORMALIZER_VERSION),
|
|
34
34
|
origin: ConnectedSourceKindSchema, sessionId: Id, branchId: Id.nullable(), parentSessionId: Id.nullable(),
|
|
35
35
|
exporterVersion: z.string().max(200).nullable(),
|
|
36
|
+
acquisition: z.object({ machineId: Id, sourceInstanceId: Id }).strict().optional(),
|
|
36
37
|
sourceFiles: z.array(z.object({ path: Id, contentHash: Hash, sizeBytes: z.number().int().nonnegative() }).strict()).max(CONNECTED_EVIDENCE_LIMITS.files),
|
|
37
38
|
events: z.array(ConnectedEventSchema).max(CONNECTED_EVIDENCE_LIMITS.events),
|
|
38
39
|
unmappedEvents: z.array(ConnectedEventSchema).max(CONNECTED_EVIDENCE_LIMITS.events),
|
|
@@ -48,8 +49,8 @@ export function hasRecordedConnectedAnswer(event) {
|
|
|
48
49
|
return event.content.length > 0;
|
|
49
50
|
return typeof event.content !== "object" || !("text" in event.content) || typeof event.content.text === "string" && event.content.text.trim().length > 0;
|
|
50
51
|
}
|
|
51
|
-
/**
|
|
52
|
-
|
|
52
|
+
/** Shared integrity verification for metadata and evaluator evidence. */
|
|
53
|
+
function verifiedConnectedBoundary(sessionValue, boundaryId) {
|
|
53
54
|
const session = ConnectedSessionSchema.parse(sessionValue);
|
|
54
55
|
const { contentHash: actual, ...body } = session;
|
|
55
56
|
if (contentHash(body) !== actual)
|
|
@@ -65,7 +66,16 @@ export function resolveConnectedBoundary(sessionValue, boundaryId) {
|
|
|
65
66
|
const answer = boundary.projection === "conversation" ? answers.map(event => event.content) : answers.at(-1)?.content ?? null;
|
|
66
67
|
if ((answers.length ? contentHash(answer) : null) !== boundary.outputHash)
|
|
67
68
|
throw new Error("connected_output_hash_mismatch");
|
|
68
|
-
|
|
69
|
+
return { input, observed, answer, boundary };
|
|
70
|
+
}
|
|
71
|
+
/** Status/discovery returns no evaluator payload. Large retained histories still
|
|
72
|
+
* require the exact session, input, observed-output and answer hashes to match. */
|
|
73
|
+
export function resolveConnectedBoundaryMetadata(sessionValue, boundaryId) {
|
|
74
|
+
return verifiedConnectedBoundary(sessionValue, boundaryId).boundary;
|
|
75
|
+
}
|
|
76
|
+
/** Bytes stay in one immutable snapshot; cases retain cutoffs, not copied history. */
|
|
77
|
+
export function resolveConnectedBoundary(sessionValue, boundaryId) {
|
|
78
|
+
const value = verifiedConnectedBoundary(sessionValue, boundaryId);
|
|
69
79
|
if (new TextEncoder().encode(JSON.stringify(value)).length > CONNECTED_EVIDENCE_LIMITS.evaluatorBytes)
|
|
70
80
|
throw new Error("connected_evaluator_context_too_large");
|
|
71
81
|
return value;
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import { contentHash } from "@openpond/harness";
|
|
2
|
-
import { CONNECTED_EVIDENCE_LIMITS } from "./contracts.js";
|
|
3
|
-
import { connectedJsonLines } from "./normalize.js";
|
|
2
|
+
import { CONNECTED_EVIDENCE_LIMITS, CONNECTED_NORMALIZER_VERSION, } from "./contracts.js";
|
|
3
|
+
import { connectedJsonLines, normalizeConnectedSession } from "./normalize.js";
|
|
4
|
+
import { parseGrokBuildSession } from "./sources/grok-build.js";
|
|
5
|
+
import { parsePiSession } from "./sources/pi.js";
|
|
6
|
+
import { parseOpenCodeSession } from "./sources/opencode.js";
|
|
7
|
+
import { CODEX_AUTHORIZATION_PROJECTION_POLICY } from "./sources/codex-authorization.js";
|
|
4
8
|
import { parseCodexSession } from "./sources/codex.js";
|
|
5
9
|
import { parseClaudeSession } from "./sources/claude-code.js";
|
|
6
|
-
import { parseHermesEvidence, parseOpenClawEvidence } from "./sources/history.js";
|
|
10
|
+
import { parseHermesEvidence, parseOpenClawEvidence, } from "./sources/history.js";
|
|
11
|
+
/** Cache identity includes source projection policy, independent of file mtimes. */
|
|
12
|
+
export function agentImportNormalizerRevision(source) {
|
|
13
|
+
return source === "codex" ? `${CONNECTED_NORMALIZER_VERSION}/${CODEX_AUTHORIZATION_PROJECTION_POLICY}` : CONNECTED_NORMALIZER_VERSION;
|
|
14
|
+
}
|
|
7
15
|
/** Never admits malformed files partly or executes referenced paths/commands. */
|
|
8
16
|
export function previewAgentImport(input) {
|
|
9
|
-
if (!input.files.length ||
|
|
17
|
+
if (!input.files.length ||
|
|
18
|
+
input.files.length > CONNECTED_EVIDENCE_LIMITS.files)
|
|
10
19
|
throw new Error("Choose between 1 and 100 source files.");
|
|
11
20
|
const paths = new Set();
|
|
12
21
|
let bytes = 0;
|
|
13
22
|
for (const file of input.files) {
|
|
14
|
-
if (file.path.length > 500 ||
|
|
23
|
+
if (file.path.length > 500 ||
|
|
24
|
+
file.path.startsWith("/") ||
|
|
25
|
+
file.path.includes("\\") ||
|
|
26
|
+
file.path
|
|
27
|
+
.split("/")
|
|
28
|
+
.some((part) => !part || part === "." || part === "..") ||
|
|
29
|
+
paths.has(file.path))
|
|
15
30
|
throw new Error("Unsafe or duplicate source file path.");
|
|
16
31
|
paths.add(file.path);
|
|
17
32
|
bytes += new TextEncoder().encode(file.text).length;
|
|
@@ -21,22 +36,47 @@ export function previewAgentImport(input) {
|
|
|
21
36
|
const sessions = [], issues = [];
|
|
22
37
|
const attempt = (file, parse) => {
|
|
23
38
|
try {
|
|
24
|
-
sessions.push(...parse())
|
|
39
|
+
sessions.push(...parse().map((session) => input.acquisition
|
|
40
|
+
? normalizeConnectedSession({
|
|
41
|
+
origin: session.origin,
|
|
42
|
+
sessionId: session.sessionId,
|
|
43
|
+
branchId: session.branchId,
|
|
44
|
+
parentSessionId: session.parentSessionId,
|
|
45
|
+
exporterVersion: session.exporterVersion,
|
|
46
|
+
files: input.files.filter((item) => session.sourceFiles.some((file) => file.path === item.path)),
|
|
47
|
+
events: session.events,
|
|
48
|
+
unmappedEvents: session.unmappedEvents,
|
|
49
|
+
warnings: session.warnings,
|
|
50
|
+
contextComplete: !session.boundaries.some((boundary) => boundary.coverage.context === "unknown"),
|
|
51
|
+
acquisition: input.acquisition,
|
|
52
|
+
})
|
|
53
|
+
: session));
|
|
25
54
|
}
|
|
26
55
|
catch (error) {
|
|
27
|
-
issues.push({
|
|
56
|
+
issues.push({
|
|
57
|
+
file,
|
|
58
|
+
message: error instanceof Error ? error.message : "Unsupported session.",
|
|
59
|
+
});
|
|
28
60
|
}
|
|
29
61
|
};
|
|
30
|
-
if (input.source === "openclaw"
|
|
62
|
+
if (input.source === "openclaw" &&
|
|
63
|
+
input.files.some((file) => file.path.endsWith("manifest.json")))
|
|
31
64
|
attempt("manifest.json", () => {
|
|
32
65
|
const root = input.files[0].path.split("/")[0];
|
|
33
|
-
const files = !input.files.some(file => file.path === "manifest.json") &&
|
|
34
|
-
|
|
66
|
+
const files = !input.files.some((file) => file.path === "manifest.json") &&
|
|
67
|
+
input.files.every((file) => file.path.startsWith(`${root}/`))
|
|
68
|
+
? input.files.map((file) => ({
|
|
69
|
+
...file,
|
|
70
|
+
path: file.path.slice(root.length + 1),
|
|
71
|
+
}))
|
|
72
|
+
: input.files;
|
|
35
73
|
return [parseOpenClawEvidence(files)];
|
|
36
74
|
});
|
|
37
75
|
else
|
|
38
76
|
for (const file of input.files)
|
|
39
77
|
attempt(file.path, () => {
|
|
78
|
+
if (input.source === "opencode")
|
|
79
|
+
return [parseOpenCodeSession(file, JSON.parse(file.text))];
|
|
40
80
|
let rows;
|
|
41
81
|
if (input.source === "hermes" && file.path.endsWith(".json")) {
|
|
42
82
|
const value = JSON.parse(file.text.replace(/^\uFEFF/u, ""));
|
|
@@ -46,26 +86,46 @@ export function previewAgentImport(input) {
|
|
|
46
86
|
}
|
|
47
87
|
else
|
|
48
88
|
rows = connectedJsonLines(file);
|
|
89
|
+
if (input.source === "grok_build")
|
|
90
|
+
return [parseGrokBuildSession(file, rows)];
|
|
91
|
+
if (input.source === "pi" ||
|
|
92
|
+
input.source === "oh_my_pi" ||
|
|
93
|
+
input.source === "openclaw")
|
|
94
|
+
return [parsePiSession(file, rows, input.source, input.branchLeafId)];
|
|
49
95
|
if (input.source === "codex")
|
|
50
96
|
return [parseCodexSession(file, rows)];
|
|
51
97
|
if (input.source === "claude_code")
|
|
52
98
|
return [parseClaudeSession(file, rows, input.branchLeafId)];
|
|
53
99
|
if (input.source === "hermes")
|
|
54
|
-
return rows.map(row => parseHermesEvidence(file, row));
|
|
100
|
+
return rows.map((row) => parseHermesEvidence(file, row));
|
|
55
101
|
throw new Error("Unsupported source adapter.");
|
|
56
102
|
});
|
|
57
|
-
if (sessions.length > CONNECTED_EVIDENCE_LIMITS.sessions ||
|
|
58
|
-
|
|
103
|
+
if (sessions.length > CONNECTED_EVIDENCE_LIMITS.sessions ||
|
|
104
|
+
sessions.reduce((total, session) => total + session.events.length + session.unmappedEvents.length, 0) > CONNECTED_EVIDENCE_LIMITS.events ||
|
|
105
|
+
sessions.reduce((total, session) => total + session.boundaries.length, 0) >
|
|
106
|
+
CONNECTED_EVIDENCE_LIMITS.boundaries)
|
|
59
107
|
throw new Error("Import exceeds the session/event/boundary limit.");
|
|
60
108
|
const identities = new Map();
|
|
61
109
|
for (const session of sessions) {
|
|
62
|
-
const key = JSON.stringify([
|
|
110
|
+
const key = JSON.stringify([
|
|
111
|
+
session.origin,
|
|
112
|
+
session.sessionId,
|
|
113
|
+
session.branchId,
|
|
114
|
+
]);
|
|
63
115
|
const prior = identities.get(key);
|
|
64
116
|
if (prior && prior !== session.contentHash)
|
|
65
117
|
throw new Error("The same session has different contents in this selection. Choose one export.");
|
|
66
118
|
identities.set(key, session.contentHash);
|
|
67
119
|
}
|
|
68
|
-
return {
|
|
69
|
-
|
|
70
|
-
|
|
120
|
+
return {
|
|
121
|
+
schemaVersion: "openpond.agentImportPreview.v1",
|
|
122
|
+
source: input.source,
|
|
123
|
+
sourceHash: contentHash({
|
|
124
|
+
source: input.source,
|
|
125
|
+
files: [...input.files].sort((a, b) => a.path.localeCompare(b.path)),
|
|
126
|
+
branchLeafId: input.branchLeafId ?? null,
|
|
127
|
+
}),
|
|
128
|
+
sessions: sessions.filter((session, index) => sessions.findIndex((other) => other.contentHash === session.contentHash) === index),
|
|
129
|
+
issues,
|
|
130
|
+
};
|
|
71
131
|
}
|
|
@@ -20,7 +20,7 @@ export function normalizeConnectedSession(input) {
|
|
|
20
20
|
throw new Error("The session has no retained user request.");
|
|
21
21
|
if (starts.length + (input.includeConversation === false ? 0 : 1) > CONNECTED_EVIDENCE_LIMITS.boundaries)
|
|
22
22
|
throw new Error("connected_boundary_limit");
|
|
23
|
-
const familyKey = `family-${contentHash([input.origin, input.parentSessionId ?? input.sessionId]).slice(0, 32)}`;
|
|
23
|
+
const familyKey = `family-${contentHash([input.origin, ...(input.acquisition ? [input.acquisition.sourceInstanceId] : []), input.parentSessionId ?? input.sessionId]).slice(0, 32)}`;
|
|
24
24
|
const boundaries = starts.map((start, position) => {
|
|
25
25
|
const end = starts[position + 1] ?? events.length;
|
|
26
26
|
const observed = events.slice(start + 1, end);
|
|
@@ -35,7 +35,7 @@ export function normalizeConnectedSession(input) {
|
|
|
35
35
|
const compacted = events.slice(0, start + 1).some(event => event.kind === "compaction");
|
|
36
36
|
const unknown = observed.some(event => event.kind === "unknown");
|
|
37
37
|
return {
|
|
38
|
-
id: `case-${contentHash([input.origin, input.sessionId, input.branchId ?? null, events[start].id]).slice(0, 40)}`,
|
|
38
|
+
id: `case-${contentHash([input.origin, ...(input.acquisition ? [input.acquisition.sourceInstanceId] : []), input.sessionId, input.branchId ?? null, events[start].id]).slice(0, 40)}`,
|
|
39
39
|
familyKey, projection: "turn", requestEventId: events[start].id, start, end,
|
|
40
40
|
inputHash: contentHash(events.slice(0, start + 1)), outputHash: answer ? contentHash(answer.content) : null,
|
|
41
41
|
revisionHash: contentHash(observed), terminal: status === "completed" || status === "failed" || status === "cancelled" ? status : "unknown",
|
|
@@ -50,7 +50,7 @@ export function normalizeConnectedSession(input) {
|
|
|
50
50
|
if (input.includeConversation !== false) {
|
|
51
51
|
const first = boundaries[0], last = boundaries.at(-1);
|
|
52
52
|
const observed = events.slice(first.start + 1), answers = observed.filter(hasRecordedConnectedAnswer);
|
|
53
|
-
boundaries.push({ ...first, id: `conversation-${contentHash([input.origin, input.sessionId, input.branchId ?? null]).slice(0, 40)}`,
|
|
53
|
+
boundaries.push({ ...first, id: `conversation-${contentHash([input.origin, ...(input.acquisition ? [input.acquisition.sourceInstanceId] : []), input.sessionId, input.branchId ?? null]).slice(0, 40)}`,
|
|
54
54
|
projection: "conversation", end: events.length, outputHash: answers.length ? contentHash(answers.map(event => event.content)) : null,
|
|
55
55
|
revisionHash: contentHash(observed), terminal: last.terminal, coverage: { ...first.coverage,
|
|
56
56
|
answer: answers.length > 0, process: boundaries.some(item => item.coverage.process === "partial") ? "partial" : boundaries.some(item => item.coverage.process === "retained") ? "retained" : "absent",
|
|
@@ -61,6 +61,7 @@ export function normalizeConnectedSession(input) {
|
|
|
61
61
|
const body = { schemaVersion: CONNECTED_EVIDENCE_VERSION, normalizerVersion: CONNECTED_NORMALIZER_VERSION,
|
|
62
62
|
origin: input.origin, sessionId: input.sessionId, branchId: input.branchId ?? null, parentSessionId: input.parentSessionId ?? null,
|
|
63
63
|
exporterVersion: input.exporterVersion ?? null, sourceFiles: input.files.map(connectedFileIdentity).sort((a, b) => a.path.localeCompare(b.path)),
|
|
64
|
+
...(input.acquisition ? { acquisition: input.acquisition } : {}),
|
|
64
65
|
events, unmappedEvents: (input.unmappedEvents ?? []).map((event, sequence) => ({ ...event, sequence })), boundaries, warnings: input.warnings ?? [] };
|
|
65
66
|
const session = ConnectedSessionSchema.parse({ ...body, contentHash: contentHash(body) });
|
|
66
67
|
if (new TextEncoder().encode(JSON.stringify(session)).length > CONNECTED_EVIDENCE_LIMITS.decodedBytes)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { contentHash } from "@openpond/harness";
|
|
2
|
+
import { connectedTimestamp, object } from "../normalize.js";
|
|
3
|
+
/** One native graph owns selection, import and Desktop inspection semantics. */
|
|
4
|
+
export function claudeGraph(rows) {
|
|
5
|
+
const messages = rows.filter(row => (row.type === "user" || row.type === "assistant") && typeof row.uuid === "string");
|
|
6
|
+
const sessions = new Set(messages.flatMap(row => typeof row.sessionId === "string" ? [row.sessionId] : []));
|
|
7
|
+
if (sessions.size !== 1 || !messages.length)
|
|
8
|
+
throw new Error("A supported Claude Code transcript must identify one session with UUID messages.");
|
|
9
|
+
const sessionId = [...sessions][0];
|
|
10
|
+
const nodes = rows.filter(row => typeof row.uuid === "string" && row.sessionId === sessionId);
|
|
11
|
+
const byId = new Map(nodes.map(row => [row.uuid, row]));
|
|
12
|
+
if (byId.size !== nodes.length)
|
|
13
|
+
throw new Error("Claude Code transcript contains duplicate message UUIDs.");
|
|
14
|
+
const parents = new Set(nodes.flatMap(row => typeof row.parentUuid === "string" ? [row.parentUuid] : []));
|
|
15
|
+
const leaves = nodes.filter(row => !parents.has(row.uuid));
|
|
16
|
+
if (!leaves.length)
|
|
17
|
+
throw new Error("Claude Code transcript has no selectable branch leaf.");
|
|
18
|
+
function select(leafId) {
|
|
19
|
+
let current = byId.get(leafId);
|
|
20
|
+
if (!current)
|
|
21
|
+
throw new Error("The selected Claude Code branch leaf is missing.");
|
|
22
|
+
const chain = [], seen = new Set();
|
|
23
|
+
let missingParent = false;
|
|
24
|
+
while (current) {
|
|
25
|
+
const id = current.uuid;
|
|
26
|
+
if (seen.has(id))
|
|
27
|
+
throw new Error("Claude Code branch contains a parent cycle.");
|
|
28
|
+
seen.add(id);
|
|
29
|
+
chain.unshift(current);
|
|
30
|
+
const parent = current.parentUuid;
|
|
31
|
+
if (typeof parent !== "string")
|
|
32
|
+
break;
|
|
33
|
+
current = byId.get(parent);
|
|
34
|
+
if (!current)
|
|
35
|
+
missingParent = true;
|
|
36
|
+
}
|
|
37
|
+
return { chain, missingParent, chainHash: contentHash(chain) };
|
|
38
|
+
}
|
|
39
|
+
function branches() {
|
|
40
|
+
if (leaves.length > 256)
|
|
41
|
+
throw new Error("This transcript exceeds the 256-branch inspection limit.");
|
|
42
|
+
return leaves.map(leaf => {
|
|
43
|
+
const selected = select(leaf.uuid);
|
|
44
|
+
const messages = selected.chain.filter(row => row.type === "user" || row.type === "assistant");
|
|
45
|
+
const last = messages.at(-1);
|
|
46
|
+
const content = object(last?.message).content;
|
|
47
|
+
const title = typeof content === "string" ? content : Array.isArray(content) ? content.flatMap(block => typeof object(block).text === "string" ? [object(block).text] : []).join(" ") : "";
|
|
48
|
+
return { leafId: leaf.uuid, chainHash: selected.chainHash, title: title.slice(0, 180), updatedAt: connectedTimestamp(last?.timestamp), messages: messages.length };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return { sessionId, leaves, select, branches };
|
|
52
|
+
}
|
|
@@ -1,38 +1,16 @@
|
|
|
1
|
+
import { claudeGraph } from "./claude-branches.js";
|
|
1
2
|
import { contentHash } from "@openpond/harness";
|
|
2
3
|
import { connectedTimestamp, event, json, normalizeConnectedSession, object } from "../normalize.js";
|
|
3
4
|
import { sourceUsage } from "./usage.js";
|
|
4
5
|
export function parseClaudeSession(file, rows, leafId) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
if (sessionIds.size !== 1 || !messages.length)
|
|
8
|
-
throw new Error("A supported Claude Code transcript must identify one session with UUID messages.");
|
|
9
|
-
const byId = new Map(messages.map(row => [row.uuid, row]));
|
|
10
|
-
if (byId.size !== messages.length)
|
|
11
|
-
throw new Error("Claude Code transcript contains duplicate message UUIDs.");
|
|
12
|
-
const parents = new Set(messages.flatMap(row => typeof row.parentUuid === "string" ? [row.parentUuid] : []));
|
|
13
|
-
const leaves = messages.filter(row => !parents.has(row.uuid));
|
|
14
|
-
if (!leafId && leaves.length !== 1)
|
|
6
|
+
const graph = claudeGraph(rows);
|
|
7
|
+
if (!leafId && graph.leaves.length !== 1)
|
|
15
8
|
throw new Error("This transcript has multiple branches. Select a branch leaf UUID before importing.");
|
|
16
|
-
|
|
17
|
-
if (!current)
|
|
18
|
-
throw new Error("The selected Claude Code branch leaf is missing.");
|
|
19
|
-
const chain = [], seen = new Set();
|
|
20
|
-
let missingParent = false;
|
|
21
|
-
while (current) {
|
|
22
|
-
const id = current.uuid;
|
|
23
|
-
if (seen.has(id))
|
|
24
|
-
throw new Error("Claude Code branch contains a parent cycle.");
|
|
25
|
-
seen.add(id);
|
|
26
|
-
chain.unshift(current);
|
|
27
|
-
const parent = current.parentUuid;
|
|
28
|
-
if (typeof parent !== "string")
|
|
29
|
-
break;
|
|
30
|
-
current = byId.get(parent);
|
|
31
|
-
if (!current)
|
|
32
|
-
missingParent = true;
|
|
33
|
-
}
|
|
9
|
+
const { chain, missingParent } = graph.select(leafId ?? graph.leaves[0].uuid);
|
|
34
10
|
const events = [];
|
|
35
11
|
for (const row of chain) {
|
|
12
|
+
if (row.type !== "user" && row.type !== "assistant")
|
|
13
|
+
continue;
|
|
36
14
|
const message = object(row.message), content = message.content;
|
|
37
15
|
const base = { occurredAt: connectedTimestamp(row.timestamp), parentId: typeof row.parentUuid === "string" ? row.parentUuid : null };
|
|
38
16
|
const role = row.type === "user" ? "user" : "assistant";
|
|
@@ -61,7 +39,7 @@ export function parseClaudeSession(file, rows, leafId) {
|
|
|
61
39
|
}
|
|
62
40
|
if (rows.some(row => row.type === "system" && row.subtype === "compact_boundary"))
|
|
63
41
|
events.unshift(event({ id: `compaction-${contentHash(rows.filter(row => row.subtype === "compact_boundary")).slice(0, 32)}`, kind: "compaction", content: { source: "claude_compact_boundary" } }));
|
|
64
|
-
return normalizeConnectedSession({ origin: "claude_code", sessionId:
|
|
42
|
+
return normalizeConnectedSession({ origin: "claude_code", sessionId: graph.sessionId,
|
|
65
43
|
// A chosen leaf is a cutoff, not a new family or the identity of every turn.
|
|
66
44
|
branchId: null, parentSessionId: typeof chain[0]?.parentSessionId === "string" ? chain[0].parentSessionId : null,
|
|
67
45
|
exporterVersion: typeof chain.at(-1)?.version === "string" ? chain.at(-1).version : null,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { contentHash } from "@openpond/harness";
|
|
2
|
+
import { object } from "../normalize.js";
|
|
3
|
+
export const CODEX_AUTHORIZATION_PROJECTION_POLICY = "codex-native-authorization-v1";
|
|
4
|
+
const POLICY = CODEX_AUTHORIZATION_PROJECTION_POLICY;
|
|
5
|
+
/** Native approved-command rules are account authority, not observed tool calls.
|
|
6
|
+
* Retain all other context and the original file/event identity. Unknown formats
|
|
7
|
+
* remain untouched for ordinary privacy admission; this is not secret scrubbing.
|
|
8
|
+
*/
|
|
9
|
+
export function projectCodexAuthorization(row) {
|
|
10
|
+
const omissions = [];
|
|
11
|
+
const omit = (sourcePath, value, entries) => {
|
|
12
|
+
const receipt = { policy: POLICY, sourcePath, sourceHash: contentHash(value), entries };
|
|
13
|
+
omissions.push(receipt);
|
|
14
|
+
return receipt;
|
|
15
|
+
};
|
|
16
|
+
const permissionsText = (text, path) => text.replace(/<permissions instructions>[\s\S]*?<\/permissions instructions>/gu, block => block.replace(/^## Approved command prefixes\r?\n[\s\S]*?(?=^## |<\/permissions instructions>)/gmu, section => {
|
|
17
|
+
const receipt = omit(path, section, null);
|
|
18
|
+
return `## Approved command prefixes\n[Native authorization rules omitted; policy ${receipt.policy}; source hash ${receipt.sourceHash}]\n`;
|
|
19
|
+
}));
|
|
20
|
+
const payload = object(row.payload);
|
|
21
|
+
if (row.type === "response_item" && payload.type === "message" && ["system", "developer"].includes(String(payload.role))) {
|
|
22
|
+
const content = typeof payload.content === "string"
|
|
23
|
+
? permissionsText(payload.content, "/payload/content")
|
|
24
|
+
: Array.isArray(payload.content) ? payload.content.map((value, index) => {
|
|
25
|
+
const block = object(value);
|
|
26
|
+
return typeof block.text === "string"
|
|
27
|
+
? { ...block, text: permissionsText(block.text, `/payload/content/${index}/text`) }
|
|
28
|
+
: value;
|
|
29
|
+
}) : payload.content;
|
|
30
|
+
return { row: { ...row, payload: { ...payload, content } }, omissions };
|
|
31
|
+
}
|
|
32
|
+
if (row.type === "world_state") {
|
|
33
|
+
const state = object(payload.state), permissions = object(state.permissions);
|
|
34
|
+
if (Array.isArray(permissions.approved_command_prefixes)) {
|
|
35
|
+
const receipt = omit("/payload/state/permissions/approved_command_prefixes", permissions.approved_command_prefixes, permissions.approved_command_prefixes.length);
|
|
36
|
+
return { row: { ...row, payload: { ...payload, state: { ...state, permissions: { ...permissions, approved_command_prefixes: { omitted: "native_authorization_state", ...receipt } } } } }, omissions };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { row, omissions };
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { contentHash } from "@openpond/harness";
|
|
2
2
|
import { connectedTimestamp, event, json, normalizeConnectedSession, object } from "../normalize.js";
|
|
3
|
+
import { projectCodexAuthorization } from "./codex-authorization.js";
|
|
3
4
|
import { sourceUsage } from "./usage.js";
|
|
4
5
|
export function parseCodexSession(file, rows) {
|
|
5
6
|
const metadata = object(rows.find(row => row.type === "session_meta")?.payload);
|
|
@@ -7,8 +8,11 @@ export function parseCodexSession(file, rows) {
|
|
|
7
8
|
throw new Error("This is not a supported Codex rollout: session_meta.id is missing.");
|
|
8
9
|
const events = [];
|
|
9
10
|
let turnId = null;
|
|
11
|
+
let authorizationOmissions = 0;
|
|
10
12
|
for (const [ordinal, row] of rows.entries()) {
|
|
11
|
-
const
|
|
13
|
+
const projected = projectCodexAuthorization(row);
|
|
14
|
+
authorizationOmissions += projected.omissions.length;
|
|
15
|
+
const payload = object(projected.row.payload);
|
|
12
16
|
if (row.type === "session_meta")
|
|
13
17
|
continue;
|
|
14
18
|
if (row.type === "turn_context") {
|
|
@@ -44,9 +48,9 @@ export function parseCodexSession(file, rows) {
|
|
|
44
48
|
events.push(event({ ...base, kind: "unknown", content: json(payload) }));
|
|
45
49
|
}
|
|
46
50
|
else
|
|
47
|
-
events.push(event({ ...base, kind: "unknown", content: json(row) }));
|
|
51
|
+
events.push(event({ ...base, kind: "unknown", content: json(projected.row) }));
|
|
48
52
|
}
|
|
49
53
|
return normalizeConnectedSession({ origin: "codex", sessionId: metadata.id, parentSessionId: typeof metadata.forked_from_id === "string" ? metadata.forked_from_id : null,
|
|
50
54
|
exporterVersion: typeof metadata.cli_version === "string" ? metadata.cli_version : null, files: [file], events,
|
|
51
|
-
warnings: ["Codex cumulative usage is retained as reported totals and is not summed as billed invocations.", "Referenced files are unavailable unless separately admitted."] });
|
|
55
|
+
warnings: ["Codex cumulative usage is retained as reported totals and is not summed as billed invocations.", "Referenced files are unavailable unless separately admitted.", ...(authorizationOmissions ? [`Codex native authorization state excluded by codex-native-authorization-v1 from ${authorizationOmissions} locations. Omission markers retain source-section hashes; sourceFiles retains original byte hashes. Other conversation evidence is unchanged and still requires privacy admission.`] : [])] });
|
|
52
56
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { connectedTimestamp, event, json, normalizeConnectedSession, object, } from "../normalize.js";
|
|
2
|
+
import { sourceUsage } from "./usage.js";
|
|
3
|
+
/** Retained ACP updates are native Grok history; Markdown exports are not accepted. */
|
|
4
|
+
export function parseGrokBuildSession(file, rows) {
|
|
5
|
+
const sessions = new Set(rows
|
|
6
|
+
.map((row) => object(row.params).sessionId)
|
|
7
|
+
.filter((value) => typeof value === "string"));
|
|
8
|
+
if (sessions.size !== 1)
|
|
9
|
+
throw new Error("Grok Build updates must identify exactly one retained session.");
|
|
10
|
+
const events = [], ids = new Set(), calls = new Map();
|
|
11
|
+
let user = null, answer = null, completed = true;
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
const params = object(row.params), update = object(params.update), metadata = object(params._meta), kind = update.sessionUpdate;
|
|
14
|
+
if (typeof metadata.eventId !== "string" || ids.has(metadata.eventId))
|
|
15
|
+
throw new Error("Grok Build updates require unique native event IDs.");
|
|
16
|
+
ids.add(metadata.eventId);
|
|
17
|
+
const id = metadata.eventId, time = connectedTimestamp(metadata.agentTimestampMs ??
|
|
18
|
+
(typeof row.timestamp === "number" ? row.timestamp * 1000 : null));
|
|
19
|
+
const content = object(update.content);
|
|
20
|
+
if (kind === "user_message_chunk") {
|
|
21
|
+
if (content.type !== "text" || typeof content.text !== "string")
|
|
22
|
+
throw new Error("Grok user content type requires a qualified adapter.");
|
|
23
|
+
if (!user || completed) {
|
|
24
|
+
user = event({
|
|
25
|
+
id,
|
|
26
|
+
kind: "message",
|
|
27
|
+
role: "user",
|
|
28
|
+
occurredAt: time,
|
|
29
|
+
content: content.text,
|
|
30
|
+
});
|
|
31
|
+
events.push(user);
|
|
32
|
+
answer = null;
|
|
33
|
+
completed = false;
|
|
34
|
+
}
|
|
35
|
+
else
|
|
36
|
+
user.content = String(user.content) + content.text;
|
|
37
|
+
}
|
|
38
|
+
else if (kind === "agent_message_chunk") {
|
|
39
|
+
if (content.type !== "text" || typeof content.text !== "string") {
|
|
40
|
+
events.push(event({
|
|
41
|
+
id,
|
|
42
|
+
kind: "unknown",
|
|
43
|
+
occurredAt: time,
|
|
44
|
+
content: json(update),
|
|
45
|
+
}));
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!answer) {
|
|
49
|
+
answer = event({
|
|
50
|
+
id,
|
|
51
|
+
kind: "message",
|
|
52
|
+
role: "assistant",
|
|
53
|
+
occurredAt: time,
|
|
54
|
+
content: content.text,
|
|
55
|
+
});
|
|
56
|
+
events.push(answer);
|
|
57
|
+
}
|
|
58
|
+
else
|
|
59
|
+
answer.content = String(answer.content) + content.text;
|
|
60
|
+
}
|
|
61
|
+
else if (kind === "tool_call" || kind === "tool_call_update") {
|
|
62
|
+
const callId = typeof update.toolCallId === "string" ? update.toolCallId : null;
|
|
63
|
+
if (!callId) {
|
|
64
|
+
events.push(event({
|
|
65
|
+
id,
|
|
66
|
+
kind: "unknown",
|
|
67
|
+
occurredAt: time,
|
|
68
|
+
content: json(update),
|
|
69
|
+
}));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
let call = calls.get(callId);
|
|
73
|
+
if (!call) {
|
|
74
|
+
call = event({
|
|
75
|
+
id: `${id}:call`,
|
|
76
|
+
kind: "tool_call",
|
|
77
|
+
callId,
|
|
78
|
+
occurredAt: time,
|
|
79
|
+
content: json({ title: update.title, input: update.rawInput }),
|
|
80
|
+
});
|
|
81
|
+
calls.set(callId, call);
|
|
82
|
+
events.push(call);
|
|
83
|
+
}
|
|
84
|
+
else if (update.rawInput !== undefined)
|
|
85
|
+
call.content = json({ title: update.title, input: update.rawInput });
|
|
86
|
+
if (update.status === "completed" || update.status === "failed")
|
|
87
|
+
events.push(event({
|
|
88
|
+
id: `${id}:result`,
|
|
89
|
+
kind: "tool_result",
|
|
90
|
+
role: "tool",
|
|
91
|
+
callId,
|
|
92
|
+
occurredAt: time,
|
|
93
|
+
content: json({
|
|
94
|
+
status: update.status,
|
|
95
|
+
output: update.rawOutput,
|
|
96
|
+
content: update.content,
|
|
97
|
+
}),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
else if (kind === "turn_completed") {
|
|
101
|
+
const usage = object(update.usage), invocationId = typeof update.prompt_id === "string" ? update.prompt_id : id;
|
|
102
|
+
events.push(event({
|
|
103
|
+
id,
|
|
104
|
+
kind: "terminal",
|
|
105
|
+
occurredAt: time,
|
|
106
|
+
content: {
|
|
107
|
+
status: update.stop_reason === "end_turn"
|
|
108
|
+
? "completed"
|
|
109
|
+
: update.stop_reason === "cancelled"
|
|
110
|
+
? "cancelled"
|
|
111
|
+
: "failed",
|
|
112
|
+
},
|
|
113
|
+
usage: sourceUsage({
|
|
114
|
+
input_tokens: usage.inputTokens,
|
|
115
|
+
output_tokens: usage.outputTokens,
|
|
116
|
+
cached_input_tokens: usage.cachedReadTokens,
|
|
117
|
+
total_tokens: usage.totalTokens,
|
|
118
|
+
}, invocationId),
|
|
119
|
+
}));
|
|
120
|
+
completed = true;
|
|
121
|
+
answer = null;
|
|
122
|
+
user = null;
|
|
123
|
+
}
|
|
124
|
+
else if (kind !== "agent_thought_chunk")
|
|
125
|
+
events.push(event({ id, kind: "unknown", occurredAt: time, content: json(update) }));
|
|
126
|
+
}
|
|
127
|
+
return normalizeConnectedSession({
|
|
128
|
+
origin: "grok_build",
|
|
129
|
+
sessionId: String([...sessions][0]),
|
|
130
|
+
files: [file],
|
|
131
|
+
events,
|
|
132
|
+
contextComplete: false,
|
|
133
|
+
warnings: [
|
|
134
|
+
"Grok retained ACP updates omit system context and encrypted reasoning. Tool coverage reflects only logged updates.",
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { connectedTimestamp, event, json, normalizeConnectedSession, object, } from "../normalize.js";
|
|
2
|
+
import { sourceUsage } from "./usage.js";
|
|
3
|
+
export function parseOpenCodeSession(file, value) {
|
|
4
|
+
const root = object(value), info = object(root.info);
|
|
5
|
+
if (typeof info.id !== "string" || !Array.isArray(root.messages))
|
|
6
|
+
throw new Error("OpenCode requires its native JSON session export (info and messages).");
|
|
7
|
+
const events = [];
|
|
8
|
+
for (const raw of root.messages) {
|
|
9
|
+
const item = object(raw), message = object(item.info), time = object(message.time);
|
|
10
|
+
if (typeof message.id !== "string" ||
|
|
11
|
+
message.sessionID !== info.id ||
|
|
12
|
+
!Array.isArray(item.parts))
|
|
13
|
+
throw new Error("OpenCode message identity does not match its session.");
|
|
14
|
+
const role = message.role;
|
|
15
|
+
if (role !== "user" && role !== "assistant")
|
|
16
|
+
throw new Error("Unsupported OpenCode message role.");
|
|
17
|
+
const base = {
|
|
18
|
+
occurredAt: connectedTimestamp(time.created),
|
|
19
|
+
parentId: typeof message.parentID === "string" ? message.parentID : null,
|
|
20
|
+
};
|
|
21
|
+
const parts = item.parts.map(object);
|
|
22
|
+
if (parts.some((part) => part.sessionID !== info.id ||
|
|
23
|
+
part.messageID !== message.id ||
|
|
24
|
+
typeof part.id !== "string"))
|
|
25
|
+
throw new Error("OpenCode part identity does not match its message.");
|
|
26
|
+
const text = parts
|
|
27
|
+
.filter((part) => part.type === "text")
|
|
28
|
+
.map((part) => String(part.text ?? ""))
|
|
29
|
+
.join("\n");
|
|
30
|
+
const tokens = object(message.tokens), cache = object(tokens.cache);
|
|
31
|
+
const usage = sourceUsage({
|
|
32
|
+
input_tokens: tokens.input,
|
|
33
|
+
output_tokens: tokens.output,
|
|
34
|
+
cached_input_tokens: cache.read,
|
|
35
|
+
total_tokens: tokens.total,
|
|
36
|
+
}, message.id);
|
|
37
|
+
if (text || role === "user")
|
|
38
|
+
events.push(event({
|
|
39
|
+
...base,
|
|
40
|
+
id: message.id,
|
|
41
|
+
kind: "message",
|
|
42
|
+
role,
|
|
43
|
+
content: text,
|
|
44
|
+
usage,
|
|
45
|
+
}));
|
|
46
|
+
else if (usage)
|
|
47
|
+
events.push(event({
|
|
48
|
+
...base,
|
|
49
|
+
id: `${message.id}:usage`,
|
|
50
|
+
kind: "usage",
|
|
51
|
+
content: null,
|
|
52
|
+
usage,
|
|
53
|
+
}));
|
|
54
|
+
for (const part of parts) {
|
|
55
|
+
const id = String(part.id);
|
|
56
|
+
if (part.type === "tool") {
|
|
57
|
+
const state = object(part.state), callId = typeof part.callID === "string" ? part.callID : null;
|
|
58
|
+
events.push(event({
|
|
59
|
+
...base,
|
|
60
|
+
id: `${id}:call`,
|
|
61
|
+
kind: "tool_call",
|
|
62
|
+
callId,
|
|
63
|
+
content: json({ name: part.tool, arguments: state.input }),
|
|
64
|
+
}));
|
|
65
|
+
if (state.status === "completed" || state.status === "error")
|
|
66
|
+
events.push(event({
|
|
67
|
+
...base,
|
|
68
|
+
id: `${id}:result`,
|
|
69
|
+
kind: "tool_result",
|
|
70
|
+
role: "tool",
|
|
71
|
+
callId,
|
|
72
|
+
content: json({
|
|
73
|
+
output: state.output,
|
|
74
|
+
error: state.error,
|
|
75
|
+
status: state.status,
|
|
76
|
+
}),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
else if (part.type === "compaction")
|
|
80
|
+
events.push(event({ ...base, id, kind: "compaction", content: json(part) }));
|
|
81
|
+
else if (!["text", "reasoning", "step-start", "step-finish"].includes(String(part.type)))
|
|
82
|
+
events.push(event({ ...base, id, kind: "unknown", content: json(part) }));
|
|
83
|
+
}
|
|
84
|
+
if (role === "assistant" && (message.finish === "stop" || message.error))
|
|
85
|
+
events.push(event({
|
|
86
|
+
...base,
|
|
87
|
+
id: `${message.id}:terminal`,
|
|
88
|
+
kind: "terminal",
|
|
89
|
+
content: { status: message.error ? "failed" : "completed" },
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
return normalizeConnectedSession({
|
|
93
|
+
origin: "opencode",
|
|
94
|
+
sessionId: info.id,
|
|
95
|
+
parentSessionId: typeof info.parentID === "string" ? info.parentID : null,
|
|
96
|
+
exporterVersion: typeof info.version === "string" ? info.version : null,
|
|
97
|
+
files: [file],
|
|
98
|
+
events,
|
|
99
|
+
warnings: ["Source-reported usage is not an OpenPond billing receipt."],
|
|
100
|
+
});
|
|
101
|
+
}
|