Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tired-years-stick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-preact': minor
---

Create initial release for preact
1 change: 1 addition & 0 deletions packages/typescript/ai-preact/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @tanstack/ai-preact
157 changes: 157 additions & 0 deletions packages/typescript/ai-preact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<div align="center">
<img src="./media/header_ai.png" >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add alt text to the header image for accessibility.

The header image is missing the alt attribute, which is flagged by static analysis (MD045).

🔎 Proposed fix
-  <img src="./media/header_ai.png" >
+  <img src="./media/header_ai.png" alt="TanStack AI" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img src="./media/header_ai.png" >
<img src="./media/header_ai.png" alt="TanStack AI" />
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

2-2: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around line 2, the header image tag
<img src="./media/header_ai.png"> is missing an alt attribute which triggers
MD045; add a concise, meaningful alt attribute (e.g., alt="AI Preact header" or
a short description of the image) to the img tag so the markup becomes <img
src="./media/header_ai.png" alt="..."> to satisfy accessibility and static
analysis.

</div>

<br />

<div align="center">
<a href="https://npmjs.com/package/@tanstack/ai" target="\_parent">
<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace hard tabs with spaces for consistent indentation.

Lines 12, 23, 24, and 25 use hard tabs instead of spaces, which violates MD010. Standardize to spaces.

🔎 Proposed fix
  <a href="https://github.com/TanStack/ai" target="\_parent">
-	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
+    <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
  </a>
  <a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
   <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
  </a>
  </div>

  <div align="center">
  <a href="#badge">
    <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
  </a>
-	<a href="#badge">
-		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
-	</a>
+  <a href="#badge">
+    <img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
+  </a>
  <a href="https://twitter.com/tan_stack">
   <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
  </a>

Also applies to: 23-25

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around lines 12 and 23-25, several
lines use hard tabs for indentation which violates MD010; replace each hard tab
character with spaces (use the repository/project standard for Markdown
indentation — e.g., two spaces per tab) so indentation is consistent with the
rest of the file and no tabs remain on those lines.

</a>
<a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
</a>
</div>

<div align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
<a href="#badge">
<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
</a>
<a href="https://twitter.com/tan_stack">
<img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
</a>
</div>

<div align="center">

### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
</div>

# TanStack AI

A powerful, type-safe AI SDK for building AI-powered applications.

- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.)
- **Tree-shakeable adapters** - Import only what you need for smaller bundles
- **Multimodal content support** - Send images, audio, video, and documents
- **Image generation** - Generate images with OpenAI DALL-E/GPT-Image and Gemini Imagen
- Chat completion, streaming, and agent loop strategies
- Headless chat state management with adapters (SSE, HTTP stream, custom)
- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix heading level hierarchy.

Line 49 uses ### (h3) but should use ## (h2) to maintain proper heading increments per MD001.

🔎 Proposed fix
-### <a href="https://tanstack.com/ai">Read the docs →</b></a>
+## <a href="https://tanstack.com/ai">Read the docs →</b></a>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### <a href="https://tanstack.com/ai">Read the docs →</b></a>
## <a href="https://tanstack.com/ai">Read the docs →</b></a>
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

49-49: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around line 49, the heading currently
uses `###` (h3) but should be `##` (h2) to preserve proper heading hierarchy per
MD001; change the leading hashes from `###` to `##` on that line so the section
increments correctly and re-run the linter to verify the MD001 warning is
resolved.


## Tree-Shakeable Adapters

Import only the functionality you need for smaller bundle sizes:

```typescript
// Only chat functionality - no summarization code bundled
import { openaiText } from '@tanstack/ai-openai/adapters'
import { generate } from '@tanstack/ai'

const textAdapter = openaiText()

const result = generate({
adapter: textAdapter,
model: 'gpt-4o',
messages: [{ role: 'user', content: [{ type: 'text', content: 'Hello!' }] }],
})

for await (const chunk of result) {
console.log(chunk)
}
```

