@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
@@ -202,7 +202,6 @@ export declare function serverInstructions(text: unknown, secrets: readonly stri
202
202
  * collides gets the hash suffix first.
203
203
  */
204
204
  export declare function listMcpTools(client: McpClient, input: {
205
- capabilityId: string;
206
205
  serverName: string;
207
206
  taken: Set<string>;
208
207
  signal?: AbortSignal;
@@ -224,4 +223,4 @@ export declare function listMcpTools(client: McpClient, input: {
224
223
  * unknown.
225
224
  */
226
225
  export declare function callMcpTool(client: McpClient, serverToolName: string, args: unknown, options?: McpCallOptions): Promise<McpToolOutcome>;
227
- export declare function diagnosticFromError(capabilityId: string, serverName: string, error: unknown): McpDiagnostic;
226
+ export declare function diagnosticFromError(serverName: string, error: unknown): McpDiagnostic;
@@ -285,7 +285,6 @@ export async function listMcpTools(client, input) {
285
285
  const { tool, inputSchema } = candidate;
286
286
  const outputSchema = tool.outputSchema ? usableSchema(tool.outputSchema) : undefined;
287
287
  tools.push({
288
- capabilityId: input.capabilityId,
289
288
  serverName: input.serverName,
290
289
  serverToolName: tool.name,
291
290
  name,
@@ -353,14 +352,13 @@ function onlyText(content) {
353
352
  return (Array.isArray(content) &&
354
353
  content.every((part) => !!part && typeof part === "object" && part.type === "text"));
355
354
  }
356
- export function diagnosticFromError(capabilityId, serverName, error) {
355
+ export function diagnosticFromError(serverName, error) {
357
356
  const credentialIds = error && typeof error === "object" && "credentialIds" in error
358
357
  ? error.credentialIds
359
358
  : undefined;
360
359
  const refused = error instanceof Error && error.message.startsWith("MCP authorization refused");
361
360
  if (refused) {
362
361
  return {
363
- capabilityId,
364
362
  serverName,
365
363
  outcome: "refused",
366
364
  message: error.message,
@@ -374,7 +372,6 @@ export function diagnosticFromError(capabilityId, serverName, error) {
374
372
  code === 401 ||
375
373
  (error instanceof Error && /\b401\b/.test(error.message));
376
374
  return {
377
- capabilityId,
378
375
  serverName,
379
376
  outcome: "failed",
380
377
  message: unauthorized
@@ -51,7 +51,6 @@ export declare class McpPool {
51
51
  call(input: {
52
52
  sessionId: string;
53
53
  agentId?: string;
54
- capabilityId: string;
55
54
  serverName: string;
56
55
  serverToolName: string;
57
56
  args: unknown;
@@ -85,7 +84,6 @@ export interface McpServerRef {
85
84
  readonly sessionId: string;
86
85
  /** The agent used as a tool that declares the server; absent for the session's root agent. */
87
86
  readonly agentId?: string;
88
- readonly capabilityId: string;
89
87
  readonly serverName: string;
90
88
  }
91
89
  export interface DeclaredServer {
@@ -93,9 +91,8 @@ export interface DeclaredServer {
93
91
  readonly agentId?: string;
94
92
  /** The manifest of the agent that declares the server. */
95
93
  readonly manifest: AgentManifest;
96
- readonly capabilityId: string;
97
94
  readonly server: McpServerManifest;
98
95
  }
99
96
  /** Servers of the root agent, then of each agent it uses as a tool. */
100
97
  export declare function serversOf(manifest: AgentManifest): DeclaredServer[];
101
- export declare function findServer(manifest: AgentManifest, agentId: string | undefined, capabilityId: string, serverName: string): DeclaredServer | undefined;
98
+ export declare function findServer(manifest: AgentManifest, agentId: string | undefined, serverName: string): DeclaredServer | undefined;
package/dist/mcp/pool.js CHANGED
@@ -34,7 +34,6 @@ export class McpPool {
34
34
  if (!taken.has(declared.agentId))
35
35
  taken.set(declared.agentId, declaredToolNames(declared.manifest));
36
36
  const listed = await listMcpTools(opened.connection.client, {
37
- capabilityId: declared.capabilityId,
38
37
  serverName: declared.server.name,
39
38
  taken: taken.get(declared.agentId),
40
39
  server: declared.server,
@@ -43,13 +42,11 @@ export class McpPool {
43
42
  tools.push(...listed.tools.map((tool) => owned(declared, tool)));
44
43
  if (opened.connection.instructions)
45
44
  notes.push(owned(declared, {
46
- capabilityId: declared.capabilityId,
47
45
  serverName: declared.server.name,
48
46
  instructions: opened.connection.instructions,
49
47
  }));
50
48
  diagnostics.push({
51
49
  ...ownerOf(declared),
52
- capabilityId: declared.capabilityId,
53
50
  serverName: declared.server.name,
54
51
  outcome: "connected",
55
52
  message: [
@@ -67,7 +64,7 @@ export class McpPool {
67
64
  }
68
65
  catch (error) {
69
66
  await opened.connection.close().catch(() => { });
70
- diagnostics.push(owned(declared, diagnosticFromError(declared.capabilityId, declared.server.name, error)));
67
+ diagnostics.push(owned(declared, diagnosticFromError(declared.server.name, error)));
71
68
  }
72
69
  }
73
70
  return {
@@ -84,7 +81,7 @@ export class McpPool {
84
81
  const diagnostics = [];
85
82
  const seen = new Set();
86
83
  for (const tool of input.tools) {
87
- const key = this.liveKey(input.sessionId, tool.agentId, tool.capabilityId, tool.serverName);
84
+ const key = this.liveKey(input.sessionId, tool.agentId, tool.serverName);
88
85
  if (seen.has(key))
89
86
  continue;
90
87
  seen.add(key);
@@ -95,11 +92,10 @@ export class McpPool {
95
92
  continue;
96
93
  }
97
94
  input.signal?.throwIfAborted();
98
- const declared = findServer(input.manifest, tool.agentId, tool.capabilityId, tool.serverName);
95
+ const declared = findServer(input.manifest, tool.agentId, tool.serverName);
99
96
  if (!declared) {
100
97
  diagnostics.push({
101
98
  ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
102
- capabilityId: tool.capabilityId,
103
99
  serverName: tool.serverName,
104
100
  outcome: "failed",
105
101
  message: "Declared MCP server is missing from the pinned manifest",
@@ -116,7 +112,7 @@ export class McpPool {
116
112
  return diagnostics;
117
113
  }
118
114
  async call(input) {
119
- const key = this.liveKey(input.sessionId, input.agentId, input.capabilityId, input.serverName);
115
+ const key = this.liveKey(input.sessionId, input.agentId, input.serverName);
120
116
  for (let attempt = 0;; attempt += 1) {
121
117
  const live = await this.liveFor(input, key);
122
118
  // No connection, so no call was sent: the model sees why, a 401 included (R2b C1, C7).
@@ -152,7 +148,7 @@ export class McpPool {
152
148
  const live = this.live.get(key);
153
149
  if (live)
154
150
  return live;
155
- const declared = findServer(input.manifest, input.agentId, input.capabilityId, input.serverName);
151
+ const declared = findServer(input.manifest, input.agentId, input.serverName);
156
152
  if (!declared)
157
153
  throw new Error(`MCP server '${input.serverName}' is not declared`);
158
154
  let opening = this.opening.get(key);
@@ -196,7 +192,7 @@ export class McpPool {
196
192
  }
197
193
  /** Keeps the connection, or closes it when the server is already connected for the session. */
198
194
  remember(sessionId, declared, connection) {
199
- const key = this.liveKey(sessionId, declared.agentId, declared.capabilityId, declared.server.name);
195
+ const key = this.liveKey(sessionId, declared.agentId, declared.server.name);
200
196
  const existing = this.live.get(key);
201
197
  if (existing) {
202
198
  if (existing.connection !== connection)
@@ -204,7 +200,6 @@ export class McpPool {
204
200
  return existing;
205
201
  }
206
202
  const live = {
207
- capabilityId: declared.capabilityId,
208
203
  serverName: declared.server.name,
209
204
  connection,
210
205
  lastUsedAt: this.now(),
@@ -213,8 +208,8 @@ export class McpPool {
213
208
  this.live.set(key, live);
214
209
  return live;
215
210
  }
216
- liveKey(sessionId, agentId, capabilityId, serverName) {
217
- return JSON.stringify([sessionId, agentId ?? null, capabilityId, serverName]);
211
+ liveKey(sessionId, agentId, serverName) {
212
+ return JSON.stringify([sessionId, agentId ?? null, serverName]);
218
213
  }
219
214
  async connectDeclared(input, declared) {
220
215
  try {
@@ -222,7 +217,6 @@ export class McpPool {
222
217
  const connection = await this.options.openRemote({
223
218
  sessionId: input.sessionId,
224
219
  ...ownerOf(declared),
225
- capabilityId: declared.capabilityId,
226
220
  serverName: declared.server.name,
227
221
  });
228
222
  return { ok: true, connection };
@@ -242,7 +236,7 @@ export class McpPool {
242
236
  return {
243
237
  ok: false,
244
238
  error,
245
- diagnostic: owned(declared, diagnosticFromError(declared.capabilityId, declared.server.name, error)),
239
+ diagnostic: owned(declared, diagnosticFromError(declared.server.name, error)),
246
240
  };
247
241
  }
248
242
  }
@@ -251,24 +245,16 @@ export class McpPool {
251
245
  export function serversOf(manifest) {
252
246
  const servers = [];
253
247
  const add = (agent, agentId) => {
254
- for (const capability of agent.capabilities)
255
- for (const server of Object.values(capability.mcpServers ?? {}))
256
- servers.push({
257
- ...(agentId === undefined ? {} : { agentId }),
258
- manifest: agent,
259
- capabilityId: capability.id,
260
- server,
261
- });
248
+ for (const server of Object.values(agent.mcpServers ?? {}))
249
+ servers.push({ ...(agentId === undefined ? {} : { agentId }), manifest: agent, server });
262
250
  };
263
251
  add(manifest);
264
252
  for (const child of delegatesOf(manifest))
265
253
  add(child.manifest, child.manifest.id);
266
254
  return servers;
267
255
  }
268
- export function findServer(manifest, agentId, capabilityId, serverName) {
269
- return serversOf(manifest).find((item) => item.agentId === agentId &&
270
- item.capabilityId === capabilityId &&
271
- item.server.name === serverName);
256
+ export function findServer(manifest, agentId, serverName) {
257
+ return serversOf(manifest).find((item) => item.agentId === agentId && item.server.name === serverName);
272
258
  }
273
259
  function ownerOf(declared) {
274
260
  return declared.agentId === undefined ? {} : { agentId: declared.agentId };
@@ -72,7 +72,6 @@ export async function previewMcpServer(body, options) {
72
72
  callTool: () => Promise.reject(new Error("A preview never calls a tool")),
73
73
  };
74
74
  const listed = await listMcpTools(listing, {
75
- capabilityId: "preview",
76
75
  serverName: name,
77
76
  taken: new Set(),
78
77
  signal,
@@ -5,7 +5,6 @@ import type { DurableSessionTool, HostEffect } from "@nylorun/harness/run";
5
5
  export interface McpToolRecord {
6
6
  /** The agent used as a tool that declares the server; absent for the session's root agent. */
7
7
  readonly agentId?: string;
8
- readonly capabilityId: string;
9
8
  readonly serverName: string;
10
9
  /** The tool's name on its server, which the call sends. */
11
10
  readonly serverToolName: string;
@@ -23,7 +22,6 @@ export interface McpToolRecord {
23
22
  /** A server's own instructions (MCP `initialize`), for the note on its deferred tools (R2b C10). */
24
23
  export interface McpServerNote {
25
24
  readonly agentId?: string;
26
- readonly capabilityId: string;
27
25
  readonly serverName: string;
28
26
  /** Cut to `SERVER_INSTRUCTIONS_MAX_CHARS`. */
29
27
  readonly instructions: string;
@@ -38,7 +36,6 @@ export interface McpSnapshot {
38
36
  export interface McpDiagnostic {
39
37
  /** The agent used as a tool that declares the server; absent for the session's root agent. */
40
38
  readonly agentId?: string;
41
- readonly capabilityId: string;
42
39
  readonly serverName: string;
43
40
  readonly outcome: "connected" | "refused" | "failed";
44
41
  readonly message: string;
@@ -55,8 +52,11 @@ export interface McpRenamedTool {
55
52
  readonly serverToolName: string;
56
53
  readonly name: string;
57
54
  }
58
- /** The `mcp.discovered` payload: each server's outcome, with the tools it added to the snapshot. */
59
- export declare function mcpDiscovered(snapshot: McpSnapshot, diagnostics: readonly McpDiagnostic[]): EventPayload<"mcp.discovered">;
55
+ /**
56
+ * The `mcp.discovered` payload: each server's outcome, with the tools it added to the snapshot,
57
+ * and the Agent Plugin that brought the server (from `manifest`, the session's pinned one).
58
+ */
59
+ export declare function mcpDiscovered(snapshot: McpSnapshot, diagnostics: readonly McpDiagnostic[], manifest?: AgentManifest): EventPayload<"mcp.discovered">;
60
60
  /** The longest tool name every model provider accepts (R2b C6, Q14). */
61
61
  export declare const MODEL_TOOL_NAME_MAX = 64;
62
62
  /**
@@ -69,6 +69,7 @@ export declare const MODEL_TOOL_NAME_MAX = 64;
69
69
  export declare function normalizeToolName(serverName: string, serverToolName: string, options?: {
70
70
  readonly suffixed?: boolean;
71
71
  }): string;
72
+ /** The names of the agent's tools: its own and those the Runtime pinned. */
72
73
  export declare function declaredToolNames(manifest: AgentManifest): Set<string>;
73
74
  /** The tool's settings in `manifest` (R2b C9); the defaults when its server is not there. */
74
75
  export declare function mcpToolSettingsIn(manifest: AgentManifest, tool: McpToolRecord): ResolvedMcpToolSettings;
@@ -78,10 +79,9 @@ export declare function mcpToolSettingsIn(manifest: AgentManifest, tool: McpTool
78
79
  *
79
80
  * - a tool its settings disable is left out (R2b C9); the Tool Gate holds a call its approval
80
81
  * or the Tenant's tool policy asks a person about (R5 step 2);
81
- * - a deferred tool (R2b C10) is marked so, out of the model's list, and its agent gets
82
- * `tool_search`, with the note on the servers it holds deferred tools of, and `tool_call` in
83
- * its `nylorun.tools` capability. An agent whose turn has no such capability keeps its tools in
84
- * its list.
82
+ * - a deferred tool (R2b C10) is marked so, out of the model's list, and its agent gets the
83
+ * session tools `tool_search`, with the note on the servers it holds deferred tools of, and
84
+ * `tool_call`. Their names are reserved: no tool of the agent's own takes them.
85
85
  *
86
86
  * Nothing here changes between the steps of a turn, so neither does the model's tool list.
87
87
  */
@@ -94,16 +94,14 @@ export declare const CHARS_PER_TOKEN = 4;
94
94
  * The snapshot with its deferred tools marked (R2b C10, Q21), decided once for the session when
95
95
  * core records it. A tool's own setting, then `"*"`'s, then its server's, decides; a tool none
96
96
  * sets is deferred when the JSON of its agent's MCP tools that would be listed passes a tenth of
97
- * the model's context window (`contextWindow` tokens, at 4 characters a token). An agent without
98
- * the `nylorun.tools` capability (its definition names a tool `tool_search` or `tool_call`, or the
99
- * session was opened before it existed) defers nothing.
97
+ * the model's context window (`contextWindow` tokens, at 4 characters a token).
100
98
  */
101
99
  export declare function pinDeferral(snapshot: McpSnapshot, manifest: AgentManifest, contextWindow: number): McpSnapshot;
102
100
  /**
103
101
  * The snapshot's record of an MCP tool call, or undefined when the call is not an MCP tool. With
104
102
  * the turn's `manifest`, a tool its settings disable is not one either (R2b C9).
105
103
  */
106
- export declare function mcpToolOf(snapshot: McpSnapshot | undefined, request: Pick<HostEffect, "agent" | "capabilityId" | "toolName">, manifest?: AgentManifest): McpToolRecord | undefined;
104
+ export declare function mcpToolOf(snapshot: McpSnapshot | undefined, request: Pick<HostEffect, "agent" | "toolName">, manifest?: AgentManifest): McpToolRecord | undefined;
107
105
  /** True when the tool's settings in `manifest` leave it enabled (R2b C9). */
108
106
  export declare function isEnabledIn(manifest: AgentManifest, tool: McpToolRecord): boolean;
109
107
  /** The manifest of the agent an effect belongs to: the root, or an agent it uses as a tool. */
@@ -1,8 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
- import { TOOLS_CAPABILITY_ID, deferredToolsInstructions, deferredToolsTools, delegateManifest, mcpToolSettings, } from "@nylorun/core/define";
3
- /** The `mcp.discovered` payload: each server's outcome, with the tools it added to the snapshot. */
4
- export function mcpDiscovered(snapshot, diagnostics) {
5
- const key = (item) => JSON.stringify([item.agentId ?? null, item.capabilityId, item.serverName]);
2
+ import { delegatesOf, pluginOf, deferredToolsInstructions, deferredToolsTools, delegateManifest, mcpToolSettings, } from "@nylorun/core/define";
3
+ /**
4
+ * The `mcp.discovered` payload: each server's outcome, with the tools it added to the snapshot,
5
+ * and the Agent Plugin that brought the server (from `manifest`, the session's pinned one).
6
+ */
7
+ export function mcpDiscovered(snapshot, diagnostics, manifest) {
8
+ const agents = new Map(manifest === undefined
9
+ ? []
10
+ : [[undefined, manifest], ...delegatesOf(manifest).map(({ manifest: child }) => [child.id, child])]);
11
+ const key = (item) => JSON.stringify([item.agentId ?? null, item.serverName]);
6
12
  const tools = new Map();
7
13
  const deferred = new Map();
8
14
  for (const tool of snapshot.mcpTools) {
@@ -13,8 +19,8 @@ export function mcpDiscovered(snapshot, diagnostics) {
13
19
  return {
14
20
  servers: diagnostics.map((item) => ({
15
21
  ...(item.agentId === undefined ? {} : { agentId: item.agentId }),
16
- capabilityId: item.capabilityId,
17
22
  serverName: item.serverName,
23
+ ...withPlugin(pluginOf(agents.get(item.agentId), { server: item.serverName })),
18
24
  outcome: item.outcome,
19
25
  message: item.message,
20
26
  tools: tools.get(key(item)) ?? 0,
@@ -26,6 +32,9 @@ export function mcpDiscovered(snapshot, diagnostics) {
26
32
  })),
27
33
  };
28
34
  }
35
+ function withPlugin(plugin) {
36
+ return plugin === undefined ? {} : { plugin };
37
+ }
29
38
  /** The longest tool name every model provider accepts (R2b C6, Q14). */
30
39
  export const MODEL_TOOL_NAME_MAX = 64;
31
40
  /**
@@ -45,38 +54,29 @@ export function normalizeToolName(serverName, serverToolName, options = {}) {
45
54
  .slice(0, 8);
46
55
  return `${name.slice(0, MODEL_TOOL_NAME_MAX - 9)}_${hash}`;
47
56
  }
57
+ /** The names of the agent's tools: its own and those the Runtime pinned. */
48
58
  export function declaredToolNames(manifest) {
49
- const names = new Set();
50
- for (const capability of manifest.capabilities)
51
- for (const tool of capability.tools ?? [])
52
- names.add(tool.name);
53
- return names;
59
+ return new Set([...(manifest.tools ?? []), ...(manifest.runtime?.tools ?? [])].map((tool) => tool.name));
54
60
  }
55
61
  /** The declaration of the server a tool of the snapshot comes from, in `manifest`. */
56
62
  function serverOf(manifest, tool) {
57
63
  const agent = tool.agentId === undefined ? manifest : delegateManifest(manifest, tool.agentId);
58
- return agent?.capabilities.find((item) => item.id === tool.capabilityId)?.mcpServers?.[tool.serverName];
64
+ return agent?.mcpServers?.[tool.serverName];
59
65
  }
60
66
  /** The tool's settings in `manifest` (R2b C9); the defaults when its server is not there. */
61
67
  export function mcpToolSettingsIn(manifest, tool) {
62
68
  const server = serverOf(manifest, tool);
63
69
  return server ? mcpToolSettings(server, tool.serverToolName) : { enabled: true, approval: "never" };
64
70
  }
65
- /** True when the agent the tool belongs to has `tool_search` and `tool_call` (`nylorun.tools`). */
66
- function reachesDeferred(manifest, agentId) {
67
- const agent = agentId === undefined ? manifest : delegateManifest(manifest, agentId);
68
- return agent?.capabilities.some((capability) => capability.id === TOOLS_CAPABILITY_ID) === true;
69
- }
70
71
  /**
71
72
  * The tools of a snapshot, as the engine gets them for a turn whose manifest is `manifest` (the
72
73
  * session's pin, or a turn variant that tightened its MCP servers' tools):
73
74
  *
74
75
  * - a tool its settings disable is left out (R2b C9); the Tool Gate holds a call its approval
75
76
  * or the Tenant's tool policy asks a person about (R5 step 2);
76
- * - a deferred tool (R2b C10) is marked so, out of the model's list, and its agent gets
77
- * `tool_search`, with the note on the servers it holds deferred tools of, and `tool_call` in
78
- * its `nylorun.tools` capability. An agent whose turn has no such capability keeps its tools in
79
- * its list.
77
+ * - a deferred tool (R2b C10) is marked so, out of the model's list, and its agent gets the
78
+ * session tools `tool_search`, with the note on the servers it holds deferred tools of, and
79
+ * `tool_call`. Their names are reserved: no tool of the agent's own takes them.
80
80
  *
81
81
  * Nothing here changes between the steps of a turn, so neither does the model's tool list.
82
82
  */
@@ -90,10 +90,9 @@ export function sessionToolsOf(snapshot, manifest) {
90
90
  const settings = mcpToolSettingsIn(manifest, tool);
91
91
  if (!settings.enabled)
92
92
  continue;
93
- const defer = tool.deferred === true && reachesDeferred(manifest, tool.agentId);
93
+ const defer = tool.deferred === true;
94
94
  const entry = {
95
95
  ...(tool.agentId === undefined ? {} : { agentId: tool.agentId }),
96
- capabilityId: tool.capabilityId,
97
96
  name: tool.name,
98
97
  ...(tool.description === undefined ? {} : { description: tool.description }),
99
98
  inputSchema: tool.inputSchema,
@@ -109,7 +108,7 @@ export function sessionToolsOf(snapshot, manifest) {
109
108
  servers.set(tool.serverName, (servers.get(tool.serverName) ?? 0) + 1);
110
109
  notes.set(tool.agentId, servers);
111
110
  }
112
- const platform = [];
111
+ const session = [];
113
112
  for (const [agentId, tools] of deferred) {
114
113
  const servers = [...notes.get(agentId)].map(([serverName, count]) => {
115
114
  const instructions = snapshot.servers?.find((item) => item.agentId === agentId && item.serverName === serverName)?.instructions;
@@ -120,14 +119,10 @@ export function sessionToolsOf(snapshot, manifest) {
120
119
  { ...search, instructions: [deferredToolsInstructions(servers)] },
121
120
  call,
122
121
  ])
123
- platform.push({
124
- ...(agentId === undefined ? {} : { agentId }),
125
- capabilityId: TOOLS_CAPABILITY_ID,
126
- ...tool,
127
- });
128
- platform.push(...tools);
122
+ session.push({ ...(agentId === undefined ? {} : { agentId }), ...tool });
123
+ session.push(...tools);
129
124
  }
130
- return [...inline, ...platform];
125
+ return [...inline, ...session];
131
126
  }
132
127
  /** The share of the model's context window past which an agent's MCP tools are deferred (Q21). */
133
128
  export const DEFER_CONTEXT_SHARE = 0.1;
@@ -137,9 +132,7 @@ export const CHARS_PER_TOKEN = 4;
137
132
  * The snapshot with its deferred tools marked (R2b C10, Q21), decided once for the session when
138
133
  * core records it. A tool's own setting, then `"*"`'s, then its server's, decides; a tool none
139
134
  * sets is deferred when the JSON of its agent's MCP tools that would be listed passes a tenth of
140
- * the model's context window (`contextWindow` tokens, at 4 characters a token). An agent without
141
- * the `nylorun.tools` capability (its definition names a tool `tool_search` or `tool_call`, or the
142
- * session was opened before it existed) defers nothing.
135
+ * the model's context window (`contextWindow` tokens, at 4 characters a token).
143
136
  */
144
137
  export function pinDeferral(snapshot, manifest, contextWindow) {
145
138
  const limit = contextWindow * CHARS_PER_TOKEN * DEFER_CONTEXT_SHARE;
@@ -153,8 +146,6 @@ export function pinDeferral(snapshot, manifest, contextWindow) {
153
146
  });
154
147
  const mcpTools = snapshot.mcpTools.map((tool, i) => {
155
148
  const { deferred: _, ...rest } = tool;
156
- if (!reachesDeferred(manifest, tool.agentId))
157
- return rest;
158
149
  const defer = set[i] ?? (listed.get(tool.agentId) ?? 0) > limit;
159
150
  return defer ? { ...rest, deferred: true } : rest;
160
151
  });
@@ -165,9 +156,7 @@ export function pinDeferral(snapshot, manifest, contextWindow) {
165
156
  * the turn's `manifest`, a tool its settings disable is not one either (R2b C9).
166
157
  */
167
158
  export function mcpToolOf(snapshot, request, manifest) {
168
- const tool = snapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id &&
169
- tool.capabilityId === request.capabilityId &&
170
- tool.name === request.toolName);
159
+ const tool = snapshot?.mcpTools.find((tool) => tool.agentId === request.agent?.id && tool.name === request.toolName);
171
160
  if (tool && manifest && !isEnabledIn(manifest, tool))
172
161
  return undefined;
173
162
  return tool;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What a model reads (protocol 12): its pi-ai catalog's `input` (text, image) and what the
3
+ * operator added. One function, `capabilitiesOf`, serves the catalog, the accept check, the Model
4
+ * Gate and `read_artifact`.
5
+ *
6
+ * The operator may add only an input our provider path sends natively for the model's API
7
+ * (`NATIVE_INPUTS`): an image on every API; a PDF on the APIs below; Office documents on the
8
+ * OpenAI Responses APIs; audio and video on Google's APIs and OpenAI-compatible completions.
9
+ */
10
+ import type { Api, Model } from "@earendil-works/pi-ai";
11
+ import { type ModelInput } from "@nylorun/core/contracts";
12
+ /** What `capabilitiesOf` reads of a pi-ai model. */
13
+ export type ModelShape = Pick<Model<Api>, "api" | "input">;
14
+ /** The inputs each API carries natively, beyond text. Any other API: images only. */
15
+ export declare const NATIVE_INPUTS: Readonly<Record<string, readonly ModelInput[]>>;
16
+ /** What the operator may add to `model`: the inputs its API carries that its catalog lacks. */
17
+ export declare function addableInputs(model: ModelShape): ModelInput[];
18
+ /**
19
+ * What `model` reads: text, its catalog's inputs and those of `added` its API carries, in
20
+ * `MODEL_INPUTS` order.
21
+ */
22
+ export declare function capabilitiesOf(model: ModelShape, added?: readonly ModelInput[]): {
23
+ readonly inputs: ModelInput[];
24
+ };
25
+ /** Why `inputs` cannot be set for `model`, or undefined when they can. */
26
+ export declare function inputsError(model: ModelShape, inputs: readonly ModelInput[]): string | undefined;
@@ -0,0 +1,44 @@
1
+ import { MODEL_INPUTS } from "@nylorun/core/contracts";
2
+ /** The inputs each API carries natively, beyond text. Any other API: images only. */
3
+ export const NATIVE_INPUTS = {
4
+ "anthropic-messages": ["image", "pdf"],
5
+ "openai-responses": ["image", "pdf", "office"],
6
+ "azure-openai-responses": ["image", "pdf", "office"],
7
+ "openai-completions": ["image", "pdf", "audio", "video"],
8
+ "google-generative-ai": ["image", "pdf", "audio", "video"],
9
+ "google-vertex": ["image", "pdf", "audio", "video"],
10
+ };
11
+ function nativeInputs(model) {
12
+ return NATIVE_INPUTS[model.api] ?? ["image"];
13
+ }
14
+ function ordered(inputs) {
15
+ const set = new Set(inputs);
16
+ return MODEL_INPUTS.filter((input) => set.has(input));
17
+ }
18
+ /** What the operator may add to `model`: the inputs its API carries that its catalog lacks. */
19
+ export function addableInputs(model) {
20
+ const own = model.input;
21
+ return ordered(nativeInputs(model).filter((input) => !own.includes(input)));
22
+ }
23
+ /**
24
+ * What `model` reads: text, its catalog's inputs and those of `added` its API carries, in
25
+ * `MODEL_INPUTS` order.
26
+ */
27
+ export function capabilitiesOf(model, added = []) {
28
+ const native = nativeInputs(model);
29
+ return {
30
+ inputs: ordered([
31
+ "text",
32
+ ...model.input,
33
+ ...added.filter((input) => native.includes(input)),
34
+ ]),
35
+ };
36
+ }
37
+ /** Why `inputs` cannot be set for `model`, or undefined when they can. */
38
+ export function inputsError(model, inputs) {
39
+ const allowed = new Set(["text", ...model.input, ...nativeInputs(model)]);
40
+ const refused = ordered(inputs.filter((input) => !allowed.has(input)));
41
+ if (refused.length === 0)
42
+ return undefined;
43
+ return `This model's API (${model.api}) cannot carry ${refused.join(", ")}; it can read ${ordered(allowed).join(", ")}.`;
44
+ }
@@ -1,4 +1,6 @@
1
- import type { HostModelCatalog } from "@nylorun/core/contracts";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { HostModelCatalog, HostModelView, ModelInput } from "@nylorun/core/contracts";
3
+ import type { SessionStore } from "../store/types.js";
2
4
  export type { HostModelCatalog };
3
5
  /**
4
6
  * The context window, in tokens, of the Tenant's model (`GET /v1/tenant/model`'s view): the
@@ -14,5 +16,34 @@ export declare function contextWindowOf(view: {
14
16
  contextWindow?: number;
15
17
  };
16
18
  }): number;
17
- /** Public provider and model names. No credentials. */
18
- export declare function hostModelCatalog(): HostModelCatalog;
19
+ /**
20
+ * Public provider and model names, and what each model reads with the operator's additions
21
+ * (`added`, by `<provider>/<model>`) and what may be added (protocol 12). No credentials.
22
+ */
23
+ export declare function hostModelCatalog(added?: Readonly<Record<string, readonly ModelInput[]>>): HostModelCatalog;
24
+ /** A catalog model, by provider and id; undefined for one the catalog does not know. */
25
+ export declare function catalogModel(provider: string, model: string): Model<Api> | undefined;
26
+ /**
27
+ * What a configured model reads (protocol 12): a custom endpoint's `settings.inputs` (text only
28
+ * by default), else its catalog's inputs and the operator's additions (`added`). Undefined for no
29
+ * model, or one the catalog does not know.
30
+ */
31
+ export declare function inputsOfHostModel(view: {
32
+ configured: boolean;
33
+ provider?: string;
34
+ model?: string;
35
+ baseUrl?: string;
36
+ settings?: {
37
+ inputs?: readonly ModelInput[];
38
+ };
39
+ }, added?: Readonly<Record<string, readonly ModelInput[]>>): ModelInput[] | undefined;
40
+ /**
41
+ * What the Tenant's model reads now, for the accept check and `read_artifact`: undefined when no
42
+ * model is in the vault (a test's fake provider, say), and the check is skipped.
43
+ */
44
+ export declare function currentModelInputs(ctx: {
45
+ readonly store: SessionStore;
46
+ readonly vault: {
47
+ getHostModel(): Promise<HostModelView>;
48
+ };
49
+ }): Promise<ModelInput[] | undefined>;
@@ -1,3 +1,5 @@
1
+ import { readAddedInputs } from "../tenant/model-setting.js";
2
+ import { addableInputs, capabilitiesOf } from "./capabilities.js";
1
3
  import { CUSTOM_CONTEXT_WINDOW, modelsFor } from "./models.js";
2
4
  const emptyStore = {
3
5
  async read() {
@@ -24,8 +26,11 @@ export function contextWindowOf(view) {
24
26
  const models = modelsFor({ provider: "", model: "" }, emptyStore, { environment: false });
25
27
  return models.getModel(view.provider, view.model)?.contextWindow ?? CUSTOM_CONTEXT_WINDOW;
26
28
  }
27
- /** Public provider and model names. No credentials. */
28
- export function hostModelCatalog() {
29
+ /**
30
+ * Public provider and model names, and what each model reads with the operator's additions
31
+ * (`added`, by `<provider>/<model>`) and what may be added (protocol 12). No credentials.
32
+ */
33
+ export function hostModelCatalog(added = {}) {
29
34
  const models = modelsFor({ provider: "", model: "" }, emptyStore, {
30
35
  environment: false,
31
36
  });
@@ -36,7 +41,38 @@ export function hostModelCatalog() {
36
41
  models: models.getModels(provider.id).map((model) => ({
37
42
  id: model.id,
38
43
  name: model.name,
44
+ inputs: capabilitiesOf(model, added[`${provider.id}/${model.id}`]).inputs,
45
+ addableInputs: addableInputs(model),
39
46
  })),
40
47
  })),
41
48
  };
42
49
  }
50
+ /** A catalog model, by provider and id; undefined for one the catalog does not know. */
51
+ export function catalogModel(provider, model) {
52
+ return modelsFor({ provider: "", model: "" }, emptyStore, { environment: false }).getModel(provider, model);
53
+ }
54
+ /**
55
+ * What a configured model reads (protocol 12): a custom endpoint's `settings.inputs` (text only
56
+ * by default), else its catalog's inputs and the operator's additions (`added`). Undefined for no
57
+ * model, or one the catalog does not know.
58
+ */
59
+ export function inputsOfHostModel(view, added = {}) {
60
+ if (!view.configured || !view.provider || !view.model)
61
+ return undefined;
62
+ if (view.baseUrl) {
63
+ const inputs = view.settings?.inputs ?? [];
64
+ return capabilitiesOf({ api: "openai-completions", input: inputs.includes("image") ? ["text", "image"] : ["text"] }, inputs).inputs;
65
+ }
66
+ const model = catalogModel(view.provider, view.model);
67
+ return model && capabilitiesOf(model, added[`${view.provider}/${view.model}`]).inputs;
68
+ }
69
+ /**
70
+ * What the Tenant's model reads now, for the accept check and `read_artifact`: undefined when no
71
+ * model is in the vault (a test's fake provider, say), and the check is skipped.
72
+ */
73
+ export async function currentModelInputs(ctx) {
74
+ const view = await ctx.vault.getHostModel();
75
+ if (!view.configured)
76
+ return undefined;
77
+ return inputsOfHostModel(view, await ctx.store.tx((t) => readAddedInputs(t)));
78
+ }
@@ -8,6 +8,8 @@ export type Selection = Readonly<{
8
8
  contextWindow?: number;
9
9
  maxTokens?: number;
10
10
  reasoning?: boolean;
11
+ /** What the endpoint reads (protocol 12); text only unless it says so. */
12
+ inputs?: readonly string[];
11
13
  compat?: Readonly<Record<string, unknown>>;
12
14
  }>;
13
15
  }>;
@@ -25,7 +25,7 @@ export function modelsFor(selection, credentials, options = {}) {
25
25
  provider: "custom",
26
26
  baseUrl: selection.custom.baseUrl,
27
27
  reasoning: selection.custom.reasoning ?? false,
28
- input: ["text", "image"],
28
+ input: selection.custom.inputs?.includes("image") ? ["text", "image"] : ["text"],
29
29
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
30
30
  contextWindow: selection.custom.contextWindow ?? CUSTOM_CONTEXT_WINDOW,
31
31
  maxTokens: selection.custom.maxTokens ?? CUSTOM_MAX_TOKENS,