@plasm_lang/vercel-agent 0.3.63

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.
Files changed (121) hide show
  1. package/README.md +235 -0
  2. package/agent/.plasm/archives/runs/runs/pr2d5c4a99707b4c19b650553d50229a1d600d28e3d98a9c58f18e5026cecc86ca.json +64 -0
  3. package/agent/.plasm/archives/runs/runs/pr2e0c0d8ad443c63c82da7435ee1a002b0e0fa718b640263c0a9d3e6e5944812f.json +64 -0
  4. package/agent/.plasm/archives/runs/runs/pr2faedb8354f40ee6d828e3af07b421fda9ccda973a4f7347fce3639f03a0a869.json +64 -0
  5. package/agent/.plasm/archives/runs/runs/pr586b47c55547b0702c572bce4255558b22dbe5e682d6359169577e0ea75fe98f.json +64 -0
  6. package/agent/.plasm/archives/runs/runs/pr76212356445e3b00fcf256835aaec18bac68576324b90d0be92d47f0b4a862a7.json +56 -0
  7. package/agent/.plasm/archives/runs/runs/pr9ec805d689e00db9270a9539858f2fb7216c24acbfea943d450e37b641149da1.json +64 -0
  8. package/agent/.plasm/archives/runs/runs/prc3c0c4ba2e28fc94ed6d37b6796e277a7997d9cb3184640d14c35c98bc6d136f.json +64 -0
  9. package/agent/.plasm/archives/runs/runs/prf04de32522f2fdcb17818907d91bccce7dcaecbd1259041cc448d447b6993244.json +64 -0
  10. package/agent/.plasm/archives/traces/traces/local/00000000000000000000000000000000/records.ndjson +1 -0
  11. package/agent/.plasm/archives/traces/traces/local/00000000000000000000000000000000/summary.json +13 -0
  12. package/agent/.plasm/discovery/manifest.json +126 -0
  13. package/agent/.plasm/sessions/TGlzdCBwcm9kdWN0cyBmcm9tIHRoZSBleGVjdXRlX3RpbnkgY2F0YWxvZw.json +44 -0
  14. package/agent/.plasm/sessions/TGlzdCBwcm9kdWN0cyBmcm9tIHRoZSBleGVjdXRlX3RpbnkgY2F0YWxvZw.teaching.tsv +23 -0
  15. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHM.json +151 -0
  16. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHM.teaching.tsv +131 -0
  17. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHMgYXN5bmMgdHJhbnNwb3J0.json +44 -0
  18. package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHMgYXN5bmMgdHJhbnNwb3J0.teaching.tsv +23 -0
  19. package/agent/.plasm/stubs/.gitkeep +0 -0
  20. package/agent/.plasm/stubs/execute_tiny.ts +107 -0
  21. package/agent/agent.ts +52 -0
  22. package/agent/catalogs/README.md +15 -0
  23. package/agent/channels/.gitkeep +0 -0
  24. package/agent/channels/execute-tiny-webhook.ts +59 -0
  25. package/agent/channels/health.ts +16 -0
  26. package/agent/hooks/.gitkeep +0 -0
  27. package/agent/hooks/trace-log.ts +10 -0
  28. package/agent/instructions.md +25 -0
  29. package/agent/schedules/.gitkeep +0 -0
  30. package/agent/schedules/ping.ts +13 -0
  31. package/agent/skills/.gitkeep +0 -0
  32. package/agent/skills/plasm-authoring.md +8 -0
  33. package/agent/subagents/.gitkeep +0 -0
  34. package/agent/subagents/tiny/agent.ts +28 -0
  35. package/bin/plasm-agent.mjs +18 -0
  36. package/package.json +77 -0
  37. package/scripts/plasm-node.mjs +19 -0
  38. package/scripts/resolve-ts-extension.mjs +18 -0
  39. package/src/archive/adapters.ts +27 -0
  40. package/src/archive/index.ts +99 -0
  41. package/src/archive/local-run-archive.ts +90 -0
  42. package/src/archive/local-trace-archive.ts +91 -0
  43. package/src/archive/paths.ts +15 -0
  44. package/src/archive/postgres-kv-adapter.ts +72 -0
  45. package/src/archive/prod-archive-store.ts +143 -0
  46. package/src/archive/resolve-backend.ts +60 -0
  47. package/src/archive/run-id.ts +23 -0
  48. package/src/archive/types.ts +75 -0
  49. package/src/archive/vercel-blob-adapter.ts +21 -0
  50. package/src/archive/vercel-kv-adapter.ts +24 -0
  51. package/src/authoring/channel-dispatch.ts +44 -0
  52. package/src/authoring/context.ts +34 -0
  53. package/src/authoring/define-channel.ts +83 -0
  54. package/src/authoring/define-hook.ts +51 -0
  55. package/src/authoring/define-schedule.ts +64 -0
  56. package/src/authoring/define-skill.ts +38 -0
  57. package/src/authoring/hook-runner.ts +18 -0
  58. package/src/authoring/schedule-manager.ts +118 -0
  59. package/src/authoring/slot-loader.ts +253 -0
  60. package/src/authoring/subagent-loader.ts +121 -0
  61. package/src/catalog/loader.ts +71 -0
  62. package/src/cli/build.ts +54 -0
  63. package/src/cli/dev.ts +60 -0
  64. package/src/cli/info.ts +12 -0
  65. package/src/cli/init.ts +372 -0
  66. package/src/cli/link.ts +68 -0
  67. package/src/cli/project-root.ts +57 -0
  68. package/src/define-agent.ts +150 -0
  69. package/src/dev/client/ansi.ts +36 -0
  70. package/src/dev/client/http-session.ts +180 -0
  71. package/src/dev/client/repl.ts +92 -0
  72. package/src/dev/client/slash.ts +119 -0
  73. package/src/dev/dev-session.ts +153 -0
  74. package/src/dev/http.ts +29 -0
  75. package/src/dev/server.ts +147 -0
  76. package/src/dev/session-routes.ts +185 -0
  77. package/src/discovery/project-walker.ts +272 -0
  78. package/src/engine/connect-auth.ts +135 -0
  79. package/src/engine/create-host-transport.ts +7 -0
  80. package/src/engine/fixture-mock-transport.ts +54 -0
  81. package/src/engine/host-transport-bridge.ts +32 -0
  82. package/src/engine/host-transport.ts +84 -0
  83. package/src/engine/napi-binding.ts +265 -0
  84. package/src/evals/define-eval.ts +56 -0
  85. package/src/evals/run-eval.ts +136 -0
  86. package/src/gateway-model.ts +43 -0
  87. package/src/index.ts +296 -0
  88. package/src/instrumentation.ts +56 -0
  89. package/src/load-env.ts +63 -0
  90. package/src/operator/routes.ts +287 -0
  91. package/src/operator/types.ts +63 -0
  92. package/src/operator/ui-shell.ts +134 -0
  93. package/src/project-info.ts +229 -0
  94. package/src/runtime/agent-runtime.ts +469 -0
  95. package/src/runtime/compaction.ts +81 -0
  96. package/src/runtime/logical-session.ts +72 -0
  97. package/src/runtime/plasm-agent.ts +199 -0
  98. package/src/server/plasm-handler.ts +331 -0
  99. package/src/session-state.ts +135 -0
  100. package/src/state/define-state.ts +57 -0
  101. package/src/state/fs-state-adapter.ts +72 -0
  102. package/src/state/kv-state-adapter.ts +62 -0
  103. package/src/state/postgres-state-adapter.ts +116 -0
  104. package/src/stubs/capability-invoke-shape.ts +135 -0
  105. package/src/stubs/catalog-client.ts +170 -0
  106. package/src/stubs/catalog-hash.ts +11 -0
  107. package/src/stubs/catalog-introspection.ts +121 -0
  108. package/src/stubs/cgs-ts-types.ts +164 -0
  109. package/src/stubs/domain-parser.ts +203 -0
  110. package/src/stubs/generator.ts +390 -0
  111. package/src/stubs/input-type-to-ts.ts +233 -0
  112. package/src/stubs/plasm-value-emitter.ts +162 -0
  113. package/src/stubs/program-builder.ts +82 -0
  114. package/src/stubs/stub-symbols.ts +89 -0
  115. package/src/symbol-registry.ts +74 -0
  116. package/src/telemetry/plasm-spans.ts +83 -0
  117. package/src/tools/descriptions.ts +94 -0
  118. package/src/tools/format.ts +29 -0
  119. package/src/tools/harness-tools.ts +65 -0
  120. package/src/tools/plasm-tools.ts +104 -0
  121. package/src/workflow/world-bootstrap.ts +52 -0
