Skip to content

Commit 5634f18

Browse files
AlemTuzlakautofix-ci[bot]tombeckenhamclaude
authored
feat: overhaul AI devtools hook dashboard (#632)
* feat: overhaul ai devtools * refactor(ai-client): consolidate devtools coupling, add E2E suite - Extract bridge construction into buildDevtoolsBridgeOptions helpers on ChatClient, GenerationClient, VideoGenerationClient - Add ChatDevtoolsAwareEventEmitter that auto-attaches run context and emits snapshots so clients call this.events.X(...) like normal events - Ship NoOp bridges by default; tree-shake real bridges into the @tanstack/ai-client/devtools subpath entry - Update React, Vue, Solid, Svelte, Preact hooks to pass the real bridge factory - Expand E2E suite: registration, chat, structured output, tools, generation hooks, responsive layout - Add devtools-name-types tests per framework integration * ci: apply automated fixes * fix: resolve eslint errors - Sort imports alphabetically across ai-client, ai-react, ai-solid, ai-vue, and ai-devtools-core - Drop leading underscore from NoOpGenerationDevtoolsBridge type parameter (used in constructor signature) - Disable no-restricted-syntax on intentional `as unknown as` casts in no-op bridge factories and generation result assignment, with reasons - Remove unnecessary type assertion in devtools.ts (auto-fixed) * fix(ai-devtools): address PR feedback - log + preserve raw localStorage payload on fixture parse failure instead of silently calling removeItem - log + report full error name/message from tool-fixture execute, and narrow the try block to executeFunc only so unrelated bugs in addToolResultForFixture don't masquerade as tool failures - cap seenEventIds at 10k via FIFO ring buffer to prevent unbounded memory growth over the lifetime of long-running sessions; also reset the order array in clearHookRegistry - restore ai-code-mode/vite.config.ts to main (rename detection swapped it with ai-client's during the merge); ai-client now correctly declares devtools.ts as a separate entry * docs: require pnpm test:pr locally before opening or updating a PR Add a mandatory pre-PR quality gate to CLAUDE.md and a new AGENTS.md (cross-agent file) documenting that `pnpm test:pr` — the exact target set the CI `PR` workflow runs — must be run and pass locally before pushing. Falling back on CI as the first signal wastes review cycles when a quick local run would have caught the failure. * fix(ai-devtools): address PR review feedback (critical + important + types + comments) Critical - ToolFixtureForm: remove dead `'{}' ? : '{}'` ternary leftover from refactor - ChatDevtoolsBridge.executeFixture: warn when execute=true but no client tool is registered instead of silently substituting the saved output - stringifyFixtureValue / parseFixtureResultContent: log + report the original error instead of swallowing JSON.stringify / JSON.parse failures - devtools-middleware: replace `chunk: any` with a mirrored discriminated union (DevtoolsKnownChunk) + isKnownChunk type guard, drop the `as` cast on chunk.error, fall back with the raw chunk payload instead of "Unknown error" when RUN_ERROR.message is missing - normalizeRunStatusFromSnapshot: return `null` for unknown statuses and let the caller skip + warn instead of silently coercing them to `'updated'` Important - Remove unreachable `'unmounted'` HookLifecycle variant + dead filters - Drop unused `runs` prop on GenerationPanel - removeHookRecord: also clean up TimelineEvents attached to the hook - IterationTimeline `totalUsage`: compare on `totalTokens` (not prompt+completion) so providers that include reasoning tokens in totalTokens don't get their max overwritten by a smaller later reading - jsonSafeValue / stringifyToolArguments: log + return an explicit placeholder instead of returning the original on failure - No-op bridges: add compile-time parity checks (Exclude<keyof, keyof>) so adding a public method to the real bridge fails the build until the no-op is updated, instead of becoming a runtime TypeError - devtools-middleware: wrap every emit with `safeEmit` so a subscriber throw can't bubble into the host chat engine - markEventSeen: warn when a dedupe key falls back entirely to literal sentinels; tools:registered: warn when hookName is missing - hook-registry tests: cover snapshot run backfill (success/error/idle/ unknown statuses) and lifecycle inference from snapshot state Type design - AIDevtoolsGenerationRunStatus union ('idle' | 'generating' | 'success' | 'error' | 'cancelled') replacing the previous `status: string` and threaded through GenerationDevtoolsCoreState / SnapshotBase / RunPatch - Drop redundant `unknown | null` on input / videoStatus fields - Remove `extends Record<string, unknown>` from public snapshot interfaces; ClientDevtoolsBridge constraint relaxed to `extends object` and the wire-envelope widening is pushed to emitSnapshot - Drop pointless `as Record<string, unknown>` casts at JsonTree call sites (JsonTree's TData accepts arbitrary value) Comment hygiene - Strip file-top docblocks and class-banner JSDoc rewriting the PR description (devtools-noop, use-real-devtools-bridges, devtools-system- prompt-mirror.test, several blocks in devtools.ts) - Remove "for backward compatibility" / "exactly like it did before the devtools work landed" PR-narrative phrases - Drop ASCII section-banner separators in devtools.ts, use-styles.ts, ai-context.tsx - Compress multi-line JSDoc on short methods to one-liners or remove where the name was self-describing - Strip obvious line-narration comments in ai-context.tsx batching helpers * fix(ai-devtools): address re-review nits - formatUnknown / stringifyToolArguments: warn + return explicit unserializable-value placeholder (mirroring jsonSafeValue) instead of silently returning String(value) on JSON.stringify failure - Delete dead visibleHooks identity function and the redundant `active` summary field (== total after the unmounted lifecycle was removed); drop the two duplicate "active" overview metrics so the dashboard grid isn't showing the same number under three labels - Strip remaining "what"-narration comments in ai-context.tsx and the section-divider comments in use-styles.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "fix(ai-devtools): address re-review nits" This reverts commit 6ae5aaf. * fix(ai-devtools): surface unserializable values in placeholder helpers formatUnknown and stringifyToolArguments previously fell back to String(value) (yielding "[object Object]") on JSON.stringify failure. Mirror the jsonSafeValue pattern: warn with the original error and return a self-describing placeholder so the failure is visible in both the console and the rendered devtools panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ef029a0 commit 5634f18

139 files changed

Lines changed: 19125 additions & 2569 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@tanstack/ai-client': minor
3+
'@tanstack/ai-devtools-core': minor
4+
'@tanstack/ai-event-client': minor
5+
'@tanstack/ai-preact': patch
6+
'@tanstack/ai-react': patch
7+
'@tanstack/ai-solid': patch
8+
'@tanstack/ai-svelte': patch
9+
'@tanstack/ai-vue': patch
10+
---
11+
12+
Add hook-aware AI devtools registration, run tracking, state snapshots, and tool fixture replay.

AGENTS.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# AGENTS.md
2+
3+
Cross-agent guidance for this repository. See `CLAUDE.md` for the full project
4+
overview, architecture, and conventions — this file mirrors the rules that
5+
apply to every coding agent regardless of tool.
6+
7+
## Pre-PR Quality Gate (MANDATORY)
8+
9+
**Before opening a PR or pushing changes intended for review, you MUST run the
10+
same checks CI runs and confirm they pass locally.** Pushing without running
11+
these is not acceptable — CI will fail and waste review cycles.
12+
13+
The single canonical command is:
14+
15+
```bash
16+
pnpm test:pr
17+
```
18+
19+
This runs the exact target set the `PR` workflow runs in CI
20+
(`nx affected --targets=test:sherif,test:knip,test:docs,test:eslint,test:lib,test:types,test:build,build --exclude=examples/**,testing/**`).
21+
22+
If you can't run `test:pr` (e.g. it's too slow on your machine), at minimum run
23+
each of these and confirm they're green before pushing:
24+
25+
- `pnpm test:sherif` — workspace consistency
26+
- `pnpm test:knip` — unused dependencies
27+
- `pnpm test:docs` — doc link verification
28+
- `pnpm test:eslint` — lint
29+
- `pnpm test:types` — typecheck
30+
- `pnpm test:lib` — unit tests
31+
- `pnpm test:build` — build artifact verification
32+
- `pnpm build` — build all affected packages
33+
- `pnpm --filter @tanstack/ai-e2e test:e2e` — E2E suite (mandatory for any
34+
behavior change; see `testing/e2e/README.md`)
35+
36+
Do **not** rely on CI as your first signal. Run locally, fix, then push.
37+
38+
## Everything Else
39+
40+
For package manager (`pnpm@10.17.0`), monorepo layout, adapter architecture,
41+
tool system, framework integrations, E2E requirements, and all other
42+
conventions, read `CLAUDE.md` in this directory.

