@ai-sdk/harness-acp 1.0.76 → 1.0.77
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/CHANGELOG.md +6 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +225 -66
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/v1/bridge/canonical-json-fingerprint.ts +17 -0
- package/src/v1/bridge/host-tool-correlation.ts +17 -40
- package/src/v1/bridge/host-tool-relay-authorization.ts +234 -0
- package/src/v1/bridge/host-tool-relay.ts +18 -14
- package/src/v1/bridge/index.ts +11 -0
- package/src/v1/bridge/permission-controller.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
6
6
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
7
7
|
import { Agent, request } from "node:http";
|
|
8
8
|
//#region src/v1/bridge/host-tool-mcp-server.ts
|
|
9
|
-
const VERSION = "1.0.
|
|
9
|
+
const VERSION = "1.0.77";
|
|
10
10
|
function createHostToolMCPServer({ tools, revision = 1, invoke, onListTools }) {
|
|
11
11
|
let catalog = createCatalog({
|
|
12
12
|
revision,
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -736,7 +736,7 @@ function mergeRecords({ left, right }) {
|
|
|
736
736
|
const result = { ...left };
|
|
737
737
|
for (const [key, value] of Object.entries(right)) {
|
|
738
738
|
const previous = result[key];
|
|
739
|
-
result[key] = isRecord$
|
|
739
|
+
result[key] = isRecord$7(previous) && isRecord$7(value) ? mergeRecords({
|
|
740
740
|
left: previous,
|
|
741
741
|
right: value
|
|
742
742
|
}) : value;
|
|
@@ -748,10 +748,10 @@ function requireGateway({ gateway }) {
|
|
|
748
748
|
return gateway;
|
|
749
749
|
}
|
|
750
750
|
function asRecord(value) {
|
|
751
|
-
if (!isRecord$
|
|
751
|
+
if (!isRecord$7(value)) throw new Error("ACP profile data must resolve to an object.");
|
|
752
752
|
return value;
|
|
753
753
|
}
|
|
754
|
-
function isRecord$
|
|
754
|
+
function isRecord$7(value) {
|
|
755
755
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
756
756
|
}
|
|
757
757
|
//#endregion
|
|
@@ -817,27 +817,27 @@ function captureACPStream({ stream }) {
|
|
|
817
817
|
}
|
|
818
818
|
function getRawSessionUpdate({ message }) {
|
|
819
819
|
const record = message;
|
|
820
|
-
if (record.method !== "session/update" || !isRecord$
|
|
820
|
+
if (record.method !== "session/update" || !isRecord$6(record.params)) return { isSessionUpdate: false };
|
|
821
821
|
const params = record.params;
|
|
822
822
|
return {
|
|
823
823
|
isSessionUpdate: true,
|
|
824
824
|
value: params.update,
|
|
825
|
-
canFilterUnknown: typeof params.sessionId === "string" && isRecord$
|
|
825
|
+
canFilterUnknown: typeof params.sessionId === "string" && isRecord$6(params.update)
|
|
826
826
|
};
|
|
827
827
|
}
|
|
828
828
|
function sessionUpdatesMatch({ rawUpdate, update }) {
|
|
829
|
-
if (!isRecord$
|
|
829
|
+
if (!isRecord$6(rawUpdate)) return false;
|
|
830
830
|
if (rawUpdate.sessionUpdate !== update.sessionUpdate) return false;
|
|
831
831
|
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") return rawUpdate.toolCallId === update.toolCallId;
|
|
832
832
|
if (update.sessionUpdate === "user_message_chunk" || update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk") return rawUpdate.messageId === update.messageId;
|
|
833
833
|
return true;
|
|
834
834
|
}
|
|
835
835
|
function getStringProperty$1({ value, property }) {
|
|
836
|
-
if (!isRecord$
|
|
836
|
+
if (!isRecord$6(value)) return void 0;
|
|
837
837
|
const propertyValue = value[property];
|
|
838
838
|
return typeof propertyValue === "string" ? propertyValue : void 0;
|
|
839
839
|
}
|
|
840
|
-
function isRecord$
|
|
840
|
+
function isRecord$6(value) {
|
|
841
841
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
842
842
|
}
|
|
843
843
|
//#endregion
|
|
@@ -1223,10 +1223,10 @@ function resolveBuiltinTool({ programmaticName, metadata, title, kind, rawInput,
|
|
|
1223
1223
|
});
|
|
1224
1224
|
}
|
|
1225
1225
|
function findMostSpecificSchemaMatch({ rawInput, schemaMatches }) {
|
|
1226
|
-
if (!isRecord$
|
|
1226
|
+
if (!isRecord$5(rawInput)) return void 0;
|
|
1227
1227
|
const rawInputKeys = Object.keys(rawInput);
|
|
1228
1228
|
const coverageOf = (tool) => {
|
|
1229
|
-
const properties = isRecord$
|
|
1229
|
+
const properties = isRecord$5(tool.inputSchema) ? isRecord$5(tool.inputSchema.properties) ? tool.inputSchema.properties : {} : {};
|
|
1230
1230
|
return rawInputKeys.filter((key) => key in properties).length;
|
|
1231
1231
|
};
|
|
1232
1232
|
const highestCoverage = Math.max(...schemaMatches.map(coverageOf));
|
|
@@ -1275,7 +1275,7 @@ function findBuiltinToolsInMetadata({ metadata, builtinToolsByName }) {
|
|
|
1275
1275
|
};
|
|
1276
1276
|
}
|
|
1277
1277
|
function matchesBuiltinToolInput({ rawInput, inputSchema }) {
|
|
1278
|
-
if (!isRecord$
|
|
1278
|
+
if (!isRecord$5(rawInput) || !isRecord$5(inputSchema)) return false;
|
|
1279
1279
|
if (inputSchema.type !== "object") return false;
|
|
1280
1280
|
const required = inputSchema.required;
|
|
1281
1281
|
if (!Array.isArray(required) || required.length === 0 || !required.every((property) => typeof property === "string")) return false;
|
|
@@ -1283,7 +1283,7 @@ function matchesBuiltinToolInput({ rawInput, inputSchema }) {
|
|
|
1283
1283
|
rawInput,
|
|
1284
1284
|
inputSchema
|
|
1285
1285
|
})) return false;
|
|
1286
|
-
const properties = isRecord$
|
|
1286
|
+
const properties = isRecord$5(inputSchema.properties) ? inputSchema.properties : {};
|
|
1287
1287
|
return Object.entries(properties).filter(([, schema]) => isJSONSchemaDiscriminator({ schema })).every(([property, schema]) => {
|
|
1288
1288
|
if (!Object.prototype.hasOwnProperty.call(rawInput, property)) return false;
|
|
1289
1289
|
return matchesJSONSchemaValue({
|
|
@@ -1293,12 +1293,12 @@ function matchesBuiltinToolInput({ rawInput, inputSchema }) {
|
|
|
1293
1293
|
});
|
|
1294
1294
|
}
|
|
1295
1295
|
function hasRequiredBuiltinToolInput({ rawInput, inputSchema }) {
|
|
1296
|
-
if (!isRecord$
|
|
1297
|
-
if (!isRecord$
|
|
1296
|
+
if (!isRecord$5(inputSchema) || inputSchema.type !== "object") return true;
|
|
1297
|
+
if (!isRecord$5(rawInput)) return false;
|
|
1298
1298
|
const required = inputSchema.required;
|
|
1299
1299
|
if (!Array.isArray(required) || required.length === 0) return true;
|
|
1300
1300
|
if (!required.every((property) => typeof property === "string")) return true;
|
|
1301
|
-
const properties = isRecord$
|
|
1301
|
+
const properties = isRecord$5(inputSchema.properties) ? inputSchema.properties : {};
|
|
1302
1302
|
return required.every((property) => {
|
|
1303
1303
|
if (!Object.prototype.hasOwnProperty.call(rawInput, property)) return false;
|
|
1304
1304
|
return matchesJSONSchemaValue({
|
|
@@ -1308,11 +1308,11 @@ function hasRequiredBuiltinToolInput({ rawInput, inputSchema }) {
|
|
|
1308
1308
|
});
|
|
1309
1309
|
}
|
|
1310
1310
|
function isJSONSchemaDiscriminator({ schema }) {
|
|
1311
|
-
return isRecord$
|
|
1311
|
+
return isRecord$5(schema) && ("const" in schema || Array.isArray(schema.enum) && schema.enum.length === 1);
|
|
1312
1312
|
}
|
|
1313
1313
|
function matchesJSONSchemaValue({ value, schema }) {
|
|
1314
1314
|
if (schema === true) return true;
|
|
1315
|
-
if (schema === false || !isRecord$
|
|
1315
|
+
if (schema === false || !isRecord$5(schema)) return false;
|
|
1316
1316
|
if ("const" in schema && !Object.is(value, schema.const)) return false;
|
|
1317
1317
|
if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => Object.is(value, candidate))) return false;
|
|
1318
1318
|
for (const alternatives of [schema.anyOf, schema.oneOf]) if (Array.isArray(alternatives) && !alternatives.some((alternative) => matchesJSONSchemaValue({
|
|
@@ -1327,7 +1327,7 @@ function matchesJSONSchemaValue({ value, schema }) {
|
|
|
1327
1327
|
case "number": return typeof value === "number" && Number.isFinite(value);
|
|
1328
1328
|
case "string": return typeof value === "string";
|
|
1329
1329
|
case "array": return Array.isArray(value);
|
|
1330
|
-
case "object": return isRecord$
|
|
1330
|
+
case "object": return isRecord$5(value);
|
|
1331
1331
|
default: return true;
|
|
1332
1332
|
}
|
|
1333
1333
|
}
|
|
@@ -1373,7 +1373,7 @@ function createACPToolCall({ state }) {
|
|
|
1373
1373
|
...Array.isArray(state.values.locations) ? { locations: state.values.locations } : {},
|
|
1374
1374
|
...Object.prototype.hasOwnProperty.call(state.values, "rawInput") ? { rawInput: state.values.rawInput } : {},
|
|
1375
1375
|
...Object.prototype.hasOwnProperty.call(state.values, "rawOutput") ? { rawOutput: state.values.rawOutput } : {},
|
|
1376
|
-
...state.values._meta === null || isRecord$
|
|
1376
|
+
...state.values._meta === null || isRecord$5(state.values._meta) ? { _meta: state.values._meta } : {}
|
|
1377
1377
|
};
|
|
1378
1378
|
}
|
|
1379
1379
|
function isACPToolKind(value) {
|
|
@@ -1385,7 +1385,7 @@ function isACPToolCallStatus(value) {
|
|
|
1385
1385
|
function emitFileChanges({ state, emit }) {
|
|
1386
1386
|
const content = Array.isArray(state.values.content) ? state.values.content : [];
|
|
1387
1387
|
for (const item of content) {
|
|
1388
|
-
if (!isRecord$
|
|
1388
|
+
if (!isRecord$5(item) || item.type !== "diff") continue;
|
|
1389
1389
|
const path = getStringProperty({
|
|
1390
1390
|
value: item,
|
|
1391
1391
|
property: "path"
|
|
@@ -1476,11 +1476,11 @@ function toSafeJSONValue({ value, fallback, seen = /* @__PURE__ */ new Set() })
|
|
|
1476
1476
|
return result;
|
|
1477
1477
|
}
|
|
1478
1478
|
function getStringProperty({ value, property }) {
|
|
1479
|
-
if (!isRecord$
|
|
1479
|
+
if (!isRecord$5(value)) return void 0;
|
|
1480
1480
|
const propertyValue = value[property];
|
|
1481
1481
|
return typeof propertyValue === "string" ? propertyValue : void 0;
|
|
1482
1482
|
}
|
|
1483
|
-
function isRecord$
|
|
1483
|
+
function isRecord$5(value) {
|
|
1484
1484
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1485
1485
|
}
|
|
1486
1486
|
function mapACPUsage({ usage }) {
|
|
@@ -1527,6 +1527,17 @@ function unknownUsage$1() {
|
|
|
1527
1527
|
};
|
|
1528
1528
|
}
|
|
1529
1529
|
//#endregion
|
|
1530
|
+
//#region src/v1/bridge/canonical-json-fingerprint.ts
|
|
1531
|
+
function canonicalFingerprint({ value }) {
|
|
1532
|
+
return JSON.stringify(canonicalizeJSON({ value }));
|
|
1533
|
+
}
|
|
1534
|
+
function canonicalizeJSON({ value }) {
|
|
1535
|
+
if (Array.isArray(value)) return value.map((item) => canonicalizeJSON({ value: item }));
|
|
1536
|
+
if (value == null || typeof value !== "object") return value;
|
|
1537
|
+
const record = value;
|
|
1538
|
+
return Object.fromEntries(Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => [key, canonicalizeJSON({ value: record[key] })]));
|
|
1539
|
+
}
|
|
1540
|
+
//#endregion
|
|
1530
1541
|
//#region src/v1/bridge/host-tool-correlation.ts
|
|
1531
1542
|
const CORRELATION_WINDOW_MS = 1e3;
|
|
1532
1543
|
const MAX_BUFFERED_UPDATES = 128;
|
|
@@ -1770,7 +1781,7 @@ function hasPortableEvidence({ candidate, invocation }) {
|
|
|
1770
1781
|
hasServerIdentity = true;
|
|
1771
1782
|
hasToolName = true;
|
|
1772
1783
|
}
|
|
1773
|
-
if (typeof deferredToolName === "string" && isRecord$
|
|
1784
|
+
if (typeof deferredToolName === "string" && isRecord$4(deferredToolInput) && hasDelimitedPair({
|
|
1774
1785
|
value: deferredToolName,
|
|
1775
1786
|
serverName: invocation.serverName,
|
|
1776
1787
|
toolName: invocation.toolName
|
|
@@ -1795,7 +1806,7 @@ function hasPortableEvidence({ candidate, invocation }) {
|
|
|
1795
1806
|
return hasServerIdentity && hasToolName && hasInput;
|
|
1796
1807
|
}
|
|
1797
1808
|
function resolvePermissionHostTool({ toolCall, serverName, toolName }) {
|
|
1798
|
-
if (!isRecord$
|
|
1809
|
+
if (!isRecord$4(toolCall.rawInput)) return void 0;
|
|
1799
1810
|
const deferredToolName = getProperty({
|
|
1800
1811
|
value: toolCall.rawInput,
|
|
1801
1812
|
property: "tool_name"
|
|
@@ -1811,7 +1822,7 @@ function resolvePermissionHostTool({ toolCall, serverName, toolName }) {
|
|
|
1811
1822
|
value: toolCall.rawInput,
|
|
1812
1823
|
property: "tool_input"
|
|
1813
1824
|
})) {
|
|
1814
|
-
if (typeof deferredToolName !== "string" || !isRecord$
|
|
1825
|
+
if (typeof deferredToolName !== "string" || !isRecord$4(deferredToolInput) || !hasDelimitedPair({
|
|
1815
1826
|
value: deferredToolName,
|
|
1816
1827
|
serverName,
|
|
1817
1828
|
toolName
|
|
@@ -1844,21 +1855,14 @@ function resolvePermissionHostTool({ toolCall, serverName, toolName }) {
|
|
|
1844
1855
|
};
|
|
1845
1856
|
}
|
|
1846
1857
|
function containsCombinedIdentity({ value, serverName, toolName }) {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1858
|
+
return containsString({
|
|
1859
|
+
value,
|
|
1860
|
+
matches: (candidate) => hasDelimitedPair({
|
|
1850
1861
|
value: candidate,
|
|
1851
1862
|
serverName,
|
|
1852
1863
|
toolName
|
|
1853
|
-
})
|
|
1854
|
-
|
|
1855
|
-
if (seen.has(candidate)) return false;
|
|
1856
|
-
seen.add(candidate);
|
|
1857
|
-
const values = Array.isArray(candidate) ? candidate : Object.values(candidate);
|
|
1858
|
-
for (const item of values) if (visit(item)) return true;
|
|
1859
|
-
return false;
|
|
1860
|
-
};
|
|
1861
|
-
return visit(value);
|
|
1864
|
+
})
|
|
1865
|
+
});
|
|
1862
1866
|
}
|
|
1863
1867
|
function hasDelimitedPair({ value, serverName, toolName }) {
|
|
1864
1868
|
if (serverName.length === 0 || toolName.length === 0) return false;
|
|
@@ -1898,9 +1902,15 @@ function isToolUpdate(message) {
|
|
|
1898
1902
|
return message.update.sessionUpdate === "tool_call" || message.update.sessionUpdate === "tool_call_update";
|
|
1899
1903
|
}
|
|
1900
1904
|
function containsExactValue({ value, target }) {
|
|
1905
|
+
return containsString({
|
|
1906
|
+
value,
|
|
1907
|
+
matches: (candidate) => candidate === target
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
function containsString({ value, matches }) {
|
|
1901
1911
|
const seen = /* @__PURE__ */ new Set();
|
|
1902
1912
|
const visit = (candidate) => {
|
|
1903
|
-
if (candidate ===
|
|
1913
|
+
if (typeof candidate === "string") return matches(candidate);
|
|
1904
1914
|
if (candidate == null || typeof candidate !== "object") return false;
|
|
1905
1915
|
if (seen.has(candidate)) return false;
|
|
1906
1916
|
seen.add(candidate);
|
|
@@ -1924,14 +1934,6 @@ function containsFingerprint({ value, target }) {
|
|
|
1924
1934
|
};
|
|
1925
1935
|
return visit(value);
|
|
1926
1936
|
}
|
|
1927
|
-
function canonicalFingerprint({ value }) {
|
|
1928
|
-
return JSON.stringify(canonicalizeJSON$1({ value }));
|
|
1929
|
-
}
|
|
1930
|
-
function canonicalizeJSON$1({ value }) {
|
|
1931
|
-
if (Array.isArray(value)) return value.map((item) => canonicalizeJSON$1({ value: item }));
|
|
1932
|
-
if (value == null || typeof value !== "object") return value;
|
|
1933
|
-
return Object.fromEntries(Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => [key, canonicalizeJSON$1({ value: value[key] })]));
|
|
1934
|
-
}
|
|
1935
1937
|
function getProperty({ value, property }) {
|
|
1936
1938
|
if (value == null || typeof value !== "object" || Array.isArray(value)) return;
|
|
1937
1939
|
return Reflect.get(value, property);
|
|
@@ -1939,7 +1941,7 @@ function getProperty({ value, property }) {
|
|
|
1939
1941
|
function hasOwnProperty({ value, property }) {
|
|
1940
1942
|
return Object.prototype.hasOwnProperty.call(value, property);
|
|
1941
1943
|
}
|
|
1942
|
-
function isRecord$
|
|
1944
|
+
function isRecord$4(value) {
|
|
1943
1945
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1944
1946
|
}
|
|
1945
1947
|
function isTerminalToolUpdate(message) {
|
|
@@ -2059,7 +2061,7 @@ function setStringAtPath({ record, path, value }) {
|
|
|
2059
2061
|
return {
|
|
2060
2062
|
...record,
|
|
2061
2063
|
[key]: remainingPath.length === 0 ? value : setStringAtPath({
|
|
2062
|
-
record: isRecord$
|
|
2064
|
+
record: isRecord$3(record[key]) ? record[key] : {},
|
|
2063
2065
|
path: remainingPath,
|
|
2064
2066
|
value
|
|
2065
2067
|
})
|
|
@@ -2068,12 +2070,12 @@ function setStringAtPath({ record, path, value }) {
|
|
|
2068
2070
|
function assertSafePath({ path }) {
|
|
2069
2071
|
if (path.length === 0 || path.some((segment) => segment.length === 0 || UNSAFE_PATH_SEGMENTS.has(segment))) throw new Error("ACP instruction mapping path must contain only safe, non-empty property names.");
|
|
2070
2072
|
}
|
|
2071
|
-
function isRecord$
|
|
2073
|
+
function isRecord$3(value) {
|
|
2072
2074
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2073
2075
|
}
|
|
2074
2076
|
//#endregion
|
|
2075
2077
|
//#region src/v1/bridge/host-tool-mcp-server.ts
|
|
2076
|
-
const VERSION = "1.0.
|
|
2078
|
+
const VERSION = "1.0.77";
|
|
2077
2079
|
function createHostToolMCPServer({ tools, revision = 1, invoke, onListTools }) {
|
|
2078
2080
|
let catalog = createCatalog({
|
|
2079
2081
|
revision,
|
|
@@ -2429,7 +2431,7 @@ async function handleRequest({ request, credential, state, serverName, turn, nex
|
|
|
2429
2431
|
});
|
|
2430
2432
|
}
|
|
2431
2433
|
async function handleCatalogNext({ body, state }) {
|
|
2432
|
-
if (!isRecord$
|
|
2434
|
+
if (!isRecord$2(body) || !Number.isSafeInteger(body.afterRevision) || body.afterRevision < 0) throw new RelayRequestError({
|
|
2433
2435
|
status: 400,
|
|
2434
2436
|
message: "Invalid host tool catalog poll request."
|
|
2435
2437
|
});
|
|
@@ -2445,7 +2447,7 @@ async function handleCatalogNext({ body, state }) {
|
|
|
2445
2447
|
} : { revision: state.revision };
|
|
2446
2448
|
}
|
|
2447
2449
|
function handleCatalogSeen({ body, state }) {
|
|
2448
|
-
if (!isRecord$
|
|
2450
|
+
if (!isRecord$2(body) || !Number.isSafeInteger(body.revision) || body.revision < 1 || body.revision > state.revision) throw new RelayRequestError({
|
|
2449
2451
|
status: 400,
|
|
2450
2452
|
message: "Invalid host tool catalog acknowledgment."
|
|
2451
2453
|
});
|
|
@@ -2467,7 +2469,7 @@ async function handleInvocation({ body, state, serverName, turn, nextInvocationO
|
|
|
2467
2469
|
status: 409,
|
|
2468
2470
|
message: "No ACP prompt turn is active."
|
|
2469
2471
|
});
|
|
2470
|
-
if (!isRecord$
|
|
2472
|
+
if (!isRecord$2(body) || typeof body.requestId !== "string" || typeof body.toolName !== "string" || !isRecord$2(body.input) || !Number.isSafeInteger(body.catalogRevision)) throw new RelayRequestError({
|
|
2471
2473
|
status: 400,
|
|
2472
2474
|
message: "Invalid host tool relay request."
|
|
2473
2475
|
});
|
|
@@ -2480,6 +2482,13 @@ async function handleInvocation({ body, state, serverName, turn, nextInvocationO
|
|
|
2480
2482
|
status: 404,
|
|
2481
2483
|
message: `Host tool ${body.toolName} is not active in catalog revision ${state.revision}.`
|
|
2482
2484
|
});
|
|
2485
|
+
if (!await turn.waitForToolCallAuthorization({
|
|
2486
|
+
toolName: tool.name,
|
|
2487
|
+
input: body.input
|
|
2488
|
+
})) throw new RelayRequestError({
|
|
2489
|
+
status: 401,
|
|
2490
|
+
message: "Unauthorized host tool relay request."
|
|
2491
|
+
});
|
|
2483
2492
|
const correlationToken = randomBytes(32).toString("hex");
|
|
2484
2493
|
turn.registerCorrelationInvocation({
|
|
2485
2494
|
token: correlationToken,
|
|
@@ -2556,12 +2565,7 @@ function resolveRefreshWaiters({ state, closing }) {
|
|
|
2556
2565
|
for (const waiter of [...state.refreshWaiters]) if (closing || state.servedRevision >= waiter.revision) waiter.resolve(!closing);
|
|
2557
2566
|
}
|
|
2558
2567
|
function catalogFingerprint({ tools }) {
|
|
2559
|
-
return
|
|
2560
|
-
}
|
|
2561
|
-
function canonicalizeJSON({ value }) {
|
|
2562
|
-
if (Array.isArray(value)) return value.map((item) => canonicalizeJSON({ value: item }));
|
|
2563
|
-
if (!isRecord$1(value)) return value;
|
|
2564
|
-
return Object.fromEntries(Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => [key, canonicalizeJSON({ value: value[key] })]));
|
|
2568
|
+
return canonicalFingerprint({ value: tools });
|
|
2565
2569
|
}
|
|
2566
2570
|
async function readJSONBody({ request }) {
|
|
2567
2571
|
const chunks = [];
|
|
@@ -2607,7 +2611,7 @@ function closeServer({ server }) {
|
|
|
2607
2611
|
});
|
|
2608
2612
|
});
|
|
2609
2613
|
}
|
|
2610
|
-
function isRecord$
|
|
2614
|
+
function isRecord$2(value) {
|
|
2611
2615
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2612
2616
|
}
|
|
2613
2617
|
var RelayRequestError = class extends Error {
|
|
@@ -2618,6 +2622,149 @@ var RelayRequestError = class extends Error {
|
|
|
2618
2622
|
}
|
|
2619
2623
|
};
|
|
2620
2624
|
//#endregion
|
|
2625
|
+
//#region src/v1/bridge/host-tool-relay-authorization.ts
|
|
2626
|
+
const DEFAULT_AUTHORIZATION_WAIT_MS = 1e4;
|
|
2627
|
+
function createHostToolRelayAuthorization({ serverName, toolNames, ttlMs = DEFAULT_AUTHORIZATION_WAIT_MS }) {
|
|
2628
|
+
const observedCalls = /* @__PURE__ */ new Map();
|
|
2629
|
+
const authorizations = /* @__PURE__ */ new Map();
|
|
2630
|
+
const pendingRequests = [];
|
|
2631
|
+
let closed = false;
|
|
2632
|
+
const observe = ({ toolCall }) => {
|
|
2633
|
+
if (closed) return;
|
|
2634
|
+
const previous = observedCalls.get(toolCall.toolCallId);
|
|
2635
|
+
if (previous?.consumed || previous?.terminal) return;
|
|
2636
|
+
const merged = {
|
|
2637
|
+
...previous?.toolCall,
|
|
2638
|
+
...toolCall,
|
|
2639
|
+
...toolCall.name == null && previous?.toolCall.name != null ? { name: previous.toolCall.name } : {},
|
|
2640
|
+
...toolCall.rawInput === void 0 && previous?.toolCall.rawInput !== void 0 ? { rawInput: previous.toolCall.rawInput } : {},
|
|
2641
|
+
...toolCall.title == null && previous?.toolCall.title != null ? { title: previous.toolCall.title } : {},
|
|
2642
|
+
...toolCall._meta == null && previous?.toolCall._meta != null ? { _meta: previous.toolCall._meta } : {}
|
|
2643
|
+
};
|
|
2644
|
+
const observed = {
|
|
2645
|
+
toolCall: merged,
|
|
2646
|
+
consumed: false,
|
|
2647
|
+
terminal: merged.status === "completed" || merged.status === "failed"
|
|
2648
|
+
};
|
|
2649
|
+
observedCalls.set(toolCall.toolCallId, observed);
|
|
2650
|
+
if (observed.terminal) {
|
|
2651
|
+
authorizations.delete(toolCall.toolCallId);
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
const call = resolveHostToolCall({
|
|
2655
|
+
toolCall: merged,
|
|
2656
|
+
serverName,
|
|
2657
|
+
toolNames
|
|
2658
|
+
});
|
|
2659
|
+
if (call == null) {
|
|
2660
|
+
authorizations.delete(toolCall.toolCallId);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
const key = callKey(call);
|
|
2664
|
+
authorizations.set(toolCall.toolCallId, key);
|
|
2665
|
+
const pendingIndex = pendingRequests.findIndex((request) => request.key === key);
|
|
2666
|
+
if (pendingIndex !== -1) {
|
|
2667
|
+
const [pending] = pendingRequests.splice(pendingIndex, 1);
|
|
2668
|
+
clearTimeout(pending.timeout);
|
|
2669
|
+
authorizations.delete(toolCall.toolCallId);
|
|
2670
|
+
observed.consumed = true;
|
|
2671
|
+
pending.resolve(true);
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
return {
|
|
2675
|
+
observeUpdate: ({ update }) => {
|
|
2676
|
+
if (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update") observe({ toolCall: update });
|
|
2677
|
+
},
|
|
2678
|
+
observeAllowedPermission: ({ toolCall }) => observe({ toolCall }),
|
|
2679
|
+
waitForToolCallAuthorization: ({ toolName, input }) => {
|
|
2680
|
+
if (closed) return Promise.resolve(false);
|
|
2681
|
+
const key = callKey({
|
|
2682
|
+
toolName,
|
|
2683
|
+
input
|
|
2684
|
+
});
|
|
2685
|
+
for (const [toolCallId, authorizationKey] of authorizations) {
|
|
2686
|
+
if (authorizationKey !== key) continue;
|
|
2687
|
+
authorizations.delete(toolCallId);
|
|
2688
|
+
observedCalls.get(toolCallId).consumed = true;
|
|
2689
|
+
return Promise.resolve(true);
|
|
2690
|
+
}
|
|
2691
|
+
return new Promise((resolve) => {
|
|
2692
|
+
const pending = {
|
|
2693
|
+
key,
|
|
2694
|
+
timeout: setTimeout(() => {
|
|
2695
|
+
const index = pendingRequests.indexOf(pending);
|
|
2696
|
+
if (index !== -1) pendingRequests.splice(index, 1);
|
|
2697
|
+
resolve(false);
|
|
2698
|
+
}, ttlMs),
|
|
2699
|
+
resolve
|
|
2700
|
+
};
|
|
2701
|
+
pendingRequests.push(pending);
|
|
2702
|
+
});
|
|
2703
|
+
},
|
|
2704
|
+
close: () => {
|
|
2705
|
+
if (closed) return;
|
|
2706
|
+
closed = true;
|
|
2707
|
+
observedCalls.clear();
|
|
2708
|
+
authorizations.clear();
|
|
2709
|
+
for (const request of pendingRequests.splice(0)) {
|
|
2710
|
+
clearTimeout(request.timeout);
|
|
2711
|
+
request.resolve(false);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
function resolveHostToolCall({ toolCall, serverName, toolNames }) {
|
|
2717
|
+
const rawInput = toolCall.rawInput;
|
|
2718
|
+
if (!isRecord$1(rawInput)) return void 0;
|
|
2719
|
+
const isDeferred = "tool_name" in rawInput;
|
|
2720
|
+
const isProvider = "providerIdentifier" in rawInput && "toolName" in rawInput && "args" in rawInput;
|
|
2721
|
+
const isOrigin = "origin" in rawInput && "operation" in rawInput && "arguments" in rawInput;
|
|
2722
|
+
const isCodex = "server" in rawInput && "tool" in rawInput && "arguments" in rawInput;
|
|
2723
|
+
if ([
|
|
2724
|
+
isDeferred,
|
|
2725
|
+
isProvider,
|
|
2726
|
+
isOrigin,
|
|
2727
|
+
isCodex
|
|
2728
|
+
].filter(Boolean).length > 1) return;
|
|
2729
|
+
const matches = [];
|
|
2730
|
+
for (const toolName of toolNames) {
|
|
2731
|
+
const qualifiedNames = [
|
|
2732
|
+
`mcp__${serverName}__${toolName}`,
|
|
2733
|
+
`${serverName}__${toolName}`,
|
|
2734
|
+
`mcp_${serverName}_${toolName}`
|
|
2735
|
+
];
|
|
2736
|
+
const isQualified = (value) => typeof value === "string" && qualifiedNames.includes(value);
|
|
2737
|
+
const isDirect = toolCall.name === toolName && isRecord$1(toolCall._meta) && toolCall._meta.serverName === serverName;
|
|
2738
|
+
if (isDeferred && isQualified(rawInput.tool_name) && isRecord$1(rawInput.tool_input) && (toolCall.name == null || toolCall.name === "use_tool" || isQualified(toolCall.name))) matches.push({
|
|
2739
|
+
toolName,
|
|
2740
|
+
input: rawInput.tool_input
|
|
2741
|
+
});
|
|
2742
|
+
else if (isProvider && rawInput.providerIdentifier === serverName && rawInput.toolName === toolName && isRecord$1(rawInput.args) && (toolCall.name == null || isQualified(toolCall.name))) matches.push({
|
|
2743
|
+
toolName,
|
|
2744
|
+
input: rawInput.args
|
|
2745
|
+
});
|
|
2746
|
+
else if (isCodex && rawInput.server === serverName && rawInput.tool === toolName && isRecord$1(rawInput.arguments) && (toolCall.name == null || isQualified(toolCall.name))) matches.push({
|
|
2747
|
+
toolName,
|
|
2748
|
+
input: rawInput.arguments
|
|
2749
|
+
});
|
|
2750
|
+
else if (isOrigin && rawInput.origin === serverName && rawInput.operation === toolName && isRecord$1(rawInput.arguments) && (toolCall.name == null || toolCall.name === toolName || isQualified(toolCall.name))) matches.push({
|
|
2751
|
+
toolName,
|
|
2752
|
+
input: rawInput.arguments
|
|
2753
|
+
});
|
|
2754
|
+
else if (!isDeferred && !isProvider && !isOrigin && !isCodex && (isDirect || isQualified(toolCall.name) || toolCall.name == null && (isQualified(toolCall.title) || toolCall.title === `${serverName}-${toolName}`))) matches.push({
|
|
2755
|
+
toolName,
|
|
2756
|
+
input: rawInput
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
2760
|
+
}
|
|
2761
|
+
function callKey({ toolName, input }) {
|
|
2762
|
+
return `${toolName}\0${canonicalFingerprint({ value: input })}`;
|
|
2763
|
+
}
|
|
2764
|
+
function isRecord$1(value) {
|
|
2765
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2766
|
+
}
|
|
2767
|
+
//#endregion
|
|
2621
2768
|
//#region src/v1/bridge/host-tool-mcp-definition.ts
|
|
2622
2769
|
function createHostToolMcpServerDefinition({ mcpTransport, relay, serverName, catalogPath, initialization, harnessId }) {
|
|
2623
2770
|
if (mcpTransport === "stdio") return {
|
|
@@ -2680,7 +2827,7 @@ async function refreshHostToolCatalog({ relay, tools, harnessId, timeoutMs }) {
|
|
|
2680
2827
|
//#endregion
|
|
2681
2828
|
//#region src/v1/bridge/permission-controller.ts
|
|
2682
2829
|
let permissionRequestCounter = 0;
|
|
2683
|
-
function createACPPermissionController({ turn, sessionId, permissionMode, hasPermissionModeMapping, emitToolCall, claimHostToolPermission }) {
|
|
2830
|
+
function createACPPermissionController({ turn, sessionId, permissionMode, hasPermissionModeMapping, emitToolCall, claimHostToolPermission, onHostToolPermissionAllowed }) {
|
|
2684
2831
|
const pendingPermissions = /* @__PURE__ */ new Map();
|
|
2685
2832
|
return {
|
|
2686
2833
|
async requestPermission(request) {
|
|
@@ -2695,10 +2842,13 @@ function createACPPermissionController({ turn, sessionId, permissionMode, hasPer
|
|
|
2695
2842
|
turn.emitWarning({ message: `ACP permission request for tool call ${JSON.stringify(request.toolCall.toolCallId)} was cancelled because it did not advertise ${missing.join(" and ")}.` });
|
|
2696
2843
|
return cancelled();
|
|
2697
2844
|
}
|
|
2698
|
-
if (claimHostToolPermission({ toolCall: request.toolCall }))
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2845
|
+
if (claimHostToolPermission({ toolCall: request.toolCall })) {
|
|
2846
|
+
onHostToolPermissionAllowed?.({ toolCall: request.toolCall });
|
|
2847
|
+
return { outcome: {
|
|
2848
|
+
outcome: "selected",
|
|
2849
|
+
optionId: allowOnce.optionId
|
|
2850
|
+
} };
|
|
2851
|
+
}
|
|
2702
2852
|
if (!hasPermissionModeMapping && shouldAutoApprove({
|
|
2703
2853
|
permissionMode,
|
|
2704
2854
|
kind: request.toolCall.kind
|
|
@@ -3132,9 +3282,14 @@ async function runTurn(start, turn) {
|
|
|
3132
3282
|
cancellationFailure.catch(() => {});
|
|
3133
3283
|
let cancellationRequested = false;
|
|
3134
3284
|
let cancellationFailureError;
|
|
3285
|
+
const hostToolAuthorization = createHostToolRelayAuthorization({
|
|
3286
|
+
serverName: HOST_TOOL_MCP_SERVER_NAME,
|
|
3287
|
+
toolNames: (start.tools ?? []).map((tool) => tool.name)
|
|
3288
|
+
});
|
|
3135
3289
|
const cancel = async () => {
|
|
3136
3290
|
if (cancellationRequested) return;
|
|
3137
3291
|
cancellationRequested = true;
|
|
3292
|
+
hostToolAuthorization.close();
|
|
3138
3293
|
activePermissionController?.cancelAll();
|
|
3139
3294
|
try {
|
|
3140
3295
|
if (connection == null) throw new Error("ACP connection closed before cancellation.");
|
|
@@ -3176,10 +3331,12 @@ async function runTurn(start, turn) {
|
|
|
3176
3331
|
permissionMode: start.permissionMode ?? "allow-all",
|
|
3177
3332
|
hasPermissionModeMapping: start.permissionModeMapping != null,
|
|
3178
3333
|
emitToolCall: emitStreamEvent.permissionToolCall,
|
|
3179
|
-
claimHostToolPermission: emitStreamEvent.claimHostToolPermission
|
|
3334
|
+
claimHostToolPermission: emitStreamEvent.claimHostToolPermission,
|
|
3335
|
+
onHostToolPermissionAllowed: hostToolAuthorization.observeAllowedPermission
|
|
3180
3336
|
});
|
|
3181
3337
|
activePermissionController = permissionController;
|
|
3182
3338
|
const relayTurn = {
|
|
3339
|
+
waitForToolCallAuthorization: hostToolAuthorization.waitForToolCallAuthorization,
|
|
3183
3340
|
emitToolCall: emitStreamEvent.hostToolCall,
|
|
3184
3341
|
emitToolResult: emitStreamEvent.hostToolResult,
|
|
3185
3342
|
requestToolResult: (toolCallId) => turn.requestToolResult(toolCallId),
|
|
@@ -3230,6 +3387,7 @@ async function runTurn(start, turn) {
|
|
|
3230
3387
|
}
|
|
3231
3388
|
if (message.kind === "session_update") {
|
|
3232
3389
|
const captured = streamCapture?.takeForUpdate({ update: message.update });
|
|
3390
|
+
hostToolAuthorization.observeUpdate({ update: message.update });
|
|
3233
3391
|
for (const rawValue of captured?.precedingRawValues ?? []) emitStreamEvent.raw({ rawValue });
|
|
3234
3392
|
emitStreamEvent.message({
|
|
3235
3393
|
message,
|
|
@@ -3241,6 +3399,7 @@ async function runTurn(start, turn) {
|
|
|
3241
3399
|
if (emitStreamEvent.message({ message })) return;
|
|
3242
3400
|
}
|
|
3243
3401
|
} finally {
|
|
3402
|
+
hostToolAuthorization.close();
|
|
3244
3403
|
if (activeQuestionRequest?.turn === turn) activeQuestionRequest = void 0;
|
|
3245
3404
|
permissionController.cancelAll();
|
|
3246
3405
|
activePermissionController = void 0;
|