veryfront 0.1.218 → 0.1.220

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/cli/commands/init/config-generator.d.ts +13 -1
  2. package/esm/cli/commands/init/config-generator.d.ts.map +1 -1
  3. package/esm/cli/commands/init/config-generator.js +15 -1
  4. package/esm/cli/commands/init/init-command.d.ts.map +1 -1
  5. package/esm/cli/commands/init/init-command.js +9 -2
  6. package/esm/deno.d.ts +0 -1
  7. package/esm/deno.js +1 -2
  8. package/esm/src/agent/ag-ui-detached-start.d.ts +98 -0
  9. package/esm/src/agent/ag-ui-detached-start.d.ts.map +1 -0
  10. package/esm/src/agent/ag-ui-detached-start.js +299 -0
  11. package/esm/src/agent/index.d.ts +1 -0
  12. package/esm/src/agent/index.d.ts.map +1 -1
  13. package/esm/src/agent/index.js +1 -0
  14. package/esm/src/integrations/_data.js +1 -1
  15. package/esm/src/integrations/schema.d.ts +1 -0
  16. package/esm/src/integrations/schema.d.ts.map +1 -1
  17. package/esm/src/integrations/schema.js +8 -0
  18. package/esm/src/server/runtime-handler/request-tracker.d.ts.map +1 -1
  19. package/esm/src/server/runtime-handler/request-tracker.js +5 -6
  20. package/esm/src/server/runtime-handler/request-utils.d.ts.map +1 -1
  21. package/esm/src/server/runtime-handler/request-utils.js +1 -0
  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/cli/commands/init/config-generator.ts +33 -0
  26. package/src/cli/commands/init/init-command.ts +9 -2
  27. package/src/deno.js +1 -2
  28. package/src/src/agent/ag-ui-detached-start.ts +453 -0
  29. package/src/src/agent/index.ts +8 -0
  30. package/src/src/integrations/_data.ts +1 -1
  31. package/src/src/integrations/schema.ts +8 -0
  32. package/src/src/server/runtime-handler/request-tracker.ts +5 -7
  33. package/src/src/server/runtime-handler/request-utils.ts +1 -0
  34. package/src/src/utils/version-constant.ts +1 -1