CLAUDE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,32 @@ Each framework integration uses the headless `ai-client` under the hood.
215215
8. Format code: `pnpm format`
216216
9. Verify build: `pnpm test:build` or `pnpm build`
217217

218+
### Pre-PR Quality Gate (MANDATORY)
219+
220+
**Before opening a PR or pushing changes intended for review, you MUST run the same checks CI runs and confirm they pass locally.** Pushing without running these is not acceptable — CI will fail and waste review cycles.
221+
222+
The single canonical command is:
223+
224+
```bash
225+
pnpm test:pr
226+
```
227+
228+
This runs the exact target set the `PR` workflow runs in CI (`nx affected --targets=test:sherif,test:knip,test:docs,test:eslint,test:lib,test:types,test:build,build --exclude=examples/**,testing/**`).
229+
230+
If you can't run `test:pr` (e.g. it's too slow on your machine), at minimum run each of these and confirm they're green before pushing:
231+
232+
- `pnpm test:sherif` — workspace consistency
233+
- `pnpm test:knip` — unused dependencies
234+
- `pnpm test:docs` — doc link verification
235+
- `pnpm test:eslint` — lint
236+
- `pnpm test:types` — typecheck
237+
- `pnpm test:lib` — unit tests
238+
- `pnpm test:build` — build artifact verification
239+
- `pnpm build` — build all affected packages
240+
- `pnpm --filter @tanstack/ai-e2e test:e2e` — E2E suite (mandatory for any behavior change; see E2E Testing)
241+
242+
Do **not** rely on CI as your first signal. Run locally, fix, then push.
243+
218244
### Working with Examples
219245

