@nifrajs/agent-app 4.0.0 → 5.0.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.
@@ -12,6 +12,7 @@
12
12
 
13
13
  import {
14
14
  type AgentEvent,
15
+ type AgentExtensionEventType,
15
16
  type AgentSessionSnapshot,
16
17
  type AgentSessionStatus,
17
18
  type ApprovalLifecycleState,
@@ -158,6 +159,39 @@ export interface SessionStoppedView extends EventViewBase {
158
159
  readonly kind: "session.stopped"
159
160
  }
160
161
 
162
+ export interface ReasoningDeltaView extends EventViewBase {
163
+ readonly kind: "reasoning.delta"
164
+ readonly turnId: string
165
+ readonly itemId?: string
166
+ readonly chars: number
167
+ }
168
+
169
+ /** A plan as counts per status: its entries' text stays with the event. */
170
+ export interface PlanUpdatedView extends EventViewBase {
171
+ readonly kind: "plan.updated"
172
+ readonly turnId: string
173
+ readonly entries: number
174
+ readonly inProgress: number
175
+ readonly completed: number
176
+ }
177
+
178
+ export interface ContextUsageView extends EventViewBase {
179
+ readonly kind: "context.usage"
180
+ readonly turnId?: string
181
+ readonly usedTokens: number
182
+ readonly windowTokens?: number
183
+ /** `usedTokens` over `windowTokens`, capped at 1, when the window is known. */
184
+ readonly fill?: number
185
+ readonly estimated: boolean
186
+ }
187
+
188
+ /** A backend's `x-<vendor>.<name>` event: its type and turn, never its data. */
189
+ export interface ExtensionEventView extends EventViewBase {
190
+ readonly kind: AgentExtensionEventType
191
+ readonly turnId?: string
192
+ readonly hasData: boolean
193
+ }
194
+
161
195
  export type AgentEventView =
162
196
  | SessionLifecycleView
163
197
  | TurnStartedView
@@ -173,6 +207,10 @@ export type AgentEventView =
173
207
  | ExtensionReloadedView
174
208
  | SessionFailedView
175
209
  | SessionStoppedView
210
+ | ReasoningDeltaView
211
+ | PlanUpdatedView
212
+ | ContextUsageView
213
+ | ExtensionEventView
176
214
 
177
215
  /** Project one protocol event to its content-free view. Total over the event union - never returns undefined. */
178
216
  export function toEventView(event: AgentEvent): AgentEventView {
@@ -196,6 +234,38 @@ export function toEventView(event: AgentEvent): AgentEventView {
196
234
  turnId: event.turnId,
197
235
  chars: event.text.length,
198
236
  })
237
+ case "reasoning.delta":
238
+ return Object.freeze({
239
+ ...base,
240
+ kind: event.type,
241
+ turnId: event.turnId,
242
+ ...(event.itemId === undefined ? {} : { itemId: event.itemId }),
243
+ chars: event.text.length,
244
+ })
245
+ case "plan.updated":
246
+ return Object.freeze({
247
+ ...base,
248
+ kind: event.type,
249
+ turnId: event.turnId,
250
+ entries: event.entries.length,
251
+ inProgress: event.entries.filter((entry) => entry.status === "in_progress").length,
252
+ completed: event.entries.filter((entry) => entry.status === "completed").length,
253
+ })
254
+ case "context.usage":
255
+ return Object.freeze({
256
+ ...base,
257
+ kind: event.type,
258
+ ...(event.turnId === undefined ? {} : { turnId: event.turnId }),
259
+ usedTokens: event.usedTokens,
260
+ ...(event.windowTokens === undefined
261
+ ? {}
262
+ : {
263
+ windowTokens: event.windowTokens,
264
+ // A zero window cannot hold anything, so any use fills it.
265
+ fill: event.windowTokens > 0 ? Math.min(1, event.usedTokens / event.windowTokens) : 1,
266
+ }),
267
+ estimated: event.estimated === true,
268
+ })
199
269
  case "tool.started":
200
270
  return Object.freeze({
201
271
  ...base,
@@ -277,6 +347,13 @@ export function toEventView(event: AgentEvent): AgentEventView {
277
347
  })
278
348
  case "session.stopped":
279
349
  return Object.freeze({ ...base, kind: event.type })
350
+ default:
351
+ return Object.freeze({
352
+ ...base,
353
+ kind: event.type,
354
+ ...(event.turnId === undefined ? {} : { turnId: event.turnId }),
355
+ hasData: event.data !== undefined,
356
+ })
280
357
  }
281
358
  }
282
359
 
@@ -525,6 +602,20 @@ export class OrderedEventBuffer {
525
602
  return this.next
526
603
  }
527
604
 
605
+ /**
606
+ * Release every held event in seq order, counting the seqs still missing between them on
607
+ * {@link dropped}. For a stream that has ended, so no gap can fill any more.
608
+ */
609
+ flush(): readonly AgentEventView[] {
610
+ const held = [...this.pending.values()].sort((a, b) => a.seq - b.seq)
611
+ this.pending.clear()
612
+ for (const view of held) {
613
+ if (this.next !== undefined) this.droppedCount += view.seq - this.next
614
+ this.next = view.seq + 1
615
+ }
616
+ return held
617
+ }
618
+
528
619
  offer(view: AgentEventView): readonly AgentEventView[] {
529
620
  if (this.next === undefined) this.next = view.seq
530
621
  if (view.seq < this.next) return [] // already delivered