@chat-js/cli 0.1.4 → 0.2.1

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 (160) hide show
  1. package/dist/index.js +394 -247
  2. package/package.json +48 -48
  3. package/templates/chat-app/.claude/skiller.toml +18 -0
  4. package/templates/chat-app/.claude/skills/chat-context/SKILL.md +6 -0
  5. package/templates/chat-app/.claude/skills/chat-context/chat-context.mdc +36 -0
  6. package/templates/chat-app/.claude/skills/lazy-prefetch-pattern/lazy-prefetch-pattern.mdc +27 -0
  7. package/templates/chat-app/.claude/skills/react/react.mdc +29 -0
  8. package/templates/chat-app/.claude/skills/trpc-patterns/trpc-patterns.mdc +77 -0
  9. package/templates/chat-app/.claude/skills/typescript/typescript.mdc +53 -0
  10. package/templates/chat-app/.claude/skills/ultracite/ultracite.mdc +129 -0
  11. package/templates/chat-app/.cursor/skills/chat-context/SKILL.md +37 -0
  12. package/templates/chat-app/.cursor/skills/lazy-prefetch-pattern/SKILL.md +26 -0
  13. package/templates/chat-app/.cursor/skills/react/SKILL.md +28 -0
  14. package/templates/chat-app/.cursor/skills/trpc-patterns/SKILL.md +76 -0
  15. package/templates/chat-app/.cursor/skills/typescript/SKILL.md +52 -0
  16. package/templates/chat-app/.cursor/skills/ultracite/SKILL.md +128 -0
  17. package/templates/chat-app/app/(chat)/actions.ts +17 -13
  18. package/templates/chat-app/app/(chat)/api/chat/[id]/stream/route.ts +6 -5
  19. package/templates/chat-app/app/(chat)/api/chat/route.ts +14 -15
  20. package/templates/chat-app/app/(chat)/chat-providers.tsx +2 -2
  21. package/templates/chat-app/app/(chat)/layout.tsx +7 -6
  22. package/templates/chat-app/app/api/cron/cleanup/route.ts +4 -3
  23. package/templates/chat-app/app/globals.css +23 -23
  24. package/templates/chat-app/app/layout.tsx +1 -1
  25. package/templates/chat-app/biome.jsonc +3 -3
  26. package/templates/chat-app/chat.config.ts +48 -21
  27. package/templates/chat-app/components/anonymous-session-init.tsx +4 -12
  28. package/templates/chat-app/components/artifact-actions.tsx +5 -5
  29. package/templates/chat-app/components/artifact-panel.tsx +6 -6
  30. package/templates/chat-app/components/assistant-message.tsx +1 -1
  31. package/templates/chat-app/components/chat/chat-layout.tsx +2 -2
  32. package/templates/chat-app/components/chat/chat-welcome.tsx +1 -0
  33. package/templates/chat-app/components/chat-features-definitions.ts +11 -8
  34. package/templates/chat-app/components/chat-menu-items.tsx +4 -4
  35. package/templates/chat-app/components/chat-sync.tsx +1 -1
  36. package/templates/chat-app/components/clone-chat-button.tsx +2 -2
  37. package/templates/chat-app/components/code-editor.tsx +5 -5
  38. package/templates/chat-app/components/connectors-dropdown.tsx +2 -2
  39. package/templates/chat-app/components/console.tsx +5 -5
  40. package/templates/chat-app/components/create-artifact.tsx +28 -28
  41. package/templates/chat-app/components/data-stream-provider.tsx +2 -2
  42. package/templates/chat-app/components/deep-research-progress.tsx +2 -2
  43. package/templates/chat-app/components/delete-chat-dialog.tsx +3 -3
  44. package/templates/chat-app/components/delete-project-dialog.tsx +3 -3
  45. package/templates/chat-app/components/diffview.tsx +3 -3
  46. package/templates/chat-app/components/favicon-group.tsx +7 -7
  47. package/templates/chat-app/components/header-breadcrumb.tsx +11 -11
  48. package/templates/chat-app/components/image-editor.tsx +5 -5
  49. package/templates/chat-app/components/image-modal.tsx +4 -4
  50. package/templates/chat-app/components/interactive-chart-impl.tsx +269 -0
  51. package/templates/chat-app/components/interactive-charts.tsx +18 -246
  52. package/templates/chat-app/components/lexical-chat-input.tsx +10 -10
  53. package/templates/chat-app/components/message-editor.tsx +3 -3
  54. package/templates/chat-app/components/message-parts.tsx +8 -3
  55. package/templates/chat-app/components/messages-pane.tsx +4 -4
  56. package/templates/chat-app/components/messages.tsx +5 -5
  57. package/templates/chat-app/components/model-selector.tsx +4 -1
  58. package/templates/chat-app/components/multimodal-input.tsx +14 -5
  59. package/templates/chat-app/components/part/code-execution.tsx +4 -1
  60. package/templates/chat-app/components/part/document-common.tsx +8 -8
  61. package/templates/chat-app/components/part/document-preview.tsx +34 -16
  62. package/templates/chat-app/components/part/document-tool.tsx +3 -3
  63. package/templates/chat-app/components/part/dynamic-tool.tsx +3 -3
  64. package/templates/chat-app/components/part/generate-video.tsx +54 -0
  65. package/templates/chat-app/components/part/message-reasoning.tsx +3 -3
  66. package/templates/chat-app/components/project-details-dialog.tsx +4 -4
  67. package/templates/chat-app/components/project-home.tsx +1 -0
  68. package/templates/chat-app/components/project-icon-picker.tsx +5 -5
  69. package/templates/chat-app/components/project-icon.tsx +4 -4
  70. package/templates/chat-app/components/project-menu-items.tsx +3 -3
  71. package/templates/chat-app/components/research-tasks.tsx +3 -3
  72. package/templates/chat-app/components/sandbox.tsx +4 -4
  73. package/templates/chat-app/components/search-chats-dialog.tsx +11 -11
  74. package/templates/chat-app/components/settings/connectors-settings.tsx +1 -1
  75. package/templates/chat-app/components/settings/settings-nav.tsx +1 -1
  76. package/templates/chat-app/components/sheet-editor.tsx +5 -5
  77. package/templates/chat-app/components/sidebar-chats-list.tsx +5 -5
  78. package/templates/chat-app/components/suggested-actions.tsx +3 -3
  79. package/templates/chat-app/components/text-editor.tsx +5 -5
  80. package/templates/chat-app/components/toolbar.tsx +6 -6
  81. package/templates/chat-app/components/upgrade-cta/login-cta-banner.tsx +5 -5
  82. package/templates/chat-app/components/upgrade-cta/login-prompt.tsx +4 -4
  83. package/templates/chat-app/components/upgrade-cta/share-menu-item.tsx +3 -3
  84. package/templates/chat-app/components/user-message.tsx +3 -3
  85. package/templates/chat-app/components/version-footer.tsx +4 -4
  86. package/templates/chat-app/hooks/chat-sync-hooks.ts +0 -55
  87. package/templates/chat-app/hooks/use-artifact.tsx +3 -3
  88. package/templates/chat-app/hooks/use-auto-focus.ts +37 -7
  89. package/templates/chat-app/hooks/use-media-query.tsx +2 -4
  90. package/templates/chat-app/lib/ai/active-gateway.ts +1 -1
  91. package/templates/chat-app/lib/ai/ai-gateway-models-schemas.ts +30 -6
  92. package/templates/chat-app/lib/ai/app-model-id.ts +1 -1
  93. package/templates/chat-app/lib/ai/app-models.ts +4 -4
  94. package/templates/chat-app/lib/ai/eval-agent.ts +5 -5
  95. package/templates/chat-app/lib/ai/followup-suggestions.ts +5 -2
  96. package/templates/chat-app/lib/ai/gateway-model-defaults.ts +131 -41
  97. package/templates/chat-app/lib/ai/gateways/gateway-provider.ts +10 -6
  98. package/templates/chat-app/lib/ai/gateways/openai-compatible-gateway.ts +9 -4
  99. package/templates/chat-app/lib/ai/gateways/openai-gateway.ts +9 -4
  100. package/templates/chat-app/lib/ai/gateways/openrouter-gateway.ts +17 -12
  101. package/templates/chat-app/lib/ai/gateways/registry.ts +9 -0
  102. package/templates/chat-app/lib/ai/gateways/vercel-gateway.ts +36 -4
  103. package/templates/chat-app/lib/ai/mcp/cache.ts +13 -13
  104. package/templates/chat-app/lib/ai/model-data.ts +21 -20
  105. package/templates/chat-app/lib/ai/models.generated.ts +4397 -3592
  106. package/templates/chat-app/lib/ai/models.ts +1 -1
  107. package/templates/chat-app/lib/ai/providers.ts +10 -0
  108. package/templates/chat-app/lib/ai/text-splitter.ts +3 -4
  109. package/templates/chat-app/lib/ai/to-model-data.ts +1 -0
  110. package/templates/chat-app/lib/ai/tools/code-execution.ts +122 -53
  111. package/templates/chat-app/lib/ai/tools/deep-research/configuration.ts +35 -32
  112. package/templates/chat-app/lib/ai/tools/deep-research/pipeline.ts +2 -2
  113. package/templates/chat-app/lib/ai/tools/deep-research/types.ts +9 -9
  114. package/templates/chat-app/lib/ai/tools/documents/types.ts +4 -4
  115. package/templates/chat-app/lib/ai/tools/generate-image.ts +42 -20
  116. package/templates/chat-app/lib/ai/tools/generate-video.ts +166 -0
  117. package/templates/chat-app/lib/ai/tools/get-weather.ts +20 -20
  118. package/templates/chat-app/lib/ai/tools/read-document.ts +3 -3
  119. package/templates/chat-app/lib/ai/tools/steps/multi-query-web-search.ts +11 -11
  120. package/templates/chat-app/lib/ai/tools/steps/web-search.ts +6 -6
  121. package/templates/chat-app/lib/ai/tools/tools-definitions.ts +10 -5
  122. package/templates/chat-app/lib/ai/tools/tools.ts +15 -6
  123. package/templates/chat-app/lib/ai/tools/types.ts +2 -2
  124. package/templates/chat-app/lib/ai/types.ts +22 -13
  125. package/templates/chat-app/lib/artifacts/code/client.tsx +5 -5
  126. package/templates/chat-app/lib/artifacts/sheet/client.tsx +2 -2
  127. package/templates/chat-app/lib/artifacts/text/client.tsx +18 -3
  128. package/templates/chat-app/lib/clone-messages.test.ts +6 -1
  129. package/templates/chat-app/lib/config-requirements.ts +19 -10
  130. package/templates/chat-app/lib/config-schema.ts +189 -103
  131. package/templates/chat-app/lib/config.ts +4 -4
  132. package/templates/chat-app/lib/credits/cost-accumulator.ts +11 -8
  133. package/templates/chat-app/lib/env-schema.ts +1 -1
  134. package/templates/chat-app/lib/features-config.ts +6 -6
  135. package/templates/chat-app/lib/stores/with-threads.ts +3 -3
  136. package/templates/chat-app/lib/thread-utils.ts +2 -2
  137. package/templates/chat-app/lib/types/anonymous.ts +4 -4
  138. package/templates/chat-app/lib/types/ui-chat.ts +7 -7
  139. package/templates/chat-app/lib/utils/download-assets.ts +3 -3
  140. package/templates/chat-app/lib/utils/rate-limit.ts +8 -8
  141. package/templates/chat-app/next.config.ts +0 -25
  142. package/templates/chat-app/package.json +16 -16
  143. package/templates/chat-app/playwright.config.ts +5 -5
  144. package/templates/chat-app/providers/chat-id-provider.tsx +5 -5
  145. package/templates/chat-app/providers/chat-input-provider.tsx +15 -15
  146. package/templates/chat-app/providers/chat-models-provider.tsx +3 -3
  147. package/templates/chat-app/providers/default-model-provider.tsx +5 -5
  148. package/templates/chat-app/providers/parse-chat-id-from-pathname.test.ts +16 -0
  149. package/templates/chat-app/providers/session-provider.tsx +2 -2
  150. package/templates/chat-app/scripts/check-env.ts +36 -4
  151. package/templates/chat-app/tests/artifacts.e2e.ts +7 -0
  152. package/templates/chat-app/tests/auth.setup.e2e.ts +10 -0
  153. package/templates/chat-app/tests/chat.e2e.ts +7 -0
  154. package/templates/chat-app/tests/reasoning.e2e.ts +7 -0
  155. package/templates/chat-app/tests/reasoning.setup.e2e.ts +10 -0
  156. package/templates/chat-app/trpc/routers/chat.router.ts +1 -1
  157. package/templates/chat-app/trpc/routers/mcp.router.ts +3 -3
  158. package/templates/chat-app/vitest.config.ts +7 -0
  159. package/templates/chat-app/next-env.d.ts +0 -6
  160. package/templates/chat-app/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,166 @@
