veryfront 0.1.63 → 0.1.65
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/agent/runtime/index.d.ts +1 -0
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +10 -2
- package/esm/src/channels/control-plane.d.ts +259 -0
- package/esm/src/channels/control-plane.d.ts.map +1 -0
- package/esm/src/channels/control-plane.js +212 -0
- package/esm/src/channels/invoke.d.ts +71 -44
- package/esm/src/channels/invoke.d.ts.map +1 -1
- package/esm/src/channels/invoke.js +33 -114
- package/esm/src/integrations/endpoint-executor.d.ts +1 -0
- package/esm/src/integrations/endpoint-executor.d.ts.map +1 -1
- package/esm/src/integrations/endpoint-executor.js +44 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts +35 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts.map +1 -0
- package/esm/src/internal-agents/ag-ui-sse.js +263 -0
- package/esm/src/internal-agents/control-plane-auth.d.ts +20 -0
- package/esm/src/internal-agents/control-plane-auth.d.ts.map +1 -0
- package/esm/src/internal-agents/control-plane-auth.js +56 -0
- package/esm/src/internal-agents/request-body.d.ts +9 -0
- package/esm/src/internal-agents/request-body.d.ts.map +1 -0
- package/esm/src/internal-agents/request-body.js +28 -0
- package/esm/src/internal-agents/run-stream.d.ts +14 -0
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -0
- package/esm/src/internal-agents/run-stream.js +259 -0
- package/esm/src/internal-agents/schema.d.ts +268 -0
- package/esm/src/internal-agents/schema.d.ts.map +1 -0
- package/esm/src/internal-agents/schema.js +71 -0
- package/esm/src/internal-agents/session-manager.d.ts +63 -0
- package/esm/src/internal-agents/session-manager.d.ts.map +1 -0
- package/esm/src/internal-agents/session-manager.js +258 -0
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.js +4 -13
- package/esm/src/platform/compat/process.d.ts.map +1 -1
- package/esm/src/platform/compat/process.js +42 -5
- package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts +11 -0
- package/esm/src/server/handlers/request/agent-run-cancel.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/agent-run-cancel.handler.js +62 -0
- package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts +11 -0
- package/esm/src/server/handlers/request/agent-run-resume.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/agent-run-resume.handler.js +77 -0
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts +14 -0
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/agent-stream.handler.js +86 -0
- package/esm/src/server/handlers/request/internal-agents-list.handler.d.ts +11 -0
- package/esm/src/server/handlers/request/internal-agents-list.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/internal-agents-list.handler.js +73 -0
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +8 -0
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/agent/runtime/index.ts +12 -2
- package/src/src/channels/control-plane.ts +332 -0
- package/src/src/channels/invoke.ts +44 -164
- package/src/src/integrations/endpoint-executor.ts +51 -0
- package/src/src/internal-agents/ag-ui-sse.ts +327 -0
- package/src/src/internal-agents/control-plane-auth.ts +82 -0
- package/src/src/internal-agents/request-body.ts +42 -0
- package/src/src/internal-agents/run-stream.ts +354 -0
- package/src/src/internal-agents/schema.ts +102 -0
- package/src/src/internal-agents/session-manager.ts +358 -0
- package/src/src/platform/adapters/runtime/deno/adapter.ts +9 -11
- package/src/src/platform/compat/process.ts +56 -3
- package/src/src/server/handlers/request/agent-run-cancel.handler.ts +86 -0
- package/src/src/server/handlers/request/agent-run-resume.handler.ts +108 -0
- package/src/src/server/handlers/request/agent-stream.handler.ts +125 -0
- package/src/src/server/handlers/request/internal-agents-list.handler.ts +100 -0
- package/src/src/server/runtime-handler/index.ts +8 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
2
|
+
function stableJsonStringify(value: unknown, depth = 0): string {
|
|
3
|
+
if (depth > 64) {
|
|
4
|
+
return JSON.stringify(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (value === null || typeof value !== "object") {
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return `[${value.map((item) => stableJsonStringify(item, depth + 1)).join(",")}]`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
16
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
17
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableJsonStringify(item, depth + 1)}`);
|
|
18
|
+
|
|
19
|
+
return `{${entries.join(",")}}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createToolResultKey(result: unknown, isError: boolean): string {
|
|
23
|
+
return `${isError ? "1" : "0"}:${stableJsonStringify(result)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AgentRunCancelledError extends Error {
|
|
27
|
+
constructor(message = "Run cancelled") {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "AgentRunCancelledError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AgentRunAlreadyExistsError extends Error {
|
|
34
|
+
constructor(runId: string) {
|
|
35
|
+
super(`Run "${runId}" is already active`);
|
|
36
|
+
this.name = "AgentRunAlreadyExistsError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class RunNotActiveError extends Error {
|
|
41
|
+
constructor(runId: string) {
|
|
42
|
+
super(`Run "${runId}" is not active`);
|
|
43
|
+
this.name = "RunNotActiveError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ToolResultNotWaitingError extends Error {
|
|
48
|
+
constructor(runId: string, toolCallId: string) {
|
|
49
|
+
super(`Run "${runId}" is not waiting for tool call "${toolCallId}"`);
|
|
50
|
+
this.name = "ToolResultNotWaitingError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class ToolResultConflictError extends Error {
|
|
55
|
+
constructor(runId: string, toolCallId: string) {
|
|
56
|
+
super(`Conflicting tool result for run "${runId}" and tool call "${toolCallId}"`);
|
|
57
|
+
this.name = "ToolResultConflictError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DEFAULT_WAITING_FOR_TOOL_TTL_MS = 5 * 60 * 1000;
|
|
62
|
+
const DEFAULT_SESSION_TTL_MS = 15 * 60 * 1000;
|
|
63
|
+
const DEFAULT_MAX_CONCURRENT_SESSIONS = 100;
|
|
64
|
+
|
|
65
|
+
type SessionStatus = "running" | "waiting" | "completed" | "cancelled" | "failed";
|
|
66
|
+
|
|
67
|
+
interface SubmittedToolResult {
|
|
68
|
+
toolCallId: string;
|
|
69
|
+
result: unknown;
|
|
70
|
+
isError: boolean;
|
|
71
|
+
key: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface WaitingToolState {
|
|
75
|
+
toolCallId: string;
|
|
76
|
+
resolve: (value: SubmittedToolResult) => void;
|
|
77
|
+
reject: (reason?: unknown) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface AgentRunSession {
|
|
81
|
+
runId: string;
|
|
82
|
+
threadId: string;
|
|
83
|
+
status: SessionStatus;
|
|
84
|
+
abortController: AbortController;
|
|
85
|
+
waitingTool: WaitingToolState | null;
|
|
86
|
+
submittedResults: Map<string, SubmittedToolResult>;
|
|
87
|
+
waitingTimeoutId: number | null;
|
|
88
|
+
sessionTimeoutId: number | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SubmitToolResultOutcome {
|
|
92
|
+
accepted: true;
|
|
93
|
+
duplicate?: true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class AgentRunSessionManager {
|
|
97
|
+
private readonly sessions = new Map<string, AgentRunSession>();
|
|
98
|
+
|
|
99
|
+
constructor(
|
|
100
|
+
private readonly options: {
|
|
101
|
+
waitingForToolTtlMs?: number;
|
|
102
|
+
sessionTtlMs?: number | null;
|
|
103
|
+
maxConcurrentSessions?: number;
|
|
104
|
+
setTimeoutFn?: typeof dntShim.setTimeout;
|
|
105
|
+
clearTimeoutFn?: typeof clearTimeout;
|
|
106
|
+
} = {},
|
|
107
|
+
) {}
|
|
108
|
+
|
|
109
|
+
private get waitingForToolTtlMs(): number {
|
|
110
|
+
return this.options.waitingForToolTtlMs ?? DEFAULT_WAITING_FOR_TOOL_TTL_MS;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private get sessionTtlMs(): number | null {
|
|
114
|
+
return this.options.sessionTtlMs ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private get setTimeoutFn(): typeof dntShim.setTimeout {
|
|
118
|
+
return this.options.setTimeoutFn ?? dntShim.dntGlobalThis.setTimeout.bind(dntShim.dntGlobalThis);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private get clearTimeoutFn(): typeof clearTimeout {
|
|
122
|
+
return this.options.clearTimeoutFn ?? globalThis.clearTimeout.bind(dntShim.dntGlobalThis);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private clearWaitingTimeout(session: AgentRunSession): void {
|
|
126
|
+
if (session.waitingTimeoutId === null) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.clearTimeoutFn(session.waitingTimeoutId);
|
|
131
|
+
session.waitingTimeoutId = null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private clearSessionTimeout(session: AgentRunSession): void {
|
|
135
|
+
if (session.sessionTimeoutId === null) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.clearTimeoutFn(session.sessionTimeoutId);
|
|
140
|
+
session.sessionTimeoutId = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private scheduleSessionTimeout(session: AgentRunSession): void {
|
|
144
|
+
if (this.sessionTtlMs === null) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.clearSessionTimeout(session);
|
|
149
|
+
session.sessionTimeoutId = this.setTimeoutFn(() => {
|
|
150
|
+
this.cancelRun(session.runId);
|
|
151
|
+
}, this.sessionTtlMs) as unknown as number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private scheduleWaitingTimeout(session: AgentRunSession): void {
|
|
155
|
+
this.clearWaitingTimeout(session);
|
|
156
|
+
session.waitingTimeoutId = this.setTimeoutFn(() => {
|
|
157
|
+
this.cancelRun(session.runId);
|
|
158
|
+
}, this.waitingForToolTtlMs) as unknown as number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private touchSession(session: AgentRunSession): void {
|
|
162
|
+
if (
|
|
163
|
+
session.status === "running" || session.status === "waiting"
|
|
164
|
+
) {
|
|
165
|
+
this.scheduleSessionTimeout(session);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private get maxConcurrentSessions(): number {
|
|
170
|
+
return this.options.maxConcurrentSessions ?? DEFAULT_MAX_CONCURRENT_SESSIONS;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
startRun(input: { runId: string; threadId: string }): AbortSignal {
|
|
174
|
+
const existing = this.sessions.get(input.runId);
|
|
175
|
+
if (existing && (existing.status === "running" || existing.status === "waiting")) {
|
|
176
|
+
throw new AgentRunAlreadyExistsError(input.runId);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.sessions.size >= this.maxConcurrentSessions) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Maximum concurrent sessions (${this.maxConcurrentSessions}) reached`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const session: AgentRunSession = {
|
|
186
|
+
runId: input.runId,
|
|
187
|
+
threadId: input.threadId,
|
|
188
|
+
status: "running",
|
|
189
|
+
abortController: new AbortController(),
|
|
190
|
+
waitingTool: null,
|
|
191
|
+
submittedResults: new Map(),
|
|
192
|
+
waitingTimeoutId: null,
|
|
193
|
+
sessionTimeoutId: null,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
this.sessions.set(input.runId, session);
|
|
197
|
+
this.touchSession(session);
|
|
198
|
+
return session.abortController.signal;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async waitForToolResult(runId: string, toolCallId: string): Promise<{
|
|
202
|
+
result: unknown;
|
|
203
|
+
isError: boolean;
|
|
204
|
+
}> {
|
|
205
|
+
const session = this.sessions.get(runId);
|
|
206
|
+
if (!session || session.status === "completed" || session.status === "failed") {
|
|
207
|
+
throw new RunNotActiveError(runId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (session.abortController.signal.aborted || session.status === "cancelled") {
|
|
211
|
+
throw new AgentRunCancelledError();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const existingResult = session.submittedResults.get(toolCallId);
|
|
215
|
+
if (existingResult) {
|
|
216
|
+
session.status = "running";
|
|
217
|
+
this.touchSession(session);
|
|
218
|
+
return { result: existingResult.result, isError: existingResult.isError };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (session.waitingTool && session.waitingTool.toolCallId !== toolCallId) {
|
|
222
|
+
throw new ToolResultNotWaitingError(runId, toolCallId);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
session.status = "waiting";
|
|
226
|
+
this.scheduleWaitingTimeout(session);
|
|
227
|
+
this.touchSession(session);
|
|
228
|
+
|
|
229
|
+
return await new Promise<{ result: unknown; isError: boolean }>((resolve, reject) => {
|
|
230
|
+
const abortHandler = () => {
|
|
231
|
+
this.clearWaitingTimeout(session);
|
|
232
|
+
session.waitingTool = null;
|
|
233
|
+
session.status = "cancelled";
|
|
234
|
+
reject(new AgentRunCancelledError());
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
session.abortController.signal.addEventListener("abort", abortHandler, { once: true });
|
|
238
|
+
session.waitingTool = {
|
|
239
|
+
toolCallId,
|
|
240
|
+
resolve: (value) => {
|
|
241
|
+
session.abortController.signal.removeEventListener("abort", abortHandler);
|
|
242
|
+
this.clearWaitingTimeout(session);
|
|
243
|
+
session.waitingTool = null;
|
|
244
|
+
session.status = "running";
|
|
245
|
+
this.touchSession(session);
|
|
246
|
+
resolve({ result: value.result, isError: value.isError });
|
|
247
|
+
},
|
|
248
|
+
reject: (reason) => {
|
|
249
|
+
session.abortController.signal.removeEventListener("abort", abortHandler);
|
|
250
|
+
this.clearWaitingTimeout(session);
|
|
251
|
+
session.waitingTool = null;
|
|
252
|
+
reject(reason);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
submitToolResult(
|
|
259
|
+
runId: string,
|
|
260
|
+
input: { toolCallId: string; result: unknown; isError?: boolean },
|
|
261
|
+
): SubmitToolResultOutcome {
|
|
262
|
+
const session = this.sessions.get(runId);
|
|
263
|
+
if (!session) {
|
|
264
|
+
throw new RunNotActiveError(runId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const normalized: SubmittedToolResult = {
|
|
268
|
+
toolCallId: input.toolCallId,
|
|
269
|
+
result: input.result,
|
|
270
|
+
isError: Boolean(input.isError),
|
|
271
|
+
key: createToolResultKey(input.result, Boolean(input.isError)),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const existingResult = session.submittedResults.get(input.toolCallId);
|
|
275
|
+
if (existingResult) {
|
|
276
|
+
if (existingResult.key === normalized.key) {
|
|
277
|
+
return { accepted: true, duplicate: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
throw new ToolResultConflictError(runId, input.toolCallId);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
session.status === "completed" || session.status === "failed" ||
|
|
285
|
+
session.status === "cancelled"
|
|
286
|
+
) {
|
|
287
|
+
throw new RunNotActiveError(runId);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!session.waitingTool || session.waitingTool.toolCallId !== input.toolCallId) {
|
|
291
|
+
throw new ToolResultNotWaitingError(runId, input.toolCallId);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
session.submittedResults.set(input.toolCallId, normalized);
|
|
295
|
+
this.touchSession(session);
|
|
296
|
+
session.waitingTool.resolve(normalized);
|
|
297
|
+
return { accepted: true };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
cancelRun(runId: string): boolean {
|
|
301
|
+
const session = this.sessions.get(runId);
|
|
302
|
+
if (!session) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (
|
|
307
|
+
session.status === "completed" || session.status === "failed" ||
|
|
308
|
+
session.status === "cancelled"
|
|
309
|
+
) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
session.status = "cancelled";
|
|
314
|
+
this.clearWaitingTimeout(session);
|
|
315
|
+
this.clearSessionTimeout(session);
|
|
316
|
+
session.abortController.abort(new AgentRunCancelledError());
|
|
317
|
+
session.waitingTool?.reject(new AgentRunCancelledError());
|
|
318
|
+
session.waitingTool = null;
|
|
319
|
+
this.sessions.delete(runId);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
completeRun(runId: string): void {
|
|
324
|
+
const session = this.sessions.get(runId);
|
|
325
|
+
if (!session) return;
|
|
326
|
+
session.status = "completed";
|
|
327
|
+
this.clearWaitingTimeout(session);
|
|
328
|
+
this.clearSessionTimeout(session);
|
|
329
|
+
session.waitingTool = null;
|
|
330
|
+
this.sessions.delete(runId);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
failRun(runId: string): void {
|
|
334
|
+
const session = this.sessions.get(runId);
|
|
335
|
+
if (!session) return;
|
|
336
|
+
session.status = "failed";
|
|
337
|
+
this.clearWaitingTimeout(session);
|
|
338
|
+
this.clearSessionTimeout(session);
|
|
339
|
+
session.waitingTool = null;
|
|
340
|
+
this.sessions.delete(runId);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
getRunStatus(runId: string): SessionStatus | null {
|
|
344
|
+
return this.sessions.get(runId)?.status ?? null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
reset(): void {
|
|
348
|
+
for (const session of this.sessions.values()) {
|
|
349
|
+
this.clearWaitingTimeout(session);
|
|
350
|
+
this.clearSessionTimeout(session);
|
|
351
|
+
}
|
|
352
|
+
this.sessions.clear();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export const agentRunSessionManager = new AgentRunSessionManager({
|
|
357
|
+
sessionTtlMs: DEFAULT_SESSION_TTL_MS,
|
|
358
|
+
});
|
|
@@ -17,7 +17,12 @@ import type {
|
|
|
17
17
|
WebSocketUpgrade,
|
|
18
18
|
} from "../../base.js";
|
|
19
19
|
import { serverLogger } from "../../../../utils/index.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
env as getEnvObject,
|
|
22
|
+
getEnv,
|
|
23
|
+
getEnvOverlayStorage,
|
|
24
|
+
setEnv,
|
|
25
|
+
} from "../../../compat/process.js";
|
|
21
26
|
import {
|
|
22
27
|
createFileWatcher,
|
|
23
28
|
createWatcherIterator,
|
|
@@ -272,22 +277,15 @@ class DenoFileSystemAdapter implements FileSystemAdapter {
|
|
|
272
277
|
|
|
273
278
|
class DenoEnvironmentAdapter implements EnvironmentAdapter {
|
|
274
279
|
get(key: string): string | undefined {
|
|
275
|
-
|
|
276
|
-
return dntShim.Deno.env.get(key);
|
|
280
|
+
return getEnv(key);
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
set(key: string, value: string): void {
|
|
280
|
-
|
|
281
|
-
throw NOT_SUPPORTED.create({
|
|
282
|
-
detail: "DenoEnvironmentAdapter.set() can only be used in Deno runtime",
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
dntShim.Deno.env.set(key, value);
|
|
284
|
+
setEnv(key, value);
|
|
286
285
|
}
|
|
287
286
|
|
|
288
287
|
toObject(): Record<string, string> {
|
|
289
|
-
|
|
290
|
-
return dntShim.Deno.env.toObject();
|
|
288
|
+
return getEnvObject();
|
|
291
289
|
}
|
|
292
290
|
}
|
|
293
291
|
|
|
@@ -57,10 +57,46 @@ export function chdir(directory: string): void {
|
|
|
57
57
|
throw new Error("chdir() is not supported in this runtime");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
type EnvOverlayValue = string | null;
|
|
61
|
+
type EnvOverlayStore = Map<string, EnvOverlayValue>;
|
|
62
|
+
|
|
63
|
+
function getEnvOverlayStore(): EnvOverlayStore | null {
|
|
64
|
+
const storage = getEnvOverlayStorage();
|
|
65
|
+
const store = storage?.getStore();
|
|
66
|
+
return store instanceof Map ? store as EnvOverlayStore : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getOverlayEnvValue(
|
|
70
|
+
store: EnvOverlayStore | null,
|
|
71
|
+
key: string,
|
|
72
|
+
): { hasValue: boolean; value: string | undefined } {
|
|
73
|
+
if (!store?.has(key)) {
|
|
74
|
+
return { hasValue: false, value: undefined };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const value = store.get(key);
|
|
78
|
+
return { hasValue: true, value: value ?? undefined };
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
export function env(): Record<string, string> {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
const base = IS_DENO
|
|
83
|
+
? dntShim.Deno.env.toObject()
|
|
84
|
+
: runtimeProcess
|
|
85
|
+
? { ...runtimeProcess.env } as Record<string, string>
|
|
86
|
+
: {};
|
|
87
|
+
|
|
88
|
+
const overlay = getEnvOverlayStore();
|
|
89
|
+
if (!overlay) return base;
|
|
90
|
+
|
|
91
|
+
for (const [key, value] of overlay.entries()) {
|
|
92
|
+
if (value === null) {
|
|
93
|
+
delete base[key];
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
base[key] = value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return base;
|
|
64
100
|
}
|
|
65
101
|
|
|
66
102
|
/**
|
|
@@ -68,6 +104,11 @@ export function env(): Record<string, string> {
|
|
|
68
104
|
* Use this for framework-owned runtime configuration that should not be shadowed by tenant env.
|
|
69
105
|
*/
|
|
70
106
|
export function getHostEnv(key: string): string | undefined {
|
|
107
|
+
const overlayResult = getOverlayEnvValue(getEnvOverlayStore(), key);
|
|
108
|
+
if (overlayResult.hasValue) {
|
|
109
|
+
return overlayResult.value;
|
|
110
|
+
}
|
|
111
|
+
|
|
71
112
|
if (IS_DENO) return dntShim.Deno.env.get(key);
|
|
72
113
|
if (runtimeProcess) return runtimeProcess.env[key];
|
|
73
114
|
return undefined;
|
|
@@ -184,6 +225,12 @@ export function getEnvBoolean(
|
|
|
184
225
|
}
|
|
185
226
|
|
|
186
227
|
export function setEnv(key: string, value: string): void {
|
|
228
|
+
const overlay = getEnvOverlayStore();
|
|
229
|
+
if (overlay) {
|
|
230
|
+
overlay.set(key, value);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
187
234
|
if (IS_DENO) {
|
|
188
235
|
dntShim.Deno.env.set(key, value);
|
|
189
236
|
return;
|
|
@@ -196,6 +243,12 @@ export function setEnv(key: string, value: string): void {
|
|
|
196
243
|
}
|
|
197
244
|
|
|
198
245
|
export function deleteEnv(key: string): void {
|
|
246
|
+
const overlay = getEnvOverlayStore();
|
|
247
|
+
if (overlay) {
|
|
248
|
+
overlay.set(key, null);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
199
252
|
if (IS_DENO) {
|
|
200
253
|
dntShim.Deno.env.delete(key);
|
|
201
254
|
return;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
2
|
+
import {
|
|
3
|
+
ControlPlaneRequestError,
|
|
4
|
+
verifyControlPlaneRequest,
|
|
5
|
+
} from "../../../internal-agents/control-plane-auth.js";
|
|
6
|
+
import {
|
|
7
|
+
type AgentRunSessionManager,
|
|
8
|
+
agentRunSessionManager,
|
|
9
|
+
} from "../../../internal-agents/session-manager.js";
|
|
10
|
+
import {
|
|
11
|
+
INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES,
|
|
12
|
+
InternalAgentRequestBodyTooLargeError,
|
|
13
|
+
readInternalAgentRequestBody,
|
|
14
|
+
} from "../../../internal-agents/request-body.js";
|
|
15
|
+
import { BaseHandler } from "../response/base.js";
|
|
16
|
+
import type { HandlerContext, HandlerMetadata, HandlerPriority, HandlerResult } from "../types.js";
|
|
17
|
+
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
18
|
+
|
|
19
|
+
const CANCEL_PATH_REGEX = /^\/internal\/agents\/runs\/([^/]+)$/;
|
|
20
|
+
|
|
21
|
+
function getRunId(pathname: string): string | null {
|
|
22
|
+
return CANCEL_PATH_REGEX.exec(pathname)?.[1] ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class AgentRunCancelHandler extends BaseHandler {
|
|
26
|
+
metadata: HandlerMetadata = {
|
|
27
|
+
name: "AgentRunCancelHandler",
|
|
28
|
+
priority: PRIORITY_MEDIUM_API as HandlerPriority,
|
|
29
|
+
patterns: [{ pattern: "/internal/agents/runs/", prefix: true, method: "DELETE" }],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
constructor(private readonly sessionManager: AgentRunSessionManager = agentRunSessionManager) {
|
|
33
|
+
super();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult> {
|
|
37
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
38
|
+
return this.continue();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const runId = getRunId(new URL(req.url).pathname);
|
|
42
|
+
if (!runId) {
|
|
43
|
+
return this.continue();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return this.withProxyContext(ctx, async () => {
|
|
47
|
+
const builder = this.createResponseBuilder(ctx)
|
|
48
|
+
.withCORS(req, ctx.securityConfig?.cors)
|
|
49
|
+
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const rawBody = await readInternalAgentRequestBody(
|
|
53
|
+
req,
|
|
54
|
+
INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES,
|
|
55
|
+
);
|
|
56
|
+
await verifyControlPlaneRequest(req, ctx, rawBody, {
|
|
57
|
+
expectedSubject: runId,
|
|
58
|
+
expectedSurface: "studio",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const accepted = this.sessionManager.cancelRun(runId);
|
|
62
|
+
if (accepted) {
|
|
63
|
+
return this.respond(builder.json({ accepted: true }, 202));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return this.respond(builder.build(null, 204));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof InternalAgentRequestBodyTooLargeError) {
|
|
69
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (error instanceof ControlPlaneRequestError) {
|
|
73
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.logWarn("Internal agent run cancel failed", {
|
|
77
|
+
error: error instanceof Error ? error.message : String(error),
|
|
78
|
+
runId,
|
|
79
|
+
projectId: ctx.projectId,
|
|
80
|
+
projectSlug: ctx.projectSlug,
|
|
81
|
+
});
|
|
82
|
+
return this.respond(builder.json({ error: "Internal cancel failed" }, 500));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as dntShim from "../../../../_dnt.shims.js";
|
|
2
|
+
import {
|
|
3
|
+
ControlPlaneRequestError,
|
|
4
|
+
verifyControlPlaneRequest,
|
|
5
|
+
} from "../../../internal-agents/control-plane-auth.js";
|
|
6
|
+
import {
|
|
7
|
+
type AgentRunSessionManager,
|
|
8
|
+
agentRunSessionManager,
|
|
9
|
+
RunNotActiveError,
|
|
10
|
+
ToolResultConflictError,
|
|
11
|
+
ToolResultNotWaitingError,
|
|
12
|
+
} from "../../../internal-agents/session-manager.js";
|
|
13
|
+
import {
|
|
14
|
+
INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES,
|
|
15
|
+
InternalAgentRequestBodyTooLargeError,
|
|
16
|
+
readInternalAgentRequestBody,
|
|
17
|
+
} from "../../../internal-agents/request-body.js";
|
|
18
|
+
import { ResumeSignalSchema } from "../../../internal-agents/schema.js";
|
|
19
|
+
import { BaseHandler } from "../response/base.js";
|
|
20
|
+
import type { HandlerContext, HandlerMetadata, HandlerPriority, HandlerResult } from "../types.js";
|
|
21
|
+
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
22
|
+
|
|
23
|
+
const RESUME_PATH_REGEX = /^\/internal\/agents\/runs\/([^/]+)\/resume$/;
|
|
24
|
+
|
|
25
|
+
function getRunId(pathname: string): string | null {
|
|
26
|
+
return RESUME_PATH_REGEX.exec(pathname)?.[1] ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class AgentRunResumeHandler extends BaseHandler {
|
|
30
|
+
metadata: HandlerMetadata = {
|
|
31
|
+
name: "AgentRunResumeHandler",
|
|
32
|
+
priority: PRIORITY_MEDIUM_API as HandlerPriority,
|
|
33
|
+
patterns: [{ pattern: "/internal/agents/runs/", prefix: true, method: "POST" }],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
constructor(private readonly sessionManager: AgentRunSessionManager = agentRunSessionManager) {
|
|
37
|
+
super();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult> {
|
|
41
|
+
if (!this.shouldHandle(req, ctx)) {
|
|
42
|
+
return this.continue();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const runId = getRunId(new URL(req.url).pathname);
|
|
46
|
+
if (!runId) {
|
|
47
|
+
return this.continue();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.withProxyContext(ctx, async () => {
|
|
51
|
+
const builder = this.createResponseBuilder(ctx)
|
|
52
|
+
.withCORS(req, ctx.securityConfig?.cors)
|
|
53
|
+
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const rawBody = await readInternalAgentRequestBody(
|
|
57
|
+
req,
|
|
58
|
+
INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES,
|
|
59
|
+
);
|
|
60
|
+
await verifyControlPlaneRequest(req, ctx, rawBody, {
|
|
61
|
+
expectedSubject: runId,
|
|
62
|
+
expectedSurface: "studio",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const signal = ResumeSignalSchema.parse(JSON.parse(rawBody));
|
|
66
|
+
const outcome = this.sessionManager.submitToolResult(runId, {
|
|
67
|
+
toolCallId: signal.toolCallId,
|
|
68
|
+
result: signal.result,
|
|
69
|
+
isError: signal.isError,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return this.respond(builder.json(outcome, 200));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof InternalAgentRequestBodyTooLargeError) {
|
|
75
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (error instanceof ControlPlaneRequestError) {
|
|
79
|
+
return this.respond(builder.json({ error: error.message }, error.status));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (error instanceof SyntaxError || (error instanceof Error && error.name === "ZodError")) {
|
|
83
|
+
return this.respond(builder.json({ error: "Invalid resume request" }, 400));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (error instanceof ToolResultConflictError) {
|
|
87
|
+
return this.respond(builder.json({ error: "TOOL_RESULT_CONFLICT" }, 409));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (error instanceof ToolResultNotWaitingError) {
|
|
91
|
+
return this.respond(builder.json({ error: "TOOL_RESULT_NOT_WAITING" }, 409));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (error instanceof RunNotActiveError) {
|
|
95
|
+
return this.respond(builder.json({ error: "RUN_NOT_ACTIVE" }, 410));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.logWarn("Internal agent run resume failed", {
|
|
99
|
+
error: error instanceof Error ? error.message : String(error),
|
|
100
|
+
runId,
|
|
101
|
+
projectId: ctx.projectId,
|
|
102
|
+
projectSlug: ctx.projectSlug,
|
|
103
|
+
});
|
|
104
|
+
return this.respond(builder.json({ error: "Internal resume failed" }, 500));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|