@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
@@ -7,11 +7,14 @@ export declare function contentKey(sha256: string): string;
7
7
  export declare function sha256Hex(bytes: Uint8Array): string;
8
8
  /** Entries in manifest order: by path, comparing UTF-16 code units. */
9
9
  export declare function sortEntries(entries: readonly FolderEntry[]): FolderEntry[];
10
- /** The manifest's bytes, as stored: deterministic for the same entries. */
11
- export declare function encodeManifest(entries: readonly FolderEntry[]): Uint8Array;
10
+ /**
11
+ * The manifest's bytes, as stored: deterministic for the same entries. A v1 manifest keeps each
12
+ * file's path, size, hash and type; a v2 one (a disk version, R2d step 3) every field.
13
+ */
14
+ export declare function encodeManifest(entries: readonly FolderEntry[], format?: FolderManifest["format"]): Uint8Array;
12
15
  /** A folder version's manifest, read from the Object store. */
13
16
  export declare function readManifest(blobs: BlobStore, version: ArtifactVersionRow): Promise<FolderManifest>;
14
- /** The entry at `path`, or the 404 of a missing file. */
17
+ /** The file at `path` (not a v2 link or directory), or the 404 of a missing file. */
15
18
  export declare function entryAt(manifest: FolderManifest, path: string): FolderEntry;
16
19
  /** What changed from `from` (absent: nothing) to `to`. */
