Zion Boggan
repos/TreeTrace/src/adapters/codex.js
zionboggan.com ↗
118 lines · javascript
History for this file →
1
import {
2
  newSession,
3
  finalizeSession,
4
  pushTurn,
5
  addAction,
6
  addThinking,
7
  flattenParts,
8
  looksSynthetic,
9
  noteAssistantRefusal,
10
  readJsonl,
11
} from './shared.js';
12
 
13
export function detectCodex(text) {
14
  for (const line of text.split(/\r?\n/)) {
15
    const trimmed = line.trim();
16
    if (!trimmed || trimmed.charCodeAt(0) !== 123) continue;
17
    try {
18
      const rec = JSON.parse(trimmed);
19
      if (rec.type === 'session_meta' && rec.payload && rec.payload.originator) return true;
20
      if (rec.type === 'response_item' || rec.type === 'turn_context') return true;
21
      return false;
22
    } catch {
23
      return false;
24
    }
25
  }
26
  return false;
27
}
28
 
29
export function parseCodex(text, path, sessionId) {
30
  const session = newSession(path, sessionId);
31
  const records = readJsonl(text);
32
  let turn = 0;
33
  let currentModel = null;
34
 
35
  for (const rec of records) {
36
    const ts = rec.timestamp || null;
37
    const payload = rec.payload || {};
38
 
39
    if (rec.type === 'session_meta') {
40
      if (payload.id && !session.sessionId) session.sessionId = payload.id;
41
      if (payload.cwd) session.cwd = payload.cwd;
42
      if (payload.cli_version) session.version = payload.cli_version;
43
      if (payload.git && payload.git.branch) session.gitBranch = payload.git.branch;
44
      continue;
45
    }
46
 
47
    if (rec.type === 'response_item' && payload.type === 'message') {
48
      if (payload.role === 'user') {
49
        const body = flattenParts(payload.content);
50
        if (looksSynthetic(body)) continue;
51
        pushTurn(session, ++turn, body, ts);
52
      } else if (payload.role === 'assistant') {
53
        session.stats.assistantLines++;
54
        noteAssistantRefusal(session, flattenParts(payload.content));
55
      }
56
      continue;
57
    }
58
 
59
    if (rec.type === 'response_item' && payload.type === 'function_call') {
60
      session.stats.toolUses++;
61
      const file = filePathFromArgs(payload.arguments);
62
      if (file) session.stats.filesTouched.add(file);
63
      addAction(session, {
64
        tool: payload.name || null,
65
        file: file || null,
66
        command: commandFromArgs(payload.name, payload.arguments),
67
        model: currentModel,
68
      });
69
      continue;
70
    }
71
 
72
    if (rec.type === 'response_item' && payload.type === 'reasoning') {
73
      addThinking(session);
74
      continue;
75
    }
76
 
77
    if (rec.type === 'event_msg' && payload.type === 'token_count') {
78
      const usage = payload.info && payload.info.total_token_usage;
79
      if (usage) {
80
        session.stats.inputTokens = usage.input_tokens || session.stats.inputTokens;
81
        session.stats.outputTokens = usage.output_tokens || session.stats.outputTokens;
82
      }
83
      continue;
84
    }
85
 
86
    if (rec.type === 'turn_context' && payload.model) {
87
      session.stats.models.add(payload.model);
88
      currentModel = payload.model;
89
    }
90
  }
91
 
92
  return finalizeSession(session);
93
}
94
 
95
function commandFromArgs(name, args) {
96
  if (!/exec|shell|bash|run|terminal|command/i.test(name || '')) return null;
97
  if (!args || typeof args !== 'string') return null;
98
  let parsed;
99
  try {
100
    parsed = JSON.parse(args);
101
  } catch {
102
    return null;
103
  }
104
  const cmd = parsed.command || parsed.cmd;
105
  if (Array.isArray(cmd)) return cmd.join(' ');
106
  return typeof cmd === 'string' ? cmd : null;
107
}
108
 
109
function filePathFromArgs(args) {
110
  if (!args || typeof args !== 'string') return null;
111
  let parsed;
112
  try {
113
    parsed = JSON.parse(args);
114
  } catch {
115
    return null;
116
  }
117
  return parsed.path || parsed.file_path || parsed.filePath || null;
118
}