veryfront 0.1.157 → 0.1.159

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.
@@ -1,4 +1,11 @@
1
1
  import type { APIRoute, LoadModuleOptions } from "./types.js";
2
+ import type { FileSystem } from "../../../platform/compat/fs.js";
2
3
  export declare function toCjsDestructureBindings(bindings: string): string;
3
4
  export declare function loadHandlerModule(options: LoadModuleOptions): Promise<APIRoute | null>;
5
+ export declare function getNodeExternalPackagesToResolve(userDeps: Map<string, string>): string[];
6
+ export declare function resolveNodePackageToFileUrl(projectDir: string, packageName: string, fs: FileSystem, pathToFileURL: typeof import("node:url").pathToFileURL): Promise<string | null>;
7
+ export declare function loadVeryfrontExportsMap(projectDir: string, fs: FileSystem): Promise<Record<string, {
8
+ import?: string;
9
+ }>>;
10
+ export declare function rewriteNodeExternalImports(code: string, projectDir: string, fs: FileSystem, userDeps: Map<string, string>): Promise<string>;
4
11
  //# sourceMappingURL=loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/loader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgL9D,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAejE;AAgBD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyBtF"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/routing/api/module-loader/loader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAI9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AA4KjE,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAejE;AAgBD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyBtF;AAuaD,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAUxF;AAED,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,cAAc,UAAU,EAAE,aAAa,GACrD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxB;AAED,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAW9C;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,CA6EjB"}
@@ -512,105 +512,115 @@ async function loadModuleFromCode(code, projectDir, fs, userDeps = new Map()) {
512
512
  await fs.remove(tempDir, { recursive: true });
513
513
  }
514
514
  }
