@chat-js/cli 0.3.0 → 0.6.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.
- package/dist/index.js +1173 -964
- package/package.json +1 -1
- package/templates/chat-app/app/(auth)/device-login/page.tsx +37 -0
- package/templates/chat-app/app/(auth)/login/page.tsx +26 -2
- package/templates/chat-app/app/(auth)/register/page.tsx +0 -12
- package/templates/chat-app/app/(chat)/api/chat/filter-reasoning-parts.ts +1 -1
- package/templates/chat-app/app/(chat)/api/chat/prepare/route.ts +94 -0
- package/templates/chat-app/app/(chat)/api/chat/route.ts +107 -16
- package/templates/chat-app/app/(chat)/layout.tsx +4 -1
- package/templates/chat-app/app/api/trpc/[trpc]/route.ts +1 -0
- package/templates/chat-app/app/globals.css +9 -9
- package/templates/chat-app/app/layout.tsx +4 -2
- package/templates/chat-app/biome.jsonc +3 -3
- package/templates/chat-app/chat.config.ts +32 -12
- package/templates/chat-app/components/ai-elements/prompt-input.tsx +1 -1
- package/templates/chat-app/components/anonymous-session-init.tsx +10 -6
- package/templates/chat-app/components/artifact-actions.tsx +81 -18
- package/templates/chat-app/components/artifact-panel.tsx +142 -41
- package/templates/chat-app/components/attachment-list.tsx +1 -1
- package/templates/chat-app/components/{social-auth-providers.tsx → auth-providers.tsx} +49 -4
- package/templates/chat-app/components/chat/chat-welcome.tsx +3 -3
- package/templates/chat-app/components/chat-menu-items.tsx +1 -1
- package/templates/chat-app/components/chat-sync.tsx +9 -11
- package/templates/chat-app/components/console.tsx +9 -9
- package/templates/chat-app/components/context-usage.tsx +2 -2
- package/templates/chat-app/components/create-artifact.tsx +15 -5
- package/templates/chat-app/components/data-stream-handler.tsx +57 -16
- package/templates/chat-app/components/device-login-page.tsx +191 -0
- package/templates/chat-app/components/diffview.tsx +8 -2
- package/templates/chat-app/components/electron-auth-handler.tsx +184 -0
- package/templates/chat-app/components/electron-auth-ui.tsx +121 -0
- package/templates/chat-app/components/favicon-group.tsx +1 -1
- package/templates/chat-app/components/feedback-actions.tsx +7 -3
- package/templates/chat-app/components/greeting.tsx +1 -1
- package/templates/chat-app/components/interactive-chart-impl.tsx +3 -4
- package/templates/chat-app/components/interactive-charts.tsx +1 -1
- package/templates/chat-app/components/login-form.tsx +52 -10
- package/templates/chat-app/components/message-editor.tsx +7 -3
- package/templates/chat-app/components/message-siblings.tsx +14 -1
- package/templates/chat-app/components/model-selector.tsx +295 -27
- package/templates/chat-app/components/multimodal-input.tsx +259 -22
- package/templates/chat-app/components/parallel-response-cards.tsx +175 -0
- package/templates/chat-app/components/part/code-execution.tsx +8 -2
- package/templates/chat-app/components/part/document-common.tsx +1 -1
- package/templates/chat-app/components/part/document-preview.tsx +5 -5
- package/templates/chat-app/components/part/retrieve-url.tsx +12 -12
- package/templates/chat-app/components/part/text-message-part.tsx +9 -1
- package/templates/chat-app/components/project-chat-item.tsx +1 -1
- package/templates/chat-app/components/project-menu-items.tsx +1 -1
- package/templates/chat-app/components/research-task.tsx +1 -1
- package/templates/chat-app/components/research-tasks.tsx +1 -1
- package/templates/chat-app/components/retry-button.tsx +25 -8
- package/templates/chat-app/components/sandbox.tsx +1 -1
- package/templates/chat-app/components/sheet-editor.tsx +7 -7
- package/templates/chat-app/components/sidebar-chats-list.tsx +1 -1
- package/templates/chat-app/components/sidebar-toggle.tsx +15 -2
- package/templates/chat-app/components/sidebar-top-row.tsx +27 -12
- package/templates/chat-app/components/sidebar-user-nav.tsx +10 -1
- package/templates/chat-app/components/signup-form.tsx +49 -10
- package/templates/chat-app/components/sources.tsx +4 -4
- package/templates/chat-app/components/text-editor.tsx +5 -2
- package/templates/chat-app/components/toolbar.tsx +3 -3
- package/templates/chat-app/components/ui/sidebar.tsx +0 -1
- package/templates/chat-app/components/upgrade-cta/limit-display.tsx +1 -1
- package/templates/chat-app/components/user-message.tsx +14 -2
- package/templates/chat-app/electron.d.ts +41 -0
- package/templates/chat-app/evals/my-eval.eval.ts +3 -1
- package/templates/chat-app/hooks/chat-sync-hooks.ts +11 -0
- package/templates/chat-app/hooks/use-artifact.tsx +13 -13
- package/templates/chat-app/hooks/use-navigate-to-message.ts +39 -0
- package/templates/chat-app/lib/ai/gateways/provider-types.ts +19 -10
- package/templates/chat-app/lib/ai/stream-errors.test.ts +72 -0
- package/templates/chat-app/lib/ai/stream-errors.ts +94 -0
- package/templates/chat-app/lib/ai/tools/code-execution.javascript.ts +171 -0
- package/templates/chat-app/lib/ai/tools/code-execution.python.ts +336 -0
- package/templates/chat-app/lib/ai/tools/code-execution.shared.test.ts +71 -0
- package/templates/chat-app/lib/ai/tools/code-execution.shared.ts +59 -0
- package/templates/chat-app/lib/ai/tools/code-execution.ts +62 -391
- package/templates/chat-app/lib/ai/tools/code-execution.types.ts +24 -0
- package/templates/chat-app/lib/ai/tools/steps/multi-query-web-search.ts +3 -2
- package/templates/chat-app/lib/ai/types.ts +74 -3
- package/templates/chat-app/lib/anonymous-session-client.ts +0 -3
- package/templates/chat-app/lib/artifacts/code/client.tsx +35 -5
- package/templates/chat-app/lib/artifacts/sheet/client.tsx +11 -3
- package/templates/chat-app/lib/auth-client.ts +23 -1
- package/templates/chat-app/lib/auth.ts +18 -1
- package/templates/chat-app/lib/blob.ts +1 -1
- package/templates/chat-app/lib/clone-messages.ts +1 -1
- package/templates/chat-app/lib/config-schema.ts +18 -1
- package/templates/chat-app/lib/constants.ts +3 -4
- package/templates/chat-app/lib/db/migrations/0044_gray_red_shift.sql +5 -0
- package/templates/chat-app/lib/db/migrations/meta/0044_snapshot.json +1480 -0
- package/templates/chat-app/lib/db/migrations/meta/_journal.json +7 -0
- package/templates/chat-app/lib/db/queries.ts +84 -4
- package/templates/chat-app/lib/db/schema.ts +4 -1
- package/templates/chat-app/lib/editor/config.ts +4 -4
- package/templates/chat-app/lib/electron-auth.ts +96 -0
- package/templates/chat-app/lib/env-schema.ts +33 -4
- package/templates/chat-app/lib/message-conversion.ts +14 -2
- package/templates/chat-app/lib/playwright-test-environment.ts +18 -0
- package/templates/chat-app/lib/social-auth.ts +5 -0
- package/templates/chat-app/lib/stores/hooks-threads.ts +38 -1
- package/templates/chat-app/lib/stores/with-threads.test.ts +137 -0
- package/templates/chat-app/lib/stores/with-threads.ts +159 -7
- package/templates/chat-app/lib/stores/with-tracing.ts +1 -1
- package/templates/chat-app/lib/thread-utils.ts +22 -3
- package/templates/chat-app/lib/utils/download-assets.ts +6 -7
- package/templates/chat-app/lib/utils/rate-limit.ts +9 -3
- package/templates/chat-app/package.json +20 -18
- package/templates/chat-app/playwright.config.ts +0 -19
- package/templates/chat-app/providers/chat-input-provider.tsx +40 -2
- package/templates/chat-app/proxy.ts +28 -3
- package/templates/chat-app/scripts/check-env.ts +10 -0
- package/templates/chat-app/scripts/db-branch-delete.sh +7 -1
- package/templates/chat-app/scripts/db-branch-use.sh +7 -1
- package/templates/chat-app/scripts/with-db.sh +7 -1
- package/templates/chat-app/trpc/server.tsx +7 -2
- package/templates/chat-app/tsconfig.json +2 -1
- package/templates/chat-app/vercel.json +0 -10
- package/templates/chat-app/vitest.config.ts +2 -0
- package/templates/electron/CHANGELOG.md +7 -0
- package/templates/electron/README.md +54 -0
- package/templates/electron/entitlements.mac.plist +10 -0
- package/templates/electron/forge.config.ts +157 -0
- package/templates/electron/icon.png +0 -0
- package/templates/electron/package.json +53 -0
- package/templates/electron/scripts/generate-icons.test.js +37 -0
- package/templates/electron/scripts/generate-icons.ts +29 -0
- package/templates/electron/scripts/run-forge.cjs +28 -0
- package/templates/electron/scripts/write-branding.ts +18 -0
- package/templates/electron/src/config.ts +16 -0
- package/templates/electron/src/lib/auth-client.ts +64 -0
- package/templates/electron/src/main.ts +670 -0
- package/templates/electron/src/preload.d.ts +27 -0
- package/templates/electron/src/preload.ts +25 -0
- package/templates/electron/tsconfig.json +18 -0
|
@@ -1,373 +1,25 @@
|
|
|
1
|
-
import { Sandbox } from "@vercel/sandbox";
|
|
1
|
+
import type { Sandbox } from "@vercel/sandbox";
|
|
2
2
|
import { tool } from "ai";
|
|
3
3
|
import z from "zod";
|
|
4
4
|
import type { CostAccumulator } from "@/lib/credits/cost-accumulator";
|
|
5
|
-
import { env } from "@/lib/env";
|
|
6
5
|
import { createModuleLogger } from "@/lib/logger";
|
|
6
|
+
import { executeJavaScriptInSandbox } from "./code-execution.javascript";
|
|
7
|
+
import { executePythonInSandbox } from "./code-execution.python";
|
|
8
|
+
import {
|
|
9
|
+
cleanupSandbox,
|
|
10
|
+
createSandbox,
|
|
11
|
+
getErrorMessage,
|
|
12
|
+
getSandboxRuntime,
|
|
13
|
+
} from "./code-execution.shared";
|
|
14
|
+
import {
|
|
15
|
+
type SupportedExecutionLanguage,
|
|
16
|
+
supportedExecutionLanguages,
|
|
17
|
+
} from "./code-execution.types";
|
|
7
18
|
import { toolsDefinitions } from "./tools-definitions";
|
|
8
19
|
|
|
9
|
-
const
|
|
10
|
-
const PACKAGE_SPEC_SPLIT_RE = /[=<>![\s]/;
|
|
20
|
+
const languageSchema = z.enum(supportedExecutionLanguages);
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
sandbox: Sandbox,
|
|
14
|
-
basePackages: readonly string[],
|
|
15
|
-
log: ReturnType<typeof createModuleLogger>,
|
|
16
|
-
requestId: string
|
|
17
|
-
): Promise<{
|
|
18
|
-
success: boolean;
|
|
19
|
-
result?: { message: string; chart: string };
|
|
20
|
-
}> {
|
|
21
|
-
const installStep = await sandbox.runCommand({
|
|
22
|
-
cmd: "pip",
|
|
23
|
-
args: ["install", ...basePackages],
|
|
24
|
-
});
|
|
25
|
-
if (installStep.exitCode !== 0) {
|
|
26
|
-
const installStderr = await installStep.stderr();
|
|
27
|
-
log.error(
|
|
28
|
-
{ requestId, stderr: installStderr },
|
|
29
|
-
"base package installation failed"
|
|
30
|
-
);
|
|
31
|
-
return {
|
|
32
|
-
success: false,
|
|
33
|
-
result: {
|
|
34
|
-
message: `Failed to install base packages: ${installStderr}`,
|
|
35
|
-
chart: "",
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
log.info({ requestId }, "base packages installed");
|
|
40
|
-
return { success: true };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function packageName(spec: string): string {
|
|
44
|
-
return spec.split(PACKAGE_SPEC_SPLIT_RE)[0].toLowerCase();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function processExtraPackages(
|
|
48
|
-
code: string,
|
|
49
|
-
basePackages: readonly string[],
|
|
50
|
-
sandbox: Sandbox,
|
|
51
|
-
log: ReturnType<typeof createModuleLogger>,
|
|
52
|
-
requestId: string
|
|
53
|
-
): Promise<{
|
|
54
|
-
codeToRun: string;
|
|
55
|
-
installResult: {
|
|
56
|
-
success: boolean;
|
|
57
|
-
result?: { message: string; chart: string };
|
|
58
|
-
};
|
|
59
|
-
}> {
|
|
60
|
-
const basePackageNames = new Set(basePackages.map((p) => p.toLowerCase()));
|
|
61
|
-
const lines = code.split("\n");
|
|
62
|
-
const pipLines = lines.filter((l) => l.trim().startsWith("!pip install "));
|
|
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
|
-
}
|
|
80
|
-
|
|
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: "",
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
codeToRun: codeWithoutPipLines,
|
|
103
|
-
installResult: { success: true },
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function createWrappedCode(codeToRun: string, chartPath: string): string {
|
|
108
|
-
return `
|
|
109
|
-
import sys
|
|
110
|
-
import json
|
|
111
|
-
import traceback
|
|
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
|
-
|
|
124
|
-
try:
|
|
125
|
-
exec(${JSON.stringify(codeToRun)})
|
|
126
|
-
try:
|
|
127
|
-
_locals = locals()
|
|
128
|
-
_globals = globals()
|
|
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"])
|
|
143
|
-
except Exception:
|
|
144
|
-
pass
|
|
145
|
-
try:
|
|
146
|
-
import matplotlib.pyplot as plt
|
|
147
|
-
if plt.get_fignums():
|
|
148
|
-
plt.savefig('${chartPath}', format='png', bbox_inches='tight', dpi=100)
|
|
149
|
-
plt.close('all')
|
|
150
|
-
except ImportError:
|
|
151
|
-
pass
|
|
152
|
-
print(json.dumps({"success": True}))
|
|
153
|
-
except Exception as e:
|
|
154
|
-
error_info = {"success": False, "error": {"name": type(e).__name__, "value": str(e), "traceback": traceback.format_exc()}}
|
|
155
|
-
print(json.dumps(error_info))
|
|
156
|
-
sys.exit(1)
|
|
157
|
-
`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const CHART_JSON_PREFIX = "__CHART_JSON__:";
|
|
161
|
-
|
|
162
|
-
async function parseExecutionOutput(execResult: {
|
|
163
|
-
stdout: () => Promise<string>;
|
|
164
|
-
exitCode: number;
|
|
165
|
-
}): Promise<{
|
|
166
|
-
outputText: string;
|
|
167
|
-
chartData: Record<string, unknown> | null;
|
|
168
|
-
execInfo: {
|
|
169
|
-
success: boolean;
|
|
170
|
-
error?: { name: string; value: string; traceback: string };
|
|
171
|
-
};
|
|
172
|
-
}> {
|
|
173
|
-
const stdout = await execResult.stdout();
|
|
174
|
-
let execInfo: {
|
|
175
|
-
success: boolean;
|
|
176
|
-
error?: { name: string; value: string; traceback: string };
|
|
177
|
-
} = { success: true };
|
|
178
|
-
let outputText = "";
|
|
179
|
-
let chartData: Record<string, unknown> | null = null;
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
const outLines = (stdout ?? "").trim().split("\n");
|
|
183
|
-
const lastLine = outLines.at(-1);
|
|
184
|
-
execInfo = JSON.parse(lastLine ?? "{}");
|
|
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
|
-
|
|
200
|
-
outputText = outLines.join("\n");
|
|
201
|
-
} catch {
|
|
202
|
-
outputText = stdout ?? "";
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return { outputText, chartData, execInfo };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function checkForChart(
|
|
209
|
-
sandbox: Sandbox,
|
|
210
|
-
chartPath: string,
|
|
211
|
-
log: ReturnType<typeof createModuleLogger>,
|
|
212
|
-
requestId: string
|
|
213
|
-
): Promise<{ base64: string; format: string } | undefined> {
|
|
214
|
-
const chartCheck = await sandbox.runCommand({
|
|
215
|
-
cmd: "test",
|
|
216
|
-
args: ["-f", chartPath],
|
|
217
|
-
});
|
|
218
|
-
if (chartCheck.exitCode === 0) {
|
|
219
|
-
const b64 = await (
|
|
220
|
-
await sandbox.runCommand({
|
|
221
|
-
cmd: "base64",
|
|
222
|
-
args: ["-w", "0", chartPath],
|
|
223
|
-
})
|
|
224
|
-
).stdout();
|
|
225
|
-
log.info({ requestId }, "chart generated");
|
|
226
|
-
return { base64: (b64 ?? "").trim(), format: "png" };
|
|
227
|
-
}
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function buildResponseMessage({
|
|
232
|
-
outputText,
|
|
233
|
-
stderr,
|
|
234
|
-
execInfo,
|
|
235
|
-
log,
|
|
236
|
-
requestId,
|
|
237
|
-
}: {
|
|
238
|
-
outputText: string;
|
|
239
|
-
stderr: string;
|
|
240
|
-
execInfo: {
|
|
241
|
-
success: boolean;
|
|
242
|
-
error?: { name: string; value: string; traceback: string };
|
|
243
|
-
};
|
|
244
|
-
log: ReturnType<typeof createModuleLogger>;
|
|
245
|
-
requestId: string;
|
|
246
|
-
}): string {
|
|
247
|
-
let message = "";
|
|
248
|
-
|
|
249
|
-
if (outputText) {
|
|
250
|
-
message += `${outputText}\n`;
|
|
251
|
-
}
|
|
252
|
-
if (stderr && stderr.trim().length > 0) {
|
|
253
|
-
message += `${stderr}\n`;
|
|
254
|
-
}
|
|
255
|
-
if (execInfo.error) {
|
|
256
|
-
message += `Error: ${execInfo.error.name}: ${execInfo.error.value}\n`;
|
|
257
|
-
log.error({ requestId, error: execInfo.error }, "python execution error");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return message;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function getTokenAuth(): Record<string, string> {
|
|
264
|
-
const { VERCEL_TEAM_ID, VERCEL_PROJECT_ID, VERCEL_TOKEN } = env;
|
|
265
|
-
if (VERCEL_TEAM_ID && VERCEL_PROJECT_ID && VERCEL_TOKEN) {
|
|
266
|
-
return {
|
|
267
|
-
teamId: VERCEL_TEAM_ID,
|
|
268
|
-
projectId: VERCEL_PROJECT_ID,
|
|
269
|
-
token: VERCEL_TOKEN,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
return {};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function createSandbox(runtime: string): Promise<Sandbox> {
|
|
276
|
-
return Sandbox.create({
|
|
277
|
-
runtime,
|
|
278
|
-
timeout: 5 * 60 * 1000,
|
|
279
|
-
resources: { vcpus: 2 },
|
|
280
|
-
...getTokenAuth(),
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async function executeInSandbox({
|
|
285
|
-
sandbox,
|
|
286
|
-
code,
|
|
287
|
-
basePackages,
|
|
288
|
-
chartPath,
|
|
289
|
-
log,
|
|
290
|
-
requestId,
|
|
291
|
-
}: {
|
|
292
|
-
sandbox: Sandbox;
|
|
293
|
-
code: string;
|
|
294
|
-
basePackages: readonly string[];
|
|
295
|
-
chartPath: string;
|
|
296
|
-
log: ReturnType<typeof createModuleLogger>;
|
|
297
|
-
requestId: string;
|
|
298
|
-
}): Promise<{
|
|
299
|
-
message: string;
|
|
300
|
-
chart: string | { base64: string; format: string } | Record<string, unknown>;
|
|
301
|
-
}> {
|
|
302
|
-
const baseInstallResult = await installBasePackages(
|
|
303
|
-
sandbox,
|
|
304
|
-
basePackages,
|
|
305
|
-
log,
|
|
306
|
-
requestId
|
|
307
|
-
);
|
|
308
|
-
if (!baseInstallResult.success) {
|
|
309
|
-
return baseInstallResult.result ?? { message: "Unknown error", chart: "" };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const { codeToRun, installResult } = await processExtraPackages(
|
|
313
|
-
code,
|
|
314
|
-
basePackages,
|
|
315
|
-
sandbox,
|
|
316
|
-
log,
|
|
317
|
-
requestId
|
|
318
|
-
);
|
|
319
|
-
if (!installResult.success) {
|
|
320
|
-
return installResult.result ?? { message: "Unknown error", chart: "" };
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const wrappedCode = createWrappedCode(codeToRun, chartPath);
|
|
324
|
-
const execResult = await sandbox.runCommand({
|
|
325
|
-
cmd: "python3",
|
|
326
|
-
args: ["-c", wrappedCode],
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
const { outputText, chartData, execInfo } =
|
|
330
|
-
await parseExecutionOutput(execResult);
|
|
331
|
-
|
|
332
|
-
const message = buildResponseMessage({
|
|
333
|
-
outputText,
|
|
334
|
-
stderr: await execResult.stderr(),
|
|
335
|
-
execInfo,
|
|
336
|
-
log,
|
|
337
|
-
requestId,
|
|
338
|
-
});
|
|
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);
|
|
346
|
-
return {
|
|
347
|
-
message: message.trim(),
|
|
348
|
-
chart: chartOut ?? "",
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async function cleanupSandbox(
|
|
353
|
-
sandbox: Sandbox | undefined,
|
|
354
|
-
log: ReturnType<typeof createModuleLogger>,
|
|
355
|
-
requestId: string
|
|
356
|
-
): Promise<void> {
|
|
357
|
-
if (!sandbox) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
try {
|
|
361
|
-
await sandbox.stop();
|
|
362
|
-
log.info({ requestId }, "sandbox closed");
|
|
363
|
-
} catch (closeErr) {
|
|
364
|
-
log.warn({ requestId, closeErr }, "failed to close sandbox");
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function getErrorMessage(err: unknown): string {
|
|
369
|
-
return err instanceof Error ? err.message : "Unknown error";
|
|
370
|
-
}
|
|
22
|
+
const defaultExecutionLanguage: SupportedExecutionLanguage = "python";
|
|
371
23
|
|
|
372
24
|
export const codeExecution = ({
|
|
373
25
|
costAccumulator,
|
|
@@ -375,14 +27,23 @@ export const codeExecution = ({
|
|
|
375
27
|
costAccumulator?: CostAccumulator;
|
|
376
28
|
}) =>
|
|
377
29
|
tool({
|
|
378
|
-
description: `
|
|
30
|
+
description: `Sandboxed code execution for Python and JavaScript.
|
|
379
31
|
|
|
380
32
|
Use for:
|
|
381
|
-
- Execute Python
|
|
33
|
+
- Execute Python for calculations, data analysis, and visualisations
|
|
34
|
+
- Execute JavaScript for scripting, transformations, async fetches, and general runtime checks
|
|
35
|
+
|
|
36
|
+
Python support:
|
|
37
|
+
- matplotlib, pandas, numpy, sympy, yfinance pre-installed — do NOT reinstall them
|
|
382
38
|
- Produce interactive line / scatter / bar charts OR matplotlib PNG charts
|
|
383
39
|
- Install extra libs by adding lines like: '!pip install <pkg> [<pkg2> ...]' (we auto-install and strip these lines; pre-installed packages are ignored)
|
|
384
40
|
|
|
385
|
-
|
|
41
|
+
JavaScript support:
|
|
42
|
+
- Runs in a sandboxed Node.js runtime, never in the browser thread
|
|
43
|
+
- Use console.log(...) to print output
|
|
44
|
+
- You can also assign 'result' or 'results', or return a value from the snippet
|
|
45
|
+
|
|
46
|
+
Chart output — Python only:
|
|
386
47
|
1. Interactive chart (preferred for line/scatter/bar): assign a 'chart' variable matching this schema:
|
|
387
48
|
chart = {
|
|
388
49
|
"type": "line" | "scatter" | "bar",
|
|
@@ -402,47 +63,57 @@ Restrictions:
|
|
|
402
63
|
- Interactive chart: only line / scatter / bar types
|
|
403
64
|
|
|
404
65
|
Output rules:
|
|
405
|
-
-
|
|
406
|
-
-
|
|
407
|
-
-
|
|
66
|
+
- Set language to 'python' or 'javascript'
|
|
67
|
+
- Python charts: assign 'chart' dict for interactive charts (takes priority over matplotlib PNG)
|
|
68
|
+
- Python values: assign 'result' or 'results', or print explicitly
|
|
69
|
+
- JavaScript values: assign 'result' or 'results', return a value, or print explicitly
|
|
408
70
|
- Don't rely on implicit REPL last-expression output`,
|
|
409
71
|
inputSchema: z.object({
|
|
410
72
|
title: z.string().describe("The title of the code snippet."),
|
|
73
|
+
language: languageSchema
|
|
74
|
+
.default(defaultExecutionLanguage)
|
|
75
|
+
.describe("The language to execute: 'python' or 'javascript'."),
|
|
411
76
|
code: z
|
|
412
77
|
.string()
|
|
413
78
|
.describe(
|
|
414
|
-
"The
|
|
79
|
+
"The code to execute in the selected sandbox language. Print anything you want to return, or assign to 'result'/'results'."
|
|
415
80
|
),
|
|
416
81
|
}),
|
|
417
|
-
execute: async ({
|
|
82
|
+
execute: async ({
|
|
83
|
+
code,
|
|
84
|
+
title,
|
|
85
|
+
language,
|
|
86
|
+
}: {
|
|
87
|
+
code: string;
|
|
88
|
+
title: string;
|
|
89
|
+
language: SupportedExecutionLanguage;
|
|
90
|
+
}) => {
|
|
418
91
|
const log = createModuleLogger("code-execution");
|
|
419
92
|
const requestId = `ci-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
420
|
-
const runtime =
|
|
421
|
-
const basePackages = [
|
|
422
|
-
"matplotlib",
|
|
423
|
-
"pandas",
|
|
424
|
-
"numpy",
|
|
425
|
-
"sympy",
|
|
426
|
-
"yfinance",
|
|
427
|
-
] as const;
|
|
428
|
-
const chartPath = "/tmp/chart.png";
|
|
93
|
+
const runtime = getSandboxRuntime(language);
|
|
429
94
|
|
|
430
95
|
let sandbox: Sandbox | undefined;
|
|
431
96
|
|
|
432
97
|
try {
|
|
433
|
-
log.info({ requestId, title, runtime }, "creating sandbox");
|
|
98
|
+
log.info({ requestId, title, runtime, language }, "creating sandbox");
|
|
434
99
|
sandbox = await createSandbox(runtime);
|
|
435
100
|
log.debug({ requestId }, "sandbox created");
|
|
436
101
|
|
|
437
|
-
log.info({ requestId, title }, "executing
|
|
438
|
-
const result =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
102
|
+
log.info({ requestId, title, language }, "executing code");
|
|
103
|
+
const result =
|
|
104
|
+
language === "javascript"
|
|
105
|
+
? await executeJavaScriptInSandbox({
|
|
106
|
+
sandbox,
|
|
107
|
+
code,
|
|
108
|
+
log,
|
|
109
|
+
requestId,
|
|
110
|
+
})
|
|
111
|
+
: await executePythonInSandbox({
|
|
112
|
+
sandbox,
|
|
113
|
+
code,
|
|
114
|
+
log,
|
|
115
|
+
requestId,
|
|
116
|
+
});
|
|
446
117
|
|
|
447
118
|
costAccumulator?.addAPICost(
|
|
448
119
|
"codeExecution",
|
|
@@ -451,7 +122,7 @@ Output rules:
|
|
|
451
122
|
|
|
452
123
|
return result;
|
|
453
124
|
} catch (err) {
|
|
454
|
-
log.error({ err, requestId }, "code execution failed");
|
|
125
|
+
log.error({ err, requestId, language }, "code execution failed");
|
|
455
126
|
return {
|
|
456
127
|
message: `Sandbox execution failed: ${getErrorMessage(err)}`,
|
|
457
128
|
chart: "",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Sandbox } from "@vercel/sandbox";
|
|
2
|
+
import type { createModuleLogger } from "@/lib/logger";
|
|
3
|
+
|
|
4
|
+
export const supportedExecutionLanguages = ["python", "javascript"] as const;
|
|
5
|
+
|
|
6
|
+
export type SupportedExecutionLanguage =
|
|
7
|
+
(typeof supportedExecutionLanguages)[number];
|
|
8
|
+
|
|
9
|
+
export type CodeExecutionChart =
|
|
10
|
+
| string
|
|
11
|
+
| { base64: string; format: string }
|
|
12
|
+
| Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
export interface CodeExecutionResult {
|
|
15
|
+
chart: CodeExecutionChart;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CodeExecutionContext {
|
|
20
|
+
code: string;
|
|
21
|
+
log: ReturnType<typeof createModuleLogger>;
|
|
22
|
+
requestId: string;
|
|
23
|
+
sandbox: Sandbox;
|
|
24
|
+
}
|
|
@@ -118,8 +118,9 @@ export async function multiQueryWebSearchStep({
|
|
|
118
118
|
return {
|
|
119
119
|
searches: searchResults,
|
|
120
120
|
};
|
|
121
|
-
} catch (error:
|
|
122
|
-
const errorMessage =
|
|
121
|
+
} catch (error: unknown) {
|
|
122
|
+
const errorMessage =
|
|
123
|
+
error instanceof Error ? error.message : "Unknown error occurred";
|
|
123
124
|
|
|
124
125
|
// Send error annotation
|
|
125
126
|
dataStream.write({
|
|
@@ -59,10 +59,83 @@ const frontendToolsSchema = z.enum([
|
|
|
59
59
|
const __ = frontendToolsSchema.options satisfies ToolNameInternal[];
|
|
60
60
|
|
|
61
61
|
export type UiToolName = z.infer<typeof frontendToolsSchema>;
|
|
62
|
+
|
|
63
|
+
export type SelectedModelCounts = Partial<Record<AppModelId, number>>;
|
|
64
|
+
export type SelectedModelValue = AppModelId | SelectedModelCounts;
|
|
65
|
+
|
|
66
|
+
export function isSelectedModelCounts(
|
|
67
|
+
value: unknown
|
|
68
|
+
): value is SelectedModelCounts {
|
|
69
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Object.keys(value).length === 0) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return Object.entries(value).every(
|
|
78
|
+
([modelId, count]) =>
|
|
79
|
+
typeof modelId === "string" &&
|
|
80
|
+
typeof count === "number" &&
|
|
81
|
+
Number.isInteger(count) &&
|
|
82
|
+
count > 0
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isSelectedModelValue(
|
|
87
|
+
value: unknown
|
|
88
|
+
): value is SelectedModelValue {
|
|
89
|
+
return typeof value === "string" || isSelectedModelCounts(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getPrimarySelectedModelId(
|
|
93
|
+
selectedModel: SelectedModelValue | null | undefined
|
|
94
|
+
): AppModelId | null {
|
|
95
|
+
if (!selectedModel) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof selectedModel === "string") {
|
|
100
|
+
return selectedModel;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const [firstSelectedModelId] = Object.entries(selectedModel).find(
|
|
104
|
+
([, count]) => typeof count === "number" && count > 0
|
|
105
|
+
) ?? [null];
|
|
106
|
+
|
|
107
|
+
return firstSelectedModelId as AppModelId | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function expandSelectedModelValue(
|
|
111
|
+
selectedModel: SelectedModelValue
|
|
112
|
+
): AppModelId[] {
|
|
113
|
+
if (typeof selectedModel === "string") {
|
|
114
|
+
return [selectedModel];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const expanded: AppModelId[] = [];
|
|
118
|
+
|
|
119
|
+
for (const [modelId, count] of Object.entries(selectedModel)) {
|
|
120
|
+
if (!(typeof count === "number" && Number.isInteger(count) && count > 0)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (let index = 0; index < count; index += 1) {
|
|
125
|
+
expanded.push(modelId as AppModelId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return expanded;
|
|
130
|
+
}
|
|
131
|
+
|
|
62
132
|
const messageMetadataSchema = z.object({
|
|
63
133
|
createdAt: z.date(),
|
|
64
134
|
parentMessageId: z.string().nullable(),
|
|
65
|
-
|
|
135
|
+
parallelGroupId: z.string().nullable().optional(),
|
|
136
|
+
parallelIndex: z.number().int().nullable().optional(),
|
|
137
|
+
isPrimaryParallel: z.boolean().nullable().optional(),
|
|
138
|
+
selectedModel: z.custom<SelectedModelValue>(isSelectedModelValue),
|
|
66
139
|
activeStreamId: z.string().nullable(),
|
|
67
140
|
selectedTool: frontendToolsSchema.optional(),
|
|
68
141
|
usage: z.custom<LanguageModelUsage | undefined>((_val) => true).optional(),
|
|
@@ -101,7 +174,6 @@ type webSearchTool = InferUITool<ReturnType<typeof tavilyWebSearch>>;
|
|
|
101
174
|
type codeExecutionTool = InferUITool<ReturnType<typeof codeExecution>>;
|
|
102
175
|
type retrieveUrlTool = InferUITool<typeof retrieveUrl>;
|
|
103
176
|
|
|
104
|
-
// biome-ignore lint/style/useConsistentTypeDefinitions: using type for mapped type compatibility
|
|
105
177
|
export type ChatTools = {
|
|
106
178
|
codeExecution: codeExecutionTool;
|
|
107
179
|
createCodeDocument: createCodeDocumentToolType;
|
|
@@ -123,7 +195,6 @@ interface FollowupSuggestions {
|
|
|
123
195
|
suggestions: string[];
|
|
124
196
|
}
|
|
125
197
|
|
|
126
|
-
// biome-ignore lint/style/useConsistentTypeDefinitions: using type for mapped type compatibility
|
|
127
198
|
export type CustomUIDataTypes = {
|
|
128
199
|
appendMessage: string;
|
|
129
200
|
chatConfirmed: {
|
|
@@ -26,7 +26,6 @@ function setCookie(name: string, value: string, maxAge: number): void {
|
|
|
26
26
|
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
27
27
|
const encodedValue = encodeURIComponent(value);
|
|
28
28
|
if ("cookieStore" in window) {
|
|
29
|
-
// @ts-expect-error cookieStore is not yet in TS lib.dom
|
|
30
29
|
window.cookieStore
|
|
31
30
|
.set({
|
|
32
31
|
name,
|
|
@@ -34,7 +33,6 @@ function setCookie(name: string, value: string, maxAge: number): void {
|
|
|
34
33
|
path: "/",
|
|
35
34
|
expires: Date.now() + maxAge * 1000,
|
|
36
35
|
sameSite: "lax",
|
|
37
|
-
secure: window.location.protocol === "https:",
|
|
38
36
|
})
|
|
39
37
|
.catch(() => {
|
|
40
38
|
// Fail silently if Cookie Store API fails
|
|
@@ -50,7 +48,6 @@ function deleteCookie(name: string): void {
|
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
52
50
|
if ("cookieStore" in window) {
|
|
53
|
-
// @ts-expect-error cookieStore is not yet in TS lib.dom
|
|
54
51
|
window.cookieStore.delete(name).catch(() => {
|
|
55
52
|
// Fail silently if Cookie Store API fails
|
|
56
53
|
});
|