Available adapters: `openaiText`, `openaiEmbed`, `openaiSummarize`, `anthropicText`, `geminiText`, `ollamaText`, and more.
Comment on lines +36 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add Preact-specific documentation and usage examples.

The README contains generic TanStack AI content but lacks Preact-specific documentation. Users need to see:

  1. Installation instructions for @tanstack/ai-preact
  2. Preact-specific imports (e.g., import { useChat } from '@tanstack/ai-preact')
  3. Preact component examples using the useChat hook
  4. Minimum version requirements (Preact >= 10.11.0, per PR objectives)
  5. Quick start example showing a complete Preact chat component

The current tree-shakeable adapters example (lines 55-71) uses generic @tanstack/ai imports and doesn't demonstrate the Preact integration at all.

💡 Suggested structure

Consider adding a "Quick Start" section after the feature list with a Preact-specific example:

## Quick Start

### Installation

\`\`\`bash
npm install @tanstack/ai-preact @tanstack/ai-client
\`\`\`

**Requirements:** Preact >= 10.11.0

### Basic Usage

\`\`\`tsx
import { useChat } from '@tanstack/ai-preact'
import { ChatClient } from '@tanstack/ai-client'
import { openaiText } from '@tanstack/ai-openai/adapters'

const chatClient = new ChatClient({
  adapter: openaiText(),
  model: 'gpt-4o',
})

function ChatComponent() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    client: chatClient,
  })

  return (
    <div>
      <div>
        {messages.map((message) => (
          <div key={message.id}>
            <strong>{message.role}:</strong> {message.content}
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading}>
          Send
        </button>
      </form>
    </div>
  )
}
\`\`\`
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

49-49: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around lines 36 to 73, the README is
generic TanStack AI content and lacks Preact-specific docs; add a "Quick Start"
section that includes installation instructions for @tanstack/ai-preact and
@tanstack/ai-client, state the minimum Preact version requirement (Preact >=
10.11.0), show Preact-specific imports (e.g., import { useChat } from
'@tanstack/ai-preact' and ChatClient usage), and include a concise Preact
component example demonstrating useChat with messages, input handling, submit
flow and loading state so users can copy-paste a complete working chat
component.


## Bonus: TanStack Start Integration

TanStack AI works with **any** framework (Next.js, Express, Remix, etc.).

**With TanStack Start**, you get a bonus: share implementations between AI tools and server functions with `createServerFnTool`:

```typescript
import { createServerFnTool } from '@tanstack/ai-preact'

// Define once, get AI tool AND server function (TanStack Start only)
const getProducts = createServerFnTool({
name: 'getProducts',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => db.products.search(query),
})

// Use in AI chat
chat({ tools: [getProducts.server] })

