@agentic-kit/run-log 0.5.0 → 0.7.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 +0 -4
- package/esm/file-store.d.ts +2 -2
- package/esm/index.d.ts +6 -0
- package/esm/index.js +5 -0
- package/esm/mirror.d.ts +6 -6
- package/esm/mirror.js +5 -2
- package/esm/projectors/events.d.ts +31 -0
- package/esm/projectors/events.js +23 -0
- package/esm/projectors/parts.d.ts +13 -10
- package/esm/projectors/parts.js +88 -105
- package/esm/projectors/session.d.ts +5 -0
- package/esm/projectors/session.js +6 -0
- package/esm/projectors/spans.d.ts +10 -9
- package/esm/projectors/spans.js +115 -111
- package/esm/projectors/tool-state.d.ts +4 -3
- package/esm/projectors/tool-state.js +28 -33
- package/esm/projectors/usage.d.ts +3 -2
- package/esm/projectors/usage.js +12 -18
- package/esm/record.d.ts +9 -6
- package/esm/record.js +15 -13
- package/esm/store.d.ts +5 -5
- package/esm/transcripts/entry.d.ts +20 -0
- package/esm/transcripts/entry.js +23 -0
- package/esm/transcripts/event.d.ts +118 -0
- package/esm/transcripts/event.js +15 -0
- package/esm/transcripts/pi-entry.d.ts +1 -0
- package/esm/transcripts/pi-reader.d.ts +16 -0
- package/esm/transcripts/pi-reader.js +137 -0
- package/esm/transcripts/reader.d.ts +45 -0
- package/esm/transcripts/reader.js +66 -0
- package/esm/transcripts/registry.d.ts +16 -0
- package/esm/transcripts/registry.js +17 -0
- package/file-store.d.ts +2 -2
- package/index.d.ts +6 -0
- package/index.js +15 -1
- package/mirror.d.ts +6 -6
- package/mirror.js +5 -2
- package/package.json +2 -2
- package/projectors/events.d.ts +31 -0
- package/projectors/events.js +27 -0
- package/projectors/parts.d.ts +13 -10
- package/projectors/parts.js +88 -105
- package/projectors/session.d.ts +5 -0
- package/projectors/session.js +6 -0
- package/projectors/spans.d.ts +10 -9
- package/projectors/spans.js +115 -111
- package/projectors/tool-state.d.ts +4 -3
- package/projectors/tool-state.js +28 -33
- package/projectors/usage.d.ts +3 -2
- package/projectors/usage.js +12 -18
- package/record.d.ts +9 -6
- package/record.js +14 -12
- package/store.d.ts +5 -5
- package/transcripts/entry.d.ts +20 -0
- package/transcripts/entry.js +28 -0
- package/transcripts/event.d.ts +118 -0
- package/transcripts/event.js +16 -0
- package/transcripts/pi-entry.d.ts +1 -0
- package/transcripts/pi-reader.d.ts +16 -0
- package/transcripts/pi-reader.js +141 -0
- package/transcripts/reader.d.ts +45 -0
- package/transcripts/reader.js +71 -0
- package/transcripts/registry.d.ts +16 -0
- package/transcripts/registry.js +21 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A harness's own session entry, as the log stores it.
|
|
3
|
+
*
|
|
4
|
+
* The log is deliberately not a re-encoding of a harness's transcript — an
|
|
5
|
+
* entry is kept byte-for-byte so the run stays resumable in the harness that
|
|
6
|
+
* wrote it. All the wrapper knows structurally is that an entry is an object
|
|
7
|
+
* with a `type`; what that type *means* is the reader's business, selected by
|
|
8
|
+
* the record's `transcriptFormat`.
|
|
9
|
+
*/
|
|
10
|
+
export interface TranscriptEntry {
|
|
11
|
+
/** The format's own entry discriminator, e.g. pi's `message`. */
|
|
12
|
+
type: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare const isEntryRecord: (value: unknown) => value is Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Narrow an untrusted value (a JSONB column, an HTTP body) to the structure
|
|
18
|
+
* every transcript shares. A format's own reader validates the rest.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertTranscriptEntry(value: unknown): TranscriptEntry;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A harness's own session entry, as the log stores it.
|
|
3
|
+
*
|
|
4
|
+
* The log is deliberately not a re-encoding of a harness's transcript — an
|
|
5
|
+
* entry is kept byte-for-byte so the run stays resumable in the harness that
|
|
6
|
+
* wrote it. All the wrapper knows structurally is that an entry is an object
|
|
7
|
+
* with a `type`; what that type *means* is the reader's business, selected by
|
|
8
|
+
* the record's `transcriptFormat`.
|
|
9
|
+
*/
|
|
10
|
+
export const isEntryRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
/**
|
|
12
|
+
* Narrow an untrusted value (a JSONB column, an HTTP body) to the structure
|
|
13
|
+
* every transcript shares. A format's own reader validates the rest.
|
|
14
|
+
*/
|
|
15
|
+
export function assertTranscriptEntry(value) {
|
|
16
|
+
if (!isEntryRecord(value)) {
|
|
17
|
+
throw new TypeError(`transcript entry must be an object, received ${typeof value}`);
|
|
18
|
+
}
|
|
19
|
+
if (typeof value.type !== 'string' || value.type.length === 0) {
|
|
20
|
+
throw new TypeError('transcript entry must carry a non-empty string `type`');
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The neutral vocabulary every projector reads.
|
|
3
|
+
*
|
|
4
|
+
* A projector used to know pi's message shapes, which made "render a run" and
|
|
5
|
+
* "read pi" the same capability. An event is what a transcript entry *means* —
|
|
6
|
+
* a model answered, a tool was called, a human was asked — so a format is
|
|
7
|
+
* understood in exactly one place (its `TranscriptReader`) and rendering,
|
|
8
|
+
* tracing and usage folding stop caring which harness ran.
|
|
9
|
+
*
|
|
10
|
+
* Kept close to what the projectors need rather than to any one harness: pi's
|
|
11
|
+
* `message`/`toolResult`/`custom` entries and DeepSeek Harness's
|
|
12
|
+
* `assistant/message`, `tool/call`, `tool/result` events both land here without
|
|
13
|
+
* either format's naming surviving the trip.
|
|
14
|
+
*/
|
|
15
|
+
import type { TranscriptEntry } from './entry';
|
|
16
|
+
/** Tokens and cost as a transcript reports them — all fields optional. */
|
|
17
|
+
export interface TranscriptUsage {
|
|
18
|
+
input?: number;
|
|
19
|
+
output?: number;
|
|
20
|
+
cacheRead?: number;
|
|
21
|
+
cacheWrite?: number;
|
|
22
|
+
totalTokens?: number;
|
|
23
|
+
cost?: {
|
|
24
|
+
total?: number;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
};
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
export interface TranscriptEventBase {
|
|
30
|
+
/** The entry's id in its own transcript, when the format gives entries ids. */
|
|
31
|
+
entryId?: string;
|
|
32
|
+
/** The entry's ISO timestamp, when it carries one. */
|
|
33
|
+
at?: string;
|
|
34
|
+
}
|
|
35
|
+
/** The transcript's opening entry: which session, and where it ran. */
|
|
36
|
+
export interface SessionStartEvent extends TranscriptEventBase {
|
|
37
|
+
kind: 'session-start';
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
cwd?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface TextEvent extends TranscriptEventBase {
|
|
42
|
+
kind: 'text';
|
|
43
|
+
role: 'user' | 'assistant';
|
|
44
|
+
text: string;
|
|
45
|
+
model?: string;
|
|
46
|
+
provider?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ThinkingEvent extends TranscriptEventBase {
|
|
49
|
+
kind: 'thinking';
|
|
50
|
+
text: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A model finished answering. Separate from the text it produced because a
|
|
54
|
+
* trace and a usage total ask about the *call* — which model, what it cost, why
|
|
55
|
+
* it stopped — while a renderer asks about the content.
|
|
56
|
+
*/
|
|
57
|
+
export interface ModelResponseEvent extends TranscriptEventBase {
|
|
58
|
+
kind: 'model-response';
|
|
59
|
+
model?: string;
|
|
60
|
+
provider?: string;
|
|
61
|
+
/** The provider's response id — the join key to a metered inference row. */
|
|
62
|
+
responseId?: string;
|
|
63
|
+
stopReason?: string;
|
|
64
|
+
usage?: TranscriptUsage;
|
|
65
|
+
errorMessage?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface ToolCallEvent extends TranscriptEventBase {
|
|
68
|
+
kind: 'tool-call';
|
|
69
|
+
toolCallId: string;
|
|
70
|
+
name: string;
|
|
71
|
+
arguments: Record<string, unknown>;
|
|
72
|
+
}
|
|
73
|
+
export interface ToolResultEvent extends TranscriptEventBase {
|
|
74
|
+
kind: 'tool-result';
|
|
75
|
+
toolCallId: string;
|
|
76
|
+
name: string;
|
|
77
|
+
output: string;
|
|
78
|
+
failed: boolean;
|
|
79
|
+
details?: unknown;
|
|
80
|
+
/** Nested model work the tool itself paid for. */
|
|
81
|
+
usage?: TranscriptUsage;
|
|
82
|
+
}
|
|
83
|
+
export interface BashEvent extends TranscriptEventBase {
|
|
84
|
+
kind: 'bash';
|
|
85
|
+
command: string;
|
|
86
|
+
output: string;
|
|
87
|
+
exitCode?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A platform entry riding in the transcript — an approval request, a gate
|
|
91
|
+
* decision — discriminated by `customType` and interpreted by the entry-type
|
|
92
|
+
* registry rather than by a format's reader.
|
|
93
|
+
*/
|
|
94
|
+
export interface CustomEvent extends TranscriptEventBase {
|
|
95
|
+
kind: 'custom';
|
|
96
|
+
customType: string;
|
|
97
|
+
text: string;
|
|
98
|
+
/** Whether the entry is part of the conversation a human is shown. */
|
|
99
|
+
display: boolean;
|
|
100
|
+
details?: unknown;
|
|
101
|
+
}
|
|
102
|
+
export interface SummaryEvent extends TranscriptEventBase {
|
|
103
|
+
kind: 'summary';
|
|
104
|
+
reason: 'compaction' | 'branch';
|
|
105
|
+
summary: string;
|
|
106
|
+
usage?: TranscriptUsage;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* An entry this reader does not understand. Carried rather than dropped: a log
|
|
110
|
+
* written by a newer harness still renders, minus the detail this version
|
|
111
|
+
* knows, and a projector never silently loses a row.
|
|
112
|
+
*/
|
|
113
|
+
export interface UnknownEvent extends TranscriptEventBase {
|
|
114
|
+
kind: 'unknown';
|
|
115
|
+
entryType: string;
|
|
116
|
+
entry: TranscriptEntry;
|
|
117
|
+
}
|
|
118
|
+
export type TranscriptEvent = SessionStartEvent | TextEvent | ThinkingEvent | ModelResponseEvent | ToolCallEvent | ToolResultEvent | BashEvent | CustomEvent | SummaryEvent | UnknownEvent;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The neutral vocabulary every projector reads.
|
|
3
|
+
*
|
|
4
|
+
* A projector used to know pi's message shapes, which made "render a run" and
|
|
5
|
+
* "read pi" the same capability. An event is what a transcript entry *means* —
|
|
6
|
+
* a model answered, a tool was called, a human was asked — so a format is
|
|
7
|
+
* understood in exactly one place (its `TranscriptReader`) and rendering,
|
|
8
|
+
* tracing and usage folding stop caring which harness ran.
|
|
9
|
+
*
|
|
10
|
+
* Kept close to what the projectors need rather than to any one harness: pi's
|
|
11
|
+
* `message`/`toolResult`/`custom` entries and DeepSeek Harness's
|
|
12
|
+
* `assistant/message`, `tool/call`, `tool/result` events both land here without
|
|
13
|
+
* either format's naming surviving the trip.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi's transcript reader: pi session entries → neutral events.
|
|
3
|
+
*
|
|
4
|
+
* This is the only place in the package that knows pi's message shapes. It stays
|
|
5
|
+
* here rather than in `@agentic-kit/pi` on purpose — a reader must be
|
|
6
|
+
* browser-safe, because renderers project logs client-side, while the adapter
|
|
7
|
+
* package pulls pi's node SDK. The types it reads are structural (see
|
|
8
|
+
* `./pi-entry`), so nothing here imports pi either.
|
|
9
|
+
*/
|
|
10
|
+
import type { TranscriptEntry } from './entry';
|
|
11
|
+
import type { TranscriptEvent } from './event';
|
|
12
|
+
import type { TranscriptReader } from './reader';
|
|
13
|
+
/** What a single pi entry means, in order. */
|
|
14
|
+
export declare function piEntryToEvents(entry: TranscriptEntry): TranscriptEvent[];
|
|
15
|
+
/** pi's session-file transcript (`@earendil-works/pi-coding-agent`). */
|
|
16
|
+
export declare const piTranscriptReader: TranscriptReader;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi's transcript reader: pi session entries → neutral events.
|
|
3
|
+
*
|
|
4
|
+
* This is the only place in the package that knows pi's message shapes. It stays
|
|
5
|
+
* here rather than in `@agentic-kit/pi` on purpose — a reader must be
|
|
6
|
+
* browser-safe, because renderers project logs client-side, while the adapter
|
|
7
|
+
* package pulls pi's node SDK. The types it reads are structural (see
|
|
8
|
+
* `./pi-entry`), so nothing here imports pi either.
|
|
9
|
+
*/
|
|
10
|
+
import { PI_TRANSCRIPT_FORMAT, SUPPORTED_PI_TRANSCRIPT_VERSION } from './format';
|
|
11
|
+
import { assertPiSessionEntry, contentText, isAssistantMessage, isPiBranchSummaryEntry, isPiCompactionEntry, isPiMessageEntry, isPiSessionHeader, isToolResultMessage, toolCalls } from './pi-entry';
|
|
12
|
+
/** What a single pi entry means, in order. */
|
|
13
|
+
export function piEntryToEvents(entry) {
|
|
14
|
+
const pi = entry;
|
|
15
|
+
const base = {
|
|
16
|
+
...(typeof pi.id === 'string' ? { entryId: pi.id } : {}),
|
|
17
|
+
...(typeof pi.timestamp === 'string' ? { at: pi.timestamp } : {})
|
|
18
|
+
};
|
|
19
|
+
if (isPiSessionHeader(pi)) {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
kind: 'session-start',
|
|
23
|
+
...base,
|
|
24
|
+
...(typeof pi.id === 'string' ? { sessionId: pi.id } : {}),
|
|
25
|
+
...(typeof pi.cwd === 'string' ? { cwd: pi.cwd } : {})
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
if (isPiCompactionEntry(pi)) {
|
|
30
|
+
return [
|
|
31
|
+
{ kind: 'summary', reason: 'compaction', summary: pi.summary, ...(pi.usage ? { usage: pi.usage } : {}), ...base }
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
if (isPiBranchSummaryEntry(pi)) {
|
|
35
|
+
return [
|
|
36
|
+
{ kind: 'summary', reason: 'branch', summary: pi.summary, ...(pi.usage ? { usage: pi.usage } : {}), ...base }
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
if (!isPiMessageEntry(pi)) {
|
|
40
|
+
return [{ kind: 'unknown', entryType: pi.type, entry, ...base }];
|
|
41
|
+
}
|
|
42
|
+
const message = pi.message;
|
|
43
|
+
if (message.role === 'user') {
|
|
44
|
+
return [{ kind: 'text', role: 'user', text: contentText(message.content), ...base }];
|
|
45
|
+
}
|
|
46
|
+
if (isAssistantMessage(message)) {
|
|
47
|
+
const events = [
|
|
48
|
+
{
|
|
49
|
+
kind: 'model-response',
|
|
50
|
+
...(message.model ? { model: message.model } : {}),
|
|
51
|
+
...(message.provider ? { provider: message.provider } : {}),
|
|
52
|
+
...(typeof message.responseId === 'string' ? { responseId: message.responseId } : {}),
|
|
53
|
+
...(message.stopReason ? { stopReason: message.stopReason } : {}),
|
|
54
|
+
...(message.usage ? { usage: message.usage } : {}),
|
|
55
|
+
...(message.errorMessage ? { errorMessage: message.errorMessage } : {}),
|
|
56
|
+
...base
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
for (const block of Array.isArray(message.content) ? message.content : []) {
|
|
60
|
+
if (block.type === 'text' && block.text.length > 0) {
|
|
61
|
+
events.push({
|
|
62
|
+
kind: 'text',
|
|
63
|
+
role: 'assistant',
|
|
64
|
+
text: block.text,
|
|
65
|
+
...(message.model ? { model: message.model } : {}),
|
|
66
|
+
...(message.provider ? { provider: message.provider } : {}),
|
|
67
|
+
...base
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else if (block.type === 'thinking') {
|
|
71
|
+
events.push({ kind: 'thinking', text: block.thinking, ...base });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const call of toolCalls(message)) {
|
|
75
|
+
events.push({
|
|
76
|
+
kind: 'tool-call',
|
|
77
|
+
toolCallId: call.id,
|
|
78
|
+
name: call.name,
|
|
79
|
+
arguments: call.arguments ?? {},
|
|
80
|
+
...base
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return events;
|
|
84
|
+
}
|
|
85
|
+
if (isToolResultMessage(message)) {
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
kind: 'tool-result',
|
|
89
|
+
toolCallId: message.toolCallId,
|
|
90
|
+
name: message.toolName,
|
|
91
|
+
output: contentText(message.content),
|
|
92
|
+
failed: message.isError === true,
|
|
93
|
+
...(message.details === undefined ? {} : { details: message.details }),
|
|
94
|
+
...(message.usage ? { usage: message.usage } : {}),
|
|
95
|
+
...base
|
|
96
|
+
}
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
if (message.role === 'bashExecution') {
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
kind: 'bash',
|
|
103
|
+
command: message.command,
|
|
104
|
+
output: message.output,
|
|
105
|
+
...(typeof message.exitCode === 'number' ? { exitCode: message.exitCode } : {}),
|
|
106
|
+
...base
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
if (message.role === 'custom') {
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
kind: 'custom',
|
|
114
|
+
customType: message.customType,
|
|
115
|
+
text: contentText(message.content),
|
|
116
|
+
display: message.display !== false,
|
|
117
|
+
...(message.details === undefined ? {} : { details: message.details }),
|
|
118
|
+
...base
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
kind: 'summary',
|
|
125
|
+
reason: message.role === 'branchSummary' ? 'branch' : 'compaction',
|
|
126
|
+
summary: message.summary ?? '',
|
|
127
|
+
...base
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
/** pi's session-file transcript (`@earendil-works/pi-coding-agent`). */
|
|
132
|
+
export const piTranscriptReader = {
|
|
133
|
+
format: PI_TRANSCRIPT_FORMAT,
|
|
134
|
+
version: SUPPORTED_PI_TRANSCRIPT_VERSION,
|
|
135
|
+
assertEntry: assertPiSessionEntry,
|
|
136
|
+
toEvents: piEntryToEvents
|
|
137
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transcript reader registry: one place per format that knows how to read
|
|
3
|
+
* it, looked up by the format a record was written under.
|
|
4
|
+
*
|
|
5
|
+
* A reader is registered *by value*, like an entry type, so a second harness
|
|
6
|
+
* ships its reader with its adapter and neither this package nor a renderer is
|
|
7
|
+
* released to support it. A record names its format, so a run that changed
|
|
8
|
+
* harness mid-flight still projects as one ordered log.
|
|
9
|
+
*/
|
|
10
|
+
import { type TranscriptEntry } from './entry';
|
|
11
|
+
import type { TranscriptEvent } from './event';
|
|
12
|
+
import type { TranscriptFormat } from './format';
|
|
13
|
+
export interface TranscriptReader {
|
|
14
|
+
format: TranscriptFormat;
|
|
15
|
+
/** The format version this reader projects without migration. */
|
|
16
|
+
version: number;
|
|
17
|
+
/** Narrow an untrusted entry of this format. Throws on anything unreadable. */
|
|
18
|
+
assertEntry: (value: unknown) => TranscriptEntry;
|
|
19
|
+
/**
|
|
20
|
+
* What the entry means, in order. One entry may mean several things — a pi
|
|
21
|
+
* assistant message is a model response plus its text plus its tool calls.
|
|
22
|
+
*/
|
|
23
|
+
toEvents: (entry: TranscriptEntry) => TranscriptEvent[];
|
|
24
|
+
}
|
|
25
|
+
export declare class TranscriptReaderRegistry {
|
|
26
|
+
private readonly readers;
|
|
27
|
+
constructor(readers?: readonly TranscriptReader[]);
|
|
28
|
+
register(reader: TranscriptReader): this;
|
|
29
|
+
get(format: TranscriptFormat): TranscriptReader | undefined;
|
|
30
|
+
has(format: TranscriptFormat): boolean;
|
|
31
|
+
formats(): TranscriptFormat[];
|
|
32
|
+
/**
|
|
33
|
+
* The reader for a format, or a loud failure. A log whose format nothing can
|
|
34
|
+
* read must not render as an empty conversation — the missing piece is an
|
|
35
|
+
* unregistered adapter, and saying so is the only useful outcome.
|
|
36
|
+
*/
|
|
37
|
+
require(format: TranscriptFormat): TranscriptReader;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A reader for a format whose entries the platform stores but cannot interpret.
|
|
41
|
+
*
|
|
42
|
+
* Every entry becomes an `unknown` event, which is enough to keep a log
|
|
43
|
+
* append-only, ordered and inspectable while its adapter is still being written.
|
|
44
|
+
*/
|
|
45
|
+
export declare function passthroughReader(format: TranscriptFormat, version?: number): TranscriptReader;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transcript reader registry: one place per format that knows how to read
|
|
3
|
+
* it, looked up by the format a record was written under.
|
|
4
|
+
*
|
|
5
|
+
* A reader is registered *by value*, like an entry type, so a second harness
|
|
6
|
+
* ships its reader with its adapter and neither this package nor a renderer is
|
|
7
|
+
* released to support it. A record names its format, so a run that changed
|
|
8
|
+
* harness mid-flight still projects as one ordered log.
|
|
9
|
+
*/
|
|
10
|
+
import { assertTranscriptEntry } from './entry';
|
|
11
|
+
export class TranscriptReaderRegistry {
|
|
12
|
+
readers = new Map();
|
|
13
|
+
constructor(readers = []) {
|
|
14
|
+
for (const reader of readers)
|
|
15
|
+
this.register(reader);
|
|
16
|
+
}
|
|
17
|
+
register(reader) {
|
|
18
|
+
this.readers.set(reader.format, reader);
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
get(format) {
|
|
22
|
+
return this.readers.get(format);
|
|
23
|
+
}
|
|
24
|
+
has(format) {
|
|
25
|
+
return this.readers.has(format);
|
|
26
|
+
}
|
|
27
|
+
formats() {
|
|
28
|
+
return Array.from(this.readers.keys()).sort();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The reader for a format, or a loud failure. A log whose format nothing can
|
|
32
|
+
* read must not render as an empty conversation — the missing piece is an
|
|
33
|
+
* unregistered adapter, and saying so is the only useful outcome.
|
|
34
|
+
*/
|
|
35
|
+
require(format) {
|
|
36
|
+
const reader = this.readers.get(format);
|
|
37
|
+
if (!reader) {
|
|
38
|
+
const known = this.formats();
|
|
39
|
+
throw new Error(`no transcript reader registered for format "${format}"` +
|
|
40
|
+
(known.length === 0 ? '' : `; this registry reads ${known.join(', ')}`));
|
|
41
|
+
}
|
|
42
|
+
return reader;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A reader for a format whose entries the platform stores but cannot interpret.
|
|
47
|
+
*
|
|
48
|
+
* Every entry becomes an `unknown` event, which is enough to keep a log
|
|
49
|
+
* append-only, ordered and inspectable while its adapter is still being written.
|
|
50
|
+
*/
|
|
51
|
+
export function passthroughReader(format, version = 1) {
|
|
52
|
+
return {
|
|
53
|
+
format,
|
|
54
|
+
version,
|
|
55
|
+
assertEntry: assertTranscriptEntry,
|
|
56
|
+
toEvents: (entry) => [
|
|
57
|
+
{
|
|
58
|
+
kind: 'unknown',
|
|
59
|
+
entryType: entry.type,
|
|
60
|
+
entry,
|
|
61
|
+
...(typeof entry.id === 'string' ? { entryId: entry.id } : {}),
|
|
62
|
+
...(typeof entry.timestamp === 'string' ? { at: entry.timestamp } : {})
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry the projectors use when a caller does not pass their own.
|
|
3
|
+
*
|
|
4
|
+
* pi is registered because it is the harness the platform ships; a host adding
|
|
5
|
+
* a second one either registers into its own registry or into this one at
|
|
6
|
+
* startup. Kept apart from `./reader` so the registry type does not depend on
|
|
7
|
+
* any format.
|
|
8
|
+
*/
|
|
9
|
+
import { TranscriptReaderRegistry } from './reader';
|
|
10
|
+
/** Readers registered out of the box. */
|
|
11
|
+
export declare const defaultTranscriptReaders: () => TranscriptReaderRegistry;
|
|
12
|
+
/**
|
|
13
|
+
* The process-wide registry. Mutable on purpose: a host registers its adapter's
|
|
14
|
+
* reader once at startup and every projector call then reads that format.
|
|
15
|
+
*/
|
|
16
|
+
export declare const transcriptReaders: TranscriptReaderRegistry;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The registry the projectors use when a caller does not pass their own.
|
|
3
|
+
*
|
|
4
|
+
* pi is registered because it is the harness the platform ships; a host adding
|
|
5
|
+
* a second one either registers into its own registry or into this one at
|
|
6
|
+
* startup. Kept apart from `./reader` so the registry type does not depend on
|
|
7
|
+
* any format.
|
|
8
|
+
*/
|
|
9
|
+
import { piTranscriptReader } from './pi-reader';
|
|
10
|
+
import { TranscriptReaderRegistry } from './reader';
|
|
11
|
+
/** Readers registered out of the box. */
|
|
12
|
+
export const defaultTranscriptReaders = () => new TranscriptReaderRegistry([piTranscriptReader]);
|
|
13
|
+
/**
|
|
14
|
+
* The process-wide registry. Mutable on purpose: a host registers its adapter's
|
|
15
|
+
* reader once at startup and every projector call then reads that format.
|
|
16
|
+
*/
|
|
17
|
+
export const transcriptReaders = defaultTranscriptReaders();
|
package/file-store.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { type SessionProjectionOptions } from './projectors/session';
|
|
14
14
|
import { type RunEventRecord } from './record';
|
|
15
15
|
import { type AppendOptions, type RunLogCursor, type RunLogPage, type RunLogStore } from './store';
|
|
16
|
-
import type {
|
|
16
|
+
import type { TranscriptEntry } from './transcripts/entry';
|
|
17
17
|
export interface FileRunLogStoreOptions {
|
|
18
18
|
/** Absolute path of the log file. Parent directories are created. */
|
|
19
19
|
path: string;
|
|
@@ -22,7 +22,7 @@ export declare class FileRunLogStore implements RunLogStore {
|
|
|
22
22
|
private readonly path;
|
|
23
23
|
constructor(options: FileRunLogStoreOptions);
|
|
24
24
|
private load;
|
|
25
|
-
append(runId: string, entries: readonly
|
|
25
|
+
append(runId: string, entries: readonly TranscriptEntry[], options?: AppendOptions): Promise<RunEventRecord[]>;
|
|
26
26
|
read(runId: string, cursor?: RunLogCursor, limit?: number): Promise<RunLogPage>;
|
|
27
27
|
}
|
|
28
28
|
/**
|
package/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
export { constructiveEntryTypes, type EntryTypeDefinition, EntryTypeRegistry, type EntryVisibility } from './entry-types';
|
|
10
10
|
export { follow, type FollowOptions, readAll } from './follow';
|
|
11
11
|
export { type SessionEntrySource, SessionMirror, type SessionMirrorOptions } from './mirror';
|
|
12
|
+
export { type EventGroup, type ProjectionOptions, type SequencedEvent, toEventGroups, toEvents } from './projectors/events';
|
|
12
13
|
export { type BashPart, type Conversation, type ConversationPart, type CustomPart, projectParts, type SummaryPart, type TextPart, type ThinkingPart, type ToolPart, type ToolStatus, type UnknownPart } from './projectors/parts';
|
|
13
14
|
export { parseSessionJsonl, projectSession, type SessionProjection, type SessionProjectionOptions } from './projectors/session';
|
|
14
15
|
export { type ApprovalSpan, type ModelSpan, projectSpans, type Span, type SpanBase, type SpanKind, type SpanProjection, type SpanStatus, type SpanTiming, type ToolSpan } from './projectors/spans';
|
|
@@ -16,5 +17,10 @@ export { APPROVAL_REQUEST_TYPE, APPROVAL_RESOLUTION_TYPE, type ApprovalRequestIn
|
|
|
16
17
|
export { modelKey, type ModelUsage, projectUsage, type RunUsage, type UsageTotals } from './projectors/usage';
|
|
17
18
|
export { assertOrdered, assertRunEventRecord, idempotencyKey, RUN_LOG_WRAPPER_VERSION, type RunEventRecord, wrapEntry, type WrapEntryOptions } from './record';
|
|
18
19
|
export { type AppendOptions, cursorAfter, MemoryRunLogStore, type RunLogAppendStore, type RunLogCursor, type RunLogPage, type RunLogReadStore, type RunLogStore, START } from './store';
|
|
20
|
+
export { assertTranscriptEntry, type TranscriptEntry } from './transcripts/entry';
|
|
21
|
+
export { type BashEvent, type CustomEvent, type ModelResponseEvent, type SessionStartEvent, type SummaryEvent, type TextEvent, type ThinkingEvent, type ToolCallEvent, type ToolResultEvent, type TranscriptEvent, type TranscriptEventBase, type TranscriptUsage, type UnknownEvent } from './transcripts/event';
|
|
19
22
|
export { assertTranscriptFormat, PI_TRANSCRIPT_FORMAT, SUPPORTED_PI_TRANSCRIPT_VERSION, type TranscriptFormat } from './transcripts/format';
|
|
20
23
|
export { assertPiSessionEntry, contentText, isAssistantMessage, isPiBranchSummaryEntry, isPiCompactionEntry, isPiMessageEntry, isPiSessionHeader, isToolResultMessage, type PiAssistantMessage, type PiBashExecutionMessage, type PiBranchSummaryEntry, type PiCompactionEntry, type PiContent, type PiCustomMessage, type PiEntryBase, type PiImageContent, type PiMessage, type PiMessageEntry, type PiOtherEntry, type PiSessionEntry, type PiSessionHeader, type PiSummaryMessage, type PiTextContent, type PiThinkingContent, type PiToolCallContent, type PiToolResultMessage, type PiUsage, type PiUsageCost, type PiUserMessage, toolCalls } from './transcripts/pi-entry';
|
|
24
|
+
export { piEntryToEvents, piTranscriptReader } from './transcripts/pi-reader';
|
|
25
|
+
export { passthroughReader, type TranscriptReader, TranscriptReaderRegistry } from './transcripts/reader';
|
|
26
|
+
export { defaultTranscriptReaders, transcriptReaders } from './transcripts/registry';
|
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `@agentic-kit/run-log/file-store`.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.toolCalls = exports.isToolResultMessage = exports.isPiSessionHeader = exports.isPiMessageEntry = exports.isPiCompactionEntry = exports.isPiBranchSummaryEntry = exports.isAssistantMessage = exports.contentText = exports.assertPiSessionEntry = exports.SUPPORTED_PI_TRANSCRIPT_VERSION = exports.PI_TRANSCRIPT_FORMAT = exports.assertTranscriptFormat = exports.START = exports.MemoryRunLogStore = exports.cursorAfter = exports.wrapEntry = exports.RUN_LOG_WRAPPER_VERSION = exports.idempotencyKey = exports.assertRunEventRecord = exports.assertOrdered = exports.projectUsage = exports.modelKey = exports.projectToolState = exports.gateDecisionMessage = exports.GATE_DECISION_TYPE = exports.assertGateDecisionDetails = exports.assertApprovalResolutionDetails = exports.assertApprovalRequestDetails = exports.approvalResolutionMessage = exports.approvalRequestMessage = exports.APPROVAL_RESOLUTION_TYPE = exports.APPROVAL_REQUEST_TYPE = exports.projectSpans = exports.projectSession = exports.parseSessionJsonl = exports.projectParts = exports.SessionMirror = exports.readAll = exports.follow = exports.EntryTypeRegistry = exports.constructiveEntryTypes = void 0;
|
|
11
|
+
exports.transcriptReaders = exports.defaultTranscriptReaders = exports.TranscriptReaderRegistry = exports.passthroughReader = exports.piTranscriptReader = exports.piEntryToEvents = exports.toolCalls = exports.isToolResultMessage = exports.isPiSessionHeader = exports.isPiMessageEntry = exports.isPiCompactionEntry = exports.isPiBranchSummaryEntry = exports.isAssistantMessage = exports.contentText = exports.assertPiSessionEntry = exports.SUPPORTED_PI_TRANSCRIPT_VERSION = exports.PI_TRANSCRIPT_FORMAT = exports.assertTranscriptFormat = exports.assertTranscriptEntry = exports.START = exports.MemoryRunLogStore = exports.cursorAfter = exports.wrapEntry = exports.RUN_LOG_WRAPPER_VERSION = exports.idempotencyKey = exports.assertRunEventRecord = exports.assertOrdered = exports.projectUsage = exports.modelKey = exports.projectToolState = exports.gateDecisionMessage = exports.GATE_DECISION_TYPE = exports.assertGateDecisionDetails = exports.assertApprovalResolutionDetails = exports.assertApprovalRequestDetails = exports.approvalResolutionMessage = exports.approvalRequestMessage = exports.APPROVAL_RESOLUTION_TYPE = exports.APPROVAL_REQUEST_TYPE = exports.projectSpans = exports.projectSession = exports.parseSessionJsonl = exports.projectParts = exports.toEvents = exports.toEventGroups = exports.SessionMirror = exports.readAll = exports.follow = exports.EntryTypeRegistry = exports.constructiveEntryTypes = void 0;
|
|
12
12
|
var entry_types_1 = require("./entry-types");
|
|
13
13
|
Object.defineProperty(exports, "constructiveEntryTypes", { enumerable: true, get: function () { return entry_types_1.constructiveEntryTypes; } });
|
|
14
14
|
Object.defineProperty(exports, "EntryTypeRegistry", { enumerable: true, get: function () { return entry_types_1.EntryTypeRegistry; } });
|
|
@@ -17,6 +17,9 @@ Object.defineProperty(exports, "follow", { enumerable: true, get: function () {
|
|
|
17
17
|
Object.defineProperty(exports, "readAll", { enumerable: true, get: function () { return follow_1.readAll; } });
|
|
18
18
|
var mirror_1 = require("./mirror");
|
|
19
19
|
Object.defineProperty(exports, "SessionMirror", { enumerable: true, get: function () { return mirror_1.SessionMirror; } });
|
|
20
|
+
var events_1 = require("./projectors/events");
|
|
21
|
+
Object.defineProperty(exports, "toEventGroups", { enumerable: true, get: function () { return events_1.toEventGroups; } });
|
|
22
|
+
Object.defineProperty(exports, "toEvents", { enumerable: true, get: function () { return events_1.toEvents; } });
|
|
20
23
|
var parts_1 = require("./projectors/parts");
|
|
21
24
|
Object.defineProperty(exports, "projectParts", { enumerable: true, get: function () { return parts_1.projectParts; } });
|
|
22
25
|
var session_1 = require("./projectors/session");
|
|
@@ -48,6 +51,8 @@ var store_1 = require("./store");
|
|
|
48
51
|
Object.defineProperty(exports, "cursorAfter", { enumerable: true, get: function () { return store_1.cursorAfter; } });
|
|
49
52
|
Object.defineProperty(exports, "MemoryRunLogStore", { enumerable: true, get: function () { return store_1.MemoryRunLogStore; } });
|
|
50
53
|
Object.defineProperty(exports, "START", { enumerable: true, get: function () { return store_1.START; } });
|
|
54
|
+
var entry_1 = require("./transcripts/entry");
|
|
55
|
+
Object.defineProperty(exports, "assertTranscriptEntry", { enumerable: true, get: function () { return entry_1.assertTranscriptEntry; } });
|
|
51
56
|
var format_1 = require("./transcripts/format");
|
|
52
57
|
Object.defineProperty(exports, "assertTranscriptFormat", { enumerable: true, get: function () { return format_1.assertTranscriptFormat; } });
|
|
53
58
|
Object.defineProperty(exports, "PI_TRANSCRIPT_FORMAT", { enumerable: true, get: function () { return format_1.PI_TRANSCRIPT_FORMAT; } });
|
|
@@ -62,3 +67,12 @@ Object.defineProperty(exports, "isPiMessageEntry", { enumerable: true, get: func
|
|
|
62
67
|
Object.defineProperty(exports, "isPiSessionHeader", { enumerable: true, get: function () { return pi_entry_1.isPiSessionHeader; } });
|
|
63
68
|
Object.defineProperty(exports, "isToolResultMessage", { enumerable: true, get: function () { return pi_entry_1.isToolResultMessage; } });
|
|
64
69
|
Object.defineProperty(exports, "toolCalls", { enumerable: true, get: function () { return pi_entry_1.toolCalls; } });
|
|
70
|
+
var pi_reader_1 = require("./transcripts/pi-reader");
|
|
71
|
+
Object.defineProperty(exports, "piEntryToEvents", { enumerable: true, get: function () { return pi_reader_1.piEntryToEvents; } });
|
|
72
|
+
Object.defineProperty(exports, "piTranscriptReader", { enumerable: true, get: function () { return pi_reader_1.piTranscriptReader; } });
|
|
73
|
+
var reader_1 = require("./transcripts/reader");
|
|
74
|
+
Object.defineProperty(exports, "passthroughReader", { enumerable: true, get: function () { return reader_1.passthroughReader; } });
|
|
75
|
+
Object.defineProperty(exports, "TranscriptReaderRegistry", { enumerable: true, get: function () { return reader_1.TranscriptReaderRegistry; } });
|
|
76
|
+
var registry_1 = require("./transcripts/registry");
|
|
77
|
+
Object.defineProperty(exports, "defaultTranscriptReaders", { enumerable: true, get: function () { return registry_1.defaultTranscriptReaders; } });
|
|
78
|
+
Object.defineProperty(exports, "transcriptReaders", { enumerable: true, get: function () { return registry_1.transcriptReaders; } });
|
package/mirror.d.ts
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { RunEventRecord } from './record';
|
|
21
21
|
import type { RunLogAppendStore } from './store';
|
|
22
|
-
import type
|
|
23
|
-
import { type
|
|
22
|
+
import { type TranscriptEntry } from './transcripts/entry';
|
|
23
|
+
import { type TranscriptFormat } from './transcripts/format';
|
|
24
24
|
/** The slice of a harness session manager the mirror needs. */
|
|
25
25
|
export interface SessionEntrySource {
|
|
26
26
|
getHeader(): unknown;
|
|
@@ -30,11 +30,11 @@ export interface SessionMirrorOptions {
|
|
|
30
30
|
runId: string;
|
|
31
31
|
store: RunLogAppendStore;
|
|
32
32
|
/**
|
|
33
|
-
* Narrow each entry before it is appended. Defaults to the
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Narrow each entry before it is appended. Defaults to the reader registered
|
|
34
|
+
* for `transcriptFormat`, so an adapter for another transcript gets its own
|
|
35
|
+
* validation by registering its reader.
|
|
36
36
|
*/
|
|
37
|
-
assertEntry?: (entry: unknown) =>
|
|
37
|
+
assertEntry?: (entry: unknown) => TranscriptEntry;
|
|
38
38
|
/** Transcript format the entries are encoded in, e.g. `pi`. */
|
|
39
39
|
transcriptFormat?: TranscriptFormat;
|
|
40
40
|
/** Transcript format version the entries are produced under. */
|
package/mirror.js
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.SessionMirror = void 0;
|
|
23
|
-
const
|
|
23
|
+
const format_1 = require("./transcripts/format");
|
|
24
|
+
const registry_1 = require("./transcripts/registry");
|
|
24
25
|
class SessionMirror {
|
|
25
26
|
runId;
|
|
26
27
|
store;
|
|
@@ -35,7 +36,9 @@ class SessionMirror {
|
|
|
35
36
|
constructor(options) {
|
|
36
37
|
this.runId = options.runId;
|
|
37
38
|
this.store = options.store;
|
|
38
|
-
this.assertEntry =
|
|
39
|
+
this.assertEntry =
|
|
40
|
+
options.assertEntry ??
|
|
41
|
+
registry_1.transcriptReaders.require(options.transcriptFormat ?? format_1.PI_TRANSCRIPT_FORMAT).assertEntry;
|
|
39
42
|
this.transcriptFormat = options.transcriptFormat;
|
|
40
43
|
this.transcriptVersion = options.transcriptVersion;
|
|
41
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic-kit/run-log",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "The append-only agent run log — pi session entries stored verbatim under a run/seq wrapper, with projections for transcript, usage, tool state and resumable sessions",
|
|
6
6
|
"main": "index.js",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"run-log",
|
|
36
36
|
"constructive"
|
|
37
37
|
],
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "baa84094b8541eb5000701b0060aa5b298032dc7"
|
|
39
39
|
}
|