veryfront 0.1.146 → 0.1.147

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 (42) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
  3. package/esm/src/internal-agents/run-stream.js +60 -79
  4. package/esm/src/internal-agents/schema.d.ts +243 -4
  5. package/esm/src/internal-agents/schema.d.ts.map +1 -1
  6. package/esm/src/internal-agents/schema.js +219 -8
  7. package/esm/src/jobs/schemas.d.ts +4 -4
  8. package/esm/src/mcp/elicitation.d.ts +16 -0
  9. package/esm/src/mcp/elicitation.d.ts.map +1 -0
  10. package/esm/src/mcp/elicitation.js +21 -0
  11. package/esm/src/mcp/index.d.ts +3 -0
  12. package/esm/src/mcp/index.d.ts.map +1 -1
  13. package/esm/src/mcp/index.js +2 -0
  14. package/esm/src/mcp/server.d.ts +22 -0
  15. package/esm/src/mcp/server.d.ts.map +1 -1
  16. package/esm/src/mcp/server.js +163 -2
  17. package/esm/src/mcp/task-store.d.ts +27 -0
  18. package/esm/src/mcp/task-store.d.ts.map +1 -0
  19. package/esm/src/mcp/task-store.js +116 -0
  20. package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.d.ts.map +1 -1
  21. package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.js +4 -2
  22. package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
  23. package/esm/src/server/handlers/request/agent-stream.handler.js +4 -3
  24. package/esm/src/tool/remote-mcp.d.ts.map +1 -1
  25. package/esm/src/tool/remote-mcp.js +60 -1
  26. package/esm/src/tool/types.d.ts +2 -0
  27. package/esm/src/tool/types.d.ts.map +1 -1
  28. package/esm/src/utils/version-constant.d.ts +1 -1
  29. package/esm/src/utils/version-constant.js +1 -1
  30. package/package.json +1 -1
  31. package/src/deno.js +1 -1
  32. package/src/src/internal-agents/run-stream.ts +61 -94
  33. package/src/src/internal-agents/schema.ts +277 -10
  34. package/src/src/mcp/elicitation.ts +42 -0
  35. package/src/src/mcp/index.ts +9 -0
  36. package/src/src/mcp/server.ts +185 -2
  37. package/src/src/mcp/task-store.ts +137 -0
  38. package/src/src/modules/react-loader/ssr-module-loader/cache/memory.ts +4 -2
  39. package/src/src/server/handlers/request/agent-stream.handler.ts +5 -3
  40. package/src/src/tool/remote-mcp.ts +86 -1
  41. package/src/src/tool/types.ts +2 -0
  42. package/src/src/utils/version-constant.ts +1 -1
