@agen-ai/agent-orchestration 0.0.0-stage → 0.4.0

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 (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -2
  3. package/dist/catalog/parse.d.ts +3 -0
  4. package/dist/catalog/parse.js +13 -0
  5. package/dist/catalog/schemas.d.ts +8 -0
  6. package/dist/catalog/schemas.js +78 -0
  7. package/dist/catalog/types.d.ts +53 -0
  8. package/dist/catalog/types.js +0 -0
  9. package/dist/coordinator/admission.d.ts +7 -0
  10. package/dist/coordinator/admission.js +189 -0
  11. package/dist/coordinator/cancel.d.ts +7 -0
  12. package/dist/coordinator/cancel.js +111 -0
  13. package/dist/coordinator/create.d.ts +3 -0
  14. package/dist/coordinator/create.js +87 -0
  15. package/dist/coordinator/parse.d.ts +10 -0
  16. package/dist/coordinator/parse.js +60 -0
  17. package/dist/coordinator/read.d.ts +4 -0
  18. package/dist/coordinator/read.js +267 -0
  19. package/dist/coordinator/recovery.d.ts +4 -0
  20. package/dist/coordinator/recovery.js +24 -0
  21. package/dist/coordinator/shutdown.d.ts +7 -0
  22. package/dist/coordinator/shutdown.js +66 -0
  23. package/dist/coordinator/types.d.ts +154 -0
  24. package/dist/coordinator/types.js +0 -0
  25. package/dist/delivery/calls.d.ts +4 -0
  26. package/dist/delivery/calls.js +34 -0
  27. package/dist/delivery/journal.d.ts +7 -0
  28. package/dist/delivery/journal.js +57 -0
  29. package/dist/delivery/parse.d.ts +4 -0
  30. package/dist/delivery/parse.js +31 -0
  31. package/dist/delivery/recovery.d.ts +3 -0
  32. package/dist/delivery/recovery.js +18 -0
  33. package/dist/delivery/scheduler.d.ts +3 -0
  34. package/dist/delivery/scheduler.js +123 -0
  35. package/dist/delivery/setup.d.ts +7 -0
  36. package/dist/delivery/setup.js +37 -0
  37. package/dist/delivery/types.d.ts +36 -0
  38. package/dist/delivery/types.js +0 -0
  39. package/dist/execution/authority.d.ts +16 -0
  40. package/dist/execution/authority.js +86 -0
  41. package/dist/execution/cleanup.d.ts +3 -0
  42. package/dist/execution/cleanup.js +75 -0
  43. package/dist/execution/control.d.ts +6 -0
  44. package/dist/execution/control.js +231 -0
  45. package/dist/execution/finish.d.ts +6 -0
  46. package/dist/execution/finish.js +49 -0
  47. package/dist/execution/journal.d.ts +19 -0
  48. package/dist/execution/journal.js +28 -0
  49. package/dist/execution/prepare.d.ts +3 -0
  50. package/dist/execution/prepare.js +153 -0
  51. package/dist/execution/scheduler.d.ts +4 -0
  52. package/dist/execution/scheduler.js +136 -0
  53. package/dist/execution/state.d.ts +107 -0
  54. package/dist/execution/state.js +120 -0
  55. package/dist/execution/stream.d.ts +9 -0
  56. package/dist/execution/stream.js +108 -0
  57. package/dist/host/types.d.ts +95 -0
  58. package/dist/host/types.js +0 -0
  59. package/dist/index.d.ts +20 -0
  60. package/dist/index.js +58 -0
  61. package/dist/mcp/bridge.d.ts +4 -0
  62. package/dist/mcp/bridge.js +222 -0
  63. package/dist/mcp/http.d.ts +11 -0
  64. package/dist/mcp/http.js +74 -0
  65. package/dist/mcp/index.d.ts +3 -0
  66. package/dist/mcp/index.js +4 -0
  67. package/dist/mcp/limits.d.ts +10 -0
  68. package/dist/mcp/limits.js +30 -0
  69. package/dist/mcp/protocol.d.ts +50 -0
  70. package/dist/mcp/protocol.js +81 -0
  71. package/dist/mcp/types.d.ts +22 -0
  72. package/dist/mcp/types.js +0 -0
  73. package/dist/policy/matching.d.ts +4 -0
  74. package/dist/policy/matching.js +18 -0
  75. package/dist/policy/parse.d.ts +3 -0
  76. package/dist/policy/parse.js +13 -0
  77. package/dist/policy/resolve.d.ts +7 -0
  78. package/dist/policy/resolve.js +67 -0
  79. package/dist/policy/schemas.d.ts +5 -0
  80. package/dist/policy/schemas.js +59 -0
  81. package/dist/policy/types.d.ts +60 -0
  82. package/dist/policy/types.js +0 -0
  83. package/dist/shared/bounds.d.ts +6 -0
  84. package/dist/shared/bounds.js +88 -0
  85. package/dist/shared/errors.d.ts +9 -0
  86. package/dist/shared/errors.js +27 -0
  87. package/dist/shared/identity.d.ts +6 -0
  88. package/dist/shared/identity.js +32 -0
  89. package/dist/shared/immutable.d.ts +2 -0
  90. package/dist/shared/immutable.js +10 -0
  91. package/dist/shared/limits.d.ts +32 -0
  92. package/dist/shared/limits.js +69 -0
  93. package/dist/shared/operational-errors.d.ts +7 -0
  94. package/dist/shared/operational-errors.js +15 -0
  95. package/dist/shared/schemas.d.ts +11 -0
  96. package/dist/shared/schemas.js +23 -0
  97. package/dist/shared/text.d.ts +2 -0
  98. package/dist/shared/text.js +15 -0
  99. package/dist/store/memory.d.ts +7 -0
  100. package/dist/store/memory.js +215 -0
  101. package/dist/store/pages.d.ts +16 -0
  102. package/dist/store/pages.js +46 -0
  103. package/dist/store/parse.d.ts +5 -0
  104. package/dist/store/parse.js +137 -0
  105. package/dist/store/types.d.ts +219 -0
  106. package/dist/store/types.js +0 -0
  107. package/dist/store-adapter/admission.d.ts +6 -0
  108. package/dist/store-adapter/admission.js +90 -0
  109. package/dist/store-adapter/context.d.ts +20 -0
  110. package/dist/store-adapter/context.js +107 -0
  111. package/dist/store-adapter/delivery.d.ts +15 -0
  112. package/dist/store-adapter/delivery.js +81 -0
  113. package/dist/store-adapter/execution.d.ts +12 -0
  114. package/dist/store-adapter/execution.js +151 -0
  115. package/dist/store-adapter/index.d.ts +7 -0
  116. package/dist/store-adapter/index.js +27 -0
  117. package/dist/store-adapter/lifecycle.d.ts +15 -0
  118. package/dist/store-adapter/lifecycle.js +80 -0
  119. package/dist/store-adapter/plan.d.ts +7 -0
  120. package/dist/store-adapter/plan.js +168 -0
  121. package/dist/store-adapter/types.d.ts +45 -0
  122. package/dist/store-adapter/types.js +0 -0
  123. package/dist/tasks/failure.d.ts +4 -0
  124. package/dist/tasks/failure.js +24 -0
  125. package/dist/tasks/parse.d.ts +18 -0
  126. package/dist/tasks/parse.js +66 -0
  127. package/dist/tasks/schemas.d.ts +47 -0
  128. package/dist/tasks/schemas.js +215 -0
  129. package/dist/tasks/types.d.ts +192 -0
  130. package/dist/tasks/types.js +0 -0
  131. package/dist/tools/create.d.ts +4 -0
  132. package/dist/tools/create.js +69 -0
  133. package/dist/tools/errors.d.ts +4 -0
  134. package/dist/tools/errors.js +35 -0
  135. package/dist/tools/guidance.d.ts +4 -0
  136. package/dist/tools/guidance.js +28 -0
  137. package/dist/tools/handlers.d.ts +4 -0
  138. package/dist/tools/handlers.js +84 -0
  139. package/dist/tools/projection.d.ts +13 -0
  140. package/dist/tools/projection.js +72 -0
  141. package/dist/tools/schemas.d.ts +6 -0
  142. package/dist/tools/schemas.js +93 -0
  143. package/dist/tools/types.d.ts +28 -0
  144. package/dist/tools/types.js +0 -0
  145. package/package.json +64 -3
@@ -0,0 +1,222 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { validateAgentSessionHostBinding } from "@agen-ai/agent-runtime";
4
+ import { DelegationOperationError } from "../shared/operational-errors.js";
5
+ import { authorizeRequest, McpHttpError, readRequestBody, webRequest, writeError, writeResponse } from "./http.js";
6
+ import { MCP_HEADER_BYTES, MCP_MAX_CONNECTIONS, MCP_MAX_HTTP_REQUESTS, MCP_RECEIPT_TIMEOUT_MS, requestTimeout, shutdownTimeout } from "./limits.js";
7
+ import { createMcpProtocol, mcpTools } from "./protocol.js";
8
+ const activeBindings = /* @__PURE__ */ new WeakSet();
9
+ function aborted() {
10
+ return new DOMException("The MCP transport operation was canceled.", "AbortError");
11
+ }
12
+ async function untilAborted(pending, signal) {
13
+ signal.throwIfAborted();
14
+ let cancel;
15
+ try {
16
+ return await Promise.race([pending, new Promise((_, reject) => {
17
+ cancel = () => reject(aborted());
18
+ signal.addEventListener("abort", cancel, { once: true });
19
+ })]);
20
+ } finally {
21
+ if (cancel) signal.removeEventListener("abort", cancel);
22
+ }
23
+ }
24
+ async function withinDeadline(pending, deadline) {
25
+ let timer;
26
+ try {
27
+ return await Promise.race([pending, new Promise((_, reject) => {
28
+ timer = setTimeout(() => reject(new DelegationOperationError(
29
+ "recovery_required",
30
+ "MCP producers have not quiesced; transport cleanup responsibility is retained."
31
+ )), Math.max(1, deadline - Date.now()));
32
+ })]);
33
+ } finally {
34
+ if (timer) clearTimeout(timer);
35
+ }
36
+ }
37
+ function requestKey(value) {
38
+ if (typeof value === "string" && Buffer.byteLength(value) <= 128) return `string:${value}`;
39
+ if (typeof value === "number" && Number.isSafeInteger(value)) return `number:${value}`;
40
+ return void 0;
41
+ }
42
+ async function startDelegationMcpBridge(options) {
43
+ const originalBinding = options.binding;
44
+ const startupSignal = options.signal;
45
+ startupSignal?.throwIfAborted();
46
+ const timeoutMs = shutdownTimeout(options.shutdownTimeoutMs);
47
+ const responseTimeoutMs = requestTimeout(options.requestTimeoutMs);
48
+ const binding = validateAgentSessionHostBinding({ binding: originalBinding, support: {
49
+ kind: "supported",
50
+ toolTransport: "mcp",
51
+ instructionPlacements: ["tool_context"]
52
+ } });
53
+ const tools = mcpTools(binding);
54
+ const authorization = Buffer.from(`Bearer ${randomBytes(32).toString("base64url")}`);
55
+ const lifetime = new AbortController();
56
+ const requests = /* @__PURE__ */ new Set();
57
+ const calls = /* @__PURE__ */ new Set();
58
+ const sockets = /* @__PURE__ */ new Set();
59
+ const controllers = /* @__PURE__ */ new Map();
60
+ const protocols = /* @__PURE__ */ new Set();
61
+ let accepting = true;
62
+ let released = false;
63
+ let releasing;
64
+ let listenerClose;
65
+ let host = "";
66
+ let url = "";
67
+ const assertAccepting = () => {
68
+ if (!accepting) throw new McpHttpError(503);
69
+ };
70
+ const cancelRequest = (requestId) => {
71
+ const key = requestKey(requestId);
72
+ if (key !== void 0) controllers.get(key)?.abort(aborted());
73
+ };
74
+ const stopAdmission = () => {
75
+ accepting = false;
76
+ lifetime.abort(aborted());
77
+ for (const socket of sockets) socket.destroy();
78
+ };
79
+ const closeProtocol = async (entry) => {
80
+ try {
81
+ await entry.protocol.close();
82
+ protocols.delete(entry);
83
+ } catch {
84
+ stopAdmission();
85
+ throw new DelegationOperationError("recovery_required", "MCP protocol cleanup is incomplete; cleanup responsibility is retained.");
86
+ }
87
+ };
88
+ const serve = async (request, response) => {
89
+ const controller = new AbortController();
90
+ const signal = AbortSignal.any([lifetime.signal, controller.signal]);
91
+ const stop = () => controller.abort(aborted());
92
+ const disconnect = () => {
93
+ if (!response.writableEnded) stop();
94
+ };
95
+ const destroy = () => {
96
+ request.destroy();
97
+ response.destroy();
98
+ };
99
+ request.once("aborted", stop);
100
+ response.once("close", disconnect);
101
+ signal.addEventListener("abort", destroy, { once: true });
102
+ let timer = setTimeout(stop, MCP_RECEIPT_TIMEOUT_MS);
103
+ let entry;
104
+ let key;
105
+ try {
106
+ assertAccepting();
107
+ authorizeRequest(request, host, authorization);
108
+ const body = await readRequestBody(request);
109
+ clearTimeout(timer);
110
+ timer = setTimeout(stop, responseTimeoutMs);
111
+ request.socket.setTimeout(responseTimeoutMs);
112
+ signal.throwIfAborted();
113
+ if (Object.hasOwn(body, "id")) {
114
+ key = requestKey(body.id);
115
+ if (key === void 0) throw new McpHttpError(400);
116
+ if (controllers.has(key)) throw new McpHttpError(409);
117
+ controllers.set(key, controller);
118
+ }
119
+ entry = createMcpProtocol({ binding, signal, calls, assertAccepting, cancelRequest }, tools);
120
+ protocols.add(entry);
121
+ await entry.protocol.connect(entry.transport);
122
+ const result = await untilAborted(entry.transport.handleRequest(webRequest(request, url), { parsedBody: body }), signal);
123
+ await writeResponse(response, result);
124
+ } catch (error) {
125
+ writeError(response, error instanceof McpHttpError ? error.status : signal.aborted ? 408 : 400);
126
+ } finally {
127
+ clearTimeout(timer);
128
+ request.removeListener("aborted", stop);
129
+ response.removeListener("close", disconnect);
130
+ signal.removeEventListener("abort", destroy);
131
+ if (key !== void 0 && controllers.get(key) === controller) controllers.delete(key);
132
+ if (entry) await closeProtocol(entry);
133
+ }
134
+ };
135
+ const listener = createServer({
136
+ maxHeaderSize: MCP_HEADER_BYTES,
137
+ requestTimeout: MCP_RECEIPT_TIMEOUT_MS,
138
+ headersTimeout: MCP_RECEIPT_TIMEOUT_MS,
139
+ connectionsCheckingInterval: 1e3,
140
+ keepAliveTimeout: 1e3
141
+ }, (request, response) => {
142
+ if (!accepting || requests.size >= MCP_MAX_HTTP_REQUESTS) {
143
+ writeError(response, accepting ? 429 : 503);
144
+ return;
145
+ }
146
+ const pending = serve(request, response).catch(() => {
147
+ writeError(response, 500);
148
+ }).finally(() => {
149
+ requests.delete(pending);
150
+ });
151
+ requests.add(pending);
152
+ });
153
+ listener.maxConnections = MCP_MAX_CONNECTIONS;
154
+ listener.on("error", stopAdmission);
155
+ listener.on("connection", (socket) => {
156
+ sockets.add(socket);
157
+ socket.once("close", () => {
158
+ sockets.delete(socket);
159
+ });
160
+ socket.setTimeout(MCP_RECEIPT_TIMEOUT_MS, () => {
161
+ socket.destroy();
162
+ });
163
+ });
164
+ const release = () => {
165
+ if (released) return Promise.resolve();
166
+ if (releasing) return releasing;
167
+ stopAdmission();
168
+ if (!listenerClose) listenerClose = new Promise((resolve, reject) => {
169
+ listener.close((error) => {
170
+ if (error && (!("code" in error) || error.code !== "ERR_SERVER_NOT_RUNNING")) reject(error);
171
+ else resolve();
172
+ });
173
+ });
174
+ const deadline = Date.now() + timeoutMs;
175
+ releasing = Promise.resolve().then(async () => {
176
+ await withinDeadline(Promise.allSettled([...requests, ...calls]), deadline);
177
+ const closed = await withinDeadline(Promise.allSettled([listenerClose, ...[...protocols].map(closeProtocol)]), deadline);
178
+ if (calls.size !== 0 || requests.size !== 0 || protocols.size !== 0 || closed.some((result) => result.status === "rejected")) {
179
+ throw new DelegationOperationError("recovery_required", "MCP transport cleanup is incomplete; the binding owner must retain its lease.");
180
+ }
181
+ released = true;
182
+ activeBindings.delete(originalBinding);
183
+ }).finally(() => {
184
+ releasing = void 0;
185
+ });
186
+ return releasing;
187
+ };
188
+ startupSignal?.throwIfAborted();
189
+ if (activeBindings.has(originalBinding)) throw new TypeError("This host binding already has an active MCP transport.");
190
+ activeBindings.add(originalBinding);
191
+ try {
192
+ await new Promise((resolve, reject) => {
193
+ const failed = () => {
194
+ listener.removeListener("listening", opened);
195
+ reject(new TypeError("The loopback MCP listener could not start."));
196
+ };
197
+ const opened = () => {
198
+ listener.removeListener("error", failed);
199
+ resolve();
200
+ };
201
+ listener.once("error", failed);
202
+ listener.once("listening", opened);
203
+ listener.listen(0, "127.0.0.1");
204
+ });
205
+ startupSignal?.throwIfAborted();
206
+ const address = listener.address();
207
+ if (!address || typeof address === "string") throw new TypeError("The loopback MCP listener has no address.");
208
+ host = `127.0.0.1:${address.port}`;
209
+ url = `http://${host}/mcp`;
210
+ } catch (error) {
211
+ await release();
212
+ throw error;
213
+ }
214
+ return Object.freeze({ server: Object.freeze({
215
+ name: "agenai_delegation",
216
+ url,
217
+ headers: Object.freeze({ Authorization: authorization.toString() })
218
+ }), release });
219
+ }
220
+ export {
221
+ startDelegationMcpBridge
222
+ };
@@ -0,0 +1,11 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ export declare class McpHttpError extends Error {
3
+ readonly status: number;
4
+ constructor(status: number);
5
+ }
6
+ export declare function authorizeRequest(request: IncomingMessage, expectedHost: string, authorization: Buffer): void;
7
+ export declare function readRequestBody(request: IncomingMessage): Promise<unknown>;
8
+ export declare function webRequest(request: IncomingMessage, url: string): Request;
9
+ export declare function writeResponse(response: ServerResponse, result: Response): Promise<void>;
10
+ export declare function writeError(response: ServerResponse, status: number): void;
11
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { MCP_REQUEST_BYTES, MCP_RESPONSE_BYTES } from "./limits.js";
3
+ class McpHttpError extends Error {
4
+ constructor(status) {
5
+ super("The MCP HTTP request is unavailable.");
6
+ this.status = status;
7
+ }
8
+ status;
9
+ }
10
+ function authorizeRequest(request, expectedHost, authorization) {
11
+ if (request.headers.host !== expectedHost || request.headers.origin !== void 0) throw new McpHttpError(403);
12
+ const supplied = request.headers.authorization;
13
+ if (typeof supplied !== "string") throw new McpHttpError(401);
14
+ const encoded = Buffer.from(supplied);
15
+ if (encoded.length !== authorization.length || !timingSafeEqual(encoded, authorization)) throw new McpHttpError(401);
16
+ if (request.url !== "/mcp") throw new McpHttpError(404);
17
+ if (request.method !== "POST") throw new McpHttpError(405);
18
+ if (request.headers["content-encoding"] !== void 0) throw new McpHttpError(415);
19
+ const length = request.headers["content-length"];
20
+ if (length !== void 0 && (!/^\d+$/u.test(length) || Number(length) > MCP_REQUEST_BYTES)) throw new McpHttpError(413);
21
+ }
22
+ async function readRequestBody(request) {
23
+ const chunks = [];
24
+ let bytes = 0;
25
+ for await (const chunk of request) {
26
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
27
+ bytes += buffer.length;
28
+ if (bytes > MCP_REQUEST_BYTES) throw new McpHttpError(413);
29
+ chunks.push(buffer);
30
+ }
31
+ let body;
32
+ try {
33
+ body = JSON.parse(Buffer.concat(chunks, bytes).toString("utf8"));
34
+ } catch {
35
+ throw new McpHttpError(400);
36
+ }
37
+ if (body === null || typeof body !== "object" || Array.isArray(body)) throw new McpHttpError(400);
38
+ return body;
39
+ }
40
+ function webRequest(request, url) {
41
+ const headers = new Headers();
42
+ for (const name of ["accept", "content-type", "mcp-protocol-version"]) {
43
+ const value = request.headers[name];
44
+ if (typeof value === "string") headers.set(name, value);
45
+ }
46
+ return new Request(url, { method: "POST", headers });
47
+ }
48
+ async function writeResponse(response, result) {
49
+ const body = Buffer.from(await result.arrayBuffer());
50
+ if (body.length > MCP_RESPONSE_BYTES) throw new McpHttpError(500);
51
+ if (response.destroyed) return;
52
+ response.statusCode = result.status;
53
+ response.setHeader("cache-control", "no-store");
54
+ response.setHeader("content-type", "application/json");
55
+ response.setHeader("content-length", body.length);
56
+ response.end(body);
57
+ }
58
+ function writeError(response, status) {
59
+ if (response.destroyed || response.writableEnded) return;
60
+ response.statusCode = status;
61
+ response.setHeader("connection", "close");
62
+ response.setHeader("cache-control", "no-store");
63
+ response.setHeader("content-type", "application/json");
64
+ if (status === 405) response.setHeader("allow", "POST");
65
+ response.end('{"error":"The MCP request is unavailable."}');
66
+ }
67
+ export {
68
+ McpHttpError,
69
+ authorizeRequest,
70
+ readRequestBody,
71
+ webRequest,
72
+ writeError,
73
+ writeResponse
74
+ };
@@ -0,0 +1,3 @@
1
+ export { startDelegationMcpBridge } from "./bridge.js";
2
+ export type { DelegationMcpBridge, DelegationMcpServerDefinition, StartDelegationMcpBridgeOptions } from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { startDelegationMcpBridge } from "./bridge.js";
2
+ export {
3
+ startDelegationMcpBridge
4
+ };
@@ -0,0 +1,10 @@
1
+ export declare const MCP_REQUEST_BYTES = 65536;
2
+ export declare const MCP_RESPONSE_BYTES = 131072;
3
+ export declare const MCP_HEADER_BYTES = 8192;
4
+ export declare const MCP_RECEIPT_TIMEOUT_MS = 10000;
5
+ export declare const MCP_MAX_HTTP_REQUESTS = 32;
6
+ export declare const MCP_MAX_INVOCATIONS = 16;
7
+ export declare const MCP_MAX_CONNECTIONS = 32;
8
+ export declare function shutdownTimeout(value?: number): number;
9
+ export declare function requestTimeout(value?: number): number;
10
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1,30 @@
1
+ const MCP_REQUEST_BYTES = 65536;
2
+ const MCP_RESPONSE_BYTES = 131072;
3
+ const MCP_HEADER_BYTES = 8192;
4
+ const MCP_RECEIPT_TIMEOUT_MS = 1e4;
5
+ const MCP_MAX_HTTP_REQUESTS = 32;
6
+ const MCP_MAX_INVOCATIONS = 16;
7
+ const MCP_MAX_CONNECTIONS = 32;
8
+ function finiteTimeout(value) {
9
+ if (!Number.isSafeInteger(value) || value < 1 || value > 2147483647) {
10
+ throw new TypeError("MCP operations require a finite positive timeout.");
11
+ }
12
+ return value;
13
+ }
14
+ function shutdownTimeout(value = 1e4) {
15
+ return finiteTimeout(value);
16
+ }
17
+ function requestTimeout(value = 6e4) {
18
+ return finiteTimeout(value);
19
+ }
20
+ export {
21
+ MCP_HEADER_BYTES,
22
+ MCP_MAX_CONNECTIONS,
23
+ MCP_MAX_HTTP_REQUESTS,
24
+ MCP_MAX_INVOCATIONS,
25
+ MCP_RECEIPT_TIMEOUT_MS,
26
+ MCP_REQUEST_BYTES,
27
+ MCP_RESPONSE_BYTES,
28
+ requestTimeout,
29
+ shutdownTimeout
30
+ };
@@ -0,0 +1,50 @@
1
+ import { type AgentHostToolResult, type AgentSessionHostBinding } from "@agen-ai/agent-runtime";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
+ import { type Tool } from "@modelcontextprotocol/sdk/types.js";
5
+ export interface McpProtocolContext {
6
+ readonly binding: AgentSessionHostBinding;
7
+ readonly signal: AbortSignal;
8
+ readonly calls: Set<Promise<AgentHostToolResult>>;
9
+ assertAccepting(): void;
10
+ cancelRequest(requestId: string | number): void;
11
+ }
12
+ export declare function mcpTools(binding: AgentSessionHostBinding): Tool[];
13
+ export declare function createMcpProtocol(context: McpProtocolContext, tools: Tool[]): {
14
+ protocol: Server<{
15
+ method: string;
16
+ params?: {
17
+ [x: string]: unknown;
18
+ _meta?: {
19
+ [x: string]: unknown;
20
+ progressToken?: string | number | undefined;
21
+ "io.modelcontextprotocol/related-task"?: {
22
+ taskId: string;
23
+ } | undefined;
24
+ } | undefined;
25
+ } | undefined;
26
+ }, {
27
+ method: string;
28
+ params?: {
29
+ [x: string]: unknown;
30
+ _meta?: {
31
+ [x: string]: unknown;
32
+ progressToken?: string | number | undefined;
33
+ "io.modelcontextprotocol/related-task"?: {
34
+ taskId: string;
35
+ } | undefined;
36
+ } | undefined;
37
+ } | undefined;
38
+ }, {
39
+ [x: string]: unknown;
40
+ _meta?: {
41
+ [x: string]: unknown;
42
+ progressToken?: string | number | undefined;
43
+ "io.modelcontextprotocol/related-task"?: {
44
+ taskId: string;
45
+ } | undefined;
46
+ } | undefined;
47
+ }>;
48
+ transport: WebStandardStreamableHTTPServerTransport;
49
+ };
50
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { parseAgentJsonValue } from "@agen-ai/agent-protocol";
3
+ import {
4
+ validateAgentHostToolInvocation,
5
+ validateAgentHostToolResult
6
+ } from "@agen-ai/agent-runtime";
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
9
+ import { CallToolRequestSchema, CancelledNotificationSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
10
+ import { MCP_MAX_INVOCATIONS, MCP_RESPONSE_BYTES } from "./limits.js";
11
+ function failure(code, message) {
12
+ return { kind: "error", error: { code, message, category: "unknown", recovery: { kind: "unknown" } } };
13
+ }
14
+ function encodeResult(result) {
15
+ return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: { ...result }, isError: result.kind === "error" };
16
+ }
17
+ function mcpTools(binding) {
18
+ const tools = binding.tools.map((tool) => {
19
+ const { required, ...schema } = tool.inputSchema;
20
+ return { name: tool.name, description: tool.description, inputSchema: {
21
+ ...schema,
22
+ properties: { ...schema.properties },
23
+ ...required ? { required: [...required] } : {}
24
+ } };
25
+ });
26
+ const envelope = { jsonrpc: "2.0", id: "preflight", result: { tools } };
27
+ if (Buffer.byteLength(JSON.stringify(envelope)) > MCP_RESPONSE_BYTES) {
28
+ throw new TypeError("The complete MCP tool list exceeds its HTTP response bound.");
29
+ }
30
+ return tools;
31
+ }
32
+ function createMcpProtocol(context, tools) {
33
+ const protocol = new Server({ name: "agenai_delegation", version: "0.4.0" }, {
34
+ capabilities: { tools: {} },
35
+ ...context.binding.instructions ? { instructions: context.binding.instructions.text } : {}
36
+ });
37
+ const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
38
+ protocol.setNotificationHandler(CancelledNotificationSchema, (notification) => {
39
+ if (notification.params.requestId !== void 0) context.cancelRequest(notification.params.requestId);
40
+ });
41
+ protocol.setRequestHandler(ListToolsRequestSchema, () => {
42
+ context.assertAccepting();
43
+ return { tools };
44
+ });
45
+ protocol.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
46
+ let call;
47
+ try {
48
+ context.assertAccepting();
49
+ const signal = AbortSignal.any([context.signal, extra.signal]);
50
+ signal.throwIfAborted();
51
+ call = validateAgentHostToolInvocation({ binding: context.binding, invocation: {
52
+ invocationId: randomUUID(),
53
+ toolName: request.params.name,
54
+ arguments: parseAgentJsonValue(request.params.arguments ?? {}),
55
+ signal
56
+ } });
57
+ } catch {
58
+ return encodeResult(failure("invalid_tool_input", "The tool invocation is invalid or unavailable."));
59
+ }
60
+ if (context.calls.size >= MCP_MAX_INVOCATIONS) {
61
+ return encodeResult(failure("host_tool_capacity_exhausted", "The MCP binding has reached its invocation limit."));
62
+ }
63
+ const pending = Promise.resolve().then(async () => {
64
+ try {
65
+ call.signal?.throwIfAborted();
66
+ return validateAgentHostToolResult(await context.binding.invoke(call));
67
+ } catch {
68
+ return failure("host_tool_failed", "The host tool did not return a valid result.");
69
+ }
70
+ }).finally(() => {
71
+ context.calls.delete(pending);
72
+ });
73
+ context.calls.add(pending);
74
+ return encodeResult(await pending);
75
+ });
76
+ return { protocol, transport };
77
+ }
78
+ export {
79
+ createMcpProtocol,
80
+ mcpTools
81
+ };
@@ -0,0 +1,22 @@
1
+ import type { AgentSessionHostBinding } from "@agen-ai/agent-runtime";
2
+ export interface DelegationMcpServerDefinition {
3
+ readonly name: "agenai_delegation";
4
+ readonly url: string;
5
+ readonly headers: Readonly<Record<string, string>>;
6
+ }
7
+ export interface DelegationMcpBridge {
8
+ /** Session-local connection data. The authorization header must never be logged or persisted. */
9
+ readonly server: DelegationMcpServerDefinition;
10
+ /** Stops and drains transport producers. The runtime owner separately releases the host binding. */
11
+ release(): Promise<void>;
12
+ }
13
+ export interface StartDelegationMcpBridgeOptions {
14
+ readonly binding: AgentSessionHostBinding;
15
+ /** Cancels startup only; successful startup transfers transport cleanup to release(). */
16
+ readonly signal?: AbortSignal;
17
+ /** Handler response budget, default 60 seconds. Set above any larger client wait budget. */
18
+ readonly requestTimeoutMs?: number;
19
+ /** Finite drain deadline; a nonquiescent callback retains cleanup responsibility for retry. */
20
+ readonly shutdownTimeoutMs?: number;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
File without changes
@@ -0,0 +1,4 @@
1
+ import type { DelegationTarget } from "../catalog/types.js";
2
+ import type { DelegationExclusion } from "./types.js";
3
+ export declare function matchDelegationExclusion(target: DelegationTarget, exclusion: DelegationExclusion, options: ReadonlyMap<string, string>): "excluded" | "unmatched" | "unverifiable";
4
+ //# sourceMappingURL=matching.d.ts.map
@@ -0,0 +1,18 @@
1
+ function matchDelegationExclusion(target, exclusion, options) {
2
+ if (exclusion.providerKey !== void 0 && exclusion.providerKey !== target.providerKey || exclusion.modelId !== void 0 && exclusion.modelId !== target.modelId || exclusion.catalogId !== void 0 && exclusion.catalogId !== target.reference.catalogId || exclusion.targetId !== void 0 && exclusion.targetId !== target.reference.targetId || exclusion.options?.some((option) => options.get(option.key) !== option.optionId)) {
3
+ return "unmatched";
4
+ }
5
+ let unknown = false;
6
+ if (exclusion.reasoning !== void 0) {
7
+ if (target.reasoning.kind === "unknown") unknown = true;
8
+ else if (target.reasoning.value !== exclusion.reasoning) return "unmatched";
9
+ }
10
+ if (exclusion.executionMode !== void 0) {
11
+ if (target.executionMode.kind === "unknown") unknown = true;
12
+ else if (target.executionMode.value !== exclusion.executionMode) return "unmatched";
13
+ }
14
+ return unknown ? "unverifiable" : "excluded";
15
+ }
16
+ export {
17
+ matchDelegationExclusion
18
+ };
@@ -0,0 +1,3 @@
1
+ import type { DelegationPolicySnapshot } from "./types.js";
2
+ export declare function parseDelegationPolicySnapshot(input: unknown): DelegationPolicySnapshot;
3
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { copyBoundedDelegationData } from "../shared/bounds.js";
2
+ import { delegationSchemaError } from "../shared/errors.js";
3
+ import { freezeDelegationData } from "../shared/immutable.js";
4
+ import { PolicySnapshotSchema } from "./schemas.js";
5
+ function parseDelegationPolicySnapshot(input) {
6
+ const captured = copyBoundedDelegationData(input);
7
+ const parsed = PolicySnapshotSchema.safeParse(captured);
8
+ if (!parsed.success) return delegationSchemaError(parsed.error, captured);
9
+ return freezeDelegationData(parsed.data);
10
+ }
11
+ export {
12
+ parseDelegationPolicySnapshot
13
+ };
@@ -0,0 +1,7 @@
1
+ import type { DelegationTargetCatalog } from "../catalog/types.js";
2
+ import type { DelegationPolicySnapshot, ResolvedDelegationPolicy } from "./types.js";
3
+ export declare function resolveDelegationPolicy(input: Readonly<{
4
+ catalog: DelegationTargetCatalog;
5
+ policy: DelegationPolicySnapshot;
6
+ }>): ResolvedDelegationPolicy;
7
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { parseDelegationTargetCatalog } from "../catalog/parse.js";
2
+ import { captureDelegationRecord } from "../shared/bounds.js";
3
+ import { invalidDelegationData } from "../shared/errors.js";
4
+ import { freezeDelegationData } from "../shared/immutable.js";
5
+ import { matchDelegationExclusion } from "./matching.js";
6
+ import { parseDelegationPolicySnapshot } from "./parse.js";
7
+ import { targetIdentityKey } from "./schemas.js";
8
+ function selectedTarget(target, selection) {
9
+ const key = targetIdentityKey(target.reference);
10
+ if (selection.kind === "explicit") return selection.targets.some((identity) => targetIdentityKey(identity) === key);
11
+ if (target.executionMode.kind === "unknown") {
12
+ return selection.unknownModeTargets.some((identity) => targetIdentityKey(identity) === key);
13
+ }
14
+ return target.executionMode.value === "standard" || selection.includeFast;
15
+ }
16
+ function resolveDelegationPolicy(input) {
17
+ const envelope = captureDelegationRecord(input);
18
+ if (Object.keys(envelope).length !== 2 || !Object.hasOwn(envelope, "catalog") || !Object.hasOwn(envelope, "policy")) {
19
+ invalidDelegationData("invalid_data", [], "Policy resolution requires exactly catalog and policy data.");
20
+ }
21
+ const catalog = parseDelegationTargetCatalog(envelope.catalog);
22
+ const policy = parseDelegationPolicySnapshot(envelope.policy);
23
+ const enabled = policy.developer.enabled && policy.user.enabled;
24
+ const selection = policy.user.enabled ? policy.user.selection : void 0;
25
+ const allowedTargets = [];
26
+ const decisions = catalog.targets.map((target) => {
27
+ const selected = selection !== void 0 && selectedTarget(target, selection);
28
+ const reasons = [];
29
+ if (!enabled) reasons.push({ code: "delegation_disabled", message: "Managed delegation is disabled by current policy." });
30
+ if (target.availability.kind === "unavailable") {
31
+ reasons.push({ code: "target_unavailable", message: target.availability.reason });
32
+ }
33
+ const options = new Map(target.options.map((option) => [option.key, option.optionId]));
34
+ const excluded = [];
35
+ const unverifiable = [];
36
+ for (const exclusion of policy.developer.exclusions) {
37
+ const match = matchDelegationExclusion(target, exclusion, options);
38
+ if (match === "excluded" && excluded.length < 10) {
39
+ excluded.push({ code: "developer_excluded", exclusionId: exclusion.exclusionId, message: "A developer exclusion denies this target." });
40
+ } else if (match === "unverifiable" && unverifiable.length < 10) {
41
+ unverifiable.push({ code: "developer_unverifiable", exclusionId: exclusion.exclusionId, message: "Unknown target semantics cannot prove compliance with a developer exclusion." });
42
+ }
43
+ }
44
+ reasons.push(...excluded, ...unverifiable);
45
+ if (selection !== void 0 && !selected) {
46
+ reasons.push(selection.kind === "all_available" && target.executionMode.kind === "unknown" ? { code: "unknown_mode_unselected", message: "Unknown execution mode requires selection of this exact target." } : { code: "user_unselected", message: "The end user has not selected this target." });
47
+ }
48
+ const firstReason = reasons[0];
49
+ const outcome = firstReason === void 0 ? "allowed" : firstReason.code === "developer_unverifiable" ? "unverifiable" : "denied";
50
+ if (outcome === "allowed") allowedTargets.push(target);
51
+ return { reference: target.reference, selected, outcome, reasons: reasons.slice(0, 10) };
52
+ });
53
+ const offered = new Set(catalog.targets.map((target) => targetIdentityKey(target.reference)));
54
+ const requested = selection === void 0 ? [] : selection.kind === "explicit" ? selection.targets : selection.unknownModeTargets;
55
+ return freezeDelegationData({
56
+ enabled,
57
+ policyRevision: policy.revision,
58
+ catalogId: catalog.catalogId,
59
+ catalogRevision: catalog.revision,
60
+ decisions,
61
+ allowedTargets,
62
+ missingSelections: requested.filter((identity) => !offered.has(targetIdentityKey(identity)))
63
+ });
64
+ }
65
+ export {
66
+ resolveDelegationPolicy
67
+ };
@@ -0,0 +1,5 @@
1
+ import { z } from "zod/v4";
2
+ import type { DelegationPolicySnapshot, DelegationTargetIdentity } from "./types.js";
3
+ export declare function targetIdentityKey(identity: DelegationTargetIdentity): string;
4
+ export declare const PolicySnapshotSchema: z.ZodType<DelegationPolicySnapshot>;
5
+ //# sourceMappingURL=schemas.d.ts.map