@nylorun/runtime 0.23.0-beta → 0.25.0-beta

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 (301) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/README.md +87 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/card.d.ts +11 -1
  10. package/dist/api/a2a/card.js +26 -2
  11. package/dist/api/a2a/endpoint.js +9 -3
  12. package/dist/api/a2a/protocol.d.ts +25 -2
  13. package/dist/api/a2a/protocol.js +20 -3
  14. package/dist/api/a2a/routes.d.ts +6 -1
  15. package/dist/api/a2a/routes.js +67 -9
  16. package/dist/api/a2a/tasks.js +2 -1
  17. package/dist/api/ag-ui/endpoint.js +8 -3
  18. package/dist/api/ag-ui/history.d.ts +6 -1
  19. package/dist/api/ag-ui/history.js +39 -3
  20. package/dist/api/ag-ui/routes.d.ts +4 -1
  21. package/dist/api/ag-ui/routes.js +63 -20
  22. package/dist/api/ag-ui/translate.js +3 -2
  23. package/dist/api/components.d.ts +5327 -1091
  24. package/dist/api/components.js +40 -16
  25. package/dist/api/gate/keys.d.ts +2 -1
  26. package/dist/api/gate/keys.js +14 -1
  27. package/dist/api/gate/routes.d.ts +6 -0
  28. package/dist/api/gate/routes.js +52 -3
  29. package/dist/api/http/app.js +17 -6
  30. package/dist/api/http/body.d.ts +3 -3
  31. package/dist/api/http/body.js +7 -7
  32. package/dist/api/http/define.d.ts +6 -0
  33. package/dist/api/http/define.js +13 -1
  34. package/dist/api/http/respond.js +1 -1
  35. package/dist/api/http/routes/artifacts.js +35 -8
  36. package/dist/api/http/routes/coverage.d.ts +9 -0
  37. package/dist/api/http/routes/coverage.js +34 -0
  38. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  39. package/dist/api/http/routes/environment-manifests.js +118 -0
  40. package/dist/api/http/routes/environments.d.ts +11 -0
  41. package/dist/api/http/routes/environments.js +332 -0
  42. package/dist/api/http/routes/mcp.d.ts +1 -1
  43. package/dist/api/http/routes/mcp.js +2 -2
  44. package/dist/api/http/routes/me.js +1 -1
  45. package/dist/api/http/routes/reads.d.ts +94 -9
  46. package/dist/api/http/routes/reads.js +54 -5
  47. package/dist/api/http/routes/sessions.js +24 -9
  48. package/dist/api/http/routes/tenant.js +65 -15
  49. package/dist/api/inbound-files.d.ts +65 -0
  50. package/dist/api/inbound-files.js +115 -0
  51. package/dist/api/openapi.js +48 -28
  52. package/dist/artifacts/disk.d.ts +59 -0
  53. package/dist/artifacts/disk.js +569 -0
  54. package/dist/artifacts/export.d.ts +1 -1
  55. package/dist/artifacts/export.js +2 -3
  56. package/dist/artifacts/folders.d.ts +6 -3
  57. package/dist/artifacts/folders.js +17 -12
  58. package/dist/artifacts/media-types.d.ts +15 -0
  59. package/dist/artifacts/media-types.js +56 -4
  60. package/dist/artifacts/parts.d.ts +12 -3
  61. package/dist/artifacts/parts.js +15 -4
  62. package/dist/artifacts/service.d.ts +2 -1
  63. package/dist/artifacts/service.js +6 -1
  64. package/dist/artifacts/workspace.d.ts +5 -5
  65. package/dist/artifacts/workspace.js +7 -19
  66. package/dist/core/flow-host.d.ts +3 -4
  67. package/dist/core/flow-host.js +5 -2
  68. package/dist/core/provider.d.ts +3 -2
  69. package/dist/core/provider.js +5 -37
  70. package/dist/core/sandbox-routes.d.ts +2 -21
  71. package/dist/core/sandbox-routes.js +11 -44
  72. package/dist/core/turn-manifest.d.ts +3 -2
  73. package/dist/core/turn-manifest.js +29 -5
  74. package/dist/execution/types.d.ts +1 -1
  75. package/dist/gates/contract.d.ts +3 -24
  76. package/dist/gates/contract.js +1 -36
  77. package/dist/gates/egress-credentials.d.ts +9 -0
  78. package/dist/gates/egress-credentials.js +11 -4
  79. package/dist/gates/egress.d.ts +22 -1
  80. package/dist/gates/egress.js +116 -10
  81. package/dist/gates/fixture-model.d.ts +28 -0
  82. package/dist/gates/fixture-model.js +92 -0
  83. package/dist/gates/handler.js +4 -4
  84. package/dist/gates/http-tool.d.ts +9 -6
  85. package/dist/gates/http-tool.js +11 -17
  86. package/dist/gates/in-process.d.ts +5 -0
  87. package/dist/gates/in-process.js +12 -1
  88. package/dist/gates/journal.d.ts +30 -0
  89. package/dist/gates/journal.js +51 -0
  90. package/dist/gates/mcp-handler.d.ts +4 -0
  91. package/dist/gates/mcp-handler.js +7 -4
  92. package/dist/gates/meter.d.ts +10 -2
  93. package/dist/gates/meter.js +20 -5
  94. package/dist/gates/policy.d.ts +49 -0
  95. package/dist/gates/policy.js +132 -0
  96. package/dist/gates/tenant-vaults.d.ts +7 -7
  97. package/dist/gates/tenant-vaults.js +5 -5
  98. package/dist/gates/tool-calls.d.ts +10 -5
  99. package/dist/gates/tool-calls.js +28 -10
  100. package/dist/gates/tool-client.d.ts +8 -0
  101. package/dist/gates/tool-client.js +27 -11
  102. package/dist/gates/tool-contract.d.ts +11 -69
  103. package/dist/gates/tool-contract.js +1 -85
  104. package/dist/gates/tool-gate.d.ts +1 -1
  105. package/dist/harness/calls.d.ts +14 -7
  106. package/dist/harness/calls.js +30 -24
  107. package/dist/harness/executors.js +12 -9
  108. package/dist/harness/main.js +24 -5
  109. package/dist/harness/pod.d.ts +39 -0
  110. package/dist/harness/pod.js +46 -0
  111. package/dist/harness/service.d.ts +0 -1
  112. package/dist/harness/service.js +17 -9
  113. package/dist/harness/setup.d.ts +29 -0
  114. package/dist/harness/setup.js +174 -0
  115. package/dist/harness/ws-client.js +3 -2
  116. package/dist/harness/ws-port.d.ts +1 -5
  117. package/dist/harness/ws-port.js +1 -5
  118. package/dist/harness-api/in-process.js +3 -3
  119. package/dist/harness-api/record.d.ts +15 -1
  120. package/dist/harness-api/record.js +180 -50
  121. package/dist/harness-api/redact.d.ts +3 -0
  122. package/dist/harness-api/redact.js +45 -0
  123. package/dist/harness-api/server.d.ts +35 -4
  124. package/dist/harness-api/server.js +130 -17
  125. package/dist/harness-api/start.d.ts +10 -3
  126. package/dist/harness-api/start.js +40 -10
  127. package/dist/harness-api/tool-journal.d.ts +105 -0
  128. package/dist/harness-api/tool-journal.js +132 -0
  129. package/dist/harness-api/tool-status.d.ts +13 -0
  130. package/dist/harness-api/tool-status.js +26 -0
  131. package/dist/harness-api/workspace.d.ts +12 -7
  132. package/dist/harness-api/workspace.js +47 -25
  133. package/dist/harness-api/ws-server.d.ts +11 -3
  134. package/dist/harness-api/ws-server.js +94 -15
  135. package/dist/host/gates.d.ts +2 -1
  136. package/dist/host/gates.js +10 -2
  137. package/dist/host/main.js +54 -5
  138. package/dist/host/operate.js +67 -5
  139. package/dist/host/stack-config.d.ts +64 -0
  140. package/dist/host/stack-config.js +62 -2
  141. package/dist/keys/client.js +4 -1
  142. package/dist/keys/keys.d.ts +13 -6
  143. package/dist/keys/keys.js +9 -8
  144. package/dist/management-openapi.json +1264 -2171
  145. package/dist/mcp/connect.d.ts +26 -22
  146. package/dist/mcp/connect.js +30 -34
  147. package/dist/mcp/pool.d.ts +3 -6
  148. package/dist/mcp/pool.js +14 -28
  149. package/dist/mcp/preview.d.ts +3 -3
  150. package/dist/mcp/preview.js +10 -21
  151. package/dist/mcp/snapshot.d.ts +16 -24
  152. package/dist/mcp/snapshot.js +33 -45
  153. package/dist/model/capabilities.d.ts +26 -0
  154. package/dist/model/capabilities.js +44 -0
  155. package/dist/model/catalog.d.ts +34 -3
  156. package/dist/model/catalog.js +38 -2
  157. package/dist/model/models.d.ts +2 -0
  158. package/dist/model/models.js +1 -1
  159. package/dist/model/native-blocks.d.ts +35 -0
  160. package/dist/model/native-blocks.js +170 -0
  161. package/dist/model/pi-model.js +114 -94
  162. package/dist/openapi.json +12822 -3943
  163. package/dist/reads/types.d.ts +16 -3
  164. package/dist/record/append.d.ts +3 -1
  165. package/dist/record/append.js +1 -0
  166. package/dist/record/envelope.d.ts +5 -1
  167. package/dist/record/envelope.js +7 -3
  168. package/dist/record/environment.d.ts +25 -0
  169. package/dist/record/environment.js +44 -0
  170. package/dist/record/index.d.ts +1 -1
  171. package/dist/record/index.js +1 -1
  172. package/dist/sandbox/disk-fs.d.ts +95 -0
  173. package/dist/sandbox/disk-fs.js +206 -0
  174. package/dist/sandbox/join.d.ts +17 -18
  175. package/dist/sandbox/join.js +46 -15
  176. package/dist/sandbox/manager.d.ts +47 -21
  177. package/dist/sandbox/manager.js +162 -107
  178. package/dist/sandbox/placement.d.ts +14 -13
  179. package/dist/sandbox/placement.js +31 -14
  180. package/dist/sandbox/pods/client.d.ts +9 -2
  181. package/dist/sandbox/pods/lifecycle.d.ts +36 -27
  182. package/dist/sandbox/pods/lifecycle.js +29 -26
  183. package/dist/sandbox/pods/reconcile.js +18 -12
  184. package/dist/sandbox/pods/spec.d.ts +15 -13
  185. package/dist/sandbox/pods/spec.js +17 -20
  186. package/dist/sandbox/records.d.ts +2 -2
  187. package/dist/sandbox/records.js +3 -3
  188. package/dist/sandbox/resolve.d.ts +37 -27
  189. package/dist/sandbox/resolve.js +73 -41
  190. package/dist/sandbox/runtime-tools.d.ts +9 -0
  191. package/dist/sandbox/runtime-tools.js +16 -0
  192. package/dist/sandbox/session-sandbox.d.ts +4 -6
  193. package/dist/sandbox/session-sandbox.js +47 -94
  194. package/dist/sandbox/setup.d.ts +64 -0
  195. package/dist/sandbox/setup.js +160 -0
  196. package/dist/sandbox/share.d.ts +21 -26
  197. package/dist/sandbox/share.js +36 -63
  198. package/dist/sandbox/skills.d.ts +8 -6
  199. package/dist/sandbox/skills.js +24 -23
  200. package/dist/sandbox/tenant-config.d.ts +12 -14
  201. package/dist/sandbox/tenant-config.js +12 -17
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  204. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  205. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  206. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  207. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  208. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  209. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  210. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  211. package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
  212. package/dist/store/postgres/reads.js +123 -17
  213. package/dist/store/postgres/record-writer.d.ts +4 -4
  214. package/dist/store/postgres/record-writer.js +9 -9
  215. package/dist/store/postgres/record.d.ts +7 -0
  216. package/dist/store/postgres/record.js +23 -2
  217. package/dist/store/postgres/schema.d.ts +1126 -736
  218. package/dist/store/postgres/schema.js +136 -73
  219. package/dist/store/postgres/store.js +195 -67
  220. package/dist/store/types.d.ts +165 -50
  221. package/dist/tenant/advance.d.ts +7 -6
  222. package/dist/tenant/advance.js +118 -42
  223. package/dist/tenant/artifact-tool.d.ts +1 -1
  224. package/dist/tenant/artifact-tool.js +46 -26
  225. package/dist/tenant/commands.d.ts +15 -2
  226. package/dist/tenant/commands.js +115 -14
  227. package/dist/tenant/context.d.ts +56 -24
  228. package/dist/tenant/context.js +4 -15
  229. package/dist/tenant/effects.d.ts +1 -1
  230. package/dist/tenant/effects.js +44 -29
  231. package/dist/tenant/environment-manifests.d.ts +57 -0
  232. package/dist/tenant/environment-manifests.js +200 -0
  233. package/dist/tenant/environments.d.ts +174 -0
  234. package/dist/tenant/environments.js +1044 -0
  235. package/dist/tenant/harness-tokens.d.ts +21 -0
  236. package/dist/tenant/harness-tokens.js +61 -0
  237. package/dist/tenant/host-token.d.ts +1 -1
  238. package/dist/tenant/host-token.js +2 -4
  239. package/dist/tenant/identity-file.d.ts +4 -4
  240. package/dist/tenant/identity-file.js +17 -8
  241. package/dist/tenant/issuers.js +3 -3
  242. package/dist/tenant/model-setting.d.ts +14 -0
  243. package/dist/tenant/model-setting.js +51 -0
  244. package/dist/tenant/principals.js +1 -1
  245. package/dist/tenant/reset.js +3 -3
  246. package/dist/tenant/runtime.d.ts +6 -1
  247. package/dist/tenant/runtime.js +7 -1
  248. package/dist/tenant/session.d.ts +3 -3
  249. package/dist/tenant/session.js +0 -14
  250. package/dist/tenant/sessions.d.ts +18 -14
  251. package/dist/tenant/sessions.js +99 -143
  252. package/dist/tenant/signing-keys.d.ts +5 -1
  253. package/dist/tenant/skill-tool.d.ts +3 -3
  254. package/dist/tenant/skill-tool.js +6 -11
  255. package/dist/tenant/status.js +3 -3
  256. package/dist/tenant/sweep.js +13 -7
  257. package/dist/tenant/tool-results.js +1 -1
  258. package/dist/tenant/tool-search.d.ts +7 -11
  259. package/dist/tenant/tool-search.js +8 -5
  260. package/dist/tenant/transcript.d.ts +245 -36
  261. package/dist/tenant/transcript.js +64 -12
  262. package/dist/tenant/turn-timing.d.ts +43 -0
  263. package/dist/tenant/turn-timing.js +39 -0
  264. package/dist/tenant/types.d.ts +2 -0
  265. package/dist/traces/ids.d.ts +41 -0
  266. package/dist/traces/ids.js +81 -0
  267. package/dist/traces/key.d.ts +6 -0
  268. package/dist/traces/key.js +37 -0
  269. package/dist/traces/map.d.ts +113 -0
  270. package/dist/traces/map.js +289 -0
  271. package/dist/traces/options.d.ts +7 -0
  272. package/dist/traces/options.js +1 -0
  273. package/dist/traces/otlp.d.ts +42 -0
  274. package/dist/traces/otlp.js +84 -0
  275. package/dist/traces/projector.d.ts +58 -0
  276. package/dist/traces/projector.js +200 -0
  277. package/dist/traces/read.d.ts +23 -0
  278. package/dist/traces/read.js +16 -0
  279. package/dist/vault/coverage.d.ts +26 -0
  280. package/dist/vault/coverage.js +62 -0
  281. package/dist/vault/error.d.ts +11 -1
  282. package/dist/vault/error.js +5 -1
  283. package/dist/vault/host-model.d.ts +4 -1
  284. package/dist/vault/host-model.js +10 -2
  285. package/dist/vault/service.d.ts +92 -48
  286. package/dist/vault/service.js +462 -376
  287. package/dist/vault/sources.d.ts +5 -16
  288. package/dist/vault/sources.js +6 -8
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +2 -2
  291. package/package.json +13 -13
  292. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  293. package/dist/api/http/routes/sandboxes.js +0 -153
  294. package/dist/api/http/routes/vaults.d.ts +0 -10
  295. package/dist/api/http/routes/vaults.js +0 -139
  296. package/dist/record/sandbox.d.ts +0 -25
  297. package/dist/record/sandbox.js +0 -43
  298. package/dist/sandbox/capability.d.ts +0 -3
  299. package/dist/sandbox/capability.js +0 -10
  300. package/dist/tenant/sandboxes.d.ts +0 -123
  301. package/dist/tenant/sandboxes.js +0 -566