@@ -140,6 +140,14 @@ export const IntegrationConfigSchema = z.object({
140
140
  description: z.string(),
141
141
  auth: OAuthConfigSchema,
142
142
  envVars: z.array(EnvVarSchema).optional(),
143
+ /**
144
+ * Optional map of npm packages to semver ranges. When this integration is
145
+ * selected during `veryfront init`, these deps are merged into the
146
+ * generated project's `package.json#dependencies`. Use this for templates
147
+ * that import packages beyond the init scaffold's defaults (react,
148
+ * react-dom, veryfront, zod).
149
+ */
150
+ npmDependencies: z.record(z.string(), z.string()).optional(),
143
151
  tools: z.array(IntegrationToolSchema),
144
152
  prompts: z.array(IntegrationPromptSchema).optional(),
145
153
  suggestedWith: z.array(z.string()).optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"request-tracker.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/request-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AASlD,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;IAClD,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;CACvD;AAiBD,cAAM,cAAc;IAClB,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAAqD;IAC3E,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,aAAa,CAAK;;IAM1B,OAAO,CAAC,kBAAkB;IA4B1B,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI;IAsDP,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,IAAI;IAmCvE,gBAAgB,IAAI,MAAM;IAI1B,mBAAmB,IAAI,cAAc,EAAE;IAIvC,QAAQ,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAS9E,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsD7E,QAAQ,IAAI,IAAI;CAUjB;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
1
+ {"version":3,"file":"request-tracker.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/request-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AASlD,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;IAClD,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC;CACvD;AAiBD,cAAM,cAAc;IAClB,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,cAAc,CAAqD;IAC3E,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,aAAa,CAAK;;IAM1B,OAAO,CAAC,kBAAkB;IA4B1B,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI;IAuDP,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,IAAI;IAgCvE,gBAAgB,IAAI,MAAM;IAI1B,mBAAmB,IAAI,cAAc,EAAE;IAIvC,QAAQ,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAS9E,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsD7E,QAAQ,IAAI,IAAI;CAUjB;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
@@ -7,7 +7,7 @@
7
7
  import * as dntShim from "../../../_dnt.shims.js";
8
8
  import { serverLogger } from "../../utils/index.js";
9
9
  import { unrefTimer } from "../../platform/compat/process.js";
10
- import { isWebSocketPath } from "./request-utils.js";
10
+ import { isLightweightPath, isWebSocketPath } from "./request-utils.js";
11
11
  const logger = serverLogger.component("request-tracker");
12
12
  /** Threshold in ms before logging a warning about a slow request */
13
13
  const SLOW_REQUEST_THRESHOLD_MS = 10_000; // 10 seconds
@@ -66,8 +66,9 @@ class RequestTracker {
66
66
  env,
67
67
  releaseId,
68
68
  };
69
- // WebSocket connections are long-lived by design don't flag them as stuck.
70
- if (!isWebSocketPath(path)) {
69
+ // WebSocket connections are long-lived by design and lightweight internal
70
+ // asset/module requests can be noisy under CI jitter — don't flag them as stuck.
71
+ if (!isWebSocketPath(path) && !isLightweightPath(path)) {
71
72
  tracked.slowTimer = dntShim.setTimeout(() => {
72
73
  const elapsedMs = Math.round(performance.now() - startTime);
73
74
  logger.warn("Slow request detected", {
@@ -118,9 +119,7 @@ class RequestTracker {
118
119
  this.totalTimedOut++;
119
120
  else
120
121
  this.totalCompleted++;
121
- const isModuleRequest = tracked.path.startsWith("/_vf_modules/") ||
122
- tracked.path.startsWith("/_veryfront/");
123
- if (isModuleRequest) {
122
+ if (isLightweightPath(tracked.path)) {
124
123
  if (durationMs > MODULE_REQUEST_LOG_THRESHOLD_MS) {
125
124
  logger.debug(`${tracked.method} ${tracked.path} ${statusCode} ${durationMs}ms`);
126
125
  }
@@ -1 +1 @@
1
- {"version":3,"file":"request-utils.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/request-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBpD;AAED,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,aAA+C,CAAC;AAK7E,yFAAyF;AACzF,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAEhC,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,eAA4B,CAAC;AAE1D,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,UAQrC,CAAC;AAEF,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEnE"}
1
+ {"version":3,"file":"request-utils.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/request-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBpD;AAED,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,aAA+C,CAAC;AAK7E,yFAAyF;AACzF,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAEhC,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,eAA4B,CAAC;AAE1D,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,UASrC,CAAC;AAEF,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEnE"}
@@ -48,6 +48,7 @@ export function isMonitoringPath(pathname) {
48
48
  /** Lightweight paths that should skip concurrency limiting (modules, static assets) */
49
49
  export const LIGHTWEIGHT_PATH_PREFIXES = [
50
50
  "/_vf_modules/",
51
+ "/_vf_styles/",
51
52
  "/_veryfront/modules/",
52
53
  "/_veryfront/hydration-runtime.js",
53
54
  "/_veryfront/preview-hmr.js",
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.218";
1
+ export declare const VERSION = "0.1.220";
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.218";
3
+ export const VERSION = "0.1.220";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.218",
3
+ "version": "0.1.220",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -5,9 +5,23 @@ import { createFileSystem } from "../../../src/platform/index.js";
5
5
  // Keep init scaffold aligned with current framework default React major/minor.
6
6
  const DEFAULT_INIT_REACT_VERSION = "19.1.1";
7
7
 
8
+ export interface CreatePackageJsonOptions {
9
+ /**
10
+ * Selected integrations whose `connector.json#npmDependencies` should be
11
+ * merged into the generated project's `package.json#dependencies`.
12
+ * First declaration wins on version collisions; framework pins
13
+ * (react, react-dom, veryfront, zod) always take precedence.
14
+ */
15
+ integrations?: Array<{
16
+ name: string;
17
+ npmDependencies?: Record<string, string>;
18
+ }>;
19
+ }
20
+
8
21
  export async function createPackageJson(
9
22
  projectDir: string,
10
23
  projectName?: string,
24
+ options: CreatePackageJsonOptions = {},
11
25
  ): Promise<void> {
12
26
  const fs = createFileSystem();
13
27
 
@@ -19,6 +33,24 @@ export async function createPackageJson(
19
33
  templateDeps = existing.dependencies ?? {};
20
34
  }
21
35
 
36
+ // Merge per-integration deps. First declaration wins; collisions are logged.
37
+ const integrationDeps: Record<string, string> = {};
38
+ for (const integration of options.integrations ?? []) {
39
+ for (const [pkg, range] of Object.entries(integration.npmDependencies ?? {})) {
40
+ if (pkg in integrationDeps) {
41
+ if (integrationDeps[pkg] !== range) {
42
+ logger.warn(
43
+ `[init] ${integration.name} requested ${pkg}@${range} but ${pkg}@${
44
+ integrationDeps[pkg]
45
+ } is already pinned by an earlier integration - keeping the earlier pin`,
46
+ );
47
+ }
48
+ continue;
49
+ }
50
+ integrationDeps[pkg] = range;
51
+ }
52
+ }
53
+
22
54
  const dirName = projectDir.split(/[/\\]/).pop();
23
55
  const packageJson = {
24
56
  name: projectName ?? dirName ?? "veryfront-project",
@@ -34,6 +66,7 @@ export async function createPackageJson(
34
66
  },
35
67
  dependencies: {
36
68
  ...templateDeps,
69
+ ...integrationDeps,
37
70
  react: `^${DEFAULT_INIT_REACT_VERSION}`,
38
71
  "react-dom": `^${DEFAULT_INIT_REACT_VERSION}`,
39
72
  veryfront: `^${VERSION}`,
@@ -269,6 +269,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
269
269
 
270
270
  const allEnvVars: EnvVarConfig[] = templateConfig?.envVars ? [...templateConfig.envVars] : [];
271
271
  const featureTips: string[] = [];
272
+ let loadedIntegrations: ResolvedIntegration[] = [];
272
273
 
273
274
  if (features.length) {
274
275
  const { ordered, errors } = await resolveFeatures(features);
@@ -309,10 +310,11 @@ export async function initCommand(options: InitOptions): Promise<void> {
309
310
  if (baseConfig?.envVars) allEnvVars.push(...baseConfig.envVars);
310
311
 
311
312
  const {
312
- integrations: loadedIntegrations,
313
+ integrations: resolvedIntegrations,
313
314
  files: integrationFiles,
314
315
  errors: integrationErrors,
315
316
  } = await loadIntegrations(integrations);
317
+ loadedIntegrations = resolvedIntegrations;
316
318
 
317
319
  if (integrationErrors.length) {
318
320
  for (const error of integrationErrors) logger.warn(error);
@@ -359,7 +361,12 @@ export async function initCommand(options: InitOptions): Promise<void> {
359
361
 
360
362
  // Skip in quiet/TUI mode since local dev uses CDN and package.json can cause hydration issues
361
363
  if (!options.quiet) {
362
- await createPackageJson(projectDir, projectName);
364
+ await createPackageJson(projectDir, projectName, {
365
+ integrations: loadedIntegrations.map((integration) => ({
366
+ name: integration.config.name,
367
+ npmDependencies: integration.config.npmDependencies,
368
+ })),
369
+ });
363
370
  }
364
371
 
365
372
  if (allEnvVars.length) {
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.218",
3
+ "version": "0.1.220",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "workspace": [
@@ -264,7 +264,6 @@ export default {
264
264
  "tailwindcss/plugin": "https://esm.sh/tailwindcss@4.2.2/plugin",
265
265
  "tailwindcss/defaultTheme": "https://esm.sh/tailwindcss@4.2.2/defaultTheme",
266
266
  "tailwindcss/colors": "https://esm.sh/tailwindcss@4.2.2/colors",
267
- "pg": "npm:pg@8.13.1",
268
267
  "@opentelemetry/api": "npm:@opentelemetry/api@1.9.0",
269
268
  "@opentelemetry/core": "npm:@opentelemetry/core@2.6.0",
270
269
  "@opentelemetry/context-async-hooks": "npm:@opentelemetry/context-async-hooks@2.6.0",
@@ -0,0 +1,453 @@
1
+ import { z } from "zod";
2
+ import { INVALID_ARGUMENT } from "../errors/index.js";
3
+ import { SKILL_TOOL_IDS } from "../skill/types.js";
4
+ import { type Tool, toolRegistry } from "../tool/index.js";
5
+ import { streamDataStreamEvents } from "./data-stream.js";
6
+ import { type AgUiInjectedTool, type AgUiRequest, AgUiRequestSchema } from "./ag-ui-handler.js";
7
+ import {
8
+ AgentRuntime,
9
+ RunAlreadyExistsError,
10
+ type RunResumeSessionManager,
11
+ } from "./runtime/index.js";
12
+ import type { Agent, Message } from "./types.js";
13
+
14
+ const AGENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
15
+ const AG_UI_DETACHED_RUN_ID_SCHEMA = z.string().min(1).max(128).regex(AGENT_ID_PATTERN);
16
+ const MAX_TEXT_PART_LENGTH = 10_000;
17
+
18
+ type AgUiResumeValue = {
19
+ result: unknown;
20
+ isError: boolean;
21
+ };
22
+
23
+ type AgUiContextValue =
24
+ | Record<string, unknown>
25
+ | ((request: Request) => Record<string, unknown> | Promise<Record<string, unknown>>);
26
+
27
+ type AgUiRuntimePart = Record<string, unknown> & { type: string };
28
+
29
+ function isRecord(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+
33
+ function normalizeToolArgs(part: Record<string, unknown>): Record<string, unknown> {
34
+ if (isRecord(part.args)) return part.args;
35
+ if (isRecord(part.input)) return part.input;
36
+ return {};
37
+ }
38
+
39
+ function normalizeMessagePart(part: Record<string, unknown>): Message["parts"][number] | null {
40
+ if (
41
+ part.type === "text" && typeof part.text === "string" &&
42
+ part.text.length <= MAX_TEXT_PART_LENGTH
43
+ ) {
44
+ return { type: "text", text: part.text };
45
+ }
46
+
47
+ if (part.type === "tool_call" && typeof part.id === "string" && typeof part.name === "string") {
48
+ return {
49
+ type: "tool-call",
50
+ toolCallId: part.id,
51
+ toolName: part.name,
52
+ args: normalizeToolArgs(part),
53
+ };
54
+ }
55
+
56
+ if (
57
+ part.type === "tool-call" &&
58
+ typeof part.toolCallId === "string" &&
59
+ typeof part.toolName === "string"
60
+ ) {
61
+ return {
62
+ type: "tool-call",
63
+ toolCallId: part.toolCallId,
64
+ toolName: part.toolName,
65
+ args: normalizeToolArgs(part),
66
+ };
67
+ }
68
+
69
+ if (
70
+ typeof part.type === "string" &&
71
+ part.type.startsWith("tool-") &&
72
+ part.type !== "tool-result" &&
73
+ typeof part.toolCallId === "string" &&
74
+ typeof part.toolName === "string"
75
+ ) {
76
+ return {
77
+ type: part.type,
78
+ toolCallId: part.toolCallId,
79
+ toolName: part.toolName,
80
+ args: normalizeToolArgs(part),
81
+ };
82
+ }
83
+
84
+ if (part.type === "tool_result" && typeof part.tool_call_id === "string") {
85
+ return {
86
+ type: "tool-result",
87
+ toolCallId: part.tool_call_id,
88
+ toolName: typeof part.tool_name === "string" ? part.tool_name : "unknown",
89
+ result: "output" in part ? part.output : undefined,
90
+ };
91
+ }
92
+
93
+ if (part.type === "tool-result" && typeof part.toolCallId === "string") {
94
+ return {
95
+ type: "tool-result",
96
+ toolCallId: part.toolCallId,
97
+ toolName: typeof part.toolName === "string" ? part.toolName : "unknown",
98
+ result: "result" in part ? part.result : undefined,
99
+ };
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ function normalizeMessages(messages: AgUiRequest["messages"]): Message[] {
106
+ return messages.map((message) => ({
107
+ id: message.id,
108
+ role: message.role,
109
+ parts: message.parts
110
+ .map((part) => normalizeMessagePart(part))
111
+ .filter((part): part is Message["parts"][number] => part !== null),
112
+ ...(message.createdAt ? { timestamp: Date.parse(message.createdAt) || undefined } : {}),
113
+ ...(message.metadata ? { metadata: message.metadata } : {}),
114
+ }));
115
+ }
116
+
117
+ function isRequest(obj: unknown): obj is Request {
118
+ return (
119
+ typeof obj === "object" &&
120
+ obj !== null &&
121
+ "json" in obj &&
122
+ typeof obj.json === "function" &&
123
+ "url" in obj &&
124
+ typeof obj.url === "string" &&
125
+ "method" in obj &&
126
+ typeof obj.method === "string"
127
+ );
128
+ }
129
+
130
+ function extractRequest(requestOrCtx: unknown): Request {
131
+ if (isRequest(requestOrCtx)) return requestOrCtx;
132
+
133
+ if (typeof requestOrCtx === "object" && requestOrCtx !== null && "request" in requestOrCtx) {
134
+ const candidate = (requestOrCtx as Record<string, unknown>).request;
135
+ if (isRequest(candidate)) return candidate;
136
+ }
137
+
138
+ throw INVALID_ARGUMENT.create({
139
+ detail: "Invalid handler argument: expected Request or APIContext",
140
+ });
141
+ }
142
+
143
+ function buildStreamContext(
144
+ request: AgUiDetachedStartRequest,
145
+ baseContext: Record<string, unknown>,
146
+ threadId: string,
147
+ runId: string,
148
+ ): Record<string, unknown> {
149
+ return {
150
+ ...baseContext,
151
+ threadId,
152
+ runId,
153
+ agUi: {
154
+ context: request.context,
155
+ forwardedProps: request.forwardedProps,
156
+ },
157
+ };
158
+ }
159
+
160
+ function createInjectedAgUiTool(
161
+ runId: string,
162
+ tool: AgUiInjectedTool,
163
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>,
164
+ ): Tool {
165
+ return {
166
+ id: tool.name,
167
+ type: "function",
168
+ description: tool.description ?? tool.name,
169
+ inputSchema: z.record(z.string(), z.unknown()),
170
+ inputSchemaJson: (tool.parameters ??
171
+ { type: "object", properties: {}, additionalProperties: true }) as Tool["inputSchemaJson"],
172
+ execute: async (_input, context) => {
173
+ const toolCallId = typeof context?.toolCallId === "string" ? context.toolCallId : null;
174
+ if (!toolCallId) {
175
+ throw new Error(`Missing toolCallId for injected tool "${tool.name}"`);
176
+ }
177
+
178
+ sessionManager.prepareForSignal(runId, toolCallId);
179
+ const submitted = await sessionManager.waitForSignal(runId, toolCallId);
180
+ if (submitted.isError) {
181
+ throw new Error(
182
+ typeof submitted.result === "string"
183
+ ? submitted.result
184
+ : JSON.stringify(submitted.result),
185
+ );
186
+ }
187
+ return submitted.result;
188
+ },
189
+ };
190
+ }
191
+
192
+ function buildMergedTools(
193
+ agent: Agent,
194
+ request: AgUiDetachedStartRequest,
195
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>,
196
+ ): Agent["config"]["tools"] {
197
+ const injectedTools = Object.fromEntries(
198
+ request.tools.map((tool) => [
199
+ tool.name,
200
+ createInjectedAgUiTool(request.runId, tool, sessionManager),
201
+ ]),
202
+ );
203
+
204
+ if (!agent.config.tools) {
205
+ return Object.keys(injectedTools).length > 0 ? injectedTools : undefined;
206
+ }
207
+
208
+ if (agent.config.tools === true) {
209
+ const merged: Record<string, Tool | boolean> = {};
210
+ for (const [toolId] of toolRegistry.getAll()) {
211
+ if (!agent.config.skills && SKILL_TOOL_IDS.has(toolId)) {
212
+ continue;
213
+ }
214
+ merged[toolId] = true;
215
+ }
216
+ return { ...merged, ...injectedTools };
217
+ }
218
+
219
+ return { ...agent.config.tools, ...injectedTools };
220
+ }
221
+
222
+ async function resolveContextValue(
223
+ value: AgUiContextValue | undefined,
224
+ request: Request,
225
+ ): Promise<Record<string, unknown>> {
226
+ if (typeof value === "function") {
227
+ return await value(request);
228
+ }
229
+
230
+ return value ?? {};
231
+ }
232
+
233
+ function scheduleDetachedTask(requestOrCtx: unknown, task: Promise<void>): void {
234
+ if (
235
+ typeof requestOrCtx === "object" &&
236
+ requestOrCtx !== null &&
237
+ "waitUntil" in requestOrCtx &&
238
+ typeof (requestOrCtx as Record<string, unknown>).waitUntil === "function"
239
+ ) {
240
+ ((requestOrCtx as { waitUntil: (promise: Promise<void>) => void }).waitUntil)(task);
241
+ return;
242
+ }
243
+
244
+ void task;
245
+ }
246
+
247
+ async function drainRuntimeStream(
248
+ stream: ReadableStream<Uint8Array>,
249
+ ): Promise<void> {
250
+ for await (const _event of streamDataStreamEvents(stream) as AsyncIterable<AgUiRuntimePart>) {
251
+ continue;
252
+ }
253
+ }
254
+
255
+ export const AgUiDetachedStartRequestSchema = AgUiRequestSchema.extend({
256
+ threadId: z.string().uuid(),
257
+ runId: AG_UI_DETACHED_RUN_ID_SCHEMA,
258
+ });
259
+
260
+ export const AgUiDetachedStartAcceptedSchema = z.object({
261
+ accepted: z.literal(true),
262
+ duplicate: z.boolean(),
263
+ runId: AG_UI_DETACHED_RUN_ID_SCHEMA,
264
+ threadId: z.string().uuid(),
265
+ });
266
+
267
+ export type AgUiDetachedStartRequest = z.infer<typeof AgUiDetachedStartRequestSchema>;
268
+ export type AgUiDetachedStartAccepted = z.infer<typeof AgUiDetachedStartAcceptedSchema>;
269
+
270
+ interface AgUiDetachedStartExecutionInput {
271
+ request: AgUiDetachedStartRequest;
272
+ requestOrCtx: unknown;
273
+ rawRequest: Request;
274
+ context: Record<string, unknown>;
275
+ abortSignal: AbortSignal;
276
+ }
277
+
278
+ type AgUiDetachedExecutionStarter = (
279
+ input: AgUiDetachedStartExecutionInput,
280
+ ) => Promise<void> | void;
281
+
282
+ interface AgUiDetachedStartHandlerOptionsBase {
283
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>;
284
+ context?: AgUiContextValue;
285
+ startDetachedExecution?: AgUiDetachedExecutionStarter;
286
+ onAccepted?: (input: {
287
+ request: AgUiDetachedStartRequest;
288
+ runId: string;
289
+ threadId: string;
290
+ }) => Promise<void> | void;
291
+ onDuplicate?: (input: {
292
+ request: AgUiDetachedStartRequest;
293
+ runId: string;
294
+ threadId: string;
295
+ }) => Promise<void> | void;
296
+ onFinish?: (input: { runId: string; threadId: string }) => Promise<void> | void;
297
+ onError?: (input: { runId: string; threadId: string; error: unknown }) => Promise<void> | void;
298
+ }
299
+
300
+ export type AgUiDetachedStartHandlerOptions =
301
+ | (AgUiDetachedStartHandlerOptionsBase & { agent: Agent })
302
+ | (AgUiDetachedStartHandlerOptionsBase & {
303
+ agent?: undefined;
304
+ startDetachedExecution: AgUiDetachedExecutionStarter;
305
+ });
306
+
307
+ async function startDefaultDetachedExecution(input: {
308
+ agent: Agent;
309
+ request: AgUiDetachedStartRequest;
310
+ context: Record<string, unknown>;
311
+ abortSignal: AbortSignal;
312
+ sessionManager: RunResumeSessionManager<AgUiResumeValue>;
313
+ }): Promise<void> {
314
+ const runtime = new AgentRuntime(input.agent.id, {
315
+ ...input.agent.config,
316
+ tools: buildMergedTools(input.agent, input.request, input.sessionManager),
317
+ });
318
+
319
+ const runtimeStream = await runtime.stream(
320
+ normalizeMessages(input.request.messages),
321
+ buildStreamContext(input.request, input.context, input.request.threadId, input.request.runId),
322
+ undefined,
323
+ input.request.model,
324
+ input.request.maxOutputTokens,
325
+ input.abortSignal,
326
+ );
327
+
328
+ await drainRuntimeStream(runtimeStream);
329
+ }
330
+
331
+ export function createAgUiDetachedStartHandler(
332
+ options: AgUiDetachedStartHandlerOptions,
333
+ ): (requestOrCtx: unknown) => Promise<Response> {
334
+ if (!options.agent && !options.startDetachedExecution) {
335
+ throw new Error(
336
+ "Detached AG-UI start requires either an agent or startDetachedExecution handler.",
337
+ );
338
+ }
339
+
340
+ return async function POST(requestOrCtx: unknown): Promise<Response> {
341
+ const request = extractRequest(requestOrCtx);
342
+
343
+ try {
344
+ const parsed = AgUiDetachedStartRequestSchema.parse(await request.json());
345
+ const context = await resolveContextValue(options.context, request);
346
+
347
+ try {
348
+ const abortSignal = options.sessionManager.startRun({
349
+ runId: parsed.runId,
350
+ threadId: parsed.threadId,
351
+ });
352
+
353
+ await options.onAccepted?.({
354
+ request: parsed,
355
+ runId: parsed.runId,
356
+ threadId: parsed.threadId,
357
+ });
358
+
359
+ const detachedTask = (async () => {
360
+ try {
361
+ if (options.startDetachedExecution) {
362
+ await options.startDetachedExecution({
363
+ request: parsed,
364
+ requestOrCtx,
365
+ rawRequest: request,
366
+ context,
367
+ abortSignal,
368
+ });
369
+ } else if (options.agent) {
370
+ await startDefaultDetachedExecution({
371
+ agent: options.agent,
372
+ request: parsed,
373
+ context,
374
+ abortSignal,
375
+ sessionManager: options.sessionManager,
376
+ });
377
+ } else {
378
+ throw new Error(
379
+ "Detached AG-UI start configuration became invalid during execution.",
380
+ );
381
+ }
382
+
383
+ options.sessionManager.completeRun(parsed.runId);
384
+ await options.onFinish?.({
385
+ runId: parsed.runId,
386
+ threadId: parsed.threadId,
387
+ });
388
+ } catch (error) {
389
+ options.sessionManager.failRun(parsed.runId);
390
+ await options.onError?.({
391
+ runId: parsed.runId,
392
+ threadId: parsed.threadId,
393
+ error,
394
+ });
395
+ }
396
+ })().catch(() => undefined);
397
+
398
+ scheduleDetachedTask(requestOrCtx, detachedTask);
399
+
400
+ return Response.json(
401
+ {
402
+ accepted: true,
403
+ duplicate: false,
404
+ runId: parsed.runId,
405
+ threadId: parsed.threadId,
406
+ } satisfies AgUiDetachedStartAccepted,
407
+ { status: 202 },
408
+ );
409
+ } catch (error) {
410
+ if (error instanceof RunAlreadyExistsError) {
411
+ await options.onDuplicate?.({
412
+ request: parsed,
413
+ runId: parsed.runId,
414
+ threadId: parsed.threadId,
415
+ });
416
+
417
+ return Response.json(
418
+ {
419
+ accepted: true,
420
+ duplicate: true,
421
+ runId: parsed.runId,
422
+ threadId: parsed.threadId,
423
+ } satisfies AgUiDetachedStartAccepted,
424
+ { status: 202 },
425
+ );
426
+ }
427
+
428
+ options.sessionManager.failRun(parsed.runId);
429
+ throw error;
430
+ }
431
+ } catch (error) {
432
+ if (error instanceof z.ZodError) {
433
+ return Response.json(
434
+ {
435
+ error: "Invalid AG-UI detached start request",
436
+ details: error.issues.map((issue) => ({
437
+ path: issue.path,
438
+ message: issue.message,
439
+ })),
440
+ },
441
+ { status: 400 },
442
+ );
443
+ }
444
+
445
+ return Response.json(
446
+ {
447
+ error: error instanceof Error ? error.message : "Internal detached start failed",
448
+ },
449
+ { status: 500 },
450
+ );
451
+ }
452
+ };
453
+ }
@@ -173,6 +173,14 @@ export {
173
173
  getProviderNativeToolNames,
174
174
  type ProviderNativeToolInventoryOptions,
175
175
  } from "./provider-native-tool-inventory.js";
176
+ export {
177
+ type AgUiDetachedStartAccepted,
178
+ AgUiDetachedStartAcceptedSchema,
179
+ type AgUiDetachedStartHandlerOptions,
180
+ type AgUiDetachedStartRequest,
181
+ AgUiDetachedStartRequestSchema,
182
+ createAgUiDetachedStartHandler,
183
+ } from "./ag-ui-detached-start.js";
176
184
  export {
177
185
  type AgUiCancelHandlerOptions,
178
186
  type AgUiResumeHandlerOptions,