@nylorun/runtime 0.24.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 (157) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +10 -2
  3. package/dist/api/a2a/card.d.ts +11 -1
  4. package/dist/api/a2a/card.js +26 -2
  5. package/dist/api/a2a/endpoint.js +4 -2
  6. package/dist/api/a2a/protocol.d.ts +25 -2
  7. package/dist/api/a2a/protocol.js +20 -3
  8. package/dist/api/a2a/routes.d.ts +4 -1
  9. package/dist/api/a2a/routes.js +65 -7
  10. package/dist/api/ag-ui/endpoint.js +5 -2
  11. package/dist/api/ag-ui/history.d.ts +6 -1
  12. package/dist/api/ag-ui/history.js +35 -2
  13. package/dist/api/ag-ui/routes.js +53 -11
  14. package/dist/api/components.d.ts +1165 -678
  15. package/dist/api/components.js +16 -1
  16. package/dist/api/gate/keys.d.ts +2 -1
  17. package/dist/api/gate/keys.js +7 -0
  18. package/dist/api/gate/routes.js +13 -0
  19. package/dist/api/http/app.js +13 -2
  20. package/dist/api/http/body.d.ts +3 -3
  21. package/dist/api/http/body.js +7 -7
  22. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  23. package/dist/api/http/routes/environment-manifests.js +118 -0
  24. package/dist/api/http/routes/environments.js +61 -12
  25. package/dist/api/http/routes/reads.d.ts +52 -3
  26. package/dist/api/http/routes/sessions.js +13 -3
  27. package/dist/api/http/routes/tenant.js +35 -7
  28. package/dist/api/inbound-files.d.ts +65 -0
  29. package/dist/api/inbound-files.js +115 -0
  30. package/dist/api/openapi.js +18 -3
  31. package/dist/artifacts/disk.js +20 -14
  32. package/dist/artifacts/media-types.d.ts +15 -0
  33. package/dist/artifacts/media-types.js +56 -4
  34. package/dist/artifacts/parts.d.ts +12 -3
  35. package/dist/artifacts/parts.js +15 -4
  36. package/dist/artifacts/workspace.d.ts +4 -4
  37. package/dist/artifacts/workspace.js +6 -18
  38. package/dist/core/flow-host.d.ts +1 -1
  39. package/dist/core/flow-host.js +2 -2
  40. package/dist/core/sandbox-routes.js +2 -2
  41. package/dist/core/turn-manifest.d.ts +3 -2
  42. package/dist/core/turn-manifest.js +29 -5
  43. package/dist/gates/egress-credentials.js +1 -1
  44. package/dist/gates/egress.d.ts +13 -19
  45. package/dist/gates/egress.js +30 -29
  46. package/dist/gates/http-tool.d.ts +3 -4
  47. package/dist/gates/http-tool.js +8 -12
  48. package/dist/gates/journal.js +0 -1
  49. package/dist/gates/mcp-handler.js +2 -3
  50. package/dist/gates/policy.js +1 -3
  51. package/dist/gates/tool-contract.d.ts +1 -1
  52. package/dist/harness/calls.d.ts +2 -2
  53. package/dist/harness/calls.js +9 -12
  54. package/dist/harness/main.js +7 -1
  55. package/dist/harness/pod.d.ts +6 -1
  56. package/dist/harness/pod.js +5 -0
  57. package/dist/harness/service.js +5 -5
  58. package/dist/harness/setup.d.ts +2 -0
  59. package/dist/harness/setup.js +25 -1
  60. package/dist/harness-api/record.d.ts +1 -1
  61. package/dist/harness-api/record.js +13 -13
  62. package/dist/harness-api/server.d.ts +27 -4
  63. package/dist/harness-api/server.js +41 -8
  64. package/dist/harness-api/tool-journal.d.ts +17 -8
  65. package/dist/harness-api/tool-journal.js +41 -11
  66. package/dist/harness-api/workspace.d.ts +7 -5
  67. package/dist/harness-api/workspace.js +25 -19
  68. package/dist/host/gates.js +2 -2
  69. package/dist/host/stack-config.js +1 -1
  70. package/dist/keys/keys.d.ts +9 -2
  71. package/dist/keys/keys.js +4 -1
  72. package/dist/management-openapi.json +366 -61
  73. package/dist/mcp/connect.d.ts +1 -2
  74. package/dist/mcp/connect.js +1 -4
  75. package/dist/mcp/pool.d.ts +1 -4
  76. package/dist/mcp/pool.js +13 -27
  77. package/dist/mcp/preview.js +0 -1
  78. package/dist/mcp/snapshot.d.ts +11 -13
  79. package/dist/mcp/snapshot.js +27 -38
  80. package/dist/model/capabilities.d.ts +26 -0
  81. package/dist/model/capabilities.js +44 -0
  82. package/dist/model/catalog.d.ts +34 -3
  83. package/dist/model/catalog.js +38 -2
  84. package/dist/model/models.d.ts +2 -0
  85. package/dist/model/models.js +1 -1
  86. package/dist/model/native-blocks.d.ts +35 -0
  87. package/dist/model/native-blocks.js +170 -0
  88. package/dist/model/pi-model.js +106 -94
  89. package/dist/openapi.json +9554 -5104
  90. package/dist/record/environment.js +1 -1
  91. package/dist/sandbox/join.js +4 -0
  92. package/dist/sandbox/manager.d.ts +32 -14
  93. package/dist/sandbox/manager.js +132 -93
  94. package/dist/sandbox/placement.d.ts +1 -1
  95. package/dist/sandbox/placement.js +2 -2
  96. package/dist/sandbox/pods/client.d.ts +3 -1
  97. package/dist/sandbox/pods/lifecycle.d.ts +8 -0
  98. package/dist/sandbox/pods/lifecycle.js +15 -0
  99. package/dist/sandbox/pods/spec.js +2 -1
  100. package/dist/sandbox/resolve.d.ts +5 -3
  101. package/dist/sandbox/resolve.js +9 -2
  102. package/dist/sandbox/runtime-tools.d.ts +9 -0
  103. package/dist/sandbox/runtime-tools.js +16 -0
  104. package/dist/sandbox/session-sandbox.d.ts +4 -6
  105. package/dist/sandbox/session-sandbox.js +47 -94
  106. package/dist/sandbox/setup.d.ts +4 -1
  107. package/dist/sandbox/setup.js +46 -9
  108. package/dist/sandbox/share.d.ts +12 -7
  109. package/dist/sandbox/share.js +27 -25
  110. package/dist/sandbox/skills.d.ts +8 -6
  111. package/dist/sandbox/skills.js +24 -23
  112. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  113. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  114. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  115. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  116. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  117. package/dist/store/postgres/reads.js +8 -2
  118. package/dist/store/postgres/schema.d.ts +217 -0
  119. package/dist/store/postgres/schema.js +36 -2
  120. package/dist/store/postgres/store.js +79 -4
  121. package/dist/store/types.d.ts +68 -14
  122. package/dist/tenant/advance.js +3 -0
  123. package/dist/tenant/artifact-tool.d.ts +1 -1
  124. package/dist/tenant/artifact-tool.js +43 -22
  125. package/dist/tenant/commands.js +24 -4
  126. package/dist/tenant/context.d.ts +10 -4
  127. package/dist/tenant/effects.js +7 -9
  128. package/dist/tenant/environment-manifests.d.ts +57 -0
  129. package/dist/tenant/environment-manifests.js +200 -0
  130. package/dist/tenant/environments.d.ts +54 -47
  131. package/dist/tenant/environments.js +304 -83
  132. package/dist/tenant/model-setting.d.ts +14 -0
  133. package/dist/tenant/model-setting.js +51 -0
  134. package/dist/tenant/sessions.d.ts +5 -1
  135. package/dist/tenant/sessions.js +45 -12
  136. package/dist/tenant/skill-tool.d.ts +3 -3
  137. package/dist/tenant/skill-tool.js +6 -11
  138. package/dist/tenant/sweep.js +1 -1
  139. package/dist/tenant/tool-results.js +1 -1
  140. package/dist/tenant/tool-search.d.ts +7 -11
  141. package/dist/tenant/tool-search.js +8 -5
  142. package/dist/tenant/transcript.d.ts +51 -32
  143. package/dist/tenant/transcript.js +2 -1
  144. package/dist/traces/ids.d.ts +0 -1
  145. package/dist/traces/ids.js +0 -4
  146. package/dist/traces/map.d.ts +2 -2
  147. package/dist/traces/map.js +1 -29
  148. package/dist/vault/coverage.js +11 -13
  149. package/dist/vault/host-model.d.ts +4 -1
  150. package/dist/vault/host-model.js +10 -2
  151. package/dist/vault/service.d.ts +49 -4
  152. package/dist/vault/service.js +153 -6
  153. package/dist/version.d.ts +1 -1
  154. package/dist/version.js +1 -1
  155. package/package.json +3 -3
  156. package/dist/sandbox/capability.d.ts +0 -3
  157. package/dist/sandbox/capability.js +0 -10
