codiedev 0.9.53 → 0.9.55

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.
@@ -0,0 +1,19 @@
1
+ export declare const CAPTURE_SIGNALS_VERSION = 1;
2
+ export type CaptureSignals = {
3
+ version: 1;
4
+ parsedRecords: number;
5
+ timestampCount: number;
6
+ firstAt?: number;
7
+ lastAt?: number;
8
+ activeMs: number;
9
+ idleThresholdMs: number;
10
+ skillInvocations: string[];
11
+ skillPulls: string[];
12
+ toolCalls: Array<{
13
+ name: string;
14
+ calls: number;
15
+ }>;
16
+ truncated: boolean;
17
+ };
18
+ /** Compact diagnostic inputs only: never persist prompts, commands or tool output. */
19
+ export declare function parseCaptureSignals(content: string, source?: string): CaptureSignals;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CAPTURE_SIGNALS_VERSION = void 0;
4
+ exports.parseCaptureSignals = parseCaptureSignals;
5
+ const transcriptModels_1 = require("./transcriptModels");
6
+ exports.CAPTURE_SIGNALS_VERSION = 1;
7
+ const IDLE_MS = 5 * 60_000;
8
+ const LIMIT = 100;
9
+ const skillKey = (value) => typeof value === "string"
10
+ ? value
11
+ .replace(/^\//, "")
12
+ .replace(/\.md$/i, "")
13
+ .replace(/["'`]/g, "")
14
+ .trim()
15
+ .toLowerCase()
16
+ : "";
17
+ /** Compact diagnostic inputs only: never persist prompts, commands or tool output. */
18
+ function parseCaptureSignals(content, source) {
19
+ const timestamps = new Set();
20
+ const invocations = new Set(), pulls = new Set();
21
+ const tools = new Map(), calls = new Set();
22
+ let parsedRecords = 0, truncated = false;
23
+ const addSkill = (set, value) => {
24
+ const key = skillKey(value);
25
+ if (!key || key.length > 200 || !/^[a-z0-9][a-z0-9_:/.-]*$/i.test(key))
26
+ return;
27
+ if (set.size >= LIMIT && !set.has(key)) {
28
+ truncated = true;
29
+ return;
30
+ }
31
+ set.add(key);
32
+ };
33
+ const command = (value) => {
34
+ if (typeof value !== "string")
35
+ return;
36
+ for (const match of value.matchAll(/\bcodiedev\s+pull\s+([^\s"'`]+)/gi))
37
+ addSkill(pulls, match[1]);
38
+ };
39
+ const tool = (name, id, input) => {
40
+ if (typeof name !== "string" || !name || name.length > 100)
41
+ return;
42
+ const key = typeof id === "string" ? id : `${parsedRecords}:${calls.size}`;
43
+ if (calls.has(key))
44
+ return;
45
+ calls.add(key);
46
+ if (tools.size < LIMIT || tools.has(name))
47
+ tools.set(name, (tools.get(name) ?? 0) + 1);
48
+ else
49
+ truncated = true;
50
+ let args = input;
51
+ if (typeof args === "string") {
52
+ try {
53
+ args = JSON.parse(args);
54
+ }
55
+ catch {
56
+ command(args);
57
+ return;
58
+ }
59
+ }
60
+ if (name.toLowerCase() === "skill")
61
+ addSkill(invocations, args?.skill ?? args?.name);
62
+ command(args?.command ?? args?.cmd);
63
+ };
64
+ for (const record of (0, transcriptModels_1.transcriptRecords)(content, source)) {
65
+ if (!record || typeof record !== "object")
66
+ continue;
67
+ const e = record;
68
+ parsedRecords++;
69
+ const time = typeof e.timestamp === "string"
70
+ ? Date.parse(e.timestamp)
71
+ : e.info?.time?.created;
72
+ if (typeof time === "number" && Number.isFinite(time) && time > 0)
73
+ timestamps.add(time);
74
+ const blocks = e.message?.content;
75
+ if (Array.isArray(blocks))
76
+ for (const part of blocks) {
77
+ if (part?.type === "tool_use")
78
+ tool(part.name, part.id, part.input);
79
+ if (part?.type === "text" && e.type === "user") {
80
+ for (const match of String(part.text ?? "").matchAll(/<command-name>\/?([a-z0-9:_-]+)<\/command-name>/gi))
81
+ addSkill(invocations, match[1]);
82
+ }
83
+ }
84
+ if (typeof blocks === "string" && e.type === "user")
85
+ for (const match of blocks.matchAll(/<command-name>\/?([a-z0-9:_-]+)<\/command-name>/gi))
86
+ addSkill(invocations, match[1]);
87
+ if (e.type === "response_item" &&
88
+ ["function_call", "custom_tool_call"].includes(e.payload?.type))
89
+ tool(e.payload.name, e.payload.call_id, e.payload.arguments ?? e.payload.input);
90
+ if (e.info?.role === "assistant" && Array.isArray(e.parts))
91
+ for (const part of e.parts) {
92
+ if (part?.type === "tool")
93
+ tool(part.tool, part.callID ?? part.id, part.state?.input);
94
+ }
95
+ }
96
+ const sorted = [...timestamps].sort((a, b) => a - b);
97
+ let activeMs = 0;
98
+ for (let i = 1; i < sorted.length; i++) {
99
+ const gap = sorted[i] - sorted[i - 1];
100
+ if (gap <= IDLE_MS)
101
+ activeMs += gap;
102
+ }
103
+ return {
104
+ version: exports.CAPTURE_SIGNALS_VERSION,
105
+ parsedRecords,
106
+ timestampCount: sorted.length,
107
+ ...(sorted.length
108
+ ? { firstAt: sorted[0], lastAt: sorted[sorted.length - 1] }
109
+ : {}),
110
+ activeMs,
111
+ idleThresholdMs: IDLE_MS,
112
+ skillInvocations: [...invocations].sort(),
113
+ skillPulls: [...pulls].sort(),
114
+ toolCalls: [...tools]
115
+ .map(([name, calls]) => ({ name, calls }))
116
+ .sort((a, b) => a.name.localeCompare(b.name)),
117
+ truncated,
118
+ };
119
+ }
package/dist/hook.js CHANGED
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.buildDiscoveryContext = buildDiscoveryContext;
38
+ const captureSignals_1 = require("./captureSignals");
39
+ const transcriptModels_1 = require("./transcriptModels");
38
40
  const fs = __importStar(require("fs"));
39
41
  const os = __importStar(require("os"));
40
42
  const https = __importStar(require("https"));
@@ -764,6 +766,8 @@ async function runCaptureFinishInner(input, config, session_id, transcript_path,
764
766
  cacheReadTokens: usage.cacheReadTokens,
765
767
  cacheCreationTokens: usage.cacheCreationTokens,
766
768
  model: usage.model,
769
+ captureSignals: (0, captureSignals_1.parseCaptureSignals)(mode === "desktop" || mode === "opencode" ? rawTranscriptContent : transcriptContent, mode),
770
+ modelUsage: (0, transcriptModels_1.parseTranscriptModels)(mode === "desktop" || mode === "opencode" ? rawTranscriptContent : transcriptContent, mode),
767
771
  sessionStartedAt,
768
772
  interruptCount: steering.interrupts,
769
773
  rejectionCount: steering.rejections,
@@ -0,0 +1,18 @@
1
+ /** Compact per-model usage, derived from captured transcript records. */
2
+ export type TranscriptModelUsage = {
3
+ model: string;
4
+ /** Assistant message IDs; nonzero cumulative usage events for Codex. */
5
+ requests: number;
6
+ inputTokens: number;
7
+ outputTokens: number;
8
+ cacheReadTokens: number;
9
+ cacheCreationTokens: number;
10
+ costUsd?: number;
11
+ /** Reported provider figure, retained separately from trusted billing inputs. */
12
+ reportedCostUsd?: number;
13
+ /** Reported reasoning is included in outputTokens, never added again. */
14
+ reasoningTokens?: number;
15
+ };
16
+ export declare function transcriptRecords(content: string, source?: string): Generator<unknown>;
17
+ export declare function parseTranscriptModels(content: string, source?: string): TranscriptModelUsage[];
18
+ export declare function totalModelTokens(row: Omit<TranscriptModelUsage, "model" | "requests">): number;
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transcriptRecords = transcriptRecords;
4
+ exports.parseTranscriptModels = parseTranscriptModels;
5
+ exports.totalModelTokens = totalModelTokens;
6
+ const positive = (n) => typeof n === "number" && Number.isFinite(n) ? Math.max(0, n) : 0;
7
+ const empty = (model) => ({
8
+ model,
9
+ requests: 0,
10
+ inputTokens: 0,
11
+ outputTokens: 0,
12
+ cacheReadTokens: 0,
13
+ cacheCreationTokens: 0,
14
+ });
15
+ function* transcriptRecords(content, source) {
16
+ if (content.trimStart().startsWith("[") ||
17
+ ((source === "opencode" || source === "desktop") &&
18
+ content.trimStart().startsWith("{"))) {
19
+ try {
20
+ const parsed = JSON.parse(content);
21
+ const list = Array.isArray(parsed) ? parsed : parsed?.data;
22
+ if (Array.isArray(list)) {
23
+ yield* list;
24
+ return;
25
+ }
26
+ }
27
+ catch {
28
+ /* Fall through to JSONL for partial captures. */
29
+ }
30
+ }
31
+ let start = 0;
32
+ while (start < content.length) {
33
+ const newline = content.indexOf("\n", start);
34
+ const end = newline < 0 ? content.length : newline;
35
+ try {
36
+ yield JSON.parse(content.slice(start, end));
37
+ }
38
+ catch {
39
+ /* partial or non-JSON line */
40
+ }
41
+ start = end + 1;
42
+ }
43
+ }
44
+ function parseTranscriptModels(content, source) {
45
+ const buckets = new Map();
46
+ const messages = new Map();
47
+ let model = "Unattributed";
48
+ let previous = {
49
+ inputTokens: 0,
50
+ outputTokens: 0,
51
+ cacheReadTokens: 0,
52
+ cacheCreationTokens: 0,
53
+ };
54
+ let previousReasoning = 0;
55
+ let index = 0;
56
+ for (const record of transcriptRecords(content, source)) {
57
+ const event = record;
58
+ index++;
59
+ if (!event || typeof event !== "object")
60
+ continue;
61
+ let usage = null;
62
+ let messageId = String(index);
63
+ if (source === "codex") {
64
+ if (event.type === "turn_context" &&
65
+ typeof event.payload?.model === "string")
66
+ model = event.payload.model;
67
+ if (event.type !== "event_msg" || event.payload?.type !== "token_count")
68
+ continue;
69
+ const raw = event.payload?.info?.total_token_usage;
70
+ if (!raw)
71
+ continue;
72
+ const total = {
73
+ inputTokens: Math.max(0, positive(raw.input_tokens) - positive(raw.cached_input_tokens)),
74
+ outputTokens: positive(raw.output_tokens),
75
+ cacheReadTokens: positive(raw.cached_input_tokens),
76
+ cacheCreationTokens: 0,
77
+ };
78
+ // Codex reasoning_output_tokens is a subset of output_tokens. Cumulative
79
+ // snapshots must be differenced, including across a model switch.
80
+ usage = { ...empty(model), requests: 1 };
81
+ const reset = positive(raw.input_tokens) <
82
+ previous.inputTokens + previous.cacheReadTokens ||
83
+ total.outputTokens < previous.outputTokens;
84
+ for (const key of [
85
+ "inputTokens",
86
+ "outputTokens",
87
+ "cacheReadTokens",
88
+ "cacheCreationTokens",
89
+ ])
90
+ usage[key] = reset
91
+ ? total[key]
92
+ : Math.max(0, total[key] - previous[key]);
93
+ const reasoning = typeof raw.reasoning_output_tokens === "number" &&
94
+ raw.reasoning_output_tokens >= 0 &&
95
+ raw.reasoning_output_tokens <= total.outputTokens
96
+ ? raw.reasoning_output_tokens
97
+ : undefined;
98
+ if (reasoning !== undefined &&
99
+ (reset || previousReasoning !== undefined)) {
100
+ const delta = reset
101
+ ? reasoning
102
+ : Math.max(0, reasoning - previousReasoning);
103
+ if (delta <= usage.outputTokens)
104
+ usage.reasoningTokens = delta;
105
+ }
106
+ previousReasoning = reasoning;
107
+ previous = total;
108
+ if (!totalModelTokens(usage))
109
+ continue;
110
+ }
111
+ else if (event.info?.role === "assistant") {
112
+ const info = event.info, tokens = info.tokens;
113
+ if (!tokens)
114
+ continue;
115
+ const id = typeof info.modelID === "string" ? info.modelID : "Unattributed";
116
+ model =
117
+ info.providerID && id !== "Unattributed"
118
+ ? `${info.providerID}/${id}`
119
+ : id;
120
+ usage = {
121
+ ...empty(model),
122
+ requests: 1,
123
+ inputTokens: positive(tokens.input),
124
+ outputTokens: positive(tokens.output) + positive(tokens.reasoning),
125
+ ...(typeof tokens.reasoning === "number"
126
+ ? { reasoningTokens: positive(tokens.reasoning) }
127
+ : {}),
128
+ cacheReadTokens: positive(tokens.cache?.read),
129
+ cacheCreationTokens: positive(tokens.cache?.write),
130
+ ...(typeof info.cost === "number"
131
+ ? {
132
+ reportedCostUsd: positive(info.cost),
133
+ ...(source === "desktop" ? { costUsd: positive(info.cost) } : {}),
134
+ }
135
+ : {}),
136
+ };
137
+ messageId = info.id ?? messageId;
138
+ }
139
+ else {
140
+ const message = event.message;
141
+ if (!message?.usage)
142
+ continue;
143
+ model =
144
+ typeof message.model === "string" ? message.model : "Unattributed";
145
+ const raw = message.usage;
146
+ usage = {
147
+ ...empty(model),
148
+ requests: 1,
149
+ inputTokens: positive(raw.input_tokens),
150
+ outputTokens: positive(raw.output_tokens),
151
+ cacheReadTokens: positive(raw.cache_read_input_tokens),
152
+ cacheCreationTokens: positive(raw.cache_creation_input_tokens),
153
+ };
154
+ messageId = message.id ?? event.uuid ?? messageId;
155
+ }
156
+ // Client placeholders are not model executions.
157
+ if (!usage.model.trim() || usage.model.startsWith("<"))
158
+ continue;
159
+ // Streaming snapshots repeat message ids; retain the latest snapshot once.
160
+ messages.set(messageId, usage);
161
+ }
162
+ const incompleteCosts = new Set();
163
+ const incompleteReasoning = new Set();
164
+ const incompleteReportedCosts = new Set();
165
+ for (const usage of messages.values()) {
166
+ const key = buckets.has(usage.model) || buckets.size < 63
167
+ ? usage.model
168
+ : "Unattributed";
169
+ const bucket = buckets.get(key) ?? empty(key);
170
+ for (const field of [
171
+ "requests",
172
+ "inputTokens",
173
+ "outputTokens",
174
+ "cacheReadTokens",
175
+ "cacheCreationTokens",
176
+ ])
177
+ bucket[field] += usage[field];
178
+ if (usage.reasoningTokens !== undefined)
179
+ bucket.reasoningTokens =
180
+ (bucket.reasoningTokens ?? 0) + usage.reasoningTokens;
181
+ else
182
+ incompleteReasoning.add(key);
183
+ if (usage.reportedCostUsd !== undefined)
184
+ bucket.reportedCostUsd =
185
+ (bucket.reportedCostUsd ?? 0) + usage.reportedCostUsd;
186
+ else
187
+ incompleteReportedCosts.add(key);
188
+ if (usage.costUsd !== undefined)
189
+ bucket.costUsd = (bucket.costUsd ?? 0) + usage.costUsd;
190
+ else
191
+ incompleteCosts.add(key);
192
+ buckets.set(key, bucket);
193
+ }
194
+ for (const key of incompleteReportedCosts)
195
+ delete buckets.get(key).reportedCostUsd;
196
+ for (const key of incompleteCosts)
197
+ delete buckets.get(key).costUsd;
198
+ for (const key of incompleteReasoning)
199
+ delete buckets.get(key).reasoningTokens;
200
+ return [...buckets.values()];
201
+ }
202
+ function totalModelTokens(row) {
203
+ return (row.inputTokens +
204
+ row.outputTokens +
205
+ row.cacheReadTokens +
206
+ row.cacheCreationTokens);
207
+ }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import * as fs from "fs";
2
+ import type { CaptureSignals } from "./captureSignals";
3
+ import type { TranscriptModelUsage } from "./transcriptModels";
2
4
  import { spawn as nodeSpawn } from "child_process";
3
5
  import type { CaptureLogEntry } from "./captureLog";
4
6
  import { codiedevStateDir } from "./configDir";
@@ -58,6 +60,8 @@ export declare function parseAuthoredShas(transcriptContent: string, repoRoot: s
58
60
  export declare function getGitBranch(cwd: string): string | null;
59
61
  export declare function getGitHeadSha(cwd: string): string | null;
60
62
  export interface CapturePayloadInput {
63
+ modelUsage?: TranscriptModelUsage[];
64
+ captureSignals?: CaptureSignals;
61
65
  sessionId?: string;
62
66
  transcriptBase64: string;
63
67
  remoteUrl: string | null;
package/dist/utils.js CHANGED
@@ -853,6 +853,8 @@ function buildCapturePayload(input) {
853
853
  cacheReadTokens: input.cacheReadTokens,
854
854
  cacheCreationTokens: input.cacheCreationTokens,
855
855
  model: input.model ?? undefined,
856
+ modelUsage: input.modelUsage?.length ? input.modelUsage : undefined,
857
+ captureSignals: input.captureSignals,
856
858
  sessionStartedAt: input.sessionStartedAt ?? undefined,
857
859
  interruptCount: input.interruptCount,
858
860
  rejectionCount: input.rejectionCount,
@@ -2047,7 +2049,8 @@ function parseCodexUsage(content) {
2047
2049
  const input = last.input_tokens ?? 0;
2048
2050
  return {
2049
2051
  inputTokens: Math.max(0, input - cached),
2050
- outputTokens: (last.output_tokens ?? 0) + (last.reasoning_output_tokens ?? 0),
2052
+ // Reasoning is included in output_tokens, not additional output.
2053
+ outputTokens: last.output_tokens ?? 0,
2051
2054
  // Codex only reports cached reads; creation is structurally zero.
2052
2055
  cacheTokens: cached,
2053
2056
  cacheReadTokens: cached,
@@ -2268,7 +2271,7 @@ function parseCodexStats(content) {
2268
2271
  if (payloadType === "message") {
2269
2272
  messageCount++;
2270
2273
  }
2271
- else if (payloadType === "function_call") {
2274
+ else if (payloadType === "function_call" || payloadType === "custom_tool_call") {
2272
2275
  toolCallCount++;
2273
2276
  }
2274
2277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.53",
3
+ "version": "0.9.55",
4
4
  "description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",