diff --git a/.changeset/ai-client-store-snapshot.md b/.changeset/ai-client-store-snapshot.md new file mode 100644 index 0000000000..6a54bb2918 --- /dev/null +++ b/.changeset/ai-client-store-snapshot.md @@ -0,0 +1,14 @@ +--- +'@tanstack/ai-client': minor +'@tanstack/ai-react': patch +'@tanstack/ai-solid': patch +'@tanstack/ai-vue': patch +'@tanstack/ai-svelte': patch +'@tanstack/ai-angular': patch +'@tanstack/ai-preact': patch +'@tanstack/ai-octane': patch +--- + +Hold client UI state in a TanStack Store atom. Framework hooks read `getSnapshot()` instead of copying fields through change callbacks. React `useChat()` first commits `initialMessages` and `initialResumeSnapshot`, then applies browser persistence after attach. This timing applies to client-only rendering and keeps the server snapshot stable during SSR hydration. The `useChat()` return shape stays the same. + +`ChatClient` adds `subscribeSnapshot` / `getSnapshot`. The live connection method stays `subscribe()`. Generation, video, realtime, audio, and BYOK clients expose the same snapshot pair (`subscribe` / `getSnapshot`) where the name does not collide. diff --git a/docs/api/ai-client.md b/docs/api/ai-client.md index 16456962b5..1fabcda168 100644 --- a/docs/api/ai-client.md +++ b/docs/api/ai-client.md @@ -216,6 +216,41 @@ Stop tailing and drop the connection. Keeps messages, the run pointer and the ru id, so a later `attach()` continues where it left off. See [Lifecycle](#lifecycle-attach-and-detach). +#### `getSnapshot()` + +Returns the current UI snapshot: messages, status, loading, error, queue, run +id, subscription, connection, session generating, and interrupt state. + +`subscribeSnapshot(listener)` calls `listener` after that snapshot changes. It +does not fire with the current value. Read `getSnapshot()` first. This is not +`subscribe()`, which starts the live connection loop. + +```typescript +import { ChatClient, fetchServerSentEvents } from "@tanstack/ai-client"; + +const client = new ChatClient({ + connection: fetchServerSentEvents("/api/chat"), +}); + +function logSnapshot() { + const snapshot = client.getSnapshot(); + console.log(snapshot.status, snapshot.messages.length); +} + +logSnapshot(); + +const stop = client.subscribeSnapshot(() => { + logSnapshot(); +}); + +client.attach(); +client.sendMessage("Hello"); +``` + +`GenerationClient`, `VideoGenerationClient`, `RealtimeClient`, `AudioRecorder`, +and `ByokClient` use `subscribe(listener)` / `getSnapshot()` for the same job. +Their `subscribe` name does not collide with a live connection method. + #### `stop()` Stops the current response generation. It aborts the in-flight request. diff --git a/docs/persistence/client-persistence.md b/docs/persistence/client-persistence.md index b38673ea1c..e2d3e79366 100644 --- a/docs/persistence/client-persistence.md +++ b/docs/persistence/client-persistence.md @@ -56,10 +56,13 @@ to pick one and how it differs from the `runId` the hook reports. The client stores one record per `threadId`, the transcript plus a small resume pointer. On the next load `useChat` reads it and: -- **Repaints the transcript** from storage with no network. Sync adapters - (`localStorage` / `sessionStorage`) hydrate during construction; IndexedDB - hydrates asynchronously after the database opens (so the first paint may be - empty for a tick). +- **Repaints the transcript** from storage with no network. In React, the first + committed render uses `initialMessages` and `initialResumeSnapshot`. After the + hook attaches, it applies browser persistence from either a synchronous or an + asynchronous adapter. This timing also prevents SSR hydration mismatches. A + direct `ChatClient` and non-React framework wrappers still apply synchronous + stores during construction. IndexedDB always hydrates asynchronously after + the database opens. - **Rehydrates a pending interrupt**, so an approval prompt comes back exactly as it was. - **Rejoins an in-flight run**, if a reply was still streaming when the page diff --git a/examples/sandbox-cloudflare/package.json b/examples/sandbox-cloudflare/package.json index b6038d8cf8..1375ec6d77 100644 --- a/examples/sandbox-cloudflare/package.json +++ b/examples/sandbox-cloudflare/package.json @@ -22,8 +22,8 @@ "@tanstack/ai-sandbox": "workspace:*", "@tanstack/ai-sandbox-cloudflare": "workspace:*", "@tanstack/ai-sandbox-local-process": "workspace:*", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", "lucide-react": "^0.561.0", "react": "^19.2.3", "react-dom": "^19.2.3", @@ -37,7 +37,7 @@ "devDependencies": { "@cloudflare/vite-plugin": "^1.42.1", "@tailwindcss/vite": "^4.1.18", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/router-plugin": "^1.168.35", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", diff --git a/examples/sandbox-web/package.json b/examples/sandbox-web/package.json index 75f96ab13e..941ff6df61 100644 --- a/examples/sandbox-web/package.json +++ b/examples/sandbox-web/package.json @@ -17,8 +17,8 @@ "@tanstack/ai-react": "workspace:*", "@tanstack/ai-sandbox": "workspace:*", "@tanstack/ai-sandbox-docker": "workspace:*", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", "lucide-react": "^0.561.0", "react": "^19.2.3", "react-dom": "^19.2.3", @@ -31,7 +31,7 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.1.18", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/router-plugin": "^1.168.35", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", diff --git a/examples/sandbox-web/src/routeTree.gen.ts b/examples/sandbox-web/src/routeTree.gen.ts index 5bc1e9dd01..1e6a913d7e 100644 --- a/examples/sandbox-web/src/routeTree.gen.ts +++ b/examples/sandbox-web/src/routeTree.gen.ts @@ -11,8 +11,8 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiRunRouteImport } from './routes/api.run' -import { Route as ApiRunCancelRouteImport } from './routes/api.run.cancel' import { Route as ApiRunActiveRouteImport } from './routes/api.run.active' +import { Route as ApiRunCancelRouteImport } from './routes/api.run.cancel' const IndexRoute = IndexRouteImport.update({ id: '/', @@ -24,16 +24,16 @@ const ApiRunRoute = ApiRunRouteImport.update({ path: '/api/run', getParentRoute: () => rootRouteImport, } as any) -const ApiRunCancelRoute = ApiRunCancelRouteImport.update({ - id: '/cancel', - path: '/cancel', - getParentRoute: () => ApiRunRoute, -} as any) const ApiRunActiveRoute = ApiRunActiveRouteImport.update({ id: '/active', path: '/active', getParentRoute: () => ApiRunRoute, } as any) +const ApiRunCancelRoute = ApiRunCancelRouteImport.update({ + id: '/cancel', + path: '/cancel', + getParentRoute: () => ApiRunRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -83,13 +83,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiRunRouteImport parentRoute: typeof rootRouteImport } - '/api/run/cancel': { - id: '/api/run/cancel' - path: '/cancel' - fullPath: '/api/run/cancel' - preLoaderRoute: typeof ApiRunCancelRouteImport - parentRoute: typeof ApiRunRoute - } '/api/run/active': { id: '/api/run/active' path: '/active' @@ -97,6 +90,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiRunActiveRouteImport parentRoute: typeof ApiRunRoute } + '/api/run/cancel': { + id: '/api/run/cancel' + path: '/cancel' + fullPath: '/api/run/cancel' + preLoaderRoute: typeof ApiRunCancelRouteImport + parentRoute: typeof ApiRunRoute + } } } diff --git a/examples/ts-code-mode-web/package.json b/examples/ts-code-mode-web/package.json index 6ffbec8b6e..c829b45cf6 100644 --- a/examples/ts-code-mode-web/package.json +++ b/examples/ts-code-mode-web/package.json @@ -32,10 +32,10 @@ "@tanstack/ai-ollama": "workspace:*", "@tanstack/ai-openai": "workspace:*", "@tanstack/ai-react": "workspace:*", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", - "@tanstack/router-plugin": "^1.158.4", - "@tanstack/store": "^0.8.0", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", + "@tanstack/router-plugin": "^1.168.35", + "@tanstack/store": "^0.11.1", "framer-motion": "^11.15.0", "highlight.js": "^11.11.1", "isolated-vm": "^6.0.2", diff --git a/examples/ts-code-mode-web/src/routeTree.gen.ts b/examples/ts-code-mode-web/src/routeTree.gen.ts index e8255fd491..81c372c1ec 100644 --- a/examples/ts-code-mode-web/src/routeTree.gen.ts +++ b/examples/ts-code-mode-web/src/routeTree.gen.ts @@ -9,78 +9,101 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as StructuredOutputRouteRouteImport } from './routes/_structured-output/route' -import { Route as ReportingRouteRouteImport } from './routes/_reporting/route' -import { Route as NpmGithubChatRouteRouteImport } from './routes/_npm-github-chat/route' -import { Route as HomeRouteRouteImport } from './routes/_home/route' -import { Route as ExecutePromptRouteRouteImport } from './routes/_execute-prompt/route' -import { Route as DatabaseDemoRouteRouteImport } from './routes/_database-demo/route' import { Route as BankingDemoRouteRouteImport } from './routes/_banking-demo/route' +import { Route as DatabaseDemoRouteRouteImport } from './routes/_database-demo/route' +import { Route as ExecutePromptRouteRouteImport } from './routes/_execute-prompt/route' +import { Route as HomeRouteRouteImport } from './routes/_home/route' +import { Route as NpmGithubChatRouteRouteImport } from './routes/_npm-github-chat/route' +import { Route as ReportingRouteRouteImport } from './routes/_reporting/route' +import { Route as StructuredOutputRouteRouteImport } from './routes/_structured-output/route' +import { Route as BankingDemoBankingDemoRouteImport } from './routes/_banking-demo/banking-demo' +import { Route as DatabaseDemoDatabaseDemoRouteImport } from './routes/_database-demo/database-demo' +import { Route as ExecutePromptExecutePromptRouteImport } from './routes/_execute-prompt/execute-prompt' import { Route as HomeIndexRouteImport } from './routes/_home/index' -import { Route as ApiRuntimeRouteImport } from './routes/api.runtime' -import { Route as StructuredOutputStructuredOutputRouteImport } from './routes/_structured-output/structured-output' -import { Route as ReportingReportingAgentRouteImport } from './routes/_reporting/reporting-agent' import { Route as NpmGithubChatNpmGithubChatRouteImport } from './routes/_npm-github-chat/npm-github-chat' -import { Route as ExecutePromptExecutePromptRouteImport } from './routes/_execute-prompt/execute-prompt' -import { Route as DatabaseDemoDatabaseDemoRouteImport } from './routes/_database-demo/database-demo' -import { Route as BankingDemoBankingDemoRouteImport } from './routes/_banking-demo/banking-demo' -import { Route as StructuredOutputApiStructuredOutputSnippetsRouteImport } from './routes/_structured-output/api.structured-output-snippets' -import { Route as StructuredOutputApiStructuredOutputRouteImport } from './routes/_structured-output/api.structured-output' -import { Route as ReportingApiReportsRouteImport } from './routes/_reporting/api.reports' -import { Route as ReportingApiReportSseRouteImport } from './routes/_reporting/api.report-sse' -import { Route as ReportingApiReportEventRouteImport } from './routes/_reporting/api.report-event' -import { Route as ReportingApiReportDemoRouteImport } from './routes/_reporting/api.report-demo' -import { Route as ReportingApiInvalidateRouteImport } from './routes/_reporting/api.invalidate' -import { Route as NpmGithubChatApiGeneratePdfRouteImport } from './routes/_npm-github-chat/api.generate-pdf' -import { Route as NpmGithubChatApiCodemodeRouteImport } from './routes/_npm-github-chat/api.codemode' -import { Route as HomeApiSnippetsRouteImport } from './routes/_home/api.snippets' -import { Route as HomeApiProductRegularRouteImport } from './routes/_home/api.product-regular' -import { Route as HomeApiProductCodemodeRouteImport } from './routes/_home/api.product-codemode' -import { Route as ExecutePromptApiRealtimeTokenRouteImport } from './routes/_execute-prompt/api.realtime-token' -import { Route as ExecutePromptApiExecutePromptRouteImport } from './routes/_execute-prompt/api.execute-prompt' -import { Route as DatabaseDemoApiJudgeRouteImport } from './routes/_database-demo/api.judge' -import { Route as DatabaseDemoApiDbSnippetsRouteImport } from './routes/_database-demo/api.db-snippets' -import { Route as DatabaseDemoApiDatabaseDemoRouteImport } from './routes/_database-demo/api.database-demo' -import { Route as BankingDemoApiBankingInitRouteImport } from './routes/_banking-demo/api.banking-init' +import { Route as ReportingReportingAgentRouteImport } from './routes/_reporting/reporting-agent' +import { Route as StructuredOutputStructuredOutputRouteImport } from './routes/_structured-output/structured-output' +import { Route as ApiRuntimeRouteImport } from './routes/api.runtime' import { Route as BankingDemoApiBankingDemoRouteImport } from './routes/_banking-demo/api.banking-demo' +import { Route as BankingDemoApiBankingInitRouteImport } from './routes/_banking-demo/api.banking-init' +import { Route as DatabaseDemoApiDatabaseDemoRouteImport } from './routes/_database-demo/api.database-demo' +import { Route as DatabaseDemoApiDbSnippetsRouteImport } from './routes/_database-demo/api.db-snippets' +import { Route as DatabaseDemoApiJudgeRouteImport } from './routes/_database-demo/api.judge' +import { Route as ExecutePromptApiExecutePromptRouteImport } from './routes/_execute-prompt/api.execute-prompt' +import { Route as ExecutePromptApiRealtimeTokenRouteImport } from './routes/_execute-prompt/api.realtime-token' +import { Route as HomeApiProductCodemodeRouteImport } from './routes/_home/api.product-codemode' +import { Route as HomeApiProductRegularRouteImport } from './routes/_home/api.product-regular' +import { Route as HomeApiSnippetsRouteImport } from './routes/_home/api.snippets' +import { Route as NpmGithubChatApiCodemodeRouteImport } from './routes/_npm-github-chat/api.codemode' +import { Route as NpmGithubChatApiGeneratePdfRouteImport } from './routes/_npm-github-chat/api.generate-pdf' +import { Route as ReportingApiInvalidateRouteImport } from './routes/_reporting/api.invalidate' +import { Route as ReportingApiReportDemoRouteImport } from './routes/_reporting/api.report-demo' +import { Route as ReportingApiReportEventRouteImport } from './routes/_reporting/api.report-event' +import { Route as ReportingApiReportSseRouteImport } from './routes/_reporting/api.report-sse' +import { Route as ReportingApiReportsRouteImport } from './routes/_reporting/api.reports' +import { Route as StructuredOutputApiStructuredOutputRouteImport } from './routes/_structured-output/api.structured-output' +import { Route as StructuredOutputApiStructuredOutputSnippetsRouteImport } from './routes/_structured-output/api.structured-output-snippets' -const StructuredOutputRouteRoute = StructuredOutputRouteRouteImport.update({ - id: '/_structured-output', +const BankingDemoRouteRoute = BankingDemoRouteRouteImport.update({ + id: '/_banking-demo', getParentRoute: () => rootRouteImport, } as any) -const ReportingRouteRoute = ReportingRouteRouteImport.update({ - id: '/_reporting', +const DatabaseDemoRouteRoute = DatabaseDemoRouteRouteImport.update({ + id: '/_database-demo', getParentRoute: () => rootRouteImport, } as any) -const NpmGithubChatRouteRoute = NpmGithubChatRouteRouteImport.update({ - id: '/_npm-github-chat', +const ExecutePromptRouteRoute = ExecutePromptRouteRouteImport.update({ + id: '/_execute-prompt', getParentRoute: () => rootRouteImport, } as any) const HomeRouteRoute = HomeRouteRouteImport.update({ id: '/_home', getParentRoute: () => rootRouteImport, } as any) -const ExecutePromptRouteRoute = ExecutePromptRouteRouteImport.update({ - id: '/_execute-prompt', +const NpmGithubChatRouteRoute = NpmGithubChatRouteRouteImport.update({ + id: '/_npm-github-chat', getParentRoute: () => rootRouteImport, } as any) -const DatabaseDemoRouteRoute = DatabaseDemoRouteRouteImport.update({ - id: '/_database-demo', +const ReportingRouteRoute = ReportingRouteRouteImport.update({ + id: '/_reporting', getParentRoute: () => rootRouteImport, } as any) -const BankingDemoRouteRoute = BankingDemoRouteRouteImport.update({ - id: '/_banking-demo', +const StructuredOutputRouteRoute = StructuredOutputRouteRouteImport.update({ + id: '/_structured-output', getParentRoute: () => rootRouteImport, } as any) +const BankingDemoBankingDemoRoute = BankingDemoBankingDemoRouteImport.update({ + id: '/banking-demo', + path: '/banking-demo', + getParentRoute: () => BankingDemoRouteRoute, +} as any) +const DatabaseDemoDatabaseDemoRoute = + DatabaseDemoDatabaseDemoRouteImport.update({ + id: '/database-demo', + path: '/database-demo', + getParentRoute: () => DatabaseDemoRouteRoute, + } as any) +const ExecutePromptExecutePromptRoute = + ExecutePromptExecutePromptRouteImport.update({ + id: '/execute-prompt', + path: '/execute-prompt', + getParentRoute: () => ExecutePromptRouteRoute, + } as any) const HomeIndexRoute = HomeIndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => HomeRouteRoute, } as any) -const ApiRuntimeRoute = ApiRuntimeRouteImport.update({ - id: '/api/runtime', - path: '/api/runtime', - getParentRoute: () => rootRouteImport, +const NpmGithubChatNpmGithubChatRoute = + NpmGithubChatNpmGithubChatRouteImport.update({ + id: '/npm-github-chat', + path: '/npm-github-chat', + getParentRoute: () => NpmGithubChatRouteRoute, + } as any) +const ReportingReportingAgentRoute = ReportingReportingAgentRouteImport.update({ + id: '/reporting-agent', + path: '/reporting-agent', + getParentRoute: () => ReportingRouteRoute, } as any) const StructuredOutputStructuredOutputRoute = StructuredOutputStructuredOutputRouteImport.update({ @@ -88,54 +111,87 @@ const StructuredOutputStructuredOutputRoute = path: '/structured-output', getParentRoute: () => StructuredOutputRouteRoute, } as any) -const ReportingReportingAgentRoute = ReportingReportingAgentRouteImport.update({ - id: '/reporting-agent', - path: '/reporting-agent', - getParentRoute: () => ReportingRouteRoute, +const ApiRuntimeRoute = ApiRuntimeRouteImport.update({ + id: '/api/runtime', + path: '/api/runtime', + getParentRoute: () => rootRouteImport, } as any) -const NpmGithubChatNpmGithubChatRoute = - NpmGithubChatNpmGithubChatRouteImport.update({ - id: '/npm-github-chat', - path: '/npm-github-chat', - getParentRoute: () => NpmGithubChatRouteRoute, +const BankingDemoApiBankingDemoRoute = + BankingDemoApiBankingDemoRouteImport.update({ + id: '/api/banking-demo', + path: '/api/banking-demo', + getParentRoute: () => BankingDemoRouteRoute, } as any) -const ExecutePromptExecutePromptRoute = - ExecutePromptExecutePromptRouteImport.update({ - id: '/execute-prompt', - path: '/execute-prompt', - getParentRoute: () => ExecutePromptRouteRoute, +const BankingDemoApiBankingInitRoute = + BankingDemoApiBankingInitRouteImport.update({ + id: '/api/banking-init', + path: '/api/banking-init', + getParentRoute: () => BankingDemoRouteRoute, } as any) -const DatabaseDemoDatabaseDemoRoute = - DatabaseDemoDatabaseDemoRouteImport.update({ - id: '/database-demo', - path: '/database-demo', +const DatabaseDemoApiDatabaseDemoRoute = + DatabaseDemoApiDatabaseDemoRouteImport.update({ + id: '/api/database-demo', + path: '/api/database-demo', getParentRoute: () => DatabaseDemoRouteRoute, } as any) -const BankingDemoBankingDemoRoute = BankingDemoBankingDemoRouteImport.update({ - id: '/banking-demo', - path: '/banking-demo', - getParentRoute: () => BankingDemoRouteRoute, +const DatabaseDemoApiDbSnippetsRoute = + DatabaseDemoApiDbSnippetsRouteImport.update({ + id: '/api/db-snippets', + path: '/api/db-snippets', + getParentRoute: () => DatabaseDemoRouteRoute, + } as any) +const DatabaseDemoApiJudgeRoute = DatabaseDemoApiJudgeRouteImport.update({ + id: '/api/judge', + path: '/api/judge', + getParentRoute: () => DatabaseDemoRouteRoute, } as any) -const StructuredOutputApiStructuredOutputSnippetsRoute = - StructuredOutputApiStructuredOutputSnippetsRouteImport.update({ - id: '/api/structured-output-snippets', - path: '/api/structured-output-snippets', - getParentRoute: () => StructuredOutputRouteRoute, +const ExecutePromptApiExecutePromptRoute = + ExecutePromptApiExecutePromptRouteImport.update({ + id: '/api/execute-prompt', + path: '/api/execute-prompt', + getParentRoute: () => ExecutePromptRouteRoute, } as any) -const StructuredOutputApiStructuredOutputRoute = - StructuredOutputApiStructuredOutputRouteImport.update({ - id: '/api/structured-output', - path: '/api/structured-output', - getParentRoute: () => StructuredOutputRouteRoute, +const ExecutePromptApiRealtimeTokenRoute = + ExecutePromptApiRealtimeTokenRouteImport.update({ + id: '/api/realtime-token', + path: '/api/realtime-token', + getParentRoute: () => ExecutePromptRouteRoute, } as any) -const ReportingApiReportsRoute = ReportingApiReportsRouteImport.update({ - id: '/api/reports', - path: '/api/reports', +const HomeApiProductCodemodeRoute = HomeApiProductCodemodeRouteImport.update({ + id: '/api/product-codemode', + path: '/api/product-codemode', + getParentRoute: () => HomeRouteRoute, +} as any) +const HomeApiProductRegularRoute = HomeApiProductRegularRouteImport.update({ + id: '/api/product-regular', + path: '/api/product-regular', + getParentRoute: () => HomeRouteRoute, +} as any) +const HomeApiSnippetsRoute = HomeApiSnippetsRouteImport.update({ + id: '/api/snippets', + path: '/api/snippets', + getParentRoute: () => HomeRouteRoute, +} as any) +const NpmGithubChatApiCodemodeRoute = + NpmGithubChatApiCodemodeRouteImport.update({ + id: '/api/codemode', + path: '/api/codemode', + getParentRoute: () => NpmGithubChatRouteRoute, + } as any) +const NpmGithubChatApiGeneratePdfRoute = + NpmGithubChatApiGeneratePdfRouteImport.update({ + id: '/api/generate-pdf', + path: '/api/generate-pdf', + getParentRoute: () => NpmGithubChatRouteRoute, + } as any) +const ReportingApiInvalidateRoute = ReportingApiInvalidateRouteImport.update({ + id: '/api/invalidate', + path: '/api/invalidate', getParentRoute: () => ReportingRouteRoute, } as any) -const ReportingApiReportSseRoute = ReportingApiReportSseRouteImport.update({ - id: '/api/report-sse', - path: '/api/report-sse', +const ReportingApiReportDemoRoute = ReportingApiReportDemoRouteImport.update({ + id: '/api/report-demo', + path: '/api/report-demo', getParentRoute: () => ReportingRouteRoute, } as any) const ReportingApiReportEventRoute = ReportingApiReportEventRouteImport.update({ @@ -143,83 +199,27 @@ const ReportingApiReportEventRoute = ReportingApiReportEventRouteImport.update({ path: '/api/report-event', getParentRoute: () => ReportingRouteRoute, } as any) -const ReportingApiReportDemoRoute = ReportingApiReportDemoRouteImport.update({ - id: '/api/report-demo', - path: '/api/report-demo', +const ReportingApiReportSseRoute = ReportingApiReportSseRouteImport.update({ + id: '/api/report-sse', + path: '/api/report-sse', getParentRoute: () => ReportingRouteRoute, } as any) -const ReportingApiInvalidateRoute = ReportingApiInvalidateRouteImport.update({ - id: '/api/invalidate', - path: '/api/invalidate', +const ReportingApiReportsRoute = ReportingApiReportsRouteImport.update({ + id: '/api/reports', + path: '/api/reports', getParentRoute: () => ReportingRouteRoute, } as any) -const NpmGithubChatApiGeneratePdfRoute = - NpmGithubChatApiGeneratePdfRouteImport.update({ - id: '/api/generate-pdf', - path: '/api/generate-pdf', - getParentRoute: () => NpmGithubChatRouteRoute, +const StructuredOutputApiStructuredOutputRoute = + StructuredOutputApiStructuredOutputRouteImport.update({ + id: '/api/structured-output', + path: '/api/structured-output', + getParentRoute: () => StructuredOutputRouteRoute, } as any) -const NpmGithubChatApiCodemodeRoute = - NpmGithubChatApiCodemodeRouteImport.update({ - id: '/api/codemode', - path: '/api/codemode', - getParentRoute: () => NpmGithubChatRouteRoute, - } as any) -const HomeApiSnippetsRoute = HomeApiSnippetsRouteImport.update({ - id: '/api/snippets', - path: '/api/snippets', - getParentRoute: () => HomeRouteRoute, -} as any) -const HomeApiProductRegularRoute = HomeApiProductRegularRouteImport.update({ - id: '/api/product-regular', - path: '/api/product-regular', - getParentRoute: () => HomeRouteRoute, -} as any) -const HomeApiProductCodemodeRoute = HomeApiProductCodemodeRouteImport.update({ - id: '/api/product-codemode', - path: '/api/product-codemode', - getParentRoute: () => HomeRouteRoute, -} as any) -const ExecutePromptApiRealtimeTokenRoute = - ExecutePromptApiRealtimeTokenRouteImport.update({ - id: '/api/realtime-token', - path: '/api/realtime-token', - getParentRoute: () => ExecutePromptRouteRoute, - } as any) -const ExecutePromptApiExecutePromptRoute = - ExecutePromptApiExecutePromptRouteImport.update({ - id: '/api/execute-prompt', - path: '/api/execute-prompt', - getParentRoute: () => ExecutePromptRouteRoute, - } as any) -const DatabaseDemoApiJudgeRoute = DatabaseDemoApiJudgeRouteImport.update({ - id: '/api/judge', - path: '/api/judge', - getParentRoute: () => DatabaseDemoRouteRoute, -} as any) -const DatabaseDemoApiDbSnippetsRoute = - DatabaseDemoApiDbSnippetsRouteImport.update({ - id: '/api/db-snippets', - path: '/api/db-snippets', - getParentRoute: () => DatabaseDemoRouteRoute, - } as any) -const DatabaseDemoApiDatabaseDemoRoute = - DatabaseDemoApiDatabaseDemoRouteImport.update({ - id: '/api/database-demo', - path: '/api/database-demo', - getParentRoute: () => DatabaseDemoRouteRoute, - } as any) -const BankingDemoApiBankingInitRoute = - BankingDemoApiBankingInitRouteImport.update({ - id: '/api/banking-init', - path: '/api/banking-init', - getParentRoute: () => BankingDemoRouteRoute, - } as any) -const BankingDemoApiBankingDemoRoute = - BankingDemoApiBankingDemoRouteImport.update({ - id: '/api/banking-demo', - path: '/api/banking-demo', - getParentRoute: () => BankingDemoRouteRoute, +const StructuredOutputApiStructuredOutputSnippetsRoute = + StructuredOutputApiStructuredOutputSnippetsRouteImport.update({ + id: '/api/structured-output-snippets', + path: '/api/structured-output-snippets', + getParentRoute: () => StructuredOutputRouteRoute, } as any) export interface FileRoutesByFullPath { @@ -427,25 +427,25 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_structured-output': { - id: '/_structured-output' + '/_banking-demo': { + id: '/_banking-demo' path: '' fullPath: '/' - preLoaderRoute: typeof StructuredOutputRouteRouteImport + preLoaderRoute: typeof BankingDemoRouteRouteImport parentRoute: typeof rootRouteImport } - '/_reporting': { - id: '/_reporting' + '/_database-demo': { + id: '/_database-demo' path: '' fullPath: '/' - preLoaderRoute: typeof ReportingRouteRouteImport + preLoaderRoute: typeof DatabaseDemoRouteRouteImport parentRoute: typeof rootRouteImport } - '/_npm-github-chat': { - id: '/_npm-github-chat' + '/_execute-prompt': { + id: '/_execute-prompt' path: '' fullPath: '/' - preLoaderRoute: typeof NpmGithubChatRouteRouteImport + preLoaderRoute: typeof ExecutePromptRouteRouteImport parentRoute: typeof rootRouteImport } '/_home': { @@ -455,27 +455,48 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HomeRouteRouteImport parentRoute: typeof rootRouteImport } - '/_execute-prompt': { - id: '/_execute-prompt' + '/_npm-github-chat': { + id: '/_npm-github-chat' path: '' fullPath: '/' - preLoaderRoute: typeof ExecutePromptRouteRouteImport + preLoaderRoute: typeof NpmGithubChatRouteRouteImport parentRoute: typeof rootRouteImport } - '/_database-demo': { - id: '/_database-demo' + '/_reporting': { + id: '/_reporting' path: '' fullPath: '/' - preLoaderRoute: typeof DatabaseDemoRouteRouteImport + preLoaderRoute: typeof ReportingRouteRouteImport parentRoute: typeof rootRouteImport } - '/_banking-demo': { - id: '/_banking-demo' + '/_structured-output': { + id: '/_structured-output' path: '' fullPath: '/' - preLoaderRoute: typeof BankingDemoRouteRouteImport + preLoaderRoute: typeof StructuredOutputRouteRouteImport parentRoute: typeof rootRouteImport } + '/_banking-demo/banking-demo': { + id: '/_banking-demo/banking-demo' + path: '/banking-demo' + fullPath: '/banking-demo' + preLoaderRoute: typeof BankingDemoBankingDemoRouteImport + parentRoute: typeof BankingDemoRouteRoute + } + '/_database-demo/database-demo': { + id: '/_database-demo/database-demo' + path: '/database-demo' + fullPath: '/database-demo' + preLoaderRoute: typeof DatabaseDemoDatabaseDemoRouteImport + parentRoute: typeof DatabaseDemoRouteRoute + } + '/_execute-prompt/execute-prompt': { + id: '/_execute-prompt/execute-prompt' + path: '/execute-prompt' + fullPath: '/execute-prompt' + preLoaderRoute: typeof ExecutePromptExecutePromptRouteImport + parentRoute: typeof ExecutePromptRouteRoute + } '/_home/': { id: '/_home/' path: '/' @@ -483,27 +504,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HomeIndexRouteImport parentRoute: typeof HomeRouteRoute } - '/api/runtime': { - id: '/api/runtime' - path: '/api/runtime' - fullPath: '/api/runtime' - preLoaderRoute: typeof ApiRuntimeRouteImport - parentRoute: typeof rootRouteImport - } - '/_structured-output/structured-output': { - id: '/_structured-output/structured-output' - path: '/structured-output' - fullPath: '/structured-output' - preLoaderRoute: typeof StructuredOutputStructuredOutputRouteImport - parentRoute: typeof StructuredOutputRouteRoute - } - '/_reporting/reporting-agent': { - id: '/_reporting/reporting-agent' - path: '/reporting-agent' - fullPath: '/reporting-agent' - preLoaderRoute: typeof ReportingReportingAgentRouteImport - parentRoute: typeof ReportingRouteRoute - } '/_npm-github-chat/npm-github-chat': { id: '/_npm-github-chat/npm-github-chat' path: '/npm-github-chat' @@ -511,95 +511,81 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof NpmGithubChatNpmGithubChatRouteImport parentRoute: typeof NpmGithubChatRouteRoute } - '/_execute-prompt/execute-prompt': { - id: '/_execute-prompt/execute-prompt' - path: '/execute-prompt' - fullPath: '/execute-prompt' - preLoaderRoute: typeof ExecutePromptExecutePromptRouteImport - parentRoute: typeof ExecutePromptRouteRoute - } - '/_database-demo/database-demo': { - id: '/_database-demo/database-demo' - path: '/database-demo' - fullPath: '/database-demo' - preLoaderRoute: typeof DatabaseDemoDatabaseDemoRouteImport - parentRoute: typeof DatabaseDemoRouteRoute - } - '/_banking-demo/banking-demo': { - id: '/_banking-demo/banking-demo' - path: '/banking-demo' - fullPath: '/banking-demo' - preLoaderRoute: typeof BankingDemoBankingDemoRouteImport - parentRoute: typeof BankingDemoRouteRoute + '/_reporting/reporting-agent': { + id: '/_reporting/reporting-agent' + path: '/reporting-agent' + fullPath: '/reporting-agent' + preLoaderRoute: typeof ReportingReportingAgentRouteImport + parentRoute: typeof ReportingRouteRoute } - '/_structured-output/api/structured-output-snippets': { - id: '/_structured-output/api/structured-output-snippets' - path: '/api/structured-output-snippets' - fullPath: '/api/structured-output-snippets' - preLoaderRoute: typeof StructuredOutputApiStructuredOutputSnippetsRouteImport + '/_structured-output/structured-output': { + id: '/_structured-output/structured-output' + path: '/structured-output' + fullPath: '/structured-output' + preLoaderRoute: typeof StructuredOutputStructuredOutputRouteImport parentRoute: typeof StructuredOutputRouteRoute } - '/_structured-output/api/structured-output': { - id: '/_structured-output/api/structured-output' - path: '/api/structured-output' - fullPath: '/api/structured-output' - preLoaderRoute: typeof StructuredOutputApiStructuredOutputRouteImport - parentRoute: typeof StructuredOutputRouteRoute + '/api/runtime': { + id: '/api/runtime' + path: '/api/runtime' + fullPath: '/api/runtime' + preLoaderRoute: typeof ApiRuntimeRouteImport + parentRoute: typeof rootRouteImport } - '/_reporting/api/reports': { - id: '/_reporting/api/reports' - path: '/api/reports' - fullPath: '/api/reports' - preLoaderRoute: typeof ReportingApiReportsRouteImport - parentRoute: typeof ReportingRouteRoute + '/_banking-demo/api/banking-demo': { + id: '/_banking-demo/api/banking-demo' + path: '/api/banking-demo' + fullPath: '/api/banking-demo' + preLoaderRoute: typeof BankingDemoApiBankingDemoRouteImport + parentRoute: typeof BankingDemoRouteRoute } - '/_reporting/api/report-sse': { - id: '/_reporting/api/report-sse' - path: '/api/report-sse' - fullPath: '/api/report-sse' - preLoaderRoute: typeof ReportingApiReportSseRouteImport - parentRoute: typeof ReportingRouteRoute + '/_banking-demo/api/banking-init': { + id: '/_banking-demo/api/banking-init' + path: '/api/banking-init' + fullPath: '/api/banking-init' + preLoaderRoute: typeof BankingDemoApiBankingInitRouteImport + parentRoute: typeof BankingDemoRouteRoute } - '/_reporting/api/report-event': { - id: '/_reporting/api/report-event' - path: '/api/report-event' - fullPath: '/api/report-event' - preLoaderRoute: typeof ReportingApiReportEventRouteImport - parentRoute: typeof ReportingRouteRoute + '/_database-demo/api/database-demo': { + id: '/_database-demo/api/database-demo' + path: '/api/database-demo' + fullPath: '/api/database-demo' + preLoaderRoute: typeof DatabaseDemoApiDatabaseDemoRouteImport + parentRoute: typeof DatabaseDemoRouteRoute } - '/_reporting/api/report-demo': { - id: '/_reporting/api/report-demo' - path: '/api/report-demo' - fullPath: '/api/report-demo' - preLoaderRoute: typeof ReportingApiReportDemoRouteImport - parentRoute: typeof ReportingRouteRoute + '/_database-demo/api/db-snippets': { + id: '/_database-demo/api/db-snippets' + path: '/api/db-snippets' + fullPath: '/api/db-snippets' + preLoaderRoute: typeof DatabaseDemoApiDbSnippetsRouteImport + parentRoute: typeof DatabaseDemoRouteRoute } - '/_reporting/api/invalidate': { - id: '/_reporting/api/invalidate' - path: '/api/invalidate' - fullPath: '/api/invalidate' - preLoaderRoute: typeof ReportingApiInvalidateRouteImport - parentRoute: typeof ReportingRouteRoute + '/_database-demo/api/judge': { + id: '/_database-demo/api/judge' + path: '/api/judge' + fullPath: '/api/judge' + preLoaderRoute: typeof DatabaseDemoApiJudgeRouteImport + parentRoute: typeof DatabaseDemoRouteRoute } - '/_npm-github-chat/api/generate-pdf': { - id: '/_npm-github-chat/api/generate-pdf' - path: '/api/generate-pdf' - fullPath: '/api/generate-pdf' - preLoaderRoute: typeof NpmGithubChatApiGeneratePdfRouteImport - parentRoute: typeof NpmGithubChatRouteRoute + '/_execute-prompt/api/execute-prompt': { + id: '/_execute-prompt/api/execute-prompt' + path: '/api/execute-prompt' + fullPath: '/api/execute-prompt' + preLoaderRoute: typeof ExecutePromptApiExecutePromptRouteImport + parentRoute: typeof ExecutePromptRouteRoute } - '/_npm-github-chat/api/codemode': { - id: '/_npm-github-chat/api/codemode' - path: '/api/codemode' - fullPath: '/api/codemode' - preLoaderRoute: typeof NpmGithubChatApiCodemodeRouteImport - parentRoute: typeof NpmGithubChatRouteRoute + '/_execute-prompt/api/realtime-token': { + id: '/_execute-prompt/api/realtime-token' + path: '/api/realtime-token' + fullPath: '/api/realtime-token' + preLoaderRoute: typeof ExecutePromptApiRealtimeTokenRouteImport + parentRoute: typeof ExecutePromptRouteRoute } - '/_home/api/snippets': { - id: '/_home/api/snippets' - path: '/api/snippets' - fullPath: '/api/snippets' - preLoaderRoute: typeof HomeApiSnippetsRouteImport + '/_home/api/product-codemode': { + id: '/_home/api/product-codemode' + path: '/api/product-codemode' + fullPath: '/api/product-codemode' + preLoaderRoute: typeof HomeApiProductCodemodeRouteImport parentRoute: typeof HomeRouteRoute } '/_home/api/product-regular': { @@ -609,61 +595,75 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HomeApiProductRegularRouteImport parentRoute: typeof HomeRouteRoute } - '/_home/api/product-codemode': { - id: '/_home/api/product-codemode' - path: '/api/product-codemode' - fullPath: '/api/product-codemode' - preLoaderRoute: typeof HomeApiProductCodemodeRouteImport + '/_home/api/snippets': { + id: '/_home/api/snippets' + path: '/api/snippets' + fullPath: '/api/snippets' + preLoaderRoute: typeof HomeApiSnippetsRouteImport parentRoute: typeof HomeRouteRoute } - '/_execute-prompt/api/realtime-token': { - id: '/_execute-prompt/api/realtime-token' - path: '/api/realtime-token' - fullPath: '/api/realtime-token' - preLoaderRoute: typeof ExecutePromptApiRealtimeTokenRouteImport - parentRoute: typeof ExecutePromptRouteRoute + '/_npm-github-chat/api/codemode': { + id: '/_npm-github-chat/api/codemode' + path: '/api/codemode' + fullPath: '/api/codemode' + preLoaderRoute: typeof NpmGithubChatApiCodemodeRouteImport + parentRoute: typeof NpmGithubChatRouteRoute } - '/_execute-prompt/api/execute-prompt': { - id: '/_execute-prompt/api/execute-prompt' - path: '/api/execute-prompt' - fullPath: '/api/execute-prompt' - preLoaderRoute: typeof ExecutePromptApiExecutePromptRouteImport - parentRoute: typeof ExecutePromptRouteRoute + '/_npm-github-chat/api/generate-pdf': { + id: '/_npm-github-chat/api/generate-pdf' + path: '/api/generate-pdf' + fullPath: '/api/generate-pdf' + preLoaderRoute: typeof NpmGithubChatApiGeneratePdfRouteImport + parentRoute: typeof NpmGithubChatRouteRoute } - '/_database-demo/api/judge': { - id: '/_database-demo/api/judge' - path: '/api/judge' - fullPath: '/api/judge' - preLoaderRoute: typeof DatabaseDemoApiJudgeRouteImport - parentRoute: typeof DatabaseDemoRouteRoute + '/_reporting/api/invalidate': { + id: '/_reporting/api/invalidate' + path: '/api/invalidate' + fullPath: '/api/invalidate' + preLoaderRoute: typeof ReportingApiInvalidateRouteImport + parentRoute: typeof ReportingRouteRoute } - '/_database-demo/api/db-snippets': { - id: '/_database-demo/api/db-snippets' - path: '/api/db-snippets' - fullPath: '/api/db-snippets' - preLoaderRoute: typeof DatabaseDemoApiDbSnippetsRouteImport - parentRoute: typeof DatabaseDemoRouteRoute + '/_reporting/api/report-demo': { + id: '/_reporting/api/report-demo' + path: '/api/report-demo' + fullPath: '/api/report-demo' + preLoaderRoute: typeof ReportingApiReportDemoRouteImport + parentRoute: typeof ReportingRouteRoute } - '/_database-demo/api/database-demo': { - id: '/_database-demo/api/database-demo' - path: '/api/database-demo' - fullPath: '/api/database-demo' - preLoaderRoute: typeof DatabaseDemoApiDatabaseDemoRouteImport - parentRoute: typeof DatabaseDemoRouteRoute + '/_reporting/api/report-event': { + id: '/_reporting/api/report-event' + path: '/api/report-event' + fullPath: '/api/report-event' + preLoaderRoute: typeof ReportingApiReportEventRouteImport + parentRoute: typeof ReportingRouteRoute } - '/_banking-demo/api/banking-init': { - id: '/_banking-demo/api/banking-init' - path: '/api/banking-init' - fullPath: '/api/banking-init' - preLoaderRoute: typeof BankingDemoApiBankingInitRouteImport - parentRoute: typeof BankingDemoRouteRoute + '/_reporting/api/report-sse': { + id: '/_reporting/api/report-sse' + path: '/api/report-sse' + fullPath: '/api/report-sse' + preLoaderRoute: typeof ReportingApiReportSseRouteImport + parentRoute: typeof ReportingRouteRoute } - '/_banking-demo/api/banking-demo': { - id: '/_banking-demo/api/banking-demo' - path: '/api/banking-demo' - fullPath: '/api/banking-demo' - preLoaderRoute: typeof BankingDemoApiBankingDemoRouteImport - parentRoute: typeof BankingDemoRouteRoute + '/_reporting/api/reports': { + id: '/_reporting/api/reports' + path: '/api/reports' + fullPath: '/api/reports' + preLoaderRoute: typeof ReportingApiReportsRouteImport + parentRoute: typeof ReportingRouteRoute + } + '/_structured-output/api/structured-output': { + id: '/_structured-output/api/structured-output' + path: '/api/structured-output' + fullPath: '/api/structured-output' + preLoaderRoute: typeof StructuredOutputApiStructuredOutputRouteImport + parentRoute: typeof StructuredOutputRouteRoute + } + '/_structured-output/api/structured-output-snippets': { + id: '/_structured-output/api/structured-output-snippets' + path: '/api/structured-output-snippets' + fullPath: '/api/structured-output-snippets' + preLoaderRoute: typeof StructuredOutputApiStructuredOutputSnippetsRouteImport + parentRoute: typeof StructuredOutputRouteRoute } } } diff --git a/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-demo.ts b/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-demo.ts index ff14d96999..335115b4be 100644 --- a/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-demo.ts +++ b/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-demo.ts @@ -199,7 +199,7 @@ async function getCodeModeTools() { return codeModeCache } -export const Route = createFileRoute('/_banking-demo/api/banking-demo' as any)({ +export const Route = createFileRoute('/_banking-demo/api/banking-demo')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-init.ts b/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-init.ts index 30630aaa38..1275b6aca7 100644 --- a/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-init.ts +++ b/examples/ts-code-mode-web/src/routes/_banking-demo/api.banking-init.ts @@ -10,7 +10,7 @@ import { getMockBalances } from '@/lib/reports/create-handler-bindings' const DASHBOARD_ID = 'dashboard' -export const Route = createFileRoute('/_banking-demo/api/banking-init' as any)({ +export const Route = createFileRoute('/_banking-demo/api/banking-init')({ server: { handlers: { POST: async () => { diff --git a/examples/ts-code-mode-web/src/routes/_banking-demo/banking-demo.tsx b/examples/ts-code-mode-web/src/routes/_banking-demo/banking-demo.tsx index d0ab65e1db..6dd38976e6 100644 --- a/examples/ts-code-mode-web/src/routes/_banking-demo/banking-demo.tsx +++ b/examples/ts-code-mode-web/src/routes/_banking-demo/banking-demo.tsx @@ -28,7 +28,7 @@ import type { UIUpdate, } from '@/lib/reports/types' -export const Route = createFileRoute('/_banking-demo/banking-demo' as any)({ +export const Route = createFileRoute('/_banking-demo/banking-demo')({ component: BankingDemoPage, }) diff --git a/examples/ts-code-mode-web/src/routes/_database-demo/api.database-demo.ts b/examples/ts-code-mode-web/src/routes/_database-demo/api.database-demo.ts index 10d7a12a85..4258f9ceb3 100644 --- a/examples/ts-code-mode-web/src/routes/_database-demo/api.database-demo.ts +++ b/examples/ts-code-mode-web/src/routes/_database-demo/api.database-demo.ts @@ -226,9 +226,7 @@ function wrapWithTimingEvents( })() } -export const Route = createFileRoute( - '/_database-demo/api/database-demo' as any, -)({ +export const Route = createFileRoute('/_database-demo/api/database-demo')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_database-demo/api.db-snippets.ts b/examples/ts-code-mode-web/src/routes/_database-demo/api.db-snippets.ts index a7f0cdcac0..a04274deb9 100644 --- a/examples/ts-code-mode-web/src/routes/_database-demo/api.db-snippets.ts +++ b/examples/ts-code-mode-web/src/routes/_database-demo/api.db-snippets.ts @@ -14,7 +14,7 @@ const snippetStorage = createFileSnippetStorage({ trustStrategy, }) -export const Route = createFileRoute('/_database-demo/api/db-snippets' as any)({ +export const Route = createFileRoute('/_database-demo/api/db-snippets')({ server: { handlers: { GET: async () => { diff --git a/examples/ts-code-mode-web/src/routes/_database-demo/api.judge.ts b/examples/ts-code-mode-web/src/routes/_database-demo/api.judge.ts index 3b31b0a3e3..b231857928 100644 --- a/examples/ts-code-mode-web/src/routes/_database-demo/api.judge.ts +++ b/examples/ts-code-mode-web/src/routes/_database-demo/api.judge.ts @@ -29,7 +29,7 @@ Rate the candidate on two dimensions, each on a scale from 1 to 10 (10 = best): Provide a two sentence summary of your evaluation.` -export const Route = createFileRoute('/_database-demo/api/judge' as any)({ +export const Route = createFileRoute('/_database-demo/api/judge')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_database-demo/database-demo.tsx b/examples/ts-code-mode-web/src/routes/_database-demo/database-demo.tsx index c4a05fab1e..d4d9c966e2 100644 --- a/examples/ts-code-mode-web/src/routes/_database-demo/database-demo.tsx +++ b/examples/ts-code-mode-web/src/routes/_database-demo/database-demo.tsx @@ -25,7 +25,7 @@ import ChatInput from '@/components/ChatInput' import { formatDuration } from '@/lib/efficiency' import { toolResultContentToString } from '@/lib/tool-result-content' -export const Route = createFileRoute('/_database-demo/database-demo' as any)({ +export const Route = createFileRoute('/_database-demo/database-demo')({ component: DatabaseDemoPage, }) diff --git a/examples/ts-code-mode-web/src/routes/_execute-prompt/api.execute-prompt.ts b/examples/ts-code-mode-web/src/routes/_execute-prompt/api.execute-prompt.ts index f5c83f3f42..f49ecf5a4f 100644 --- a/examples/ts-code-mode-web/src/routes/_execute-prompt/api.execute-prompt.ts +++ b/examples/ts-code-mode-web/src/routes/_execute-prompt/api.execute-prompt.ts @@ -33,9 +33,7 @@ async function getDriver(): Promise { return cachedDriver } -export const Route = createFileRoute( - '/_execute-prompt/api/execute-prompt' as any, -)({ +export const Route = createFileRoute('/_execute-prompt/api/execute-prompt')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_execute-prompt/api.realtime-token.ts b/examples/ts-code-mode-web/src/routes/_execute-prompt/api.realtime-token.ts index 9b4b38cafe..5b7ab96876 100644 --- a/examples/ts-code-mode-web/src/routes/_execute-prompt/api.realtime-token.ts +++ b/examples/ts-code-mode-web/src/routes/_execute-prompt/api.realtime-token.ts @@ -2,9 +2,7 @@ import { createFileRoute } from '@tanstack/react-router' import { realtimeToken } from '@tanstack/ai' import { openaiRealtimeToken } from '@tanstack/ai-openai' -export const Route = createFileRoute( - '/_execute-prompt/api/realtime-token' as any, -)({ +export const Route = createFileRoute('/_execute-prompt/api/realtime-token')({ server: { handlers: { POST: async () => { diff --git a/examples/ts-code-mode-web/src/routes/_execute-prompt/execute-prompt.tsx b/examples/ts-code-mode-web/src/routes/_execute-prompt/execute-prompt.tsx index e44c07563d..d42bebb877 100644 --- a/examples/ts-code-mode-web/src/routes/_execute-prompt/execute-prompt.tsx +++ b/examples/ts-code-mode-web/src/routes/_execute-prompt/execute-prompt.tsx @@ -29,7 +29,7 @@ import type { CodeExecution, } from '@/lib/execute-prompt-realtime-tools' -export const Route = createFileRoute('/_execute-prompt/execute-prompt' as any)({ +export const Route = createFileRoute('/_execute-prompt/execute-prompt')({ component: DashboardDemoPage, }) diff --git a/examples/ts-code-mode-web/src/routes/_reporting/api.invalidate.ts b/examples/ts-code-mode-web/src/routes/_reporting/api.invalidate.ts index f7100859e5..f473ebd017 100644 --- a/examples/ts-code-mode-web/src/routes/_reporting/api.invalidate.ts +++ b/examples/ts-code-mode-web/src/routes/_reporting/api.invalidate.ts @@ -4,7 +4,7 @@ import { getReportState, getSSEController } from '@/lib/reports/report-storage' import { refreshComponent } from '@/lib/reports/refresh-component' import { evaluateWatchersForSignals } from '@/lib/reports/evaluate-watchers' -export const Route = createFileRoute('/_reporting/api/invalidate' as any)({ +export const Route = createFileRoute('/_reporting/api/invalidate')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_reporting/api.report-demo.ts b/examples/ts-code-mode-web/src/routes/_reporting/api.report-demo.ts index dcf599641b..2ef92fdd4d 100644 --- a/examples/ts-code-mode-web/src/routes/_reporting/api.report-demo.ts +++ b/examples/ts-code-mode-web/src/routes/_reporting/api.report-demo.ts @@ -4,7 +4,7 @@ import { simulateDeposit, } from '@/lib/reports/create-handler-bindings' -export const Route = createFileRoute('/_reporting/api/report-demo' as any)({ +export const Route = createFileRoute('/_reporting/api/report-demo')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_reporting/api.report-event.ts b/examples/ts-code-mode-web/src/routes/_reporting/api.report-event.ts index 90bf68b2f5..11d97742ee 100644 --- a/examples/ts-code-mode-web/src/routes/_reporting/api.report-event.ts +++ b/examples/ts-code-mode-web/src/routes/_reporting/api.report-event.ts @@ -16,7 +16,7 @@ import { import { refreshComponent } from '@/lib/reports/refresh-component' import { evaluateWatchersForSignals } from '@/lib/reports/evaluate-watchers' -export const Route = createFileRoute('/_reporting/api/report-event' as any)({ +export const Route = createFileRoute('/_reporting/api/report-event')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_reporting/api.report-sse.ts b/examples/ts-code-mode-web/src/routes/_reporting/api.report-sse.ts index d53bf55080..97a429b400 100644 --- a/examples/ts-code-mode-web/src/routes/_reporting/api.report-sse.ts +++ b/examples/ts-code-mode-web/src/routes/_reporting/api.report-sse.ts @@ -5,7 +5,7 @@ import { getSSEController, } from '@/lib/reports/report-storage' -export const Route = createFileRoute('/_reporting/api/report-sse' as any)({ +export const Route = createFileRoute('/_reporting/api/report-sse')({ server: { handlers: { GET: async ({ request }) => { diff --git a/examples/ts-code-mode-web/src/routes/_reporting/api.reports.ts b/examples/ts-code-mode-web/src/routes/_reporting/api.reports.ts index 4ff06ad390..4a64e80fa1 100644 --- a/examples/ts-code-mode-web/src/routes/_reporting/api.reports.ts +++ b/examples/ts-code-mode-web/src/routes/_reporting/api.reports.ts @@ -49,7 +49,7 @@ async function getCodeModeTools() { return codeModeCache } -export const Route = createFileRoute('/_reporting/api/reports' as any)({ +export const Route = createFileRoute('/_reporting/api/reports')({ server: { handlers: { POST: async ({ request }) => { diff --git a/examples/ts-group-chat/package.json b/examples/ts-group-chat/package.json index 8b0e844330..66edb51124 100644 --- a/examples/ts-group-chat/package.json +++ b/examples/ts-group-chat/package.json @@ -14,10 +14,10 @@ "@tanstack/ai": "workspace:*", "@tanstack/ai-anthropic": "workspace:*", "@tanstack/react-devtools": "^0.9.10", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-router-devtools": "^1.158.4", - "@tanstack/react-start": "^1.159.0", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-start": "^1.168.49", + "@tanstack/router-plugin": "^1.168.35", "capnweb": "^0.10.0", "nitro": "3.0.260610-beta", "react": "^19.2.3", diff --git a/examples/ts-react-chat/package.json b/examples/ts-react-chat/package.json index 15cd77c5b5..797ed025b1 100644 --- a/examples/ts-react-chat/package.json +++ b/examples/ts-react-chat/package.json @@ -48,13 +48,13 @@ "@tanstack/ai-sandbox-local-process": "workspace:*", "@tanstack/ai-sandbox-vercel": "workspace:*", "@tanstack/react-devtools": "^0.9.10", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-router-devtools": "^1.158.4", - "@tanstack/react-router-ssr-query": "^1.158.4", - "@tanstack/react-start": "^1.159.0", - "@tanstack/react-store": "^0.8.0", - "@tanstack/router-plugin": "^1.158.4", - "@tanstack/store": "^0.8.0", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-router-ssr-query": "^1.167.1", + "@tanstack/react-start": "^1.168.49", + "@tanstack/react-store": "^0.11.1", + "@tanstack/router-plugin": "^1.168.35", + "@tanstack/store": "^0.11.1", "dockerode": "^4.0.2", "highlight.js": "^11.11.1", "lucide-react": "^0.561.0", diff --git a/examples/ts-react-chat/src/routeTree.gen.ts b/examples/ts-react-chat/src/routeTree.gen.ts index c4dd562270..4180f5d44f 100644 --- a/examples/ts-react-chat/src/routeTree.gen.ts +++ b/examples/ts-react-chat/src/routeTree.gen.ts @@ -9,127 +9,122 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as WebsocketChatRouteImport } from './routes/websocket-chat' -import { Route as TypesafeToolsRouteImport } from './routes/typesafe-tools' -import { Route as ThreadsRouteImport } from './routes/threads' -import { Route as ServerFnChatRouteImport } from './routes/server-fn-chat' -import { Route as SandboxesDurableRouteImport } from './routes/sandboxes-durable' -import { Route as SandboxesRouteImport } from './routes/sandboxes' -import { Route as ResumableRouteImport } from './routes/resumable' -import { Route as RepoReportRouteImport } from './routes/repo-report' -import { Route as RealtimeRouteImport } from './routes/realtime' -import { Route as QueueingRouteImport } from './routes/queueing' -import { Route as PersistentChatRouteImport } from './routes/persistent-chat' -import { Route as McpDemoRouteImport } from './routes/mcp-demo' -import { Route as McpAppsRouteImport } from './routes/mcp-apps' -import { Route as Issue176ToolResultRouteImport } from './routes/issue-176-tool-result' -import { Route as InterruptsRouteImport } from './routes/interrupts' -import { Route as ImageToolReproRouteImport } from './routes/image-tool-repro' -import { Route as ImageGenRouteImport } from './routes/image-gen' -import { Route as GenericInterruptsRouteImport } from './routes/generic-interrupts' -import { Route as GenerationHooksRouteImport } from './routes/generation-hooks' -import { Route as CapabilityDemoRouteImport } from './routes/capability-demo' -import { Route as AppStudioRouteImport } from './routes/app-studio' import { Route as IndexRouteImport } from './routes/index' -import { Route as GenerationsVideoRouteImport } from './routes/generations.video' -import { Route as GenerationsTranscriptionRouteImport } from './routes/generations.transcription' -import { Route as GenerationsSummarizeRouteImport } from './routes/generations.summarize' -import { Route as GenerationsStructuredOutputRouteImport } from './routes/generations.structured-output' -import { Route as GenerationsStructuredChatRouteImport } from './routes/generations.structured-chat' -import { Route as GenerationsSpeechRouteImport } from './routes/generations.speech' -import { Route as GenerationsPersistentGenerationRouteImport } from './routes/generations.persistent-generation' -import { Route as GenerationsOpenrouterCombinedRouteImport } from './routes/generations.openrouter-combined' -import { Route as GenerationsImageRouteImport } from './routes/generations.image' -import { Route as GenerationsAudioRouteImport } from './routes/generations.audio' -import { Route as ExampleRuntimeContextRouteImport } from './routes/example.runtime-context' -import { Route as ApiTranscribeRouteImport } from './routes/api.transcribe' -import { Route as ApiTanchatRouteImport } from './routes/api.tanchat' -import { Route as ApiSummarizeRouteImport } from './routes/api.summarize' -import { Route as ApiStructuredOutputRouteImport } from './routes/api.structured-output' -import { Route as ApiStructuredChatRouteImport } from './routes/api.structured-chat' -import { Route as ApiSandboxTriageDurableRouteImport } from './routes/api.sandbox-triage-durable' -import { Route as ApiSandboxTriageRouteImport } from './routes/api.sandbox-triage' -import { Route as ApiSandboxRepoReportRouteImport } from './routes/api.sandbox-repo-report' -import { Route as ApiResumableRouteImport } from './routes/api.resumable' -import { Route as ApiPersistentChatRouteImport } from './routes/api.persistent-chat' -import { Route as ApiOpenrouterCombinedRouteImport } from './routes/api.openrouter-combined' -import { Route as ApiMcpStatusRouteImport } from './routes/api.mcp-status' -import { Route as ApiMcpPoolRouteImport } from './routes/api.mcp-pool' -import { Route as ApiMcpManualRouteImport } from './routes/api.mcp-manual' -import { Route as ApiMcpChatRouteImport } from './routes/api.mcp-chat' -import { Route as ApiMcpAppsWeatherServerRouteImport } from './routes/api.mcp-apps-weather-server' -import { Route as ApiMcpAppsShopServerRouteImport } from './routes/api.mcp-apps-shop-server' -import { Route as ApiMcpAppsChatRouteImport } from './routes/api.mcp-apps-chat' -import { Route as ApiMcpAppsCallRouteImport } from './routes/api.mcp-apps-call' -import { Route as ApiInterruptsRouteImport } from './routes/api.interrupts' -import { Route as ApiImageToolReproRouteImport } from './routes/api.image-tool-repro' -import { Route as ApiImageGenRouteImport } from './routes/api.image-gen' -import { Route as ApiGenericInterruptsRouteImport } from './routes/api.generic-interrupts' -import { Route as ApiCapabilityDemoRouteImport } from './routes/api.capability-demo' -import { Route as ApiArtifactsRouteImport } from './routes/api.artifacts' -import { Route as ApiAppStudioForkRouteImport } from './routes/api.app-studio-fork' +import { Route as AppStudioRouteImport } from './routes/app-studio' +import { Route as CapabilityDemoRouteImport } from './routes/capability-demo' +import { Route as GenerationHooksRouteImport } from './routes/generation-hooks' +import { Route as GenericInterruptsRouteImport } from './routes/generic-interrupts' +import { Route as ImageGenRouteImport } from './routes/image-gen' +import { Route as ImageToolReproRouteImport } from './routes/image-tool-repro' +import { Route as InterruptsRouteImport } from './routes/interrupts' +import { Route as Issue176ToolResultRouteImport } from './routes/issue-176-tool-result' +import { Route as McpAppsRouteImport } from './routes/mcp-apps' +import { Route as McpDemoRouteImport } from './routes/mcp-demo' +import { Route as PersistentChatRouteImport } from './routes/persistent-chat' +import { Route as QueueingRouteImport } from './routes/queueing' +import { Route as RealtimeRouteImport } from './routes/realtime' +import { Route as RepoReportRouteImport } from './routes/repo-report' +import { Route as ResumableRouteImport } from './routes/resumable' +import { Route as SandboxesRouteImport } from './routes/sandboxes' +import { Route as SandboxesDurableRouteImport } from './routes/sandboxes-durable' +import { Route as ServerFnChatRouteImport } from './routes/server-fn-chat' +import { Route as ThreadsRouteImport } from './routes/threads' +import { Route as TypesafeToolsRouteImport } from './routes/typesafe-tools' +import { Route as WebsocketChatRouteImport } from './routes/websocket-chat' import { Route as ApiAppStudioRouteImport } from './routes/api.app-studio' +import { Route as ApiAppStudioForkRouteImport } from './routes/api.app-studio-fork' +import { Route as ApiArtifactsRouteImport } from './routes/api.artifacts' +import { Route as ApiCapabilityDemoRouteImport } from './routes/api.capability-demo' +import { Route as ApiGenericInterruptsRouteImport } from './routes/api.generic-interrupts' +import { Route as ApiImageGenRouteImport } from './routes/api.image-gen' +import { Route as ApiImageToolReproRouteImport } from './routes/api.image-tool-repro' +import { Route as ApiInterruptsRouteImport } from './routes/api.interrupts' +import { Route as ApiMcpAppsCallRouteImport } from './routes/api.mcp-apps-call' +import { Route as ApiMcpAppsChatRouteImport } from './routes/api.mcp-apps-chat' +import { Route as ApiMcpAppsShopServerRouteImport } from './routes/api.mcp-apps-shop-server' +import { Route as ApiMcpAppsWeatherServerRouteImport } from './routes/api.mcp-apps-weather-server' +import { Route as ApiMcpChatRouteImport } from './routes/api.mcp-chat' +import { Route as ApiMcpManualRouteImport } from './routes/api.mcp-manual' +import { Route as ApiMcpPoolRouteImport } from './routes/api.mcp-pool' +import { Route as ApiMcpStatusRouteImport } from './routes/api.mcp-status' +import { Route as ApiOpenrouterCombinedRouteImport } from './routes/api.openrouter-combined' +import { Route as ApiPersistentChatRouteImport } from './routes/api.persistent-chat' +import { Route as ApiResumableRouteImport } from './routes/api.resumable' +import { Route as ApiSandboxRepoReportRouteImport } from './routes/api.sandbox-repo-report' +import { Route as ApiSandboxTriageRouteImport } from './routes/api.sandbox-triage' +import { Route as ApiSandboxTriageDurableRouteImport } from './routes/api.sandbox-triage-durable' +import { Route as ApiStructuredChatRouteImport } from './routes/api.structured-chat' +import { Route as ApiStructuredOutputRouteImport } from './routes/api.structured-output' +import { Route as ApiSummarizeRouteImport } from './routes/api.summarize' +import { Route as ApiTanchatRouteImport } from './routes/api.tanchat' +import { Route as ApiTranscribeRouteImport } from './routes/api.transcribe' +import { Route as ExampleRuntimeContextRouteImport } from './routes/example.runtime-context' +import { Route as GenerationsAudioRouteImport } from './routes/generations.audio' +import { Route as GenerationsImageRouteImport } from './routes/generations.image' +import { Route as GenerationsOpenrouterCombinedRouteImport } from './routes/generations.openrouter-combined' +import { Route as GenerationsPersistentGenerationRouteImport } from './routes/generations.persistent-generation' +import { Route as GenerationsSpeechRouteImport } from './routes/generations.speech' +import { Route as GenerationsStructuredChatRouteImport } from './routes/generations.structured-chat' +import { Route as GenerationsStructuredOutputRouteImport } from './routes/generations.structured-output' +import { Route as GenerationsSummarizeRouteImport } from './routes/generations.summarize' +import { Route as GenerationsTranscriptionRouteImport } from './routes/generations.transcription' +import { Route as GenerationsVideoRouteImport } from './routes/generations.video' +import { Route as ApiGenerateAudioRouteImport } from './routes/api.generate.audio' +import { Route as ApiGenerateImageRouteImport } from './routes/api.generate.image' +import { Route as ApiGenerateSpeechRouteImport } from './routes/api.generate.speech' +import { Route as ApiGenerateVideoRouteImport } from './routes/api.generate.video' import { Route as ExampleGuitarsIndexRouteImport } from './routes/example.guitars/index' import { Route as ExampleGuitarsGuitarIdRouteImport } from './routes/example.guitars/$guitarId' -import { Route as ApiGenerateVideoRouteImport } from './routes/api.generate.video' -import { Route as ApiGenerateSpeechRouteImport } from './routes/api.generate.speech' -import { Route as ApiGenerateImageRouteImport } from './routes/api.generate.image' -import { Route as ApiGenerateAudioRouteImport } from './routes/api.generate.audio' import { Route as ApiGenerateImageArtifactRouteImport } from './routes/api.generate.image.artifact' -const WebsocketChatRoute = WebsocketChatRouteImport.update({ - id: '/websocket-chat', - path: '/websocket-chat', - getParentRoute: () => rootRouteImport, -} as any) -const TypesafeToolsRoute = TypesafeToolsRouteImport.update({ - id: '/typesafe-tools', - path: '/typesafe-tools', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const ThreadsRoute = ThreadsRouteImport.update({ - id: '/threads', - path: '/threads', +const AppStudioRoute = AppStudioRouteImport.update({ + id: '/app-studio', + path: '/app-studio', getParentRoute: () => rootRouteImport, } as any) -const ServerFnChatRoute = ServerFnChatRouteImport.update({ - id: '/server-fn-chat', - path: '/server-fn-chat', +const CapabilityDemoRoute = CapabilityDemoRouteImport.update({ + id: '/capability-demo', + path: '/capability-demo', getParentRoute: () => rootRouteImport, } as any) -const SandboxesDurableRoute = SandboxesDurableRouteImport.update({ - id: '/sandboxes-durable', - path: '/sandboxes-durable', +const GenerationHooksRoute = GenerationHooksRouteImport.update({ + id: '/generation-hooks', + path: '/generation-hooks', getParentRoute: () => rootRouteImport, } as any) -const SandboxesRoute = SandboxesRouteImport.update({ - id: '/sandboxes', - path: '/sandboxes', +const GenericInterruptsRoute = GenericInterruptsRouteImport.update({ + id: '/generic-interrupts', + path: '/generic-interrupts', getParentRoute: () => rootRouteImport, } as any) -const ResumableRoute = ResumableRouteImport.update({ - id: '/resumable', - path: '/resumable', +const ImageGenRoute = ImageGenRouteImport.update({ + id: '/image-gen', + path: '/image-gen', getParentRoute: () => rootRouteImport, } as any) -const RepoReportRoute = RepoReportRouteImport.update({ - id: '/repo-report', - path: '/repo-report', +const ImageToolReproRoute = ImageToolReproRouteImport.update({ + id: '/image-tool-repro', + path: '/image-tool-repro', getParentRoute: () => rootRouteImport, } as any) -const RealtimeRoute = RealtimeRouteImport.update({ - id: '/realtime', - path: '/realtime', +const InterruptsRoute = InterruptsRouteImport.update({ + id: '/interrupts', + path: '/interrupts', getParentRoute: () => rootRouteImport, } as any) -const QueueingRoute = QueueingRouteImport.update({ - id: '/queueing', - path: '/queueing', +const Issue176ToolResultRoute = Issue176ToolResultRouteImport.update({ + id: '/issue-176-tool-result', + path: '/issue-176-tool-result', getParentRoute: () => rootRouteImport, } as any) -const PersistentChatRoute = PersistentChatRouteImport.update({ - id: '/persistent-chat', - path: '/persistent-chat', +const McpAppsRoute = McpAppsRouteImport.update({ + id: '/mcp-apps', + path: '/mcp-apps', getParentRoute: () => rootRouteImport, } as any) const McpDemoRoute = McpDemoRouteImport.update({ @@ -137,169 +132,134 @@ const McpDemoRoute = McpDemoRouteImport.update({ path: '/mcp-demo', getParentRoute: () => rootRouteImport, } as any) -const McpAppsRoute = McpAppsRouteImport.update({ - id: '/mcp-apps', - path: '/mcp-apps', +const PersistentChatRoute = PersistentChatRouteImport.update({ + id: '/persistent-chat', + path: '/persistent-chat', getParentRoute: () => rootRouteImport, } as any) -const Issue176ToolResultRoute = Issue176ToolResultRouteImport.update({ - id: '/issue-176-tool-result', - path: '/issue-176-tool-result', +const QueueingRoute = QueueingRouteImport.update({ + id: '/queueing', + path: '/queueing', getParentRoute: () => rootRouteImport, } as any) -const InterruptsRoute = InterruptsRouteImport.update({ - id: '/interrupts', - path: '/interrupts', +const RealtimeRoute = RealtimeRouteImport.update({ + id: '/realtime', + path: '/realtime', getParentRoute: () => rootRouteImport, } as any) -const ImageToolReproRoute = ImageToolReproRouteImport.update({ - id: '/image-tool-repro', - path: '/image-tool-repro', +const RepoReportRoute = RepoReportRouteImport.update({ + id: '/repo-report', + path: '/repo-report', getParentRoute: () => rootRouteImport, } as any) -const ImageGenRoute = ImageGenRouteImport.update({ - id: '/image-gen', - path: '/image-gen', +const ResumableRoute = ResumableRouteImport.update({ + id: '/resumable', + path: '/resumable', getParentRoute: () => rootRouteImport, } as any) -const GenericInterruptsRoute = GenericInterruptsRouteImport.update({ - id: '/generic-interrupts', - path: '/generic-interrupts', +const SandboxesRoute = SandboxesRouteImport.update({ + id: '/sandboxes', + path: '/sandboxes', getParentRoute: () => rootRouteImport, } as any) -const GenerationHooksRoute = GenerationHooksRouteImport.update({ - id: '/generation-hooks', - path: '/generation-hooks', +const SandboxesDurableRoute = SandboxesDurableRouteImport.update({ + id: '/sandboxes-durable', + path: '/sandboxes-durable', getParentRoute: () => rootRouteImport, } as any) -const CapabilityDemoRoute = CapabilityDemoRouteImport.update({ - id: '/capability-demo', - path: '/capability-demo', +const ServerFnChatRoute = ServerFnChatRouteImport.update({ + id: '/server-fn-chat', + path: '/server-fn-chat', getParentRoute: () => rootRouteImport, } as any) -const AppStudioRoute = AppStudioRouteImport.update({ - id: '/app-studio', - path: '/app-studio', +const ThreadsRoute = ThreadsRouteImport.update({ + id: '/threads', + path: '/threads', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const TypesafeToolsRoute = TypesafeToolsRouteImport.update({ + id: '/typesafe-tools', + path: '/typesafe-tools', getParentRoute: () => rootRouteImport, } as any) -const GenerationsVideoRoute = GenerationsVideoRouteImport.update({ - id: '/generations/video', - path: '/generations/video', +const WebsocketChatRoute = WebsocketChatRouteImport.update({ + id: '/websocket-chat', + path: '/websocket-chat', getParentRoute: () => rootRouteImport, } as any) -const GenerationsTranscriptionRoute = - GenerationsTranscriptionRouteImport.update({ - id: '/generations/transcription', - path: '/generations/transcription', - getParentRoute: () => rootRouteImport, - } as any) -const GenerationsSummarizeRoute = GenerationsSummarizeRouteImport.update({ - id: '/generations/summarize', - path: '/generations/summarize', - getParentRoute: () => rootRouteImport, -} as any) -const GenerationsStructuredOutputRoute = - GenerationsStructuredOutputRouteImport.update({ - id: '/generations/structured-output', - path: '/generations/structured-output', - getParentRoute: () => rootRouteImport, - } as any) -const GenerationsStructuredChatRoute = - GenerationsStructuredChatRouteImport.update({ - id: '/generations/structured-chat', - path: '/generations/structured-chat', - getParentRoute: () => rootRouteImport, - } as any) -const GenerationsSpeechRoute = GenerationsSpeechRouteImport.update({ - id: '/generations/speech', - path: '/generations/speech', +const ApiAppStudioRoute = ApiAppStudioRouteImport.update({ + id: '/api/app-studio', + path: '/api/app-studio', getParentRoute: () => rootRouteImport, } as any) -const GenerationsPersistentGenerationRoute = - GenerationsPersistentGenerationRouteImport.update({ - id: '/generations/persistent-generation', - path: '/generations/persistent-generation', - getParentRoute: () => rootRouteImport, - } as any) -const GenerationsOpenrouterCombinedRoute = - GenerationsOpenrouterCombinedRouteImport.update({ - id: '/generations/openrouter-combined', - path: '/generations/openrouter-combined', - getParentRoute: () => rootRouteImport, - } as any) -const GenerationsImageRoute = GenerationsImageRouteImport.update({ - id: '/generations/image', - path: '/generations/image', +const ApiAppStudioForkRoute = ApiAppStudioForkRouteImport.update({ + id: '/api/app-studio-fork', + path: '/api/app-studio-fork', getParentRoute: () => rootRouteImport, } as any) -const GenerationsAudioRoute = GenerationsAudioRouteImport.update({ - id: '/generations/audio', - path: '/generations/audio', +const ApiArtifactsRoute = ApiArtifactsRouteImport.update({ + id: '/api/artifacts', + path: '/api/artifacts', getParentRoute: () => rootRouteImport, } as any) -const ExampleRuntimeContextRoute = ExampleRuntimeContextRouteImport.update({ - id: '/example/runtime-context', - path: '/example/runtime-context', +const ApiCapabilityDemoRoute = ApiCapabilityDemoRouteImport.update({ + id: '/api/capability-demo', + path: '/api/capability-demo', getParentRoute: () => rootRouteImport, } as any) -const ApiTranscribeRoute = ApiTranscribeRouteImport.update({ - id: '/api/transcribe', - path: '/api/transcribe', +const ApiGenericInterruptsRoute = ApiGenericInterruptsRouteImport.update({ + id: '/api/generic-interrupts', + path: '/api/generic-interrupts', getParentRoute: () => rootRouteImport, } as any) -const ApiTanchatRoute = ApiTanchatRouteImport.update({ - id: '/api/tanchat', - path: '/api/tanchat', +const ApiImageGenRoute = ApiImageGenRouteImport.update({ + id: '/api/image-gen', + path: '/api/image-gen', getParentRoute: () => rootRouteImport, } as any) -const ApiSummarizeRoute = ApiSummarizeRouteImport.update({ - id: '/api/summarize', - path: '/api/summarize', +const ApiImageToolReproRoute = ApiImageToolReproRouteImport.update({ + id: '/api/image-tool-repro', + path: '/api/image-tool-repro', getParentRoute: () => rootRouteImport, } as any) -const ApiStructuredOutputRoute = ApiStructuredOutputRouteImport.update({ - id: '/api/structured-output', - path: '/api/structured-output', +const ApiInterruptsRoute = ApiInterruptsRouteImport.update({ + id: '/api/interrupts', + path: '/api/interrupts', getParentRoute: () => rootRouteImport, } as any) -const ApiStructuredChatRoute = ApiStructuredChatRouteImport.update({ - id: '/api/structured-chat', - path: '/api/structured-chat', +const ApiMcpAppsCallRoute = ApiMcpAppsCallRouteImport.update({ + id: '/api/mcp-apps-call', + path: '/api/mcp-apps-call', getParentRoute: () => rootRouteImport, } as any) -const ApiSandboxTriageDurableRoute = ApiSandboxTriageDurableRouteImport.update({ - id: '/api/sandbox-triage-durable', - path: '/api/sandbox-triage-durable', +const ApiMcpAppsChatRoute = ApiMcpAppsChatRouteImport.update({ + id: '/api/mcp-apps-chat', + path: '/api/mcp-apps-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiSandboxTriageRoute = ApiSandboxTriageRouteImport.update({ - id: '/api/sandbox-triage', - path: '/api/sandbox-triage', +const ApiMcpAppsShopServerRoute = ApiMcpAppsShopServerRouteImport.update({ + id: '/api/mcp-apps-shop-server', + path: '/api/mcp-apps-shop-server', getParentRoute: () => rootRouteImport, } as any) -const ApiSandboxRepoReportRoute = ApiSandboxRepoReportRouteImport.update({ - id: '/api/sandbox-repo-report', - path: '/api/sandbox-repo-report', +const ApiMcpAppsWeatherServerRoute = ApiMcpAppsWeatherServerRouteImport.update({ + id: '/api/mcp-apps-weather-server', + path: '/api/mcp-apps-weather-server', getParentRoute: () => rootRouteImport, } as any) -const ApiResumableRoute = ApiResumableRouteImport.update({ - id: '/api/resumable', - path: '/api/resumable', +const ApiMcpChatRoute = ApiMcpChatRouteImport.update({ + id: '/api/mcp-chat', + path: '/api/mcp-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiPersistentChatRoute = ApiPersistentChatRouteImport.update({ - id: '/api/persistent-chat', - path: '/api/persistent-chat', +const ApiMcpManualRoute = ApiMcpManualRouteImport.update({ + id: '/api/mcp-manual', + path: '/api/mcp-manual', getParentRoute: () => rootRouteImport, } as any) -const ApiOpenrouterCombinedRoute = ApiOpenrouterCombinedRouteImport.update({ - id: '/api/openrouter-combined', - path: '/api/openrouter-combined', +const ApiMcpPoolRoute = ApiMcpPoolRouteImport.update({ + id: '/api/mcp-pool', + path: '/api/mcp-pool', getParentRoute: () => rootRouteImport, } as any) const ApiMcpStatusRoute = ApiMcpStatusRouteImport.update({ @@ -307,94 +267,129 @@ const ApiMcpStatusRoute = ApiMcpStatusRouteImport.update({ path: '/api/mcp-status', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpPoolRoute = ApiMcpPoolRouteImport.update({ - id: '/api/mcp-pool', - path: '/api/mcp-pool', +const ApiOpenrouterCombinedRoute = ApiOpenrouterCombinedRouteImport.update({ + id: '/api/openrouter-combined', + path: '/api/openrouter-combined', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpManualRoute = ApiMcpManualRouteImport.update({ - id: '/api/mcp-manual', - path: '/api/mcp-manual', +const ApiPersistentChatRoute = ApiPersistentChatRouteImport.update({ + id: '/api/persistent-chat', + path: '/api/persistent-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpChatRoute = ApiMcpChatRouteImport.update({ - id: '/api/mcp-chat', - path: '/api/mcp-chat', +const ApiResumableRoute = ApiResumableRouteImport.update({ + id: '/api/resumable', + path: '/api/resumable', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpAppsWeatherServerRoute = ApiMcpAppsWeatherServerRouteImport.update({ - id: '/api/mcp-apps-weather-server', - path: '/api/mcp-apps-weather-server', +const ApiSandboxRepoReportRoute = ApiSandboxRepoReportRouteImport.update({ + id: '/api/sandbox-repo-report', + path: '/api/sandbox-repo-report', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpAppsShopServerRoute = ApiMcpAppsShopServerRouteImport.update({ - id: '/api/mcp-apps-shop-server', - path: '/api/mcp-apps-shop-server', +const ApiSandboxTriageRoute = ApiSandboxTriageRouteImport.update({ + id: '/api/sandbox-triage', + path: '/api/sandbox-triage', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpAppsChatRoute = ApiMcpAppsChatRouteImport.update({ - id: '/api/mcp-apps-chat', - path: '/api/mcp-apps-chat', +const ApiSandboxTriageDurableRoute = ApiSandboxTriageDurableRouteImport.update({ + id: '/api/sandbox-triage-durable', + path: '/api/sandbox-triage-durable', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpAppsCallRoute = ApiMcpAppsCallRouteImport.update({ - id: '/api/mcp-apps-call', - path: '/api/mcp-apps-call', +const ApiStructuredChatRoute = ApiStructuredChatRouteImport.update({ + id: '/api/structured-chat', + path: '/api/structured-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiInterruptsRoute = ApiInterruptsRouteImport.update({ - id: '/api/interrupts', - path: '/api/interrupts', +const ApiStructuredOutputRoute = ApiStructuredOutputRouteImport.update({ + id: '/api/structured-output', + path: '/api/structured-output', getParentRoute: () => rootRouteImport, } as any) -const ApiImageToolReproRoute = ApiImageToolReproRouteImport.update({ - id: '/api/image-tool-repro', - path: '/api/image-tool-repro', +const ApiSummarizeRoute = ApiSummarizeRouteImport.update({ + id: '/api/summarize', + path: '/api/summarize', getParentRoute: () => rootRouteImport, } as any) -const ApiImageGenRoute = ApiImageGenRouteImport.update({ - id: '/api/image-gen', - path: '/api/image-gen', +const ApiTanchatRoute = ApiTanchatRouteImport.update({ + id: '/api/tanchat', + path: '/api/tanchat', getParentRoute: () => rootRouteImport, } as any) -const ApiGenericInterruptsRoute = ApiGenericInterruptsRouteImport.update({ - id: '/api/generic-interrupts', - path: '/api/generic-interrupts', +const ApiTranscribeRoute = ApiTranscribeRouteImport.update({ + id: '/api/transcribe', + path: '/api/transcribe', getParentRoute: () => rootRouteImport, } as any) -const ApiCapabilityDemoRoute = ApiCapabilityDemoRouteImport.update({ - id: '/api/capability-demo', - path: '/api/capability-demo', +const ExampleRuntimeContextRoute = ExampleRuntimeContextRouteImport.update({ + id: '/example/runtime-context', + path: '/example/runtime-context', getParentRoute: () => rootRouteImport, } as any) -const ApiArtifactsRoute = ApiArtifactsRouteImport.update({ - id: '/api/artifacts', - path: '/api/artifacts', +const GenerationsAudioRoute = GenerationsAudioRouteImport.update({ + id: '/generations/audio', + path: '/generations/audio', getParentRoute: () => rootRouteImport, } as any) -const ApiAppStudioForkRoute = ApiAppStudioForkRouteImport.update({ - id: '/api/app-studio-fork', - path: '/api/app-studio-fork', +const GenerationsImageRoute = GenerationsImageRouteImport.update({ + id: '/generations/image', + path: '/generations/image', getParentRoute: () => rootRouteImport, } as any) -const ApiAppStudioRoute = ApiAppStudioRouteImport.update({ - id: '/api/app-studio', - path: '/api/app-studio', +const GenerationsOpenrouterCombinedRoute = + GenerationsOpenrouterCombinedRouteImport.update({ + id: '/generations/openrouter-combined', + path: '/generations/openrouter-combined', + getParentRoute: () => rootRouteImport, + } as any) +const GenerationsPersistentGenerationRoute = + GenerationsPersistentGenerationRouteImport.update({ + id: '/generations/persistent-generation', + path: '/generations/persistent-generation', + getParentRoute: () => rootRouteImport, + } as any) +const GenerationsSpeechRoute = GenerationsSpeechRouteImport.update({ + id: '/generations/speech', + path: '/generations/speech', getParentRoute: () => rootRouteImport, } as any) -const ExampleGuitarsIndexRoute = ExampleGuitarsIndexRouteImport.update({ - id: '/example/guitars/', - path: '/example/guitars/', +const GenerationsStructuredChatRoute = + GenerationsStructuredChatRouteImport.update({ + id: '/generations/structured-chat', + path: '/generations/structured-chat', + getParentRoute: () => rootRouteImport, + } as any) +const GenerationsStructuredOutputRoute = + GenerationsStructuredOutputRouteImport.update({ + id: '/generations/structured-output', + path: '/generations/structured-output', + getParentRoute: () => rootRouteImport, + } as any) +const GenerationsSummarizeRoute = GenerationsSummarizeRouteImport.update({ + id: '/generations/summarize', + path: '/generations/summarize', getParentRoute: () => rootRouteImport, } as any) -const ExampleGuitarsGuitarIdRoute = ExampleGuitarsGuitarIdRouteImport.update({ - id: '/example/guitars/$guitarId', - path: '/example/guitars/$guitarId', +const GenerationsTranscriptionRoute = + GenerationsTranscriptionRouteImport.update({ + id: '/generations/transcription', + path: '/generations/transcription', + getParentRoute: () => rootRouteImport, + } as any) +const GenerationsVideoRoute = GenerationsVideoRouteImport.update({ + id: '/generations/video', + path: '/generations/video', getParentRoute: () => rootRouteImport, } as any) -const ApiGenerateVideoRoute = ApiGenerateVideoRouteImport.update({ - id: '/api/generate/video', - path: '/api/generate/video', +const ApiGenerateAudioRoute = ApiGenerateAudioRouteImport.update({ + id: '/api/generate/audio', + path: '/api/generate/audio', + getParentRoute: () => rootRouteImport, +} as any) +const ApiGenerateImageRoute = ApiGenerateImageRouteImport.update({ + id: '/api/generate/image', + path: '/api/generate/image', getParentRoute: () => rootRouteImport, } as any) const ApiGenerateSpeechRoute = ApiGenerateSpeechRouteImport.update({ @@ -402,14 +397,19 @@ const ApiGenerateSpeechRoute = ApiGenerateSpeechRouteImport.update({ path: '/api/generate/speech', getParentRoute: () => rootRouteImport, } as any) -const ApiGenerateImageRoute = ApiGenerateImageRouteImport.update({ - id: '/api/generate/image', - path: '/api/generate/image', +const ApiGenerateVideoRoute = ApiGenerateVideoRouteImport.update({ + id: '/api/generate/video', + path: '/api/generate/video', getParentRoute: () => rootRouteImport, } as any) -const ApiGenerateAudioRoute = ApiGenerateAudioRouteImport.update({ - id: '/api/generate/audio', - path: '/api/generate/audio', +const ExampleGuitarsIndexRoute = ExampleGuitarsIndexRouteImport.update({ + id: '/example/guitars/', + path: '/example/guitars/', + getParentRoute: () => rootRouteImport, +} as any) +const ExampleGuitarsGuitarIdRoute = ExampleGuitarsGuitarIdRouteImport.update({ + id: '/example/guitars/$guitarId', + path: '/example/guitars/$guitarId', getParentRoute: () => rootRouteImport, } as any) const ApiGenerateImageArtifactRoute = @@ -908,74 +908,81 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/websocket-chat': { - id: '/websocket-chat' - path: '/websocket-chat' - fullPath: '/websocket-chat' - preLoaderRoute: typeof WebsocketChatRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/typesafe-tools': { - id: '/typesafe-tools' - path: '/typesafe-tools' - fullPath: '/typesafe-tools' - preLoaderRoute: typeof TypesafeToolsRouteImport + '/app-studio': { + id: '/app-studio' + path: '/app-studio' + fullPath: '/app-studio' + preLoaderRoute: typeof AppStudioRouteImport parentRoute: typeof rootRouteImport } - '/threads': { - id: '/threads' - path: '/threads' - fullPath: '/threads' - preLoaderRoute: typeof ThreadsRouteImport + '/capability-demo': { + id: '/capability-demo' + path: '/capability-demo' + fullPath: '/capability-demo' + preLoaderRoute: typeof CapabilityDemoRouteImport parentRoute: typeof rootRouteImport } - '/server-fn-chat': { - id: '/server-fn-chat' - path: '/server-fn-chat' - fullPath: '/server-fn-chat' - preLoaderRoute: typeof ServerFnChatRouteImport + '/generation-hooks': { + id: '/generation-hooks' + path: '/generation-hooks' + fullPath: '/generation-hooks' + preLoaderRoute: typeof GenerationHooksRouteImport parentRoute: typeof rootRouteImport } - '/sandboxes-durable': { - id: '/sandboxes-durable' - path: '/sandboxes-durable' - fullPath: '/sandboxes-durable' - preLoaderRoute: typeof SandboxesDurableRouteImport + '/generic-interrupts': { + id: '/generic-interrupts' + path: '/generic-interrupts' + fullPath: '/generic-interrupts' + preLoaderRoute: typeof GenericInterruptsRouteImport parentRoute: typeof rootRouteImport } - '/sandboxes': { - id: '/sandboxes' - path: '/sandboxes' - fullPath: '/sandboxes' - preLoaderRoute: typeof SandboxesRouteImport + '/image-gen': { + id: '/image-gen' + path: '/image-gen' + fullPath: '/image-gen' + preLoaderRoute: typeof ImageGenRouteImport parentRoute: typeof rootRouteImport } - '/resumable': { - id: '/resumable' - path: '/resumable' - fullPath: '/resumable' - preLoaderRoute: typeof ResumableRouteImport + '/image-tool-repro': { + id: '/image-tool-repro' + path: '/image-tool-repro' + fullPath: '/image-tool-repro' + preLoaderRoute: typeof ImageToolReproRouteImport parentRoute: typeof rootRouteImport } - '/repo-report': { - id: '/repo-report' - path: '/repo-report' - fullPath: '/repo-report' - preLoaderRoute: typeof RepoReportRouteImport + '/interrupts': { + id: '/interrupts' + path: '/interrupts' + fullPath: '/interrupts' + preLoaderRoute: typeof InterruptsRouteImport parentRoute: typeof rootRouteImport } - '/realtime': { - id: '/realtime' - path: '/realtime' - fullPath: '/realtime' - preLoaderRoute: typeof RealtimeRouteImport + '/issue-176-tool-result': { + id: '/issue-176-tool-result' + path: '/issue-176-tool-result' + fullPath: '/issue-176-tool-result' + preLoaderRoute: typeof Issue176ToolResultRouteImport parentRoute: typeof rootRouteImport } - '/queueing': { - id: '/queueing' - path: '/queueing' - fullPath: '/queueing' - preLoaderRoute: typeof QueueingRouteImport + '/mcp-apps': { + id: '/mcp-apps' + path: '/mcp-apps' + fullPath: '/mcp-apps' + preLoaderRoute: typeof McpAppsRouteImport + parentRoute: typeof rootRouteImport + } + '/mcp-demo': { + id: '/mcp-demo' + path: '/mcp-demo' + fullPath: '/mcp-demo' + preLoaderRoute: typeof McpDemoRouteImport parentRoute: typeof rootRouteImport } '/persistent-chat': { @@ -985,200 +992,214 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PersistentChatRouteImport parentRoute: typeof rootRouteImport } - '/mcp-demo': { - id: '/mcp-demo' - path: '/mcp-demo' - fullPath: '/mcp-demo' - preLoaderRoute: typeof McpDemoRouteImport + '/queueing': { + id: '/queueing' + path: '/queueing' + fullPath: '/queueing' + preLoaderRoute: typeof QueueingRouteImport parentRoute: typeof rootRouteImport } - '/mcp-apps': { - id: '/mcp-apps' - path: '/mcp-apps' - fullPath: '/mcp-apps' - preLoaderRoute: typeof McpAppsRouteImport + '/realtime': { + id: '/realtime' + path: '/realtime' + fullPath: '/realtime' + preLoaderRoute: typeof RealtimeRouteImport parentRoute: typeof rootRouteImport } - '/issue-176-tool-result': { - id: '/issue-176-tool-result' - path: '/issue-176-tool-result' - fullPath: '/issue-176-tool-result' - preLoaderRoute: typeof Issue176ToolResultRouteImport + '/repo-report': { + id: '/repo-report' + path: '/repo-report' + fullPath: '/repo-report' + preLoaderRoute: typeof RepoReportRouteImport parentRoute: typeof rootRouteImport } - '/interrupts': { - id: '/interrupts' - path: '/interrupts' - fullPath: '/interrupts' - preLoaderRoute: typeof InterruptsRouteImport + '/resumable': { + id: '/resumable' + path: '/resumable' + fullPath: '/resumable' + preLoaderRoute: typeof ResumableRouteImport parentRoute: typeof rootRouteImport } - '/image-tool-repro': { - id: '/image-tool-repro' - path: '/image-tool-repro' - fullPath: '/image-tool-repro' - preLoaderRoute: typeof ImageToolReproRouteImport + '/sandboxes': { + id: '/sandboxes' + path: '/sandboxes' + fullPath: '/sandboxes' + preLoaderRoute: typeof SandboxesRouteImport parentRoute: typeof rootRouteImport } - '/image-gen': { - id: '/image-gen' - path: '/image-gen' - fullPath: '/image-gen' - preLoaderRoute: typeof ImageGenRouteImport + '/sandboxes-durable': { + id: '/sandboxes-durable' + path: '/sandboxes-durable' + fullPath: '/sandboxes-durable' + preLoaderRoute: typeof SandboxesDurableRouteImport parentRoute: typeof rootRouteImport } - '/generic-interrupts': { - id: '/generic-interrupts' - path: '/generic-interrupts' - fullPath: '/generic-interrupts' - preLoaderRoute: typeof GenericInterruptsRouteImport + '/server-fn-chat': { + id: '/server-fn-chat' + path: '/server-fn-chat' + fullPath: '/server-fn-chat' + preLoaderRoute: typeof ServerFnChatRouteImport parentRoute: typeof rootRouteImport } - '/generation-hooks': { - id: '/generation-hooks' - path: '/generation-hooks' - fullPath: '/generation-hooks' - preLoaderRoute: typeof GenerationHooksRouteImport + '/threads': { + id: '/threads' + path: '/threads' + fullPath: '/threads' + preLoaderRoute: typeof ThreadsRouteImport parentRoute: typeof rootRouteImport } - '/capability-demo': { - id: '/capability-demo' - path: '/capability-demo' - fullPath: '/capability-demo' - preLoaderRoute: typeof CapabilityDemoRouteImport + '/typesafe-tools': { + id: '/typesafe-tools' + path: '/typesafe-tools' + fullPath: '/typesafe-tools' + preLoaderRoute: typeof TypesafeToolsRouteImport parentRoute: typeof rootRouteImport } - '/app-studio': { - id: '/app-studio' - path: '/app-studio' - fullPath: '/app-studio' - preLoaderRoute: typeof AppStudioRouteImport + '/websocket-chat': { + id: '/websocket-chat' + path: '/websocket-chat' + fullPath: '/websocket-chat' + preLoaderRoute: typeof WebsocketChatRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/api/app-studio': { + id: '/api/app-studio' + path: '/api/app-studio' + fullPath: '/api/app-studio' + preLoaderRoute: typeof ApiAppStudioRouteImport parentRoute: typeof rootRouteImport } - '/generations/video': { - id: '/generations/video' - path: '/generations/video' - fullPath: '/generations/video' - preLoaderRoute: typeof GenerationsVideoRouteImport + '/api/app-studio-fork': { + id: '/api/app-studio-fork' + path: '/api/app-studio-fork' + fullPath: '/api/app-studio-fork' + preLoaderRoute: typeof ApiAppStudioForkRouteImport parentRoute: typeof rootRouteImport } - '/generations/transcription': { - id: '/generations/transcription' - path: '/generations/transcription' - fullPath: '/generations/transcription' - preLoaderRoute: typeof GenerationsTranscriptionRouteImport + '/api/artifacts': { + id: '/api/artifacts' + path: '/api/artifacts' + fullPath: '/api/artifacts' + preLoaderRoute: typeof ApiArtifactsRouteImport parentRoute: typeof rootRouteImport } - '/generations/summarize': { - id: '/generations/summarize' - path: '/generations/summarize' - fullPath: '/generations/summarize' - preLoaderRoute: typeof GenerationsSummarizeRouteImport + '/api/capability-demo': { + id: '/api/capability-demo' + path: '/api/capability-demo' + fullPath: '/api/capability-demo' + preLoaderRoute: typeof ApiCapabilityDemoRouteImport parentRoute: typeof rootRouteImport } - '/generations/structured-output': { - id: '/generations/structured-output' - path: '/generations/structured-output' - fullPath: '/generations/structured-output' - preLoaderRoute: typeof GenerationsStructuredOutputRouteImport + '/api/generic-interrupts': { + id: '/api/generic-interrupts' + path: '/api/generic-interrupts' + fullPath: '/api/generic-interrupts' + preLoaderRoute: typeof ApiGenericInterruptsRouteImport parentRoute: typeof rootRouteImport } - '/generations/structured-chat': { - id: '/generations/structured-chat' - path: '/generations/structured-chat' - fullPath: '/generations/structured-chat' - preLoaderRoute: typeof GenerationsStructuredChatRouteImport + '/api/image-gen': { + id: '/api/image-gen' + path: '/api/image-gen' + fullPath: '/api/image-gen' + preLoaderRoute: typeof ApiImageGenRouteImport parentRoute: typeof rootRouteImport } - '/generations/speech': { - id: '/generations/speech' - path: '/generations/speech' - fullPath: '/generations/speech' - preLoaderRoute: typeof GenerationsSpeechRouteImport + '/api/image-tool-repro': { + id: '/api/image-tool-repro' + path: '/api/image-tool-repro' + fullPath: '/api/image-tool-repro' + preLoaderRoute: typeof ApiImageToolReproRouteImport parentRoute: typeof rootRouteImport } - '/generations/persistent-generation': { - id: '/generations/persistent-generation' - path: '/generations/persistent-generation' - fullPath: '/generations/persistent-generation' - preLoaderRoute: typeof GenerationsPersistentGenerationRouteImport + '/api/interrupts': { + id: '/api/interrupts' + path: '/api/interrupts' + fullPath: '/api/interrupts' + preLoaderRoute: typeof ApiInterruptsRouteImport parentRoute: typeof rootRouteImport } - '/generations/openrouter-combined': { - id: '/generations/openrouter-combined' - path: '/generations/openrouter-combined' - fullPath: '/generations/openrouter-combined' - preLoaderRoute: typeof GenerationsOpenrouterCombinedRouteImport + '/api/mcp-apps-call': { + id: '/api/mcp-apps-call' + path: '/api/mcp-apps-call' + fullPath: '/api/mcp-apps-call' + preLoaderRoute: typeof ApiMcpAppsCallRouteImport parentRoute: typeof rootRouteImport } - '/generations/image': { - id: '/generations/image' - path: '/generations/image' - fullPath: '/generations/image' - preLoaderRoute: typeof GenerationsImageRouteImport + '/api/mcp-apps-chat': { + id: '/api/mcp-apps-chat' + path: '/api/mcp-apps-chat' + fullPath: '/api/mcp-apps-chat' + preLoaderRoute: typeof ApiMcpAppsChatRouteImport parentRoute: typeof rootRouteImport } - '/generations/audio': { - id: '/generations/audio' - path: '/generations/audio' - fullPath: '/generations/audio' - preLoaderRoute: typeof GenerationsAudioRouteImport + '/api/mcp-apps-shop-server': { + id: '/api/mcp-apps-shop-server' + path: '/api/mcp-apps-shop-server' + fullPath: '/api/mcp-apps-shop-server' + preLoaderRoute: typeof ApiMcpAppsShopServerRouteImport parentRoute: typeof rootRouteImport } - '/example/runtime-context': { - id: '/example/runtime-context' - path: '/example/runtime-context' - fullPath: '/example/runtime-context' - preLoaderRoute: typeof ExampleRuntimeContextRouteImport + '/api/mcp-apps-weather-server': { + id: '/api/mcp-apps-weather-server' + path: '/api/mcp-apps-weather-server' + fullPath: '/api/mcp-apps-weather-server' + preLoaderRoute: typeof ApiMcpAppsWeatherServerRouteImport parentRoute: typeof rootRouteImport } - '/api/transcribe': { - id: '/api/transcribe' - path: '/api/transcribe' - fullPath: '/api/transcribe' - preLoaderRoute: typeof ApiTranscribeRouteImport + '/api/mcp-chat': { + id: '/api/mcp-chat' + path: '/api/mcp-chat' + fullPath: '/api/mcp-chat' + preLoaderRoute: typeof ApiMcpChatRouteImport parentRoute: typeof rootRouteImport } - '/api/tanchat': { - id: '/api/tanchat' - path: '/api/tanchat' - fullPath: '/api/tanchat' - preLoaderRoute: typeof ApiTanchatRouteImport + '/api/mcp-manual': { + id: '/api/mcp-manual' + path: '/api/mcp-manual' + fullPath: '/api/mcp-manual' + preLoaderRoute: typeof ApiMcpManualRouteImport parentRoute: typeof rootRouteImport } - '/api/summarize': { - id: '/api/summarize' - path: '/api/summarize' - fullPath: '/api/summarize' - preLoaderRoute: typeof ApiSummarizeRouteImport + '/api/mcp-pool': { + id: '/api/mcp-pool' + path: '/api/mcp-pool' + fullPath: '/api/mcp-pool' + preLoaderRoute: typeof ApiMcpPoolRouteImport parentRoute: typeof rootRouteImport } - '/api/structured-output': { - id: '/api/structured-output' - path: '/api/structured-output' - fullPath: '/api/structured-output' - preLoaderRoute: typeof ApiStructuredOutputRouteImport + '/api/mcp-status': { + id: '/api/mcp-status' + path: '/api/mcp-status' + fullPath: '/api/mcp-status' + preLoaderRoute: typeof ApiMcpStatusRouteImport + parentRoute: typeof rootRouteImport + } + '/api/openrouter-combined': { + id: '/api/openrouter-combined' + path: '/api/openrouter-combined' + fullPath: '/api/openrouter-combined' + preLoaderRoute: typeof ApiOpenrouterCombinedRouteImport + parentRoute: typeof rootRouteImport + } + '/api/persistent-chat': { + id: '/api/persistent-chat' + path: '/api/persistent-chat' + fullPath: '/api/persistent-chat' + preLoaderRoute: typeof ApiPersistentChatRouteImport parentRoute: typeof rootRouteImport } - '/api/structured-chat': { - id: '/api/structured-chat' - path: '/api/structured-chat' - fullPath: '/api/structured-chat' - preLoaderRoute: typeof ApiStructuredChatRouteImport + '/api/resumable': { + id: '/api/resumable' + path: '/api/resumable' + fullPath: '/api/resumable' + preLoaderRoute: typeof ApiResumableRouteImport parentRoute: typeof rootRouteImport } - '/api/sandbox-triage-durable': { - id: '/api/sandbox-triage-durable' - path: '/api/sandbox-triage-durable' - fullPath: '/api/sandbox-triage-durable' - preLoaderRoute: typeof ApiSandboxTriageDurableRouteImport + '/api/sandbox-repo-report': { + id: '/api/sandbox-repo-report' + path: '/api/sandbox-repo-report' + fullPath: '/api/sandbox-repo-report' + preLoaderRoute: typeof ApiSandboxRepoReportRouteImport parentRoute: typeof rootRouteImport } '/api/sandbox-triage': { @@ -1188,144 +1209,151 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiSandboxTriageRouteImport parentRoute: typeof rootRouteImport } - '/api/sandbox-repo-report': { - id: '/api/sandbox-repo-report' - path: '/api/sandbox-repo-report' - fullPath: '/api/sandbox-repo-report' - preLoaderRoute: typeof ApiSandboxRepoReportRouteImport + '/api/sandbox-triage-durable': { + id: '/api/sandbox-triage-durable' + path: '/api/sandbox-triage-durable' + fullPath: '/api/sandbox-triage-durable' + preLoaderRoute: typeof ApiSandboxTriageDurableRouteImport parentRoute: typeof rootRouteImport } - '/api/resumable': { - id: '/api/resumable' - path: '/api/resumable' - fullPath: '/api/resumable' - preLoaderRoute: typeof ApiResumableRouteImport + '/api/structured-chat': { + id: '/api/structured-chat' + path: '/api/structured-chat' + fullPath: '/api/structured-chat' + preLoaderRoute: typeof ApiStructuredChatRouteImport parentRoute: typeof rootRouteImport } - '/api/persistent-chat': { - id: '/api/persistent-chat' - path: '/api/persistent-chat' - fullPath: '/api/persistent-chat' - preLoaderRoute: typeof ApiPersistentChatRouteImport + '/api/structured-output': { + id: '/api/structured-output' + path: '/api/structured-output' + fullPath: '/api/structured-output' + preLoaderRoute: typeof ApiStructuredOutputRouteImport parentRoute: typeof rootRouteImport } - '/api/openrouter-combined': { - id: '/api/openrouter-combined' - path: '/api/openrouter-combined' - fullPath: '/api/openrouter-combined' - preLoaderRoute: typeof ApiOpenrouterCombinedRouteImport + '/api/summarize': { + id: '/api/summarize' + path: '/api/summarize' + fullPath: '/api/summarize' + preLoaderRoute: typeof ApiSummarizeRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-status': { - id: '/api/mcp-status' - path: '/api/mcp-status' - fullPath: '/api/mcp-status' - preLoaderRoute: typeof ApiMcpStatusRouteImport + '/api/tanchat': { + id: '/api/tanchat' + path: '/api/tanchat' + fullPath: '/api/tanchat' + preLoaderRoute: typeof ApiTanchatRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-pool': { - id: '/api/mcp-pool' - path: '/api/mcp-pool' - fullPath: '/api/mcp-pool' - preLoaderRoute: typeof ApiMcpPoolRouteImport + '/api/transcribe': { + id: '/api/transcribe' + path: '/api/transcribe' + fullPath: '/api/transcribe' + preLoaderRoute: typeof ApiTranscribeRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-manual': { - id: '/api/mcp-manual' - path: '/api/mcp-manual' - fullPath: '/api/mcp-manual' - preLoaderRoute: typeof ApiMcpManualRouteImport + '/example/runtime-context': { + id: '/example/runtime-context' + path: '/example/runtime-context' + fullPath: '/example/runtime-context' + preLoaderRoute: typeof ExampleRuntimeContextRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-chat': { - id: '/api/mcp-chat' - path: '/api/mcp-chat' - fullPath: '/api/mcp-chat' - preLoaderRoute: typeof ApiMcpChatRouteImport + '/generations/audio': { + id: '/generations/audio' + path: '/generations/audio' + fullPath: '/generations/audio' + preLoaderRoute: typeof GenerationsAudioRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-weather-server': { - id: '/api/mcp-apps-weather-server' - path: '/api/mcp-apps-weather-server' - fullPath: '/api/mcp-apps-weather-server' - preLoaderRoute: typeof ApiMcpAppsWeatherServerRouteImport + '/generations/image': { + id: '/generations/image' + path: '/generations/image' + fullPath: '/generations/image' + preLoaderRoute: typeof GenerationsImageRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-shop-server': { - id: '/api/mcp-apps-shop-server' - path: '/api/mcp-apps-shop-server' - fullPath: '/api/mcp-apps-shop-server' - preLoaderRoute: typeof ApiMcpAppsShopServerRouteImport + '/generations/openrouter-combined': { + id: '/generations/openrouter-combined' + path: '/generations/openrouter-combined' + fullPath: '/generations/openrouter-combined' + preLoaderRoute: typeof GenerationsOpenrouterCombinedRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-chat': { - id: '/api/mcp-apps-chat' - path: '/api/mcp-apps-chat' - fullPath: '/api/mcp-apps-chat' - preLoaderRoute: typeof ApiMcpAppsChatRouteImport + '/generations/persistent-generation': { + id: '/generations/persistent-generation' + path: '/generations/persistent-generation' + fullPath: '/generations/persistent-generation' + preLoaderRoute: typeof GenerationsPersistentGenerationRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-call': { - id: '/api/mcp-apps-call' - path: '/api/mcp-apps-call' - fullPath: '/api/mcp-apps-call' - preLoaderRoute: typeof ApiMcpAppsCallRouteImport + '/generations/speech': { + id: '/generations/speech' + path: '/generations/speech' + fullPath: '/generations/speech' + preLoaderRoute: typeof GenerationsSpeechRouteImport parentRoute: typeof rootRouteImport } - '/api/interrupts': { - id: '/api/interrupts' - path: '/api/interrupts' - fullPath: '/api/interrupts' - preLoaderRoute: typeof ApiInterruptsRouteImport + '/generations/structured-chat': { + id: '/generations/structured-chat' + path: '/generations/structured-chat' + fullPath: '/generations/structured-chat' + preLoaderRoute: typeof GenerationsStructuredChatRouteImport parentRoute: typeof rootRouteImport } - '/api/image-tool-repro': { - id: '/api/image-tool-repro' - path: '/api/image-tool-repro' - fullPath: '/api/image-tool-repro' - preLoaderRoute: typeof ApiImageToolReproRouteImport + '/generations/structured-output': { + id: '/generations/structured-output' + path: '/generations/structured-output' + fullPath: '/generations/structured-output' + preLoaderRoute: typeof GenerationsStructuredOutputRouteImport parentRoute: typeof rootRouteImport } - '/api/image-gen': { - id: '/api/image-gen' - path: '/api/image-gen' - fullPath: '/api/image-gen' - preLoaderRoute: typeof ApiImageGenRouteImport + '/generations/summarize': { + id: '/generations/summarize' + path: '/generations/summarize' + fullPath: '/generations/summarize' + preLoaderRoute: typeof GenerationsSummarizeRouteImport parentRoute: typeof rootRouteImport } - '/api/generic-interrupts': { - id: '/api/generic-interrupts' - path: '/api/generic-interrupts' - fullPath: '/api/generic-interrupts' - preLoaderRoute: typeof ApiGenericInterruptsRouteImport + '/generations/transcription': { + id: '/generations/transcription' + path: '/generations/transcription' + fullPath: '/generations/transcription' + preLoaderRoute: typeof GenerationsTranscriptionRouteImport parentRoute: typeof rootRouteImport } - '/api/capability-demo': { - id: '/api/capability-demo' - path: '/api/capability-demo' - fullPath: '/api/capability-demo' - preLoaderRoute: typeof ApiCapabilityDemoRouteImport + '/generations/video': { + id: '/generations/video' + path: '/generations/video' + fullPath: '/generations/video' + preLoaderRoute: typeof GenerationsVideoRouteImport parentRoute: typeof rootRouteImport } - '/api/artifacts': { - id: '/api/artifacts' - path: '/api/artifacts' - fullPath: '/api/artifacts' - preLoaderRoute: typeof ApiArtifactsRouteImport + '/api/generate/audio': { + id: '/api/generate/audio' + path: '/api/generate/audio' + fullPath: '/api/generate/audio' + preLoaderRoute: typeof ApiGenerateAudioRouteImport parentRoute: typeof rootRouteImport } - '/api/app-studio-fork': { - id: '/api/app-studio-fork' - path: '/api/app-studio-fork' - fullPath: '/api/app-studio-fork' - preLoaderRoute: typeof ApiAppStudioForkRouteImport + '/api/generate/image': { + id: '/api/generate/image' + path: '/api/generate/image' + fullPath: '/api/generate/image' + preLoaderRoute: typeof ApiGenerateImageRouteImport parentRoute: typeof rootRouteImport } - '/api/app-studio': { - id: '/api/app-studio' - path: '/api/app-studio' - fullPath: '/api/app-studio' - preLoaderRoute: typeof ApiAppStudioRouteImport + '/api/generate/speech': { + id: '/api/generate/speech' + path: '/api/generate/speech' + fullPath: '/api/generate/speech' + preLoaderRoute: typeof ApiGenerateSpeechRouteImport + parentRoute: typeof rootRouteImport + } + '/api/generate/video': { + id: '/api/generate/video' + path: '/api/generate/video' + fullPath: '/api/generate/video' + preLoaderRoute: typeof ApiGenerateVideoRouteImport parentRoute: typeof rootRouteImport } '/example/guitars/': { @@ -1342,34 +1370,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ExampleGuitarsGuitarIdRouteImport parentRoute: typeof rootRouteImport } - '/api/generate/video': { - id: '/api/generate/video' - path: '/api/generate/video' - fullPath: '/api/generate/video' - preLoaderRoute: typeof ApiGenerateVideoRouteImport - parentRoute: typeof rootRouteImport - } - '/api/generate/speech': { - id: '/api/generate/speech' - path: '/api/generate/speech' - fullPath: '/api/generate/speech' - preLoaderRoute: typeof ApiGenerateSpeechRouteImport - parentRoute: typeof rootRouteImport - } - '/api/generate/image': { - id: '/api/generate/image' - path: '/api/generate/image' - fullPath: '/api/generate/image' - preLoaderRoute: typeof ApiGenerateImageRouteImport - parentRoute: typeof rootRouteImport - } - '/api/generate/audio': { - id: '/api/generate/audio' - path: '/api/generate/audio' - fullPath: '/api/generate/audio' - preLoaderRoute: typeof ApiGenerateAudioRouteImport - parentRoute: typeof rootRouteImport - } '/api/generate/image/artifact': { id: '/api/generate/image/artifact' path: '/artifact' diff --git a/examples/ts-react-media/package.json b/examples/ts-react-media/package.json index 89f5d0b74f..1f832eb91c 100644 --- a/examples/ts-react-media/package.json +++ b/examples/ts-react-media/package.json @@ -18,9 +18,9 @@ "@tanstack/ai-grok": "workspace:*", "@tanstack/ai-openrouter": "workspace:*", "@tanstack/ai-react": "workspace:*", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", + "@tanstack/router-plugin": "^1.168.35", "lucide-react": "^0.561.0", "nitro": "3.0.260610-beta", "react": "^19.2.3", diff --git a/examples/ts-react-media/src/routeTree.gen.ts b/examples/ts-react-media/src/routeTree.gen.ts index cfbe5387c9..3182bcf153 100644 --- a/examples/ts-react-media/src/routeTree.gen.ts +++ b/examples/ts-react-media/src/routeTree.gen.ts @@ -9,19 +9,19 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as SeedanceRouteImport } from './routes/seedance' import { Route as IndexRouteImport } from './routes/index' +import { Route as SeedanceRouteImport } from './routes/seedance' -const SeedanceRoute = SeedanceRouteImport.update({ - id: '/seedance', - path: '/seedance', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) +const SeedanceRoute = SeedanceRouteImport.update({ + id: '/seedance', + path: '/seedance', + getParentRoute: () => rootRouteImport, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -51,13 +51,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/seedance': { - id: '/seedance' - path: '/seedance' - fullPath: '/seedance' - preLoaderRoute: typeof SeedanceRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -65,6 +58,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/seedance': { + id: '/seedance' + path: '/seedance' + fullPath: '/seedance' + preLoaderRoute: typeof SeedanceRouteImport + parentRoute: typeof rootRouteImport + } } } diff --git a/examples/ts-react-rerank/package.json b/examples/ts-react-rerank/package.json index 83785b9b13..045fd110d7 100644 --- a/examples/ts-react-rerank/package.json +++ b/examples/ts-react-rerank/package.json @@ -14,9 +14,9 @@ "@tanstack/ai": "workspace:*", "@tanstack/ai-cohere": "workspace:*", "@tanstack/ai-openrouter": "workspace:*", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", + "@tanstack/router-plugin": "^1.168.35", "lucide-react": "^0.561.0", "nitro": "3.0.260610-beta", "react": "^19.2.3", diff --git a/examples/ts-react-search/package.json b/examples/ts-react-search/package.json index 26b915c7df..3ffaa70b71 100644 --- a/examples/ts-react-search/package.json +++ b/examples/ts-react-search/package.json @@ -19,11 +19,11 @@ "@tanstack/react-db": "^0.1.55", "@tanstack/react-devtools": "^0.9.10", "@tanstack/react-query": "^5.90.12", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-router-devtools": "^1.158.4", - "@tanstack/react-router-ssr-query": "^1.158.4", - "@tanstack/react-start": "^1.159.0", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-router-devtools": "^1.167.1", + "@tanstack/react-router-ssr-query": "^1.167.1", + "@tanstack/react-start": "^1.168.49", + "@tanstack/router-plugin": "^1.168.35", "@tanstack/zod-adapter": "^1.140.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/examples/ts-react-search/src/routeTree.gen.ts b/examples/ts-react-search/src/routeTree.gen.ts index 1ef02fbb58..3fa520b194 100644 --- a/examples/ts-react-search/src/routeTree.gen.ts +++ b/examples/ts-react-search/src/routeTree.gen.ts @@ -9,59 +9,59 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as LayoutRouteRouteImport } from './routes/_layout/route' import { Route as IndexRouteImport } from './routes/index' -import { Route as ApiSettlementsRouteImport } from './routes/api/settlements' -import { Route as ApiSearchRouteImport } from './routes/api/search' -import { Route as ApiOrdersRouteImport } from './routes/api/orders' -import { Route as ApiDisputesRouteImport } from './routes/api/disputes' -import { Route as LayoutSettlementsRouteImport } from './routes/_layout/settlements' -import { Route as LayoutOrdersRouteImport } from './routes/_layout/orders' +import { Route as LayoutRouteRouteImport } from './routes/_layout/route' import { Route as LayoutDisputesRouteImport } from './routes/_layout/disputes' +import { Route as LayoutOrdersRouteImport } from './routes/_layout/orders' +import { Route as LayoutSettlementsRouteImport } from './routes/_layout/settlements' +import { Route as ApiDisputesRouteImport } from './routes/api/disputes' +import { Route as ApiOrdersRouteImport } from './routes/api/orders' +import { Route as ApiSearchRouteImport } from './routes/api/search' +import { Route as ApiSettlementsRouteImport } from './routes/api/settlements' -const LayoutRouteRoute = LayoutRouteRouteImport.update({ - id: '/_layout', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', getParentRoute: () => rootRouteImport, } as any) -const ApiSettlementsRoute = ApiSettlementsRouteImport.update({ - id: '/api/settlements', - path: '/api/settlements', +const LayoutRouteRoute = LayoutRouteRouteImport.update({ + id: '/_layout', getParentRoute: () => rootRouteImport, } as any) -const ApiSearchRoute = ApiSearchRouteImport.update({ - id: '/api/search', - path: '/api/search', - getParentRoute: () => rootRouteImport, +const LayoutDisputesRoute = LayoutDisputesRouteImport.update({ + id: '/disputes', + path: '/disputes', + getParentRoute: () => LayoutRouteRoute, } as any) -const ApiOrdersRoute = ApiOrdersRouteImport.update({ - id: '/api/orders', - path: '/api/orders', - getParentRoute: () => rootRouteImport, +const LayoutOrdersRoute = LayoutOrdersRouteImport.update({ + id: '/orders', + path: '/orders', + getParentRoute: () => LayoutRouteRoute, +} as any) +const LayoutSettlementsRoute = LayoutSettlementsRouteImport.update({ + id: '/settlements', + path: '/settlements', + getParentRoute: () => LayoutRouteRoute, } as any) const ApiDisputesRoute = ApiDisputesRouteImport.update({ id: '/api/disputes', path: '/api/disputes', getParentRoute: () => rootRouteImport, } as any) -const LayoutSettlementsRoute = LayoutSettlementsRouteImport.update({ - id: '/settlements', - path: '/settlements', - getParentRoute: () => LayoutRouteRoute, +const ApiOrdersRoute = ApiOrdersRouteImport.update({ + id: '/api/orders', + path: '/api/orders', + getParentRoute: () => rootRouteImport, } as any) -const LayoutOrdersRoute = LayoutOrdersRouteImport.update({ - id: '/orders', - path: '/orders', - getParentRoute: () => LayoutRouteRoute, +const ApiSearchRoute = ApiSearchRouteImport.update({ + id: '/api/search', + path: '/api/search', + getParentRoute: () => rootRouteImport, } as any) -const LayoutDisputesRoute = LayoutDisputesRouteImport.update({ - id: '/disputes', - path: '/disputes', - getParentRoute: () => LayoutRouteRoute, +const ApiSettlementsRoute = ApiSettlementsRouteImport.update({ + id: '/api/settlements', + path: '/api/settlements', + getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { @@ -141,13 +141,6 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_layout': { - id: '/_layout' - path: '' - fullPath: '/' - preLoaderRoute: typeof LayoutRouteRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -155,26 +148,33 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/api/settlements': { - id: '/api/settlements' - path: '/api/settlements' - fullPath: '/api/settlements' - preLoaderRoute: typeof ApiSettlementsRouteImport + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteRouteImport parentRoute: typeof rootRouteImport } - '/api/search': { - id: '/api/search' - path: '/api/search' - fullPath: '/api/search' - preLoaderRoute: typeof ApiSearchRouteImport - parentRoute: typeof rootRouteImport + '/_layout/disputes': { + id: '/_layout/disputes' + path: '/disputes' + fullPath: '/disputes' + preLoaderRoute: typeof LayoutDisputesRouteImport + parentRoute: typeof LayoutRouteRoute } - '/api/orders': { - id: '/api/orders' - path: '/api/orders' - fullPath: '/api/orders' - preLoaderRoute: typeof ApiOrdersRouteImport - parentRoute: typeof rootRouteImport + '/_layout/orders': { + id: '/_layout/orders' + path: '/orders' + fullPath: '/orders' + preLoaderRoute: typeof LayoutOrdersRouteImport + parentRoute: typeof LayoutRouteRoute + } + '/_layout/settlements': { + id: '/_layout/settlements' + path: '/settlements' + fullPath: '/settlements' + preLoaderRoute: typeof LayoutSettlementsRouteImport + parentRoute: typeof LayoutRouteRoute } '/api/disputes': { id: '/api/disputes' @@ -183,26 +183,26 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiDisputesRouteImport parentRoute: typeof rootRouteImport } - '/_layout/settlements': { - id: '/_layout/settlements' - path: '/settlements' - fullPath: '/settlements' - preLoaderRoute: typeof LayoutSettlementsRouteImport - parentRoute: typeof LayoutRouteRoute + '/api/orders': { + id: '/api/orders' + path: '/api/orders' + fullPath: '/api/orders' + preLoaderRoute: typeof ApiOrdersRouteImport + parentRoute: typeof rootRouteImport } - '/_layout/orders': { - id: '/_layout/orders' - path: '/orders' - fullPath: '/orders' - preLoaderRoute: typeof LayoutOrdersRouteImport - parentRoute: typeof LayoutRouteRoute + '/api/search': { + id: '/api/search' + path: '/api/search' + fullPath: '/api/search' + preLoaderRoute: typeof ApiSearchRouteImport + parentRoute: typeof rootRouteImport } - '/_layout/disputes': { - id: '/_layout/disputes' - path: '/disputes' - fullPath: '/disputes' - preLoaderRoute: typeof LayoutDisputesRouteImport - parentRoute: typeof LayoutRouteRoute + '/api/settlements': { + id: '/api/settlements' + path: '/api/settlements' + fullPath: '/api/settlements' + preLoaderRoute: typeof ApiSettlementsRouteImport + parentRoute: typeof rootRouteImport } } } diff --git a/examples/ts-solid-chat/package.json b/examples/ts-solid-chat/package.json index c00979bd29..98b7f475bb 100644 --- a/examples/ts-solid-chat/package.json +++ b/examples/ts-solid-chat/package.json @@ -21,15 +21,15 @@ "@tanstack/ai-solid": "workspace:*", "@tanstack/ai-solid-ui": "workspace:*", "@tanstack/nitro-v2-vite-plugin": "^1.155.0", - "@tanstack/router-plugin": "^1.158.4", + "@tanstack/router-plugin": "^1.168.35", "@tanstack/solid-ai-devtools": "workspace:*", "@tanstack/solid-devtools": "^0.7.15", - "@tanstack/solid-router": "^1.139.10", - "@tanstack/solid-router-devtools": "^1.139.10", - "@tanstack/solid-router-ssr-query": "^1.139.10", - "@tanstack/solid-start": "^1.139.10", + "@tanstack/solid-router": "^1.170.30", + "@tanstack/solid-router-devtools": "^1.167.1", + "@tanstack/solid-router-ssr-query": "^1.167.1", + "@tanstack/solid-start": "^1.168.47", "@tanstack/solid-store": "^0.8.0", - "@tanstack/store": "^0.8.0", + "@tanstack/store": "^0.11.1", "highlight.js": "^11.11.1", "lucide-solid": "^0.554.0", "solid-js": "^1.9.10", diff --git a/examples/ts-solid-chat/src/routeTree.gen.ts b/examples/ts-solid-chat/src/routeTree.gen.ts index f474ce588d..573ae36e46 100644 --- a/examples/ts-solid-chat/src/routeTree.gen.ts +++ b/examples/ts-solid-chat/src/routeTree.gen.ts @@ -39,7 +39,7 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/api/chat': typeof ApiChatRoute '/example/guitars/$guitarId': typeof ExampleGuitarsGuitarIdRoute - '/example/guitars': typeof ExampleGuitarsIndexRoute + '/example/guitars/': typeof ExampleGuitarsIndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -60,7 +60,7 @@ export interface FileRouteTypes { | '/' | '/api/chat' | '/example/guitars/$guitarId' - | '/example/guitars' + | '/example/guitars/' fileRoutesByTo: FileRoutesByTo to: '/' | '/api/chat' | '/example/guitars/$guitarId' | '/example/guitars' id: @@ -97,7 +97,7 @@ declare module '@tanstack/solid-router' { '/example/guitars/': { id: '/example/guitars/' path: '/example/guitars' - fullPath: '/example/guitars' + fullPath: '/example/guitars/' preLoaderRoute: typeof ExampleGuitarsIndexRouteImport parentRoute: typeof rootRouteImport } diff --git a/packages/ai-angular/src/inject-chat.ts b/packages/ai-angular/src/inject-chat.ts index 58a4a13502..77847b3823 100644 --- a/packages/ai-angular/src/inject-chat.ts +++ b/packages/ai-angular/src/inject-chat.ts @@ -133,13 +133,7 @@ export function injectChat< onChunk: (chunk: StreamChunk) => options.onChunk?.(chunk), onFinish: (message) => options.onFinish?.(message), onError: (err) => options.onError?.(err), - onRunIdChange: (nextRunId) => runId.set(nextRunId), - // No `onResumeStateChange`: the run identity is surfaced as the `runId` - // signal (via `onRunIdChange`) and pending interrupts arrive through - // `onInterruptStateChange`, so there is nothing left for it to do — and it - // is not a public option here, matching the other framework packages. onInterruptStateChange: (nextInterruptState, context) => { - interruptState.set(nextInterruptState) options.onInterruptStateChange?.(nextInterruptState, context) }, tools: options.tools, @@ -151,19 +145,24 @@ export function injectChat< ...(options.streamProcessor !== undefined && { streamProcessor: options.streamProcessor, }), - onMessagesChange: (m: Array>) => messages.set(m), - onLoadingChange: (v: boolean) => isLoading.set(v), - onStatusChange: (v: ChatClientState) => status.set(v), - onErrorChange: (v: Error | undefined) => error.set(v), - onSubscriptionChange: (v: boolean) => isSubscribed.set(v), - onConnectionStatusChange: (v: ConnectionStatus) => connectionStatus.set(v), - onSessionGeneratingChange: (v: boolean) => sessionGenerating.set(v), ...(options.queue !== undefined && { queue: options.queue }), - onQueueChange: (nextQueue: Array) => queue.set(nextQueue), }) - messages.set(client.getMessages()) - interruptState.set(client.getInterruptState()) + const applySnapshot = () => { + const next = client.getSnapshot() + messages.set(next.messages) + isLoading.set(next.isLoading) + error.set(next.error) + status.set(next.status) + isSubscribed.set(next.isSubscribed) + connectionStatus.set(next.connectionStatus) + sessionGenerating.set(next.sessionGenerating) + queue.set(next.queue) + runId.set(next.runId) + interruptState.set(next.interruptState) + } + applySnapshot() + const unsubscribeSnapshot = client.subscribeSnapshot(applySnapshot) // START TAILING HERE, not in the constructor. A client is idle until something // attaches it, so a client that gets built and thrown away never opens a @@ -217,6 +216,7 @@ export function injectChat< ) destroyRef.onDestroy(() => { + unsubscribeSnapshot() // Release the connection first: the counterpart of the `attach` above. client.detach() if (liveSource?.()) { diff --git a/packages/ai-angular/src/inject-generate-video.ts b/packages/ai-angular/src/inject-generate-video.ts index 3287fbea29..43f19d7afb 100644 --- a/packages/ai-angular/src/inject-generate-video.ts +++ b/packages/ai-angular/src/inject-generate-video.ts @@ -181,27 +181,6 @@ export function injectGenerateVideo( onStatusUpdate: (s: VideoStatusInfo) => { if (!disposed) options.onStatusUpdate?.(s) }, - onResultChange: (r: TOutput | null) => { - if (!disposed) result.set(r) - }, - onLoadingChange: (l: boolean) => { - if (!disposed) isLoading.set(l) - }, - onErrorChange: (e: Error | undefined) => { - if (!disposed) error.set(e) - }, - onStatusChange: (s: GenerationClientState) => { - if (!disposed) status.set(s) - }, - onJobIdChange: (id: string | null) => { - if (!disposed) jobId.set(id) - }, - onVideoStatusChange: (s: VideoStatusInfo | null) => { - if (!disposed) videoStatus.set(s) - }, - onResumeStateChange: (rs: { runId: string } | null) => { - if (!disposed) runId.set(rs?.runId ?? null) - }, } let client: VideoGenerationClient @@ -221,6 +200,19 @@ export function injectGenerateVideo( ) } + const applySnapshot = () => { + const next = client.getSnapshot() + result.set(next.result) + isLoading.set(next.isLoading) + error.set(next.error) + status.set(next.status) + runId.set(next.runId) + jobId.set(next.jobId) + videoStatus.set(next.videoStatus) + } + applySnapshot() + const unsubscribeSnapshot = client.subscribe(applySnapshot) + if (bodySource) { effect( () => { @@ -242,6 +234,7 @@ export function injectGenerateVideo( ) destroyRef.onDestroy(() => { disposed = true + unsubscribeSnapshot() client.dispose() }) diff --git a/packages/ai-angular/src/inject-generation.ts b/packages/ai-angular/src/inject-generation.ts index dfd095e079..a103f38744 100644 --- a/packages/ai-angular/src/inject-generation.ts +++ b/packages/ai-angular/src/inject-generation.ts @@ -205,21 +205,6 @@ export function injectGeneration< onChunk: (c: StreamChunk) => { if (!disposed) options.onChunk?.(c) }, - onResultChange: (r: TOutput | null) => { - if (!disposed) result.set(r) - }, - onLoadingChange: (l: boolean) => { - if (!disposed) isLoading.set(l) - }, - onErrorChange: (e: Error | undefined) => { - if (!disposed) error.set(e) - }, - onStatusChange: (s: GenerationClientState) => { - if (!disposed) status.set(s) - }, - onResumeStateChange: (rs) => { - if (!disposed) runId.set(rs?.runId ?? null) - }, } const persistenceProps = @@ -253,6 +238,17 @@ export function injectGeneration< ) } + const applySnapshot = () => { + const next = client.getSnapshot() + result.set(next.result) + isLoading.set(next.isLoading) + error.set(next.error) + status.set(next.status) + runId.set(next.runId) + } + applySnapshot() + const unsubscribeSnapshot = client.subscribe(applySnapshot) + if (bodySource) { effect( () => { @@ -274,6 +270,7 @@ export function injectGeneration< ) destroyRef.onDestroy(() => { disposed = true + unsubscribeSnapshot() client.dispose() }) diff --git a/packages/ai-client/package.json b/packages/ai-client/package.json index d8112524e9..7c77b16585 100644 --- a/packages/ai-client/package.json +++ b/packages/ai-client/package.json @@ -67,7 +67,8 @@ "dependencies": { "@tanstack/ai": "workspace:^", "@tanstack/ai-event-client": "workspace:^", - "@tanstack/ai-utils": "workspace:^" + "@tanstack/ai-utils": "workspace:^", + "@tanstack/store": "^0.11.1" }, "devDependencies": { "@standard-schema/spec": "^1.1.0", diff --git a/packages/ai-client/src/audio-recorder.ts b/packages/ai-client/src/audio-recorder.ts index 0f2b79737f..070ff95f58 100644 --- a/packages/ai-client/src/audio-recorder.ts +++ b/packages/ai-client/src/audio-recorder.ts @@ -1,5 +1,7 @@ import { arrayBufferToBase64 } from '@tanstack/ai-utils' import type { AudioPart } from '@tanstack/ai/client' +import { createAtom } from './snapshot-atom' +import type { Atom } from './snapshot-atom' /** Lifecycle state of an {@link AudioRecorder}. */ export type AudioRecorderState = 'idle' | 'recording' | 'stopping' @@ -62,13 +64,14 @@ export class AudioRecorder { private stream: MediaStream | null = null private chunks: Array = [] private startedAt = 0 - private _state: AudioRecorderState = 'idle' + private readonly snapshotAtom: Atom = + createAtom('idle') + private readonly listeners = new Set<(state: AudioRecorderState) => void>() // True while start() is awaiting getUserMedia (state is still 'idle' then). private starting = false // Set by cancel()/teardown during that window so start() releases the // freshly acquired stream instead of beginning a leaked recording. private pendingCancel = false - private readonly listeners = new Set<(state: AudioRecorderState) => void>() private stopResolve: ((recording: AudioRecording) => void) | null = null private stopReject: ((error: Error) => void) | null = null @@ -87,25 +90,24 @@ export class AudioRecorder { } get state(): AudioRecorderState { - return this._state + return this.snapshotAtom.get() } subscribe(cb: (state: AudioRecorderState) => void): () => void { this.listeners.add(cb) - return () => { - this.listeners.delete(cb) - } + return () => this.listeners.delete(cb) } + /** Current recorder lifecycle state. */ + getSnapshot = (): AudioRecorderState => this.snapshotAtom.get() + private setState(state: AudioRecorderState): void { - this._state = state - for (const cb of this.listeners) { - cb(state) - } + this.snapshotAtom.set(state) + for (const listener of this.listeners) listener(state) } async start(): Promise { - if (this._state !== 'idle' || this.starting) { + if (this.state !== 'idle' || this.starting) { return } this.starting = true @@ -168,7 +170,7 @@ export class AudioRecorder { } stop(): Promise { - if (this._state !== 'recording' || !this.recorder) { + if (this.state !== 'recording' || !this.recorder) { return Promise.reject( new Error('AudioRecorder.stop() called while not recording'), ) @@ -179,7 +181,7 @@ export class AudioRecorder { // Some browsers/codecs never fire onstop; this watchdog unwedges the // recorder instead of leaking this promise forever. const watchdog = setTimeout(() => { - if (this._state !== 'stopping') { + if (this.state !== 'stopping') { return } // Detach handlers so a late onstop/onerror from the stalled recorder @@ -215,7 +217,7 @@ export class AudioRecorder { this.pendingCancel = true return } - if (this._state === 'idle') { + if (this.state === 'idle') { return } const recorder = this.recorder diff --git a/packages/ai-client/src/byok/client.ts b/packages/ai-client/src/byok/client.ts index 0324349e1e..b41d6c264a 100644 --- a/packages/ai-client/src/byok/client.ts +++ b/packages/ai-client/src/byok/client.ts @@ -4,6 +4,8 @@ import { isProviderId, maskKey, } from '@tanstack/ai/byok' +import { createAtom } from '../snapshot-atom' +import type { Atom } from '../snapshot-atom' import { memoryStorage } from './storage' import type { ProviderId } from '@tanstack/ai/byok' import type { Keyring, KeyringStorage } from './storage' @@ -85,15 +87,15 @@ export class ByokClient { #prompt: ByokPrompt | null = null #coverageAll = false #coverage: Record = {} - readonly #listeners = new Set<() => void>() - #snapshot: ByokSnapshot #storageError: string | null = null + readonly #snapshotAtom: Atom + readonly #listeners = new Set<() => void>() readonly #ready: Promise constructor(options: DefineByokOptions = {}) { this.storage = options.storage ?? memoryStorage() this.#locked = Boolean(this.storage.unlockable) - this.#snapshot = this.#buildSnapshot() + this.#snapshotAtom = createAtom(this.#buildSnapshot()) this.#ready = this.#hydrate() } @@ -102,20 +104,25 @@ export class ByokClient { subscribe = (listener: () => void): (() => void) => { this.#listeners.add(listener) - return () => { - this.#listeners.delete(listener) - } + return () => this.#listeners.delete(listener) } - getSnapshot = (): ByokSnapshot => this.#snapshot + getSnapshot = (): ByokSnapshot => this.#snapshotAtom.get() #buildSnapshot(): ByokSnapshot { - return { - status: { ...this.#statuses }, + return Object.freeze({ + status: Object.freeze( + Object.fromEntries( + Object.entries(this.#statuses).map(([key, value]) => [ + key, + value ? Object.freeze({ ...value }) : value, + ]), + ), + ), locked: this.#locked, - prompt: this.#prompt, + prompt: this.#prompt ? Object.freeze({ ...this.#prompt }) : null, storageError: this.#storageError, - } + }) } keys(): Keyring { @@ -350,7 +357,7 @@ export class ByokClient { } #emit(): void { - this.#snapshot = this.#buildSnapshot() + this.#snapshotAtom.set(this.#buildSnapshot()) for (const listener of this.#listeners) listener() } } diff --git a/packages/ai-client/src/chat-client.ts b/packages/ai-client/src/chat-client.ts index 0d320cdd84..6085b6a4e2 100644 --- a/packages/ai-client/src/chat-client.ts +++ b/packages/ai-client/src/chat-client.ts @@ -52,9 +52,12 @@ import type { ChatDevtoolsBridge, ChatDevtoolsBridgeOptions, } from './devtools' +import { createAtom, patchAtom, subscribeAtom } from './snapshot-atom' +import type { Atom } from './snapshot-atom' import type { BoundInterrupts, ChatClientOptions, + ChatClientSnapshot, ChatClientState, ChatFetcher, ChatInterruptState, @@ -334,6 +337,24 @@ export class ChatClient< private readonly persistor?: ChatPersistor private readonly clearedStreamTracker = new ClearedStreamTracker() private currentRunId: string | null = null + private readonly snapshotAtom: Atom> = + createAtom>({ + messages: [], + status: 'ready', + isLoading: false, + error: undefined, + isSubscribed: false, + connectionStatus: 'disconnected', + sessionGenerating: false, + queue: [], + runId: null, + interruptState: { + interrupts: Object.freeze([]), + pendingInterrupts: Object.freeze([]), + interruptErrors: Object.freeze([]), + resuming: false, + } as ChatInterruptState, + }) // Interrupt-resume tracking: the run/thread of the most recent interrupted // run, so approvals/client-tool results can be sent back. Cleared when the // run terminates. This is STATE (interrupt) resume, not delivery/cursor. @@ -627,6 +648,11 @@ export class ChatClient< events: { onMessagesChange: (messages: Array) => { this.persistor?.notifyMessagesChanged(messages) + this.patchSnapshot({ + messages: this.freezeSnapshotMessages( + messages as Array>, + ), + }) this.callbacksRef.current.onMessagesChange(messages) }, onStreamStart: () => { @@ -834,6 +860,7 @@ export class ChatClient< }, }) + this.snapshotAtom.set(this.readSnapshot()) this.persistor?.hydrateAsync(persistedState) this.rejoinRunId = rejoinRunId @@ -1255,6 +1282,7 @@ export class ChatClient< private setCurrentRunId(runId: string | null): void { if (this.currentRunId === runId) return this.currentRunId = runId + this.patchSnapshot({ runId }) this.callbacksRef.current.onRunIdChange(runId) } @@ -1446,26 +1474,134 @@ export class ChatClient< return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}` } + private freezeSnapshotPart>( + part: TPart, + ): TPart { + return Object.freeze({ + ...part, + ...('source' in part && + typeof part.source === 'object' && + part.source !== null + ? { source: Object.freeze({ ...part.source }) } + : {}), + ...(part.type === 'tool-call' && 'approval' in part && part.approval + ? { approval: Object.freeze({ ...part.approval }) } + : {}), + ...(part.type === 'tool-result' && Array.isArray(part.content) + ? { + content: Object.freeze( + part.content.map((contentPart) => + this.freezeSnapshotPart(contentPart), + ), + ), + } + : {}), + }) as TPart + } + + private freezeSnapshotMessages( + messages: Array>, + ): Array> { + return Object.freeze( + messages.map((message) => + Object.freeze({ + ...message, + parts: Object.freeze( + message.parts.map((part) => this.freezeSnapshotPart(part)), + ), + }), + ), + ) as Array> + } + + private freezeSnapshotQueue( + queue: Array, + ): Array { + return Object.freeze( + queue.map((item) => + Object.freeze({ + ...item, + ...(typeof item.content === 'object' + ? { + content: Object.freeze({ + ...item.content, + ...(Array.isArray(item.content.content) + ? { + content: Object.freeze( + item.content.content.map((part) => + this.freezeSnapshotPart(part), + ), + ), + } + : {}), + }), + } + : {}), + }), + ), + ) as Array + } + + private readSnapshot(): ChatClientSnapshot { + return Object.freeze({ + messages: this.freezeSnapshotMessages( + this.processor.getMessages() as Array>, + ), + status: this.status, + isLoading: this.isLoading, + error: this.error, + isSubscribed: this.isSubscribed, + connectionStatus: this.connectionStatus, + sessionGenerating: this.sessionGenerating, + queue: this.freezeSnapshotQueue(this.getQueue()), + runId: this.currentRunId, + interruptState: this.interruptManager.getState(), + }) + } + + private patchSnapshot( + patch: Partial>, + ): void { + patchAtom(this.snapshotAtom, patch) + } + + /** + * Subscribe to UI snapshot changes. Does not fire with the current value; + * read {@link getSnapshot} first. + * + * Named separately from {@link subscribe} (the live connection loop). + */ + subscribeSnapshot = (listener: () => void): (() => void) => + subscribeAtom(this.snapshotAtom, listener) + + /** Current UI snapshot. */ + getSnapshot = (): ChatClientSnapshot => + this.snapshotAtom.get() + private setIsLoading(isLoading: boolean): void { this.isLoading = isLoading + this.patchSnapshot({ isLoading }) this.callbacksRef.current.onLoadingChange(isLoading) this.events.loadingChanged(isLoading) } private setStatus(status: ChatClientState): void { this.status = status + this.patchSnapshot({ status }) this.callbacksRef.current.onStatusChange(status) this.devtoolsBridge.emitSnapshot() } private setIsSubscribed(isSubscribed: boolean): void { this.isSubscribed = isSubscribed + this.patchSnapshot({ isSubscribed }) this.callbacksRef.current.onSubscriptionChange(isSubscribed) this.devtoolsBridge.emitSnapshot() } private setConnectionStatus(status: ConnectionStatus): void { this.connectionStatus = status + this.patchSnapshot({ connectionStatus: status }) this.callbacksRef.current.onConnectionStatusChange(status) this.devtoolsBridge.emitSnapshot() } @@ -1473,6 +1609,7 @@ export class ChatClient< private setSessionGenerating(isGenerating: boolean): void { if (this.sessionGenerating === isGenerating) return this.sessionGenerating = isGenerating + this.patchSnapshot({ sessionGenerating: isGenerating }) this.callbacksRef.current.onSessionGeneratingChange(isGenerating) this.devtoolsBridge.emitSnapshot() } @@ -1482,6 +1619,7 @@ export class ChatClient< // Capture state before invoking callbacks so a synchronous nested change // cannot pair this publication's source with a later manager snapshot. const interruptState = this.interruptManager.getState() + this.patchSnapshot({ interruptState }) // Persist (or clear) the durable resume snapshot so a full page reload can // rehydrate pending interrupts and rejoin the run. Folded into the same // persistence adapter that stores messages (one record per chat). @@ -1524,6 +1662,7 @@ export class ChatClient< private setError(error: Error | undefined): void { this.error = error + this.patchSnapshot({ error }) this.callbacksRef.current.onErrorChange(error) this.events.errorChanged(error?.message || null) } @@ -2939,7 +3078,9 @@ export class ChatClient< } private emitQueueChange(): void { - this.callbacksRef.current.onQueueChange(this.getQueue()) + const queue = this.getQueue() + this.patchSnapshot({ queue: this.freezeSnapshotQueue(queue) }) + this.callbacksRef.current.onQueueChange(queue) this.devtoolsBridge.emitSnapshot() } diff --git a/packages/ai-client/src/generation-client.ts b/packages/ai-client/src/generation-client.ts index b61634bd9b..a6fed26e1c 100644 --- a/packages/ai-client/src/generation-client.ts +++ b/packages/ai-client/src/generation-client.ts @@ -29,8 +29,11 @@ import type { GenerationDevtoolsBridge, GenerationDevtoolsBridgeOptions, } from './devtools' +import { createAtom, patchAtom, subscribeAtom } from './snapshot-atom' +import type { Atom } from './snapshot-atom' import type { GenerationClientOptions, + GenerationClientSnapshot, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, @@ -134,6 +137,14 @@ export class GenerationClient< private isLoading = false private error: Error | undefined = undefined private status: GenerationClientState = 'idle' + private readonly snapshotAtom: Atom> = + createAtom>({ + result: null, + isLoading: false, + error: undefined, + status: 'idle', + runId: null, + }) private resumeSnapshot: GenerationResumeSnapshot | undefined private lastEmittedResumeState: string | undefined private abortController: AbortController | null = null @@ -265,14 +276,19 @@ export class GenerationClient< this.input = input this.progress = null - const runId = this.devtoolsBridge.beginRun(input) - this.setIsLoading(true) - this.setStatus('generating') - this.setError(undefined) - const abortController = new AbortController() this.abortController = abortController const { signal } = abortController + const runId = this.devtoolsBridge.beginRun(input) + patchAtom(this.snapshotAtom, { runId }) + if (!this.ownsRun(abortController)) return + + this.setIsLoading(true, abortController) + if (!this.ownsRun(abortController)) return + this.setStatus('generating', abortController) + if (!this.ownsRun(abortController)) return + this.setError(undefined, abortController) + if (!this.ownsRun(abortController)) return try { let headers: Record | undefined @@ -282,6 +298,7 @@ export class GenerationClient< this.body.provider, ) headers = await prepareResolvedByokHeaders(this.byok, provider) + if (!this.ownsRun(abortController)) return } if (this.fetcher) { @@ -290,19 +307,21 @@ export class GenerationClient< input, headers === undefined ? { signal } : { signal, headers }, ) - if (signal.aborted) return + if (!this.ownsRun(abortController)) return if (result instanceof Response) { // Server function returned SSE Response — parse stream await this.processStream( parseSSEResponse(result, signal), runId, - signal, + abortController, ) } else { this.devtoolsBridge.ensureRunStarted(runId) - this.setResult(result) - this.setStatus('success') - this.completePlainFetcherResumeSnapshot(result) + this.setResult(result, abortController) + if (!this.ownsRun(abortController)) return + this.setStatus('success', abortController) + if (!this.ownsRun(abortController)) return + this.completePlainFetcherResumeSnapshot(result, abortController) } } else if (this.connection) { // Streaming adapter path @@ -313,13 +332,13 @@ export class GenerationClient< signal, this.createRunContext(runId, headers), ) - await this.processStream(stream, runId, signal) + await this.processStream(stream, runId, abortController) } else { throw new Error( 'GenerationClient requires either a connection or fetcher option', ) } - if (!signal.aborted && this.status === 'success') { + if (this.ownsRun(abortController) && this.status === 'success') { // Bump progress to 100 on successful completion so devtools // snapshots reflect the final state. The bridge mirrors this in // the run's recorded progress, but the snapshot reads `progress` @@ -332,17 +351,22 @@ export class GenerationClient< ) } } catch (err: unknown) { - if (signal.aborted) return + if (!this.ownsRun(abortController)) return const error = err instanceof Error ? err : new Error(String(err)) if (error instanceof ByokMissingError) { this.byok?.request(error.provider, 'missing') + if (!this.ownsRun(abortController)) return } if (error instanceof ByokBlockedError && error.reason === 'locked') { this.byok?.request(error.provider, 'locked') + if (!this.ownsRun(abortController)) return } - this.setError(error) - this.setStatus('error') - this.recordResumeSnapshotError(error) + this.setError(error, abortController) + if (!this.ownsRun(abortController)) return + this.setStatus('error', abortController) + if (!this.ownsRun(abortController)) return + this.recordResumeSnapshotError(error, abortController) + if (!this.ownsRun(abortController)) return this.devtoolsBridge.finishRun( this.devtoolsBridge.getActiveRunId() ?? runId, 'run:errored', @@ -352,8 +376,11 @@ export class GenerationClient< this.callbacksRef.onError?.(error) } finally { if (this.abortController === abortController) { - this.abortController = null - this.setIsLoading(false) + patchAtom(this.snapshotAtom, { runId: null }) + if (this.ownsRun(abortController)) { + this.setIsLoading(false, abortController) + if (this.ownsRun(abortController)) this.abortController = null + } } } } @@ -373,8 +400,9 @@ export class GenerationClient< private async processStream( source: AsyncIterable, fallbackRunId: string, - signal: AbortSignal, + abortController: AbortController, ): Promise { + const { signal } = abortController let streamRunId: string | undefined let sawTerminalChunk = false @@ -383,7 +411,9 @@ export class GenerationClient< const chunk = restoreInboundChunk(raw) this.callbacksRef.onChunk?.(chunk) - this.observeResumeSnapshot(chunk) + if (!this.ownsRun(abortController)) return + this.observeResumeSnapshot(chunk, abortController) + if (!this.ownsRun(abortController)) return const chunkRunId = 'runId' in chunk && typeof chunk.runId === 'string' ? chunk.runId @@ -393,19 +423,23 @@ export class GenerationClient< switch (chunk.type) { case 'RUN_STARTED': { streamRunId = chunk.runId + patchAtom(this.snapshotAtom, { runId: chunk.runId }) + if (!this.ownsRun(abortController)) return this.devtoolsBridge.ensureRunStarted(chunk.runId) break } case 'CUSTOM': { this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId) if (chunk.name === GENERATION_EVENTS.RESULT) { - this.setResult(chunk.value as TResult) + this.setResult(chunk.value as TResult, abortController) + if (!this.ownsRun(abortController)) return } else if (chunk.name === GENERATION_EVENTS.PROGRESS) { const { progress, message } = chunk.value as { progress: number message?: string } - this.setProgress(progress, message) + this.setProgress(progress, message, abortController) + if (!this.ownsRun(abortController)) return } break } @@ -413,7 +447,8 @@ export class GenerationClient< streamRunId = chunk.runId sawTerminalChunk = true this.devtoolsBridge.ensureRunStarted(chunk.runId) - this.setStatus('success') + this.setStatus('success', abortController) + if (!this.ownsRun(abortController)) return break } case 'RUN_ERROR': { @@ -441,17 +476,29 @@ export class GenerationClient< */ stop(): void { const runId = this.devtoolsBridge.getActiveRunId() + const stoppedStatus = this.status if (this.abortController) { this.abortController.abort() this.abortController = null } - this.setIsLoading(false) - if (this.status === 'generating') { - this.setStatus('idle') - if (runId) { + if (runId) { + if (stoppedStatus === 'success') { + this.devtoolsBridge.finishRun(runId, 'run:completed', 'completed') + } else if (stoppedStatus === 'error') { + this.devtoolsBridge.finishRun( + runId, + 'run:errored', + 'errored', + this.error?.message, + ) + } else { this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled') } } + patchAtom(this.snapshotAtom, { runId: null }) + if (stoppedStatus === 'generating') { + this.setStatus('idle') + } // A stopped run is no longer resumable. Without this the in-memory // snapshot stays `running`, and a remount's `maybeResumeInFlight` would // rejoin a run the user just cancelled. @@ -463,6 +510,7 @@ export class GenerationClient< } this.notifyResumeSnapshotChanged() } + this.setIsLoading(false) } /** @@ -473,6 +521,7 @@ export class GenerationClient< */ reset(): void { this.stop() + if (this.abortController || this.isLoading) return this.setResult(null) this.input = null this.progress = null @@ -537,7 +586,9 @@ export class GenerationClient< this.abortController.abort() this.abortController = null } - this.setIsLoading(false) + if (this.resumeSnapshot?.status !== 'running') { + patchAtom(this.snapshotAtom, { runId: null }) + } this.devtoolsBridge.dispose() this.devtoolsMounted = false // Re-arm mount hydration + rejoin so a remount resumes from the (preserved) @@ -545,12 +596,23 @@ export class GenerationClient< // `maybeResumeInFlight`, both individually guarded. this.serverHydrationStarted = false this.rejoinedRunId = undefined + this.setIsLoading(false) } // =========================== // Getters // =========================== + /** + * Subscribe to UI snapshot changes. Does not fire with the current value; + * read {@link getSnapshot} first. + */ + subscribe = (listener: () => void): (() => void) => + subscribeAtom(this.snapshotAtom, listener) + + /** Current UI snapshot. */ + getSnapshot = (): GenerationClientSnapshot => this.snapshotAtom.get() + getResult(): TOutput | null { return this.result } @@ -598,16 +660,23 @@ export class GenerationClient< // Private state setters // =========================== - private setResult(rawResult: TResult | null): void { + private setResult( + rawResult: TResult | null, + controller?: AbortController, + ): void { if (rawResult === null) { this.result = null + patchAtom(this.snapshotAtom, { result: null }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResultChange?.(null) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordResultChange() return } if (this.callbacksRef.onResult) { const transformed = this.callbacksRef.onResult(rawResult) + if (controller && !this.ownsRun(controller)) return if (transformed === null) { // null return → keep previous result unchanged, just re-emit this.devtoolsBridge.emitState() @@ -616,7 +685,10 @@ export class GenerationClient< if (transformed !== undefined) { // Non-null, non-undefined → use transformed value this.result = transformed + patchAtom(this.snapshotAtom, { result: this.result }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResultChange?.(this.result) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordResultChange() return } @@ -627,29 +699,51 @@ export class GenerationClient< // `TOutput` defaults to `TResult`, so the runtime cast is sound. // oxlint-disable-next-line eslint-js/no-restricted-syntax -- TOutput defaults to TResult when no onResult transform is supplied this.result = rawResult as unknown as TOutput + patchAtom(this.snapshotAtom, { result: this.result }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResultChange?.(this.result) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordResultChange() } - private setIsLoading(isLoading: boolean): void { + private setIsLoading(isLoading: boolean, controller?: AbortController): void { this.isLoading = isLoading + patchAtom(this.snapshotAtom, { isLoading }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onLoadingChange?.(isLoading) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordLoadingChange() } - private setError(error: Error | undefined): void { + private setError( + error: Error | undefined, + controller?: AbortController, + ): void { this.error = error + patchAtom(this.snapshotAtom, { error }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onErrorChange?.(error) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordErrorChange(error) } - private setStatus(status: GenerationClientState): void { + private setStatus( + status: GenerationClientState, + controller?: AbortController, + ): void { this.status = status + patchAtom(this.snapshotAtom, { status }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onStatusChange?.(status) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordStatusChange(status) } - private setProgress(value: number, message?: string): void { + private setProgress( + value: number, + message?: string, + controller?: AbortController, + ): void { this.progress = { value, ...(message ? { message } : {}), @@ -659,6 +753,7 @@ export class GenerationClient< } else { this.callbacksRef.onProgress?.(value, message) } + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordProgressChange() } @@ -685,6 +780,10 @@ export class GenerationClient< return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}` } + private ownsRun(controller: AbortController): boolean { + return !controller.signal.aborted && this.abortController === controller + } + private createRunContext( runId: string, headers?: Record, @@ -696,12 +795,15 @@ export class GenerationClient< } } - private observeResumeSnapshot(chunk: StreamChunk): void { + private observeResumeSnapshot( + chunk: StreamChunk, + controller: AbortController, + ): void { this.resumeSnapshot = updateGenerationResumeSnapshot( this.resumeSnapshot, chunk, ) - this.notifyResumeSnapshotChanged() + this.notifyResumeSnapshotChanged(controller) } /** @@ -709,9 +811,10 @@ export class GenerationClient< * The snapshot stays internal (persistence + devtools); the hook consumes * `resumeState`, mirroring the chat client. */ - private notifyResumeSnapshotChanged(): void { + private notifyResumeSnapshotChanged(controller?: AbortController): void { this.callbacksRef.onResumeSnapshotChange?.(this.resumeSnapshot) - this.emitResumeState() + if (controller && !this.ownsRun(controller)) return + this.emitResumeState(controller) } /** @@ -725,7 +828,7 @@ export class GenerationClient< * boundaries and when artifacts land, so skip the notification unless it * materially changed — same gate the persistence writes use. */ - private emitResumeState(): void { + private emitResumeState(controller?: AbortController): void { const snapshot = this.resumeSnapshot const state = snapshot?.resumeState const resumeState: GenerationResumeState | null = state @@ -741,6 +844,8 @@ export class GenerationClient< return } this.lastEmittedResumeState = signature + patchAtom(this.snapshotAtom, { runId: resumeState?.runId ?? null }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResumeStateChange?.(resumeState) } @@ -869,7 +974,10 @@ export class GenerationClient< * stale `error` from a previous run is intentionally dropped — this run * succeeded. */ - private completePlainFetcherResumeSnapshot(rawResult: unknown): void { + private completePlainFetcherResumeSnapshot( + rawResult: unknown, + controller: AbortController, + ): void { const previous = this.resumeSnapshot const result = createGenerationResultSnapshot(rawResult) this.resumeSnapshot = { @@ -886,7 +994,7 @@ export class GenerationClient< ? { result: { ...previous.result } } : {}), } - this.notifyResumeSnapshotChanged() + this.notifyResumeSnapshotChanged(controller) } /** @@ -895,15 +1003,20 @@ export class GenerationClient< * mark the snapshot `error`, leaving a persisted record that claims the * run is still in flight. */ - private recordResumeSnapshotError(error: Error): void { + private recordResumeSnapshotError( + error: Error, + controller?: AbortController, + ): void { // Surface the failure on the OBSERVABLE fields FIRST: a rejoin (or live // stream) that emits RUN_ERROR has already flipped the snapshot to `error` // via `observeResumeSnapshot`, so the early-return below would otherwise // skip this and leave `status` stuck on `generating` — the run would look // like it is still going forever. The guard avoids a duplicate `error` // emission on the live `generate()` path, which sets the status itself. - if (this.status !== 'error') this.setStatus('error') - this.setError(error) + if (this.status !== 'error') this.setStatus('error', controller) + if (controller && !this.ownsRun(controller)) return + this.setError(error, controller) + if (controller && !this.ownsRun(controller)) return if (this.resumeSnapshot?.status === 'error') return if (!this.resumeSnapshot && !this.serverDriven) return const previous = this.resumeSnapshot @@ -918,7 +1031,7 @@ export class GenerationClient< ...(previous?.result ? { result: { ...previous.result } } : {}), error: { message: error.message }, } - this.notifyResumeSnapshotChanged() + this.notifyResumeSnapshotChanged(controller) } /** @@ -1042,23 +1155,26 @@ export class GenerationClient< this.rejoinedRunId = runId const controller = new AbortController() this.abortController = controller - this.setIsLoading(true) - this.setStatus('generating') + this.setIsLoading(true, controller) + if (!this.ownsRun(controller)) return + this.setStatus('generating', controller) + if (!this.ownsRun(controller)) return void (async () => { try { await this.processStream( joinRun(runId, controller.signal), runId, - controller.signal, + controller, ) } catch (error) { - if (!controller.signal.aborted) { + if (this.ownsRun(controller)) { const failure = error instanceof Error ? error : new Error(String(error)) // Settles `status`/`error` AND rewrites the snapshot to a terminal // `error` with a null `resumeState`, so the next mount does not // rejoin this run again. - this.recordResumeSnapshotError(failure) + this.recordResumeSnapshotError(failure, controller) + if (!this.ownsRun(controller)) return this.callbacksRef.onError?.(failure) } } finally { @@ -1066,8 +1182,8 @@ export class GenerationClient< // fresh `generate()` may have replaced the controller while the tail // was settling, and that live run owns `isLoading` now. if (this.abortController === controller) { - this.abortController = null - this.setIsLoading(false) + this.setIsLoading(false, controller) + if (this.ownsRun(controller)) this.abortController = null } } })() diff --git a/packages/ai-client/src/generation-types.ts b/packages/ai-client/src/generation-types.ts index bee7670bb0..cb2cf82c01 100644 --- a/packages/ai-client/src/generation-types.ts +++ b/packages/ai-client/src/generation-types.ts @@ -64,6 +64,29 @@ export type InferGenerationOutput = TFn extends ( */ export type GenerationClientState = 'idle' | 'generating' | 'success' | 'error' +/** + * UI snapshot published by {@link GenerationClient.subscribe} / + * {@link GenerationClient.getSnapshot}. + */ +export interface GenerationClientSnapshot { + result: TOutput | null + isLoading: boolean + error: Error | undefined + status: GenerationClientState + runId: string | null +} + +/** + * UI snapshot published by {@link VideoGenerationClient.subscribe} / + * {@link VideoGenerationClient.getSnapshot}. + */ +export interface VideoGenerationClientSnapshot< + TOutput = unknown, +> extends GenerationClientSnapshot { + jobId: string | null + videoStatus: VideoStatusInfo | null +} + /** * Status of a persisted/restored generation run. * diff --git a/packages/ai-client/src/index.ts b/packages/ai-client/src/index.ts index db4c11d2b1..a2b0e4c15a 100644 --- a/packages/ai-client/src/index.ts +++ b/packages/ai-client/src/index.ts @@ -50,6 +50,7 @@ export type { ChatRequestBody, InferChatMessages, InferredClientContext, + ChatClientSnapshot, ChatClientState, ConnectionStatus, ChatFetcher, @@ -70,7 +71,9 @@ export type { export type { InferGenerationOutput, InferGenerationOutputFromReturn, + GenerationClientSnapshot, GenerationClientState, + VideoGenerationClientSnapshot, GenerationPersistenceOptions, GenerationClientOptions, GenerationFetcher, diff --git a/packages/ai-client/src/realtime-client.ts b/packages/ai-client/src/realtime-client.ts index 7808818745..a97f420d99 100644 --- a/packages/ai-client/src/realtime-client.ts +++ b/packages/ai-client/src/realtime-client.ts @@ -1,4 +1,6 @@ import { convertSchemaToJsonSchema } from '@tanstack/ai/client' +import { createAtom, subscribeAtom } from './snapshot-atom' +import type { Atom } from './snapshot-atom' import type { AnyClientTool, AudioVisualization, @@ -57,6 +59,9 @@ export class RealtimeClient { pendingAssistantTranscript: null, error: null, } + private readonly snapshotAtom: Atom = createAtom( + this.buildSnapshot(), + ) constructor(options: RealtimeClientOptions) { this.options = { @@ -88,7 +93,13 @@ export class RealtimeClient { return } - this.updateState({ status: 'connecting', error: null }) + this.updateState({ + status: 'connecting', + error: null, + messages: [], + pendingUserTranscript: null, + pendingAssistantTranscript: null, + }) try { // Fetch token from server @@ -195,6 +206,7 @@ export class RealtimeClient { return } this.connection.interrupt() + this.updateState({ pendingAssistantTranscript: null }) } // ============================================================================ @@ -331,6 +343,16 @@ export class RealtimeClient { } } + /** + * Subscribe to UI snapshot changes. Does not fire with the current value; + * read {@link getSnapshot} first. + */ + subscribe = (listener: () => void): (() => void) => + subscribeAtom(this.snapshotAtom, listener) + + /** Current UI snapshot. */ + getSnapshot = (): RealtimeClientState => this.snapshotAtom.get() + // ============================================================================ // Cleanup // ============================================================================ @@ -349,11 +371,18 @@ export class RealtimeClient { // ============================================================================ private updateState(updates: Partial): void { + const changed = Object.keys(updates).some((key) => { + const stateKey = key as keyof RealtimeClientState + return !Object.is(this.state[stateKey], updates[stateKey]) + }) + if (!changed) return this.state = { ...this.state, ...updates } + this.snapshotAtom.set(this.buildSnapshot()) + const snapshot = this.snapshotAtom.get() // Notify callbacks for (const callback of this.stateChangeCallbacks) { - callback(this.state) + callback(snapshot) } // Notify specific callbacks @@ -365,6 +394,31 @@ export class RealtimeClient { } } + private buildSnapshot(): RealtimeClientState { + return { + ...this.state, + messages: Object.freeze( + this.state.messages.map((message) => + Object.freeze({ + ...message, + parts: Object.freeze( + message.parts.map((part) => + Object.freeze({ + ...part, + ...('source' in part && + typeof part.source === 'object' && + part.source !== null + ? { source: Object.freeze({ ...part.source }) } + : {}), + }), + ), + ), + }), + ), + ) as Array, + } + } + private addMessage(message: RealtimeMessage): void { this.updateState({ messages: [...this.state.messages, message], diff --git a/packages/ai-client/src/snapshot-atom.ts b/packages/ai-client/src/snapshot-atom.ts new file mode 100644 index 0000000000..b05b7ff9d2 --- /dev/null +++ b/packages/ai-client/src/snapshot-atom.ts @@ -0,0 +1,60 @@ +import { createAtom as createStoreAtom } from '@tanstack/store' +import type { Atom } from '@tanstack/store' + +export type { Atom } + +function freezeSnapshot(value: T): T { + return typeof value === 'object' && value !== null + ? Object.freeze(value) + : value +} + +export function createAtom(initialValue: T): Atom { + const atom = createStoreAtom(freezeSnapshot(initialValue)) + const set = atom.set.bind(atom) + atom.set = (updater) => + set((previous) => + freezeSnapshot( + typeof updater === 'function' + ? (updater as (value: T) => T)(previous) + : updater, + ), + ) + return atom +} + +/** + * Merge a partial update into an object atom. Skips notify when every + * provided field is `Object.is`-equal to the current value. + */ +export function patchAtom( + atom: Atom, + patch: Partial, +): void { + atom.set((prev) => { + let changed = false + const next = { ...prev } + for (const key of Object.keys(patch) as Array) { + if (Object.is(prev[key], patch[key])) continue + next[key] = patch[key] as T[keyof T] + changed = true + } + return changed ? next : prev + }) +} + +/** + * Adapt a Store atom's `{ unsubscribe }` subscription to the + * `() => void` unsubscribe that `useSyncExternalStore` expects. + */ +export function subscribeAtom( + atom: { + subscribe: (listener: (value: T) => void) => { unsubscribe: () => void } + }, + listener: () => void, +): () => void { + const { unsubscribe } = atom.subscribe(() => { + listener() + }) + return unsubscribe +} diff --git a/packages/ai-client/src/types.ts b/packages/ai-client/src/types.ts index 01373cef02..9c6868b837 100644 --- a/packages/ai-client/src/types.ts +++ b/packages/ai-client/src/types.ts @@ -364,6 +364,27 @@ export type ToolResultState = */ export type ChatClientState = 'ready' | 'submitted' | 'streaming' | 'error' +/** + * UI snapshot published by {@link ChatClient.subscribeSnapshot} / + * {@link ChatClient.getSnapshot}. + */ +export interface ChatClientSnapshot< + TTools extends ReadonlyArray = any, + TInterrupts extends ReadonlyArray> = + readonly [], +> { + messages: Array> + status: ChatClientState + isLoading: boolean + error: Error | undefined + isSubscribed: boolean + connectionStatus: ConnectionStatus + sessionGenerating: boolean + queue: Array + runId: string | null + interruptState: ChatInterruptState +} + /** * Connection lifecycle state for the subscription loop. */ diff --git a/packages/ai-client/src/video-generation-client.ts b/packages/ai-client/src/video-generation-client.ts index 7ae11c2fc1..eceddf9595 100644 --- a/packages/ai-client/src/video-generation-client.ts +++ b/packages/ai-client/src/video-generation-client.ts @@ -29,6 +29,8 @@ import type { VideoDevtoolsBridge, VideoDevtoolsBridgeOptions, } from './devtools' +import { createAtom, patchAtom, subscribeAtom } from './snapshot-atom' +import type { Atom } from './snapshot-atom' import type { GenerationClientState, GenerationFetcher, @@ -39,6 +41,7 @@ import type { VideoGenerateInput, VideoGenerateResult, VideoGenerationClientOptions, + VideoGenerationClientSnapshot, VideoStatusInfo, } from './generation-types' @@ -139,6 +142,16 @@ export class VideoGenerationClient { private isLoading = false private error: Error | undefined = undefined private status: GenerationClientState = 'idle' + private readonly snapshotAtom: Atom> = + createAtom>({ + result: null, + isLoading: false, + error: undefined, + status: 'idle', + runId: null, + jobId: null, + videoStatus: null, + }) private resumeSnapshot: GenerationResumeSnapshot | undefined private abortController: AbortController | null = null private rejoinedRunId: string | undefined @@ -256,16 +269,23 @@ export class VideoGenerationClient { this.input = input this.progress = null - const runId = this.devtoolsBridge.beginRun(input) - this.setIsLoading(true) - this.setStatus('generating') - this.setError(undefined) - this.setJobId(null) - this.setVideoStatus(null) - const abortController = new AbortController() this.abortController = abortController const { signal } = abortController + const runId = this.devtoolsBridge.beginRun(input) + patchAtom(this.snapshotAtom, { runId }) + if (!this.ownsRun(abortController)) return + + this.setIsLoading(true, abortController) + if (!this.ownsRun(abortController)) return + this.setStatus('generating', abortController) + if (!this.ownsRun(abortController)) return + this.setError(undefined, abortController) + if (!this.ownsRun(abortController)) return + this.setJobId(null, abortController) + if (!this.ownsRun(abortController)) return + this.setVideoStatus(null, abortController) + if (!this.ownsRun(abortController)) return try { let headers: Record | undefined @@ -275,10 +295,11 @@ export class VideoGenerationClient { this.body.provider, ) headers = await prepareResolvedByokHeaders(this.byok, provider) + if (!this.ownsRun(abortController)) return } if (this.fetcher) { - await this.generateWithFetcher(input, signal, runId, headers) + await this.generateWithFetcher(input, abortController, runId, headers) } else if (this.connection) { const mergedData = { ...this.body, ...input } const stream = this.connection.connect( @@ -287,13 +308,13 @@ export class VideoGenerationClient { signal, this.createRunContext(runId, headers), ) - await this.processStream(stream, runId, signal) + await this.processStream(stream, runId, abortController) } else { throw new Error( 'VideoGenerationClient requires either a connection or fetcher option', ) } - if (!signal.aborted && this.status === 'success') { + if (this.ownsRun(abortController) && this.status === 'success') { this.devtoolsBridge.finishRun( this.devtoolsBridge.getActiveRunId() ?? runId, 'run:completed', @@ -301,17 +322,22 @@ export class VideoGenerationClient { ) } } catch (err: unknown) { - if (signal.aborted) return + if (!this.ownsRun(abortController)) return const error = err instanceof Error ? err : new Error(String(err)) if (error instanceof ByokMissingError) { this.byok?.request(error.provider, 'missing') + if (!this.ownsRun(abortController)) return } if (error instanceof ByokBlockedError && error.reason === 'locked') { this.byok?.request(error.provider, 'locked') + if (!this.ownsRun(abortController)) return } - this.setError(error) - this.setStatus('error') - this.recordResumeSnapshotError(error) + this.setError(error, abortController) + if (!this.ownsRun(abortController)) return + this.setStatus('error', abortController) + if (!this.ownsRun(abortController)) return + this.recordResumeSnapshotError(error, abortController) + if (!this.ownsRun(abortController)) return this.devtoolsBridge.finishRun( this.devtoolsBridge.getActiveRunId() ?? runId, 'run:errored', @@ -321,8 +347,11 @@ export class VideoGenerationClient { this.callbacksRef.onError?.(error) } finally { if (this.abortController === abortController) { - this.abortController = null - this.setIsLoading(false) + patchAtom(this.snapshotAtom, { runId: null }) + if (this.ownsRun(abortController)) { + this.setIsLoading(false, abortController) + if (this.ownsRun(abortController)) this.abortController = null + } } } } @@ -332,27 +361,34 @@ export class VideoGenerationClient { */ private async generateWithFetcher( input: VideoGenerateInput, - signal: AbortSignal, + abortController: AbortController, runId: string, headers?: Record, ): Promise { if (!this.fetcher) return + const { signal } = abortController // Fetcher returns a completed result directly, or a Response with SSE body const result = await this.fetcher( input, headers === undefined ? { signal } : { signal, headers }, ) - if (signal.aborted) return + if (!this.ownsRun(abortController)) return if (result instanceof Response) { // Server function returned SSE Response — parse stream - await this.processStream(parseSSEResponse(result, signal), runId, signal) + await this.processStream( + parseSSEResponse(result, signal), + runId, + abortController, + ) } else { this.devtoolsBridge.ensureRunStarted(runId) - this.setResult(result) - this.setStatus('success') - this.completePlainFetcherResumeSnapshot(result) + this.setResult(result, abortController) + if (!this.ownsRun(abortController)) return + this.setStatus('success', abortController) + if (!this.ownsRun(abortController)) return + this.completePlainFetcherResumeSnapshot(result, abortController) } } @@ -368,8 +404,9 @@ export class VideoGenerationClient { private async processStream( source: AsyncIterable, fallbackRunId: string, - signal: AbortSignal, + abortController: AbortController, ): Promise { + const { signal } = abortController let streamRunId: string | undefined let sawTerminalChunk = false @@ -378,7 +415,9 @@ export class VideoGenerationClient { const chunk = restoreInboundChunk(raw) this.callbacksRef.onChunk?.(chunk) - this.observeResumeSnapshot(chunk) + if (!this.ownsRun(abortController)) return + this.observeResumeSnapshot(chunk, abortController) + if (!this.ownsRun(abortController)) return const chunkRunId = 'runId' in chunk && typeof chunk.runId === 'string' ? chunk.runId @@ -388,6 +427,8 @@ export class VideoGenerationClient { switch (chunk.type) { case 'RUN_STARTED': { streamRunId = chunk.runId + patchAtom(this.snapshotAtom, { runId: chunk.runId }) + if (!this.ownsRun(abortController)) return this.devtoolsBridge.ensureRunStarted(chunk.runId) break } @@ -395,23 +436,30 @@ export class VideoGenerationClient { this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId) if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) { const { jobId } = chunk.value as { jobId: string } - this.setJobId(jobId) + this.setJobId(jobId, abortController) + if (!this.ownsRun(abortController)) return this.callbacksRef.onJobCreated?.(jobId) + if (!this.ownsRun(abortController)) return } else if (chunk.name === GENERATION_EVENTS.VIDEO_STATUS) { const statusInfo = chunk.value as VideoStatusInfo - this.setVideoStatus(statusInfo) + this.setVideoStatus(statusInfo, abortController) + if (!this.ownsRun(abortController)) return this.callbacksRef.onStatusUpdate?.(statusInfo) + if (!this.ownsRun(abortController)) return if (statusInfo.progress !== undefined) { - this.setProgress(statusInfo.progress) + this.setProgress(statusInfo.progress, undefined, abortController) + if (!this.ownsRun(abortController)) return } } else if (chunk.name === GENERATION_EVENTS.RESULT) { - this.setResult(chunk.value as VideoGenerateResult) + this.setResult(chunk.value as VideoGenerateResult, abortController) + if (!this.ownsRun(abortController)) return } else if (chunk.name === GENERATION_EVENTS.PROGRESS) { const { progress, message } = chunk.value as { progress: number message?: string } - this.setProgress(progress, message) + this.setProgress(progress, message, abortController) + if (!this.ownsRun(abortController)) return } break } @@ -419,7 +467,8 @@ export class VideoGenerationClient { streamRunId = chunk.runId sawTerminalChunk = true this.devtoolsBridge.ensureRunStarted(chunk.runId) - this.setStatus('success') + this.setStatus('success', abortController) + if (!this.ownsRun(abortController)) return break } case 'RUN_ERROR': { @@ -447,17 +496,29 @@ export class VideoGenerationClient { */ stop(): void { const runId = this.devtoolsBridge.getActiveRunId() + const stoppedStatus = this.status if (this.abortController) { this.abortController.abort() this.abortController = null } - this.setIsLoading(false) - if (this.status === 'generating') { - this.setStatus('idle') - if (runId) { + if (runId) { + if (stoppedStatus === 'success') { + this.devtoolsBridge.finishRun(runId, 'run:completed', 'completed') + } else if (stoppedStatus === 'error') { + this.devtoolsBridge.finishRun( + runId, + 'run:errored', + 'errored', + this.error?.message, + ) + } else { this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled') } } + patchAtom(this.snapshotAtom, { runId: null }) + if (stoppedStatus === 'generating') { + this.setStatus('idle') + } // A stopped run is no longer resumable. Without this the in-memory // snapshot stays `running`, and a remount's `maybeResumeInFlight` would // rejoin a run the user just cancelled. @@ -469,6 +530,7 @@ export class VideoGenerationClient { } this.notifyResumeSnapshotChanged() } + this.setIsLoading(false) } /** @@ -479,6 +541,7 @@ export class VideoGenerationClient { */ reset(): void { this.stop() + if (this.abortController || this.isLoading) return this.setResult(null) this.input = null this.progress = null @@ -547,17 +610,31 @@ export class VideoGenerationClient { this.abortController.abort() this.abortController = null } - this.setIsLoading(false) + if (this.resumeSnapshot?.status !== 'running') { + patchAtom(this.snapshotAtom, { runId: null }) + } this.devtoolsBridge.dispose() this.devtoolsMounted = false this.serverHydrationStarted = false this.rejoinedRunId = undefined + this.setIsLoading(false) } // =========================== // Getters // =========================== + /** + * Subscribe to UI snapshot changes. Does not fire with the current value; + * read {@link getSnapshot} first. + */ + subscribe = (listener: () => void): (() => void) => + subscribeAtom(this.snapshotAtom, listener) + + /** Current UI snapshot. */ + getSnapshot = (): VideoGenerationClientSnapshot => + this.snapshotAtom.get() + getResult(): TOutput | null { return this.result } @@ -613,23 +690,33 @@ export class VideoGenerationClient { // Private state setters // =========================== - private setResult(rawResult: VideoGenerateResult | null): void { + private setResult( + rawResult: VideoGenerateResult | null, + controller?: AbortController, + ): void { if (rawResult === null) { this.result = null + patchAtom(this.snapshotAtom, { result: null }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResultChange?.(null) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordResultChange() return } const completedStatus = this.createCompletedVideoStatus(rawResult) if (this.progress?.value !== 100) { - this.setProgress(100, this.progress?.message) + this.setProgress(100, this.progress?.message, controller) + if (controller && !this.ownsRun(controller)) return } - this.setJobId(rawResult.jobId) - this.setVideoStatus(completedStatus) + this.setJobId(rawResult.jobId, controller) + if (controller && !this.ownsRun(controller)) return + this.setVideoStatus(completedStatus, controller) + if (controller && !this.ownsRun(controller)) return if (this.callbacksRef.onResult) { const transformed = this.callbacksRef.onResult(rawResult) + if (controller && !this.ownsRun(controller)) return if (transformed === null) { // null return → keep previous result unchanged, just re-emit this.devtoolsBridge.emitState() @@ -638,7 +725,10 @@ export class VideoGenerationClient { if (transformed !== undefined) { // Non-null, non-undefined → use transformed value this.result = transformed + patchAtom(this.snapshotAtom, { result: this.result }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResultChange?.(this.result) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordResultChange() return } @@ -650,41 +740,76 @@ export class VideoGenerationClient { // sound. // oxlint-disable-next-line eslint-js/no-restricted-syntax -- TOutput defaults to VideoGenerateResult when no onResult transform is supplied this.result = rawResult as unknown as TOutput + patchAtom(this.snapshotAtom, { result: this.result }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResultChange?.(this.result) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordResultChange() } - private setJobId(jobId: string | null): void { + private setJobId(jobId: string | null, controller?: AbortController): void { this.jobId = jobId + patchAtom(this.snapshotAtom, { jobId }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onJobIdChange?.(jobId) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordJobIdChange() } - private setVideoStatus(status: VideoStatusInfo | null): void { - this.videoStatus = status - this.callbacksRef.onVideoStatusChange?.(status) + private setVideoStatus( + status: VideoStatusInfo | null, + controller?: AbortController, + ): void { + this.videoStatus = status ? { ...status } : null + patchAtom(this.snapshotAtom, { + videoStatus: this.videoStatus + ? Object.freeze({ ...this.videoStatus }) + : null, + }) + if (controller && !this.ownsRun(controller)) return + this.callbacksRef.onVideoStatusChange?.(this.videoStatus) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordVideoStatusChange() } - private setIsLoading(isLoading: boolean): void { + private setIsLoading(isLoading: boolean, controller?: AbortController): void { this.isLoading = isLoading + patchAtom(this.snapshotAtom, { isLoading }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onLoadingChange?.(isLoading) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordLoadingChange() } - private setError(error: Error | undefined): void { + private setError( + error: Error | undefined, + controller?: AbortController, + ): void { this.error = error + patchAtom(this.snapshotAtom, { error }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onErrorChange?.(error) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordErrorChange(error) } - private setStatus(status: GenerationClientState): void { + private setStatus( + status: GenerationClientState, + controller?: AbortController, + ): void { this.status = status + patchAtom(this.snapshotAtom, { status }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onStatusChange?.(status) + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordStatusChange(status) } - private setProgress(value: number, message?: string): void { + private setProgress( + value: number, + message?: string, + controller?: AbortController, + ): void { this.progress = { value, ...(message ? { message } : {}), @@ -694,6 +819,7 @@ export class VideoGenerationClient { } else { this.callbacksRef.onProgress?.(value, message) } + if (controller && !this.ownsRun(controller)) return this.devtoolsBridge.recordProgressChange() } @@ -731,6 +857,10 @@ export class VideoGenerationClient { return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}` } + private ownsRun(controller: AbortController): boolean { + return !controller.signal.aborted && this.abortController === controller + } + private createRunContext( runId: string, headers?: Record, @@ -742,22 +872,26 @@ export class VideoGenerationClient { } } - private observeResumeSnapshot(chunk: StreamChunk): void { + private observeResumeSnapshot( + chunk: StreamChunk, + controller: AbortController, + ): void { this.resumeSnapshot = updateGenerationResumeSnapshot( this.resumeSnapshot, chunk, ) - this.notifyResumeSnapshotChanged() + this.notifyResumeSnapshotChanged(controller) } /** Notify the internal snapshot listener AND emit the public resume state. */ - private notifyResumeSnapshotChanged(): void { + private notifyResumeSnapshotChanged(controller?: AbortController): void { this.callbacksRef.onResumeSnapshotChange?.(this.resumeSnapshot) - this.emitResumeState() + if (controller && !this.ownsRun(controller)) return + this.emitResumeState(controller) } /** Derive the public `resumeState` from the internal snapshot. */ - private emitResumeState(): void { + private emitResumeState(controller?: AbortController): void { const snapshot = this.resumeSnapshot const state = snapshot?.resumeState const resumeState: GenerationResumeState | null = state @@ -768,6 +902,8 @@ export class VideoGenerationClient { : {}), } : null + patchAtom(this.snapshotAtom, { runId: resumeState?.runId ?? null }) + if (controller && !this.ownsRun(controller)) return this.callbacksRef.onResumeStateChange?.(resumeState) } @@ -881,7 +1017,10 @@ export class VideoGenerationClient { * stale `error` from a previous run is intentionally dropped — this run * succeeded. */ - private completePlainFetcherResumeSnapshot(rawResult: unknown): void { + private completePlainFetcherResumeSnapshot( + rawResult: unknown, + controller: AbortController, + ): void { const previous = this.resumeSnapshot const result = createGenerationResultSnapshot(rawResult) this.resumeSnapshot = { @@ -898,7 +1037,7 @@ export class VideoGenerationClient { ? { result: { ...previous.result } } : {}), } - this.notifyResumeSnapshotChanged() + this.notifyResumeSnapshotChanged(controller) } /** @@ -907,14 +1046,19 @@ export class VideoGenerationClient { * mark the snapshot `error`, leaving a persisted record that claims the * run is still in flight. */ - private recordResumeSnapshotError(error: Error): void { + private recordResumeSnapshotError( + error: Error, + controller?: AbortController, + ): void { // Surface the failure on the observable fields FIRST, unconditionally (see // the note in GenerationClient.recordResumeSnapshotError): a RUN_ERROR // already flipped the snapshot to `error`, so the early-return would else // skip this and leave `status` stuck on `generating`. The guard avoids a // duplicate `error` emission on the live `generate()` path. - if (this.status !== 'error') this.setStatus('error') - this.setError(error) + if (this.status !== 'error') this.setStatus('error', controller) + if (controller && !this.ownsRun(controller)) return + this.setError(error, controller) + if (controller && !this.ownsRun(controller)) return if (this.resumeSnapshot?.status === 'error') return if (!this.resumeSnapshot && !this.serverDriven) return const previous = this.resumeSnapshot @@ -929,7 +1073,7 @@ export class VideoGenerationClient { ...(previous?.result ? { result: { ...previous.result } } : {}), error: { message: error.message }, } - this.notifyResumeSnapshotChanged() + this.notifyResumeSnapshotChanged(controller) } /** @@ -1046,23 +1190,26 @@ export class VideoGenerationClient { this.rejoinedRunId = runId const controller = new AbortController() this.abortController = controller - this.setIsLoading(true) - this.setStatus('generating') + this.setIsLoading(true, controller) + if (!this.ownsRun(controller)) return + this.setStatus('generating', controller) + if (!this.ownsRun(controller)) return void (async () => { try { await this.processStream( joinRun(runId, controller.signal), runId, - controller.signal, + controller, ) } catch (error) { - if (!controller.signal.aborted) { + if (this.ownsRun(controller)) { const failure = error instanceof Error ? error : new Error(String(error)) // Settles `status`/`error` AND rewrites the snapshot to a terminal // `error` with a null `resumeState`, so the next mount does not // rejoin this run again. - this.recordResumeSnapshotError(failure) + this.recordResumeSnapshotError(failure, controller) + if (!this.ownsRun(controller)) return this.callbacksRef.onError?.(failure) } } finally { @@ -1070,8 +1217,8 @@ export class VideoGenerationClient { // fresh `generate()` may have replaced the controller while the tail // was settling, and that live run owns `isLoading` now. if (this.abortController === controller) { - this.abortController = null - this.setIsLoading(false) + this.setIsLoading(false, controller) + if (this.ownsRun(controller)) this.abortController = null } } })() diff --git a/packages/ai-client/tests/audio-recorder.test.ts b/packages/ai-client/tests/audio-recorder.test.ts index 49c773b0cd..387308fb8b 100644 --- a/packages/ai-client/tests/audio-recorder.test.ts +++ b/packages/ai-client/tests/audio-recorder.test.ts @@ -75,6 +75,21 @@ afterEach(() => { }) describe('AudioRecorder', () => { + it('uses Set subscription behavior and reports repeated states', async () => { + const recorder = new AudioRecorder() + const listener = vi.fn() + const first = recorder.subscribe(listener) + recorder.subscribe(listener) + + getUserMedia.mockRejectedValueOnce(new Error('denied')) + await expect(recorder.start()).rejects.toThrow('denied') + expect(listener).toHaveBeenCalledTimes(1) + first() + getUserMedia.mockRejectedValueOnce(new Error('denied')) + await expect(recorder.start()).rejects.toThrow('denied') + expect(listener).toHaveBeenCalledTimes(1) + }) + it('isSupported() is true when media APIs exist', () => { expect(AudioRecorder.isSupported()).toBe(true) }) diff --git a/packages/ai-client/tests/byok-client.test.ts b/packages/ai-client/tests/byok-client.test.ts index b2bab1c253..8851378c86 100644 --- a/packages/ai-client/tests/byok-client.test.ts +++ b/packages/ai-client/tests/byok-client.test.ts @@ -8,6 +8,34 @@ describe('defineByok memory', () => { vi.unstubAllGlobals() }) + it('isolates and freezes nested snapshot state', async () => { + const byok = defineByok() + await byok.update('openai', 'sk-abcdefghij') + const snapshot = byok.getSnapshot() + + expect(Object.isFrozen(snapshot)).toBe(true) + expect(Object.isFrozen(snapshot.status)).toBe(true) + expect(Object.isFrozen(snapshot.status.openai)).toBe(true) + Reflect.set(snapshot.status.openai!, 'masked', 'leak') + expect(byok.getSnapshot().status.openai).toEqual({ + state: 'set', + masked: 'ghij', + }) + }) + + it('uses Set subscription behavior', () => { + const byok = defineByok() + const listener = vi.fn() + const first = byok.subscribe(listener) + byok.subscribe(listener) + + byok.request('openai', 'missing') + expect(listener).toHaveBeenCalledTimes(1) + first() + byok.request('openai', 'missing') + expect(listener).toHaveBeenCalledTimes(1) + }) + it('updates and snapshots without exposing the raw key', async () => { const byok = defineByok({ storage: memoryStorage() }) await byok.update('openai', 'sk-abcdefghij') diff --git a/packages/ai-client/tests/byok-generation-client.test.ts b/packages/ai-client/tests/byok-generation-client.test.ts index 948635c5b2..eeceb405e9 100644 --- a/packages/ai-client/tests/byok-generation-client.test.ts +++ b/packages/ai-client/tests/byok-generation-client.test.ts @@ -87,6 +87,64 @@ describe('GenerationClient byok', () => { expect(client.getError()).toBeInstanceOf(ByokMissingError) }) + it('stops the failed run when the BYOK request subscriber stops', async () => { + const byok = defineByok({ storage: memoryStorage() }) + await byok.update('elevenlabs', ELEVENLABS_KEY) + const onErrorChange = vi.fn() + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher: async (): Promise<{ id: string }> => { + throw new ByokMissingError('elevenlabs') + }, + byok, + byokProvider: () => 'elevenlabs', + onErrorChange, + }) + byok.subscribe(() => { + if (byok.getSnapshot().prompt) client.stop() + }) + + await client.generate({ prompt: 'first' }) + + expect(client.getStatus()).toBe('idle') + expect(client.getError()).toBeUndefined() + expect(onErrorChange).not.toHaveBeenCalledWith(expect.any(Error)) + }) + + it('does not let a failed BYOK run overwrite a replacement', async () => { + const byok = defineByok({ storage: memoryStorage() }) + await byok.update('elevenlabs', ELEVENLABS_KEY) + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + let secondGenerate: Promise | undefined + let replaced = false + const client = new GenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') { + throw new ByokMissingError('elevenlabs') + } + return secondResult + }, + byok, + byokProvider: () => 'elevenlabs', + }) + byok.subscribe(() => { + if (!byok.getSnapshot().prompt || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(client.getStatus()).toBe('generating') + expect(client.getError()).toBeUndefined() + releaseSecond({ id: 'second' }) + await secondGenerate + }) + it('requests a missing key when the fetcher returns a byokMissing Response', async () => { const byok = defineByok({ storage: memoryStorage() }) await byok.update('elevenlabs', ELEVENLABS_KEY) diff --git a/packages/ai-client/tests/byok-video-generation-client.test.ts b/packages/ai-client/tests/byok-video-generation-client.test.ts index 166b3eb4d4..2b7633c385 100644 --- a/packages/ai-client/tests/byok-video-generation-client.test.ts +++ b/packages/ai-client/tests/byok-video-generation-client.test.ts @@ -92,6 +92,64 @@ describe('VideoGenerationClient byok', () => { expect(client.getError()).toBeInstanceOf(ByokMissingError) }) + it('stops the failed run when the BYOK request subscriber stops', async () => { + const byok = defineByok({ storage: memoryStorage() }) + await byok.update('elevenlabs', ELEVENLABS_KEY) + const onErrorChange = vi.fn() + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher: async () => { + throw new ByokMissingError('elevenlabs') + }, + byok, + byokProvider: () => 'elevenlabs', + onErrorChange, + }) + byok.subscribe(() => { + if (byok.getSnapshot().prompt) client.stop() + }) + + await client.generate({ prompt: 'first' }) + + expect(client.getStatus()).toBe('idle') + expect(client.getError()).toBeUndefined() + expect(onErrorChange).not.toHaveBeenCalledWith(expect.any(Error)) + }) + + it('does not let a failed BYOK run overwrite a replacement', async () => { + const byok = defineByok({ storage: memoryStorage() }) + await byok.update('elevenlabs', ELEVENLABS_KEY) + let releaseSecond!: (value: typeof VIDEO_RESULT) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + let secondGenerate: Promise | undefined + let replaced = false + const client = new VideoGenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') { + throw new ByokMissingError('elevenlabs') + } + return secondResult + }, + byok, + byokProvider: () => 'elevenlabs', + }) + byok.subscribe(() => { + if (!byok.getSnapshot().prompt || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(client.getStatus()).toBe('generating') + expect(client.getError()).toBeUndefined() + releaseSecond(VIDEO_RESULT) + await secondGenerate + }) + it('requests a missing key when the fetcher returns a byokMissing Response', async () => { const byok = defineByok({ storage: memoryStorage() }) await byok.update('elevenlabs', ELEVENLABS_KEY) diff --git a/packages/ai-client/tests/chat-client.test.ts b/packages/ai-client/tests/chat-client.test.ts index 67429010b6..e3f8a5cb74 100644 --- a/packages/ai-client/tests/chat-client.test.ts +++ b/packages/ai-client/tests/chat-client.test.ts @@ -68,6 +68,105 @@ describe('ChatClient', () => { expect(client.getError()).toBeUndefined() }) + it('exposes a UI snapshot that subscribeSnapshot notifies', () => { + const adapter = createMockConnectionAdapter() + const initialMessages: Array = [ + { + id: 'msg-1', + role: 'user', + parts: [{ type: 'text', content: 'Hello' }], + createdAt: new Date(), + }, + ] + const client = new ChatClient({ + connection: adapter, + initialMessages, + }) + + const snapshot = client.getSnapshot() + expect(snapshot.messages).toEqual(initialMessages) + expect(snapshot.status).toBe('ready') + expect(snapshot.isLoading).toBe(false) + expect(client.getSnapshot()).toBe(snapshot) + + const listener = vi.fn() + const stop = client.subscribeSnapshot(listener) + client.setMessagesManually([]) + expect(listener).toHaveBeenCalledTimes(1) + expect(client.getSnapshot().messages).toEqual([]) + expect(client.getSnapshot()).not.toBe(snapshot) + stop() + }) + + it('isolates nested message parts from callers and old snapshots', () => { + const adapter = createMockConnectionAdapter() + const source = { + type: 'url' as const, + value: 'https://example.com/a.png', + } + const client = new ChatClient({ + connection: adapter, + initialMessages: [ + { + id: 'msg-1', + role: 'user', + parts: [{ type: 'image', source }], + createdAt: new Date(), + }, + ], + }) + const oldSnapshot = client.getSnapshot() + + source.value = 'https://example.com/changed.png' + expect(oldSnapshot.messages[0]?.parts[0]).toMatchObject({ + source: { value: 'https://example.com/a.png' }, + }) + const publishedPart = oldSnapshot.messages[0]?.parts[0] + expect(Object.isFrozen(publishedPart)).toBe(true) + if (!publishedPart || !('source' in publishedPart)) { + throw new Error('Expected a message part with a source') + } + expect(Object.isFrozen(publishedPart.source)).toBe(true) + }) + + it('isolates messages published by normal updates', () => { + const client = new ChatClient({ + connection: createMockConnectionAdapter(), + }) + const oldSnapshot = client.getSnapshot() + const source = { + type: 'url' as const, + value: 'https://example.com/updated.png', + } + + client.setMessagesManually([ + { + id: 'updated-1', + role: 'user', + parts: [{ type: 'image', source }], + }, + ]) + + const snapshot = client.getSnapshot() + const part = snapshot.messages[0]?.parts[0] + expect(snapshot).not.toBe(oldSnapshot) + expect(oldSnapshot.messages).toEqual([]) + expect(Object.isFrozen(snapshot.messages)).toBe(true) + expect(Object.isFrozen(snapshot.messages[0])).toBe(true) + expect(Object.isFrozen(snapshot.messages[0]?.parts)).toBe(true) + expect(Object.isFrozen(part)).toBe(true) + expect(part).toMatchObject({ source }) + + if (!part || !('source' in part)) { + throw new Error('Expected a message part with a source') + } + const publishedSource = part.source + expect(Object.isFrozen(publishedSource)).toBe(true) + expect(Reflect.set(publishedSource, 'value', 'changed')).toBe(false) + expect(client.getMessages()[0]?.parts[0]).toMatchObject({ source }) + expect(client.getSnapshot()).toBe(snapshot) + }) + it('should initialize with provided messages', () => { const adapter = createMockConnectionAdapter() const initialMessages: Array = [ @@ -2248,6 +2347,61 @@ describe('ChatClient', () => { { type: 'text', content: 'Second' }, ]) }) + + it('isolates queue entries published by normal updates', async () => { + const client = new ChatClient({ + connection: createMockConnectionAdapter({ + chunks: createTextChunks('Response'), + chunkDelay: 100, + }), + }) + const firstSend = client.sendMessage('First') + const oldSnapshot = client.getSnapshot() + + await client.sendMessage({ + content: [ + { + type: 'image', + source: { + type: 'url', + value: 'https://example.com/queued.png', + }, + }, + ], + }) + + const snapshot = client.getSnapshot() + const queuedContent = snapshot.queue[0]?.content + expect(snapshot).not.toBe(oldSnapshot) + expect(oldSnapshot.queue).toEqual([]) + expect(Object.isFrozen(snapshot.queue)).toBe(true) + expect(Object.isFrozen(snapshot.queue[0])).toBe(true) + expect(typeof queuedContent).toBe('object') + if (typeof queuedContent === 'object') { + expect(Object.isFrozen(queuedContent)).toBe(true) + expect(Object.isFrozen(queuedContent.content)).toBe(true) + const queuedPart = Array.isArray(queuedContent.content) + ? queuedContent.content[0] + : undefined + expect(Object.isFrozen(queuedPart)).toBe(true) + if (!queuedPart || !('source' in queuedPart)) { + throw new Error('Expected a queued content part with a source') + } + const publishedSource = queuedPart.source + expect(Object.isFrozen(publishedSource)).toBe(true) + expect(Reflect.set(publishedSource, 'value', 'changed')).toBe(false) + } + expect(client.getQueue()[0]?.content).toMatchObject({ + content: [ + { + source: { value: 'https://example.com/queued.png' }, + }, + ], + }) + expect(client.getSnapshot()).toBe(snapshot) + + await firstSend + }) }) describe('append', () => { diff --git a/packages/ai-client/tests/client-snapshots.test.ts b/packages/ai-client/tests/client-snapshots.test.ts new file mode 100644 index 0000000000..bcbf6b8636 --- /dev/null +++ b/packages/ai-client/tests/client-snapshots.test.ts @@ -0,0 +1,153 @@ +import { describe, expect, it, vi } from 'vitest' +import { + AudioRecorder, + GenerationClient, + RealtimeClient, + VideoGenerationClient, +} from '../src' +import type { RealtimeAdapter } from '../src' + +describe('client UI snapshots', () => { + it('publishes and clears the active generation run id', async () => { + let resolve!: (value: { id: string }) => void + const pending = new Promise<{ id: string }>((done) => { + resolve = done + }) + const client = new GenerationClient({ fetcher: async () => pending }) + + const generating = client.generate({ prompt: 'test' }) + expect(client.getSnapshot().runId).toEqual(expect.any(String)) + expect(client.getSnapshot().runId).not.toBe('') + + resolve({ id: '1' }) + await generating + expect(client.getSnapshot().runId).toBeNull() + }) + + it('GenerationClient notifies subscribe and keeps getSnapshot identity until a change', async () => { + const client = new GenerationClient({ + fetcher: async () => ({ id: '1' }), + }) + const snapshot = client.getSnapshot() + expect(snapshot).toEqual({ + result: null, + isLoading: false, + error: undefined, + status: 'idle', + runId: null, + }) + expect(client.getSnapshot()).toBe(snapshot) + + const listener = vi.fn() + const stop = client.subscribe(listener) + await client.generate({ prompt: 'test' }) + + expect(listener).toHaveBeenCalled() + expect(client.getSnapshot().status).toBe('success') + expect(client.getSnapshot().result).toEqual({ id: '1' }) + expect(client.getSnapshot()).not.toBe(snapshot) + stop() + }) + + it('VideoGenerationClient snapshot includes job fields', async () => { + const result = { + jobId: 'job-1', + status: 'completed' as const, + url: 'https://example.com/video.mp4', + } + const client = new VideoGenerationClient({ + fetcher: async () => result, + }) + const snapshot = client.getSnapshot() + expect(snapshot.jobId).toBeNull() + expect(snapshot.videoStatus).toBeNull() + expect(client.getSnapshot()).toBe(snapshot) + + const listener = vi.fn() + const stop = client.subscribe(listener) + await client.generate({ prompt: 'test video' }) + + expect(listener).toHaveBeenCalled() + expect(client.getSnapshot().status).toBe('success') + expect(client.getSnapshot().result).toEqual(result) + expect(client.getSnapshot()).not.toBe(snapshot) + const completed = client.getSnapshot() + expect(Object.isFrozen(completed.videoStatus)).toBe(true) + Reflect.set(completed.videoStatus!, 'status', 'failed') + expect(client.getVideoStatus()?.status).toBe('completed') + stop() + }) + + it('AudioRecorder getSnapshot matches state', () => { + const recorder = new AudioRecorder() + expect(recorder.getSnapshot()).toBe('idle') + expect(recorder.getSnapshot()).toBe(recorder.state) + }) + + it('RealtimeClient subscribe fires when connect fails', async () => { + const adapter: RealtimeAdapter = { + provider: 'test', + connect: async () => { + throw new Error('unused') + }, + } + const client = new RealtimeClient({ + getToken: async () => { + throw new Error('no token') + }, + adapter, + }) + const snapshot = client.getSnapshot() + expect(snapshot.status).toBe('idle') + expect(client.getSnapshot()).toBe(snapshot) + + const listener = vi.fn() + const stop = client.subscribe(listener) + await expect(client.connect()).rejects.toThrow('no token') + + expect(listener).toHaveBeenCalled() + expect(client.getSnapshot().status).toBe('error') + expect(client.getSnapshot()).not.toBe(snapshot) + stop() + client.destroy() + }) + + it('RealtimeClient does not read connection control from its public snapshot', async () => { + const getToken = vi.fn(async () => { + throw new Error('token attempted') + }) + const client = new RealtimeClient({ + getToken, + adapter: { provider: 'test', connect: vi.fn() }, + }) + + Reflect.set(client.getSnapshot(), 'status', 'connected') + await expect(client.connect()).rejects.toThrow('token attempted') + expect(getToken).toHaveBeenCalledOnce() + }) + + it('RealtimeClient isolates connection control from state change callbacks', async () => { + const getToken = vi.fn(async () => ({ + provider: 'test', + token: 'token', + expiresAt: Date.now() + 120_000, + config: {}, + })) + const connect = vi.fn(async () => { + throw new Error('connect attempted') + }) + const client = new RealtimeClient({ + getToken, + adapter: { provider: 'test', connect }, + }) + client.onStateChange((state) => { + Reflect.set(state, 'status', 'connected') + }) + + await expect(client.connect()).rejects.toThrow('connect attempted') + await expect(client.connect()).rejects.toThrow('connect attempted') + + expect(getToken).toHaveBeenCalledTimes(2) + expect(connect).toHaveBeenCalledTimes(2) + }) +}) diff --git a/packages/ai-client/tests/generation-client.test.ts b/packages/ai-client/tests/generation-client.test.ts index 8b941689dd..cf93a55495 100644 --- a/packages/ai-client/tests/generation-client.test.ts +++ b/packages/ai-client/tests/generation-client.test.ts @@ -115,6 +115,7 @@ describe('GenerationClient', () => { expect(onResultChange).toHaveBeenCalledWith(mockResult) expect(client.getResult()).toEqual(mockResult) expect(client.getStatus()).toBe('success') + expect(client.getSnapshot().runId).toBeNull() expect(client.getIsLoading()).toBe(false) }) @@ -274,6 +275,7 @@ describe('GenerationClient', () => { expect(onError).toHaveBeenCalledWith(expect.any(Error)) expect(client.getStatus()).toBe('error') expect(client.getError()?.message).toBe('Generation failed') + expect(client.getSnapshot().runId).toBeNull() }) it('should report progress from CUSTOM progress events', async () => { @@ -408,14 +410,366 @@ describe('GenerationClient', () => { const generatePromise = client.generate({ prompt: 'test' }) expect(client.getIsLoading()).toBe(true) + expect(client.getSnapshot().runId).toEqual(expect.any(String)) client.stop() expect(client.getIsLoading()).toBe(false) expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() resolvePromise!({ id: '1' }) await generatePromise }) + + it('should not overwrite a run started by the stopped loading callback', async () => { + let releaseFirst!: (value: { id: string }) => void + let releaseSecond!: (value: { id: string }) => void + const firstResult = new Promise<{ id: string }>((resolve) => { + releaseFirst = resolve + }) + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + let replaced = false + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher: async (input) => + input.prompt === 'first' ? firstResult : secondResult, + onLoadingChange: (isLoading) => { + if (isLoading || replaced) return + replaced = true + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + + const firstGenerate = client.generate({ prompt: 'first' }) + client.stop() + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getIsLoading()).toBe(true) + expect(client.getStatus()).toBe('generating') + releaseFirst({ id: 'first' }) + releaseSecond({ id: 'second' }) + await Promise.all([firstGenerate, secondGenerate]) + }) + + it('should stop before transport work when loading callback stops', async () => { + const fetcher = vi.fn(async () => ({ id: 'unexpected' })) + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher, + onLoadingChange: (isLoading) => { + if (isLoading) client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not complete resume state after success callback stops', async () => { + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher: async () => ({ id: 'result' }), + onStatusChange: (status) => { + if (status === 'success') client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(client.getStatus()).toBe('success') + expect(client.getSnapshot().runId).toBeNull() + expect(client.getResumeSnapshot()).toBeUndefined() + }) + + it('should not let initialization continue after loading callback starts a replacement', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + const prompts: Array = [] + let client!: GenerationClient<{ prompt: string }, { id: string }> + let replaced = false + let secondGenerate: Promise | undefined + client = new GenerationClient({ + fetcher: async (input) => { + prompts.push(input.prompt) + return input.prompt === 'second' ? secondResult : { id: 'first' } + }, + onLoadingChange: (isLoading) => { + if (!isLoading || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(prompts).toEqual(['second']) + expect(client.getStatus()).toBe('generating') + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should not let an old result callback complete a replacement run', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + let client!: GenerationClient<{ prompt: string }, { id: string }> + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + client = new GenerationClient({ + fetcher: async (input) => + input.prompt === 'first' ? { id: 'first' } : secondResult, + onResultChange: (result) => { + if (result?.id !== 'first') return + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + expect(client.getResumeSnapshot()).toBeUndefined() + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should not let an old error callback overwrite a replacement run', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + let client!: GenerationClient<{ prompt: string }, { id: string }> + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + client = new GenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') throw new Error('first failed') + return secondResult + }, + onErrorChange: (error) => { + if (error?.message !== 'first failed') return + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should remain stopped when the error callback stops', async () => { + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher: async (): Promise<{ id: string }> => { + throw new Error('failed') + }, + onErrorChange: (error) => { + if (error) client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not let an old error status callback overwrite a replacement run', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + let client!: GenerationClient<{ prompt: string }, { id: string }> + let secondGenerate: Promise | undefined + let replaced = false + client = new GenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') throw new Error('first failed') + return secondResult + }, + onStatusChange: (status) => { + if (status !== 'error' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(client.getStatus()).toBe('generating') + expect(client.getError()).toBeUndefined() + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should stop before transport when the run id subscriber stops', async () => { + const fetcher = vi.fn(async () => ({ id: 'unexpected' })) + const onLoadingChange = vi.fn() + const client = new GenerationClient({ fetcher, onLoadingChange }) + client.subscribe(() => { + if (client.getSnapshot().runId) client.stop() + }) + + await client.generate({ prompt: 'test' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(onLoadingChange).not.toHaveBeenCalledWith(true) + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not continue an old run after the run id subscriber starts a replacement', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + const prompts: Array = [] + let secondGenerate: Promise | undefined + let replaced = false + const client = new GenerationClient({ + fetcher: async (input) => { + prompts.push(input.prompt) + return input.prompt === 'second' ? secondResult : { id: 'first' } + }, + }) + client.subscribe(() => { + if (!client.getSnapshot().runId || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(prompts).toEqual(['second']) + expect(client.getStatus()).toBe('generating') + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should not call the loading callback after its snapshot subscriber stops', async () => { + const fetcher = vi.fn(async () => ({ id: 'unexpected' })) + const onLoadingChange = vi.fn() + const client = new GenerationClient({ fetcher, onLoadingChange }) + client.subscribe(() => { + if (client.getSnapshot().isLoading) client.stop() + }) + + await client.generate({ prompt: 'test' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(onLoadingChange).not.toHaveBeenCalledWith(true) + }) + + it('should not call the old status callback after its subscriber starts a replacement', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + const generatingStatuses: Array = [] + let secondGenerate: Promise | undefined + let replaced = false + const client = new GenerationClient({ + fetcher: async (input) => + input.prompt === 'second' ? secondResult : { id: 'first' }, + onStatusChange: (status) => { + if (status === 'generating') generatingStatuses.push(status) + }, + }) + client.subscribe(() => { + if (client.getSnapshot().status !== 'generating' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(generatingStatuses).toEqual(['generating']) + expect(client.getStatus()).toBe('generating') + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should not call the old result callback after its subscriber starts a replacement', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + const onResultChange = vi.fn() + let secondGenerate: Promise | undefined + let replaced = false + const client = new GenerationClient({ + fetcher: async (input) => + input.prompt === 'second' ? secondResult : { id: 'first' }, + onResultChange, + }) + client.subscribe(() => { + if (client.getResult()?.id !== 'first' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(onResultChange).not.toHaveBeenCalledWith({ id: 'first' }) + expect(client.getStatus()).toBe('generating') + releaseSecond({ id: 'second' }) + await secondGenerate + }) + + it('should not call the old error callback after its subscriber starts a replacement', async () => { + let releaseSecond!: (value: { id: string }) => void + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + const onErrorChange = vi.fn() + let secondGenerate: Promise | undefined + let replaced = false + const client = new GenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') throw new Error('first failed') + return secondResult + }, + onErrorChange, + }) + client.subscribe(() => { + if (client.getError()?.message !== 'first failed' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(onErrorChange).not.toHaveBeenCalledWith(expect.any(Error)) + expect(client.getStatus()).toBe('generating') + releaseSecond({ id: 'second' }) + await secondGenerate + }) }) describe('reset()', () => { @@ -432,6 +786,82 @@ describe('GenerationClient', () => { expect(client.getResult()).toBeNull() expect(client.getError()).toBeUndefined() expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not overwrite a run started by the reset loading callback', async () => { + let releaseFirst!: (value: { id: string }) => void + let releaseSecond!: (value: { id: string }) => void + const firstResult = new Promise<{ id: string }>((resolve) => { + releaseFirst = resolve + }) + const secondResult = new Promise<{ id: string }>((resolve) => { + releaseSecond = resolve + }) + let replaceOnStop = false + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'seed') return { id: 'seed' } + return input.prompt === 'first' ? firstResult : secondResult + }, + onLoadingChange: (isLoading) => { + if (isLoading || !replaceOnStop) return + replaceOnStop = false + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + await client.generate({ prompt: 'seed' }) + const firstGenerate = client.generate({ prompt: 'first' }) + replaceOnStop = true + + client.reset() + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + expect(client.getIsLoading()).toBe(true) + expect(client.getResult()).toEqual({ id: 'seed' }) + releaseFirst({ id: 'first' }) + releaseSecond({ id: 'second' }) + await Promise.all([firstGenerate, secondGenerate]) + }) + }) + + describe('dispose()', () => { + it('should not overwrite a run started by the dispose loading callback', async () => { + const first = createDeferred<{ id: string }>() + const second = createDeferred<{ id: string }>() + let replaceOnDispose = false + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + fetcher: (input) => + input.prompt === 'first' ? first.promise : second.promise, + onLoadingChange: (isLoading) => { + if (isLoading || !replaceOnDispose) return + replaceOnDispose = false + client.mountDevtools() + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + const firstGenerate = client.generate({ prompt: 'first' }) + replaceOnDispose = true + + client.dispose() + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + expect(client.getIsLoading()).toBe(true) + first.resolve({ id: 'first' }) + second.resolve({ id: 'second' }) + await Promise.all([firstGenerate, secondGenerate]) }) }) @@ -560,6 +990,75 @@ describe('GenerationClient', () => { expect(client.getStatus()).toBe('idle') }) + it('should not publish a run id after onChunk stops the run', async () => { + const connection: ConnectConnectionAdapter = { + async *connect() { + yield { + type: EventType.RUN_STARTED as const, + runId: 'run-1', + threadId: 'thread-1', + timestamp: Date.now(), + } + }, + } + let client!: GenerationClient<{ prompt: string }, { id: string }> + client = new GenerationClient({ + connection, + onChunk: (chunk) => { + if (chunk.type === EventType.RUN_STARTED) client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(client.getSnapshot().runId).toBeNull() + expect(client.getStatus()).toBe('idle') + }) + + it('should not let an old onChunk continuation overwrite a newer run', async () => { + let connectionCount = 0 + let releaseSecond!: () => void + const secondBlocked = new Promise((resolve) => { + releaseSecond = resolve + }) + const connection: ConnectConnectionAdapter = { + async *connect() { + connectionCount++ + if (connectionCount === 1) { + yield { + type: EventType.RUN_STARTED as const, + runId: 'run-1', + threadId: 'thread-1', + timestamp: Date.now(), + } + return + } + await secondBlocked + }, + } + let client!: GenerationClient<{ prompt: string }, { id: string }> + let secondGenerate: Promise | undefined + let secondLocalRunId: string | null = null + client = new GenerationClient({ + connection, + onChunk: (chunk) => { + if (chunk.type !== EventType.RUN_STARTED) return + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + secondLocalRunId = client.getSnapshot().runId + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(secondLocalRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondLocalRunId) + expect(client.getIsLoading()).toBe(true) + client.stop() + releaseSecond() + await secondGenerate + }) + it('should not let a stopped run clear the controller for a newer generation', async () => { const firstAborted = createDeferred() const firstCanFinish = createDeferred() @@ -621,12 +1120,14 @@ describe('GenerationClient', () => { await waitForCondition(() => { expect(signals).toHaveLength(2) expect(client.getIsLoading()).toBe(true) + expect(client.getSnapshot().runId).toBe('run-2') }) firstCanFinish.resolve(undefined) await firstGenerate expect(client.getIsLoading()).toBe(true) + expect(client.getSnapshot().runId).toBe('run-2') client.stop() expect(signals[1]?.aborted).toBe(true) @@ -758,12 +1259,14 @@ describe('GenerationClient', () => { await waitForCondition(() => { expect(signals).toHaveLength(2) expect(client.getIsLoading()).toBe(true) + expect(client.getSnapshot().runId).toBe('run-2') }) firstCanFinish.resolve(undefined) await firstGenerate expect(client.getIsLoading()).toBe(true) + expect(client.getSnapshot().runId).toBe('run-2') client.stop() expect(signals[1]?.aborted).toBe(true) diff --git a/packages/ai-client/tests/generation-devtools.test.ts b/packages/ai-client/tests/generation-devtools.test.ts index 133519a42d..ce967ca388 100644 --- a/packages/ai-client/tests/generation-devtools.test.ts +++ b/packages/ai-client/tests/generation-devtools.test.ts @@ -289,6 +289,184 @@ describe('generation client devtools bridge', () => { client.dispose() }) + it('finishes a generation devtools run when a runId subscriber stops it', async () => { + const fetcher = vi.fn(async () => ({ text: 'done' })) + const client = new GenerationClient({ + threadId: 'generation-run-id-stop', + fetcher, + devtools: { hookName: 'useGenerationRunIdStop' }, + }) + const unsubscribe = client.subscribe(() => { + if (client.getSnapshot().runId) client.stop() + }) + + await client.generate({ prompt: 'stop early' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(latestSnapshotState().activeRunId).toBeNull() + unsubscribe() + client.dispose() + }) + + it('finishes a generation devtools run when loading callback stops it', async () => { + const fetcher = vi.fn(async () => ({ text: 'done' })) + let client: GenerationClient<{ prompt: string }, { text: string }> + client = new GenerationClient({ + threadId: 'generation-loading-stop', + fetcher, + devtools: { hookName: 'useGenerationLoadingStop' }, + onLoadingChange: (isLoading) => { + if (isLoading) client.stop() + }, + }) + + await client.generate({ prompt: 'stop while loading' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(latestSnapshotState().activeRunId).toBeNull() + client.dispose() + }) + + it('finishes a completed generation devtools run when success callback stops it', async () => { + let client: GenerationClient<{ prompt: string }, { text: string }> + client = new GenerationClient({ + threadId: 'generation-success-stop', + fetcher: async () => ({ text: 'done' }), + devtools: { hookName: 'useGenerationSuccessStop' }, + onStatusChange: (status) => { + if (status === 'success') client.stop() + }, + }) + + await client.generate({ prompt: 'finish then stop' }) + + expect(latestSnapshotState().activeRunId).toBeNull() + expect(latestGenerationRuns().at(-1)).toEqual( + expect.objectContaining({ status: 'success', result: { text: 'done' } }), + ) + client.dispose() + }) + + it('finishes an errored generation devtools run when error callback stops it', async () => { + let client: GenerationClient<{ prompt: string }, { text: string }> + client = new GenerationClient<{ prompt: string }, { text: string }>({ + threadId: 'generation-error-stop', + devtools: { hookName: 'useGenerationErrorStop' }, + fetcher: async (): Promise<{ text: string }> => { + throw new Error('generation failed') + }, + onStatusChange: (status) => { + if (status === 'error') client.stop() + }, + }) + + await client.generate({ prompt: 'fail then stop' }) + + expect(latestSnapshotState().activeRunId).toBeNull() + expect(latestGenerationRuns().at(-1)).toEqual( + expect.objectContaining({ + status: 'error', + error: 'generation failed', + }), + ) + client.dispose() + }) + + it('finishes a video devtools run when a runId subscriber stops it', async () => { + const fetcher = vi.fn(async () => ({ + jobId: 'video-1', + status: 'completed' as const, + url: 'https://example.com/video-1.mp4', + })) + const client = new VideoGenerationClient({ + threadId: 'video-run-id-stop', + fetcher, + devtools: { hookName: 'useVideoRunIdStop' }, + }) + const unsubscribe = client.subscribe(() => { + if (client.getSnapshot().runId) client.stop() + }) + + await client.generate({ prompt: 'stop early' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(latestSnapshotState().activeRunId).toBeNull() + unsubscribe() + client.dispose() + }) + + it('finishes a video devtools run when loading callback stops it', async () => { + const fetcher = vi.fn(async () => ({ + jobId: 'video-1', + status: 'completed' as const, + url: 'https://example.com/video-1.mp4', + })) + let client: VideoGenerationClient + client = new VideoGenerationClient({ + threadId: 'video-loading-stop', + fetcher, + devtools: { hookName: 'useVideoLoadingStop' }, + onLoadingChange: (isLoading) => { + if (isLoading) client.stop() + }, + }) + + await client.generate({ prompt: 'stop while loading' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(latestSnapshotState().activeRunId).toBeNull() + client.dispose() + }) + + it('finishes a completed video devtools run when success callback stops it', async () => { + let client: VideoGenerationClient + client = new VideoGenerationClient({ + threadId: 'video-success-stop', + fetcher: async () => ({ + jobId: 'video-1', + status: 'completed' as const, + url: 'https://example.com/video-1.mp4', + }), + devtools: { hookName: 'useVideoSuccessStop' }, + onStatusChange: (status) => { + if (status === 'success') client.stop() + }, + }) + + await client.generate({ prompt: 'finish then stop' }) + + expect(latestSnapshotState().activeRunId).toBeNull() + expect(latestGenerationRuns().at(-1)).toEqual( + expect.objectContaining({ + status: 'success', + result: expect.objectContaining({ jobId: 'video-1' }), + }), + ) + client.dispose() + }) + + it('finishes an errored video devtools run when error callback stops it', async () => { + let client: VideoGenerationClient + client = new VideoGenerationClient({ + threadId: 'video-error-stop', + devtools: { hookName: 'useVideoErrorStop' }, + fetcher: async () => { + throw new Error('video failed') + }, + onStatusChange: (status) => { + if (status === 'error') client.stop() + }, + }) + + await client.generate({ prompt: 'fail then stop' }) + + expect(latestSnapshotState().activeRunId).toBeNull() + expect(latestGenerationRuns().at(-1)).toEqual( + expect.objectContaining({ status: 'error', error: 'video failed' }), + ) + client.dispose() + }) + it('includes input, progress, and renderable previews in generation snapshots', async () => { const client = new GenerationClient({ threadId: 'image-hook', diff --git a/packages/ai-client/tests/snapshot-atom.test.ts b/packages/ai-client/tests/snapshot-atom.test.ts new file mode 100644 index 0000000000..dbd0742190 --- /dev/null +++ b/packages/ai-client/tests/snapshot-atom.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it, vi } from 'vitest' +import { createAtom, patchAtom, subscribeAtom } from '../src/snapshot-atom' + +describe('snapshot-atom', () => { + it('freezes object snapshots and keeps identity without a change', () => { + const atom = createAtom({ count: 1 }) + const snapshot = atom.get() + + expect(Object.isFrozen(snapshot)).toBe(true) + expect(atom.get()).toBe(snapshot) + }) + + it('patchAtom skips notify when every field is unchanged', () => { + const atom = createAtom({ count: 1, label: 'a' }) + const listener = vi.fn() + const stop = subscribeAtom(atom, listener) + + patchAtom(atom, { count: 1, label: 'a' }) + expect(listener).not.toHaveBeenCalled() + expect(atom.get()).toEqual({ count: 1, label: 'a' }) + + patchAtom(atom, { count: 2 }) + expect(listener).toHaveBeenCalledTimes(1) + expect(atom.get()).toEqual({ count: 2, label: 'a' }) + stop() + }) + + it('subscribeAtom adapts { unsubscribe } to a plain cleanup', () => { + const atom = createAtom(0) + const listener = vi.fn() + const stop = subscribeAtom(atom, listener) + + atom.set(1) + expect(listener).toHaveBeenCalledTimes(1) + stop() + atom.set(2) + expect(listener).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/ai-client/tests/video-generation-client.test.ts b/packages/ai-client/tests/video-generation-client.test.ts index be788e4d57..7e8ad13be0 100644 --- a/packages/ai-client/tests/video-generation-client.test.ts +++ b/packages/ai-client/tests/video-generation-client.test.ts @@ -224,6 +224,7 @@ describe('VideoGenerationClient', () => { ) expect(client.getStatus()).toBe('success') expect(client.getJobId()).toBe('job-123') + expect(client.getSnapshot().runId).toBeNull() }) it('should track video status updates', async () => { @@ -314,6 +315,7 @@ describe('VideoGenerationClient', () => { expect(onError).toHaveBeenCalledWith(expect.any(Error)) expect(client.getStatus()).toBe('error') expect(client.getError()?.message).toBe('Video generation failed') + expect(client.getSnapshot().runId).toBeNull() }) it('should report progress from video:status events', async () => { @@ -483,10 +485,12 @@ describe('VideoGenerationClient', () => { const generatePromise = client.generate({ prompt: 'test' }) expect(client.getIsLoading()).toBe(true) + expect(client.getSnapshot().runId).toEqual(expect.any(String)) client.stop() expect(client.getIsLoading()).toBe(false) expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() resolvePromise!({ jobId: 'job-1', @@ -495,6 +499,454 @@ describe('VideoGenerationClient', () => { }) await generatePromise }) + + it('should not overwrite a run started by the stopped loading callback', async () => { + const firstVideo = { + jobId: 'job-1', + status: 'completed' as const, + url: 'https://example.com/first.mp4', + } + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseFirst!: (value: typeof firstVideo) => void + let releaseSecond!: (value: typeof secondVideo) => void + const firstResult = new Promise((resolve) => { + releaseFirst = resolve + }) + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + let replaced = false + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher: async (input) => + input.prompt === 'first' ? firstResult : secondResult, + onLoadingChange: (isLoading) => { + if (isLoading || replaced) return + replaced = true + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + + const firstGenerate = client.generate({ prompt: 'first' }) + client.stop() + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getIsLoading()).toBe(true) + expect(client.getStatus()).toBe('generating') + releaseFirst(firstVideo) + releaseSecond(secondVideo) + await Promise.all([firstGenerate, secondGenerate]) + }) + + it('should clear the run id when the success status callback stops', async () => { + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher: async () => ({ + jobId: 'job-1', + status: 'completed', + url: 'https://example.com/video.mp4', + }), + onStatusChange: (status) => { + if (status === 'success') client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(client.getStatus()).toBe('success') + expect(client.getSnapshot().runId).toBeNull() + expect(client.getResumeSnapshot()).toBeUndefined() + }) + + it('should remain stopped when the error callback stops', async () => { + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher: async () => { + throw new Error('failed') + }, + onErrorChange: (error) => { + if (error) client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should stop before transport work when loading callback stops', async () => { + const fetcher = vi.fn(async () => ({ + jobId: 'unexpected', + status: 'completed' as const, + url: 'https://example.com/unexpected.mp4', + })) + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher, + onLoadingChange: (isLoading) => { + if (isLoading) client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not let initialization continue after loading callback starts a replacement', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + const prompts: Array = [] + let client!: VideoGenerationClient + let replaced = false + let secondGenerate: Promise | undefined + client = new VideoGenerationClient({ + fetcher: async (input) => { + if (typeof input.prompt === 'string') prompts.push(input.prompt) + return input.prompt === 'second' + ? secondResult + : { + jobId: 'job-1', + status: 'completed', + url: 'https://example.com/first.mp4', + } + }, + onLoadingChange: (isLoading) => { + if (!isLoading || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(prompts).toEqual(['second']) + expect(client.getStatus()).toBe('generating') + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should not let an old result callback complete a replacement run', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + let client!: VideoGenerationClient + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + client = new VideoGenerationClient({ + fetcher: async (input) => + input.prompt === 'first' + ? { + jobId: 'job-1', + status: 'completed', + url: 'https://example.com/first.mp4', + } + : secondResult, + onResultChange: (result) => { + if (result?.jobId !== 'job-1') return + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + expect(client.getResumeSnapshot()).toBeUndefined() + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should not let an old error callback overwrite a replacement run', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + let client!: VideoGenerationClient + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + client = new VideoGenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') throw new Error('first failed') + return secondResult + }, + onErrorChange: (error) => { + if (error?.message !== 'first failed') return + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should not let an old error status callback overwrite a replacement run', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + let client!: VideoGenerationClient + let secondGenerate: Promise | undefined + let replaced = false + client = new VideoGenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') throw new Error('first failed') + return secondResult + }, + onStatusChange: (status) => { + if (status !== 'error' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(client.getStatus()).toBe('generating') + expect(client.getError()).toBeUndefined() + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should stop before transport when the run id subscriber stops', async () => { + const fetcher = vi.fn(async () => ({ + jobId: 'unexpected', + status: 'completed' as const, + url: 'https://example.com/unexpected.mp4', + })) + const onLoadingChange = vi.fn() + const client = new VideoGenerationClient({ fetcher, onLoadingChange }) + client.subscribe(() => { + if (client.getSnapshot().runId) client.stop() + }) + + await client.generate({ prompt: 'test' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(onLoadingChange).not.toHaveBeenCalledWith(true) + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not continue an old run after the run id subscriber starts a replacement', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + const prompts: Array = [] + let secondGenerate: Promise | undefined + let replaced = false + const client = new VideoGenerationClient({ + fetcher: async (input) => { + if (typeof input.prompt === 'string') prompts.push(input.prompt) + return input.prompt === 'second' + ? secondResult + : { + jobId: 'job-1', + status: 'completed', + url: 'https://example.com/first.mp4', + } + }, + }) + client.subscribe(() => { + if (!client.getSnapshot().runId || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(prompts).toEqual(['second']) + expect(client.getStatus()).toBe('generating') + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should not call the loading callback after its snapshot subscriber stops', async () => { + const fetcher = vi.fn(async () => ({ + jobId: 'unexpected', + status: 'completed' as const, + url: 'https://example.com/unexpected.mp4', + })) + const onLoadingChange = vi.fn() + const client = new VideoGenerationClient({ fetcher, onLoadingChange }) + client.subscribe(() => { + if (client.getSnapshot().isLoading) client.stop() + }) + + await client.generate({ prompt: 'test' }) + + expect(fetcher).not.toHaveBeenCalled() + expect(onLoadingChange).not.toHaveBeenCalledWith(true) + }) + + it('should not call the old status callback after its subscriber starts a replacement', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + const generatingStatuses: Array = [] + let secondGenerate: Promise | undefined + let replaced = false + const client = new VideoGenerationClient({ + fetcher: async (input) => + input.prompt === 'second' + ? secondResult + : { + jobId: 'job-1', + status: 'completed', + url: 'https://example.com/first.mp4', + }, + onStatusChange: (status) => { + if (status === 'generating') generatingStatuses.push(status) + }, + }) + client.subscribe(() => { + if (client.getSnapshot().status !== 'generating' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(generatingStatuses).toEqual(['generating']) + expect(client.getStatus()).toBe('generating') + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should not call the old result callback after its subscriber starts a replacement', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + const onResultChange = vi.fn() + let secondGenerate: Promise | undefined + let replaced = false + const client = new VideoGenerationClient({ + fetcher: async (input) => + input.prompt === 'second' + ? secondResult + : { + jobId: 'job-1', + status: 'completed', + url: 'https://example.com/first.mp4', + }, + onResultChange, + }) + client.subscribe(() => { + if (client.getResult()?.jobId !== 'job-1' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(onResultChange).not.toHaveBeenCalledWith( + expect.objectContaining({ jobId: 'job-1' }), + ) + expect(client.getStatus()).toBe('generating') + releaseSecond(secondVideo) + await secondGenerate + }) + + it('should not call the old error callback after its subscriber starts a replacement', async () => { + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseSecond!: (value: typeof secondVideo) => void + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + const onErrorChange = vi.fn() + let secondGenerate: Promise | undefined + let replaced = false + const client = new VideoGenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'first') throw new Error('first failed') + return secondResult + }, + onErrorChange, + }) + client.subscribe(() => { + if (client.getError()?.message !== 'first failed' || replaced) return + replaced = true + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + }) + + await client.generate({ prompt: 'first' }) + + expect(onErrorChange).not.toHaveBeenCalledWith(expect.any(Error)) + expect(client.getStatus()).toBe('generating') + releaseSecond(secondVideo) + await secondGenerate + }) }) describe('reset()', () => { @@ -561,6 +1013,112 @@ describe('VideoGenerationClient', () => { expect(client.getVideoStatus()).toBeNull() expect(client.getError()).toBeUndefined() expect(client.getStatus()).toBe('idle') + expect(client.getSnapshot().runId).toBeNull() + }) + + it('should not overwrite a run started by the reset loading callback', async () => { + const seedVideo = { + jobId: 'seed', + status: 'completed' as const, + url: 'https://example.com/seed.mp4', + } + const firstVideo = { + jobId: 'job-1', + status: 'completed' as const, + url: 'https://example.com/first.mp4', + } + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseFirst!: (value: typeof firstVideo) => void + let releaseSecond!: (value: typeof secondVideo) => void + const firstResult = new Promise((resolve) => { + releaseFirst = resolve + }) + const secondResult = new Promise((resolve) => { + releaseSecond = resolve + }) + let replaceOnStop = false + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher: async (input) => { + if (input.prompt === 'seed') return seedVideo + return input.prompt === 'first' ? firstResult : secondResult + }, + onLoadingChange: (isLoading) => { + if (isLoading || !replaceOnStop) return + replaceOnStop = false + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + await client.generate({ prompt: 'seed' }) + const firstGenerate = client.generate({ prompt: 'first' }) + replaceOnStop = true + + client.reset() + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + expect(client.getIsLoading()).toBe(true) + expect(client.getResult()).toEqual(seedVideo) + releaseFirst(firstVideo) + releaseSecond(secondVideo) + await Promise.all([firstGenerate, secondGenerate]) + }) + }) + + describe('dispose()', () => { + it('should not overwrite a run started by the dispose loading callback', async () => { + const firstVideo = { + jobId: 'job-1', + status: 'completed' as const, + url: 'https://example.com/first.mp4', + } + const secondVideo = { + jobId: 'job-2', + status: 'completed' as const, + url: 'https://example.com/second.mp4', + } + let releaseFirst!: (value: typeof firstVideo) => void + let releaseSecond!: (value: typeof secondVideo) => void + const first = new Promise((resolve) => { + releaseFirst = resolve + }) + const second = new Promise((resolve) => { + releaseSecond = resolve + }) + let replaceOnDispose = false + let secondGenerate: Promise | undefined + let secondRunId: string | null = null + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + fetcher: (input) => (input.prompt === 'first' ? first : second), + onLoadingChange: (isLoading) => { + if (isLoading || !replaceOnDispose) return + replaceOnDispose = false + client.mountDevtools() + secondGenerate = client.generate({ prompt: 'second' }) + secondRunId = client.getSnapshot().runId + }, + }) + const firstGenerate = client.generate({ prompt: 'first' }) + replaceOnDispose = true + + client.dispose() + + expect(secondRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondRunId) + expect(client.getStatus()).toBe('generating') + expect(client.getIsLoading()).toBe(true) + releaseFirst(firstVideo) + releaseSecond(secondVideo) + await Promise.all([firstGenerate, secondGenerate]) }) }) @@ -652,6 +1210,75 @@ describe('VideoGenerationClient', () => { expect(client.getResult()).toBeNull() expect(client.getStatus()).toBe('idle') }) + + it('should not publish a run id after onChunk stops the run', async () => { + const connection: ConnectConnectionAdapter = { + async *connect() { + yield { + type: EventType.RUN_STARTED as const, + runId: 'run-1', + threadId: 'thread-1', + timestamp: Date.now(), + } + }, + } + let client!: VideoGenerationClient + client = new VideoGenerationClient({ + connection, + onChunk: (chunk) => { + if (chunk.type === EventType.RUN_STARTED) client.stop() + }, + }) + + await client.generate({ prompt: 'test' }) + + expect(client.getSnapshot().runId).toBeNull() + expect(client.getStatus()).toBe('idle') + }) + + it('should not let an old onChunk continuation overwrite a newer run', async () => { + let connectionCount = 0 + let releaseSecond!: () => void + const secondBlocked = new Promise((resolve) => { + releaseSecond = resolve + }) + const connection: ConnectConnectionAdapter = { + async *connect() { + connectionCount++ + if (connectionCount === 1) { + yield { + type: EventType.RUN_STARTED as const, + runId: 'run-1', + threadId: 'thread-1', + timestamp: Date.now(), + } + return + } + await secondBlocked + }, + } + let client!: VideoGenerationClient + let secondGenerate: Promise | undefined + let secondLocalRunId: string | null = null + client = new VideoGenerationClient({ + connection, + onChunk: (chunk) => { + if (chunk.type !== EventType.RUN_STARTED) return + client.stop() + secondGenerate = client.generate({ prompt: 'second' }) + secondLocalRunId = client.getSnapshot().runId + }, + }) + + await client.generate({ prompt: 'first' }) + + expect(secondLocalRunId).not.toBeNull() + expect(client.getSnapshot().runId).toBe(secondLocalRunId) + expect(client.getIsLoading()).toBe(true) + client.stop() + releaseSecond() + await secondGenerate + }) }) describe('error wrapping', () => { diff --git a/packages/ai-octane/src/use-chat.tsrx b/packages/ai-octane/src/use-chat.tsrx index f95f7f9fb8..d197d37ba9 100644 --- a/packages/ai-octane/src/use-chat.tsrx +++ b/packages/ai-octane/src/use-chat.tsrx @@ -90,12 +90,6 @@ export function useChat< useRef>(options) optionsRef.current = options - const syncResumeState = useCallback((target: ChatClient | null) => { - if (!target) return - setRunId(target.getCurrentRunId()) - setInterruptState(target.getInterruptState()) - }, []) - // Create ChatClient instance with callbacks to sync state const { client, initialization } = useMemo(() => { const messagesToUse = options.initialMessages || [] @@ -203,69 +197,11 @@ export function useChat< ...(options.streamProcessor !== undefined && { streamProcessor: options.streamProcessor, }), - onMessagesChange: (newMessages: Array>) => { - runOrQueueForActiveInstance(() => { - setMessages(newMessages) - }) - }, - onLoadingChange: (newIsLoading: boolean) => { - runOrQueueForActiveInstance(() => { - const currentInstance = getActiveInstance() - if (!currentInstance) return - setIsLoading(newIsLoading) - syncResumeState(currentInstance) - }) - }, - onErrorChange: (newError: Error | undefined) => { - runOrQueueForActiveInstance(() => { - setError(newError) - }) - }, - onStatusChange: (status: ChatClientState) => { - runOrQueueForActiveInstance(() => { - setStatus(status) - }) - }, - onSubscriptionChange: (nextIsSubscribed: boolean) => { - runOrQueueForActiveInstance(() => { - setIsSubscribed(nextIsSubscribed) - }) - }, - onConnectionStatusChange: (nextStatus: ConnectionStatus) => { - runOrQueueForActiveInstance(() => { - setConnectionStatus(nextStatus) - }) - }, - onSessionGeneratingChange: (isGenerating: boolean) => { - runOrQueueForActiveInstance(() => { - setSessionGenerating(isGenerating) - }) - }, ...(optionsRef.current.queue !== undefined && { queue: optionsRef.current.queue, }), - onQueueChange: (nextQueue: Array) => { - runOrQueueForActiveInstance(() => { - setQueue(nextQueue) - }) - }, - onRunIdChange: (nextRunId) => { - runOrQueueForActiveInstance(() => { - setRunId(nextRunId) - }) - }, - onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => { - runOrQueueForActiveInstance(() => { - setInterruptState((current) => ({ - ...current, - interrupts: nextPendingInterrupts, - pendingInterrupts: nextPendingInterrupts, - })) - }) - }, onInterruptStateChange: (nextInterruptState, context) => { runOrQueueForActiveInstance(() => { - setInterruptState(nextInterruptState) optionsRef.current.onInterruptStateChange?.( nextInterruptState, context, @@ -275,7 +211,7 @@ export function useChat< }) instanceHolder.current = instance return { client: instance, initialization: initializationState } - }, [clientId, syncResumeState]) + }, [clientId]) useEffect(() => { activeClientRef.current = client @@ -296,10 +232,21 @@ export function useChat< }, [client, initialization]) useEffect(() => { - const clientMessages = client.getMessages() - if (clientMessages !== messagesRef.current) { - setMessages(clientMessages) + const applySnapshot = () => { + const next = client.getSnapshot() + setMessages(next.messages) + setIsLoading(next.isLoading) + setError(next.error) + setStatus(next.status) + setIsSubscribed(next.isSubscribed) + setConnectionStatus(next.connectionStatus) + setSessionGenerating(next.sessionGenerating) + setQueue(next.queue) + setRunId(next.runId) + setInterruptState(next.interruptState) } + applySnapshot() + return client.subscribeSnapshot(applySnapshot) }, [client]) // OCTANE DIVERGENCE: upstream captures the initial transport. ChatClient @@ -369,7 +316,6 @@ export function useChat< useEffect(() => { client.attach() client.mountDevtools() - syncResumeState(client) return () => { client.detach() if (optionsRef.current.live) { @@ -382,20 +328,16 @@ export function useChat< activeClientRef.current = null } } - }, [client, syncResumeState]) + }, [client]) const sendMessage = useCallback( async ( content: string | MultimodalContent, sendOptions?: SendMessageOptions, ) => { - try { - await client.sendMessage(content, undefined, sendOptions) - } finally { - syncResumeState(client) - } + await client.sendMessage(content, undefined, sendOptions) }, - [client, syncResumeState], + [client], ) const cancelQueued = useCallback( @@ -407,22 +349,14 @@ export function useChat< const append = useCallback( async (message: ModelMessage | UIMessage) => { - try { - await client.append(message) - } finally { - syncResumeState(client) - } + await client.append(message) }, - [client, syncResumeState], + [client], ) const reload = useCallback(async () => { - try { - await client.reload() - } finally { - syncResumeState(client) - } - }, [client, syncResumeState]) + await client.reload() + }, [client]) const stop = useCallback(() => { client.stop() @@ -430,8 +364,7 @@ export function useChat< const clear = useCallback(() => { client.clear() - syncResumeState(client) - }, [client, syncResumeState]) + }, [client]) const setMessagesManually = useCallback( (newMessages: Array>) => { @@ -456,21 +389,15 @@ export function useChat< const addToolApprovalResponse = useCallback( async (response: { id: string; approved: boolean }) => { await client.addToolApprovalResponse(response) - syncResumeState(client) }, - [client, syncResumeState], + [client], ) const resumeInterrupts = useCallback( async (resumeItems: Array, state?: ChatResumeState) => { - const result = await client.resumeInterrupts( - resumeItems, - state ?? undefined, - ) - syncResumeState(client) - return result + return await client.resumeInterrupts(resumeItems, state ?? undefined) }, - [client, syncResumeState], + [client], ) const resolveInterrupts = useCallback( diff --git a/packages/ai-octane/src/use-generate-video.tsrx b/packages/ai-octane/src/use-generate-video.tsrx index 5772a97326..c18dddc7bf 100644 --- a/packages/ai-octane/src/use-generate-video.tsrx +++ b/packages/ai-octane/src/use-generate-video.tsrx @@ -172,12 +172,6 @@ export function useGenerateVideo( onStatusUpdate: (s: VideoStatusInfo) => { optionsRef.current.onStatusUpdate?.(s) }, - onResultChange: setResult, - onLoadingChange: setIsLoading, - onErrorChange: setError, - onStatusChange: setStatus, - onJobIdChange: setJobId, - onVideoStatusChange: setVideoStatus, } if (opts.connection) { @@ -199,6 +193,20 @@ export function useGenerateVideo( ) }, [clientId]) + useEffect(() => { + const applySnapshot = () => { + const next = client.getSnapshot() + setResult(next.result) + setIsLoading(next.isLoading) + setError(next.error) + setStatus(next.status) + setJobId(next.jobId) + setVideoStatus(next.videoStatus) + } + applySnapshot() + return client.subscribe(applySnapshot) + }, [client]) + // Sync body changes without recreating client useEffect(() => { // Conditional spread: target uses strict-optional `body?: T`. diff --git a/packages/ai-octane/src/use-generation.tsrx b/packages/ai-octane/src/use-generation.tsrx index e3ca19c084..23eb3b0489 100644 --- a/packages/ai-octane/src/use-generation.tsrx +++ b/packages/ai-octane/src/use-generation.tsrx @@ -160,10 +160,6 @@ export function useGeneration< onChunk: (c: StreamChunk) => { optionsRef.current.onChunk?.(c) }, - onResultChange: setResult, - onLoadingChange: setIsLoading, - onErrorChange: setError, - onStatusChange: setStatus, } if (opts.connection) { @@ -185,6 +181,18 @@ export function useGeneration< ) }, [clientId]) + useEffect(() => { + const applySnapshot = () => { + const next = client.getSnapshot() + setResult(next.result) + setIsLoading(next.isLoading) + setError(next.error) + setStatus(next.status) + } + applySnapshot() + return client.subscribe(applySnapshot) + }, [client]) + // Sync body changes without recreating client useEffect(() => { // Conditional spread: target uses strict-optional `body?: T`. diff --git a/packages/ai-octane/src/use-realtime-chat.tsrx b/packages/ai-octane/src/use-realtime-chat.tsrx index b486b8cc12..4ed30a20ee 100644 --- a/packages/ai-octane/src/use-realtime-chat.tsrx +++ b/packages/ai-octane/src/use-realtime-chat.tsrx @@ -124,17 +124,14 @@ export function useRealtimeChat( semanticEagerness: initial.semanticEagerness, }), onStatusChange: (newStatus) => { - setStatus(newStatus) // OCTANE DIVERGENCE: upstream declares this callback but does not // forward status changes beyond its own hook state. optionsRef.current.onStatusChange?.(newStatus) }, onModeChange: (newMode) => { - setMode(newMode) optionsRef.current.onModeChange?.(newMode) }, onMessage: (message) => { - setMessages((prev) => [...prev, message]) optionsRef.current.onMessage?.(message) }, onUsage: (usage) => { @@ -144,31 +141,36 @@ export function useRealtimeChat( optionsRef.current.onGoAway?.(timeLeft) }, onError: (err) => { - setError(err) optionsRef.current.onError?.(err) }, onConnect: () => { - setError(null) optionsRef.current.onConnect?.() }, onDisconnect: () => { optionsRef.current.onDisconnect?.() }, onInterrupted: () => { - setPendingAssistantTranscript(null) optionsRef.current.onInterrupted?.() }, }) - - // Subscribe to state changes for transcripts - clientRef.current.onStateChange((state) => { - setPendingUserTranscript(state.pendingUserTranscript) - setPendingAssistantTranscript(state.pendingAssistantTranscript) - }) } const client = clientRef.current + useEffect(() => { + const applySnapshot = () => { + const next = client.getSnapshot() + setStatus(next.status) + setMode(next.mode) + setMessages(next.messages) + setPendingUserTranscript(next.pendingUserTranscript) + setPendingAssistantTranscript(next.pendingAssistantTranscript) + setError(next.error) + } + applySnapshot() + return client.subscribe(applySnapshot) + }, [client]) + // Audio level animation loop useEffect(() => { function updateLevels() { @@ -200,10 +202,6 @@ export function useRealtimeChat( // Connection methods const connect = useCallback(async () => { - setError(null) - setMessages([]) - setPendingUserTranscript(null) - setPendingAssistantTranscript(null) await client.connect() }, [client]) diff --git a/packages/ai-preact/src/use-chat.ts b/packages/ai-preact/src/use-chat.ts index f8376da1ee..2eaaeb7c62 100644 --- a/packages/ai-preact/src/use-chat.ts +++ b/packages/ai-preact/src/use-chat.ts @@ -1,21 +1,11 @@ import { ChatClient } from '@tanstack/ai-client' import { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools' -import { - useCallback, - useEffect, - useId, - useMemo, - useRef, - useState, -} from 'preact/hooks' +import { useSyncExternalStore } from 'preact/compat' +import { useCallback, useEffect, useId, useMemo, useRef } from 'preact/hooks' import type { - ChatClientState, ResolvableChatInterrupt, - ChatInterruptState, ChatResumeState, - ConnectionStatus, InferredClientContext, - QueuedMessage, SendMessageOptions, } from '@tanstack/ai-client' import type { @@ -32,9 +22,6 @@ import type { UseChatReturn, } from './types' -const EMPTY_INTERRUPTS = Object.freeze([]) -const EMPTY_INTERRUPT_ERRORS = Object.freeze([]) - export function useChat< const TTools extends ReadonlyArray = any, TContext = InferredClientContext, @@ -50,32 +37,6 @@ export function useChat< const hookId = useId() const clientId = options.threadId ?? hookId - const [messages, setMessages] = useState>>( - options.initialMessages || [], - ) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(undefined) - const [status, setStatus] = useState('ready') - const [isSubscribed, setIsSubscribed] = useState(false) - const [connectionStatus, setConnectionStatus] = - useState('disconnected') - const [sessionGenerating, setSessionGenerating] = useState(false) - const [queue, setQueue] = useState>([]) - const [runId, setRunId] = useState(null) - const [interruptState, setInterruptState] = useState< - ChatInterruptState - >(() => ({ - interrupts: EMPTY_INTERRUPTS, - pendingInterrupts: EMPTY_INTERRUPTS, - interruptErrors: EMPTY_INTERRUPT_ERRORS, - resuming: false, - })) - - // Track current messages in a ref to preserve them when client is recreated - const messagesRef = useRef>>( - options.initialMessages || [], - ) - const isFirstMountRef = useRef(true) const activeClientRef = useRef(null) const cleanupInvalidationRef = useRef | null>( null, @@ -89,19 +50,8 @@ export function useChat< optionsRef.current = options - const syncResumeState = useCallback((target: ChatClient | null) => { - if (!target) return - setRunId(target.getCurrentRunId()) - setInterruptState(target.getInterruptState()) - }, []) - - useEffect(() => { - messagesRef.current = messages - }, [messages]) - const { client, initialization } = useMemo(() => { const messagesToUse = options.initialMessages || [] - isFirstMountRef.current = false // Build options with conditional spreads for fields whose source // type is `T | undefined` but the ChatClient target uses a strict @@ -209,69 +159,11 @@ export function useChat< ...(options.streamProcessor !== undefined && { streamProcessor: options.streamProcessor, }), - onMessagesChange: (newMessages: Array>) => { - runOrQueueForActiveInstance(() => { - setMessages(newMessages) - }) - }, - onLoadingChange: (newIsLoading: boolean) => { - runOrQueueForActiveInstance(() => { - const currentInstance = getActiveInstance() - if (!currentInstance) return - setIsLoading(newIsLoading) - syncResumeState(currentInstance) - }) - }, - onStatusChange: (newStatus: ChatClientState) => { - runOrQueueForActiveInstance(() => { - setStatus(newStatus) - }) - }, - onErrorChange: (newError: Error | undefined) => { - runOrQueueForActiveInstance(() => { - setError(newError) - }) - }, - onSubscriptionChange: (nextIsSubscribed: boolean) => { - runOrQueueForActiveInstance(() => { - setIsSubscribed(nextIsSubscribed) - }) - }, - onConnectionStatusChange: (nextStatus: ConnectionStatus) => { - runOrQueueForActiveInstance(() => { - setConnectionStatus(nextStatus) - }) - }, - onSessionGeneratingChange: (isGenerating: boolean) => { - runOrQueueForActiveInstance(() => { - setSessionGenerating(isGenerating) - }) - }, ...(optionsRef.current.queue !== undefined && { queue: optionsRef.current.queue, }), - onQueueChange: (nextQueue: Array) => { - runOrQueueForActiveInstance(() => { - setQueue(nextQueue) - }) - }, - onRunIdChange: (nextRunId) => { - runOrQueueForActiveInstance(() => { - setRunId(nextRunId) - }) - }, - onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => { - runOrQueueForActiveInstance(() => { - setInterruptState((current) => ({ - ...current, - interrupts: nextPendingInterrupts, - pendingInterrupts: nextPendingInterrupts, - })) - }) - }, onInterruptStateChange: (nextInterruptState, context) => { runOrQueueForActiveInstance(() => { - setInterruptState(nextInterruptState) optionsRef.current.onInterruptStateChange?.( nextInterruptState, context, @@ -281,7 +173,7 @@ export function useChat< }) instanceHolder.current = instance return { client: instance, initialization: initializationState } - }, [clientId, syncResumeState]) + }, [clientId]) useEffect(() => { activeClientRef.current = client @@ -301,12 +193,10 @@ export function useChat< } }, [client, initialization]) - useEffect(() => { - const clientMessages = client.getMessages() - if (clientMessages !== messagesRef.current) { - setMessages(clientMessages) - } - }, [client]) + const snapshot = useSyncExternalStore( + client.subscribeSnapshot, + client.getSnapshot, + ) // Sync body / forwardedProps changes to the client. // Both populate the same wire payload; `forwardedProps` is preferred @@ -368,10 +258,6 @@ export function useChat< cleanupInvalidationRef.current = null } client.mountDevtools() - // Delivery-durability resume is transparent: the resumable SSE connection - // adapter reattaches via the browser's native Last-Event-ID on reconnect. - // We only seed interrupt (state) resume from the client here. - syncResumeState(client) return () => { cleanupInvalidationRef.current = setTimeout(() => { @@ -400,7 +286,7 @@ export function useChat< } cleanupDisposalRef.current = disposal } - }, [client, syncResumeState]) + }, [client]) // All callback options are read through optionsRef at call time, so fresh // closures from each render are picked up without recreating the client. @@ -409,40 +295,28 @@ export function useChat< content: string | MultimodalContent, sendOptions?: SendMessageOptions, ) => { - try { - await client.sendMessage(content, undefined, sendOptions) - } finally { - syncResumeState(client) - } + await client.sendMessage(content, undefined, sendOptions) }, - [client, syncResumeState], + [client], ) const cancelQueued = useCallback( (id: string) => { client.cancelQueued(id) }, - [client, syncResumeState], + [client], ) const append = useCallback( async (message: ModelMessage | UIMessage) => { - try { - await client.append(message) - } finally { - syncResumeState(client) - } + await client.append(message) }, - [client, syncResumeState], + [client], ) const reload = useCallback(async () => { - try { - await client.reload() - } finally { - syncResumeState(client) - } - }, [client, syncResumeState]) + await client.reload() + }, [client]) const stop = useCallback(() => { client.stop() @@ -450,8 +324,7 @@ export function useChat< const clear = useCallback(() => { client.clear() - syncResumeState(client) - }, [client, syncResumeState]) + }, [client]) const setMessagesManually = useCallback( (newMessages: Array>) => { @@ -476,18 +349,15 @@ export function useChat< const addToolApprovalResponse = useCallback( async (response: { id: string; approved: boolean }) => { await client.addToolApprovalResponse(response) - syncResumeState(client) }, - [client, syncResumeState], + [client], ) const resumeInterrupts = useCallback( async (resumeItems: Array, state?: ChatResumeState) => { - const result = await client.resumeInterrupts(resumeItems, state) - syncResumeState(client) - return result + return await client.resumeInterrupts(resumeItems, state) }, - [client, syncResumeState], + [client], ) const resolveInterrupts = useCallback( @@ -521,7 +391,7 @@ export function useChat< [client], ) - const renderedMessages = client.getMessages() + const renderedMessages = snapshot.messages return { messages: renderedMessages, @@ -529,23 +399,23 @@ export function useChat< append, reload, stop, - isLoading, - error, - status, - isSubscribed, - connectionStatus, - sessionGenerating, + isLoading: snapshot.isLoading, + error: snapshot.error, + status: snapshot.status, + isSubscribed: snapshot.isSubscribed, + connectionStatus: snapshot.connectionStatus, + sessionGenerating: snapshot.sessionGenerating, setMessages: setMessagesManually, clear, addToolResult, addToolApprovalResponse, - queue, + queue: snapshot.queue, cancelQueued, - runId, - interrupts: interruptState.interrupts, - pendingInterrupts: interruptState.pendingInterrupts, - interruptErrors: interruptState.interruptErrors, - resuming: interruptState.resuming, + runId: snapshot.runId, + interrupts: snapshot.interruptState.interrupts, + pendingInterrupts: snapshot.interruptState.pendingInterrupts, + interruptErrors: snapshot.interruptState.interruptErrors, + resuming: snapshot.interruptState.resuming, resolveInterrupts, cancelInterrupts, retryInterrupts, diff --git a/packages/ai-react/src/use-chat.ts b/packages/ai-react/src/use-chat.ts index e5edb97d50..e1cdf0e004 100644 --- a/packages/ai-react/src/use-chat.ts +++ b/packages/ai-react/src/use-chat.ts @@ -1,6 +1,13 @@ import { ChatClient } from '@tanstack/ai-client' import { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools' -import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' +import { + useCallback, + useEffect, + useId, + useMemo, + useRef, + useSyncExternalStore, +} from 'react' import type { AnyClientTool, InferSchemaType, @@ -11,13 +18,10 @@ import type { StreamChunk, } from '@tanstack/ai/client' import type { - ChatClientState, + ChatClientPersistence, ResolvableChatInterrupt, - ChatInterruptState, ChatResumeState, - ConnectionStatus, InferredClientContext, - QueuedMessage, SendMessageOptions, StructuredOutputPart, } from '@tanstack/ai-client' @@ -30,9 +34,6 @@ import type { UseChatReturn, } from './types' -const EMPTY_INTERRUPTS = Object.freeze([]) -const EMPTY_INTERRUPT_ERRORS = Object.freeze([]) - export function useChat< const TTools extends ReadonlyArray = any, TSchema extends SchemaInput | undefined = undefined, @@ -49,35 +50,9 @@ export function useChat< const hookId = useId() const clientId = options.threadId ?? hookId - const [messages, setMessages] = useState>>( - options.initialMessages || [], - ) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(undefined) - const [status, setStatus] = useState('ready') - const [isSubscribed, setIsSubscribed] = useState(false) - const [connectionStatus, setConnectionStatus] = - useState('disconnected') - const [sessionGenerating, setSessionGenerating] = useState(false) - const [queue, setQueue] = useState>([]) - const [runId, setRunId] = useState(null) - const [interruptState, setInterruptState] = useState< - ChatInterruptState - >(() => ({ - interrupts: EMPTY_INTERRUPTS, - pendingInterrupts: EMPTY_INTERRUPTS, - interruptErrors: EMPTY_INTERRUPT_ERRORS, - resuming: false, - })) - type Partial = DeepPartial>> type Final = InferSchemaType> - // Track current messages in a ref to preserve them when client is recreated - const messagesRef = useRef>>( - options.initialMessages || [], - ) - const isFirstMountRef = useRef(true) const subscribedRef = useRef(false) const activeClientRef = useRef(null) const cleanupInvalidationRef = useRef | null>( @@ -88,202 +63,186 @@ export function useChat< timeout: ReturnType } | null>(null) - // Update ref synchronously during render so it's always current when useMemo runs. - messagesRef.current = messages - // Track current options in a ref to avoid recreating client when options change const optionsRef = useRef>(options) optionsRef.current = options - const syncResumeState = useCallback((target: ChatClient | null) => { - if (!target) return - setRunId(target.getCurrentRunId()) - setInterruptState(target.getInterruptState()) - }, []) - // Create ChatClient instance with callbacks to sync state - const { client, initialization } = useMemo(() => { - const messagesToUse = options.initialMessages || [] - isFirstMountRef.current = false - - // Build options with conditional spreads for fields whose source - // type is `T | undefined` but the ChatClient target uses a strict - // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects - // assigning `undefined` to those, so we omit the key when absent. - const initialOptions = optionsRef.current - const transport = initialOptions.connection - ? { connection: initialOptions.connection } - : { fetcher: initialOptions.fetcher } - - const instanceHolder: { - current: ChatClient | undefined - } = { current: undefined } - const getActiveInstance = () => { - const currentInstance = instanceHolder.current - if (!currentInstance || activeClientRef.current !== currentInstance) { - return undefined + const { client, initialization, serverSnapshot, startPersistenceHydration } = + useMemo(() => { + const messagesToUse = options.initialMessages || [] + + // Build options with conditional spreads for fields whose source + // type is `T | undefined` but the ChatClient target uses a strict + // optional (`field?: T`) — `exactOptionalPropertyTypes` rejects + // assigning `undefined` to those, so we omit the key when absent. + const initialOptions = optionsRef.current + let persistence = initialOptions.persistence + let releasePersistenceHydration = () => {} + if (persistence && typeof persistence === 'object') { + const source = persistence + let hydrationStarted = false + let releaseRead: (() => void) | undefined + let hydrationPromise: + | Promise< + Awaited['getItem']>> + > + | undefined + const gatedPersistence: ChatClientPersistence = { + getItem(id) { + if (!hydrationPromise) { + hydrationPromise = new Promise((resolve, reject) => { + let readStarted = false + releaseRead = () => { + if (readStarted) return + readStarted = true + try { + resolve(source.getItem.call(source, id)) + } catch (error) { + reject(error) + } + } + }) + } + if (hydrationStarted) releaseRead?.() + return hydrationPromise + }, + setItem(id, state) { + return source.setItem.call(source, id, state) + }, + removeItem(id) { + return source.removeItem.call(source, id) + }, + } + persistence = gatedPersistence + releasePersistenceHydration = () => { + if (hydrationStarted) return + hydrationStarted = true + releaseRead?.() + } } - return currentInstance - } - // ChatClient may publish while its constructor is running or while async - // persistence resolves before commit. Preserve those exact notifications - // until this render commits; invoking them here would run state setters and - // user callbacks for a client React may abandon. - const initializationState = { - ready: false, - callbacks: [] as Array<() => void>, - } - const runOrQueueForActiveInstance = (callback: () => void) => { - if (!initializationState.ready) { - initializationState.callbacks.push(callback) - return + const transport = initialOptions.connection + ? { connection: initialOptions.connection } + : { fetcher: initialOptions.fetcher } + + const instanceHolder: { + current: ChatClient | undefined + } = { current: undefined } + const getActiveInstance = () => { + const currentInstance = instanceHolder.current + if (!currentInstance || activeClientRef.current !== currentInstance) { + return undefined + } + return currentInstance } - const currentInstance = instanceHolder.current - if (!currentInstance || activeClientRef.current !== currentInstance) - return - callback() - } - const instance = new ChatClient({ - devtoolsBridgeFactory: createChatDevtoolsBridge, - ...transport, - initialMessages: messagesToUse, - ...(typeof initialOptions.threadId === 'string' && - initialOptions.persistence - ? { - persistence: initialOptions.persistence, - threadId: initialOptions.threadId, - } - : { - ...(initialOptions.threadId !== undefined && { + // ChatClient may publish while its constructor is running or while async + // persistence resolves before commit. Preserve those exact notifications + // until this render commits; invoking them here would run state setters and + // user callbacks for a client React may abandon. + const initializationState = { + ready: false, + callbacks: [] as Array<() => void>, + } + const runOrQueueForActiveInstance = (callback: () => void) => { + if (!initializationState.ready) { + initializationState.callbacks.push(callback) + return + } + const currentInstance = instanceHolder.current + if (!currentInstance || activeClientRef.current !== currentInstance) + return + callback() + } + const instance = new ChatClient({ + devtoolsBridgeFactory: createChatDevtoolsBridge, + ...transport, + initialMessages: messagesToUse, + ...(typeof initialOptions.threadId === 'string' && persistence + ? { + persistence, threadId: initialOptions.threadId, + } + : { + ...(initialOptions.threadId !== undefined && { + threadId: initialOptions.threadId, + }), }), - }), - ...(initialOptions.body !== undefined && { body: initialOptions.body }), - ...(initialOptions.forwardedProps !== undefined && { - forwardedProps: initialOptions.forwardedProps, - }), - ...(initialOptions.byok !== undefined && { byok: initialOptions.byok }), - byokProvider: () => optionsRef.current.byokProvider?.(), - ...(initialOptions.initialResumeSnapshot !== undefined && { - initialResumeSnapshot: initialOptions.initialResumeSnapshot, - }), - ...(initialOptions.context !== undefined && { - context: initialOptions.context, - }), - devtools: { - ...initialOptions.devtools, - framework: 'react', - hookName: 'useChat', - outputKind: initialOptions.outputSchema ? 'structured' : 'chat', - }, - onResponse: (response) => { - // ChatClient awaits this return value. Queuing would drop the promise. - if (!getActiveInstance()) return - return optionsRef.current.onResponse?.(response) - }, - onChunk: (chunk: StreamChunk) => { - runOrQueueForActiveInstance(() => { - optionsRef.current.onChunk?.(chunk) - }) - }, - onFinish: (message: UIMessage) => { - runOrQueueForActiveInstance(() => { - optionsRef.current.onFinish?.(message) - }) - }, - onError: (error: Error) => { - runOrQueueForActiveInstance(() => { - optionsRef.current.onError?.(error) - }) - }, - ...(initialOptions.tools !== undefined && { - tools: initialOptions.tools, - }), - ...(initialOptions.interrupts !== undefined && { - interrupts: initialOptions.interrupts, - }), - onCustomEvent: (eventType, data, context) => { - runOrQueueForActiveInstance(() => { - optionsRef.current.onCustomEvent?.(eventType, data, context) - }) - }, - ...(options.streamProcessor !== undefined && { - streamProcessor: options.streamProcessor, - }), - onMessagesChange: (newMessages: Array>) => { - runOrQueueForActiveInstance(() => { - setMessages(newMessages) - }) - }, - onLoadingChange: (newIsLoading: boolean) => { - runOrQueueForActiveInstance(() => { - const currentInstance = getActiveInstance() - if (!currentInstance) return - setIsLoading(newIsLoading) - syncResumeState(currentInstance) - }) - }, - onErrorChange: (newError: Error | undefined) => { - runOrQueueForActiveInstance(() => { - setError(newError) - }) - }, - onStatusChange: (status: ChatClientState) => { - runOrQueueForActiveInstance(() => { - setStatus(status) - }) - }, - onSubscriptionChange: (nextIsSubscribed: boolean) => { - runOrQueueForActiveInstance(() => { - setIsSubscribed(nextIsSubscribed) - }) - }, - onConnectionStatusChange: (nextStatus: ConnectionStatus) => { - runOrQueueForActiveInstance(() => { - setConnectionStatus(nextStatus) - }) - }, - onSessionGeneratingChange: (isGenerating: boolean) => { - runOrQueueForActiveInstance(() => { - setSessionGenerating(isGenerating) - }) - }, - ...(optionsRef.current.queue !== undefined && { - queue: optionsRef.current.queue, - }), - onQueueChange: (nextQueue: Array) => { - runOrQueueForActiveInstance(() => { - setQueue(nextQueue) - }) - }, - onRunIdChange: (nextRunId) => { - runOrQueueForActiveInstance(() => { - setRunId(nextRunId) - }) - }, - onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => { - runOrQueueForActiveInstance(() => { - setInterruptState((current) => ({ - ...current, - interrupts: nextPendingInterrupts, - pendingInterrupts: nextPendingInterrupts, - })) - }) - }, - onInterruptStateChange: (nextInterruptState, context) => { - runOrQueueForActiveInstance(() => { - setInterruptState(nextInterruptState) - optionsRef.current.onInterruptStateChange?.( - nextInterruptState, - context, - ) - }) - }, - }) - instanceHolder.current = instance - return { client: instance, initialization: initializationState } - }, [clientId, syncResumeState]) + ...(initialOptions.body !== undefined && { body: initialOptions.body }), + ...(initialOptions.forwardedProps !== undefined && { + forwardedProps: initialOptions.forwardedProps, + }), + ...(initialOptions.byok !== undefined && { byok: initialOptions.byok }), + byokProvider: () => optionsRef.current.byokProvider?.(), + ...(initialOptions.initialResumeSnapshot !== undefined && { + initialResumeSnapshot: initialOptions.initialResumeSnapshot, + }), + ...(initialOptions.context !== undefined && { + context: initialOptions.context, + }), + devtools: { + ...initialOptions.devtools, + framework: 'react', + hookName: 'useChat', + outputKind: initialOptions.outputSchema ? 'structured' : 'chat', + }, + onResponse: (response) => { + // ChatClient awaits this return value. Queuing would drop the promise. + if (!getActiveInstance()) return + return optionsRef.current.onResponse?.(response) + }, + onChunk: (chunk: StreamChunk) => { + runOrQueueForActiveInstance(() => { + optionsRef.current.onChunk?.(chunk) + }) + }, + onFinish: (message: UIMessage) => { + runOrQueueForActiveInstance(() => { + optionsRef.current.onFinish?.(message) + }) + }, + onError: (error: Error) => { + runOrQueueForActiveInstance(() => { + optionsRef.current.onError?.(error) + }) + }, + ...(initialOptions.tools !== undefined && { + tools: initialOptions.tools, + }), + ...(initialOptions.interrupts !== undefined && { + interrupts: initialOptions.interrupts, + }), + onCustomEvent: (eventType, data, context) => { + runOrQueueForActiveInstance(() => { + optionsRef.current.onCustomEvent?.(eventType, data, context) + }) + }, + ...(options.streamProcessor !== undefined && { + streamProcessor: options.streamProcessor, + }), + ...(optionsRef.current.queue !== undefined && { + queue: optionsRef.current.queue, + }), + onInterruptStateChange: (nextInterruptState, context) => { + runOrQueueForActiveInstance(() => { + optionsRef.current.onInterruptStateChange?.( + nextInterruptState, + context, + ) + }) + }, + }) + instanceHolder.current = instance + const capturedServerSnapshot = instance.getSnapshot() + return { + client: instance, + initialization: initializationState, + serverSnapshot: capturedServerSnapshot, + startPersistenceHydration: releasePersistenceHydration, + } + }, [clientId]) + + const getServerSnapshot = useCallback(() => serverSnapshot, [serverSnapshot]) useEffect(() => { activeClientRef.current = client @@ -303,12 +262,11 @@ export function useChat< } }, [client, initialization]) - useEffect(() => { - const clientMessages = client.getMessages() - if (clientMessages !== messagesRef.current) { - setMessages(clientMessages) - } - }, [client]) + const snapshot = useSyncExternalStore( + client.subscribeSnapshot, + client.getSnapshot, + getServerSnapshot, + ) // Sync each wire-payload slot in its own effect so an unrelated option // changing doesn't re-run the others. `updateOptions` declares strict-optional @@ -372,10 +330,11 @@ export function useChat< // run is picked straight back up from the durable log. useEffect(() => { client.attach() + startPersistenceHydration() return () => { client.detach() } - }, [client]) + }, [client, startPersistenceHydration]) useEffect(() => { if (cleanupDisposalRef.current?.client === client) { @@ -387,10 +346,6 @@ export function useChat< cleanupInvalidationRef.current = null } client.mountDevtools() - // Delivery-durability resume is transparent: the resumable SSE connection - // adapter re-attaches via the browser's native Last-Event-ID on reconnect. - // We only seed interrupt (state) resume from the client here. - syncResumeState(client) return () => { cleanupInvalidationRef.current = setTimeout(() => { @@ -423,47 +378,35 @@ export function useChat< } cleanupDisposalRef.current = disposal } - }, [client, syncResumeState]) + }, [client]) const sendMessage = useCallback( async ( content: string | MultimodalContent, sendOptions?: SendMessageOptions, ) => { - try { - await client.sendMessage(content, undefined, sendOptions) - } finally { - syncResumeState(client) - } + await client.sendMessage(content, undefined, sendOptions) }, - [client, syncResumeState], + [client], ) const cancelQueued = useCallback( (id: string) => { client.cancelQueued(id) }, - [client, syncResumeState], + [client], ) const append = useCallback( async (message: ModelMessage | UIMessage) => { - try { - await client.append(message) - } finally { - syncResumeState(client) - } + await client.append(message) }, - [client, syncResumeState], + [client], ) const reload = useCallback(async () => { - try { - await client.reload() - } finally { - syncResumeState(client) - } - }, [client, syncResumeState]) + await client.reload() + }, [client]) const stop = useCallback(() => { client.stop() @@ -471,8 +414,7 @@ export function useChat< const clear = useCallback(() => { client.clear() - syncResumeState(client) - }, [client, syncResumeState]) + }, [client]) const setMessagesManually = useCallback( (newMessages: Array>) => { @@ -497,21 +439,15 @@ export function useChat< const addToolApprovalResponse = useCallback( async (response: { id: string; approved: boolean }) => { await client.addToolApprovalResponse(response) - syncResumeState(client) }, - [client, syncResumeState], + [client], ) const resumeInterrupts = useCallback( async (resumeItems: Array, state?: ChatResumeState) => { - const result = await client.resumeInterrupts( - resumeItems, - state ?? undefined, - ) - syncResumeState(client) - return result + return await client.resumeInterrupts(resumeItems, state ?? undefined) }, - [client, syncResumeState], + [client], ) const resolveInterrupts = useCallback( @@ -555,7 +491,7 @@ export function useChat< // a stale assistant turn or a system prompt) we deliberately return null // rather than scanning historical assistants — otherwise a `final` from a // previous session would leak into the hook value on first render. - const renderedMessages = client.getMessages() + const renderedMessages = snapshot.messages const activeStructuredPart = useMemo(() => { let lastUserIndex = -1 @@ -600,23 +536,23 @@ export function useChat< append, reload, stop, - isLoading, - error, - status, - isSubscribed, - connectionStatus, - sessionGenerating, + isLoading: snapshot.isLoading, + error: snapshot.error, + status: snapshot.status, + isSubscribed: snapshot.isSubscribed, + connectionStatus: snapshot.connectionStatus, + sessionGenerating: snapshot.sessionGenerating, setMessages: setMessagesManually, clear, addToolResult, addToolApprovalResponse, - queue, + queue: snapshot.queue, cancelQueued, - runId, - interrupts: interruptState.interrupts, - pendingInterrupts: interruptState.pendingInterrupts, - interruptErrors: interruptState.interruptErrors, - resuming: interruptState.resuming, + runId: snapshot.runId, + interrupts: snapshot.interruptState.interrupts, + pendingInterrupts: snapshot.interruptState.pendingInterrupts, + interruptErrors: snapshot.interruptState.interruptErrors, + resuming: snapshot.interruptState.resuming, resolveInterrupts, cancelInterrupts, retryInterrupts, diff --git a/packages/ai-react/src/use-generate-video.ts b/packages/ai-react/src/use-generate-video.ts index 708281fee2..393c17f1ab 100644 --- a/packages/ai-react/src/use-generate-video.ts +++ b/packages/ai-react/src/use-generate-video.ts @@ -1,6 +1,13 @@ import { VideoGenerationClient } from '@tanstack/ai-client' import { createVideoDevtoolsBridge } from '@tanstack/ai-client/devtools' -import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' +import { + useCallback, + useEffect, + useId, + useMemo, + useRef, + useSyncExternalStore, +} from 'react' import type { StreamChunk } from '@tanstack/ai' import type { AIDevtoolsDisplayOptions, @@ -175,14 +182,6 @@ export function useGenerateVideo( // The hook identity is `threadId`. `hookId` is only a React recreation key. const clientIdentity = options.threadId ?? hookId - const [result, setResult] = useState(null) - const [jobId, setJobId] = useState(null) - const [videoStatus, setVideoStatus] = useState(null) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(undefined) - const [status, setStatus] = useState('idle') - const [runId, setRunId] = useState(null) - const optionsRef = useRef(options) optionsRef.current = options const disposedRef = useRef(false) @@ -235,27 +234,6 @@ export function useGenerateVideo( onStatusUpdate: (s: VideoStatusInfo) => { if (!disposedRef.current) optionsRef.current.onStatusUpdate?.(s) }, - onResultChange: (r: TOutput | null) => { - if (!disposedRef.current) setResult(r) - }, - onLoadingChange: (l: boolean) => { - if (!disposedRef.current) setIsLoading(l) - }, - onErrorChange: (e: Error | undefined) => { - if (!disposedRef.current) setError(e) - }, - onStatusChange: (s: GenerationClientState) => { - if (!disposedRef.current) setStatus(s) - }, - onJobIdChange: (id: string | null) => { - if (!disposedRef.current) setJobId(id) - }, - onVideoStatusChange: (s: VideoStatusInfo | null) => { - if (!disposedRef.current) setVideoStatus(s) - }, - onResumeStateChange: (rs: { runId: string } | null) => { - if (!disposedRef.current) setRunId(rs?.runId ?? null) - }, } const persistenceProps = @@ -289,6 +267,12 @@ export function useGenerateVideo( ) }, [clientIdentity, hookId]) + const snapshot = useSyncExternalStore( + client.subscribe, + client.getSnapshot, + client.getSnapshot, + ) + // Sync body changes without recreating client useEffect(() => { // Conditional spread: target uses strict-optional `body?: T`. @@ -327,14 +311,14 @@ export function useGenerateVideo( return { generate, - result, - jobId, - videoStatus, - isLoading, - error, - status, + result: snapshot.result, + jobId: snapshot.jobId, + videoStatus: snapshot.videoStatus, + isLoading: snapshot.isLoading, + error: snapshot.error, + status: snapshot.status, stop, reset, - runId, + runId: snapshot.runId, } } diff --git a/packages/ai-react/src/use-generation.ts b/packages/ai-react/src/use-generation.ts index 8106f4cf90..6ebe77c942 100644 --- a/packages/ai-react/src/use-generation.ts +++ b/packages/ai-react/src/use-generation.ts @@ -1,6 +1,13 @@ import { GenerationClient } from '@tanstack/ai-client' import { createGenerationDevtoolsBridge } from '@tanstack/ai-client/devtools' -import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' +import { + useCallback, + useEffect, + useId, + useMemo, + useRef, + useSyncExternalStore, +} from 'react' import type { StreamChunk } from '@tanstack/ai' import type { AIDevtoolsDisplayOptions, @@ -174,12 +181,6 @@ export function useGeneration< // The hook identity is `threadId`. `hookId` is only a React recreation key. const clientIdentity = options.threadId ?? hookId - const [result, setResult] = useState(null) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(undefined) - const [status, setStatus] = useState('idle') - const [runId, setRunId] = useState(null) - const optionsRef = useRef(options) optionsRef.current = options const disposedRef = useRef(false) @@ -226,21 +227,6 @@ export function useGeneration< onChunk: (c: StreamChunk) => { if (!disposedRef.current) optionsRef.current.onChunk?.(c) }, - onResultChange: (r) => { - if (!disposedRef.current) setResult(r) - }, - onLoadingChange: (l) => { - if (!disposedRef.current) setIsLoading(l) - }, - onErrorChange: (e) => { - if (!disposedRef.current) setError(e) - }, - onStatusChange: (s) => { - if (!disposedRef.current) setStatus(s) - }, - onResumeStateChange: (rs) => { - if (!disposedRef.current) setRunId(rs?.runId ?? null) - }, } const persistenceProps = @@ -274,6 +260,12 @@ export function useGeneration< ) }, [clientIdentity, hookId]) + const snapshot = useSyncExternalStore( + client.subscribe, + client.getSnapshot, + client.getSnapshot, + ) + // Sync body changes without recreating client useEffect(() => { // Conditional spread: target uses strict-optional `body?: T`. @@ -312,12 +304,12 @@ export function useGeneration< return { generate, - result, - isLoading, - error, - status, + result: snapshot.result, + isLoading: snapshot.isLoading, + error: snapshot.error, + status: snapshot.status, stop, reset, - runId, + runId: snapshot.runId, } } diff --git a/packages/ai-react/src/use-realtime-chat.ts b/packages/ai-react/src/use-realtime-chat.ts index bddef502c6..9646765285 100644 --- a/packages/ai-react/src/use-realtime-chat.ts +++ b/packages/ai-react/src/use-realtime-chat.ts @@ -1,11 +1,12 @@ -import { useCallback, useEffect, useRef, useState } from 'react' +import { + useCallback, + useEffect, + useRef, + useState, + useSyncExternalStore, +} from 'react' import { RealtimeClient } from '@tanstack/ai-client' -import type { - RealtimeMessage, - RealtimeMode, - RealtimeSessionConfig, - RealtimeStatus, -} from '@tanstack/ai' +import type { RealtimeSessionConfig } from '@tanstack/ai' import type { UseRealtimeChatOptions, UseRealtimeChatReturn, @@ -58,17 +59,6 @@ const emptyTimeDomainData = new Uint8Array(128).fill(128) export function useRealtimeChat( options: UseRealtimeChatOptions, ): UseRealtimeChatReturn { - // State - const [status, setStatus] = useState('idle') - const [mode, setMode] = useState('idle') - const [messages, setMessages] = useState>([]) - const [pendingUserTranscript, setPendingUserTranscript] = useState< - string | null - >(null) - const [pendingAssistantTranscript, setPendingAssistantTranscript] = useState< - string | null - >(null) - const [error, setError] = useState(null) const [inputLevel, setInputLevel] = useState(0) const [outputLevel, setOutputLevel] = useState(0) @@ -113,15 +103,10 @@ export function useRealtimeChat( ...(initial.semanticEagerness !== undefined && { semanticEagerness: initial.semanticEagerness, }), - onStatusChange: (newStatus) => { - setStatus(newStatus) - }, onModeChange: (newMode) => { - setMode(newMode) optionsRef.current.onModeChange?.(newMode) }, onMessage: (message) => { - setMessages((prev) => [...prev, message]) optionsRef.current.onMessage?.(message) }, onUsage(usage) { @@ -131,30 +116,26 @@ export function useRealtimeChat( optionsRef.current.onGoAway?.(timeLeft) }, onError: (err) => { - setError(err) optionsRef.current.onError?.(err) }, onConnect: () => { - setError(null) optionsRef.current.onConnect?.() }, onDisconnect: () => { optionsRef.current.onDisconnect?.() }, onInterrupted: () => { - setPendingAssistantTranscript(null) optionsRef.current.onInterrupted?.() }, }) - - // Subscribe to state changes for transcripts - clientRef.current.onStateChange((state) => { - setPendingUserTranscript(state.pendingUserTranscript) - setPendingAssistantTranscript(state.pendingAssistantTranscript) - }) } const client = clientRef.current + const snapshot = useSyncExternalStore( + client.subscribe, + client.getSnapshot, + client.getSnapshot, + ) // Audio level animation loop useEffect(() => { @@ -166,7 +147,7 @@ export function useRealtimeChat( animationFrameRef.current = requestAnimationFrame(updateLevels) } - if (status === 'connected') { + if (snapshot.status === 'connected') { updateLevels() } @@ -176,7 +157,7 @@ export function useRealtimeChat( animationFrameRef.current = null } } - }, [status]) + }, [snapshot.status]) // Cleanup on unmount useEffect(() => { @@ -187,10 +168,6 @@ export function useRealtimeChat( // Connection methods const connect = useCallback(async () => { - setError(null) - setMessages([]) - setPendingUserTranscript(null) - setPendingAssistantTranscript(null) await client.connect() }, [client]) @@ -258,16 +235,16 @@ export function useRealtimeChat( return { // Connection state - status, - error, + status: snapshot.status, + error: snapshot.error, connect, disconnect, // Conversation state - mode, - messages, - pendingUserTranscript, - pendingAssistantTranscript, + mode: snapshot.mode, + messages: snapshot.messages, + pendingUserTranscript: snapshot.pendingUserTranscript, + pendingAssistantTranscript: snapshot.pendingAssistantTranscript, // Voice control startListening, diff --git a/packages/ai-react/tests/use-chat.test.ts b/packages/ai-react/tests/use-chat.test.ts index 3f6d6593f8..04f2fe9f01 100644 --- a/packages/ai-react/tests/use-chat.test.ts +++ b/packages/ai-react/tests/use-chat.test.ts @@ -1,7 +1,13 @@ import { EventType } from '@tanstack/ai' import { ChatClient } from '@tanstack/ai-client' import { act, render, renderHook, waitFor } from '@testing-library/react' -import { StrictMode, Suspense, createElement, useState } from 'react' +import { + StrictMode, + Suspense, + createElement, + useLayoutEffect, + useState, +} from 'react' import { afterEach, describe, expect, it, vi } from 'vitest' import { useChat } from '../src/use-chat' import { @@ -232,7 +238,7 @@ describe('useChat', () => { expect(persistence.getItem).toHaveBeenCalledWith('persisted-chat') }) - it('should forward synchronous persisted interrupt hydration', () => { + it('forwards persisted interrupt hydration from a synchronous store', async () => { const onInterruptStateChange = vi.fn() const persistence = { getItem: vi.fn(() => ({ @@ -254,7 +260,9 @@ describe('useChat', () => { { wrapper: StrictMode }, ) - expect(result.current.interrupts).toHaveLength(2) + await waitFor(() => { + expect(result.current.interrupts).toHaveLength(2) + }) expect(onInterruptStateChange).toHaveBeenCalledOnce() expect(onInterruptStateChange).toHaveBeenCalledWith( expect.objectContaining({ interrupts: result.current.interrupts }), @@ -262,6 +270,360 @@ describe('useChat', () => { ) }) + it('keeps the server message count for the first hydration render', async () => { + const initialMessages: Array = [ + { + id: 'server-message', + role: 'user', + parts: [{ type: 'text', content: 'Server message' }], + createdAt: new Date(), + }, + ] + const persistedMessages: Array = [ + ...initialMessages, + { + id: 'persisted-message', + role: 'assistant', + parts: [{ type: 'text', content: 'Persisted message' }], + createdAt: new Date(), + }, + ] + const persistence = { + getItem: vi.fn(() => persistedMessages), + setItem: vi.fn(), + removeItem: vi.fn(), + } + const renderCounts: Array = [] + const onRecoverableError = vi.fn() + const container = document.createElement('div') + container.innerHTML = '
1
' + document.body.append(container) + + function HydratedChat() { + const chat = useChat({ + connection: createMockConnectionAdapter(), + threadId: 'hydrated-message-chat', + initialMessages, + persistence, + }) + renderCounts.push(chat.messages.length) + return createElement( + 'div', + { 'data-testid': 'message-count' }, + chat.messages.length, + ) + } + + const view = render( + createElement(StrictMode, null, createElement(HydratedChat)), + { container, hydrate: true, onRecoverableError }, + ) + + expect(renderCounts[0]).toBe(1) + await waitFor(() => { + expect(view.getByTestId('message-count').textContent).toBe('2') + }) + expect(persistence.getItem).toHaveBeenCalledOnce() + expect(onRecoverableError).not.toHaveBeenCalled() + }) + + it('keeps server interrupts empty until persistence hydration starts after commit', async () => { + const persistence = { + getItem: vi.fn(() => ({ + messages: [], + resume: createInterruptResumeSnapshot(), + })), + setItem: vi.fn(), + removeItem: vi.fn(), + } + const renderCounts: Array = [] + const hydrationCommitStates: Array = [] + const onRecoverableError = vi.fn() + const container = document.createElement('div') + container.innerHTML = '
0
' + document.body.append(container) + + function HydratedInterrupts() { + let committed = false + useLayoutEffect(() => { + committed = true + }, []) + const chat = useChat({ + connection: createMockConnectionAdapter(), + threadId: 'hydrated-interrupt-chat', + persistence, + onInterruptStateChange: (_state, context) => { + if (context.source === 'hydrate') { + hydrationCommitStates.push(committed) + } + }, + }) + renderCounts.push(chat.interrupts.length) + return createElement( + 'div', + { 'data-testid': 'interrupt-count' }, + chat.interrupts.length, + ) + } + + const view = render( + createElement(StrictMode, null, createElement(HydratedInterrupts)), + { container, hydrate: true, onRecoverableError }, + ) + + expect(renderCounts[0]).toBe(0) + await waitFor(() => { + expect(view.getByTestId('interrupt-count').textContent).toBe('2') + }) + expect(hydrationCommitStates).toEqual([true]) + expect(persistence.getItem).toHaveBeenCalledOnce() + expect(onRecoverableError).not.toHaveBeenCalled() + }) + + it('keeps the initial resume snapshot through hydration before rejoining the stored run', async () => { + const initialResumeSnapshot = createInterruptResumeSnapshot() + const persistence = { + getItem: vi.fn(() => ({ + messages: [], + resume: { + schemaVersion: 2, + resumeState: { + threadId: 'hydrated-resume-chat', + runId: 'stored-run', + }, + }, + })), + setItem: vi.fn(), + removeItem: vi.fn(), + } + const joinRun = vi.fn(async function* (_runId: string) {}) + const connection: ResumableConnectConnectionAdapter = { + connect: async function* () {}, + joinRun, + } + const renderCounts: Array = [] + const onRecoverableError = vi.fn() + const container = document.createElement('div') + container.innerHTML = '
2
' + document.body.append(container) + + function HydratedResume() { + const chat = useChat({ + connection, + threadId: 'hydrated-resume-chat', + initialResumeSnapshot, + persistence, + }) + renderCounts.push(chat.interrupts.length) + return createElement( + 'div', + { 'data-testid': 'interrupt-count' }, + chat.interrupts.length, + ) + } + + const view = render(createElement(HydratedResume), { + container, + hydrate: true, + onRecoverableError, + }) + + expect(renderCounts[0]).toBe(2) + expect(joinRun).not.toHaveBeenCalled() + await waitFor(() => { + expect(view.getByTestId('interrupt-count').textContent).toBe('0') + expect(joinRun).toHaveBeenCalledOnce() + }) + expect(joinRun).toHaveBeenCalledWith('stored-run', expect.anything()) + expect(persistence.getItem).toHaveBeenCalledOnce() + expect(onRecoverableError).not.toHaveBeenCalled() + }) + + it('preserves the persistence adapter receiver for reads, writes, and removal', async () => { + class ReceiverSensitivePersistence implements ChatClientPersistence { + calls: Array = [] + + getItem(id: string) { + this.calls.push(`get:${id}`) + return [] + } + + setItem(id: string) { + this.calls.push(`set:${id}`) + } + + removeItem(id: string) { + this.calls.push(`remove:${id}`) + } + } + + const persistence = new ReceiverSensitivePersistence() + const { result } = renderUseChat({ + connection: createMockConnectionAdapter({ + chunks: createTextChunks('Persisted reply'), + }), + threadId: 'receiver-chat', + persistence, + }) + + await waitFor(() => { + expect(persistence.calls).toContain('get:receiver-chat') + }) + await act(async () => { + await result.current.sendMessage('Persist me') + }) + await waitFor(() => { + expect(persistence.calls).toContain('set:receiver-chat') + }) + const removalsBeforeClear = persistence.calls.filter( + (call) => call === 'remove:receiver-chat', + ).length + act(() => result.current.clear()) + await waitFor(() => { + const removalsAfterClear = persistence.calls.filter( + (call) => call === 'remove:receiver-chat', + ).length + expect(removalsAfterClear).toBeGreaterThan(removalsBeforeClear) + }) + }) + + it('keeps initial state when a synchronous persistence read throws during hydration', async () => { + const initialMessages: Array = [ + { + id: 'initial-message', + role: 'user', + parts: [{ type: 'text', content: 'Initial' }], + createdAt: new Date(), + }, + ] + const persistence = { + getItem: vi.fn(() => { + throw new Error('storage unavailable') + }), + setItem: vi.fn(), + removeItem: vi.fn(), + } + const onInterruptStateChange = vi.fn() + const onRecoverableError = vi.fn() + const container = document.createElement('div') + container.innerHTML = '
1
' + document.body.append(container) + + function HydratedChat() { + const chat = useChat({ + connection: createMockConnectionAdapter(), + threadId: 'throwing-sync-chat', + initialMessages, + persistence, + onInterruptStateChange, + }) + return createElement( + 'div', + { 'data-testid': 'message-count' }, + chat.messages.length, + ) + } + + const view = render(createElement(HydratedChat), { + container, + hydrate: true, + onRecoverableError, + }) + + await waitFor(() => { + expect(persistence.getItem).toHaveBeenCalledOnce() + }) + expect(view.getByTestId('message-count').textContent).toBe('1') + expect(onInterruptStateChange).not.toHaveBeenCalled() + expect(onRecoverableError).not.toHaveBeenCalled() + }) + + it('keeps initial state when an asynchronous persistence read rejects', async () => { + const initialMessages: Array = [ + { + id: 'initial-message', + role: 'user', + parts: [{ type: 'text', content: 'Initial' }], + createdAt: new Date(), + }, + ] + const persistence = { + getItem: vi.fn(async () => { + throw new Error('storage unavailable') + }), + setItem: vi.fn(), + removeItem: vi.fn(), + } + const onInterruptStateChange = vi.fn() + const { result } = renderUseChat({ + connection: createMockConnectionAdapter(), + threadId: 'throwing-async-chat', + initialMessages, + persistence, + onInterruptStateChange, + }) + + await waitFor(() => { + expect(persistence.getItem).toHaveBeenCalledOnce() + }) + expect(result.current.messages).toEqual(initialMessages) + expect(onInterruptStateChange).not.toHaveBeenCalled() + }) + + it('applies asynchronous persistence only after attach and commit', async () => { + const hydration = createDeferred<{ + messages: Array + resume: ReturnType + }>() + let committed = false + const readCommitStates: Array = [] + const hydrationCommitStates: Array = [] + const persistence = { + getItem: vi.fn(() => { + readCommitStates.push(committed) + return hydration.promise + }), + setItem: vi.fn(), + removeItem: vi.fn(), + } + + function useHydratedChat() { + useLayoutEffect(() => { + committed = true + }, []) + const chat = useChat({ + connection: createMockConnectionAdapter(), + threadId: 'async-hydration-chat', + persistence, + onInterruptStateChange: (_state, context) => { + if (context.source === 'hydrate') { + hydrationCommitStates.push(committed) + } + }, + }) + return chat.interrupts.length + } + + const { result } = renderHook(() => useHydratedChat()) + + expect(result.current).toBe(0) + await waitFor(() => { + expect(persistence.getItem).toHaveBeenCalledOnce() + }) + expect(readCommitStates).toEqual([true]) + await act(async () => { + hydration.resolve({ + messages: [], + resume: createInterruptResumeSnapshot(), + }) + await hydration.promise + }) + await waitFor(() => { + expect(result.current).toBe(2) + }) + expect(hydrationCommitStates).toEqual([true]) + }) + it('does not publish async hydration from an abandoned render', async () => { const hydration = createDeferred<{ messages: Array @@ -302,6 +664,7 @@ describe('useChat', () => { await hydration.promise }) + expect(persistence.getItem).not.toHaveBeenCalled() expect(onInterruptStateChange).not.toHaveBeenCalled() }) diff --git a/packages/ai-solid/src/use-chat.ts b/packages/ai-solid/src/use-chat.ts index 6d5737b6b4..b5eba10893 100644 --- a/packages/ai-solid/src/use-chat.ts +++ b/packages/ai-solid/src/use-chat.ts @@ -82,11 +82,6 @@ export function useChat< resuming: false, }) - const syncResumeState = () => { - setRunId(client().getCurrentRunId()) - setInterruptState(client().getInterruptState()) - } - // Structured-output `partial` / `final` are derived from `messages` — // specifically from the structured-output part on the latest assistant // message (the one after the most recent user message). Per-turn parts @@ -159,44 +154,8 @@ export function useChat< ...(options.streamProcessor !== undefined && { streamProcessor: options.streamProcessor, }), - onMessagesChange: (newMessages: Array>) => { - setMessages(newMessages) - }, - onLoadingChange: (newIsLoading: boolean) => { - setIsLoading(newIsLoading) - syncResumeState() - }, - onStatusChange: (newStatus: ChatClientState) => { - setStatus(newStatus) - }, - onErrorChange: (newError: Error | undefined) => { - setError(newError) - }, - onSubscriptionChange: (nextIsSubscribed: boolean) => { - setIsSubscribed(nextIsSubscribed) - }, - onConnectionStatusChange: (nextStatus: ConnectionStatus) => { - setConnectionStatus(nextStatus) - }, - onSessionGeneratingChange: (isGenerating: boolean) => { - setSessionGenerating(isGenerating) - }, ...(options.queue !== undefined && { queue: options.queue }), - onQueueChange: (nextQueue: Array) => { - setQueue(nextQueue) - }, - onRunIdChange: (nextRunId) => { - setRunId(nextRunId) - }, - onResumeStateChange: (_nextResumeState, nextPendingInterrupts) => { - setInterruptState((current) => ({ - ...current, - interrupts: nextPendingInterrupts, - pendingInterrupts: nextPendingInterrupts, - })) - }, onInterruptStateChange: (nextInterruptState, context) => { - setInterruptState(nextInterruptState) options.onInterruptStateChange?.(nextInterruptState, context) }, }) @@ -204,8 +163,26 @@ export function useChat< // Connection and other options are captured at creation time }, [clientId]) - setMessages(client().getMessages()) - syncResumeState() + const applySnapshot = (target: ChatClient) => { + const next = target.getSnapshot() + setMessages(next.messages) + setIsLoading(next.isLoading) + setError(next.error) + setStatus(next.status) + setIsSubscribed(next.isSubscribed) + setConnectionStatus(next.connectionStatus) + setSessionGenerating(next.sessionGenerating) + setQueue(next.queue) + setRunId(next.runId) + setInterruptState(next.interruptState) + } + + createEffect(() => { + const target = client() + applySnapshot(target) + const unsubscribe = target.subscribeSnapshot(() => applySnapshot(target)) + onCleanup(unsubscribe) + }) // Sync body / forwardedProps changes to the client. // Both populate the same wire payload; `forwardedProps` is preferred @@ -230,6 +207,11 @@ export function useChat< client().unsubscribe() } + // First paint must see constructor hydration (sync store / resume snapshot) + // and the live subscribe above. `createEffect` runs after paint, so the + // subscribe effect cannot be the first apply. + applySnapshot(client()) + createEffect(() => { if (options.live) { client().subscribe() @@ -245,10 +227,6 @@ export function useChat< // connections per origin until the page reloaded. client().attach() client().mountDevtools() - // Delivery-durability resume is transparent: the resumable SSE connection - // adapter reattaches via the browser's native Last-Event-ID on reconnect. - // We only seed interrupt (state) resume from the client here. - syncResumeState() }) // Cleanup on unmount: stop any in-flight requests. @@ -272,27 +250,15 @@ export function useChat< content: string | MultimodalContent, sendOptions?: SendMessageOptions, ) => { - try { - await client().sendMessage(content, undefined, sendOptions) - } finally { - syncResumeState() - } + await client().sendMessage(content, undefined, sendOptions) } const append = async (message: ModelMessage | UIMessage) => { - try { - await client().append(message) - } finally { - syncResumeState() - } + await client().append(message) } const reload = async () => { - try { - await client().reload() - } finally { - syncResumeState() - } + await client().reload() } const stop = () => { @@ -301,7 +267,6 @@ export function useChat< const clear = () => { client().clear() - syncResumeState() } const setMessagesManually = (newMessages: Array>) => { @@ -323,7 +288,6 @@ export function useChat< approved: boolean }) => { await client().addToolApprovalResponse(response) - syncResumeState() } const cancelQueued = (id: string) => client().cancelQueued(id) @@ -332,9 +296,7 @@ export function useChat< resumeItems: Array, state?: ChatResumeState, ) => { - const result = await client().resumeInterrupts(resumeItems, state) - syncResumeState() - return result + return await client().resumeInterrupts(resumeItems, state) } const resolveInterrupts = ( diff --git a/packages/ai-solid/src/use-generate-video.ts b/packages/ai-solid/src/use-generate-video.ts index 42acd27e53..ceba999c4f 100644 --- a/packages/ai-solid/src/use-generate-video.ts +++ b/packages/ai-solid/src/use-generate-video.ts @@ -244,27 +244,6 @@ export function useGenerateVideo( onStatusUpdate: (s: VideoStatusInfo) => { if (!disposed) options.onStatusUpdate?.(s) }, - onResultChange: (r: TOutput | null) => { - if (!disposed) setResult(() => r) - }, - onLoadingChange: (l: boolean) => { - if (!disposed) setIsLoading(l) - }, - onErrorChange: (e: Error | undefined) => { - if (!disposed) setError(e) - }, - onStatusChange: (s: GenerationClientState) => { - if (!disposed) setStatus(s) - }, - onJobIdChange: (id: string | null) => { - if (!disposed) setJobId(id) - }, - onVideoStatusChange: (s: VideoStatusInfo | null) => { - if (!disposed) setVideoStatus(s) - }, - onResumeStateChange: (rs: { runId: string } | null) => { - if (!disposed) setRunId(rs?.runId ?? null) - }, } if (options.connection) { @@ -286,6 +265,19 @@ export function useGenerateVideo( ) }) + const applySnapshot = () => { + const next = client.getSnapshot() + setResult(() => next.result) + setIsLoading(next.isLoading) + setError(next.error) + setStatus(next.status) + setRunId(next.runId) + setJobId(next.jobId) + setVideoStatus(next.videoStatus) + } + applySnapshot() + onCleanup(client.subscribe(applySnapshot)) + // Sync body changes without recreating client createEffect(() => { const currentBody = options.body diff --git a/packages/ai-solid/src/use-generation.ts b/packages/ai-solid/src/use-generation.ts index eedc8eb5c2..ee614f1d8c 100644 --- a/packages/ai-solid/src/use-generation.ts +++ b/packages/ai-solid/src/use-generation.ts @@ -230,21 +230,6 @@ export function useGeneration< onChunk: (c: StreamChunk) => { if (!disposed) options.onChunk?.(c) }, - onResultChange: (r) => { - if (!disposed) setResult(() => r) - }, - onLoadingChange: (l) => { - if (!disposed) setIsLoading(l) - }, - onErrorChange: (e) => { - if (!disposed) setError(e) - }, - onStatusChange: (s) => { - if (!disposed) setStatus(s) - }, - onResumeStateChange: (rs) => { - if (!disposed) setRunId(rs?.runId ?? null) - }, } const persistenceProps = @@ -280,6 +265,17 @@ export function useGeneration< ) }) + const applySnapshot = () => { + const next = client.getSnapshot() + setResult(() => next.result) + setIsLoading(next.isLoading) + setError(next.error) + setStatus(next.status) + setRunId(next.runId) + } + applySnapshot() + onCleanup(client.subscribe(applySnapshot)) + // Sync body changes without recreating client createEffect(() => { const currentBody = options.body diff --git a/packages/ai-solid/tests/use-chat.test.ts b/packages/ai-solid/tests/use-chat.test.ts index 050654c896..52d182bac7 100644 --- a/packages/ai-solid/tests/use-chat.test.ts +++ b/packages/ai-solid/tests/use-chat.test.ts @@ -153,14 +153,20 @@ describe('useChat', () => { removeItem: vi.fn(), } - const { result } = renderUseChat({ - connection: adapter, - threadId: 'persisted-chat', - persistence: persistence, + let firstMessages: Array | undefined + const rendered = renderHook(() => { + const chat = useChat({ + connection: adapter, + threadId: 'persisted-chat', + persistence, + }) + firstMessages ??= chat.messages() + return chat }) + expect(firstMessages).toEqual(persistedMessages) await waitFor(() => { - expect(result.current.messages).toEqual(persistedMessages) + expect(rendered.result.messages()).toEqual(persistedMessages) }) expect(persistence.getItem).toHaveBeenCalledWith('persisted-chat') }) diff --git a/packages/ai-svelte/src/create-chat.svelte.ts b/packages/ai-svelte/src/create-chat.svelte.ts index 9e72e21aeb..97cd334617 100644 --- a/packages/ai-svelte/src/create-chat.svelte.ts +++ b/packages/ai-svelte/src/create-chat.svelte.ts @@ -163,48 +163,28 @@ export function createChat< ...(options.streamProcessor !== undefined && { streamProcessor: options.streamProcessor, }), - onMessagesChange: (newMessages: Array>) => { - messages = newMessages - }, - onLoadingChange: (newIsLoading: boolean) => { - isLoading = newIsLoading - syncResumeState() - }, - onStatusChange: (newStatus: ChatClientState) => { - status = newStatus - }, - onErrorChange: (newError: Error | undefined) => { - error = newError - }, - onSubscriptionChange: (nextIsSubscribed: boolean) => { - isSubscribed = nextIsSubscribed - }, - onConnectionStatusChange: (nextStatus: ConnectionStatus) => { - connectionStatus = nextStatus - }, - onSessionGeneratingChange: (isGenerating: boolean) => { - sessionGenerating = isGenerating - }, ...(options.queue !== undefined && { queue: options.queue }), - onQueueChange: (nextQueue: Array) => { - queue = nextQueue - }, - onRunIdChange: (nextRunId) => { - runId = nextRunId - }, onInterruptStateChange: (nextInterruptState, context) => { - interruptState = nextInterruptState options.onInterruptStateChange?.(nextInterruptState, context) }, }) - function syncResumeState() { - runId = client.getCurrentRunId() - interruptState = client.getInterruptState() + function applySnapshot() { + const next = client.getSnapshot() + messages = next.messages + isLoading = next.isLoading + error = next.error + status = next.status + isSubscribed = next.isSubscribed + connectionStatus = next.connectionStatus + sessionGenerating = next.sessionGenerating + queue = next.queue + runId = next.runId + interruptState = next.interruptState } - messages = client.getMessages() - interruptState = client.getInterruptState() + applySnapshot() + const unsubscribeSnapshot = client.subscribeSnapshot(applySnapshot) if (options.live) { client.subscribe() @@ -218,7 +198,6 @@ export function createChat< // Delivery-durability resume is transparent: the resumable SSE // connection adapter reattaches via the browser's native // Last-Event-ID on reconnect. We only seed interrupt (state) resume. - syncResumeState() client.attach() // ONLY THE VIEW ON SCREEN HOLDS A STREAM. `onMount`'s returned function // runs when the component is destroyed, which is the one automatic @@ -230,6 +209,7 @@ export function createChat< // `detach` keeps the transcript and the resume pointer, so re-entering // the view picks the run back up from the durable log. return () => { + unsubscribeSnapshot() client.detach() } }) @@ -247,29 +227,17 @@ export function createChat< content: string | MultimodalContent, sendOptions?: SendMessageOptions, ) => { - try { - await client.sendMessage(content, undefined, sendOptions) - } finally { - syncResumeState() - } + await client.sendMessage(content, undefined, sendOptions) } const cancelQueued = (id: string) => client.cancelQueued(id) const append = async (message: ModelMessage | UIMessage) => { - try { - await client.append(message) - } finally { - syncResumeState() - } + await client.append(message) } const reload = async () => { - try { - await client.reload() - } finally { - syncResumeState() - } + await client.reload() } const stop = () => { @@ -277,12 +245,12 @@ export function createChat< } const dispose = () => { + unsubscribeSnapshot() client.dispose() } const clear = () => { client.clear() - syncResumeState() } const setMessages = (newMessages: Array>) => { @@ -304,16 +272,13 @@ export function createChat< approved: boolean }) => { await client.addToolApprovalResponse(response) - syncResumeState() } const resumeInterrupts = async ( resumeItems: Array, state?: ChatResumeState, ) => { - const result = await client.resumeInterrupts(resumeItems, state) - syncResumeState() - return result + return await client.resumeInterrupts(resumeItems, state) } const resolveInterrupts = ( diff --git a/packages/ai-svelte/src/create-generate-video.svelte.ts b/packages/ai-svelte/src/create-generate-video.svelte.ts index d8b8615bcf..4448245f8e 100644 --- a/packages/ai-svelte/src/create-generate-video.svelte.ts +++ b/packages/ai-svelte/src/create-generate-video.svelte.ts @@ -235,34 +235,6 @@ export function createGenerateVideo( onStatusUpdate: (s: VideoStatusInfo) => { if (!disposed) options.onStatusUpdate?.(s) }, - onResultChange: (r: TOutput | null) => { - if (disposed) return - result = r - }, - onLoadingChange: (l: boolean) => { - if (disposed) return - isLoading = l - }, - onErrorChange: (e: Error | undefined) => { - if (disposed) return - error = e - }, - onStatusChange: (s: GenerationClientState) => { - if (disposed) return - status = s - }, - onJobIdChange: (id: string | null) => { - if (disposed) return - jobId = id - }, - onVideoStatusChange: (s: VideoStatusInfo | null) => { - if (disposed) return - videoStatus = s - }, - onResumeStateChange: (rs: { runId: string } | null) => { - if (disposed) return - runId = rs?.runId ?? null - }, } let client: VideoGenerationClient @@ -283,6 +255,19 @@ export function createGenerateVideo( ) } + const applySnapshot = () => { + const next = client.getSnapshot() + result = next.result + isLoading = next.isLoading + error = next.error + status = next.status + runId = next.runId + jobId = next.jobId + videoStatus = next.videoStatus + } + applySnapshot() + let unsubscribeSnapshot = client.subscribe(applySnapshot) + // Mount devtools only. Generation runs are never auto-started on setup — // persisted state is read-only for display. client.mountDevtools() @@ -297,7 +282,11 @@ export function createGenerateVideo( // frameworks revive via mountDevtools() in their mount effects), so an // explicit generate() after dispose() is the Svelte revive path: bring // the client and the reactive bindings back together. - disposed = false + if (disposed) { + disposed = false + applySnapshot() + unsubscribeSnapshot = client.subscribe(applySnapshot) + } client.mountDevtools() await client.generate(input) } @@ -312,6 +301,8 @@ export function createGenerateVideo( const dispose = () => { disposed = true + unsubscribeSnapshot() + unsubscribeSnapshot = () => {} client.dispose() } diff --git a/packages/ai-svelte/src/create-generation.svelte.ts b/packages/ai-svelte/src/create-generation.svelte.ts index bf99c9dd3b..1c659dd2f8 100644 --- a/packages/ai-svelte/src/create-generation.svelte.ts +++ b/packages/ai-svelte/src/create-generation.svelte.ts @@ -233,26 +233,6 @@ export function createGeneration< onChunk: (c: StreamChunk) => { if (!disposed) options.onChunk?.(c) }, - onResultChange: (r: TOutput | null) => { - if (disposed) return - result = r - }, - onLoadingChange: (l: boolean) => { - if (disposed) return - isLoading = l - }, - onErrorChange: (e: Error | undefined) => { - if (disposed) return - error = e - }, - onStatusChange: (s: GenerationClientState) => { - if (disposed) return - status = s - }, - onResumeStateChange: (rs) => { - if (disposed) return - runId = rs?.runId ?? null - }, } const persistenceProps = @@ -287,6 +267,17 @@ export function createGeneration< ) } + const applySnapshot = () => { + const next = client.getSnapshot() + result = next.result + isLoading = next.isLoading + error = next.error + status = next.status + runId = next.runId + } + applySnapshot() + let unsubscribeSnapshot = client.subscribe(applySnapshot) + // Mount devtools only. Generation runs are never auto-started on setup — // persisted state is read-only for display. client.mountDevtools() @@ -301,7 +292,11 @@ export function createGeneration< // frameworks revive via mountDevtools() in their mount effects), so an // explicit generate() after dispose() is the Svelte revive path: bring // the client and the reactive bindings back together. - disposed = false + if (disposed) { + disposed = false + applySnapshot() + unsubscribeSnapshot = client.subscribe(applySnapshot) + } client.mountDevtools() await client.generate(input) } @@ -316,6 +311,8 @@ export function createGeneration< const dispose = () => { disposed = true + unsubscribeSnapshot() + unsubscribeSnapshot = () => {} client.dispose() } diff --git a/packages/ai-svelte/tests/create-generation-dispose.test.ts b/packages/ai-svelte/tests/create-generation-dispose.test.ts new file mode 100644 index 0000000000..31920ea009 --- /dev/null +++ b/packages/ai-svelte/tests/create-generation-dispose.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest' +import { createGeneration } from '../src/create-generation.svelte' +import { createGenerateVideo } from '../src/create-generate-video.svelte' + +describe('Svelte generate() after dispose() rebinds the snapshot', () => { + it('createGeneration updates result after dispose then generate', async () => { + const gen = createGeneration<{ prompt: string }, { id: string }>({ + fetcher: async (input) => ({ id: input.prompt }), + }) + + await gen.generate({ prompt: 'a' }) + expect(gen.result).toEqual({ id: 'a' }) + + gen.dispose() + await gen.generate({ prompt: 'b' }) + expect(gen.result).toEqual({ id: 'b' }) + }) + + it('createGenerateVideo updates result after dispose then generate', async () => { + const video = createGenerateVideo({ + fetcher: async (input) => { + if (typeof input.prompt !== 'string') { + throw new Error('Expected a text prompt') + } + + return { + jobId: input.prompt, + status: 'completed' as const, + url: `https://example.com/${input.prompt}.mp4`, + } + }, + }) + + await video.generate({ prompt: 'one' }) + expect(video.result?.jobId).toBe('one') + + video.dispose() + await video.generate({ prompt: 'two' }) + expect(video.result?.jobId).toBe('two') + }) +}) diff --git a/packages/ai-vertex/package.json b/packages/ai-vertex/package.json index 55bf952d85..bdf083f945 100644 --- a/packages/ai-vertex/package.json +++ b/packages/ai-vertex/package.json @@ -58,7 +58,6 @@ "@tanstack/ai": "workspace:^" }, "devDependencies": { - "@google/genai": "^2.10.0", "@tanstack/ai": "workspace:*", "@vitest/coverage-v8": "4.1.10", "vite": "^8.2.1" diff --git a/packages/ai-vertex/tests/factories.test.ts b/packages/ai-vertex/tests/factories.test.ts index 54c9c86b84..c21f6c0d5e 100644 --- a/packages/ai-vertex/tests/factories.test.ts +++ b/packages/ai-vertex/tests/factories.test.ts @@ -11,22 +11,35 @@ import { const mocks = vi.hoisted(() => { return { - constructorSpy: vi.fn<(options: Record) => void>(), + constructorSpy: + vi.fn< + (kind: string, options: Record, model: string) => void + >(), } }) -vi.mock('@google/genai', async (importOriginal) => { - const actual = await importOriginal() +vi.mock('@tanstack/ai-gemini', async (importOriginal) => { + const actual = await importOriginal() + const adapterClass = (kind: string) => { + return class { + readonly name = 'gemini' + readonly model: string - class MockGoogleGenAI { - constructor(options: Record) { - mocks.constructorSpy(options) + constructor(options: Record, model: string) { + this.model = model + mocks.constructorSpy(kind, options, model) + } } } return { ...actual, - GoogleGenAI: MockGoogleGenAI, + GeminiTextAdapter: adapterClass('text'), + GeminiImageAdapter: adapterClass('image'), + GeminiEmbeddingAdapter: adapterClass('embedding'), + GeminiTTSAdapter: adapterClass('speech'), + GeminiAudioAdapter: adapterClass('audio'), + GeminiVideoAdapter: adapterClass('video'), } }) @@ -49,11 +62,15 @@ describe('vertex factories', () => { expect(adapter.name).toBe('gemini') expect(adapter.model).toBe('gemini-3.7-flash') - expect(mocks.constructorSpy).toHaveBeenCalledExactlyOnceWith({ - project: 'my-project', - location: 'europe-west1', - vertexai: true, - }) + expect(mocks.constructorSpy).toHaveBeenCalledExactlyOnceWith( + 'text', + { + project: 'my-project', + location: 'europe-west1', + vertexai: true, + }, + 'gemini-3.7-flash', + ) }) it('builds summarize, image, embedding, speech, audio, and video adapters', () => { @@ -66,13 +83,42 @@ describe('vertex factories', () => { expect(vertexAudio('lyria-3-pro-preview', auth).name).toBe('gemini') expect(vertexVideo('veo-3.1-generate-preview', auth).name).toBe('gemini') - expect(mocks.constructorSpy).toHaveBeenCalledTimes(6) - for (const call of mocks.constructorSpy.mock.calls) { - expect(call[0]).toMatchObject({ - project: 'my-project', - location: 'europe-west1', - vertexai: true, - }) - } + expect(mocks.constructorSpy.mock.calls).toEqual([ + [ + 'text', + { project: 'my-project', location: 'europe-west1', vertexai: true }, + 'gemini-3.7-flash', + ], + [ + 'image', + { project: 'my-project', location: 'europe-west1', vertexai: true }, + 'gemini-3.1-flash-image', + ], + [ + 'embedding', + { project: 'my-project', location: 'europe-west1', vertexai: true }, + 'gemini-embedding-001', + ], + [ + 'speech', + { project: 'my-project', location: 'europe-west1', vertexai: true }, + 'gemini-3.1-flash-tts-preview', + ], + [ + 'audio', + { project: 'my-project', location: 'europe-west1', vertexai: true }, + 'lyria-3-pro-preview', + ], + [ + 'video', + { + project: 'my-project', + location: 'europe-west1', + vertexai: true, + allowUrlFetch: undefined, + }, + 'veo-3.1-generate-preview', + ], + ]) }) }) diff --git a/packages/ai-vue/src/use-chat.ts b/packages/ai-vue/src/use-chat.ts index 8e748f1a75..a51299cccd 100644 --- a/packages/ai-vue/src/use-chat.ts +++ b/packages/ai-vue/src/use-chat.ts @@ -149,48 +149,28 @@ export function useChat< ...(options.streamProcessor !== undefined && { streamProcessor: options.streamProcessor, }), - onMessagesChange: (newMessages: Array>) => { - messages.value = newMessages - }, - onLoadingChange: (newIsLoading: boolean) => { - isLoading.value = newIsLoading - syncResumeState() - }, - onStatusChange: (newStatus: ChatClientState) => { - status.value = newStatus - }, - onErrorChange: (newError: Error | undefined) => { - error.value = newError - }, - onSubscriptionChange: (nextIsSubscribed: boolean) => { - isSubscribed.value = nextIsSubscribed - }, - onConnectionStatusChange: (nextStatus: ConnectionStatus) => { - connectionStatus.value = nextStatus - }, - onSessionGeneratingChange: (isGenerating: boolean) => { - sessionGenerating.value = isGenerating - }, ...(options.queue !== undefined && { queue: options.queue }), - onQueueChange: (nextQueue: Array) => { - queue.value = nextQueue - }, - onRunIdChange: (nextRunId) => { - runId.value = nextRunId - }, onInterruptStateChange: (nextInterruptState, context) => { - interruptState.value = nextInterruptState options.onInterruptStateChange?.(nextInterruptState, context) }, }) - function syncResumeState() { - runId.value = client.getCurrentRunId() - interruptState.value = client.getInterruptState() + function applySnapshot() { + const next = client.getSnapshot() + messages.value = next.messages + isLoading.value = next.isLoading + error.value = next.error + status.value = next.status + isSubscribed.value = next.isSubscribed + connectionStatus.value = next.connectionStatus + sessionGenerating.value = next.sessionGenerating + queue.value = next.queue + runId.value = next.runId + interruptState.value = next.interruptState } - messages.value = client.getMessages() - interruptState.value = client.getInterruptState() + applySnapshot() + onScopeDispose(client.subscribeSnapshot(applySnapshot)) // Sync body / forwardedProps changes to the client. // Both populate the same wire payload; `forwardedProps` is preferred @@ -236,10 +216,6 @@ export function useChat< // connections per origin until the page reloaded. client.attach() client.mountDevtools() - // Delivery-durability resume is transparent: the resumable SSE connection - // adapter reattaches via the browser's native Last-Event-ID on reconnect. - // We only seed interrupt (state) resume from the client here. - syncResumeState() }) // Cleanup on unmount: stop any in-flight requests @@ -263,29 +239,17 @@ export function useChat< content: string | MultimodalContent, sendOptions?: SendMessageOptions, ) => { - try { - await client.sendMessage(content, undefined, sendOptions) - } finally { - syncResumeState() - } + await client.sendMessage(content, undefined, sendOptions) } const cancelQueued = (id: string) => client.cancelQueued(id) const append = async (message: ModelMessage | UIMessage) => { - try { - await client.append(message) - } finally { - syncResumeState() - } + await client.append(message) } const reload = async () => { - try { - await client.reload() - } finally { - syncResumeState() - } + await client.reload() } const stop = () => { @@ -294,7 +258,6 @@ export function useChat< const clear = () => { client.clear() - syncResumeState() } const setMessagesManually = (newMessages: Array>) => { @@ -316,16 +279,13 @@ export function useChat< approved: boolean }) => { await client.addToolApprovalResponse(response) - syncResumeState() } const resumeInterrupts = async ( resumeItems: Array, state?: ChatResumeState, ) => { - const result = await client.resumeInterrupts(resumeItems, state) - syncResumeState() - return result + return await client.resumeInterrupts(resumeItems, state) } const interrupts = computed(() => interruptState.value.interrupts) diff --git a/packages/ai-vue/src/use-generate-video.ts b/packages/ai-vue/src/use-generate-video.ts index fb2ac7a2a7..66ae22cd0a 100644 --- a/packages/ai-vue/src/use-generate-video.ts +++ b/packages/ai-vue/src/use-generate-video.ts @@ -231,34 +231,6 @@ export function useGenerateVideo( onStatusUpdate: (s: VideoStatusInfo) => { if (!disposed) options.onStatusUpdate?.(s) }, - onResultChange: (r: TOutput | null) => { - if (disposed) return - result.value = r - }, - onLoadingChange: (l: boolean) => { - if (disposed) return - isLoading.value = l - }, - onErrorChange: (e: Error | undefined) => { - if (disposed) return - error.value = e - }, - onStatusChange: (s: GenerationClientState) => { - if (disposed) return - status.value = s - }, - onJobIdChange: (id: string | null) => { - if (disposed) return - jobId.value = id - }, - onVideoStatusChange: (s: VideoStatusInfo | null) => { - if (disposed) return - videoStatus.value = s - }, - onResumeStateChange: (rs: { runId: string } | null) => { - if (disposed) return - runId.value = rs?.runId ?? null - }, } let client: VideoGenerationClient @@ -279,6 +251,19 @@ export function useGenerateVideo( ) } + const applySnapshot = () => { + const next = client.getSnapshot() + result.value = next.result + isLoading.value = next.isLoading + error.value = next.error + status.value = next.status + runId.value = next.runId + jobId.value = next.jobId + videoStatus.value = next.videoStatus + } + applySnapshot() + onScopeDispose(client.subscribe(applySnapshot)) + // Sync body changes to the client. // Conditional spread: `updateOptions` declares `body?: Record` // (strict optional) and rejects explicit `undefined` under EOPT. diff --git a/packages/ai-vue/src/use-generation.ts b/packages/ai-vue/src/use-generation.ts index cbec8f435f..2095da2d28 100644 --- a/packages/ai-vue/src/use-generation.ts +++ b/packages/ai-vue/src/use-generation.ts @@ -220,26 +220,6 @@ export function useGeneration< onChunk: (c: StreamChunk) => { if (!disposed) options.onChunk?.(c) }, - onResultChange: (r: TOutput | null) => { - if (disposed) return - result.value = r - }, - onLoadingChange: (l: boolean) => { - if (disposed) return - isLoading.value = l - }, - onErrorChange: (e: Error | undefined) => { - if (disposed) return - error.value = e - }, - onStatusChange: (s: GenerationClientState) => { - if (disposed) return - status.value = s - }, - onResumeStateChange: (rs) => { - if (disposed) return - runId.value = rs?.runId ?? null - }, } const persistenceProps = @@ -274,6 +254,17 @@ export function useGeneration< ) } + const applySnapshot = () => { + const next = client.getSnapshot() + result.value = next.result + isLoading.value = next.isLoading + error.value = next.error + status.value = next.status + runId.value = next.runId + } + applySnapshot() + onScopeDispose(client.subscribe(applySnapshot)) + // Sync body changes to the client. // Conditional spread: `updateOptions` declares `body?: Record` // (strict optional) and rejects explicit `undefined` under EOPT. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 827b407241..41cfa800f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,11 +195,11 @@ importers: specifier: workspace:* version: link:../../packages/ai-sandbox-local-process '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) lucide-react: specifier: ^0.561.0 version: 0.561.0(react@19.2.3) @@ -235,8 +235,8 @@ importers: specifier: ^4.1.18 version: 4.1.18(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@types/node': specifier: ^24.10.1 version: 24.10.3 @@ -283,11 +283,11 @@ importers: specifier: workspace:* version: link:../../packages/ai-sandbox-docker '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) lucide-react: specifier: ^0.561.0 version: 0.561.0(react@19.2.3) @@ -320,8 +320,8 @@ importers: specifier: ^4.1.18 version: 4.1.18(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@types/node': specifier: ^24.10.1 version: 24.10.3 @@ -471,17 +471,17 @@ importers: specifier: workspace:* version: link:../../packages/ai-react '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/store': - specifier: ^0.8.0 - version: 0.8.0 + specifier: ^0.11.1 + version: 0.11.1 framer-motion: specifier: ^11.15.0 version: 11.18.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -580,17 +580,17 @@ importers: specifier: ^0.9.10 version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-router-devtools': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) capnweb: specifier: ^0.10.0 version: 0.10.0 @@ -761,26 +761,26 @@ importers: specifier: ^0.9.10 version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-router-devtools': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-router-ssr-query': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/react-store': - specifier: ^0.8.0 - version: 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^0.11.1 + version: 0.11.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/store': - specifier: ^0.8.0 - version: 0.8.0 + specifier: ^0.11.1 + version: 0.11.1 dockerode: specifier: ^4.0.2 version: 4.0.12(supports-color@7.2.0) @@ -897,14 +897,14 @@ importers: specifier: workspace:* version: link:../../packages/ai-react '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) lucide-react: specifier: ^0.561.0 version: 0.561.0(react@19.2.3) @@ -1001,14 +1001,14 @@ importers: specifier: workspace:* version: link:../../packages/ai-openrouter '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) lucide-react: specifier: ^0.561.0 version: 0.561.0(react@19.2.3) @@ -1074,23 +1074,23 @@ importers: specifier: ^5.90.12 version: 5.90.12(react@19.2.3) '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-router-devtools': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-router-ssr-query': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/zod-adapter': specifier: ^1.140.1 - version: 1.166.9(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6) + version: 1.166.9(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1201,8 +1201,8 @@ importers: specifier: ^1.155.0 version: 1.155.0(@electric-sql/pglite@0.4.3)(aws4fetch@1.0.20)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260317.1)(@electric-sql/pglite@0.4.3)(@opentelemetry/api@1.9.1)(@prisma/client@7.9.0(prisma@7.9.0(@types/react@19.2.18)(better-sqlite3@12.11.1)(magicast@0.5.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(better-sqlite3@12.11.1)(bun-types@1.3.14)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.9.0(@types/react@19.2.18)(better-sqlite3@12.11.1)(magicast@0.5.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mysql2@3.15.3)(rolldown@1.1.5)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-plugin': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.35 + version: 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/solid-ai-devtools': specifier: workspace:* version: link:../../packages/solid-ai-devtools @@ -1210,23 +1210,23 @@ importers: specifier: ^0.7.15 version: 0.7.17(csstype@3.2.3)(solid-js@1.9.10) '@tanstack/solid-router': - specifier: ^1.139.10 - version: 1.141.1(solid-js@1.9.10) + specifier: ^1.170.30 + version: 1.170.30(solid-js@1.9.10) '@tanstack/solid-router-devtools': - specifier: ^1.139.10 - version: 1.141.1(@tanstack/router-core@1.159.4)(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(csstype@3.2.3)(solid-js@1.9.10) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/router-core@1.171.27)(@tanstack/solid-router@1.170.30(solid-js@1.9.10))(csstype@3.2.3)(solid-js@1.9.10) '@tanstack/solid-router-ssr-query': - specifier: ^1.139.10 - version: 1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(eslint@9.39.4(jiti@2.7.0))(solid-js@1.9.10)(supports-color@7.2.0)(typescript@5.9.3) + specifier: ^1.167.1 + version: 1.167.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.171.27)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.170.30(solid-js@1.9.10))(solid-js@1.9.10) '@tanstack/solid-start': - specifier: ^1.139.10 - version: 1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(solid-js@1.9.10)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.47 + version: 1.168.47(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(solid-js@1.9.10)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/solid-store': specifier: ^0.8.0 version: 0.8.0(solid-js@1.9.10) '@tanstack/store': - specifier: ^0.8.0 - version: 0.8.0 + specifier: ^0.11.1 + version: 0.11.1 highlight.js: specifier: ^11.11.1 version: 11.11.1 @@ -1657,6 +1657,9 @@ importers: '@tanstack/ai-utils': specifier: workspace:^ version: link:../ai-utils + '@tanstack/store': + specifier: ^0.11.1 + version: 0.11.1 devDependencies: '@standard-schema/spec': specifier: ^1.1.0 @@ -2718,9 +2721,6 @@ importers: specifier: workspace:^ version: link:../ai-gemini devDependencies: - '@google/genai': - specifier: ^2.10.0 - version: 2.10.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)) '@tanstack/ai': specifier: workspace:* version: link:../ai @@ -3008,11 +3008,11 @@ importers: specifier: workspace:* version: link:../../packages/react-ai-devtools '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) arktype: specifier: ^2.1.28 version: 2.2.0 @@ -3129,11 +3129,11 @@ importers: specifier: ^0.9.10 version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) '@tanstack/react-router': - specifier: ^1.158.4 - version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.170.32 + version: 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.159.0 - version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + specifier: ^1.168.49 + version: 1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/start': specifier: ^1.120.20 version: 1.120.20(340eefabdbae5b0c948fd6f8943277d1) @@ -4063,10 +4063,6 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} @@ -4083,10 +4079,6 @@ packages: resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -5913,9 +5905,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nothing-but/utils@0.17.0': - resolution: {integrity: sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ==} - '@nx/nx-darwin-arm64@23.1.1': resolution: {integrity: sha512-Rq/RXLX5uIvJQfb6kuUgEirquT5ARaAgQyNaMZVnOPAL5wuxaDvQag8WX/WUCIgbUKZuGkclJwM7Vlnvtn3bdg==} cpu: [arm64] @@ -8216,9 +8205,6 @@ packages: '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rolldown/pluginutils@1.0.0-beta.40': - resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==} - '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -8668,26 +8654,6 @@ packages: resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} engines: {node: '>=14.0.0'} - '@solid-devtools/debugger@0.28.1': - resolution: {integrity: sha512-6qIUI6VYkXoRnL8oF5bvh2KgH71qlJ18hNw/mwSyY6v48eb80ZR48/5PDXufUa3q+MBSuYa1uqTMwLewpay9eg==} - peerDependencies: - solid-js: ^1.9.0 - - '@solid-devtools/logger@0.9.11': - resolution: {integrity: sha512-THbiY1iQlieL6vdgJc4FIsLe7V8a57hod/Thm8zdKrTkWL88UPZjkBBfM+mVNGusd4OCnAN20tIFBhNnuT1Dew==} - peerDependencies: - solid-js: ^1.9.0 - - '@solid-devtools/shared@0.20.0': - resolution: {integrity: sha512-o5TACmUOQsxpzpOKCjbQqGk8wL8PMi+frXG9WNu4Lh3PQVUB6hs95Kl/S8xc++zwcMguUKZJn8h5URUiMOca6Q==} - peerDependencies: - solid-js: ^1.9.0 - - '@solid-primitives/bounds@0.1.3': - resolution: {integrity: sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q==} - peerDependencies: - solid-js: ^1.6.12 - '@solid-primitives/event-listener@2.4.3': resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==} peerDependencies: @@ -8698,11 +8664,6 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/media@2.3.3': - resolution: {integrity: sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA==} - peerDependencies: - solid-js: ^1.6.12 - '@solid-primitives/refs@1.1.2': resolution: {integrity: sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg==} peerDependencies: @@ -8718,21 +8679,11 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/scheduled@1.5.2': - resolution: {integrity: sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA==} - peerDependencies: - solid-js: ^1.6.12 - '@solid-primitives/static-store@0.1.2': resolution: {integrity: sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/styles@0.1.2': - resolution: {integrity: sha512-7iX5K+J5b1PRrbgw3Ki92uvU2LgQ0Kd/QMsrAZxDg5dpUBwMyTijZkA3bbs1ikZsT1oQhS41bTyKbjrXeU0Awg==} - peerDependencies: - solid-js: ^1.6.12 - '@solid-primitives/utils@6.3.2': resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} peerDependencies: @@ -9024,6 +8975,10 @@ packages: resolution: {integrity: sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA==} engines: {node: '>=12'} + '@tanstack/history@1.162.1': + resolution: {integrity: sha512-DR9t6lfLVdrjgCwpglrR9DR7Ok8/HlXjcOE+goWXF3zyuLUO/ug7vMbSFxTqrQTtbRghJfyhmIZ0S6LhPIy44w==} + engines: {node: '>=20.19'} + '@tanstack/nitro-v2-vite-plugin@1.155.0': resolution: {integrity: sha512-iW9S2c4QxlT+WgKVT+Mw8hzvLqyVSg4UefzX74LX5dfUxbGDahxZItp+07JSD5Ok8ZdTlUHzwLnIsncXh/MNAQ==} engines: {node: '>=22.12'} @@ -9062,21 +9017,21 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-devtools@1.159.5': - resolution: {integrity: sha512-IIyomu+ypWTxyoYT32mxamVmdTs7ZCGcTbdj7HVvtD3xp1lvo/bwRXj9oERENmb+OAPOaWF2doRYC/pmKjK5vg==} - engines: {node: '>=12'} + '@tanstack/react-router-devtools@1.167.1': + resolution: {integrity: sha512-pjfGrmjj4d7naEPM7oshqFfwBoxDPNo/UxltlHH5ePbHsJ+plBhd+JaAewm1ueYOjZ0js9hckjWWDYXpCrSfKw==} + engines: {node: '>=20.19'} peerDependencies: - '@tanstack/react-router': ^1.159.5 - '@tanstack/router-core': ^1.159.4 + '@tanstack/react-router': ^1.170.19 + '@tanstack/router-core': ^1.171.16 react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' peerDependenciesMeta: '@tanstack/router-core': optional: true - '@tanstack/react-router-ssr-query@1.159.5': - resolution: {integrity: sha512-r/Um9DASjmaTdiyXA3cpooGLrSD1BlcXUNsRz4zAlvD38m0gIFvaykOM2Naztqm9H+2CJkocRZp2/kR69Xcmyg==} - engines: {node: '>=12'} + '@tanstack/react-router-ssr-query@1.167.1': + resolution: {integrity: sha512-W9j5JPnBikyafvuUfykFfHIWod58OAbAAa5leNkXBcoDoocghMmu6w9uZOmUZvAWT7CSvgj5tBUtF7CM2OoHXQ==} + engines: {node: '>=20.19'} peerDependencies: '@tanstack/query-core': '>=5.90.0' '@tanstack/react-query': '>=5.90.0' @@ -9091,9 +9046,9 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.159.5': - resolution: {integrity: sha512-rVb0MtKzP5c0BkWIoFgWBiRAJHYSU3bhsEHbT0cRdRLmlJiw21Awb6VEjgYq3hJiEhowcKKm6J8AdRD/8oZ5dQ==} - engines: {node: '>=12'} + '@tanstack/react-router@1.170.32': + resolution: {integrity: sha512-SIpxvaTKco100a5ZR3ePmArbhtm3XOx+w1dpGYY9gxHDta4iXSKDdQuhLonwJbIMkVJsU1rwXf0UDHMrF/1snw==} + engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' @@ -9105,8 +9060,8 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.159.5': - resolution: {integrity: sha512-Qynx7XWHI1rhVpSUx6P40zazcQVJRhl2fnAcSH0I6vaAxSuZm8lvI37YacXPRu8flvI/ZGlF095arxiyps+A0w==} + '@tanstack/react-start-client@1.168.30': + resolution: {integrity: sha512-qsZuykUl1EF0/rc1bin1RtjFzz07YMOTBzhOstSDzbOVm/WKf1QKFTN+qAZi74xlbXSWNuT2MiFRyg4RKwj8iw==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -9123,6 +9078,23 @@ packages: resolution: {integrity: sha512-z+4YL6shTtsHjk32yaIemQwgkx6FcqwPBYfeNt7Co2eOpWrvsoo/Fe9869/oIY2sPyhiWDs1rDb3e0qnAy8Cag==} engines: {node: '>=12'} + '@tanstack/react-start-rsc@0.1.48': + resolution: {integrity: sha512-UglRdTMuF3c4dvzL/gh4dMVbMWHsPy8ZgTQdT2qpTlyt1b/3m+R40tBFdsfTgw76VTXivW+qV/ih0PN9000XTw==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@rspack/core': '>=2.0.0-0' + '@vitejs/plugin-rsc': '>=0.5.30' + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + react-server-dom-rspack: '>=0.0.2' + peerDependenciesMeta: + '@rspack/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + react-server-dom-rspack: + optional: true + '@tanstack/react-start-server@1.141.1': resolution: {integrity: sha512-GsZ7De5CCDkKcrimvwJiYJHMFGw7LQRu8IuLvqyzYgQvJPcbkfY5UIQd2IZfgRNFegzb2aYzkAXwv5lxfdu1VQ==} engines: {node: '>=22.12.0'} @@ -9130,20 +9102,35 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-server@1.159.5': - resolution: {integrity: sha512-X4/SunwDTEbGkYTfM0gR+79amfk6phAM+2+NlC2s7TX2cD51xE8bUz2a//RxfOh9xg8f0f2CRIO34xTEDHGTfQ==} + '@tanstack/react-start-server@1.167.37': + resolution: {integrity: sha512-cODHpFU8vIm7AdHii9W3NEuwyruNmT5wLDZjRsJLT9Jp+7FACnfJrRbvxnp1ldSv/9mxcHKi/OgwbdHQeMZcAQ==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start@1.159.5': - resolution: {integrity: sha512-vfnF7eYswAK54ru6Ay08nb0TXVzTBaVRsbbRW7hx2M0chgwtSx+YScYzoixqkccRARQBN8a/CeVq7vNFW8525w==} + '@tanstack/react-start@1.168.49': + resolution: {integrity: sha512-iQb1ZoEHqvZMGLR4G7v3tTtgL06yv/bwxvGP3waVHxVn7bRpyopM44YbOluaGkcJzc13ZTvdLKWYNAN3bxMX/Q==} engines: {node: '>=22.12.0'} peerDependencies: + '@rsbuild/core': ^2.0.0 + '@vitejs/plugin-rsc': '*' react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@vitejs/plugin-rsc': + optional: true + vite: + optional: true + + '@tanstack/react-store@0.11.1': + resolution: {integrity: sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@tanstack/react-store@0.8.0': resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} @@ -9151,6 +9138,12 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/router-core@1.131.50': resolution: {integrity: sha512-eojd4JZ5ziUhGEmXZ4CaVX5mQdiTMiz56Sp8ZQ6r7deb55Q+5G4JQDkeuXpI7HMAvzr+4qlsFeLaDRXXjXyOqQ==} engines: {node: '>=12'} @@ -9163,22 +9156,15 @@ packages: resolution: {integrity: sha512-MFzPH39ijNO83qJN3pe7x4iAlhZyqgao3sJIzv3SJ4Pnk12xMnzuDzIAQT/1WV6JolPQEcw0Wr4L5agF8yxoeg==} engines: {node: '>=12'} - '@tanstack/router-devtools-core@1.141.1': - resolution: {integrity: sha512-wD9yRvOk6FI+thiNBplhkGutPIPBlXvWu9ttU/obdFY5oXQj9WYgNS+IO9BEe8Pz5rNEu8zE/oLn4RUGIVdtnw==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/router-core': ^1.141.1 - csstype: ^3.0.10 - solid-js: '>=1.9.5' - peerDependenciesMeta: - csstype: - optional: true + '@tanstack/router-core@1.171.27': + resolution: {integrity: sha512-wDwSLvoLwIaNcnx9UNcN9Mb7Y8QwCYq1U1RQZwyN186gnkIoIYI2SOxy8VqH1vFigbkHkk4FmwMAQlghPgDK2g==} + engines: {node: '>=20.19'} - '@tanstack/router-devtools-core@1.159.4': - resolution: {integrity: sha512-qMUeIv+6n1mZOcO2raCIbdOeDeMpJEmgm6oMs/nWEG61lYrzJYaCcpBTviAX0nRhSiQSUCX9cHiosUEA0e2HAw==} - engines: {node: '>=12'} + '@tanstack/router-devtools-core@1.168.1': + resolution: {integrity: sha512-qr4voa4cpSMwQvS3867xkU3AB3MtJbTuovKIy+btjJ/Faju6er9w0nDylmD+005Mk/3YKw9/iueZJl2JAB7JOA==} + engines: {node: '>=20.19'} peerDependencies: - '@tanstack/router-core': ^1.159.4 + '@tanstack/router-core': ^1.171.16 csstype: ^3.0.10 peerDependenciesMeta: csstype: @@ -9188,14 +9174,14 @@ packages: resolution: {integrity: sha512-zlMBw5l88GIg3v+378JsfDYq3ejEaJmD3P1R+m0yEPxh0N//Id1FjKNSS7yJbejlK2WGVm9DUG46iBdTDMQM+Q==} engines: {node: '>=12'} - '@tanstack/router-generator@1.141.1': - resolution: {integrity: sha512-21RbVAoIDn7s/n/PKMN6U60d5hCeVADrBH/uN6B/poMT4MVYtJXqISVzkc2RAboVRw6eRdYFeF+YlwA3nF6y3Q==} - engines: {node: '>=12'} - '@tanstack/router-generator@1.159.4': resolution: {integrity: sha512-O8tICQoSuvK6vs3mvBdI3zVLFmYfj/AYDCX0a5msSADP/2S0GsgDDTB5ah731TqYCtjeNriaWz9iqst38cjF/Q==} engines: {node: '>=12'} + '@tanstack/router-generator@1.167.33': + resolution: {integrity: sha512-Z3lCWIPuRUMPmuI8Mm48x/s49TxmHOaFVZ52j1W1QKYrsFHyT6U/h9bqfHJDxfQ8kz7y9q+W1YPKZ15Ee7yuCA==} + engines: {node: '>=20.19'} + '@tanstack/router-plugin@1.131.50': resolution: {integrity: sha512-gdEBPGzx7llQNRnaqfPJ1iaPS3oqB8SlvKRG5l7Fxp4q4yINgkeowFYSKEhPOc9bjoNhGrIHOlvPTPXEzAQXzQ==} engines: {node: '>=12'} @@ -9217,12 +9203,12 @@ packages: webpack: optional: true - '@tanstack/router-plugin@1.141.1': - resolution: {integrity: sha512-SoNXm8QK8cqX1Q4y1AfLU0tZLOt4mB4wkdpBI/Mi3ZEezF8tIrmaenyJ3987cjT9jedTy3VDBab1wN0g8MoOXg==} + '@tanstack/router-plugin@1.159.5': + resolution: {integrity: sha512-i2LR3WRaBOAZ1Uab5QBG9UxZIRJ3V56JVu890NysbuX15rgzRiL5yLAbfenOHdhaHy2+4joX35VICAHuVWy7Og==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.141.1 + '@tanstack/react-router': ^1.159.5 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' vite-plugin-solid: ^2.11.10 webpack: '>=5.92.0' @@ -9238,14 +9224,14 @@ packages: webpack: optional: true - '@tanstack/router-plugin@1.159.5': - resolution: {integrity: sha512-i2LR3WRaBOAZ1Uab5QBG9UxZIRJ3V56JVu890NysbuX15rgzRiL5yLAbfenOHdhaHy2+4joX35VICAHuVWy7Og==} - engines: {node: '>=12'} + '@tanstack/router-plugin@1.168.35': + resolution: {integrity: sha512-foDAZKFqHXae+oFbIgcsSvy2QCVRn7XdS3nhwcRvD+ed6JrKPUP/1lQMsZLJqWycgR1vkZF7gs955KGa0NZQ0w==} + engines: {node: '>=20.19'} peerDependencies: - '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.159.5 - vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' - vite-plugin-solid: ^2.11.10 + '@rsbuild/core': '>=1.0.2 || ^2.0.0' + '@tanstack/react-router': ^1.170.32 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 webpack: '>=5.92.0' peerDependenciesMeta: '@rsbuild/core': @@ -9259,16 +9245,9 @@ packages: webpack: optional: true - '@tanstack/router-ssr-query-core@1.141.1': - resolution: {integrity: sha512-bkRXUhktifxBewnnphH59E0sGcsUI1NmNqxzCAmXIb93xYgafhjUGGYwfK6FqFBOmCB5isr32exGO3+UMHJr/A==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/query-core': '>=5.90.0' - '@tanstack/router-core': '>=1.127.0' - - '@tanstack/router-ssr-query-core@1.159.4': - resolution: {integrity: sha512-zqgJaf8iTk2ugrJqvU+b8NnFf3MKcnFaurtlp5tP38lNHQGW4En9UaOBOou/6zYZMlKQBcvqxSGJiwaLIyTK2g==} - engines: {node: '>=12'} + '@tanstack/router-ssr-query-core@1.169.1': + resolution: {integrity: sha512-rngux8s/3mPQzcjLYDLkNU31coYVyCgrVTfpdwqUdY5jIEHqGTXrO73DTkPR1PppwYUeVhmNCgl8TctRcnupjg==} + engines: {node: '>=20.19'} peerDependencies: '@tanstack/query-core': '>=5.90.0' '@tanstack/router-core': '>=1.127.0' @@ -9285,6 +9264,10 @@ packages: resolution: {integrity: sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg==} engines: {node: '>=12'} + '@tanstack/router-utils@1.162.2': + resolution: {integrity: sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ==} + engines: {node: '>=20.19'} + '@tanstack/server-functions-plugin@1.131.2': resolution: {integrity: sha512-hWsaSgEZAVyzHg8+IcJWCEtfI9ZSlNELErfLiGHG9XCHEXMegFWsrESsKHlASzJqef9RsuOLDl+1IMPIskwdDw==} engines: {node: '>=12'} @@ -9304,50 +9287,56 @@ packages: peerDependencies: solid-js: ^1.6.0 - '@tanstack/solid-router-devtools@1.141.1': - resolution: {integrity: sha512-ezm8V1UR47kY/xquIobascexHkbWOcH1YXc8BRvYDKV33NEdeUhMVUqkZmwuojlRYXhF7LEfj2aSwZr0uBfH8A==} - engines: {node: '>=12'} + '@tanstack/solid-router-devtools@1.167.1': + resolution: {integrity: sha512-GZiRbHx7E1vbuv1zGjPl1EmCKbwKZKoCU0TjycBjU6SW0rMt/24SON0MbtMbPlLs3XE28jFi7C5odwE2hryh/Q==} + engines: {node: '>=20.19'} peerDependencies: - '@tanstack/router-core': ^1.141.1 - '@tanstack/solid-router': ^1.141.1 + '@tanstack/router-core': ^1.171.16 + '@tanstack/solid-router': ^1.170.19 solid-js: ^1.9.10 peerDependenciesMeta: '@tanstack/router-core': optional: true - '@tanstack/solid-router-ssr-query@1.141.1': - resolution: {integrity: sha512-jM2RKblxWbrMV2nmZ+L+Y2HvfBZ448tZHXYBP/NrQa2YAHVXCZfVE5ojjB5vyDZHc7ueFAF8+yIvMGnIQQE99g==} - engines: {node: '>=12'} + '@tanstack/solid-router-ssr-query@1.167.1': + resolution: {integrity: sha512-N76V3oz1ZQlTveBTRpLGOTlUtXU5GKOD++IkMnLBtuHEsge9ReluB94QBgVA+ElpQgdeso9uKUs/JsPluAynUA==} + engines: {node: '>=20.19'} peerDependencies: '@tanstack/query-core': '>=5.90.0' '@tanstack/solid-query': '>=5.90.0' '@tanstack/solid-router': '>=1.127.0' solid-js: ^1.9.10 - '@tanstack/solid-router@1.141.1': - resolution: {integrity: sha512-xcWkYdwvTXzQwLd4gI0efq7dUzXlcTyent/UDrGf1K5dDBqlFSAj9c11vNwP0+YvCaPt4U1r8czyMD1Qr3aNGA==} - engines: {node: '>=12'} + '@tanstack/solid-router@1.170.30': + resolution: {integrity: sha512-FozWeln6hx6o2T45gFFdkGQJxvq/LN0EUlzx7+eENy9HJH/thEBSb89KTkQahULzs9bsMOufzeXJJVgB6H9/wA==} + engines: {node: '>=20.19'} peerDependencies: solid-js: ^1.9.10 - '@tanstack/solid-start-client@1.141.1': - resolution: {integrity: sha512-NGc70Jj9DoWFieFX9iQG70fmuTvX7nfTUG5vlboxBzvTuPrIdyeddrQVnBX/kXUdFzLGzez7QlxqyOrm3zouuQ==} + '@tanstack/solid-start-client@1.168.29': + resolution: {integrity: sha512-+F3Z/J1SVxPj2Hs53Wvos2DhIe7cGvlHpwZD9x0+aIWofIlkRSnrGm2wehWZzun1tzgZNnK2L2thkQlXVtuVaw==} engines: {node: '>=22.12.0'} peerDependencies: solid-js: '>=1.0.0' - '@tanstack/solid-start-server@1.141.1': - resolution: {integrity: sha512-vIuw74+6cgBmh1zAqc27dKHd5rU2K4V6nHt+u7vBKUUu/9QIF7PoQD0fj2dWnrjnz7EJKb1W7Gr9rAtOdglgGw==} + '@tanstack/solid-start-server@1.167.36': + resolution: {integrity: sha512-7WMqLxe1Yr+9Q+Ze2dsYrPnousiPmA5LndJtg1MnaIRNecL1NInd5jsiIwNB42qOWTwcGr8aD8LnHH4dcVh/wA==} engines: {node: '>=22.12.0'} peerDependencies: solid-js: ^1.0.0 - '@tanstack/solid-start@1.141.1': - resolution: {integrity: sha512-b7B7UUPn3GANvnvTusiryTr+CUEs+lMMFRRiDiBZXx+Yb1cqtp/UYilEDHdqnx2xUoAhR0FBd4kRcieCV69DVg==} + '@tanstack/solid-start@1.168.47': + resolution: {integrity: sha512-yqXrj5Q1wGh/9Gb/wGp117AavC48tFk29LP/2oGHBJKq4ZATYes8s3yPBRSWlSkqQG9N2yQzjmF+sutXZSZVzw==} engines: {node: '>=22.12.0'} peerDependencies: + '@rsbuild/core': ^2.0.0 solid-js: '>=1.0.0' vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true '@tanstack/solid-store@0.8.0': resolution: {integrity: sha512-JwqTedbxyOGw7mfmdGkB0RGgefRCw/tNauc8tlMcaS1mV5wTFT8c1KIB3LgttuHaanMJEBeqQJ7bc/R0WTP1fA==} @@ -9370,6 +9359,10 @@ packages: resolution: {integrity: sha512-9j2i1PRTIGcYAD+509znve0ngK81ZUfbX4XCpoNFMaUUpRHoEPPK5I9+PzLFvL9sNOto67x+WULCUggzX+lEKQ==} engines: {node: '>=22.12.0'} + '@tanstack/start-client-core@1.170.27': + resolution: {integrity: sha512-Ro6ZSM0NgYKDMxM0e8qyU4mBfnld2Zb74BA/9f4i35C0Y3IAA8Zxs/DIPfOo9VRYWp5c5n5Q8dRBn2qn0vtPhQ==} + engines: {node: '>=22.12.0'} + '@tanstack/start-config@1.120.20': resolution: {integrity: sha512-oH/mfTSHV8Qbil74tWicPLW6+kKmT3esXCnDzvrkhi3+N8ZuVUDr01Qpil0Wxf9lLPfM5L6VX03nF4hSU8vljg==} engines: {node: '>=12'} @@ -9382,23 +9375,27 @@ packages: resolution: {integrity: sha512-D69B78L6pcFN5X5PHaydv7CScQcKLzJeEYqs7jpuyyqGQHSUIZUjS955j+Sir8cHhuDIovCe2LmsYHeZfWf3dQ==} engines: {node: '>=22.12.0'} + '@tanstack/start-fn-stubs@1.162.0': + resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} + engines: {node: '>=22.12.0'} + '@tanstack/start-plugin-core@1.131.50': resolution: {integrity: sha512-eFvMA0chqLtHbq+8ojp1fXN7AQjhmeoOpQaZaU1d51wb7ugetrn0k3OuHblxtE/O0L4HEC9s4X5zmFJt0vLh0w==} engines: {node: '>=12'} peerDependencies: vite: '>=6.0.0' - '@tanstack/start-plugin-core@1.141.1': - resolution: {integrity: sha512-jXfgKeM4XX7aoP7WF/cJZH52N0ewIYRdP028ItXaSwUMcDyBO+PPTNbSEqTu3zbzLsmTOOyuJEpIrVA0/lmbjA==} - engines: {node: '>=22.12.0'} - peerDependencies: - vite: '>=7.0.0' - - '@tanstack/start-plugin-core@1.159.5': - resolution: {integrity: sha512-QGiBw+L3qu2sUY0Tg9KovxjDSi5kevoANEcq9RLX7iIhLkTjrILN6hnAlXZUzqk5Egaf0aN2yWhwI4HWucMprw==} + '@tanstack/start-plugin-core@1.171.39': + resolution: {integrity: sha512-Zyj6G4MDFLXcHYhPavhewCBo8dsxi3qvPk31zl/QtTzYzOY9rJHDDBGarKsJq20ziChmkGn/sttYqb4vGCxJDA==} engines: {node: '>=22.12.0'} peerDependencies: + '@rsbuild/core': ^2.0.0 vite: '>=7.0.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + vite: + optional: true '@tanstack/start-server-core@1.131.50': resolution: {integrity: sha512-3SWwwhW2GKMhPSaqWRal6Jj1Y9ObfdWEXKFQid1LBuk5xk/Es4bmW68o++MbVgs/GxUxyeZ3TRVqb0c7RG1sog==} @@ -9412,6 +9409,10 @@ packages: resolution: {integrity: sha512-sGpr+iil+pcY3Gglvbnxaj7fCEPTQJv4oF7YA24SVv8YvayLXtBXpF26miJLA+KR9P31dQdPYe1gTjv5zRyvHg==} engines: {node: '>=22.12.0'} + '@tanstack/start-server-core@1.169.31': + resolution: {integrity: sha512-56w8l+Fao01YCrmv0hzNxL3b3FRmqLRGdE11izZNQsW+1CcSYuehAM5khWKABuI1DI/UIBLGV7BwL4Dlg0eHCw==} + engines: {node: '>=22.12.0'} + '@tanstack/start-server-functions-client@1.131.50': resolution: {integrity: sha512-4aM17fFdVAFH6uLPswKJxzrhhIjcCwKqzfTcgY3OnhUKnaZBTQwJA+nUHQCI6IWvEvrcrNVtFTtv13TkDk3YMw==} engines: {node: '>=12'} @@ -9444,16 +9445,26 @@ packages: resolution: {integrity: sha512-iGkmuCIq3PLI4GKOGwgUNHQKZ13YV8LGq62o2hVnyXE64Jm2SP7c5z6D1ndydpk4JwdRzQKlcOFT/1agvS6Nsg==} engines: {node: '>=22.12.0'} + '@tanstack/start-storage-context@1.167.29': + resolution: {integrity: sha512-8qfprC5774XMRDQlMogkfiGpFLiBf0xDG4bMFUbfkSzpCAQwpLbgGY4Zwft22O9rKYq2vUXusKAdVjvJTUEquQ==} + engines: {node: '>=22.12.0'} + '@tanstack/start@1.120.20': resolution: {integrity: sha512-fQO+O/5xJpli5KlV6pwDz6DtpbqO/0atdVSyVnkemzk0Mej9azm4HXtw+cKkIPtsSplWs4B1EbMtgGMb9ADhSA==} engines: {node: '>=12'} + '@tanstack/store@0.11.1': + resolution: {integrity: sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==} + '@tanstack/store@0.7.7': resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==} '@tanstack/store@0.8.0': resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + '@tanstack/typedoc-config@0.3.4': resolution: {integrity: sha512-WXJavpiNqumNwdBQTM3wBqbHaULgoZ3cIri7xJinKqX/mOGXgc6ZghfAardD2PydP8tOcPf+xhrd1Qk/am9DMg==} engines: {node: '>=18'} @@ -9462,14 +9473,14 @@ packages: resolution: {integrity: sha512-VEEOxc4mvyu67O+Bl0APtYjwcNRcL9it9B4HKbNgcBTIOEalhk+ufBl4kiqc8WP1sx1+NAaiS+3CcJBhrqaSRg==} engines: {node: '>=12'} - '@tanstack/virtual-file-routes@1.141.0': - resolution: {integrity: sha512-CJrWtr6L9TVzEImm9S7dQINx+xJcYP/aDkIi6gnaWtIgbZs1pnzsE0yJc2noqXZ+yAOqLx3TBGpBEs9tS0P9/A==} - engines: {node: '>=12'} - '@tanstack/virtual-file-routes@1.154.7': resolution: {integrity: sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg==} engines: {node: '>=12'} + '@tanstack/virtual-file-routes@1.162.0': + resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} + engines: {node: '>=20.19'} + '@tanstack/vite-config@0.6.0': resolution: {integrity: sha512-TtcetFc7aBm9BBclG3/8pM8ZlyzS8yNwi+eScN4gqsB9xnxyOPCbuvvkk//ybfQj8MVEZavQ03EOQxmOJdW8Rw==} engines: {node: '>=18'} @@ -9750,32 +9761,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.49.0': - resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.59.4': resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.49.0': - resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.59.4': resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.49.0': - resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.59.4': resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -9789,33 +9784,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.49.0': - resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.59.4': resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.49.0': - resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/typescript-estree@8.59.4': resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.49.0': - resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.59.4': resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -9823,10 +9801,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.49.0': - resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.59.4': resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -11018,6 +10992,9 @@ packages: cookie-es@2.0.1: resolution: {integrity: sha512-aVf4A4hI2w70LnF7GG+7xDQUkliwiXWXFvTjkip4+b64ygDQ2sJPRSKFDHbxn8o0xu9QzPkMuuiWIXyFSE2slA==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -11751,13 +11728,6 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-plugin-solid@0.14.5: - resolution: {integrity: sha512-nfuYK09ah5aJG/oEN6P1qziy1zLgW4PDWe75VNPi4CEFYk1x2AEqwFeQfEPR7gNn0F2jOeqKhx2E+5oNCOBYWQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - typescript: '>=4.8.4' - eslint-plugin-unused-imports@4.3.0: resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==} peerDependencies: @@ -12409,6 +12379,16 @@ packages: crossws: optional: true + h3@2.0.1-rc.20: + resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + h3@2.0.1-rc.22: resolution: {integrity: sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==} engines: {node: '>=20.11.1'} @@ -12534,10 +12514,6 @@ packages: html-link-extractor@1.0.5: resolution: {integrity: sha512-ADd49pudM157uWHwHQPUSX4ssMsvR/yHIswOR5CUfBdK9g9ZYGMhVSE6KZVHJ6kCkR0gH4htsfzU6zECDNVwyw==} - html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} - html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -12765,10 +12741,6 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-html@2.0.0: - resolution: {integrity: sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==} - engines: {node: '>=8'} - is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -13092,9 +13064,6 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - kebab-case@1.0.2: - resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -13129,9 +13098,6 @@ packages: knitwork@1.3.0: resolution: {integrity: sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==} - known-css-properties@0.30.0: - resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==} - kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -15304,6 +15270,12 @@ packages: peerDependencies: seroval: ^1.0 + seroval-plugins@1.6.2: + resolution: {integrity: sha512-TfxuUjlbBESzUOWdTkTKqvSmav0ABym+itetDXLK6mDz8SmrpdI30aF8RTXE8Bvq+tH/1yIDkvy3W0lfQb1ipQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + seroval@1.3.2: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} @@ -15316,6 +15288,10 @@ packages: resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} engines: {node: '>=10'} + seroval@1.6.2: + resolution: {integrity: sha512-mPT+SD2TrlB6wvte1KkYOYUkubaTbd6pZ/6Kk3C9nxzrHmCZyhxOO7XGAeL7f+yLKZglzGtM9odUVvg/EhO+vQ==} + engines: {node: '>=10'} + serve-placeholder@2.0.2: resolution: {integrity: sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ==} @@ -15898,12 +15874,6 @@ packages: ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -16229,6 +16199,39 @@ packages: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + unplugin@3.3.0: + resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@farmfe/core': '*' + '@rspack/core': '*' + bun-types-no-globals: '*' + esbuild: '*' + rolldown: '*' + rollup: '*' + unloader: '*' + vite: '*' + webpack: '*' + peerDependenciesMeta: + '@farmfe/core': + optional: true + '@rspack/core': + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true + unrun@0.2.19: resolution: {integrity: sha512-DbwbJ9BvPEb3BeZnIpP9S5tGLO/JIgPQ3JrpMRFIfZMZfMG19f26OlLbC2ml8RRdrI2ZA7z2t+at5tsIHbh6Qw==} engines: {node: '>=20.19.0'} @@ -16629,14 +16632,6 @@ packages: yaml: optional: true - vitefu@1.1.1: - resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 - peerDependenciesMeta: - vite: - optional: true - vitefu@1.1.3: resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} peerDependencies: @@ -17044,6 +17039,9 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zone.js@0.15.1: resolution: {integrity: sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==} @@ -17238,7 +17236,7 @@ snapshots: '@angular/compiler-cli@21.2.17(@angular/compiler@21.2.20)(typescript@5.9.3)': dependencies: '@angular/compiler': 21.2.20 - '@babel/core': 7.29.0(supports-color@7.2.0) + '@babel/core': 7.29.0 '@jridgewell/sourcemap-codec': 1.5.5 chokidar: 5.0.0 convert-source-map: 1.9.0 @@ -17725,9 +17723,9 @@ snapshots: '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 + '@babel/template': 7.29.7 '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@7.2.0) @@ -17745,9 +17743,9 @@ snapshots: '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5(supports-color@7.2.0)) '@babel/helpers': 7.28.4 '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 + '@babel/template': 7.29.7 '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@7.2.0) @@ -17757,12 +17755,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@7.29.0(supports-color@7.2.0)': + '@babel/core@7.29.0': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0(supports-color@7.2.0)) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.0) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 @@ -17800,7 +17798,7 @@ snapshots: '@babel/generator@7.28.5': dependencies: '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -17884,19 +17882,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -17982,15 +17967,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18000,9 +17976,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0(supports-color@7.2.0))': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) + '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 '@babel/traverse': 7.29.7 @@ -18068,15 +18044,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18140,7 +18107,7 @@ snapshots: '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 '@babel/parser@7.29.7': dependencies: @@ -18190,21 +18157,11 @@ snapshots: '@babel/core': 7.28.5(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18225,11 +18182,6 @@ snapshots: '@babel/core': 7.28.5(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18340,14 +18292,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18496,17 +18440,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18542,17 +18475,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -18575,12 +18497,6 @@ snapshots: '@babel/runtime@7.29.2': {} - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -18593,8 +18509,8 @@ snapshots: '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -18623,11 +18539,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -19696,7 +19607,7 @@ snapshots: '@expo/metro-config@56.0.13(expo@56.0.5)(typescript@5.9.3)': dependencies: '@babel/code-frame': 7.29.0 - '@babel/core': 7.29.0(supports-color@7.2.0) + '@babel/core': 7.29.7 '@babel/generator': 7.29.1 '@expo/config': 56.0.9(typescript@5.9.3) '@expo/env': 2.3.0 @@ -20531,8 +20442,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@nothing-but/utils@0.17.0': {} - '@nx/nx-darwin-arm64@23.1.1': optional: true @@ -22588,8 +22497,6 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rolldown/pluginutils@1.0.0-beta.40': {} - '@rolldown/pluginutils@1.0.0-beta.53': {} '@rolldown/pluginutils@1.0.0-rc.3': {} @@ -22929,48 +22836,6 @@ snapshots: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 - '@solid-devtools/debugger@0.28.1(solid-js@1.9.10)': - dependencies: - '@nothing-but/utils': 0.17.0 - '@solid-devtools/shared': 0.20.0(solid-js@1.9.10) - '@solid-primitives/bounds': 0.1.3(solid-js@1.9.10) - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.10) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) - '@solid-primitives/scheduled': 1.5.2(solid-js@1.9.10) - '@solid-primitives/static-store': 0.1.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 - - '@solid-devtools/logger@0.9.11(solid-js@1.9.10)': - dependencies: - '@nothing-but/utils': 0.17.0 - '@solid-devtools/debugger': 0.28.1(solid-js@1.9.10) - '@solid-devtools/shared': 0.20.0(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 - - '@solid-devtools/shared@0.20.0(solid-js@1.9.10)': - dependencies: - '@nothing-but/utils': 0.17.0 - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/media': 2.3.3(solid-js@1.9.10) - '@solid-primitives/refs': 1.1.2(solid-js@1.9.10) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) - '@solid-primitives/scheduled': 1.5.2(solid-js@1.9.10) - '@solid-primitives/static-store': 0.1.2(solid-js@1.9.10) - '@solid-primitives/styles': 0.1.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 - - '@solid-primitives/bounds@0.1.3(solid-js@1.9.10)': - dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.10) - '@solid-primitives/static-store': 0.1.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 - '@solid-primitives/event-listener@2.4.3(solid-js@1.9.10)': dependencies: '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) @@ -22983,14 +22848,6 @@ snapshots: '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/media@2.3.3(solid-js@1.9.10)': - dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.10) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) - '@solid-primitives/static-store': 0.1.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 - '@solid-primitives/refs@1.1.2(solid-js@1.9.10)': dependencies: '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) @@ -23009,21 +22866,11 @@ snapshots: '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/scheduled@1.5.2(solid-js@1.9.10)': - dependencies: - solid-js: 1.9.10 - '@solid-primitives/static-store@0.1.2(solid-js@1.9.10)': dependencies: '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) solid-js: 1.9.10 - '@solid-primitives/styles@0.1.2(solid-js@1.9.10)': - dependencies: - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.10) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.10) - solid-js: 1.9.10 - '@solid-primitives/utils@6.3.2(solid-js@1.9.10)': dependencies: solid-js: 1.9.10 @@ -23355,6 +23202,8 @@ snapshots: '@tanstack/history@1.154.14': {} + '@tanstack/history@1.162.1': {} + '@tanstack/nitro-v2-vite-plugin@1.155.0(@electric-sql/pglite@0.4.3)(aws4fetch@1.0.20)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260317.1)(@electric-sql/pglite@0.4.3)(@opentelemetry/api@1.9.1)(@prisma/client@7.9.0(prisma@7.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(better-sqlite3@12.11.1)(magicast@0.5.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(better-sqlite3@12.11.1)(bun-types@1.3.14)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(better-sqlite3@12.11.1)(magicast@0.5.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mysql2@3.15.3)(rolldown@1.2.4)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: nitropack: 2.13.1(@electric-sql/pglite@0.4.3)(aws4fetch@1.0.20)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260317.1)(@electric-sql/pglite@0.4.3)(@opentelemetry/api@1.9.1)(@prisma/client@7.9.0(prisma@7.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(better-sqlite3@12.11.1)(magicast@0.5.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(better-sqlite3@12.11.1)(bun-types@1.3.14)(mysql2@3.15.3)(postgres@3.4.7)(prisma@7.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(better-sqlite3@12.11.1)(magicast@0.5.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mysql2@3.15.3)(rolldown@1.2.4) @@ -23464,23 +23313,23 @@ snapshots: '@tanstack/query-core': 5.90.12 react: 19.2.3 - '@tanstack/react-router-devtools@1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-router-devtools@1.167.1(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-devtools-core': 1.159.4(@tanstack/router-core@1.159.4)(csstype@3.2.3) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-devtools-core': 1.168.1(@tanstack/router-core@1.171.27)(csstype@3.2.3) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@tanstack/router-core': 1.159.4 + '@tanstack/router-core': 1.171.27 transitivePeerDependencies: - csstype - '@tanstack/react-router-ssr-query@1.159.5(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-router-ssr-query@1.167.1(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.171.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/query-core': 5.90.12 '@tanstack/react-query': 5.90.12(react@19.2.3) - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-ssr-query-core': 1.159.4(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-ssr-query-core': 1.169.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.171.27) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: @@ -23497,16 +23346,14 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/history': 1.154.14 - '@tanstack/react-store': 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-core': 1.159.4 + '@tanstack/history': 1.162.1 + '@tanstack/react-store': 0.9.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.171.27 isbot: 5.1.32 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 '@tanstack/react-start-client@1.141.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: @@ -23518,19 +23365,17 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-client@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-start-client@1.168.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-core': 1.159.4 - '@tanstack/start-client-core': 1.159.4 + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.171.27 + '@tanstack/start-client-core': 1.170.27 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/react-start-plugin@1.131.50(e7a157c953e155a2765d901b34df38b3)': + '@tanstack/react-start-plugin@1.131.50(5e76e58f58fcdc553da46eb093ac388b)': dependencies: - '@tanstack/start-plugin-core': 1.131.50(1a1cdd8b9c2c533357f243540b1932ef) + '@tanstack/start-plugin-core': 1.131.50(b114ec7757aa79b8f316a176b0457a25) '@vitejs/plugin-react': 4.7.0(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) pathe: 2.0.3 vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) @@ -23620,6 +23465,32 @@ snapshots: - xml2js - yaml + '@tanstack/react-start-rsc@0.1.48(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + dependencies: + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.171.27 + '@tanstack/router-utils': 1.162.2 + '@tanstack/start-client-core': 1.170.27 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-plugin-core': 1.171.39(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/start-storage-context': 1.167.29 + pathe: 2.0.3 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - '@farmfe/core' + - '@rsbuild/core' + - bun-types-no-globals + - crossws + - esbuild + - rolldown + - rollup + - supports-color + - unloader + - vite + - vite-plugin-solid + - webpack + '@tanstack/react-start-server@1.141.1(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/history': 1.141.0 @@ -23632,38 +23503,52 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/react-start-server@1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-start-server@1.167.37(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/history': 1.154.14 - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-core': 1.159.4 - '@tanstack/start-client-core': 1.159.4 - '@tanstack/start-server-core': 1.159.4(crossws@0.4.6(srvx@0.11.17)) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.171.27 + '@tanstack/start-server-core': 1.169.31(crossws@0.4.6(srvx@0.11.17)) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: - crossws - '@tanstack/react-start@1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + '@tanstack/react-start@1.168.49(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-start-client': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-start-server': 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-utils': 1.158.0 - '@tanstack/start-client-core': 1.159.4 - '@tanstack/start-plugin-core': 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) - '@tanstack/start-server-core': 1.159.4(crossws@0.4.6(srvx@0.11.17)) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-start-client': 1.168.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-start-rsc': 0.1.48(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/react-start-server': 1.167.37(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-utils': 1.162.2 + '@tanstack/start-client-core': 1.170.27 + '@tanstack/start-plugin-core': 1.171.39(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/start-server-core': 1.169.31(crossws@0.4.6(srvx@0.11.17)) pathe: 2.0.3 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + optionalDependencies: vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - - '@rsbuild/core' + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals - crossws + - esbuild + - react-server-dom-rspack + - rolldown + - rollup - supports-color + - unloader - vite-plugin-solid - webpack + '@tanstack/react-store@0.11.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/store': 0.11.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + '@tanstack/react-store@0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/store': 0.8.0 @@ -23671,6 +23556,13 @@ snapshots: react-dom: 19.2.3(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) + '@tanstack/react-store@0.9.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/store': 0.9.3 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + '@tanstack/router-core@1.131.50': dependencies: '@tanstack/history': 1.131.2 @@ -23701,22 +23593,18 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.141.1(@tanstack/router-core@1.159.4)(csstype@3.2.3)(solid-js@1.9.10)': + '@tanstack/router-core@1.171.27': dependencies: - '@tanstack/router-core': 1.159.4 - clsx: 2.1.1 - goober: 2.1.18(csstype@3.2.3) - solid-js: 1.9.10 - tiny-invariant: 1.3.3 - optionalDependencies: - csstype: 3.2.3 + '@tanstack/history': 1.162.1 + cookie-es: 3.1.1 + seroval: 1.6.2 + seroval-plugins: 1.6.2(seroval@1.6.2) - '@tanstack/router-devtools-core@1.159.4(@tanstack/router-core@1.159.4)(csstype@3.2.3)': + '@tanstack/router-devtools-core@1.168.1(@tanstack/router-core@1.171.27)(csstype@3.2.3)': dependencies: - '@tanstack/router-core': 1.159.4 + '@tanstack/router-core': 1.171.27 clsx: 2.1.1 goober: 2.1.18(csstype@3.2.3) - tiny-invariant: 1.3.3 optionalDependencies: csstype: 3.2.3 @@ -23733,11 +23621,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-generator@1.141.1': + '@tanstack/router-generator@1.159.4': dependencies: - '@tanstack/router-core': 1.141.1 - '@tanstack/router-utils': 1.141.0 - '@tanstack/virtual-file-routes': 1.141.0 + '@tanstack/router-core': 1.159.4 + '@tanstack/router-utils': 1.158.0 + '@tanstack/virtual-file-routes': 1.154.7 prettier: 3.7.4 recast: 0.23.11 source-map: 0.7.6 @@ -23746,20 +23634,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-generator@1.159.4': + '@tanstack/router-generator@1.167.33': dependencies: - '@tanstack/router-core': 1.159.4 - '@tanstack/router-utils': 1.158.0 - '@tanstack/virtual-file-routes': 1.154.7 + '@babel/types': 7.29.7 + '@tanstack/router-core': 1.171.27 + '@tanstack/router-utils': 1.162.2 + '@tanstack/virtual-file-routes': 1.162.0 + jiti: 2.7.0 + magic-string: 0.30.21 prettier: 3.7.4 - recast: 0.23.11 - source-map: 0.7.6 - tsx: 4.21.0 - zod: 3.25.76 + zod: 4.4.3 transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.131.50(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + '@tanstack/router-plugin@1.131.50(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) @@ -23776,13 +23664,13 @@ snapshots: unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) vite-plugin-solid: 2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + '@tanstack/router-plugin@1.159.5(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) @@ -23790,52 +23678,49 @@ snapshots: '@babel/template': 7.29.7 '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 - '@tanstack/router-core': 1.141.1 - '@tanstack/router-generator': 1.141.1 - '@tanstack/router-utils': 1.141.0 - '@tanstack/virtual-file-routes': 1.141.0 - babel-dead-code-elimination: 1.0.10 + '@tanstack/router-core': 1.159.4 + '@tanstack/router-generator': 1.159.4 + '@tanstack/router-utils': 1.158.0 + '@tanstack/virtual-file-routes': 1.154.7 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) vite-plugin-solid: 2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + '@tanstack/router-plugin@1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0) + '@babel/core': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 - '@tanstack/router-core': 1.159.4 - '@tanstack/router-generator': 1.159.4 - '@tanstack/router-utils': 1.158.0 - '@tanstack/virtual-file-routes': 1.154.7 - chokidar: 3.6.0 - unplugin: 2.3.11 - zod: 3.25.76 + '@tanstack/router-core': 1.171.27 + '@tanstack/router-generator': 1.167.33 + '@tanstack/router-utils': 1.162.2 + chokidar: 5.0.0 + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + zod: 4.4.3 optionalDependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) vite-plugin-solid: 2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup - supports-color + - unloader - '@tanstack/router-ssr-query-core@1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)': + '@tanstack/router-ssr-query-core@1.169.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.171.27)': dependencies: '@tanstack/query-core': 5.90.12 - '@tanstack/router-core': 1.159.4 - - '@tanstack/router-ssr-query-core@1.159.4(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)': - dependencies: - '@tanstack/query-core': 5.90.12 - '@tanstack/router-core': 1.159.4 + '@tanstack/router-core': 1.171.27 '@tanstack/router-utils@1.131.2': dependencies: @@ -23863,7 +23748,20 @@ snapshots: '@tanstack/router-utils@1.158.0': dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + ansis: 4.2.0 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.2 + pathe: 2.0.3 + tinyglobby: 0.2.17 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.162.2': + dependencies: '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 @@ -23921,80 +23819,74 @@ snapshots: '@tanstack/query-core': 5.90.12 solid-js: 1.9.10 - '@tanstack/solid-router-devtools@1.141.1(@tanstack/router-core@1.159.4)(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(csstype@3.2.3)(solid-js@1.9.10)': + '@tanstack/solid-router-devtools@1.167.1(@tanstack/router-core@1.171.27)(@tanstack/solid-router@1.170.30(solid-js@1.9.10))(csstype@3.2.3)(solid-js@1.9.10)': dependencies: - '@tanstack/router-devtools-core': 1.141.1(@tanstack/router-core@1.159.4)(csstype@3.2.3)(solid-js@1.9.10) - '@tanstack/solid-router': 1.141.1(solid-js@1.9.10) + '@tanstack/router-devtools-core': 1.168.1(@tanstack/router-core@1.171.27)(csstype@3.2.3) + '@tanstack/solid-router': 1.170.30(solid-js@1.9.10) solid-js: 1.9.10 optionalDependencies: - '@tanstack/router-core': 1.159.4 + '@tanstack/router-core': 1.171.27 transitivePeerDependencies: - csstype - '@tanstack/solid-router-ssr-query@1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.141.1(solid-js@1.9.10))(eslint@9.39.4(jiti@2.7.0))(solid-js@1.9.10)(supports-color@7.2.0)(typescript@5.9.3)': + '@tanstack/solid-router-ssr-query@1.167.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.171.27)(@tanstack/solid-query@5.90.15(solid-js@1.9.10))(@tanstack/solid-router@1.170.30(solid-js@1.9.10))(solid-js@1.9.10)': dependencies: '@tanstack/query-core': 5.90.12 - '@tanstack/router-ssr-query-core': 1.141.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.159.4) + '@tanstack/router-ssr-query-core': 1.169.1(@tanstack/query-core@5.90.12)(@tanstack/router-core@1.171.27) '@tanstack/solid-query': 5.90.15(solid-js@1.9.10) - '@tanstack/solid-router': 1.141.1(solid-js@1.9.10) - eslint-plugin-solid: 0.14.5(eslint@9.39.4(jiti@2.7.0))(supports-color@7.2.0)(typescript@5.9.3) + '@tanstack/solid-router': 1.170.30(solid-js@1.9.10) solid-js: 1.9.10 transitivePeerDependencies: - '@tanstack/router-core' - - eslint - - supports-color - - typescript - '@tanstack/solid-router@1.141.1(solid-js@1.9.10)': + '@tanstack/solid-router@1.170.30(solid-js@1.9.10)': dependencies: - '@solid-devtools/logger': 0.9.11(solid-js@1.9.10) '@solid-primitives/refs': 1.1.2(solid-js@1.9.10) '@solidjs/meta': 0.29.4(solid-js@1.9.10) - '@tanstack/history': 1.141.0 - '@tanstack/router-core': 1.141.1 - '@tanstack/solid-store': 0.8.0(solid-js@1.9.10) + '@tanstack/history': 1.162.1 + '@tanstack/router-core': 1.171.27 isbot: 5.1.32 solid-js: 1.9.10 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/solid-start-client@1.141.1(solid-js@1.9.10)': + '@tanstack/solid-start-client@1.168.29(solid-js@1.9.10)': dependencies: - '@tanstack/router-core': 1.141.1 - '@tanstack/solid-router': 1.141.1(solid-js@1.9.10) - '@tanstack/start-client-core': 1.141.1 + '@tanstack/router-core': 1.171.27 + '@tanstack/solid-router': 1.170.30(solid-js@1.9.10) + '@tanstack/start-client-core': 1.170.27 solid-js: 1.9.10 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - '@tanstack/solid-start-server@1.141.1(crossws@0.4.6(srvx@0.11.17))(solid-js@1.9.10)': + '@tanstack/solid-start-server@1.167.36(crossws@0.4.6(srvx@0.11.17))(solid-js@1.9.10)': dependencies: - '@solidjs/meta': 0.29.4(solid-js@1.9.10) - '@tanstack/history': 1.141.0 - '@tanstack/router-core': 1.141.1 - '@tanstack/solid-router': 1.141.1(solid-js@1.9.10) - '@tanstack/start-client-core': 1.141.1 - '@tanstack/start-server-core': 1.141.1(crossws@0.4.6(srvx@0.11.17)) + '@tanstack/router-core': 1.171.27 + '@tanstack/solid-router': 1.170.30(solid-js@1.9.10) + '@tanstack/start-server-core': 1.169.31(crossws@0.4.6(srvx@0.11.17)) solid-js: 1.9.10 transitivePeerDependencies: - crossws - '@tanstack/solid-start@1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(solid-js@1.9.10)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + '@tanstack/solid-start@1.168.47(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(solid-js@1.9.10)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: - '@tanstack/solid-router': 1.141.1(solid-js@1.9.10) - '@tanstack/solid-start-client': 1.141.1(solid-js@1.9.10) - '@tanstack/solid-start-server': 1.141.1(crossws@0.4.6(srvx@0.11.17))(solid-js@1.9.10) - '@tanstack/start-client-core': 1.141.1 - '@tanstack/start-plugin-core': 1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) - '@tanstack/start-server-core': 1.141.1(crossws@0.4.6(srvx@0.11.17)) + '@tanstack/solid-router': 1.170.30(solid-js@1.9.10) + '@tanstack/solid-start-client': 1.168.29(solid-js@1.9.10) + '@tanstack/solid-start-server': 1.167.36(crossws@0.4.6(srvx@0.11.17))(solid-js@1.9.10) + '@tanstack/start-client-core': 1.170.27 + '@tanstack/start-plugin-core': 1.171.39(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/start-server-core': 1.169.31(crossws@0.4.6(srvx@0.11.17)) pathe: 2.0.3 solid-js: 1.9.10 + optionalDependencies: vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - - '@rsbuild/core' + - '@farmfe/core' + - '@rspack/core' - '@tanstack/react-router' + - bun-types-no-globals - crossws + - esbuild + - rolldown + - rollup - supports-color + - unloader - vite-plugin-solid - webpack @@ -24079,12 +23971,19 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 + '@tanstack/start-client-core@1.170.27': + dependencies: + '@tanstack/router-core': 1.171.27 + '@tanstack/start-fn-stubs': 1.162.0 + '@tanstack/start-storage-context': 1.167.29 + seroval: 1.6.2 + '@tanstack/start-config@1.120.20(340eefabdbae5b0c948fd6f8943277d1)': dependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-start-plugin': 1.131.50(e7a157c953e155a2765d901b34df38b3) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-start-plugin': 1.131.50(5e76e58f58fcdc553da46eb093ac388b) '@tanstack/router-generator': 1.159.4 - '@tanstack/router-plugin': 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/router-plugin': 1.159.5(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/server-functions-plugin': 1.141.0(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/start-server-functions-handler': 1.120.19(crossws@0.4.6(srvx@0.11.17)) '@vitejs/plugin-react': 4.7.0(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) @@ -24147,14 +24046,16 @@ snapshots: '@tanstack/start-fn-stubs@1.154.7': {} - '@tanstack/start-plugin-core@1.131.50(1a1cdd8b9c2c533357f243540b1932ef)': + '@tanstack/start-fn-stubs@1.162.0': {} + + '@tanstack/start-plugin-core@1.131.50(b114ec7757aa79b8f316a176b0457a25)': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7 '@babel/types': 7.29.7 '@tanstack/router-core': 1.131.50 '@tanstack/router-generator': 1.131.50 - '@tanstack/router-plugin': 1.131.50(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/router-plugin': 1.131.50(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/router-utils': 1.131.2 '@tanstack/server-functions-plugin': 1.131.2(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/start-server-core': 1.131.50 @@ -24167,7 +24068,7 @@ snapshots: pathe: 2.0.3 ufo: 1.6.3 vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) - vitefu: 1.1.1(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + vitefu: 1.1.3(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) xmlbuilder2: 3.1.1 zod: 3.25.76 transitivePeerDependencies: @@ -24205,65 +24106,41 @@ snapshots: - webpack - xml2js - '@tanstack/start-plugin-core@1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/types': 7.29.0 - '@rolldown/pluginutils': 1.0.0-beta.40 - '@tanstack/router-core': 1.141.1 - '@tanstack/router-generator': 1.141.1 - '@tanstack/router-plugin': 1.141.1(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) - '@tanstack/router-utils': 1.141.0 - '@tanstack/server-functions-plugin': 1.141.0(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) - '@tanstack/start-client-core': 1.141.1 - '@tanstack/start-server-core': 1.141.1(crossws@0.4.6(srvx@0.11.17)) - babel-dead-code-elimination: 1.0.10 - cheerio: 1.1.2 - exsolve: 1.0.8 - pathe: 2.0.3 - srvx: 0.8.16 - tinyglobby: 0.2.17 - ufo: 1.6.1 - vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) - vitefu: 1.1.1(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) - xmlbuilder2: 4.0.3 - zod: 3.25.76 - transitivePeerDependencies: - - '@rsbuild/core' - - '@tanstack/react-router' - - crossws - - supports-color - - vite-plugin-solid - - webpack - - '@tanstack/start-plugin-core@1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': + '@tanstack/start-plugin-core@1.171.39(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.6(srvx@0.11.17))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.27.1 - '@babel/core': 7.29.0(supports-color@7.2.0) + '@babel/core': 7.29.7 '@babel/types': 7.29.7 - '@rolldown/pluginutils': 1.0.0-beta.40 - '@tanstack/router-core': 1.159.4 - '@tanstack/router-generator': 1.159.4 - '@tanstack/router-plugin': 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(supports-color@7.2.0)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) - '@tanstack/router-utils': 1.158.0 - '@tanstack/start-client-core': 1.159.4 - '@tanstack/start-server-core': 1.159.4(crossws@0.4.6(srvx@0.11.17)) - cheerio: 1.1.2 + '@tanstack/router-core': 1.171.27 + '@tanstack/router-generator': 1.167.33 + '@tanstack/router-plugin': 1.168.35(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite-plugin-solid@2.11.14(solid-js@1.9.10)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + '@tanstack/router-utils': 1.162.2 + '@tanstack/start-server-core': 1.169.31(crossws@0.4.6(srvx@0.11.17)) exsolve: 1.0.8 + lightningcss: 1.33.0 pathe: 2.0.3 + picomatch: 4.0.5 + seroval: 1.6.2 + source-map: 0.7.6 srvx: 0.11.17 tinyglobby: 0.2.17 ufo: 1.6.3 - vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) - vitefu: 1.1.1(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) + vitefu: 1.1.3(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) xmlbuilder2: 4.0.3 - zod: 3.25.76 + zod: 4.4.3 + optionalDependencies: + vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - - '@rsbuild/core' + - '@farmfe/core' + - '@rspack/core' - '@tanstack/react-router' + - bun-types-no-globals - crossws + - esbuild + - rolldown + - rollup - supports-color + - unloader - vite-plugin-solid - webpack @@ -24303,6 +24180,18 @@ snapshots: transitivePeerDependencies: - crossws + '@tanstack/start-server-core@1.169.31(crossws@0.4.6(srvx@0.11.17))': + dependencies: + '@tanstack/history': 1.162.1 + '@tanstack/router-core': 1.171.27 + '@tanstack/start-client-core': 1.170.27 + '@tanstack/start-storage-context': 1.167.29 + fetchdts: 0.1.7 + h3-v2: h3@2.0.1-rc.20(crossws@0.4.6(srvx@0.11.17)) + seroval: 1.6.2 + transitivePeerDependencies: + - crossws + '@tanstack/start-server-functions-client@1.131.50(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@tanstack/server-functions-plugin': 1.131.2(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)) @@ -24357,6 +24246,10 @@ snapshots: dependencies: '@tanstack/router-core': 1.159.4 + '@tanstack/start-storage-context@1.167.29': + dependencies: + '@tanstack/router-core': 1.171.27 + '@tanstack/start@1.120.20(340eefabdbae5b0c948fd6f8943277d1)': dependencies: '@tanstack/react-start-client': 1.141.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -24420,10 +24313,14 @@ snapshots: - xml2js - yaml + '@tanstack/store@0.11.1': {} + '@tanstack/store@0.7.7': {} '@tanstack/store@0.8.0': {} + '@tanstack/store@0.9.3': {} + '@tanstack/typedoc-config@0.3.4': dependencies: typedoc: 0.28.20(@typescript/typescript6@6.0.2) @@ -24433,10 +24330,10 @@ snapshots: '@tanstack/virtual-file-routes@1.131.2': {} - '@tanstack/virtual-file-routes@1.141.0': {} - '@tanstack/virtual-file-routes@1.154.7': {} + '@tanstack/virtual-file-routes@1.162.0': {} + '@tanstack/vite-config@0.6.0(@microsoft/api-extractor@7.47.7(@types/node@24.10.3))(esbuild@0.28.1)(rolldown@1.1.5)(rollup@4.60.1)(supports-color@7.2.0)(typescript@7.0.2)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0))': dependencies: vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) @@ -24454,9 +24351,9 @@ snapshots: - typescript - webpack - '@tanstack/zod-adapter@1.166.9(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6)': + '@tanstack/zod-adapter@1.166.9(@tanstack/react-router@1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6)': dependencies: - '@tanstack/react-router': 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.170.32(react-dom@19.2.3(react@19.2.3))(react@19.2.3) zod: 4.3.6 '@testing-library/dom@10.4.1': @@ -24785,15 +24682,6 @@ snapshots: - supports-color optional: true - '@typescript-eslint/project-service@8.49.0(supports-color@7.2.0)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.59.4 - debug: 4.4.3(supports-color@7.2.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.59.4(typescript@7.0.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@7.0.2) @@ -24804,21 +24692,12 @@ snapshots: - supports-color optional: true - '@typescript-eslint/scope-manager@8.49.0': - dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 - '@typescript-eslint/scope-manager@8.59.4': dependencies: '@typescript-eslint/types': 8.59.4 '@typescript-eslint/visitor-keys': 8.59.4 optional: true - '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.59.4(typescript@7.0.2)': dependencies: typescript: 7.0.2 @@ -24837,25 +24716,8 @@ snapshots: - supports-color optional: true - '@typescript-eslint/types@8.49.0': {} - '@typescript-eslint/types@8.59.4': {} - '@typescript-eslint/typescript-estree@8.49.0(supports-color@7.2.0)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.49.0(supports-color@7.2.0)(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 - debug: 4.4.3(supports-color@7.2.0) - minimatch: 9.0.5 - semver: 7.8.4 - tinyglobby: 0.2.17 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.59.4(typescript@7.0.2)': dependencies: '@typescript-eslint/project-service': 8.59.4(typescript@7.0.2) @@ -24872,17 +24734,6 @@ snapshots: - supports-color optional: true - '@typescript-eslint/utils@8.49.0(eslint@9.39.4(jiti@2.7.0))(supports-color@7.2.0)(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(supports-color@7.2.0)(typescript@5.9.3) - eslint: 9.39.4(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) @@ -24895,11 +24746,6 @@ snapshots: - supports-color optional: true - '@typescript-eslint/visitor-keys@8.49.0': - dependencies: - '@typescript-eslint/types': 8.49.0 - eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.59.4': dependencies: '@typescript-eslint/types': 8.59.4 @@ -25624,15 +25470,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) - '@babel/types': 7.29.7 - html-entities: 2.3.3 - parse5: 7.3.0 - babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.29.7): dependencies: '@babel/core': 7.29.7 @@ -25786,13 +25623,6 @@ snapshots: - '@babel/core' - supports-color - babel-preset-solid@1.9.10(@babel/core@7.29.0)(solid-js@1.9.10): - dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.29.0) - optionalDependencies: - solid-js: 1.9.10 - babel-preset-solid@1.9.10(@babel/core@7.29.7)(solid-js@1.9.10): dependencies: '@babel/core': 7.29.7 @@ -26383,6 +26213,8 @@ snapshots: cookie-es@2.0.1: {} + cookie-es@3.1.1: {} + cookie-signature@1.2.2: {} cookie@0.6.0: {} @@ -26959,9 +26791,9 @@ snapshots: esbuild-plugin-solid@0.5.0(esbuild@0.28.1)(solid-js@1.9.10): dependencies: - '@babel/core': 7.29.0(supports-color@7.2.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - babel-preset-solid: 1.9.10(@babel/core@7.29.0)(solid-js@1.9.10) + '@babel/core': 7.29.7 + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7) + babel-preset-solid: 1.9.10(@babel/core@7.29.7)(solid-js@1.9.10) esbuild: 0.28.1 solid-js: 1.9.10 transitivePeerDependencies: @@ -27098,19 +26930,6 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-plugin-solid@0.14.5(eslint@9.39.4(jiti@2.7.0))(supports-color@7.2.0)(typescript@5.9.3): - dependencies: - '@typescript-eslint/utils': 8.49.0(eslint@9.39.4(jiti@2.7.0))(supports-color@7.2.0)(typescript@5.9.3) - eslint: 9.39.4(jiti@2.7.0) - estraverse: 5.3.0 - is-html: 2.0.0 - kebab-case: 1.0.2 - known-css-properties: 0.30.0 - style-to-object: 1.0.14 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2))(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2))(eslint@9.39.4(jiti@2.7.0)): dependencies: eslint: 9.39.4(jiti@2.7.0) @@ -27954,6 +27773,13 @@ snapshots: optionalDependencies: crossws: 0.4.6(srvx@0.11.17) + h3@2.0.1-rc.20(crossws@0.4.6(srvx@0.11.17)): + dependencies: + rou3: 0.8.1 + srvx: 0.11.17 + optionalDependencies: + crossws: 0.4.6(srvx@0.11.17) + h3@2.0.1-rc.22(crossws@0.4.6(srvx@0.11.17)): dependencies: rou3: 0.8.1 @@ -28130,8 +27956,6 @@ snapshots: dependencies: cheerio: 1.1.2 - html-tags@3.3.1: {} - html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} @@ -28360,10 +28184,6 @@ snapshots: is-hexadecimal@2.0.1: {} - is-html@2.0.0: - dependencies: - html-tags: 3.3.1 - is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -28728,8 +28548,6 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - kebab-case@1.0.2: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -28771,8 +28589,6 @@ snapshots: knitwork@1.3.0: {} - known-css-properties@0.30.0: {} - kolorist@1.8.0: {} lan-network@0.2.1: {} @@ -29398,7 +29214,7 @@ snapshots: metro-source-map@0.84.4: dependencies: '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 flow-enums-runtime: 0.0.6 invariant: 2.2.4 metro-symbolicate: 0.84.4 @@ -31806,7 +31622,7 @@ snapshots: dependencies: '@babel/generator': 7.28.5 '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 ast-kit: 2.2.0 birpc: 3.0.0 dts-resolver: 2.1.3(oxc-resolver@11.21.3) @@ -32142,12 +31958,18 @@ snapshots: dependencies: seroval: 1.5.0 + seroval-plugins@1.6.2(seroval@1.6.2): + dependencies: + seroval: 1.6.2 + seroval@1.3.2: {} seroval@1.4.0: {} seroval@1.5.0: {} + seroval@1.6.2: {} + serve-placeholder@2.0.2: dependencies: defu: 6.1.4 @@ -32804,10 +32626,6 @@ snapshots: ts-algebra@2.0.0: {} - ts-api-utils@2.1.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - ts-api-utils@2.5.0(typescript@7.0.2): dependencies: typescript: 7.0.2 @@ -33200,6 +33018,17 @@ snapshots: picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.4)(rollup@4.60.1)(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)): + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + optionalDependencies: + esbuild: 0.28.1 + rolldown: 1.2.4 + rollup: 4.60.1 + vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) + unrun@0.2.19(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1): dependencies: rolldown: 1.0.0-beta.53(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) @@ -33563,10 +33392,6 @@ snapshots: tsx: 4.23.12 yaml: 2.9.0 - vitefu@1.1.1(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)): - optionalDependencies: - vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) - vitefu@1.1.3(vite@8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0)): optionalDependencies: vite: 8.2.1(@types/node@24.10.3)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass@1.101.0)(terser@5.44.1)(tsx@4.23.12)(yaml@2.9.0) @@ -33993,6 +33818,8 @@ snapshots: zod@4.3.6: {} + zod@4.4.3: {} + zone.js@0.15.1: {} zwitch@2.0.4: {} diff --git a/scripts/lovable-gateway.models.json b/scripts/lovable-gateway.models.json index 8a193376e4..79c6babbc9 100644 --- a/scripts/lovable-gateway.models.json +++ b/scripts/lovable-gateway.models.json @@ -12,15 +12,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -85,15 +78,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -150,15 +136,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -222,12 +201,8 @@ "context_window": 8192, "max_tokens": 16384, "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -265,12 +240,8 @@ "max_tokens": 16384, "knowledge": "2025-01", "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -308,15 +279,8 @@ "max_tokens": 65536, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -406,12 +370,8 @@ "max_tokens": 16384, "knowledge": "2025-01", "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -449,15 +409,8 @@ "max_tokens": 65000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -522,15 +475,8 @@ "max_tokens": 32768, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -595,15 +541,8 @@ "max_tokens": 32768, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -668,15 +607,8 @@ "max_tokens": 65000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -740,15 +672,8 @@ "context_window": 65536, "max_tokens": 4096, "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text", - "image" - ] + "input": ["text", "image", "video"], + "output": ["text", "image"] }, "pricing": { "input": { @@ -813,12 +738,8 @@ "max_tokens": 16384, "knowledge": "2025-01", "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -856,15 +777,8 @@ "max_tokens": 64000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -954,15 +868,8 @@ "max_tokens": 64000, "knowledge": "2025-01", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -1027,15 +934,8 @@ "max_tokens": 64000, "knowledge": "2026-03", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -1100,12 +1000,8 @@ "max_tokens": 0, "knowledge": "2025-05", "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "input": ["text"], + "output": ["text"] }, "pricing": { "input": { @@ -1133,15 +1029,8 @@ "max_tokens": 0, "knowledge": "2025-11", "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] + "input": ["text", "image", "audio", "video"], + "output": ["text"] }, "pricing": { "input": { @@ -1192,13 +1081,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] + "input": ["text", "image"], + "output": ["video"] }, "pricing": { "video_duration": { @@ -1226,13 +1110,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] + "input": ["text", "image"], + "output": ["video"] }, "pricing": { "video_duration": { @@ -1260,13 +1139,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] + "input": ["text", "image"], + "output": ["video"] }, "pricing": { "video_duration": { @@ -1291,13 +1165,8 @@ "context_window": 16000, "max_tokens": 2000, "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] + "input": ["text", "audio"], + "output": ["text"] }, "pricing": { "input": { @@ -1342,12 +1211,8 @@ "context_window": 2000, "max_tokens": 0, "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] + "input": ["text"], + "output": ["audio"] }, "pricing": { "input": { @@ -1384,13 +1249,8 @@ "context_window": 16000, "max_tokens": 2000, "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] + "input": ["text", "audio"], + "output": ["text"] }, "pricing": { "input": { @@ -1436,13 +1296,8 @@ "max_tokens": 128000, "knowledge": "2024-09-30", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1526,13 +1381,8 @@ "max_tokens": 128000, "knowledge": "2024-05-30", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1616,13 +1466,8 @@ "max_tokens": 128000, "knowledge": "2024-05-30", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1671,13 +1516,8 @@ "max_tokens": 128000, "knowledge": "2024-10", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1761,13 +1601,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1866,13 +1701,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -1956,13 +1786,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2011,13 +1836,8 @@ "max_tokens": 128000, "knowledge": "2025-08-31", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2081,13 +1901,8 @@ "max_tokens": 128000, "knowledge": "2025-12-01", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2186,13 +2001,8 @@ "max_tokens": 128000, "knowledge": "2025-12-01", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2256,13 +2066,8 @@ "max_tokens": 128000, "knowledge": "2026-02-16", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2361,13 +2166,8 @@ "max_tokens": 128000, "knowledge": "2026-02-16", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2466,13 +2266,8 @@ "max_tokens": 128000, "knowledge": "2026-02-16", "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "input": ["text", "image"], + "output": ["text"] }, "pricing": { "input": { @@ -2570,13 +2365,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] + "input": ["text", "image"], + "output": ["image"] }, "pricing": { "input": { @@ -2621,13 +2411,8 @@ "context_window": 0, "max_tokens": 0, "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] + "input": ["text", "image"], + "output": ["image"] }, "pricing": { "input": { @@ -2672,12 +2457,8 @@ "context_window": 8191, "max_tokens": 0, "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "input": ["text"], + "output": ["text"] }, "pricing": { "input": { @@ -2704,12 +2485,8 @@ "context_window": 8191, "max_tokens": 0, "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "input": ["text"], + "output": ["text"] }, "pricing": { "input": { diff --git a/testing/e2e/package.json b/testing/e2e/package.json index 0d100c5160..59e0b387f1 100644 --- a/testing/e2e/package.json +++ b/testing/e2e/package.json @@ -45,8 +45,8 @@ "@tanstack/devtools-event-bus": "^0.4.1", "@tanstack/nitro-v2-vite-plugin": "^1.155.0", "@tanstack/react-ai-devtools": "workspace:*", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", "arktype": "^2.1.28", "react": "^19.2.3", "react-dom": "^19.2.3", diff --git a/testing/e2e/src/components/ImageGenUI.tsx b/testing/e2e/src/components/ImageGenUI.tsx index 93cec8b7ce..bd59c7d159 100644 --- a/testing/e2e/src/components/ImageGenUI.tsx +++ b/testing/e2e/src/components/ImageGenUI.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useRef, useState } from 'react' import { useGenerateImage, fetchServerSentEvents, @@ -67,8 +67,12 @@ export function ImageGenUI({ } } - const { generate, result, isLoading, error, status } = + const { generate, result, isLoading, error, status, runId } = useGenerateImage(connectionOptions()) + const activeTuple = useRef({ runId: '', status: 'idle', isLoading: false }) + if (runId && status === 'generating' && isLoading) { + activeTuple.current = { runId, status, isLoading } + } const handleGenerate = async () => { if (!imageFile) { @@ -89,6 +93,12 @@ export function ImageGenUI({ return (
+ {activeTuple.current.runId} + {activeTuple.current.status} + + {String(activeTuple.current.isLoading)} + + {runId ?? ''}
{ if (!imageFile) { @@ -92,6 +96,12 @@ export function VideoGenUI({ return (
+ {activeTuple.current.runId} + {activeTuple.current.status} + + {String(activeTuple.current.isLoading)} + + {runId ?? ''}
rootRouteImport, -} as any) -const ToolsTestRoute = ToolsTestRouteImport.update({ - id: '/tools-test', - path: '/tools-test', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const PersistenceDurabilityRoute = PersistenceDurabilityRouteImport.update({ - id: '/persistence-durability', - path: '/persistence-durability', +const ByokRoute = ByokRouteImport.update({ + id: '/byok', + path: '/byok', getParentRoute: () => rootRouteImport, } as any) -const MiddlewareTestRoute = MiddlewareTestRouteImport.update({ - id: '/middleware-test', - path: '/middleware-test', +const ChatClientDefaultBridgeRoute = ChatClientDefaultBridgeRouteImport.update({ + id: '/chat-client-default-bridge', + path: '/chat-client-default-bridge', getParentRoute: () => rootRouteImport, } as any) -const MarkdownCjkRoute = MarkdownCjkRouteImport.update({ - id: '/markdown-cjk', - path: '/markdown-cjk', +const DevtoolsChatRoute = DevtoolsChatRouteImport.update({ + id: '/devtools-chat', + path: '/devtools-chat', getParentRoute: () => rootRouteImport, } as any) -const JoinRunClientToolRoute = JoinRunClientToolRouteImport.update({ - id: '/join-run-client-tool', - path: '/join-run-client-tool', +const DevtoolsGenerationHooksRoute = DevtoolsGenerationHooksRouteImport.update({ + id: '/devtools-generation-hooks', + path: '/devtools-generation-hooks', getParentRoute: () => rootRouteImport, } as any) -const InterruptsTestRoute = InterruptsTestRouteImport.update({ - id: '/interrupts-test', - path: '/interrupts-test', +const DevtoolsMemoryRoute = DevtoolsMemoryRouteImport.update({ + id: '/devtools-memory', + path: '/devtools-memory', getParentRoute: () => rootRouteImport, } as any) -const GenerationPersistenceServerRoute = - GenerationPersistenceServerRouteImport.update({ - id: '/generation-persistence-server', - path: '/generation-persistence-server', - getParentRoute: () => rootRouteImport, - } as any) -const GenerationPersistenceResumeRoute = - GenerationPersistenceResumeRouteImport.update({ - id: '/generation-persistence-resume', - path: '/generation-persistence-resume', - getParentRoute: () => rootRouteImport, - } as any) -const ForeignInterruptRoute = ForeignInterruptRouteImport.update({ - id: '/foreign-interrupt', - path: '/foreign-interrupt', +const DevtoolsRouteARoute = DevtoolsRouteARouteImport.update({ + id: '/devtools-route-a', + path: '/devtools-route-a', getParentRoute: () => rootRouteImport, } as any) -const DevtoolsToolsRoute = DevtoolsToolsRouteImport.update({ - id: '/devtools-tools', - path: '/devtools-tools', +const DevtoolsRouteBRoute = DevtoolsRouteBRouteImport.update({ + id: '/devtools-route-b', + path: '/devtools-route-b', getParentRoute: () => rootRouteImport, } as any) const DevtoolsStructuredRoute = DevtoolsStructuredRouteImport.update({ @@ -156,44 +139,61 @@ const DevtoolsStructuredRoute = DevtoolsStructuredRouteImport.update({ path: '/devtools-structured', getParentRoute: () => rootRouteImport, } as any) -const DevtoolsRouteBRoute = DevtoolsRouteBRouteImport.update({ - id: '/devtools-route-b', - path: '/devtools-route-b', +const DevtoolsToolsRoute = DevtoolsToolsRouteImport.update({ + id: '/devtools-tools', + path: '/devtools-tools', getParentRoute: () => rootRouteImport, } as any) -const DevtoolsRouteARoute = DevtoolsRouteARouteImport.update({ - id: '/devtools-route-a', - path: '/devtools-route-a', +const ForeignInterruptRoute = ForeignInterruptRouteImport.update({ + id: '/foreign-interrupt', + path: '/foreign-interrupt', getParentRoute: () => rootRouteImport, } as any) -const DevtoolsMemoryRoute = DevtoolsMemoryRouteImport.update({ - id: '/devtools-memory', - path: '/devtools-memory', +const GenerationPersistenceResumeRoute = + GenerationPersistenceResumeRouteImport.update({ + id: '/generation-persistence-resume', + path: '/generation-persistence-resume', + getParentRoute: () => rootRouteImport, + } as any) +const GenerationPersistenceServerRoute = + GenerationPersistenceServerRouteImport.update({ + id: '/generation-persistence-server', + path: '/generation-persistence-server', + getParentRoute: () => rootRouteImport, + } as any) +const InterruptsTestRoute = InterruptsTestRouteImport.update({ + id: '/interrupts-test', + path: '/interrupts-test', getParentRoute: () => rootRouteImport, } as any) -const DevtoolsGenerationHooksRoute = DevtoolsGenerationHooksRouteImport.update({ - id: '/devtools-generation-hooks', - path: '/devtools-generation-hooks', +const JoinRunClientToolRoute = JoinRunClientToolRouteImport.update({ + id: '/join-run-client-tool', + path: '/join-run-client-tool', getParentRoute: () => rootRouteImport, } as any) -const DevtoolsChatRoute = DevtoolsChatRouteImport.update({ - id: '/devtools-chat', - path: '/devtools-chat', +const MarkdownCjkRoute = MarkdownCjkRouteImport.update({ + id: '/markdown-cjk', + path: '/markdown-cjk', getParentRoute: () => rootRouteImport, } as any) -const ChatClientDefaultBridgeRoute = ChatClientDefaultBridgeRouteImport.update({ - id: '/chat-client-default-bridge', - path: '/chat-client-default-bridge', +const MiddlewareTestRoute = MiddlewareTestRouteImport.update({ + id: '/middleware-test', + path: '/middleware-test', getParentRoute: () => rootRouteImport, } as any) -const ByokRoute = ByokRouteImport.update({ - id: '/byok', - path: '/byok', +const PersistenceDurabilityRoute = PersistenceDurabilityRouteImport.update({ + id: '/persistence-durability', + path: '/persistence-durability', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ToolsTestRoute = ToolsTestRouteImport.update({ + id: '/tools-test', + path: '/tools-test', + getParentRoute: () => rootRouteImport, +} as any) +const WebsocketAdapterRoute = WebsocketAdapterRouteImport.update({ + id: '/websocket-adapter', + path: '/websocket-adapter', getParentRoute: () => rootRouteImport, } as any) const ProviderIndexRoute = ProviderIndexRouteImport.update({ @@ -201,176 +201,134 @@ const ProviderIndexRoute = ProviderIndexRouteImport.update({ path: '/$provider/', getParentRoute: () => rootRouteImport, } as any) -const ApiVideoRoute = ApiVideoRouteImport.update({ - id: '/api/video', - path: '/api/video', - getParentRoute: () => rootRouteImport, -} as any) -const ApiTtsRoute = ApiTtsRouteImport.update({ - id: '/api/tts', - path: '/api/tts', +const ProviderFeatureRoute = ProviderFeatureRouteImport.update({ + id: '/$provider/$feature', + path: '/$provider/$feature', getParentRoute: () => rootRouteImport, } as any) -const ApiTranscriptionRoute = ApiTranscriptionRouteImport.update({ - id: '/api/transcription', - path: '/api/transcription', +const ApiAnthropicBugTestRoute = ApiAnthropicBugTestRouteImport.update({ + id: '/api/anthropic-bug-test', + path: '/api/anthropic-bug-test', getParentRoute: () => rootRouteImport, } as any) -const ApiToolsTestRoute = ApiToolsTestRouteImport.update({ - id: '/api/tools-test', - path: '/api/tools-test', +const ApiAnthropicSkillsWireRoute = ApiAnthropicSkillsWireRouteImport.update({ + id: '/api/anthropic-skills-wire', + path: '/api/anthropic-skills-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiToolCallLifecycleWireRoute = - ApiToolCallLifecycleWireRouteImport.update({ - id: '/api/tool-call-lifecycle-wire', - path: '/api/tool-call-lifecycle-wire', +const ApiAnthropicStructuredUsageRoute = + ApiAnthropicStructuredUsageRouteImport.update({ + id: '/api/anthropic-structured-usage', + path: '/api/anthropic-structured-usage', getParentRoute: () => rootRouteImport, } as any) -const ApiSummarizeRoute = ApiSummarizeRouteImport.update({ - id: '/api/summarize', - path: '/api/summarize', +const ApiArktypeToolWireRoute = ApiArktypeToolWireRouteImport.update({ + id: '/api/arktype-tool-wire', + path: '/api/arktype-tool-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiSandboxToolHistoryRoute = ApiSandboxToolHistoryRouteImport.update({ - id: '/api/sandbox-tool-history', - path: '/api/sandbox-tool-history', +const ApiAudioRoute = ApiAudioRouteImport.update({ + id: '/api/audio', + path: '/api/audio', getParentRoute: () => rootRouteImport, } as any) -const ApiSandboxFilePersistenceRoute = - ApiSandboxFilePersistenceRouteImport.update({ - id: '/api/sandbox-file-persistence', - path: '/api/sandbox-file-persistence', - getParentRoute: () => rootRouteImport, - } as any) -const ApiSandboxDurabilityRoute = ApiSandboxDurabilityRouteImport.update({ - id: '/api/sandbox-durability', - path: '/api/sandbox-durability', +const ApiByokChatRoute = ApiByokChatRouteImport.update({ + id: '/api/byok-chat', + path: '/api/byok-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiProviderToolDispatchWireRoute = - ApiProviderToolDispatchWireRouteImport.update({ - id: '/api/provider-tool-dispatch-wire', - path: '/api/provider-tool-dispatch-wire', - getParentRoute: () => rootRouteImport, - } as any) -const ApiPersistenceDurabilityRoute = - ApiPersistenceDurabilityRouteImport.update({ - id: '/api/persistence-durability', - path: '/api/persistence-durability', +const ApiByteplusSeedance1080pWireRoute = + ApiByteplusSeedance1080pWireRouteImport.update({ + id: '/api/byteplus-seedance-1080p-wire', + path: '/api/byteplus-seedance-1080p-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiOtelUsageRoute = ApiOtelUsageRouteImport.update({ - id: '/api/otel-usage', - path: '/api/otel-usage', +const ApiChatRoute = ApiChatRouteImport.update({ + id: '/api/chat', + path: '/api/chat', getParentRoute: () => rootRouteImport, } as any) -const ApiOtelTranscriptionRoute = ApiOtelTranscriptionRouteImport.update({ - id: '/api/otel-transcription', - path: '/api/otel-transcription', +const ApiDevtoolsMemoryRoute = ApiDevtoolsMemoryRouteImport.update({ + id: '/api/devtools-memory', + path: '/api/devtools-memory', getParentRoute: () => rootRouteImport, } as any) -const ApiOtelMediaRoute = ApiOtelMediaRouteImport.update({ - id: '/api/otel-media', - path: '/api/otel-media', +const ApiDurableDeliveryRoute = ApiDurableDeliveryRouteImport.update({ + id: '/api/durable-delivery', + path: '/api/durable-delivery', getParentRoute: () => rootRouteImport, } as any) -const ApiOpenrouterWebToolsWireRoute = - ApiOpenrouterWebToolsWireRouteImport.update({ - id: '/api/openrouter-web-tools-wire', - path: '/api/openrouter-web-tools-wire', - getParentRoute: () => rootRouteImport, - } as any) -const ApiOpenrouterReasoningWireRoute = - ApiOpenrouterReasoningWireRouteImport.update({ - id: '/api/openrouter-reasoning-wire', - path: '/api/openrouter-reasoning-wire', - getParentRoute: () => rootRouteImport, - } as any) -const ApiOpenrouterJsonObjectWireRoute = - ApiOpenrouterJsonObjectWireRouteImport.update({ - id: '/api/openrouter-json-object-wire', - path: '/api/openrouter-json-object-wire', - getParentRoute: () => rootRouteImport, - } as any) -const ApiOpenrouterCostRoute = ApiOpenrouterCostRouteImport.update({ - id: '/api/openrouter-cost', - path: '/api/openrouter-cost', +const ApiDurableTakeoverRoute = ApiDurableTakeoverRouteImport.update({ + id: '/api/durable-takeover', + path: '/api/durable-takeover', getParentRoute: () => rootRouteImport, } as any) -const ApiOpenaiUsageDetailsRoute = ApiOpenaiUsageDetailsRouteImport.update({ - id: '/api/openai-usage-details', - path: '/api/openai-usage-details', +const ApiEmbeddingRoute = ApiEmbeddingRouteImport.update({ + id: '/api/embedding', + path: '/api/embedding', getParentRoute: () => rootRouteImport, } as any) -const ApiOpenaiStrictToolNullWireRoute = - ApiOpenaiStrictToolNullWireRouteImport.update({ - id: '/api/openai-strict-tool-null-wire', - path: '/api/openai-strict-tool-null-wire', - getParentRoute: () => rootRouteImport, - } as any) -const ApiOpenaiShellSkillsWireRoute = - ApiOpenaiShellSkillsWireRouteImport.update({ - id: '/api/openai-shell-skills-wire', - path: '/api/openai-shell-skills-wire', - getParentRoute: () => rootRouteImport, - } as any) -const ApiOpenaiCompletedResponseTextRoute = - ApiOpenaiCompletedResponseTextRouteImport.update({ - id: '/api/openai-completed-response-text', - path: '/api/openai-completed-response-text', - getParentRoute: () => rootRouteImport, - } as any) -const ApiNonStreamingRunErrorRoute = ApiNonStreamingRunErrorRouteImport.update({ - id: '/api/non-streaming-run-error', - path: '/api/non-streaming-run-error', +const ApiForeignInterruptRoute = ApiForeignInterruptRouteImport.update({ + id: '/api/foreign-interrupt', + path: '/api/foreign-interrupt', getParentRoute: () => rootRouteImport, } as any) -const ApiMultimodalToolResultWireRoute = - ApiMultimodalToolResultWireRouteImport.update({ - id: '/api/multimodal-tool-result-wire', - path: '/api/multimodal-tool-result-wire', +const ApiGeminiImageGaModelsRoute = ApiGeminiImageGaModelsRouteImport.update({ + id: '/api/gemini-image-ga-models', + path: '/api/gemini-image-ga-models', + getParentRoute: () => rootRouteImport, +} as any) +const ApiGeminiNativeImageWireRoute = + ApiGeminiNativeImageWireRouteImport.update({ + id: '/api/gemini-native-image-wire', + path: '/api/gemini-native-image-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiMistralStrictToolNullWireRoute = - ApiMistralStrictToolNullWireRouteImport.update({ - id: '/api/mistral-strict-tool-null-wire', - path: '/api/mistral-strict-tool-null-wire', +const ApiGenerationPersistenceResumeRoute = + ApiGenerationPersistenceResumeRouteImport.update({ + id: '/api/generation-persistence-resume', + path: '/api/generation-persistence-resume', getParentRoute: () => rootRouteImport, } as any) -const ApiMiddlewareTestRoute = ApiMiddlewareTestRouteImport.update({ - id: '/api/middleware-test', - path: '/api/middleware-test', +const ApiGenerationPersistenceServerRoute = + ApiGenerationPersistenceServerRouteImport.update({ + id: '/api/generation-persistence-server', + path: '/api/generation-persistence-server', + getParentRoute: () => rootRouteImport, + } as any) +const ApiImageRoute = ApiImageRouteImport.update({ + id: '/api/image', + path: '/api/image', getParentRoute: () => rootRouteImport, } as any) -const ApiMessageIdsRoute = ApiMessageIdsRouteImport.update({ - id: '/api/message-ids', - path: '/api/message-ids', +const ApiInterruptsTestRoute = ApiInterruptsTestRouteImport.update({ + id: '/api/interrupts-test', + path: '/api/interrupts-test', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpTestRoute = ApiMcpTestRouteImport.update({ - id: '/api/mcp-test', - path: '/api/mcp-test', +const ApiJoinRunClientToolRoute = ApiJoinRunClientToolRouteImport.update({ + id: '/api/join-run-client-tool', + path: '/api/join-run-client-tool', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpStatusTestRoute = ApiMcpStatusTestRouteImport.update({ - id: '/api/mcp-status-test', - path: '/api/mcp-status-test', +const ApiLazyToolsWireRoute = ApiLazyToolsWireRouteImport.update({ + id: '/api/lazy-tools-wire', + path: '/api/lazy-tools-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpServerRoute = ApiMcpServerRouteImport.update({ - id: '/api/mcp-server', - path: '/api/mcp-server', +const ApiMaxToolCallsWireRoute = ApiMaxToolCallsWireRouteImport.update({ + id: '/api/max-tool-calls-wire', + path: '/api/max-tool-calls-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpManagedTestRoute = ApiMcpManagedTestRouteImport.update({ - id: '/api/mcp-managed-test', - path: '/api/mcp-managed-test', +const ApiMcpAppsCallRoute = ApiMcpAppsCallRouteImport.update({ + id: '/api/mcp-apps-call', + path: '/api/mcp-apps-call', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpLifecycleTestRoute = ApiMcpLifecycleTestRouteImport.update({ - id: '/api/mcp-lifecycle-test', - path: '/api/mcp-lifecycle-test', +const ApiMcpAppsChatRoute = ApiMcpAppsChatRouteImport.update({ + id: '/api/mcp-apps-chat', + path: '/api/mcp-apps-chat', getParentRoute: () => rootRouteImport, } as any) const ApiMcpAppsServerRoute = ApiMcpAppsServerRouteImport.update({ @@ -378,160 +336,202 @@ const ApiMcpAppsServerRoute = ApiMcpAppsServerRouteImport.update({ path: '/api/mcp-apps-server', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpAppsChatRoute = ApiMcpAppsChatRouteImport.update({ - id: '/api/mcp-apps-chat', - path: '/api/mcp-apps-chat', +const ApiMcpLifecycleTestRoute = ApiMcpLifecycleTestRouteImport.update({ + id: '/api/mcp-lifecycle-test', + path: '/api/mcp-lifecycle-test', getParentRoute: () => rootRouteImport, } as any) -const ApiMcpAppsCallRoute = ApiMcpAppsCallRouteImport.update({ - id: '/api/mcp-apps-call', - path: '/api/mcp-apps-call', +const ApiMcpManagedTestRoute = ApiMcpManagedTestRouteImport.update({ + id: '/api/mcp-managed-test', + path: '/api/mcp-managed-test', + getParentRoute: () => rootRouteImport, +} as any) +const ApiMcpServerRoute = ApiMcpServerRouteImport.update({ + id: '/api/mcp-server', + path: '/api/mcp-server', getParentRoute: () => rootRouteImport, } as any) -const ApiMaxToolCallsWireRoute = ApiMaxToolCallsWireRouteImport.update({ - id: '/api/max-tool-calls-wire', - path: '/api/max-tool-calls-wire', +const ApiMcpStatusTestRoute = ApiMcpStatusTestRouteImport.update({ + id: '/api/mcp-status-test', + path: '/api/mcp-status-test', getParentRoute: () => rootRouteImport, } as any) -const ApiLazyToolsWireRoute = ApiLazyToolsWireRouteImport.update({ - id: '/api/lazy-tools-wire', - path: '/api/lazy-tools-wire', +const ApiMcpTestRoute = ApiMcpTestRouteImport.update({ + id: '/api/mcp-test', + path: '/api/mcp-test', getParentRoute: () => rootRouteImport, } as any) -const ApiJoinRunClientToolRoute = ApiJoinRunClientToolRouteImport.update({ - id: '/api/join-run-client-tool', - path: '/api/join-run-client-tool', +const ApiMessageIdsRoute = ApiMessageIdsRouteImport.update({ + id: '/api/message-ids', + path: '/api/message-ids', getParentRoute: () => rootRouteImport, } as any) -const ApiInterruptsTestRoute = ApiInterruptsTestRouteImport.update({ - id: '/api/interrupts-test', - path: '/api/interrupts-test', +const ApiMiddlewareTestRoute = ApiMiddlewareTestRouteImport.update({ + id: '/api/middleware-test', + path: '/api/middleware-test', getParentRoute: () => rootRouteImport, } as any) -const ApiImageRoute = ApiImageRouteImport.update({ - id: '/api/image', - path: '/api/image', +const ApiMistralStrictToolNullWireRoute = + ApiMistralStrictToolNullWireRouteImport.update({ + id: '/api/mistral-strict-tool-null-wire', + path: '/api/mistral-strict-tool-null-wire', + getParentRoute: () => rootRouteImport, + } as any) +const ApiMultimodalToolResultWireRoute = + ApiMultimodalToolResultWireRouteImport.update({ + id: '/api/multimodal-tool-result-wire', + path: '/api/multimodal-tool-result-wire', + getParentRoute: () => rootRouteImport, + } as any) +const ApiNonStreamingRunErrorRoute = ApiNonStreamingRunErrorRouteImport.update({ + id: '/api/non-streaming-run-error', + path: '/api/non-streaming-run-error', getParentRoute: () => rootRouteImport, } as any) -const ApiGenerationPersistenceServerRoute = - ApiGenerationPersistenceServerRouteImport.update({ - id: '/api/generation-persistence-server', - path: '/api/generation-persistence-server', +const ApiOpenaiCompletedResponseTextRoute = + ApiOpenaiCompletedResponseTextRouteImport.update({ + id: '/api/openai-completed-response-text', + path: '/api/openai-completed-response-text', getParentRoute: () => rootRouteImport, } as any) -const ApiGenerationPersistenceResumeRoute = - ApiGenerationPersistenceResumeRouteImport.update({ - id: '/api/generation-persistence-resume', - path: '/api/generation-persistence-resume', +const ApiOpenaiShellSkillsWireRoute = + ApiOpenaiShellSkillsWireRouteImport.update({ + id: '/api/openai-shell-skills-wire', + path: '/api/openai-shell-skills-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiGeminiNativeImageWireRoute = - ApiGeminiNativeImageWireRouteImport.update({ - id: '/api/gemini-native-image-wire', - path: '/api/gemini-native-image-wire', +const ApiOpenaiStrictToolNullWireRoute = + ApiOpenaiStrictToolNullWireRouteImport.update({ + id: '/api/openai-strict-tool-null-wire', + path: '/api/openai-strict-tool-null-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiGeminiImageGaModelsRoute = ApiGeminiImageGaModelsRouteImport.update({ - id: '/api/gemini-image-ga-models', - path: '/api/gemini-image-ga-models', - getParentRoute: () => rootRouteImport, -} as any) -const ApiForeignInterruptRoute = ApiForeignInterruptRouteImport.update({ - id: '/api/foreign-interrupt', - path: '/api/foreign-interrupt', - getParentRoute: () => rootRouteImport, -} as any) -const ApiEmbeddingRoute = ApiEmbeddingRouteImport.update({ - id: '/api/embedding', - path: '/api/embedding', +const ApiOpenaiUsageDetailsRoute = ApiOpenaiUsageDetailsRouteImport.update({ + id: '/api/openai-usage-details', + path: '/api/openai-usage-details', getParentRoute: () => rootRouteImport, } as any) -const ApiDurableTakeoverRoute = ApiDurableTakeoverRouteImport.update({ - id: '/api/durable-takeover', - path: '/api/durable-takeover', +const ApiOpenrouterCostRoute = ApiOpenrouterCostRouteImport.update({ + id: '/api/openrouter-cost', + path: '/api/openrouter-cost', getParentRoute: () => rootRouteImport, } as any) -const ApiDurableDeliveryRoute = ApiDurableDeliveryRouteImport.update({ - id: '/api/durable-delivery', - path: '/api/durable-delivery', +const ApiOpenrouterJsonObjectWireRoute = + ApiOpenrouterJsonObjectWireRouteImport.update({ + id: '/api/openrouter-json-object-wire', + path: '/api/openrouter-json-object-wire', + getParentRoute: () => rootRouteImport, + } as any) +const ApiOpenrouterReasoningWireRoute = + ApiOpenrouterReasoningWireRouteImport.update({ + id: '/api/openrouter-reasoning-wire', + path: '/api/openrouter-reasoning-wire', + getParentRoute: () => rootRouteImport, + } as any) +const ApiOpenrouterWebToolsWireRoute = + ApiOpenrouterWebToolsWireRouteImport.update({ + id: '/api/openrouter-web-tools-wire', + path: '/api/openrouter-web-tools-wire', + getParentRoute: () => rootRouteImport, + } as any) +const ApiOtelMediaRoute = ApiOtelMediaRouteImport.update({ + id: '/api/otel-media', + path: '/api/otel-media', getParentRoute: () => rootRouteImport, } as any) -const ApiDevtoolsMemoryRoute = ApiDevtoolsMemoryRouteImport.update({ - id: '/api/devtools-memory', - path: '/api/devtools-memory', +const ApiOtelTranscriptionRoute = ApiOtelTranscriptionRouteImport.update({ + id: '/api/otel-transcription', + path: '/api/otel-transcription', getParentRoute: () => rootRouteImport, } as any) -const ApiChatRoute = ApiChatRouteImport.update({ - id: '/api/chat', - path: '/api/chat', +const ApiOtelUsageRoute = ApiOtelUsageRouteImport.update({ + id: '/api/otel-usage', + path: '/api/otel-usage', getParentRoute: () => rootRouteImport, } as any) -const ApiByokChatRoute = ApiByokChatRouteImport.update({ - id: '/api/byok-chat', - path: '/api/byok-chat', +const ApiPersistenceDurabilityRoute = + ApiPersistenceDurabilityRouteImport.update({ + id: '/api/persistence-durability', + path: '/api/persistence-durability', + getParentRoute: () => rootRouteImport, + } as any) +const ApiProviderToolDispatchWireRoute = + ApiProviderToolDispatchWireRouteImport.update({ + id: '/api/provider-tool-dispatch-wire', + path: '/api/provider-tool-dispatch-wire', + getParentRoute: () => rootRouteImport, + } as any) +const ApiSandboxDurabilityRoute = ApiSandboxDurabilityRouteImport.update({ + id: '/api/sandbox-durability', + path: '/api/sandbox-durability', getParentRoute: () => rootRouteImport, } as any) -const ApiByteplusSeedance1080pWireRoute = - ApiByteplusSeedance1080pWireRouteImport.update({ - id: '/api/byteplus-seedance-1080p-wire', - path: '/api/byteplus-seedance-1080p-wire', +const ApiSandboxFilePersistenceRoute = + ApiSandboxFilePersistenceRouteImport.update({ + id: '/api/sandbox-file-persistence', + path: '/api/sandbox-file-persistence', getParentRoute: () => rootRouteImport, } as any) -const ApiAudioRoute = ApiAudioRouteImport.update({ - id: '/api/audio', - path: '/api/audio', +const ApiSandboxToolHistoryRoute = ApiSandboxToolHistoryRouteImport.update({ + id: '/api/sandbox-tool-history', + path: '/api/sandbox-tool-history', getParentRoute: () => rootRouteImport, } as any) -const ApiArktypeToolWireRoute = ApiArktypeToolWireRouteImport.update({ - id: '/api/arktype-tool-wire', - path: '/api/arktype-tool-wire', +const ApiSummarizeRoute = ApiSummarizeRouteImport.update({ + id: '/api/summarize', + path: '/api/summarize', getParentRoute: () => rootRouteImport, } as any) -const ApiAnthropicStructuredUsageRoute = - ApiAnthropicStructuredUsageRouteImport.update({ - id: '/api/anthropic-structured-usage', - path: '/api/anthropic-structured-usage', +const ApiToolCallLifecycleWireRoute = + ApiToolCallLifecycleWireRouteImport.update({ + id: '/api/tool-call-lifecycle-wire', + path: '/api/tool-call-lifecycle-wire', getParentRoute: () => rootRouteImport, } as any) -const ApiAnthropicSkillsWireRoute = ApiAnthropicSkillsWireRouteImport.update({ - id: '/api/anthropic-skills-wire', - path: '/api/anthropic-skills-wire', +const ApiToolsTestRoute = ApiToolsTestRouteImport.update({ + id: '/api/tools-test', + path: '/api/tools-test', getParentRoute: () => rootRouteImport, } as any) -const ApiAnthropicBugTestRoute = ApiAnthropicBugTestRouteImport.update({ - id: '/api/anthropic-bug-test', - path: '/api/anthropic-bug-test', +const ApiTranscriptionRoute = ApiTranscriptionRouteImport.update({ + id: '/api/transcription', + path: '/api/transcription', getParentRoute: () => rootRouteImport, } as any) -const ProviderFeatureRoute = ProviderFeatureRouteImport.update({ - id: '/$provider/$feature', - path: '/$provider/$feature', +const ApiTtsRoute = ApiTtsRouteImport.update({ + id: '/api/tts', + path: '/api/tts', getParentRoute: () => rootRouteImport, } as any) -const ApiVideoStreamRoute = ApiVideoStreamRouteImport.update({ +const ApiVideoRoute = ApiVideoRouteImport.update({ + id: '/api/video', + path: '/api/video', + getParentRoute: () => rootRouteImport, +} as any) +const ApiAudioStreamRoute = ApiAudioStreamRouteImport.update({ id: '/stream', path: '/stream', - getParentRoute: () => ApiVideoRoute, + getParentRoute: () => ApiAudioRoute, } as any) -const ApiTtsStreamRoute = ApiTtsStreamRouteImport.update({ +const ApiImageStreamRoute = ApiImageStreamRouteImport.update({ id: '/stream', path: '/stream', - getParentRoute: () => ApiTtsRoute, + getParentRoute: () => ApiImageRoute, } as any) const ApiTranscriptionStreamRoute = ApiTranscriptionStreamRouteImport.update({ id: '/stream', path: '/stream', getParentRoute: () => ApiTranscriptionRoute, } as any) -const ApiImageStreamRoute = ApiImageStreamRouteImport.update({ +const ApiTtsStreamRoute = ApiTtsStreamRouteImport.update({ id: '/stream', path: '/stream', - getParentRoute: () => ApiImageRoute, + getParentRoute: () => ApiTtsRoute, } as any) -const ApiAudioStreamRoute = ApiAudioStreamRouteImport.update({ +const ApiVideoStreamRoute = ApiVideoStreamRouteImport.update({ id: '/stream', path: '/stream', - getParentRoute: () => ApiAudioRoute, + getParentRoute: () => ApiVideoRoute, } as any) export interface FileRoutesByFullPath { @@ -1138,74 +1138,67 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/websocket-adapter': { - id: '/websocket-adapter' - path: '/websocket-adapter' - fullPath: '/websocket-adapter' - preLoaderRoute: typeof WebsocketAdapterRouteImport - parentRoute: typeof rootRouteImport - } - '/tools-test': { - id: '/tools-test' - path: '/tools-test' - fullPath: '/tools-test' - preLoaderRoute: typeof ToolsTestRouteImport - parentRoute: typeof rootRouteImport - } - '/persistence-durability': { - id: '/persistence-durability' - path: '/persistence-durability' - fullPath: '/persistence-durability' - preLoaderRoute: typeof PersistenceDurabilityRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/middleware-test': { - id: '/middleware-test' - path: '/middleware-test' - fullPath: '/middleware-test' - preLoaderRoute: typeof MiddlewareTestRouteImport + '/byok': { + id: '/byok' + path: '/byok' + fullPath: '/byok' + preLoaderRoute: typeof ByokRouteImport parentRoute: typeof rootRouteImport } - '/markdown-cjk': { - id: '/markdown-cjk' - path: '/markdown-cjk' - fullPath: '/markdown-cjk' - preLoaderRoute: typeof MarkdownCjkRouteImport + '/chat-client-default-bridge': { + id: '/chat-client-default-bridge' + path: '/chat-client-default-bridge' + fullPath: '/chat-client-default-bridge' + preLoaderRoute: typeof ChatClientDefaultBridgeRouteImport parentRoute: typeof rootRouteImport } - '/join-run-client-tool': { - id: '/join-run-client-tool' - path: '/join-run-client-tool' - fullPath: '/join-run-client-tool' - preLoaderRoute: typeof JoinRunClientToolRouteImport + '/devtools-chat': { + id: '/devtools-chat' + path: '/devtools-chat' + fullPath: '/devtools-chat' + preLoaderRoute: typeof DevtoolsChatRouteImport parentRoute: typeof rootRouteImport } - '/interrupts-test': { - id: '/interrupts-test' - path: '/interrupts-test' - fullPath: '/interrupts-test' - preLoaderRoute: typeof InterruptsTestRouteImport + '/devtools-generation-hooks': { + id: '/devtools-generation-hooks' + path: '/devtools-generation-hooks' + fullPath: '/devtools-generation-hooks' + preLoaderRoute: typeof DevtoolsGenerationHooksRouteImport parentRoute: typeof rootRouteImport } - '/generation-persistence-server': { - id: '/generation-persistence-server' - path: '/generation-persistence-server' - fullPath: '/generation-persistence-server' - preLoaderRoute: typeof GenerationPersistenceServerRouteImport + '/devtools-memory': { + id: '/devtools-memory' + path: '/devtools-memory' + fullPath: '/devtools-memory' + preLoaderRoute: typeof DevtoolsMemoryRouteImport parentRoute: typeof rootRouteImport } - '/generation-persistence-resume': { - id: '/generation-persistence-resume' - path: '/generation-persistence-resume' - fullPath: '/generation-persistence-resume' - preLoaderRoute: typeof GenerationPersistenceResumeRouteImport + '/devtools-route-a': { + id: '/devtools-route-a' + path: '/devtools-route-a' + fullPath: '/devtools-route-a' + preLoaderRoute: typeof DevtoolsRouteARouteImport parentRoute: typeof rootRouteImport } - '/foreign-interrupt': { - id: '/foreign-interrupt' - path: '/foreign-interrupt' - fullPath: '/foreign-interrupt' - preLoaderRoute: typeof ForeignInterruptRouteImport + '/devtools-route-b': { + id: '/devtools-route-b' + path: '/devtools-route-b' + fullPath: '/devtools-route-b' + preLoaderRoute: typeof DevtoolsRouteBRouteImport + parentRoute: typeof rootRouteImport + } + '/devtools-structured': { + id: '/devtools-structured' + path: '/devtools-structured' + fullPath: '/devtools-structured' + preLoaderRoute: typeof DevtoolsStructuredRouteImport parentRoute: typeof rootRouteImport } '/devtools-tools': { @@ -1215,67 +1208,74 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DevtoolsToolsRouteImport parentRoute: typeof rootRouteImport } - '/devtools-structured': { - id: '/devtools-structured' - path: '/devtools-structured' - fullPath: '/devtools-structured' - preLoaderRoute: typeof DevtoolsStructuredRouteImport + '/foreign-interrupt': { + id: '/foreign-interrupt' + path: '/foreign-interrupt' + fullPath: '/foreign-interrupt' + preLoaderRoute: typeof ForeignInterruptRouteImport parentRoute: typeof rootRouteImport } - '/devtools-route-b': { - id: '/devtools-route-b' - path: '/devtools-route-b' - fullPath: '/devtools-route-b' - preLoaderRoute: typeof DevtoolsRouteBRouteImport + '/generation-persistence-resume': { + id: '/generation-persistence-resume' + path: '/generation-persistence-resume' + fullPath: '/generation-persistence-resume' + preLoaderRoute: typeof GenerationPersistenceResumeRouteImport parentRoute: typeof rootRouteImport } - '/devtools-route-a': { - id: '/devtools-route-a' - path: '/devtools-route-a' - fullPath: '/devtools-route-a' - preLoaderRoute: typeof DevtoolsRouteARouteImport + '/generation-persistence-server': { + id: '/generation-persistence-server' + path: '/generation-persistence-server' + fullPath: '/generation-persistence-server' + preLoaderRoute: typeof GenerationPersistenceServerRouteImport parentRoute: typeof rootRouteImport } - '/devtools-memory': { - id: '/devtools-memory' - path: '/devtools-memory' - fullPath: '/devtools-memory' - preLoaderRoute: typeof DevtoolsMemoryRouteImport + '/interrupts-test': { + id: '/interrupts-test' + path: '/interrupts-test' + fullPath: '/interrupts-test' + preLoaderRoute: typeof InterruptsTestRouteImport parentRoute: typeof rootRouteImport } - '/devtools-generation-hooks': { - id: '/devtools-generation-hooks' - path: '/devtools-generation-hooks' - fullPath: '/devtools-generation-hooks' - preLoaderRoute: typeof DevtoolsGenerationHooksRouteImport + '/join-run-client-tool': { + id: '/join-run-client-tool' + path: '/join-run-client-tool' + fullPath: '/join-run-client-tool' + preLoaderRoute: typeof JoinRunClientToolRouteImport parentRoute: typeof rootRouteImport } - '/devtools-chat': { - id: '/devtools-chat' - path: '/devtools-chat' - fullPath: '/devtools-chat' - preLoaderRoute: typeof DevtoolsChatRouteImport + '/markdown-cjk': { + id: '/markdown-cjk' + path: '/markdown-cjk' + fullPath: '/markdown-cjk' + preLoaderRoute: typeof MarkdownCjkRouteImport parentRoute: typeof rootRouteImport } - '/chat-client-default-bridge': { - id: '/chat-client-default-bridge' - path: '/chat-client-default-bridge' - fullPath: '/chat-client-default-bridge' - preLoaderRoute: typeof ChatClientDefaultBridgeRouteImport + '/middleware-test': { + id: '/middleware-test' + path: '/middleware-test' + fullPath: '/middleware-test' + preLoaderRoute: typeof MiddlewareTestRouteImport parentRoute: typeof rootRouteImport } - '/byok': { - id: '/byok' - path: '/byok' - fullPath: '/byok' - preLoaderRoute: typeof ByokRouteImport + '/persistence-durability': { + id: '/persistence-durability' + path: '/persistence-durability' + fullPath: '/persistence-durability' + preLoaderRoute: typeof PersistenceDurabilityRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/tools-test': { + id: '/tools-test' + path: '/tools-test' + fullPath: '/tools-test' + preLoaderRoute: typeof ToolsTestRouteImport + parentRoute: typeof rootRouteImport + } + '/websocket-adapter': { + id: '/websocket-adapter' + path: '/websocket-adapter' + fullPath: '/websocket-adapter' + preLoaderRoute: typeof WebsocketAdapterRouteImport parentRoute: typeof rootRouteImport } '/$provider/': { @@ -1285,207 +1285,200 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProviderIndexRouteImport parentRoute: typeof rootRouteImport } - '/api/video': { - id: '/api/video' - path: '/api/video' - fullPath: '/api/video' - preLoaderRoute: typeof ApiVideoRouteImport - parentRoute: typeof rootRouteImport - } - '/api/tts': { - id: '/api/tts' - path: '/api/tts' - fullPath: '/api/tts' - preLoaderRoute: typeof ApiTtsRouteImport + '/$provider/$feature': { + id: '/$provider/$feature' + path: '/$provider/$feature' + fullPath: '/$provider/$feature' + preLoaderRoute: typeof ProviderFeatureRouteImport parentRoute: typeof rootRouteImport } - '/api/transcription': { - id: '/api/transcription' - path: '/api/transcription' - fullPath: '/api/transcription' - preLoaderRoute: typeof ApiTranscriptionRouteImport + '/api/anthropic-bug-test': { + id: '/api/anthropic-bug-test' + path: '/api/anthropic-bug-test' + fullPath: '/api/anthropic-bug-test' + preLoaderRoute: typeof ApiAnthropicBugTestRouteImport parentRoute: typeof rootRouteImport } - '/api/tools-test': { - id: '/api/tools-test' - path: '/api/tools-test' - fullPath: '/api/tools-test' - preLoaderRoute: typeof ApiToolsTestRouteImport + '/api/anthropic-skills-wire': { + id: '/api/anthropic-skills-wire' + path: '/api/anthropic-skills-wire' + fullPath: '/api/anthropic-skills-wire' + preLoaderRoute: typeof ApiAnthropicSkillsWireRouteImport parentRoute: typeof rootRouteImport } - '/api/tool-call-lifecycle-wire': { - id: '/api/tool-call-lifecycle-wire' - path: '/api/tool-call-lifecycle-wire' - fullPath: '/api/tool-call-lifecycle-wire' - preLoaderRoute: typeof ApiToolCallLifecycleWireRouteImport + '/api/anthropic-structured-usage': { + id: '/api/anthropic-structured-usage' + path: '/api/anthropic-structured-usage' + fullPath: '/api/anthropic-structured-usage' + preLoaderRoute: typeof ApiAnthropicStructuredUsageRouteImport parentRoute: typeof rootRouteImport } - '/api/summarize': { - id: '/api/summarize' - path: '/api/summarize' - fullPath: '/api/summarize' - preLoaderRoute: typeof ApiSummarizeRouteImport + '/api/arktype-tool-wire': { + id: '/api/arktype-tool-wire' + path: '/api/arktype-tool-wire' + fullPath: '/api/arktype-tool-wire' + preLoaderRoute: typeof ApiArktypeToolWireRouteImport parentRoute: typeof rootRouteImport } - '/api/sandbox-tool-history': { - id: '/api/sandbox-tool-history' - path: '/api/sandbox-tool-history' - fullPath: '/api/sandbox-tool-history' - preLoaderRoute: typeof ApiSandboxToolHistoryRouteImport + '/api/audio': { + id: '/api/audio' + path: '/api/audio' + fullPath: '/api/audio' + preLoaderRoute: typeof ApiAudioRouteImport parentRoute: typeof rootRouteImport } - '/api/sandbox-file-persistence': { - id: '/api/sandbox-file-persistence' - path: '/api/sandbox-file-persistence' - fullPath: '/api/sandbox-file-persistence' - preLoaderRoute: typeof ApiSandboxFilePersistenceRouteImport + '/api/byok-chat': { + id: '/api/byok-chat' + path: '/api/byok-chat' + fullPath: '/api/byok-chat' + preLoaderRoute: typeof ApiByokChatRouteImport parentRoute: typeof rootRouteImport } - '/api/sandbox-durability': { - id: '/api/sandbox-durability' - path: '/api/sandbox-durability' - fullPath: '/api/sandbox-durability' - preLoaderRoute: typeof ApiSandboxDurabilityRouteImport + '/api/byteplus-seedance-1080p-wire': { + id: '/api/byteplus-seedance-1080p-wire' + path: '/api/byteplus-seedance-1080p-wire' + fullPath: '/api/byteplus-seedance-1080p-wire' + preLoaderRoute: typeof ApiByteplusSeedance1080pWireRouteImport parentRoute: typeof rootRouteImport } - '/api/provider-tool-dispatch-wire': { - id: '/api/provider-tool-dispatch-wire' - path: '/api/provider-tool-dispatch-wire' - fullPath: '/api/provider-tool-dispatch-wire' - preLoaderRoute: typeof ApiProviderToolDispatchWireRouteImport + '/api/chat': { + id: '/api/chat' + path: '/api/chat' + fullPath: '/api/chat' + preLoaderRoute: typeof ApiChatRouteImport parentRoute: typeof rootRouteImport } - '/api/persistence-durability': { - id: '/api/persistence-durability' - path: '/api/persistence-durability' - fullPath: '/api/persistence-durability' - preLoaderRoute: typeof ApiPersistenceDurabilityRouteImport + '/api/devtools-memory': { + id: '/api/devtools-memory' + path: '/api/devtools-memory' + fullPath: '/api/devtools-memory' + preLoaderRoute: typeof ApiDevtoolsMemoryRouteImport parentRoute: typeof rootRouteImport } - '/api/otel-usage': { - id: '/api/otel-usage' - path: '/api/otel-usage' - fullPath: '/api/otel-usage' - preLoaderRoute: typeof ApiOtelUsageRouteImport + '/api/durable-delivery': { + id: '/api/durable-delivery' + path: '/api/durable-delivery' + fullPath: '/api/durable-delivery' + preLoaderRoute: typeof ApiDurableDeliveryRouteImport parentRoute: typeof rootRouteImport } - '/api/otel-transcription': { - id: '/api/otel-transcription' - path: '/api/otel-transcription' - fullPath: '/api/otel-transcription' - preLoaderRoute: typeof ApiOtelTranscriptionRouteImport + '/api/durable-takeover': { + id: '/api/durable-takeover' + path: '/api/durable-takeover' + fullPath: '/api/durable-takeover' + preLoaderRoute: typeof ApiDurableTakeoverRouteImport parentRoute: typeof rootRouteImport } - '/api/otel-media': { - id: '/api/otel-media' - path: '/api/otel-media' - fullPath: '/api/otel-media' - preLoaderRoute: typeof ApiOtelMediaRouteImport + '/api/embedding': { + id: '/api/embedding' + path: '/api/embedding' + fullPath: '/api/embedding' + preLoaderRoute: typeof ApiEmbeddingRouteImport parentRoute: typeof rootRouteImport } - '/api/openrouter-web-tools-wire': { - id: '/api/openrouter-web-tools-wire' - path: '/api/openrouter-web-tools-wire' - fullPath: '/api/openrouter-web-tools-wire' - preLoaderRoute: typeof ApiOpenrouterWebToolsWireRouteImport + '/api/foreign-interrupt': { + id: '/api/foreign-interrupt' + path: '/api/foreign-interrupt' + fullPath: '/api/foreign-interrupt' + preLoaderRoute: typeof ApiForeignInterruptRouteImport parentRoute: typeof rootRouteImport } - '/api/openrouter-reasoning-wire': { - id: '/api/openrouter-reasoning-wire' - path: '/api/openrouter-reasoning-wire' - fullPath: '/api/openrouter-reasoning-wire' - preLoaderRoute: typeof ApiOpenrouterReasoningWireRouteImport + '/api/gemini-image-ga-models': { + id: '/api/gemini-image-ga-models' + path: '/api/gemini-image-ga-models' + fullPath: '/api/gemini-image-ga-models' + preLoaderRoute: typeof ApiGeminiImageGaModelsRouteImport parentRoute: typeof rootRouteImport } - '/api/openrouter-json-object-wire': { - id: '/api/openrouter-json-object-wire' - path: '/api/openrouter-json-object-wire' - fullPath: '/api/openrouter-json-object-wire' - preLoaderRoute: typeof ApiOpenrouterJsonObjectWireRouteImport + '/api/gemini-native-image-wire': { + id: '/api/gemini-native-image-wire' + path: '/api/gemini-native-image-wire' + fullPath: '/api/gemini-native-image-wire' + preLoaderRoute: typeof ApiGeminiNativeImageWireRouteImport parentRoute: typeof rootRouteImport } - '/api/openrouter-cost': { - id: '/api/openrouter-cost' - path: '/api/openrouter-cost' - fullPath: '/api/openrouter-cost' - preLoaderRoute: typeof ApiOpenrouterCostRouteImport + '/api/generation-persistence-resume': { + id: '/api/generation-persistence-resume' + path: '/api/generation-persistence-resume' + fullPath: '/api/generation-persistence-resume' + preLoaderRoute: typeof ApiGenerationPersistenceResumeRouteImport parentRoute: typeof rootRouteImport } - '/api/openai-usage-details': { - id: '/api/openai-usage-details' - path: '/api/openai-usage-details' - fullPath: '/api/openai-usage-details' - preLoaderRoute: typeof ApiOpenaiUsageDetailsRouteImport + '/api/generation-persistence-server': { + id: '/api/generation-persistence-server' + path: '/api/generation-persistence-server' + fullPath: '/api/generation-persistence-server' + preLoaderRoute: typeof ApiGenerationPersistenceServerRouteImport parentRoute: typeof rootRouteImport } - '/api/openai-strict-tool-null-wire': { - id: '/api/openai-strict-tool-null-wire' - path: '/api/openai-strict-tool-null-wire' - fullPath: '/api/openai-strict-tool-null-wire' - preLoaderRoute: typeof ApiOpenaiStrictToolNullWireRouteImport + '/api/image': { + id: '/api/image' + path: '/api/image' + fullPath: '/api/image' + preLoaderRoute: typeof ApiImageRouteImport parentRoute: typeof rootRouteImport } - '/api/openai-shell-skills-wire': { - id: '/api/openai-shell-skills-wire' - path: '/api/openai-shell-skills-wire' - fullPath: '/api/openai-shell-skills-wire' - preLoaderRoute: typeof ApiOpenaiShellSkillsWireRouteImport + '/api/interrupts-test': { + id: '/api/interrupts-test' + path: '/api/interrupts-test' + fullPath: '/api/interrupts-test' + preLoaderRoute: typeof ApiInterruptsTestRouteImport parentRoute: typeof rootRouteImport } - '/api/openai-completed-response-text': { - id: '/api/openai-completed-response-text' - path: '/api/openai-completed-response-text' - fullPath: '/api/openai-completed-response-text' - preLoaderRoute: typeof ApiOpenaiCompletedResponseTextRouteImport + '/api/join-run-client-tool': { + id: '/api/join-run-client-tool' + path: '/api/join-run-client-tool' + fullPath: '/api/join-run-client-tool' + preLoaderRoute: typeof ApiJoinRunClientToolRouteImport parentRoute: typeof rootRouteImport } - '/api/non-streaming-run-error': { - id: '/api/non-streaming-run-error' - path: '/api/non-streaming-run-error' - fullPath: '/api/non-streaming-run-error' - preLoaderRoute: typeof ApiNonStreamingRunErrorRouteImport + '/api/lazy-tools-wire': { + id: '/api/lazy-tools-wire' + path: '/api/lazy-tools-wire' + fullPath: '/api/lazy-tools-wire' + preLoaderRoute: typeof ApiLazyToolsWireRouteImport parentRoute: typeof rootRouteImport } - '/api/multimodal-tool-result-wire': { - id: '/api/multimodal-tool-result-wire' - path: '/api/multimodal-tool-result-wire' - fullPath: '/api/multimodal-tool-result-wire' - preLoaderRoute: typeof ApiMultimodalToolResultWireRouteImport + '/api/max-tool-calls-wire': { + id: '/api/max-tool-calls-wire' + path: '/api/max-tool-calls-wire' + fullPath: '/api/max-tool-calls-wire' + preLoaderRoute: typeof ApiMaxToolCallsWireRouteImport parentRoute: typeof rootRouteImport } - '/api/mistral-strict-tool-null-wire': { - id: '/api/mistral-strict-tool-null-wire' - path: '/api/mistral-strict-tool-null-wire' - fullPath: '/api/mistral-strict-tool-null-wire' - preLoaderRoute: typeof ApiMistralStrictToolNullWireRouteImport + '/api/mcp-apps-call': { + id: '/api/mcp-apps-call' + path: '/api/mcp-apps-call' + fullPath: '/api/mcp-apps-call' + preLoaderRoute: typeof ApiMcpAppsCallRouteImport parentRoute: typeof rootRouteImport } - '/api/middleware-test': { - id: '/api/middleware-test' - path: '/api/middleware-test' - fullPath: '/api/middleware-test' - preLoaderRoute: typeof ApiMiddlewareTestRouteImport + '/api/mcp-apps-chat': { + id: '/api/mcp-apps-chat' + path: '/api/mcp-apps-chat' + fullPath: '/api/mcp-apps-chat' + preLoaderRoute: typeof ApiMcpAppsChatRouteImport parentRoute: typeof rootRouteImport } - '/api/message-ids': { - id: '/api/message-ids' - path: '/api/message-ids' - fullPath: '/api/message-ids' - preLoaderRoute: typeof ApiMessageIdsRouteImport + '/api/mcp-apps-server': { + id: '/api/mcp-apps-server' + path: '/api/mcp-apps-server' + fullPath: '/api/mcp-apps-server' + preLoaderRoute: typeof ApiMcpAppsServerRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-test': { - id: '/api/mcp-test' - path: '/api/mcp-test' - fullPath: '/api/mcp-test' - preLoaderRoute: typeof ApiMcpTestRouteImport + '/api/mcp-lifecycle-test': { + id: '/api/mcp-lifecycle-test' + path: '/api/mcp-lifecycle-test' + fullPath: '/api/mcp-lifecycle-test' + preLoaderRoute: typeof ApiMcpLifecycleTestRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-status-test': { - id: '/api/mcp-status-test' - path: '/api/mcp-status-test' - fullPath: '/api/mcp-status-test' - preLoaderRoute: typeof ApiMcpStatusTestRouteImport + '/api/mcp-managed-test': { + id: '/api/mcp-managed-test' + path: '/api/mcp-managed-test' + fullPath: '/api/mcp-managed-test' + preLoaderRoute: typeof ApiMcpManagedTestRouteImport parentRoute: typeof rootRouteImport } '/api/mcp-server': { @@ -1495,215 +1488,222 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiMcpServerRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-managed-test': { - id: '/api/mcp-managed-test' - path: '/api/mcp-managed-test' - fullPath: '/api/mcp-managed-test' - preLoaderRoute: typeof ApiMcpManagedTestRouteImport + '/api/mcp-status-test': { + id: '/api/mcp-status-test' + path: '/api/mcp-status-test' + fullPath: '/api/mcp-status-test' + preLoaderRoute: typeof ApiMcpStatusTestRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-lifecycle-test': { - id: '/api/mcp-lifecycle-test' - path: '/api/mcp-lifecycle-test' - fullPath: '/api/mcp-lifecycle-test' - preLoaderRoute: typeof ApiMcpLifecycleTestRouteImport + '/api/mcp-test': { + id: '/api/mcp-test' + path: '/api/mcp-test' + fullPath: '/api/mcp-test' + preLoaderRoute: typeof ApiMcpTestRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-server': { - id: '/api/mcp-apps-server' - path: '/api/mcp-apps-server' - fullPath: '/api/mcp-apps-server' - preLoaderRoute: typeof ApiMcpAppsServerRouteImport + '/api/message-ids': { + id: '/api/message-ids' + path: '/api/message-ids' + fullPath: '/api/message-ids' + preLoaderRoute: typeof ApiMessageIdsRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-chat': { - id: '/api/mcp-apps-chat' - path: '/api/mcp-apps-chat' - fullPath: '/api/mcp-apps-chat' - preLoaderRoute: typeof ApiMcpAppsChatRouteImport + '/api/middleware-test': { + id: '/api/middleware-test' + path: '/api/middleware-test' + fullPath: '/api/middleware-test' + preLoaderRoute: typeof ApiMiddlewareTestRouteImport parentRoute: typeof rootRouteImport } - '/api/mcp-apps-call': { - id: '/api/mcp-apps-call' - path: '/api/mcp-apps-call' - fullPath: '/api/mcp-apps-call' - preLoaderRoute: typeof ApiMcpAppsCallRouteImport + '/api/mistral-strict-tool-null-wire': { + id: '/api/mistral-strict-tool-null-wire' + path: '/api/mistral-strict-tool-null-wire' + fullPath: '/api/mistral-strict-tool-null-wire' + preLoaderRoute: typeof ApiMistralStrictToolNullWireRouteImport parentRoute: typeof rootRouteImport } - '/api/max-tool-calls-wire': { - id: '/api/max-tool-calls-wire' - path: '/api/max-tool-calls-wire' - fullPath: '/api/max-tool-calls-wire' - preLoaderRoute: typeof ApiMaxToolCallsWireRouteImport + '/api/multimodal-tool-result-wire': { + id: '/api/multimodal-tool-result-wire' + path: '/api/multimodal-tool-result-wire' + fullPath: '/api/multimodal-tool-result-wire' + preLoaderRoute: typeof ApiMultimodalToolResultWireRouteImport parentRoute: typeof rootRouteImport } - '/api/lazy-tools-wire': { - id: '/api/lazy-tools-wire' - path: '/api/lazy-tools-wire' - fullPath: '/api/lazy-tools-wire' - preLoaderRoute: typeof ApiLazyToolsWireRouteImport + '/api/non-streaming-run-error': { + id: '/api/non-streaming-run-error' + path: '/api/non-streaming-run-error' + fullPath: '/api/non-streaming-run-error' + preLoaderRoute: typeof ApiNonStreamingRunErrorRouteImport + parentRoute: typeof rootRouteImport + } + '/api/openai-completed-response-text': { + id: '/api/openai-completed-response-text' + path: '/api/openai-completed-response-text' + fullPath: '/api/openai-completed-response-text' + preLoaderRoute: typeof ApiOpenaiCompletedResponseTextRouteImport parentRoute: typeof rootRouteImport } - '/api/join-run-client-tool': { - id: '/api/join-run-client-tool' - path: '/api/join-run-client-tool' - fullPath: '/api/join-run-client-tool' - preLoaderRoute: typeof ApiJoinRunClientToolRouteImport + '/api/openai-shell-skills-wire': { + id: '/api/openai-shell-skills-wire' + path: '/api/openai-shell-skills-wire' + fullPath: '/api/openai-shell-skills-wire' + preLoaderRoute: typeof ApiOpenaiShellSkillsWireRouteImport parentRoute: typeof rootRouteImport } - '/api/interrupts-test': { - id: '/api/interrupts-test' - path: '/api/interrupts-test' - fullPath: '/api/interrupts-test' - preLoaderRoute: typeof ApiInterruptsTestRouteImport + '/api/openai-strict-tool-null-wire': { + id: '/api/openai-strict-tool-null-wire' + path: '/api/openai-strict-tool-null-wire' + fullPath: '/api/openai-strict-tool-null-wire' + preLoaderRoute: typeof ApiOpenaiStrictToolNullWireRouteImport parentRoute: typeof rootRouteImport } - '/api/image': { - id: '/api/image' - path: '/api/image' - fullPath: '/api/image' - preLoaderRoute: typeof ApiImageRouteImport + '/api/openai-usage-details': { + id: '/api/openai-usage-details' + path: '/api/openai-usage-details' + fullPath: '/api/openai-usage-details' + preLoaderRoute: typeof ApiOpenaiUsageDetailsRouteImport parentRoute: typeof rootRouteImport } - '/api/generation-persistence-server': { - id: '/api/generation-persistence-server' - path: '/api/generation-persistence-server' - fullPath: '/api/generation-persistence-server' - preLoaderRoute: typeof ApiGenerationPersistenceServerRouteImport + '/api/openrouter-cost': { + id: '/api/openrouter-cost' + path: '/api/openrouter-cost' + fullPath: '/api/openrouter-cost' + preLoaderRoute: typeof ApiOpenrouterCostRouteImport parentRoute: typeof rootRouteImport } - '/api/generation-persistence-resume': { - id: '/api/generation-persistence-resume' - path: '/api/generation-persistence-resume' - fullPath: '/api/generation-persistence-resume' - preLoaderRoute: typeof ApiGenerationPersistenceResumeRouteImport + '/api/openrouter-json-object-wire': { + id: '/api/openrouter-json-object-wire' + path: '/api/openrouter-json-object-wire' + fullPath: '/api/openrouter-json-object-wire' + preLoaderRoute: typeof ApiOpenrouterJsonObjectWireRouteImport parentRoute: typeof rootRouteImport } - '/api/gemini-native-image-wire': { - id: '/api/gemini-native-image-wire' - path: '/api/gemini-native-image-wire' - fullPath: '/api/gemini-native-image-wire' - preLoaderRoute: typeof ApiGeminiNativeImageWireRouteImport + '/api/openrouter-reasoning-wire': { + id: '/api/openrouter-reasoning-wire' + path: '/api/openrouter-reasoning-wire' + fullPath: '/api/openrouter-reasoning-wire' + preLoaderRoute: typeof ApiOpenrouterReasoningWireRouteImport parentRoute: typeof rootRouteImport } - '/api/gemini-image-ga-models': { - id: '/api/gemini-image-ga-models' - path: '/api/gemini-image-ga-models' - fullPath: '/api/gemini-image-ga-models' - preLoaderRoute: typeof ApiGeminiImageGaModelsRouteImport + '/api/openrouter-web-tools-wire': { + id: '/api/openrouter-web-tools-wire' + path: '/api/openrouter-web-tools-wire' + fullPath: '/api/openrouter-web-tools-wire' + preLoaderRoute: typeof ApiOpenrouterWebToolsWireRouteImport parentRoute: typeof rootRouteImport } - '/api/foreign-interrupt': { - id: '/api/foreign-interrupt' - path: '/api/foreign-interrupt' - fullPath: '/api/foreign-interrupt' - preLoaderRoute: typeof ApiForeignInterruptRouteImport + '/api/otel-media': { + id: '/api/otel-media' + path: '/api/otel-media' + fullPath: '/api/otel-media' + preLoaderRoute: typeof ApiOtelMediaRouteImport parentRoute: typeof rootRouteImport } - '/api/embedding': { - id: '/api/embedding' - path: '/api/embedding' - fullPath: '/api/embedding' - preLoaderRoute: typeof ApiEmbeddingRouteImport + '/api/otel-transcription': { + id: '/api/otel-transcription' + path: '/api/otel-transcription' + fullPath: '/api/otel-transcription' + preLoaderRoute: typeof ApiOtelTranscriptionRouteImport parentRoute: typeof rootRouteImport } - '/api/durable-takeover': { - id: '/api/durable-takeover' - path: '/api/durable-takeover' - fullPath: '/api/durable-takeover' - preLoaderRoute: typeof ApiDurableTakeoverRouteImport + '/api/otel-usage': { + id: '/api/otel-usage' + path: '/api/otel-usage' + fullPath: '/api/otel-usage' + preLoaderRoute: typeof ApiOtelUsageRouteImport parentRoute: typeof rootRouteImport } - '/api/durable-delivery': { - id: '/api/durable-delivery' - path: '/api/durable-delivery' - fullPath: '/api/durable-delivery' - preLoaderRoute: typeof ApiDurableDeliveryRouteImport + '/api/persistence-durability': { + id: '/api/persistence-durability' + path: '/api/persistence-durability' + fullPath: '/api/persistence-durability' + preLoaderRoute: typeof ApiPersistenceDurabilityRouteImport parentRoute: typeof rootRouteImport } - '/api/devtools-memory': { - id: '/api/devtools-memory' - path: '/api/devtools-memory' - fullPath: '/api/devtools-memory' - preLoaderRoute: typeof ApiDevtoolsMemoryRouteImport + '/api/provider-tool-dispatch-wire': { + id: '/api/provider-tool-dispatch-wire' + path: '/api/provider-tool-dispatch-wire' + fullPath: '/api/provider-tool-dispatch-wire' + preLoaderRoute: typeof ApiProviderToolDispatchWireRouteImport parentRoute: typeof rootRouteImport } - '/api/chat': { - id: '/api/chat' - path: '/api/chat' - fullPath: '/api/chat' - preLoaderRoute: typeof ApiChatRouteImport + '/api/sandbox-durability': { + id: '/api/sandbox-durability' + path: '/api/sandbox-durability' + fullPath: '/api/sandbox-durability' + preLoaderRoute: typeof ApiSandboxDurabilityRouteImport parentRoute: typeof rootRouteImport } - '/api/byok-chat': { - id: '/api/byok-chat' - path: '/api/byok-chat' - fullPath: '/api/byok-chat' - preLoaderRoute: typeof ApiByokChatRouteImport + '/api/sandbox-file-persistence': { + id: '/api/sandbox-file-persistence' + path: '/api/sandbox-file-persistence' + fullPath: '/api/sandbox-file-persistence' + preLoaderRoute: typeof ApiSandboxFilePersistenceRouteImport parentRoute: typeof rootRouteImport } - '/api/byteplus-seedance-1080p-wire': { - id: '/api/byteplus-seedance-1080p-wire' - path: '/api/byteplus-seedance-1080p-wire' - fullPath: '/api/byteplus-seedance-1080p-wire' - preLoaderRoute: typeof ApiByteplusSeedance1080pWireRouteImport + '/api/sandbox-tool-history': { + id: '/api/sandbox-tool-history' + path: '/api/sandbox-tool-history' + fullPath: '/api/sandbox-tool-history' + preLoaderRoute: typeof ApiSandboxToolHistoryRouteImport parentRoute: typeof rootRouteImport } - '/api/audio': { - id: '/api/audio' - path: '/api/audio' - fullPath: '/api/audio' - preLoaderRoute: typeof ApiAudioRouteImport + '/api/summarize': { + id: '/api/summarize' + path: '/api/summarize' + fullPath: '/api/summarize' + preLoaderRoute: typeof ApiSummarizeRouteImport parentRoute: typeof rootRouteImport } - '/api/arktype-tool-wire': { - id: '/api/arktype-tool-wire' - path: '/api/arktype-tool-wire' - fullPath: '/api/arktype-tool-wire' - preLoaderRoute: typeof ApiArktypeToolWireRouteImport + '/api/tool-call-lifecycle-wire': { + id: '/api/tool-call-lifecycle-wire' + path: '/api/tool-call-lifecycle-wire' + fullPath: '/api/tool-call-lifecycle-wire' + preLoaderRoute: typeof ApiToolCallLifecycleWireRouteImport parentRoute: typeof rootRouteImport } - '/api/anthropic-structured-usage': { - id: '/api/anthropic-structured-usage' - path: '/api/anthropic-structured-usage' - fullPath: '/api/anthropic-structured-usage' - preLoaderRoute: typeof ApiAnthropicStructuredUsageRouteImport + '/api/tools-test': { + id: '/api/tools-test' + path: '/api/tools-test' + fullPath: '/api/tools-test' + preLoaderRoute: typeof ApiToolsTestRouteImport parentRoute: typeof rootRouteImport } - '/api/anthropic-skills-wire': { - id: '/api/anthropic-skills-wire' - path: '/api/anthropic-skills-wire' - fullPath: '/api/anthropic-skills-wire' - preLoaderRoute: typeof ApiAnthropicSkillsWireRouteImport + '/api/transcription': { + id: '/api/transcription' + path: '/api/transcription' + fullPath: '/api/transcription' + preLoaderRoute: typeof ApiTranscriptionRouteImport parentRoute: typeof rootRouteImport } - '/api/anthropic-bug-test': { - id: '/api/anthropic-bug-test' - path: '/api/anthropic-bug-test' - fullPath: '/api/anthropic-bug-test' - preLoaderRoute: typeof ApiAnthropicBugTestRouteImport + '/api/tts': { + id: '/api/tts' + path: '/api/tts' + fullPath: '/api/tts' + preLoaderRoute: typeof ApiTtsRouteImport parentRoute: typeof rootRouteImport } - '/$provider/$feature': { - id: '/$provider/$feature' - path: '/$provider/$feature' - fullPath: '/$provider/$feature' - preLoaderRoute: typeof ProviderFeatureRouteImport + '/api/video': { + id: '/api/video' + path: '/api/video' + fullPath: '/api/video' + preLoaderRoute: typeof ApiVideoRouteImport parentRoute: typeof rootRouteImport } - '/api/video/stream': { - id: '/api/video/stream' + '/api/audio/stream': { + id: '/api/audio/stream' path: '/stream' - fullPath: '/api/video/stream' - preLoaderRoute: typeof ApiVideoStreamRouteImport - parentRoute: typeof ApiVideoRoute + fullPath: '/api/audio/stream' + preLoaderRoute: typeof ApiAudioStreamRouteImport + parentRoute: typeof ApiAudioRoute } - '/api/tts/stream': { - id: '/api/tts/stream' + '/api/image/stream': { + id: '/api/image/stream' path: '/stream' - fullPath: '/api/tts/stream' - preLoaderRoute: typeof ApiTtsStreamRouteImport - parentRoute: typeof ApiTtsRoute + fullPath: '/api/image/stream' + preLoaderRoute: typeof ApiImageStreamRouteImport + parentRoute: typeof ApiImageRoute } '/api/transcription/stream': { id: '/api/transcription/stream' @@ -1712,19 +1712,19 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiTranscriptionStreamRouteImport parentRoute: typeof ApiTranscriptionRoute } - '/api/image/stream': { - id: '/api/image/stream' + '/api/tts/stream': { + id: '/api/tts/stream' path: '/stream' - fullPath: '/api/image/stream' - preLoaderRoute: typeof ApiImageStreamRouteImport - parentRoute: typeof ApiImageRoute + fullPath: '/api/tts/stream' + preLoaderRoute: typeof ApiTtsStreamRouteImport + parentRoute: typeof ApiTtsRoute } - '/api/audio/stream': { - id: '/api/audio/stream' + '/api/video/stream': { + id: '/api/video/stream' path: '/stream' - fullPath: '/api/audio/stream' - preLoaderRoute: typeof ApiAudioStreamRouteImport - parentRoute: typeof ApiAudioRoute + fullPath: '/api/video/stream' + preLoaderRoute: typeof ApiVideoStreamRouteImport + parentRoute: typeof ApiVideoRoute } } } diff --git a/testing/e2e/tests/chat.spec.ts b/testing/e2e/tests/chat.spec.ts index c0e5b25663..0e0f5a7b8b 100644 --- a/testing/e2e/tests/chat.spec.ts +++ b/testing/e2e/tests/chat.spec.ts @@ -224,6 +224,26 @@ test.describe('openai chat persistence', () => { 'Fender Stratocaster', ) + const hydrationErrors: Array = [] + page.on('pageerror', (error) => { + if ( + /hydration failed|hydrated but|server rendered html/i.test( + error.message, + ) + ) { + hydrationErrors.push(error.message) + } + }) + page.on('console', (message) => { + const text = message.text() + if ( + message.type() === 'error' && + /hydration failed|hydrated but|server rendered html/i.test(text) + ) { + hydrationErrors.push(text) + } + }) + await page.reload() await expect(page.getByTestId('user-message')).toContainText( @@ -232,6 +252,7 @@ test.describe('openai chat persistence', () => { await expect(page.getByTestId('assistant-message')).toContainText( 'Fender Stratocaster', ) + expect(hydrationErrors).toEqual([]) }) test('clear() removes the persisted conversation so a reload starts empty', async ({ diff --git a/testing/e2e/tests/generic-middleware-interrupts.spec.ts b/testing/e2e/tests/generic-middleware-interrupts.spec.ts index 083e841fd3..d6b65b69bf 100644 --- a/testing/e2e/tests/generic-middleware-interrupts.spec.ts +++ b/testing/e2e/tests/generic-middleware-interrupts.spec.ts @@ -307,6 +307,23 @@ test('restores a pending generic interrupt after reload and resumes it', async ( aimockPort, }) => { await startScenario(page, testId, aimockPort, 'generic-before-model') + const hydrationErrors: Array = [] + page.on('pageerror', (error) => { + if ( + /hydration failed|hydrated but|server rendered html/i.test(error.message) + ) { + hydrationErrors.push(error.message) + } + }) + page.on('console', (message) => { + const text = message.text() + if ( + message.type() === 'error' && + /hydration failed|hydrated but|server rendered html/i.test(text) + ) { + hydrationErrors.push(text) + } + }) await page.reload() await expect(page.getByTestId('generic-review-plan')).toBeVisible() await resolveReview(page) @@ -318,4 +335,5 @@ test('restores a pending generic interrupt after reload and resumes it', async ( expect(capture.resolutions).toEqual([ expect.objectContaining({ status: 'resolved' }), ]) + expect(hydrationErrors).toEqual([]) }) diff --git a/testing/e2e/tests/helpers.ts b/testing/e2e/tests/helpers.ts index de8ddfb72c..8cbc6df980 100644 --- a/testing/e2e/tests/helpers.ts +++ b/testing/e2e/tests/helpers.ts @@ -187,28 +187,32 @@ export async function getTranscriptionResult(page: Page): Promise { export async function fillPrompt(page: Page, text: string) { const input = page.getByTestId('prompt-input') - await input.click() - await input.fill(text) - await input.dispatchEvent('input', { bubbles: true }) - // If fill() didn't trigger React onChange, fall back to pressSequentially const btn = page.getByTestId('generate-button') - if (await btn.isDisabled()) { - await input.clear() - await input.pressSequentially(text, { delay: 30 }) - } + await expect + .poll(async () => { + await input.click() + await input.fill('') + await input.dispatchEvent('input', { bubbles: true }) + await input.fill(text) + await input.dispatchEvent('input', { bubbles: true }) + return btn.isEnabled() + }) + .toBe(true) } export async function fillTextInput(page: Page, text: string) { const input = page.getByTestId('text-input') - await input.click() - await input.fill(text) - await input.dispatchEvent('input', { bubbles: true }) - // If fill() didn't trigger React onChange, fall back to pressSequentially const btn = page.getByTestId('generate-button') - if (await btn.isDisabled()) { - await input.clear() - await input.pressSequentially(text, { delay: 30 }) - } + await expect + .poll(async () => { + await input.click() + await input.fill('') + await input.dispatchEvent('input', { bubbles: true }) + await input.fill(text) + await input.dispatchEvent('input', { bubbles: true }) + return btn.isEnabled() + }) + .toBe(true) } export async function clickGenerate(page: Page) { diff --git a/testing/e2e/tests/image-gen.spec.ts b/testing/e2e/tests/image-gen.spec.ts index 7997996fa5..927b6d77a5 100644 --- a/testing/e2e/tests/image-gen.spec.ts +++ b/testing/e2e/tests/image-gen.spec.ts @@ -20,6 +20,10 @@ for (const provider of providersFor('image-gen')) { await fillPrompt(page, 'a guitar in a music store') await clickGenerate(page) await waitForGenerationComplete(page) + await expect(page.getByTestId('active-run-id')).not.toHaveText('') + await expect(page.getByTestId('active-status')).toHaveText('generating') + await expect(page.getByTestId('active-loading')).toHaveText('true') + await expect(page.getByTestId('final-run-id')).toHaveText('') const images = page.getByTestId('generated-image') await expect(images).toHaveCount(1) }) @@ -35,6 +39,10 @@ for (const provider of providersFor('image-gen')) { await fillPrompt(page, 'a guitar in a music store') await clickGenerate(page) await waitForGenerationComplete(page) + await expect(page.getByTestId('active-run-id')).not.toHaveText('') + await expect(page.getByTestId('active-status')).toHaveText('generating') + await expect(page.getByTestId('active-loading')).toHaveText('true') + await expect(page.getByTestId('final-run-id')).toHaveText('') const images = page.getByTestId('generated-image') await expect(images).toHaveCount(1) }) @@ -50,6 +58,10 @@ for (const provider of providersFor('image-gen')) { await fillPrompt(page, 'a guitar in a music store') await clickGenerate(page) await waitForGenerationComplete(page) + await expect(page.getByTestId('active-run-id')).not.toHaveText('') + await expect(page.getByTestId('active-status')).toHaveText('generating') + await expect(page.getByTestId('active-loading')).toHaveText('true') + await expect(page.getByTestId('final-run-id')).toHaveText('') const images = page.getByTestId('generated-image') await expect(images).toHaveCount(1) }) diff --git a/testing/e2e/tests/persistence-durability.spec.ts b/testing/e2e/tests/persistence-durability.spec.ts index 0c57dcd9ff..a6af750e7f 100644 --- a/testing/e2e/tests/persistence-durability.spec.ts +++ b/testing/e2e/tests/persistence-durability.spec.ts @@ -59,6 +59,26 @@ test.describe('persistence durability (browser refresh)', () => { expect(Array.isArray(record.messages)).toBe(true) expect(record.messages.length).toBeGreaterThanOrEqual(2) + const hydrationErrors: Array = [] + page.on('pageerror', (error) => { + if ( + /hydration failed|hydrated but|server rendered html/i.test( + error.message, + ) + ) { + hydrationErrors.push(error.message) + } + }) + page.on('console', (message) => { + const text = message.text() + if ( + message.type() === 'error' && + /hydration failed|hydrated but|server rendered html/i.test(text) + ) { + hydrationErrors.push(text) + } + }) + // Full reload: the conversation must come back from localStorage alone. await page.reload() @@ -71,6 +91,7 @@ test.describe('persistence durability (browser refresh)', () => { ) // No in-flight run to rejoin after a clean finish — the page settles idle. await expect(page.getByTestId('loading-indicator')).toHaveCount(0) + expect(hydrationErrors).toEqual([]) }) test('a pending interrupt survives a reload (rehydrated from the resume snapshot)', async ({ @@ -98,6 +119,26 @@ test.describe('persistence durability (browser refresh)', () => { const record = JSON.parse(stored!) as { resume?: unknown } expect(record.resume).toBeTruthy() + const hydrationErrors: Array = [] + page.on('pageerror', (error) => { + if ( + /hydration failed|hydrated but|server rendered html/i.test( + error.message, + ) + ) { + hydrationErrors.push(error.message) + } + }) + page.on('console', (message) => { + const text = message.text() + if ( + message.type() === 'error' && + /hydration failed|hydrated but|server rendered html/i.test(text) + ) { + hydrationErrors.push(text) + } + }) + // Full reload: the interrupt must rehydrate from localStorage, not a refetch. await page.reload() @@ -108,6 +149,7 @@ test.describe('persistence durability (browser refresh)', () => { await expect(page.getByTestId('interrupt-confirm-shipment')).toBeVisible() await expect(page.getByTestId('interrupt-kind')).toHaveText('generic') await expect(page.getByTestId('interrupt-source')).toHaveText('hydrate') + expect(hydrationErrors).toEqual([]) }) test('restores a pending interrupt from the SERVER on a fresh load (persistence: true)', async ({ diff --git a/testing/e2e/tests/video-gen.spec.ts b/testing/e2e/tests/video-gen.spec.ts index 841fbccb8e..d7f5da6a62 100644 --- a/testing/e2e/tests/video-gen.spec.ts +++ b/testing/e2e/tests/video-gen.spec.ts @@ -20,6 +20,10 @@ for (const provider of providersFor('video-gen')) { await fillPrompt(page, 'a guitar being played in a store') await clickGenerate(page) await waitForGenerationComplete(page, 60_000) + await expect(page.getByTestId('active-run-id')).not.toHaveText('') + await expect(page.getByTestId('active-status')).toHaveText('generating') + await expect(page.getByTestId('active-loading')).toHaveText('true') + await expect(page.getByTestId('final-run-id')).toHaveText('') const video = page.getByTestId('generated-video') await expect(video).toBeVisible() }) @@ -35,6 +39,10 @@ for (const provider of providersFor('video-gen')) { await fillPrompt(page, 'a guitar being played in a store') await clickGenerate(page) await waitForGenerationComplete(page, 60_000) + await expect(page.getByTestId('active-run-id')).not.toHaveText('') + await expect(page.getByTestId('active-status')).toHaveText('generating') + await expect(page.getByTestId('active-loading')).toHaveText('true') + await expect(page.getByTestId('final-run-id')).toHaveText('') const video = page.getByTestId('generated-video') await expect(video).toBeVisible() }) @@ -50,6 +58,10 @@ for (const provider of providersFor('video-gen')) { await fillPrompt(page, 'a guitar being played in a store') await clickGenerate(page) await waitForGenerationComplete(page, 60_000) + await expect(page.getByTestId('active-run-id')).not.toHaveText('') + await expect(page.getByTestId('active-status')).toHaveText('generating') + await expect(page.getByTestId('active-loading')).toHaveText('true') + await expect(page.getByTestId('final-run-id')).toHaveText('') const video = page.getByTestId('generated-video') await expect(video).toBeVisible() }) diff --git a/testing/panel/package.json b/testing/panel/package.json index 7b1e3d63be..583e07d6de 100644 --- a/testing/panel/package.json +++ b/testing/panel/package.json @@ -27,8 +27,8 @@ "@tanstack/nitro-v2-vite-plugin": "^1.155.0", "@tanstack/react-ai-devtools": "workspace:*", "@tanstack/react-devtools": "^0.9.10", - "@tanstack/react-router": "^1.158.4", - "@tanstack/react-start": "^1.159.0", + "@tanstack/react-router": "^1.170.32", + "@tanstack/react-start": "^1.168.49", "@tanstack/start": "^1.120.20", "highlight.js": "^11.11.1", "lucide-react": "^0.561.0", diff --git a/testing/panel/src/routeTree.gen.ts b/testing/panel/src/routeTree.gen.ts index c9ce6fdaf3..3cea702cd0 100644 --- a/testing/panel/src/routeTree.gen.ts +++ b/testing/panel/src/routeTree.gen.ts @@ -9,54 +9,54 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as VideoRouteImport } from './routes/video' -import { Route as TtsRouteImport } from './routes/tts' -import { Route as TranscriptionRouteImport } from './routes/transcription' -import { Route as SummarizeRouteImport } from './routes/summarize' -import { Route as StructuredRouteImport } from './routes/structured' -import { Route as StreamDebuggerRouteImport } from './routes/stream-debugger' -import { Route as SimulatorRouteImport } from './routes/simulator' -import { Route as MemoryRouteImport } from './routes/memory' -import { Route as ImageRouteImport } from './routes/image' -import { Route as AddonManagerRouteImport } from './routes/addon-manager' import { Route as IndexRouteImport } from './routes/index' -import { Route as ApiVideoRouteImport } from './routes/api.video' -import { Route as ApiTtsRouteImport } from './routes/api.tts' -import { Route as ApiTranscriptionRouteImport } from './routes/api.transcription' -import { Route as ApiSummarizeRouteImport } from './routes/api.summarize' -import { Route as ApiStructuredRouteImport } from './routes/api.structured' -import { Route as ApiSimulatorChatRouteImport } from './routes/api.simulator-chat' -import { Route as ApiMemoryInspectRouteImport } from './routes/api.memory-inspect' -import { Route as ApiMemoryChatRouteImport } from './routes/api.memory-chat' -import { Route as ApiLoadTraceRouteImport } from './routes/api.load-trace' -import { Route as ApiListTracesRouteImport } from './routes/api.list-traces' -import { Route as ApiImageRouteImport } from './routes/api.image' -import { Route as ApiChatRouteImport } from './routes/api.chat' +import { Route as AddonManagerRouteImport } from './routes/addon-manager' +import { Route as ImageRouteImport } from './routes/image' +import { Route as MemoryRouteImport } from './routes/memory' +import { Route as SimulatorRouteImport } from './routes/simulator' +import { Route as StreamDebuggerRouteImport } from './routes/stream-debugger' +import { Route as StructuredRouteImport } from './routes/structured' +import { Route as SummarizeRouteImport } from './routes/summarize' +import { Route as TranscriptionRouteImport } from './routes/transcription' +import { Route as TtsRouteImport } from './routes/tts' +import { Route as VideoRouteImport } from './routes/video' import { Route as ApiAddonChatRouteImport } from './routes/api.addon-chat' +import { Route as ApiChatRouteImport } from './routes/api.chat' +import { Route as ApiImageRouteImport } from './routes/api.image' +import { Route as ApiListTracesRouteImport } from './routes/api.list-traces' +import { Route as ApiLoadTraceRouteImport } from './routes/api.load-trace' +import { Route as ApiMemoryChatRouteImport } from './routes/api.memory-chat' +import { Route as ApiMemoryInspectRouteImport } from './routes/api.memory-inspect' +import { Route as ApiSimulatorChatRouteImport } from './routes/api.simulator-chat' +import { Route as ApiStructuredRouteImport } from './routes/api.structured' +import { Route as ApiSummarizeRouteImport } from './routes/api.summarize' +import { Route as ApiTranscriptionRouteImport } from './routes/api.transcription' +import { Route as ApiTtsRouteImport } from './routes/api.tts' +import { Route as ApiVideoRouteImport } from './routes/api.video' -const VideoRoute = VideoRouteImport.update({ - id: '/video', - path: '/video', +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', getParentRoute: () => rootRouteImport, } as any) -const TtsRoute = TtsRouteImport.update({ - id: '/tts', - path: '/tts', +const AddonManagerRoute = AddonManagerRouteImport.update({ + id: '/addon-manager', + path: '/addon-manager', getParentRoute: () => rootRouteImport, } as any) -const TranscriptionRoute = TranscriptionRouteImport.update({ - id: '/transcription', - path: '/transcription', +const ImageRoute = ImageRouteImport.update({ + id: '/image', + path: '/image', getParentRoute: () => rootRouteImport, } as any) -const SummarizeRoute = SummarizeRouteImport.update({ - id: '/summarize', - path: '/summarize', +const MemoryRoute = MemoryRouteImport.update({ + id: '/memory', + path: '/memory', getParentRoute: () => rootRouteImport, } as any) -const StructuredRoute = StructuredRouteImport.update({ - id: '/structured', - path: '/structured', +const SimulatorRoute = SimulatorRouteImport.update({ + id: '/simulator', + path: '/simulator', getParentRoute: () => rootRouteImport, } as any) const StreamDebuggerRoute = StreamDebuggerRouteImport.update({ @@ -64,59 +64,59 @@ const StreamDebuggerRoute = StreamDebuggerRouteImport.update({ path: '/stream-debugger', getParentRoute: () => rootRouteImport, } as any) -const SimulatorRoute = SimulatorRouteImport.update({ - id: '/simulator', - path: '/simulator', +const StructuredRoute = StructuredRouteImport.update({ + id: '/structured', + path: '/structured', getParentRoute: () => rootRouteImport, } as any) -const MemoryRoute = MemoryRouteImport.update({ - id: '/memory', - path: '/memory', +const SummarizeRoute = SummarizeRouteImport.update({ + id: '/summarize', + path: '/summarize', getParentRoute: () => rootRouteImport, } as any) -const ImageRoute = ImageRouteImport.update({ - id: '/image', - path: '/image', +const TranscriptionRoute = TranscriptionRouteImport.update({ + id: '/transcription', + path: '/transcription', getParentRoute: () => rootRouteImport, } as any) -const AddonManagerRoute = AddonManagerRouteImport.update({ - id: '/addon-manager', - path: '/addon-manager', +const TtsRoute = TtsRouteImport.update({ + id: '/tts', + path: '/tts', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const VideoRoute = VideoRouteImport.update({ + id: '/video', + path: '/video', getParentRoute: () => rootRouteImport, } as any) -const ApiVideoRoute = ApiVideoRouteImport.update({ - id: '/api/video', - path: '/api/video', +const ApiAddonChatRoute = ApiAddonChatRouteImport.update({ + id: '/api/addon-chat', + path: '/api/addon-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiTtsRoute = ApiTtsRouteImport.update({ - id: '/api/tts', - path: '/api/tts', +const ApiChatRoute = ApiChatRouteImport.update({ + id: '/api/chat', + path: '/api/chat', getParentRoute: () => rootRouteImport, } as any) -const ApiTranscriptionRoute = ApiTranscriptionRouteImport.update({ - id: '/api/transcription', - path: '/api/transcription', +const ApiImageRoute = ApiImageRouteImport.update({ + id: '/api/image', + path: '/api/image', getParentRoute: () => rootRouteImport, } as any) -const ApiSummarizeRoute = ApiSummarizeRouteImport.update({ - id: '/api/summarize', - path: '/api/summarize', +const ApiListTracesRoute = ApiListTracesRouteImport.update({ + id: '/api/list-traces', + path: '/api/list-traces', getParentRoute: () => rootRouteImport, } as any) -const ApiStructuredRoute = ApiStructuredRouteImport.update({ - id: '/api/structured', - path: '/api/structured', +const ApiLoadTraceRoute = ApiLoadTraceRouteImport.update({ + id: '/api/load-trace', + path: '/api/load-trace', getParentRoute: () => rootRouteImport, } as any) -const ApiSimulatorChatRoute = ApiSimulatorChatRouteImport.update({ - id: '/api/simulator-chat', - path: '/api/simulator-chat', +const ApiMemoryChatRoute = ApiMemoryChatRouteImport.update({ + id: '/api/memory-chat', + path: '/api/memory-chat', getParentRoute: () => rootRouteImport, } as any) const ApiMemoryInspectRoute = ApiMemoryInspectRouteImport.update({ @@ -124,34 +124,34 @@ const ApiMemoryInspectRoute = ApiMemoryInspectRouteImport.update({ path: '/api/memory-inspect', getParentRoute: () => rootRouteImport, } as any) -const ApiMemoryChatRoute = ApiMemoryChatRouteImport.update({ - id: '/api/memory-chat', - path: '/api/memory-chat', +const ApiSimulatorChatRoute = ApiSimulatorChatRouteImport.update({ + id: '/api/simulator-chat', + path: '/api/simulator-chat', getParentRoute: () => rootRouteImport, } as any) -const ApiLoadTraceRoute = ApiLoadTraceRouteImport.update({ - id: '/api/load-trace', - path: '/api/load-trace', +const ApiStructuredRoute = ApiStructuredRouteImport.update({ + id: '/api/structured', + path: '/api/structured', getParentRoute: () => rootRouteImport, } as any) -const ApiListTracesRoute = ApiListTracesRouteImport.update({ - id: '/api/list-traces', - path: '/api/list-traces', +const ApiSummarizeRoute = ApiSummarizeRouteImport.update({ + id: '/api/summarize', + path: '/api/summarize', getParentRoute: () => rootRouteImport, } as any) -const ApiImageRoute = ApiImageRouteImport.update({ - id: '/api/image', - path: '/api/image', +const ApiTranscriptionRoute = ApiTranscriptionRouteImport.update({ + id: '/api/transcription', + path: '/api/transcription', getParentRoute: () => rootRouteImport, } as any) -const ApiChatRoute = ApiChatRouteImport.update({ - id: '/api/chat', - path: '/api/chat', +const ApiTtsRoute = ApiTtsRouteImport.update({ + id: '/api/tts', + path: '/api/tts', getParentRoute: () => rootRouteImport, } as any) -const ApiAddonChatRoute = ApiAddonChatRouteImport.update({ - id: '/api/addon-chat', - path: '/api/addon-chat', +const ApiVideoRoute = ApiVideoRouteImport.update({ + id: '/api/video', + path: '/api/video', getParentRoute: () => rootRouteImport, } as any) @@ -344,39 +344,39 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/video': { - id: '/video' - path: '/video' - fullPath: '/video' - preLoaderRoute: typeof VideoRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/tts': { - id: '/tts' - path: '/tts' - fullPath: '/tts' - preLoaderRoute: typeof TtsRouteImport + '/addon-manager': { + id: '/addon-manager' + path: '/addon-manager' + fullPath: '/addon-manager' + preLoaderRoute: typeof AddonManagerRouteImport parentRoute: typeof rootRouteImport } - '/transcription': { - id: '/transcription' - path: '/transcription' - fullPath: '/transcription' - preLoaderRoute: typeof TranscriptionRouteImport + '/image': { + id: '/image' + path: '/image' + fullPath: '/image' + preLoaderRoute: typeof ImageRouteImport parentRoute: typeof rootRouteImport } - '/summarize': { - id: '/summarize' - path: '/summarize' - fullPath: '/summarize' - preLoaderRoute: typeof SummarizeRouteImport + '/memory': { + id: '/memory' + path: '/memory' + fullPath: '/memory' + preLoaderRoute: typeof MemoryRouteImport parentRoute: typeof rootRouteImport } - '/structured': { - id: '/structured' - path: '/structured' - fullPath: '/structured' - preLoaderRoute: typeof StructuredRouteImport + '/simulator': { + id: '/simulator' + path: '/simulator' + fullPath: '/simulator' + preLoaderRoute: typeof SimulatorRouteImport parentRoute: typeof rootRouteImport } '/stream-debugger': { @@ -386,81 +386,81 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof StreamDebuggerRouteImport parentRoute: typeof rootRouteImport } - '/simulator': { - id: '/simulator' - path: '/simulator' - fullPath: '/simulator' - preLoaderRoute: typeof SimulatorRouteImport + '/structured': { + id: '/structured' + path: '/structured' + fullPath: '/structured' + preLoaderRoute: typeof StructuredRouteImport parentRoute: typeof rootRouteImport } - '/memory': { - id: '/memory' - path: '/memory' - fullPath: '/memory' - preLoaderRoute: typeof MemoryRouteImport + '/summarize': { + id: '/summarize' + path: '/summarize' + fullPath: '/summarize' + preLoaderRoute: typeof SummarizeRouteImport parentRoute: typeof rootRouteImport } - '/image': { - id: '/image' - path: '/image' - fullPath: '/image' - preLoaderRoute: typeof ImageRouteImport + '/transcription': { + id: '/transcription' + path: '/transcription' + fullPath: '/transcription' + preLoaderRoute: typeof TranscriptionRouteImport parentRoute: typeof rootRouteImport } - '/addon-manager': { - id: '/addon-manager' - path: '/addon-manager' - fullPath: '/addon-manager' - preLoaderRoute: typeof AddonManagerRouteImport + '/tts': { + id: '/tts' + path: '/tts' + fullPath: '/tts' + preLoaderRoute: typeof TtsRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/video': { + id: '/video' + path: '/video' + fullPath: '/video' + preLoaderRoute: typeof VideoRouteImport parentRoute: typeof rootRouteImport } - '/api/video': { - id: '/api/video' - path: '/api/video' - fullPath: '/api/video' - preLoaderRoute: typeof ApiVideoRouteImport + '/api/addon-chat': { + id: '/api/addon-chat' + path: '/api/addon-chat' + fullPath: '/api/addon-chat' + preLoaderRoute: typeof ApiAddonChatRouteImport parentRoute: typeof rootRouteImport } - '/api/tts': { - id: '/api/tts' - path: '/api/tts' - fullPath: '/api/tts' - preLoaderRoute: typeof ApiTtsRouteImport + '/api/chat': { + id: '/api/chat' + path: '/api/chat' + fullPath: '/api/chat' + preLoaderRoute: typeof ApiChatRouteImport parentRoute: typeof rootRouteImport } - '/api/transcription': { - id: '/api/transcription' - path: '/api/transcription' - fullPath: '/api/transcription' - preLoaderRoute: typeof ApiTranscriptionRouteImport + '/api/image': { + id: '/api/image' + path: '/api/image' + fullPath: '/api/image' + preLoaderRoute: typeof ApiImageRouteImport parentRoute: typeof rootRouteImport } - '/api/summarize': { - id: '/api/summarize' - path: '/api/summarize' - fullPath: '/api/summarize' - preLoaderRoute: typeof ApiSummarizeRouteImport + '/api/list-traces': { + id: '/api/list-traces' + path: '/api/list-traces' + fullPath: '/api/list-traces' + preLoaderRoute: typeof ApiListTracesRouteImport parentRoute: typeof rootRouteImport } - '/api/structured': { - id: '/api/structured' - path: '/api/structured' - fullPath: '/api/structured' - preLoaderRoute: typeof ApiStructuredRouteImport + '/api/load-trace': { + id: '/api/load-trace' + path: '/api/load-trace' + fullPath: '/api/load-trace' + preLoaderRoute: typeof ApiLoadTraceRouteImport parentRoute: typeof rootRouteImport } - '/api/simulator-chat': { - id: '/api/simulator-chat' - path: '/api/simulator-chat' - fullPath: '/api/simulator-chat' - preLoaderRoute: typeof ApiSimulatorChatRouteImport + '/api/memory-chat': { + id: '/api/memory-chat' + path: '/api/memory-chat' + fullPath: '/api/memory-chat' + preLoaderRoute: typeof ApiMemoryChatRouteImport parentRoute: typeof rootRouteImport } '/api/memory-inspect': { @@ -470,46 +470,46 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiMemoryInspectRouteImport parentRoute: typeof rootRouteImport } - '/api/memory-chat': { - id: '/api/memory-chat' - path: '/api/memory-chat' - fullPath: '/api/memory-chat' - preLoaderRoute: typeof ApiMemoryChatRouteImport + '/api/simulator-chat': { + id: '/api/simulator-chat' + path: '/api/simulator-chat' + fullPath: '/api/simulator-chat' + preLoaderRoute: typeof ApiSimulatorChatRouteImport parentRoute: typeof rootRouteImport } - '/api/load-trace': { - id: '/api/load-trace' - path: '/api/load-trace' - fullPath: '/api/load-trace' - preLoaderRoute: typeof ApiLoadTraceRouteImport + '/api/structured': { + id: '/api/structured' + path: '/api/structured' + fullPath: '/api/structured' + preLoaderRoute: typeof ApiStructuredRouteImport parentRoute: typeof rootRouteImport } - '/api/list-traces': { - id: '/api/list-traces' - path: '/api/list-traces' - fullPath: '/api/list-traces' - preLoaderRoute: typeof ApiListTracesRouteImport + '/api/summarize': { + id: '/api/summarize' + path: '/api/summarize' + fullPath: '/api/summarize' + preLoaderRoute: typeof ApiSummarizeRouteImport parentRoute: typeof rootRouteImport } - '/api/image': { - id: '/api/image' - path: '/api/image' - fullPath: '/api/image' - preLoaderRoute: typeof ApiImageRouteImport + '/api/transcription': { + id: '/api/transcription' + path: '/api/transcription' + fullPath: '/api/transcription' + preLoaderRoute: typeof ApiTranscriptionRouteImport parentRoute: typeof rootRouteImport } - '/api/chat': { - id: '/api/chat' - path: '/api/chat' - fullPath: '/api/chat' - preLoaderRoute: typeof ApiChatRouteImport + '/api/tts': { + id: '/api/tts' + path: '/api/tts' + fullPath: '/api/tts' + preLoaderRoute: typeof ApiTtsRouteImport parentRoute: typeof rootRouteImport } - '/api/addon-chat': { - id: '/api/addon-chat' - path: '/api/addon-chat' - fullPath: '/api/addon-chat' - preLoaderRoute: typeof ApiAddonChatRouteImport + '/api/video': { + id: '/api/video' + path: '/api/video' + fullPath: '/api/video' + preLoaderRoute: typeof ApiVideoRouteImport parentRoute: typeof rootRouteImport } }