@@ -5,7 +5,7 @@
5
5
  * global registry.
6
6
  */
7
7
  import { z } from "zod";
8
- import { SessionListItemSchema, ModelCallSchema, SessionPageSchema, SessionManifestViewSchema, HistoryPageSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, ModelCallExportPageSchema, EnvironmentPageSchema, ToolCallSchema, ToolCallsPageSchema, TurnTraceSchema, JwksSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, MeResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, ToolPolicySchema, PutToolPolicyRequestSchema, PutTenantEnvironmentsRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, PutEnvironmentSecretRequestSchema, EnvironmentSecretViewSchema, ListEnvironmentSecretsResponseSchema, McpPreviewRequestSchema, McpPreviewSchema, CredentialCoverageRequestSchema, CredentialCoverageSchema, DeletedResponseSchema, ListAgentsResponseSchema, ListPublicAgentsResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutAgentResponseSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, SandboxToolOutcomeSchema, PutEnvironmentRequestSchema, EnvironmentViewSchema, EnvironmentSnapshotSchema, RestoreEnvironmentRequestSchema, EnvironmentRestoreSchema, EnvironmentVersionsSchema, ListEnvironmentsResponseSchema, DeleteEnvironmentResponseSchema, ListEnvironmentEventsResponseSchema, ArtifactViewSchema, ArtifactListItemSchema, ArtifactPageSchema, ArtifactVersionViewSchema, ListArtifactsResponseSchema, UploadArtifactResponseSchema, DefinitionFileViewSchema, DeleteArtifactResponseSchema, CreateArtifactLinkRequestSchema, ArtifactLinkSchema, PutTenantArtifactsRequestSchema, TenantArtifactsViewSchema, ArtifactTreeSchema, ArtifactDiffSchema, SessionCommandSchema, ListOperatorKeysResponseSchema, PutOperatorKeyResponseSchema, DeleteOperatorKeyResponseSchema, OperatorKeySchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
8
+ import { EnvironmentManifestSchema, EnvironmentSecretSlotsSchema, DeleteEnvironmentManifestResponseSchema, EnvironmentManifestPlanSchema, EnvironmentManifestVersionsSchema, EnvironmentManifestViewSchema, ListEnvironmentManifestsResponseSchema, PutEnvironmentManifestRequestSchema, PutEnvironmentSecretValueRequestSchema, SaveEnvironmentManifestResponseSchema, SessionListItemSchema, ModelCallSchema, SessionPageSchema, SessionManifestViewSchema, HistoryPageSchema, SessionUsageTotalsSchema, ModelCallsPageSchema, ModelCallExportPageSchema, EnvironmentPageSchema, ToolCallSchema, ToolCallsPageSchema, TurnTraceSchema, JwksSchema, SigningKeyListSchema, SigningKeyViewSchema, RotateSigningKeysRequestSchema, RevokeSigningKeyRequestSchema, MeResponseSchema, TenantStatusSchema, ResetTenantRequestSchema, ResetTenantResponseSchema, SeedTenantConfigRequestSchema, SeedTenantConfigResponseSchema, HostModelCatalogSchema, TenantSandboxViewSchema, ModelUsageTotalsSchema, ModelBudgetsSchema, PutModelBudgetsRequestSchema, ToolPolicySchema, PutToolPolicyRequestSchema, PutModelInputsRequestSchema, ModelInputsViewSchema, PutTenantEnvironmentsRequestSchema, ListProvidersResponseSchema, HostModelViewSchema, PutHostModelRequestSchema, SelectHostModelRequestSchema, PutEnvironmentSecretRequestSchema, EnvironmentSecretViewSchema, ListEnvironmentSecretsResponseSchema, McpPreviewRequestSchema, McpPreviewSchema, CredentialCoverageRequestSchema, CredentialCoverageSchema, DeletedResponseSchema, ListAgentsResponseSchema, ListPublicAgentsResponseSchema, ListSessionsResponseSchema, LiveEventSchema, PutAgentRequestSchema, PutAgentResponseSchema, PutSessionRequestSchema, SessionItemsResponseSchema, SessionViewSchema, StreamClosedFrameSchema, AcceptedResponseSchema, SandboxToolOutcomeSchema, PutEnvironmentRequestSchema, EnvironmentViewSchema, EnvironmentSnapshotSchema, RestoreEnvironmentRequestSchema, ForkEnvironmentRequestSchema, EnvironmentForkPlanSchema, ForkEnvironmentResponseSchema, EnvironmentRestoreSchema, EnvironmentVersionsSchema, ListEnvironmentsResponseSchema, DeleteEnvironmentResponseSchema, ListEnvironmentEventsResponseSchema, ArtifactViewSchema, ArtifactListItemSchema, ArtifactPageSchema, ArtifactVersionViewSchema, ListArtifactsResponseSchema, UploadArtifactResponseSchema, DefinitionFileViewSchema, DeleteArtifactResponseSchema, CreateArtifactLinkRequestSchema, ArtifactLinkSchema, PutTenantArtifactsRequestSchema, TenantArtifactsViewSchema, ArtifactTreeSchema, ArtifactDiffSchema, SessionCommandSchema, ListOperatorKeysResponseSchema, PutOperatorKeyResponseSchema, DeleteOperatorKeyResponseSchema, OperatorKeySchema, ProtocolRejectedResponseSchema, RejectedResponseSchema, EVENT_SCHEMAS, EVENT_TYPES, } from "@nylorun/core/contracts";
9
9
  function named(id, schema) {
10
10
  return schema.meta({ id });
11
11
  }
@@ -18,9 +18,22 @@ export const DeleteOperatorKeyResponse = named("DeleteOperatorKeyResponse", Dele
18
18
  export const SandboxToolOutcome = named("SandboxToolOutcome", SandboxToolOutcomeSchema);
19
19
  export const PutEnvironmentRequest = named("PutEnvironmentRequest", PutEnvironmentRequestSchema);
20
20
  export const EnvironmentView = named("EnvironmentView", EnvironmentViewSchema);
21
+ export const EnvironmentSecretSlots = named("EnvironmentSecretSlots", EnvironmentSecretSlotsSchema);
22
+ export const EnvironmentManifest = named("EnvironmentManifest", EnvironmentManifestSchema);
23
+ export const PutEnvironmentManifestRequest = named("PutEnvironmentManifestRequest", PutEnvironmentManifestRequestSchema);
24
+ export const EnvironmentManifestView = named("EnvironmentManifestView", EnvironmentManifestViewSchema);
25
+ export const EnvironmentManifestPlan = named("EnvironmentManifestPlan", EnvironmentManifestPlanSchema);
26
+ export const SaveEnvironmentManifestResponse = named("SaveEnvironmentManifestResponse", SaveEnvironmentManifestResponseSchema);
27
+ export const ListEnvironmentManifestsResponse = named("ListEnvironmentManifestsResponse", ListEnvironmentManifestsResponseSchema);
28
+ export const EnvironmentManifestVersions = named("EnvironmentManifestVersions", EnvironmentManifestVersionsSchema);
29
+ export const DeleteEnvironmentManifestResponse = named("DeleteEnvironmentManifestResponse", DeleteEnvironmentManifestResponseSchema);
30
+ export const PutEnvironmentSecretValueRequest = named("PutEnvironmentSecretValueRequest", PutEnvironmentSecretValueRequestSchema);
21
31
  export const EnvironmentSnapshot = named("EnvironmentSnapshot", EnvironmentSnapshotSchema);
22
32
  export const RestoreEnvironmentRequest = named("RestoreEnvironmentRequest", RestoreEnvironmentRequestSchema);
23
33
  export const EnvironmentRestore = named("EnvironmentRestore", EnvironmentRestoreSchema);
34
+ export const ForkEnvironmentRequest = named("ForkEnvironmentRequest", ForkEnvironmentRequestSchema);
35
+ export const EnvironmentForkPlan = named("EnvironmentForkPlan", EnvironmentForkPlanSchema);
36
+ export const ForkEnvironmentResponse = named("ForkEnvironmentResponse", ForkEnvironmentResponseSchema);
24
37
  export const EnvironmentVersions = named("EnvironmentVersions", EnvironmentVersionsSchema);
25
38
  export const ListEnvironmentsResponse = named("ListEnvironmentsResponse", ListEnvironmentsResponseSchema);
26
39
  export const DeleteEnvironmentResponse = named("DeleteEnvironmentResponse", DeleteEnvironmentResponseSchema);
@@ -72,6 +85,8 @@ export const ModelBudgets = named("ModelBudgets", ModelBudgetsSchema);
72
85
  export const PutModelBudgetsRequest = named("PutModelBudgetsRequest", PutModelBudgetsRequestSchema);
73
86
  export const ToolPolicy = named("ToolPolicy", ToolPolicySchema);
74
87
  export const PutToolPolicyRequest = named("PutToolPolicyRequest", PutToolPolicyRequestSchema);
88
+ export const PutModelInputsRequest = named("PutModelInputsRequest", PutModelInputsRequestSchema);
89
+ export const ModelInputsView = named("ModelInputsView", ModelInputsViewSchema);
75
90
  export const PutTenantEnvironmentsRequest = named("PutTenantEnvironmentsRequest", PutTenantEnvironmentsRequestSchema);
76
91
  export const ListProvidersResponse = named("ListProvidersResponse", ListProvidersResponseSchema);
77
92
  export const HostModelView = named("HostModelView", HostModelViewSchema);
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The `keys` service's route on the gateway's listener (F4.2, `contract.ts`): one operation of
3
3
  * `Keys` per request, run with the vault key of the gateway's Tenant. Errors keep their status,
4
- * code and details. Logs the operation and its outcome, never an argument or a result.
4
+ * code and details. Logs the operation, its outcome and an internal failure's cause, never an
5
+ * argument or a result.
5
6
  */
6
7
  import type { Env, Hono, MiddlewareHandler } from "hono";
7
8
  import type { Logger } from "../../tenant/types.js";
@@ -29,6 +29,13 @@ export function mountKeysRoutes(app, options) {
29
29
  }
30
30
  catch (error) {
31
31
  answer = { error: errorOf(error) };
32
+ // The caller sees only "The keys service failed": the cause is logged, never the args.
33
+ if (answer.error.status === 500)
34
+ options.logger.error("keys_request_failed", {
35
+ operation,
36
+ error: error?.message ?? String(error),
37
+ stack: error?.stack,
38
+ });
32
39
  }
33
40
  options.logger.info("keys_request", {
34
41
  operation,
@@ -20,6 +20,7 @@
20
20
  import { createHash } from "node:crypto";
21
21
  import { Hono } from "hono";
22
22
  import { bodyLimit } from "hono/body-limit";
23
+ import { HTTPException } from "hono/http-exception";
23
24
  import { isTenantId } from "@nylorun/core/compatibility";
24
25
  import { MAX_MODEL_CALL_BYTES, MODEL_CALLS_PATH, ModelCallBodySchema, TENANT_HEADER, } from "../../gates/contract.js";
25
26
  import { InflightConflict, InflightStale } from "../../gates/inflight.js";
@@ -38,6 +39,18 @@ export function createGatesApp(options) {
38
39
  const app = new Hono();
39
40
  const maxSize = options.maxBodyBytes ?? MAX_MODEL_CALL_BYTES;
40
41
  const logger = options.logger ?? { info() { }, warn() { }, error() { } };
42
+ // Hono's default, but logged through the logger: never the body, which may hold secrets.
43
+ app.onError((error, c) => {
44
+ if (error instanceof HTTPException)
45
+ return error.getResponse();
46
+ logger.error("request_failed", {
47
+ method: c.req.method,
48
+ route: c.req.routePath,
49
+ error: error.message,
50
+ stack: error.stack,
51
+ });
52
+ return c.text("Internal Server Error", 500);
53
+ });
41
54
  app.get("/health", (c) => c.json({ status: "ok" }));
42
55
  app.get("/ready", async (c) => (await options.ready()) ? c.json({ ready: true }) : c.json({ ready: false }, 503));
43
56
  const bearerOf = (c) => /^Bearer (.+)$/.exec(c.req.header("authorization") ?? "")?.[1];
@@ -13,6 +13,7 @@ import { jsonResponse, rejectionOf } from "./respond.js";
13
13
  import { accessRoutes } from "./routes/access.js";
14
14
  import { meRoutes } from "./routes/me.js";
15
15
  import { environmentRoutes } from "./routes/environments.js";
16
+ import { environmentManifestRoutes } from "./routes/environment-manifests.js";
16
17
  import { artifactRoutes } from "./routes/artifacts.js";
17
18
  import { readRoutes } from "./routes/reads.js";
18
19
  import { sessionRoutes } from "./routes/sessions.js";
@@ -45,6 +46,7 @@ function build() {
45
46
  sessionRoutes(api);
46
47
  fileRoutes(api);
47
48
  environmentRoutes(api);
49
+ environmentManifestRoutes(api);
48
50
  artifactRoutes(api);
49
51
  tenantRoutes(api);
50
52
  keyRoutes(api);
@@ -56,13 +58,22 @@ function build() {
56
58
  a2aRoutes(api);
57
59
  api.notFound(routeNotFound);
58
60
  api.onError((error, c) => {
59
- const { outgoing } = c.env;
61
+ const { outgoing, tenant } = c.env;
62
+ const rejection = rejectionOf(error);
63
+ // The client sees only `internal_error`: the operator's log has the cause. The route's
64
+ // pattern, not its path, and never the body, which may hold secrets.
65
+ if (rejection.status === 500)
66
+ tenant.config.logger.error("request_failed", {
67
+ method: c.req.method,
68
+ route: c.req.routePath,
69
+ error: error.message,
70
+ stack: error.stack,
71
+ });
60
72
  // Once a response has started, the rejection can only end it.
61
73
  if (outgoing.headersSent) {
62
74
  outgoing.end();
63
75
  return RESPONSE_ALREADY_SENT;
64
76
  }
65
- const rejection = rejectionOf(error);
66
77
  return jsonResponse(rejection.status, rejection.body, rejection.headers);
67
78
  });
68
79
  readRoutes(api);
@@ -1,4 +1,4 @@
1
- /** The body as text. */
2
- export declare function readText(request: Request): Promise<string>;
1
+ /** The body as text; past `maxBytes`, `413 Request too large`. */
2
+ export declare function readText(request: Request, maxBytes?: number): Promise<string>;
3
3
  /** The body as JSON, before any schema. */
4
- export declare function readJson(request: Request): Promise<unknown>;
4
+ export declare function readJson(request: Request, maxBytes?: number): Promise<unknown>;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Request bodies of Tenant routes, read as the router they replace read them
3
- * (`tenant/http.ts`): at most 1 MiB, UTF-8, and a missing or malformed JSON body is
4
- * `400 Invalid JSON`.
3
+ * (`tenant/http.ts`): at most 1 MiB unless a route allows more (an endpoint that takes files
4
+ * inline), UTF-8, and a missing or malformed JSON body is `400 Invalid JSON`.
5
5
  */
6
6
  import { fail } from "../../tenant/http.js";
7
7
  const MAX_BODY_BYTES = 1024 * 1024;
8
- /** The body as text. */
9
- export async function readText(request) {
8
+ /** The body as text; past `maxBytes`, `413 Request too large`. */
9
+ export async function readText(request, maxBytes = MAX_BODY_BYTES) {
10
10
  if (request.body === null)
11
11
  return "";
12
12
  const chunks = [];
@@ -17,7 +17,7 @@ export async function readText(request) {
17
17
  if (done)
18
18
  break;
19
19
  bytes += value.byteLength;
20
- if (bytes > MAX_BODY_BYTES) {
20
+ if (bytes > maxBytes) {
21
21
  await reader.cancel();
22
22
  fail(413, "Request too large");
23
23
  }
@@ -26,8 +26,8 @@ export async function readText(request) {
26
26
  return Buffer.concat(chunks).toString("utf8");
27
27
  }
28
28
  /** The body as JSON, before any schema. */
29
- export async function readJson(request) {
30
- const text = await readText(request);
29
+ export async function readJson(request, maxBytes = MAX_BODY_BYTES) {
30
+ const text = await readText(request, maxBytes);
31
31
  try {
32
32
  return JSON.parse(text);
33
33
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Environment manifests (`/v1/environment-manifests/**`, protocol 14, D63): save, read, list and
3
+ * delete the named, versioned definitions Environments are made from. A save never changes an
4
+ * Environment. Reading needs what reading an Environment needs; saving and deleting need
5
+ * `environments:write`, from any client (Q12).
6
+ */
7
+ import type { OpenAPIHono } from "@hono/zod-openapi";
8
+ import type { TenantEnv } from "../app.js";
9
+ export declare function environmentManifestRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import { isEnvironmentManifestName, PutEnvironmentManifestRequestSchema } from "@nylorun/core/contracts";
3
+ import { DeleteEnvironmentManifestResponse, EnvironmentManifestPlan, EnvironmentManifestVersions, EnvironmentManifestView, ListEnvironmentManifestsResponse, PutEnvironmentManifestRequest, SaveEnvironmentManifestResponse, } from "../../components.js";
4
+ import { fail } from "../../../tenant/http.js";
5
+ import { deleteEnvironmentManifest, environmentManifestVersions, getEnvironmentManifest, listEnvironmentManifests, saveEnvironmentManifest, } from "../../../tenant/environment-manifests.js";
6
+ import { readJson } from "../body.js";
7
+ import { tenantRoute } from "../define.js";
8
+ import { jsonResponse } from "../respond.js";
9
+ const READ = {
10
+ credentials: ["application", "subject", "token"],
11
+ scopes: ["sessions:own", "environments:write"],
12
+ };
13
+ const WRITE = {
14
+ credentials: ["application", "subject", "token"],
15
+ scopes: ["environments:write"],
16
+ };
17
+ const json = (schema, description) => ({
18
+ description,
19
+ content: { "application/json": { schema } },
20
+ });
21
+ const namePath = z.object({
22
+ name: z.string().meta({ description: "The environment manifest's name, such as `analyst-box`" }),
23
+ });
24
+ function nameOf(value) {
25
+ if (!isEnvironmentManifestName(value))
26
+ return fail(400, "An environment manifest's name is 1-64 letters, digits, '.', '_' and '-', starting with a letter or digit");
27
+ return value;
28
+ }
29
+ function versionOf(value) {
30
+ const version = Number(value);
31
+ if (!Number.isInteger(version) || version < 1)
32
+ return fail(400, "version is a positive integer");
33
+ return version;
34
+ }
35
+ /** `If-Match: <version>` (or `"<version>"`), when sent. */
36
+ function ifMatchOf(value) {
37
+ if (value === undefined || value.trim() === "")
38
+ return undefined;
39
+ const version = Number(value.trim().replace(/^W\//, "").replace(/^"|"$/g, ""));
40
+ if (!Number.isInteger(version) || version < 0)
41
+ return fail(400, "If-Match is the version you read: a whole number, 0 for none yet");
42
+ return version;
43
+ }
44
+ export function environmentManifestRoutes(api) {
45
+ tenantRoute(api, READ, {
46
+ method: "get",
47
+ path: "/v1/environment-manifests",
48
+ tags: ["Environment manifests"],
49
+ summary: "List environment manifests",
50
+ description: "Each manifest's latest version, by name, with how many Environments were made from it.",
51
+ responses: { 200: json(ListEnvironmentManifestsResponse, "The manifests") },
52
+ }, async (c) => jsonResponse(200, await listEnvironmentManifests(c.env.tenant)));
53
+ tenantRoute(api, WRITE, {
54
+ method: "put",
55
+ path: "/v1/environment-manifests/{name}",
56
+ tags: ["Environment manifests"],
57
+ summary: "Save an environment manifest",
58
+ description: "Saves the manifest as its next version, or answers the latest unchanged when nothing differs. No Environment changes: an Environment keeps the version it was made from, and a fork moves it to another (D63). `If-Match: <version>` refuses the save with `412 manifest_changed` when the latest version is no longer the one you read (`0` for a manifest that does not exist yet). With `?dryRun=true` the answer is the plan: the changes, every reason a save would be refused (`errors`), warnings, and how many Environments stay on older versions; nothing is saved. Clients drop a file's `$schema` before they send it.",
59
+ request: {
60
+ params: namePath,
61
+ query: z.object({
62
+ dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: answer the plan and save nothing" }),
63
+ }),
64
+ body: { required: true, content: { "application/json": { schema: PutEnvironmentManifestRequest } } },
65
+ },
66
+ responses: {
67
+ 200: json(z.union([SaveEnvironmentManifestResponse, EnvironmentManifestPlan]), "The version saved, or the plan with dryRun"),
68
+ 400: { description: "The manifest breaks the Tenant's limits or names files the Runtime cannot use" },
69
+ 412: { description: "If-Match named a version that is no longer the latest (`manifest_changed`)" },
70
+ },
71
+ }, async (c) => {
72
+ const name = nameOf(c.req.param("name"));
73
+ const body = PutEnvironmentManifestRequestSchema.parse(await readJson(c.req.raw));
74
+ const ifMatch = ifMatchOf(c.req.header("if-match"));
75
+ const options = ifMatch === undefined ? {} : { ifMatch };
76
+ return jsonResponse(200, c.req.query("dryRun") === "true"
77
+ ? await saveEnvironmentManifest(c.env.tenant, name, body, c.get("scope"), { ...options, dryRun: true })
78
+ : await saveEnvironmentManifest(c.env.tenant, name, body, c.get("scope"), options));
79
+ });
80
+ tenantRoute(api, READ, {
81
+ method: "get",
82
+ path: "/v1/environment-manifests/{name}",
83
+ tags: ["Environment manifests"],
84
+ summary: "Get an environment manifest",
85
+ description: "Its latest version.",
86
+ request: { params: namePath },
87
+ responses: { 200: json(EnvironmentManifestView, "The latest version") },
88
+ }, async (c) => jsonResponse(200, await getEnvironmentManifest(c.env.tenant, nameOf(c.req.param("name")))));
89
+ tenantRoute(api, READ, {
90
+ method: "get",
91
+ path: "/v1/environment-manifests/{name}/versions",
92
+ tags: ["Environment manifests"],
93
+ summary: "List an environment manifest's versions",
94
+ description: "Every version, newest first, with who saved it and how many Environments were made from it.",
95
+ request: { params: namePath },
96
+ responses: { 200: json(EnvironmentManifestVersions, "The versions") },
97
+ }, async (c) => jsonResponse(200, await environmentManifestVersions(c.env.tenant, nameOf(c.req.param("name")))));
98
+ tenantRoute(api, READ, {
99
+ method: "get",
100
+ path: "/v1/environment-manifests/{name}/versions/{version}",
101
+ tags: ["Environment manifests"],
102
+ summary: "Get one version of an environment manifest",
103
+ request: { params: namePath.extend({ version: z.string().meta({ description: "The version number" }) }) },
104
+ responses: { 200: json(EnvironmentManifestView, "The version") },
105
+ }, async (c) => jsonResponse(200, await getEnvironmentManifest(c.env.tenant, nameOf(c.req.param("name")), versionOf(c.req.param("version")))));
106
+ tenantRoute(api, WRITE, {
107
+ method: "delete",
108
+ path: "/v1/environment-manifests/{name}",
109
+ tags: ["Environment manifests"],
110
+ summary: "Delete an environment manifest",
111
+ description: "Deletes every version. Refused while any Environment was made from one (`409 manifest_in_use`).",
112
+ request: { params: namePath },
113
+ responses: {
114
+ 200: json(DeleteEnvironmentManifestResponse, "Deleted, or there was none"),
115
+ 409: { description: "An Environment was made from one of its versions (`manifest_in_use`)" },
116
+ },
117
+ }, async (c) => jsonResponse(200, await deleteEnvironmentManifest(c.env.tenant, nameOf(c.req.param("name")))));
118
+ }
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
- import { isEnvironmentId, PutEnvironmentRequestSchema, PutEnvironmentSecretRequestSchema, RestoreEnvironmentRequestSchema, } from "@nylorun/core/contracts";
2
+ import { ENVIRONMENT_EVENT_TYPES, ForkEnvironmentRequestSchema, isEnvironmentId, PutEnvironmentRequestSchema, RestoreEnvironmentRequestSchema, } from "@nylorun/core/contracts";
3
3
  import { deleteEnvironmentVersion, environmentVersions, restoreEnvironment, snapshotEnvironment, } from "../../../artifacts/disk.js";
4
- import { DeleteEnvironmentResponse, DeletedResponse, EnvironmentPage, EnvironmentRestore, EnvironmentSnapshot, EnvironmentVersions, RestoreEnvironmentRequest, EnvironmentSecretView, EnvironmentView, ListEnvironmentEventsResponse, ListEnvironmentSecretsResponse, ListEnvironmentsResponse, PutEnvironmentRequest, PutEnvironmentSecretRequest, } from "../../components.js";
4
+ import { DeleteEnvironmentResponse, DeletedResponse, EnvironmentPage, EnvironmentRestore, EnvironmentSnapshot, EnvironmentVersions, ForkEnvironmentRequest, ForkEnvironmentResponse, RestoreEnvironmentRequest, EnvironmentSecretView, EnvironmentView, ListEnvironmentEventsResponse, ListEnvironmentSecretsResponse, ListEnvironmentsResponse, PutEnvironmentRequest, PutEnvironmentSecretRequest, PutEnvironmentSecretValueRequest, } from "../../components.js";
5
5
  import { fail } from "../../../tenant/http.js";
6
- import { actorOf, assertReaches, deleteEnvironment, environmentEventsOf, getEnvironment, listEnvironments, putEnvironment, resetEnvironment, stopEnvironment, } from "../../../tenant/environments.js";
6
+ import { actorOf, assertReaches, deleteEnvironment, environmentEventsOf, forkEnvironment, getEnvironment, listEnvironments, putEnvironment, resetEnvironment, secretRequestFor, stopEnvironment, } from "../../../tenant/environments.js";
7
7
  import { readJson } from "../body.js";
8
8
  import { tenantRoute } from "../define.js";
9
9
  import { environmentPage, pageQuery } from "./reads.js";
@@ -75,7 +75,7 @@ export function environmentRoutes(api) {
75
75
  path: "/v1/environments/{environmentId}",
76
76
  tags: ["Environments"],
77
77
  summary: "Create, find or change an Environment",
78
- description: "Creates the Environment and its vault within the Tenant's limits, or answers the one with this id, so get-or-create is one call. On an existing one the fields sent are compared: `kind` and `storage` are fixed (`409`), labels replace, `ownerUserId` sets or (with `null`) clears the owner guard, and any other change writes the recipe and bumps `version`. A pod's image and resources apply when it is next started from stopped (`appliedVersion`); `network.allowedHosts`, `harness` and `lifecycle` apply at once. Kind `pod` needs sandbox pods (`nylorun sandbox enable`) and is created at once.",
78
+ description: "Creates the Environment and its vault within the Tenant's limits, or answers the one with this id, so get-or-create is one call. It is made from one version of an environment manifest (`manifest: \"name\"` takes the latest, `\"name@n\"` version n) or from the inline fields, and never changes what it runs after that (protocol 14, D63): on an existing one, labels replace, `ownerUserId` sets or (with `null`) clears the owner guard, fields that match what it runs are accepted, and anything else is `409 environment_immutable`, which names the fork that makes a new Environment instead. Kind `pod` needs sandbox pods (`nylorun local sandbox enable`) and is created at once.",
79
79
  request: {
80
80
  params: environmentId,
81
81
  body: { required: true, content: { "application/json": { schema: PutEnvironmentRequest } } },
@@ -83,7 +83,7 @@ export function environmentRoutes(api) {
83
83
  responses: {
84
84
  200: json(EnvironmentView, "The Environment"),
85
85
  409: {
86
- description: "An Environment with this id has another kind or storage, the Tenant holds as many Environments as it allows (`limit_exceeded`), kind pod without sandbox pods (`environment_unavailable`), or it is being deleted (`environment_busy`)",
86
+ description: "An Environment with this id runs something else (`environment_immutable`), the Tenant holds as many Environments as it allows (`limit_exceeded`), kind pod without sandbox pods (`environment_unavailable`), or it is being deleted (`environment_busy`)",
87
87
  },
88
88
  },
89
89
  }, async (c) => {
@@ -104,12 +104,15 @@ export function environmentRoutes(api) {
104
104
  method: "get",
105
105
  path: "/v1/environments/{environmentId}/events",
106
106
  tags: ["Environments"],
107
- summary: "Read an Environment's lifecycle events",
108
- description: "`environment.created`, `.attached`, `.detached`, `.deleted` and a pod's `.running`, `.suspended`, `.relaunched`, `.lost`, `.reset` and `.failed`, in order, numbered from 0.",
107
+ summary: "Read an Environment's events",
108
+ description: "`environment.created`, `.attached`, `.detached`, `.deleted`, a pod's `.running`, `.suspended`, `.relaunched`, `.lost`, `.reset` and `.failed`, and `environment.egress` for every connection its sandbox opens through egress-gate, naming the turns that were running (D67), in order, numbered from 0, at most 1000 per call.",
109
109
  request: {
110
110
  params: environmentId,
111
111
  query: z.object({
112
112
  from: z.string().optional().meta({ description: "The first `seq` to return" }),
113
+ types: z.string().optional().meta({ description: "Only these event types, comma-separated (`environment.egress`)" }),
114
+ since: z.string().optional().meta({ description: "Only events recorded at or after this ISO time" }),
115
+ until: z.string().optional().meta({ description: "Only events recorded before this ISO time" }),
113
116
  }),
114
117
  },
115
118
  responses: { 200: json(ListEnvironmentEventsResponse, "The events") },
@@ -117,7 +120,27 @@ export function environmentRoutes(api) {
117
120
  const from = c.req.query("from");
118
121
  if (from !== undefined && !/^\d+$/.test(from))
119
122
  fail(400, "from must be a seq");
120
- return jsonResponse(200, await environmentEventsOf(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"), from === undefined ? undefined : Number(from)));
123
+ const types = c.req.query("types")?.split(",").map((type) => type.trim()).filter(Boolean);
124
+ const unknown = types?.find((type) => !ENVIRONMENT_EVENT_TYPES.includes(type));
125
+ if (unknown !== undefined)
126
+ fail(400, `types: ${unknown} is not an Environment event type`);
127
+ const time = (name) => {
128
+ const value = c.req.query(name);
129
+ if (value === undefined)
130
+ return undefined;
131
+ const at = Date.parse(value);
132
+ if (!Number.isFinite(at))
133
+ fail(400, `${name} must be an ISO time`);
134
+ return new Date(at).toISOString();
135
+ };
136
+ const since = time("since");
137
+ const until = time("until");
138
+ return jsonResponse(200, await environmentEventsOf(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"), {
139
+ ...(from === undefined ? {} : { fromSeq: Number(from) }),
140
+ ...(types === undefined || types.length === 0 ? {} : { types }),
141
+ ...(since === undefined ? {} : { since }),
142
+ ...(until === undefined ? {} : { until }),
143
+ }));
121
144
  });
122
145
  tenantRoute(api, WRITE, {
123
146
  method: "delete",
@@ -193,6 +216,28 @@ export function environmentRoutes(api) {
193
216
  const body = RestoreEnvironmentRequestSchema.parse(await readJson(c.req.raw));
194
217
  return jsonResponse(200, await restoreEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), c.get("scope"), body.version));
195
218
  });
219
+ tenantRoute(api, WRITE, {
220
+ method: "post",
221
+ path: "/v1/environments/{environmentId}/fork",
222
+ tags: ["Environments"],
223
+ summary: "Fork an Environment",
224
+ description: "How an Environment gets a new manifest version (Q15): a new Environment `id`, made from `manifest` (default the source's manifest at its latest version), its disk started from a version of this one's (`disk`: `latest`, the default, takes a snapshot first; a version number; or `empty`) and its secrets copied inside the Runtime (`secrets`: `copy`, the default, copies those the new manifest declares; `none`). On a fork onto other setup the new manifest's setup runs on the copied disk before its first turn. This Environment keeps what it runs, its disk and its sessions. `dryRun` answers the plan and makes nothing.",
225
+ request: {
226
+ params: environmentId,
227
+ body: { required: true, content: { "application/json": { schema: ForkEnvironmentRequest } } },
228
+ },
229
+ responses: {
230
+ 200: json(ForkEnvironmentResponse, "The plan (`dryRun`)"),
231
+ 201: json(ForkEnvironmentResponse, "The new Environment, and the plan it ran"),
232
+ 400: { description: "The source was made from inline fields and no manifest is named, or the manifest does not resolve" },
233
+ 404: { description: "No such Environment, manifest version (`manifest_not_found`) or disk version" },
234
+ 409: { description: "The id exists, or a turn or another copy holds the source's disk (`environment_busy`)" },
235
+ },
236
+ }, async (c) => {
237
+ const body = ForkEnvironmentRequestSchema.parse(await readJson(c.req.raw));
238
+ const answer = await forkEnvironment(c.env.tenant, idOf(c.req.param("environmentId")), body, c.get("scope"));
239
+ return jsonResponse("dryRun" in answer ? 200 : 201, answer);
240
+ });
196
241
  tenantRoute(api, READ, {
197
242
  method: "get",
198
243
  path: "/v1/environments/{environmentId}/versions",
@@ -250,10 +295,13 @@ export function environmentRoutes(api) {
250
295
  path: "/v1/environments/{environmentId}/secrets/{name}",
251
296
  tags: ["Environments"],
252
297
  summary: "Write an Environment's secret",
253
- description: "Creates or replaces one credential of the Environment's vault: an `environment_secret` (the variable `name` reads the sentinel `nylorun-managed` in the sandbox, and egress-gate sets the value as a header on requests to `allowedHosts`), an `environment_variable` (a plain variable), or a `bearer` token or `headers` map for the MCP servers and HTTP tools at `url`. The same name and type re-seals the value and binding; another type replaces it. A rotated value is used from the next request. A second credential for a host or a URL is `409 credential_conflict`.",
298
+ description: "Creates or replaces one credential of the Environment's vault: an `environment_secret` (the variable `name` reads the sentinel `nylorun-managed` in the sandbox, and egress-gate sets the value as a header on requests to `allowedHosts`), an `environment_variable` (a plain variable), or a `bearer` token or `headers` map for the MCP servers and HTTP tools at `url`. The same name and type re-seals the value and binding; another type replaces it. A rotated value is used from the next request. A second credential for a host or a URL is `409 credential_conflict`. When what the Environment runs declares secret slots (protocol 14, D64), send only the value (`{ value }`, or `{ headers }` for a `headers` slot): the type and binding come from the slot, and a name it does not declare is `409 secret_not_in_manifest`.",
254
299
  request: {
255
300
  params: secretPath,
256
- body: { required: true, content: { "application/json": { schema: PutEnvironmentSecretRequest } } },
301
+ body: {
302
+ required: true,
303
+ content: { "application/json": { schema: z.union([PutEnvironmentSecretRequest, PutEnvironmentSecretValueRequest]) } },
304
+ },
257
305
  },
258
306
  responses: {
259
307
  200: json(EnvironmentSecretView, "The secret, without its value"),
@@ -263,8 +311,9 @@ export function environmentRoutes(api) {
263
311
  const id = idOf(c.req.param("environmentId"));
264
312
  const scope = c.get("scope");
265
313
  assertReaches(scope, id);
266
- const body = PutEnvironmentSecretRequestSchema.parse(await readJson(c.req.raw));
267
- return jsonResponse(200, await c.env.tenant.keys.putEnvironmentSecret(id, c.req.param("name") ?? "", body, actorOf(scope)));
314
+ const name = c.req.param("name") ?? "";
315
+ const body = await secretRequestFor(c.env.tenant, id, name, await readJson(c.req.raw));
316
+ return jsonResponse(200, await c.env.tenant.keys.putEnvironmentSecret(id, name, body, actorOf(scope)));
268
317
  });
269
318
  tenantRoute(api, WRITE, {
270
319
  method: "delete",
@@ -44,7 +44,7 @@ export declare function environmentPage(ctx: TenantEnv["Bindings"]["tenant"], sc
44
44
  id: string;
45
45
  kind: "none" | "pod" | "virtual";
46
46
  labels: Record<string, string>;
47
- recipe: {
47
+ manifest: {
48
48
  image?: string | undefined;
49
49
  resources?: {
50
50
  cpus: number;
@@ -64,12 +64,60 @@ export declare function environmentPage(ctx: TenantEnv["Bindings"]["tenant"], sc
64
64
  cwd?: string | undefined;
65
65
  timeout?: string | undefined;
66
66
  }[] | undefined;
67
- files?: {
67
+ files?: ({
68
68
  artifactId: string;
69
69
  version: number;
70
70
  path: string;
71
- }[] | undefined;
71
+ } | {
72
+ path: string;
73
+ files: Record<string, string>;
74
+ })[] | undefined;
75
+ secrets?: Record<string, {
76
+ required?: boolean | undefined;
77
+ description?: string | undefined;
78
+ type: "environment_secret";
79
+ allowedHosts: string[];
80
+ inject?: {
81
+ header: string;
82
+ format: string;
83
+ } | undefined;
84
+ } | {
85
+ required?: boolean | undefined;
86
+ description?: string | undefined;
87
+ type: "environment_variable";
88
+ } | {
89
+ via?: string | undefined;
90
+ identity?: {
91
+ header: string;
92
+ } | undefined;
93
+ required?: boolean | undefined;
94
+ description?: string | undefined;
95
+ type: "bearer";
96
+ url: string;
97
+ } | {
98
+ via?: string | undefined;
99
+ identity?: {
100
+ header: string;
101
+ } | undefined;
102
+ required?: boolean | undefined;
103
+ description?: string | undefined;
104
+ type: "headers";
105
+ url: string;
106
+ }> | undefined;
72
107
  };
108
+ manifestName?: string | undefined;
109
+ manifestVersion?: number | undefined;
110
+ manifestHash?: string | undefined;
111
+ forkedFrom?: {
112
+ environment: string;
113
+ disk: number | null;
114
+ } | undefined;
115
+ slots?: {
116
+ name: string;
117
+ type: string;
118
+ required: boolean;
119
+ set: boolean;
120
+ }[] | undefined;
73
121
  version: number;
74
122
  appliedVersion: number;
75
123
  ownerUserId: string | null;
@@ -80,6 +128,7 @@ export declare function environmentPage(ctx: TenantEnv["Bindings"]["tenant"], sc
80
128
  volumeGeneration: number;
81
129
  hostEpoch: number;
82
130
  reason?: string | undefined;
131
+ imageDigest?: string | undefined;
83
132
  } | undefined;
84
133
  lastUsedAt: string;
85
134
  disk?: {
@@ -57,6 +57,9 @@ export function sessionRoutes(api) {
57
57
  responses: {
58
58
  200: json(AcceptedResponse, "The command, accepted"),
59
59
  409: { description: "A turn is running, or the session does not wait on this" },
60
+ 422: {
61
+ description: "A message names a file the Tenant's model does not read (`attachment_unsupported`; `details.parts` lists each with its reason). No turn starts.",
62
+ },
60
63
  },
61
64
  }, async (c) => {
62
65
  // A `traceparent` joins the turn to the caller's trace (R7.4); a malformed one is ignored.
@@ -87,10 +90,17 @@ export function sessionRoutes(api) {
87
90
  path: "/v1/agents/{agentId}",
88
91
  tags: ["Agents"],
89
92
  summary: "Put an agent's definition",
90
- request: { params: z.object({ agentId: z.string() }), body: body(PutAgentRequest) },
91
- responses: { 200: json(PutAgentResponse, "The definition, stored") },
93
+ description: "Saves the agent's definition. With `?dryRun=true` (protocol 14) every check runs and nothing is saved: the answer says what a save would record.",
94
+ request: {
95
+ params: z.object({ agentId: z.string() }),
96
+ query: z.object({
97
+ dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: check and save nothing" }),
98
+ }),
99
+ body: body(PutAgentRequest),
100
+ },
101
+ responses: { 200: json(PutAgentResponse, "The definition, stored (or checked, with dryRun)") },
92
102
  }, async (c) => {
93
- return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw))));
103
+ return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw)), { dryRun: c.req.query("dryRun") === "true" }));
94
104
  });
95
105
  tenantRoute(api, OWN_SESSIONS, {
96
106
  method: "get",