220246
Examples are not built by Nx. To run an example:

docs/getting-started/devtools.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,68 @@ keywords:
1616
TanStack Devtools is a unified devtools panel for inspecting and debugging TanStack libraries, including TanStack AI. It provides real-time insights into AI interactions, tool calls, and state changes, making it easier to develop and troubleshoot AI-powered applications.
1717

1818
## Features
19+
- **Hook dashboard** - Discover every active TanStack AI hook on the page, including chat, structured output, image, video, audio, speech, transcription, and summarize hooks.
20+
- **Run timeline** - Inspect user turns, linked runs, stream events, client snapshots, and server-only events by `threadId` and `runId`.
1921
- **Real-time Monitoring** - View live chat messages, tool invocations, and AI responses.
2022
- **Tool Call Inspection** - Inspect input and output of tool calls.
23+
- **Tool Fixture Replay** - Build tool payloads from a tool's standard-schema input, append the result into chat messages, and save fixtures in localStorage for repeated UI iteration.
2124
- **State Visualization** - Visualize chat state and message history.
2225
- **Error Tracking** - Monitor errors and exceptions in AI interactions.
2326

27+
## Hook Dashboard
28+
29+
The AI devtools panel listens for active TanStack AI clients and shows them in the left sidebar. Hooks register when they are created, emit a snapshot immediately, and respond again whenever the devtools panel opens or requests state. This keeps hooks discoverable even when the panel is opened after the app has already rendered.
30+
31+
Each hook entry includes its type, lifecycle, message count, run count, and the latest linked `threadId`. Selecting a hook opens the full timeline for that hook. Chat hooks keep the current turn-based view: a user message wraps every run and event that happened while answering that turn. The details view also includes lightweight client/server state snapshots between runs so you can see exactly what changed.
32+
33+
### Naming Hooks
34+
35+
When a page has more than one AI hook, pass `devtools.name` to give each hook a user-facing label in the dashboard. The configured name is display-only; hook type, framework, thread id, and run correlation still come from the TanStack AI client.
36+
37+
```tsx
38+
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
39+
40+
export function SupportChat() {
41+
const chat = useChat({
42+
id: 'support-chat',
43+
connection: fetchServerSentEvents('/api/chat'),
44+
devtools: {
45+
name: 'Support Chat',
46+
},
47+
})
48+
49+
// render your chat UI with `chat.messages`, `chat.sendMessage`, etc.
50+
}
51+
```
52+
53+
The same display option works for specialized generation hooks:
54+
55+
```tsx
56+
import { fetchServerSentEvents, useGenerateImage } from '@tanstack/ai-react'
57+
58+
export function ImageStudio() {
59+
const image = useGenerateImage({
60+
id: 'generation-hooks:useGenerateImage',
61+
connection: fetchServerSentEvents('/api/image'),
62+
devtools: {
63+
name: 'Image Studio',
64+
},
65+
})
66+
67+
// render your image generation UI with `image.generate` and `image.result`
68+
}
69+
```
70+
71+
## Tool Fixtures
72+
73+
When a `useChat` hook receives tools, the devtools panel lists those tools and their schemas. For standard-schema-compatible inputs, the panel renders a small form from the input schema so you can create a tool call payload without hand-writing JSON.
74+
75+
Applying a tool fixture appends the tool call and result into the real chat messages for that hook. Saved fixtures are stored in browser localStorage under the AI devtools namespace so they are available the next time you open the panel.
76+
77+
## Event Sources
78+
79+
Client-visible state is emitted by the headless client. Server-only details, such as middleware and provider stream events that never exist on the client, are emitted from the server counterpart. Events include a source descriptor and stable envelope id so the panel can link related events and avoid displaying duplicates.
80+
2481
## Installation
2582
To use TanStack Devtools with TanStack AI, install the `@tanstack/react-ai-devtools` package:
2683

