Skip to content

Commit f412d8f

Browse files
feat(cli): upgrade to ai sdk v5 (#487)
1 parent ea908dd commit f412d8f

File tree

18 files changed

+438
-276
lines changed

18 files changed

+438
-276
lines changed

.changeset/ripe-peaches-crash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-better-t-stack": minor
3+
---
4+
5+
Upgrade to AI SDK v5

apps/cli/src/constants.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ export const dependencyVersionMap = {
8787

8888
turbo: "^2.5.4",
8989

90-
ai: "^4.3.16",
91-
"@ai-sdk/google": "^1.2.3",
92-
"@ai-sdk/vue": "^1.2.8",
93-
"@ai-sdk/svelte": "^2.1.9",
94-
"@ai-sdk/react": "^1.2.12",
90+
ai: "^5.0.9",
91+
"@ai-sdk/google": "^2.0.3",
92+
"@ai-sdk/vue": "^2.0.9",
93+
"@ai-sdk/svelte": "^3.0.9",
94+
"@ai-sdk/react": "^2.0.9",
9595

9696
"@orpc/server": "^1.5.0",
9797
"@orpc/client": "^1.5.0",

apps/cli/src/helpers/setup/examples-setup.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,44 @@ export async function setupExamples(config: ProjectConfig) {
1717
}
1818

1919
if (examples.includes("ai")) {
20-
const clientDir = path.join(projectDir, "apps/web");
20+
const webClientDir = path.join(projectDir, "apps/web");
21+
const nativeClientDir = path.join(projectDir, "apps/native");
2122
const serverDir = path.join(projectDir, "apps/server");
22-
const clientDirExists = await fs.pathExists(clientDir);
23+
24+
const webClientDirExists = await fs.pathExists(webClientDir);
25+
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
2326
const serverDirExists = await fs.pathExists(serverDir);
2427

2528
const hasNuxt = frontend.includes("nuxt");
2629
const hasSvelte = frontend.includes("svelte");
27-
const hasReact =
30+
const hasReactWeb =
2831
frontend.includes("react-router") ||
2932
frontend.includes("tanstack-router") ||
3033
frontend.includes("next") ||
31-
frontend.includes("tanstack-start") ||
34+
frontend.includes("tanstack-start");
35+
const hasReactNative =
3236
frontend.includes("native-nativewind") ||
3337
frontend.includes("native-unistyles");
3438

35-
if (clientDirExists) {
39+
if (webClientDirExists) {
3640
const dependencies: AvailableDependencies[] = ["ai"];
3741
if (hasNuxt) {
3842
dependencies.push("@ai-sdk/vue");
3943
} else if (hasSvelte) {
4044
dependencies.push("@ai-sdk/svelte");
41-
} else if (hasReact) {
45+
} else if (hasReactWeb) {
4246
dependencies.push("@ai-sdk/react");
4347
}
4448
await addPackageDependency({
4549
dependencies,
46-
projectDir: clientDir,
50+
projectDir: webClientDir,
51+
});
52+
}
53+
54+
if (nativeClientDirExists && hasReactNative) {
55+
await addPackageDependency({
56+
dependencies: ["ai", "@ai-sdk/react"],
57+
projectDir: nativeClientDir,
4758
});
4859
}
4960

apps/cli/templates/backend/server/express/src/index.ts.hbs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { createContext } from "./lib/context";
1414
import cors from "cors";
1515
import express from "express";
1616
{{#if (includes examples "ai")}}
17-
import { streamText } from "ai";
17+
import { streamText, type UIMessage, convertToModelMessages } from "ai";
1818
import { google } from "@ai-sdk/google";
1919
{{/if}}
2020
{{#if auth}}
@@ -44,16 +44,16 @@ app.use(
4444
"/trpc",
4545
createExpressMiddleware({
4646
router: appRouter,
47-
createContext
47+
createContext,
4848
})
4949
);
5050
{{/if}}
5151

5252
{{#if (eq api "orpc")}}
5353
const handler = new RPCHandler(appRouter);
54-
app.use('/rpc{*path}', async (req, res, next) => {
54+
app.use("/rpc{*path}", async (req, res, next) => {
5555
const { matched } = await handler.handle(req, res, {
56-
prefix: '/rpc',
56+
prefix: "/rpc",
5757
{{#if auth}}
5858
context: await createContext({ req }),
5959
{{else}}
@@ -65,16 +65,16 @@ app.use('/rpc{*path}', async (req, res, next) => {
6565
});
6666
{{/if}}
6767

68-
app.use(express.json())
68+
app.use(express.json());
6969

7070
{{#if (includes examples "ai")}}
7171
app.post("/ai", async (req, res) => {
72-
const { messages = [] } = req.body || {};
72+
const { messages = [] } = (req.body || {}) as { messages: UIMessage[] };
7373
const result = streamText({
7474
model: google("gemini-1.5-flash"),
75-
messages,
75+
messages: convertToModelMessages(messages),
7676
});
77-
result.pipeDataStreamToResponse(res);
77+
result.pipeUIMessageStreamToResponse(res);
7878
});
7979
{{/if}}
8080

@@ -85,4 +85,4 @@ app.get("/", (_req, res) => {
8585
const port = process.env.PORT || 3000;
8686
app.listen(port, () => {
8787
console.log(`Server is running on port ${port}`);
88-
});
88+
});

apps/cli/templates/backend/server/fastify/src/index.ts.hbs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import { createContext } from "./lib/context";
1919
{{/if}}
2020

2121
{{#if (includes examples "ai")}}
22-
import type { FastifyRequest, FastifyReply } from "fastify";
23-
import { streamText, type Message } from "ai";
22+
import { streamText, type UIMessage, convertToModelMessages } from "ai";
2423
import { google } from "@ai-sdk/google";
2524
{{/if}}
2625

@@ -99,7 +98,7 @@ fastify.route({
9998
response.headers.forEach((value, key) => reply.header(key, value));
10099
reply.send(response.body ? await response.text() : null);
101100
} catch (error) {
102-
fastify.log.error("Authentication Error:", error);
101+
fastify.log.error({ err: error }, "Authentication Error:");
103102
reply.status(500).send({
104103
error: "Internal authentication error",
105104
code: "AUTH_FAILURE"
@@ -125,31 +124,29 @@ fastify.register(fastifyTRPCPlugin, {
125124
{{#if (includes examples "ai")}}
126125
interface AiRequestBody {
127126
id?: string;
128-
messages: Message[];
127+
messages: UIMessage[];
129128
}
130129

131130
fastify.post('/ai', async function (request, reply) {
131+
// there are some issues with the ai sdk and fastify, docs: https://ai-sdk.dev/cookbook/api-servers/fastify
132132
const { messages } = request.body as AiRequestBody;
133133
const result = streamText({
134134
model: google('gemini-1.5-flash'),
135-
messages,
135+
messages: convertToModelMessages(messages),
136136
});
137137

138-
reply.header('X-Vercel-AI-Data-Stream', 'v1');
139-
reply.header('Content-Type', 'text/plain; charset=utf-8');
140-
141-
return reply.send(result.toDataStream());
138+
return result.pipeUIMessageStreamToResponse(reply.raw);
142139
});
143140
{{/if}}
144141

145142
fastify.get('/', async () => {
146-
return 'OK'
147-
})
143+
return 'OK';
144+
});
148145

149146
fastify.listen({ port: 3000 }, (err) => {
150147
if (err) {
151148
fastify.log.error(err);
152149
process.exit(1);
153150
}
154151
console.log("Server running on port 3000");
155-
});
152+
});

apps/cli/templates/backend/server/hono/src/index.ts.hbs

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,33 @@ import { Hono } from "hono";
2121
import { cors } from "hono/cors";
2222
import { logger } from "hono/logger";
2323
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
24-
import { streamText } from "ai";
24+
import { streamText, convertToModelMessages } from "ai";
2525
import { google } from "@ai-sdk/google";
26-
import { stream } from "hono/streaming";
2726
{{/if}}
2827
{{#if (and (includes examples "ai") (eq runtime "workers"))}}
29-
import { streamText } from "ai";
30-
import { stream } from "hono/streaming";
28+
import { streamText, convertToModelMessages } from "ai";
3129
import { createGoogleGenerativeAI } from "@ai-sdk/google";
3230
{{/if}}
3331

3432
const app = new Hono();
3533

3634
app.use(logger());
37-
app.use("/*", cors({
38-
{{#if (or (eq runtime "bun") (eq runtime "node"))}}
39-
origin: process.env.CORS_ORIGIN || "",
40-
{{/if}}
41-
{{#if (eq runtime "workers")}}
42-
origin: env.CORS_ORIGIN || "",
43-
{{/if}}
44-
allowMethods: ["GET", "POST", "OPTIONS"],
45-
{{#if auth}}
46-
allowHeaders: ["Content-Type", "Authorization"],
47-
credentials: true,
48-
{{/if}}
49-
}));
35+
app.use(
36+
"/*",
37+
cors({
38+
{{#if (or (eq runtime "bun") (eq runtime "node"))}}
39+
origin: process.env.CORS_ORIGIN || "",
40+
{{/if}}
41+
{{#if (eq runtime "workers")}}
42+
origin: env.CORS_ORIGIN || "",
43+
{{/if}}
44+
allowMethods: ["GET", "POST", "OPTIONS"],
45+
{{#if auth}}
46+
allowHeaders: ["Content-Type", "Authorization"],
47+
credentials: true,
48+
{{/if}}
49+
})
50+
);
5051

5152
{{#if auth}}
5253
app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
@@ -69,44 +70,43 @@ app.use("/rpc/*", async (c, next) => {
6970
{{/if}}
7071

7172
{{#if (eq api "trpc")}}
72-
app.use("/trpc/*", trpcServer({
73-
router: appRouter,
74-
createContext: (_opts, context) => {
75-
return createContext({ context });
76-
},
77-
}));
73+
app.use(
74+
"/trpc/*",
75+
trpcServer({
76+
router: appRouter,
77+
createContext: (_opts, context) => {
78+
return createContext({ context });
79+
},
80+
})
81+
);
7882
{{/if}}
7983

8084
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
8185
app.post("/ai", async (c) => {
8286
const body = await c.req.json();
83-
const messages = body.messages || [];
87+
const uiMessages = body.messages || [];
8488
const result = streamText({
8589
model: google("gemini-1.5-flash"),
86-
messages,
90+
messages: convertToModelMessages(uiMessages),
8791
});
8892

89-
c.header("X-Vercel-AI-Data-Stream", "v1");
90-
c.header("Content-Type", "text/plain; charset=utf-8");
91-
return stream(c, (stream) => stream.pipe(result.toDataStream()));
93+
return result.toUIMessageStreamResponse();
9294
});
9395
{{/if}}
9496

9597
{{#if (and (includes examples "ai") (eq runtime "workers"))}}
9698
app.post("/ai", async (c) => {
9799
const body = await c.req.json();
98-
const messages = body.messages || [];
100+
const uiMessages = body.messages || [];
99101
const google = createGoogleGenerativeAI({
100102
apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY,
101103
});
102104
const result = streamText({
103105
model: google("gemini-1.5-flash"),
104-
messages,
106+
messages: convertToModelMessages(uiMessages),
105107
});
106108

107-
c.header("X-Vercel-AI-Data-Stream", "v1");
108-
c.header("Content-Type", "text/plain; charset=utf-8");
109-
return stream(c, (stream) => stream.pipe(result.toDataStream()));
109+
return result.toUIMessageStreamResponse();
110110
});
111111
{{/if}}
112112

@@ -117,17 +117,20 @@ app.get("/", (c) => {
117117
{{#if (eq runtime "node")}}
118118
import { serve } from "@hono/node-server";
119119

120-
serve({
121-
fetch: app.fetch,
122-
port: 3000,
123-
}, (info) => {
124-
console.log(`Server is running on http://localhost:${info.port}`);
125-
});
120+
serve(
121+
{
122+
fetch: app.fetch,
123+
port: 3000,
124+
},
125+
(info) => {
126+
console.log(`Server is running on http://localhost:${info.port}`);
127+
}
128+
);
126129
{{else}}
127-
{{#if (eq runtime "bun")}}
130+
{{#if (eq runtime "bun")}}
128131
export default app;
129-
{{/if}}
130-
{{#if (eq runtime "workers")}}
132+
{{/if}}
133+
{{#if (eq runtime "workers")}}
131134
export default app;
132-
{{/if}}
133135
{{/if}}
136+
{{/if}}

0 commit comments

Comments
 (0)