veryfront 0.1.131 → 0.1.136
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/_dnt.polyfills.d.ts +11 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -1
- package/esm/_dnt.polyfills.js +14 -0
- package/esm/cli/commands/build/handler.js +2 -0
- package/esm/cli/commands/deploy/command.d.ts.map +1 -1
- package/esm/cli/commands/deploy/command.js +2 -0
- package/esm/cli/commands/files/command.d.ts.map +1 -1
- package/esm/cli/commands/files/command.js +1 -0
- package/esm/cli/commands/uploads/command.d.ts.map +1 -1
- package/esm/cli/commands/uploads/command.js +1 -0
- package/esm/cli/help/tips.d.ts +3 -0
- package/esm/cli/help/tips.d.ts.map +1 -1
- package/esm/cli/help/tips.js +15 -1
- package/esm/cli/router.d.ts.map +1 -1
- package/esm/cli/router.js +4 -0
- package/esm/cli/shared/animation.d.ts +3 -0
- package/esm/cli/shared/animation.d.ts.map +1 -0
- package/esm/cli/shared/animation.js +23 -0
- package/esm/cli/templates/manifest.d.ts +6 -0
- package/esm/cli/templates/manifest.js +12 -6
- package/esm/cli/ui/progress.d.ts.map +1 -1
- package/esm/cli/ui/progress.js +13 -1
- package/esm/deno.js +1 -1
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.d.ts.map +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.js +56 -5
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +21 -3
- package/esm/src/agent/runtime/model-tool-converter.d.ts +5 -1
- package/esm/src/agent/runtime/model-tool-converter.d.ts.map +1 -1
- package/esm/src/agent/runtime/model-tool-converter.js +35 -4
- package/esm/src/agent/runtime/tool-helpers.d.ts +2 -1
- package/esm/src/agent/runtime/tool-helpers.d.ts.map +1 -1
- package/esm/src/agent/runtime/tool-helpers.js +6 -3
- package/esm/src/agent/types.d.ts +19 -0
- package/esm/src/agent/types.d.ts.map +1 -1
- package/esm/src/channels/control-plane.d.ts +67 -0
- package/esm/src/channels/control-plane.d.ts.map +1 -1
- package/esm/src/channels/control-plane.js +27 -0
- package/esm/src/discovery/handlers/tool-handler.d.ts.map +1 -1
- package/esm/src/discovery/handlers/tool-handler.js +12 -2
- package/esm/src/html/html-injection.d.ts +2 -0
- package/esm/src/html/html-injection.d.ts.map +1 -1
- package/esm/src/html/html-injection.js +10 -5
- package/esm/src/html/nonce-injection.d.ts +3 -0
- package/esm/src/html/nonce-injection.d.ts.map +1 -0
- package/esm/src/html/nonce-injection.js +249 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts +1 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts.map +1 -1
- package/esm/src/internal-agents/ag-ui-sse.js +18 -0
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
- package/esm/src/internal-agents/run-stream.js +26 -4
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts +1 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +15 -1
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts +2 -0
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +2 -0
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +25 -5
- package/esm/src/react/components/Head.d.ts +9 -0
- package/esm/src/react/components/Head.d.ts.map +1 -1
- package/esm/src/react/components/Head.js +9 -0
- package/esm/src/react/context/index.d.ts +9 -0
- package/esm/src/react/context/index.d.ts.map +1 -1
- package/esm/src/react/context/index.js +9 -0
- package/esm/src/react/router/index.d.ts +9 -0
- package/esm/src/react/router/index.d.ts.map +1 -1
- package/esm/src/react/router/index.js +9 -0
- package/esm/src/rendering/orchestrator/html.d.ts +1 -0
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/html.js +81 -89
- package/esm/src/rendering/script-page-handling.js +1 -0
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +9 -0
- package/esm/src/server/handlers/request/ssr/ssr-response-builder.d.ts.map +1 -1
- package/esm/src/server/handlers/request/ssr/ssr-response-builder.js +3 -7
- package/esm/src/server/handlers/request/static.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/static.handler.js +18 -10
- package/esm/src/tool/factory.d.ts.map +1 -1
- package/esm/src/tool/factory.js +14 -4
- package/esm/src/tool/types.d.ts +2 -0
- package/esm/src/tool/types.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/constants.d.ts +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/constants.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/constants.js +3 -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 +4 -2
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts +1 -1
- 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 +10 -9
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.js +3 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/_dnt.polyfills.ts +27 -0
- package/src/cli/commands/build/handler.ts +3 -0
- package/src/cli/commands/deploy/command.ts +3 -0
- package/src/cli/commands/files/command.ts +1 -0
- package/src/cli/commands/uploads/command.ts +3 -0
- package/src/cli/help/tips.ts +18 -1
- package/src/cli/router.ts +5 -0
- package/src/cli/shared/animation.ts +25 -0
- package/src/cli/templates/manifest.js +12 -6
- package/src/cli/ui/progress.ts +13 -1
- package/src/deno.js +1 -1
- package/src/src/agent/index.ts +2 -0
- package/src/src/agent/runtime/ai-stream-handler.ts +64 -6
- package/src/src/agent/runtime/index.ts +26 -1
- package/src/src/agent/runtime/model-tool-converter.ts +47 -3
- package/src/src/agent/runtime/tool-helpers.ts +15 -3
- package/src/src/agent/types.ts +23 -0
- package/src/src/channels/control-plane.ts +31 -0
- package/src/src/discovery/handlers/tool-handler.ts +13 -2
- package/src/src/html/html-injection.ts +16 -5
- package/src/src/html/nonce-injection.ts +300 -0
- package/src/src/internal-agents/ag-ui-sse.ts +20 -0
- package/src/src/internal-agents/run-stream.ts +35 -4
- package/src/src/platform/adapters/fs/veryfront/proxy-manager.ts +29 -3
- package/src/src/platform/adapters/fs/veryfront/types.ts +2 -0
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +2 -0
- package/src/src/proxy/handler.ts +43 -5
- package/src/src/react/components/Head.tsx +10 -0
- package/src/src/react/context/index.tsx +10 -0
- package/src/src/react/router/index.tsx +10 -0
- package/src/src/rendering/orchestrator/html.ts +125 -100
- package/src/src/rendering/script-page-handling.ts +1 -0
- package/src/src/server/handlers/dev/framework-candidates.generated.ts +9 -0
- package/src/src/server/handlers/request/ssr/ssr-response-builder.ts +7 -11
- package/src/src/server/handlers/request/static.handler.ts +22 -10
- package/src/src/tool/factory.ts +17 -4
- package/src/src/tool/types.ts +2 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts +4 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts +4 -2
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +18 -15
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts +4 -1
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -77,6 +77,10 @@ import { resolveConfiguredAgentModel, resolveRuntimeModel } from "./model-resolu
|
|
|
77
77
|
const logger = serverLogger.component("agent");
|
|
78
78
|
const LOAD_SKILL_TOOL_ID = "load-skill";
|
|
79
79
|
|
|
80
|
+
type RuntimeToolFilterConfig = AgentConfig & {
|
|
81
|
+
__vfAllowedRemoteTools?: string[];
|
|
82
|
+
};
|
|
83
|
+
|
|
80
84
|
function createAbortError(reason?: unknown): Error {
|
|
81
85
|
if (reason instanceof Error) {
|
|
82
86
|
return reason;
|
|
@@ -187,6 +191,18 @@ function isLocalModel(model: LanguageModel): boolean {
|
|
|
187
191
|
(typeof m.modelId === "string" && m.modelId.startsWith("local/"));
|
|
188
192
|
}
|
|
189
193
|
|
|
194
|
+
function getRuntimeAllowedRemoteTools(config: AgentConfig): string[] | undefined {
|
|
195
|
+
const configWithRuntimeFilters = config as RuntimeToolFilterConfig;
|
|
196
|
+
if (!Object.hasOwn(configWithRuntimeFilters, "__vfAllowedRemoteTools")) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
const raw = configWithRuntimeFilters.__vfAllowedRemoteTools;
|
|
200
|
+
if (!Array.isArray(raw)) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
return raw.every((toolName) => typeof toolName === "string") ? raw : [];
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
export class AgentRuntime {
|
|
191
207
|
private id: string;
|
|
192
208
|
private config: AgentConfig;
|
|
@@ -418,6 +434,7 @@ export class AgentRuntime {
|
|
|
418
434
|
|
|
419
435
|
// Request-scoped skill policy (not class-level mutable state)
|
|
420
436
|
let activeSkillPolicy: string[] | undefined;
|
|
437
|
+
const allowedRemoteToolNames = getRuntimeAllowedRemoteTools(this.config);
|
|
421
438
|
|
|
422
439
|
for (let step = 0; step < maxSteps; step++) {
|
|
423
440
|
this.status = "thinking";
|
|
@@ -425,6 +442,7 @@ export class AgentRuntime {
|
|
|
425
442
|
|
|
426
443
|
let tools = isLocal ? [] : await getAvailableTools(this.config.tools, {
|
|
427
444
|
includeSkillTools: Boolean(this.config.skills),
|
|
445
|
+
allowedRemoteToolNames,
|
|
428
446
|
});
|
|
429
447
|
|
|
430
448
|
// Layer 1: Filter tools based on active skill policy (planning-time)
|
|
@@ -548,6 +566,7 @@ export class AgentRuntime {
|
|
|
548
566
|
toolCallId: tc.toolCallId,
|
|
549
567
|
projectId: cacheCtx?.projectId,
|
|
550
568
|
},
|
|
569
|
+
allowedRemoteToolNames,
|
|
551
570
|
);
|
|
552
571
|
|
|
553
572
|
toolCall.status = "completed";
|
|
@@ -662,6 +681,7 @@ export class AgentRuntime {
|
|
|
662
681
|
// Request-scoped skill policy (not class-level mutable state)
|
|
663
682
|
let activeSkillPolicy: string[] | undefined;
|
|
664
683
|
let finalFinishReason: string | undefined;
|
|
684
|
+
const allowedRemoteToolNames = getRuntimeAllowedRemoteTools(this.config);
|
|
665
685
|
|
|
666
686
|
for (let step = 0; step < maxSteps; step++) {
|
|
667
687
|
throwIfAborted(abortSignal);
|
|
@@ -669,6 +689,7 @@ export class AgentRuntime {
|
|
|
669
689
|
|
|
670
690
|
let tools = isLocalStreaming ? [] : await getAvailableTools(this.config.tools, {
|
|
671
691
|
includeSkillTools: Boolean(this.config.skills),
|
|
692
|
+
allowedRemoteToolNames,
|
|
672
693
|
});
|
|
673
694
|
|
|
674
695
|
// Layer 1: Filter tools based on active skill policy (planning-time)
|
|
@@ -680,7 +701,10 @@ export class AgentRuntime {
|
|
|
680
701
|
model: languageModel,
|
|
681
702
|
system: systemPrompt,
|
|
682
703
|
messages: convertToModelMessages(currentMessages),
|
|
683
|
-
tools: convertToolsToAISDK(tools
|
|
704
|
+
tools: convertToolsToAISDK(tools, {
|
|
705
|
+
model: effectiveModel,
|
|
706
|
+
allowedToolNames: allowedRemoteToolNames,
|
|
707
|
+
}),
|
|
684
708
|
maxOutputTokens: this.resolveMaxOutputTokens(maxOutputTokensOverride),
|
|
685
709
|
temperature: DEFAULT_TEMPERATURE,
|
|
686
710
|
abortSignal,
|
|
@@ -791,6 +815,7 @@ export class AgentRuntime {
|
|
|
791
815
|
toolCallId: tc.id,
|
|
792
816
|
...toolContext,
|
|
793
817
|
},
|
|
818
|
+
allowedRemoteToolNames,
|
|
794
819
|
);
|
|
795
820
|
throwIfAborted(abortSignal);
|
|
796
821
|
|
|
@@ -10,6 +10,42 @@
|
|
|
10
10
|
import { jsonSchema, tool } from "ai";
|
|
11
11
|
import type { ToolSet } from "ai";
|
|
12
12
|
import type { ToolDefinition } from "../../tool/index.js";
|
|
13
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
14
|
+
|
|
15
|
+
export interface ConvertToolsToAISDKOptions {
|
|
16
|
+
model?: string;
|
|
17
|
+
allowedToolNames?: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveHostedProvider(model?: string): string | undefined {
|
|
21
|
+
if (!model) return undefined;
|
|
22
|
+
|
|
23
|
+
const [provider, second] = model.split("/", 3);
|
|
24
|
+
if (!provider) return undefined;
|
|
25
|
+
if (provider === "veryfront-cloud") {
|
|
26
|
+
return second || undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return provider;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveProviderNativeTools(
|
|
33
|
+
options?: ConvertToolsToAISDKOptions,
|
|
34
|
+
): ToolSet | undefined {
|
|
35
|
+
if (!options?.allowedToolNames?.includes("web_search")) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (resolveHostedProvider(options.model) !== "anthropic") {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
web_search: anthropic.tools.webSearch_20250305({
|
|
45
|
+
maxUses: 5,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
13
49
|
|
|
14
50
|
/**
|
|
15
51
|
* Convert veryfront tool definitions to AI SDK ToolSet.
|
|
@@ -19,9 +55,8 @@ import type { ToolDefinition } from "../../tool/index.js";
|
|
|
19
55
|
*/
|
|
20
56
|
export function convertToolsToAISDK(
|
|
21
57
|
tools: ToolDefinition[],
|
|
58
|
+
options?: ConvertToolsToAISDKOptions,
|
|
22
59
|
): ToolSet | undefined {
|
|
23
|
-
if (!tools.length) return undefined;
|
|
24
|
-
|
|
25
60
|
const toolSet: ToolSet = {};
|
|
26
61
|
|
|
27
62
|
for (const def of tools) {
|
|
@@ -31,5 +66,14 @@ export function convertToolsToAISDK(
|
|
|
31
66
|
});
|
|
32
67
|
}
|
|
33
68
|
|
|
34
|
-
|
|
69
|
+
const providerNativeTools = resolveProviderNativeTools(options);
|
|
70
|
+
if (providerNativeTools) {
|
|
71
|
+
for (const [name, providerTool] of Object.entries(providerNativeTools)) {
|
|
72
|
+
if (!Object.hasOwn(toolSet, name)) {
|
|
73
|
+
toolSet[name] = providerTool;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Object.keys(toolSet).length > 0 ? toolSet : undefined;
|
|
35
79
|
}
|
|
@@ -100,6 +100,7 @@ export async function executeConfiguredTool(
|
|
|
100
100
|
input: Record<string, unknown>,
|
|
101
101
|
toolsConfig: true | Record<string, ToolConfigEntry> | undefined,
|
|
102
102
|
context?: ToolExecutionContext,
|
|
103
|
+
allowedRemoteToolNames?: string[],
|
|
103
104
|
): Promise<unknown> {
|
|
104
105
|
const configuredTool = resolveConfiguredTool(toolsConfig, toolName);
|
|
105
106
|
if (configuredTool) {
|
|
@@ -114,6 +115,9 @@ export async function executeConfiguredTool(
|
|
|
114
115
|
|
|
115
116
|
// Fall back to remote execution for integration tools (e.g., github:list-repos)
|
|
116
117
|
if (isRemoteIntegrationTool(toolName)) {
|
|
118
|
+
if (allowedRemoteToolNames && !allowedRemoteToolNames.includes(toolName)) {
|
|
119
|
+
throw new Error(`Tool "${toolName}" is not allowed for this run`);
|
|
120
|
+
}
|
|
117
121
|
return await executeRemoteIntegrationTool(
|
|
118
122
|
toolName,
|
|
119
123
|
input,
|
|
@@ -152,7 +156,11 @@ function addToolDefinition(
|
|
|
152
156
|
*/
|
|
153
157
|
export async function getAvailableTools(
|
|
154
158
|
toolsConfig: true | Record<string, ToolConfigEntry> | undefined,
|
|
155
|
-
options?: {
|
|
159
|
+
options?: {
|
|
160
|
+
includeSkillTools?: boolean;
|
|
161
|
+
includeIntegrationTools?: boolean;
|
|
162
|
+
allowedRemoteToolNames?: string[];
|
|
163
|
+
},
|
|
156
164
|
): Promise<ToolDefinition[]> {
|
|
157
165
|
if (!toolsConfig) return [];
|
|
158
166
|
|
|
@@ -176,7 +184,9 @@ export async function getAvailableTools(
|
|
|
176
184
|
const { getRemoteIntegrationToolDefinitions } = await import(
|
|
177
185
|
"../../integrations/remote-tools.js"
|
|
178
186
|
);
|
|
179
|
-
const remoteDefs = await getRemoteIntegrationToolDefinitions()
|
|
187
|
+
const remoteDefs = (await getRemoteIntegrationToolDefinitions()).filter((def) =>
|
|
188
|
+
!options?.allowedRemoteToolNames || options.allowedRemoteToolNames.includes(def.name)
|
|
189
|
+
);
|
|
180
190
|
for (const def of remoteDefs) {
|
|
181
191
|
logToolDefinition(def.name, def);
|
|
182
192
|
}
|
|
@@ -211,7 +221,9 @@ export async function getAvailableTools(
|
|
|
211
221
|
const { getRemoteIntegrationToolDefinitions } = await import(
|
|
212
222
|
"../../integrations/remote-tools.js"
|
|
213
223
|
);
|
|
214
|
-
const remoteDefs = await getRemoteIntegrationToolDefinitions()
|
|
224
|
+
const remoteDefs = (await getRemoteIntegrationToolDefinitions()).filter((def) =>
|
|
225
|
+
!options?.allowedRemoteToolNames || options.allowedRemoteToolNames.includes(def.name)
|
|
226
|
+
);
|
|
215
227
|
for (const def of remoteDefs) {
|
|
216
228
|
// Skip if already present (e.g., explicitly configured by name)
|
|
217
229
|
if (!tools.some((t) => t.name === def.name)) {
|
package/src/src/agent/types.ts
CHANGED
|
@@ -45,6 +45,28 @@ export type ModelString = string;
|
|
|
45
45
|
// Import for use in AgentConfig
|
|
46
46
|
import type { EdgeConfig, MemoryConfig } from "./schemas/index.js";
|
|
47
47
|
|
|
48
|
+
export type AgentSuggestion =
|
|
49
|
+
| {
|
|
50
|
+
id: string;
|
|
51
|
+
type: "prompt";
|
|
52
|
+
title: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
prompt: string;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
id: string;
|
|
58
|
+
type: "task";
|
|
59
|
+
title: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
task: string;
|
|
62
|
+
prompt?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export interface AgentSuggestions {
|
|
66
|
+
welcomeMessage?: string;
|
|
67
|
+
suggestions: AgentSuggestion[];
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
export interface AgentConfig {
|
|
49
71
|
id?: string;
|
|
50
72
|
/**
|
|
@@ -78,6 +100,7 @@ export interface AgentConfig {
|
|
|
78
100
|
* and registers the skill tools.
|
|
79
101
|
*/
|
|
80
102
|
skills?: true | string[];
|
|
103
|
+
suggestions?: AgentSuggestions;
|
|
81
104
|
/** Set to false to disable the default security middleware */
|
|
82
105
|
security?: false;
|
|
83
106
|
}
|
|
@@ -29,6 +29,29 @@ export const RuntimeAgentSkillSchema = z.object({
|
|
|
29
29
|
examples: z.array(z.string()).optional(),
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
export const RuntimeAgentSuggestionSchema = z.discriminatedUnion("type", [
|
|
33
|
+
z.object({
|
|
34
|
+
id: z.string().min(1),
|
|
35
|
+
type: z.literal("prompt"),
|
|
36
|
+
title: z.string().min(1),
|
|
37
|
+
description: z.string().optional(),
|
|
38
|
+
prompt: z.string().min(1),
|
|
39
|
+
}),
|
|
40
|
+
z.object({
|
|
41
|
+
id: z.string().min(1),
|
|
42
|
+
type: z.literal("task"),
|
|
43
|
+
title: z.string().min(1),
|
|
44
|
+
description: z.string().optional(),
|
|
45
|
+
task: z.string().min(1),
|
|
46
|
+
prompt: z.string().min(1).optional(),
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
export const RuntimeAgentSuggestionsSchema = z.object({
|
|
51
|
+
welcomeMessage: z.string().min(1).optional(),
|
|
52
|
+
suggestions: z.array(RuntimeAgentSuggestionSchema),
|
|
53
|
+
});
|
|
54
|
+
|
|
32
55
|
export const RuntimeAgentSchema = z.object({
|
|
33
56
|
id: z.string().min(1),
|
|
34
57
|
name: z.string().min(1),
|
|
@@ -36,6 +59,7 @@ export const RuntimeAgentSchema = z.object({
|
|
|
36
59
|
model: z.string().nullable().optional(),
|
|
37
60
|
version: z.string().nullable().optional(),
|
|
38
61
|
skills: z.array(RuntimeAgentSkillSchema).optional(),
|
|
62
|
+
suggestions: RuntimeAgentSuggestionsSchema.optional(),
|
|
39
63
|
});
|
|
40
64
|
|
|
41
65
|
export const RuntimeAgentListResponseSchema = z.object({
|
|
@@ -67,6 +91,8 @@ const controlPlaneClaimsSchema = z.object({
|
|
|
67
91
|
export type ControlPlaneSurface = z.infer<typeof ControlPlaneSurfaceSchema>;
|
|
68
92
|
export type ControlPlaneAgentsListRequest = z.infer<typeof ControlPlaneAgentsListRequestSchema>;
|
|
69
93
|
export type RuntimeAgentSkill = z.infer<typeof RuntimeAgentSkillSchema>;
|
|
94
|
+
export type RuntimeAgentSuggestion = z.infer<typeof RuntimeAgentSuggestionSchema>;
|
|
95
|
+
export type RuntimeAgentSuggestions = z.infer<typeof RuntimeAgentSuggestionsSchema>;
|
|
70
96
|
export type RuntimeAgent = z.infer<typeof RuntimeAgentSchema>;
|
|
71
97
|
export type RuntimeAgentListResponse = z.infer<typeof RuntimeAgentListResponseSchema>;
|
|
72
98
|
export type DispatchClaims = z.infer<typeof dispatchClaimsSchema>;
|
|
@@ -235,6 +261,10 @@ function resolveAgentSkills(agent: Agent): RuntimeAgentSkill[] {
|
|
|
235
261
|
|
|
236
262
|
function getRuntimeAgentMetadata(id: string, agent: Agent): RuntimeAgent {
|
|
237
263
|
const rawConfig = agent.config as unknown as Record<string, unknown>;
|
|
264
|
+
const suggestionsParseResult = rawConfig.suggestions === undefined
|
|
265
|
+
? null
|
|
266
|
+
: RuntimeAgentSuggestionsSchema.safeParse(rawConfig.suggestions);
|
|
267
|
+
const suggestions = suggestionsParseResult?.success ? suggestionsParseResult.data : undefined;
|
|
238
268
|
|
|
239
269
|
return RuntimeAgentSchema.parse({
|
|
240
270
|
id,
|
|
@@ -245,6 +275,7 @@ function getRuntimeAgentMetadata(id: string, agent: Agent): RuntimeAgent {
|
|
|
245
275
|
model: agent.config.model ?? null,
|
|
246
276
|
version: typeof rawConfig.version === "string" ? rawConfig.version : null,
|
|
247
277
|
skills: resolveAgentSkills(agent),
|
|
278
|
+
...(suggestions === undefined ? {} : { suggestions }),
|
|
248
279
|
});
|
|
249
280
|
}
|
|
250
281
|
|
|
@@ -7,13 +7,24 @@ import { registerTool } from "../../mcp/index.js";
|
|
|
7
7
|
import type { DiscoveryHandler } from "../types.js";
|
|
8
8
|
import { filenameToId } from "../discovery-utils.js";
|
|
9
9
|
|
|
10
|
+
function hasGeneratedToolId(tool: Tool): boolean {
|
|
11
|
+
return typeof tool.__veryfrontGeneratedId === "string" &&
|
|
12
|
+
tool.id === tool.__veryfrontGeneratedId;
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export const toolHandler: DiscoveryHandler<Tool> = {
|
|
11
16
|
typeName: "tool",
|
|
12
17
|
validate: (item): item is Tool =>
|
|
13
18
|
item !== null && typeof item === "object" && typeof (item as Tool).execute === "function",
|
|
14
|
-
getId: (
|
|
19
|
+
getId: (tool, file) => {
|
|
20
|
+
return typeof tool.id === "string" &&
|
|
21
|
+
tool.id.trim().length > 0 &&
|
|
22
|
+
!hasGeneratedToolId(tool)
|
|
23
|
+
? tool.id
|
|
24
|
+
: filenameToId(file);
|
|
25
|
+
},
|
|
15
26
|
register: (id, tool) => {
|
|
16
|
-
const toolWithId = { ...tool, id };
|
|
27
|
+
const toolWithId = tool.id === id ? tool : { ...tool, id };
|
|
17
28
|
registerTool(id, toolWithId);
|
|
18
29
|
return toolWithId;
|
|
19
30
|
},
|
|
@@ -44,6 +44,8 @@ export interface InjectHTMLContentOptions {
|
|
|
44
44
|
yjsGuid?: string;
|
|
45
45
|
/** Pre-built import map JSON for ESM module resolution (injected into <head>) */
|
|
46
46
|
importMapJson?: string;
|
|
47
|
+
/** Framework-generated project stylesheet for production shells */
|
|
48
|
+
projectStylesheetHref?: string;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function toProjectRelativePath(absolutePath: string, projectDir?: string): string {
|
|
@@ -96,6 +98,11 @@ export function injectHTMLContent(
|
|
|
96
98
|
html = html.replace(/<\/head>/i, `${importMapTag}\n</head>`);
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
if (options.projectStylesheetHref && /<\/head>/i.test(html) && !hasProjectStylesheet(html)) {
|
|
102
|
+
const projectStylesheetTag = `<link rel="stylesheet" href="${options.projectStylesheetHref}">`;
|
|
103
|
+
html = html.replace(/<\/head>/i, `${projectStylesheetTag}\n</head>`);
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
const shouldUsePreviewStylesheet = options.mode === "development" ||
|
|
100
107
|
options.environment === "preview";
|
|
101
108
|
|
|
@@ -116,8 +123,9 @@ export function injectHTMLContent(
|
|
|
116
123
|
environment: options.environment,
|
|
117
124
|
}),
|
|
118
125
|
});
|
|
126
|
+
const nonceAttr = buildNonceAttribute(options.nonce);
|
|
119
127
|
const hydrationScript =
|
|
120
|
-
`<script id="veryfront-hydration-data" type="application/json">${hydrationData}</script>`;
|
|
128
|
+
`<script id="veryfront-hydration-data" type="application/json"${nonceAttr}>${hydrationData}</script>`;
|
|
121
129
|
html = html.replace(/<\/body>/i, `${hydrationScript}</body>`);
|
|
122
130
|
}
|
|
123
131
|
|
|
@@ -125,19 +133,22 @@ export function injectHTMLContent(
|
|
|
125
133
|
const hasDevScriptsPlaceholder = /{{\s*devScripts\s*}}/i.test(html);
|
|
126
134
|
|
|
127
135
|
if (hasDevScriptsPlaceholder) {
|
|
128
|
-
html = html.replace(/{{\s*devScripts\s*}}/gi, getDevScripts());
|
|
136
|
+
html = html.replace(/{{\s*devScripts\s*}}/gi, getDevScripts(options.devPort, options.nonce));
|
|
129
137
|
}
|
|
130
138
|
|
|
131
|
-
html = html.replace(/{{\s*devStyles\s*}}/gi, getDevStyles());
|
|
139
|
+
html = html.replace(/{{\s*devStyles\s*}}/gi, getDevStyles(options.nonce));
|
|
132
140
|
|
|
133
141
|
if (!hasDevScriptsPlaceholder && hasBodyClose) {
|
|
134
|
-
html = html.replace(
|
|
142
|
+
html = html.replace(
|
|
143
|
+
/<\/body>/i,
|
|
144
|
+
`${getDevStyles(options.nonce)}${getDevScripts(options.devPort, options.nonce)}</body>`,
|
|
145
|
+
);
|
|
135
146
|
}
|
|
136
147
|
} else {
|
|
137
148
|
html = html.replace(/{{\s*devScripts\s*}}/gi, "");
|
|
138
149
|
html = html.replace(/{{\s*devStyles\s*}}/gi, "");
|
|
139
150
|
|
|
140
|
-
const prodScripts = getProdScripts(options.slug);
|
|
151
|
+
const prodScripts = getProdScripts(options.slug, options.nonce);
|
|
141
152
|
const hasProdScriptsPlaceholder = /{{\s*prodScripts\s*}}/i.test(html);
|
|
142
153
|
|
|
143
154
|
if (hasProdScriptsPlaceholder) {
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { escapeHtml } from "../utils/html-escape.js";
|
|
2
|
+
|
|
3
|
+
function findTagEnd(html: string, start: number): number {
|
|
4
|
+
let activeQuote: '"' | "'" | null = null;
|
|
5
|
+
|
|
6
|
+
for (let index = start + 1; index < html.length; index++) {
|
|
7
|
+
const char = html[index];
|
|
8
|
+
|
|
9
|
+
if (activeQuote) {
|
|
10
|
+
if (char === activeQuote) activeQuote = null;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (char === '"' || char === "'") {
|
|
15
|
+
activeQuote = char;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (char === ">") return index;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return -1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getOpeningTagName(tag: string): "script" | "style" | undefined {
|
|
26
|
+
const match = /^<\s*([a-zA-Z][\w:-]*)/u.exec(tag);
|
|
27
|
+
const tagName = match?.[1]?.toLowerCase();
|
|
28
|
+
if (tagName === "script" || tagName === "style") return tagName;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isTagBoundary(char: string | undefined): boolean {
|
|
33
|
+
return char === undefined || /\s|\/|>/u.test(char);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findRawTextClosingTagStart(
|
|
37
|
+
lowerHtml: string,
|
|
38
|
+
tagName: "script" | "style",
|
|
39
|
+
fromIndex: number,
|
|
40
|
+
): number {
|
|
41
|
+
const needle = `</${tagName}`;
|
|
42
|
+
let searchIndex = fromIndex;
|
|
43
|
+
|
|
44
|
+
while (searchIndex < lowerHtml.length) {
|
|
45
|
+
const closingIndex = lowerHtml.indexOf(needle, searchIndex);
|
|
46
|
+
if (closingIndex === -1) return -1;
|
|
47
|
+
|
|
48
|
+
if (isTagBoundary(lowerHtml[closingIndex + needle.length])) {
|
|
49
|
+
return closingIndex;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
searchIndex = closingIndex + needle.length;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return -1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface ParsedAttribute {
|
|
59
|
+
name: string;
|
|
60
|
+
start: number;
|
|
61
|
+
end: number;
|
|
62
|
+
value: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function findAttribute(tag: string, attributeName: string): ParsedAttribute | undefined {
|
|
66
|
+
const closeIndex = tag.lastIndexOf(">");
|
|
67
|
+
if (closeIndex <= 0) return undefined;
|
|
68
|
+
|
|
69
|
+
let index = 1;
|
|
70
|
+
while (index < closeIndex && !/\s|\/|>/u.test(tag[index] ?? "")) index++;
|
|
71
|
+
|
|
72
|
+
while (index < closeIndex) {
|
|
73
|
+
while (index < closeIndex && /\s/u.test(tag[index] ?? "")) index++;
|
|
74
|
+
if (index >= closeIndex) break;
|
|
75
|
+
|
|
76
|
+
const char = tag[index];
|
|
77
|
+
if (char === "/" || char === ">") break;
|
|
78
|
+
|
|
79
|
+
const start = index;
|
|
80
|
+
while (index < closeIndex && !/[\s=/>]/u.test(tag[index] ?? "")) index++;
|
|
81
|
+
const name = tag.slice(start, index);
|
|
82
|
+
|
|
83
|
+
while (index < closeIndex && /\s/u.test(tag[index] ?? "")) index++;
|
|
84
|
+
|
|
85
|
+
let value: string | null = null;
|
|
86
|
+
if (tag[index] === "=") {
|
|
87
|
+
index++;
|
|
88
|
+
while (index < closeIndex && /\s/u.test(tag[index] ?? "")) index++;
|
|
89
|
+
|
|
90
|
+
const quote = tag[index];
|
|
91
|
+
if (quote === '"' || quote === "'") {
|
|
92
|
+
index++;
|
|
93
|
+
const valueStart = index;
|
|
94
|
+
while (index < closeIndex && tag[index] !== quote) index++;
|
|
95
|
+
value = tag.slice(valueStart, index);
|
|
96
|
+
if (index < closeIndex) index++;
|
|
97
|
+
} else {
|
|
98
|
+
const valueStart = index;
|
|
99
|
+
while (index < closeIndex && !/[\s>]/u.test(tag[index] ?? "")) index++;
|
|
100
|
+
value = tag.slice(valueStart, index);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (name.toLowerCase() === attributeName) {
|
|
105
|
+
return { name, start, end: index, value };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function injectNonceIntoOpeningTag(tag: string, escapedNonce: string): string {
|
|
113
|
+
const existingNonce = findAttribute(tag, "nonce");
|
|
114
|
+
if (existingNonce) {
|
|
115
|
+
if (existingNonce.value?.trim()) return tag;
|
|
116
|
+
|
|
117
|
+
return `${tag.slice(0, existingNonce.start)}nonce="${escapedNonce}"${
|
|
118
|
+
tag.slice(existingNonce.end)
|
|
119
|
+
}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const closeIndex = tag.lastIndexOf(">");
|
|
123
|
+
if (closeIndex === -1) return tag;
|
|
124
|
+
|
|
125
|
+
const insertAt = /\/\s*>$/u.test(tag) ? closeIndex - 1 : closeIndex;
|
|
126
|
+
return `${tag.slice(0, insertAt)} nonce="${escapedNonce}"${tag.slice(insertAt)}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function addNonceToHtmlTags(html: string, nonce?: string): string {
|
|
130
|
+
if (!nonce) return html;
|
|
131
|
+
|
|
132
|
+
const escapedNonce = escapeHtml(nonce);
|
|
133
|
+
const lowerHtml = html.toLowerCase();
|
|
134
|
+
let result = "";
|
|
135
|
+
let index = 0;
|
|
136
|
+
let rawTextTag: "script" | "style" | null = null;
|
|
137
|
+
|
|
138
|
+
while (index < html.length) {
|
|
139
|
+
if (rawTextTag) {
|
|
140
|
+
const closingIndex = findRawTextClosingTagStart(lowerHtml, rawTextTag, index);
|
|
141
|
+
if (closingIndex === -1) {
|
|
142
|
+
result += html.slice(index);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
result += html.slice(index, closingIndex);
|
|
147
|
+
index = closingIndex;
|
|
148
|
+
rawTextTag = null;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (html.startsWith("<!--", index)) {
|
|
153
|
+
const commentEnd = html.indexOf("-->", index + 4);
|
|
154
|
+
const endIndex = commentEnd === -1 ? html.length : commentEnd + 3;
|
|
155
|
+
result += html.slice(index, endIndex);
|
|
156
|
+
index = endIndex;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (html[index] !== "<") {
|
|
161
|
+
result += html[index];
|
|
162
|
+
index++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const tagEnd = findTagEnd(html, index);
|
|
167
|
+
if (tagEnd === -1) {
|
|
168
|
+
result += html.slice(index);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const tag = html.slice(index, tagEnd + 1);
|
|
173
|
+
const tagName = getOpeningTagName(tag);
|
|
174
|
+
|
|
175
|
+
if (!tagName) {
|
|
176
|
+
result += tag;
|
|
177
|
+
index = tagEnd + 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
result += injectNonceIntoOpeningTag(tag, escapedNonce);
|
|
182
|
+
index = tagEnd + 1;
|
|
183
|
+
|
|
184
|
+
if (!/\/\s*>$/u.test(tag)) {
|
|
185
|
+
rawTextTag = tagName;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function addNonceToHtmlStream(
|
|
193
|
+
stream: ReadableStream<Uint8Array>,
|
|
194
|
+
nonce?: string,
|
|
195
|
+
): ReadableStream<Uint8Array> {
|
|
196
|
+
if (!nonce) return stream;
|
|
197
|
+
|
|
198
|
+
const escapedNonce = escapeHtml(nonce);
|
|
199
|
+
const decoder = new TextDecoder();
|
|
200
|
+
const encoder = new TextEncoder();
|
|
201
|
+
let buffer = "";
|
|
202
|
+
let rawTextTag: "script" | "style" | null = null;
|
|
203
|
+
|
|
204
|
+
function transformBuffer(flush: boolean): string {
|
|
205
|
+
let result = "";
|
|
206
|
+
let index = 0;
|
|
207
|
+
const lowerBuffer = buffer.toLowerCase();
|
|
208
|
+
|
|
209
|
+
while (index < buffer.length) {
|
|
210
|
+
if (rawTextTag) {
|
|
211
|
+
const closingIndex = findRawTextClosingTagStart(lowerBuffer, rawTextTag, index);
|
|
212
|
+
if (closingIndex === -1) {
|
|
213
|
+
if (flush) {
|
|
214
|
+
result += buffer.slice(index);
|
|
215
|
+
index = buffer.length;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const retainLength = `</${rawTextTag}`.length;
|
|
220
|
+
const safeEnd = Math.max(index, buffer.length - retainLength);
|
|
221
|
+
result += buffer.slice(index, safeEnd);
|
|
222
|
+
index = safeEnd;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
result += buffer.slice(index, closingIndex);
|
|
227
|
+
index = closingIndex;
|
|
228
|
+
rawTextTag = null;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (buffer.startsWith("<!--", index)) {
|
|
233
|
+
const commentEnd = buffer.indexOf("-->", index + 4);
|
|
234
|
+
if (commentEnd === -1) {
|
|
235
|
+
if (flush) {
|
|
236
|
+
result += buffer.slice(index);
|
|
237
|
+
index = buffer.length;
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const endIndex = commentEnd + 3;
|
|
243
|
+
result += buffer.slice(index, endIndex);
|
|
244
|
+
index = endIndex;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (buffer[index] !== "<") {
|
|
249
|
+
const nextTagIndex = buffer.indexOf("<", index);
|
|
250
|
+
const endIndex = nextTagIndex === -1 ? buffer.length : nextTagIndex;
|
|
251
|
+
result += buffer.slice(index, endIndex);
|
|
252
|
+
index = endIndex;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const tagEnd = findTagEnd(buffer, index);
|
|
257
|
+
if (tagEnd === -1) {
|
|
258
|
+
if (flush) {
|
|
259
|
+
result += buffer.slice(index);
|
|
260
|
+
index = buffer.length;
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const tag = buffer.slice(index, tagEnd + 1);
|
|
266
|
+
const tagName = getOpeningTagName(tag);
|
|
267
|
+
|
|
268
|
+
if (!tagName) {
|
|
269
|
+
result += tag;
|
|
270
|
+
index = tagEnd + 1;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
result += injectNonceIntoOpeningTag(tag, escapedNonce);
|
|
275
|
+
index = tagEnd + 1;
|
|
276
|
+
|
|
277
|
+
if (!/\/\s*>$/u.test(tag)) {
|
|
278
|
+
rawTextTag = tagName;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
buffer = buffer.slice(index);
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return stream.pipeThrough(
|
|
287
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
288
|
+
transform(chunk, controller) {
|
|
289
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
290
|
+
const transformed = transformBuffer(false);
|
|
291
|
+
if (transformed) controller.enqueue(encoder.encode(transformed));
|
|
292
|
+
},
|
|
293
|
+
flush(controller) {
|
|
294
|
+
buffer += decoder.decode();
|
|
295
|
+
const transformed = transformBuffer(true);
|
|
296
|
+
if (transformed) controller.enqueue(encoder.encode(transformed));
|
|
297
|
+
},
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
300
|
+
}
|