Skip to content

Commit 33a84b6

Browse files
committed
feat: add optional Anthropic starter support
1 parent 117169a commit 33a84b6

3 files changed

Lines changed: 109 additions & 9 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@tailwindcss/typography": "^0.5.19",
4747
"@tailwindcss/vite": "^4.2.2",
4848
"@tanstack/ai": "^0.10.0",
49+
"@tanstack/ai-anthropic": "^0.7.2",
4950
"@tanstack/ai-client": "^0.7.7",
5051
"@tanstack/ai-openai": "^0.7.3",
5152
"@tanstack/create": "^0.63.2",

pnpm-lock.yaml

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/utils/application-starter.server.ts

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { chat } from '@tanstack/ai'
2+
import { anthropicText } from '@tanstack/ai-anthropic'
23
import { openaiText } from '@tanstack/ai-openai'
34
import { z } from 'zod'
45
import {
@@ -52,6 +53,11 @@ type ApplicationStarterAnalysisPlan = z.infer<
5253
typeof applicationStarterAnalysisSchema
5354
>
5455
type OpenAIStarterModel = Parameters<typeof openaiText>[0]
56+
type AnthropicStarterModel = Parameters<typeof anthropicText>[0]
57+
type ApplicationStarterPromptModel = OpenAIStarterModel | AnthropicStarterModel
58+
type ApplicationStarterPromptAdapter =
59+
| ReturnType<typeof openaiText>
60+
| ReturnType<typeof anthropicText>
5561

5662
const applicationStarterAnalysisSchema = z.object({
5763
deployment: z.enum(['cloudflare', 'netlify', 'nitro', 'railway']).nullable(),
@@ -61,7 +67,7 @@ const applicationStarterAnalysisSchema = z.object({
6167
template: z.string().trim().min(1).max(80).nullable(),
6268
})
6369

64-
const supportedApplicationStarterModels = [
70+
const supportedApplicationStarterAnalysisModels = [
6571
'gpt-4.1',
6672
'gpt-4.1-mini',
6773
'gpt-4.1-nano',
@@ -72,6 +78,13 @@ const supportedApplicationStarterModels = [
7278
'gpt-5-nano',
7379
] as const satisfies Array<OpenAIStarterModel>
7480

81+
const supportedApplicationStarterPromptModels = [
82+
...supportedApplicationStarterAnalysisModels,
83+
'claude-haiku-4-5',
84+
'claude-sonnet-4-5',
85+
'claude-opus-4-5',
86+
] as const satisfies Array<ApplicationStarterPromptModel>
87+
7588
const deterministicProvider: ApplicationStarterProvider = {
7689
name: 'deterministic',
7790
resolve: async (request) =>
@@ -182,7 +195,7 @@ function getRequestedProviderName(): ApplicationStarterProviderName {
182195
return 'model'
183196
}
184197

185-
return process.env.OPENAI_API_KEY ? 'model' : 'deterministic'
198+
return hasApplicationStarterModelKey() ? 'model' : 'deterministic'
186199
}
187200

188201
async function resolveApplicationStarterWithModel(
@@ -228,16 +241,16 @@ async function tryGenerateApplicationStarterPrompt({
228241
request: ApplicationStarterRequest
229242
deterministicResult: ApplicationStarterResult
230243
}): Promise<ApplicationStarterPromptPlan | null> {
231-
const apiKey = process.env.OPENAI_API_KEY
244+
const adapter = getApplicationStarterPromptAdapter()
232245

233-
if (!apiKey) {
246+
if (!adapter) {
234247
return null
235248
}
236249

237250
try {
238251
return applicationStarterPromptSchema.parse(
239252
await chat({
240-
adapter: openaiText(getApplicationStarterPromptModel()),
253+
adapter,
241254
maxTokens: 900,
242255
messages: [
243256
{
@@ -300,15 +313,29 @@ function getApplicationStarterAnalysisModel(): OpenAIStarterModel {
300313
})
301314
}
302315

303-
function getApplicationStarterPromptModel(): OpenAIStarterModel {
304-
return getConfiguredApplicationStarterModel({
305-
fallbackModel: 'gpt-4.1-mini',
316+
function getApplicationStarterPromptModel(): ApplicationStarterPromptModel {
317+
return getConfiguredApplicationStarterPromptModel({
318+
fallbackModel: getDefaultApplicationStarterPromptModel(),
306319
requestedModel:
307320
process.env.APPLICATION_STARTER_PROMPT_MODEL ??
308321
process.env.APPLICATION_STARTER_MODEL,
309322
})
310323
}
311324

325+
function getApplicationStarterPromptAdapter(): ApplicationStarterPromptAdapter | null {
326+
const model = getApplicationStarterPromptModel()
327+
328+
if (isAnthropicStarterModel(model)) {
329+
return process.env.ANTHROPIC_API_KEY ? anthropicText(model) : null
330+
}
331+
332+
return process.env.OPENAI_API_KEY ? openaiText(model) : null
333+
}
334+
335+
function getDefaultApplicationStarterPromptModel(): ApplicationStarterPromptModel {
336+
return 'gpt-4.1-mini'
337+
}
338+
312339
function getConfiguredApplicationStarterModel({
313340
fallbackModel,
314341
requestedModel,
@@ -320,13 +347,41 @@ function getConfiguredApplicationStarterModel({
320347
return fallbackModel
321348
}
322349

323-
const matchingModel = supportedApplicationStarterModels.find(
350+
const matchingModel = supportedApplicationStarterAnalysisModels.find(
351+
(model) => model === requestedModel,
352+
)
353+
354+
return matchingModel ?? fallbackModel
355+
}
356+
357+
function getConfiguredApplicationStarterPromptModel({
358+
fallbackModel,
359+
requestedModel,
360+
}: {
361+
fallbackModel: ApplicationStarterPromptModel
362+
requestedModel: string | undefined
363+
}) {
364+
if (!requestedModel) {
365+
return fallbackModel
366+
}
367+
368+
const matchingModel = supportedApplicationStarterPromptModels.find(
324369
(model) => model === requestedModel,
325370
)
326371

327372
return matchingModel ?? fallbackModel
328373
}
329374

375+
function isAnthropicStarterModel(
376+
model: ApplicationStarterPromptModel,
377+
): model is AnthropicStarterModel {
378+
return model.startsWith('claude-')
379+
}
380+
381+
function hasApplicationStarterModelKey() {
382+
return Boolean(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY)
383+
}
384+
330385
function buildPromptGenerationRequest({
331386
request,
332387
deterministicResult,

0 commit comments

Comments
 (0)