feat: add ai category with vercel-ai-sdk and tanstack-ai modules

- Register a new `ai` category in `CATEGORIES` (cardinality `one`, home `packages/ai/`) so both modules land under the same single-choice slot
- Add `ai-vercel-ai-sdk`: installs `ai` + `@ai-sdk/openai`, emits a `model.ts` configured for `gpt-4o-mini`, a shared package entry-point, and a streaming `/api/chat` route for Next.js (`StreamingTextResponse`) and TanStack Start (`EventStream`); peers on `framework`
- Add `ai-tanstack-ai`: installs `@tanstack/ai` + `@tanstack/ai-openai`, emits an `adapter.ts` wrapping `openaiText("gpt-4o-mini")`, a shared entry-point, and a streaming `/api/chat` route for both frameworks via `toServerSentEventsResponse`; peers on `framework`
- Both modules declare `OPENAI_API_KEY` in `env` and ship provider-swap instructions in `readme.md`
- Update `registry.mdx` AI table: mark both modules 🟢 Available with short capability notes
This commit is contained in:
2026-05-26 18:44:52 -04:00
parent 3028165da4
commit 42da7f238b
34 changed files with 406 additions and 4 deletions
+4 -4
View File
@@ -53,10 +53,10 @@ Single-choice, constraint-bearing. An optional layer between the framework and t
Single-choice, constraint-bearing, installs into `packages/ai/`.
| Module | Status | Notes |
| --------------- | --------------- | ------------------------------------ |
| `vercel-ai-sdk` | 🟨 Planned | `ai` package + provider sub-recipes. |
| `tanstack-ai` | 🟨 Planned | TanStack AI. |
| Module | Status | Notes |
| --------------- | ----------------- | ----------------------------------------------------------------------------------- |
| `vercel-ai-sdk` | 🟢 Available | `ai` + `@ai-sdk/openai` with a streaming `/api/chat` route. Peers on `framework`. |
| `tanstack-ai` | 🟢 Available | `@tanstack/ai` + `@tanstack/ai-openai` with a streaming `/api/chat` route over SSE. |
## Authentication
+7
View File
@@ -87,6 +87,13 @@ export const CATEGORIES = [
cardinality: "one",
home: { kind: "package", dir: "email" },
},
{
id: "ai",
label: "AI",
description: "AI SDK + provider wiring.",
cardinality: "one",
home: { kind: "package", dir: "ai" },
},
{
id: "tooling",
label: "Tooling",
+72
View File
@@ -310,6 +310,24 @@ importers:
specifier: npm:@voidzero-dev/vite-plus-test@^0.1.22
version: '@voidzero-dev/vite-plus-test@0.1.22(@opentelemetry/api@1.9.1)(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))'
registry/modules/ai-tanstack-ai:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/ai-vercel-ai-sdk:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/auth-better-auth:
dependencies:
'@stanza/codemods':
@@ -374,6 +392,15 @@ importers:
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/monorepo-turbo:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/orm-drizzle:
dependencies:
'@stanza/codemods':
@@ -404,6 +431,51 @@ importers:
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/testing-playwright:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/testing-vitest:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/tooling-biome:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/tooling-eslint-prettier:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/tooling-oxlint-oxfmt:
dependencies:
'@stanza/codemods':
specifier: workspace:*
version: link:../../../packages/codemods
'@stanza/registry':
specifier: workspace:*
version: link:../../../packages/registry
registry/modules/ui-shadcn-base:
dependencies:
'@stanza/codemods':
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

+55
View File
@@ -0,0 +1,55 @@
import { defineModule } from "@stanza/registry";
export default defineModule({
id: "tanstack-ai",
category: "ai",
label: "TanStack AI",
description: "Composable AI primitives across providers.",
version: "0.1.0",
peers: {
framework: ["next", "tanstack-start"],
},
homepage: "https://tanstack.com/ai",
dependencies: {
"@tanstack/ai": "^0.22.0",
"@tanstack/ai-openai": "^0.10.2",
},
env: [
{
name: "OPENAI_API_KEY",
example: "sk-...",
required: true,
description: "OpenAI API key for the chat completion adapter.",
},
],
adapters: [
{
key: "next",
match: { framework: "next" },
templates: [
{ src: "shared/index.ts", dest: "src/index.ts", scope: "package" },
{ src: "shared/adapter.ts", dest: "src/adapter.ts", scope: "package" },
{
src: "next/route.ts",
dest: "app/api/chat/route.ts",
scope: "app",
template: true,
},
],
},
{
key: "tanstack-start",
match: { framework: "tanstack-start" },
templates: [
{ src: "shared/index.ts", dest: "src/index.ts", scope: "package" },
{ src: "shared/adapter.ts", dest: "src/adapter.ts", scope: "package" },
{
src: "tanstack/route.ts",
dest: "src/routes/api/chat.ts",
scope: "app",
template: true,
},
],
},
],
});
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/ai-tanstack-ai",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1 @@
Set `OPENAI_API_KEY` in `.env` and POST to `/api/chat` with an `{ messages }` body to stream a response over Server-Sent Events. The configured adapter is exported from `{{package.name}}` — edit `src/adapter.ts` to swap providers (`@tanstack/ai-anthropic`, `@tanstack/ai-google`, …) or models.
@@ -0,0 +1,11 @@
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { adapter } from "{{package.name}}";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({ adapter, messages });
return toServerSentEventsResponse(stream);
}
@@ -0,0 +1,3 @@
import { openaiText } from "@tanstack/ai-openai";
export const adapter = openaiText("gpt-4o-mini");
@@ -0,0 +1 @@
export { adapter } from "./adapter.ts";
@@ -0,0 +1,18 @@
import { createFileRoute } from "@tanstack/react-router";
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { adapter } from "{{package.name}}";
export const Route = createFileRoute("/api/chat")({
server: {
handlers: {
POST: async ({ request }) => {
const { messages } = await request.json();
const stream = chat({ adapter, messages });
return toServerSentEventsResponse(stream);
},
},
},
});
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1 @@
<svg viewBox="0 0 256 222" width="256" height="222" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path fill="#fff" d="m128 0 128 221.705H0z"/></svg>

