kimaki 0.26.0 → 0.27.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/dist/agent-model.e2e.test.js +25 -122
- package/dist/anthropic-account-identity.js +1 -0
- package/dist/anthropic-auth-plugin.js +1 -0
- package/dist/anthropic-auth-state.js +1 -0
- package/dist/bin.js +73 -19
- package/dist/cli-commands/maintenance.js +46 -7
- package/dist/cli-commands/send.js +24 -0
- package/dist/cli-commands/session.js +183 -8
- package/dist/cli-commands/task.js +13 -1
- package/dist/cli-parsing.test.js +59 -3
- package/dist/cli-runner.js +28 -4
- package/dist/cli-runner.test.js +27 -0
- package/dist/cli-send-thread.e2e.test.js +1 -1
- package/dist/cli.js +32 -0
- package/dist/commands/abort.js +5 -1
- package/dist/commands/agent.js +6 -8
- package/dist/commands/ask-question.js +21 -32
- package/dist/commands/ask-question.test.js +43 -1
- package/dist/commands/login.js +6 -0
- package/dist/commands/merge-worktree.js +36 -10
- package/dist/commands/model-variant.js +7 -1
- package/dist/commands/model.js +14 -8
- package/dist/commands/multioauth.js +1 -0
- package/dist/commands/new-worktree.js +170 -102
- package/dist/commands/user-command.js +6 -8
- package/dist/commands/worktrees.js +5 -13
- package/dist/config.js +16 -1
- package/dist/database.js +124 -3
- package/dist/db.js +22 -0
- package/dist/db.test.js +167 -1
- package/dist/discord-bot.js +34 -32
- package/dist/discord-command-registration.js +9 -1
- package/dist/discord-utils.js +13 -5
- package/dist/discord-utils.test.js +17 -1
- package/dist/errors.js +5 -0
- package/dist/exec-async.js +17 -5
- package/dist/external-directory-permissions.e2e.test.js +2 -2
- package/dist/file-edit-log.js +197 -0
- package/dist/file-edit-log.test.js +258 -0
- package/dist/format-tables.js +52 -0
- package/dist/format-tables.test.js +93 -0
- package/dist/gateway-proxy-reconnect.e2e.test.js +28 -8
- package/dist/gateway-proxy.e2e.test.js +5 -5
- package/dist/git-worktree-core.js +182 -17
- package/dist/ipc-tools-plugin.js +63 -3
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +122 -58
- package/dist/kimaki-opencode-plugin.js +7 -3
- package/dist/kimaki-workspace-adaptor.js +33 -10
- package/dist/logger.js +10 -1
- package/dist/markdown.js +3 -1
- package/dist/markdown.test.js +11 -3
- package/dist/message-formatting.js +18 -7
- package/dist/message-formatting.test.js +56 -9
- package/dist/oauth-rotation-shared.js +10 -0
- package/dist/openai-auth-plugin.js +1 -0
- package/dist/openai-auth-state.js +1 -0
- package/dist/opencode-command.test.js +60 -0
- package/dist/opencode.js +53 -12
- package/dist/question-abort-clears-dropdown.e2e.test.js +89 -0
- package/dist/question-text-before-dropdown.e2e.test.js +52 -0
- package/dist/queue-advanced-abort.e2e.test.js +1 -1
- package/dist/queue-advanced-action-buttons.e2e.test.js +3 -3
- package/dist/queue-advanced-e2e-setup.js +78 -5
- package/dist/queue-advanced-footer.e2e.test.js +40 -246
- package/dist/queue-advanced-model-switch.e2e.test.js +2 -2
- package/dist/queue-advanced-permissions-typing.e2e.test.js +7 -9
- package/dist/queue-advanced-question.e2e.test.js +31 -26
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +2 -2
- package/dist/queue-delete-message.e2e.test.js +1 -1
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +2 -2
- package/dist/queue-interrupt-drain.e2e.test.js +112 -2
- package/dist/queue-question-select-drain.e2e.test.js +30 -128
- package/dist/runtime-lifecycle.e2e.test.js +10 -37
- package/dist/schema.js +34 -0
- package/dist/session-handler/event-stream-state.js +267 -50
- package/dist/session-handler/event-stream-state.test.js +415 -8
- package/dist/session-handler/model-utils.js +159 -5
- package/dist/session-handler/model-utils.test.js +345 -0
- package/dist/session-handler/thread-session-runtime.js +252 -182
- package/dist/session-sleep.test.js +224 -0
- package/dist/session-title-rename.test.js +7 -39
- package/dist/sleep.e2e.test.js +491 -0
- package/dist/startup-time.e2e.test.js +2 -1
- package/dist/store.js +3 -0
- package/dist/system-message.js +59 -6
- package/dist/system-message.test.js +76 -6
- package/dist/task-runner.js +94 -4
- package/dist/task-schedule.js +56 -0
- package/dist/task-schedule.test.js +71 -1
- package/dist/thread-message-queue.e2e.test.js +10 -169
- package/dist/undo-redo.e2e.test.js +2 -2
- package/dist/voice-attachment.js +23 -1
- package/dist/voice-message.e2e.test.js +10 -10
- package/dist/voice.test.js +55 -29
- package/dist/worktree-clone-isolation.e2e.test.js +188 -0
- package/dist/worktree-lifecycle.e2e.test.js +29 -10
- package/dist/worktrees.js +226 -443
- package/dist/worktrees.test.js +435 -58
- package/dist/xai-auth-plugin.js +1 -0
- package/dist/xai-auth-state.js +1 -0
- package/package.json +8 -6
- package/skills/errore/SKILL.md +18 -0
- package/skills/goke/SKILL.md +150 -10
- package/skills/holocron/SKILL.md +37 -0
- package/skills/npm-package/SKILL.md +55 -35
- package/skills/profano/SKILL.md +17 -2
- package/skills/spiceflow/SKILL.md +39 -3
- package/skills/strada/SKILL.md +39 -0
- package/skills/zele/SKILL.md +19 -0
- package/src/agent-model.e2e.test.ts +25 -151
- package/src/anthropic-account-identity.ts +1 -0
- package/src/anthropic-auth-plugin.ts +1 -0
- package/src/anthropic-auth-state.ts +1 -0
- package/src/bin.ts +78 -26
- package/src/cli-commands/maintenance.ts +59 -13
- package/src/cli-commands/send.ts +25 -0
- package/src/cli-commands/session.ts +203 -9
- package/src/cli-commands/task.ts +13 -1
- package/src/cli-parsing.test.ts +69 -3
- package/src/cli-runner.test.ts +49 -0
- package/src/cli-runner.ts +28 -4
- package/src/cli-send-thread.e2e.test.ts +1 -1
- package/src/cli.ts +53 -0
- package/src/commands/abort.ts +6 -1
- package/src/commands/agent.ts +6 -6
- package/src/commands/ask-question.test.ts +48 -1
- package/src/commands/ask-question.ts +21 -32
- package/src/commands/login.ts +6 -0
- package/src/commands/merge-worktree.ts +43 -12
- package/src/commands/model-variant.ts +8 -1
- package/src/commands/model.ts +18 -8
- package/src/commands/multioauth.ts +1 -0
- package/src/commands/new-worktree.ts +138 -55
- package/src/commands/user-command.ts +6 -6
- package/src/commands/worktrees.ts +5 -12
- package/src/config.ts +18 -1
- package/src/database.ts +195 -3
- package/src/db.test.ts +188 -0
- package/src/db.ts +26 -0
- package/src/discord-bot.ts +35 -29
- package/src/discord-command-registration.ts +15 -1
- package/src/discord-utils.test.ts +19 -0
- package/src/discord-utils.ts +21 -6
- package/src/errors.ts +5 -0
- package/src/exec-async.ts +20 -11
- package/src/external-directory-permissions.e2e.test.ts +2 -2
- package/src/file-edit-log.test.ts +287 -0
- package/src/file-edit-log.ts +283 -0
- package/src/format-tables.test.ts +101 -0
- package/src/format-tables.ts +75 -0
- package/src/gateway-proxy-reconnect.e2e.test.ts +38 -8
- package/src/gateway-proxy.e2e.test.ts +5 -5
- package/src/git-worktree-core.ts +247 -20
- package/src/ipc-tools-plugin.ts +75 -5
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +188 -97
- package/src/kimaki-opencode-plugin.ts +7 -3
- package/src/kimaki-workspace-adaptor.ts +40 -11
- package/src/logger.ts +13 -4
- package/src/markdown.test.ts +13 -3
- package/src/markdown.ts +4 -1
- package/src/message-formatting.test.ts +86 -12
- package/src/message-formatting.ts +19 -7
- package/src/oauth-rotation-shared.ts +10 -0
- package/src/openai-auth-plugin.ts +1 -0
- package/src/openai-auth-state.ts +1 -0
- package/src/opencode-command.test.ts +81 -0
- package/src/opencode.ts +71 -11
- package/src/question-abort-clears-dropdown.e2e.test.ts +109 -0
- package/src/question-text-before-dropdown.e2e.test.ts +67 -0
- package/src/queue-advanced-abort.e2e.test.ts +1 -1
- package/src/queue-advanced-action-buttons.e2e.test.ts +3 -3
- package/src/queue-advanced-e2e-setup.ts +83 -4
- package/src/queue-advanced-footer.e2e.test.ts +47 -294
- package/src/queue-advanced-model-switch.e2e.test.ts +2 -2
- package/src/queue-advanced-permissions-typing.e2e.test.ts +7 -9
- package/src/queue-advanced-question.e2e.test.ts +35 -29
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +2 -2
- package/src/queue-delete-message.e2e.test.ts +1 -1
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +2 -2
- package/src/queue-interrupt-drain.e2e.test.ts +123 -2
- package/src/queue-question-select-drain.e2e.test.ts +32 -157
- package/src/runtime-lifecycle.e2e.test.ts +10 -41
- package/src/schema.sql +14 -1
- package/src/schema.ts +36 -0
- package/src/session-handler/event-stream-state.test.ts +437 -7
- package/src/session-handler/event-stream-state.ts +389 -53
- package/src/session-handler/model-utils.test.ts +415 -0
- package/src/session-handler/model-utils.ts +249 -5
- package/src/session-handler/thread-session-runtime.ts +300 -215
- package/src/session-sleep.test.ts +294 -0
- package/src/session-title-rename.test.ts +9 -48
- package/src/sleep.e2e.test.ts +562 -0
- package/src/startup-time.e2e.test.ts +5 -1
- package/src/store.ts +21 -0
- package/src/system-message.test.ts +86 -6
- package/src/system-message.ts +66 -7
- package/src/task-runner.ts +136 -2
- package/src/task-schedule.test.ts +76 -0
- package/src/task-schedule.ts +95 -0
- package/src/thread-message-queue.e2e.test.ts +10 -202
- package/src/undo-redo.e2e.test.ts +2 -2
- package/src/voice-attachment.ts +29 -1
- package/src/voice-message.e2e.test.ts +10 -10
- package/src/voice.test.ts +63 -29
- package/src/worktree-clone-isolation.e2e.test.ts +213 -0
- package/src/worktree-lifecycle.e2e.test.ts +29 -9
- package/src/worktrees.test.ts +505 -64
- package/src/worktrees.ts +257 -603
- package/src/xai-auth-plugin.ts +1 -0
- package/src/xai-auth-state.ts +1 -0
- package/dist/message-finish-field.e2e.test.js +0 -169
- package/dist/subagent-rate-limit-plugin.js +0 -184
- package/src/message-finish-field.e2e.test.ts +0 -196
- package/src/subagent-rate-limit-plugin.ts +0 -228
|
@@ -165,6 +165,119 @@ export function didQuestionQueueHandoffSinceLatestQuestionAsked({
|
|
|
165
165
|
return false
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
export type DerivedUnansweredQuestion = {
|
|
169
|
+
id: string
|
|
170
|
+
questions: Array<{
|
|
171
|
+
question: string
|
|
172
|
+
header: string
|
|
173
|
+
options: Array<{
|
|
174
|
+
label: string
|
|
175
|
+
description: string
|
|
176
|
+
}>
|
|
177
|
+
multiple?: boolean
|
|
178
|
+
}>
|
|
179
|
+
tool?: {
|
|
180
|
+
messageID: string
|
|
181
|
+
callID: string
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// OpenCode emits question.asked when the tool starts, often before the
|
|
186
|
+
// preceding text part gets time.end. Discord must wait for that end event
|
|
187
|
+
// or the question UI posts first and the text dumps later.
|
|
188
|
+
export function isAssistantTextReadyForQuestion({
|
|
189
|
+
events,
|
|
190
|
+
sessionId,
|
|
191
|
+
messageId,
|
|
192
|
+
upToIndex,
|
|
193
|
+
}: {
|
|
194
|
+
events: EventBufferEntry[]
|
|
195
|
+
sessionId: string
|
|
196
|
+
messageId: string
|
|
197
|
+
upToIndex?: number
|
|
198
|
+
}): boolean {
|
|
199
|
+
const end = upToIndex ?? events.length - 1
|
|
200
|
+
for (let i = end; i >= 0; i--) {
|
|
201
|
+
const event = events[i]?.event
|
|
202
|
+
if (!event || event.type !== 'message.part.updated') {
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
const part = event.properties.part
|
|
206
|
+
if (part.sessionID !== sessionId) {
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
if (part.messageID !== messageId) {
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
if (part.type !== 'text') {
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
return Boolean(part.time?.end)
|
|
216
|
+
}
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function deriveLatestUnansweredQuestion({
|
|
221
|
+
events,
|
|
222
|
+
sessionId,
|
|
223
|
+
upToIndex,
|
|
224
|
+
}: {
|
|
225
|
+
events: EventBufferEntry[]
|
|
226
|
+
sessionId: string
|
|
227
|
+
upToIndex?: number
|
|
228
|
+
}): DerivedUnansweredQuestion | undefined {
|
|
229
|
+
const end = upToIndex ?? events.length - 1
|
|
230
|
+
for (let i = end; i >= 0; i--) {
|
|
231
|
+
const entry = events[i]
|
|
232
|
+
if (!entry) {
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
const event = entry.event
|
|
236
|
+
if (getEventBufferSessionId(event) !== sessionId) {
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
if (event.type === 'question.replied' || event.type === 'question.rejected') {
|
|
240
|
+
return undefined
|
|
241
|
+
}
|
|
242
|
+
if (event.type === 'message.part.updated') {
|
|
243
|
+
const part = event.properties.part
|
|
244
|
+
if (
|
|
245
|
+
part.type === 'tool'
|
|
246
|
+
&& part.tool === 'question'
|
|
247
|
+
&& (part.state.status === 'error' || part.state.status === 'completed')
|
|
248
|
+
) {
|
|
249
|
+
return undefined
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (event.type === 'question.asked') {
|
|
253
|
+
const messageId = event.properties.tool?.messageID
|
|
254
|
+
const latestUserMessage = getLatestUserMessage({
|
|
255
|
+
events,
|
|
256
|
+
sessionId,
|
|
257
|
+
upToIndex: end,
|
|
258
|
+
})
|
|
259
|
+
if (
|
|
260
|
+
messageId
|
|
261
|
+
&& latestUserMessage
|
|
262
|
+
&& !isAssistantMessageInLatestUserTurn({
|
|
263
|
+
events,
|
|
264
|
+
sessionId,
|
|
265
|
+
messageId,
|
|
266
|
+
upToIndex: end,
|
|
267
|
+
})
|
|
268
|
+
) {
|
|
269
|
+
return undefined
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
id: event.properties.id,
|
|
273
|
+
questions: event.properties.questions,
|
|
274
|
+
tool: event.properties.tool,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return undefined
|
|
279
|
+
}
|
|
280
|
+
|
|
168
281
|
export function derivePendingPermissionRequests({
|
|
169
282
|
events,
|
|
170
283
|
sessionId,
|
|
@@ -337,73 +450,162 @@ function emptyTurnTokenUsage(): TurnTokenUsage {
|
|
|
337
450
|
}
|
|
338
451
|
}
|
|
339
452
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
453
|
+
function addAssistantTokens({
|
|
454
|
+
usage,
|
|
455
|
+
message,
|
|
456
|
+
}: {
|
|
457
|
+
usage: TurnTokenUsage
|
|
458
|
+
message: AssistantMessage
|
|
459
|
+
}): void {
|
|
460
|
+
if (message.tokens) {
|
|
461
|
+
usage.input += message.tokens.input
|
|
462
|
+
usage.output += message.tokens.output
|
|
463
|
+
usage.reasoning += message.tokens.reasoning
|
|
464
|
+
usage.cacheRead += message.tokens.cache.read
|
|
465
|
+
usage.cacheWrite += message.tokens.cache.write
|
|
466
|
+
usage.total += message.tokens.total ?? getTokenTotal(message.tokens)
|
|
467
|
+
}
|
|
468
|
+
usage.cost += message.cost
|
|
469
|
+
usage.model = message.modelID
|
|
470
|
+
usage.providerID = message.providerID
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function sumAssistantMessages({
|
|
474
|
+
messages,
|
|
475
|
+
userMessageId,
|
|
476
|
+
}: {
|
|
477
|
+
messages: Map<string, AssistantMessage>
|
|
478
|
+
userMessageId?: string
|
|
479
|
+
}): TurnTokenUsage {
|
|
480
|
+
if (messages.size === 0) {
|
|
481
|
+
return {
|
|
482
|
+
...emptyTurnTokenUsage(),
|
|
483
|
+
userMessageId,
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const usage = emptyTurnTokenUsage()
|
|
487
|
+
usage.userMessageId = userMessageId
|
|
488
|
+
usage.assistantMessageCount = messages.size
|
|
489
|
+
for (const message of messages.values()) {
|
|
490
|
+
addAssistantTokens({ usage, message })
|
|
491
|
+
}
|
|
492
|
+
return usage
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function collectAssistantMessages({
|
|
345
496
|
events,
|
|
346
497
|
sessionId,
|
|
347
498
|
upToIndex,
|
|
499
|
+
parentID,
|
|
348
500
|
}: {
|
|
349
501
|
events: EventBufferEntry[]
|
|
350
502
|
sessionId: string
|
|
351
|
-
upToIndex
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
events,
|
|
355
|
-
sessionId,
|
|
356
|
-
upToIndex,
|
|
357
|
-
})
|
|
358
|
-
if (!latestUserMessage) {
|
|
359
|
-
return emptyTurnTokenUsage()
|
|
360
|
-
}
|
|
361
|
-
const end = upToIndex ?? events.length - 1
|
|
503
|
+
upToIndex: number
|
|
504
|
+
parentID?: string
|
|
505
|
+
}): Map<string, AssistantMessage> {
|
|
362
506
|
const latestByMessageId = new Map<string, AssistantMessage>()
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (!entry) {
|
|
367
|
-
continue
|
|
368
|
-
}
|
|
369
|
-
const event = entry.event
|
|
370
|
-
if (event.type !== 'message.updated') {
|
|
507
|
+
for (let i = 0; i <= upToIndex; i++) {
|
|
508
|
+
const event = events[i]?.event
|
|
509
|
+
if (event?.type !== 'message.updated') {
|
|
371
510
|
continue
|
|
372
511
|
}
|
|
373
512
|
const info = event.properties.info
|
|
374
513
|
if (info.sessionID !== sessionId || info.role !== 'assistant') {
|
|
375
514
|
continue
|
|
376
515
|
}
|
|
377
|
-
if (info.parentID !==
|
|
516
|
+
if (parentID && info.parentID !== parentID) {
|
|
378
517
|
continue
|
|
379
518
|
}
|
|
380
519
|
latestByMessageId.set(info.id, info)
|
|
381
520
|
}
|
|
521
|
+
return latestByMessageId
|
|
522
|
+
}
|
|
382
523
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
524
|
+
function getSessionInfoTokenUsage({
|
|
525
|
+
events,
|
|
526
|
+
sessionId,
|
|
527
|
+
upToIndex,
|
|
528
|
+
}: {
|
|
529
|
+
events: EventBufferEntry[]
|
|
530
|
+
sessionId: string
|
|
531
|
+
upToIndex: number
|
|
532
|
+
}): TurnTokenUsage | undefined {
|
|
533
|
+
for (let i = upToIndex; i >= 0; i--) {
|
|
534
|
+
const event = events[i]?.event
|
|
535
|
+
if (event?.type !== 'session.updated' && event?.type !== 'session.created') {
|
|
536
|
+
continue
|
|
387
537
|
}
|
|
538
|
+
const info = event.properties.info
|
|
539
|
+
if (info.id !== sessionId) {
|
|
540
|
+
continue
|
|
541
|
+
}
|
|
542
|
+
if (!info.tokens) {
|
|
543
|
+
continue
|
|
544
|
+
}
|
|
545
|
+
const usage = emptyTurnTokenUsage()
|
|
546
|
+
usage.input = info.tokens.input
|
|
547
|
+
usage.output = info.tokens.output
|
|
548
|
+
usage.reasoning = info.tokens.reasoning
|
|
549
|
+
usage.cacheRead = info.tokens.cache.read
|
|
550
|
+
usage.cacheWrite = info.tokens.cache.write
|
|
551
|
+
usage.total = getTokenTotal(info.tokens)
|
|
552
|
+
usage.cost = info.cost ?? 0
|
|
553
|
+
usage.model = info.model?.id
|
|
554
|
+
usage.providerID = info.model?.providerID
|
|
555
|
+
return usage.total > 0 || usage.cost > 0 ? usage : undefined
|
|
388
556
|
}
|
|
557
|
+
return undefined
|
|
558
|
+
}
|
|
389
559
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
560
|
+
// Latest billed token snapshot for the current user turn.
|
|
561
|
+
// Sums the last message.updated tokens per assistant message id so streaming
|
|
562
|
+
// updates are not double-counted. Scoped to sessionId so subagent idles
|
|
563
|
+
// report their own usage. Child task sessions often have no user message in
|
|
564
|
+
// the buffer; fall back to all assistant messages, then Session.tokens on
|
|
565
|
+
// session.updated (OpenCode projects per-session usage there).
|
|
566
|
+
export function getLatestTurnTokenUsage({
|
|
567
|
+
events,
|
|
568
|
+
sessionId,
|
|
569
|
+
upToIndex,
|
|
570
|
+
}: {
|
|
571
|
+
events: EventBufferEntry[]
|
|
572
|
+
sessionId: string
|
|
573
|
+
upToIndex?: number
|
|
574
|
+
}): TurnTokenUsage {
|
|
575
|
+
const end = upToIndex ?? events.length - 1
|
|
576
|
+
const latestUserMessage = getLatestUserMessage({
|
|
577
|
+
events,
|
|
578
|
+
sessionId,
|
|
579
|
+
upToIndex,
|
|
580
|
+
})
|
|
581
|
+
if (latestUserMessage) {
|
|
582
|
+
return sumAssistantMessages({
|
|
583
|
+
messages: collectAssistantMessages({
|
|
584
|
+
events,
|
|
585
|
+
sessionId,
|
|
586
|
+
upToIndex: end,
|
|
587
|
+
parentID: latestUserMessage.id,
|
|
588
|
+
}),
|
|
589
|
+
userMessageId: latestUserMessage.id,
|
|
590
|
+
})
|
|
405
591
|
}
|
|
406
|
-
|
|
592
|
+
|
|
593
|
+
const sessionAssistants = sumAssistantMessages({
|
|
594
|
+
messages: collectAssistantMessages({
|
|
595
|
+
events,
|
|
596
|
+
sessionId,
|
|
597
|
+
upToIndex: end,
|
|
598
|
+
}),
|
|
599
|
+
})
|
|
600
|
+
if (sessionAssistants.total > 0 || sessionAssistants.assistantMessageCount > 0) {
|
|
601
|
+
return sessionAssistants
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return getSessionInfoTokenUsage({
|
|
605
|
+
events,
|
|
606
|
+
sessionId,
|
|
607
|
+
upToIndex: end,
|
|
608
|
+
}) ?? emptyTurnTokenUsage()
|
|
407
609
|
}
|
|
408
610
|
|
|
409
611
|
function findFirstUserMessageIndex({
|
|
@@ -469,8 +671,31 @@ function subtractTokenUsage({
|
|
|
469
671
|
}
|
|
470
672
|
}
|
|
471
673
|
|
|
674
|
+
function findFirstSessionEventIndex({
|
|
675
|
+
events,
|
|
676
|
+
sessionId,
|
|
677
|
+
upToIndex,
|
|
678
|
+
}: {
|
|
679
|
+
events: EventBufferEntry[]
|
|
680
|
+
sessionId: string
|
|
681
|
+
upToIndex: number
|
|
682
|
+
}): number {
|
|
683
|
+
for (let i = 0; i <= upToIndex; i++) {
|
|
684
|
+
const event = events[i]?.event
|
|
685
|
+
if (!event) {
|
|
686
|
+
continue
|
|
687
|
+
}
|
|
688
|
+
if (getEventBufferSessionId(event) === sessionId) {
|
|
689
|
+
return i
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return 0
|
|
693
|
+
}
|
|
694
|
+
|
|
472
695
|
// Tokens billed since the previous session.idle in this user turn.
|
|
473
696
|
// Survives process restart because both idles stay in the event buffer.
|
|
697
|
+
// Child task sessions may have no user message.updated; scope from the first
|
|
698
|
+
// event for that sessionId instead so their tokens still emit.
|
|
474
699
|
export function getIdleTokenUsageDelta({
|
|
475
700
|
events,
|
|
476
701
|
sessionId,
|
|
@@ -485,16 +710,22 @@ export function getIdleTokenUsageDelta({
|
|
|
485
710
|
sessionId,
|
|
486
711
|
upToIndex: idleEventIndex,
|
|
487
712
|
})
|
|
488
|
-
if (
|
|
713
|
+
if (current.total <= 0) {
|
|
489
714
|
return undefined
|
|
490
715
|
}
|
|
491
|
-
const firstUserMessageIndex =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
716
|
+
const firstUserMessageIndex = current.userMessageId
|
|
717
|
+
? findFirstUserMessageIndex({
|
|
718
|
+
events,
|
|
719
|
+
userMessageId: current.userMessageId,
|
|
720
|
+
upToIndex: idleEventIndex,
|
|
721
|
+
})
|
|
722
|
+
: findFirstSessionEventIndex({
|
|
723
|
+
events,
|
|
724
|
+
sessionId,
|
|
725
|
+
upToIndex: idleEventIndex,
|
|
726
|
+
})
|
|
496
727
|
if (firstUserMessageIndex === undefined) {
|
|
497
|
-
return
|
|
728
|
+
return current
|
|
498
729
|
}
|
|
499
730
|
const previousIdleIndex = findPreviousIdleIndexInTurn({
|
|
500
731
|
events,
|
|
@@ -503,7 +734,7 @@ export function getIdleTokenUsageDelta({
|
|
|
503
734
|
beforeIndex: idleEventIndex,
|
|
504
735
|
})
|
|
505
736
|
if (previousIdleIndex === undefined) {
|
|
506
|
-
return current
|
|
737
|
+
return current
|
|
507
738
|
}
|
|
508
739
|
const previous = getLatestTurnTokenUsage({
|
|
509
740
|
events,
|
|
@@ -945,3 +1176,108 @@ export function getDerivedSubagentSessions({
|
|
|
945
1176
|
|
|
946
1177
|
return sessions
|
|
947
1178
|
}
|
|
1179
|
+
|
|
1180
|
+
function getParentIdFromSessionEvent(event: EventBufferEvent): {
|
|
1181
|
+
sessionId: string
|
|
1182
|
+
parentID: string
|
|
1183
|
+
} | undefined {
|
|
1184
|
+
if (event.type !== 'session.created' && event.type !== 'session.updated') {
|
|
1185
|
+
return undefined
|
|
1186
|
+
}
|
|
1187
|
+
const parentID = event.properties.info.parentID
|
|
1188
|
+
if (typeof parentID !== 'string' || parentID.length === 0) {
|
|
1189
|
+
return undefined
|
|
1190
|
+
}
|
|
1191
|
+
return {
|
|
1192
|
+
sessionId: event.properties.info.id,
|
|
1193
|
+
parentID,
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Child sessions of the main thread: task tool metadata.sessionId, plus
|
|
1198
|
+
// session.created/updated parentID (available before task metadata lands).
|
|
1199
|
+
export function getDerivedChildSessionIds({
|
|
1200
|
+
events,
|
|
1201
|
+
mainSessionId,
|
|
1202
|
+
upToIndex,
|
|
1203
|
+
}: {
|
|
1204
|
+
events: EventBufferEntry[]
|
|
1205
|
+
mainSessionId: string
|
|
1206
|
+
upToIndex?: number
|
|
1207
|
+
}): Set<string> {
|
|
1208
|
+
const end = upToIndex ?? events.length - 1
|
|
1209
|
+
const ids = new Set<string>()
|
|
1210
|
+
for (const session of getDerivedSubagentSessions({
|
|
1211
|
+
events,
|
|
1212
|
+
mainSessionId,
|
|
1213
|
+
upToIndex,
|
|
1214
|
+
})) {
|
|
1215
|
+
ids.add(session.childSessionId)
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
let grew = true
|
|
1219
|
+
while (grew) {
|
|
1220
|
+
grew = false
|
|
1221
|
+
for (let i = 0; i <= end; i++) {
|
|
1222
|
+
const event = events[i]?.event
|
|
1223
|
+
if (!event) {
|
|
1224
|
+
continue
|
|
1225
|
+
}
|
|
1226
|
+
const parented = getParentIdFromSessionEvent(event)
|
|
1227
|
+
if (!parented || ids.has(parented.sessionId)) {
|
|
1228
|
+
continue
|
|
1229
|
+
}
|
|
1230
|
+
if (parented.parentID !== mainSessionId && !ids.has(parented.parentID)) {
|
|
1231
|
+
continue
|
|
1232
|
+
}
|
|
1233
|
+
ids.add(parented.sessionId)
|
|
1234
|
+
grew = true
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return ids
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
export function isDerivedChildSession({
|
|
1241
|
+
events,
|
|
1242
|
+
mainSessionId,
|
|
1243
|
+
candidateSessionId,
|
|
1244
|
+
upToIndex,
|
|
1245
|
+
}: {
|
|
1246
|
+
events: EventBufferEntry[]
|
|
1247
|
+
mainSessionId: string
|
|
1248
|
+
candidateSessionId: string
|
|
1249
|
+
upToIndex?: number
|
|
1250
|
+
}): boolean {
|
|
1251
|
+
if (candidateSessionId === mainSessionId) {
|
|
1252
|
+
return false
|
|
1253
|
+
}
|
|
1254
|
+
return getDerivedChildSessionIds({
|
|
1255
|
+
events,
|
|
1256
|
+
mainSessionId,
|
|
1257
|
+
upToIndex,
|
|
1258
|
+
}).has(candidateSessionId)
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
export function getTokenUsageSessionIdsForIdle({
|
|
1262
|
+
events,
|
|
1263
|
+
mainSessionId,
|
|
1264
|
+
idleSessionId,
|
|
1265
|
+
upToIndex,
|
|
1266
|
+
}: {
|
|
1267
|
+
events: EventBufferEntry[]
|
|
1268
|
+
mainSessionId: string
|
|
1269
|
+
idleSessionId: string
|
|
1270
|
+
upToIndex?: number
|
|
1271
|
+
}): string[] {
|
|
1272
|
+
if (idleSessionId !== mainSessionId) {
|
|
1273
|
+
return [idleSessionId]
|
|
1274
|
+
}
|
|
1275
|
+
return [
|
|
1276
|
+
mainSessionId,
|
|
1277
|
+
...getDerivedChildSessionIds({
|
|
1278
|
+
events,
|
|
1279
|
+
mainSessionId,
|
|
1280
|
+
upToIndex,
|
|
1281
|
+
}),
|
|
1282
|
+
]
|
|
1283
|
+
}
|