spiracha 2.0.0 → 2.2.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/AGENTS.md +27 -12
- package/README.md +42 -19
- package/apps/ui/AGENTS.md +18 -10
- package/apps/ui/README.md +36 -11
- package/apps/ui/src/components/analytics-breakdowns.tsx +106 -0
- package/apps/ui/src/components/antigravity-conversations-table.tsx +51 -11
- package/apps/ui/src/components/antigravity-workspaces-table.tsx +1 -1
- package/apps/ui/src/components/app-shell.tsx +46 -3
- package/apps/ui/src/components/breadcrumbs.tsx +21 -3
- package/apps/ui/src/components/claude-code-sessions-table.tsx +42 -6
- package/apps/ui/src/components/claude-code-workspaces-table.tsx +3 -1
- package/apps/ui/src/components/cursor-threads-table.tsx +10 -47
- package/apps/ui/src/components/data-table.tsx +23 -5
- package/apps/ui/src/components/delete-confirm-dialog.tsx +4 -3
- package/apps/ui/src/components/export-dialog.tsx +79 -74
- package/apps/ui/src/components/grok-sessions-table.tsx +146 -0
- package/apps/ui/src/components/grok-workspaces-table.tsx +53 -0
- package/apps/ui/src/components/kiro-sessions-table.tsx +40 -4
- package/apps/ui/src/components/opencode-sessions-table.tsx +40 -4
- package/apps/ui/src/components/page-header.tsx +15 -9
- package/apps/ui/src/components/projects-table.tsx +4 -1
- package/apps/ui/src/components/qoder-sessions-table.tsx +23 -2
- package/apps/ui/src/components/qoder-workspaces-table.tsx +5 -1
- package/apps/ui/src/components/route-error-panel.tsx +13 -0
- package/apps/ui/src/components/selection-actions-toolbar.tsx +80 -0
- package/apps/ui/src/components/thread-goals-panel.tsx +50 -0
- package/apps/ui/src/components/thread-tools-panel.tsx +142 -0
- package/apps/ui/src/components/threads-table.tsx +13 -47
- package/apps/ui/src/components/transcript-view.tsx +148 -52
- package/apps/ui/src/components/ui/select.tsx +1 -1
- package/apps/ui/src/lib/antigravity-conversation-state.ts +17 -4
- package/apps/ui/src/lib/antigravity-server.ts +209 -11
- package/apps/ui/src/lib/antigravity-transcript-events.ts +57 -73
- package/apps/ui/src/lib/claude-code-queries.ts +8 -0
- package/apps/ui/src/lib/claude-code-server.ts +185 -7
- package/apps/ui/src/lib/claude-code-transcript-events.ts +8 -1
- package/apps/ui/src/lib/codex-queries.ts +16 -0
- package/apps/ui/src/lib/codex-server.ts +134 -52
- package/apps/ui/src/lib/cursor-server.ts +68 -53
- package/apps/ui/src/lib/cursor-transcript-events.ts +11 -86
- package/apps/ui/src/lib/delete-batch.ts +52 -0
- package/apps/ui/src/lib/error-presentation.ts +43 -0
- package/apps/ui/src/lib/export-mutation.ts +20 -0
- package/apps/ui/src/lib/export-options.ts +15 -0
- package/apps/ui/src/lib/formatters.ts +3 -5
- package/apps/ui/src/lib/grok-queries.ts +22 -0
- package/apps/ui/src/lib/grok-server.ts +168 -0
- package/apps/ui/src/lib/grok-transcript-events.ts +149 -0
- package/apps/ui/src/lib/kiro-server.ts +97 -7
- package/apps/ui/src/lib/mutation-error.ts +15 -0
- package/apps/ui/src/lib/opencode-server.ts +96 -7
- package/apps/ui/src/lib/path-utils.ts +1 -1
- package/apps/ui/src/lib/qoder-queries.ts +15 -0
- package/apps/ui/src/lib/qoder-server.ts +67 -11
- package/apps/ui/src/lib/qoder-transcript-events.ts +1 -1
- package/apps/ui/src/lib/route-search.ts +114 -0
- package/apps/ui/src/lib/route-state-reset.tsx +10 -0
- package/apps/ui/src/lib/settings-server.ts +33 -0
- package/apps/ui/src/lib/settings-store.tsx +82 -38
- package/apps/ui/src/lib/settings.ts +65 -0
- package/apps/ui/src/lib/source-session-export-server.ts +86 -3
- package/apps/ui/src/lib/thread-metadata.ts +22 -0
- package/apps/ui/src/lib/thread-transcript-load.ts +15 -0
- package/apps/ui/src/lib/thread-transcript-stats.ts +10 -1
- package/apps/ui/src/lib/workspace-delete-navigation.ts +20 -0
- package/apps/ui/src/routeTree.gen.ts +341 -234
- package/apps/ui/src/routes/__root.tsx +12 -15
- package/apps/ui/src/routes/analytics.tsx +10 -61
- package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +149 -29
- package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +262 -42
- package/apps/ui/src/routes/antigravity.index.tsx +2 -2
- package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +4 -0
- package/apps/ui/src/routes/api.v1.conversations.delete.ts +12 -0
- package/apps/ui/src/routes/api.v1.conversations.export.ts +12 -0
- package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +168 -28
- package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +179 -45
- package/apps/ui/src/routes/claude-code.index.tsx +2 -2
- package/apps/ui/src/routes/codex.$project.tsx +33 -31
- package/apps/ui/src/routes/codex.index.tsx +5 -10
- package/apps/ui/src/routes/cursor-threads.$composerId.tsx +31 -18
- package/apps/ui/src/routes/cursor.$workspaceKey.tsx +44 -28
- package/apps/ui/src/routes/cursor.index.tsx +5 -2
- package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +401 -0
- package/apps/ui/src/routes/grok.$workspaceKey.tsx +295 -0
- package/apps/ui/src/routes/grok.index.tsx +48 -0
- package/apps/ui/src/routes/index.tsx +2 -18
- package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +91 -27
- package/apps/ui/src/routes/kiro.$workspaceKey.tsx +213 -51
- package/apps/ui/src/routes/kiro.index.tsx +2 -2
- package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +108 -29
- package/apps/ui/src/routes/opencode.$workspaceKey.tsx +231 -55
- package/apps/ui/src/routes/opencode.index.tsx +6 -2
- package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +19 -15
- package/apps/ui/src/routes/qoder.$workspaceKey.tsx +89 -48
- package/apps/ui/src/routes/qoder.index.tsx +2 -2
- package/apps/ui/src/routes/threads.$threadId.tsx +620 -105
- package/apps/ui/vite.config.ts +22 -12
- package/bin/spiracha.ts +28 -1
- package/package.json +37 -27
- package/src/client.ts +183 -5
- package/src/lib/antigravity-db.ts +424 -94
- package/src/lib/antigravity-exporter-types.ts +3 -0
- package/src/lib/antigravity-keychain.ts +7 -8
- package/src/lib/antigravity-projects.ts +201 -0
- package/src/lib/antigravity-transcript-phase.ts +47 -0
- package/src/lib/claude-code-db.ts +534 -42
- package/src/lib/claude-code-exporter-types.ts +5 -0
- package/src/lib/claude-code-transcript-phase.ts +60 -1
- package/src/lib/claude-code-transcript.ts +8 -5
- package/src/lib/codex-analytics.ts +32 -5
- package/src/lib/codex-browser-db.ts +540 -137
- package/src/lib/codex-browser-export.ts +18 -27
- package/src/lib/codex-browser-types.ts +21 -1
- package/src/lib/codex-thread-cache.ts +106 -23
- package/src/lib/codex-thread-parser.ts +149 -55
- package/src/lib/codex-thread-recovery.ts +38 -11
- package/src/lib/codex-transcript-filter.ts +31 -0
- package/src/lib/codex-transcript-renderer.ts +87 -79
- package/src/lib/concurrency.ts +56 -3
- package/src/lib/conversation-api.ts +410 -27
- package/src/lib/conversation-data/adapter-helpers.ts +25 -3
- package/src/lib/conversation-data/antigravity-adapter.ts +70 -26
- package/src/lib/conversation-data/claude-code-adapter.ts +69 -12
- package/src/lib/conversation-data/codex-adapter.ts +108 -73
- package/src/lib/conversation-data/cursor-adapter.ts +77 -29
- package/src/lib/conversation-data/grok-adapter.ts +223 -0
- package/src/lib/conversation-data/index.ts +105 -11
- package/src/lib/conversation-data/kiro-adapter.ts +59 -12
- package/src/lib/conversation-data/message-selector.ts +2 -5
- package/src/lib/conversation-data/opencode-adapter.ts +67 -17
- package/src/lib/conversation-data/path-match.ts +1 -13
- package/src/lib/conversation-data/qoder-adapter.ts +67 -37
- package/src/lib/conversation-data/types.ts +43 -0
- package/src/lib/conversation-zip-export.ts +75 -0
- package/src/lib/cursor-db.ts +68 -28
- package/src/lib/cursor-id.ts +17 -0
- package/src/lib/cursor-recovery.ts +146 -35
- package/src/lib/cursor-transcript-phase.ts +40 -0
- package/src/lib/cursor-transcript.ts +21 -5
- package/src/lib/grok-db.ts +1068 -0
- package/src/lib/grok-exporter-types.ts +110 -0
- package/src/lib/grok-transcript-phase.ts +61 -0
- package/src/lib/grok-transcript.ts +167 -0
- package/src/lib/kiro-db.ts +187 -43
- package/src/lib/kiro-transcript.ts +0 -4
- package/src/lib/model-label.ts +11 -3
- package/src/lib/opencode-db.ts +599 -60
- package/src/lib/opencode-think-tags.ts +17 -3
- package/src/lib/opencode-transcript.ts +8 -5
- package/src/lib/portable-path.ts +9 -0
- package/src/lib/qoder-acp-client.ts +11 -2
- package/src/lib/qoder-db.ts +51 -17
- package/src/lib/qoder-transcript-phase.ts +64 -0
- package/src/lib/qoder-transcript.ts +30 -2
- package/src/lib/shared.ts +63 -15
- package/src/lib/sqlite-error.ts +31 -5
- package/src/lib/transcript-load-limiter.ts +87 -0
- package/src/lib/ui-cache.ts +185 -24
- package/src/lib/ui-export-archive.ts +58 -39
- package/src/lib/ui-export-files.ts +75 -15
- package/src/lib/ui-export-zip.ts +40 -0
- package/apps/ui/package.json +0 -65
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import type { ClaudeCodeAssistantMessagePhase } from './claude-code-transcript-phase';
|
|
3
4
|
import type { ExportFormat, JsonValue } from './shared';
|
|
4
5
|
|
|
5
6
|
export type { ClaudeCodeAssistantMessagePhase } from './claude-code-transcript-phase';
|
|
@@ -84,6 +85,7 @@ export type ClaudeCodeTranscriptPart = {
|
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
export type ClaudeCodeTranscriptEntry = {
|
|
88
|
+
assistantPhase?: ClaudeCodeAssistantMessagePhase | null;
|
|
87
89
|
cwd: string | null;
|
|
88
90
|
entryId: string;
|
|
89
91
|
model?: string | null;
|
|
@@ -97,6 +99,9 @@ export type ClaudeCodeTranscriptEntry = {
|
|
|
97
99
|
|
|
98
100
|
export type ClaudeCodeSessionTranscript = {
|
|
99
101
|
entries: ClaudeCodeTranscriptEntry[];
|
|
102
|
+
isPartial?: boolean;
|
|
103
|
+
omittedEntryCount?: number;
|
|
104
|
+
rawPayloadsOmitted?: boolean;
|
|
100
105
|
rawEvents: Record<string, JsonValue>[];
|
|
101
106
|
renderablePartCount: number;
|
|
102
107
|
session: ClaudeCodeSessionSummary;
|
|
@@ -3,16 +3,39 @@ import type { JsonValue } from './shared';
|
|
|
3
3
|
export type ClaudeCodeAssistantMessagePhase = 'commentary' | 'final_answer';
|
|
4
4
|
|
|
5
5
|
type ClaudeCodeAssistantPhaseEntry = {
|
|
6
|
+
assistantPhase?: ClaudeCodeAssistantMessagePhase | null;
|
|
7
|
+
parts?: Array<{
|
|
8
|
+
text?: string;
|
|
9
|
+
type: string;
|
|
10
|
+
}>;
|
|
6
11
|
raw: Record<string, JsonValue>;
|
|
7
12
|
role: string;
|
|
8
13
|
};
|
|
9
14
|
|
|
15
|
+
type ClaudeCodeFilterEntry = ClaudeCodeAssistantPhaseEntry & {
|
|
16
|
+
parts: Array<{
|
|
17
|
+
text?: string;
|
|
18
|
+
type: string;
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const SYNTHETIC_USER_TEXT_PREFIXES = [
|
|
23
|
+
'<local-command-caveat>',
|
|
24
|
+
'<local-command-stdout>',
|
|
25
|
+
'<task-notification>',
|
|
26
|
+
'[Request interrupted by user]',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
10
29
|
const asMessageObject = (value: JsonValue | undefined): Record<string, JsonValue> | null => {
|
|
11
30
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
12
31
|
? (value as Record<string, JsonValue>)
|
|
13
32
|
: null;
|
|
14
33
|
};
|
|
15
34
|
|
|
35
|
+
export const isClaudeCodeRawFlagEnabled = (value: JsonValue | undefined): boolean => {
|
|
36
|
+
return value === true || value === 'true';
|
|
37
|
+
};
|
|
38
|
+
|
|
16
39
|
export const getClaudeCodeAssistantMessagePhase = (
|
|
17
40
|
entry: ClaudeCodeAssistantPhaseEntry,
|
|
18
41
|
): ClaudeCodeAssistantMessagePhase | null => {
|
|
@@ -20,6 +43,42 @@ export const getClaudeCodeAssistantMessagePhase = (
|
|
|
20
43
|
return null;
|
|
21
44
|
}
|
|
22
45
|
|
|
46
|
+
if (entry.assistantPhase) {
|
|
47
|
+
return entry.assistantPhase;
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
const stopReason = asMessageObject(entry.raw.message)?.stop_reason;
|
|
24
|
-
|
|
51
|
+
if (stopReason !== 'tool_use') {
|
|
52
|
+
return 'final_answer';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const toolUseIndex = entry.parts?.findIndex((part) => part.type === 'tool_use') ?? -1;
|
|
56
|
+
const hasTextAfterToolUse =
|
|
57
|
+
toolUseIndex >= 0 &&
|
|
58
|
+
entry.parts?.slice(toolUseIndex + 1).some((part) => part.type === 'text' && part.text?.trim()) === true;
|
|
59
|
+
return hasTextAfterToolUse ? 'final_answer' : 'commentary';
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const isClaudeCodeSyntheticTranscriptEntry = (entry: ClaudeCodeFilterEntry): boolean => {
|
|
63
|
+
if (
|
|
64
|
+
isClaudeCodeRawFlagEnabled(entry.raw.isApiErrorMessage) ||
|
|
65
|
+
isClaudeCodeRawFlagEnabled(entry.raw.isMeta) ||
|
|
66
|
+
isClaudeCodeRawFlagEnabled(entry.raw.isCompactSummary)
|
|
67
|
+
) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (asMessageObject(entry.raw.message)?.model === '<synthetic>') {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (entry.role !== 'user') {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const text = entry.parts.find((part) => part.type === 'text')?.text?.trim() ?? '';
|
|
80
|
+
return (
|
|
81
|
+
text.startsWith('<command-name>/compact</command-name>') ||
|
|
82
|
+
SYNTHETIC_USER_TEXT_PREFIXES.some((prefix) => text.startsWith(prefix))
|
|
83
|
+
);
|
|
25
84
|
};
|
|
@@ -5,7 +5,10 @@ import type {
|
|
|
5
5
|
ClaudeCodeTranscriptEntry,
|
|
6
6
|
ClaudeCodeTranscriptPart,
|
|
7
7
|
} from './claude-code-exporter-types';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getClaudeCodeAssistantMessagePhase,
|
|
10
|
+
isClaudeCodeSyntheticTranscriptEntry,
|
|
11
|
+
} from './claude-code-transcript-phase';
|
|
9
12
|
import {
|
|
10
13
|
cleanExtractedText,
|
|
11
14
|
cleanInlineTitle,
|
|
@@ -102,7 +105,7 @@ const renderToolResultPart = (part: ClaudeCodeTranscriptPart, options: ClaudeCod
|
|
|
102
105
|
return '';
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
const outputText = part.outputText
|
|
108
|
+
const outputText = (part.outputText ?? '').trim();
|
|
106
109
|
if (!outputText) {
|
|
107
110
|
return '';
|
|
108
111
|
}
|
|
@@ -160,9 +163,9 @@ export const renderClaudeCodeTranscript = (
|
|
|
160
163
|
transcript: ClaudeCodeSessionTranscript,
|
|
161
164
|
options: ClaudeCodeExportOptions,
|
|
162
165
|
): string | null => {
|
|
163
|
-
const sections = transcript.entries
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
const sections = transcript.entries
|
|
167
|
+
.filter((entry) => !isClaudeCodeSyntheticTranscriptEntry(entry))
|
|
168
|
+
.flatMap((entry) => entry.parts.map((part) => renderPart(entry, part, options)).filter(Boolean));
|
|
166
169
|
if (sections.length === 0) {
|
|
167
170
|
return null;
|
|
168
171
|
}
|
|
@@ -2,8 +2,9 @@ import { listScopedThreads } from './codex-browser-db';
|
|
|
2
2
|
import type { CodexAnalytics, DistributionItem, ModelTokenSummary } from './codex-browser-types';
|
|
3
3
|
import type { ThreadRow } from './codex-thread-types';
|
|
4
4
|
import { mapWithConcurrency } from './concurrency';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { getPortablePathBasename } from './portable-path';
|
|
6
|
+
import { asObject, asString, readJsonlObjects } from './shared';
|
|
7
|
+
import { hashCacheKeyPartsIterable, withCachedJson } from './ui-cache';
|
|
7
8
|
|
|
8
9
|
export type CodexAnalyticsInput = {
|
|
9
10
|
dbPath: string;
|
|
@@ -38,6 +39,20 @@ const roundToTwoDecimals = (value: number) => {
|
|
|
38
39
|
return Number(value.toFixed(2));
|
|
39
40
|
};
|
|
40
41
|
|
|
42
|
+
const median = (values: number[]) => {
|
|
43
|
+
if (values.length === 0) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
48
|
+
const midpoint = Math.floor(sorted.length / 2);
|
|
49
|
+
if (sorted.length % 2 === 1) {
|
|
50
|
+
return sorted[midpoint]!;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return roundToTwoDecimals((sorted[midpoint - 1]! + sorted[midpoint]!) / 2);
|
|
54
|
+
};
|
|
55
|
+
|
|
41
56
|
const incrementCount = (counts: Map<string, number>, key: string) => {
|
|
42
57
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
43
58
|
};
|
|
@@ -90,6 +105,8 @@ const threadMetadataCacheKeyParts = (thread: ThreadRow) => [
|
|
|
90
105
|
String(thread.archived_at ?? ''),
|
|
91
106
|
thread.cwd,
|
|
92
107
|
thread.model ?? '',
|
|
108
|
+
thread.reasoning_effort ?? '',
|
|
109
|
+
thread.source,
|
|
93
110
|
thread.model_provider,
|
|
94
111
|
thread.cli_version,
|
|
95
112
|
thread.title,
|
|
@@ -98,7 +115,7 @@ const threadMetadataCacheKeyParts = (thread: ThreadRow) => [
|
|
|
98
115
|
|
|
99
116
|
export const buildCodexAnalyticsCacheKey = (dbPath: string, threads: ThreadRow[], project: string | null) => {
|
|
100
117
|
const parts = (function* () {
|
|
101
|
-
yield '
|
|
118
|
+
yield 'v3';
|
|
102
119
|
yield dbPath;
|
|
103
120
|
yield project ?? 'all';
|
|
104
121
|
yield String(threads.length);
|
|
@@ -111,7 +128,7 @@ export const buildCodexAnalyticsCacheKey = (dbPath: string, threads: ThreadRow[]
|
|
|
111
128
|
};
|
|
112
129
|
|
|
113
130
|
const buildThreadAnalyticsCacheKey = (thread: ThreadRow) => {
|
|
114
|
-
return `thread-analytics-${
|
|
131
|
+
return `thread-analytics-${hashCacheKeyPartsIterable(['v1', ...threadMetadataCacheKeyParts(thread)])}`;
|
|
115
132
|
};
|
|
116
133
|
|
|
117
134
|
const parseThreadAnalyticsFile = async (sessionFile: string): Promise<ThreadAnalyticsSummary> => {
|
|
@@ -129,7 +146,7 @@ const parseThreadAnalyticsFile = async (sessionFile: string): Promise<ThreadAnal
|
|
|
129
146
|
}
|
|
130
147
|
|
|
131
148
|
const payloadType = asString(payload.type);
|
|
132
|
-
if (payloadType === 'function_call') {
|
|
149
|
+
if (payloadType === 'function_call' || payloadType === 'custom_tool_call') {
|
|
133
150
|
toolNames.push(asString(payload.name) ?? 'unknown');
|
|
134
151
|
continue;
|
|
135
152
|
}
|
|
@@ -155,6 +172,8 @@ export const computeCodexAnalyticsFromThreads = async (
|
|
|
155
172
|
): Promise<CodexAnalytics> => {
|
|
156
173
|
const totalTokens = threads.reduce((sum, thread) => sum + thread.tokens_used, 0);
|
|
157
174
|
const projectNames = new Set(threads.map((thread) => getPortablePathBasename(thread.cwd)).filter(Boolean));
|
|
175
|
+
const reasoningEfforts = new Map<string, number>();
|
|
176
|
+
const sources = new Map<string, number>();
|
|
158
177
|
const toolUsage = new Map<string, number>();
|
|
159
178
|
let threadsWithWebSearch = 0;
|
|
160
179
|
const loadThreadAnalytics = options.loadThreadAnalytics ?? getCachedThreadAnalytics;
|
|
@@ -163,6 +182,11 @@ export const computeCodexAnalyticsFromThreads = async (
|
|
|
163
182
|
loadThreadAnalytics(thread),
|
|
164
183
|
);
|
|
165
184
|
|
|
185
|
+
for (const thread of threads) {
|
|
186
|
+
incrementCount(reasoningEfforts, thread.reasoning_effort?.trim() || 'unspecified');
|
|
187
|
+
incrementCount(sources, thread.source.trim() || 'unknown');
|
|
188
|
+
}
|
|
189
|
+
|
|
166
190
|
for (const analytics of threadAnalytics) {
|
|
167
191
|
if (analytics.hasWebSearch) {
|
|
168
192
|
threadsWithWebSearch += 1;
|
|
@@ -175,10 +199,13 @@ export const computeCodexAnalyticsFromThreads = async (
|
|
|
175
199
|
|
|
176
200
|
return {
|
|
177
201
|
modelsByTokens: buildModelsByTokens(threads),
|
|
202
|
+
reasoningEfforts: toDistribution(reasoningEfforts),
|
|
203
|
+
sources: toDistribution(sources),
|
|
178
204
|
summary: {
|
|
179
205
|
archivedThreads: threads.filter((thread) => Boolean(thread.archived)).length,
|
|
180
206
|
averageTokensPerThread: threads.length === 0 ? 0 : roundToTwoDecimals(totalTokens / threads.length),
|
|
181
207
|
distinctToolNames: toolUsage.size,
|
|
208
|
+
medianTokensPerThread: median(threads.map((thread) => thread.tokens_used)),
|
|
182
209
|
threadsWithWebSearch,
|
|
183
210
|
totalProjects: projectNames.size,
|
|
184
211
|
totalThreads: threads.length,
|