// Call directly from components (no API endpoint needed!)
const products = await getProducts.serverFn({ query: 'laptop' })
```

No duplicate logic, full type safety, automatic validation. The `serverFn` feature requires TanStack Start. See [docs](https://tanstack.com/ai) for details.

## Get Involved

- We welcome issues and pull requests!
- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions)
- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions

## Partners

<table align="center">
<tr>
<td>
<a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
<source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
<img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
</picture>
</a>
</td>
<td>
<a href="https://www.cloudflare.com?utm_source=tanstack">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
<source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
<img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
</picture>
</a>
</td>
</tr>
</table>

<div align="center">
<img src="./media/partner_logo.svg" alt="AI & you?" height="65">
<p>
We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together.
</p>
<a href="mailto:partners@tanstack.com?subject=TanStack AI Partnership"><b>LET'S CHAT</b></a>
</div>

## Explore the TanStack Ecosystem

- <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
- <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
- <a href="https://github.com/tanstack/devtools"><b>TanStack Devtools</b></a> – Unified devtools panel
- <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
- <a href="https://github.com/tanstack/pacer"><b>TanStack Pacer</b></a> – Debouncing, throttling, batching
- <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
- <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
- <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
- <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
- <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
- <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
- <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering

… and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>

<!-- USE THE FORCE LUKE -->
57 changes: 57 additions & 0 deletions packages/typescript/ai-preact/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@tanstack/ai-preact",
"version": "0.0.0",
"description": "Preact hooks for TanStack AI",
"author": "",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/TanStack/ai.git",
"directory": "packages/typescript/ai-preact"
},
"type": "module",
"module": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"exports": {
".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.js"
}
},
"files": [
"dist",
"src"
],
"scripts": {
"clean": "premove ./build ./dist",
"lint:fix": "eslint ./src --fix",
"test:eslint": "eslint ./src",
"test:lib": "vitest run",
"test:lib:dev": "pnpm test:lib --watch",
"test:types": "tsc",
"test:build": "publint --strict",
"build": "vite build"
},
"keywords": [
"ai",
"preact",
"hooks",
"tanstack",
"chat",
"streaming"
],
"dependencies": {
"@tanstack/ai-client": "workspace:*"
},
"devDependencies": {
"@testing-library/preact": "^3.2.4",
"@vitest/coverage-v8": "4.0.14",
"jsdom": "^27.2.0",
"preact": "^10.26.9",
"vite": "^7.2.7"
},
"peerDependencies": {
"@tanstack/ai": "workspace:^",
"preact": ">=10.0.0"
}
}
17 changes: 17 additions & 0 deletions packages/typescript/ai-preact/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export { useChat } from './use-chat'
export type {
UseChatOptions,
UseChatReturn,
UIMessage,
ChatRequestBody,
} from './types'

export {
fetchServerSentEvents,
fetchHttpStream,
stream,
createChatClientOptions,
type ConnectionAdapter,
type FetchConnectionOptions,
type InferChatMessages,
} from '@tanstack/ai-client'
98 changes: 98 additions & 0 deletions packages/typescript/ai-preact/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { AnyClientTool, ModelMessage } from '@tanstack/ai'
import type {
ChatClientOptions,
ChatRequestBody,
UIMessage,
} from '@tanstack/ai-client'

// Re-export types from ai-client
export type { UIMessage, ChatRequestBody }

/**
* Options for the useChat hook.
*
* This extends ChatClientOptions but omits the state change callbacks that are
* managed internally by Preact state:
* - `onMessagesChange` - Managed by Preact state (exposed as `messages`)
* - `onLoadingChange` - Managed by Preact state (exposed as `isLoading`)
* - `onErrorChange` - Managed by Preact state (exposed as `error`)
*
* All other callbacks (onResponse, onChunk, onFinish, onError) are
* passed through to the underlying ChatClient and can be used for side effects.
*
* Note: Connection and body changes will recreate the ChatClient instance.
* To update these options, remount the component or use a key prop.
*/
export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any> =
Omit<
ChatClientOptions<TTools>,
'onMessagesChange' | 'onLoadingChange' | 'onErrorChange'
>

export interface UseChatReturn<
TTools extends ReadonlyArray<AnyClientTool> = any,
> {
/**
* Current messages in the conversation
*/
messages: Array<UIMessage<TTools>>

/**
* Send a message and get a response
*/
sendMessage: (content: string) => Promise<void>

/**
* Append a message to the conversation
*/
append: (message: ModelMessage | UIMessage<TTools>) => Promise<void>

/**
* Add the result of a client-side tool execution
*/
addToolResult: (result: {
toolCallId: string
tool: string
output: any
state?: 'output-available' | 'output-error'
errorText?: string
}) => Promise<void>

/**
* Respond to a tool approval request
*/
addToolApprovalResponse: (response: {
id: string // approval.id, not toolCallId
approved: boolean
}) => Promise<void>

/**
* Reload the last assistant message
*/
reload: () => Promise<void>

/**
* Stop the current response generation
*/
stop: () => void

/**
* Whether a response is currently being generated
*/
isLoading: boolean

/**
* Current error, if any
*/
error: Error | undefined

/**
* Set messages manually
*/
setMessages: (messages: Array<UIMessage<TTools>>) => void

/**
* Clear all messages
*/
clear: () => void
}
Loading