17
20
  export declare function diffManifests(artifactId: string, from: {
@@ -34,16 +34,16 @@ export function sha256Hex(bytes) {
34
34
  export function sortEntries(entries) {
35
35
  return [...entries].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
36
36
  }
37
- /** The manifest's bytes, as stored: deterministic for the same entries. */
38
- export function encodeManifest(entries) {
37
+ /**
38
+ * The manifest's bytes, as stored: deterministic for the same entries. A v1 manifest keeps each
39
+ * file's path, size, hash and type; a v2 one (a disk version, R2d step 3) every field.
40
+ */
41
+ export function encodeManifest(entries, format = FOLDER_MANIFEST_FORMAT) {
39
42
  const manifest = {
40
- format: FOLDER_MANIFEST_FORMAT,
41
- entries: sortEntries(entries).map(({ path, size, sha256, contentType }) => ({
42
- path,
43
- size,
44
- sha256,
45
- contentType,
46
- })),
43
+ format,
44
+ entries: sortEntries(entries).map((entry) => format === FOLDER_MANIFEST_FORMAT
45
+ ? { path: entry.path, size: entry.size, sha256: entry.sha256, contentType: entry.contentType }
46
+ : { ...entry }),
47
47
  };
48
48
  return new TextEncoder().encode(JSON.stringify(manifest));
49
49
  }
@@ -57,9 +57,9 @@ export async function readManifest(blobs, version) {
57
57
  throw new Error(`The manifest of ${version.artifactId} v${version.version} is malformed`);
58
58
  return parsed.data;
59
59
  }
60
- /** The entry at `path`, or the 404 of a missing file. */
60
+ /** The file at `path` (not a v2 link or directory), or the 404 of a missing file. */
61
61
  export function entryAt(manifest, path) {
62
- return (manifest.entries.find((entry) => entry.path === path) ??
62
+ return (manifest.entries.find((entry) => entry.path === path && entry.type === undefined) ??
63
63
  fail(404, `No file ${path} in this folder version`));
64
64
  }
65
65
  /** What changed from `from` (absent: nothing) to `to`. */
@@ -72,7 +72,11 @@ export function diffManifests(artifactId, from, to) {
72
72
  const prior = before.get(entry.path);
73
73
  if (!prior)
74
74
  added.push(entry);
75
- else if (prior.sha256 !== entry.sha256 || prior.contentType !== entry.contentType)
75
+ else if (prior.sha256 !== entry.sha256 ||
76
+ prior.contentType !== entry.contentType ||
77
+ prior.type !== entry.type ||
78
+ prior.target !== entry.target ||
79
+ prior.mode !== entry.mode)
76
80
  changed.push({ path: entry.path, from: prior, to: entry });
77
81
  }
78
82
  const removed = (from?.manifest.entries ?? []).filter((entry) => !after.has(entry.path));
@@ -182,6 +186,7 @@ export async function commitFolderVersion(ctx, input) {
182
186
  labelsJson: null,
183
187
  createdAt,
184
188
  updatedAt: createdAt,
189
+ environmentId: null,
185
190
  };
186
191
  await t.insertArtifact(row, version);
187
192
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Media types of file artifacts: what an upload may declare, what a name's extension implies,
3
+ * and which model input a file is (`modelInputOf`, protocol 12): text, an image, a PDF, an Office
4
+ * document, audio or video. A model reads a file only when its inputs include that one.
5
+ */
6
+ import type { ModelInput } from "@nylorun/core/contracts";
1
7
  /** Images the model reads as images. */
2
8
  export declare const MODEL_IMAGE_TYPES: readonly string[];
3
9
  /** The largest image or text file a model call carries inline. */
@@ -13,3 +19,12 @@ export declare function isModelImage(contentType: string): boolean;
13
19
  export declare function isText(contentType: string): boolean;
14
20
  /** The usual extension of a media type, for a name the Runtime gives a file; undefined if none. */
15
21
  export declare function extensionFor(contentType: string): string | undefined;
22
+ /** The model input a file of `contentType` is, or undefined for a type no model reads. */
23
+ export declare function modelInputOf(contentType: string): ModelInput | undefined;
24
+ /** An input, in a sentence: "PDFs", "Office documents"; "files of this type" for none. */
25
+ export declare function inputLabel(input: ModelInput | undefined): string;
26
+ /**
27
+ * Why a model that reads `inputs` cannot read a file of `contentType`, or undefined when it can.
28
+ * An image must also be PNG, JPEG, GIF or WebP.
29
+ */
30
+ export declare function unreadableReason(contentType: string, inputs: readonly ModelInput[]): string | undefined;
@@ -1,7 +1,3 @@
1
- /**
2
- * Media types of file artifacts: what an upload may declare, what a name's extension implies,
3
- * and how the model reads a file (an image as an image, text as text, anything else refused).
4
- */
5
1
  import { DEFAULT_CONTENT_TYPE } from "../blob/index.js";
6
2
  const MEDIA_TYPE = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/;
7
3
  const BY_EXTENSION = {
@@ -12,6 +8,12 @@ const BY_EXTENSION = {
12
8
  webp: "image/webp",
13
9
  svg: "image/svg+xml",
14
10
  pdf: "application/pdf",
11
+ doc: "application/msword",
12
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
13
+ xls: "application/vnd.ms-excel",
14
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
15
+ ppt: "application/vnd.ms-powerpoint",
16
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
15
17
  txt: "text/plain",
16
18
  md: "text/markdown",
17
19
  csv: "text/csv",
@@ -83,3 +85,53 @@ export function extensionFor(contentType) {
83
85
  const type = essence(contentType);
84
86
  return Object.keys(BY_EXTENSION).find((extension) => BY_EXTENSION[extension] === type);
85
87
  }
88
+ /** Word, Excel and PowerPoint documents, old and Open XML. */
89
+ function isOffice(type) {
90
+ return (type === "application/msword" ||
91
+ type === "application/vnd.ms-excel" ||
92
+ type === "application/vnd.ms-powerpoint" ||
93
+ type.startsWith("application/vnd.openxmlformats-officedocument."));
94
+ }
95
+ /** The model input a file of `contentType` is, or undefined for a type no model reads. */
96
+ export function modelInputOf(contentType) {
97
+ const type = essence(contentType);
98
+ if (isText(type))
99
+ return "text";
100
+ if (type.startsWith("image/"))
101
+ return "image";
102
+ if (type === "application/pdf")
103
+ return "pdf";
104
+ if (isOffice(type))
105
+ return "office";
106
+ if (type.startsWith("audio/"))
107
+ return "audio";
108
+ if (type.startsWith("video/"))
109
+ return "video";
110
+ return undefined;
111
+ }
112
+ const LABELS = {
113
+ text: "text files",
114
+ image: "images",
115
+ pdf: "PDFs",
116
+ office: "Office documents",
117
+ audio: "audio",
118
+ video: "video",
119
+ };
120
+ /** An input, in a sentence: "PDFs", "Office documents"; "files of this type" for none. */
121
+ export function inputLabel(input) {
122
+ return input === undefined ? "files of this type" : LABELS[input];
123
+ }
124
+ /**
125
+ * Why a model that reads `inputs` cannot read a file of `contentType`, or undefined when it can.
126
+ * An image must also be PNG, JPEG, GIF or WebP.
127
+ */
128
+ export function unreadableReason(contentType, inputs) {
129
+ const input = modelInputOf(contentType);
130
+ if (input === undefined)
131
+ return `no model reads ${essence(contentType)} files`;
132
+ if (!inputs.includes(input))
133
+ return `this model does not read ${LABELS[input]}`;
134
+ if (input === "image" && !isModelImage(contentType))
135
+ return "the model reads PNG, JPEG, GIF and WebP images only";
136
+ return undefined;
137
+ }
@@ -5,7 +5,7 @@
5
5
  * The engine gets an opaque media part whose `reference` is `{ artifactId, version }`: model-gate
6
6
  * resolves it to bytes (`./files.ts`), and the transcript and the record never hold them.
7
7
  */
8
- import type { MessagePart } from "@nylorun/core/contracts";
8
+ import type { MessagePart, ModelInput } from "@nylorun/core/contracts";
9
9
  import type { JsonObject, JsonValue } from "@nylorun/core/define";
10
10
  import type { Tx } from "../store/types.js";
11
11
  import { type ArtifactAccess } from "./service.js";
@@ -24,11 +24,20 @@ export interface FileReference {
24
24
  readonly version: number;
25
25
  }
26
26
  export declare function fileReferenceOf(value: JsonValue | undefined): FileReference | undefined;
27
+ /** A file part the Tenant's model does not read (protocol 12, `attachment_unsupported`). */
28
+ export interface RefusedPart {
29
+ readonly artifactId: string;
30
+ readonly name: string;
31
+ readonly mediaType: string;
32
+ readonly reason: string;
33
+ }
27
34
  /**
28
35
  * The parts with each file pinned to a version: for the engine (`engine`) and for a workflow's
29
- * JSON input (`pinned`). Reads in the caller's transaction.
36
+ * JSON input (`pinned`). Reads in the caller's transaction. With `inputs`, what the Tenant's model
37
+ * reads, each file of a type it does not read is in `refused` (its type only: no size is checked).
30
38
  */
31
- export declare function resolveMessageParts(t: Tx, parts: readonly MessagePart[], sessionId: string, access: ArtifactAccess): Promise<{
39
+ export declare function resolveMessageParts(t: Tx, parts: readonly MessagePart[], sessionId: string, access: ArtifactAccess, inputs?: readonly ModelInput[]): Promise<{
32
40
  engine: EnginePart[];
33
41
  pinned: MessagePart[];
42
+ refused: RefusedPart[];
34
43
  }>;
@@ -1,4 +1,5 @@
1
1
  import { fail } from "../tenant/http.js";
2
+ import { unreadableReason } from "./media-types.js";
2
3
  import { readableArtifact, versionOf } from "./service.js";
3
4
  export function fileReferenceOf(value) {
4
5
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -10,11 +11,13 @@ export function fileReferenceOf(value) {
10
11
  }
11
12
  /**
12
13
  * The parts with each file pinned to a version: for the engine (`engine`) and for a workflow's
13
- * JSON input (`pinned`). Reads in the caller's transaction.
14
+ * JSON input (`pinned`). Reads in the caller's transaction. With `inputs`, what the Tenant's model
15
+ * reads, each file of a type it does not read is in `refused` (its type only: no size is checked).
14
16
  */
15
- export async function resolveMessageParts(t, parts, sessionId, access) {
17
+ export async function resolveMessageParts(t, parts, sessionId, access, inputs) {
16
18
  const engine = [];
17
19
  const pinned = [];
20
+ const refused = [];
18
21
  for (const part of parts) {
19
22
  if (part.type === "text") {
20
23
  engine.push({ type: "text", text: part.text });
@@ -27,12 +30,20 @@ export async function resolveMessageParts(t, parts, sessionId, access) {
27
30
  if (artifact.kind !== "file")
28
31
  fail(400, `Artifact ${artifact.id} is a folder; a file part names a file artifact`);
29
32
  const version = await versionOf(t, artifact, part.version);
33
+ const reason = inputs && unreadableReason(version.contentType, inputs);
34
+ if (reason)
35
+ refused.push({ artifactId: artifact.id, name: artifact.name, mediaType: version.contentType, reason });
30
36
  engine.push({
31
37
  type: "media",
32
38
  mediaType: version.contentType,
33
- reference: { artifactId: artifact.id, version: version.version, name: artifact.name },
39
+ reference: {
40
+ artifactId: artifact.id,
41
+ version: version.version,
42
+ name: artifact.name,
43
+ size: version.size,
44
+ },
34
45
  });
35
46
  pinned.push({ type: "file", artifactId: artifact.id, version: version.version });
36
47
  }
37
- return { engine, pinned };
48
+ return { engine, pinned, refused };
38
49
  }
@@ -66,7 +66,8 @@ export declare function folderContent(ctx: TenantContext, id: string, version: n
66
66
  /**
67
67
  * Deletes the artifact and every version: its rows and, for a session's artifact, its
68
68
  * `artifact.deleted` event in one transaction, then its bytes. A folder's content-addressed
69
- * files go too, those no other version names, under the quota lock (`folders.ts`).
69
+ * files go too, those no other version names, under the quota lock (`folders.ts`). An
70
+ * Environment's disk artifact is refused: its own routes delete its versions.
70
71
  */
71
72
  export declare function deleteArtifact(ctx: TenantContext, id: string, access: ArtifactAccess): Promise<{
72
73
  artifactId: string;
@@ -52,6 +52,7 @@ export function artifactView(row, versions) {
52
52
  name: row.name,
53
53
  contentType: row.contentType,
54
54
  ...(row.sessionId === null ? {} : { sessionId: row.sessionId }),
55
+ ...(row.environmentId === null ? {} : { environmentId: row.environmentId }),
55
56
  latestVersion: row.latestVersion,
56
57
  ...(row.labelsJson === null ? {} : { labels: JSON.parse(row.labelsJson) }),
57
58
  createdAt: row.createdAt,
@@ -194,6 +195,7 @@ export async function uploadArtifact(ctx, input, body, access, signal) {
194
195
  : JSON.stringify(input.labels),
195
196
  createdAt,
196
197
  updatedAt: createdAt,
198
+ environmentId: null,
197
199
  };
198
200
  version = {
199
201
  artifactId,
@@ -278,11 +280,14 @@ export async function folderContent(ctx, id, version, access) {
278
280
  /**
279
281
  * Deletes the artifact and every version: its rows and, for a session's artifact, its
280
282
  * `artifact.deleted` event in one transaction, then its bytes. A folder's content-addressed
281
- * files go too, those no other version names, under the quota lock (`folders.ts`).
283
+ * files go too, those no other version names, under the quota lock (`folders.ts`). An
284
+ * Environment's disk artifact is refused: its own routes delete its versions.
282
285
  */
283
286
  export async function deleteArtifact(ctx, id, access) {
284
287
  const keys = await ctx.store.tx(async (t) => {
285
288
  const found = await readableArtifact(t, id, access);
289
+ if (found.environmentId !== null)
290
+ fail(409, `Artifact ${id} holds Environment ${found.environmentId}'s disk versions: delete them with DELETE /v1/environments/{id}/versions/{version}, or delete the Environment.`);
286
291
  if (found.sessionId !== null)
287
292
  await lockedSession(t, found.sessionId, access);
288
293
  if (found.kind === "folder")
@@ -3,15 +3,15 @@
3
3
  * export its outputs as a folder artifact. Two calls, a listing and one file's bytes, both the
4
4
  * claim of whoever holds the workspace: the Runtime stores what it is given without observing it.
5
5
  *
6
- * One implementation, `sandboxWorkspaceReader`, over the workspace capability (F6.2,
6
+ * One implementation, `sandboxWorkspaceReader`, over the workspace feature (F6.2,
7
7
  * `harness-api/workspace.ts`): the in-process `SandboxManager`, or the harness that serves the
8
8
  * Tenant's workspaces (`workspace.read`). F6.2 reimplements it over the Harness API's `workspace.read`, and F7.2's pod
9
9
  * sandboxes are read through the same seam, so the export never knows which kind of sandbox it
10
10
  * reads.
11
11
  */
12
- import { type AgentManifest } from "@nylorun/core/define";
12
+ import type { AgentManifest } from "@nylorun/core/define";
13
13
  import { type SandboxManager } from "../sandbox/manager.js";
14
- /** What the reader needs of a workspace: a `SandboxManager`, or the workspace capability. */
14
+ /** What the reader needs of a workspace: a `SandboxManager`, or the workspace feature. */
15
15
  export type WorkspaceFiles = Pick<SandboxManager, "listFiles" | "readBytes">;
16
16
  /** Whose workspace to read: the session whose turn ended, resolved to the workspace it uses. */
17
17
  export interface WorkspaceSession {
@@ -22,8 +22,8 @@ export interface WorkspaceSession {
22
22
  /** The session that owns the workspace (one hop through `sandboxOwnerId`). */
23
23
  readonly ownerId: string;
24
24
  /** The sandbox resource the workspace belongs to, when the owner is attached to one. */
25
- readonly sandboxId?: string;
26
- /** The session's pinned manifest: its sandbox capability carries the sandbox's spec. */
25
+ readonly environmentId?: string;
26
+ /** The session's pinned manifest: its `runtime` block carries the sandbox's spec. */
27
27
  readonly manifest: AgentManifest;
28
28
  }
29
29
  /** One regular file under the listed directory. */
@@ -1,30 +1,18 @@
1
- /**
2
- * The `WorkspaceReader` seam (F8.2): how core reads a session's sandbox workspace at turn end, to
3
- * export its outputs as a folder artifact. Two calls, a listing and one file's bytes, both the
4
- * claim of whoever holds the workspace: the Runtime stores what it is given without observing it.
5
- *
6
- * One implementation, `sandboxWorkspaceReader`, over the workspace capability (F6.2,
7
- * `harness-api/workspace.ts`): the in-process `SandboxManager`, or the harness that serves the
8
- * Tenant's workspaces (`workspace.read`). F6.2 reimplements it over the Harness API's `workspace.read`, and F7.2's pod
9
- * sandboxes are read through the same seam, so the export never knows which kind of sandbox it
10
- * reads.
11
- */
12
- import { SANDBOX_CAPABILITY_ID, } from "@nylorun/core/define";
13
- import { sandboxCapabilityOf } from "../sandbox/manager.js";
1
+ import { sandboxToolSpec } from "../sandbox/manager.js";
14
2
  import { SandboxFileTooLargeError } from "../sandbox/types.js";
15
3
  /** The reader over the workspaces, in each sandbox's queue of tool calls. */
16
4
  export function sandboxWorkspaceReader(sandbox) {
17
5
  const target = (session) => {
18
- const capability = sandboxCapabilityOf(session.manifest, SANDBOX_CAPABILITY_ID, "read");
19
- if (!capability)
6
+ const spec = sandboxToolSpec(session.manifest, "read");
7
+ if (!spec)
20
8
  return undefined;
21
9
  return {
22
- capability,
10
+ spec,
23
11
  ref: {
24
12
  id: session.ownerId,
25
13
  activeTurnId: session.turnId,
26
14
  manifest: session.manifest,
27
- ...(session.sandboxId === undefined ? {} : { sandboxId: session.sandboxId }),
15
+ ...(session.environmentId === undefined ? {} : { environmentId: session.environmentId }),
28
16
  },
29
17
  };
30
18
  };
@@ -33,7 +21,7 @@ export function sandboxWorkspaceReader(sandbox) {
33
21
  const found = target(session);
34
22
  if (!found)
35
23
  return undefined;
36
- const listed = await sandbox.listFiles(found.ref, found.capability, dir, options.maxEntries, options.signal);
24
+ const listed = await sandbox.listFiles(found.ref, found.spec, dir, options.maxEntries, options.signal);
37
25
  if (listed.kind === "missing")
38
26
  return undefined;
39
27
  if (listed.kind === "failed")
@@ -44,7 +32,7 @@ export function sandboxWorkspaceReader(sandbox) {
44
32
  const found = target(session);
45
33
  if (!found)
46
34
  return undefined;
47
- const read = await sandbox.readBytes(found.ref, found.capability, path, options.maxBytes, options.signal);
35
+ const read = await sandbox.readBytes(found.ref, found.spec, path, options.maxBytes, options.signal);
48
36
  if (read.kind === "read")
49
37
  return read.bytes;
50
38
  if (read.kind === "missing")
@@ -2,6 +2,7 @@ import type { EffectOutcome } from "@nylorun/core/contracts";
2
2
  import { type JsonValue, type WorkflowManifest } from "@nylorun/core/define";
3
3
  import type { HostEffect } from "@nylorun/harness/run";
4
4
  import type { EffectDoc, Tx } from "../store/types.js";
5
+ import { type TurnTiming } from "../tenant/turn-timing.js";
5
6
  /** Deterministic agent session id: derive(workflowSessionId, path, …parts). */
6
7
  export declare function deriveSessionId(workflowSessionId: string, path: string, ...parts: string[]): string;
7
8
  /**
@@ -15,7 +16,7 @@ export declare function deriveAgentEffectSessionId(workflowSessionId: string, pa
15
16
  readonly context?: Record<string, unknown>;
16
17
  }): string;
17
18
  export declare function isWorkflowManifest(manifest: unknown): manifest is WorkflowManifest;
18
- /** Flow tool-node effect: `tool` with path/key and no agent capabilityId. */
19
+ /** Flow tool-node effect: `tool` with path/key and no tool name (an agent's tool call names one). */
19
20
  export declare function isFlowToolEffect(request: HostEffect): boolean;
20
21
  export declare function isFlowEffect(request: HostEffect): boolean;
21
22
  /**
@@ -59,15 +60,13 @@ export type FlowHostSession = {
59
60
  status: string;
60
61
  activeTurnId: string | null;
61
62
  info?: any;
62
- vaultIds?: readonly string[];
63
- credentialSelections?: readonly unknown[];
64
63
  creation?: unknown;
65
64
  waits?: unknown;
66
65
  lastOutput?: JsonValue;
67
66
  /** The turn `status`, `lastOutput` and `error` describe, once it ended. */
68
67
  lastTurnId?: string;
69
68
  error?: string;
70
- };
69
+ } & TurnTiming;
71
70
  /** How a linked agent turn ended. */
72
71
  export type LinkedTurnEnd = {
73
72
  readonly turnId: string;
@@ -50,6 +50,7 @@
50
50
  */
51
51
  import { createHash } from "node:crypto";
52
52
  import { isVerdict } from "@nylorun/core/define";
53
+ import { resumeTurn } from "../tenant/turn-timing.js";
53
54
  /** Deterministic agent session id: derive(workflowSessionId, path, …parts). */
54
55
  export function deriveSessionId(workflowSessionId, path, ...parts) {
55
56
  const digest = createHash("sha256")
@@ -79,14 +80,14 @@ export function isWorkflowManifest(manifest) {
79
80
  typeof manifest === "object" &&
80
81
  manifest.kind === "workflow");
81
82
  }
82
- /** Flow tool-node effect: `tool` with path/key and no agent capabilityId. */
83
+ /** Flow tool-node effect: `tool` with path/key and no tool name (an agent's tool call names one). */
83
84
  export function isFlowToolEffect(request) {
84
85
  return (request.kind === "tool" &&
85
86
  typeof request.path === "string" &&
86
87
  request.path.length > 0 &&
87
88
  typeof request.key === "string" &&
88
89
  request.key.length > 0 &&
89
- request.capabilityId === undefined);
90
+ request.toolName === undefined);
90
91
  }
91
92
  export function isFlowEffect(request) {
92
93
  return request.kind === "agent" || isFlowToolEffect(request);
@@ -237,6 +238,8 @@ export async function wakeLinkedWorkflow(input) {
237
238
  }
238
239
  : { value: input.output ?? null };
239
240
  await settleAgentEffect({ t, effect, outcome });
241
+ // A workflow paused on its agents' interactions goes on (R7.1, `tenant/turn-timing.ts`).
242
+ resumeTurn(workflow);
240
243
  workflow.status = "runnable";
241
244
  await t.put("sessions", workflow.id, workflow);
242
245
  await t.wake(workflow.id, {
@@ -9,7 +9,8 @@ export declare function gatewayModel(options: {
9
9
  model: string;
10
10
  }): ModelProvider;
11
11
  /**
12
- * Deterministic release fixture; still uses the real engine. With a `lookup_order` tool it looks
13
- * up the demo order through it once; without one it answers in text.
12
+ * The fixture model as a provider (`model: {kind: "fixture"}` in the Runtime's config). The Tenant
13
+ * setting `model.fixture` is answered by the Model Gate instead (R4.3); both answer the same way
14
+ * (`gates/fixture-model.ts`).
14
15
  */
15
16
  export declare function toolFixtureModel(): ModelProvider;
@@ -1,3 +1,4 @@
1
+ import { fixtureAnswer } from "../gates/fixture-model.js";
1
2
  export function scriptedModel(output = "Scripted development response") {
2
3
  return async () => ({ output: [{ type: "text", text: output }] });
3
4
  }
@@ -23,43 +24,10 @@ export function gatewayModel(options) {
23
24
  };
24
25
  }
25
26
  /**
26
- * Deterministic release fixture; still uses the real engine. With a `lookup_order` tool it looks
27
- * up the demo order through it once; without one it answers in text.
27
+ * The fixture model as a provider (`model: {kind: "fixture"}` in the Runtime's config). The Tenant
28
+ * setting `model.fixture` is answered by the Model Gate instead (R4.3); both answer the same way
29
+ * (`gates/fixture-model.ts`).
28
30
  */
29
31
  export function toolFixtureModel() {
30
- return async (effect) => {
31
- const call = effect.input;
32
- const last = call.prompt.at(-1);
33
- if (last?.kind === "tool-result")
34
- return {
35
- output: [
36
- {
37
- type: "text",
38
- text: `Order lookup complete: ${last.content.map((part) => (part.type === "text" ? part.text : JSON.stringify(part))).join(" ")}`,
39
- },
40
- ],
41
- };
42
- const users = call.prompt.filter((p) => p.kind === "message" && p.role === "user");
43
- if (users.length > 1)
44
- return {
45
- output: [
46
- {
47
- type: "text",
48
- text: `I remember: ${users[0].content.map((part) => (part.type === "text" ? part.text : "")).join(" ")}`,
49
- },
50
- ],
51
- };
52
- if (!call.tools.some((tool) => tool.name === "lookup_order"))
53
- return { output: [{ type: "text", text: "Hello from the fixture model." }] };
54
- return {
55
- output: [
56
- {
57
- type: "tool-call",
58
- id: "fixture_lookup",
59
- name: "lookup_order",
60
- args: { orderId: "demo-123" },
61
- },
62
- ],
63
- };
64
- };
32
+ return async (effect, signal) => fixtureAnswer(effect.input, signal);
65
33
  }
@@ -9,32 +9,13 @@ export type SandboxRouteSession = SessionSandboxRef & {
9
9
  readonly activeTurnId: string | null;
10
10
  readonly status: string;
11
11
  };
12
- /** A session together with a synchronous lookup over its sandbox owner chain. */
13
- export type SandboxRouteRead = {
14
- readonly session: SandboxRouteSession;
15
- readonly lookup: (id: string) => SessionSandboxRef | undefined;
16
- };
17
12
  export type SandboxRouteDeps = {
18
13
  readonly sandbox: Pick<WorkspacePort, "run">;
19
- /** The session (rejects when missing) and its owner chain, read in one transaction. */
20
- readonly session: (id: string) => Promise<SandboxRouteRead>;
14
+ /** The session; rejects when it is missing. */
15
+ readonly session: (id: string) => Promise<SandboxRouteSession>;
21
16
  };
22
17
  /**
23
18
  * `POST /v1/sessions/:id/sandbox/:tool` — application principal.
24
19
  * Refused while the session has an active agent turn.
25
20
  */
26
21
  export declare function handleSessionSandboxTool(deps: SandboxRouteDeps, sessionId: string, toolName: string, body: unknown, signal: AbortSignal): Promise<SandboxToolOutcome>;
27
- /**
28
- * Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
29
- * definition declares a sandbox must declare the owner's spec; one whose definition declares
30
- * none inherits it (`inherit`).
31
- */
32
- export declare function validateSandboxAttach(body: {
33
- ownerUserId: string;
34
- sandbox?: unknown;
35
- }, newManifest: SessionSandboxRef["manifest"], lookup: (id: string) => SessionSandboxRef | undefined,
36
- /** Acting for a subject: another owner's session is the 404 of a missing one. */
37
- options?: {
38
- opaque?: boolean;
39
- inherit?: boolean;
40
- }): string | undefined;
@@ -1,9 +1,9 @@
1
1
  /**
2
- * HTTP handlers for the session-scoped sandbox tool route (`POST /v1/sessions/:id/sandbox/:tool`)
3
- * and the sandbox attach check of `PUT /v1/sessions/:id`.
2
+ * HTTP handlers for the session-scoped sandbox tool route (`POST /v1/sessions/:id/sandbox/:tool`):
3
+ * a tool run in the sandbox of the session's Environment.
4
4
  */
5
5
  import { isSandboxToolName, } from "@nylorun/core/define";
6
- import { capabilityForSandbox, owningSandboxSessionId, sandboxSpecOf, sandboxWorkspaceOf, sandboxSpecsEqual, sessionSandboxSpec, } from "../sandbox/share.js";
6
+ import { sandboxWorkspaceOf, sessionSandboxSpec, } from "../sandbox/share.js";
7
7
  export class SandboxRouteError extends Error {
8
8
  status;
9
9
  constructor(status, message) {
@@ -12,10 +12,8 @@ export class SandboxRouteError extends Error {
12
12
  this.name = "SandboxRouteError";
13
13
  }
14
14
  }
15
- function resolveSpec(session, lookup) {
16
- const ownerId = owningSandboxSessionId(session, lookup);
17
- const owner = ownerId === session.id ? session : lookup(ownerId) ?? session;
18
- const spec = sessionSandboxSpec(owner) ?? sessionSandboxSpec(session);
15
+ function resolveSpec(session) {
16
+ const spec = sessionSandboxSpec(session);
19
17
  if (!spec)
20
18
  throw new SandboxRouteError(404, "Session has no sandbox");
21
19
  return spec;
@@ -34,16 +32,16 @@ function toolInput(body) {
34
32
  const { requestId: _r, ...input } = record;
35
33
  return input;
36
34
  }
37
- async function runTool(deps, { session, lookup }, tool, input, signal) {
38
- const spec = resolveSpec(session, lookup);
39
- const workspace = sandboxWorkspaceOf(session, lookup);
35
+ async function runTool(deps, session, tool, input, signal) {
36
+ const spec = resolveSpec(session);
37
+ const workspace = sandboxWorkspaceOf(session);
40
38
  const ref = {
41
39
  id: workspace.ownerId,
42
40
  activeTurnId: session.activeTurnId,
43
41
  manifest: session.manifest,
44
- ...(workspace.sandboxId === undefined ? {} : { sandboxId: workspace.sandboxId }),
42
+ ...(workspace.environmentId === undefined ? {} : { environmentId: workspace.environmentId }),
45
43
  };
46
- return deps.sandbox.run(ref, capabilityForSandbox(spec), tool, input, signal);
44
+ return deps.sandbox.run(ref, spec, tool, input, signal);
47
45
  }
48
46
  /**
49
47
  * `POST /v1/sessions/:id/sandbox/:tool` — application principal.
@@ -51,39 +49,8 @@ async function runTool(deps, { session, lookup }, tool, input, signal) {
51
49
  */
52
50
  export async function handleSessionSandboxTool(deps, sessionId, toolName, body, signal) {
53
51
  const read = await deps.session(sessionId);
54
- if (read.session.activeTurnId)
52
+ if (read.activeTurnId)
55
53
  throw new SandboxRouteError(409, "Sandbox is unavailable while the session has an active agent turn");
56
54
  const tool = parseTool(toolName);
57
55
  return runTool(deps, read, tool, toolInput(body), signal);
58
56
  }
59
- /**
60
- * Validate PutSession.sandbox attach: same owner, and a sandbox to share. A session whose
61
- * definition declares a sandbox must declare the owner's spec; one whose definition declares
62
- * none inherits it (`inherit`).
63
- */
64
- export function validateSandboxAttach(body, newManifest, lookup,
65
- /** Acting for a subject: another owner's session is the 404 of a missing one. */
66
- options = {}) {
67
- const request = body.sandbox;
68
- const share = typeof request === "object" && request !== null && "session" in request
69
- ? String(request.session)
70
- : undefined;
71
- if (!share)
72
- return undefined;
73
- const owner = lookup(share);
74
- if (!owner || (options.opaque && owner.ownerUserId !== body.ownerUserId))
75
- throw new SandboxRouteError(404, "Sandbox session not found");
76
- if (owner.ownerUserId !== body.ownerUserId)
77
- throw new SandboxRouteError(403, "Sandbox session must belong to the same owner");
78
- const ownerSpec = sessionSandboxSpec(owner);
79
- if (!ownerSpec)
80
- throw new SandboxRouteError(400, "Target session has no sandbox to share");
81
- if (options.inherit)
82
- return owningSandboxSessionId(owner, lookup);
83
- const newSpec = sandboxSpecOf(newManifest);
84
- if (!newSpec)
85
- throw new SandboxRouteError(400, "Session must declare a sandbox identical to the shared session");
86
- if (!sandboxSpecsEqual(ownerSpec, newSpec))
87
- throw new SandboxRouteError(409, "Sandbox specs must be identical to share a sandbox");
88
- return owningSandboxSessionId(owner, lookup);
89
- }