1
+ import { experimental_generateVideo as generateVideo, tool } from "ai";
2
+ import { z } from "zod";
3
+ import { type AppModelId, getAppModelDefinition } from "@/lib/ai/app-models";
4
+ import { getVideoModel } from "@/lib/ai/providers";
5
+ import { uploadFile } from "@/lib/blob";
6
+ import { config } from "@/lib/config";
7
+ import type { CostAccumulator } from "@/lib/credits/cost-accumulator";
8
+ import { createModuleLogger } from "@/lib/logger";
9
+ import { toolsDefinitions } from "./tools-definitions";
10
+
11
+ interface GenerateVideoProps {
12
+ costAccumulator?: CostAccumulator;
13
+ selectedModel?: string;
14
+ }
15
+
16
+ const log = createModuleLogger("ai.tools.generate-video");
17
+ const DEFAULT_ASPECT_RATIO = "16:9";
18
+ const DEFAULT_DURATION_SECONDS = 5;
19
+ const ALLOWED_EXTENSIONS = new Set(["mp4", "webm", "mov"]);
20
+
21
+ function resolveVideoExtension(mediaType?: string): string {
22
+ if (!mediaType) {
23
+ return "mp4";
24
+ }
25
+
26
+ const [, subtypeWithParams] = mediaType.split("/");
27
+ if (!subtypeWithParams) {
28
+ return "mp4";
29
+ }
30
+
31
+ const subtype = subtypeWithParams.split(";")[0]?.trim().toLowerCase();
32
+ if (!subtype) {
33
+ return "mp4";
34
+ }
35
+
36
+ const mappedSubtype = subtype === "quicktime" ? "mov" : subtype;
37
+ return ALLOWED_EXTENSIONS.has(mappedSubtype) ? mappedSubtype : "mp4";
38
+ }
39
+
40
+ async function resolveVideoModel(selectedModel?: string): Promise<string> {
41
+ if (selectedModel) {
42
+ try {
43
+ const model = await getAppModelDefinition(selectedModel as AppModelId);
44
+ if (model.output.video) {
45
+ return selectedModel;
46
+ }
47
+ } catch {
48
+ // Not in app models registry, fall through
49
+ }
50
+ }
51
+ return config.ai.tools.video.default;
52
+ }
53
+
54
+ export const generateVideoTool = ({
55
+ selectedModel,
56
+ costAccumulator,
57
+ }: GenerateVideoProps = {}) =>
58
+ tool({
59
+ description:
60
+ "Generate a short video clip from a text prompt. Use this when the user asks to create, make, or generate a video.",
61
+ inputSchema: z.object({
62
+ prompt: z
63
+ .string()
64
+ .describe("A descriptive prompt for the video to generate."),
65
+ aspectRatio: z
66
+ .enum(["16:9", "9:16", "1:1"])
67
+ .optional()
68
+ .describe("Optional output aspect ratio. Defaults to 16:9."),
69
+ durationSeconds: z
70
+ .number()
71
+ .int()
72
+ .min(1)
73
+ .max(10)
74
+ .optional()
75
+ .describe("Optional video duration in seconds. Defaults to 5."),
76
+ }),
77
+ execute: async ({ prompt, aspectRatio, durationSeconds }) => {
78
+ const startMs = Date.now();
79
+ const finalAspectRatio = aspectRatio ?? DEFAULT_ASPECT_RATIO;
80
+ const finalDurationSeconds = durationSeconds ?? DEFAULT_DURATION_SECONDS;
81
+
82
+ log.info(
83
+ {
84
+ promptLength: prompt.length,
85
+ selectedModel,
86
+ aspectRatio: finalAspectRatio,
87
+ durationSeconds: finalDurationSeconds,
88
+ },
89
+ "generateVideo: start"
90
+ );
91
+
92
+ try {
93
+ const modelId = await resolveVideoModel(selectedModel);
94
+ const isGoogleModel =
95
+ modelId.startsWith("google/") || modelId.includes("gemini");
96
+
97
+ log.debug({ modelId }, "generateVideo: resolved model");
98
+
99
+ const result = await generateVideo({
100
+ model: getVideoModel(modelId),
101
+ prompt,
102
+ aspectRatio: finalAspectRatio,
103
+ duration: finalDurationSeconds,
104
+ providerOptions: {
105
+ ...(isGoogleModel && {
106
+ google: {
107
+ aspectRatio: finalAspectRatio,
108
+ },
109
+ }),
110
+ },
111
+ });
112
+
113
+ const video = result.video;
114
+ if (!video) {
115
+ throw new Error("No video generated");
116
+ }
117
+
118
+ const buffer = Buffer.from(video.uint8Array);
119
+ const timestamp = Date.now();
120
+ const ext = resolveVideoExtension(video.mediaType);
121
+ const filename = `generated-video-${timestamp}.${ext}`;
122
+ const uploaded = await uploadFile(filename, buffer);
123
+
124
+ costAccumulator?.addAPICost(
125
+ "generateVideo",
126
+ toolsDefinitions.generateVideo.cost
127
+ );
128
+
129
+ log.info(
130
+ {
131
+ ms: Date.now() - startMs,
132
+ modelId,
133
+ videoUrl: uploaded.url,
134
+ },
135
+ "generateVideo: success"
136
+ );
137
+
138
+ return { videoUrl: uploaded.url, prompt };
139
+ } catch (error) {
140
+ const errorMessage = error instanceof Error ? error.message : "";
141
+ const isUnsupportedVideoGateway = errorMessage.includes(
142
+ "does not support video models"
143
+ );
144
+
145
+ log.error(
146
+ {
147
+ ms: Date.now() - startMs,
148
+ selectedModel,
149
+ error:
150
+ error instanceof Error
151
+ ? { message: error.message, name: error.name }
152
+ : error,
153
+ },
154
+ "generateVideo: failure"
155
+ );
156
+
157
+ if (isUnsupportedVideoGateway) {
158
+ throw new Error(
159
+ "Video generation is not available for the active gateway."
160
+ );
161
+ }
162
+
163
+ throw error;
164
+ }
165
+ },
166
+ });
@@ -23,40 +23,40 @@ export const getWeather = tool({
23
23
  },
24
24
  });
