@tanstack/ai-devtools-core 0.5.9 → 0.5.11

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.
@@ -6,9 +6,9 @@ import {
6
6
  applyHookEvent,
7
7
  clearHookRegistry,
8
8
  createHookRegistryState,
9
+ markHookViewed,
9
10
  removeSavedFixture,
10
11
  replaceSavedFixtures,
11
- setActiveHook,
12
12
  } from './hook-registry'
13
13
  import {
14
14
  createClientToolCallMessage,
@@ -20,6 +20,16 @@ import {
20
20
  clearMemoryRegistry,
21
21
  createMemoryRegistryState,
22
22
  } from './memory-registry'
23
+ import {
24
+ applyCompactionEvent,
25
+ clearCompactionRegistry,
26
+ createCompactionRegistryState,
27
+ } from './compaction-registry'
28
+ import {
29
+ applySkillsSnapshot,
30
+ clearSkillsRegistry,
31
+ createSkillsRegistryState,
32
+ } from './skills-registry'
23
33
  import type { ContentPartSource, TokenUsage } from '@tanstack/ai'
24
34
  import type {
25
35
  ContentPart,
@@ -28,6 +38,8 @@ import type {
28
38
  } from '@tanstack/ai-event-client'
29
39
  import type { HookRegistryState, ToolFixtureRecord } from './hook-registry'
30
40
  import type { MemoryRegistryState } from './memory-registry'
41
+ import type { CompactionRegistryState } from './compaction-registry'
42
+ import type { SkillsRegistryState } from './skills-registry'
31
43
  import type { ParentComponent } from 'solid-js'
32
44
 
33
45
  interface MessagePart {
@@ -236,6 +248,8 @@ interface AIStoreState {
236
248
  activeConversationId: string | null
237
249
  hooks: HookRegistryState
238
250
  memory: MemoryRegistryState
251
+ compaction: CompactionRegistryState
252
+ skills: SkillsRegistryState
239
253
  }
240
254
 
241
255
  interface AIContextValue {
@@ -244,6 +258,8 @@ interface AIContextValue {
244
258
  selectConversation: (id: string) => void
245
259
  clearHooks: () => void
246
260
  clearMemory: () => void
261
+ clearCompaction: () => void
262
+ clearSkills: () => void
247
263
  selectHook: (id: string | null) => void
248
264
  saveToolFixture: (fixture: ToolFixtureRecord) => void
249
265
  deleteToolFixture: (fixtureId: string) => void
@@ -266,6 +282,8 @@ export const AIProvider: ParentComponent = (props) => {
266
282
  activeConversationId: null,
267
283
  hooks: createHookRegistryState(),
268
284
  memory: createMemoryRegistryState(),
285
+ compaction: createCompactionRegistryState(),
286
+ skills: createSkillsRegistryState(),
269
287
  })
270
288
 
271
289
  const streamToConversation = new Map<string, string>()
@@ -661,15 +679,36 @@ export const AIProvider: ParentComponent = (props) => {
661
679
  )
662
680
  }
663
681
 
664
- function selectHook(id: string | null) {
682
+ function clearCompaction() {
665
683
  setState(
666
- 'hooks',
667
- produce((hooks: HookRegistryState) => {
668
- setActiveHook(hooks, id)
684
+ 'compaction',
685
+ produce((compaction: CompactionRegistryState) => {
686
+ clearCompactionRegistry(compaction)
669
687
  }),
670
688
  )
671
689
  }
672
690
 
691
+ function clearSkills() {
692
+ setState(
693
+ 'skills',
694
+ produce((skills: SkillsRegistryState) => {
695
+ clearSkillsRegistry(skills)
696
+ }),
697
+ )
698
+ }
699
+
700
+ function selectHook(id: string | null) {
701
+ setState('hooks', 'activeHookId', id)
702
+ if (id) {
703
+ setState(
704
+ 'hooks',
705
+ produce((hooks: HookRegistryState) => {
706
+ markHookViewed(hooks, id)
707
+ }),
708
+ )
709
+ }
710
+ }
711
+
673
712
  function saveToolFixture(fixture: ToolFixtureRecord) {
674
713
  const fixtures = mergeFixtures(fixture, state.hooks.fixtures)
675
714
  setState(
@@ -1248,6 +1287,14 @@ export const AIProvider: ParentComponent = (props) => {
1248
1287
  }),
1249
1288
  )
1250
1289
  }),
1290
+ aiEventClient.on('skills:snapshot', (e) => {
1291
+ setState(
1292
+ 'skills',
1293
+ produce((skills: SkillsRegistryState) => {
1294
+ applySkillsSnapshot(skills, e.payload)
1295
+ }),
1296
+ )
1297
+ }),
1251
1298
  )
1252
1299
 
1253
1300
  const recordRunEvent = (
@@ -2958,6 +3005,90 @@ export const AIProvider: ParentComponent = (props) => {
2958
3005
  }),
2959
3006
  )
2960
3007
 
3008
+ const recordCompactionLifecycle = (
3009
+ kind: 'started' | 'state' | 'ended',
3010
+ hookName: 'onCompactStart' | 'onCompact' | 'onCompactEnd',
3011
+ payload: {
3012
+ timestamp: number
3013
+ requestId?: string
3014
+ streamId?: string
3015
+ clientId?: string
3016
+ hookId?: string
3017
+ threadId?: string
3018
+ runId?: string
3019
+ eventId?: string
3020
+ before?: number
3021
+ after?: number
3022
+ messagesBefore?: number
3023
+ messagesAfter?: number
3024
+ reusedCheckpoint?: boolean
3025
+ maxTokens?: number
3026
+ strategyKey?: string
3027
+ durationMs?: number
3028
+ dropped?: CompactionRegistryState['events'][number]['dropped']
3029
+ result?: CompactionRegistryState['events'][number]['result']
3030
+ },
3031
+ ) => {
3032
+ setState(
3033
+ 'compaction',
3034
+ produce((compaction: CompactionRegistryState) => {
3035
+ applyCompactionEvent(compaction, kind, payload)
3036
+ }),
3037
+ )
3038
+
3039
+ const { requestId, streamId, clientId } = payload
3040
+ const conversationId =
3041
+ (clientId && state.conversations[clientId] ? clientId : undefined) ||
3042
+ (streamId ? streamToConversation.get(streamId) : undefined) ||
3043
+ (requestId ? requestToConversation.get(requestId) : undefined)
3044
+ if (!conversationId || !state.conversations[conversationId]) return
3045
+
3046
+ const conv = state.conversations[conversationId]
3047
+ const iterIndex = findLatestIterationIndex(conv, requestId)
3048
+ if (iterIndex < 0) return
3049
+
3050
+ const mwEvent: MiddlewareEvent = {
3051
+ id: `mw-cmp-${kind}-${Date.now()}-${Math.random()}`,
3052
+ middlewareName: 'compaction',
3053
+ hookName,
3054
+ timestamp: payload.timestamp,
3055
+ hasTransform: kind === 'state',
3056
+ configChanges: {
3057
+ before: payload.before,
3058
+ after: payload.after,
3059
+ messagesBefore: payload.messagesBefore,
3060
+ messagesAfter: payload.messagesAfter,
3061
+ reusedCheckpoint: payload.reusedCheckpoint,
3062
+ maxTokens: payload.maxTokens,
3063
+ strategyKey: payload.strategyKey,
3064
+ durationMs: payload.durationMs,
3065
+ },
3066
+ }
3067
+
3068
+ setState(
3069
+ 'conversations',
3070
+ conversationId,
3071
+ 'iterations',
3072
+ iterIndex,
3073
+ 'middlewareEvents',
3074
+ produce((arr: Array<MiddlewareEvent>) => {
3075
+ arr.push(mwEvent)
3076
+ }),
3077
+ )
3078
+ }
3079
+
3080
+ cleanupFns.push(
3081
+ aiEventClient.on('compaction:started', (e) => {
3082
+ recordCompactionLifecycle('started', 'onCompactStart', e.payload)
3083
+ }),
3084
+ aiEventClient.on('compaction:state', (e) => {
3085
+ recordCompactionLifecycle('state', 'onCompact', e.payload)
3086
+ }),
3087
+ aiEventClient.on('compaction:ended', (e) => {
3088
+ recordCompactionLifecycle('ended', 'onCompactEnd', e.payload)
3089
+ }),
3090
+ )
3091
+
2961
3092
  cleanupFns.push(
2962
3093
  aiEventClient.on('summarize:request:started', (e) => {
2963
3094
  const { requestId, model, inputLength, timestamp, clientId } = e.payload
@@ -3465,6 +3596,8 @@ export const AIProvider: ParentComponent = (props) => {
3465
3596
  selectConversation,
3466
3597
  clearHooks,
3467
3598
  clearMemory,
3599
+ clearCompaction,
3600
+ clearSkills,
3468
3601
  selectHook,
3469
3602
  saveToolFixture,
3470
3603
  deleteToolFixture,
@@ -0,0 +1,121 @@
1
+ const MAX_EVENTS = 50
2
+
3
+ export type CompactionEventKind = 'started' | 'state' | 'ended'
4
+
5
+ export interface CompactionMessagePreview {
6
+ role: string
7
+ tokens: number
8
+ text: string
9
+ }
10
+
11
+ export interface CompactionEventRecord {
12
+ id: string
13
+ kind: CompactionEventKind
14
+ timestamp: number
15
+ hookId?: string
16
+ clientId?: string
17
+ threadId?: string
18
+ runId?: string
19
+ before?: number
20
+ after?: number
21
+ messagesBefore?: number
22
+ messagesAfter?: number
23
+ reusedCheckpoint?: boolean
24
+ maxTokens?: number
25
+ strategyKey?: string
26
+ durationMs?: number
27
+ dropped?: Array<CompactionMessagePreview>
28
+ result?: Array<CompactionMessagePreview>
29
+ }
30
+
31
+ export interface CompactionRegistryState {
32
+ events: Array<CompactionEventRecord>
33
+ }
34
+
35
+ export function createCompactionRegistryState(): CompactionRegistryState {
36
+ return { events: [] }
37
+ }
38
+
39
+ export interface CompactionLifecycleInput {
40
+ timestamp: number
41
+ eventId?: string
42
+ hookId?: string
43
+ clientId?: string
44
+ threadId?: string
45
+ runId?: string
46
+ before?: number
47
+ after?: number
48
+ messagesBefore?: number
49
+ messagesAfter?: number
50
+ reusedCheckpoint?: boolean
51
+ maxTokens?: number
52
+ strategyKey?: string
53
+ durationMs?: number
54
+ dropped?: Array<CompactionMessagePreview>
55
+ result?: Array<CompactionMessagePreview>
56
+ }
57
+
58
+ function eventId(
59
+ kind: CompactionEventKind,
60
+ event: CompactionLifecycleInput,
61
+ ): string {
62
+ return [
63
+ 'cmp',
64
+ kind,
65
+ String(event.timestamp),
66
+ event.hookId ?? event.clientId ?? event.threadId ?? 'unknown',
67
+ String(event.before ?? event.after ?? ''),
68
+ ].join('-')
69
+ }
70
+
71
+ /** Append one compaction lifecycle event. Newest stays at the end. */
72
+ export function applyCompactionEvent(
73
+ state: CompactionRegistryState,
74
+ kind: CompactionEventKind,
75
+ event: CompactionLifecycleInput,
76
+ ): void {
77
+ state.events.push({
78
+ id: event.eventId ?? eventId(kind, event),
79
+ kind,
80
+ timestamp: event.timestamp,
81
+ hookId: event.hookId,
82
+ clientId: event.clientId,
83
+ threadId: event.threadId,
84
+ runId: event.runId,
85
+ before: event.before,
86
+ after: event.after,
87
+ messagesBefore: event.messagesBefore,
88
+ messagesAfter: event.messagesAfter,
89
+ reusedCheckpoint: event.reusedCheckpoint,
90
+ maxTokens: event.maxTokens,
91
+ strategyKey: event.strategyKey,
92
+ durationMs: event.durationMs,
93
+ dropped: event.dropped,
94
+ result: event.result,
95
+ })
96
+ if (state.events.length > MAX_EVENTS) {
97
+ state.events.splice(0, state.events.length - MAX_EVENTS)
98
+ }
99
+ }
100
+
101
+ export function clearCompactionRegistry(state: CompactionRegistryState): void {
102
+ state.events = []
103
+ }
104
+
105
+ export function compactionEventsForHook(
106
+ state: CompactionRegistryState,
107
+ hook: { id: string; clientId?: string; threadId?: string },
108
+ ): Array<CompactionEventRecord> {
109
+ const matched = state.events.filter((event) => {
110
+ if (event.hookId && event.hookId === hook.id) return true
111
+ if (event.clientId && hook.clientId && event.clientId === hook.clientId) {
112
+ return true
113
+ }
114
+ if (event.threadId && hook.threadId && event.threadId === hook.threadId) {
115
+ return true
116
+ }
117
+ return false
118
+ })
119
+ if (matched.length > 0) return matched
120
+ return state.events
121
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * DevTools accumulator for the `skills:snapshot` event. Pure reducers so the
3
+ * mapping is unit-testable without a Solid store.
4
+ *
5
+ * Keyed by hookId (the chat client that received the CUSTOM chunk). The Skills
6
+ * tab resolves the active hook and reads that snapshot.
7
+ */
8
+
9
+ export interface SkillCatalogEntry {
10
+ name: string
11
+ description: string
12
+ }
13
+
14
+ export interface SkillsSnapshot {
15
+ catalog: Array<SkillCatalogEntry>
16
+ activated: Array<string>
17
+ updatedAt: number
18
+ }
19
+
20
+ export interface SkillsRegistryState {
21
+ snapshots: Record<string, SkillsSnapshot>
22
+ }
23
+
24
+ export function createSkillsRegistryState(): SkillsRegistryState {
25
+ return { snapshots: {} }
26
+ }
27
+
28
+ export function clearSkillsRegistry(state: SkillsRegistryState): void {
29
+ state.snapshots = {}
30
+ }
31
+
32
+ export function applySkillsSnapshot(
33
+ state: SkillsRegistryState,
34
+ payload: {
35
+ hookId?: string
36
+ catalog?: Array<SkillCatalogEntry>
37
+ activated?: Array<string>
38
+ timestamp?: number
39
+ },
40
+ ): void {
41
+ const key =
42
+ payload.hookId && payload.hookId.length > 0 ? payload.hookId : '_default'
43
+ const catalog = Array.isArray(payload.catalog) ? payload.catalog : []
44
+ const activated = Array.isArray(payload.activated) ? payload.activated : []
45
+ state.snapshots[key] = {
46
+ catalog,
47
+ activated,
48
+ updatedAt: payload.timestamp ?? Date.now(),
49
+ }
50
+ }
@@ -1695,7 +1695,6 @@ const stylesFactory = (theme: 'light' | 'dark') => {
1695
1695
  border: 1px solid ${t(colors.gray[200], colors.darkGray[600])};
1696
1696
  border-radius: ${border.radius.sm};
1697
1697
  background: ${t(colors.white, colors.darkGray[900])};
1698
- min-height: 260px;
1699
1698
  max-height: 420px;
1700
1699
  overflow: auto;
1701
1700
  padding: ${size[2]} ${size[2]} ${size[2]} ${size[5]};
@@ -2570,7 +2569,7 @@ const stylesFactory = (theme: 'light' | 'dark') => {
2570
2569
  gap: ${size[3]};
2571
2570
  align-items: start;
2572
2571
  & > * {
2573
- min-height: 280px;
2572
+ max-height: 420px;
2574
2573
  border: 1px solid oklch(0.28 0.03 260);
2575
2574
  border-radius: 6px;
2576
2575
  background: oklch(0.18 0.02 260);
@@ -3634,7 +3633,6 @@ const stylesFactory = (theme: 'light' | 'dark') => {
3634
3633
  `,
3635
3634
  stepJsonPanel: css`
3636
3635
  margin: ${size[1.5]} ${size[3]};
3637
- min-height: 280px;
3638
3636
  max-height: 520px;
3639
3637
  overflow: auto;
3640
3638
  padding: ${size[2]} ${size[2]} ${size[2]} ${size[5]};
@@ -3652,6 +3650,7 @@ const stylesFactory = (theme: 'light' | 'dark') => {
3652
3650
  grid-template-columns: repeat(2, minmax(320px, 1fr));
3653
3651
  gap: ${size[3]};
3654
3652
  align-items: start;
3653
+ padding-left: ${size[3]};
3655
3654
  padding-right: ${size[3]};
3656
3655
  @media (max-width: 760px) {
3657
3656
  grid-template-columns: 1fr;