@chat-js/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/index.js +11 -6
  2. package/package.json +1 -1
  3. package/templates/chat-app/app/(chat)/api/chat/prepare/route.ts +94 -0
  4. package/templates/chat-app/app/(chat)/api/chat/route.ts +97 -14
  5. package/templates/chat-app/chat.config.ts +141 -124
  6. package/templates/chat-app/components/chat-sync.tsx +6 -3
  7. package/templates/chat-app/components/feedback-actions.tsx +7 -3
  8. package/templates/chat-app/components/message-editor.tsx +8 -3
  9. package/templates/chat-app/components/message-siblings.tsx +14 -1
  10. package/templates/chat-app/components/model-selector.tsx +669 -407
  11. package/templates/chat-app/components/multimodal-input.tsx +252 -18
  12. package/templates/chat-app/components/parallel-response-cards.tsx +157 -0
  13. package/templates/chat-app/components/part/text-message-part.tsx +9 -5
  14. package/templates/chat-app/components/retry-button.tsx +25 -8
  15. package/templates/chat-app/components/user-message.tsx +136 -125
  16. package/templates/chat-app/hooks/chat-sync-hooks.ts +11 -0
  17. package/templates/chat-app/hooks/use-navigate-to-message.ts +39 -0
  18. package/templates/chat-app/lib/ai/types.ts +74 -3
  19. package/templates/chat-app/lib/config-schema.ts +5 -0
  20. package/templates/chat-app/lib/db/migrations/0044_gray_red_shift.sql +5 -0
  21. package/templates/chat-app/lib/db/migrations/meta/0044_snapshot.json +1567 -0
  22. package/templates/chat-app/lib/db/migrations/meta/_journal.json +8 -1
  23. package/templates/chat-app/lib/db/queries.ts +84 -4
  24. package/templates/chat-app/lib/db/schema.ts +4 -1
  25. package/templates/chat-app/lib/message-conversion.ts +14 -2
  26. package/templates/chat-app/lib/stores/hooks-threads.ts +37 -1
  27. package/templates/chat-app/lib/stores/with-threads.test.ts +137 -0
  28. package/templates/chat-app/lib/stores/with-threads.ts +157 -4
  29. package/templates/chat-app/lib/thread-utils.ts +23 -2
  30. package/templates/chat-app/providers/chat-input-provider.tsx +40 -2
  31. package/templates/chat-app/scripts/db-branch-delete.sh +7 -1
  32. package/templates/chat-app/scripts/db-branch-use.sh +7 -1
  33. package/templates/chat-app/scripts/with-db.sh +7 -1
  34. package/templates/chat-app/vitest.config.ts +2 -0
