veryfront 0.1.152 → 0.1.155

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 (34) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/agent/ag-ui-handler.d.ts +6 -0
  3. package/esm/src/agent/ag-ui-handler.d.ts.map +1 -1
  4. package/esm/src/agent/ag-ui-handler.js +115 -19
  5. package/esm/src/agent/ag-ui-run-control.d.ts +33 -0
  6. package/esm/src/agent/ag-ui-run-control.d.ts.map +1 -0
  7. package/esm/src/agent/ag-ui-run-control.js +102 -0
  8. package/esm/src/agent/index.d.ts +1 -0
  9. package/esm/src/agent/index.d.ts.map +1 -1
  10. package/esm/src/agent/index.js +1 -0
  11. package/esm/src/agent/runtime/chat-stream-handler.d.ts.map +1 -1
  12. package/esm/src/agent/runtime/chat-stream-handler.js +1 -25
  13. package/esm/src/agent/runtime/error-utils.d.ts +4 -0
  14. package/esm/src/agent/runtime/error-utils.d.ts.map +1 -0
  15. package/esm/src/agent/runtime/error-utils.js +25 -0
  16. package/esm/src/agent/runtime/index.d.ts.map +1 -1
  17. package/esm/src/agent/runtime/index.js +1 -25
  18. package/esm/src/observability/error-collector.d.ts +1 -0
  19. package/esm/src/observability/error-collector.d.ts.map +1 -1
  20. package/esm/src/observability/error-collector.js +6 -3
  21. package/esm/src/routing/api/module-loader/loader.js +26 -28
  22. package/esm/src/utils/version-constant.d.ts +1 -1
  23. package/esm/src/utils/version-constant.js +1 -1
  24. package/package.json +1 -1
  25. package/src/deno.js +1 -1
  26. package/src/src/agent/ag-ui-handler.ts +187 -27
  27. package/src/src/agent/ag-ui-run-control.ts +161 -0
  28. package/src/src/agent/index.ts +8 -0
  29. package/src/src/agent/runtime/chat-stream-handler.ts +1 -33
  30. package/src/src/agent/runtime/error-utils.ts +32 -0
  31. package/src/src/agent/runtime/index.ts +1 -33
  32. package/src/src/observability/error-collector.ts +13 -3
  33. package/src/src/routing/api/module-loader/loader.ts +37 -38
  34. package/src/src/utils/version-constant.ts +1 -1
@@ -293,24 +293,32 @@ function createImportMapPlugin(projectDir, adapter, config) {
293
293
  logger.debug(`Import map resolved: ${args.path} -> ${absolutePath}`);
294
294
  return { path: absolutePath, namespace: "import-map" };
295
295
  });
296
- build.onLoad({ filter: /.*/, namespace: "import-map" }, wrapWithCurrentContext(async (args) => {
297
- try {
298
- const { filePath, contents } = await readFileWithExtensions(adapter, args.path, FILE_EXTENSIONS, projectDir);
299
- return {
300
- contents,
301
- loader: getLoaderForFile(filePath),
302
- resolveDir: pathHelper.dirname(filePath),
303
- };
304
- }
305
- catch (error) {
306
- const msg = error instanceof Error ? error.message : String(error);
307
- logger.error(`Failed to load file via import map: ${args.path}`, error);
308
- return { errors: [{ text: `Failed to load: ${msg}` }] };
309
- }
296
+ build.onLoad({ filter: /.*/, namespace: "import-map" }, createNamespaceOnLoadHandler({
297
+ adapter,
298
+ projectDir,
299
+ errorLabel: "file via import map",
310
300
  }));
311
301
  },
312
302
  };
313
303
  }
