@emseepea/server 0.23.1 → 0.24.1
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.
- package/dist/app-shutdown.d.ts +11 -0
- package/dist/app-shutdown.d.ts.map +1 -0
- package/dist/app-shutdown.js +46 -0
- package/dist/app-shutdown.js.map +1 -0
- package/dist/events-cloud-tasks-transport.d.ts +34 -0
- package/dist/events-cloud-tasks-transport.d.ts.map +1 -0
- package/dist/events-cloud-tasks-transport.js +232 -0
- package/dist/events-cloud-tasks-transport.js.map +1 -0
- package/dist/events-external.d.ts +62 -0
- package/dist/events-external.d.ts.map +1 -0
- package/dist/events-external.js +289 -0
- package/dist/events-external.js.map +1 -0
- package/dist/events-google-sdk-lifetime.d.ts +7 -0
- package/dist/events-google-sdk-lifetime.d.ts.map +1 -0
- package/dist/events-google-sdk-lifetime.js +70 -0
- package/dist/events-google-sdk-lifetime.js.map +1 -0
- package/dist/events-postgres-delivery-migrations.d.ts +18 -0
- package/dist/events-postgres-delivery-migrations.d.ts.map +1 -0
- package/dist/events-postgres-delivery-migrations.js +146 -0
- package/dist/events-postgres-delivery-migrations.js.map +1 -0
- package/dist/events-postgres-delivery-owned.d.ts +16 -0
- package/dist/events-postgres-delivery-owned.d.ts.map +1 -0
- package/dist/events-postgres-delivery-owned.js +178 -0
- package/dist/events-postgres-delivery-owned.js.map +1 -0
- package/dist/events-postgres-delivery.d.ts +29 -0
- package/dist/events-postgres-delivery.d.ts.map +1 -0
- package/dist/events-postgres-delivery.js +299 -0
- package/dist/events-postgres-delivery.js.map +1 -0
- package/dist/events-postgres-dispatch-migrations.d.ts +19 -0
- package/dist/events-postgres-dispatch-migrations.d.ts.map +1 -0
- package/dist/events-postgres-dispatch-migrations.js +162 -0
- package/dist/events-postgres-dispatch-migrations.js.map +1 -0
- package/dist/events-postgres-dispatch.d.ts +39 -0
- package/dist/events-postgres-dispatch.d.ts.map +1 -0
- package/dist/events-postgres-dispatch.js +270 -0
- package/dist/events-postgres-dispatch.js.map +1 -0
- package/dist/events-postgres-lifecycle-migrations.d.ts +17 -0
- package/dist/events-postgres-lifecycle-migrations.d.ts.map +1 -0
- package/dist/events-postgres-lifecycle-migrations.js +125 -0
- package/dist/events-postgres-lifecycle-migrations.js.map +1 -0
- package/dist/events-postgres-lifecycle.d.ts +13 -0
- package/dist/events-postgres-lifecycle.d.ts.map +1 -0
- package/dist/events-postgres-lifecycle.js +176 -0
- package/dist/events-postgres-lifecycle.js.map +1 -0
- package/dist/events-postgres-migrations.d.ts +32 -0
- package/dist/events-postgres-migrations.d.ts.map +1 -0
- package/dist/events-postgres-migrations.js +329 -0
- package/dist/events-postgres-migrations.js.map +1 -0
- package/dist/events-postgres-owned.d.ts +16 -0
- package/dist/events-postgres-owned.d.ts.map +1 -0
- package/dist/events-postgres-owned.js +161 -0
- package/dist/events-postgres-owned.js.map +1 -0
- package/dist/events-postgres.d.ts +53 -0
- package/dist/events-postgres.d.ts.map +1 -0
- package/dist/events-postgres.js +289 -0
- package/dist/events-postgres.js.map +1 -0
- package/dist/events.d.ts +30 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +146 -134
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +219 -90
- package/dist/index.js.map +1 -1
- package/package.json +31 -6
package/dist/index.js
CHANGED
|
@@ -8,9 +8,13 @@ import { readFileSync } from "node:fs";
|
|
|
8
8
|
import { readdir, realpath } from "node:fs/promises";
|
|
9
9
|
import { isIP } from "node:net";
|
|
10
10
|
import { isAbsolute, join } from "node:path";
|
|
11
|
+
import { performance } from "node:perf_hooks";
|
|
11
12
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
13
|
import { z } from "zod";
|
|
14
|
+
import { prepareAppShutdown, remainingAppShutdown, startAppShutdown, registerAppShutdownOwner } from "./app-shutdown.js";
|
|
13
15
|
import { createMcpEventRuntime, McpEventInputError } from "./events.js";
|
|
16
|
+
import { createExternalMcpEventRuntime } from "./events-external.js";
|
|
17
|
+
export { ExternalMcpEventError } from "./events-external.js";
|
|
14
18
|
import { createResourceInventory, ResourceInventoryCursorError } from "./resource-inventory.js";
|
|
15
19
|
import { installObservability, observabilityState, } from "./telemetry.js";
|
|
16
20
|
export { openTelemetry, structuredLogging, } from "./telemetry.js";
|
|
@@ -1093,6 +1097,8 @@ export function createEmseepea(options) {
|
|
|
1093
1097
|
const readinessTimeoutMs = callbackTimeout("readiness", readiness, "readinessTimeoutMs", options.readinessTimeoutMs);
|
|
1094
1098
|
const stopping = new AbortController();
|
|
1095
1099
|
let eventRuntime;
|
|
1100
|
+
let internalEventRuntime;
|
|
1101
|
+
let externalEventRuntime;
|
|
1096
1102
|
let pendingReadiness;
|
|
1097
1103
|
async function ready() {
|
|
1098
1104
|
if (stopping.signal.aborted)
|
|
@@ -1173,7 +1179,14 @@ export function createEmseepea(options) {
|
|
|
1173
1179
|
if (options.events && !authentication) {
|
|
1174
1180
|
throw new TypeError("MCP Events require authentication configuration");
|
|
1175
1181
|
}
|
|
1176
|
-
|
|
1182
|
+
if (options.events) {
|
|
1183
|
+
if ("mode" in options.events) {
|
|
1184
|
+
eventRuntime = externalEventRuntime = createExternalMcpEventRuntime(options.events);
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
eventRuntime = internalEventRuntime = createMcpEventRuntime(options.events);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1177
1190
|
const hasStreaming = tools.some((tool) => tool[TOOL_STREAMING]);
|
|
1178
1191
|
const hasProgress = hasStreaming || resources.length > 0 || prompts.length > 0;
|
|
1179
1192
|
if ([
|
|
@@ -1332,6 +1345,22 @@ export function createEmseepea(options) {
|
|
|
1332
1345
|
});
|
|
1333
1346
|
const nodeHandler = toNodeHandler(sdkHandler);
|
|
1334
1347
|
const app = createMcpFastifyApp({ host: deployment.mode === "loopback" ? "127.0.0.1" : "0.0.0.0" });
|
|
1348
|
+
const unstartedSockets = new Set();
|
|
1349
|
+
app.server.on("connection", (socket) => {
|
|
1350
|
+
if (stopping.signal.aborted) {
|
|
1351
|
+
socket.destroy();
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
unstartedSockets.add(socket);
|
|
1355
|
+
socket.once("close", () => unstartedSockets.delete(socket));
|
|
1356
|
+
});
|
|
1357
|
+
app.server.on("request", (request, response) => {
|
|
1358
|
+
unstartedSockets.delete(request.socket);
|
|
1359
|
+
response.once("finish", () => {
|
|
1360
|
+
if (stopping.signal.aborted)
|
|
1361
|
+
queueMicrotask(() => app.server.closeIdleConnections());
|
|
1362
|
+
});
|
|
1363
|
+
});
|
|
1335
1364
|
async function serveResourceSubscription(request, reply, uri) {
|
|
1336
1365
|
if (!resourceSubscriptions || !resourceEvents)
|
|
1337
1366
|
return;
|
|
@@ -1421,7 +1450,11 @@ export function createEmseepea(options) {
|
|
|
1421
1450
|
.code(available ? 200 : 503).send(available ? "ready\n" : "not ready\n");
|
|
1422
1451
|
});
|
|
1423
1452
|
app.addHook("preClose", async () => {
|
|
1453
|
+
prepareAppShutdown(app);
|
|
1454
|
+
void startAppShutdown(app);
|
|
1424
1455
|
stopping.abort();
|
|
1456
|
+
for (const socket of unstartedSockets)
|
|
1457
|
+
socket.destroy();
|
|
1425
1458
|
await Promise.all([...activeSubscriptions].map((handler) => handler.close()));
|
|
1426
1459
|
});
|
|
1427
1460
|
if (authentication) {
|
|
@@ -1442,6 +1475,7 @@ export function createEmseepea(options) {
|
|
|
1442
1475
|
handler: async (_request, reply) => { await reply.header("allow", "POST").code(405).send(); },
|
|
1443
1476
|
});
|
|
1444
1477
|
app.post("/mcp", { bodyLimit: maxRequestBytes }, async (request, reply) => {
|
|
1478
|
+
const admittedAt = performance.now();
|
|
1445
1479
|
if (deployment.mode === "production-behind-proxy" &&
|
|
1446
1480
|
!validateProductionRequest(request, reply, deployment, limiter))
|
|
1447
1481
|
return;
|
|
@@ -1538,95 +1572,111 @@ export function createEmseepea(options) {
|
|
|
1538
1572
|
}
|
|
1539
1573
|
if (eventRuntime && !legacy && isRecord(request.body) &&
|
|
1540
1574
|
(request.body.method === "server/discover" || eventMethod)) {
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
if (request
|
|
1546
|
-
|
|
1547
|
-
? tools.filter((tool) => accessAllows(tool[TOOL_ACCESS], principal)) : tools;
|
|
1548
|
-
const activeResources = protectsCatalogue
|
|
1549
|
-
? resources.filter((resource) => accessAllows(resource[RESOURCE_ACCESS], principal)) : resources;
|
|
1550
|
-
const activePrompts = protectsCatalogue
|
|
1551
|
-
? prompts.filter((prompt) => accessAllows(prompt[PROMPT_ACCESS], principal)) : prompts;
|
|
1552
|
-
const capabilities = {
|
|
1553
|
-
...(activeTools.length ? { tools: { listChanged: false } } : {}),
|
|
1554
|
-
...(activeResources.length ? { resources: {
|
|
1555
|
-
subscribe: Boolean(resourceSubscriptions), listChanged: false,
|
|
1556
|
-
} } : {}),
|
|
1557
|
-
...(activePrompts.length ? { prompts: { listChanged: false } } : {}),
|
|
1558
|
-
...(activeResources.some((resource) => resource[HAS_COMPLETION]) ||
|
|
1559
|
-
activePrompts.some((prompt) => prompt[HAS_COMPLETION]) ? { completions: {} } : {}),
|
|
1560
|
-
...(clientLogging ? { logging: {} } : {}),
|
|
1561
|
-
events: {},
|
|
1562
|
-
};
|
|
1563
|
-
await reply.code(200).send({
|
|
1564
|
-
jsonrpc: "2.0",
|
|
1565
|
-
id: requestId(request.body.id),
|
|
1566
|
-
result: {
|
|
1567
|
-
supportedVersions: [PROTOCOL_VERSION],
|
|
1568
|
-
capabilities,
|
|
1569
|
-
resultType: "complete",
|
|
1570
|
-
ttlMs: protectsCatalogue ? 0 : cacheHints?.["server/discover"]?.ttlMs ?? 0,
|
|
1571
|
-
cacheScope: protectsCatalogue ? "private" : cacheHints?.["server/discover"]?.cacheScope ?? "private",
|
|
1572
|
-
...(options.instructions ? { instructions: options.instructions } : {}),
|
|
1573
|
-
_meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
|
|
1574
|
-
},
|
|
1575
|
-
});
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
if (!authInfo)
|
|
1579
|
-
throw new Error("Events reached execution without verified authorization");
|
|
1580
|
-
let ownerKey;
|
|
1575
|
+
const disconnectedEvents = new AbortController();
|
|
1576
|
+
const abortEvents = () => disconnectedEvents.abort();
|
|
1577
|
+
request.raw.once("aborted", abortEvents);
|
|
1578
|
+
reply.raw.once("close", abortEvents);
|
|
1579
|
+
if (requestAlreadyClosed(request, reply))
|
|
1580
|
+
abortEvents();
|
|
1581
1581
|
try {
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1582
|
+
const requestEvents = externalEventRuntime ? externalEventRuntime.forInvocation({
|
|
1583
|
+
signal: AbortSignal.any([stopping.signal, disconnectedEvents.signal]),
|
|
1584
|
+
deadlineMs: Date.now() + Math.max(0, operationTimeoutMs - (performance.now() - admittedAt)),
|
|
1585
|
+
}) : eventRuntime;
|
|
1586
|
+
if (!await requestEvents.ready()) {
|
|
1587
|
+
await sendRpcError(reply, 503, -32603, "Events are unavailable", requestId(request.body.id));
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (request.body.method === "server/discover") {
|
|
1591
|
+
const activeTools = protectsCatalogue
|
|
1592
|
+
? tools.filter((tool) => accessAllows(tool[TOOL_ACCESS], principal)) : tools;
|
|
1593
|
+
const activeResources = protectsCatalogue
|
|
1594
|
+
? resources.filter((resource) => accessAllows(resource[RESOURCE_ACCESS], principal)) : resources;
|
|
1595
|
+
const activePrompts = protectsCatalogue
|
|
1596
|
+
? prompts.filter((prompt) => accessAllows(prompt[PROMPT_ACCESS], principal)) : prompts;
|
|
1597
|
+
const capabilities = {
|
|
1598
|
+
...(activeTools.length ? { tools: { listChanged: false } } : {}),
|
|
1599
|
+
...(activeResources.length ? { resources: {
|
|
1600
|
+
subscribe: Boolean(resourceSubscriptions), listChanged: false,
|
|
1601
|
+
} } : {}),
|
|
1602
|
+
...(activePrompts.length ? { prompts: { listChanged: false } } : {}),
|
|
1603
|
+
...(activeResources.some((resource) => resource[HAS_COMPLETION]) ||
|
|
1604
|
+
activePrompts.some((prompt) => prompt[HAS_COMPLETION]) ? { completions: {} } : {}),
|
|
1605
|
+
...(clientLogging ? { logging: {} } : {}),
|
|
1606
|
+
events: {},
|
|
1607
|
+
};
|
|
1591
1608
|
await reply.code(200).send({
|
|
1592
1609
|
jsonrpc: "2.0",
|
|
1593
1610
|
id: requestId(request.body.id),
|
|
1594
|
-
result: {
|
|
1611
|
+
result: {
|
|
1612
|
+
supportedVersions: [PROTOCOL_VERSION],
|
|
1613
|
+
capabilities,
|
|
1614
|
+
resultType: "complete",
|
|
1615
|
+
ttlMs: protectsCatalogue ? 0 : cacheHints?.["server/discover"]?.ttlMs ?? 0,
|
|
1616
|
+
cacheScope: protectsCatalogue ? "private" : cacheHints?.["server/discover"]?.cacheScope ?? "private",
|
|
1617
|
+
...(options.instructions ? { instructions: options.instructions } : {}),
|
|
1618
|
+
_meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
|
|
1619
|
+
},
|
|
1595
1620
|
});
|
|
1621
|
+
return;
|
|
1596
1622
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
return;
|
|
1601
|
-
}
|
|
1602
|
-
if (request.body.method === "events/subscribe") {
|
|
1623
|
+
if (!authInfo)
|
|
1624
|
+
throw new Error("Events reached execution without verified authorization");
|
|
1625
|
+
let ownerKey;
|
|
1603
1626
|
try {
|
|
1604
|
-
|
|
1605
|
-
await reply.code(200).send({ jsonrpc: "2.0", id: requestId(request.body.id), result });
|
|
1627
|
+
ownerKey = await requestEvents.ownerKey(authInfo);
|
|
1606
1628
|
}
|
|
1607
|
-
catch
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
await sendRpcError(reply, 503, -32603, "Events are unavailable", requestId(request.body.id));
|
|
1629
|
+
catch {
|
|
1630
|
+
await sendRpcError(reply, 500, -32603, "Event owner identity unavailable", requestId(request.body.id));
|
|
1631
|
+
return;
|
|
1613
1632
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1633
|
+
if (request.body.method === "events/list") {
|
|
1634
|
+
try {
|
|
1635
|
+
const events = await requestEvents.list(ownerKey);
|
|
1636
|
+
await reply.code(200).send({
|
|
1637
|
+
jsonrpc: "2.0",
|
|
1638
|
+
id: requestId(request.body.id),
|
|
1639
|
+
result: { events, _meta: { "io.modelcontextprotocol/serverInfo": serverInfo } },
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
catch {
|
|
1643
|
+
await sendRpcError(reply, 503, -32603, "Events are unavailable", requestId(request.body.id));
|
|
1644
|
+
}
|
|
1645
|
+
return;
|
|
1620
1646
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1647
|
+
if (request.body.method === "events/subscribe") {
|
|
1648
|
+
try {
|
|
1649
|
+
const result = await requestEvents.subscribe(ownerKey, request.body.params);
|
|
1650
|
+
await reply.code(200).send({ jsonrpc: "2.0", id: requestId(request.body.id), result });
|
|
1624
1651
|
}
|
|
1625
|
-
|
|
1652
|
+
catch (error) {
|
|
1653
|
+
if (error instanceof McpEventInputError) {
|
|
1654
|
+
await sendRpcError(reply, error.code === -32015 ? 200 : 400, error.code, error.message, requestId(request.body.id), { reason: error.reason });
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1626
1657
|
await sendRpcError(reply, 503, -32603, "Events are unavailable", requestId(request.body.id));
|
|
1627
1658
|
}
|
|
1659
|
+
return;
|
|
1628
1660
|
}
|
|
1629
|
-
|
|
1661
|
+
if (request.body.method === "events/unsubscribe") {
|
|
1662
|
+
try {
|
|
1663
|
+
await requestEvents.unsubscribe(ownerKey, request.body.params);
|
|
1664
|
+
await reply.code(200).send({ jsonrpc: "2.0", id: requestId(request.body.id), result: {} });
|
|
1665
|
+
}
|
|
1666
|
+
catch (error) {
|
|
1667
|
+
if (error instanceof McpEventInputError) {
|
|
1668
|
+
await sendRpcError(reply, 400, error.code, error.message, requestId(request.body.id), { reason: error.reason });
|
|
1669
|
+
}
|
|
1670
|
+
else {
|
|
1671
|
+
await sendRpcError(reply, 503, -32603, "Events are unavailable", requestId(request.body.id));
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
finally {
|
|
1678
|
+
request.raw.off("aborted", abortEvents);
|
|
1679
|
+
reply.raw.off("close", abortEvents);
|
|
1630
1680
|
}
|
|
1631
1681
|
}
|
|
1632
1682
|
if (subscriptionTarget) {
|
|
@@ -1644,7 +1694,7 @@ export function createEmseepea(options) {
|
|
|
1644
1694
|
try {
|
|
1645
1695
|
await requestOperations.run({
|
|
1646
1696
|
deadlineMs: Date.now() + operationTimeoutMs,
|
|
1647
|
-
signal: disconnected.signal,
|
|
1697
|
+
signal: AbortSignal.any([disconnected.signal, stopping.signal]),
|
|
1648
1698
|
principal,
|
|
1649
1699
|
filterCatalogues: protectsCatalogue,
|
|
1650
1700
|
capability,
|
|
@@ -1670,17 +1720,32 @@ export function createEmseepea(options) {
|
|
|
1670
1720
|
}
|
|
1671
1721
|
});
|
|
1672
1722
|
let drainingEvents = false;
|
|
1673
|
-
const eventTimer =
|
|
1723
|
+
const eventTimer = internalEventRuntime ? setInterval(() => {
|
|
1674
1724
|
if (drainingEvents || stopping.signal.aborted)
|
|
1675
1725
|
return;
|
|
1676
1726
|
drainingEvents = true;
|
|
1677
|
-
void
|
|
1727
|
+
void internalEventRuntime.drain().catch(() => { }).finally(() => { drainingEvents = false; });
|
|
1678
1728
|
}, 1000) : undefined;
|
|
1679
1729
|
eventTimer?.unref();
|
|
1730
|
+
let externalSettled = true;
|
|
1731
|
+
if (externalEventRuntime)
|
|
1732
|
+
registerAppShutdownOwner(app, async (invocation) => {
|
|
1733
|
+
try {
|
|
1734
|
+
return externalSettled = await externalEventRuntime.settle(invocation);
|
|
1735
|
+
}
|
|
1736
|
+
catch {
|
|
1737
|
+
return externalSettled = false;
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1680
1740
|
app.addHook("onClose", async () => {
|
|
1681
1741
|
if (eventTimer)
|
|
1682
1742
|
clearInterval(eventTimer);
|
|
1743
|
+
const ownersSettled = await startAppShutdown(app);
|
|
1683
1744
|
await sdkHandler.close();
|
|
1745
|
+
if (!externalSettled)
|
|
1746
|
+
throw new Error("External Events shutdown has unfinished operations");
|
|
1747
|
+
if (!ownersSettled)
|
|
1748
|
+
throw new Error("APP_SHUTDOWN_INCOMPLETE");
|
|
1684
1749
|
});
|
|
1685
1750
|
runtimes.set(app, {
|
|
1686
1751
|
deployment,
|
|
@@ -1912,8 +1977,12 @@ export async function serveEmseepea(app, options = {}) {
|
|
|
1912
1977
|
return {
|
|
1913
1978
|
url: new URL(`http://${urlHost}:${address.port}/mcp`),
|
|
1914
1979
|
close: () => {
|
|
1980
|
+
if (closing)
|
|
1981
|
+
return closing;
|
|
1982
|
+
prepareAppShutdown(app, shutdownTimeoutMs);
|
|
1915
1983
|
runtime.stopping.abort();
|
|
1916
|
-
|
|
1984
|
+
void startAppShutdown(app);
|
|
1985
|
+
return closing = closeApp(app, remainingAppShutdown(app), observabilityFlushTimeoutMs, runtime.finishObservability);
|
|
1917
1986
|
},
|
|
1918
1987
|
};
|
|
1919
1988
|
}
|
|
@@ -2714,15 +2783,69 @@ function requestId(value) {
|
|
|
2714
2783
|
}
|
|
2715
2784
|
async function runWithDeadline(requestSignal, deadlineMs, work) {
|
|
2716
2785
|
const remainingMs = deadlineMs - Date.now();
|
|
2717
|
-
const
|
|
2718
|
-
const signal = AbortSignal.any([requestSignal, deadlineSignal]);
|
|
2786
|
+
const invocationSignal = requestOperations.getStore()?.signal;
|
|
2719
2787
|
return new Promise((resolve, reject) => {
|
|
2720
|
-
const
|
|
2788
|
+
const controller = new AbortController();
|
|
2789
|
+
const signal = controller.signal;
|
|
2790
|
+
let timer;
|
|
2791
|
+
let settled = false;
|
|
2792
|
+
const abortFrom = (source) => {
|
|
2793
|
+
if (!signal.aborted)
|
|
2794
|
+
controller.abort(source.reason);
|
|
2795
|
+
};
|
|
2796
|
+
const onRequestAbort = () => abortFrom(requestSignal);
|
|
2797
|
+
const onInvocationAbort = () => {
|
|
2798
|
+
if (invocationSignal)
|
|
2799
|
+
abortFrom(invocationSignal);
|
|
2800
|
+
};
|
|
2801
|
+
const onAbort = () => finishReject(new Error("Tool execution cancelled"));
|
|
2802
|
+
const cleanup = () => {
|
|
2803
|
+
if (timer !== undefined)
|
|
2804
|
+
clearTimeout(timer);
|
|
2805
|
+
requestSignal.removeEventListener("abort", onRequestAbort);
|
|
2806
|
+
if (invocationSignal && invocationSignal !== requestSignal) {
|
|
2807
|
+
invocationSignal.removeEventListener("abort", onInvocationAbort);
|
|
2808
|
+
}
|
|
2809
|
+
signal.removeEventListener("abort", onAbort);
|
|
2810
|
+
};
|
|
2811
|
+
function finishResolve(value) {
|
|
2812
|
+
if (settled)
|
|
2813
|
+
return;
|
|
2814
|
+
settled = true;
|
|
2815
|
+
cleanup();
|
|
2816
|
+
resolve(value);
|
|
2817
|
+
}
|
|
2818
|
+
function finishReject(error) {
|
|
2819
|
+
if (settled)
|
|
2820
|
+
return;
|
|
2821
|
+
settled = true;
|
|
2822
|
+
cleanup();
|
|
2823
|
+
reject(error);
|
|
2824
|
+
}
|
|
2825
|
+
if (requestSignal.aborted) {
|
|
2826
|
+
abortFrom(requestSignal);
|
|
2827
|
+
}
|
|
2828
|
+
else if (remainingMs <= 0) {
|
|
2829
|
+
controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
|
|
2830
|
+
}
|
|
2831
|
+
else if (invocationSignal?.aborted) {
|
|
2832
|
+
abortFrom(invocationSignal);
|
|
2833
|
+
}
|
|
2834
|
+
else {
|
|
2835
|
+
requestSignal.addEventListener("abort", onRequestAbort, { once: true });
|
|
2836
|
+
if (invocationSignal && invocationSignal !== requestSignal) {
|
|
2837
|
+
invocationSignal.addEventListener("abort", onInvocationAbort, { once: true });
|
|
2838
|
+
}
|
|
2839
|
+
timer = setTimeout(() => {
|
|
2840
|
+
if (!signal.aborted) {
|
|
2841
|
+
controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
|
|
2842
|
+
}
|
|
2843
|
+
}, remainingMs);
|
|
2844
|
+
}
|
|
2721
2845
|
if (signal.aborted)
|
|
2722
2846
|
return onAbort();
|
|
2723
2847
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2724
|
-
Promise.resolve().then(() => work(signal)).then(
|
|
2725
|
-
.finally(() => signal.removeEventListener("abort", onAbort));
|
|
2848
|
+
Promise.resolve().then(() => work(signal)).then(finishResolve, finishReject);
|
|
2726
2849
|
});
|
|
2727
2850
|
}
|
|
2728
2851
|
function assertUniqueToolNames(tools) {
|
|
@@ -2981,13 +3104,19 @@ async function closeApp(app, timeoutMs, flushTimeoutMs, finishObservability) {
|
|
|
2981
3104
|
let onClosed;
|
|
2982
3105
|
const httpClosed = new Promise((resolve) => { onClosed = resolve; });
|
|
2983
3106
|
app.server.once("close", onClosed);
|
|
2984
|
-
const timeout = new Promise((
|
|
3107
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
2985
3108
|
timer = setTimeout(() => {
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3109
|
+
try {
|
|
3110
|
+
app.server.close();
|
|
3111
|
+
app.server.closeAllConnections();
|
|
3112
|
+
}
|
|
3113
|
+
catch {
|
|
3114
|
+
// Cleanup failure must not hide the incomplete shutdown outcome.
|
|
3115
|
+
}
|
|
3116
|
+
finally {
|
|
3117
|
+
reject(new Error("SERVER_SHUTDOWN_INCOMPLETE"));
|
|
3118
|
+
}
|
|
2989
3119
|
}, timeoutMs);
|
|
2990
|
-
timer.unref();
|
|
2991
3120
|
});
|
|
2992
3121
|
try {
|
|
2993
3122
|
await Promise.race([app.close(), timeout]);
|