@@ -0,0 +1,153 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { ModelMessage } from "ai";
3
+
4
+ import type { PlasmAgent } from "../runtime/plasm-agent.js";
5
+ import type { AgentTurnResult } from "../runtime/plasm-agent.js";
6
+
7
+ export type SessionEventType =
8
+ | "turn:start"
9
+ | "turn:step"
10
+ | "turn:finish"
11
+ | "turn:error";
12
+
13
+ export interface SessionEvent {
14
+ id: string;
15
+ type: SessionEventType;
16
+ at: string;
17
+ data: Record<string, unknown>;
18
+ }
19
+
20
+ export interface DevSessionRecord {
21
+ id: string;
22
+ continuationToken: string;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ status: "idle" | "running" | "error";
26
+ messages: ModelMessage[];
27
+ events: SessionEvent[];
28
+ lastText?: string;
29
+ lastError?: string;
30
+ }
31
+
32
+ type SessionListener = (event: SessionEvent) => void;
33
+
34
+ export class DevSessionStore {
35
+ private readonly sessions = new Map<string, DevSessionRecord>();
36
+ private readonly listeners = new Map<string, Set<SessionListener>>();
37
+
38
+ list(): DevSessionRecord[] {
39
+ return [...this.sessions.values()];
40
+ }
41
+
42
+ get(sessionId: string): DevSessionRecord | undefined {
43
+ return this.sessions.get(sessionId);
44
+ }
45
+
46
+ create(): DevSessionRecord {
47
+ const id = randomUUID();
48
+ const record: DevSessionRecord = {
49
+ id,
50
+ continuationToken: id,
51
+ createdAt: new Date().toISOString(),
52
+ updatedAt: new Date().toISOString(),
53
+ status: "idle",
54
+ messages: [],
55
+ events: [],
56
+ };
57
+ this.sessions.set(id, record);
58
+ return record;
59
+ }
60
+
61
+ subscribe(sessionId: string, listener: SessionListener): () => void {
62
+ let set = this.listeners.get(sessionId);
63
+ if (!set) {
64
+ set = new Set();
65
+ this.listeners.set(sessionId, set);
66
+ }
67
+ set.add(listener);
68
+ return () => {
69
+ set?.delete(listener);
70
+ if (set && set.size === 0) this.listeners.delete(sessionId);
71
+ };
72
+ }
73
+
74
+ private pushEvent(
75
+ session: DevSessionRecord,
76
+ type: SessionEventType,
77
+ data: Record<string, unknown>,
78
+ ): SessionEvent {
79
+ const event: SessionEvent = {
80
+ id: randomUUID(),
81
+ type,
82
+ at: new Date().toISOString(),
83
+ data,
84
+ };
85
+ session.events.push(event);
86
+ session.updatedAt = event.at;
87
+ const listeners = this.listeners.get(session.id);
88
+ if (listeners) {
89
+ for (const listener of listeners) listener(event);
90
+ }
91
+ return event;
92
+ }
93
+
94
+ validateContinuation(sessionId: string, token?: string): DevSessionRecord | null {
95
+ const session = this.sessions.get(sessionId);
96
+ if (!session) return null;
97
+ if (!token || token !== session.continuationToken) return null;
98
+ return session;
99
+ }
100
+
101
+ async runTurn(
102
+ session: DevSessionRecord,
103
+ message: string,
104
+ agent: PlasmAgent,
105
+ ): Promise<AgentTurnResult> {
106
+ if (session.status === "running") {
107
+ throw new Error("session_busy");
108
+ }
109
+
110
+ session.status = "running";
111
+ session.messages.push({ role: "user", content: message });
112
+ this.pushEvent(session, "turn:start", { message });
113
+
114
+ try {
115
+ const result = await agent.generate(message, {
116
+ messages: [...session.messages],
117
+ onStepFinish: async (step) => {
118
+ const toolsUsed = (step.toolCalls ?? []).map((call) => call.toolName);
119
+ this.pushEvent(session, "turn:step", {
120
+ toolsUsed,
121
+ text: step.text ?? null,
122
+ finishReason: step.finishReason ?? null,
123
+ });
124
+ },
125
+ });
126
+
127
+ session.messages.push({ role: "assistant", content: result.text });
128
+ session.lastText = result.text;
129
+ session.status = "idle";
130
+ this.pushEvent(session, "turn:finish", {
131
+ text: result.text,
132
+ steps: result.steps.length,
133
+ usage: result.usage,
134
+ });
135
+ return result;
136
+ } catch (err) {
137
+ session.status = "error";
138
+ session.lastError = String(err);
139
+ this.pushEvent(session, "turn:error", { message: session.lastError });
140
+ throw err;
141
+ }
142
+ }
143
+ }
144
+
145
+ export function formatSseEvent(event: SessionEvent): string {
146
+ const payload = JSON.stringify({
147
+ id: event.id,
148
+ type: event.type,
149
+ at: event.at,
150
+ ...event.data,
151
+ });
152
+ return `event: ${event.type}\ndata: ${payload}\n\n`;
153
+ }
@@ -0,0 +1,29 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+
3
+ export function readJsonBody(req: IncomingMessage): Promise<unknown> {
4
+ return new Promise((resolve, reject) => {
5
+ let body = "";
6
+ req.on("data", (chunk) => {
7
+ body += chunk;
8
+ });
9
+ req.on("end", () => {
10
+ if (!body.trim()) {
11
+ resolve({});
12
+ return;
13
+ }
14
+ try {
15
+ resolve(JSON.parse(body));
16
+ } catch (err) {
17
+ reject(err);
18
+ }
19
+ });
20
+ req.on("error", reject);
21
+ });
22
+ }
23
+
24
+ export function sendJson(res: ServerResponse, status: number, payload: unknown): void {
25
+ const body = JSON.stringify(payload, null, 2);
26
+ res.statusCode = status;
27
+ res.setHeader("content-type", "application/json; charset=utf-8");
28
+ res.end(body);
29
+ }
@@ -0,0 +1,147 @@
1
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
+ import { watch, type FSWatcher } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import type { AgentDefinition } from "../define-agent.js";
6
+ import { sendJson } from "./http.js";
7
+ import {
8
+ createPlasmApp,
9
+ handlePlasmOperatorRequest,
10
+ handlePlasmRequest,
11
+ type PlasmApp,
12
+ } from "../server/plasm-handler.js";
13
+
14
+ export interface DevServerOptions {
15
+ agentRoot: string;
16
+ definition: AgentDefinition;
17
+ port?: number;
18
+ host?: string;
19
+ tenantScope?: string;
20
+ maxSteps?: number;
21
+ telemetry?: boolean;
22
+ }
23
+
24
+ export interface DevServerHandle {
25
+ server: Server;
26
+ url: string;
27
+ close(): Promise<void>;
28
+ reload(): Promise<void>;
29
+ }
30
+
31
+ const HOT_RELOAD_PATHS = [
32
+ "catalogs",
33
+ "skills",
34
+ "channels",
35
+ "schedules",
36
+ "hooks",
37
+ "subagents",
38
+ "instructions.md",
39
+ "instructions.ts",
40
+ ] as const;
41
+
42
+ function watchHotReload(
43
+ agentRoot: string,
44
+ onChange: () => void,
45
+ ): FSWatcher[] {
46
+ const watchers: FSWatcher[] = [];
47
+ let timer: ReturnType<typeof setTimeout> | undefined;
48
+
49
+ const schedule = () => {
50
+ if (timer) clearTimeout(timer);
51
+ timer = setTimeout(() => {
52
+ timer = undefined;
53
+ onChange();
54
+ }, 150);
55
+ };
56
+
57
+ for (const relative of HOT_RELOAD_PATHS) {
58
+ const target = path.join(agentRoot, relative);
59
+ try {
60
+ const recursive = !relative.endsWith(".md") && !relative.endsWith(".ts");
61
+ const watcher = watch(target, { recursive }, schedule);
62
+ watchers.push(watcher);
63
+ } catch {
64
+ // Missing slot at dev start — discovery will report warnings.
65
+ }
66
+ }
67
+
68
+ return watchers;
69
+ }
70
+
71
+ async function routeDevRequest(
72
+ req: IncomingMessage,
73
+ res: ServerResponse,
74
+ app: PlasmApp,
75
+ ): Promise<void> {
76
+ const url = new URL(req.url ?? "/", "http://localhost");
77
+ if (url.pathname === "/operator" || url.pathname.startsWith("/operator/")) {
78
+ await handlePlasmOperatorRequest(req, res, app);
79
+ return;
80
+ }
81
+ await handlePlasmRequest(req, res, app);
82
+ }
83
+
84
+ export async function createDevServer(options: DevServerOptions): Promise<DevServerHandle> {
85
+ const agentRoot = path.resolve(options.agentRoot);
86
+
87
+ const app = await createPlasmApp({
88
+ agentRoot,
89
+ definition: options.definition,
90
+ mode: "dev",
91
+ tenantScope: options.tenantScope,
92
+ maxSteps: options.maxSteps,
93
+ telemetry: options.telemetry,
94
+ sessions: true,
95
+ });
96
+
97
+ const reload = async (): Promise<void> => {
98
+ await app.reload();
99
+ };
100
+
101
+ const watchers = watchHotReload(agentRoot, () => {
102
+ void reload().catch((err) => {
103
+ console.error("[plasm:dev] hot reload failed:", err);
104
+ });
105
+ });
106
+
107
+ const host = options.host ?? "127.0.0.1";
108
+ const port = options.port ?? 3000;
109
+
110
+ const server = createServer((req, res) => {
111
+ void routeDevRequest(req, res, app).catch((err) => {
112
+ console.error("[plasm:dev] request error:", err);
113
+ sendJson(res, 500, { error: "internal_error", message: String(err) });
114
+ });
115
+ });
116
+
117
+ await new Promise<void>((resolve, reject) => {
118
+ server.once("error", reject);
119
+ server.listen(port, host, () => resolve());
120
+ });
121
+
122
+ const address = server.address();
123
+ const resolvedPort =
124
+ typeof address === "object" && address ? address.port : port;
125
+ const url = `http://${host}:${resolvedPort}`;
126
+
127
+ return {
128
+ server,
129
+ url,
130
+ reload,
131
+ close: () =>
132
+ new Promise((resolve, reject) => {
133
+ for (const watcher of watchers) watcher.close();
134
+ server.close((err) => (err ? reject(err) : resolve()));
135
+ }),
136
+ };
137
+ }
138
+
139
+ export async function startDevServer(options: DevServerOptions): Promise<DevServerHandle> {
140
+ const handle = await createDevServer(options);
141
+ console.log(`[plasm:dev] listening on ${handle.url}`);
142
+ console.log(`[plasm:dev] GET ${handle.url}/plasm/v1/info`);
143
+ console.log(`[plasm:dev] POST ${handle.url}/plasm/v1/session`);
144
+ console.log(`[plasm:dev] GET ${handle.url}/plasm/v1/session/:id/stream`);
145
+ console.log(`[plasm:dev] GET ${handle.url}/operator`);
146
+ return handle;
147
+ }
@@ -0,0 +1,185 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { z } from "zod";
3
+
4
+ import type { PlasmAgent, AgentTurnResult } from "../runtime/plasm-agent.js";
5
+ import { readJsonBody, sendJson } from "./http.js";
6
+ import {
7
+ DevSessionStore,
8
+ formatSseEvent,
9
+ type DevSessionRecord,
10
+ } from "./dev-session.js";
11
+
12
+ const newSessionBody = z.object({
13
+ message: z.string().trim().min(1),
14
+ wait: z.boolean().optional(),
15
+ });
16
+
17
+ const continueSessionBody = z.object({
18
+ message: z.string().trim().min(1),
19
+ continuationToken: z.string().trim().min(1),
20
+ wait: z.boolean().optional(),
21
+ });
22
+
23
+ export interface SessionRouteContext {
24
+ sessionStore: DevSessionStore;
25
+ getAgent: () => Promise<PlasmAgent>;
26
+ }
27
+
28
+ function sessionJson(
29
+ session: DevSessionRecord,
30
+ extra: Record<string, unknown> = {},
31
+ ): Record<string, unknown> {
32
+ return {
33
+ sessionId: session.id,
34
+ continuationToken: session.continuationToken,
35
+ status: session.status,
36
+ ...extra,
37
+ };
38
+ }
39
+
40
+ async function respondSessionTurn(
41
+ res: ServerResponse,
42
+ session: DevSessionRecord,
43
+ run: () => Promise<AgentTurnResult>,
44
+ ): Promise<void> {
45
+ try {
46
+ const result = await run();
47
+ sendJson(res, 200, sessionJson(session, {
48
+ text: result.text,
49
+ usage: result.usage,
50
+ steps: result.steps.length,
51
+ }));
52
+ } catch (err) {
53
+ sendJson(res, 500, sessionJson(session, {
54
+ error: "turn_failed",
55
+ message: String(err),
56
+ }));
57
+ }
58
+ }
59
+
60
+ function startAsyncTurn(
61
+ session: DevSessionRecord,
62
+ message: string,
63
+ agent: PlasmAgent,
64
+ sessionStore: DevSessionStore,
65
+ ): void {
66
+ void sessionStore.runTurn(session, message, agent).catch((err) => {
67
+ console.error("[plasm:dev] async turn failed:", err);
68
+ });
69
+ }
70
+
71
+ function handleSessionStream(
72
+ res: ServerResponse,
73
+ session: DevSessionRecord,
74
+ sessionStore: DevSessionStore,
75
+ ): void {
76
+ res.statusCode = 200;
77
+ res.setHeader("content-type", "text/event-stream; charset=utf-8");
78
+ res.setHeader("cache-control", "no-cache");
79
+ res.setHeader("connection", "keep-alive");
80
+
81
+ for (const event of session.events) {
82
+ res.write(formatSseEvent(event));
83
+ }
84
+
85
+ const terminal = session.status === "idle" || session.status === "error";
86
+ if (terminal) {
87
+ res.end();
88
+ return;
89
+ }
90
+
91
+ let unsubscribe = () => {};
92
+ unsubscribe = sessionStore.subscribe(session.id, (event) => {
93
+ res.write(formatSseEvent(event));
94
+ if (event.type === "turn:finish" || event.type === "turn:error") {
95
+ unsubscribe();
96
+ res.end();
97
+ }
98
+ });
99
+ res.on("close", unsubscribe);
100
+ }
101
+
102
+ /**
103
+ * Session routes. `wait: false` returns immediately (202) and runs the turn in the background;
104
+ * clients attach to GET /stream for step events. Default `wait: true` blocks until the turn ends.
105
+ */
106
+ export async function tryHandleSessionRoutes(
107
+ req: IncomingMessage,
108
+ res: ServerResponse,
109
+ url: URL,
110
+ ctx: SessionRouteContext,
111
+ ): Promise<boolean> {
112
+ const method = req.method ?? "GET";
113
+
114
+ if (method === "POST" && url.pathname === "/plasm/v1/session") {
115
+ const parsed = newSessionBody.safeParse(await readJsonBody(req));
116
+ if (!parsed.success) {
117
+ sendJson(res, 400, { error: "message_required" });
118
+ return true;
119
+ }
120
+
121
+ const session = ctx.sessionStore.create();
122
+ const agent = await ctx.getAgent();
123
+ if (parsed.data.wait === false) {
124
+ startAsyncTurn(session, parsed.data.message, agent, ctx.sessionStore);
125
+ sendJson(res, 202, sessionJson(session));
126
+ return true;
127
+ }
128
+
129
+ await respondSessionTurn(res, session, () =>
130
+ ctx.sessionStore.runTurn(session, parsed.data.message, agent),
131
+ );
132
+ return true;
133
+ }
134
+
135
+ const streamMatch = url.pathname.match(/^\/plasm\/v1\/session\/([^/]+)\/stream$/);
136
+ if (method === "GET" && streamMatch) {
137
+ const sessionId = streamMatch[1]!;
138
+ const session = ctx.sessionStore.get(sessionId);
139
+ if (!session) {
140
+ sendJson(res, 404, { error: "session_not_found", sessionId });
141
+ return true;
142
+ }
143
+ handleSessionStream(res, session, ctx.sessionStore);
144
+ return true;
145
+ }
146
+
147
+ const continueMatch = url.pathname.match(/^\/plasm\/v1\/session\/([^/]+)$/);
148
+ if (method === "POST" && continueMatch) {
149
+ const sessionId = continueMatch[1]!;
150
+ const raw = await readJsonBody(req);
151
+ const tokenParsed = z.object({ continuationToken: z.string().trim().min(1) }).safeParse(raw);
152
+ if (!tokenParsed.success) {
153
+ sendJson(res, 400, { error: "continuation_token_required" });
154
+ return true;
155
+ }
156
+ const messageParsed = continueSessionBody.safeParse(raw);
157
+ if (!messageParsed.success) {
158
+ sendJson(res, 400, { error: "message_required" });
159
+ return true;
160
+ }
161
+
162
+ const session = ctx.sessionStore.validateContinuation(
163
+ sessionId,
164
+ messageParsed.data.continuationToken,
165
+ );
166
+ if (!session) {
167
+ sendJson(res, 404, { error: "session_not_found", sessionId });
168
+ return true;
169
+ }
170
+
171
+ const agent = await ctx.getAgent();
172
+ if (messageParsed.data.wait === false) {
173
+ startAsyncTurn(session, messageParsed.data.message, agent, ctx.sessionStore);
174
+ sendJson(res, 202, sessionJson(session));
175
+ return true;
176
+ }
177
+
178
+ await respondSessionTurn(res, session, () =>
179
+ ctx.sessionStore.runTurn(session, messageParsed.data.message, agent),
180
+ );
181
+ return true;
182
+ }
183
+
184
+ return false;
185
+ }