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.
@@ -64,7 +64,9 @@ import {
64
64
  flexIpcMaximumPromptQueueEntries,
65
65
  flexIpcMaximumQueuedSessionChangeBytes,
66
66
  flexIpcMaximumQueuedSessionChanges,
67
+ flexIpcMaximumDeltaBytes,
67
68
  flexIpcProtocolVersion,
69
+ flexIpcTargetDeltaBytes,
68
70
  isFlexHostResponseResult,
69
71
  } from './interfaces.flexipc.js';
70
72
  import { openCodeOAuthAuthFromProviderCredential } from './functions.opencodeauth.js';
@@ -78,6 +80,24 @@ type TFlexHarnessEvent = plugins.flexharness.TFlexHarnessEvent;
78
80
 
79
81
  const flexFailureStatusTimeoutMs = 1_000;
80
82
 
83
+ const fragmentUtf8 = (valueArg: string, targetBytesArg: number): string[] => {
84
+ const fragments: string[] = [];
85
+ let fragment = '';
86
+ let fragmentBytes = 0;
87
+ for (const codePoint of valueArg) {
88
+ const codePointBytes = Buffer.byteLength(codePoint, 'utf8');
89
+ if (fragment && fragmentBytes + codePointBytes > targetBytesArg) {
90
+ fragments.push(fragment);
91
+ fragment = '';
92
+ fragmentBytes = 0;
93
+ }
94
+ fragment += codePoint;
95
+ fragmentBytes += codePointBytes;
96
+ }
97
+ if (fragment) fragments.push(fragment);
98
+ return fragments;
99
+ };
100
+
81
101
  const waitForFlexServiceDeadline = async <TValue>(
82
102
  promiseArg: Promise<TValue>,
83
103
  timeoutMsArg: number,
@@ -452,7 +472,9 @@ export class FlexService {
452
472
  private browserFrameReservationCount = 0;
453
473
  private browserFrameReservationBytes = 0;
454
474
  private readonly sessionChangeQueue = new Map<string, TFlexChildEvent>();
475
+ private sessionDeltaTailKeys = new Map<string, string>();
455
476
  private sessionChangeQueueBytes = 0;
477
+ private sessionChangeQueueTailKey: string | undefined;
456
478
  private sessionChangeFlushRunning = false;
457
479
  private hostRequestAdmissionOpen = true;
458
480
  private admissionOpen = true;
@@ -2517,9 +2539,28 @@ export class FlexService {
2517
2539
  part: eventArg.part,
2518
2540
  });
2519
2541
  }
2542
+ if (eventArg.type === 'part.delta') {
2543
+ this.queuePartDelta({
2544
+ type: 'part.delta',
2545
+ scopeId: eventArg.scopeId,
2546
+ sessionId: eventArg.sessionId,
2547
+ runId: eventArg.runId,
2548
+ messageId: eventArg.messageId,
2549
+ messageIndex: eventArg.messageIndex,
2550
+ partId: eventArg.partId,
2551
+ partIndex: eventArg.partIndex,
2552
+ sequence: eventArg.sequence,
2553
+ timestamp: eventArg.timestamp,
2554
+ partType: eventArg.partType,
2555
+ delta: eventArg.delta,
2556
+ baseTextUtf8Bytes: eventArg.baseTextUtf8Bytes,
2557
+ textUtf8Bytes: eventArg.textUtf8Bytes,
2558
+ });
2559
+ this.queueSessionChange(eventArg.scopeId, eventArg.sessionId);
2560
+ return;
2561
+ }
2520
2562
  if (
2521
2563
  (eventArg.type === 'part.started'
2522
- || eventArg.type === 'part.delta'
2523
2564
  || eventArg.type === 'part.completed')
2524
2565
  && eventArg.part.type === 'reasoning'
2525
2566
  ) {
@@ -2538,7 +2579,6 @@ export class FlexService {
2538
2579
  }
2539
2580
  if (
2540
2581
  (eventArg.type === 'part.started'
2541
- || eventArg.type === 'part.delta'
2542
2582
  || eventArg.type === 'part.completed')
2543
2583
  && eventArg.part.type === 'text'
2544
2584
  ) {
@@ -2949,10 +2989,23 @@ export class FlexService {
2949
2989
  }), 'utf8');