@@ -1,4 +1,4 @@
1
- import type { SandboxBackend } from "../../sandbox/types.js";
1
+ import type { ExecRequest, ExecResult, SandboxBackend } from "../../sandbox/types.js";
2
2
  export interface LocalBackendOptions {
3
3
  /** Default `/workspace`. */
4
4
  readonly workspace?: string;
@@ -6,5 +6,13 @@ export interface LocalBackendOptions {
6
6
  readonly proxyEnv?: () => Readonly<Record<string, string>>;
7
7
  /** The base environment of commands (the engine's allowlisted one); `NYLORUN_*` is dropped. */
8
8
  readonly env: Readonly<Record<string, string | undefined>>;
9
+ /** The engine's `/harness` on the same volume, a disk root of its versions (R2d step 3). */
10
+ readonly harnessRoot?: string;
9
11
  }
12
+ /**
13
+ * How the pod runs a command: `/bin/sh -c` in its own process group, with the base environment,
14
+ * the request's variables and the proxy of the moment. The sandbox tools and the disk's setup
15
+ * (`harness/setup.ts`) both run commands this way.
16
+ */
17
+ export declare function localExec(options: LocalBackendOptions): (request: ExecRequest, signal: AbortSignal) => Promise<ExecResult>;
10
18
  export declare function localBackend(options: LocalBackendOptions): SandboxBackend;
@@ -49,10 +49,14 @@ function collector() {
49
49
  },
50
50
  };
