@tanstack/ai-client 0.29.1 → 0.29.2

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/devtools.ts CHANGED
@@ -8,6 +8,7 @@ import { DefaultChatClientEventEmitter } from './events'
8
8
  import type { AnyClientTool, StreamChunk } from '@tanstack/ai/client'
9
9
  import type {
10
10
  AIDevtoolsEventVisibility,
11
+ CompactionMessagePreview,
11
12
  MemoryScopeLite,
12
13
  } from '@tanstack/ai-event-client'
13
14
  import type {
@@ -60,6 +61,127 @@ interface MemoryStateEventValue {
60
61
  }
61
62
  }
62
63
 
64
+ function readCompactionBoundaryValue(rawValue: unknown): {
65
+ before?: number
66
+ after?: number
67
+ messagesBefore?: number
68
+ messagesAfter?: number
69
+ reusedCheckpoint?: boolean
70
+ maxTokens?: number
71
+ strategyKey?: string
72
+ durationMs?: number
73
+ } {
74
+ if (!rawValue || typeof rawValue !== 'object') return {}
75
+ return {
76
+ ...('before' in rawValue && typeof rawValue.before === 'number'
77
+ ? { before: rawValue.before }
78
+ : {}),
79
+ ...('after' in rawValue && typeof rawValue.after === 'number'
80
+ ? { after: rawValue.after }
81
+ : {}),
82
+ ...('messagesBefore' in rawValue &&
83
+ typeof rawValue.messagesBefore === 'number'
84
+ ? { messagesBefore: rawValue.messagesBefore }
85
+ : {}),
86
+ ...('messagesAfter' in rawValue &&
87
+ typeof rawValue.messagesAfter === 'number'
88
+ ? { messagesAfter: rawValue.messagesAfter }
89
+ : {}),
90
+ ...('reusedCheckpoint' in rawValue &&
91
+ typeof rawValue.reusedCheckpoint === 'boolean'
92
+ ? { reusedCheckpoint: rawValue.reusedCheckpoint }
93
+ : {}),
94
+ ...('maxTokens' in rawValue && typeof rawValue.maxTokens === 'number'
95
+ ? { maxTokens: rawValue.maxTokens }
96
+ : {}),
97
+ ...('strategyKey' in rawValue && typeof rawValue.strategyKey === 'string'
98
+ ? { strategyKey: rawValue.strategyKey }
99
+ : {}),
100
+ ...('durationMs' in rawValue && typeof rawValue.durationMs === 'number'
101
+ ? { durationMs: rawValue.durationMs }
102
+ : {}),
103
+ }
104
+ }
105
+
106
+ function readPreviewList(
107
+ value: unknown,
108
+ ): Array<CompactionMessagePreview> | undefined {
109
+ if (!Array.isArray(value)) return undefined
110
+ const previews: Array<CompactionMessagePreview> = []
111
+ for (const item of value) {
112
+ if (!item || typeof item !== 'object') continue
113
+ if (!('role' in item) || !('tokens' in item) || !('text' in item)) continue
114
+ if (
115
+ typeof item.role !== 'string' ||
116
+ typeof item.tokens !== 'number' ||
117
+ typeof item.text !== 'string'
118
+ ) {
119
+ continue
120
+ }
121
+ previews.push({
122
+ role: item.role,
123
+ tokens: item.tokens,
124
+ text: item.text,
125
+ })
126
+ }
127
+ return previews
128
+ }
129
+
130
+ function readCompactionStateValue(rawValue: unknown): {
131
+ before: number
132
+ after: number
133
+ messagesBefore: number
134
+ messagesAfter: number
135
+ reusedCheckpoint: boolean
136
+ maxTokens?: number
137
+ strategyKey?: string
138
+ dropped?: Array<CompactionMessagePreview>
139
+ result?: Array<CompactionMessagePreview>
140
+ } | null {
141
+ if (!rawValue || typeof rawValue !== 'object') return null
142
+ if (
143
+ !('before' in rawValue) ||
144
+ !('after' in rawValue) ||
145
+ !('messagesBefore' in rawValue) ||
146
+ !('messagesAfter' in rawValue)
147
+ ) {
148
+ return null
149
+ }
150
+ if (
151
+ typeof rawValue.before !== 'number' ||
152
+ typeof rawValue.after !== 'number' ||
153
+ typeof rawValue.messagesBefore !== 'number' ||
154
+ typeof rawValue.messagesAfter !== 'number'
155
+ ) {
156
+ return null
157
+ }
158
+ const reusedCheckpoint =
159
+ 'reusedCheckpoint' in rawValue && rawValue.reusedCheckpoint === true
160
+ const maxTokens =
161
+ 'maxTokens' in rawValue && typeof rawValue.maxTokens === 'number'
162
+ ? rawValue.maxTokens
163
+ : undefined
164
+ const strategyKey =
165
+ 'strategyKey' in rawValue && typeof rawValue.strategyKey === 'string'
166
+ ? rawValue.strategyKey
167
+ : undefined
168
+ const dropped =
169
+ 'dropped' in rawValue ? readPreviewList(rawValue.dropped) : undefined
170
+ const result =
171
+ 'result' in rawValue ? readPreviewList(rawValue.result) : undefined
172
+ return {
173
+ before: rawValue.before,
174
+ after: rawValue.after,
175
+ messagesBefore: rawValue.messagesBefore,
176
+ messagesAfter: rawValue.messagesAfter,
177
+ reusedCheckpoint,
178
+ ...(maxTokens !== undefined ? { maxTokens } : {}),
179
+ ...(strategyKey ? { strategyKey } : {}),
180
+ ...(dropped ? { dropped } : {}),
181
+ ...(result ? { result } : {}),
182
+ }
183
+ }
184
+
63
185
  export interface AIDevtoolsClientMetadata extends AIDevtoolsDisplayOptions {
64
186
  framework?: string
65
187
  hookName: string
@@ -760,6 +882,9 @@ export class ClientDevtoolsBridge<TSnapshot extends object> {
760
882
  | 'memory:retrieve:started'
761
883
  | 'memory:retrieve:completed'
762
884
  | 'memory:snapshot'
885
+ | 'compaction:started'
886
+ | 'compaction:state'
887
+ | 'compaction:ended'
763
888
  | 'skills:snapshot'
764
889
  | AIDevtoolsRunEventType,
765
890
  visibility: AIDevtoolsEventVisibility = 'client-state',
@@ -819,6 +944,11 @@ export class ChatDevtoolsBridge extends ClientDevtoolsBridge<AIDevtoolsChatSnaps
819
944
  private lastRunEventContext: ChatClientRunEventContext | undefined
820
945
  /** Last transported `memory:state` value, replayed when a panel opens. */
821
946
  private lastMemoryStateValue: unknown = null
947
+ /** Transported compaction CUSTOM events, replayed when a panel opens. */
948
+ private readonly lastCompactionEvents: Array<{
949
+ eventType: string
950
+ value: unknown
951
+ }> = []
822
952
  /** Last transported `skills:state` value, replayed when a panel opens. */
823
953
  private lastSkillsStateValue: unknown = null
824
954
 
@@ -987,6 +1117,55 @@ export class ChatDevtoolsBridge extends ClientDevtoolsBridge<AIDevtoolsChatSnaps
987
1117
  }
988
1118
  }
