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.
- package/esm/deno.js +1 -1
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
- package/esm/src/internal-agents/run-stream.js +60 -79
- package/esm/src/internal-agents/schema.d.ts +243 -4
- package/esm/src/internal-agents/schema.d.ts.map +1 -1
- package/esm/src/internal-agents/schema.js +219 -8
- package/esm/src/jobs/schemas.d.ts +4 -4
- package/esm/src/mcp/elicitation.d.ts +16 -0
- package/esm/src/mcp/elicitation.d.ts.map +1 -0
- package/esm/src/mcp/elicitation.js +21 -0
- package/esm/src/mcp/index.d.ts +3 -0
- package/esm/src/mcp/index.d.ts.map +1 -1
- package/esm/src/mcp/index.js +2 -0
- package/esm/src/mcp/server.d.ts +22 -0
- package/esm/src/mcp/server.d.ts.map +1 -1
- package/esm/src/mcp/server.js +163 -2
- package/esm/src/mcp/task-store.d.ts +27 -0
- package/esm/src/mcp/task-store.d.ts.map +1 -0
- package/esm/src/mcp/task-store.js +116 -0
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.js +4 -2
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/agent-stream.handler.js +4 -3
- package/esm/src/tool/remote-mcp.d.ts.map +1 -1
- package/esm/src/tool/remote-mcp.js +60 -1
- package/esm/src/tool/types.d.ts +2 -0
- package/esm/src/tool/types.d.ts.map +1 -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/deno.js +1 -1
- package/src/src/internal-agents/run-stream.ts +61 -94
- package/src/src/internal-agents/schema.ts +277 -10
- package/src/src/mcp/elicitation.ts +42 -0
- package/src/src/mcp/index.ts +9 -0
- package/src/src/mcp/server.ts +185 -2
- package/src/src/mcp/task-store.ts +137 -0
- package/src/src/modules/react-loader/ssr-module-loader/cache/memory.ts +4 -2
- package/src/src/server/handlers/request/agent-stream.handler.ts +5 -3
- package/src/src/tool/remote-mcp.ts +86 -1
- package/src/src/tool/types.ts +2 -0
- 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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
253
|
+
return await parseJsonRpcResponse(response);
|
|
169
254
|
}
|
|
170
255
|
|
|
171
256
|
function getJsonRpcResult(payload: unknown): unknown {
|
package/src/src/tool/types.ts
CHANGED
|
@@ -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) */
|