25
25
 
26
- export type WeatherAtLocation = {
27
- latitude: number;
28
- longitude: number;
29
- generationtime_ms: number;
30
- utc_offset_seconds: number;
31
- timezone: string;
32
- timezone_abbreviation: string;
33
- elevation: number;
34
- current_units: {
35
- time: string;
36
- interval: string;
37
- temperature_2m: string;
38
- };
26
+ export interface WeatherAtLocation {
39
27
  current: {
40
28
  time: string;
41
29
  interval: number;
42
30
  temperature_2m: number;
43
31
  };
44
- hourly_units: {
32
+ current_units: {
45
33
  time: string;
34
+ interval: string;
46
35
  temperature_2m: string;
47
36
  };
48
- hourly: {
37
+ daily: {
49
38
  time: string[];
50
- temperature_2m: number[];
39
+ sunrise: string[];
40
+ sunset: string[];
51
41
  };
52
42
  daily_units: {
53
43
  time: string;
54
44
  sunrise: string;
55
45
  sunset: string;
56
46
  };
57
- daily: {
47
+ elevation: number;
48
+ generationtime_ms: number;
49
+ hourly: {
58
50
  time: string[];
59
- sunrise: string[];
60
- sunset: string[];
51
+ temperature_2m: number[];
61
52
  };
62
- };
53
+ hourly_units: {
54
+ time: string;
55
+ temperature_2m: string;
56
+ };
57
+ latitude: number;
58
+ longitude: number;
59
+ timezone: string;
60
+ timezone_abbreviation: string;
61
+ utc_offset_seconds: number;
62
+ }
@@ -4,10 +4,10 @@ import type { ToolSession } from "@/lib/ai/tools/types";
4
4
  import { getDocumentById } from "@/lib/db/queries";
5
5
  import type { StreamWriter } from "../types";
6
6
 
7
- type ReadDocumentProps = {
8
- session: ToolSession;
7
+ interface ReadDocumentProps {
9
8
  dataStream: StreamWriter;
10
- };
9
+ session: ToolSession;
10
+ }
11
11
 
12
12
  export const readDocument = ({
13
13
  session,
@@ -4,30 +4,30 @@ import { deduplicateByDomainAndUrl } from "./search-utils";
4
4
  import type { SearchProviderOptions } from "./web-search";
5
5
  import { webSearchStep } from "./web-search";
6
6
 
7
- export type SearchQuery = {
8
- query: string;
7
+ export interface SearchQuery {
9
8
  maxResults: number;
10
- };
9
+ query: string;
10
+ }
11
11
 
12
- export type MultiQuerySearchOptions = {
12
+ export interface MultiQuerySearchOptions {
13
13
  baseProviderOptions: SearchProviderOptions;
14
- topics?: string[];
15
14
  excludeDomains?: string[];
16
- };
15
+ topics?: string[];
16
+ }
17
17
 
18
- type MultiQuerySearchResult = {
18
+ interface MultiQuerySearchResult {
19
19
  query: SearchQuery;
20
20
  results: Array<{
21
21
  url: string;
22
22
  title: string;
23
23
  content: string;
24
24
  }>;
25
- };
25
+ }
26
26
 
27
- export type MultiQuerySearchResponse = {
28
- searches: MultiQuerySearchResult[];
27
+ export interface MultiQuerySearchResponse {
29
28
  error?: string;
30
- };
29
+ searches: MultiQuerySearchResult[];
30
+ }
31
31
 
32
32
  export async function multiQueryWebSearchStep({
33
33
  queries,
@@ -11,17 +11,17 @@ export type SearchProviderOptions =
11
11
  provider: "firecrawl";
12
12
  } & SearchParams);
13
13
 
14
- export type WebSearchResult = {
14
+ export interface WebSearchResult {
15
+ content: string;
15
16
  source: "web";
16
17
  title: string;
17
18
  url: string;
18
- content: string;
19
- };
19
+ }
20
20
 
21
- export type WebSearchResponse = {
22
- results: WebSearchResult[];
21
+ export interface WebSearchResponse {
23
22
  error?: string;
24
- };
23
+ results: WebSearchResult[];
24
+ }
25
25
 
26
26
  // Initialize search providers lazily to avoid runtime errors when keys are missing
27
27
  const tvly = env.TAVILY_API_KEY ? tavily({ apiKey: env.TAVILY_API_KEY }) : null;
@@ -61,7 +61,12 @@ export const toolsDefinitions: Record<ToolName, ToolDefinition> = {
61
61
  generateImage: {
62
62
  name: "generateImage",
63
63
  description: "Generate images from text descriptions",
64
- cost: 17, // Nano banana pro ~17¢
64
+ cost: 0, // LLM cost tracked via token usage
65
+ },
66
+ generateVideo: {
67
+ name: "generateVideo",
68
+ description: "Generate video clips from text descriptions",
69
+ cost: 0, // LLM cost tracked via token usage
65
70
  },
66
71
  deepResearch: {
67
72
  name: "deepResearch",
@@ -71,8 +76,8 @@ export const toolsDefinitions: Record<ToolName, ToolDefinition> = {
71
76
  };
72
77
 
73
78
  export const allTools = toolNameSchema.options;
74
- type ToolDefinition = {
75
- name: string;
76
- description: string;
79
+ interface ToolDefinition {
77
80
  cost: number;
78
- };
81
+ description: string;
82
+ name: string;
83
+ }
@@ -10,6 +10,7 @@ import { editCodeDocumentTool } from "@/lib/ai/tools/documents/edit-code-documen
10
10
  import { editSheetDocumentTool } from "@/lib/ai/tools/documents/edit-sheet-document";
11
11
  import { editTextDocumentTool } from "@/lib/ai/tools/documents/edit-text-document";
12
12
  import { generateImageTool } from "@/lib/ai/tools/generate-image";
13
+ import { generateVideoTool } from "@/lib/ai/tools/generate-video";
13
14
  import { getWeather } from "@/lib/ai/tools/get-weather";
14
15
  import { readDocument } from "@/lib/ai/tools/read-document";
15
16
  import { retrieveUrl } from "@/lib/ai/tools/retrieve-url";
@@ -62,8 +63,8 @@ export function getTools({
62
63
  session,
63
64
  dataStream,
64
65
  }),
65
- ...(config.features.urlRetrieval ? { retrieveUrl } : {}),
66
- ...(config.features.webSearch
66
+ ...(config.ai.tools.urlRetrieval.enabled ? { retrieveUrl } : {}),
67
+ ...(config.ai.tools.webSearch.enabled
67
68
  ? {
68
69
  webSearch: tavilyWebSearch({
69
70
  dataStream,
@@ -73,10 +74,10 @@ export function getTools({
73
74
  }
74
75
  : {}),
75
76
 
76
- ...(config.features.sandbox
77
+ ...(config.ai.tools.codeExecution.enabled
77
78
  ? { codeExecution: codeExecution({ costAccumulator }) }
78
79
  : {}),
79
- ...(config.features.imageGeneration
80
+ ...(config.ai.tools.image.enabled
80
81
  ? {
81
82
  generateImage: generateImageTool({
82
83
  attachments,
@@ -86,7 +87,7 @@ export function getTools({
86
87
  }),
87
88
  }
88
89
  : {}),
89
- ...(config.features.deepResearch
90
+ ...(config.ai.tools.deepResearch.enabled
90
91
  ? {
91
92
  deepResearch: deepResearch({
92
93
  session,
@@ -97,6 +98,14 @@ export function getTools({
97
98
  }),
98
99
  }
99
100
  : {}),
101
+ ...(config.ai.tools.video.enabled
102
+ ? {
103
+ generateVideo: generateVideoTool({
104
+ selectedModel,
105
+ costAccumulator,
106
+ }),
107
+ }
108
+ : {}),
100
109
  };
101
110
  }
102
111
 
@@ -113,7 +122,7 @@ export async function getMcpTools({
113
122
  tools: Record<string, Tool>;
114
123
  cleanup: () => Promise<void>;
115
124
  }> {
116
- if (!config.features.mcp) {
125
+ if (!config.ai.tools.mcp.enabled) {
117
126
  return {
118
127
  tools: {},
119
128
  cleanup: async () => Promise.resolve(),
@@ -3,6 +3,6 @@ import type { Session } from "@/lib/auth";
3
3
  type SessionUser = NonNullable<Session["user"]>;
4
4
 
5
5
  // Minimal session slice for tools - derived from Better Auth Session to avoid drift
6
- export type ToolSession = {
6
+ export interface ToolSession {
7
7
  user?: Pick<SessionUser, "id">;
8
- };
8
+ }
@@ -8,6 +8,7 @@ import { z } from "zod";
8
8
  import type { codeExecution } from "@/lib/ai/tools/code-execution";
9
9
  import type { deepResearch } from "@/lib/ai/tools/deep-research/deep-research";
10
10
  import type { generateImageTool as generateImageToolFactory } from "@/lib/ai/tools/generate-image";
11
+ import type { generateVideoTool as generateVideoToolFactory } from "@/lib/ai/tools/generate-video";
11
12
  import type { getWeather } from "@/lib/ai/tools/get-weather";
12
13
  import type { readDocument } from "@/lib/ai/tools/read-document";
13
14
  import type { retrieveUrl } from "@/lib/ai/tools/retrieve-url";
@@ -34,6 +35,7 @@ export const toolNameSchema = z.enum([
34
35
  "webSearch",
35
36
  "codeExecution",
36
37
  "generateImage",
38
+ "generateVideo",
37
39
  "deepResearch",
38
40
  ]);
39
41
 
@@ -45,6 +47,7 @@ const frontendToolsSchema = z.enum([
45
47
  "webSearch",
46
48
  "deepResearch",
47
49
  "generateImage",
50
+ "generateVideo",
48
51
  "createTextDocument",
49
52
  "createCodeDocument",
50
53
  "createSheetDocument",
@@ -91,37 +94,43 @@ type readDocumentTool = InferUITool<ReturnType<typeof readDocument>>;
91
94
  type generateImageTool = InferUITool<
92
95
  ReturnType<typeof generateImageToolFactory>
93
96
  >;
97
+ type generateVideoTool = InferUITool<
98
+ ReturnType<typeof generateVideoToolFactory>
99
+ >;
94
100
  type webSearchTool = InferUITool<ReturnType<typeof tavilyWebSearch>>;
95
101
  type codeExecutionTool = InferUITool<ReturnType<typeof codeExecution>>;
96
102
  type retrieveUrlTool = InferUITool<typeof retrieveUrl>;
97
103
 
104
+ // biome-ignore lint/style/useConsistentTypeDefinitions: <explanation>
98
105
  export type ChatTools = {
99
- getWeather: weatherTool;
100
- createTextDocument: createTextDocumentToolType;
106
+ codeExecution: codeExecutionTool;
101
107
  createCodeDocument: createCodeDocumentToolType;
102
108
  createSheetDocument: createSheetDocumentToolType;
103
- editTextDocument: editTextDocumentToolType;
109
+ createTextDocument: createTextDocumentToolType;
110
+ deepResearch: deepResearchTool;
104
111
  editCodeDocument: editCodeDocumentToolType;
105
112
  editSheetDocument: editSheetDocumentToolType;
106
- deepResearch: deepResearchTool;
107
- readDocument: readDocumentTool;
113
+ editTextDocument: editTextDocumentToolType;
108
114
  generateImage: generateImageTool;
109
- webSearch: webSearchTool;
110
- codeExecution: codeExecutionTool;
115
+ generateVideo: generateVideoTool;
116
+ getWeather: weatherTool;
117
+ readDocument: readDocumentTool;
111
118
  retrieveUrl: retrieveUrlTool;
119
+ webSearch: webSearchTool;
112
120
  };
113
121
 
114
- type FollowupSuggestions = {
122
+ interface FollowupSuggestions {
115
123
  suggestions: string[];
116
- };
124
+ }
117
125
 
126
+ // biome-ignore lint/style/useConsistentTypeDefinitions: <explanation>
118
127
  export type CustomUIDataTypes = {
119
128
  appendMessage: string;
120
129
  chatConfirmed: {
121
130
  chatId: string;
122
131
  };
123
- researchUpdate: ResearchUpdate;
124
132
  followupSuggestions: FollowupSuggestions;
133
+ researchUpdate: ResearchUpdate;
125
134
  };
126
135
 
127
136
  export type ChatMessage = Omit<
@@ -137,8 +146,8 @@ export type ToolOutput<T extends ToolName> = ChatTools[T]["output"];
137
146
 
138
147
  export type StreamWriter = UIMessageStreamWriter<ChatMessage>;
139
148
 
140
- export type Attachment = {
149
+ export interface Attachment {
150
+ contentType: string;
141
151
  name: string;
142
152
  url: string;
143
- contentType: string;
144
- };
153
+ }
@@ -56,10 +56,10 @@ function detectRequiredHandlers(code: string): string[] {
56
56
  return handlers;
57
57
  }
58
58
 
59
- type Metadata = {
60
- outputs: ConsoleOutput[];
59
+ interface Metadata {
61
60
  language: string;
62
- };
61
+ outputs: ConsoleOutput[];
62
+ }
63
63
 
64
64
  export const codeArtifact = new Artifact<"code", Metadata>({
65
65
  kind: "code",
@@ -257,7 +257,7 @@ export const codeArtifact = new Artifact<"code", Metadata>({
257
257
  },
258
258
  ],
259
259
  metadata: {
260
- selectedModel: config.models.defaults.codeEdits,
260
+ selectedModel: config.ai.tools.code.edits,
261
261
  createdAt: new Date(),
262
262
  parentMessageId: storeApi.getState().getLastMessageId(),
263
263
  activeStreamId: null,
@@ -278,7 +278,7 @@ export const codeArtifact = new Artifact<"code", Metadata>({
278
278
  },
279
279
  ],
280
280
  metadata: {
281
- selectedModel: config.models.defaults.codeEdits,
281
+ selectedModel: config.ai.tools.code.edits,
282
282
  createdAt: new Date(),
283
283
  parentMessageId: storeApi.getState().getLastMessageId(),
284
284
  activeStreamId: null,
@@ -87,7 +87,7 @@ export const sheetArtifact = new Artifact<"sheet", Metadata>({
87
87
  { type: "text", text: "Can you please format and clean the data?" },
88
88
  ],
89
89
  metadata: {
90
- selectedModel: config.models.defaults.formatSheet,
90
+ selectedModel: config.ai.tools.sheet.format,
91
91
  createdAt: new Date(),
92
92
  parentMessageId: storeApi.getState().getLastMessageId(),
93
93
  activeStreamId: null,
@@ -108,7 +108,7 @@ export const sheetArtifact = new Artifact<"sheet", Metadata>({
108
108
  },
109
109
  ],
110
110
  metadata: {
111
- selectedModel: config.models.defaults.analyzeSheet,
111
+ selectedModel: config.ai.tools.sheet.analyze,
112
112
  createdAt: new Date(),
113
113
  parentMessageId: storeApi.getState().getLastMessageId(),
114
114
  activeStreamId: null,
@@ -1,10 +1,25 @@
1
1
  import { Copy, History, Pen, Redo2, Undo2 } from "lucide-react";
2
+ import dynamic from "next/dynamic";
2
3
  import { toast } from "sonner";
3
4
  import { Artifact } from "@/components/create-artifact";
4
- import { DiffView } from "@/components/diffview";
5
5
  import { DocumentSkeleton } from "@/components/document-skeleton";
6
- import { Editor } from "@/components/text-editor";
7
6
  import { config } from "@/lib/config";
7
+
8
+ const DiffView = dynamic(
9
+ () => import("@/components/diffview").then((m) => ({ default: m.DiffView })),
10
+ {
11
+ loading: () => <DocumentSkeleton artifactKind="text" />,
12
+ ssr: false,
13
+ }
14
+ );
15
+
16
+ const Editor = dynamic(
17
+ () => import("@/components/text-editor").then((m) => ({ default: m.Editor })),
18
+ {
19
+ loading: () => <DocumentSkeleton artifactKind="text" />,
20
+ ssr: false,
21
+ }
22
+ );
8
23
  export const textArtifact = new Artifact<"text">({
9
24
  kind: "text",
10
25
  description: "Useful for text content, like drafting essays and emails.",
@@ -113,7 +128,7 @@ export const textArtifact = new Artifact<"text">({
113
128
  },
114
129
  ],
115
130
  metadata: {
116
- selectedModel: config.models.defaults.polishText,
131
+ selectedModel: config.ai.tools.text.polish,
117
132
  createdAt: new Date(),
118
133
  parentMessageId: storeApi.getState().getLastMessageId(),
119
134
  activeStreamId: null,
@@ -1,6 +1,11 @@
1
1
  import assert from "node:assert/strict";
2
- import { describe, it } from "vitest";
2
+ import { describe, it, vi } from "vitest";
3
3
  import type { ChatMessage } from "./ai/types";
4
+
5
+ vi.mock("@/lib/config", () => ({
6
+ config: { appPrefix: "test" },
7
+ }));
8
+
4
9
  import { cloneMessagesWithDocuments } from "./clone-messages";
5
10
 
6
11
  function createMessage({
@@ -1,12 +1,16 @@
1
1
  import type { GatewayType } from "./ai/gateways/registry";
2
- import type { AuthenticationConfig, FeaturesConfig } from "./config-schema";
2
+ import type {
3
+ AiConfig,
4
+ AuthenticationConfig,
5
+ FeaturesConfig,
6
+ } from "./config-schema";
3
7
 
4
8
  type EnvVarName = keyof NodeJS.ProcessEnv;
5
9
 
6
- export type EnvRequirement = {
7
- options: EnvVarName[][];
10
+ export interface EnvRequirement {
8
11
  description: string;
9
- };
12
+ options: EnvVarName[][];
13
+ }
10
14
 
11
15
  export const gatewayEnvRequirements: Record<GatewayType, EnvRequirement> = {
12
16
  openrouter: {
@@ -29,6 +33,15 @@ export const gatewayEnvRequirements: Record<GatewayType, EnvRequirement> = {
29
33
 
30
34
  export const featureEnvRequirements: Partial<
31
35
  Record<keyof FeaturesConfig, EnvRequirement>
36
+ > = {
37
+ attachments: {
38
+ options: [["BLOB_READ_WRITE_TOKEN"]],
39
+ description: "BLOB_READ_WRITE_TOKEN",
40
+ },
41
+ };
42
+
43
+ export const aiToolEnvRequirements: Partial<
44
+ Record<keyof AiConfig["tools"], EnvRequirement>
32
45
  > = {
33
46
  webSearch: {
34
47
  options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
@@ -46,7 +59,7 @@ export const featureEnvRequirements: Partial<
46
59
  options: [["MCP_ENCRYPTION_KEY"]],
47
60
  description: "MCP_ENCRYPTION_KEY",
48
61
  },
49
- sandbox: {
62
+ codeExecution: {
50
63
  options: [
51
64
  ["VERCEL_OIDC_TOKEN"],
52
65
  ["VERCEL_TEAM_ID", "VERCEL_PROJECT_ID", "VERCEL_TOKEN"],
@@ -54,11 +67,7 @@ export const featureEnvRequirements: Partial<
54
67
  description:
55
68
  "VERCEL_OIDC_TOKEN (auto on Vercel) or VERCEL_TEAM_ID + VERCEL_PROJECT_ID + VERCEL_TOKEN",
56
69
  },
57
- imageGeneration: {
58
- options: [["BLOB_READ_WRITE_TOKEN"]],
59
- description: "BLOB_READ_WRITE_TOKEN",
60
- },
61
- attachments: {
70
+ image: {
62
71
  options: [["BLOB_READ_WRITE_TOKEN"]],
63
72
  description: "BLOB_READ_WRITE_TOKEN",
64
73
  },