agl 22.0.4 → 23.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 @@ import {
12
12
  controllerBuiltinCommands,
13
13
  controllerMaxToolEventBytes,
14
14
  controllerMaxLiveMessageEventBytes,
15
+ controllerLiveMessageDeltaTargetBytes,
15
16
  controllerInitialMessageBundleLimit,
16
17
  controllerMaximumTranscriptBytes,
17
18
  controllerLiveToolPayloadNotice,
@@ -26,7 +27,9 @@ import {
26
27
  type IControllerMessagePage,
27
28
  type IControllerMessageStreamCursor,
28
29
  type IControllerPendingPrompt,
30
+ type IControllerReasoningDelta,
29
31
  type IControllerReasoningUpdate,
32
+ type IControllerTextDelta,
30
33
  type IControllerTextUpdate,
31
34
  type IControllerTodo,
32
35
  type TControllerModelChoice,
@@ -540,15 +543,24 @@ interface ILatestToolExecution {
540
543
  interface ILatestReasoningUpdate {
541
544
  projectId: string;
542
545
  update: IControllerReasoningUpdate;
546
+ textUtf8Bytes: number;
543
547
  bytes?: number;
544
548
  }
545
549
 
546
550
  interface ILatestTextUpdate {
547
551
  projectId: string;
548
552
  update: IControllerTextUpdate;
553
+ textUtf8Bytes: number;
549
554
  bytes?: number;
550
555
  }
551
556
 
557
+ type TControllerMessageDelta = IControllerReasoningDelta | IControllerTextDelta;
558
+
559
+ interface IControllerTextDeltaFragment {
560
+ delta: string;
561
+ utf8Bytes: number;
562
+ }
563
+
552
564
  type TControllerToolExecutionSnapshot = Omit<
553
565
  IControllerToolExecution,
554
566
  'revision' | 'streamEpoch'
@@ -558,6 +570,103 @@ const historyLimitedNotice = '[earlier message content omitted: this transcript
558
570
  const historyLimitedDigestPrefix = 'history-limited:';
559
571
  const liveToolExecutionBytes = controllerMaxToolEventBytes - (8 * 1024);
560
572
 
573
+ const controllerEventMessageDelta = (
574
+ eventArg: IControllerEvent,
575
+ ): TControllerMessageDelta | undefined => eventArg.reasoningDelta ?? eventArg.textDelta;
576
+
577
+ const controllerTextAfterAppendUtf8Bytes = (
578
+ textArg: string,
579
+ textUtf8BytesArg: number,
580
+ deltaArg: string,
581
+ ): number | undefined => {
582
+ // Separately encoded surrogate halves cost six bytes; after append the pair costs four.
583
+ const boundaryCorrection = (
584
+ textArg.length > 0
585
+ && deltaArg.length > 0
586
+ && textArg.charCodeAt(textArg.length - 1) >= 0xD800
587
+ && textArg.charCodeAt(textArg.length - 1) <= 0xDBFF
588
+ && deltaArg.charCodeAt(0) >= 0xDC00
589
+ && deltaArg.charCodeAt(0) <= 0xDFFF
590
+ ) ? -2 : 0;
591
+ const result = textUtf8BytesArg + Buffer.byteLength(deltaArg, 'utf8') + boundaryCorrection;
592
+ return Number.isSafeInteger(result) && result >= 0 ? result : undefined;
593
+ };
594
+
595
+ const fragmentControllerTextDelta = (valueArg: string): IControllerTextDeltaFragment[] => {
596
+ const fragments: IControllerTextDeltaFragment[] = [];
597
+ let delta = '';
598
+ let utf8Bytes = 0;
599
+ for (const codePoint of valueArg) {
600
+ const codePointBytes = Buffer.byteLength(codePoint, 'utf8');
601
+ if (
602
+ delta !== ''
603
+ && utf8Bytes + codePointBytes > controllerLiveMessageDeltaTargetBytes
604
+ ) {
605
+ fragments.push({ delta, utf8Bytes });
606
+ delta = '';
607
+ utf8Bytes = 0;
608
+ }
609
+ delta += codePoint;
610
+ utf8Bytes += codePointBytes;
611
+ }
612
+ if (delta !== '') fragments.push({ delta, utf8Bytes });
613
+ return fragments;
614
+ };
615
+
616
+ const controllerMessageDeltaPendingKey = (eventArg: IControllerEvent): string | undefined => {
617
+ const delta = controllerEventMessageDelta(eventArg);
618
+ if (!delta) return undefined;
619
+ return JSON.stringify([
620
+ eventArg.type,
621
+ eventArg.harnessId ?? null,
622
+ eventArg.projectId ?? null,
623
+ controllerRuntimeIdKey(delta.sessionId),
624
+ controllerRuntimeIdKey(delta.partId),
625
+ ]);
626
+ };
627
+
628
+ const controllerMessageDeltaEventsAreContiguous = (
629
+ previousEventArg: IControllerEvent,
630
+ eventArg: IControllerEvent,
631
+ ): boolean => {
632
+ const previous = controllerEventMessageDelta(previousEventArg);
633
+ const delta = controllerEventMessageDelta(eventArg);
634
+ if (!previous || !delta) return false;
635
+ return previousEventArg.type === eventArg.type
636
+ && previousEventArg.harnessId === eventArg.harnessId
637
+ && previousEventArg.projectId === eventArg.projectId
638
+ && (previousEventArg.sessionId
639
+ ? eventArg.sessionId !== undefined
640
+ && controllerRuntimeIdKey(previousEventArg.sessionId)
641
+ === controllerRuntimeIdKey(eventArg.sessionId)
642
+ : eventArg.sessionId === undefined)
643
+ && controllerRuntimeIdKey(previous.sessionId) === controllerRuntimeIdKey(delta.sessionId)
644
+ && controllerRuntimeIdKey(previous.messageId) === controllerRuntimeIdKey(delta.messageId)
645
+ && controllerRuntimeIdKey(previous.partId) === controllerRuntimeIdKey(delta.partId)
646
+ && previous.streamEpoch === delta.streamEpoch
647
+ && previous.order?.messageIndex === delta.order?.messageIndex
648
+ && previous.order?.partIndex === delta.order?.partIndex
649
+ && previous.textUtf8Bytes === delta.baseTextUtf8Bytes
650
+ && delta.revision > previous.revision;
651
+ };
652
+
653
+ const mergeControllerMessageDeltaEvents = (
654
+ previousEventArg: IControllerEvent,
655
+ eventArg: IControllerEvent,
656
+ ): IControllerEvent | undefined => {
657
+ if (!controllerMessageDeltaEventsAreContiguous(previousEventArg, eventArg)) return undefined;
658
+ const previous = controllerEventMessageDelta(previousEventArg)!;
659
+ const delta = controllerEventMessageDelta(eventArg)!;
660
+ const mergedDelta = {
661
+ ...delta,
662
+ delta: `${previous.delta}${delta.delta}`,
663
+ baseTextUtf8Bytes: previous.baseTextUtf8Bytes,
664
+ };
665
+ return eventArg.reasoningDelta
666
+ ? { ...eventArg, reasoningDelta: mergedDelta }
667
+ : { ...eventArg, textDelta: mergedDelta };
668
+ };
669
+
561
670
  const markHistoryLimitedDigest = (digestArg: string): string => (
562
671
  digestArg.startsWith(historyLimitedDigestPrefix)
563
672
  ? digestArg
@@ -9992,6 +10101,10 @@ export class OpenCodeController {
9992
10101
  this.handleFlexTextUpdate(eventArg);
9993
10102
  return;
9994
10103
  }
10104
+ if (eventArg.type === 'part.delta') {
10105
+ this.handleFlexPartDelta(eventArg);
10106
+ return;
10107
+ }
9995
10108
  if (eventArg.type === 'permissions.changed') {
9996
10109
  if (this.autoAcceptPermissions || this.hasFlexYoloSessions()) {
9997
10110
  this.queueFlexAutoAcceptSweep(eventArg.scopeId, eventArg.sessionId);
@@ -10413,7 +10526,12 @@ export class OpenCodeController {
10413
10526
  const bytes = Buffer.byteLength(JSON.stringify(eventArg), 'utf8');
10414
10527
  return {
10415
10528
  tool: eventArg.toolExecution ? bytes : 0,
10416
- message: eventArg.reasoningUpdate || eventArg.textUpdate ? bytes : 0,
10529
+ message: eventArg.reasoningUpdate
10530
+ || eventArg.reasoningDelta
10531
+ || eventArg.textUpdate
10532
+ || eventArg.textDelta
10533
+ ? bytes
10534
+ : 0,
10417
10535
  };
10418
10536
  }
10419
10537
 
@@ -10573,7 +10691,12 @@ export class OpenCodeController {
10573
10691
  this.latestFlexTextUpdates.clear();
10574
10692
  this.latestFlexMessageUpdateBytes = 0;
10575
10693
  for (const [key, event] of this.pendingFlexControllerEvents) {
10576
- if (event.reasoningUpdate || event.textUpdate) this.deletePendingFlexControllerEvent(key);
10694
+ if (
10695
+ event.reasoningUpdate
10696
+ || event.reasoningDelta
10697
+ || event.textUpdate
10698
+ || event.textDelta
10699
+ ) this.deletePendingFlexControllerEvent(key);
10577
10700
  }
10578
10701
  const timestamp = Date.now();
10579
10702
  const barrier: IControllerEvent = {
@@ -10659,11 +10782,19 @@ export class OpenCodeController {
10659
10782
  for (const [key, event] of this.pendingFlexControllerEvents) {
10660
10783
  if (
10661
10784
  event.projectId === projectIdArg
10662
- && (event.toolExecution || event.reasoningUpdate || event.textUpdate)
10785
+ && (
10786
+ event.toolExecution
10787
+ || event.reasoningUpdate
10788
+ || event.reasoningDelta
10789
+ || event.textUpdate
10790
+ || event.textDelta
10791
+ )
10663
10792
  && controllerRuntimeIdKey(
10664
10793
  event.toolExecution?.sessionId
10665
10794
  ?? event.reasoningUpdate?.sessionId
10666
- ?? event.textUpdate!.sessionId,
10795
+ ?? event.reasoningDelta?.sessionId
10796
+ ?? event.textUpdate?.sessionId
10797
+ ?? event.textDelta!.sessionId,
10667
10798
  ) === sessionKey
10668
10799
  ) this.deletePendingFlexControllerEvent(key);
10669
10800
  }
@@ -10729,7 +10860,13 @@ export class OpenCodeController {
10729
10860
  for (const [key, event] of this.pendingFlexControllerEvents) {
10730
10861
  if (
10731
10862
  event.projectId === projectIdArg
10732
- && (event.toolExecution || event.reasoningUpdate || event.textUpdate)
10863
+ && (
10864
+ event.toolExecution
10865
+ || event.reasoningUpdate
10866
+ || event.reasoningDelta
10867
+ || event.textUpdate
10868
+ || event.textDelta
10869
+ )
10733
10870
  ) {
10734
10871
  this.deletePendingFlexControllerEvent(key);
10735
10872
  }
@@ -10886,7 +11023,11 @@ export class OpenCodeController {
10886
11023
  }
10887
11024
  this.setLatestFlexReasoningUpdate(
10888
11025
  key,
10889
- { projectId: eventArg.scopeId, update },
11026
+ {
11027
+ projectId: eventArg.scopeId,
11028
+ update,
11029
+ textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
11030
+ },
10890
11031
  eventBytes,
10891
11032
  );
10892
11033
  this.queueFlexControllerEvent(controllerEvent);
@@ -10946,12 +11087,169 @@ export class OpenCodeController {
10946
11087
  }
10947
11088
  this.setLatestFlexTextUpdate(
10948
11089
  key,
10949
- { projectId: eventArg.scopeId, update },
11090
+ {
11091
+ projectId: eventArg.scopeId,
11092
+ update,
11093
+ textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
11094
+ },
10950
11095
  eventBytes,
10951
11096
  );
10952
11097
  this.queueFlexControllerEvent(controllerEvent);
10953
11098
  }
10954
11099
 
11100
+ private handleFlexPartDelta(
11101
+ eventArg: Extract<TFlexChildEvent, { type: 'part.delta' }>,
11102
+ ): void {
11103
+ if (this.lifecycleState !== 'starting' && this.lifecycleState !== 'ready') return;
11104
+ const sourceUpdatedAt = Date.parse(eventArg.timestamp);
11105
+ if (!Number.isFinite(sourceUpdatedAt)) {
11106
+ this.advanceFlexMessageStreamEpoch(true);
11107
+ return;
11108
+ }
11109
+ const sessionId = flexRuntimeId(eventArg.sessionId);
11110
+ const messageId = flexRuntimeId(eventArg.messageId);
11111
+ const partId = flexRuntimeId(eventArg.partId);
11112
+ const order = { messageIndex: eventArg.messageIndex, partIndex: eventArg.partIndex };
11113
+ const key = this.openCodeToolExecutionKey(eventArg.scopeId, { sessionId, partId });
11114
+ const cache = eventArg.partType === 'reasoning'
11115
+ ? this.latestFlexReasoningUpdates
11116
+ : this.latestFlexTextUpdates;
11117
+ const entry = cache.get(key);
11118
+ const previous = entry?.update;
11119
+ if (
11120
+ !entry
11121
+ || !previous
11122
+ || this.flexMessageStreamEpoch === 0
11123
+ || entry.projectId !== eventArg.scopeId
11124
+ || previous.status !== 'running'
11125
+ || previous.streamEpoch !== this.flexMessageStreamEpoch
11126
+ || previous.revision > this.flexMessageStreamRevision
11127
+ || controllerRuntimeIdKey(previous.sessionId) !== controllerRuntimeIdKey(sessionId)
11128
+ || controllerRuntimeIdKey(previous.messageId) !== controllerRuntimeIdKey(messageId)
11129
+ || controllerRuntimeIdKey(previous.partId) !== controllerRuntimeIdKey(partId)
11130
+ || previous.order?.messageIndex !== order.messageIndex
11131
+ || previous.order.partIndex !== order.partIndex
11132
+ || sourceUpdatedAt < previous.sourceUpdatedAt
11133
+ || entry.textUtf8Bytes !== eventArg.baseTextUtf8Bytes
11134
+ ) {
11135
+ this.advanceFlexMessageStreamEpoch(true);
11136
+ return;
11137
+ }
11138
+ const textUtf8Bytes = controllerTextAfterAppendUtf8Bytes(
11139
+ previous.text,
11140
+ entry.textUtf8Bytes,
11141
+ eventArg.delta,
11142
+ );
11143
+ if (
11144
+ textUtf8Bytes === undefined
11145
+ || textUtf8Bytes <= entry.textUtf8Bytes
11146
+ || textUtf8Bytes !== eventArg.textUtf8Bytes
11147
+ ) {
11148
+ this.advanceFlexMessageStreamEpoch(true);
11149
+ return;
11150
+ }
11151
+
11152
+ const revision = this.flexMessageStreamRevision + 1;
11153
+ const update = {
11154
+ ...previous,
11155
+ text: `${previous.text}${eventArg.delta}`,
11156
+ sourceUpdatedAt,
11157
+ revision,
11158
+ streamEpoch: this.flexMessageStreamEpoch,
11159
+ } as IControllerReasoningUpdate | IControllerTextUpdate;
11160
+ const controllerEvent: IControllerEvent = eventArg.partType === 'reasoning'
11161
+ ? {
11162
+ type: 'session.reasoning.delta',
11163
+ harnessId: 'flex',
11164
+ projectId: eventArg.scopeId,
11165
+ sessionId,
11166
+ reasoningDelta: {
11167
+ sessionId,
11168
+ messageId,
11169
+ partId,
11170
+ delta: eventArg.delta,
11171
+ baseTextUtf8Bytes: eventArg.baseTextUtf8Bytes,
11172
+ textUtf8Bytes,
11173
+ order,
11174
+ sourceUpdatedAt,
11175
+ revision,
11176
+ streamEpoch: this.flexMessageStreamEpoch,
11177
+ },
11178
+ timestamp: sourceUpdatedAt,
11179
+ }
11180
+ : {
11181
+ type: 'session.text.delta',
11182
+ harnessId: 'flex',
11183
+ projectId: eventArg.scopeId,
11184
+ sessionId,
11185
+ textDelta: {
11186
+ sessionId,
11187
+ messageId,
11188
+ partId,
11189
+ delta: eventArg.delta,
11190
+ baseTextUtf8Bytes: eventArg.baseTextUtf8Bytes,
11191
+ textUtf8Bytes,
11192
+ order,
11193
+ sourceUpdatedAt,
11194
+ revision,
11195
+ streamEpoch: this.flexMessageStreamEpoch,
11196
+ },
11197
+ timestamp: sourceUpdatedAt,
11198
+ };
11199
+ const snapshotEvent: IControllerEvent = eventArg.partType === 'reasoning'
11200
+ ? {
11201
+ type: 'session.reasoning.updated',
11202
+ harnessId: 'flex',
11203
+ projectId: eventArg.scopeId,
11204
+ sessionId,
11205
+ reasoningUpdate: update as IControllerReasoningUpdate,
11206
+ timestamp: sourceUpdatedAt,
11207
+ }
11208
+ : {
11209
+ type: 'session.text.updated',
11210
+ harnessId: 'flex',
11211
+ projectId: eventArg.scopeId,
11212
+ sessionId,
11213
+ textUpdate: update as IControllerTextUpdate,
11214
+ timestamp: sourceUpdatedAt,
11215
+ };
11216
+ const eventBytes = Buffer.byteLength(JSON.stringify(controllerEvent), 'utf8');
11217
+ const snapshotBytes = Buffer.byteLength(JSON.stringify(snapshotEvent), 'utf8');
11218
+ if (
11219
+ eventBytes > controllerMaxLiveMessageEventBytes
11220
+ || snapshotBytes > controllerMaxLiveMessageEventBytes
11221
+ || this.latestFlexMessageUpdateBytes - (entry.bytes ?? 0) + snapshotBytes
11222
+ > maxLatestFlexMessageUpdateBytes
11223
+ ) {
11224
+ this.advanceFlexMessageStreamEpoch(true);
11225
+ return;
11226
+ }
11227
+
11228
+ this.flexMessageStreamRevision = revision;
11229
+ if (eventArg.partType === 'reasoning') {
11230
+ this.setLatestFlexReasoningUpdate(
11231
+ key,
11232
+ {
11233
+ projectId: eventArg.scopeId,
11234
+ update: update as IControllerReasoningUpdate,
11235
+ textUtf8Bytes,
11236
+ },
11237
+ snapshotBytes,
11238
+ );
11239
+ } else {
11240
+ this.setLatestFlexTextUpdate(
11241
+ key,
11242
+ {
11243
+ projectId: eventArg.scopeId,
11244
+ update: update as IControllerTextUpdate,
11245
+ textUtf8Bytes,
11246
+ },
11247
+ snapshotBytes,
11248
+ );
11249
+ }
11250
+ this.queueFlexControllerEvent(controllerEvent);
11251
+ }
11252
+
10955
11253
  private getOpenCodeToolStreamCursor(): IControllerToolStreamCursor {
10956
11254
  return {
10957
11255
  streamEpoch: this.openCodeToolStreamEpoch,
@@ -11075,12 +11373,25 @@ export class OpenCodeController {
11075
11373
 
11076
11374
  private advanceOpenCodeMessageStreamEpoch(includeSessionsChangedArg: boolean): void {
11077
11375
  if (this.lifecycleState !== 'starting' && this.lifecycleState !== 'ready') return;
11376
+ const pendingEntries = includeSessionsChangedArg ? 2 : 1;
11377
+ if (
11378
+ this.pendingOpenCodeControllerEvents.size
11379
+ > maxPendingOpenCodeControllerEvents - pendingEntries
11380
+ ) {
11381
+ this.advanceOpenCodeStreams(includeSessionsChangedArg);
11382
+ return;
11383
+ }
11078
11384
  this.openCodeMessageStreamEpoch += 1;
11079
11385
  this.openCodeMessageStreamRevision = 0;
11080
11386
  this.latestOpenCodeReasoningUpdates.clear();
11081
11387
  this.latestOpenCodeTextUpdates.clear();
11082
11388
  for (const [key, event] of this.pendingOpenCodeControllerEvents) {
11083
- if (event.reasoningUpdate || event.textUpdate) this.pendingOpenCodeControllerEvents.delete(key);
11389
+ if (
11390
+ event.reasoningUpdate
11391
+ || event.reasoningDelta
11392
+ || event.textUpdate
11393
+ || event.textDelta
11394
+ ) this.pendingOpenCodeControllerEvents.delete(key);
11084
11395
  }
11085
11396
  const timestamp = Date.now();
11086
11397
  const barrier: IControllerEvent = {
@@ -11167,6 +11478,143 @@ export class OpenCodeController {
11167
11478
  if (!cache.has(key) && cache.size >= maxLatestOpenCodeMessageUpdates) {
11168
11479
  this.advanceOpenCodeMessageStreamEpoch(true);
11169
11480
  }
11481
+ const previousEntry = cache.get(key);
11482
+ const previous = previousEntry?.update;
11483
+ if (
11484
+ previousEntry !== undefined
11485
+ && previous !== undefined
11486
+ && previousEntry.projectId === projectId
11487
+ && previous.streamEpoch === this.openCodeMessageStreamEpoch
11488
+ && previous.revision <= this.openCodeMessageStreamRevision
11489
+ && controllerRuntimeIdKey(previous.sessionId) === controllerRuntimeIdKey(updateArg.sessionId)
11490
+ && controllerRuntimeIdKey(previous.messageId) === controllerRuntimeIdKey(updateArg.messageId)
11491
+ && controllerRuntimeIdKey(previous.partId) === controllerRuntimeIdKey(updateArg.partId)
11492
+ && previous.status === 'running'
11493
+ && updateArg.status === 'running'
11494
+ && updateArg.text.length > previous.text.length
11495
+ && updateArg.text.startsWith(previous.text)
11496
+ && Number.isSafeInteger(previousEntry.textUtf8Bytes)
11497
+ ) {
11498
+ const deltaText = updateArg.text.slice(previous.text.length);
11499
+ const fragments = fragmentControllerTextDelta(deltaText);
11500
+ const textUtf8Bytes = controllerTextAfterAppendUtf8Bytes(
11501
+ previous.text,
11502
+ previousEntry.textUtf8Bytes,
11503
+ deltaText,
11504
+ );
11505
+ const finalRevision = this.openCodeMessageStreamRevision + fragments.length;
11506
+ if (
11507
+ fragments.length === 0
11508
+ || textUtf8Bytes === undefined
11509
+ || textUtf8Bytes <= previousEntry.textUtf8Bytes
11510
+ || !Number.isSafeInteger(finalRevision)
11511
+ ) {
11512
+ this.advanceOpenCodeMessageStreamEpoch(true);
11513
+ return;
11514
+ }
11515
+ const update = {
11516
+ ...updateArg,
11517
+ revision: finalRevision,
11518
+ streamEpoch: this.openCodeMessageStreamEpoch,
11519
+ } as IControllerReasoningUpdate | IControllerTextUpdate;
11520
+ const snapshotEvent: IControllerEvent = typeArg === 'reasoning'
11521
+ ? {
11522
+ type: 'session.reasoning.updated',
11523
+ harnessId: 'opencode',
11524
+ projectId,
11525
+ sessionId: update.sessionId,
11526
+ reasoningUpdate: update as IControllerReasoningUpdate,
11527
+ timestamp: update.sourceUpdatedAt,
11528
+ }
11529
+ : {
11530
+ type: 'session.text.updated',
11531
+ harnessId: 'opencode',
11532
+ projectId,
11533
+ sessionId: update.sessionId,
11534
+ textUpdate: update as IControllerTextUpdate,
11535
+ timestamp: update.sourceUpdatedAt,
11536
+ };
11537
+ const snapshotBytes = Buffer.byteLength(JSON.stringify(snapshotEvent), 'utf8');
11538
+ if (snapshotBytes > controllerMaxLiveMessageEventBytes) {
11539
+ this.advanceOpenCodeMessageStreamEpoch(true);
11540
+ return;
11541
+ }
11542
+
11543
+ const events: IControllerEvent[] = [];
11544
+ let baseTextUtf8Bytes = previousEntry.textUtf8Bytes;
11545
+ for (const [index, fragment] of fragments.entries()) {
11546
+ const fragmentTextUtf8Bytes = index === 0
11547
+ ? controllerTextAfterAppendUtf8Bytes(
11548
+ previous.text,
11549
+ baseTextUtf8Bytes,
11550
+ fragment.delta,
11551
+ )
11552
+ : baseTextUtf8Bytes + fragment.utf8Bytes;
11553
+ if (
11554
+ fragmentTextUtf8Bytes === undefined
11555
+ || !Number.isSafeInteger(fragmentTextUtf8Bytes)
11556
+ || fragmentTextUtf8Bytes <= baseTextUtf8Bytes
11557
+ ) {
11558
+ this.advanceOpenCodeMessageStreamEpoch(true);
11559
+ return;
11560
+ }
11561
+ const revision = this.openCodeMessageStreamRevision + index + 1;
11562
+ const commonDelta = {
11563
+ sessionId: update.sessionId,
11564
+ messageId: update.messageId,
11565
+ partId: update.partId,
11566
+ delta: fragment.delta,
11567
+ baseTextUtf8Bytes,
11568
+ textUtf8Bytes: fragmentTextUtf8Bytes,
11569
+ sourceUpdatedAt: update.sourceUpdatedAt,
11570
+ revision,
11571
+ streamEpoch: this.openCodeMessageStreamEpoch,
11572
+ };
11573
+ const event: IControllerEvent = typeArg === 'reasoning'
11574
+ ? {
11575
+ type: 'session.reasoning.delta',
11576
+ harnessId: 'opencode',
11577
+ projectId,
11578
+ sessionId: update.sessionId,
11579
+ reasoningDelta: commonDelta,
11580
+ timestamp: update.sourceUpdatedAt,
11581
+ }
11582
+ : {
11583
+ type: 'session.text.delta',
11584
+ harnessId: 'opencode',
11585
+ projectId,
11586
+ sessionId: update.sessionId,
11587
+ textDelta: commonDelta,
11588
+ timestamp: update.sourceUpdatedAt,
11589
+ };
11590
+ if (Buffer.byteLength(JSON.stringify(event), 'utf8') > controllerMaxLiveMessageEventBytes) {
11591
+ this.advanceOpenCodeMessageStreamEpoch(true);
11592
+ return;
11593
+ }
11594
+ events.push(event);
11595
+ baseTextUtf8Bytes = fragmentTextUtf8Bytes;
11596
+ }
11597
+ if (baseTextUtf8Bytes !== textUtf8Bytes) {
11598
+ this.advanceOpenCodeMessageStreamEpoch(true);
11599
+ return;
11600
+ }
11601
+
11602
+ this.openCodeMessageStreamRevision = finalRevision;
11603
+ cache.delete(key);
11604
+ cache.set(key, {
11605
+ projectId,
11606
+ update,
11607
+ textUtf8Bytes,
11608
+ bytes: snapshotBytes,
11609
+ } as ILatestReasoningUpdate & ILatestTextUpdate);
11610
+ const streamEpoch = this.openCodeMessageStreamEpoch;
11611
+ for (const event of events) {
11612
+ this.queueOpenCodeControllerEvent(event);
11613
+ if (this.openCodeMessageStreamEpoch !== streamEpoch) return;
11614
+ }
11615
+ return;
11616
+ }
11617
+
11170
11618
  this.openCodeMessageStreamRevision += 1;
11171
11619
  const update = {
11172
11620
  ...updateArg,
@@ -11190,12 +11638,18 @@ export class OpenCodeController {
11190
11638
  textUpdate: update as IControllerTextUpdate,
11191
11639
  timestamp: update.sourceUpdatedAt,
11192
11640
  };
11193
- if (Buffer.byteLength(JSON.stringify(event), 'utf8') > controllerMaxLiveMessageEventBytes) {
11641
+ const eventBytes = Buffer.byteLength(JSON.stringify(event), 'utf8');
11642
+ if (eventBytes > controllerMaxLiveMessageEventBytes) {
11194
11643
  this.advanceOpenCodeMessageStreamEpoch(true);
11195
11644
  return;
11196
11645
  }
11197
11646
  cache.delete(key);
11198
- cache.set(key, { projectId, update } as ILatestReasoningUpdate & ILatestTextUpdate);
11647
+ cache.set(key, {
11648
+ projectId,
11649
+ update,
11650
+ textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
11651
+ bytes: eventBytes,
11652
+ } as ILatestReasoningUpdate & ILatestTextUpdate);
11199
11653
  this.queueOpenCodeControllerEvent(event);
11200
11654
  }
11201
11655
 
@@ -11242,6 +11696,7 @@ export class OpenCodeController {
11242
11696
  }
11243
11697
 
11244
11698
  private controllerEventKey(eventArg: IControllerEvent): string {
11699
+ const delta = controllerEventMessageDelta(eventArg);
11245
11700
  return JSON.stringify([
11246
11701
  eventArg.type,
11247
11702
  eventArg.harnessId ?? null,
@@ -11256,6 +11711,18 @@ export class OpenCodeController {
11256
11711
  eventArg.textUpdate
11257
11712
  ? controllerRuntimeIdKey(eventArg.textUpdate.partId)
11258
11713
  : null,
11714
+ delta
11715
+ ? [
11716
+ controllerRuntimeIdKey(delta.messageId),
11717
+ controllerRuntimeIdKey(delta.partId),
11718
+ delta.streamEpoch,
11719
+ delta.order?.messageIndex ?? null,
11720
+ delta.order?.partIndex ?? null,
11721
+ delta.baseTextUtf8Bytes,
11722
+ delta.textUtf8Bytes,
11723
+ delta.revision,
11724
+ ]
11725
+ : null,
11259
11726
  eventArg.toolExecution
11260
11727
  ? eventArg.toolExecution.status === 'completed' || eventArg.toolExecution.status === 'error'
11261
11728
  ? 'terminal'
@@ -11272,15 +11739,59 @@ export class OpenCodeController {
11272
11739
  ]);
11273
11740
  }
11274
11741
 
11275
- private queueOpenCodeControllerEvent(eventArg: IControllerEvent): void {
11276
- if (this.lifecycleState !== 'starting' && this.lifecycleState !== 'ready') return;
11742
+ private latestPendingControllerDelta(
11743
+ queueArg: Map<string, IControllerEvent>,
11744
+ eventArg: IControllerEvent,
11745
+ ): { key: string; event: IControllerEvent } | undefined {
11746
+ const pendingKey = controllerMessageDeltaPendingKey(eventArg);
11747
+ if (!pendingKey) return undefined;
11748
+ let tail: [string, IControllerEvent] | undefined;
11749
+ for (const entry of queueArg) tail = entry;
11750
+ if (!tail || controllerMessageDeltaPendingKey(tail[1]) !== pendingKey) return undefined;
11751
+ return { key: tail[0], event: tail[1] };
11752
+ }
11753
+
11754
+ private queueOpenCodeControllerEvent(eventArg: IControllerEvent): boolean {
11755
+ if (this.lifecycleState !== 'starting' && this.lifecycleState !== 'ready') return false;
11756
+ const delta = controllerEventMessageDelta(eventArg);
11757
+ if (delta) {
11758
+ if (Buffer.byteLength(JSON.stringify(eventArg), 'utf8') > controllerMaxLiveMessageEventBytes) {
11759
+ this.advanceOpenCodeMessageStreamEpoch(true);
11760
+ return false;
11761
+ }
11762
+ const pending = this.latestPendingControllerDelta(
11763
+ this.pendingOpenCodeControllerEvents,
11764
+ eventArg,
11765
+ );
11766
+ if (pending) {
11767
+ const merged = mergeControllerMessageDeltaEvents(pending.event, eventArg);
11768
+ if (!merged) {
11769
+ this.advanceOpenCodeMessageStreamEpoch(true);
11770
+ return false;
11771
+ }
11772
+ if (
11773
+ Buffer.byteLength(JSON.stringify(merged), 'utf8')
11774
+ <= controllerMaxLiveMessageEventBytes
11775
+ ) {
11776
+ this.pendingOpenCodeControllerEvents.set(pending.key, merged);
11777
+ this.startOpenCodeControllerEventDrain();
11778
+ return true;
11779
+ }
11780
+ }
11781
+ }
11277
11782
  const key = this.controllerEventKey(eventArg);
11278
- if (this.pendingOpenCodeControllerEvents.has(key)) {
11783
+ if (delta && this.pendingOpenCodeControllerEvents.has(key)) {
11784
+ this.advanceOpenCodeMessageStreamEpoch(true);
11785
+ return false;
11786
+ }
11787
+ if (!delta && this.pendingOpenCodeControllerEvents.has(key)) {
11279
11788
  this.pendingOpenCodeControllerEvents.delete(key);
11280
11789
  } else if (
11281
11790
  this.pendingOpenCodeControllerEvents.size >= maxPendingOpenCodeControllerEvents
11282
11791
  ) {
11283
- if (eventArg.reasoningUpdate || eventArg.textUpdate) {
11792
+ if (delta) {
11793
+ this.advanceOpenCodeStreams(true);
11794
+ } else if (eventArg.reasoningUpdate || eventArg.textUpdate) {
11284
11795
  this.advanceOpenCodeMessageStreamEpoch(true);
11285
11796
  } else if (eventArg.toolExecution) {
11286
11797
  this.advanceOpenCodeToolStreamEpoch(true);
@@ -11312,10 +11823,11 @@ export class OpenCodeController {
11312
11823
  rebasedEvent,
11313
11824
  );
11314
11825
  }
11315
- return;
11826
+ return false;
11316
11827
  }
11317
11828
  this.pendingOpenCodeControllerEvents.set(key, eventArg);
11318
11829
  this.startOpenCodeControllerEventDrain();
11830
+ return true;
11319
11831
  }
11320
11832
 
11321
11833
  private startOpenCodeControllerEventDrain(): void {
@@ -11382,7 +11894,11 @@ export class OpenCodeController {
11382
11894
  };
11383
11895
  this.setLatestFlexReasoningUpdate(
11384
11896
  this.openCodeToolExecutionKey(eventArg.projectId, update),
11385
- { projectId: eventArg.projectId, update },
11897
+ {
11898
+ projectId: eventArg.projectId,
11899
+ update,
11900
+ textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
11901
+ },
11386
11902
  Buffer.byteLength(JSON.stringify(event), 'utf8'),
11387
11903
  );
11388
11904
  return event;
@@ -11401,7 +11917,11 @@ export class OpenCodeController {
11401
11917
  };
11402
11918
  this.setLatestFlexTextUpdate(
11403
11919
  this.openCodeToolExecutionKey(eventArg.projectId, update),
11404
- { projectId: eventArg.projectId, update },
11920
+ {
11921
+ projectId: eventArg.projectId,
11922
+ update,
11923
+ textUtf8Bytes: Buffer.byteLength(update.text, 'utf8'),
11924
+ },
11405
11925
  Buffer.byteLength(JSON.stringify(event), 'utf8'),
11406
11926
  );
11407
11927
  return event;
@@ -11409,10 +11929,51 @@ export class OpenCodeController {
11409
11929
  return eventArg;
11410
11930
  }
11411
11931
 
11412
- private queueFlexControllerEvent(eventArg: IControllerEvent): void {
11413
- if (this.lifecycleState !== 'starting' && this.lifecycleState !== 'ready') return;
11932
+ private queueFlexControllerEvent(eventArg: IControllerEvent): boolean {
11933
+ if (this.lifecycleState !== 'starting' && this.lifecycleState !== 'ready') return false;
11934
+ const delta = controllerEventMessageDelta(eventArg);
11935
+ if (delta) {
11936
+ if (Buffer.byteLength(JSON.stringify(eventArg), 'utf8') > controllerMaxLiveMessageEventBytes) {
11937
+ this.advanceFlexMessageStreamEpoch(true);
11938
+ return false;
11939
+ }
11940
+ const pending = this.latestPendingControllerDelta(
11941
+ this.pendingFlexControllerEvents,
11942
+ eventArg,
11943
+ );
11944
+ if (pending) {
11945
+ const merged = mergeControllerMessageDeltaEvents(pending.event, eventArg);
11946
+ if (!merged) {
11947
+ this.advanceFlexMessageStreamEpoch(true);
11948
+ return false;
11949
+ }
11950
+ const mergedBytes = this.flexControllerEventLiveBytes(merged);
11951
+ if (
11952
+ mergedBytes.message <= controllerMaxLiveMessageEventBytes
11953
+ ) {
11954
+ const pendingBytes = this.flexControllerEventLiveBytes(pending.event);
11955
+ if (
11956
+ this.latestFlexMessageUpdateBytes
11957
+ + this.pendingFlexMessageEventBytes
11958
+ - pendingBytes.message
11959
+ + mergedBytes.message
11960
+ > maxLatestFlexMessageUpdateBytes
11961
+ ) {
11962
+ this.advanceFlexMessageStreamEpoch(true);
11963
+ return false;
11964
+ }
11965
+ this.setPendingFlexControllerEvent(pending.key, merged);
11966
+ this.startFlexControllerEventDrain();
11967
+ return true;
11968
+ }
11969
+ }
11970
+ }
11414
11971
  const key = this.controllerEventKey(eventArg);
11415
- this.deletePendingFlexControllerEvent(key);
11972
+ if (delta && this.pendingFlexControllerEvents.has(key)) {
11973
+ this.advanceFlexMessageStreamEpoch(true);
11974
+ return false;
11975
+ }
11976
+ if (!delta) this.deletePendingFlexControllerEvent(key);
11416
11977
  const liveBytes = this.flexControllerEventLiveBytes(eventArg);
11417
11978
  const exceedsCountBound = this.pendingFlexControllerEvents.size >= maxPendingFlexControllerEvents;
11418
11979
  const exceedsPendingBound = exceedsCountBound
@@ -11427,7 +11988,12 @@ export class OpenCodeController {
11427
11988
  || this.pendingFlexControllerEvents.size > maxPendingFlexControllerEvents - 3;
11428
11989
  if (requiresCountCollapse) {
11429
11990
  this.advanceFlexStreams(true);
11430
- } else if (eventArg.reasoningUpdate || eventArg.textUpdate) {
11991
+ } else if (
11992
+ eventArg.reasoningUpdate
11993
+ || eventArg.reasoningDelta
11994
+ || eventArg.textUpdate
11995
+ || eventArg.textDelta
11996
+ ) {
11431
11997
  this.advanceFlexMessageStreamEpoch(true);
11432
11998
  } else if (eventArg.toolExecution) {
11433
11999
  this.advanceFlexToolStreamEpoch(true);
@@ -11445,10 +12011,11 @@ export class OpenCodeController {
11445
12011
  this.setPendingFlexControllerEvent(this.controllerEventKey(rebasedEvent), rebasedEvent);
11446
12012
  }
11447
12013
  this.startFlexControllerEventDrain();
11448
- return;
12014
+ return false;
11449
12015
  }
11450
12016
  this.setPendingFlexControllerEvent(key, eventArg);
11451
12017
  this.startFlexControllerEventDrain();
12018
+ return true;
11452
12019
  }
11453
12020
 
11454
12021
  private startFlexControllerEventDrain(): void {