Skip to content

Commit 035fba2

Browse files
committed
refactor(tanstack-ai): unify names with the Vercel convention (createSimplePDFTools / simplePDFToolDefinitions / useEmbedTools) + keep server defs React-free
1 parent 063decf commit 035fba2

7 files changed

Lines changed: 35 additions & 34 deletions

File tree

.changeset/tanstack-ai-adapter.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55

66
Add a TanStack AI adapter (the `/tanstack-ai` subpath) for client-side tool calling, alongside the existing Vercel AI SDK (`/ai-sdk`) adapter. Both wrap the same generated tool registry + bridge router, so the editor is drivable from either SDK with no duplicated logic.
77

8-
- `@simplepdf/embed/tanstack-ai`: `simplePDFTanstackToolDefinitions()` (server, for `chat({ tools })`) and `createSimplePDFTanstackTools({ embed })` (browser `.client()` tools for `clientTools(...)` then `useChat({ tools })`).
9-
- `@simplepdf/react-embed-pdf/tanstack-ai`: `useEmbedTanstackTools(embedRef)`, the editor-bound client tools, plus the re-exported server definitions.
8+
- `@simplepdf/embed/tanstack-ai`: `simplePDFToolDefinitions()` (server, for `chat({ tools })`) and `createSimplePDFTools({ embed })` (browser `.client()` tools for `clientTools(...)` then `useChat({ tools })`).
9+
- `@simplepdf/react-embed-pdf/tanstack-ai`: `useEmbedTools(embedRef)`, the editor-bound client tools. Server definitions stay in the React-free core `@simplepdf/embed/tanstack-ai`, so a server route never pulls React in.
1010
- `@tanstack/ai` is a new optional peer, pulled only by the `/tanstack-ai` subpath; the package roots stay free of it (and of `zod`).

embed/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ For TanStack AI, the same registry is exposed via `@simplepdf/embed/tanstack-ai`
5353

5454
```ts
5555
// server: execute-less definitions so the model is aware of the tools
56-
import { simplePDFTanstackToolDefinitions } from '@simplepdf/embed/tanstack-ai'
57-
chat({ adapter, messages, tools: simplePDFTanstackToolDefinitions() })
56+
import { simplePDFToolDefinitions } from '@simplepdf/embed/tanstack-ai'
57+
chat({ adapter, messages, tools: simplePDFToolDefinitions() })
5858

5959
// browser: the same definitions bound to the live editor via .client()
6060
import { clientTools } from '@tanstack/ai-react'
61-
import { createSimplePDFTanstackTools } from '@simplepdf/embed/tanstack-ai'
62-
useChat({ connection, tools: clientTools(...createSimplePDFTanstackTools({ embed })) })
61+
import { createSimplePDFTools } from '@simplepdf/embed/tanstack-ai'
62+
useChat({ connection, tools: clientTools(...createSimplePDFTools({ embed })) })
6363
```
6464

6565
## Install
@@ -79,7 +79,7 @@ Zero runtime dependencies at the root. `zod` is an optional peer, needed by the
7979
| `@simplepdf/embed/schemas` | zod schema for every operation input | `zod` |
8080
| `@simplepdf/embed/tools` | SDK-agnostic agentic tool registry + `routeToolCall` + `isSimplePDFToolName` | `zod` |
8181
| `@simplepdf/embed/ai-sdk` | `simplePDFToolDefinitions()` (server) + `createSimplePDFExecutor({ embed })` (browser) for the Vercel AI SDK | `zod` |
82-
| `@simplepdf/embed/tanstack-ai` | `simplePDFTanstackToolDefinitions()` (server) + `createSimplePDFTanstackTools({ embed })` (browser) for TanStack AI | `zod`, `@tanstack/ai` |
82+
| `@simplepdf/embed/tanstack-ai` | `simplePDFToolDefinitions()` (server) + `createSimplePDFTools({ embed })` (browser) for TanStack AI | `zod`, `@tanstack/ai` |
8383

8484
## Where the editor goes
8585

embed/src/tanstack-ai.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ const define = (name: SimplePDFToolName) =>
2626
// Server: execute-less definitions for `chat({ tools })`, so the model is aware of
2727
// the tools. A fresh array each call so the host can pick/omit (e.g. gate submit XOR
2828
// download) without mutating shared state.
29-
export const simplePDFTanstackToolDefinitions = (): ReturnType<typeof define>[] => TOOL_NAMES.map(define)
29+
export const simplePDFToolDefinitions = (): ReturnType<typeof define>[] => TOOL_NAMES.map(define)
3030

3131
// Browser: the same definitions bound to the live editor via `.client()`, for
3232
// `clientTools(...)` -> `useChat({ tools })`. Each call validates input against the
3333
// tool schema and dispatches to the matching editor action, resolving to a BridgeResult.
34-
export const createSimplePDFTanstackTools = ({ embed }: { embed: Embed }): AnyClientTool[] =>
34+
export const createSimplePDFTools = ({ embed }: { embed: Embed }): AnyClientTool[] =>
3535
TOOL_NAMES.map((name) => define(name).client((input) => routeToolCall(embed.actions, name, input)))

