@bububuger/spanory 0.1.14

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/otlp.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { buildResource } from '../../otlp-core/dist/index.js';
2
+ export { buildResource };
3
+ export declare function parseHeaders(input: any): any;
4
+ export declare function compileOtlp(events: any, resource: any): any;
5
+ export declare function sendOtlp(endpoint: any, payload: any, headers?: {}): Promise<void>;
package/dist/otlp.js ADDED
@@ -0,0 +1,12 @@
1
+ // @ts-nocheck
2
+ import { buildResource, compileOtlpSpans, parseOtlpHeaders, sendOtlpHttp, } from '../../otlp-core/dist/index.js';
3
+ export { buildResource };
4
+ export function parseHeaders(input) {
5
+ return parseOtlpHeaders(input);
6
+ }
7
+ export function compileOtlp(events, resource) {
8
+ return compileOtlpSpans(events, resource);
9
+ }
10
+ export async function sendOtlp(endpoint, payload, headers = {}) {
11
+ await sendOtlpHttp(endpoint, payload, headers);
12
+ }
@@ -0,0 +1,21 @@
1
+ export declare function loadExportedEvents(inputPath: any): Promise<any[]>;
2
+ export declare function summarizeSessions(sessions: any): any;
3
+ export declare function summarizeMcp(sessions: any): {
4
+ server: any;
5
+ calls: any;
6
+ sessions: any;
7
+ }[];
8
+ export declare function summarizeCommands(sessions: any): {
9
+ command: any;
10
+ calls: any;
11
+ sessions: any;
12
+ }[];
13
+ export declare function summarizeAgents(sessions: any): any[];
14
+ export declare function summarizeCache(sessions: any): any;
15
+ export declare function summarizeTools(sessions: any): {
16
+ category: any;
17
+ tool: any;
18
+ calls: any;
19
+ sessions: any;
20
+ }[];
21
+ export declare function summarizeTurnDiff(sessions: any): any[];
@@ -0,0 +1,245 @@
1
+ // @ts-nocheck
2
+ import { readdir, readFile, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ function toNumber(value) {
5
+ const n = Number(value);
6
+ return Number.isFinite(n) ? n : 0;
7
+ }
8
+ function toOptionalNumber(value) {
9
+ const n = Number(value);
10
+ return Number.isFinite(n) ? n : undefined;
11
+ }
12
+ function round6(value) {
13
+ return Number(Number(value).toFixed(6));
14
+ }
15
+ function parseTurnOrdinal(turnId) {
16
+ const m = String(turnId ?? '').match(/^turn-(\d+)$/);
17
+ if (!m)
18
+ return undefined;
19
+ const n = Number(m[1]);
20
+ return Number.isFinite(n) ? n : undefined;
21
+ }
22
+ function usageFromEvent(event) {
23
+ const attrs = event.attributes ?? {};
24
+ const input = toNumber(attrs['gen_ai.usage.input_tokens']);
25
+ const output = toNumber(attrs['gen_ai.usage.output_tokens']);
26
+ const total = toNumber(attrs['gen_ai.usage.total_tokens']) || input + output;
27
+ return { input, output, total };
28
+ }
29
+ export async function loadExportedEvents(inputPath) {
30
+ const inputStat = await stat(inputPath);
31
+ const files = [];
32
+ if (inputStat.isDirectory()) {
33
+ const names = await readdir(inputPath);
34
+ for (const name of names) {
35
+ if (name.endsWith('.json'))
36
+ files.push(path.join(inputPath, name));
37
+ }
38
+ }
39
+ else {
40
+ files.push(inputPath);
41
+ }
42
+ const sessions = [];
43
+ for (const file of files) {
44
+ const raw = await readFile(file, 'utf-8');
45
+ const parsed = JSON.parse(raw);
46
+ if (!Array.isArray(parsed.events))
47
+ continue;
48
+ sessions.push({
49
+ file,
50
+ context: parsed.context ?? {},
51
+ events: parsed.events,
52
+ });
53
+ }
54
+ return sessions;
55
+ }
56
+ export function summarizeSessions(sessions) {
57
+ return sessions.map((s) => {
58
+ const turns = s.events.filter((e) => e.category === 'turn');
59
+ const usage = turns.reduce((acc, e) => {
60
+ const u = usageFromEvent(e);
61
+ acc.input += u.input;
62
+ acc.output += u.output;
63
+ acc.total += u.total;
64
+ return acc;
65
+ }, { input: 0, output: 0, total: 0 });
66
+ return {
67
+ projectId: s.context.projectId ?? s.events[0]?.projectId,
68
+ sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
69
+ runtime: s.events[0]?.runtime,
70
+ turns: turns.length,
71
+ events: s.events.length,
72
+ usage,
73
+ };
74
+ });
75
+ }
76
+ export function summarizeMcp(sessions) {
77
+ const agg = new Map();
78
+ for (const s of sessions) {
79
+ for (const e of s.events) {
80
+ if (e.category !== 'mcp')
81
+ continue;
82
+ const attrs = e.attributes ?? {};
83
+ const key = attrs['agentic.mcp.server.name'] ?? attrs['gen_ai.tool.name'] ?? e.name;
84
+ const cur = agg.get(key) ?? { server: key, calls: 0, sessions: new Set() };
85
+ cur.calls += 1;
86
+ cur.sessions.add(e.sessionId);
87
+ agg.set(key, cur);
88
+ }
89
+ }
90
+ return [...agg.values()].map((v) => ({ server: v.server, calls: v.calls, sessions: v.sessions.size }));
91
+ }
92
+ export function summarizeCommands(sessions) {
93
+ const agg = new Map();
94
+ for (const s of sessions) {
95
+ for (const e of s.events) {
96
+ if (e.category !== 'agent_command')
97
+ continue;
98
+ const command = e.attributes?.['agentic.command.name'] ?? e.name;
99
+ const cur = agg.get(command) ?? { command, calls: 0, sessions: new Set() };
100
+ cur.calls += 1;
101
+ cur.sessions.add(e.sessionId);
102
+ agg.set(command, cur);
103
+ }
104
+ }
105
+ return [...agg.values()].map((v) => ({ command: v.command, calls: v.calls, sessions: v.sessions.size }));
106
+ }
107
+ export function summarizeAgents(sessions) {
108
+ const out = [];
109
+ for (const s of sessions) {
110
+ const turns = s.events.filter((e) => e.category === 'turn');
111
+ const tasks = s.events.filter((e) => e.category === 'agent_task');
112
+ const shell = s.events.filter((e) => e.category === 'shell_command');
113
+ const mcp = s.events.filter((e) => e.category === 'mcp');
114
+ const usage = turns.reduce((acc, e) => {
115
+ const u = usageFromEvent(e);
116
+ acc.input += u.input;
117
+ acc.output += u.output;
118
+ acc.total += u.total;
119
+ return acc;
120
+ }, { input: 0, output: 0, total: 0 });
121
+ out.push({
122
+ sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
123
+ projectId: s.context.projectId ?? s.events[0]?.projectId,
124
+ runtime: s.events[0]?.runtime,
125
+ turns: turns.length,
126
+ agentTasks: tasks.length,
127
+ shellCommands: shell.length,
128
+ mcpCalls: mcp.length,
129
+ usage,
130
+ });
131
+ }
132
+ return out;
133
+ }
134
+ export function summarizeCache(sessions) {
135
+ return sessions.map((s) => {
136
+ const turns = s.events.filter((e) => e.category === 'turn');
137
+ let inputTokens = 0;
138
+ let cacheReadInputTokens = 0;
139
+ let cacheCreationInputTokens = 0;
140
+ const explicitHitRates = [];
141
+ for (const turn of turns) {
142
+ const attrs = turn.attributes ?? {};
143
+ inputTokens += toNumber(attrs['gen_ai.usage.input_tokens']);
144
+ cacheReadInputTokens += toNumber(attrs['gen_ai.usage.details.cache_read_input_tokens']);
145
+ cacheCreationInputTokens += toNumber(attrs['gen_ai.usage.details.cache_creation_input_tokens']);
146
+ const hitRate = toOptionalNumber(attrs['gen_ai.usage.details.cache_hit_rate']);
147
+ if (hitRate !== undefined)
148
+ explicitHitRates.push(hitRate);
149
+ }
150
+ const cacheHitRate = explicitHitRates.length > 0
151
+ ? round6(explicitHitRates.reduce((acc, v) => acc + v, 0) / explicitHitRates.length)
152
+ : round6((inputTokens + cacheReadInputTokens) > 0 ? cacheReadInputTokens / (inputTokens + cacheReadInputTokens) : 0);
153
+ return {
154
+ projectId: s.context.projectId ?? s.events[0]?.projectId,
155
+ sessionId: s.context.sessionId ?? s.events[0]?.sessionId,
156
+ runtime: s.events[0]?.runtime,
157
+ turns: turns.length,
158
+ inputTokens,
159
+ cacheReadInputTokens,
160
+ cacheCreationInputTokens,
161
+ cacheHitRate,
162
+ };
163
+ });
164
+ }
165
+ export function summarizeTools(sessions) {
166
+ const agg = new Map();
167
+ for (const s of sessions) {
168
+ for (const e of s.events) {
169
+ if (!['tool', 'mcp', 'shell_command', 'agent_task'].includes(e.category))
170
+ continue;
171
+ const attrs = e.attributes ?? {};
172
+ const tool = attrs['gen_ai.tool.name'] ?? e.name;
173
+ const key = `${e.category}:${tool}`;
174
+ const cur = agg.get(key) ?? {
175
+ category: e.category,
176
+ tool,
177
+ calls: 0,
178
+ sessions: new Set(),
179
+ };
180
+ cur.calls += 1;
181
+ cur.sessions.add(e.sessionId);
182
+ agg.set(key, cur);
183
+ }
184
+ }
185
+ return [...agg.values()]
186
+ .map((v) => ({
187
+ category: v.category,
188
+ tool: v.tool,
189
+ calls: v.calls,
190
+ sessions: v.sessions.size,
191
+ }))
192
+ .sort((a, b) => {
193
+ if (b.calls !== a.calls)
194
+ return b.calls - a.calls;
195
+ if (a.category !== b.category)
196
+ return a.category.localeCompare(b.category);
197
+ return a.tool.localeCompare(b.tool);
198
+ });
199
+ }
200
+ export function summarizeTurnDiff(sessions) {
201
+ const rows = [];
202
+ for (const s of sessions) {
203
+ const turns = s.events
204
+ .filter((e) => e.category === 'turn')
205
+ .slice()
206
+ .sort((a, b) => {
207
+ const ao = parseTurnOrdinal(a.turnId);
208
+ const bo = parseTurnOrdinal(b.turnId);
209
+ if (ao === undefined && bo === undefined)
210
+ return String(a.turnId ?? '').localeCompare(String(b.turnId ?? ''));
211
+ if (ao === undefined)
212
+ return 1;
213
+ if (bo === undefined)
214
+ return -1;
215
+ return ao - bo;
216
+ });
217
+ for (let i = 0; i < turns.length; i += 1) {
218
+ const turn = turns[i];
219
+ const attrs = turn.attributes ?? {};
220
+ const input = String(turn.input ?? '');
221
+ const prevInput = String(turns[i - 1]?.input ?? '');
222
+ const charDelta = toOptionalNumber(attrs['agentic.turn.diff.char_delta'])
223
+ ?? (i === 0 ? 0 : input.length - prevInput.length);
224
+ const lineDelta = toOptionalNumber(attrs['agentic.turn.diff.line_delta'])
225
+ ?? (i === 0 ? 0 : (input ? input.split(/\r?\n/).length : 0) - (prevInput ? prevInput.split(/\r?\n/).length : 0));
226
+ const similarity = toOptionalNumber(attrs['agentic.turn.diff.similarity']) ?? (i === 0 ? 1 : undefined);
227
+ const changed = typeof attrs['agentic.turn.diff.changed'] === 'boolean'
228
+ ? attrs['agentic.turn.diff.changed']
229
+ : (i === 0 ? false : input !== prevInput);
230
+ rows.push({
231
+ projectId: s.context.projectId ?? turn.projectId,
232
+ sessionId: s.context.sessionId ?? turn.sessionId,
233
+ runtime: turn.runtime,
234
+ turnId: turn.turnId,
235
+ inputHash: attrs['agentic.turn.input.hash'] ?? '',
236
+ prevHash: attrs['agentic.turn.input.prev_hash'] ?? '',
237
+ charDelta,
238
+ lineDelta,
239
+ similarity,
240
+ changed,
241
+ });
242
+ }
243
+ }
244
+ return rows;
245
+ }
@@ -0,0 +1,18 @@
1
+ export declare const claudeCodeAdapter: {
2
+ runtimeName: string;
3
+ capabilities: {
4
+ turnDetection: boolean;
5
+ toolCallAttribution: boolean;
6
+ toolResultCorrelation: boolean;
7
+ modelName: boolean;
8
+ usageDetails: boolean;
9
+ slashCommandExtraction: boolean;
10
+ mcpServerExtraction: boolean;
11
+ };
12
+ resolveContextFromHook(payload: any): {
13
+ projectId: any;
14
+ sessionId: any;
15
+ transcriptPath: any;
16
+ };
17
+ collectEvents(context: any): Promise<any[]>;
18
+ };
@@ -0,0 +1,73 @@
1
+ // @ts-nocheck
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { RUNTIME_CAPABILITIES } from '../shared/capabilities.js';
5
+ import { normalizeTranscriptMessages, parseProjectIdFromTranscriptPath, pickUsage } from '../shared/normalize.js';
6
+ function parseTimestamp(entry) {
7
+ const raw = entry?.timestamp;
8
+ const date = raw ? new Date(raw) : new Date();
9
+ if (Number.isNaN(date.getTime()))
10
+ return new Date();
11
+ return date;
12
+ }
13
+ function normalizeIsSidechain(entry) {
14
+ const raw = entry?.isSidechain ?? entry?.is_sidechain ?? entry?.message?.isSidechain ?? entry?.message?.is_sidechain;
15
+ return raw === true;
16
+ }
17
+ function normalizeAgentId(entry) {
18
+ return entry?.agentId ?? entry?.agent_id ?? entry?.message?.agentId ?? entry?.message?.agent_id;
19
+ }
20
+ async function readClaudeTranscript(transcriptPath) {
21
+ const raw = await readFile(transcriptPath, 'utf-8');
22
+ const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean);
23
+ const messages = [];
24
+ for (const line of lines) {
25
+ try {
26
+ const entry = JSON.parse(line);
27
+ messages.push({
28
+ role: entry.type,
29
+ isMeta: entry.isMeta ?? false,
30
+ isSidechain: normalizeIsSidechain(entry),
31
+ agentId: normalizeAgentId(entry),
32
+ content: entry?.message?.content ?? entry.content ?? '',
33
+ model: entry?.message?.model ?? entry.model,
34
+ usage: pickUsage(entry?.message?.usage ?? entry?.usage ?? entry?.message_usage),
35
+ runtimeVersion: entry?.version,
36
+ messageId: entry?.message?.id,
37
+ toolUseResult: entry?.toolUseResult,
38
+ sourceToolUseId: entry?.sourceToolUseID ?? entry?.sourceToolUseId,
39
+ timestamp: parseTimestamp(entry),
40
+ });
41
+ }
42
+ catch {
43
+ // ignore malformed lines
44
+ }
45
+ }
46
+ messages.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
47
+ return messages;
48
+ }
49
+ export const claudeCodeAdapter = {
50
+ runtimeName: 'claude-code',
51
+ capabilities: RUNTIME_CAPABILITIES['claude-code'],
52
+ resolveContextFromHook(payload) {
53
+ const sessionId = payload.sessionId;
54
+ const transcriptPath = payload.transcriptPath;
55
+ if (!sessionId || !transcriptPath)
56
+ return null;
57
+ const projectId = parseProjectIdFromTranscriptPath(transcriptPath, '/.claude/projects/');
58
+ if (!projectId)
59
+ return null;
60
+ return { projectId, sessionId, transcriptPath };
61
+ },
62
+ async collectEvents(context) {
63
+ const transcriptPath = context.transcriptPath ??
64
+ path.join(process.env.HOME || '', '.claude', 'projects', context.projectId, `${context.sessionId}.jsonl`);
65
+ const messages = await readClaudeTranscript(transcriptPath);
66
+ return normalizeTranscriptMessages({
67
+ runtime: 'claude-code',
68
+ projectId: context.projectId,
69
+ sessionId: context.sessionId,
70
+ messages,
71
+ });
72
+ },
73
+ };
@@ -0,0 +1,18 @@
1
+ export declare const codexAdapter: {
2
+ runtimeName: string;
3
+ capabilities: {
4
+ turnDetection: boolean;
5
+ toolCallAttribution: boolean;
6
+ toolResultCorrelation: boolean;
7
+ modelName: boolean;
8
+ usageDetails: boolean;
9
+ slashCommandExtraction: boolean;
10
+ mcpServerExtraction: boolean;
11
+ };
12
+ resolveContextFromHook(payload: any): {
13
+ transcriptPath?: any;
14
+ projectId: string;
15
+ sessionId: any;
16
+ };
17
+ collectEvents(context: any): Promise<any>;
18
+ };