@plasm_lang/vercel-agent 0.3.91 → 0.3.93

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasm_lang/vercel-agent",
3
- "version": "0.3.91",
3
+ "version": "0.3.93",
4
4
  "description": "Catalog-native TypeScript agent framework (Plasm CGS/CML, Vercel AI SDK, Nitro-oriented)",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -19,6 +19,7 @@
19
19
  "./server": "./src/server/plasm-handler.ts",
20
20
  "./instrumentation": "./src/instrumentation.ts",
21
21
  "./operator": "./src/operator/routes.ts",
22
+ "./stub-runtime": "./src/stubs/stub-runtime.ts",
22
23
  "./agent": "./agent/agent.ts"
23
24
  },
24
25
  "bin": {
@@ -63,7 +64,7 @@
63
64
  "dependencies": {
64
65
  "@ai-sdk/otel": "^1.0.3",
65
66
  "@opentelemetry/api": "^1.9.0",
66
- "@plasm_lang/engine": "^0.3.91",
67
+ "@plasm_lang/engine": "^0.3.93",
67
68
  "@vercel/blob": "^0.27.3",
68
69
  "@vercel/connect": "^0.2.6",
69
70
  "@vercel/kv": "^3.0.0",
@@ -14,6 +14,8 @@ export interface CreateAuthoringContextOptions {
14
14
  agentRoot: string;
15
15
  getAgent: () => Promise<PlasmAgent>;
16
16
  importCacheBust?: number;
17
+ /** Generated stub extension — `.mjs` on Vercel (bundled at build); `.ts` in dev. */
18
+ stubImportExt?: "ts" | "mjs";
17
19
  }
18
20
 
19
21
  export function createAuthoringContext(
@@ -21,12 +23,13 @@ export function createAuthoringContext(
21
23
  ): AuthoringContext {
22
24
  const agentRoot = path.resolve(options.agentRoot);
23
25
  const bust = options.importCacheBust ?? Date.now();
26
+ const stubExt = options.stubImportExt ?? "ts";
24
27
 
25
28
  return {
26
29
  agentRoot,
27
30
  getAgent: options.getAgent,
28
31
  importStub: async (entryId: string) => {
29
- const stubPath = path.join(agentRoot, ".plasm", "stubs", `${entryId}.ts`);
32
+ const stubPath = path.join(agentRoot, ".plasm", "stubs", `${entryId}.${stubExt}`);
30
33
  const url = `${pathToFileURL(stubPath).href}?t=${bust}`;
31
34
  return import(url);
32
35
  },
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
 
4
4
  export interface PlasmBuildManifest {
5
5
  compiledSlots?: Record<string, string>;
6
+ compiledStubs?: Record<string, string>;
6
7
  projectRoot?: string;
7
8
  }
8
9
 
package/src/cli/build.ts CHANGED
@@ -2,6 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
4
  import { readBuildManifest } from "./build-manifest.js";
5
+ import { compileGeneratedStubs } from "./compile-generated-stubs.js";
5
6
  import { compileAuthoredSlots } from "./compile-authored-slots.js";
6
7
  import type { ResolvedAgentProject } from "./project-root.js";
7
8
  import { isNativeEngineAvailable } from "../engine/napi-binding.js";
@@ -15,6 +16,7 @@ export interface PlasmBuildResult {
15
16
  discoveryDir: string;
16
17
  manifestPath: string;
17
18
  stubs: Array<{ entryId: string; catalogCgsHash: string; outPath: string }>;
19
+ compiledStubs: Record<string, string>;
18
20
  outputDir?: string;
19
21
  agentSummaryPath?: string;
20
22
  vercelOutput?: boolean;
@@ -22,6 +24,11 @@ export interface PlasmBuildResult {
22
24
 
23
25
  export async function runPlasmBuild(project: ResolvedAgentProject): Promise<PlasmBuildResult> {
24
26
  const stubs = await generateAllStubs(project.agentRoot);
27
+ const { compiledStubs } = await compileGeneratedStubs(
28
+ project.projectRoot,
29
+ project.agentRoot,
30
+ stubs,
31
+ );
25
32
  const discovery = await walkAgentProject(project.agentRoot);
26
33
  const { compiledSlots } = await compileAuthoredSlots(
27
34
  project.projectRoot,
@@ -38,6 +45,7 @@ export async function runPlasmBuild(project: ResolvedAgentProject): Promise<Plas
38
45
  projectRoot: project.projectRoot,
39
46
  agentRoot: project.agentRoot,
40
47
  compiledSlots,
48
+ compiledStubs,
41
49
  stubs: stubs.map((s) => ({
42
50
  entryId: s.entryId,
43
51
  catalogCgsHash: s.catalogCgsHash,
@@ -74,6 +82,7 @@ export async function runPlasmBuild(project: ResolvedAgentProject): Promise<Plas
74
82
  discoveryDir,
75
83
  manifestPath,
76
84
  stubs,
85
+ compiledStubs,
77
86
  outputDir: appBuild.outputDir,
78
87
  agentSummaryPath: appBuild.agentSummaryPath,
79
88
  vercelOutput: appBuild.vercelOutput,
@@ -71,6 +71,17 @@ export async function compileAuthoredSlots(
71
71
  "workflow",
72
72
  "workflow/api",
73
73
  ],
74
+ plugins: [
75
+ {
76
+ name: "external-workflows",
77
+ setup(build) {
78
+ build.onResolve({ filter: /\/workflows\// }, (args) => ({
79
+ path: args.path,
80
+ external: true,
81
+ }));
82
+ },
83
+ },
84
+ ],
74
85
  logLevel: "silent",
75
86
  });
76
87
 
@@ -0,0 +1,97 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import * as esbuild from "esbuild";
6
+
7
+ import type { StubGenerationResult } from "../stubs/generator.js";
8
+
9
+ const ESBUILD_BANNER =
10
+ 'import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);';
11
+
12
+ const STUB_RUNTIME_SOURCE = path.join(
13
+ path.dirname(fileURLToPath(import.meta.url)),
14
+ "../stubs/stub-runtime.ts",
15
+ );
16
+
17
+ const STUB_RUNTIME_BASENAME = "_stub-runtime.mjs";
18
+
19
+ /** Native engine stays external; runtime is a shared sibling module on Vercel. */
20
+ const STUB_RUNTIME_EXTERNAL = ["@plasm_lang/engine"];
21
+
22
+ export interface CompileGeneratedStubsResult {
23
+ /** entry_id → projectRoot-relative compiled `.mjs` path */
24
+ compiledStubs: Record<string, string>;
25
+ }
26
+
27
+ function stubRuntimeExternalPlugin(): esbuild.Plugin {
28
+ return {
29
+ name: "stub-runtime-sibling",
30
+ setup(build) {
31
+ build.onResolve({ filter: /^\.\/_stub-runtime\.js$/ }, () => ({
32
+ path: "./_stub-runtime.mjs",
33
+ external: true,
34
+ }));
35
+ },
36
+ };
37
+ }
38
+
39
+ async function compileStubRuntime(stubsDir: string): Promise<string> {
40
+ const runtimePath = path.join(stubsDir, STUB_RUNTIME_BASENAME);
41
+ await esbuild.build({
42
+ entryPoints: [STUB_RUNTIME_SOURCE],
43
+ bundle: true,
44
+ platform: "node",
45
+ target: "node22",
46
+ format: "esm",
47
+ outfile: runtimePath,
48
+ banner: { js: ESBUILD_BANNER },
49
+ external: STUB_RUNTIME_EXTERNAL,
50
+ logLevel: "silent",
51
+ });
52
+ return runtimePath;
53
+ }
54
+
55
+ /** Compile generated CGS stubs to `.mjs` for serverless dynamic import. */
56
+ export async function compileGeneratedStubs(
57
+ projectRoot: string,
58
+ agentRoot: string,
59
+ stubs: StubGenerationResult[],
60
+ ): Promise<CompileGeneratedStubsResult> {
61
+ const stubsDir = path.join(agentRoot, ".plasm", "stubs");
62
+ await mkdir(stubsDir, { recursive: true });
63
+ await compileStubRuntime(stubsDir);
64
+
65
+ const compiledStubs: Record<string, string> = {};
66
+
67
+ await Promise.all(
68
+ stubs.map(async (stub) => {
69
+ const entryId = stub.entryId;
70
+ const tsPath = stub.outPath;
71
+ const mjsPath = tsPath.replace(/\.ts$/, ".mjs");
72
+
73
+ await esbuild.build({
74
+ entryPoints: [tsPath],
75
+ bundle: true,
76
+ platform: "node",
77
+ target: "node22",
78
+ format: "esm",
79
+ outfile: mjsPath,
80
+ banner: { js: ESBUILD_BANNER },
81
+ external: STUB_RUNTIME_EXTERNAL,
82
+ plugins: [stubRuntimeExternalPlugin()],
83
+ logLevel: "silent",
84
+ });
85
+
86
+ compiledStubs[entryId] = path.relative(projectRoot, mjsPath);
87
+ }),
88
+ );
89
+
90
+ await writeFile(
91
+ path.join(stubsDir, "package.json"),
92
+ `${JSON.stringify({ type: "module" }, null, 2)}\n`,
93
+ "utf8",
94
+ );
95
+
96
+ return { compiledStubs };
97
+ };
@@ -44,6 +44,42 @@ async function publicDirExists(projectRoot: string): Promise<boolean> {
44
44
  }
45
45
  }
46
46
 
47
+ async function pathExists(p: string): Promise<boolean> {
48
+ try {
49
+ await access(p);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async function plasmNitroHandlers(
57
+ projectRoot: string,
58
+ workflowEnabled: boolean,
59
+ ): Promise<NonNullable<NitroConfig["handlers"]>> {
60
+ const catchAllHandler = path.join(plasmNitroRoutesDir(projectRoot), "[[path]].ts");
61
+ const handlers: NonNullable<NitroConfig["handlers"]> = [];
62
+
63
+ if (workflowEnabled) {
64
+ const dispatchHandler = path.join(
65
+ plasmNitroRoutesDir(projectRoot),
66
+ "internal",
67
+ "workflow",
68
+ "dispatch.post.ts",
69
+ );
70
+ if (await pathExists(dispatchHandler)) {
71
+ handlers.push({
72
+ route: "/internal/workflow/dispatch",
73
+ handler: dispatchHandler,
74
+ method: "post",
75
+ });
76
+ }
77
+ }
78
+
79
+ handlers.push({ route: "/**", handler: catchAllHandler });
80
+ return handlers;
81
+ }
82
+
47
83
  export async function createPlasmNitro(
48
84
  host: PreparedPlasmHost,
49
85
  dev: boolean,
@@ -59,8 +95,6 @@ export async function createPlasmNitro(
59
95
 
60
96
  const traceDeps = [...new Set([...SERVER_TRACE_DEPS, ...host.externalDeps])];
61
97
 
62
- const catchAllHandler = path.join(plasmNitroRoutesDir(host.projectRoot), "[[path]].ts");
63
-
64
98
  const publicAssets = (await publicDirExists(host.projectRoot))
65
99
  ? [
66
100
  {
@@ -80,7 +114,7 @@ export async function createPlasmNitro(
80
114
  dev,
81
115
  serverDir: false,
82
116
  ignore: ["api/**", "server/**", "routes/**", "nitro.config.ts"],
83
- handlers: [{ route: "/**", handler: catchAllHandler }],
117
+ handlers: await plasmNitroHandlers(host.projectRoot, host.workflowEnabled),
84
118
  publicAssets,
85
119
  modules,
86
120
  experimental: {
@@ -11,7 +11,7 @@ export async function writeWorkflowDispatchRoute(projectRoot: string): Promise<s
11
11
  const source = `import path from "node:path";
12
12
  import type { IncomingMessage, ServerResponse } from "node:http";
13
13
 
14
- import agentDefinition from "../../../../agent/agent.js";
14
+ import agentDefinition from "../../../../../agent/agent.js";
15
15
  import { createPlasmApp } from "@plasm_lang/vercel-agent/server";
16
16
 
17
17
  const agentRoot = path.join(process.cwd(), "agent");
@@ -53,7 +53,7 @@ export default async (event: NitroNodeEvent) => {
53
53
  mode: "prod",
54
54
  sessions: false,
55
55
  });
56
- const { runRadar } = await import("../../../../lib/run-radar.js");
56
+ const { runRadar } = await import("../../../../../lib/run-radar.js");
57
57
  const result = await runRadar(app.getAuthoringContext(), { force });
58
58
 
59
59
  res.statusCode = 200;
@@ -229,6 +229,7 @@ export async function createPlasmApp(options: PlasmAppOptions): Promise<PlasmApp
229
229
  agentRoot,
230
230
  getAgent: async () => agent ?? bootstrap(),
231
231
  importCacheBust,
232
+ stubImportExt: mode === "prod" ? "mjs" : "ts",
232
233
  });
233
234
 
234
235
  const bootstrap = async (): Promise<PlasmAgent> => {
@@ -59,6 +59,23 @@ export function computeCatalogCgsHash(domainYaml: string, mappingsYaml: string):
59
59
  return createHash("sha256").update(domainYaml).update("\n").update(mappingsYaml).digest("hex");
60
60
  }
61
61
 
62
+ /** Dev-time shim beside generated stubs; prod uses `_stub-runtime.mjs` from compile. */
63
+ export async function writeStubRuntimeShim(outDir: string): Promise<void> {
64
+ await mkdir(outDir, { recursive: true });
65
+ const source = `/** @generated plasm-agent stub runtime shim (dev only) */
66
+ export {
67
+ buildDottedArgs,
68
+ createCatalogClient,
69
+ executeRows,
70
+ plasmBoolean,
71
+ plasmLiteral,
72
+ plasmNumber,
73
+ type StubInvokeOptions,
74
+ } from "@plasm_lang/vercel-agent/stub-runtime";
75
+ `;
76
+ await writeFile(path.join(outDir, "_stub-runtime.ts"), source, "utf8");
77
+ }
78
+
62
79
  /** Legacy parser — delegates to {@link parseCgsDomain}. */
63
80
  export function parseDomainYaml(raw: string, fallbackEntryId: string): ParsedDomain {
64
81
  return toLegacyParsedDomain(parseCgsDomain(raw, fallbackEntryId));
@@ -154,7 +171,7 @@ export function renderStubModule(
154
171
  bindings?: Map<string, CapabilityBinding>,
155
172
  ): string {
156
173
  const exportName = toExportName(catalog.entry_id);
157
- const clientImport = "@plasm_lang/vercel-agent";
174
+ const clientImport = "./_stub-runtime.js";
158
175
 
159
176
  const entityTypes = new Set<string>();
160
177
  const inputTypes = new Set<string>();
@@ -288,6 +305,8 @@ export async function generateAllStubs(
288
305
  const outDir = path.join(agentRoot, ".plasm", "stubs");
289
306
  const { access, readdir, stat } = await import("node:fs/promises");
290
307
 
308
+ await writeStubRuntimeShim(outDir);
309
+
291
310
  const engine = options?.engine ?? createEngine();
292
311
 
293
312
  let entries;
@@ -0,0 +1,10 @@
1
+ /** Narrow stub import surface — bundled into generated `.mjs` stubs on Vercel. */
2
+ export {
3
+ buildDottedArgs,
4
+ createCatalogClient,
5
+ executeRows,
6
+ plasmBoolean,
7
+ plasmLiteral,
8
+ plasmNumber,
9
+ type StubInvokeOptions,
10
+ } from "./catalog-client.js";
@@ -2,11 +2,17 @@ import type { AuthoringContext } from "@plasm_lang/vercel-agent";
2
2
 
3
3
  import type { RadarRunOptions } from "./run-radar.js";
4
4
 
5
+ /** Must match @workflow/nitro manifest for `workflows/mcp-radar-scan.ts`. */
6
+ const MCP_RADAR_SCAN_WORKFLOW_ID =
7
+ "workflow//./workflows/mcp-radar-scan//mcpRadarScanWorkflow";
8
+
5
9
  export async function startMcpRadarRun(
6
10
  ctx: AuthoringContext,
7
11
  options: RadarRunOptions = {},
8
12
  ): Promise<unknown> {
9
13
  const { start } = await import("workflow/api");
10
- const { mcpRadarScanWorkflow } = await import("../workflows/mcp-radar-scan.js");
11
- return start(mcpRadarScanWorkflow, [ctx.agentRoot, options.force === true]);
14
+ return start({ workflowId: MCP_RADAR_SCAN_WORKFLOW_ID }, [
15
+ ctx.agentRoot,
16
+ options.force === true,
17
+ ]);
12
18
  }
@@ -1,6 +1,8 @@
1
1
  function workflowDispatchUrl(): string {
2
2
  const explicit = process.env.PLASM_WORKFLOW_DISPATCH_URL?.trim();
3
3
  if (explicit) return explicit;
4
+ const production = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
5
+ if (production) return `https://${production}/internal/workflow/dispatch`;
4
6
  const vercelUrl = process.env.VERCEL_URL?.trim();
5
7
  if (vercelUrl) return `https://${vercelUrl}/internal/workflow/dispatch`;
6
8
  const port = process.env.PORT ?? "3000";