sidekick-shared 0.28.3 → 0.29.1
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 +86 -0
- package/README.md +188 -27
- package/dist/aggregation/EventAggregator.d.ts +19 -0
- package/dist/aggregation/EventAggregator.js +174 -35
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +13 -0
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +16 -6
- package/dist/browser.js +24 -2
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/context/sessionContext.js +4 -0
- package/dist/esm/aggregation/EventAggregator.js +174 -35
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +5 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/context/sessionContext.js +4 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +11 -6
- package/dist/esm/modelCatalog.js +7 -5
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +425 -47
- package/dist/esm/node.js +9 -3
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +351 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +85 -22
- package/dist/esm/parsers/subagentScanner.js +33 -12
- package/dist/esm/parsers/subagentTraceParser.js +97 -37
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +17 -9
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/claudeCode.js +10 -2
- package/dist/esm/providers/codex.js +84 -62
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/sessionEvent.js +35 -14
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/subagentSpawn.js +163 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +46 -3
- package/dist/esm/turns/assistantTurn.js +3 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/eventBridge.js +15 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +442 -58
- package/dist/esm/watchers/jsonlWatcher.js +51 -4
- package/dist/esm/watchers/providerReaderWatcher.js +13 -0
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -11
- package/dist/index.js +44 -12
- package/dist/modelCatalog.js +7 -5
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +434 -49
- package/dist/node.d.ts +16 -5
- package/dist/node.js +33 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +118 -0
- package/dist/parsers/codexParser.js +352 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +88 -22
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +33 -12
- package/dist/parsers/subagentTraceParser.d.ts +25 -5
- package/dist/parsers/subagentTraceParser.js +97 -37
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +16 -8
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/claudeCode.js +9 -1
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +85 -64
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/providers/types.d.ts +14 -0
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/sessionEvent.d.ts +27 -10
- package/dist/schemas/sessionEvent.js +35 -14
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/subagentSpawn.d.ts +80 -0
- package/dist/subagentSpawn.js +174 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +46 -3
- package/dist/turns/assistantTurn.d.ts +1 -0
- package/dist/turns/assistantTurn.js +3 -2
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/eventBridge.js +15 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +124 -4
- package/dist/watchers/jsonlTail.js +446 -59
- package/dist/watchers/jsonlWatcher.d.ts +18 -1
- package/dist/watchers/jsonlWatcher.js +50 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +13 -0
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +41 -1
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* record over `maxRecordBytes` is skipped without being buffered and reported
|
|
15
15
|
* in `exclusions`, the only way a record is permanently left out.
|
|
16
16
|
*
|
|
17
|
+
* `includeText: false` returns the same entries, ordinals, timestamps,
|
|
18
|
+
* metadata, exclusions, and cursor without prompt text, for consumers that
|
|
19
|
+
* only count or attribute prompts. Cursors are interchangeable between modes.
|
|
20
|
+
*
|
|
17
21
|
* A prompt can be written before its answer. The cursor then keeps the byte
|
|
18
22
|
* offset of that prompt's line and its answer-tracking state (never text), and
|
|
19
23
|
* the prompt is returned again under the same identity once its first
|
|
@@ -28,12 +32,19 @@ import { listCodexSessionFilesAsync } from './providers/codexRollouts';
|
|
|
28
32
|
import { DirectoryListingCache } from './providers/directoryListingCache';
|
|
29
33
|
import { realpathOrNull, resolveWorkspaceRoots } from './workspaceRoots';
|
|
30
34
|
import { fileHeadOf, HEAD_BYTES, headMatches } from './fileHead';
|
|
35
|
+
/**
|
|
36
|
+
* The default `maxRecordBytes` (16 MiB). Prompt ordinals are only comparable
|
|
37
|
+
* between readers that skip the same records, so every reader that publishes
|
|
38
|
+
* or joins on ordinals (prompt history, session prompt history, usage events'
|
|
39
|
+
* `promptOrdinal`) uses this bound.
|
|
40
|
+
*/
|
|
41
|
+
export const PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES = 16 * 1024 * 1024;
|
|
31
42
|
/** @internal */
|
|
32
43
|
export const DEFAULT_BOUNDS = {
|
|
33
44
|
maxSessions: 1000,
|
|
34
45
|
maxFileBytes: 16 * 1024 * 1024,
|
|
35
46
|
maxTotalBytes: 256 * 1024 * 1024,
|
|
36
|
-
maxRecordBytes:
|
|
47
|
+
maxRecordBytes: PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES,
|
|
37
48
|
maxEntries: 10_000,
|
|
38
49
|
deadlineMs: 30_000,
|
|
39
50
|
};
|
|
@@ -108,6 +119,7 @@ export async function collectPromptHistory(options) {
|
|
|
108
119
|
if (exclusions.length < MAX_EXCLUSIONS)
|
|
109
120
|
exclusions.push(exclusion);
|
|
110
121
|
},
|
|
122
|
+
includeText: options.includeText !== false,
|
|
111
123
|
};
|
|
112
124
|
let totalBytes = 0;
|
|
113
125
|
let scannedAny = false;
|
|
@@ -221,6 +233,46 @@ export async function collectPromptHistory(options) {
|
|
|
221
233
|
}
|
|
222
234
|
return finish();
|
|
223
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Every human prompt of one session file with its ordinal and timestamp, read
|
|
238
|
+
* whole in one pass with the default record bound, so the ordinals are the
|
|
239
|
+
* ones `collectPromptHistory()` returns with default bounds. No scope check
|
|
240
|
+
* and no text: prompts are counted wherever their cwd points. Prompts whose
|
|
241
|
+
* line has no usable timestamp still take an ordinal but are left out. A
|
|
242
|
+
* non-interactive Codex session (subagent, fork, unknown source) has none.
|
|
243
|
+
* In file order. Rejects when the file cannot be opened or changes while its
|
|
244
|
+
* head is read.
|
|
245
|
+
* @internal
|
|
246
|
+
*/
|
|
247
|
+
export async function readPromptOrdinalMarks(provider, sessionId, filePath) {
|
|
248
|
+
const marks = [];
|
|
249
|
+
const stat = await fs.promises.stat(filePath);
|
|
250
|
+
const unbounded = Number.MAX_SAFE_INTEGER;
|
|
251
|
+
const context = {
|
|
252
|
+
roots: [],
|
|
253
|
+
sinceMs: null,
|
|
254
|
+
stats: emptyStats(),
|
|
255
|
+
realpaths: new Map(),
|
|
256
|
+
bounds: {
|
|
257
|
+
maxSessions: unbounded,
|
|
258
|
+
maxFileBytes: unbounded,
|
|
259
|
+
maxTotalBytes: unbounded,
|
|
260
|
+
maxRecordBytes: PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES,
|
|
261
|
+
maxEntries: unbounded,
|
|
262
|
+
deadlineMs: unbounded,
|
|
263
|
+
},
|
|
264
|
+
shouldStop: () => null,
|
|
265
|
+
entriesSoFar: () => 0,
|
|
266
|
+
exclude: () => undefined,
|
|
267
|
+
includeText: false,
|
|
268
|
+
onPrompt: ({ ordinal, timestamp }) => {
|
|
269
|
+
if (timestamp !== undefined)
|
|
270
|
+
marks.push({ ordinal, timestampMs: Date.parse(timestamp) });
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
await scanFile({ provider, sessionId, filePath, key: `${provider}:${sessionId}:${hashPath(filePath)}` }, stat, undefined, { bytes: unbounded, bound: 'maxFileBytes', forceProgress: true }, context);
|
|
274
|
+
return marks;
|
|
275
|
+
}
|
|
224
276
|
/** @internal */
|
|
225
277
|
export function emptyStats() {
|
|
226
278
|
return {
|
|
@@ -899,7 +951,7 @@ class LineScanner {
|
|
|
899
951
|
return {
|
|
900
952
|
entry: {
|
|
901
953
|
...entry,
|
|
902
|
-
text,
|
|
954
|
+
...this.textFields(text),
|
|
903
955
|
cwd: scoped,
|
|
904
956
|
...(place.gitBranch ? { gitBranch: place.gitBranch } : {}),
|
|
905
957
|
...(entry.usage ? { usage: { ...entry.usage } } : {}),
|
|
@@ -918,11 +970,21 @@ class LineScanner {
|
|
|
918
970
|
this.closedCarried = open;
|
|
919
971
|
this.open = null;
|
|
920
972
|
}
|
|
973
|
+
/** Entry text fields: the text, or an empty string flagged as omitted. */
|
|
974
|
+
textFields(text) {
|
|
975
|
+
return this.context.includeText === false ? { text: '', textOmitted: true } : { text };
|
|
976
|
+
}
|
|
921
977
|
/** Assign an ordinal to a human prompt and emit it when in scope. */
|
|
922
978
|
recordPrompt(text, timestamp, cwd, gitBranch, lineOffset, lineBytes, stateBefore) {
|
|
923
979
|
const ordinal = this.nextOrdinal++;
|
|
924
980
|
this.closeOpen();
|
|
925
|
-
|
|
981
|
+
const usable = typeof timestamp === 'string' && !Number.isNaN(Date.parse(timestamp));
|
|
982
|
+
this.context.onPrompt?.({
|
|
983
|
+
ordinal,
|
|
984
|
+
timestamp: usable ? timestamp : undefined,
|
|
985
|
+
lineOffset,
|
|
986
|
+
});
|
|
987
|
+
if (!usable) {
|
|
926
988
|
this.context.stats.promptsMissingTimestamp++;
|
|
927
989
|
return;
|
|
928
990
|
}
|
|
@@ -940,7 +1002,7 @@ class LineScanner {
|
|
|
940
1002
|
sessionId: this.candidate.sessionId,
|
|
941
1003
|
ordinal,
|
|
942
1004
|
timestamp,
|
|
943
|
-
text,
|
|
1005
|
+
...this.textFields(text),
|
|
944
1006
|
cwd: scoped,
|
|
945
1007
|
...(gitBranch ? { gitBranch } : {}),
|
|
946
1008
|
metadataStatus: 'pending',
|
|
@@ -42,6 +42,31 @@ export const PROVIDER_FAILURE_EVIDENCE_SOURCES = [
|
|
|
42
42
|
'message',
|
|
43
43
|
'authentication-check',
|
|
44
44
|
];
|
|
45
|
+
/**
|
|
46
|
+
* The variants Codex reports in `codexErrorInfo` on `error` and failed-turn
|
|
47
|
+
* payloads (app-server camelCase spelling; the snake_case spelling Codex
|
|
48
|
+
* writes to rollout files is accepted too). `rateLimitExceeded` is an older
|
|
49
|
+
* spelling some Codex builds emitted.
|
|
50
|
+
*/
|
|
51
|
+
export const CODEX_ERROR_INFO_KINDS = [
|
|
52
|
+
'contextWindowExceeded',
|
|
53
|
+
'sessionBudgetExceeded',
|
|
54
|
+
'usageLimitExceeded',
|
|
55
|
+
'rateLimitExceeded',
|
|
56
|
+
'serverOverloaded',
|
|
57
|
+
'cyberPolicy',
|
|
58
|
+
'httpConnectionFailed',
|
|
59
|
+
'responseStreamConnectionFailed',
|
|
60
|
+
'internalServerError',
|
|
61
|
+
'unauthorized',
|
|
62
|
+
'badRequest',
|
|
63
|
+
'threadRollbackFailed',
|
|
64
|
+
'sandboxError',
|
|
65
|
+
'responseStreamDisconnected',
|
|
66
|
+
'responseTooManyFailedAttempts',
|
|
67
|
+
'activeTurnNotSteerable',
|
|
68
|
+
'other',
|
|
69
|
+
];
|
|
45
70
|
function read(value, key) {
|
|
46
71
|
try {
|
|
47
72
|
return value && typeof value === 'object' ? value[key] : undefined;
|
|
@@ -77,6 +102,40 @@ function retryAfter(value) {
|
|
|
77
102
|
const at = timestamp(value);
|
|
78
103
|
return at ? { kind: 'date', at } : undefined;
|
|
79
104
|
}
|
|
105
|
+
function compactKey(value) {
|
|
106
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
107
|
+
}
|
|
108
|
+
const CODEX_ERROR_INFO_BY_KEY = new Map(CODEX_ERROR_INFO_KINDS.map((kind) => [compactKey(kind), kind]));
|
|
109
|
+
/**
|
|
110
|
+
* Decode one Codex `codexErrorInfo` value: a string variant
|
|
111
|
+
* (`"serverOverloaded"`, `"usage_limit_exceeded"`) or an object variant
|
|
112
|
+
* (`{ "httpConnectionFailed": { "httpStatusCode": 503 } }`, snake_case keys
|
|
113
|
+
* accepted). Returns `undefined` for anything else, including `null`.
|
|
114
|
+
*/
|
|
115
|
+
export function decodeCodexErrorInfo(value) {
|
|
116
|
+
if (typeof value === 'string') {
|
|
117
|
+
const kind = value.length <= 64 ? CODEX_ERROR_INFO_BY_KEY.get(compactKey(value)) : undefined;
|
|
118
|
+
return kind ? { kind } : undefined;
|
|
119
|
+
}
|
|
120
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
121
|
+
return undefined;
|
|
122
|
+
let keys;
|
|
123
|
+
try {
|
|
124
|
+
keys = Object.keys(value);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
for (const key of keys.slice(0, 8)) {
|
|
130
|
+
const kind = key.length <= 64 ? CODEX_ERROR_INFO_BY_KEY.get(compactKey(key)) : undefined;
|
|
131
|
+
if (!kind)
|
|
132
|
+
continue;
|
|
133
|
+
const payload = read(value, key);
|
|
134
|
+
const status = statusNumber(read(payload, 'httpStatusCode') ?? read(payload, 'http_status_code'));
|
|
135
|
+
return status === undefined ? { kind } : { kind, httpStatusCode: status };
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
80
139
|
function rejected(kind) {
|
|
81
140
|
return kind === 'oauth'
|
|
82
141
|
? 'oauth_reauthentication_required'
|
|
@@ -167,6 +226,44 @@ function classifyStatus(status, kind) {
|
|
|
167
226
|
return 'service_unavailable';
|
|
168
227
|
return undefined;
|
|
169
228
|
}
|
|
229
|
+
/** Variants with no matching failure code (bad request, policy, budget, other) stay unclassified. */
|
|
230
|
+
function classifyCodexErrorInfo(info, kind) {
|
|
231
|
+
switch (info.kind) {
|
|
232
|
+
case 'usageLimitExceeded':
|
|
233
|
+
case 'rateLimitExceeded':
|
|
234
|
+
return 'rate_limited';
|
|
235
|
+
case 'serverOverloaded':
|
|
236
|
+
return 'overloaded';
|
|
237
|
+
case 'internalServerError':
|
|
238
|
+
return 'service_unavailable';
|
|
239
|
+
case 'unauthorized':
|
|
240
|
+
return rejected(kind);
|
|
241
|
+
case 'contextWindowExceeded':
|
|
242
|
+
return 'context_overflow';
|
|
243
|
+
case 'httpConnectionFailed':
|
|
244
|
+
case 'responseStreamConnectionFailed':
|
|
245
|
+
case 'responseStreamDisconnected':
|
|
246
|
+
case 'responseTooManyFailedAttempts':
|
|
247
|
+
return info.httpStatusCode === undefined
|
|
248
|
+
? 'connection_failed'
|
|
249
|
+
: classifyStatus(info.httpStatusCode, kind);
|
|
250
|
+
default:
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function isRetryable(code, quotaExhausted, retry) {
|
|
255
|
+
switch (code) {
|
|
256
|
+
case 'rate_limited':
|
|
257
|
+
return !quotaExhausted || retry !== undefined;
|
|
258
|
+
case 'overloaded':
|
|
259
|
+
case 'service_unavailable':
|
|
260
|
+
case 'timeout':
|
|
261
|
+
case 'connection_failed':
|
|
262
|
+
return true;
|
|
263
|
+
default:
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
170
267
|
function classifyMessage(message, kind) {
|
|
171
268
|
if (/\b(execution policy|sandbox policy|approval)\b.{0,60}\b(denied|blocked|rejected|not allowed)\b|\bdenied by policy\b/i.test(message))
|
|
172
269
|
return 'execution_policy_denied';
|
|
@@ -232,6 +329,13 @@ function recovery(code, kind) {
|
|
|
232
329
|
/**
|
|
233
330
|
* Classify a caller-designated terminal failure without I/O or recovery actions.
|
|
234
331
|
* Public status and runtime readiness do not establish request authentication.
|
|
332
|
+
*
|
|
333
|
+
* A Codex `codexErrorInfo` (or `codex_error_info`) on the error or on any
|
|
334
|
+
* nested `error` / `cause` / `response` / `data` / `body` / `errors` entry is
|
|
335
|
+
* structured evidence. It ranks ahead of the same object's `code` / `type` /
|
|
336
|
+
* `name`, objects are read outer first, and every structured code ranks ahead
|
|
337
|
+
* of HTTP status and message text. Its transport variants also supply
|
|
338
|
+
* `httpStatus`.
|
|
235
339
|
*/
|
|
236
340
|
export function diagnoseProviderFailure(input) {
|
|
237
341
|
const codes = [];
|
|
@@ -249,11 +353,15 @@ export function diagnoseProviderFailure(input) {
|
|
|
249
353
|
if (!value || typeof value !== 'object' || visited.has(value))
|
|
250
354
|
continue;
|
|
251
355
|
visited.add(value);
|
|
356
|
+
const codex = decodeCodexErrorInfo(read(value, 'codexErrorInfo') ?? read(value, 'codex_error_info'));
|
|
357
|
+
if (codex)
|
|
358
|
+
codes.push(codex);
|
|
252
359
|
for (const key of ['code', 'type', 'name'])
|
|
253
360
|
codes.push(text(read(value, key)));
|
|
254
361
|
messages.push(text(read(value, 'message')));
|
|
255
362
|
for (const key of ['status', 'statusCode', 'httpStatus', 'status_code'])
|
|
256
363
|
httpStatus ??= statusNumber(read(value, key));
|
|
364
|
+
httpStatus ??= codex?.httpStatusCode;
|
|
257
365
|
const headers = read(value, 'headers');
|
|
258
366
|
const header = typeof Headers !== 'undefined' && headers instanceof Headers
|
|
259
367
|
? headers.get('retry-after')
|
|
@@ -306,9 +414,20 @@ export function diagnoseProviderFailure(input) {
|
|
|
306
414
|
source: auth.source,
|
|
307
415
|
};
|
|
308
416
|
}
|
|
309
|
-
let diagnosis
|
|
310
|
-
|
|
311
|
-
|
|
417
|
+
let diagnosis;
|
|
418
|
+
let codexErrorInfo;
|
|
419
|
+
for (const code of codes) {
|
|
420
|
+
if (typeof code === 'string') {
|
|
421
|
+
diagnosis = classifyCode(code, input.credentialKind);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
diagnosis = classifyCodexErrorInfo(code, input.credentialKind);
|
|
425
|
+
if (diagnosis)
|
|
426
|
+
codexErrorInfo = code.kind;
|
|
427
|
+
}
|
|
428
|
+
if (diagnosis)
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
312
431
|
let source = 'structured-error';
|
|
313
432
|
if (!diagnosis && httpStatus !== undefined) {
|
|
314
433
|
diagnosis = classifyStatus(httpStatus, input.credentialKind);
|
|
@@ -344,6 +463,7 @@ export function diagnoseProviderFailure(input) {
|
|
|
344
463
|
credentialKind: input.credentialKind,
|
|
345
464
|
diagnosis,
|
|
346
465
|
recovery: recovery(diagnosis, input.credentialKind),
|
|
466
|
+
retryable: isRetryable(diagnosis, codexErrorInfo === 'usageLimitExceeded', retry),
|
|
347
467
|
evidence: diagnosis === 'unknown'
|
|
348
468
|
? []
|
|
349
469
|
: [
|
|
@@ -353,6 +473,7 @@ export function diagnoseProviderFailure(input) {
|
|
|
353
473
|
...(source === 'authentication-check'
|
|
354
474
|
? { checkedAt: authentication.checkedAt }
|
|
355
475
|
: {}),
|
|
476
|
+
...(codexErrorInfo ? { codexErrorInfo } : {}),
|
|
356
477
|
},
|
|
357
478
|
],
|
|
358
479
|
...(authentication ? { authentication } : {}),
|
|
@@ -13,7 +13,7 @@ import { StringDecoder } from 'string_decoder';
|
|
|
13
13
|
import { readSessionContextSnapshot } from '../context/sessionContext';
|
|
14
14
|
import { readSessionFileStats } from '../sessionStats';
|
|
15
15
|
import { JsonlParser } from '../parsers/jsonl';
|
|
16
|
-
import { ClaudeUsageDeduper, normalizeClaudeUsage } from '../usage/claudeUsageDedupe';
|
|
16
|
+
import { ClaudeUsageDeduper, normalizeClaudeUsage, } from '../usage/claudeUsageDedupe';
|
|
17
17
|
import { ProviderDiagnosticTracker } from './diagnostics';
|
|
18
18
|
import { DirectoryListingCache, splitWatchedPath } from './directoryListingCache';
|
|
19
19
|
import { encodeWorkspacePath as encodeWsPath, getSessionDirectory as getSessionDir, discoverSessionDirectory as discoverSessionDir, findActiveSession as findActiveSessionPath, findAllSessions as findAllSessionPaths, findSessionsInDirectory as findSessionsInDir, decodeEncodedPath, getAllProjectFolders as getAllProjectFoldersRaw, findSessionFilesWithStats, } from '../parsers/sessionPathResolver';
|
|
@@ -119,7 +119,8 @@ class ClaudeCodeReader {
|
|
|
119
119
|
this.usageDeduper = usageDeduper ?? new ClaudeUsageDeduper();
|
|
120
120
|
this.parser = new JsonlParser({
|
|
121
121
|
onEvent: (raw) => {
|
|
122
|
-
|
|
122
|
+
// Mid-turn task notifications finish background subagents.
|
|
123
|
+
for (const event of extractSessionEvents(raw, { includeTaskNotifications: true })) {
|
|
123
124
|
this.events.push(this.usageDeduper.apply(normalizeClaudeUsage(event)));
|
|
124
125
|
}
|
|
125
126
|
},
|
|
@@ -208,6 +209,13 @@ class ClaudeCodeReader {
|
|
|
208
209
|
wasTruncated() {
|
|
209
210
|
return this._wasTruncated;
|
|
210
211
|
}
|
|
212
|
+
/** The usage deduper's recent message ids, so a resumed reader does not recount a split response. */
|
|
213
|
+
getResumeState() {
|
|
214
|
+
return this.usageDeduper.exportState();
|
|
215
|
+
}
|
|
216
|
+
restoreResumeState(state) {
|
|
217
|
+
return this.usageDeduper.importState(state);
|
|
218
|
+
}
|
|
211
219
|
}
|
|
212
220
|
/**
|
|
213
221
|
* Session provider for Claude Code CLI.
|
|
@@ -18,7 +18,7 @@ import { CodexDatabase, spawnParentFromSource } from './codexDatabase';
|
|
|
18
18
|
import { cachedSubagentStats } from '../parsers/subagentStatsCache';
|
|
19
19
|
import { DEFAULT_ROLLOUT_WALK_MAX_DEPTH, extractRolloutSessionId as extractSessionId, isRolloutFile, walkRolloutFiles, } from './rolloutWalker';
|
|
20
20
|
import { getCodexSessionsDirs } from '../codexPaths';
|
|
21
|
-
import { cwdMatches, getSessionsDirs, listCodexSessionFilesAsync, normalizePath, readFirstLines, readSessionCwd, readSessionCwdAsync, readSessionMeta, } from './codexRollouts';
|
|
21
|
+
import { cwdMatches, findCodexRolloutFile, getSessionsDirs, listCodexSessionFilesAsync, normalizePath, readFirstLines, readSessionCwd, readSessionCwdAsync, readSessionMeta, } from './codexRollouts';
|
|
22
22
|
import { DirectoryListingCache, splitWatchedPath } from './directoryListingCache';
|
|
23
23
|
import { ProviderDiagnosticTracker, diagnosticFromRuntimeStatus } from './diagnostics';
|
|
24
24
|
import { getModelContextWindowSize } from '../modelContext';
|
|
@@ -47,29 +47,7 @@ function hasRolloutFile(dir) {
|
|
|
47
47
|
function findRolloutFilesInConfiguredHomes(options = {}) {
|
|
48
48
|
return walkRolloutFiles(getSessionsDirs(), options);
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
* Locates the rollout file for a Codex session id by walking
|
|
52
|
-
* `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
|
|
53
|
-
* `<home>/archived_sessions/`, where Codex moves the rollouts of archived
|
|
54
|
-
* threads.
|
|
55
|
-
*
|
|
56
|
-
* Filename-only match, case-insensitive on the id — no file contents are read
|
|
57
|
-
* and files are only stat'ed once their name matches, so the walk stays cheap
|
|
58
|
-
* even over a large history. When the same id exists in more than one home,
|
|
59
|
-
* the most recently modified copy wins. Returns `null` for an unknown, empty,
|
|
60
|
-
* or whitespace id. Consumers who want the database's answer instead can query
|
|
61
|
-
* the already-public `CodexDatabase` (requires the external `sqlite3` binary).
|
|
62
|
-
*/
|
|
63
|
-
export function findCodexRolloutFile(sessionId, options = {}) {
|
|
64
|
-
const normalizedId = sessionId.trim().toLowerCase();
|
|
65
|
-
if (!normalizedId)
|
|
66
|
-
return null;
|
|
67
|
-
const sessionsDirs = options.codexHome
|
|
68
|
-
? getCodexSessionsDirs([options.codexHome])
|
|
69
|
-
: getSessionsDirs();
|
|
70
|
-
// Name-only filter, one walk across the homes, newest modification first.
|
|
71
|
-
return walkRolloutFiles(sessionsDirs, { sessionId: normalizedId })[0]?.path ?? null;
|
|
72
|
-
}
|
|
50
|
+
export { findCodexRolloutFile };
|
|
73
51
|
/** Pick the best sessions directory for UI/search entrypoints. */
|
|
74
52
|
function getSessionsDir() {
|
|
75
53
|
const dirs = getCodexSessionsDirs(undefined, { includeArchived: false });
|
|
@@ -292,6 +270,12 @@ class CodexReader {
|
|
|
292
270
|
lineBuffer = '';
|
|
293
271
|
_wasTruncated = false;
|
|
294
272
|
decoder = new StringDecoder('utf8');
|
|
273
|
+
/**
|
|
274
|
+
* Position from `seekTo()` whose prefix still has to be parsed for model,
|
|
275
|
+
* turn, and cumulative-token context; `restoreResumeState()` supplies that
|
|
276
|
+
* context instead and clears it.
|
|
277
|
+
*/
|
|
278
|
+
pendingSeek = null;
|
|
295
279
|
constructor(rolloutPath, onContextWindowLimit, onRateLimits, onTokenUsage) {
|
|
296
280
|
this.rolloutPath = rolloutPath;
|
|
297
281
|
this.onContextWindowLimit = onContextWindowLimit;
|
|
@@ -301,9 +285,11 @@ class CodexReader {
|
|
|
301
285
|
}
|
|
302
286
|
/** Get the parser's session metadata. */
|
|
303
287
|
getSessionMeta() {
|
|
288
|
+
this.settleSeek();
|
|
304
289
|
return this.parser.getSessionMeta();
|
|
305
290
|
}
|
|
306
291
|
readNew(endPosition) {
|
|
292
|
+
this.settleSeek();
|
|
307
293
|
this._wasTruncated = false;
|
|
308
294
|
const events = [];
|
|
309
295
|
try {
|
|
@@ -357,47 +343,51 @@ class CodexReader {
|
|
|
357
343
|
// Skip malformed JSON lines
|
|
358
344
|
}
|
|
359
345
|
}
|
|
360
|
-
|
|
361
|
-
// The model comes along so the value can be persisted per model — the
|
|
362
|
-
// window Codex reports is tier-specific, not the published maximum.
|
|
363
|
-
const mcw = this.parser.getModelContextWindow();
|
|
364
|
-
if (mcw && this.onContextWindowLimit) {
|
|
365
|
-
this.onContextWindowLimit(mcw, this.parser.getCurrentModel() ?? undefined);
|
|
366
|
-
}
|
|
367
|
-
// Propagate rate_limits from token_count events to the provider
|
|
368
|
-
const rateLimits = this.parser.getLastRateLimits();
|
|
369
|
-
if (rateLimits && this.onRateLimits) {
|
|
370
|
-
this.onRateLimits(rateLimits);
|
|
371
|
-
}
|
|
372
|
-
// Propagate last token usage snapshot to the provider
|
|
373
|
-
const lastUsage = this.parser.getLastTokenUsage();
|
|
374
|
-
if (lastUsage && this.onTokenUsage) {
|
|
375
|
-
const normalized = normalizeProviderUsage({
|
|
376
|
-
semantics: 'openai',
|
|
377
|
-
provider: 'openai',
|
|
378
|
-
source: 'codex-reader-snapshot',
|
|
379
|
-
model: this.parser.getCurrentModel() || undefined,
|
|
380
|
-
inputTokens: lastUsage.input_tokens,
|
|
381
|
-
outputTokens: lastUsage.output_tokens,
|
|
382
|
-
cacheReadTokens: lastUsage.cached_input_tokens,
|
|
383
|
-
reasoningTokens: lastUsage.reasoning_output_tokens,
|
|
384
|
-
});
|
|
385
|
-
this.onTokenUsage({
|
|
386
|
-
inputTokens: normalized.uncachedInputTokens,
|
|
387
|
-
outputTokens: normalized.outputTokens,
|
|
388
|
-
cacheWriteTokens: normalized.cacheWriteTokens,
|
|
389
|
-
cacheReadTokens: normalized.cacheReadTokens,
|
|
390
|
-
reasoningTokens: normalized.reasoningTokens,
|
|
391
|
-
model: this.parser.getCurrentModel() || 'unknown',
|
|
392
|
-
timestamp: new Date(),
|
|
393
|
-
});
|
|
394
|
-
}
|
|
346
|
+
this.propagateParserState();
|
|
395
347
|
}
|
|
396
348
|
catch {
|
|
397
349
|
// Skip read errors
|
|
398
350
|
}
|
|
399
351
|
return events;
|
|
400
352
|
}
|
|
353
|
+
/** Hands the parser's context window, rate limits, and last usage to the provider. */
|
|
354
|
+
propagateParserState() {
|
|
355
|
+
// Propagate model_context_window from token_count events to the provider.
|
|
356
|
+
// The model comes along so the value can be persisted per model — the
|
|
357
|
+
// window Codex reports is tier-specific, not the published maximum.
|
|
358
|
+
const mcw = this.parser.getModelContextWindow();
|
|
359
|
+
if (mcw && this.onContextWindowLimit) {
|
|
360
|
+
this.onContextWindowLimit(mcw, this.parser.getCurrentModel() ?? undefined);
|
|
361
|
+
}
|
|
362
|
+
// Propagate rate_limits from token_count events to the provider
|
|
363
|
+
const rateLimits = this.parser.getLastRateLimits();
|
|
364
|
+
if (rateLimits && this.onRateLimits) {
|
|
365
|
+
this.onRateLimits(rateLimits);
|
|
366
|
+
}
|
|
367
|
+
// Propagate last token usage snapshot to the provider
|
|
368
|
+
const lastUsage = this.parser.getLastTokenUsage();
|
|
369
|
+
if (lastUsage && this.onTokenUsage) {
|
|
370
|
+
const normalized = normalizeProviderUsage({
|
|
371
|
+
semantics: 'openai',
|
|
372
|
+
provider: 'openai',
|
|
373
|
+
source: 'codex-reader-snapshot',
|
|
374
|
+
model: this.parser.getCurrentModel() || undefined,
|
|
375
|
+
inputTokens: lastUsage.input_tokens,
|
|
376
|
+
outputTokens: lastUsage.output_tokens,
|
|
377
|
+
cacheReadTokens: lastUsage.cached_input_tokens,
|
|
378
|
+
reasoningTokens: lastUsage.reasoning_output_tokens,
|
|
379
|
+
});
|
|
380
|
+
this.onTokenUsage({
|
|
381
|
+
inputTokens: normalized.uncachedInputTokens,
|
|
382
|
+
outputTokens: normalized.outputTokens,
|
|
383
|
+
cacheWriteTokens: normalized.cacheWriteTokens,
|
|
384
|
+
cacheReadTokens: normalized.cacheReadTokens,
|
|
385
|
+
reasoningTokens: normalized.reasoningTokens,
|
|
386
|
+
model: this.parser.getCurrentModel() || 'unknown',
|
|
387
|
+
timestamp: new Date(),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
401
391
|
readAll() {
|
|
402
392
|
this.reset();
|
|
403
393
|
return this.readNew();
|
|
@@ -409,11 +399,13 @@ class CodexReader {
|
|
|
409
399
|
this.parser.reset();
|
|
410
400
|
this.decoder = new StringDecoder('utf8');
|
|
411
401
|
this._wasTruncated = false;
|
|
402
|
+
this.pendingSeek = null;
|
|
412
403
|
}
|
|
413
404
|
exists() {
|
|
414
405
|
return fs.existsSync(this.rolloutPath);
|
|
415
406
|
}
|
|
416
407
|
flush() {
|
|
408
|
+
this.settleSeek();
|
|
417
409
|
this.lineBuffer += this.decoder.end();
|
|
418
410
|
// Process any remaining data in the line buffer
|
|
419
411
|
if (this.lineBuffer.trim()) {
|
|
@@ -431,15 +423,45 @@ class CodexReader {
|
|
|
431
423
|
return this.committedPosition;
|
|
432
424
|
}
|
|
433
425
|
seekTo(position) {
|
|
434
|
-
// Rebuild model, turn, and cumulative-token context before resuming.
|
|
435
|
-
// The prefix is parsed only; its events are already in the consumer snapshot.
|
|
436
426
|
this.reset();
|
|
427
|
+
this.filePosition = position;
|
|
428
|
+
this.committedPosition = position;
|
|
429
|
+
// The prefix's context is rebuilt on the next read, unless
|
|
430
|
+
// restoreResumeState() supplies it first.
|
|
437
431
|
if (position > 0)
|
|
438
|
-
this.
|
|
432
|
+
this.pendingSeek = position;
|
|
439
433
|
}
|
|
440
434
|
wasTruncated() {
|
|
441
435
|
return this._wasTruncated;
|
|
442
436
|
}
|
|
437
|
+
/** The parser's continuation state (see `CodexRolloutParser.exportResumeState`). */
|
|
438
|
+
getResumeState() {
|
|
439
|
+
this.settleSeek();
|
|
440
|
+
return this.parser.exportResumeState();
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Restores parser state from `getResumeState()`; after `seekTo()` this
|
|
444
|
+
* replaces re-parsing the rollout up to that position.
|
|
445
|
+
*/
|
|
446
|
+
restoreResumeState(state) {
|
|
447
|
+
if (!this.parser.importResumeState(state))
|
|
448
|
+
return false;
|
|
449
|
+
this.pendingSeek = null;
|
|
450
|
+
this.propagateParserState();
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Rebuilds model, turn, and cumulative-token context for a pending
|
|
455
|
+
* `seekTo()` position. The prefix is parsed only; its events are already
|
|
456
|
+
* in the consumer snapshot.
|
|
457
|
+
*/
|
|
458
|
+
settleSeek() {
|
|
459
|
+
const position = this.pendingSeek;
|
|
460
|
+
if (position === null)
|
|
461
|
+
return;
|
|
462
|
+
this.reset();
|
|
463
|
+
this.readNew(position);
|
|
464
|
+
}
|
|
443
465
|
}
|
|
444
466
|
// ---------------------------------------------------------------------------
|
|
445
467
|
// CodexProvider — full SessionProviderBase implementation
|
|
@@ -5,30 +5,14 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { execFile, execFileSync } from 'child_process';
|
|
8
|
+
import { codexSpawnParentFromSource as spawnParentFromSource } from '../parsers/codexRolloutOrigin';
|
|
8
9
|
/**
|
|
9
10
|
* Ids per inlined `IN (…)` query. Values are passed inside a single argv
|
|
10
11
|
* element to sqlite3, so an unbounded list would hit E2BIG on large stores.
|
|
11
12
|
*/
|
|
12
13
|
const SQL_ID_CHUNK_SIZE = 400;
|
|
13
|
-
/** Parent thread id
|
|
14
|
-
export
|
|
15
|
-
let parsed = source;
|
|
16
|
-
if (typeof source === 'string') {
|
|
17
|
-
if (!source.startsWith('{'))
|
|
18
|
-
return undefined;
|
|
19
|
-
try {
|
|
20
|
-
parsed = JSON.parse(source);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (!parsed || typeof parsed !== 'object')
|
|
27
|
-
return undefined;
|
|
28
|
-
const parent = parsed
|
|
29
|
-
.subagent?.thread_spawn?.parent_thread_id;
|
|
30
|
-
return typeof parent === 'string' ? parent : undefined;
|
|
31
|
-
}
|
|
14
|
+
/** Parent thread id of a spawned subagent's `source`; one rule shared with rollout parsing. */
|
|
15
|
+
export { spawnParentFromSource };
|
|
32
16
|
export class CodexDatabase {
|
|
33
17
|
dbPath;
|
|
34
18
|
sqlite3Available = null;
|
|
@@ -8,7 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { getCodexSessionsDirs } from '../codexPaths';
|
|
10
10
|
import { DirectoryListingCache } from './directoryListingCache';
|
|
11
|
-
import { walkRolloutFilesAsync } from './rolloutWalker';
|
|
11
|
+
import { walkRolloutFiles, walkRolloutFilesAsync } from './rolloutWalker';
|
|
12
12
|
/** Every directory across the monitored homes that may hold rollouts, archived ones included. */
|
|
13
13
|
export function getSessionsDirs() {
|
|
14
14
|
return getCodexSessionsDirs();
|
|
@@ -213,3 +213,26 @@ export async function listCodexSessionFilesAsync(workspacePath, options = {}, ca
|
|
|
213
213
|
}
|
|
214
214
|
return results;
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Locates the rollout file for a Codex session id by walking
|
|
218
|
+
* `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
|
|
219
|
+
* `<home>/archived_sessions/`, where Codex moves the rollouts of archived
|
|
220
|
+
* threads.
|
|
221
|
+
*
|
|
222
|
+
* Filename-only match, case-insensitive on the id — no file contents are read
|
|
223
|
+
* and files are only stat'ed once their name matches, so the walk stays cheap
|
|
224
|
+
* even over a large history. When the same id exists in more than one home,
|
|
225
|
+
* the most recently modified copy wins. Returns `null` for an unknown, empty,
|
|
226
|
+
* or whitespace id. Consumers who want the database's answer instead can query
|
|
227
|
+
* the already-public `CodexDatabase` (requires the external `sqlite3` binary).
|
|
228
|
+
*/
|
|
229
|
+
export function findCodexRolloutFile(sessionId, options = {}) {
|
|
230
|
+
const normalizedId = sessionId.trim().toLowerCase();
|
|
231
|
+
if (!normalizedId)
|
|
232
|
+
return null;
|
|
233
|
+
const sessionsDirs = options.codexHome
|
|
234
|
+
? getCodexSessionsDirs([options.codexHome])
|
|
235
|
+
: getSessionsDirs();
|
|
236
|
+
// Name-only filter, one walk across the homes, newest modification first.
|
|
237
|
+
return walkRolloutFiles(sessionsDirs, { sessionId: normalizedId })[0]?.path ?? null;
|
|
238
|
+
}
|
|
@@ -21,9 +21,8 @@ import * as path from 'path';
|
|
|
21
21
|
import { execSync } from 'child_process';
|
|
22
22
|
import { readSessionContextSnapshot } from '../context/sessionContext';
|
|
23
23
|
import { readSessionFileStats } from '../sessionStats';
|
|
24
|
-
import { convertOpenCodeMessage, parseDbMessageData, parseDbPartData, normalizeToolName, normalizeToolInput, } from '../parsers/openCodeParser';
|
|
24
|
+
import { convertOpenCodeMessage, parseDbMessageData, parseDbPartData, normalizeOpenCodeUsage, normalizeToolName, normalizeToolInput, } from '../parsers/openCodeParser';
|
|
25
25
|
import { OpenCodeDatabase } from './openCodeDatabase';
|
|
26
|
-
import { normalizeProviderUsage } from '../usageNormalization';
|
|
27
26
|
import { addUsageToSubagent } from '../usage/subagentUsage';
|
|
28
27
|
import { accumulateZaiUsage, inferZaiQuotaState, parseZaiQuotaError, ZAI_PROVIDER_IDS, } from '../zaiQuota';
|
|
29
28
|
import { ProviderDiagnosticTracker, diagnosticFromRuntimeStatus } from './diagnostics';
|
|
@@ -1456,21 +1455,14 @@ export class OpenCodeProvider {
|
|
|
1456
1455
|
if (tokens) {
|
|
1457
1456
|
// Same semantics as the main-session parser (openCodeParser).
|
|
1458
1457
|
const cache = tokens.cache;
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
inputTokens: tokens.input,
|
|
1468
|
-
outputTokens: tokens.output,
|
|
1469
|
-
cacheReadTokens: cache?.read,
|
|
1470
|
-
cacheWriteTokens: cache?.write,
|
|
1471
|
-
reasoningTokens: tokens.reasoning,
|
|
1472
|
-
reasoningIncludedInOutput: providerID === 'openai',
|
|
1473
|
-
}));
|
|
1458
|
+
addUsageToSubagent(tokenTotals, normalizeOpenCodeUsage({
|
|
1459
|
+
input: tokens.input,
|
|
1460
|
+
output: tokens.output,
|
|
1461
|
+
cacheRead: cache?.read,
|
|
1462
|
+
cacheWrite: cache?.write,
|
|
1463
|
+
reasoning: tokens.reasoning,
|
|
1464
|
+
total: tokens.total,
|
|
1465
|
+
}, msgData.providerID));
|
|
1474
1466
|
}
|
|
1475
1467
|
}
|
|
1476
1468
|
catch {
|