veryfront 0.1.13 → 0.1.15
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/esm/cli/app/data/slug-words.d.ts.map +1 -1
- package/esm/cli/app/data/slug-words.js +225 -90
- package/esm/cli/app/operations/project-creation.js +4 -3
- package/esm/cli/app/shell.js +1 -1
- package/esm/cli/app/utils.d.ts +5 -4
- package/esm/cli/app/utils.d.ts.map +1 -1
- package/esm/cli/app/utils.js +0 -23
- package/esm/cli/app/views/dashboard.d.ts +1 -1
- package/esm/cli/app/views/dashboard.d.ts.map +1 -1
- package/esm/cli/app/views/dashboard.js +22 -4
- package/esm/cli/auth/callback-server.d.ts.map +1 -1
- package/esm/cli/auth/callback-server.js +3 -2
- package/esm/cli/commands/dev/handler.d.ts.map +1 -1
- package/esm/cli/commands/dev/handler.js +2 -0
- package/esm/cli/commands/init/init-command.d.ts.map +1 -1
- package/esm/cli/commands/init/init-command.js +20 -3
- package/esm/cli/commands/init/interactive-wizard.d.ts +3 -2
- package/esm/cli/commands/init/interactive-wizard.d.ts.map +1 -1
- package/esm/cli/commands/init/interactive-wizard.js +55 -27
- package/esm/cli/mcp/remote-file-tools.d.ts +0 -6
- package/esm/cli/mcp/remote-file-tools.d.ts.map +1 -1
- package/esm/cli/mcp/remote-file-tools.js +37 -15
- package/esm/cli/shared/reserve-slug.d.ts.map +1 -1
- package/esm/cli/shared/reserve-slug.js +8 -3
- package/esm/cli/utils/env-prompt.d.ts.map +1 -1
- package/esm/cli/utils/env-prompt.js +3 -0
- package/esm/deno.d.ts +5 -1
- package/esm/deno.js +11 -4
- package/esm/src/agent/chat-handler.d.ts +4 -3
- package/esm/src/agent/chat-handler.d.ts.map +1 -1
- package/esm/src/agent/chat-handler.js +55 -4
- package/esm/src/agent/react/index.d.ts +1 -1
- package/esm/src/agent/react/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts +18 -0
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.js +54 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts +43 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.js +4 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts +23 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.js +67 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts +8 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.js +97 -0
- package/esm/src/agent/react/use-chat/index.d.ts +1 -1
- package/esm/src/agent/react/use-chat/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/types.d.ts +12 -0
- package/esm/src/agent/react/use-chat/types.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.js +120 -6
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +59 -7
- package/esm/src/build/production-build/templates.d.ts +2 -2
- package/esm/src/build/production-build/templates.d.ts.map +1 -1
- package/esm/src/build/production-build/templates.js +2 -68
- package/esm/src/chat/index.d.ts +1 -1
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/errors/veryfront-error.d.ts +3 -0
- package/esm/src/errors/veryfront-error.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.js +24 -3
- package/esm/src/platform/compat/http/deno-server.d.ts.map +1 -1
- package/esm/src/platform/compat/http/deno-server.js +23 -2
- package/esm/src/provider/index.d.ts +1 -1
- package/esm/src/provider/index.d.ts.map +1 -1
- package/esm/src/provider/index.js +1 -1
- package/esm/src/provider/local/ai-sdk-adapter.d.ts +19 -0
- package/esm/src/provider/local/ai-sdk-adapter.d.ts.map +1 -0
- package/esm/src/provider/local/ai-sdk-adapter.js +164 -0
- package/esm/src/provider/local/env.d.ts +10 -0
- package/esm/src/provider/local/env.d.ts.map +1 -0
- package/esm/src/provider/local/env.js +23 -0
- package/esm/src/provider/local/local-engine.d.ts +61 -0
- package/esm/src/provider/local/local-engine.d.ts.map +1 -0
- package/esm/src/provider/local/local-engine.js +211 -0
- package/esm/src/provider/local/model-catalog.d.ts +30 -0
- package/esm/src/provider/local/model-catalog.d.ts.map +1 -0
- package/esm/src/provider/local/model-catalog.js +58 -0
- package/esm/src/provider/model-registry.d.ts +14 -0
- package/esm/src/provider/model-registry.d.ts.map +1 -1
- package/esm/src/provider/model-registry.js +58 -2
- package/esm/src/proxy/main.js +34 -6
- package/esm/src/proxy/server-resolver.d.ts +23 -0
- package/esm/src/proxy/server-resolver.d.ts.map +1 -0
- package/esm/src/proxy/server-resolver.js +124 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.d.ts +8 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.js +36 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts +7 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.js +33 -0
- package/esm/src/react/components/ai/chat/index.d.ts +7 -1
- package/esm/src/react/components/ai/chat/index.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/index.js +16 -4
- package/esm/src/sandbox/index.d.ts +31 -0
- package/esm/src/sandbox/index.d.ts.map +1 -0
- package/esm/src/sandbox/index.js +30 -0
- package/esm/src/sandbox/sandbox.d.ts +48 -0
- package/esm/src/sandbox/sandbox.d.ts.map +1 -0
- package/esm/src/sandbox/sandbox.js +178 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.js +8 -2
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.js +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.js +15 -1
- package/package.json +8 -1
- package/src/cli/app/data/slug-words.ts +225 -90
- package/src/cli/app/operations/project-creation.ts +3 -3
- package/src/cli/app/shell.ts +1 -1
- package/src/cli/app/utils.ts +0 -30
- package/src/cli/app/views/dashboard.ts +27 -4
- package/src/cli/auth/callback-server.ts +3 -2
- package/src/cli/commands/dev/handler.ts +2 -0
- package/src/cli/commands/init/init-command.ts +30 -3
- package/src/cli/commands/init/interactive-wizard.ts +62 -34
- package/src/cli/mcp/remote-file-tools.ts +50 -15
- package/src/cli/shared/reserve-slug.ts +9 -2
- package/src/cli/utils/env-prompt.ts +3 -0
- package/src/deno.js +11 -4
- package/src/src/agent/chat-handler.ts +57 -4
- package/src/src/agent/react/index.ts +2 -0
- package/src/src/agent/react/use-chat/browser-inference/browser-engine.ts +81 -0
- package/src/src/agent/react/use-chat/browser-inference/types.ts +52 -0
- package/src/src/agent/react/use-chat/browser-inference/worker-client.ts +89 -0
- package/src/src/agent/react/use-chat/browser-inference/worker-script.ts +98 -0
- package/src/src/agent/react/use-chat/index.ts +2 -0
- package/src/src/agent/react/use-chat/types.ts +20 -0
- package/src/src/agent/react/use-chat/use-chat.ts +148 -8
- package/src/src/agent/runtime/index.ts +72 -6
- package/src/src/build/production-build/templates.ts +2 -68
- package/src/src/chat/index.ts +2 -0
- package/src/src/errors/veryfront-error.ts +2 -1
- package/src/src/platform/adapters/runtime/deno/adapter.ts +25 -3
- package/src/src/platform/compat/http/deno-server.ts +28 -1
- package/src/src/provider/index.ts +1 -0
- package/src/src/provider/local/ai-sdk-adapter.ts +207 -0
- package/src/src/provider/local/env.ts +26 -0
- package/src/src/provider/local/local-engine.ts +288 -0
- package/src/src/provider/local/model-catalog.ts +73 -0
- package/src/src/provider/model-registry.ts +66 -2
- package/src/src/proxy/main.ts +41 -6
- package/src/src/proxy/server-resolver.ts +151 -0
- package/src/src/react/components/ai/chat/components/inference-badge.tsx +48 -0
- package/src/src/react/components/ai/chat/components/upgrade-cta.tsx +56 -0
- package/src/src/react/components/ai/chat/index.tsx +43 -6
- package/src/src/sandbox/index.ts +32 -0
- package/src/src/sandbox/sandbox.ts +236 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts +9 -2
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +1 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts +17 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*******************************/
|
|
5
5
|
|
|
6
6
|
import { cliLogger as logger } from "../../utils/index.js";
|
|
7
|
-
import { brand, dim, green } from "../../ui/index.js";
|
|
7
|
+
import { brand, dim, green, red } from "../../ui/index.js";
|
|
8
8
|
import { createSpinner } from "../../ui/progress.js";
|
|
9
9
|
import { box } from "../../ui/box.js";
|
|
10
10
|
import { ensureDir } from "../../../deps/jsr.io/@std/fs/1.0.22/mod.js";
|
|
@@ -35,7 +35,11 @@ import {
|
|
|
35
35
|
loadIntegrations,
|
|
36
36
|
validateIntegrations,
|
|
37
37
|
} from "../../templates/integration-loader.js";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
runInteractiveWizard,
|
|
40
|
+
shouldRunWizard,
|
|
41
|
+
validateProjectName,
|
|
42
|
+
} from "./interactive-wizard.js";
|
|
39
43
|
|
|
40
44
|
/**
|
|
41
45
|
* Icon mapping for integrations based on category/name
|
|
@@ -185,8 +189,31 @@ export async function initCommand(options: InitOptions): Promise<void> {
|
|
|
185
189
|
let projectName = name;
|
|
186
190
|
let initGit = false;
|
|
187
191
|
|
|
192
|
+
// Validate project name before doing anything else
|
|
193
|
+
if (name) {
|
|
194
|
+
const nameError = validateProjectName(name);
|
|
195
|
+
if (nameError) {
|
|
196
|
+
console.error(red(nameError));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if directory already exists before entering the wizard
|
|
202
|
+
if (name && !options.force) {
|
|
203
|
+
const fs = createFileSystem();
|
|
204
|
+
const targetDir = join(cwd(), name);
|
|
205
|
+
if (await fs.exists(targetDir)) {
|
|
206
|
+
console.error(
|
|
207
|
+
red(
|
|
208
|
+
`Directory "${name}" already exists. Choose a different name or use --force to overwrite.`,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
if (shouldRunWizard(options)) {
|
|
189
|
-
const wizardResult = await runInteractiveWizard();
|
|
216
|
+
const wizardResult = await runInteractiveWizard(name);
|
|
190
217
|
if (wizardResult.cancelled) {
|
|
191
218
|
return;
|
|
192
219
|
}
|
|
@@ -6,6 +6,13 @@ import { select, textInput } from "../../utils/terminal-select.js";
|
|
|
6
6
|
import { getTemplateSelectOptions, TEMPLATES } from "./catalog.js";
|
|
7
7
|
import type { InitTemplate } from "./types.js";
|
|
8
8
|
|
|
9
|
+
/** Reject path separators and traversal so the name stays a single directory. */
|
|
10
|
+
export function validateProjectName(name: string): string | null {
|
|
11
|
+
if (/[/\\]/.test(name)) return 'Project name cannot contain "/" or "\\"';
|
|
12
|
+
if (name === "." || name === "..") return 'Project name cannot be "." or ".."';
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export interface WizardResult {
|
|
10
17
|
projectName: string | null; // null = use current directory
|
|
11
18
|
template: InitTemplate;
|
|
@@ -18,10 +25,10 @@ function canRunWizard(): boolean {
|
|
|
18
25
|
return !(isCiEnv() || isDenoTestingEnv()) && checkIsInteractive();
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
28
|
+
export async function runInteractiveWizard(existingName?: string): Promise<WizardResult> {
|
|
22
29
|
if (!canRunWizard()) {
|
|
23
30
|
return {
|
|
24
|
-
projectName: null,
|
|
31
|
+
projectName: existingName ?? null,
|
|
25
32
|
template: "minimal",
|
|
26
33
|
initGit: false,
|
|
27
34
|
skipped: true,
|
|
@@ -37,31 +44,28 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
37
44
|
console.log(`│ Let's set up your project.`);
|
|
38
45
|
console.log("│");
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (locationChoice === "new") {
|
|
63
|
-
const name = await textInput("Project name", "my-app");
|
|
64
|
-
if (name === null) {
|
|
47
|
+
let projectName: string | null = existingName ?? null;
|
|
48
|
+
|
|
49
|
+
// Location prompt (skip when name was provided via CLI)
|
|
50
|
+
if (!existingName) {
|
|
51
|
+
const locationChoice = await select(
|
|
52
|
+
"Where should we create your project?",
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
value: "current",
|
|
56
|
+
label: "Current folder",
|
|
57
|
+
description: "Use this directory",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
value: "new",
|
|
61
|
+
label: "New folder",
|
|
62
|
+
description: "Create a new directory",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
0,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (locationChoice === null) {
|
|
65
69
|
console.log(muted("\n Cancelled.\n"));
|
|
66
70
|
return {
|
|
67
71
|
projectName: null,
|
|
@@ -71,12 +75,36 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
71
75
|
cancelled: true,
|
|
72
76
|
};
|
|
73
77
|
}
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
|
|
79
|
+
if (locationChoice === "new") {
|
|
80
|
+
const name = await textInput("Project name", "my-app");
|
|
81
|
+
if (name === null) {
|
|
82
|
+
console.log(muted("\n Cancelled.\n"));
|
|
83
|
+
return {
|
|
84
|
+
projectName: null,
|
|
85
|
+
template: "minimal",
|
|
86
|
+
initGit: false,
|
|
87
|
+
skipped: false,
|
|
88
|
+
cancelled: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const validName = name || "my-app";
|
|
92
|
+
const nameError = validateProjectName(validName);
|
|
93
|
+
if (nameError) {
|
|
94
|
+
console.log(muted(`\n ${nameError}\n`));
|
|
95
|
+
return {
|
|
96
|
+
projectName: null,
|
|
97
|
+
template: "minimal",
|
|
98
|
+
initGit: false,
|
|
99
|
+
skipped: false,
|
|
100
|
+
cancelled: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
projectName = validName;
|
|
76
104
|
}
|
|
77
105
|
}
|
|
78
106
|
|
|
79
|
-
//
|
|
107
|
+
// Template selection
|
|
80
108
|
const templateChoice = await select(
|
|
81
109
|
"What would you like to build?",
|
|
82
110
|
getTemplateSelectOptions(),
|
|
@@ -96,7 +124,7 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
96
124
|
|
|
97
125
|
const template = templateChoice as InitTemplate;
|
|
98
126
|
|
|
99
|
-
//
|
|
127
|
+
// Git init prompt
|
|
100
128
|
const gitChoice = await select(
|
|
101
129
|
"Initialize a git repository?",
|
|
102
130
|
[
|
|
@@ -136,7 +164,7 @@ export async function runInteractiveWizard(): Promise<WizardResult> {
|
|
|
136
164
|
return { projectName, template, initGit, skipped: false, cancelled: false };
|
|
137
165
|
}
|
|
138
166
|
|
|
139
|
-
export function shouldRunWizard(options: { template?: string
|
|
140
|
-
//
|
|
141
|
-
return !options.template
|
|
167
|
+
export function shouldRunWizard(options: { template?: string }): boolean {
|
|
168
|
+
// Always run wizard unless template is explicitly specified via --template
|
|
169
|
+
return !options.template;
|
|
142
170
|
}
|
|
@@ -15,6 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
import type { MCPTool } from "./tools.js";
|
|
16
16
|
import { getEnvironmentConfig } from "../../src/config/index.js";
|
|
17
17
|
import { withSpan } from "../../src/observability/tracing/otlp-setup.js";
|
|
18
|
+
import { randomSuffix } from "../shared/slug.js";
|
|
18
19
|
|
|
19
20
|
import { DEFAULT_LOCAL_API_URL } from "../shared/constants.js";
|
|
20
21
|
|
|
@@ -135,6 +136,46 @@ interface Project {
|
|
|
135
136
|
created_at?: string;
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
const MAX_SLUG_ATTEMPTS = 10;
|
|
140
|
+
|
|
141
|
+
function slugToName(slug: string): string {
|
|
142
|
+
return slug
|
|
143
|
+
.split("-")
|
|
144
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
145
|
+
.join(" ");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a project with slug conflict retry.
|
|
150
|
+
* On 409, appends a random suffix and retries (matches reserveProjectSlug behavior).
|
|
151
|
+
* Name is derived from the base slug (without random suffix) for readability.
|
|
152
|
+
*/
|
|
153
|
+
async function createProjectWithRetry(
|
|
154
|
+
slug: string,
|
|
155
|
+
body: Record<string, unknown>,
|
|
156
|
+
): Promise<{ ok: true; data: Project; slug: string } | { ok: false; error: string }> {
|
|
157
|
+
const name = slugToName(slug);
|
|
158
|
+
let currentSlug = slug;
|
|
159
|
+
|
|
160
|
+
for (let attempt = 1; attempt <= MAX_SLUG_ATTEMPTS; attempt++) {
|
|
161
|
+
const result = await apiRequest<Project>("POST", "/projects", {
|
|
162
|
+
body: { ...body, slug: currentSlug, name },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (result.ok && result.data) {
|
|
166
|
+
return { ok: true, data: result.data, slug: currentSlug };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (result.status !== 409) {
|
|
170
|
+
return { ok: false, error: result.error ?? "Failed to create project" };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
currentSlug = `${slug}-${randomSuffix()}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { ok: false, error: `Could not find available slug after ${MAX_SLUG_ATTEMPTS} attempts` };
|
|
177
|
+
}
|
|
178
|
+
|
|
138
179
|
// ============================================================================
|
|
139
180
|
// Tool: vf_remote_list_files
|
|
140
181
|
// ============================================================================
|
|
@@ -573,8 +614,9 @@ export const vfRemoteDeleteBranch: MCPTool<RemoteDeleteBranchInput, RemoteDelete
|
|
|
573
614
|
// ============================================================================
|
|
574
615
|
|
|
575
616
|
const remoteCreateProjectInput = z.object({
|
|
576
|
-
|
|
577
|
-
|
|
617
|
+
slug: z.string().describe(
|
|
618
|
+
"Project slug (lowercase letters, numbers, hyphens only). A random suffix is appended if the slug is already taken.",
|
|
619
|
+
),
|
|
578
620
|
template: z.string().optional().describe("Template to use (e.g., 'chat', 'rag', 'minimal')"),
|
|
579
621
|
is_public: z.boolean().optional().describe("Whether the project is public (default: false)"),
|
|
580
622
|
});
|
|
@@ -592,13 +634,9 @@ export const vfRemoteCreateProject: MCPTool<RemoteCreateProjectInput, RemoteCrea
|
|
|
592
634
|
description: "Create a new Veryfront project. Returns the project details including ID and slug.",
|
|
593
635
|
inputSchema: remoteCreateProjectInput,
|
|
594
636
|
execute: async (input) => {
|
|
595
|
-
const result = await
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
slug: input.slug,
|
|
599
|
-
template: input.template,
|
|
600
|
-
isPublic: input.is_public,
|
|
601
|
-
},
|
|
637
|
+
const result = await createProjectWithRetry(input.slug, {
|
|
638
|
+
template: input.template,
|
|
639
|
+
isPublic: input.is_public,
|
|
602
640
|
});
|
|
603
641
|
|
|
604
642
|
if (!result.ok) return { success: false, error: result.error };
|
|
@@ -612,9 +650,8 @@ export const vfRemoteCreateProject: MCPTool<RemoteCreateProjectInput, RemoteCrea
|
|
|
612
650
|
|
|
613
651
|
const remoteCloneProjectInput = z.object({
|
|
614
652
|
source_project: z.string().describe("Source project slug or ID to clone from"),
|
|
615
|
-
target_name: z.string().describe("Name for the new project"),
|
|
616
653
|
target_slug: z.string().describe(
|
|
617
|
-
"Slug for the new project (lowercase letters, numbers, hyphens only)",
|
|
654
|
+
"Slug for the new project (lowercase letters, numbers, hyphens only). A random suffix is appended if the slug is already taken.",
|
|
618
655
|
),
|
|
619
656
|
file_pattern: z.string().optional().describe(
|
|
620
657
|
"Optional file pattern to filter which files to clone (e.g., '*.tsx')",
|
|
@@ -639,9 +676,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
|
|
|
639
676
|
withSpan(
|
|
640
677
|
"cli.mcp.tool.vf_remote_clone_project",
|
|
641
678
|
async () => {
|
|
642
|
-
const createResult = await
|
|
643
|
-
body: { name: input.target_name, slug: input.target_slug },
|
|
644
|
-
});
|
|
679
|
+
const createResult = await createProjectWithRetry(input.target_slug, {});
|
|
645
680
|
|
|
646
681
|
if (!createResult.ok) {
|
|
647
682
|
return { success: false, error: `Failed to create project: ${createResult.error}` };
|
|
@@ -684,7 +719,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
|
|
|
684
719
|
|
|
685
720
|
const createFileResult = await apiRequest<{ id: string; path: string }>(
|
|
686
721
|
"PUT",
|
|
687
|
-
`/${
|
|
722
|
+
`/${createResult.slug}/files/${encodeFilePath(file.path)}`,
|
|
688
723
|
{ body: { content: getResult.data.content } },
|
|
689
724
|
);
|
|
690
725
|
|
|
@@ -9,8 +9,13 @@ import * as dntShim from "../../_dnt.shims.js";
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import { type EnvironmentConfig, getEnvironmentConfig } from "../../src/config/index.js";
|
|
12
|
+
import { capitalizeSeparatedWords } from "../../src/utils/case-utils.js";
|
|
12
13
|
import { randomSuffix } from "./slug.js";
|
|
13
14
|
|
|
15
|
+
function slugToName(slug: string): string {
|
|
16
|
+
return capitalizeSeparatedWords(slug, "-", " ");
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export interface ReserveResult {
|
|
15
20
|
slug: string;
|
|
16
21
|
projectId: string;
|
|
@@ -39,10 +44,11 @@ export async function reserveProjectSlug(
|
|
|
39
44
|
token: string,
|
|
40
45
|
env: EnvironmentConfig = getEnvironmentConfig(),
|
|
41
46
|
): Promise<ReserveResult> {
|
|
47
|
+
const name = slugToName(slug);
|
|
42
48
|
let currentSlug = slug;
|
|
43
49
|
|
|
44
50
|
for (let attempt = 1; attempt <= MAX_SLUG_ATTEMPTS; attempt++) {
|
|
45
|
-
const result = await tryCreateProject(currentSlug, token, env);
|
|
51
|
+
const result = await tryCreateProject(currentSlug, name, token, env);
|
|
46
52
|
|
|
47
53
|
if (result.success) {
|
|
48
54
|
return {
|
|
@@ -64,6 +70,7 @@ export async function reserveProjectSlug(
|
|
|
64
70
|
|
|
65
71
|
async function tryCreateProject(
|
|
66
72
|
slug: string,
|
|
73
|
+
name: string,
|
|
67
74
|
token: string,
|
|
68
75
|
env: EnvironmentConfig = getEnvironmentConfig(),
|
|
69
76
|
): Promise<CreateProjectResult> {
|
|
@@ -75,7 +82,7 @@ async function tryCreateProject(
|
|
|
75
82
|
"Content-Type": "application/json",
|
|
76
83
|
Accept: "application/json",
|
|
77
84
|
},
|
|
78
|
-
body: JSON.stringify({ slug, name
|
|
85
|
+
body: JSON.stringify({ slug, name }),
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
if (response.ok) {
|
package/src/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"nodeModulesDir": "auto",
|
|
5
5
|
"exclude": [
|
|
6
6
|
"npm/",
|
|
@@ -37,6 +37,7 @@ export default {
|
|
|
37
37
|
"./provider": "./src/provider/index.ts",
|
|
38
38
|
"./fs": "./src/fs/index.ts",
|
|
39
39
|
"./integrations": "./src/integrations/index.ts",
|
|
40
|
+
"./sandbox": "./src/sandbox/index.ts",
|
|
40
41
|
"./cli": "./cli/main.ts"
|
|
41
42
|
},
|
|
42
43
|
"imports": {
|
|
@@ -55,6 +56,7 @@ export default {
|
|
|
55
56
|
"veryfront/react/fonts": "./src/react/fonts/index.ts",
|
|
56
57
|
"veryfront/react/components/ai": "./src/react/components/ai/index.ts",
|
|
57
58
|
"veryfront/components/ai": "./src/react/components/ai/index.ts",
|
|
59
|
+
"veryfront/sandbox": "./src/sandbox/index.ts",
|
|
58
60
|
"veryfront/agent/react": "./src/agent/react/index.ts",
|
|
59
61
|
"veryfront/agent/testing": "./src/agent/testing/index.ts",
|
|
60
62
|
"veryfront/agent/middleware": "./src/agent/middleware/index.ts",
|
|
@@ -137,6 +139,7 @@ export default {
|
|
|
137
139
|
"#veryfront/testing/bdd.ts": "./src/testing/bdd.ts",
|
|
138
140
|
"#veryfront/testing/deno-compat": "./src/testing/deno-compat.ts",
|
|
139
141
|
"#veryfront/testing/utils": "./src/testing/utils.ts",
|
|
142
|
+
"#veryfront/sandbox": "./src/sandbox/index.ts",
|
|
140
143
|
"#veryfront/tool": "./src/tool/index.ts",
|
|
141
144
|
"#veryfront/tool/schema": "./src/tool/schema/index.ts",
|
|
142
145
|
"#veryfront/transforms": "./src/transforms/index.ts",
|
|
@@ -252,7 +255,8 @@ export default {
|
|
|
252
255
|
"@babel/parser": "npm:@babel/parser@7.26.3",
|
|
253
256
|
"@babel/traverse": "npm:@babel/traverse@7.26.3",
|
|
254
257
|
"@babel/generator": "npm:@babel/generator@7.26.3",
|
|
255
|
-
"@babel/types": "npm:@babel/types@7.26.3"
|
|
258
|
+
"@babel/types": "npm:@babel/types@7.26.3",
|
|
259
|
+
"@huggingface/transformers": "npm:@huggingface/transformers@3.4.2"
|
|
256
260
|
},
|
|
257
261
|
"compilerOptions": {
|
|
258
262
|
"jsx": "react-jsx",
|
|
@@ -278,7 +282,7 @@ export default {
|
|
|
278
282
|
"proxy": "deno run --allow-all cli/main.ts serve --mode=proxy",
|
|
279
283
|
"dev": "deno run --allow-all cli/main.ts dev",
|
|
280
284
|
"production": "deno run --allow-all cli/main.ts serve --mode=production",
|
|
281
|
-
"build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
|
|
285
|
+
"build": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/generate-templates-manifest.ts && deno run -A scripts/build/generate-dev-ui-manifest.ts && deno run -A scripts/build/prepare-framework-sources.ts && deno run -A scripts/build/prebundle-client-scripts.ts && deno compile --allow-all --include src/platform/polyfills --include src/proxy/main.ts --include dist/framework-src --output ./bin/veryfront cli/main.ts",
|
|
282
286
|
"build:npm": "deno run -A scripts/build/generate-integrations-module.ts && deno run -A scripts/build/build-npm-dnt.ts",
|
|
283
287
|
"release": "deno run -A scripts/release.ts",
|
|
284
288
|
"test": "VF_DISABLE_LRU_INTERVAL=1 SSR_TRANSFORM_PER_PROJECT_LIMIT=0 REVALIDATION_PER_PROJECT_LIMIT=0 NODE_ENV=production LOG_FORMAT=text deno test --no-check --parallel --allow-all '--ignore=tests/e2e,tests/integration/compiled-binary-e2e.test.ts' --unstable-worker-options --unstable-net",
|
|
@@ -383,7 +387,10 @@ export default {
|
|
|
383
387
|
"proseWrap": "preserve"
|
|
384
388
|
},
|
|
385
389
|
"allowScripts": {
|
|
386
|
-
"allow": [
|
|
390
|
+
"allow": [
|
|
391
|
+
"npm:sharp@0.33.5",
|
|
392
|
+
"npm:onnxruntime-node@1.20.1"
|
|
393
|
+
],
|
|
387
394
|
"deny": [
|
|
388
395
|
"npm:esbuild@0.20.2",
|
|
389
396
|
"npm:protobufjs@7.5.4"
|
|
@@ -2,6 +2,8 @@ import * as dntShim from "../../_dnt.shims.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAgent } from "./composition/index.js";
|
|
4
4
|
import type { Message } from "./types.js";
|
|
5
|
+
import { fromError } from "../errors/veryfront-error.js";
|
|
6
|
+
import { DEFAULT_LOCAL_MODEL } from "../provider/local/model-catalog.js";
|
|
5
7
|
|
|
6
8
|
// ---------------------------------------------------------------------------
|
|
7
9
|
// Zod schemas for validating AI SDK v5 chat UI messages
|
|
@@ -129,11 +131,25 @@ export interface ChatHandlerOptions {
|
|
|
129
131
|
) => Record<string, unknown> | Promise<Record<string, unknown>>);
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Extract the raw Request from either a raw Request or a Pages Router APIContext.
|
|
136
|
+
* Pages Router handlers receive `(ctx)` where `ctx.request` is the Request.
|
|
137
|
+
* App Router handlers receive `(request, context)` where `request` IS the Request.
|
|
138
|
+
*/
|
|
139
|
+
function extractRequest(requestOrCtx: unknown): dntShim.Request {
|
|
140
|
+
if (requestOrCtx instanceof dntShim.Request) return requestOrCtx;
|
|
141
|
+
// Pages Router APIContext — has a .request property
|
|
142
|
+
const ctx = requestOrCtx as { request?: dntShim.Request };
|
|
143
|
+
if (ctx.request instanceof dntShim.Request) return ctx.request;
|
|
144
|
+
throw new Error("Invalid handler argument: expected Request or APIContext");
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
/**
|
|
133
148
|
* Create a POST handler for a chat API route.
|
|
134
149
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
150
|
+
* Works with both App Router and Pages Router:
|
|
151
|
+
* - App Router: `app/api/chat/route.ts` — handler receives `(request, context)`
|
|
152
|
+
* - Pages Router: `pages/api/chat.ts` — handler receives `(ctx)`
|
|
137
153
|
*
|
|
138
154
|
* @example
|
|
139
155
|
* ```ts
|
|
@@ -145,12 +161,22 @@ export function createChatHandler(
|
|
|
145
161
|
agentId: string,
|
|
146
162
|
options?: ChatHandlerOptions,
|
|
147
163
|
) {
|
|
148
|
-
|
|
164
|
+
// deno-lint-ignore no-explicit-any
|
|
165
|
+
return async function POST(requestOrCtx: any): Promise<dntShim.Response> {
|
|
166
|
+
const request = extractRequest(requestOrCtx);
|
|
167
|
+
// Resolve agent outside try so it's available in the catch block for
|
|
168
|
+
// extracting the system prompt in the 503 fallback response.
|
|
169
|
+
let agent: ReturnType<typeof getAgent> | undefined;
|
|
170
|
+
try {
|
|
171
|
+
agent = getAgent(agentId);
|
|
172
|
+
} catch {
|
|
173
|
+
return dntShim.Response.json({ error: "Agent not found" }, { status: 404 });
|
|
174
|
+
}
|
|
175
|
+
|
|
149
176
|
try {
|
|
150
177
|
const body = await request.json();
|
|
151
178
|
const { messages: rawMessages } = chatRequestSchema.parse(body);
|
|
152
179
|
|
|
153
|
-
const agent = getAgent(agentId);
|
|
154
180
|
if (!agent) {
|
|
155
181
|
return dntShim.Response.json({ error: "Agent not found" }, { status: 404 });
|
|
156
182
|
}
|
|
@@ -177,6 +203,33 @@ export function createChatHandler(
|
|
|
177
203
|
);
|
|
178
204
|
}
|
|
179
205
|
|
|
206
|
+
// Detect structured "no_ai_available" errors from local engine
|
|
207
|
+
const vfError = fromError(error);
|
|
208
|
+
if (vfError?.type === "no_ai_available") {
|
|
209
|
+
// Resolve the agent's system prompt so the browser can use it for inference
|
|
210
|
+
const systemConfig = agent?.config?.system;
|
|
211
|
+
let systemPrompt = "You are a helpful AI assistant.";
|
|
212
|
+
if (typeof systemConfig === "string") {
|
|
213
|
+
systemPrompt = systemConfig;
|
|
214
|
+
} else if (typeof systemConfig === "function") {
|
|
215
|
+
try {
|
|
216
|
+
systemPrompt = await systemConfig();
|
|
217
|
+
} catch {
|
|
218
|
+
// Fall back to default
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return dntShim.Response.json(
|
|
223
|
+
{
|
|
224
|
+
code: "NO_AI_AVAILABLE",
|
|
225
|
+
fallback: "browser",
|
|
226
|
+
model: DEFAULT_LOCAL_MODEL,
|
|
227
|
+
systemPrompt,
|
|
228
|
+
},
|
|
229
|
+
{ status: 503 },
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
180
233
|
return dntShim.Response.json(
|
|
181
234
|
{ error: "Internal server error" },
|
|
182
235
|
{ status: 500 },
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge between BrowserInferenceClient (Worker) and useChat's streaming interface.
|
|
3
|
+
*
|
|
4
|
+
* Converts UIMessage[] to simple {role, content}[] for the Worker,
|
|
5
|
+
* and synthesizes the same streaming updates that handleStreamingResponse produces.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BrowserInferenceStatus } from "../types.js";
|
|
9
|
+
import type { UIMessage, UIMessagePart } from "../types.js";
|
|
10
|
+
import { generateClientId } from "../utils.js";
|
|
11
|
+
import { BrowserInferenceClient } from "./worker-client.js";
|
|
12
|
+
|
|
13
|
+
export interface BrowserInferenceCallbacks {
|
|
14
|
+
onUpdate: (parts: UIMessagePart[], messageId: string) => void;
|
|
15
|
+
onMessage: (message: UIMessage) => void;
|
|
16
|
+
onStatusChange: (status: BrowserInferenceStatus) => void;
|
|
17
|
+
onDownloadProgress?: (progress: number) => void;
|
|
18
|
+
onError: (error: Error) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractTextFromMessages(
|
|
22
|
+
messages: UIMessage[],
|
|
23
|
+
): Array<{ role: string; content: string }> {
|
|
24
|
+
return messages
|
|
25
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
26
|
+
.map((m) => {
|
|
27
|
+
const textPart = m.parts.find((p) => p.type === "text");
|
|
28
|
+
const content = textPart && "text" in textPart ? textPart.text : "";
|
|
29
|
+
return { role: m.role, content };
|
|
30
|
+
})
|
|
31
|
+
.filter((m) => m.content.length > 0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function runBrowserInference(
|
|
35
|
+
messages: UIMessage[],
|
|
36
|
+
systemPrompt: string,
|
|
37
|
+
callbacks: BrowserInferenceCallbacks,
|
|
38
|
+
): void {
|
|
39
|
+
const client = BrowserInferenceClient.getInstance();
|
|
40
|
+
const messageId = generateClientId("msg");
|
|
41
|
+
const simpleMessages = extractTextFromMessages(messages);
|
|
42
|
+
|
|
43
|
+
let accumulated = "";
|
|
44
|
+
|
|
45
|
+
client.generate(
|
|
46
|
+
messageId,
|
|
47
|
+
simpleMessages,
|
|
48
|
+
{ systemPrompt, maxNewTokens: 512, temperature: 0.7 },
|
|
49
|
+
{
|
|
50
|
+
onStatus: (status) => callbacks.onStatusChange(status),
|
|
51
|
+
onDownloadProgress: (progress) => callbacks.onDownloadProgress?.(progress),
|
|
52
|
+
onToken: (token) => {
|
|
53
|
+
accumulated = token; // transformers.js callback_function sends full text each time
|
|
54
|
+
const parts: UIMessagePart[] = [
|
|
55
|
+
{ type: "text", text: accumulated, state: "streaming" },
|
|
56
|
+
];
|
|
57
|
+
callbacks.onUpdate(parts, messageId);
|
|
58
|
+
},
|
|
59
|
+
onDone: (text) => {
|
|
60
|
+
const finalParts: UIMessagePart[] = [
|
|
61
|
+
{ type: "text", text: text || accumulated, state: "done" },
|
|
62
|
+
];
|
|
63
|
+
const assistantMessage: UIMessage = {
|
|
64
|
+
id: messageId,
|
|
65
|
+
role: "assistant",
|
|
66
|
+
parts: finalParts,
|
|
67
|
+
};
|
|
68
|
+
callbacks.onMessage(assistantMessage);
|
|
69
|
+
callbacks.onStatusChange("ready");
|
|
70
|
+
},
|
|
71
|
+
onError: (errorMsg) => {
|
|
72
|
+
callbacks.onStatusChange("error");
|
|
73
|
+
callbacks.onError(new Error(errorMsg));
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function stopBrowserInference(): void {
|
|
80
|
+
BrowserInferenceClient.getInstance().stop();
|
|
81
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker ↔ main thread message protocol for browser-side inference.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface WorkerGenerateRequest {
|
|
6
|
+
type: "generate";
|
|
7
|
+
id: string;
|
|
8
|
+
messages: Array<{ role: string; content: string }>;
|
|
9
|
+
options?: {
|
|
10
|
+
maxNewTokens?: number;
|
|
11
|
+
temperature?: number;
|
|
12
|
+
systemPrompt?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type WorkerRequest = WorkerGenerateRequest;
|
|
17
|
+
|
|
18
|
+
export interface WorkerStatusResponse {
|
|
19
|
+
type: "status";
|
|
20
|
+
status: "loading-runtime" | "downloading-model" | "ready" | "generating";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WorkerDownloadProgressResponse {
|
|
24
|
+
type: "download-progress";
|
|
25
|
+
progress: number; // 0–100
|
|
26
|
+
file?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkerTokenResponse {
|
|
30
|
+
type: "token";
|
|
31
|
+
id: string;
|
|
32
|
+
token: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WorkerDoneResponse {
|
|
36
|
+
type: "done";
|
|
37
|
+
id: string;
|
|
38
|
+
text: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WorkerErrorResponse {
|
|
42
|
+
type: "error";
|
|
43
|
+
id: string;
|
|
44
|
+
error: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type WorkerResponse =
|
|
48
|
+
| WorkerStatusResponse
|
|
49
|
+
| WorkerDownloadProgressResponse
|
|
50
|
+
| WorkerTokenResponse
|
|
51
|
+
| WorkerDoneResponse
|
|
52
|
+
| WorkerErrorResponse;
|