examples/ts-react-chat/src/components/Header.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Link } from '@tanstack/react-router'
22

33
import { useState } from 'react'
44
import {
5+
Activity,
56
Braces,
67
FileAudio,
78
FileText,
@@ -76,6 +77,19 @@ export default function Header() {
7677
Generations
7778
</p>
7879

80+
<Link
81+
to="/generation-hooks"
82+
onClick={() => setIsOpen(false)}
83+
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-1"
84+
activeProps={{
85+
className:
86+
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-1',
87+
}}
88+
>
89+
<Activity size={20} />
90+
<span className="font-medium">Generation Hooks</span>
91+
</Link>
92+
7993
<Link
8094
to="/generations/image"
8195
onClick={() => setIsOpen(false)}

examples/ts-react-chat/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Route as ServerFnChatRouteImport } from './routes/server-fn-chat'
1313
import { Route as RealtimeRouteImport } from './routes/realtime'
1414
import { Route as Issue176ToolResultRouteImport } from './routes/issue-176-tool-result'
1515
import { Route as ImageGenRouteImport } from './routes/image-gen'
16+
import { Route as GenerationHooksRouteImport } from './routes/generation-hooks'
1617
import { Route as IndexRouteImport } from './routes/index'
1718
import { Route as GenerationsVideoRouteImport } from './routes/generations.video'
1819
import { Route as GenerationsTranscriptionRouteImport } from './routes/generations.transcription'
@@ -55,6 +56,11 @@ const ImageGenRoute = ImageGenRouteImport.update({
5556
path: '/image-gen',
5657
getParentRoute: () => rootRouteImport,
5758
} as any)
59+
const GenerationHooksRoute = GenerationHooksRouteImport.update({
60+
id: '/generation-hooks',
61+
path: '/generation-hooks',
62+
getParentRoute: () => rootRouteImport,
63+
} as any)
5864
const IndexRoute = IndexRouteImport.update({
5965
id: '/',
6066
path: '/',
@@ -166,6 +172,7 @@ const ApiGenerateAudioRoute = ApiGenerateAudioRouteImport.update({
166172

167173
export interface FileRoutesByFullPath {
168174
'/': typeof IndexRoute
175+
'/generation-hooks': typeof GenerationHooksRoute
169176
'/image-gen': typeof ImageGenRoute
170177
'/issue-176-tool-result': typeof Issue176ToolResultRoute
171178
'/realtime': typeof RealtimeRoute
@@ -193,6 +200,7 @@ export interface FileRoutesByFullPath {
193200
}
194201
export interface FileRoutesByTo {
195202
'/': typeof IndexRoute
203+
'/generation-hooks': typeof GenerationHooksRoute
196204
'/image-gen': typeof ImageGenRoute
197205
'/issue-176-tool-result': typeof Issue176ToolResultRoute
198206
'/realtime': typeof RealtimeRoute
@@ -221,6 +229,7 @@ export interface FileRoutesByTo {
221229
export interface FileRoutesById {
222230
__root__: typeof rootRouteImport
223231
'/': typeof IndexRoute
232+
'/generation-hooks': typeof GenerationHooksRoute
224233
'/image-gen': typeof ImageGenRoute
225234
'/issue-176-tool-result': typeof Issue176ToolResultRoute
226235
'/realtime': typeof RealtimeRoute
@@ -250,6 +259,7 @@ export interface FileRouteTypes {
250259
fileRoutesByFullPath: FileRoutesByFullPath
251260
fullPaths:
252261
| '/'
262+
| '/generation-hooks'
253263
| '/image-gen'
254264
| '/issue-176-tool-result'
255265
| '/realtime'
@@ -277,6 +287,7 @@ export interface FileRouteTypes {
277287
fileRoutesByTo: FileRoutesByTo
278288
to:
279289
| '/'
290+
| '/generation-hooks'
280291
| '/image-gen'
281292
| '/issue-176-tool-result'
282293
| '/realtime'
@@ -304,6 +315,7 @@ export interface FileRouteTypes {
304315
id:
305316
| '__root__'
306317
| '/'
318+
| '/generation-hooks'
307319
| '/image-gen'
308320
| '/issue-176-tool-result'
309321
| '/realtime'
@@ -332,6 +344,7 @@ export interface FileRouteTypes {
332344
}
333345
export interface RootRouteChildren {
334346
IndexRoute: typeof IndexRoute
347+
GenerationHooksRoute: typeof GenerationHooksRoute
335348
ImageGenRoute: typeof ImageGenRoute
336349
Issue176ToolResultRoute: typeof Issue176ToolResultRoute
337350
RealtimeRoute: typeof RealtimeRoute
@@ -388,6 +401,13 @@ declare module '@tanstack/react-router' {
388401
preLoaderRoute: typeof ImageGenRouteImport
389402
parentRoute: typeof rootRouteImport
390403
}
404+
'/generation-hooks': {
405+
id: '/generation-hooks'
406+
path: '/generation-hooks'
407+
fullPath: '/generation-hooks'
408+
preLoaderRoute: typeof GenerationHooksRouteImport
409+
parentRoute: typeof rootRouteImport
410+
}
391411
'/': {
392412
id: '/'
393413
path: '/'
@@ -540,6 +560,7 @@ declare module '@tanstack/react-router' {
540560

541561
const rootRouteChildren: RootRouteChildren = {
542562
IndexRoute: IndexRoute,
563+
GenerationHooksRoute: GenerationHooksRoute,
543564
ImageGenRoute: ImageGenRoute,
544565
Issue176ToolResultRoute: Issue176ToolResultRoute,
545566
RealtimeRoute: RealtimeRoute,

0 commit comments

Comments
 (0)