@agentstrack/collector 0.1.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/CHANGELOG.md +168 -0
- package/LICENSE +202 -0
- package/README.md +779 -0
- package/dist/adapters/account.d.ts +22 -0
- package/dist/adapters/account.js +142 -0
- package/dist/adapters/account.js.map +1 -0
- package/dist/adapters/claude.d.ts +49 -0
- package/dist/adapters/claude.js +259 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +64 -0
- package/dist/adapters/codex.js +350 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/opencode.d.ts +79 -0
- package/dist/adapters/opencode.js +338 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/types.d.ts +97 -0
- package/dist/adapters/types.js +20 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +450 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/service.d.ts +3 -0
- package/dist/commands/service.js +86 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon.d.ts +57 -0
- package/dist/daemon.js +368 -0
- package/dist/daemon.js.map +1 -0
- package/dist/git/commits.d.ts +34 -0
- package/dist/git/commits.js +85 -0
- package/dist/git/commits.js.map +1 -0
- package/dist/git/repo.d.ts +36 -0
- package/dist/git/repo.js +141 -0
- package/dist/git/repo.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/privacy/mode.d.ts +11 -0
- package/dist/privacy/mode.js +17 -0
- package/dist/privacy/mode.js.map +1 -0
- package/dist/privacy/paths.d.ts +11 -0
- package/dist/privacy/paths.js +30 -0
- package/dist/privacy/paths.js.map +1 -0
- package/dist/privacy/pipeline.d.ts +26 -0
- package/dist/privacy/pipeline.js +112 -0
- package/dist/privacy/pipeline.js.map +1 -0
- package/dist/privacy/redact.d.ts +29 -0
- package/dist/privacy/redact.js +58 -0
- package/dist/privacy/redact.js.map +1 -0
- package/dist/queue/spool.d.ts +47 -0
- package/dist/queue/spool.js +152 -0
- package/dist/queue/spool.js.map +1 -0
- package/dist/queue/tailer.d.ts +25 -0
- package/dist/queue/tailer.js +48 -0
- package/dist/queue/tailer.js.map +1 -0
- package/dist/schema.d.ts +76 -0
- package/dist/schema.js +46 -0
- package/dist/schema.js.map +1 -0
- package/dist/sessions/title.d.ts +2 -0
- package/dist/sessions/title.js +11 -0
- package/dist/sessions/title.js.map +1 -0
- package/dist/transport/client.d.ts +76 -0
- package/dist/transport/client.js +80 -0
- package/dist/transport/client.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AccountIdentity } from './types.js';
|
|
2
|
+
/** Test seam: forget cached identities so a rewritten file is re-read. */
|
|
3
|
+
export declare function resetAccountCache(): void;
|
|
4
|
+
/**
|
|
5
|
+
* The Claude Code account that is active RIGHT NOW.
|
|
6
|
+
*
|
|
7
|
+
* ~/.claude.json holds exactly one `oauthAccount` and is rewritten when the
|
|
8
|
+
* user switches accounts, so there is no history to consult: this is a
|
|
9
|
+
* point-in-time reading and must be taken fresh on every scan cycle. The
|
|
10
|
+
* daemon therefore attaches it only to events written while the collector was
|
|
11
|
+
* running — see `Collector.liveSinceMs`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readClaudeAccount(claudeDir?: string): AccountIdentity | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* The OpenCode accounts that are active right now, keyed by serviceID.
|
|
16
|
+
*
|
|
17
|
+
* account.json is `{ accounts: { <id>: { id, serviceID, description, credential } },
|
|
18
|
+
* active: { <serviceID>: <id> } }`. `credential` is a live API key and is never
|
|
19
|
+
* read. One install can hold an active account per provider, so a session is
|
|
20
|
+
* attributed by the provider its model actually ran on.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readOpenCodeAccounts(dataDir: string): Map<string, AccountIdentity>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { str } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Per-account attribution.
|
|
7
|
+
*
|
|
8
|
+
* One machine routinely drives several accounts — a personal Claude login and
|
|
9
|
+
* a work one, an OpenRouter key and an Anthropic subscription. Without an
|
|
10
|
+
* account key every session on the host collapses into one identity and the
|
|
11
|
+
* cost split is wrong.
|
|
12
|
+
*
|
|
13
|
+
* Two rules this file exists to keep:
|
|
14
|
+
*
|
|
15
|
+
* 1. `key` is a stable opaque id and is the only field that must travel. It is
|
|
16
|
+
* what makes sessions SPLIT per account, which has to work even in
|
|
17
|
+
* `metadata` mode. `label` and `org` name a human and their employer, so
|
|
18
|
+
* the privacy pipeline strips them below `analytics` mode.
|
|
19
|
+
* 2. Credentials are never read. OpenCode's account.json carries a live
|
|
20
|
+
* `credential` next to the id, and auth.json is nothing but credentials;
|
|
21
|
+
* only `id` and `serviceID` are touched, and auth.json is never opened.
|
|
22
|
+
*/
|
|
23
|
+
/** Parsed value cached against the file's mtime — these files are re-read every scan cycle. */
|
|
24
|
+
const cache = new Map();
|
|
25
|
+
/** Test seam: forget cached identities so a rewritten file is re-read. */
|
|
26
|
+
export function resetAccountCache() {
|
|
27
|
+
cache.clear();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reads and parses `path`, reusing the last result while mtime and size are
|
|
31
|
+
* unchanged. Both files are re-read on every 5s scan cycle because they are
|
|
32
|
+
* rewritten in place on account switch, so the cache is what keeps that free.
|
|
33
|
+
*/
|
|
34
|
+
function readCached(path, parse, empty) {
|
|
35
|
+
let stats;
|
|
36
|
+
try {
|
|
37
|
+
stats = statSync(path);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
cache.delete(path);
|
|
41
|
+
return empty; // not logged in, or the file was removed
|
|
42
|
+
}
|
|
43
|
+
const hit = cache.get(path);
|
|
44
|
+
if (hit && hit.mtimeMs === stats.mtimeMs && hit.size === stats.size)
|
|
45
|
+
return hit.value;
|
|
46
|
+
let value;
|
|
47
|
+
try {
|
|
48
|
+
value = parse(readFileSync(path, 'utf8'));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// A half-written or hand-mangled file must not take the collector down.
|
|
52
|
+
value = empty;
|
|
53
|
+
}
|
|
54
|
+
cache.set(path, { mtimeMs: stats.mtimeMs, size: stats.size, value });
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The Claude Code account that is active RIGHT NOW.
|
|
59
|
+
*
|
|
60
|
+
* ~/.claude.json holds exactly one `oauthAccount` and is rewritten when the
|
|
61
|
+
* user switches accounts, so there is no history to consult: this is a
|
|
62
|
+
* point-in-time reading and must be taken fresh on every scan cycle. The
|
|
63
|
+
* daemon therefore attaches it only to events written while the collector was
|
|
64
|
+
* running — see `Collector.liveSinceMs`.
|
|
65
|
+
*/
|
|
66
|
+
export function readClaudeAccount(claudeDir = join(homedir(), '.claude')) {
|
|
67
|
+
// The file sits NEXT TO the config directory: ~/.claude.json, not ~/.claude/.
|
|
68
|
+
const path = join(dirname(claudeDir), '.claude.json');
|
|
69
|
+
return readCached(path, (raw) => {
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
if (!parsed || typeof parsed !== 'object')
|
|
72
|
+
return undefined;
|
|
73
|
+
const oauth = parsed['oauthAccount'];
|
|
74
|
+
if (!oauth || typeof oauth !== 'object')
|
|
75
|
+
return undefined;
|
|
76
|
+
const o = oauth;
|
|
77
|
+
const key = str(o['accountUuid']);
|
|
78
|
+
if (!key)
|
|
79
|
+
return undefined; // no stable id means no attribution, not a guess
|
|
80
|
+
return identity({
|
|
81
|
+
key,
|
|
82
|
+
label: str(o['emailAddress']) ?? str(o['displayName']),
|
|
83
|
+
org: str(o['organizationName']),
|
|
84
|
+
provider: 'anthropic',
|
|
85
|
+
// Claude Code transcripts carry no plan signal, so cost was being
|
|
86
|
+
// labelled ESTIMATED — i.e. "at API list rates" — for people paying a
|
|
87
|
+
// flat subscription. This is the one place on disk that knows, and it
|
|
88
|
+
// is what lets the server report SUBSCRIPTION instead.
|
|
89
|
+
planType: str(o['organizationType']) ?? str(o['billingType']),
|
|
90
|
+
});
|
|
91
|
+
}, undefined);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The OpenCode accounts that are active right now, keyed by serviceID.
|
|
95
|
+
*
|
|
96
|
+
* account.json is `{ accounts: { <id>: { id, serviceID, description, credential } },
|
|
97
|
+
* active: { <serviceID>: <id> } }`. `credential` is a live API key and is never
|
|
98
|
+
* read. One install can hold an active account per provider, so a session is
|
|
99
|
+
* attributed by the provider its model actually ran on.
|
|
100
|
+
*/
|
|
101
|
+
export function readOpenCodeAccounts(dataDir) {
|
|
102
|
+
return readCached(join(dataDir, 'account.json'), (raw) => {
|
|
103
|
+
const result = new Map();
|
|
104
|
+
const parsed = JSON.parse(raw);
|
|
105
|
+
if (!parsed || typeof parsed !== 'object')
|
|
106
|
+
return result;
|
|
107
|
+
const root = parsed;
|
|
108
|
+
const accounts = (root['accounts'] ?? {});
|
|
109
|
+
const active = (root['active'] ?? {});
|
|
110
|
+
for (const [serviceID, id] of Object.entries(active)) {
|
|
111
|
+
const accountId = str(id);
|
|
112
|
+
if (!accountId)
|
|
113
|
+
continue;
|
|
114
|
+
const entry = accounts[accountId];
|
|
115
|
+
const description = entry && typeof entry === 'object'
|
|
116
|
+
? str(entry['description'])
|
|
117
|
+
: undefined;
|
|
118
|
+
result.set(serviceID, identity({
|
|
119
|
+
// Neither half is a secret: serviceID is a provider name and id is
|
|
120
|
+
// an opaque account id. The sibling `credential` is not read.
|
|
121
|
+
key: `${serviceID}:${accountId}`,
|
|
122
|
+
label: description,
|
|
123
|
+
provider: serviceID,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}, new Map());
|
|
128
|
+
}
|
|
129
|
+
/** Builds the wire shape, omitting absent optional fields rather than sending nulls. */
|
|
130
|
+
function identity(account) {
|
|
131
|
+
const out = { key: account.key };
|
|
132
|
+
if (account.label)
|
|
133
|
+
out.label = account.label;
|
|
134
|
+
if (account.org)
|
|
135
|
+
out.org = account.org;
|
|
136
|
+
if (account.provider)
|
|
137
|
+
out.provider = account.provider;
|
|
138
|
+
if (account.planType)
|
|
139
|
+
out.planType = account.planType;
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/adapters/account.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AccountIdentity, AgentAdapter, DetectionResult, HealthStatus, NormalizeContext, NormalizedEvent } from './types.js';
|
|
2
|
+
import { type TokenUsage } from '../schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code adapter.
|
|
5
|
+
*
|
|
6
|
+
* Source: ~/.claude/projects/<path-slug>/<session-uuid>.jsonl
|
|
7
|
+
*
|
|
8
|
+
* Verified line shapes (Claude Code 2.1.x):
|
|
9
|
+
* {"type":"user","message":{"role":"user","content":"..."},"timestamp":"…",
|
|
10
|
+
* "sessionId":"…","cwd":"…","gitBranch":"…","version":"…"}
|
|
11
|
+
* {"type":"assistant","message":{"model":"claude-opus-5","content":[…],
|
|
12
|
+
* "usage":{"input_tokens":2,"cache_creation_input_tokens":39728,
|
|
13
|
+
* "cache_read_input_tokens":26254,"output_tokens":505,
|
|
14
|
+
* "output_tokens_details":{"thinking_tokens":257}}},"timestamp":"…"}
|
|
15
|
+
*
|
|
16
|
+
* Tool calls appear as tool_use / tool_result blocks inside message.content.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CLAUDE_DIR: string;
|
|
19
|
+
export declare class ClaudeCodeAdapter implements AgentAdapter {
|
|
20
|
+
readonly id = "claude_code";
|
|
21
|
+
detect(): Promise<DetectionResult>;
|
|
22
|
+
/**
|
|
23
|
+
* The account Claude Code is signed in as right now.
|
|
24
|
+
*
|
|
25
|
+
* ~/.claude.json carries a single `oauthAccount` and is rewritten on account
|
|
26
|
+
* switch, so this is a live reading with no history behind it. The daemon
|
|
27
|
+
* attaches it only to events written after the collector started — a
|
|
28
|
+
* transcript that was already on disk cannot be attributed retroactively and
|
|
29
|
+
* gets no account rather than the wrong one.
|
|
30
|
+
*/
|
|
31
|
+
account(): AccountIdentity | undefined;
|
|
32
|
+
health(): Promise<HealthStatus>;
|
|
33
|
+
normalize(line: string, ctx: NormalizeContext): NormalizedEvent[];
|
|
34
|
+
private normalizeUser;
|
|
35
|
+
private normalizeAssistant;
|
|
36
|
+
}
|
|
37
|
+
export declare function readUsage(raw: unknown): TokenUsage;
|
|
38
|
+
/**
|
|
39
|
+
* Line deltas for one edit, derived from the tool input.
|
|
40
|
+
*
|
|
41
|
+
* It has to happen here: the privacy pipeline drops old_string/new_string
|
|
42
|
+
* before anything is uploaded, so no later stage can recover the counts. Lines
|
|
43
|
+
* present on both sides are not counted, which matches what `git diff
|
|
44
|
+
* --numstat` reports for the same change.
|
|
45
|
+
*/
|
|
46
|
+
export declare function countEditLines(toolName: string, input: Record<string, unknown>): {
|
|
47
|
+
lines_added: number;
|
|
48
|
+
lines_removed: number;
|
|
49
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { num, safeJsonParse, str } from './types.js';
|
|
5
|
+
import { readClaudeAccount } from './account.js';
|
|
6
|
+
import { emptyUsage } from '../schema.js';
|
|
7
|
+
import { deriveTitle } from '../sessions/title.js';
|
|
8
|
+
/**
|
|
9
|
+
* Claude Code adapter.
|
|
10
|
+
*
|
|
11
|
+
* Source: ~/.claude/projects/<path-slug>/<session-uuid>.jsonl
|
|
12
|
+
*
|
|
13
|
+
* Verified line shapes (Claude Code 2.1.x):
|
|
14
|
+
* {"type":"user","message":{"role":"user","content":"..."},"timestamp":"…",
|
|
15
|
+
* "sessionId":"…","cwd":"…","gitBranch":"…","version":"…"}
|
|
16
|
+
* {"type":"assistant","message":{"model":"claude-opus-5","content":[…],
|
|
17
|
+
* "usage":{"input_tokens":2,"cache_creation_input_tokens":39728,
|
|
18
|
+
* "cache_read_input_tokens":26254,"output_tokens":505,
|
|
19
|
+
* "output_tokens_details":{"thinking_tokens":257}}},"timestamp":"…"}
|
|
20
|
+
*
|
|
21
|
+
* Tool calls appear as tool_use / tool_result blocks inside message.content.
|
|
22
|
+
*/
|
|
23
|
+
export const CLAUDE_DIR = process.env['CLAUDE_CONFIG_DIR'] ?? join(homedir(), '.claude');
|
|
24
|
+
const PROJECTS_DIR = join(CLAUDE_DIR, 'projects');
|
|
25
|
+
export class ClaudeCodeAdapter {
|
|
26
|
+
id = 'claude_code';
|
|
27
|
+
async detect() {
|
|
28
|
+
if (!existsSync(PROJECTS_DIR)) {
|
|
29
|
+
return { installed: false, watchPaths: [], note: `No transcripts at ${PROJECTS_DIR}` };
|
|
30
|
+
}
|
|
31
|
+
let version;
|
|
32
|
+
try {
|
|
33
|
+
// The version is stamped on every transcript line; read one cheaply.
|
|
34
|
+
const projects = readdirSync(PROJECTS_DIR).slice(0, 5);
|
|
35
|
+
for (const project of projects) {
|
|
36
|
+
const files = readdirSync(join(PROJECTS_DIR, project)).filter((f) => f.endsWith('.jsonl'));
|
|
37
|
+
if (files.length === 0)
|
|
38
|
+
continue;
|
|
39
|
+
// The version is stamped on every transcript line; read the newest one
|
|
40
|
+
// rather than reporting a placeholder string as the agent version.
|
|
41
|
+
version = readVersionFromTranscript(join(PROJECTS_DIR, project, files[0]));
|
|
42
|
+
if (version)
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Unreadable project dir is not fatal; the tailer reports per-file errors.
|
|
48
|
+
}
|
|
49
|
+
return { installed: true, version, watchPaths: [PROJECTS_DIR] };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The account Claude Code is signed in as right now.
|
|
53
|
+
*
|
|
54
|
+
* ~/.claude.json carries a single `oauthAccount` and is rewritten on account
|
|
55
|
+
* switch, so this is a live reading with no history behind it. The daemon
|
|
56
|
+
* attaches it only to events written after the collector started — a
|
|
57
|
+
* transcript that was already on disk cannot be attributed retroactively and
|
|
58
|
+
* gets no account rather than the wrong one.
|
|
59
|
+
*/
|
|
60
|
+
account() {
|
|
61
|
+
return readClaudeAccount(CLAUDE_DIR);
|
|
62
|
+
}
|
|
63
|
+
async health() {
|
|
64
|
+
const detection = await this.detect();
|
|
65
|
+
if (!detection.installed)
|
|
66
|
+
return { healthy: false, filesTracked: 0, error: detection.note };
|
|
67
|
+
let filesTracked = 0;
|
|
68
|
+
try {
|
|
69
|
+
for (const project of readdirSync(PROJECTS_DIR)) {
|
|
70
|
+
filesTracked += readdirSync(join(PROJECTS_DIR, project)).filter((f) => f.endsWith('.jsonl')).length;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return { healthy: false, filesTracked: 0, error: error instanceof Error ? error.message : String(error) };
|
|
75
|
+
}
|
|
76
|
+
return { healthy: true, filesTracked };
|
|
77
|
+
}
|
|
78
|
+
normalize(line, ctx) {
|
|
79
|
+
const raw = safeJsonParse(line);
|
|
80
|
+
if (!raw)
|
|
81
|
+
return [];
|
|
82
|
+
const type = str(raw['type']);
|
|
83
|
+
const sessionId = str(raw['sessionId']) ?? str(raw['session_id']);
|
|
84
|
+
const occurredAt = str(raw['timestamp']);
|
|
85
|
+
if (!type || !sessionId || !occurredAt)
|
|
86
|
+
return [];
|
|
87
|
+
const agentVersion = str(raw['version']);
|
|
88
|
+
const cwd = str(raw['cwd']);
|
|
89
|
+
const branch = str(raw['gitBranch']);
|
|
90
|
+
const repo = branch || cwd ? { branch, project_path: cwd } : undefined;
|
|
91
|
+
const base = {
|
|
92
|
+
occurred_at: occurredAt,
|
|
93
|
+
session_id: sessionId,
|
|
94
|
+
agent: 'claude_code',
|
|
95
|
+
agent_version: agentVersion,
|
|
96
|
+
};
|
|
97
|
+
const wrap = (event_type, payload) => ({ event: { ...base, event_type, payload }, cwd, repo });
|
|
98
|
+
void ctx;
|
|
99
|
+
if (type === 'user')
|
|
100
|
+
return this.normalizeUser(raw, wrap);
|
|
101
|
+
if (type === 'assistant')
|
|
102
|
+
return this.normalizeAssistant(raw, wrap);
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
normalizeUser(raw, wrap) {
|
|
106
|
+
const message = (raw['message'] ?? {});
|
|
107
|
+
const content = message['content'];
|
|
108
|
+
// A "user" line is either a real human prompt or a tool result the agent
|
|
109
|
+
// fed back to itself. Only the former is human interaction time.
|
|
110
|
+
if (Array.isArray(content)) {
|
|
111
|
+
const events = [];
|
|
112
|
+
for (const block of content) {
|
|
113
|
+
if (!block || typeof block !== 'object')
|
|
114
|
+
continue;
|
|
115
|
+
const b = block;
|
|
116
|
+
if (b['type'] !== 'tool_result')
|
|
117
|
+
continue;
|
|
118
|
+
const failed = b['is_error'] === true;
|
|
119
|
+
events.push(wrap(failed ? 'tool.failed' : 'tool.completed', {
|
|
120
|
+
tool_name: str(b['name']) ?? 'unknown',
|
|
121
|
+
tool_call_id: str(b['tool_use_id']),
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
return events;
|
|
125
|
+
}
|
|
126
|
+
if (typeof content !== 'string')
|
|
127
|
+
return [];
|
|
128
|
+
// Meta lines (command output, system reminders) are not prompts.
|
|
129
|
+
if (raw['isMeta'] === true || str(raw['promptSource']) === 'system')
|
|
130
|
+
return [];
|
|
131
|
+
return [
|
|
132
|
+
wrap('user.prompted', {
|
|
133
|
+
prompt_chars: content.length,
|
|
134
|
+
prompt_text: content,
|
|
135
|
+
derived_title: deriveTitle(content),
|
|
136
|
+
}),
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
normalizeAssistant(raw, wrap) {
|
|
140
|
+
const message = (raw['message'] ?? {});
|
|
141
|
+
const model = str(message['model']);
|
|
142
|
+
// Claude Code stamps '<synthetic>' on locally generated messages (interrupts,
|
|
143
|
+
// injected notices). They are not model calls and must not reach the model
|
|
144
|
+
// analytics or the cost table.
|
|
145
|
+
if (!model || model.startsWith('<'))
|
|
146
|
+
return [];
|
|
147
|
+
const events = [
|
|
148
|
+
wrap('model.response', {
|
|
149
|
+
model,
|
|
150
|
+
provider: 'anthropic',
|
|
151
|
+
usage: readUsage(message['usage']),
|
|
152
|
+
stop_reason: str(message['stop_reason']),
|
|
153
|
+
}),
|
|
154
|
+
];
|
|
155
|
+
const content = message['content'];
|
|
156
|
+
if (Array.isArray(content)) {
|
|
157
|
+
for (const block of content) {
|
|
158
|
+
if (!block || typeof block !== 'object')
|
|
159
|
+
continue;
|
|
160
|
+
const b = block;
|
|
161
|
+
if (b['type'] !== 'tool_use')
|
|
162
|
+
continue;
|
|
163
|
+
const toolName = str(b['name']) ?? 'unknown';
|
|
164
|
+
events.push(wrap('tool.started', { tool_name: toolName, tool_call_id: str(b['id']) }));
|
|
165
|
+
// Bash invocations are the interesting ones for command analytics.
|
|
166
|
+
const input = (b['input'] ?? {});
|
|
167
|
+
const command = str(input['command']);
|
|
168
|
+
if (toolName === 'Bash' && command) {
|
|
169
|
+
events.push(wrap('command.executed', { command }));
|
|
170
|
+
}
|
|
171
|
+
const filePath = str(input['file_path']);
|
|
172
|
+
if (filePath && (toolName === 'Edit' || toolName === 'Write' || toolName === 'NotebookEdit')) {
|
|
173
|
+
events.push(wrap('file.changed', {
|
|
174
|
+
path: filePath,
|
|
175
|
+
change_kind: toolName === 'Write' ? 'create' : 'edit',
|
|
176
|
+
...countEditLines(toolName, input),
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
if (filePath && toolName === 'Read') {
|
|
180
|
+
events.push(wrap('file.read', { path: filePath }));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return events;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Maps Claude Code's usage block onto the normalized shape.
|
|
189
|
+
*
|
|
190
|
+
* thinking_tokens sits under output_tokens_details and is a SUBSET of
|
|
191
|
+
* output_tokens — it must not be added on top, or thinking gets billed twice.
|
|
192
|
+
*/
|
|
193
|
+
/** Reads the `version` field off the first line that carries one. */
|
|
194
|
+
function readVersionFromTranscript(file) {
|
|
195
|
+
try {
|
|
196
|
+
for (const line of readFileSync(file, 'utf8').split('\n', 40)) {
|
|
197
|
+
const parsed = safeJsonParse(line);
|
|
198
|
+
const version = parsed ? str(parsed['version']) : undefined;
|
|
199
|
+
if (version)
|
|
200
|
+
return version;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Unreadable transcript: absent version is better than a fabricated one.
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
export function readUsage(raw) {
|
|
209
|
+
const usage = (raw ?? {});
|
|
210
|
+
const details = (usage['output_tokens_details'] ?? {});
|
|
211
|
+
return {
|
|
212
|
+
...emptyUsage(),
|
|
213
|
+
input_tokens: num(usage['input_tokens']),
|
|
214
|
+
cached_input_tokens: num(usage['cache_read_input_tokens']),
|
|
215
|
+
cache_creation_input_tokens: num(usage['cache_creation_input_tokens']),
|
|
216
|
+
output_tokens: num(usage['output_tokens']),
|
|
217
|
+
reasoning_output_tokens: num(details['thinking_tokens']),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Line deltas for one edit, derived from the tool input.
|
|
222
|
+
*
|
|
223
|
+
* It has to happen here: the privacy pipeline drops old_string/new_string
|
|
224
|
+
* before anything is uploaded, so no later stage can recover the counts. Lines
|
|
225
|
+
* present on both sides are not counted, which matches what `git diff
|
|
226
|
+
* --numstat` reports for the same change.
|
|
227
|
+
*/
|
|
228
|
+
export function countEditLines(toolName, input) {
|
|
229
|
+
if (toolName === 'Write')
|
|
230
|
+
return { lines_added: splitLines(str(input['content'])).length, lines_removed: 0 };
|
|
231
|
+
if (toolName === 'NotebookEdit')
|
|
232
|
+
return diffLines(str(input['old_source']), str(input['new_source']));
|
|
233
|
+
return diffLines(str(input['old_string']), str(input['new_string']));
|
|
234
|
+
}
|
|
235
|
+
function splitLines(text) {
|
|
236
|
+
if (!text)
|
|
237
|
+
return [];
|
|
238
|
+
// A trailing newline terminates the last line, it does not start a new one.
|
|
239
|
+
return text.replace(/\n$/, '').split('\n');
|
|
240
|
+
}
|
|
241
|
+
/** Multiset difference — a line that survives the edit is neither added nor removed. */
|
|
242
|
+
function diffLines(before, after) {
|
|
243
|
+
const remaining = new Map();
|
|
244
|
+
for (const line of splitLines(before))
|
|
245
|
+
remaining.set(line, (remaining.get(line) ?? 0) + 1);
|
|
246
|
+
let added = 0;
|
|
247
|
+
for (const line of splitLines(after)) {
|
|
248
|
+
const left = remaining.get(line) ?? 0;
|
|
249
|
+
if (left > 0)
|
|
250
|
+
remaining.set(line, left - 1);
|
|
251
|
+
else
|
|
252
|
+
added += 1;
|
|
253
|
+
}
|
|
254
|
+
let removed = 0;
|
|
255
|
+
for (const count of remaining.values())
|
|
256
|
+
removed += count;
|
|
257
|
+
return { lines_added: added, lines_removed: removed };
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/adapters/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AgentAdapter, DetectionResult, HealthStatus, NormalizeContext, NormalizedEvent } from './types.js';
|
|
2
|
+
import { type TokenUsage } from '../schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* OpenAI Codex CLI adapter.
|
|
5
|
+
*
|
|
6
|
+
* Source: ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl
|
|
7
|
+
*
|
|
8
|
+
* Verified line shapes (Codex 0.120.x):
|
|
9
|
+
* {"type":"session_meta","payload":{"id":"…","cwd":"…","cli_version":"0.120.0",
|
|
10
|
+
* "model_provider":"openai"}}
|
|
11
|
+
* {"type":"turn_context","payload":{"turn_id":"…","model":"gpt-5.4","effort":"high"}}
|
|
12
|
+
* {"type":"event_msg","payload":{"type":"token_count","info":{
|
|
13
|
+
* "total_token_usage":{…},"last_token_usage":{…}},"rate_limits":{"plan_type":"plus"}}}
|
|
14
|
+
* {"type":"response_item","payload":{"type":"function_call","name":"shell",…}}
|
|
15
|
+
*
|
|
16
|
+
* The important subtlety: token_count reports RUNNING TOTALS. These are emitted
|
|
17
|
+
* as cumulative snapshots so the server treats them as a gauge, not a counter —
|
|
18
|
+
* summing them would multiply a session's tokens by the number of snapshots.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CODEX_DIR: string;
|
|
21
|
+
export declare class CodexAdapter implements AgentAdapter {
|
|
22
|
+
readonly id = "codex";
|
|
23
|
+
/** Session id and model carry across lines within one rollout file. */
|
|
24
|
+
private readonly fileState;
|
|
25
|
+
detect(): Promise<DetectionResult>;
|
|
26
|
+
health(): Promise<HealthStatus>;
|
|
27
|
+
normalize(line: string, ctx: NormalizeContext): NormalizedEvent[];
|
|
28
|
+
private normalizeEventMsg;
|
|
29
|
+
private normalizeResponseItem;
|
|
30
|
+
private wrap;
|
|
31
|
+
}
|
|
32
|
+
export declare function readCodexUsage(raw: unknown): TokenUsage;
|
|
33
|
+
interface FileTouch {
|
|
34
|
+
event_type: 'file.changed' | 'file.read';
|
|
35
|
+
path: string;
|
|
36
|
+
change_kind?: 'create' | 'edit' | 'delete' | 'rename';
|
|
37
|
+
lines_added?: number;
|
|
38
|
+
lines_removed?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* File activity recovered from a tool call.
|
|
42
|
+
*
|
|
43
|
+
* Codex has no dedicated file event: everything is apply_patch or a shell
|
|
44
|
+
* command, so the patch body is the only place exact line counts exist.
|
|
45
|
+
*/
|
|
46
|
+
export declare function fileTouches(payload: Record<string, unknown>): FileTouch[];
|
|
47
|
+
export interface PatchFile {
|
|
48
|
+
path: string;
|
|
49
|
+
change_kind: 'create' | 'edit' | 'delete' | 'rename';
|
|
50
|
+
lines_added: number;
|
|
51
|
+
lines_removed: number;
|
|
52
|
+
}
|
|
53
|
+
/** Parses the apply_patch envelope Codex writes files with. */
|
|
54
|
+
export declare function parseApplyPatch(patch: string): PatchFile[];
|
|
55
|
+
/**
|
|
56
|
+
* Files a shell command touched.
|
|
57
|
+
*
|
|
58
|
+
* ponytail: deliberately a narrow heuristic — a redirect target is a write, a
|
|
59
|
+
* pager/cat argument is a read, and nothing else is guessed at. Widening it
|
|
60
|
+
* would manufacture file events out of ordinary shell noise. Upgrade path is
|
|
61
|
+
* a real shell parser if the numbers ever need to be exact.
|
|
62
|
+
*/
|
|
63
|
+
export declare function shellFileTouches(command: string): FileTouch[];
|
|
64
|
+
export {};
|