2950
2990
  }
2951
2991
 
2992
+ private getSessionDeltaTailKeys(): Map<string, string> {
2993
+ this.sessionDeltaTailKeys ??= new Map<string, string>();
2994
+ return this.sessionDeltaTailKeys;
2995
+ }
2996
+
2952
2997
  private deleteSessionChangeQueueEntry(keyArg: string): void {
2953
2998
  const existing = this.sessionChangeQueue.get(keyArg);
2954
2999
  if (!existing) return;
2955
3000
  this.sessionChangeQueue.delete(keyArg);
3001
+ const deltaTailKeys = this.getSessionDeltaTailKeys();
3002
+ for (const [partKey, tailKey] of deltaTailKeys) {
3003
+ if (tailKey === keyArg) deltaTailKeys.delete(partKey);
3004
+ }
3005
+ if (this.sessionChangeQueueTailKey === keyArg) {
3006
+ this.sessionChangeQueueTailKey = undefined;
3007
+ for (const key of this.sessionChangeQueue.keys()) this.sessionChangeQueueTailKey = key;
3008
+ }
2956
3009
  this.sessionChangeQueueBytes = Math.max(
2957
3010
  0,
2958
3011
  (this.sessionChangeQueueBytes ?? 0) - this.sessionChangeEventBytes(existing),
@@ -2961,7 +3014,9 @@ export class FlexService {
2961
3014
 
2962
3015
  private clearSessionChangeQueue(): void {
2963
3016
  this.sessionChangeQueue.clear();
3017
+ this.getSessionDeltaTailKeys().clear();
2964
3018
  this.sessionChangeQueueBytes = 0;
3019
+ this.sessionChangeQueueTailKey = undefined;
2965
3020
  }
2966
3021
 
2967
3022
  private setSessionChangeQueueEntry(keyArg: string, eventArg: TFlexChildEvent): boolean {
@@ -2978,6 +3033,7 @@ export class FlexService {
2978
3033
  || nextBytes > flexIpcMaximumQueuedSessionChangeBytes
2979
3034
  ) return false;
2980
3035
  this.sessionChangeQueue.set(keyArg, eventArg);
3036
+ if (!existing) this.sessionChangeQueueTailKey = keyArg;
2981
3037
  this.sessionChangeQueueBytes = nextBytes;
2982
3038
  return true;
2983
3039
  }
@@ -2987,6 +3043,7 @@ export class FlexService {
2987
3043
  this.clearSessionChangeQueue();
2988
3044
  this.sessionChangeQueue.set('*', event);
2989
3045
  this.sessionChangeQueueBytes = this.sessionChangeEventBytes(event);
3046
+ this.sessionChangeQueueTailKey = '*';
2990
3047
  }
2991
3048
 
2992
3049
  private queueToolChange(eventArg: Extract<TFlexChildEvent, {
@@ -3026,6 +3083,78 @@ export class FlexService {
3026
3083
  this.startSessionChangeFlush();
3027
3084
  }
3028
3085
 
3086
+ private queuePartDelta(eventArg: Extract<TFlexChildEvent, { type: 'part.delta' }>): void {
3087
+ if (this.disposing || this.disposed || this.sessionChangeQueue.has('*')) return;
3088
+ const encodedDeltaBytes = Buffer.byteLength(eventArg.delta, 'utf8');
3089
+ const boundaryCorrection = eventArg.textUtf8Bytes
3090
+ - eventArg.baseTextUtf8Bytes
3091
+ - encodedDeltaBytes;
3092
+ const fragments = fragmentUtf8(eventArg.delta, flexIpcTargetDeltaBytes);
3093
+ if (fragments.length === 0) {
3094
+ this.collapseSessionChangeQueue();
3095
+ this.startSessionChangeFlush();
3096
+ return;
3097
+ }
3098
+ let baseTextUtf8Bytes = eventArg.baseTextUtf8Bytes;
3099
+ for (const [fragmentIndex, fragment] of fragments.entries()) {
3100
+ const textUtf8Bytes = baseTextUtf8Bytes
3101
+ + Buffer.byteLength(fragment, 'utf8')
3102
+ + (fragmentIndex === 0 ? boundaryCorrection : 0);
3103
+ if (textUtf8Bytes <= baseTextUtf8Bytes) {
3104
+ this.collapseSessionChangeQueue();
3105
+ this.startSessionChangeFlush();
3106
+ return;
3107
+ }
3108
+ const fragmentEvent: Extract<TFlexChildEvent, { type: 'part.delta' }> = {
3109
+ ...eventArg,
3110
+ delta: fragment,
3111
+ baseTextUtf8Bytes,
3112
+ textUtf8Bytes,
3113
+ };
3114
+ if (!this.enqueuePartDeltaFragment(fragmentEvent)) {
3115
+ this.collapseSessionChangeQueue();
3116
+ this.startSessionChangeFlush();
3117
+ return;
3118
+ }
3119
+ baseTextUtf8Bytes = textUtf8Bytes;
3120
+ }
3121
+ if (baseTextUtf8Bytes !== eventArg.textUtf8Bytes) this.collapseSessionChangeQueue();
3122
+ this.startSessionChangeFlush();
3123
+ }
3124
+
3125
+ private enqueuePartDeltaFragment(
3126
+ eventArg: Extract<TFlexChildEvent, { type: 'part.delta' }>,
3127
+ ): boolean {
3128
+ const partKey = `${eventArg.scopeId}\0${eventArg.sessionId}\0${eventArg.partType}\0${eventArg.partId}`;
3129
+ const deltaTailKeys = this.getSessionDeltaTailKeys();
3130
+ const tailKey = deltaTailKeys.get(partKey);
3131
+ const tail = tailKey ? this.sessionChangeQueue.get(tailKey) : undefined;
3132
+ if (
3133
+ tail?.type === 'part.delta'
3134
+ && tailKey === this.sessionChangeQueueTailKey
3135
+ && tail.textUtf8Bytes === eventArg.baseTextUtf8Bytes
3136
+ && tail.runId === eventArg.runId
3137
+ && tail.messageId === eventArg.messageId
3138
+ && tail.messageIndex === eventArg.messageIndex
3139
+ && tail.partIndex === eventArg.partIndex
3140
+ && Buffer.byteLength(tail.delta, 'utf8') + Buffer.byteLength(eventArg.delta, 'utf8')
3141
+ <= flexIpcMaximumDeltaBytes
3142
+ ) {
3143
+ const merged: Extract<TFlexChildEvent, { type: 'part.delta' }> = {
3144
+ ...tail,
3145
+ sequence: eventArg.sequence,
3146
+ timestamp: eventArg.timestamp,
3147
+ delta: `${tail.delta}${eventArg.delta}`,
3148
+ textUtf8Bytes: eventArg.textUtf8Bytes,
3149
+ };
3150
+ return this.setSessionChangeQueueEntry(tailKey!, merged);
3151
+ }
3152
+ const key = `${partKey}\0${eventArg.baseTextUtf8Bytes}\0${eventArg.sequence}`;
3153
+ if (!this.setSessionChangeQueueEntry(key, eventArg)) return false;
3154
+ deltaTailKeys.set(partKey, key);
3155
+ return true;
3156
+ }
3157
+
3029
3158
 
3030
3159
  private queueSessionChange(scopeIdArg: string, sessionIdArg: string): void {
3031
3160
  const globalKey = '*';
@@ -3069,6 +3198,7 @@ export class FlexService {
3069
3198
  }
3070
3199
  } catch {
3071
3200
  this.clearSessionChangeQueue();
3201
+ await this.closeTransport().catch(() => undefined);
3072
3202
  } finally {
3073
3203
  this.sessionChangeFlushRunning = false;
3074
3204
  if (this.sessionChangeQueue.size > 0 && !this.disposing && !this.disposed) {
@@ -54,6 +54,7 @@ const maxLiveToolExecutionBytes = controllerMaxToolEventBytes - (8 * 1024);
54
54
  const maxLiveMessageSnapshotBytes = controllerMaxLiveMessageEventBytes - (8 * 1024);
55
55
  const maxLiveMessagePartCacheEntries = 512;
56
56
  const maxLiveMessagePartCacheBytes = 4 * 1024 * 1024;
57
+ const maxLiveMessageEventIds = 4_096;
57
58
  const maxMetadataStringBytes = 16 * 1024;
58
59
  const maxMetadataNodes = 4096;
59
60
  const maxCollectionEntries = 512;
@@ -1816,6 +1817,7 @@ export class OpenCodeClientAdapter {
1816
1817
  private readonly sessionMetricsCache = new Map<string, ISessionMetricsCacheEntry>();
1817
1818
  private readonly liveMessagePartCache = new Map<string, IOpenCodeLiveMessagePartCacheEntry>();
1818
1819
  private liveMessagePartCacheBytes = 0;
1820
+ private readonly liveMessageEventIds = new Set<string>();
1819
1821
 
1820
1822
  constructor(options: IOpenCodeClientOptions) {
1821
1823
  this.directory = plugins.fs.realpathSync.native(options.directory);
@@ -3651,6 +3653,21 @@ export class OpenCodeClientAdapter {
3651
3653
  private clearLiveMessagePartCache(): void {
3652
3654
  this.liveMessagePartCache.clear();
3653
3655
  this.liveMessagePartCacheBytes = 0;
3656
+ this.liveMessageEventIds.clear();
3657
+ }
3658
+
3659
+ private acceptLiveMessageEventId(payloadArg: Record<string, unknown>): boolean {
3660
+ if (typeof payloadArg.id !== 'string' || Buffer.byteLength(payloadArg.id, 'utf8') > 512) {
3661
+ return true;
3662
+ }
3663
+ if (this.liveMessageEventIds.has(payloadArg.id)) return false;
3664
+ this.liveMessageEventIds.add(payloadArg.id);
3665
+ while (this.liveMessageEventIds.size > maxLiveMessageEventIds) {
3666
+ const oldest = this.liveMessageEventIds.values().next().value;
3667
+ if (oldest === undefined) break;
3668
+ this.liveMessageEventIds.delete(oldest);
3669
+ }
3670
+ return true;
3654
3671
  }
3655
3672
 
3656
3673
  private clearLiveMessagePartsForSession(directoryArg: string, sessionIdArg: string): void {
@@ -3743,6 +3760,7 @@ export class OpenCodeClientAdapter {
3743
3760
  };
3744
3761
 
3745
3762
  if (payload.type === 'message.part.updated') {
3763
+ if (!this.acceptLiveMessageEventId(payload)) return undefined;
3746
3764
  if (!isRecord(properties.part)) {
3747
3765
  this.clearLiveMessagePartCache();
3748
3766
  return {
@@ -3819,6 +3837,7 @@ export class OpenCodeClientAdapter {
3819
3837
  }
3820
3838
 
3821
3839
  if (payload.type === 'message.part.delta') {
3840
+ if (!this.acceptLiveMessageEventId(payload)) return undefined;
3822
3841
  try {
3823
3842
  const validatedSessionId = requireString(
3824
3843
  properties.sessionID,
@@ -32,8 +32,10 @@ export type TFlexTurnReversionCaptureInspectionV2 =
32
32
  export type TFlexTurnReversionApplyInspection =
33
33
  plugins.flexharness.IFlexTurnReversionApplyInspection;
34
34
 
35
- export const flexIpcProtocolVersion = 14 as const;
35
+ export const flexIpcProtocolVersion = 15 as const;
36
36
  export const flexIpcMaximumFrameBytes = 2 * 1024 * 1024;
37
+ export const flexIpcTargetDeltaBytes = 16 * 1024;
38
+ export const flexIpcMaximumDeltaBytes = 32 * 1024;
37
39
  export const flexIpcMaximumPendingRequests = 64;
38
40
  export const flexIpcMaximumBrowserChannels = 128;
39
41
  export const flexIpcMaximumBrowserFrameBytes = 256 * 1024 + 4;
@@ -764,6 +766,22 @@ export type TFlexChildEvent =
764
766
  status: 'running' | 'completed';
765
767
  part: TFlexTextMessagePart;
766
768
  }
769
+ | {
770
+ type: 'part.delta';
771
+ scopeId: string;
772
+ sessionId: string;
773
+ runId: string;
774
+ messageId: string;
775
+ messageIndex: number;
776
+ partId: string;
777
+ partIndex: number;
778
+ sequence: number;
779
+ timestamp: string;
780
+ partType: 'text' | 'reasoning';
781
+ delta: string;
782
+ baseTextUtf8Bytes: number;
783
+ textUtf8Bytes: number;
784
+ }
767
785
  | {
768
786
  type: 'prompt.queued';
769
787
  scopeId: string;
@@ -945,6 +963,20 @@ const isBoundedString = (
945
963
  && (allowEmptyArg || valueArg.length > 0)
946
964
  && Buffer.byteLength(valueArg, 'utf8') <= maximumBytesArg;
947
965
 
966
+ const hasExactDeltaUtf8Coordinates = (
967
+ deltaArg: string,
968
+ baseTextUtf8BytesArg: number,
969
+ textUtf8BytesArg: number,
970
+ ): boolean => {
971
+ const encodedDeltaBytes = Buffer.byteLength(deltaArg, 'utf8');
972
+ const increment = textUtf8BytesArg - baseTextUtf8BytesArg;
973
+ if (increment === encodedDeltaBytes) return true;
974
+ const firstCodeUnit = deltaArg.charCodeAt(0);
975
+ return firstCodeUnit >= 0xDC00
976
+ && firstCodeUnit <= 0xDFFF
977
+ && increment === encodedDeltaBytes - 2;
978
+ };
979
+
948
980
  const isIdentifier = (valueArg: unknown): valueArg is string =>
949
981
  isBoundedString(valueArg, 512) && identifierPattern.test(valueArg);
950
982
 
@@ -1244,15 +1276,15 @@ const isUsage = (valueArg: unknown): boolean => isFlexPlainObject(valueArg)
1244
1276
  ])
1245
1277
  && Object.values(valueArg).every(isNonNegativeInteger);
1246
1278
 
1247
- const isMessagePart = (valueArg: unknown): boolean => {
1279
+ const isMessagePart = (valueArg: unknown, textByteLimitArg = 96 * 1024): boolean => {
1248
1280
  if (!isFlexPlainObject(valueArg) || !isIdentifier(valueArg.partId)) return false;
1249
1281
  switch (valueArg.type) {
1250
1282
  case 'text':
1251
1283
  return hasFlexExactKeys(valueArg, ['partId', 'type', 'text'])
1252
- && isBoundedString(valueArg.text, 96 * 1024, true);
1284
+ && isBoundedString(valueArg.text, textByteLimitArg, true);
1253
1285
  case 'reasoning':
1254
1286
  return hasFlexExactKeys(valueArg, ['partId', 'type', 'text', 'status'])
1255
- && isBoundedString(valueArg.text, 96 * 1024, true)
1287
+ && isBoundedString(valueArg.text, textByteLimitArg, true)
1256
1288
  && ['running', 'completed', 'cancelled'].includes(String(valueArg.status));
1257
1289
  case 'tool':
1258
1290
  return hasFlexExactKeys(
@@ -1302,7 +1334,7 @@ export const isFlexPublicMessage = (valueArg: unknown): valueArg is TFlexMessage
1302
1334
  && (valueArg.completedAt === undefined || isIsoDate(valueArg.completedAt))
1303
1335
  && Array.isArray(valueArg.parts)
1304
1336
  && valueArg.parts.length <= 2000
1305
- && valueArg.parts.every(isMessagePart)
1337
+ && valueArg.parts.every((partArg) => isMessagePart(partArg))
1306
1338
  && (valueArg.model === undefined || isModelIdentity(valueArg.model))
1307
1339
  && (valueArg.usage === undefined || isUsage(valueArg.usage))
1308
1340
  && (valueArg.error === undefined || isBoundedString(valueArg.error, 2048));
@@ -2339,7 +2371,7 @@ const isFlexChildEvent = (valueArg: unknown): valueArg is TFlexChildEvent => {
2339
2371
  && isIsoDate(valueArg.timestamp)
2340
2372
  && isFlexPlainObject(valueArg.part)
2341
2373
  && valueArg.part.type === 'reasoning'
2342
- && isMessagePart(valueArg.part);
2374
+ && isMessagePart(valueArg.part, flexIpcMaximumFrameBytes - (16 * 1024));
2343
2375
  case 'text.updated':
2344
2376
  return hasFlexExactKeys(valueArg, [
2345
2377
  'type', 'scopeId', 'sessionId', 'runId', 'messageId',
@@ -2357,7 +2389,33 @@ const isFlexChildEvent = (valueArg: unknown): valueArg is TFlexChildEvent => {
2357
2389
  && ['running', 'completed'].includes(String(valueArg.status))
2358
2390
  && isFlexPlainObject(valueArg.part)
2359
2391
  && valueArg.part.type === 'text'
2360
- && isMessagePart(valueArg.part);
2392
+ && isMessagePart(valueArg.part, flexIpcMaximumFrameBytes - (16 * 1024));
2393
+ case 'part.delta':
2394
+ return hasFlexExactKeys(valueArg, [
2395
+ 'type', 'scopeId', 'sessionId', 'runId', 'messageId', 'messageIndex', 'partId',
2396
+ 'partIndex', 'sequence', 'timestamp', 'partType', 'delta',
2397
+ 'baseTextUtf8Bytes', 'textUtf8Bytes',
2398
+ ])
2399
+ && isIdentifier(valueArg.scopeId)
2400
+ && isIdentifier(valueArg.sessionId)
2401
+ && isIdentifier(valueArg.runId)
2402
+ && isIdentifier(valueArg.messageId)
2403
+ && isIdentifier(valueArg.partId)
2404
+ && isNonNegativeInteger(valueArg.messageIndex)
2405
+ && isNonNegativeInteger(valueArg.partIndex)
2406
+ && isNonNegativeInteger(valueArg.sequence)
2407
+ && valueArg.sequence >= 1
2408
+ && isIsoDate(valueArg.timestamp)
2409
+ && (valueArg.partType === 'text' || valueArg.partType === 'reasoning')
2410
+ && isBoundedString(valueArg.delta, flexIpcMaximumDeltaBytes)
2411
+ && isNonNegativeInteger(valueArg.baseTextUtf8Bytes)
2412
+ && isNonNegativeInteger(valueArg.textUtf8Bytes)
2413
+ && valueArg.textUtf8Bytes > valueArg.baseTextUtf8Bytes
2414
+ && hasExactDeltaUtf8Coordinates(
2415
+ valueArg.delta,
2416
+ valueArg.baseTextUtf8Bytes,
2417
+ valueArg.textUtf8Bytes,
2418
+ );
2361
2419
  case 'prompt.queued':
2362
2420
  case 'prompt.started':
2363
2421
  case 'prompt.running':
@@ -1,10 +1,11 @@
1
1
  import * as plugins from './plugins.js';
2
2
 
3
- export const controllerProtocolVersion = 22 as const;
3
+ export const controllerProtocolVersion = 23 as const;
4
4
  export const controllerUpgradeManagementVersion = 2 as const;
5
5
  export const controllerPackageName = 'agl' as const;
6
6
  export const controllerMaxToolEventBytes = 48 * 1024;
7
7
  export const controllerMaxLiveMessageEventBytes = 128 * 1024;
8
+ export const controllerLiveMessageDeltaTargetBytes = 16 * 1024;
8
9
  export const controllerInitialMessageBundleLimit = 20;
9
10
  export const controllerMaximumMessageBundles = 200;
10
11
  export const controllerMaximumTranscriptBytes = 512 * 1024;
@@ -475,6 +476,34 @@ export interface IControllerTextUpdate {
475
476
  streamEpoch: number;
476
477
  }
477
478
 
479
+ export interface IControllerReasoningDelta {
480
+ sessionId: IControllerRuntimeId;
481
+ messageId: IControllerRuntimeId;
482
+ partId: IControllerRuntimeId;
483
+ delta: string;
484
+ baseTextUtf8Bytes: number;
485
+ textUtf8Bytes: number;
486
+ /** Authoritative source coordinates; required for Flex and absent for OpenCode. */
487
+ order?: IControllerTranscriptOrder;
488
+ sourceUpdatedAt: number;
489
+ revision: number;
490
+ streamEpoch: number;
491
+ }
492
+
493
+ export interface IControllerTextDelta {
494
+ sessionId: IControllerRuntimeId;
495
+ messageId: IControllerRuntimeId;
496
+ partId: IControllerRuntimeId;
497
+ delta: string;
498
+ baseTextUtf8Bytes: number;
499
+ textUtf8Bytes: number;
500
+ /** Authoritative source coordinates; required for Flex and absent for OpenCode. */
501
+ order?: IControllerTranscriptOrder;
502
+ sourceUpdatedAt: number;
503
+ revision: number;
504
+ streamEpoch: number;
505
+ }
506
+
478
507
  export interface IControllerToolStreamCursor {
479
508
  streamEpoch: number;
480
509
  revision: number;
@@ -788,7 +817,9 @@ export const controllerEventTypes = [
788
817
  'session.history.changed',
789
818
  'session.tool.updated',
790
819
  'session.reasoning.updated',
820
+ 'session.reasoning.delta',
791
821
  'session.text.updated',
822
+ 'session.text.delta',
792
823
  'permissions.changed',
793
824
  'harness.changed',
794
825
  'projects.changed',
@@ -813,8 +844,12 @@ export interface IControllerEvent {
813
844
  toolExecution?: IControllerToolExecution;
814
845
  /** Present only on a live OpenCode or Flex reasoning snapshot. */
815
846
  reasoningUpdate?: IControllerReasoningUpdate;
847
+ /** Present only on an exact live OpenCode or Flex reasoning delta. */
848
+ reasoningDelta?: IControllerReasoningDelta;
816
849
  /** Present only on a live OpenCode or Flex text snapshot. */
817
850
  textUpdate?: IControllerTextUpdate;
851
+ /** Present only on an exact live OpenCode or Flex text delta. */
852
+ textDelta?: IControllerTextDelta;
818
853
  /** Present only on a session.draft.changed event. */
819
854
  sessionDraftUpdate?: IControllerSessionDraftUpdate;
820
855
  /** Ordered harness stream barrier carried by harness.changed. */
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: 'agl',
6
- version: '22.0.4',
6
+ version: '23.0.0',
7
7
  description: 'Agent Gateway Layer for OpenCode and FlexHarness sessions'
8
8
  }