@@ -0,0 +1,137 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ export interface Task {
3
+ taskId: string;
4
+ status: "working" | "input_required" | "completed" | "failed" | "cancelled";
5
+ statusMessage?: string;
6
+ createdAt: string;
7
+ lastUpdatedAt: string;
8
+ ttl: number;
9
+ pollInterval?: number;
10
+ }
11
+
12
+ const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
13
+ const DEFAULT_POLL_INTERVAL = 2000;
14
+ const SWEEP_INTERVAL_MS = 30_000;
15
+ const MAX_TASKS = 1000;
16
+
17
+ export class TaskStore {
18
+ private tasks = new Map<string, Task>();
19
+ private results = new Map<string, unknown>();
20
+ private lastSweep = 0;
21
+
22
+ create(ttl: number): Task {
23
+ this.lazySweep();
24
+ if (this.tasks.size >= MAX_TASKS) {
25
+ this.evictOldest();
26
+ }
27
+
28
+ const now = new Date().toISOString();
29
+ const task: Task = {
30
+ taskId: dntShim.crypto.randomUUID(),
31
+ status: "working",
32
+ createdAt: now,
33
+ lastUpdatedAt: now,
34
+ ttl,
35
+ pollInterval: DEFAULT_POLL_INTERVAL,
36
+ };
37
+ this.tasks.set(task.taskId, task);
38
+ return task;
39
+ }
40
+
41
+ get(taskId: string): Task | undefined {
42
+ const task = this.tasks.get(taskId);
43
+ if (task && this.isExpired(task)) {
44
+ this.tasks.delete(taskId);
45
+ this.results.delete(taskId);
46
+ return undefined;
47
+ }
48
+ return task;
49
+ }
50
+
51
+ complete(taskId: string, result: unknown): void {
52
+ const task = this.tasks.get(taskId);
53
+ if (!task || TERMINAL_STATUSES.has(task.status)) return;
54
+ task.status = "completed";
55
+ task.lastUpdatedAt = new Date().toISOString();
56
+ this.results.set(taskId, result);
57
+ }
58
+
59
+ fail(taskId: string, message: string): void {
60
+ const task = this.tasks.get(taskId);
61
+ if (!task || TERMINAL_STATUSES.has(task.status)) return;
62
+ task.status = "failed";
63
+ task.statusMessage = message;
64
+ task.lastUpdatedAt = new Date().toISOString();
65
+ }
66
+
67
+ cancel(taskId: string): boolean {
68
+ const task = this.tasks.get(taskId);
69
+ if (!task || TERMINAL_STATUSES.has(task.status)) return false;
70
+ task.status = "cancelled";
71
+ task.statusMessage = "The task was cancelled by request.";
72
+ task.lastUpdatedAt = new Date().toISOString();
73
+ return true;
74
+ }
75
+
76
+ getResult(taskId: string): unknown | undefined {
77
+ const task = this.get(taskId);
78
+ if (!task || !TERMINAL_STATUSES.has(task.status)) return undefined;
79
+ return this.results.get(taskId);
80
+ }
81
+
82
+ list(): Task[] {
83
+ this.lazySweep();
84
+ return Array.from(this.tasks.values());
85
+ }
86
+
87
+ clear(): void {
88
+ this.tasks.clear();
89
+ this.results.clear();
90
+ }
91
+
92
+ private isExpired(task: Task): boolean {
93
+ return Date.now() - new Date(task.createdAt).getTime() > task.ttl;
94
+ }
95
+
96
+ private lazySweep(): void {
97
+ const now = Date.now();
98
+ if (now - this.lastSweep < SWEEP_INTERVAL_MS) return;
99
+ this.lastSweep = now;
100
+ this.sweep();
101
+ }
102
+
103
+ private sweep(): void {
104
+ for (const [id, task] of this.tasks) {
105
+ if (this.isExpired(task)) {
106
+ this.tasks.delete(id);
107
+ this.results.delete(id);
108
+ }
109
+ }
110
+ }
111
+
112
+ private evictOldest(): void {
113
+ // Evict the oldest terminal task, or the oldest task overall
114
+ let oldestTerminal: string | undefined;
115
+ let oldestAny: string | undefined;
116
+ let oldestTerminalTime = Infinity;
117
+ let oldestAnyTime = Infinity;
118
+
119
+ for (const [id, task] of this.tasks) {
120
+ const created = new Date(task.createdAt).getTime();
121
+ if (created < oldestAnyTime) {
122
+ oldestAnyTime = created;
123
+ oldestAny = id;
124
+ }
125
+ if (TERMINAL_STATUSES.has(task.status) && created < oldestTerminalTime) {
126
+ oldestTerminalTime = created;
127
+ oldestTerminal = id;
128
+ }
129
+ }
130
+
131
+ const toEvict = oldestTerminal ?? oldestAny;
132
+ if (toEvict) {
133
+ this.tasks.delete(toEvict);
134
+ this.results.delete(toEvict);
135
+ }
136
+ }
137
+ }
@@ -74,10 +74,12 @@ const RATE_LIMIT_BYPASS_PROJECTS = new Set(["__single__"]);
74
74
 
75
75
  /**
76
76
  * Project ID prefixes that bypass per-project rate limiting.
77
- * - "local-": Used for compiled binary CLI, local filesystem projects, and tests
77
+ * - "local-": Used for compiled binary CLI, local filesystem projects
78
78
  * where there's no multi-tenancy and noisy-neighbor protection isn't needed.
79
+ * - "test_": Used by integration tests (TestContext.projectId) where there's
80
+ * no multi-tenancy concern and rate limiting causes flaky failures under CI load.
79
81
  */
80
- const RATE_LIMIT_BYPASS_PREFIXES = ["local-"];
82
+ const RATE_LIMIT_BYPASS_PREFIXES = ["local-", "test_"];
81
83
 