51
51
  }
52
- export function localBackend(options) {
53
- const workspace = options.workspace ?? SANDBOX_WORKSPACE;
54
- const env = baseEnv(options.env, workspace);
55
- const exec = (request, signal) => new Promise((resolve, reject) => {
52
+ /**
53
+ * How the pod runs a command: `/bin/sh -c` in its own process group, with the base environment,
54
+ * the request's variables and the proxy of the moment. The sandbox tools and the disk's setup
55
+ * (`harness/setup.ts`) both run commands this way.
56
+ */
57
+ export function localExec(options) {
58
+ const env = baseEnv(options.env, options.workspace ?? SANDBOX_WORKSPACE);
59
+ return (request, signal) => new Promise((resolve, reject) => {
56
60
  if (signal.aborted)
57
61
  return reject(signal.reason ?? new Error("Turn cancelled"));
58
62
  const child = spawn("/bin/sh", ["-c", request.command], {
@@ -102,10 +106,18 @@ export function localBackend(options) {
102
106
  });
103
107
  });
104
108
  });
109
+ }
110
+ export function localBackend(options) {
111
+ const workspace = options.workspace ?? SANDBOX_WORKSPACE;
112
+ const exec = localExec(options);
105
113
  const missing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR";
106
114
  const handle = {
107
115
  workspace,
108
116
  exec,
117
+ diskRoots: {
118
+ workspace,
119
+ ...(options.harnessRoot === undefined ? {} : { harness: options.harnessRoot }),
120
+ },
109
121
  async readFile(path) {
110
122
  try {
111
123
  return await readFile(path, "utf8");
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Virtual backend: an emulated bash with a virtual filesystem, running in the Runtime process.
2
+ * Virtual backend: an emulated bash with a virtual filesystem, running in the harness's process
3
+ * (the harness container, or the Runtime process with NYLORUN_HARNESS=in-process).
3
4
  * `/workspace` is backed by a host directory so files survive stop and Runtime restarts.
4
5
  * It is not a VM boundary; it exists so the first run and CI work on any machine.
5
6
  */
@@ -67,6 +68,8 @@ export function virtualBackend(options) {
67
68
  ...(network ? { network } : {}),
68
69
  });
69
70
  return {
71
+ // A virtual workspace's disk is its host directory: `/workspace` only.
72
+ diskRoots: { workspace: root },
70
73
  async exec(request, signal) {
71
74
  const controller = new AbortController();
72
75
  let timedOut = false;
@@ -12,4 +12,14 @@ export interface CardDefinition {
12
12
  };
13
13
  readonly implementationVersion: string;
14
14
  }
15
- export declare function agentCard(agentId: string, definition: CardDefinition): Record<string, unknown>;
15
+ import type { ModelInput } from "@nylorun/core/contracts";
16
+ /**
17
+ * The input modes for a model that reads `inputs`: text and JSON, then the media types of the
18
+ * files it reads. No inputs known (no model in the vault): text and JSON.
19
+ */
20
+ export declare function inputModesFor(inputs: readonly ModelInput[] | undefined): string[];
21
+ /**
22
+ * `inputModes`: the media types the agent takes, when its model reads files; default text and
23
+ * JSON. The output modes stay text and JSON.
24
+ */
25
+ export declare function agentCard(agentId: string, definition: CardDefinition, inputModes?: readonly string[]): Record<string, unknown>;
@@ -4,8 +4,32 @@
4
4
  * `supportedInterfaces` is empty: whoever publishes the card (the gateway in v1) adds its own
5
5
  * URL, provider and security schemes.
6
6
  */
7
+ import { MODEL_IMAGE_TYPES } from "../../artifacts/media-types.js";
7
8
  const MODES = ["text/plain", "application/json"];
8
- export function agentCard(agentId, definition) {
9
+ /** The media types a card advertises for each file input a model reads. */
10
+ const INPUT_MODES = {
11
+ image: MODEL_IMAGE_TYPES,
12
+ pdf: ["application/pdf"],
13
+ office: [
14
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
15
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
16
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
17
+ ],
18
+ audio: ["audio/*"],
19
+ video: ["video/*"],
20
+ };
21
+ /**
22
+ * The input modes for a model that reads `inputs`: text and JSON, then the media types of the
23
+ * files it reads. No inputs known (no model in the vault): text and JSON.
24
+ */
25
+ export function inputModesFor(inputs) {
26
+ return [...MODES, ...(inputs ?? []).flatMap((input) => (input === "text" ? [] : INPUT_MODES[input]))];
27
+ }
28
+ /**
29
+ * `inputModes`: the media types the agent takes, when its model reads files; default text and
30
+ * JSON. The output modes stay text and JSON.
31
+ */
32
+ export function agentCard(agentId, definition, inputModes = MODES) {
9
33
  const { manifest } = definition;
10
34
  const name = typeof manifest.name === "string" && manifest.name !== "" ? manifest.name : agentId;
11
35
  const description = typeof manifest.description === "string" && manifest.description !== ""
@@ -17,7 +41,7 @@ export function agentCard(agentId, definition) {
17
41
  supportedInterfaces: [],
18
42
  version: definition.implementationVersion,
19
43
  capabilities: { streaming: false, pushNotifications: false, extendedAgentCard: false },
20
- defaultInputModes: MODES,
44
+ defaultInputModes: [...inputModes],
21
45
  defaultOutputModes: MODES,
22
46
  skills: [{ id: agentId, name, description, tags: [] }],
23
47
  };
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { readText } from "../http/body.js";
3
- import { tenantRoute } from "../http/define.js";
3
+ import { INLINE_BODY_MAX_BYTES } from "../inbound-files.js";
4
+ import { TraceHeaders, tenantRoute } from "../http/define.js";
5
+ import { traceContextOf } from "../../traces/ids.js";
4
6
  import { jsonResponse } from "../http/respond.js";
5
7
  import { a2aCall, a2aCard } from "./routes.js";
6
8
  const A2A_SPECIFICATION = {
@@ -55,7 +57,7 @@ export function a2aRoutes(api) {
55
57
  path: "/v1/a2a/agents/{agentId}",
56
58
  tags: TAGS,
57
59
  summary: "Call an agent over A2A",
58
- description: "A2A 1.0 JSON-RPC, for a subject: `SendMessage` (blocking up to 5 minutes, or `returnImmediately`), `GetTask` and `CancelTask`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors.",
60
+ description: "A2A 1.0 JSON-RPC, for a subject: `SendMessage` (blocking up to 5 minutes, or `returnImmediately`), `GetTask` and `CancelTask`. A message is text, one data part, or text and `raw` files (at most 20 MiB each, stored as artifacts of the context's session); a file `url` is `ContentTypeNotSupportedError`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors.",
59
61
  externalDocs: A2A_SPECIFICATION,
60
62
  request: {
61
63
  params: agentId,
@@ -63,6 +65,7 @@ export function a2aRoutes(api) {
63
65
  "A2A-Version": z.string().optional().meta({ description: "Or the `A2A-Version` header: `1.0`" }),
64
66
  }),
65
67
  body: { required: true, content: { "application/json": { schema: JsonRpcRequest } } },
68
+ headers: TraceHeaders,
66
69
  },
67
70
  responses: {
68
71
  200: {
@@ -73,9 +76,12 @@ export function a2aRoutes(api) {
73
76
  }, async (c) => {
74
77
  const scope = c.get("scope");
75
78
  const version = c.env.incoming.headers["a2a-version"];
79
+ const trace = traceContextOf(c.req.raw.headers);
76
80
  return jsonResponse(200, await a2aCall(c.env.tenant, scope, c.req.param("agentId"), {
77
- body: () => readText(c.req.raw),
81
+ // A message may carry files inline.
82
+ body: () => readText(c.req.raw, INLINE_BODY_MAX_BYTES),
78
83
  version: typeof version === "string" ? version : (c.req.query("A2A-Version") ?? null),
84
+ ...(trace ? { trace } : {}),
79
85
  }, c.env.outgoing));
80
86
  });
81
87
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A2A 1.0 over JSON-RPC: the wire types the Runtime answers with, the envelope, the error
3
3
  * codes and the parameter checks (Host feature `a2a-endpoint`). Only what v1 serves is typed:
4
- * text and data parts, tasks, the card. Field names are the spec's ProtoJSON (camelCase,
4
+ * text, data and file parts, tasks, the card. Field names are the spec's ProtoJSON (camelCase,
5
5
  * enum values such as `TASK_STATE_WORKING`). No Tenant state is read here.
6
6
  */
7
7
  /** The A2A version this endpoint speaks (`A2A-Version`, Major.Minor). */
@@ -16,6 +16,16 @@ export type Part = {
16
16
  data: unknown;
17
17
  mediaType?: string;
18
18
  metadata?: Record<string, unknown>;
19
+ } | {
20
+ raw: string;
21
+ mediaType?: string;
22
+ filename?: string;
23
+ metadata?: Record<string, unknown>;
24
+ } | {
25
+ url: string;
26
+ mediaType?: string;
27
+ filename?: string;
28
+ metadata?: Record<string, unknown>;
19
29
  };
20
30
  export interface Message {
21
31
  messageId: string;
@@ -120,11 +130,24 @@ export declare function parseEnvelope(text: string): {
120
130
  };
121
131
  /** `A2A-Version` as Major.Minor; empty means 0.3 (spec §3.6.2). */
122
132
  export declare function checkVersion(value: string | null | undefined): void;
123
- /** A client's message reduced to what the Runtime runs: text, or one JSON value. */
133
+ /** A file sent inline (`raw`, base64): stored as an artifact of the context's session. */
134
+ export interface RawFile {
135
+ readonly raw: string;
136
+ readonly mediaType?: string;
137
+ readonly filename?: string;
138
+ }
139
+ /**
140
+ * A client's message reduced to what the Runtime runs: text, one JSON value, or text and files
141
+ * sent inline.
142
+ */
124
143
  export type MessageInput = {
125
144
  content: string;
126
145
  } | {
127
146
  data: unknown;
147
+ } | {
148
+ files: readonly ({
149
+ text: string;
150
+ } | RawFile)[];
128
151
  };
129
152
  export interface SendParams {
130
153
  messageId: string;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A2A 1.0 over JSON-RPC: the wire types the Runtime answers with, the envelope, the error
3
3
  * codes and the parameter checks (Host feature `a2a-endpoint`). Only what v1 serves is typed:
4
- * text and data parts, tasks, the card. Field names are the spec's ProtoJSON (camelCase,
4
+ * text, data and file parts, tasks, the card. Field names are the spec's ProtoJSON (camelCase,
5
5
  * enum values such as `TASK_STATE_WORKING`). No Tenant state is read here.
6
6
  */
7
7
  /** The A2A version this endpoint speaks (`A2A-Version`, Major.Minor). */
@@ -136,7 +136,12 @@ function partKind(part, field) {
136
136
  return a2aFail("invalidParams", `${field} must contain exactly one of text, raw, url, data`, {}, field);
137
137
  if (kinds[0] === "text" && typeof part.text !== "string")
138
138
  return a2aFail("invalidParams", `${field}.text must be a string`, {}, `${field}.text`);
139
- return kinds[0] === "raw" || kinds[0] === "url" ? "file" : kinds[0];
139
+ if (kinds[0] === "raw" && typeof part.raw !== "string")
140
+ return a2aFail("invalidParams", `${field}.raw must be base64 bytes`, {}, `${field}.raw`);
141
+ for (const key of ["mediaType", "filename"])
142
+ if (part[key] !== undefined && typeof part[key] !== "string")
143
+ return a2aFail("invalidParams", `${field}.${key} must be a string`, {}, `${field}.${key}`);
144
+ return kinds[0] === "raw" ? "raw" : kinds[0] === "url" ? "url" : kinds[0];
140
145
  }
141
146
  /** `SendMessage` params (spec §3.2.1), checked in the order a client can fix them. */
142
147
  export function parseSendParams(params) {
@@ -166,8 +171,20 @@ export function parseSendParams(params) {
166
171
  input = { content: parts.map((part) => part.text).join("\n") };
167
172
  else if (kinds.length === 1 && kinds[0] === "data")
168
173
  input = { data: parts[0].data };
174
+ else if (kinds.includes("url"))
175
+ return a2aFail("contentTypeNotSupported", "File URLs are not fetched yet; send the bytes inline as raw", { accepted: "raw" });
176
+ else if (kinds.includes("data"))
177
+ return a2aFail("contentTypeNotSupported", "This agent accepts text and file parts, or a single data part", { accepted: "text/plain,application/json" });
169
178
  else
170
- return a2aFail("contentTypeNotSupported", "This agent accepts text parts, or a single data part; files are not supported", { accepted: "text/plain,application/json" });
179
+ input = {
180
+ files: parts.map((part, index) => kinds[index] === "text"
181
+ ? { text: part.text }
182
+ : {
183
+ raw: part.raw,
184
+ ...(part.mediaType !== undefined ? { mediaType: part.mediaType } : {}),
185
+ ...(part.filename !== undefined ? { filename: part.filename } : {}),
186
+ }),
187
+ };
171
188
  if ("content" in input && input.content.trim() === "")
172
189
  a2aFail("invalidParams", "The message has no text", {}, "message.parts");
173
190
  return {
@@ -1,11 +1,15 @@
1
1
  import type { ServerResponse } from "node:http";
2
2
  import { type AuthScope, type TenantContext } from "../../tenant/context.js";
3
+ import type { TraceContext } from "../../traces/ids.js";
3
4
  /**
4
5
  * How long a blocking `SendMessage` waits for the task to end or ask for input. After it, the
5
6
  * task is returned `WORKING` and keeps running; the caller polls `GetTask`.
6
7
  */
7
8
  export declare const A2A_BLOCKING_WAIT_MS: number;
8
- /** `GET /v1/a2a/agents/:agent/card`: the agent's card, for a caller who may use the agent. */
9
+ /**
10
+ * `GET /v1/a2a/agents/:agent/card`: the agent's card, for a caller who may use the agent. Its
11
+ * input modes name the files the Tenant's model reads.
12
+ */
9
13
  export declare function a2aCard(ctx: TenantContext, scope: AuthScope, agentId: string): Promise<unknown>;
10
14
  /**
11
15
  * `POST /v1/a2a/agents/:agent`: one A2A JSON-RPC call, for a subject. JSON-RPC answers (errors
@@ -16,4 +20,5 @@ export declare function a2aCard(ctx: TenantContext, scope: AuthScope, agentId: s
16
20
  export declare function a2aCall(ctx: TenantContext, scope: AuthScope, agentId: string, input: {
17
21
  body: () => Promise<string>;
18
22
  version: string | null;
23
+ trace?: TraceContext;
19
24
  }, response: ServerResponse): Promise<unknown>;
@@ -9,13 +9,20 @@
9
9
  * caller's scope (limits and owner checks apply), sessions derived from the caller's own
10
10
  * subject, history from Durable Streams. A task id from the client only selects a context and
11
11
  * a turn within the caller's own sessions, so another subject's task is `TaskNotFoundError`.
12
+ *
13
+ * A message's `raw` file parts are stored as artifacts of the context's session once it is open,
14
+ * and the message goes in as `parts` (`../inbound-files.ts`): a file the model does not read is
15
+ * `ContentTypeNotSupportedError`, and the files of a refused message are deleted.
12
16
  */
13
17
  import { randomUUID } from "node:crypto";
14
18
  import { SessionCommandSchema, } from "@nylorun/core/contracts";
15
19
  import { commandKey } from "../../core/flow-host.js";
16
- import { agentCard } from "./card.js";
20
+ import { contentTypeFor } from "../../artifacts/media-types.js";
21
+ import { discardFiles, inlineFileName, messageParts, } from "../inbound-files.js";
22
+ import { agentCard, inputModesFor } from "./card.js";
17
23
  import { A2aError, a2aFail, checkVersion, jsonRpcError, jsonRpcResult, parseEnvelope, parseSendParams, parseTaskParams, } from "./protocol.js";
18
24
  import { SETTLED_EVENTS, buildTask, contextSessionId, newContextId, parseTaskId, pendingInteraction, taskIdOf, } from "./tasks.js";
25
+ import { currentModelInputs } from "../../model/catalog.js";
19
26
  import { accessOf } from "../../tenant/auth.js";
20
27
  import { command } from "../../tenant/commands.js";
21
28
  import { loadSession, } from "../../tenant/context.js";
@@ -27,9 +34,14 @@ import { putSession } from "../../tenant/sessions.js";
27
34
  * task is returned `WORKING` and keeps running; the caller polls `GetTask`.
28
35
  */
29
36
  export const A2A_BLOCKING_WAIT_MS = 5 * 60_000;
30
- /** `GET /v1/a2a/agents/:agent/card`: the agent's card, for a caller who may use the agent. */
37
+ /**
38
+ * `GET /v1/a2a/agents/:agent/card`: the agent's card, for a caller who may use the agent. Its
39
+ * input modes name the files the Tenant's model reads.
40
+ */
31
41
  export async function a2aCard(ctx, scope, agentId) {
32
- return agentCard(agentId, await definitionFor(ctx, agentId, accessOf(scope)));
42
+ const definition = await definitionFor(ctx, agentId, accessOf(scope));
43
+ // The files a caller may send: those the Tenant's model reads now.
44
+ return agentCard(agentId, definition, inputModesFor(await currentModelInputs(ctx)));
33
45
  }
34
46
  /**
35
47
  * `POST /v1/a2a/agents/:agent`: one A2A JSON-RPC call, for a subject. JSON-RPC answers (errors
@@ -50,7 +62,7 @@ export async function a2aCall(ctx, scope, agentId, input, response) {
50
62
  // A blocking call stops waiting when the caller hangs up; the task keeps running.
51
63
  const hangup = new AbortController();
52
64
  response.on("close", () => hangup.abort());
53
- const caller = { ctx, scope, access, agentId };
65
+ const caller = { ctx, scope, access, agentId, ...(input.trace ? { trace: input.trace } : {}) };
54
66
  const { id } = envelope.request;
55
67
  try {
56
68
  checkVersion(input.version);
@@ -60,7 +72,7 @@ export async function a2aCall(ctx, scope, agentId, input, response) {
60
72
  if (error instanceof A2aError)
61
73
  return jsonRpcError(id, error);
62
74
  // Limits, scopes and unavailable streams stay HTTP answers, as on every Tenant route.
63
- if (error instanceof HttpError && [403, 429, 503].includes(error.status))
75
+ if (error instanceof HttpError && [403, 413, 429, 503].includes(error.status))
64
76
  throw error;
65
77
  ctx.config.logger.warn("a2a request failed", {
66
78
  agentId,
@@ -137,11 +149,18 @@ async function run(caller, sessionId, input,
137
149
  /** The error for a `409` conflict: the session is busy or no longer waiting. */
138
150
  conflict) {
139
151
  try {
140
- const accepted = (await command(caller.ctx, sessionId, SessionCommandSchema.parse(input), caller.scope));
152
+ const accepted = (await command(caller.ctx, sessionId, SessionCommandSchema.parse(input), caller.scope, caller.trace ? { trace: caller.trace } : {}));
141
153
  return { turnId: accepted.turnId, cursor: accepted.cursor };
142
154
  }
143
155
  catch (error) {
144
- if (!(error instanceof HttpError) || error.status !== 409)
156
+ if (!(error instanceof HttpError))
157
+ throw error;
158
+ // A file the agent's model does not read (`attachment_unsupported`).
159
+ if (error.status === 422)
160
+ return a2aFail("contentTypeNotSupported", error.message, {
161
+ reason: error.rejection.code ?? "request_rejected",
162
+ });
163
+ if (error.status !== 409)
145
164
  throw error;
146
165
  if (error.message.startsWith("Idempotency key"))
147
166
  return a2aFail("invalidParams", "message.messageId was already used for a different message", {}, "message.messageId");
@@ -162,13 +181,17 @@ async function openContext(caller, sessionId) {
162
181
  async function sendMessage(caller, params, signal) {
163
182
  const send = parseSendParams(params);
164
183
  const idempotencyKey = `a2a:${send.messageId}`;
165
- const value = "content" in send.input ? send.input.content : send.input.data;
166
184
  let contextId;
167
185
  let sessionId;
168
186
  let accepted;
169
187
  if (send.taskId !== undefined) {
170
188
  // A reply on an existing task: only a question it is waiting on can take it.
171
189
  const ids = parseTaskId(send.taskId) ?? taskNotFound(send.taskId);
190
+ const value = "content" in send.input
191
+ ? send.input.content
192
+ : "data" in send.input
193
+ ? send.input.data
194
+ : a2aFail("contentTypeNotSupported", "A reply takes text or one data part; send files in a new message", { accepted: "text/plain,application/json" });
172
195
  if (send.contextId !== undefined && send.contextId !== ids.contextId)
173
196
  a2aFail("invalidParams", "message.contextId does not match the task's context", {}, "message.contextId");
174
197
  contextId = ids.contextId;
@@ -198,17 +221,21 @@ async function sendMessage(caller, params, signal) {
198
221
  sessionId = contextSessionId(caller.access.owner, caller.agentId, contextId);
199
222
  await openContext(caller, sessionId);
200
223
  const context = contextId;
224
+ const { body, stored } = await messageBody(caller, sessionId, idempotencyKey, send.input);
201
225
  accepted = await run(caller, sessionId, {
202
226
  type: "message",
203
227
  requestId: randomUUID(),
204
228
  idempotencyKey,
205
- ...send.input,
229
+ ...body,
206
230
  }, async () => {
207
231
  const active = (await contextSession(caller, sessionId))?.activeTurnId;
208
232
  const taskId = active ? taskIdOf(context, active) : undefined;
209
233
  return taskId
210
234
  ? new A2aError("unsupportedOperation", `The context has a task in progress (${taskId}); reply to it or cancel it`, { taskId })
211
235
  : new A2aError("unsupportedOperation", "The context has unresolved work; start a new context");
236
+ }).catch(async (error) => {
237
+ await discardFiles(caller.ctx, stored, caller.access);
238
+ throw error;
212
239
  });
213
240
  }
214
241
  if (!send.returnImmediately)
@@ -216,6 +243,37 @@ async function sendMessage(caller, params, signal) {
216
243
  const { task } = await ownedTask(caller, taskIdOf(contextId, accepted.turnId), send.historyLength);
217
244
  return { task };
218
245
  }
246
+ /**
247
+ * A new message's body: its text, its JSON value, or parts with its `raw` files stored as
248
+ * artifacts of the session (`stored`: the ones this call created). A file the Runtime cannot
249
+ * store is `InvalidParamsError`; one past a limit stays an HTTP `413`.
250
+ */
251
+ async function messageBody(caller, sessionId, idempotencyKey, input) {
252
+ if (!("files" in input))
253
+ return { body: input, stored: [] };
254
+ let files = 0;
255
+ const inbound = input.files.map((part) => {
256
+ if ("text" in part)
257
+ return { type: "text", text: part.text };
258
+ const index = files++;
259
+ const mediaType = part.mediaType ?? (part.filename !== undefined ? contentTypeFor(part.filename) : "application/octet-stream");
260
+ return {
261
+ type: "inline",
262
+ name: part.filename ?? inlineFileName(mediaType, index),
263
+ mediaType,
264
+ data: part.raw,
265
+ };
266
+ });
267
+ try {
268
+ const { parts, stored } = await messageParts(caller.ctx, sessionId, idempotencyKey, inbound, caller.access);
269
+ return { body: { parts }, stored };
270
+ }
271
+ catch (error) {
272
+ if (error instanceof HttpError && error.status === 400)
273
+ return a2aFail("invalidParams", error.message, {}, "message.parts");
274
+ throw error;
275
+ }
276
+ }
219
277
  /** The interaction a reply answers: the task must be active and paused on a question. */
220
278
  async function replyTarget(caller, session, sessionId, turnId, taskId) {
221
279
  if (session.activeTurnId !== turnId) {
@@ -97,7 +97,8 @@ export function historyOf(events, contextId, taskId) {
97
97
  add(clientMessageId(event), "ROLE_USER", [valuePart(payload.value)]);
98
98
  break;
99
99
  case "message.assistant":
100
- // Agents used as tools speak inside the turn; only the agent's own words are history.
100
+ // Agents used as tools speak inside the turn; only the agent's own words are history. A
101
+ // step that said nothing (`text: ""`, R7.1) is none.
101
102
  if (!payload.agent && typeof payload.text === "string" && payload.text.trim() !== "")
102
103
  add(event.eventId, "ROLE_AGENT", [{ text: payload.text }]);
103
104
  break;
@@ -2,7 +2,9 @@ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
2
2
  import { z } from "zod";
3
3
  import { EventSchemas, MessageSchema, RunAgentInputSchema } from "@ag-ui/core/schemas";
4
4
  import { readJson } from "../http/body.js";
5
- import { tenantRoute } from "../http/define.js";
5
+ import { INLINE_BODY_MAX_BYTES } from "../inbound-files.js";
6
+ import { TraceHeaders, tenantRoute } from "../http/define.js";
7
+ import { traceContextOf } from "../../traces/ids.js";
6
8
  import { jsonResponse } from "../http/respond.js";
7
9
  import { agUiCaller, cancelRun, reattachRun, startRun, threadMessages } from "./routes.js";
8
10
  import { sessionIdFor } from "./session-id.js";
@@ -49,18 +51,21 @@ export function agUiRoutes(api) {
49
51
  path: "/v1/ag-ui/agents/{agentId}",
50
52
  tags: TAGS,
51
53
  summary: "Run an agent",
52
- description: "For a person, named by a trusted issuer's token or by `Nylorun-Subject`. The thread is one session per person, agent and thread, created on its first run with `forwardedProps.nylorun.session`. The trailing user message starts a turn (its id is the idempotency key); `resume` answers interrupts. A busy session is a `RUN_ERROR` in the stream.",
54
+ description: "For a person, named by a trusted issuer's token or by `Nylorun-Subject`. The thread is one session per person, agent and thread, created on its first run with `forwardedProps.nylorun.session`. The trailing user message starts a turn (its id is the idempotency key); its media parts are files: bytes sent inline (`data`, at most 20 MiB each) are stored as artifacts of the session, a `file` source with provider `nylorun` names an artifact, and a URL is refused. `resume` answers interrupts. A busy session, or a file the model does not read, is a `RUN_ERROR` in the stream.",
53
55
  request: {
54
56
  params: agentId,
55
57
  body: {
56
58
  required: true,
57
59
  content: { "application/json": { schema: ref("AgUiRunAgentInput") } },
58
60
  },
61
+ headers: TraceHeaders,
59
62
  },
60
63
  responses: { 200: events("The run's events, from RUN_STARTED to its end") },
61
64
  }, async (c) => {
62
65
  const { scope, subject, access } = callerOf(c);
63
- await startRun(c.env.tenant, scope, c.req.param("agentId"), { subject, access }, await readJson(c.req.raw), c.env.outgoing);
66
+ await startRun(c.env.tenant, scope, c.req.param("agentId"), { subject, access },
67
+ // A message may carry files inline.
68
+ await readJson(c.req.raw, INLINE_BODY_MAX_BYTES), c.env.outgoing, traceContextOf(c.req.raw.headers));
64
69
  return RESPONSE_ALREADY_SENT;
65
70
  });
66
71
  tenantRoute(api, FOR_A_PERSON, {
@@ -1,7 +1,12 @@
1
1
  /**
2
2
  * An AG-UI transcript rebuilt from a session's log, with the ids a live run gave each
3
- * message (see `translate.ts`), usable as `HttpAgent`'s `initialMessages`.
3
+ * message (see `translate.ts`), usable as `HttpAgent`'s `initialMessages`. A user message with
4
+ * files has array content: its text parts, and each file as a media part whose source is the
5
+ * artifact (`{ type: "file", provider: "nylorun", value: <artifact id> }`), typed by the media
6
+ * type its `artifact.created` event recorded.
4
7
  */
5
8
  import type { Message } from "@ag-ui/core";
6
9
  import { type LiveEvent } from "@nylorun/core/contracts";
10
+ /** The `provider` of a file source that names one of the Tenant's artifacts. */
11
+ export declare const NYLORUN_FILE_PROVIDER = "nylorun";
7
12
  export declare function messagesFromEvents(items: readonly LiveEvent[]): Message[];
@@ -1,10 +1,44 @@
1
- import { parseTranscriptEvent } from "@nylorun/core/contracts";
1
+ import { isEmptyAssistantMessage, parseTranscriptEvent, } from "@nylorun/core/contracts";
2
2
  import { outputText, toolResultOf } from "./translate.js";
3
+ /** The `provider` of a file source that names one of the Tenant's artifacts. */
4
+ export const NYLORUN_FILE_PROVIDER = "nylorun";
5
+ /** A file part as an AG-UI media part, its kind from its media type when the log has it. */
6
+ function filePart(artifactId, mediaType) {
7
+ const top = mediaType?.split("/")[0];
8
+ const type = top === "image" || top === "audio" || top === "video" ? top : "document";
9
+ return {
10
+ type,
11
+ source: {
12
+ type: "file",
13
+ value: artifactId,
14
+ provider: NYLORUN_FILE_PROVIDER,
15
+ ...(mediaType ? { mimeType: mediaType } : {}),
16
+ },
17
+ };
18
+ }
19
+ /** A message's parts (protocol 6): its text, or text and media parts when it has files. */
20
+ function partsContent(parts, mediaTypes) {
21
+ if (!parts.some((part) => part.type === "file"))
22
+ return parts.flatMap((part) => (typeof part.text === "string" ? [part.text] : [])).join("\n");
23
+ return parts.flatMap((part) => part.type === "file" && typeof part.artifactId === "string"
24
+ ? [filePart(part.artifactId, mediaTypes.get(part.artifactId))]
25
+ : typeof part.text === "string"
26
+ ? [{ type: "text", text: part.text }]
27
+ : []);
28
+ }
3
29
  export function messagesFromEvents(items) {
4
30
  const messages = [];
5
31
  const results = new Set();
32
+ // Each artifact's media type, from its events, for the files a message names.
33
+ const mediaTypes = new Map();
6
34
  let sawAssistant = false;
7
35
  for (const raw of items) {
36
+ if (raw.type === "artifact.created" || raw.type === "artifact.version.created") {
37
+ const p = (raw.payload ?? {});
38
+ if (typeof p.artifactId === "string" && typeof p.contentType === "string")
39
+ mediaTypes.set(p.artifactId, p.contentType);
40
+ continue;
41
+ }
8
42
  if (raw.type === "command.message") {
9
43
  const p = (raw.payload ?? {});
10
44
  messages.push({
@@ -14,8 +48,7 @@ export function messagesFromEvents(items) {
14
48
  content: typeof p.content === "string"
15
49
  ? p.content
16
50
  : Array.isArray(p.parts)
17
- ? // A message with parts (protocol 6): its text; files are artifacts, not text.
18
- p.parts.flatMap((part) => (typeof part.text === "string" ? [part.text] : [])).join("\n")
51
+ ? partsContent(p.parts, mediaTypes)
19
52
  : JSON.stringify(p.data),
20
53
  });
21
54
  sawAssistant = false;
@@ -37,6 +70,9 @@ export function messagesFromEvents(items) {
37
70
  });
38
71
  continue;
39
72
  }
73
+ // A step that said nothing is in the log for its timing (R7.1), never a message.
74
+ if (event.type === "message.assistant" && isEmptyAssistantMessage(event.payload))
75
+ continue;
40
76
  if (event.type === "message.assistant" && !event.payload.agent) {
41
77
  const p = event.payload;
42
78
  sawAssistant = true;
@@ -1,6 +1,7 @@
1
1
  import type { ServerResponse } from "node:http";
2
2
  import { type Message } from "@ag-ui/core";
3
3
  import { type AuthScope, type SessionAccess, type TenantContext } from "../../tenant/context.js";
4
+ import type { TraceContext } from "../../traces/ids.js";
4
5
  /**
5
6
  * Who an AG-UI request acts for, once its agent is known: a person (an issuer token's subject,
6
7
  * or `Nylorun-Subject`), who may use the agent; else 400, or the 404 of a missing agent.
@@ -13,7 +14,9 @@ export declare function agUiCaller(scope: AuthScope, agentId: string): {
13
14
  export declare function startRun(ctx: TenantContext, scope: AuthScope, agentId: string, caller: {
14
15
  subject: string;
15
16
  access: SessionAccess | undefined;
16
- }, body: unknown, response: ServerResponse): Promise<void>;
17
+ }, body: unknown, response: ServerResponse,
18
+ /** The request's trace context (R7.4): the turn the run starts joins the caller's trace. */
19
+ trace?: TraceContext): Promise<void>;
17
20
  /** `GET …/threads/:thread/messages`: the thread's messages; none before its first run. */
18
21
  export declare function threadMessages(ctx: TenantContext, id: string, access: SessionAccess | undefined): Promise<Message[]>;
19
22
  /**