515
- async function rewriteExternalImports(code, projectDir, fs, userDeps = new Map()) {
515
+ export function getNodeExternalPackagesToResolve(userDeps) {
516
+ const externalPackagesToResolve = ["zod"];
517
+ for (const name of userDeps.keys()) {
518
+ if (!externalPackagesToResolve.includes(name)) {
519
+ externalPackagesToResolve.push(name);
520
+ }
521
+ }
522
+ return externalPackagesToResolve;
523
+ }
524
+ export async function resolveNodePackageToFileUrl(projectDir, packageName, fs, pathToFileURL) {
525
+ const packagePath = pathHelper.join(projectDir, "node_modules", packageName);
526
+ const packageJsonPath = pathHelper.join(packagePath, "package.json");
527
+ try {
528
+ const pkgJson = JSON.parse(await fs.readTextFile(packageJsonPath));
529
+ let entryPoint;
530
+ if (pkgJson.exports) {
531
+ entryPoint = resolveExportEntry(pkgJson.exports["."]);
532
+ }
533
+ entryPoint ||= pkgJson.module || pkgJson.main || "index.js";
534
+ if (!entryPoint)
535
+ return null;
536
+ return pathToFileURL(pathHelper.join(packagePath, entryPoint)).href;
537
+ }
538
+ catch (_) {
539
+ /* expected: package.json may not exist or be invalid */
540
+ return null;
541
+ }
542
+ }
543
+ export async function loadVeryfrontExportsMap(projectDir, fs) {
544
+ const vfPackagePath = pathHelper.join(projectDir, "node_modules", "veryfront");
545
+ const vfPackageJsonPath = pathHelper.join(vfPackagePath, "package.json");
546
+ try {
547
+ const pkgJson = JSON.parse(await fs.readTextFile(vfPackageJsonPath));
548
+ return pkgJson.exports || {};
549
+ }
550
+ catch (_error) {
551
+ logger.debug("Could not read veryfront package.json");
552
+ return {};
553
+ }
554
+ }
555
+ export async function rewriteNodeExternalImports(code, projectDir, fs, userDeps) {
556
+ const { pathToFileURL } = await import("node:url");
516
557
  let transformed = code;
517
- if (isNode) {
518
- try {
519
- const { pathToFileURL } = await import("node:url");
520
- logger.debug(`Rewriting external imports for Node.js, projectDir: ${projectDir}`);
521
- const resolvePackageToFileUrl = async (packageName) => {
522
- const packagePath = pathHelper.join(projectDir, "node_modules", packageName);
523
- const packageJsonPath = pathHelper.join(packagePath, "package.json");
524
- try {
525
- const pkgJson = JSON.parse(await fs.readTextFile(packageJsonPath));
526
- let entryPoint;
527
- if (pkgJson.exports) {
528
- entryPoint = resolveExportEntry(pkgJson.exports["."]);
529
- }
530
- entryPoint ||= pkgJson.module || pkgJson.main || "index.js";
531
- if (!entryPoint)
532
- return null;
533
- return pathToFileURL(pathHelper.join(packagePath, entryPoint)).href;
534
- }
535
- catch (_) {
536
- /* expected: package.json may not exist or be invalid */
537
- return null;
538
- }
539
- };
540
- const externalPackagesToResolve = ["zod"];
541
- for (const name of userDeps.keys()) {
542
- if (!externalPackagesToResolve.includes(name)) {
543
- externalPackagesToResolve.push(name);
544
- }
545
- }
546
- for (const pkg of externalPackagesToResolve) {
547
- const escapedPkg = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
548
- // Match both exact imports (from "pkg") and subpath imports (from "pkg/sub")
549
- const staticImportRegex = new RegExp(`from\\s*["']${escapedPkg}(/[^"']*)?["']`, "g");
550
- const dynamicImportRegex = new RegExp(`import\\s*\\(\\s*["']${escapedPkg}(/[^"']*)?["']\\s*\\)`, "g");
551
- const needsStatic = staticImportRegex.test(transformed);
552
- staticImportRegex.lastIndex = 0;
553
- const needsDynamic = dynamicImportRegex.test(transformed);
554
- dynamicImportRegex.lastIndex = 0;
555
- if (!needsStatic && !needsDynamic)
556
- continue;
557
- const packageDir = pathToFileURL(pathHelper.join(projectDir, "node_modules", pkg)).href;
558
- const resolvedUrl = await resolvePackageToFileUrl(pkg);
559
- if (needsStatic) {
560
- transformed = transformed.replace(staticImportRegex, (_, subpath) => {
561
- if (subpath) {
562
- const subUrl = `${packageDir}${subpath}`;
563
- logger.debug(`Resolved ${pkg}${subpath} -> ${subUrl}`);
564
- return `from "${subUrl}"`;
565
- }
566
- if (!resolvedUrl)
567
- return `from "${pkg}"`;
568
- logger.debug(`Resolved ${pkg} -> ${resolvedUrl}`);
569
- return `from "${resolvedUrl}"`;
570
- });
571
- }
572
- if (needsDynamic) {
573
- transformed = transformed.replace(dynamicImportRegex, (_, subpath) => {
574
- if (subpath) {
575
- const subUrl = `${packageDir}${subpath}`;
576
- return `import("${subUrl}")`;
577
- }
578
- if (!resolvedUrl)
579
- return `import("${pkg}")`;
580
- return `import("${resolvedUrl}")`;
581
- });
582
- }
583
- }
584
- const vfPackagePath = pathHelper.join(projectDir, "node_modules", "veryfront");
585
- const vfPackageJsonPath = pathHelper.join(vfPackagePath, "package.json");
586
- let exportsMap = {};
587
- try {
588
- const pkgJson = JSON.parse(await fs.readTextFile(vfPackageJsonPath));
589
- exportsMap = pkgJson.exports || {};
590
- }
591
- catch (_error) {
592
- logger.debug(`Could not read veryfront package.json: `);
593
- }
594
- transformed = transformed.replace(/from\s+["'](veryfront\/[^"']+)["']/g, (match, fullSpecifier) => {
595
- const subpath = "./" + fullSpecifier.replace("veryfront/", "");
596
- const exportEntry = exportsMap[subpath];
597
- if (!exportEntry?.import) {
598
- logger.warn(`No export found for ${subpath}`);
599
- return match;
558
+ logger.debug(`Rewriting external imports for Node.js, projectDir: ${projectDir}`);
559
+ for (const pkg of getNodeExternalPackagesToResolve(userDeps)) {
560
+ const escapedPkg = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
561
+ const staticImportRegex = new RegExp(`from\\s*["']${escapedPkg}(/[^"']*)?["']`, "g");
562
+ const dynamicImportRegex = new RegExp(`import\\s*\\(\\s*["']${escapedPkg}(/[^"']*)?["']\\s*\\)`, "g");
563
+ const needsStatic = staticImportRegex.test(transformed);
564
+ staticImportRegex.lastIndex = 0;
565
+ const needsDynamic = dynamicImportRegex.test(transformed);
566
+ dynamicImportRegex.lastIndex = 0;
567
+ if (!needsStatic && !needsDynamic)
568
+ continue;
569
+ const packageDir = pathToFileURL(pathHelper.join(projectDir, "node_modules", pkg)).href;
570
+ const resolvedUrl = await resolveNodePackageToFileUrl(projectDir, pkg, fs, pathToFileURL);
571
+ if (needsStatic) {
572
+ transformed = transformed.replace(staticImportRegex, (_, subpath) => {
573
+ if (subpath) {
574
+ const subUrl = `${packageDir}${subpath}`;
575
+ logger.debug(`Resolved ${pkg}${subpath} -> ${subUrl}`);
576
+ return `from "${subUrl}"`;
600
577
  }
601
- const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
602
- logger.debug(`Resolved ${fullSpecifier} -> ${resolvedPath}`);
603
- return `from "${pathToFileURL(resolvedPath).href}"`;
578
+ if (!resolvedUrl)
579
+ return `from "${pkg}"`;
580
+ logger.debug(`Resolved ${pkg} -> ${resolvedUrl}`);
581
+ return `from "${resolvedUrl}"`;
604
582
  });
605
- transformed = transformed.replace(/from\s+["']veryfront["']/g, () => {
606
- const exportEntry = exportsMap["."];
607
- if (!exportEntry?.import)
608
- return 'from "veryfront"';
609
- const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
610
- logger.debug(`Resolved veryfront -> ${resolvedPath}`);
611
- return `from "${pathToFileURL(resolvedPath).href}"`;
583
+ }
584
+ if (needsDynamic) {
585
+ transformed = transformed.replace(dynamicImportRegex, (_, subpath) => {
586
+ if (subpath) {
587
+ return `import("${packageDir}${subpath}")`;
588
+ }
589
+ if (!resolvedUrl)
590
+ return `import("${pkg}")`;
591
+ return `import("${resolvedUrl}")`;
612
592
  });
613
593
  }
594
+ }
595
+ const vfPackagePath = pathHelper.join(projectDir, "node_modules", "veryfront");
596
+ const exportsMap = await loadVeryfrontExportsMap(projectDir, fs);
597
+ transformed = transformed.replace(/from\s+["'](veryfront\/[^"']+)["']/g, (match, fullSpecifier) => {
598
+ const subpath = "./" + fullSpecifier.replace("veryfront/", "");
599
+ const exportEntry = exportsMap[subpath];
600
+ if (!exportEntry?.import) {
601
+ logger.warn(`No export found for ${subpath}`);
602
+ return match;
603
+ }
604
+ const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
605
+ logger.debug(`Resolved ${fullSpecifier} -> ${resolvedPath}`);
606
+ return `from "${pathToFileURL(resolvedPath).href}"`;
607
+ });
608
+ transformed = transformed.replace(/from\s+["']veryfront["']/g, () => {
609
+ const exportEntry = exportsMap["."];
610
+ if (!exportEntry?.import)
611
+ return 'from "veryfront"';
612
+ const resolvedPath = pathHelper.join(vfPackagePath, exportEntry.import);
613
+ logger.debug(`Resolved veryfront -> ${resolvedPath}`);
614
+ return `from "${pathToFileURL(resolvedPath).href}"`;
615
+ });
616
+ return transformed;
617
+ }
618
+ async function rewriteExternalImports(code, projectDir, fs, userDeps = new Map()) {
619
+ let transformed = code;
620
+ if (isNode) {
621
+ try {
622
+ transformed = await rewriteNodeExternalImports(transformed, projectDir, fs, userDeps);
623
+ }
614
624
  catch (e) {
615
625
  logger.warn(`Failed to import node:module: ${e}`);
616
626
  }
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.157";
1
+ export declare const VERSION = "0.1.159";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.157";
3
+ export const VERSION = "0.1.159";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.157",
3
+ "version": "0.1.159",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -87,6 +87,15 @@ function getBranchParam(branch?: string): string {
87
87
  return branch ? `?branch_id=${branch}` : "";
88
88
  }
89
89
 
90
+ function buildProjectApiPath(project: string, resource: string, branch?: string): string {
91
+ const normalizedResource = resource.startsWith("/") ? resource.slice(1) : resource;
92
+ return `/${project}${getBranchPath(branch)}/${normalizedResource}`;
93
+ }
94
+
95
+ function buildProjectFilePath(project: string, filePath: string, branch?: string): string {
96
+ return buildProjectApiPath(project, `files/${encodeFilePath(filePath)}`, branch);
97
+ }
98
+
90
99
  interface RemoteFile {
91
100
  id?: string;
92
101
  path: string;
@@ -214,9 +223,9 @@ export const vfRemoteListFiles: MCPTool<RemoteListFilesInput, RemoteListFilesOut
214
223
  params.set("limit", String(input.limit));
215
224
  params.set("fields", "(path,type,size)");
216
225
 
217
- const apiPath = `/${input.project}${
218
- getBranchPath(input.branch)
219
- }/files?${params.toString()}`;
226
+ const apiPath = `${
227
+ buildProjectApiPath(input.project, "files", input.branch)
228
+ }?${params.toString()}`;
220
229
  const result = await apiRequest<FileListResponse>("GET", apiPath);
221
230
  if (!result.ok) return { success: false, error: result.error };
222
231
 
@@ -260,9 +269,7 @@ export const vfRemoteGetFile: MCPTool<RemoteGetFileInput, RemoteGetFileOutput> =
260
269
  withSpan(
261
270
  "cli.mcp.tool.vf_remote_get_file",
262
271
  async () => {
263
- const apiPath = `/${input.project}${getBranchPath(input.branch)}/files/${
264
- encodeFilePath(input.path)
265
- }`;
272
+ const apiPath = buildProjectFilePath(input.project, input.path, input.branch);
266
273
  const result = await apiRequest<RemoteFile>("GET", apiPath);
267
274
  if (!result.ok) return { success: false, error: result.error };
268
275
 
@@ -314,7 +321,7 @@ export const vfRemoteUpdateFile: MCPTool<RemoteUpdateFileInput, RemoteUpdateFile
314
321
  withSpan(
315
322
  "cli.mcp.tool.vf_remote_update_file",
316
323
  async () => {
317
- const apiPath = `/${input.project}/files/${encodeFilePath(input.path)}${
324
+ const apiPath = `${buildProjectFilePath(input.project, input.path)}${
318
325
  getBranchParam(input.branch)
319
326
  }`;
320
327
  const result = await apiRequest<{ id: string; path: string }>("PUT", apiPath, {
@@ -362,7 +369,7 @@ export const vfRemoteDeleteFile: MCPTool<RemoteDeleteFileInput, RemoteDeleteFile
362
369
  description: "Delete a file from a remote Veryfront project.",
363
370
  inputSchema: remoteDeleteFileInput,
364
371
  execute: async (input) => {
365
- const apiPath = `/${input.project}/files/${encodeFilePath(input.path)}${
372
+ const apiPath = `${buildProjectFilePath(input.project, input.path)}${
366
373
  getBranchParam(input.branch)
367
374
  }`;
368
375
  const result = await apiRequest<void>("DELETE", apiPath);
@@ -405,7 +412,7 @@ export const vfRemoteSearchFiles: MCPTool<RemoteSearchFilesInput, RemoteSearchFi
405
412
  withSpan(
406
413
  "cli.mcp.tool.vf_remote_search_files",
407
414
  async () => {
408
- const apiPath = `/${input.project}${getBranchPath(input.branch)}/files/search`;
415
+ const apiPath = buildProjectApiPath(input.project, "files/search", input.branch);
409
416
  const result = await apiRequest<SearchResponse>("POST", apiPath, {
410
417
  body: {
411
418
  query: input.query,
@@ -455,7 +462,11 @@ export const vfRemoteMoveFile: MCPTool<RemoteMoveFileInput, RemoteMoveFileOutput
455
462
  description: "Move or rename a file in a remote Veryfront project.",
456
463
  inputSchema: remoteMoveFileInput,
457
464
  execute: async (input) => {
458
- const apiPath = `/${input.project}/files/move${getBranchParam(input.branch)}`;
465
+ const apiPath = `${buildProjectApiPath(input.project, "files/move")}${
466
+ getBranchParam(
467
+ input.branch,
468
+ )
469
+ }`;
459
470
  const result = await apiRequest<{ source_path: string; destination_path: string }>(
460
471
  "POST",
461
472
  apiPath,
@@ -732,7 +743,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
732
743
 
733
744
  const listResult = await apiRequest<FileListResponse>(
734
745
  "GET",
735
- `/${input.source_project}/files?${params.toString()}`,
746
+ `${buildProjectApiPath(input.source_project, "files")}?${params.toString()}`,
736
747
  );
737
748
 
738
749
  if (!listResult.ok) {
@@ -750,7 +761,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
750
761
  for (const file of sourceFiles) {
751
762
  const getResult = await apiRequest<RemoteFile>(
752
763
  "GET",
753
- `/${input.source_project}/files/${encodeFilePath(file.path)}`,
764
+ buildProjectFilePath(input.source_project, file.path),
754
765
  );
755
766
 
756
767
  if (!getResult.ok || !getResult.data) {
@@ -760,7 +771,7 @@ export const vfRemoteCloneProject: MCPTool<RemoteCloneProjectInput, RemoteCloneP
760
771
 
761
772
  const createFileResult = await apiRequest<{ id: string; path: string }>(
762
773
  "PUT",
763
- `/${createResult.slug}/files/${encodeFilePath(file.path)}`,
774
+ buildProjectFilePath(createResult.slug, file.path),
764
775
  { body: { content: getResult.data.content } },
765
776
  );
766
777
 
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.157",
3
+ "version": "0.1.159",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import { AgUiRuntimeRunIdSchema } from "./runtime-ag-ui-contract.js";
3
+ import { RunResumeSessionManager } from "./runtime/index.js";
4
+
5
+ const TOOL_CALL_ID_SCHEMA = z.string().min(1).max(128);
6
+
7
+ export const HumanInputOptionSchema = z.object({
8
+ value: z.string(),
9
+ label: z.string(),
10
+ description: z.string().optional(),
11
+ recommended: z.boolean().optional(),
12
+ });
13
+
14
+ const BaseHumanInputFieldSchema = z.object({
15
+ name: z.string().min(1).max(128),
16
+ label: z.string().min(1).max(256),
17
+ description: z.string().max(1024).optional(),
18
+ required: z.boolean().optional().default(false),
19
+ secret: z.boolean().optional().default(false),
20
+ });
21
+
22
+ export const HumanInputFieldSchema = z.discriminatedUnion("type", [
23
+ BaseHumanInputFieldSchema.extend({
24
+ type: z.enum(["text", "email", "url", "password", "number"]),
25
+ placeholder: z.string().max(512).optional(),
26
+ defaultValue: z.string().optional(),
27
+ pattern: z.string().max(512).optional(),
28
+ minLength: z.number().int().nonnegative().optional(),
29
+ maxLength: z.number().int().positive().optional(),
30
+ min: z.number().optional(),
31
+ max: z.number().optional(),
32
+ }),
33
+ BaseHumanInputFieldSchema.extend({
34
+ type: z.literal("textarea"),
35
+ placeholder: z.string().max(512).optional(),
36
+ defaultValue: z.string().optional(),
37
+ minLength: z.number().int().nonnegative().optional(),
38
+ maxLength: z.number().int().positive().optional(),
39
+ rows: z.number().int().positive().optional().default(3),
40
+ }),
41
+ BaseHumanInputFieldSchema.extend({
42
+ type: z.literal("select"),
43
+ options: z.array(HumanInputOptionSchema).min(1),
44
+ defaultValue: z.string().optional(),
45
+ placeholder: z.string().max(512).optional(),
46
+ }),
47
+ BaseHumanInputFieldSchema.extend({
48
+ type: z.literal("checkbox"),
49
+ defaultValue: z.boolean().optional().default(false),
50
+ }),
51
+ BaseHumanInputFieldSchema.extend({
52
+ type: z.literal("radio"),
53
+ options: z.array(HumanInputOptionSchema).min(1),
54
+ defaultValue: z.string().optional(),
55
+ }),
56
+ BaseHumanInputFieldSchema.extend({
57
+ type: z.literal("confirm"),
58
+ confirmLabel: z.string().max(64).optional().default("Yes"),
59
+ denyLabel: z.string().max(64).optional().default("No"),
60
+ }),
61
+ ]);
62
+
63
+ export const HumanInputRequestSchema = z.object({
64
+ title: z.string().min(1).max(256),
65
+ description: z.string().max(2048).optional(),
66
+ fields: z.array(HumanInputFieldSchema).min(1),
67
+ submitLabel: z.string().max(64).optional().default("Submit"),
68
+ metadata: z.record(z.string(), z.unknown()).optional(),
69
+ });
70
+
71
+ export const HumanInputResponseValuesSchema = z.record(
72
+ z.string(),
73
+ z.union([z.string(), z.boolean(), z.number(), z.null()]),
74
+ );
75
+
76
+ export const HumanInputResultSchema = z.discriminatedUnion("submitted", [
77
+ z.object({
78
+ submitted: z.literal(true),
79
+ values: HumanInputResponseValuesSchema,
80
+ }),
81
+ z.object({
82
+ submitted: z.literal(false),
83
+ values: HumanInputResponseValuesSchema.default({}),
84
+ }),
85
+ ]);
86
+
87
+ export const HumanInputPendingRequestSchema = z.object({
88
+ runId: AgUiRuntimeRunIdSchema,
89
+ toolCallId: TOOL_CALL_ID_SCHEMA,
90
+ request: HumanInputRequestSchema,
91
+ });
92
+
93
+ export type HumanInputOption = z.infer<typeof HumanInputOptionSchema>;
94
+ export type HumanInputField = z.infer<typeof HumanInputFieldSchema>;
95
+ export type HumanInputFieldInput = z.input<typeof HumanInputFieldSchema>;
96
+ export type HumanInputRequest = z.infer<typeof HumanInputRequestSchema>;
97
+ export type HumanInputRequestInput = z.input<typeof HumanInputRequestSchema>;
98
+ export type HumanInputResult = z.infer<typeof HumanInputResultSchema>;
99
+ export type HumanInputPendingRequest = z.infer<typeof HumanInputPendingRequestSchema>;
100
+
101
+ type HumanInputResumeValue = {
102
+ result: unknown;
103
+ isError: boolean;
104
+ };
105
+
106
+ export interface WaitForHumanInputOptions {
107
+ sessionManager: RunResumeSessionManager<HumanInputResumeValue>;
108
+ runId: string;
109
+ toolCallId: string;
110
+ request: HumanInputRequestInput;
111
+ onRequest?: ((request: HumanInputPendingRequest) => void | Promise<void>) | undefined;
112
+ }
113
+
114
+ export class HumanInputResumeError extends Error {
115
+ constructor(readonly detail: unknown) {
116
+ super(
117
+ typeof detail === "string" ? detail : "Human input resume failed",
118
+ );
119
+ this.name = "HumanInputResumeError";
120
+ }
121
+ }
122
+
123
+ export class InvalidHumanInputResultError extends Error {
124
+ constructor(readonly detail: z.ZodIssue[]) {
125
+ super("Invalid human input resume payload");
126
+ this.name = "InvalidHumanInputResultError";
127
+ }
128
+ }
129
+
130
+ export async function waitForHumanInput(
131
+ options: WaitForHumanInputOptions,
132
+ ): Promise<HumanInputResult> {
133
+ const pendingRequest = HumanInputPendingRequestSchema.parse({
134
+ runId: options.runId,
135
+ toolCallId: options.toolCallId,
136
+ request: options.request,
137
+ });
138
+
139
+ await options.onRequest?.(pendingRequest);
140
+
141
+ const resumed = await options.sessionManager.waitForSignal(options.runId, options.toolCallId);
142
+ if (resumed.isError) {
143
+ throw new HumanInputResumeError(resumed.result);
144
+ }
145
+
146
+ const parsed = HumanInputResultSchema.safeParse(resumed.result);
147
+ if (!parsed.success) {
148
+ throw new InvalidHumanInputResultError(parsed.error.issues);
149
+ }
150
+
151
+ return parsed.data;
152
+ }
@@ -162,6 +162,24 @@ export {
162
162
  AgUiRequestSchema,
163
163
  createAgUiHandler,
164
164
  } from "./ag-ui-handler.js";
165
+ export {
166
+ type HumanInputField,
167
+ type HumanInputFieldInput,
168
+ HumanInputFieldSchema,
169
+ type HumanInputOption,
170
+ HumanInputOptionSchema,
171
+ type HumanInputPendingRequest,
172
+ HumanInputPendingRequestSchema,
173
+ type HumanInputRequest,
174
+ type HumanInputRequestInput,
175
+ HumanInputRequestSchema,
176
+ type HumanInputResult,
177
+ HumanInputResultSchema,
178
+ HumanInputResumeError,
179
+ InvalidHumanInputResultError,
180
+ waitForHumanInput,
181
+ type WaitForHumanInputOptions,
182
+ } from "./human-input.js";
165
183
  export {
166
184
  type ChatHandlerBeforeStream,
167
185
  type ChatHandlerBeforeStreamContext,
@@ -128,9 +128,8 @@ export class MCPServer {
128
128
  private sessionManager = new SessionManager();
129
129
  private taskStore = new TaskStore();
130
130
  private pendingTasks = new Map<string, Promise<void>>();
131
- // TODO(#842): capabilities should be stored per-session (keyed by MCP-Session-Id)
132
- // so concurrent clients don't overwrite each other's capability flags.
133
131
  private clientCapabilities: Record<string, unknown> = {};
132
+ private sessionCapabilities = new Map<string, Record<string, unknown>>();
134
133
 
135
134
  /** Callback for server-initiated notifications. Set by transport layer. */
136
135
  onNotification?: (notification: { jsonrpc: "2.0"; method: string; params?: unknown }) => void;
@@ -167,8 +166,11 @@ export class MCPServer {
167
166
  this.integrationsLoaded = false;
168
167
  }
169
168
 
170
- clientSupportsElicitation(mode: "form" | "url"): boolean {
171
- const raw = this.clientCapabilities.elicitation;
169
+ clientSupportsElicitation(mode: "form" | "url", sessionId?: string): boolean {
170
+ const capabilities = sessionId
171
+ ? this.sessionCapabilities.get(sessionId) ?? {}
172
+ : this.clientCapabilities;
173
+ const raw = capabilities.elicitation;
172
174
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
173
175
  const elicitation = raw as Record<string, unknown>;
174
176
  // Per MCP spec: empty elicitation object implies basic form support (backwards compat)
@@ -650,7 +652,10 @@ export class MCPServer {
650
652
  // DELETE = terminate session
651
653
  if (request.method === "DELETE") {
652
654
  const sessionId = request.headers.get("MCP-Session-Id");
653
- if (sessionId) this.sessionManager.terminate(sessionId);
655
+ if (sessionId) {
656
+ this.sessionManager.terminate(sessionId);
657
+ this.sessionCapabilities.delete(sessionId);
658
+ }
654
659
  return new dntShim.Response(null, { status: 200, headers: this.getCORSHeaders(requestOrigin) });
655
660
  }
656
661
 
@@ -692,7 +697,11 @@ export class MCPServer {
692
697
  if (rpcRequest.method === "initialize") {
693
698
  const context = this.extractRequestContext(request);
694
699
  const rpcResponse = await this.handleRequest(rpcRequest, context);
700
+ const clientCaps =
701
+ toParamsRecord(rpcRequest.params).capabilities as Record<string, unknown> ??
702
+ {};
695
703
  const sessionId = this.sessionManager.create();
704
+ this.sessionCapabilities.set(sessionId, clientCaps);
696
705
  responseHeaders["MCP-Session-Id"] = sessionId;
697
706
  return createJSONResponse(rpcResponse, { headers: responseHeaders });
698
707
  }