After

Width:  |  Height:  |  Size: 169 B

@@ -0,0 +1 @@
<svg viewBox="0 0 256 222" width="256" height="222" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path fill="#000" d="m128 0 128 221.705H0z"/></svg>

After

Width:  |  Height:  |  Size: 169 B

@@ -0,0 +1,55 @@
import { defineModule } from "@stanza/registry";
export default defineModule({
id: "vercel-ai-sdk",
category: "ai",
label: "Vercel AI SDK",
description: "Streaming AI SDK with tools, structured output, and React hooks.",
version: "0.1.0",
peers: {
framework: ["next", "tanstack-start"],
},
homepage: "https://ai-sdk.dev",
dependencies: {
ai: "^6.0.191",
"@ai-sdk/openai": "^3.0.65",
},
env: [
{
name: "OPENAI_API_KEY",
example: "sk-...",
required: true,
description: "OpenAI API key for the chat completion provider.",
},
],
adapters: [
{
key: "next",
match: { framework: "next" },
templates: [
{ src: "shared/index.ts", dest: "src/index.ts", scope: "package" },
{ src: "shared/model.ts", dest: "src/model.ts", scope: "package" },
{
src: "next/route.ts",
dest: "app/api/chat/route.ts",
scope: "app",
template: true,
},
],
},
{
key: "tanstack-start",
match: { framework: "tanstack-start" },
templates: [
{ src: "shared/index.ts", dest: "src/index.ts", scope: "package" },
{ src: "shared/model.ts", dest: "src/model.ts", scope: "package" },
{
src: "tanstack/route.ts",
dest: "src/routes/api/chat.ts",
scope: "app",
template: true,
},
],
},
],
});
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/ai-vercel-ai-sdk",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1 @@
Set `OPENAI_API_KEY` in `.env` and POST to `/api/chat` with an `{ messages }` body to stream a response. The configured model is exported from `{{package.name}}` — edit `src/model.ts` to swap providers or models.
@@ -0,0 +1,14 @@
import { convertToModelMessages, streamText, type UIMessage } from "ai";
import { model } from "{{package.name}}";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model,
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}
@@ -0,0 +1 @@
export { model } from "./model.ts";
@@ -0,0 +1,3 @@
import { openai } from "@ai-sdk/openai";
export const model = openai("gpt-4o-mini");
@@ -0,0 +1,21 @@
import { createFileRoute } from "@tanstack/react-router";
import { convertToModelMessages, streamText, type UIMessage } from "ai";
import { model } from "{{package.name}}";
export const Route = createFileRoute("/api/chat")({
server: {
handlers: {
POST: async ({ request }) => {
const { messages }: { messages: UIMessage[] } = await request.json();
const result = streamText({
model,
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
},
},
},
});
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/monorepo-turbo",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/testing-playwright",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/testing-vitest",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/tooling-biome",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/tooling-eslint-prettier",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}
@@ -0,0 +1,12 @@
{
"name": "@stanza-modules/tooling-oxlint-oxfmt",
"version": "0.1.0",
"private": true,
"license": "MIT",
"type": "module",
"main": "./module.ts",
"dependencies": {
"@stanza/codemods": "workspace:*",
"@stanza/registry": "workspace:*"
}
}
@@ -0,0 +1,5 @@
{
"extends": "../../../tsconfig.json",
"include": ["module.ts"],
"exclude": ["templates"]
}