989
1119
 
1120
+ /**
1121
+ * Record a transported compaction CUSTOM event. Called from the chat
1122
+ * client's `onCustomEvent` handler so server-side compaction reaches the
1123
+ * browser DevTools panel.
1124
+ */
1125
+ recordCompactionEvent(eventType: string, rawValue: unknown): void {
1126
+ this.lastCompactionEvents.push({ eventType, value: rawValue })
1127
+ if (this.lastCompactionEvents.length > 60) {
1128
+ this.lastCompactionEvents.splice(0, this.lastCompactionEvents.length - 60)
1129
+ }
1130
+ this.emitCompactionEvent(eventType, rawValue)
1131
+ }
1132
+
1133
+ recordCompactionState(rawValue: unknown): void {
1134
+ this.recordCompactionEvent('compaction:state', rawValue)
1135
+ }
1136
+
1137
+ private emitCompactionEvent(eventType: string, rawValue: unknown): void {
1138
+ const runContext = this.currentRunId ? { runId: this.currentRunId } : {}
1139
+ if (eventType === 'compaction:started') {
1140
+ const value = readCompactionBoundaryValue(rawValue)
1141
+ emitAIDevtoolsEvent('compaction:started', {
1142
+ ...this.createEnvelope(
1143
+ 'compaction:started',
1144
+ 'client-state',
1145
+ runContext,
1146
+ ),
1147
+ ...value,
1148
+ })
1149
+ return
1150
+ }
1151
+ if (eventType === 'compaction:ended') {
1152
+ const value = readCompactionBoundaryValue(rawValue)
1153
+ emitAIDevtoolsEvent('compaction:ended', {
1154
+ ...this.createEnvelope('compaction:ended', 'client-state', runContext),
1155
+ ...value,
1156
+ })
1157
+ return
1158
+ }
1159
+ if (eventType === 'compaction:state') {
1160
+ const value = readCompactionStateValue(rawValue)
1161
+ if (!value) return
1162
+ emitAIDevtoolsEvent('compaction:state', {
1163
+ ...this.createEnvelope('compaction:state', 'client-state', runContext),
1164
+ ...value,
1165
+ })
1166
+ }
1167
+ }
1168
+
990
1169
  private emitSkillsState(rawValue: unknown): void {
991
1170
  if (!rawValue || typeof rawValue !== 'object') return
992
1171
  const value = rawValue as SkillsStateEventValue
@@ -1004,6 +1183,9 @@ export class ChatDevtoolsBridge extends ClientDevtoolsBridge<AIDevtoolsChatSnaps
1004
1183
  if (this.lastMemoryStateValue != null) {
1005
1184
  this.emitMemoryState(this.lastMemoryStateValue)
1006
1185
  }
1186
+ for (const event of this.lastCompactionEvents) {
1187
+ this.emitCompactionEvent(event.eventType, event.value)
1188
+ }
1007
1189
  if (this.lastSkillsStateValue != null) {
1008
1190
  this.emitSkillsState(this.lastSkillsStateValue)
1009
1191
  }