dsh-ntr 0.3.2 → 0.3.4

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/src/index.ts CHANGED
@@ -277,6 +277,31 @@ export function apply(ctx: Context, config?: unknown): void {
277
277
  })
278
278
  }
279
279
 
280
+ const wireAttachments = (injectedCtx?: Context): void => {
281
+ const c = injectedCtx ?? ctx
282
+ let attachments: unknown
283
+ try {
284
+ if (typeof c.get === 'function') {
285
+ attachments = c.get('attachments') ?? c.get('attachmentService')
286
+ }
287
+ } catch {}
288
+ try {
289
+ if (attachments === undefined) {
290
+ attachments = (c as unknown as Record<string, unknown>).attachments ?? (c as unknown as Record<string, unknown>).attachmentService
291
+ }
292
+ } catch {}
293
+ if (attachments !== undefined) {
294
+ manager.setAttachmentResolver(attachments)
295
+ } else {
296
+ manager.setAttachmentResolver(c)
297
+ }
298
+ }
299
+ wireAttachments()
300
+ if (typeof ctx.inject === 'function') {
301
+ ctx.inject(['attachments'], injectedCtx => { wireAttachments(injectedCtx) })
302
+ ctx.inject(['attachmentService'], injectedCtx => { wireAttachments(injectedCtx) })
303
+ }
304
+
280
305
  /** Registration handle: callable disposer, plus `replace` to re-announce routes. */
281
306
  interface LlmAdapterHandle {
282
307
  (): void
package/src/protocol.ts CHANGED
@@ -458,6 +458,18 @@ export type AcpEvent =
458
458
  */
459
459
  | { type: 'permission/synced'; requestIds: string[] }
460
460
  | { type: 'turn/start'; sessionId: string; turnId: string; text: string }
461
+ | {
462
+ type: 'turn/metrics'
463
+ /** ACP session id (the store keys live burst state by it). */
464
+ sessionId: string
465
+ /** DSH chat session id (routing/context only). */
466
+ dshSessionId: string
467
+ turnId: string
468
+ /** Session aggregate INCLUDING the current turn when measurable. */
469
+ outputTokens: number
470
+ burstMs: number
471
+ final: boolean
472
+ }
461
473
  | { type: 'turn/end'; sessionId: string; turnId: string; stopReason?: string; error?: string }
462
474
  /**
463
475
  * A DSH chat conversation was locked to its agent at the start of its first
@@ -481,6 +493,14 @@ export interface PermissionOptionView {
481
493
 
482
494
  export type PermissionOutcome = { outcome: 'selected'; optionId: string } | { outcome: 'cancelled' }
483
495
 
496
+ /** Plugin-side measurable burst aggregate for one ACP session (not host turn timings). */
497
+ export interface SessionBurst {
498
+ /** Sum of estimated output tokens over measurable turns. */
499
+ outputTokens: number
500
+ /** Sum of within-burst chunk gaps over those turns; tool/approval/long pauses excluded. */
501
+ burstMs: number
502
+ }
503
+
484
504
  /** Derived session snapshot for replay/reconnect (source of truth stays in the transcript). */
485
505
  export interface SessionView {
486
506
  sessionId: string
@@ -494,6 +514,8 @@ export interface SessionView {
494
514
  availableCommands?: Array<{ name: string; description?: string; [key: string]: unknown }>
495
515
  plan?: unknown[]
496
516
  usage?: { used: number; size: number; cost?: { amount: number; currency: string } }
517
+ /** Measurable burst aggregate over this session's turns; absent until measured. */
518
+ burst?: SessionBurst
497
519
  running: boolean
498
520
  closed: boolean
499
521
  /**
@@ -569,6 +591,8 @@ export interface ChatSessionView {
569
591
  * browser live-patches this row from the update frames (see AGENTS/ui.md).
570
592
  */
571
593
  usage?: SessionView['usage']
594
+ /** Measurable burst aggregate over this session's turns; absent until measured. */
595
+ burst?: SessionView['burst']
572
596
  /** The agent process died; the session awaits a `session/load` resume. */
573
597
  released?: boolean
574
598
  /** The agent is running a turn on this session right now. */
package/src/tps.ts ADDED
@@ -0,0 +1,40 @@
1
+ /** ACP `usage_update` is context occupancy, not request tokens; this is the plugin's 4-chars/token OUTPUT estimate (tool args excluded). */
2
+ export const OUTPUT_CHARS_PER_TOKEN = 4
3
+ export const BURST_GAP_MS = 1000
4
+
5
+ export interface TurnTpsSnapshot {
6
+ outputTokens: number
7
+ burstMs: number
8
+ }
9
+
10
+ export class TurnTpsTracker {
11
+ private readonly gapMs: number
12
+ private lastProseAt: number | undefined
13
+ private outputChars = 0
14
+ private burstMs = 0
15
+
16
+ constructor(gapMs = BURST_GAP_MS) {
17
+ this.gapMs = gapMs
18
+ }
19
+
20
+ onProse(text: string, at: number): void {
21
+ if (text === '') return
22
+ if (this.lastProseAt !== undefined) {
23
+ const gap = at - this.lastProseAt
24
+ if (gap <= this.gapMs) this.burstMs += gap
25
+ }
26
+ this.lastProseAt = at
27
+ this.outputChars += text.length
28
+ }
29
+
30
+ onBreak(): void {
31
+ this.lastProseAt = undefined
32
+ }
33
+
34
+ snapshot(): TurnTpsSnapshot {
35
+ return {
36
+ outputTokens: Math.round(this.outputChars / OUTPUT_CHARS_PER_TOKEN),
37
+ burstMs: this.burstMs,
38
+ }
39
+ }
40
+ }