82
84
  /**
83
85
  * Attempt to acquire a project-level transform slot immediately.
@@ -24,8 +24,9 @@ import {
24
24
  agentRunSessionManager,
25
25
  } from "../../../internal-agents/session-manager.js";
26
26
  import {
27
+ InternalAgentStreamRequestSchema,
27
28
  type RuntimeAgentSourceContext,
28
- RuntimeRunAgentInputSchema,
29
+ toRuntimeRunAgentInput,
29
30
  } from "../../../internal-agents/schema.js";
30
31
  import { BaseHandler } from "../response/base.js";
31
32
  import type { HandlerContext, HandlerMetadata, HandlerPriority, HandlerResult } from "../types.js";
@@ -162,7 +163,7 @@ export class AgentStreamHandler extends BaseHandler {
162
163
  req,
163
164
  INTERNAL_AGENT_STREAM_MAX_BODY_BYTES,
164
165
  );
165
- const payload = RuntimeRunAgentInputSchema.parse(JSON.parse(rawBody));
166
+ const payload = InternalAgentStreamRequestSchema.parse(JSON.parse(rawBody));
166
167
  await verifyControlPlaneRequest(req, ctx, rawBody, {
167
168
  expectedSubject: payload.runId,
168
169
  expectedSurface: "studio",
@@ -195,8 +196,9 @@ export class AgentStreamHandler extends BaseHandler {
195
196
  return this.respond(builder.json({ error: "Agent not found" }, 404));
196
197
  }
197
198
 
199
+ const runtimeInput = toRuntimeRunAgentInput(payload);
198
200
  const response = await createRuntimeAgentStreamResponse(
199
- payload,
201
+ runtimeInput,
200
202
  agent as Agent,
201
203
  this.deps,
202
204
  );
@@ -23,6 +23,10 @@ interface JsonRpcCallToolContentItem {
23
23
  text?: unknown;
24
24
  }
25
25
 
26
+ interface SseEvent {
27
+ data: string[];
28
+ }
29
+
26
30
  function isRecord(value: unknown): value is Record<string, unknown> {
27
31
  return typeof value === "object" && value !== null;
28
32
  }
@@ -126,6 +130,67 @@ function extractJsonRpcErrorMessage(payload: Record<string, unknown>): string {
126
130
  return "Remote MCP server returned an error";
127
131
  }
128
132
 
133
+ function parseSseEvents(text: string): SseEvent[] {
134
+ const events: SseEvent[] = [];
135
+ let currentEvent: SseEvent = { data: [] };
136
+
137
+ for (const rawLine of text.split(/\r?\n/)) {
138
+ const line = rawLine.trimEnd();
139
+
140
+ if (line.length === 0) {
141
+ if (currentEvent.data.length > 0) {
142
+ events.push(currentEvent);
143
+ }
144
+ currentEvent = { data: [] };
145
+ continue;
146
+ }
147
+
148
+ if (line.startsWith(":")) {
149
+ continue;
150
+ }
151
+
152
+ if (line.startsWith("data:")) {
153
+ currentEvent.data.push(line.slice(5).trimStart());
154
+ }
155
+ }
156
+
157
+ if (currentEvent.data.length > 0) {
158
+ events.push(currentEvent);
159
+ }
160
+
161
+ return events;
162
+ }
163
+
164
+ function parseJsonRpcSsePayload(text: string): unknown {
165
+ const parsedPayloads = parseSseEvents(text)
166
+ .map((event) => parseJsonText(event.data.join("\n")))
167
+ .filter((payload): payload is unknown => payload !== undefined);
168
+
169
+ const jsonRpcPayload = parsedPayloads.find(
170
+ (payload) => isRecord(payload) && ("result" in payload || "error" in payload),
171
+ );
172
+
173
+ if (jsonRpcPayload !== undefined) {
174
+ return jsonRpcPayload;
175
+ }
176
+
177
+ if (parsedPayloads.length > 0) {
178
+ return parsedPayloads[0];
179
+ }
180
+
181
+ throw new Error("Remote MCP SSE response did not include a JSON-RPC payload");
182
+ }
183
+
184
+ async function parseJsonRpcResponse(response: dntShim.Response): Promise<unknown> {
185
+ const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
186
+
187
+ if (contentType.includes("text/event-stream")) {
188
+ return parseJsonRpcSsePayload(await response.text());
189
+ }
190
+
191
+ return await response.json();
192
+ }
193
+
129
194
  async function resolveValue<T>(
130
195
  value: ResolvableValue<T>,
131
196
  context?: ToolExecutionContext,
@@ -143,9 +208,29 @@ async function resolveHeaders(
143
208
  const resolvedHeaders = headers ? await resolveValue(headers, context) : undefined;
144
209
  const finalHeaders = new dntShim.Headers(resolvedHeaders);
145
210
  finalHeaders.set("Content-Type", "application/json");
211
+ finalHeaders.set("Accept", mergeAcceptHeader(finalHeaders.get("Accept")));
146
212
  return finalHeaders;
147
213
  }
148
214
 
215
+ function mergeAcceptHeader(existingAccept: string | null): string {
216
+ const requiredTypes = ["application/json", "text/event-stream"];
217
+ const existingTypes = (existingAccept ?? "")
218
+ .split(",")
219
+ .map((entry) => entry.trim())
220
+ .filter((entry) => entry.length > 0);
221
+ const existingKeys = new Set(
222
+ existingTypes.map((entry) => entry.split(";")[0]?.trim().toLowerCase()).filter(Boolean),
223
+ );
224
+
225
+ for (const requiredType of requiredTypes) {
226
+ if (!existingKeys.has(requiredType)) {
227
+ existingTypes.push(requiredType);
228
+ }
229
+ }
230
+
231
+ return existingTypes.join(", ");
232
+ }
233
+
149
234
  async function postJsonRpc(
150
235
  endpoint: string,
151
236
  headers: dntShim.Headers,
@@ -165,7 +250,7 @@ async function postJsonRpc(
165
250
  );
166
251
  }
167
252
 
168
- return await response.json();
253
+ return await parseJsonRpcResponse(response);
169
254
  }
170
255
 
171
256
  function getJsonRpcResult(payload: unknown): unknown {
@@ -61,6 +61,8 @@ export interface ToolExecutionContext {
61
61
  projectId?: string;
62
62
  /** End-user identity for per-user token resolution in integration tools */
63
63
  endUserId?: string;
64
+ /** Progress token for sending progress notifications (MCP 2025-11-25) */
65
+ progressToken?: string | number;
64
66
  /** Additional context */
65
67
  [key: string]: unknown;
66
68
  /** Blob storage access (if configured in workflow) */
@@ -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.146";
3
+ export const VERSION = "0.1.147";