package/dist/index.js CHANGED
@@ -3914,7 +3914,7 @@ var {
3914
3914
  // package.json
3915
3915
  var package_default = {
3916
3916
  name: "@chat-js/cli",
3917
- version: "0.3.0",
3917
+ version: "0.4.0",
3918
3918
  description: "CLI for creating and extending ChatJS apps",
3919
3919
  license: "Apache-2.0",
3920
3920
  repository: {
@@ -19051,9 +19051,11 @@ var attachmentsConfigSchema = exports_external.object({
19051
19051
  }
19052
19052
  });
19053
19053
  var featuresConfigSchema = exports_external.object({
19054
- attachments: exports_external.boolean().describe("File attachments (requires BLOB_READ_WRITE_TOKEN)")
19054
+ attachments: exports_external.boolean().describe("File attachments (requires BLOB_READ_WRITE_TOKEN)"),
19055
+ parallelResponses: exports_external.boolean().default(true).describe("Send one message to multiple models simultaneously")
19055
19056
  }).default({
19056
- attachments: false
19057
+ attachments: false,
19058
+ parallelResponses: true
19057
19059
  });
19058
19060
  var authenticationConfigSchema = exports_external.object({
19059
19061
  google: exports_external.boolean().describe("Google OAuth (requires AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET)"),
@@ -19352,7 +19354,8 @@ var FEATURE_KEYS = [
19352
19354
  "mcp",
19353
19355
  "imageGeneration",
19354
19356
  "attachments",
19355
- "followupSuggestions"
19357
+ "followupSuggestions",
19358
+ "parallelResponses"
19356
19359
  ];
19357
19360
 
19358
19361
  // src/helpers/env-checklist.ts
@@ -19427,7 +19430,8 @@ var FEATURE_DEFAULTS = {
19427
19430
  mcp: false,
19428
19431
  imageGeneration: false,
19429
19432
  attachments: false,
19430
- followupSuggestions: true
19433
+ followupSuggestions: true,
19434
+ parallelResponses: true
19431
19435
  };
19432
19436
  var AUTH_DEFAULTS = {
19433
19437
  google: false,
@@ -19442,7 +19446,8 @@ var FEATURE_LABELS = {
19442
19446
  mcp: "MCP Tool Servers",
19443
19447
  imageGeneration: "Image Generation",
19444
19448
  attachments: "File Attachments",
19445
- followupSuggestions: "Follow-up Suggestions"
19449
+ followupSuggestions: "Follow-up Suggestions",
19450
+ parallelResponses: "Parallel Responses"
19446
19451
  };
19447
19452
  var AUTH_LABELS = {
19448
19453
  google: "Google OAuth",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-js/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for creating and extending ChatJS apps",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,94 @@
1
+ import { headers } from "next/headers";
2
+ import type { NextRequest } from "next/server";
3
+ import type { ChatMessage } from "@/lib/ai/types";
4
+ import { auth } from "@/lib/auth";
5
+ import {
6
+ getChatById,
7
+ getMessageById,
8
+ getProjectById,
9
+ getUserById,
10
+ saveChatIfNotExists,
11
+ saveMessageIfNotExists,
12
+ } from "@/lib/db/queries";
13
+ import { createModuleLogger } from "@/lib/logger";
14
+ import { generateTitleFromUserMessage } from "../../../actions";
15
+
16
+ const log = createModuleLogger("api:chat:prepare");
17
+
18
+ export async function POST(request: NextRequest) {
19
+ try {
20
+ const {
21
+ id: chatId,
22
+ message: userMessage,
23
+ projectId,
24
+ }: {
25
+ id: string;
26
+ message: ChatMessage;
27
+ projectId?: string;
28
+ } = await request.json();
29
+
30
+ if (!userMessage) {
31
+ return new Response("Missing user message", { status: 400 });
32
+ }
33
+
34
+ const session = await auth.api.getSession({ headers: await headers() });
35
+ const userId = session?.user?.id;
36
+
37
+ if (!userId) {
38
+ return new Response("Multiple models require authentication", {
39
+ status: 401,
40
+ });
41
+ }
42
+
43
+ const user = await getUserById({ userId });
44
+ if (!user) {
45
+ return new Response("User not found", { status: 404 });
46
+ }
47
+
48
+ const chat = await getChatById({ id: chatId });
49
+ let isNewChat = false;
50
+
51
+ if (chat) {
52
+ if (chat.userId !== userId) {
53
+ return new Response("Unauthorized", { status: 401 });
54
+ }
55
+ } else {
56
+ isNewChat = true;
57
+
58
+ if (projectId) {
59
+ const project = await getProjectById({ id: projectId });
60
+ if (!project || project.userId !== userId) {
61
+ return new Response("Unauthorized", { status: 401 });
62
+ }
63
+ }
64
+
65
+ const title = await generateTitleFromUserMessage({
66
+ message: userMessage,
67
+ });
68
+
69
+ await saveChatIfNotExists({
70
+ id: chatId,
71
+ userId,
72
+ title,
73
+ projectId,
74
+ });
75
+ }
76
+
77
+ const [existingMessage] = await getMessageById({ id: userMessage.id });
78
+
79
+ if (existingMessage && existingMessage.chatId !== chatId) {
80
+ return new Response("Unauthorized", { status: 401 });
81
+ }
82
+
83
+ await saveMessageIfNotExists({
84
+ id: userMessage.id,
85
+ chatId,
86
+ message: userMessage,
87
+ });
88
+
89
+ return Response.json({ isNewChat });
90
+ } catch (error) {
91
+ log.error({ error }, "POST /api/chat/prepare failed");
92
+ return new Response("Internal Server Error", { status: 500 });
93
+ }
94
+ }
@@ -27,7 +27,11 @@ import {
27
27
  import { systemPrompt } from "@/lib/ai/prompts";
28
28
  import { calculateMessagesTokens } from "@/lib/ai/token-utils";
29
29
  import { allTools } from "@/lib/ai/tools/tools-definitions";
30
- import type { ChatMessage, ToolName } from "@/lib/ai/types";
30
+ import {
31
+ getPrimarySelectedModelId,
32
+ type ChatMessage,
33
+ type ToolName,
34
+ } from "@/lib/ai/types";
31
35
  import {
32
36
  getAnonymousSession,
33
37
  setAnonymousSession,
@@ -44,8 +48,9 @@ import {
44
48
  getMessageCanceledAt,
45
49
  getProjectById,
46
50
  getUserById,
47
- saveChat,
51
+ saveChatIfNotExists,
48
52
  saveMessage,
53
+ saveMessageIfNotExists,
49
54
  updateMessage,
50
55
  updateMessageActiveStreamId,
51
56
  } from "@/lib/db/queries";
@@ -191,7 +196,7 @@ async function handleChatValidation({
191
196
  message: userMessage,
192
197
  });
193
198
 
194
- await saveChat({ id: chatId, userId, title, projectId });
199
+ await saveChatIfNotExists({ id: chatId, userId, title, projectId });
195
200
  }
196
201
 
197
202
  const [existentMessage] = await getMessageById({ id: userMessage.id });
@@ -201,18 +206,35 @@ async function handleChatValidation({
201
206
  return { error: new Response("Unauthorized", { status: 401 }), isNewChat };
202
207
  }
203
208
 
204
- if (!existentMessage) {
205
- // If the message does not exist, save it
206
- await saveMessage({
207
- id: userMessage.id,
208
- chatId,
209
- message: userMessage,
210
- });
211
- }
209
+ await saveMessageIfNotExists({
210
+ id: userMessage.id,
211
+ chatId,
212
+ message: userMessage,
213
+ });
212
214
 
213
215
  return { error: null, isNewChat };
214
216
  }
215
217
 
218
+ function resolveSelectedModelId({
219
+ requestSelectedModelId,
220
+ selectedModel,
221
+ }: {
222
+ requestSelectedModelId?: AppModelId;
223
+ selectedModel: ChatMessage["metadata"]["selectedModel"];
224
+ }): AppModelId | null {
225
+ if (typeof selectedModel === "string") {
226
+ return requestSelectedModelId ?? selectedModel;
227
+ }
228
+
229
+ if (requestSelectedModelId) {
230
+ if (!selectedModel || typeof selectedModel !== "object") return null;
231
+ const requestedCount = selectedModel[requestSelectedModelId];
232
+ return requestedCount && requestedCount > 0 ? requestSelectedModelId : null;
233
+ }
234
+
235
+ return getPrimarySelectedModelId(selectedModel);
236
+ }
237
+
216
238
  async function checkUserCanSpend(userId: string): Promise<Response | null> {
217
239
  const userCanSpend = await canSpend(userId);
218
240
  if (!userCanSpend) {
@@ -309,6 +331,9 @@ async function createChatStream({
309
331
  userMessage,
310
332
  previousMessages,
311
333
  selectedModelId,
334
+ parallelGroupId,
335
+ parallelIndex,
336
+ isPrimaryParallel,
312
337
  explicitlyRequestedTools,
313
338
  userId,
314
339
  allowedTools,
@@ -325,6 +350,9 @@ async function createChatStream({
325
350
  userMessage: ChatMessage;
326
351
  previousMessages: ChatMessage[];
327
352
  selectedModelId: AppModelId;
353
+ parallelGroupId: string | null;
354
+ parallelIndex: number | null;
355
+ isPrimaryParallel: boolean | null;
328
356
  explicitlyRequestedTools: ToolName[] | null;
329
357
  userId: string | null;
330
358
  allowedTools: ToolName[];
@@ -377,6 +405,9 @@ async function createChatStream({
377
405
  const initialMetadata: ChatMessage["metadata"] = {
378
406
  createdAt: new Date(),
379
407
  parentMessageId: userMessage.id,
408
+ parallelGroupId,
409
+ parallelIndex,
410
+ isPrimaryParallel,
380
411
  selectedModel: selectedModelId,
381
412
  activeStreamId: isAnonymous ? null : streamId,
382
413
  };
@@ -435,6 +466,10 @@ async function createChatStream({
435
466
  isAnonymous,
436
467
  chatId,
437
468
  costAccumulator,
469
+ selectedModelId,
470
+ parallelGroupId,
471
+ parallelIndex,
472
+ isPrimaryParallel,
438
473
  });
439
474
  },
440
475
  onError: (error) => {
@@ -468,6 +503,10 @@ async function executeChatRequest({
468
503
  userMessage,
469
504
  previousMessages,
470
505
  selectedModelId,
506
+ assistantMessageId,
507
+ parallelGroupId,
508
+ parallelIndex,
509
+ isPrimaryParallel,
471
510
  explicitlyRequestedTools,
472
511
  userId,
473
512
  isAnonymous,
@@ -481,6 +520,10 @@ async function executeChatRequest({
481
520
  userMessage: ChatMessage;
482
521
  previousMessages: ChatMessage[];
483
522
  selectedModelId: AppModelId;
523
+ assistantMessageId?: string;
524
+ parallelGroupId: string | null;
525
+ parallelIndex: number | null;
526
+ isPrimaryParallel: boolean | null;
484
527
  explicitlyRequestedTools: ToolName[] | null;
485
528
  userId: string | null;
486
529
  isAnonymous: boolean;
@@ -491,7 +534,7 @@ async function executeChatRequest({
491
534
  mcpConnectors: McpConnector[];
492
535
  }): Promise<Response> {
493
536
  const log = createModuleLogger("api:chat:execute");
494
- const messageId = generateUUID();
537
+ const messageId = assistantMessageId ?? generateUUID();
495
538
  const streamId = generateUUID();
496
539
 
497
540
  if (!isAnonymous) {
@@ -506,6 +549,9 @@ async function executeChatRequest({
506
549
  metadata: {
507
550
  createdAt: new Date(),
508
551
  parentMessageId: userMessage.id,
552
+ parallelGroupId,
553
+ parallelIndex,
554
+ isPrimaryParallel,
509
555
  selectedModel: selectedModelId,
510
556
  selectedTool: undefined,
511
557
  activeStreamId: streamId,
@@ -532,6 +578,9 @@ async function executeChatRequest({
532
578
  userMessage,
533
579
  previousMessages,
534
580
  selectedModelId,
581
+ parallelGroupId,
582
+ parallelIndex,
583
+ isPrimaryParallel,
535
584
  explicitlyRequestedTools,
536
585
  userId,
537
586
  allowedTools,
@@ -712,12 +761,20 @@ async function finalizeMessageAndCredits({
712
761
  isAnonymous,
713
762
  chatId,
714
763
  costAccumulator,
764
+ selectedModelId,
765
+ parallelGroupId,
766
+ parallelIndex,
767
+ isPrimaryParallel,
715
768
  }: {
716
769
  messages: ChatMessage[];
717
770
  userId: string | null;
718
771
  isAnonymous: boolean;
719
772
  chatId: string;
720
773
  costAccumulator: CostAccumulator;
774
+ selectedModelId: AppModelId;
775
+ parallelGroupId: string | null;
776
+ parallelIndex: number | null;
777
+ isPrimaryParallel: boolean | null;
721
778
  }): Promise<void> {
722
779
  const log = createModuleLogger("api:chat:finalize");
723
780
 
@@ -736,6 +793,15 @@ async function finalizeMessageAndCredits({
736
793
  ...assistantMessage,
737
794
  metadata: {
738
795
  ...assistantMessage.metadata,
796
+ parallelGroupId:
797
+ parallelGroupId ?? assistantMessage.metadata.parallelGroupId ?? null,
798
+ parallelIndex:
799
+ parallelIndex ?? assistantMessage.metadata.parallelIndex ?? null,
800
+ isPrimaryParallel:
801
+ isPrimaryParallel ??
802
+ assistantMessage.metadata.isPrimaryParallel ??
803
+ null,
804
+ selectedModel: selectedModelId,
739
805
  activeStreamId: null,
740
806
  },
741
807
  },
@@ -768,11 +834,21 @@ export async function POST(request: NextRequest) {
768
834
  message: userMessage,
769
835
  prevMessages: anonymousPreviousMessages,
770
836
  projectId,
837
+ assistantMessageId,
838
+ selectedModelId: requestSelectedModelId,
839
+ parallelGroupId,
840
+ parallelIndex,
841
+ isPrimaryParallel,
771
842
  }: {
772
843
  id: string;
773
844
  message: ChatMessage;
774
845
  prevMessages: ChatMessage[];
775
846
  projectId?: string;
847
+ assistantMessageId?: string;
848
+ selectedModelId?: AppModelId;
849
+ parallelGroupId?: string | null;
850
+ parallelIndex?: number | null;
851
+ isPrimaryParallel?: boolean | null;
776
852
  } = await request.json();
777
853
 
778
854
  if (!userMessage) {
@@ -780,8 +856,10 @@ export async function POST(request: NextRequest) {
780
856
  return new ChatSDKError("bad_request:api").toResponse();
781
857
  }
782
858
 
783
- // Extract selectedModel from user message metadata
784
- const selectedModelId = userMessage.metadata?.selectedModel as AppModelId;
859
+ const selectedModelId = resolveSelectedModelId({
860
+ requestSelectedModelId,
861
+ selectedModel: userMessage.metadata.selectedModel,
862
+ });
785
863
 
786
864
  if (!selectedModelId) {
787
865
  log.warn("No selectedModel in user message metadata");
@@ -857,6 +935,11 @@ export async function POST(request: NextRequest) {
857
935
  userMessage,
858
936
  previousMessages,
859
937
  selectedModelId,
938
+ assistantMessageId,
939
+ parallelGroupId:
940
+ parallelGroupId ?? userMessage.metadata.parallelGroupId ?? null,
941
+ parallelIndex: parallelIndex ?? null,
942
+ isPrimaryParallel: isPrimaryParallel ?? null,
860
943
  explicitlyRequestedTools,
861
944
  userId,
862
945
  isAnonymous,
@@ -9,130 +9,147 @@ const isProd = process.env.NODE_ENV === "production";
9
9
  * @see https://chatjs.dev/docs/reference/config
10
10
  */
11
11
  const config = defineConfig({
12
- appPrefix: "chatjs",
13
- appName: "ChatJS",
14
- appTitle: "ChatJS - The prod ready AI chat app",
15
- appDescription:
16
- "Build and deploy AI chat applications in minutes. ChatJS provides authentication, streaming, tool calling, and all the features you need for production-ready AI conversations.",
17
- appUrl: "https://chatjs.dev",
18
- organization: {
19
- name: "ChatJS",
20
- contact: {
21
- privacyEmail: "privacy@chatjs.dev",
22
- legalEmail: "legal@chatjs.dev",
23
- },
24
- },
25
- services: {
26
- hosting: "Vercel",
27
- aiProviders: [
28
- "OpenAI",
29
- "Anthropic",
30
- "xAI",
31
- "Google",
32
- "Meta",
33
- "Mistral",
34
- "Alibaba",
35
- "Amazon",
36
- "Cohere",
37
- "DeepSeek",
38
- "Perplexity",
39
- "Vercel",
40
- "Inception",
41
- "Moonshot",
42
- "Morph",
43
- "ZAI",
44
- ],
45
- paymentProcessors: [],
46
- },
47
- features: {
48
- attachments: true, // Requires BLOB_READ_WRITE_TOKEN
49
- },
50
- legal: {
51
- minimumAge: 13,
52
- governingLaw: "United States",
53
- refundPolicy: "no-refunds",
54
- },
55
- policies: {
56
- privacy: {
57
- title: "Privacy Policy",
58
- lastUpdated: "July 24, 2025",
59
- },
60
- terms: {
61
- title: "Terms of Service",
62
- lastUpdated: "July 24, 2025",
63
- },
64
- },
65
- authentication: {
66
- google: true, // Requires AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET
67
- github: true, // Requires AUTH_GITHUB_ID + AUTH_GITHUB_SECRET
68
- vercel: true, // Requires VERCEL_APP_CLIENT_ID + VERCEL_APP_CLIENT_SECRET
69
- },
70
- ai: {
71
- gateway: "openai",
72
- providerOrder: ["openai"],
73
- disabledModels: [],
74
- anonymousModels: ["gpt-5-nano"],
75
- workflows: {
76
- chatImageCompatible: "gpt-4o-mini",
77
- },
78
- tools: {
79
- webSearch: {
80
- enabled: true, // Requires TAVILY_API_KEY or FIRECRAWL_API_KEY
81
- },
82
- urlRetrieval: {
83
- enabled: true, // Requires FIRECRAWL_API_KEY
84
- },
85
- codeExecution: {
86
- enabled: true, // Vercel-native, no key needed
87
- },
88
- mcp: {
89
- enabled: true, // Requires MCP_ENCRYPTION_KEY
90
- },
91
- followupSuggestions: {
92
- enabled: true,
93
- },
94
- text: {
95
- polish: "gpt-5-mini",
96
- },
97
- sheet: {
98
- format: "gpt-5-mini",
99
- analyze: "gpt-5-mini",
100
- },
101
- code: {
102
- edits: "gpt-5-mini",
103
- },
104
- image: {
105
- enabled: true, // Requires BLOB_READ_WRITE_TOKEN
106
- default: "gpt-image-1",
107
- },
108
- deepResearch: {
109
- enabled: true, // Requires webSearch
110
- defaultModel: "gpt-5-nano",
111
- finalReportModel: "gpt-5-mini",
112
- allowClarification: true,
113
- maxResearcherIterations: 1,
114
- maxConcurrentResearchUnits: 2,
115
- maxSearchQueries: 2,
116
- },
117
- },
118
- },
119
- anonymous: {
120
- credits: isProd ? 10 : 1000,
121
- availableTools: [],
122
- rateLimit: {
123
- requestsPerMinute: isProd ? 5 : 60,
124
- requestsPerMonth: isProd ? 10 : 1000,
125
- },
126
- },
127
- attachments: {
128
- maxBytes: 1024 * 1024, // 1MB
129
- maxDimension: 2048,
130
- acceptedTypes: {
131
- "image/png": [".png"],
132
- "image/jpeg": [".jpg", ".jpeg"],
133
- "application/pdf": [".pdf"],
134
- },
135
- },
12
+ appPrefix: "chatjs",
13
+ appName: "ChatJS",
14
+ appTitle: "ChatJS - The prod ready AI chat app",
15
+ appDescription:
16
+ "Build and deploy AI chat applications in minutes. ChatJS provides authentication, streaming, tool calling, and all the features you need for production-ready AI conversations.",
17
+ appUrl: "https://chatjs.dev",
18
+ organization: {
19
+ name: "ChatJS",
20
+ contact: {
21
+ privacyEmail: "privacy@chatjs.dev",
22
+ legalEmail: "legal@chatjs.dev",
23
+ },
24
+ },
25
+ services: {
26
+ hosting: "Vercel",
27
+ aiProviders: [
28
+ "OpenAI",
29
+ "Anthropic",
30
+ "xAI",
31
+ "Google",
32
+ "Meta",
33
+ "Mistral",
34
+ "Alibaba",
35
+ "Amazon",
36
+ "Cohere",
37
+ "DeepSeek",
38
+ "Perplexity",
39
+ "Vercel",
40
+ "Inception",
41
+ "Moonshot",
42
+ "Morph",
43
+ "ZAI",
44
+ ],
45
+ paymentProcessors: [],
46
+ },
47
+ features: {
48
+ attachments: true, // Requires BLOB_READ_WRITE_TOKEN
49
+ parallelResponses: true,
50
+ },
51
+ legal: {
52
+ minimumAge: 13,
53
+ governingLaw: "United States",
54
+ refundPolicy: "no-refunds",
55
+ },
56
+ policies: {
57
+ privacy: {
58
+ title: "Privacy Policy",
59
+ lastUpdated: "July 24, 2025",
60
+ },
61
+ terms: {
62
+ title: "Terms of Service",
63
+ lastUpdated: "July 24, 2025",
64
+ },
65
+ },
66
+ authentication: {
67
+ google: true, // Requires AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET
68
+ github: true, // Requires AUTH_GITHUB_ID + AUTH_GITHUB_SECRET
69
+ vercel: true, // Requires VERCEL_APP_CLIENT_ID + VERCEL_APP_CLIENT_SECRET
70
+ },
71
+ ai: {
72
+ gateway: "vercel",
73
+ providerOrder: [
74
+ "openai",
75
+ "anthropic",
76
+ "google",
77
+ "xai",
78
+ "meta",
79
+ "mistral",
80
+ "deepseek",
81
+ "perplexity",
82
+ "cohere",
83
+ "alibaba",
84
+ "amazon",
85
+ "inception",
86
+ "moonshot",
87
+ "morph",
88
+ "zai",
89
+ ],
90
+ disabledModels: [],
91
+ anonymousModels: ["openai/gpt-5-nano"],
92
+ workflows: {
93
+ chatImageCompatible: "openai/gpt-4o-mini",
94
+ },
95
+ tools: {
96
+ webSearch: {
97
+ enabled: true, // Requires TAVILY_API_KEY or FIRECRAWL_API_KEY
98
+ },
99
+ urlRetrieval: {
100
+ enabled: true, // Requires FIRECRAWL_API_KEY
101
+ },
102
+ codeExecution: {
103
+ enabled: true, // Vercel-native, no key needed
104
+ },
105
+ mcp: {
106
+ enabled: true, // Requires MCP_ENCRYPTION_KEY
107
+ },
108
+ followupSuggestions: {
109
+ enabled: true,
110
+ },
111
+ text: {
112
+ polish: "openai/gpt-5-mini",
113
+ },
114
+ sheet: {
115
+ format: "openai/gpt-5-mini",
116
+ analyze: "openai/gpt-5-mini",
117
+ },
118
+ code: {
119
+ edits: "openai/gpt-5-mini",
120
+ },
121
+ image: {
122
+ enabled: true, // Requires BLOB_READ_WRITE_TOKEN
123
+ default: "google/gemini-3-pro-image",
124
+ },
125
+ deepResearch: {
126
+ enabled: true, // Requires webSearch
127
+ defaultModel: "openai/gpt-5-nano",
128
+ finalReportModel: "openai/gpt-5-mini",
129
+ allowClarification: true,
130
+ maxResearcherIterations: 1,
131
+ maxConcurrentResearchUnits: 2,
132
+ maxSearchQueries: 2,
133
+ },
134
+ },
135
+ },
136
+ anonymous: {
137
+ credits: isProd ? 10 : 1000,
138
+ availableTools: [],
139
+ rateLimit: {
140
+ requestsPerMinute: isProd ? 5 : 60,
141
+ requestsPerMonth: isProd ? 10 : 1000,
142
+ },
143
+ },
144
+ attachments: {
145
+ maxBytes: 1024 * 1024, // 1MB
146
+ maxDimension: 2048,
147
+ acceptedTypes: {
148
+ "image/png": [".png"],
149
+ "image/jpeg": [".jpg", ".jpeg"],
150
+ "application/pdf": [".pdf"],
151
+ },
152
+ },
136
153
  });
137
154
 
138
155
  export default config;