@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.
- package/dist/index.js +394 -247
- package/package.json +48 -48
- package/templates/chat-app/.claude/skiller.toml +18 -0
- package/templates/chat-app/.claude/skills/chat-context/SKILL.md +6 -0
- package/templates/chat-app/.claude/skills/chat-context/chat-context.mdc +36 -0
- package/templates/chat-app/.claude/skills/lazy-prefetch-pattern/lazy-prefetch-pattern.mdc +27 -0
- package/templates/chat-app/.claude/skills/react/react.mdc +29 -0
- package/templates/chat-app/.claude/skills/trpc-patterns/trpc-patterns.mdc +77 -0
- package/templates/chat-app/.claude/skills/typescript/typescript.mdc +53 -0
- package/templates/chat-app/.claude/skills/ultracite/ultracite.mdc +129 -0
- package/templates/chat-app/.cursor/skills/chat-context/SKILL.md +37 -0
- package/templates/chat-app/.cursor/skills/lazy-prefetch-pattern/SKILL.md +26 -0
- package/templates/chat-app/.cursor/skills/react/SKILL.md +28 -0
- package/templates/chat-app/.cursor/skills/trpc-patterns/SKILL.md +76 -0
- package/templates/chat-app/.cursor/skills/typescript/SKILL.md +52 -0
- package/templates/chat-app/.cursor/skills/ultracite/SKILL.md +128 -0
- package/templates/chat-app/app/(chat)/actions.ts +17 -13
- package/templates/chat-app/app/(chat)/api/chat/[id]/stream/route.ts +6 -5
- package/templates/chat-app/app/(chat)/api/chat/route.ts +14 -15
- package/templates/chat-app/app/(chat)/chat-providers.tsx +2 -2
- package/templates/chat-app/app/(chat)/layout.tsx +7 -6
- package/templates/chat-app/app/api/cron/cleanup/route.ts +4 -3
- package/templates/chat-app/app/globals.css +23 -23
- package/templates/chat-app/app/layout.tsx +1 -1
- package/templates/chat-app/biome.jsonc +3 -3
- package/templates/chat-app/chat.config.ts +48 -21
- package/templates/chat-app/components/anonymous-session-init.tsx +4 -12
- package/templates/chat-app/components/artifact-actions.tsx +5 -5
- package/templates/chat-app/components/artifact-panel.tsx +6 -6
- package/templates/chat-app/components/assistant-message.tsx +1 -1
- package/templates/chat-app/components/chat/chat-layout.tsx +2 -2
- package/templates/chat-app/components/chat/chat-welcome.tsx +1 -0
- package/templates/chat-app/components/chat-features-definitions.ts +11 -8
- package/templates/chat-app/components/chat-menu-items.tsx +4 -4
- package/templates/chat-app/components/chat-sync.tsx +1 -1
- package/templates/chat-app/components/clone-chat-button.tsx +2 -2
- package/templates/chat-app/components/code-editor.tsx +5 -5
- package/templates/chat-app/components/connectors-dropdown.tsx +2 -2
- package/templates/chat-app/components/console.tsx +5 -5
- package/templates/chat-app/components/create-artifact.tsx +28 -28
- package/templates/chat-app/components/data-stream-provider.tsx +2 -2
- package/templates/chat-app/components/deep-research-progress.tsx +2 -2
- package/templates/chat-app/components/delete-chat-dialog.tsx +3 -3
- package/templates/chat-app/components/delete-project-dialog.tsx +3 -3
- package/templates/chat-app/components/diffview.tsx +3 -3
- package/templates/chat-app/components/favicon-group.tsx +7 -7
- package/templates/chat-app/components/header-breadcrumb.tsx +11 -11
- package/templates/chat-app/components/image-editor.tsx +5 -5
- package/templates/chat-app/components/image-modal.tsx +4 -4
- package/templates/chat-app/components/interactive-chart-impl.tsx +269 -0
- package/templates/chat-app/components/interactive-charts.tsx +18 -246
- package/templates/chat-app/components/lexical-chat-input.tsx +10 -10
- package/templates/chat-app/components/message-editor.tsx +3 -3
- package/templates/chat-app/components/message-parts.tsx +8 -3
- package/templates/chat-app/components/messages-pane.tsx +4 -4
- package/templates/chat-app/components/messages.tsx +5 -5
- package/templates/chat-app/components/model-selector.tsx +4 -1
- package/templates/chat-app/components/multimodal-input.tsx +14 -5
- package/templates/chat-app/components/part/code-execution.tsx +4 -1
- package/templates/chat-app/components/part/document-common.tsx +8 -8
- package/templates/chat-app/components/part/document-preview.tsx +34 -16
- package/templates/chat-app/components/part/document-tool.tsx +3 -3
- package/templates/chat-app/components/part/dynamic-tool.tsx +3 -3
- package/templates/chat-app/components/part/generate-video.tsx +54 -0
- package/templates/chat-app/components/part/message-reasoning.tsx +3 -3
- package/templates/chat-app/components/project-details-dialog.tsx +4 -4
- package/templates/chat-app/components/project-home.tsx +1 -0
- package/templates/chat-app/components/project-icon-picker.tsx +5 -5
- package/templates/chat-app/components/project-icon.tsx +4 -4
- package/templates/chat-app/components/project-menu-items.tsx +3 -3
- package/templates/chat-app/components/research-tasks.tsx +3 -3
- package/templates/chat-app/components/sandbox.tsx +4 -4
- package/templates/chat-app/components/search-chats-dialog.tsx +11 -11
- package/templates/chat-app/components/settings/connectors-settings.tsx +1 -1
- package/templates/chat-app/components/settings/settings-nav.tsx +1 -1
- package/templates/chat-app/components/sheet-editor.tsx +5 -5
- package/templates/chat-app/components/sidebar-chats-list.tsx +5 -5
- package/templates/chat-app/components/suggested-actions.tsx +3 -3
- package/templates/chat-app/components/text-editor.tsx +5 -5
- package/templates/chat-app/components/toolbar.tsx +6 -6
- package/templates/chat-app/components/upgrade-cta/login-cta-banner.tsx +5 -5
- package/templates/chat-app/components/upgrade-cta/login-prompt.tsx +4 -4
- package/templates/chat-app/components/upgrade-cta/share-menu-item.tsx +3 -3
- package/templates/chat-app/components/user-message.tsx +3 -3
- package/templates/chat-app/components/version-footer.tsx +4 -4
- package/templates/chat-app/hooks/chat-sync-hooks.ts +0 -55
- package/templates/chat-app/hooks/use-artifact.tsx +3 -3
- package/templates/chat-app/hooks/use-auto-focus.ts +37 -7
- package/templates/chat-app/hooks/use-media-query.tsx +2 -4
- package/templates/chat-app/lib/ai/active-gateway.ts +1 -1
- package/templates/chat-app/lib/ai/ai-gateway-models-schemas.ts +30 -6
- package/templates/chat-app/lib/ai/app-model-id.ts +1 -1
- package/templates/chat-app/lib/ai/app-models.ts +4 -4
- package/templates/chat-app/lib/ai/eval-agent.ts +5 -5
- package/templates/chat-app/lib/ai/followup-suggestions.ts +5 -2
- package/templates/chat-app/lib/ai/gateway-model-defaults.ts +131 -41
- package/templates/chat-app/lib/ai/gateways/gateway-provider.ts +10 -6
- package/templates/chat-app/lib/ai/gateways/openai-compatible-gateway.ts +9 -4
- package/templates/chat-app/lib/ai/gateways/openai-gateway.ts +9 -4
- package/templates/chat-app/lib/ai/gateways/openrouter-gateway.ts +17 -12
- package/templates/chat-app/lib/ai/gateways/registry.ts +9 -0
- package/templates/chat-app/lib/ai/gateways/vercel-gateway.ts +36 -4
- package/templates/chat-app/lib/ai/mcp/cache.ts +13 -13
- package/templates/chat-app/lib/ai/model-data.ts +21 -20
- package/templates/chat-app/lib/ai/models.generated.ts +4397 -3592
- package/templates/chat-app/lib/ai/models.ts +1 -1
- package/templates/chat-app/lib/ai/providers.ts +10 -0
- package/templates/chat-app/lib/ai/text-splitter.ts +3 -4
- package/templates/chat-app/lib/ai/to-model-data.ts +1 -0
- package/templates/chat-app/lib/ai/tools/code-execution.ts +122 -53
- package/templates/chat-app/lib/ai/tools/deep-research/configuration.ts +35 -32
- package/templates/chat-app/lib/ai/tools/deep-research/pipeline.ts +2 -2
- package/templates/chat-app/lib/ai/tools/deep-research/types.ts +9 -9
- package/templates/chat-app/lib/ai/tools/documents/types.ts +4 -4
- package/templates/chat-app/lib/ai/tools/generate-image.ts +42 -20
- package/templates/chat-app/lib/ai/tools/generate-video.ts +166 -0
- package/templates/chat-app/lib/ai/tools/get-weather.ts +20 -20
- package/templates/chat-app/lib/ai/tools/read-document.ts +3 -3
- package/templates/chat-app/lib/ai/tools/steps/multi-query-web-search.ts +11 -11
- package/templates/chat-app/lib/ai/tools/steps/web-search.ts +6 -6
- package/templates/chat-app/lib/ai/tools/tools-definitions.ts +10 -5
- package/templates/chat-app/lib/ai/tools/tools.ts +15 -6
- package/templates/chat-app/lib/ai/tools/types.ts +2 -2
- package/templates/chat-app/lib/ai/types.ts +22 -13
- package/templates/chat-app/lib/artifacts/code/client.tsx +5 -5
- package/templates/chat-app/lib/artifacts/sheet/client.tsx +2 -2
- package/templates/chat-app/lib/artifacts/text/client.tsx +18 -3
- package/templates/chat-app/lib/clone-messages.test.ts +6 -1
- package/templates/chat-app/lib/config-requirements.ts +19 -10
- package/templates/chat-app/lib/config-schema.ts +189 -103
- package/templates/chat-app/lib/config.ts +4 -4
- package/templates/chat-app/lib/credits/cost-accumulator.ts +11 -8
- package/templates/chat-app/lib/env-schema.ts +1 -1
- package/templates/chat-app/lib/features-config.ts +6 -6
- package/templates/chat-app/lib/stores/with-threads.ts +3 -3
- package/templates/chat-app/lib/thread-utils.ts +2 -2
- package/templates/chat-app/lib/types/anonymous.ts +4 -4
- package/templates/chat-app/lib/types/ui-chat.ts +7 -7
- package/templates/chat-app/lib/utils/download-assets.ts +3 -3
- package/templates/chat-app/lib/utils/rate-limit.ts +8 -8
- package/templates/chat-app/next.config.ts +0 -25
- package/templates/chat-app/package.json +16 -16
- package/templates/chat-app/playwright.config.ts +5 -5
- package/templates/chat-app/providers/chat-id-provider.tsx +5 -5
- package/templates/chat-app/providers/chat-input-provider.tsx +15 -15
- package/templates/chat-app/providers/chat-models-provider.tsx +3 -3
- package/templates/chat-app/providers/default-model-provider.tsx +5 -5
- package/templates/chat-app/providers/parse-chat-id-from-pathname.test.ts +16 -0
- package/templates/chat-app/providers/session-provider.tsx +2 -2
- package/templates/chat-app/scripts/check-env.ts +36 -4
- package/templates/chat-app/tests/artifacts.e2e.ts +7 -0
- package/templates/chat-app/tests/auth.setup.e2e.ts +10 -0
- package/templates/chat-app/tests/chat.e2e.ts +7 -0
- package/templates/chat-app/tests/reasoning.e2e.ts +7 -0
- package/templates/chat-app/tests/reasoning.setup.e2e.ts +10 -0
- package/templates/chat-app/trpc/routers/chat.router.ts +1 -1
- package/templates/chat-app/trpc/routers/mcp.router.ts +3 -3
- package/templates/chat-app/vitest.config.ts +7 -0
- package/templates/chat-app/next-env.d.ts +0 -6
- package/templates/chat-app/tsconfig.tsbuildinfo +0 -1
|
@@ -34,7 +34,7 @@ export const fetchModels = unstable_cache(
|
|
|
34
34
|
const models = await fetchModelsRaw();
|
|
35
35
|
return models.map(toModelData);
|
|
36
36
|
},
|
|
37
|
-
[`ai-gateway-models-${config.
|
|
37
|
+
[`ai-gateway-models-${config.ai.gateway}`],
|
|
38
38
|
{
|
|
39
39
|
revalidate: 3600,
|
|
40
40
|
tags: ["ai-gateway-models"],
|
|
@@ -51,6 +51,16 @@ export const getImageModel = (modelId: string) => {
|
|
|
51
51
|
return imageModel;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
export const getVideoModel = (modelId: string) => {
|
|
55
|
+
const videoModel = getActiveGateway().createVideoModel(modelId);
|
|
56
|
+
if (!videoModel) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Gateway '${getActiveGateway().type}' does not support video models.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return videoModel;
|
|
62
|
+
};
|
|
63
|
+
|
|
54
64
|
// Get a multimodal language model that can generate images via generateText
|
|
55
65
|
export const getMultimodalImageModel = (modelId: string) =>
|
|
56
66
|
getActiveGateway().createLanguageModel(modelId);
|
|
@@ -7,6 +7,7 @@ import { createModuleLogger } from "@/lib/logger";
|
|
|
7
7
|
import { toolsDefinitions } from "./tools-definitions";
|
|
8
8
|
|
|
9
9
|
const WHITESPACE_REGEX = /\s+/;
|
|
10
|
+
const PACKAGE_SPEC_SPLIT_RE = /[=<>![\s]/;
|
|
10
11
|
|
|
11
12
|
async function installBasePackages(
|
|
12
13
|
sandbox: Sandbox,
|
|
@@ -39,8 +40,13 @@ async function installBasePackages(
|
|
|
39
40
|
return { success: true };
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function packageName(spec: string): string {
|
|
44
|
+
return spec.split(PACKAGE_SPEC_SPLIT_RE)[0].toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
async function processExtraPackages(
|
|
43
48
|
code: string,
|
|
49
|
+
basePackages: readonly string[],
|
|
44
50
|
sandbox: Sandbox,
|
|
45
51
|
log: ReturnType<typeof createModuleLogger>,
|
|
46
52
|
requestId: string
|
|
@@ -51,43 +57,51 @@ async function processExtraPackages(
|
|
|
51
57
|
result?: { message: string; chart: string };
|
|
52
58
|
};
|
|
53
59
|
}> {
|
|
60
|
+
const basePackageNames = new Set(basePackages.map((p) => p.toLowerCase()));
|
|
54
61
|
const lines = code.split("\n");
|
|
55
62
|
const pipLines = lines.filter((l) => l.trim().startsWith("!pip install "));
|
|
56
|
-
const extraPackages = pipLines
|
|
57
|
-
l
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
const extraPackages = pipLines
|
|
64
|
+
.flatMap((l) =>
|
|
65
|
+
l
|
|
66
|
+
.trim()
|
|
67
|
+
.slice("!pip install ".length)
|
|
68
|
+
.split(WHITESPACE_REGEX)
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
)
|
|
71
|
+
.filter((spec) => !basePackageNames.has(packageName(spec)));
|
|
72
|
+
|
|
73
|
+
const codeWithoutPipLines = lines
|
|
74
|
+
.filter((l) => !l.trim().startsWith("!pip install "))
|
|
75
|
+
.join("\n");
|
|
76
|
+
|
|
77
|
+
if (extraPackages.length === 0) {
|
|
78
|
+
return { codeToRun: codeWithoutPipLines, installResult: { success: true } };
|
|
79
|
+
}
|
|
63
80
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
message: `Failed to install packages: ${stderr}`,
|
|
80
|
-
chart: "",
|
|
81
|
-
},
|
|
81
|
+
log.info({ requestId, extraPackages }, "installing extra packages");
|
|
82
|
+
const dynamicInstall = await sandbox.runCommand({
|
|
83
|
+
cmd: "pip",
|
|
84
|
+
args: ["install", ...extraPackages],
|
|
85
|
+
});
|
|
86
|
+
if (dynamicInstall.exitCode !== 0) {
|
|
87
|
+
const stderr = await dynamicInstall.stderr();
|
|
88
|
+
log.error({ requestId, stderr }, "dynamic package installation failed");
|
|
89
|
+
return {
|
|
90
|
+
codeToRun: code,
|
|
91
|
+
installResult: {
|
|
92
|
+
success: false,
|
|
93
|
+
result: {
|
|
94
|
+
message: `Failed to install packages: ${stderr}`,
|
|
95
|
+
chart: "",
|
|
82
96
|
},
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
codeToRun = lines
|
|
86
|
-
.filter((l) => !l.trim().startsWith("!pip install "))
|
|
87
|
-
.join("\n");
|
|
97
|
+
},
|
|
98
|
+
};
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
codeToRun: codeWithoutPipLines,
|
|
103
|
+
installResult: { success: true },
|
|
104
|
+
};
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
function createWrappedCode(codeToRun: string, chartPath: string): string {
|
|
@@ -96,19 +110,36 @@ import sys
|
|
|
96
110
|
import json
|
|
97
111
|
import traceback
|
|
98
112
|
|
|
113
|
+
try:
|
|
114
|
+
import matplotlib.pyplot as _plt_module
|
|
115
|
+
_orig_savefig = _plt_module.savefig
|
|
116
|
+
def _intercepted_savefig(*args, **kwargs):
|
|
117
|
+
_orig_savefig('${chartPath}', format='png', bbox_inches='tight', dpi=100)
|
|
118
|
+
if args or kwargs.get('fname') not in (None, '${chartPath}'):
|
|
119
|
+
return _orig_savefig(*args, **kwargs)
|
|
120
|
+
_plt_module.savefig = _intercepted_savefig
|
|
121
|
+
except ImportError:
|
|
122
|
+
pass
|
|
123
|
+
|
|
99
124
|
try:
|
|
100
125
|
exec(${JSON.stringify(codeToRun)})
|
|
101
126
|
try:
|
|
102
127
|
_locals = locals()
|
|
103
128
|
_globals = globals()
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
_chart_var = _locals.get("chart") or _globals.get("chart")
|
|
130
|
+
if (isinstance(_chart_var, dict)
|
|
131
|
+
and isinstance(_chart_var.get("type"), str)
|
|
132
|
+
and isinstance(_chart_var.get("elements"), list)):
|
|
133
|
+
print("__CHART_JSON__:" + json.dumps(_chart_var))
|
|
134
|
+
else:
|
|
135
|
+
if "result" in _locals:
|
|
136
|
+
print(_locals["result"])
|
|
137
|
+
elif "result" in _globals:
|
|
138
|
+
print(_globals["result"])
|
|
139
|
+
elif "results" in _locals:
|
|
140
|
+
print(_locals["results"])
|
|
141
|
+
elif "results" in _globals:
|
|
142
|
+
print(_globals["results"])
|
|
112
143
|
except Exception:
|
|
113
144
|
pass
|
|
114
145
|
try:
|
|
@@ -126,11 +157,14 @@ except Exception as e:
|
|
|
126
157
|
`;
|
|
127
158
|
}
|
|
128
159
|
|
|
160
|
+
const CHART_JSON_PREFIX = "__CHART_JSON__:";
|
|
161
|
+
|
|
129
162
|
async function parseExecutionOutput(execResult: {
|
|
130
163
|
stdout: () => Promise<string>;
|
|
131
164
|
exitCode: number;
|
|
132
165
|
}): Promise<{
|
|
133
166
|
outputText: string;
|
|
167
|
+
chartData: Record<string, unknown> | null;
|
|
134
168
|
execInfo: {
|
|
135
169
|
success: boolean;
|
|
136
170
|
error?: { name: string; value: string; traceback: string };
|
|
@@ -142,18 +176,33 @@ async function parseExecutionOutput(execResult: {
|
|
|
142
176
|
error?: { name: string; value: string; traceback: string };
|
|
143
177
|
} = { success: true };
|
|
144
178
|
let outputText = "";
|
|
179
|
+
let chartData: Record<string, unknown> | null = null;
|
|
145
180
|
|
|
146
181
|
try {
|
|
147
182
|
const outLines = (stdout ?? "").trim().split("\n");
|
|
148
183
|
const lastLine = outLines.at(-1);
|
|
149
184
|
execInfo = JSON.parse(lastLine ?? "{}");
|
|
150
185
|
outLines.pop();
|
|
186
|
+
|
|
187
|
+
const chartLineIdx = outLines.findIndex((l) =>
|
|
188
|
+
l.startsWith(CHART_JSON_PREFIX)
|
|
189
|
+
);
|
|
190
|
+
if (chartLineIdx !== -1) {
|
|
191
|
+
const raw = outLines[chartLineIdx].slice(CHART_JSON_PREFIX.length);
|
|
192
|
+
try {
|
|
193
|
+
chartData = JSON.parse(raw) as Record<string, unknown>;
|
|
194
|
+
} catch {
|
|
195
|
+
// ignore malformed chart JSON
|
|
196
|
+
}
|
|
197
|
+
outLines.splice(chartLineIdx, 1);
|
|
198
|
+
}
|
|
199
|
+
|
|
151
200
|
outputText = outLines.join("\n");
|
|
152
201
|
} catch {
|
|
153
202
|
outputText = stdout ?? "";
|
|
154
203
|
}
|
|
155
204
|
|
|
156
|
-
return { outputText, execInfo };
|
|
205
|
+
return { outputText, chartData, execInfo };
|
|
157
206
|
}
|
|
158
207
|
|
|
159
208
|
async function checkForChart(
|
|
@@ -248,7 +297,7 @@ async function executeInSandbox({
|
|
|
248
297
|
requestId: string;
|
|
249
298
|
}): Promise<{
|
|
250
299
|
message: string;
|
|
251
|
-
chart: string | { base64: string; format: string }
|
|
300
|
+
chart: string | { base64: string; format: string } | Record<string, unknown>;
|
|
252
301
|
}> {
|
|
253
302
|
const baseInstallResult = await installBasePackages(
|
|
254
303
|
sandbox,
|
|
@@ -262,6 +311,7 @@ async function executeInSandbox({
|
|
|
262
311
|
|
|
263
312
|
const { codeToRun, installResult } = await processExtraPackages(
|
|
264
313
|
code,
|
|
314
|
+
basePackages,
|
|
265
315
|
sandbox,
|
|
266
316
|
log,
|
|
267
317
|
requestId
|
|
@@ -276,8 +326,8 @@ async function executeInSandbox({
|
|
|
276
326
|
args: ["-c", wrappedCode],
|
|
277
327
|
});
|
|
278
328
|
|
|
279
|
-
const { outputText, execInfo } =
|
|
280
|
-
|
|
329
|
+
const { outputText, chartData, execInfo } =
|
|
330
|
+
await parseExecutionOutput(execResult);
|
|
281
331
|
|
|
282
332
|
const message = buildResponseMessage({
|
|
283
333
|
outputText,
|
|
@@ -287,6 +337,12 @@ async function executeInSandbox({
|
|
|
287
337
|
requestId,
|
|
288
338
|
});
|
|
289
339
|
|
|
340
|
+
if (chartData) {
|
|
341
|
+
log.info({ requestId }, "interactive chart data returned");
|
|
342
|
+
return { message: message.trim(), chart: chartData };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const chartOut = await checkForChart(sandbox, chartPath, log, requestId);
|
|
290
346
|
return {
|
|
291
347
|
message: message.trim(),
|
|
292
348
|
chart: chartOut ?? "",
|
|
@@ -319,23 +375,36 @@ export const codeExecution = ({
|
|
|
319
375
|
costAccumulator?: CostAccumulator;
|
|
320
376
|
}) =>
|
|
321
377
|
tool({
|
|
322
|
-
description: `Python-only sandbox for calculations, data analysis &
|
|
378
|
+
description: `Python-only sandbox for calculations, data analysis & visualisations.
|
|
323
379
|
|
|
324
380
|
Use for:
|
|
325
|
-
- Execute Python (matplotlib, pandas, numpy, sympy, yfinance pre-installed)
|
|
326
|
-
- Produce line / scatter / bar charts
|
|
327
|
-
- Install extra libs by adding lines like: '!pip install <pkg> [<pkg2> ...]' (we auto-install and strip these lines)
|
|
381
|
+
- Execute Python (matplotlib, pandas, numpy, sympy, yfinance pre-installed — do NOT reinstall them)
|
|
382
|
+
- Produce interactive line / scatter / bar charts OR matplotlib PNG charts
|
|
383
|
+
- Install extra libs by adding lines like: '!pip install <pkg> [<pkg2> ...]' (we auto-install and strip these lines; pre-installed packages are ignored)
|
|
384
|
+
|
|
385
|
+
Chart output — choose ONE:
|
|
386
|
+
1. Interactive chart (preferred for line/scatter/bar): assign a 'chart' variable matching this schema:
|
|
387
|
+
chart = {
|
|
388
|
+
"type": "line" | "scatter" | "bar",
|
|
389
|
+
"title": "My Chart",
|
|
390
|
+
"x_label": "X", # optional
|
|
391
|
+
"y_label": "Y", # optional
|
|
392
|
+
"x_scale": "datetime" | None, # optional, for time-series x axes
|
|
393
|
+
"elements": [
|
|
394
|
+
# for line/scatter: {"label": "Series A", "points": [[x1,y1],[x2,y2],...]}
|
|
395
|
+
# for bar: {"label": "Category", "group": "Group A", "value": 42}
|
|
396
|
+
]
|
|
397
|
+
}
|
|
398
|
+
2. Matplotlib PNG: use plt.plot()/plt.savefig() normally (no need to call plt.show())
|
|
328
399
|
|
|
329
400
|
Restrictions:
|
|
330
401
|
- No images in the assistant response; don't embed them
|
|
331
|
-
|
|
332
|
-
Avoid:
|
|
333
|
-
- Any non-Python language
|
|
334
|
-
- Chart types other than line / scatter / bar
|
|
402
|
+
- Interactive chart: only line / scatter / bar types
|
|
335
403
|
|
|
336
404
|
Output rules:
|
|
337
|
-
-
|
|
338
|
-
-
|
|
405
|
+
- Assign 'chart' dict for interactive charts (takes priority over matplotlib PNG)
|
|
406
|
+
- Assign 'result' or 'results' for other computed values (auto-printed)
|
|
407
|
+
- Or print explicitly: print(answer)
|
|
339
408
|
- Don't rely on implicit REPL last-expression output`,
|
|
340
409
|
inputSchema: z.object({
|
|
341
410
|
title: z.string().describe("The title of the code snippet."),
|
|
@@ -3,28 +3,16 @@ import { env } from "@/lib/env";
|
|
|
3
3
|
|
|
4
4
|
export type SearchAPI = "firecrawl" | "tavily" | "none";
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
// General Configuration
|
|
8
|
-
max_structured_output_retries: number;
|
|
6
|
+
export interface DeepResearchRuntimeConfig {
|
|
9
7
|
allow_clarification: boolean;
|
|
10
|
-
max_concurrent_research_units: number;
|
|
11
|
-
|
|
12
|
-
// Research Configuration
|
|
13
|
-
search_api: SearchAPI;
|
|
14
|
-
search_api_max_queries: number;
|
|
15
|
-
max_researcher_iterations: number;
|
|
16
|
-
|
|
17
|
-
// Model Configuration
|
|
18
|
-
summarization_model: string;
|
|
19
|
-
summarization_model_max_tokens: number;
|
|
20
|
-
research_model: string;
|
|
21
|
-
research_model_max_tokens: number;
|
|
22
8
|
compression_model: string;
|
|
23
9
|
compression_model_max_tokens: number;
|
|
24
10
|
final_report_model: string;
|
|
25
11
|
final_report_model_max_tokens: number;
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
max_concurrent_research_units: number;
|
|
13
|
+
max_researcher_iterations: number;
|
|
14
|
+
// General Configuration
|
|
15
|
+
max_structured_output_retries: number;
|
|
28
16
|
|
|
29
17
|
// MCP server configuration (not yet implemented)
|
|
30
18
|
mcp_config?: {
|
|
@@ -33,7 +21,19 @@ export type DeepResearchRuntimeConfig = {
|
|
|
33
21
|
headers?: Record<string, string>;
|
|
34
22
|
};
|
|
35
23
|
mcp_prompt?: string;
|
|
36
|
-
|
|
24
|
+
research_model: string;
|
|
25
|
+
research_model_max_tokens: number;
|
|
26
|
+
|
|
27
|
+
// Research Configuration
|
|
28
|
+
search_api: SearchAPI;
|
|
29
|
+
search_api_max_queries: number;
|
|
30
|
+
status_update_model: string;
|
|
31
|
+
status_update_model_max_tokens: number;
|
|
32
|
+
|
|
33
|
+
// Model Configuration
|
|
34
|
+
summarization_model: string;
|
|
35
|
+
summarization_model_max_tokens: number;
|
|
36
|
+
}
|
|
37
37
|
|
|
38
38
|
function getSearchApi(): SearchAPI {
|
|
39
39
|
if (env.TAVILY_API_KEY) {
|
|
@@ -46,33 +46,36 @@ function getSearchApi(): SearchAPI {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export function getDeepResearchConfig(): DeepResearchRuntimeConfig {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const {
|
|
50
|
+
defaultModel,
|
|
51
|
+
finalReportModel,
|
|
52
|
+
allowClarification,
|
|
53
|
+
maxConcurrentResearchUnits,
|
|
54
|
+
maxSearchQueries,
|
|
55
|
+
maxResearcherIterations,
|
|
56
|
+
} = config.ai.tools.deepResearch;
|
|
53
57
|
|
|
54
58
|
return {
|
|
55
59
|
// General Configuration
|
|
56
60
|
max_structured_output_retries: 3,
|
|
57
|
-
allow_clarification:
|
|
58
|
-
max_concurrent_research_units:
|
|
59
|
-
deepResearchSettings.maxConcurrentResearchUnits,
|
|
61
|
+
allow_clarification: allowClarification,
|
|
62
|
+
max_concurrent_research_units: maxConcurrentResearchUnits,
|
|
60
63
|
|
|
61
64
|
// Research Configuration
|
|
62
65
|
search_api: getSearchApi(),
|
|
63
|
-
search_api_max_queries:
|
|
64
|
-
max_researcher_iterations:
|
|
66
|
+
search_api_max_queries: maxSearchQueries,
|
|
67
|
+
max_researcher_iterations: maxResearcherIterations,
|
|
65
68
|
|
|
66
69
|
// Model Configuration - use same model for research/compression/summarization
|
|
67
|
-
summarization_model:
|
|
70
|
+
summarization_model: defaultModel,
|
|
68
71
|
summarization_model_max_tokens: 4000,
|
|
69
|
-
research_model:
|
|
72
|
+
research_model: defaultModel,
|
|
70
73
|
research_model_max_tokens: 4000,
|
|
71
|
-
compression_model:
|
|
74
|
+
compression_model: defaultModel,
|
|
72
75
|
compression_model_max_tokens: 4000,
|
|
73
|
-
final_report_model:
|
|
76
|
+
final_report_model: finalReportModel,
|
|
74
77
|
final_report_model_max_tokens: 6000,
|
|
75
|
-
status_update_model:
|
|
78
|
+
status_update_model: defaultModel,
|
|
76
79
|
status_update_model_max_tokens: 4000,
|
|
77
80
|
};
|
|
78
81
|
}
|
|
@@ -163,10 +163,10 @@ async function clarifyWithUser(
|
|
|
163
163
|
|
|
164
164
|
// Step 2: Research Brief
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
interface ResearchBrief {
|
|
167
167
|
research_brief: string;
|
|
168
168
|
title: string;
|
|
169
|
-
}
|
|
169
|
+
}
|
|
170
170
|
|
|
171
171
|
async function writeResearchBrief(
|
|
172
172
|
messages: ModelMessage[],
|
|
@@ -9,15 +9,15 @@ import type { DeepResearchRuntimeConfig } from "./configuration";
|
|
|
9
9
|
// Shared Agent Options
|
|
10
10
|
//##################
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export interface AgentOptions {
|
|
13
|
+
abortSignal?: AbortSignal;
|
|
13
14
|
config: DeepResearchRuntimeConfig;
|
|
15
|
+
costAccumulator?: CostAccumulator;
|
|
14
16
|
dataStream: StreamWriter;
|
|
15
|
-
toolCallId: string;
|
|
16
17
|
messageId: string;
|
|
17
18
|
requestId: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
19
|
+
toolCallId: string;
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
//##################
|
|
23
23
|
// Telemetry Helper
|
|
@@ -67,12 +67,12 @@ export const ResearchQuestionSchema = z.object({
|
|
|
67
67
|
// Pipeline IO
|
|
68
68
|
//##################
|
|
69
69
|
|
|
70
|
-
export
|
|
71
|
-
requestId: string;
|
|
70
|
+
export interface DeepResearchInput {
|
|
72
71
|
messageId: string;
|
|
73
|
-
toolCallId: string;
|
|
74
72
|
messages: ModelMessage[];
|
|
75
|
-
|
|
73
|
+
requestId: string;
|
|
74
|
+
toolCallId: string;
|
|
75
|
+
}
|
|
76
76
|
|
|
77
77
|
export type DeepResearchResult =
|
|
78
78
|
| {
|
|
@@ -15,13 +15,13 @@ export type DocumentToolResult =
|
|
|
15
15
|
error: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
18
|
+
export interface DocumentToolContext {
|
|
19
|
+
costAccumulator?: CostAccumulator;
|
|
20
20
|
// dataStream: StreamWriter;
|
|
21
21
|
messageId: string;
|
|
22
22
|
selectedModel: ModelId;
|
|
23
|
-
|
|
24
|
-
}
|
|
23
|
+
session: ToolSession;
|
|
24
|
+
}
|
|
25
25
|
|
|
26
26
|
// Document tool type names as they appear in ChatMessage parts
|
|
27
27
|
export const createDocumentToolTypes = [
|
|
@@ -6,14 +6,13 @@ import { uploadFile } from "@/lib/blob";
|
|
|
6
6
|
import { config } from "@/lib/config";
|
|
7
7
|
import type { CostAccumulator } from "@/lib/credits/cost-accumulator";
|
|
8
8
|
import { createModuleLogger } from "@/lib/logger";
|
|
9
|
-
import { toolsDefinitions } from "./tools-definitions";
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
interface GenerateImageProps {
|
|
12
11
|
attachments?: FileUIPart[];
|
|
12
|
+
costAccumulator?: CostAccumulator;
|
|
13
13
|
lastGeneratedImage?: { imageUrl: string; name: string } | null;
|
|
14
14
|
selectedModel?: string;
|
|
15
|
-
|
|
16
|
-
};
|
|
15
|
+
}
|
|
17
16
|
|
|
18
17
|
const log = createModuleLogger("ai.tools.generate-image");
|
|
19
18
|
|
|
@@ -42,7 +41,7 @@ async function resolveImageModel(selectedModel?: string): Promise<{
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
// Fall back to the configured default image model
|
|
45
|
-
const defaultId = config.
|
|
44
|
+
const defaultId = config.ai.tools.image.default;
|
|
46
45
|
try {
|
|
47
46
|
const model = await getAppModelDefinition(defaultId as AppModelId);
|
|
48
47
|
// Default could be a multimodal language model (e.g. gemini-3-pro-image)
|
|
@@ -126,12 +125,14 @@ async function runGenerateImageTraditional({
|
|
|
126
125
|
imageParts,
|
|
127
126
|
lastGeneratedImage,
|
|
128
127
|
startMs,
|
|
128
|
+
costAccumulator,
|
|
129
129
|
}: {
|
|
130
130
|
mode: ImageMode;
|
|
131
131
|
prompt: string;
|
|
132
132
|
imageParts: FileUIPart[];
|
|
133
133
|
lastGeneratedImage: { imageUrl: string; name: string } | null;
|
|
134
134
|
startMs: number;
|
|
135
|
+
costAccumulator?: CostAccumulator;
|
|
135
136
|
}): Promise<{ imageUrl: string; prompt: string }> {
|
|
136
137
|
let promptInput:
|
|
137
138
|
| string
|
|
@@ -160,7 +161,7 @@ async function runGenerateImageTraditional({
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
const res = await generateImage({
|
|
163
|
-
model: getImageModel(config.
|
|
164
|
+
model: getImageModel(config.ai.tools.image.default),
|
|
164
165
|
prompt: promptInput,
|
|
165
166
|
n: 1,
|
|
166
167
|
providerOptions: {
|
|
@@ -181,6 +182,17 @@ async function runGenerateImageTraditional({
|
|
|
181
182
|
const filename = `generated-image-${timestamp}.png`;
|
|
182
183
|
const result = await uploadFile(filename, buffer);
|
|
183
184
|
|
|
185
|
+
if (res.usage) {
|
|
186
|
+
costAccumulator?.addLLMCost(
|
|
187
|
+
config.ai.tools.image.default as AppModelId,
|
|
188
|
+
{
|
|
189
|
+
inputTokens: res.usage.inputTokens,
|
|
190
|
+
outputTokens: res.usage.outputTokens,
|
|
191
|
+
},
|
|
192
|
+
"generateImage-traditional"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
184
196
|
log.info(
|
|
185
197
|
{
|
|
186
198
|
mode,
|
|
@@ -212,8 +224,14 @@ async function runGenerateImageMultimodal({
|
|
|
212
224
|
costAccumulator?: CostAccumulator;
|
|
213
225
|
}): Promise<{ imageUrl: string; prompt: string }> {
|
|
214
226
|
// Build messages with image context if in edit mode
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
interface ImageContent {
|
|
228
|
+
image: URL;
|
|
229
|
+
type: "image";
|
|
230
|
+
}
|
|
231
|
+
interface TextContent {
|
|
232
|
+
text: string;
|
|
233
|
+
type: "text";
|
|
234
|
+
}
|
|
217
235
|
const userContent: Array<TextContent | ImageContent> = [];
|
|
218
236
|
|
|
219
237
|
// Add reference images if in edit mode
|
|
@@ -247,13 +265,24 @@ async function runGenerateImageMultimodal({
|
|
|
247
265
|
"generateImage: using multimodal model"
|
|
248
266
|
);
|
|
249
267
|
|
|
268
|
+
const isGoogleModel =
|
|
269
|
+
modelId.startsWith("google/") || modelId.includes("gemini");
|
|
270
|
+
const isOpenAIModel = modelId.startsWith("openai/");
|
|
271
|
+
|
|
250
272
|
const res = await generateText({
|
|
251
273
|
model: getMultimodalImageModel(modelId),
|
|
252
274
|
messages: [{ role: "user", content: userContent }],
|
|
253
275
|
providerOptions: {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
...(isGoogleModel && {
|
|
277
|
+
google: {
|
|
278
|
+
responseModalities: ["TEXT", "IMAGE"],
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
...(isOpenAIModel && {
|
|
282
|
+
openai: {
|
|
283
|
+
modalities: ["text", "image"],
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
257
286
|
},
|
|
258
287
|
});
|
|
259
288
|
|
|
@@ -362,21 +391,14 @@ The assistant must not add new subjects, claims, branding, or alter the tone or
|
|
|
362
391
|
}
|
|
363
392
|
|
|
364
393
|
// Traditional image generation for dedicated image models
|
|
365
|
-
|
|
394
|
+
return await runGenerateImageTraditional({
|
|
366
395
|
mode,
|
|
367
396
|
prompt,
|
|
368
397
|
imageParts,
|
|
369
398
|
lastGeneratedImage,
|
|
370
399
|
startMs,
|
|
400
|
+
costAccumulator,
|
|
371
401
|
});
|
|
372
|
-
|
|
373
|
-
// Report API cost for traditional image generation
|
|
374
|
-
costAccumulator?.addAPICost(
|
|
375
|
-
"generateImage",
|
|
376
|
-
toolsDefinitions.generateImage.cost
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
return result;
|
|
380
402
|
} catch (error) {
|
|
381
403
|
const resolvedError = await resolveError(error);
|
|
382
404
|
log.error(
|