304
+ function createNamespaceOnLoadHandler(options) {
305
+ const { adapter, projectDir, errorLabel } = options;
306
+ return wrapWithCurrentContext(async (args) => {
307
+ try {
308
+ const { filePath, contents } = await readFileWithExtensions(adapter, args.path, FILE_EXTENSIONS, projectDir);
309
+ return {
310
+ contents,
311
+ loader: getLoaderForFile(filePath),
312
+ resolveDir: pathHelper.dirname(filePath),
313
+ };
314
+ }
315
+ catch (error) {
316
+ const msg = error instanceof Error ? error.message : String(error);
317
+ logger.error(`Failed to load ${errorLabel}: ${args.path}`, error);
318
+ return { errors: [{ text: `Failed to load: ${msg}` }] };
319
+ }
320
+ });
321
+ }
314
322
  /** Resolves relative imports through the adapter's virtual FS for remote projects. */
315
323
  function createAdapterResolvePlugin(adapter, projectDir) {
316
324
  return {
@@ -337,20 +345,10 @@ function createAdapterResolvePlugin(adapter, projectDir) {
337
345
  // callbacks fire from the child process message handler, losing the
338
346
  // AsyncLocalStorage store. Without this, MultiProjectFSAdapter.getAdapter()
339
347
  // cannot resolve the per-project adapter and all file reads fail silently.
340
- build.onLoad({ filter: /.*/, namespace: "vf-adapter" }, wrapWithCurrentContext(async (args) => {
341
- try {
342
- const { filePath, contents } = await readFileWithExtensions(adapter, args.path, FILE_EXTENSIONS, projectDir);
343
- return {
344
- contents,
345
- loader: getLoaderForFile(filePath),
346
- resolveDir: pathHelper.dirname(filePath),
347
- };
348
- }
349
- catch (error) {
350
- const msg = error instanceof Error ? error.message : String(error);
351
- logger.error(`Failed to load via adapter: ${args.path}`, error);
352
- return { errors: [{ text: `Failed to load: ${msg}` }] };
353
- }
348
+ build.onLoad({ filter: /.*/, namespace: "vf-adapter" }, createNamespaceOnLoadHandler({
349
+ adapter,
350
+ projectDir,
351
+ errorLabel: "via adapter",
354
352
  }));
355
353
  },
356
354
  };
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.152";
1
+ export declare const VERSION = "0.1.155";
2
2
  //# sourceMappingURL=version-constant.d.ts.map
@@ -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.152";
3
+ export const VERSION = "0.1.155";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.152",
3
+ "version": "0.1.155",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.152",
3
+ "version": "0.1.155",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -2,7 +2,14 @@ import * as dntShim from "../../_dnt.shims.js";
2
2
  import { z } from "zod";
3
3
  import { getAgent } from "./composition/index.js";
4
4
  import type { Agent, Message } from "./types.js";
5
+ import {
6
+ AgentRuntime,
7
+ RunAlreadyExistsError,
8
+ type RunResumeSessionManager,
9
+ } from "./runtime/index.js";
5
10
  import { INVALID_ARGUMENT } from "../errors/index.js";
11
+ import { SKILL_TOOL_IDS } from "../skill/types.js";
12
+ import { type Tool, toolRegistry } from "../tool/index.js";
6
13
  import {
7
14
  createStreamTransformState,
8
15
  finalizeRunEvents,
@@ -101,6 +108,7 @@ export type AgUiContextItem = z.infer<typeof AgUiContextItemSchema>;
101
108
  export type AgUiRequest = z.infer<typeof AgUiRequestSchema>;
102
109
 
103
110
  type AgUiRuntimePart = Record<string, unknown> & { type: string };
111
+ type AgUiResumeValue = { result: unknown; isError: boolean };
104
112
 
105
113
  function isRecord(value: unknown): value is Record<string, unknown> {
106
114
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -259,24 +267,29 @@ function enqueueEvent(
259
267
  }
260
268
 
261
269
  async function createAgUiStreamResponse(
262
- agent: Agent,
263
- request: AgUiRequest,
264
- baseContext: Record<string, unknown>,
270
+ options: {
271
+ agentId: string;
272
+ request: AgUiRequest;
273
+ runId: string;
274
+ threadId: string;
275
+ upstreamBody: ReadableStream<Uint8Array> | null;
276
+ upstreamStatus: number;
277
+ upstreamStatusText?: string;
278
+ onFinish?: () => void;
279
+ onError?: (error: unknown) => void;
280
+ },
265
281
  ): Promise<dntShim.Response> {
266
- const threadId = request.threadId ?? dntShim.crypto.randomUUID();
267
- const runId = request.runId ?? generateRunId();
268
-
269
- await agent.clearMemory();
270
-
271
- const result = await agent.stream({
272
- messages: normalizeMessages(request.messages),
273
- context: buildStreamContext(request, baseContext, threadId, runId),
274
- ...(request.model ? { model: request.model } : {}),
275
- ...(request.maxOutputTokens ? { maxOutputTokens: request.maxOutputTokens } : {}),
276
- });
277
-
278
- const upstream = result.toDataStreamResponse();
279
- const upstreamBody = upstream.body;
282
+ const {
283
+ agentId,
284
+ request,
285
+ runId,
286
+ threadId,
287
+ upstreamBody,
288
+ upstreamStatus,
289
+ upstreamStatusText,
290
+ onFinish,
291
+ onError,
292
+ } = options;
280
293
 
281
294
  const stream = new ReadableStream<Uint8Array>({
282
295
  start: async (controller) => {
@@ -285,7 +298,7 @@ async function createAgUiStreamResponse(
285
298
  let remainder = "";
286
299
  const decoder = new TextDecoder();
287
300
 
288
- if (!enqueueEvent(controller, "RunStarted", { runId, threadId, agentId: agent.id })) {
301
+ if (!enqueueEvent(controller, "RunStarted", { runId, threadId, agentId })) {
289
302
  return;
290
303
  }
291
304
  if (!enqueueEvent(controller, "StateSnapshot", { snapshot: {} })) {
@@ -302,6 +315,7 @@ async function createAgUiStreamResponse(
302
315
  return;
303
316
  }
304
317
  }
318
+ onFinish?.();
305
319
  closeController(controller);
306
320
  return;
307
321
  }
@@ -340,7 +354,9 @@ async function createAgUiStreamResponse(
340
354
  return;
341
355
  }
342
356
  }
357
+ onFinish?.();
343
358
  } catch (error) {
359
+ onError?.(error);
344
360
  enqueueEvent(controller, "RunError", {
345
361
  message: error instanceof Error ? error.message : "Agent run failed",
346
362
  });
@@ -352,16 +368,147 @@ async function createAgUiStreamResponse(
352
368
  });
353
369
 
354
370
  return new dntShim.Response(stream, {
355
- status: upstream.status,
356
- statusText: upstream.statusText,
371
+ status: upstreamStatus,
372
+ statusText: upstreamStatusText,
357
373
  headers: { ...AG_UI_HEADERS },
358
374
  });
359
375
  }
360
376
 
377
+ async function createAgUiDirectStreamResponse(
378
+ agent: Agent,
379
+ request: AgUiRequest,
380
+ baseContext: Record<string, unknown>,
381
+ ): Promise<dntShim.Response> {
382
+ const threadId = request.threadId ?? dntShim.crypto.randomUUID();
383
+ const runId = request.runId ?? generateRunId();
384
+
385
+ await agent.clearMemory();
386
+
387
+ const result = await agent.stream({
388
+ messages: normalizeMessages(request.messages),
389
+ context: buildStreamContext(request, baseContext, threadId, runId),
390
+ ...(request.model ? { model: request.model } : {}),
391
+ ...(request.maxOutputTokens ? { maxOutputTokens: request.maxOutputTokens } : {}),
392
+ });
393
+
394
+ const upstream = result.toDataStreamResponse();
395
+ return await createAgUiStreamResponse({
396
+ agentId: agent.id,
397
+ request,
398
+ runId,
399
+ threadId,
400
+ upstreamBody: upstream.body,
401
+ upstreamStatus: upstream.status,
402
+ upstreamStatusText: upstream.statusText,
403
+ });
404
+ }
405
+
406
+ function createInjectedAgUiTool(
407
+ runId: string,
408
+ tool: AgUiInjectedTool,
409
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>,
410
+ ): Tool {
411
+ return {
412
+ id: tool.name,
413
+ type: "function",
414
+ description: tool.description ?? tool.name,
415
+ inputSchema: z.record(z.string(), z.unknown()),
416
+ inputSchemaJson: (tool.parameters ??
417
+ { type: "object", properties: {}, additionalProperties: true }) as Tool["inputSchemaJson"],
418
+ execute: async (_input, context) => {
419
+ const toolCallId = typeof context?.toolCallId === "string" ? context.toolCallId : null;
420
+ if (!toolCallId) {
421
+ throw new Error(`Missing toolCallId for injected tool "${tool.name}"`);
422
+ }
423
+
424
+ const submitted = await sessionManager.waitForSignal(runId, toolCallId);
425
+ if (submitted.isError) {
426
+ throw new Error(
427
+ typeof submitted.result === "string"
428
+ ? submitted.result
429
+ : JSON.stringify(submitted.result),
430
+ );
431
+ }
432
+ return submitted.result;
433
+ },
434
+ };
435
+ }
436
+
437
+ async function createAgUiInjectedToolsStreamResponse(
438
+ agent: Agent,
439
+ request: AgUiRequest,
440
+ baseContext: Record<string, unknown>,
441
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>,
442
+ ): Promise<dntShim.Response> {
443
+ const threadId = request.threadId ?? dntShim.crypto.randomUUID();
444
+ const runId = request.runId ?? generateRunId();
445
+
446
+ try {
447
+ sessionManager.startRun({ runId, threadId });
448
+ } catch (error) {
449
+ if (error instanceof RunAlreadyExistsError) {
450
+ return dntShim.Response.json({ error: "Run already active" }, { status: 409 });
451
+ }
452
+ throw error;
453
+ }
454
+
455
+ const injectedTools = Object.fromEntries(
456
+ request.tools.map((tool) => [tool.name, createInjectedAgUiTool(runId, tool, sessionManager)]),
457
+ );
458
+
459
+ const mergedTools: Agent["config"]["tools"] = !agent.config.tools
460
+ ? injectedTools
461
+ : agent.config.tools === true
462
+ ? {
463
+ ...Object.fromEntries(
464
+ [...toolRegistry.getAll()]
465
+ .filter(([toolId]) => agent.config.skills || !SKILL_TOOL_IDS.has(toolId))
466
+ .map(([toolId]) => [toolId, true]),
467
+ ),
468
+ ...injectedTools,
469
+ }
470
+ : { ...agent.config.tools, ...injectedTools };
471
+
472
+ const runtime = new AgentRuntime(agent.id, {
473
+ ...agent.config,
474
+ tools: mergedTools,
475
+ });
476
+
477
+ let upstreamBody: ReadableStream<Uint8Array>;
478
+ try {
479
+ upstreamBody = await runtime.stream(
480
+ normalizeMessages(request.messages),
481
+ buildStreamContext(request, baseContext, threadId, runId),
482
+ undefined,
483
+ request.model,
484
+ request.maxOutputTokens,
485
+ );
486
+ } catch (error) {
487
+ sessionManager.failRun(runId);
488
+ throw error;
489
+ }
490
+
491
+ return await createAgUiStreamResponse({
492
+ agentId: agent.id,
493
+ request,
494
+ runId,
495
+ threadId,
496
+ upstreamBody,
497
+ upstreamStatus: 200,
498
+ onFinish: () => {
499
+ sessionManager.completeRun(runId);
500
+ },
501
+ onError: () => {
502
+ sessionManager.failRun(runId);
503
+ },
504
+ });
505
+ }
506
+
361
507
  export interface AgUiHandlerOptions {
362
508
  context?:
363
509
  | Record<string, unknown>
364
510
  | ((request: dntShim.Request) => Record<string, unknown> | Promise<Record<string, unknown>>);
511
+ sessionManager?: RunResumeSessionManager<AgUiResumeValue>;
365
512
  }
366
513
 
367
514
  export interface AgUiHandlerConfigWithAgent extends AgUiHandlerOptions {
@@ -418,12 +565,25 @@ export function createAgUiHandler(
418
565
  const parsed = AgUiRequestSchema.parse(await request.json());
419
566
 
420
567
  if (parsed.tools.length > 0) {
421
- return dntShim.Response.json(
422
- {
423
- error:
424
- "Injected AG-UI tools are not supported by createAgUiHandler yet. Use package-level wait/resume runtime primitives instead.",
425
- },
426
- { status: 501 },
568
+ if (!options?.sessionManager) {
569
+ return dntShim.Response.json(
570
+ {
571
+ error:
572
+ "Injected AG-UI tools require a public RunResumeSessionManager on createAgUiHandler().",
573
+ },
574
+ { status: 501 },
575
+ );
576
+ }
577
+
578
+ const context = typeof options?.context === "function"
579
+ ? await options.context(request)
580
+ : options?.context ?? {};
581
+
582
+ return await createAgUiInjectedToolsStreamResponse(
583
+ agent,
584
+ parsed,
585
+ context,
586
+ options.sessionManager,
427
587
  );
428
588
  }
429
589
 
@@ -431,7 +591,7 @@ export function createAgUiHandler(
431
591
  ? await options.context(request)
432
592
  : options?.context ?? {};
433
593
 
434
- return await createAgUiStreamResponse(agent, parsed, context);
594
+ return await createAgUiDirectStreamResponse(agent, parsed, context);
435
595
  } catch (error) {
436
596
  if (error instanceof z.ZodError) {
437
597
  return dntShim.Response.json(
@@ -0,0 +1,161 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { z } from "zod";
3
+ import { INVALID_ARGUMENT } from "../errors/index.js";
4
+ import {
5
+ RunNotActiveError,
6
+ RunResumeSessionManager,
7
+ WaitConflictError,
8
+ WaitNotPendingError,
9
+ } from "./runtime/resume-session.js";
10
+
11
+ const RESUME_PATH_REGEX = /^\/api\/ag-ui\/runs\/([^/]+)\/resume$/;
12
+ const CANCEL_PATH_REGEX = /^\/api\/ag-ui\/runs\/([^/]+)$/;
13
+
14
+ export const AgUiResumeSignalSchema = z.discriminatedUnion("type", [
15
+ z.object({
16
+ type: z.literal("tool_result"),
17
+ toolCallId: z.string().min(1).max(128),
18
+ result: z.unknown(),
19
+ isError: z.boolean().optional().default(false),
20
+ }),
21
+ ]);
22
+
23
+ export type AgUiResumeSignal = z.infer<typeof AgUiResumeSignalSchema>;
24
+
25
+ type ResumeValue = {
26
+ result: unknown;
27
+ isError: boolean;
28
+ };
29
+
30
+ function isRequest(value: unknown): value is dntShim.Request {
31
+ return (
32
+ typeof value === "object" &&
33
+ value !== null &&
34
+ "json" in value &&
35
+ typeof value.json === "function" &&
36
+ "url" in value &&
37
+ typeof value.url === "string" &&
38
+ "method" in value &&
39
+ typeof value.method === "string"
40
+ );
41
+ }
42
+
43
+ function extractRequest(requestOrCtx: unknown): dntShim.Request {
44
+ if (isRequest(requestOrCtx)) return requestOrCtx;
45
+
46
+ if (typeof requestOrCtx === "object" && requestOrCtx !== null && "request" in requestOrCtx) {
47
+ const candidate = (requestOrCtx as Record<string, unknown>).request;
48
+ if (isRequest(candidate)) return candidate;
49
+ }
50
+
51
+ throw INVALID_ARGUMENT.create({
52
+ detail: "Invalid handler argument: expected Request or APIContext",
53
+ });
54
+ }
55
+
56
+ function getRunId(pathname: string, regex: RegExp): string | null {
57
+ return regex.exec(pathname)?.[1] ?? null;
58
+ }
59
+
60
+ export interface AgUiRunControlHandlerOptions {
61
+ resolveRunId?:
62
+ | ((input: { request: dntShim.Request; requestOrCtx: unknown }) => string | null)
63
+ | ((input: { request: dntShim.Request; requestOrCtx: unknown }) => Promise<string | null>);
64
+ }
65
+
66
+ export interface AgUiResumeHandlerOptions extends AgUiRunControlHandlerOptions {
67
+ sessionManager: RunResumeSessionManager<ResumeValue>;
68
+ }
69
+
70
+ export interface AgUiCancelHandlerOptions<T = unknown> extends AgUiRunControlHandlerOptions {
71
+ sessionManager: RunResumeSessionManager<T>;
72
+ }
73
+
74
+ async function resolveRunId(
75
+ requestOrCtx: unknown,
76
+ request: dntShim.Request,
77
+ options: AgUiRunControlHandlerOptions | undefined,
78
+ regex: RegExp,
79
+ ): Promise<string | null> {
80
+ const explicit = await options?.resolveRunId?.({ request, requestOrCtx });
81
+ if (explicit) return explicit;
82
+ return getRunId(new URL(request.url).pathname, regex);
83
+ }
84
+
85
+ export function createAgUiResumeHandler(
86
+ options: AgUiResumeHandlerOptions,
87
+ ): (requestOrCtx: unknown) => Promise<dntShim.Response> {
88
+ return async function POST(requestOrCtx: unknown): Promise<dntShim.Response> {
89
+ const request = extractRequest(requestOrCtx);
90
+ const runId = await resolveRunId(requestOrCtx, request, options, RESUME_PATH_REGEX);
91
+
92
+ if (!runId) {
93
+ return dntShim.Response.json({ error: "Run not found" }, { status: 404 });
94
+ }
95
+
96
+ try {
97
+ const parsed = AgUiResumeSignalSchema.parse(await request.json());
98
+ const outcome = options.sessionManager.submitSignal(runId, {
99
+ waitKey: parsed.toolCallId,
100
+ value: {
101
+ result: parsed.result,
102
+ isError: parsed.isError,
103
+ },
104
+ });
105
+
106
+ return dntShim.Response.json(outcome, { status: 200 });
107
+ } catch (error) {
108
+ if (error instanceof z.ZodError) {
109
+ return dntShim.Response.json(
110
+ {
111
+ error: "Invalid AG-UI resume request",
112
+ details: error.issues.map((issue) => ({
113
+ path: issue.path,
114
+ message: issue.message,
115
+ })),
116
+ },
117
+ { status: 400 },
118
+ );
119
+ }
120
+
121
+ if (error instanceof WaitConflictError) {
122
+ return dntShim.Response.json({ error: "TOOL_RESULT_CONFLICT" }, { status: 409 });
123
+ }
124
+
125
+ if (error instanceof WaitNotPendingError) {
126
+ return dntShim.Response.json({ error: "TOOL_RESULT_NOT_WAITING" }, { status: 409 });
127
+ }
128
+
129
+ if (error instanceof RunNotActiveError) {
130
+ return dntShim.Response.json({ error: "RUN_NOT_ACTIVE" }, { status: 410 });
131
+ }
132
+
133
+ return dntShim.Response.json(
134
+ {
135
+ error: error instanceof Error ? error.message : "Internal resume failed",
136
+ },
137
+ { status: 500 },
138
+ );
139
+ }
140
+ };
141
+ }
142
+
143
+ export function createAgUiCancelHandler<T = unknown>(
144
+ options: AgUiCancelHandlerOptions<T>,
145
+ ): (requestOrCtx: unknown) => Promise<dntShim.Response> {
146
+ return async function DELETE(requestOrCtx: unknown): Promise<dntShim.Response> {
147
+ const request = extractRequest(requestOrCtx);
148
+ const runId = await resolveRunId(requestOrCtx, request, options, CANCEL_PATH_REGEX);
149
+
150
+ if (!runId) {
151
+ return dntShim.Response.json({ error: "Run not found" }, { status: 404 });
152
+ }
153
+
154
+ const accepted = options.sessionManager.cancelRun(runId);
155
+ if (accepted) {
156
+ return dntShim.Response.json({ accepted: true }, { status: 202 });
157
+ }
158
+
159
+ return new dntShim.Response(null, { status: 204 });
160
+ };
161
+ }
@@ -145,6 +145,14 @@ export {
145
145
  type AgUiRuntimeRequest,
146
146
  AgUiRuntimeRequestSchema,
147
147
  } from "./runtime-ag-ui-contract.js";
148
+ export {
149
+ type AgUiCancelHandlerOptions,
150
+ type AgUiResumeHandlerOptions,
151
+ type AgUiResumeSignal,
152
+ AgUiResumeSignalSchema,
153
+ createAgUiCancelHandler,
154
+ createAgUiResumeHandler,
155
+ } from "./ag-ui-run-control.js";
148
156
  export {
149
157
  type AgUiContextItem,
150
158
  type AgUiHandlerConfigWithAgent,
@@ -15,6 +15,7 @@ import { serverLogger } from "../../utils/index.js";
15
15
  import { isAnyDebugEnabled } from "../../utils/constants/env.js";
16
16
  import { setActiveSpanAttributes, withSpan } from "../../observability/tracing/otlp-setup.js";
17
17
  import { getHostEnv } from "../../platform/compat/process.js";
18
+ import { stringifyToolError, throwIfAborted } from "./error-utils.js";
18
19
 
19
20
  const logger = serverLogger.component("agent");
20
21
 
@@ -84,39 +85,6 @@ function normalizeToolInputObject(input: unknown): Record<string, unknown> {
84
85
  return {};
85
86
  }
86
87
 
87
- function createAbortError(reason?: unknown): Error {
88
- if (reason instanceof Error) {
89
- return reason;
90
- }
91
-
92
- return new DOMException(
93
- typeof reason === "string" && reason.length > 0 ? reason : "The operation was aborted",
94
- "AbortError",
95
- );
96
- }
97
-
98
- function throwIfAborted(abortSignal?: AbortSignal): void {
99
- if (abortSignal?.aborted) {
100
- throw createAbortError(abortSignal.reason);
101
- }
102
- }
103
-
104
- function stringifyToolError(output: unknown): string {
105
- if (typeof output === "string" && output.length > 0) {
106
- return output;
107
- }
108
-
109
- if (output instanceof Error && typeof output.message === "string" && output.message.length > 0) {
110
- return output.message;
111
- }
112
-
113
- try {
114
- return JSON.stringify(output);
115
- } catch {
116
- return String(output);
117
- }
118
- }
119
-
120
88
  function summarizeDebugValue(value: unknown): unknown {
121
89
  if (value instanceof Error) {
122
90
  return {
@@ -0,0 +1,32 @@
1
+ export function createAbortError(reason?: unknown): Error {
2
+ if (reason instanceof Error) {
3
+ return reason;
4
+ }
5
+
6
+ return new DOMException(
7
+ typeof reason === "string" && reason.length > 0 ? reason : "The operation was aborted",
8
+ "AbortError",
9
+ );
10
+ }
11
+
12
+ export function throwIfAborted(abortSignal?: AbortSignal): void {
13
+ if (abortSignal?.aborted) {
14
+ throw createAbortError(abortSignal.reason);
15
+ }
16
+ }
17
+
18
+ export function stringifyToolError(error: unknown): string {
19
+ if (typeof error === "string" && error.length > 0) {
20
+ return error;
21
+ }
22
+
23
+ if (error instanceof Error && typeof error.message === "string" && error.message.length > 0) {
24
+ return error.message;
25
+ }
26
+
27
+ try {
28
+ return JSON.stringify(error);
29
+ } catch {
30
+ return String(error);
31
+ }
32
+ }