embed/test/tanstack-ai.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { describe, expect, it } from 'vitest'
2-
import { createSimplePDFTanstackTools, simplePDFTanstackToolDefinitions } from '../src/tanstack-ai'
2+
import { createSimplePDFTools, simplePDFToolDefinitions } from '../src/tanstack-ai'
33
import type { BridgeResult } from '../src/types'
44
import { makeEmbedStub } from './helpers'
55

6-
describe('simplePDFTanstackToolDefinitions', () => {
6+
describe('simplePDFToolDefinitions', () => {
77
it('returns the 14 agentic operations as execute-less definitions (loadDocument excluded)', () => {
8-
const definitions = simplePDFTanstackToolDefinitions()
8+
const definitions = simplePDFToolDefinitions()
99
expect(definitions).toHaveLength(14)
1010
expect(definitions.map((definition) => definition.name)).not.toContain('loadDocument')
1111
for (const definition of definitions) {
@@ -15,16 +15,16 @@ describe('simplePDFTanstackToolDefinitions', () => {
1515
})
1616
})
1717

18-
describe('createSimplePDFTanstackTools', () => {
18+
describe('createSimplePDFTools', () => {
1919
it('produces a client tool for each of the 14 agentic operations', () => {
20-
const tools = createSimplePDFTanstackTools({ embed: makeEmbedStub() })
20+
const tools = createSimplePDFTools({ embed: makeEmbedStub() })
2121
expect(tools).toHaveLength(14)
2222
expect(tools.every((tool) => typeof tool.execute === 'function')).toBe(true)
2323
})
2424

2525
it('binds each tool to the editor: a client call validates input + dispatches to the matching action', async () => {
2626
const embed = makeEmbedStub()
27-
const goTo = createSimplePDFTanstackTools({ embed }).find((tool) => tool.name === 'goTo')
27+
const goTo = createSimplePDFTools({ embed }).find((tool) => tool.name === 'goTo')
2828
if (goTo === undefined || goTo.execute === undefined) {
2929
throw new Error('expected a goTo client tool with an execute')
3030
}
@@ -34,7 +34,7 @@ describe('createSimplePDFTanstackTools', () => {
3434

3535
it('returns bad_request:invalid_input on schema-invalid input without dispatching', async () => {
3636
const embed = makeEmbedStub()
37-
const goTo = createSimplePDFTanstackTools({ embed }).find((tool) => tool.name === 'goTo')
37+
const goTo = createSimplePDFTools({ embed }).find((tool) => tool.name === 'goTo')
3838
if (goTo === undefined || goTo.execute === undefined) {
3939
throw new Error('expected a goTo client tool with an execute')
4040
}

react/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,26 +223,26 @@ const CopilotEditor = () => {
223223
};
224224
```
225225

226-
For server-side tool definitions (execute-less, for `streamText`), import `simplePDFToolDefinitions` from `@simplepdf/react-embed-pdf/ai-sdk`. `embedRef.current` is the flat editor-actions handle, every camelCase operation, with the deprecated `selectTool` / `submit` overloads; subscribe to editor events via the `onEmbedEvent` prop. (The framework-free `@simplepdf/embed` core exposes the grouped `embed.actions` / `embed.events` / `embed.lifecycle` handle for non-React use.)
226+
For server-side tool definitions (execute-less, for `streamText`), import `simplePDFToolDefinitions` from the React-free core `@simplepdf/embed/ai-sdk` (importing it from this React subpath would pull React into your server). `embedRef.current` is the flat editor-actions handle, every camelCase operation, with the deprecated `selectTool` / `submit` overloads; subscribe to editor events via the `onEmbedEvent` prop. (The framework-free `@simplepdf/embed` core exposes the grouped `embed.actions` / `embed.events` / `embed.lifecycle` handle for non-React use.)
227227

228-
#### Agentic: `useEmbedTanstackTools` (TanStack AI)
228+
#### Agentic: `useEmbedTools` (TanStack AI)
229229

230-
The TanStack mirror lives in the opt-in `@simplepdf/react-embed-pdf/tanstack-ai` subpath (importing it pulls `@tanstack/ai`). `useEmbedTanstackTools(embedRef)` returns the editor-bound client tools; pass them to `clientTools(...)`, then `useChat`:
230+
The TanStack mirror lives in the opt-in `@simplepdf/react-embed-pdf/tanstack-ai` subpath (importing it pulls `@tanstack/ai`). `useEmbedTools(embedRef)` returns the editor-bound client tools; pass them to `clientTools(...)`, then `useChat`:
231231

232232
```jsx
233233
import { useChat, clientTools } from '@tanstack/ai-react';
234234
import { EmbedPDF, useEmbed } from '@simplepdf/react-embed-pdf';
235-
import { useEmbedTanstackTools } from '@simplepdf/react-embed-pdf/tanstack-ai';
235+
import { useEmbedTools } from '@simplepdf/react-embed-pdf/tanstack-ai';
236236

237237
const CopilotEditor = () => {
238238
const { embedRef } = useEmbed();
239-
const tools = clientTools(...useEmbedTanstackTools(embedRef));
239+
const tools = clientTools(...useEmbedTools(embedRef));
240240
useChat({ connection, tools }); // the model's tool calls run against the live editor
241241
return <EmbedPDF ref={embedRef} mode="inline" companyIdentifier="yourcompany" style={{ width: 900, height: 800 }} />;
242242
};
243243
```
244244

245-
On your server `chat({ tools })` route, register `simplePDFTanstackToolDefinitions()` (re-exported from the same subpath) so the model is aware of the tools.
245+
On your server `chat({ tools })` route, register `simplePDFToolDefinitions()` imported from the React-free core `@simplepdf/embed/tanstack-ai` (not from this React subpath, which would pull React into your server) so the model is aware of the tools.
246246

247247
See [Retrieving PDF Data](../README.md#retrieving-pdf-data) for text extraction, downloading, and server-side storage options.
248248

react/src/tanstack-ai.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ import { describe, expect, it, vi } from 'vitest';
55
import { render } from '@testing-library/react';
66
import type { BridgeResult } from '@simplepdf/embed';
77
import { useEmbed } from './index';
8-
import { useEmbedTanstackTools } from './tanstack-ai';
8+
import { useEmbedTools } from './tanstack-ai';
99

1010
// useEmbed pulls in <EmbedPDF>, which imports scss (a build concern, irrelevant here).
1111
vi.mock('./styles.scss', () => ({}));
1212

13-
describe('useEmbedTanstackTools', () => {
13+
describe('useEmbedTools', () => {
1414
it('returns TanStack client tools, each execute null-safe before <EmbedPDF> mounts', async () => {
15-
const captured: ReturnType<typeof useEmbedTanstackTools>[] = [];
15+
const captured: ReturnType<typeof useEmbedTools>[] = [];
1616
const Probe = (): null => {
1717
const { embedRef } = useEmbed();
18-
captured.push(useEmbedTanstackTools(embedRef));
18+
captured.push(useEmbedTools(embedRef));
1919
return null;
2020
};
2121
render(<Probe />);
2222
const tools = captured[0];
2323
if (tools === undefined) {
24-
throw new Error('expected useEmbedTanstackTools to have rendered');
24+
throw new Error('expected useEmbedTools to have rendered');
2525
}
2626
const goTo = tools.find((tool) => tool.name === 'goTo');
2727
if (goTo === undefined || goTo.execute === undefined) {

react/src/tanstack-ai.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,30 @@
44
// with useEmbed():
55
//
66
// const { embedRef } = useEmbed()
7-
// const tools = clientTools(...useEmbedTanstackTools(embedRef)) // from @tanstack/ai-react
7+
// const tools = clientTools(...useEmbedTools(embedRef)) // from @tanstack/ai-react
88
// useChat({ connection, tools })
99

1010
import * as React from 'react';
1111
import type { RefObject } from 'react';
1212
import type { AnyClientTool } from '@tanstack/ai';
1313
import { routeToolCall } from '@simplepdf/embed/tools';
14-
import { simplePDFTanstackToolDefinitions } from '@simplepdf/embed/tanstack-ai';
14+
import { simplePDFToolDefinitions } from '@simplepdf/embed/tanstack-ai';
1515
import type { EmbedActions } from './embed-pdf';
1616
import { notMounted } from './not-mounted';
1717

18-
// Re-export the server-side definitions + the tool-name type so React consumers get
19-
// the whole TanStack surface from this one subpath (mirroring /ai-sdk).
20-
export { simplePDFTanstackToolDefinitions } from '@simplepdf/embed/tanstack-ai';
18+
// The server-side tool definitions are NOT re-exported here on purpose: this module
19+
// imports React (for the hook), so re-exporting them would drag React into a server
20+
// route that only needs the defs. Import those from the React-free core instead
21+
// (`@simplepdf/embed/tanstack-ai`). Only the tool-name type (erased at build) is re-exported.
2122
export type { SimplePDFToolName } from '@simplepdf/embed/tanstack-ai';
2223

2324
// The agentic tools bound to the live editor via useEmbed().embedRef. Stable and
2425
// null-safe before the editor mounts (each .client() reads embedRef.current at call
2526
// time). Pass to clientTools(...) -> useChat({ tools }).
26-
export const useEmbedTanstackTools = (embedRef: RefObject<EmbedActions | null>): AnyClientTool[] =>
27+
export const useEmbedTools = (embedRef: RefObject<EmbedActions | null>): AnyClientTool[] =>
2728
React.useMemo<AnyClientTool[]>(
2829
() =>
29-
simplePDFTanstackToolDefinitions().map((definition) =>
30+
simplePDFToolDefinitions().map((definition) =>
3031
definition.client((input) =>
3132
embedRef.current === null ? notMounted() : routeToolCall(embedRef.current, definition.name, input),
3233
),

0 commit comments

Comments
 (0)