@ai-sdk/harness-acp 1.0.70 → 1.0.72
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 +20 -0
- package/dist/bridge/host-tool-mcp.mjs +203 -281
- package/dist/bridge/host-tool-mcp.mjs.map +1 -1
- package/dist/bridge/index.mjs +3543 -4271
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +298 -292
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2165 -2946
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/v1/acp-v1-harness.ts +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @ai-sdk/harness-acp
|
|
2
2
|
|
|
3
|
+
## 1.0.72
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ede5b89: chore: migrate package builds from tsup to tsdown
|
|
8
|
+
- Updated dependencies [ede5b89]
|
|
9
|
+
- @ai-sdk/harness@1.0.134
|
|
10
|
+
- @ai-sdk/provider-utils@5.0.52
|
|
11
|
+
|
|
12
|
+
## 1.0.71
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- b2049fa: fix(harness): expose `resolveSandboxCredentialEnvironment()` helper in favor of `createSandboxCredentialEnvironment()` and use it across bridge backed adapters
|
|
17
|
+
- Updated dependencies [b2049fa]
|
|
18
|
+
- Updated dependencies [11f0e71]
|
|
19
|
+
- Updated dependencies [2b9195b]
|
|
20
|
+
- Updated dependencies [446725d]
|
|
21
|
+
- @ai-sdk/harness@1.0.133
|
|
22
|
+
|
|
3
23
|
## 1.0.70
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -1,302 +1,224 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { env } from "process";
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { env } from "node:process";
|
|
5
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
|
|
7
|
-
// src/v1/bridge/host-tool-mcp-server.ts
|
|
8
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
return {
|
|
63
|
-
server,
|
|
64
|
-
updateCatalog: async ({ revision: nextRevision, tools: nextTools }) => {
|
|
65
|
-
catalog = createCatalog({
|
|
66
|
-
revision: nextRevision,
|
|
67
|
-
tools: nextTools
|
|
68
|
-
});
|
|
69
|
-
await server.sendToolListChanged();
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function createCatalog({
|
|
74
|
-
revision,
|
|
75
|
-
tools: tools2
|
|
76
|
-
}) {
|
|
77
|
-
return {
|
|
78
|
-
revision,
|
|
79
|
-
tools: [...tools2],
|
|
80
|
-
byName: new Map(tools2.map((tool) => [tool.name, tool]))
|
|
81
|
-
};
|
|
6
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { Agent, request } from "node:http";
|
|
8
|
+
//#region src/v1/bridge/host-tool-mcp-server.ts
|
|
9
|
+
const VERSION = "1.0.72";
|
|
10
|
+
function createHostToolMCPServer({ tools, revision = 1, invoke, onListTools }) {
|
|
11
|
+
let catalog = createCatalog({
|
|
12
|
+
revision,
|
|
13
|
+
tools
|
|
14
|
+
});
|
|
15
|
+
let catalogAcknowledgmentError;
|
|
16
|
+
const server = new Server({
|
|
17
|
+
name: "@ai-sdk/harness-acp-host-tools",
|
|
18
|
+
version: VERSION
|
|
19
|
+
}, { capabilities: { tools: { listChanged: true } } });
|
|
20
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
21
|
+
if (catalogAcknowledgmentError != null) throw catalogAcknowledgmentError;
|
|
22
|
+
const current = catalog;
|
|
23
|
+
if (onListTools != null) setImmediate(() => {
|
|
24
|
+
onListTools({ revision: current.revision }).catch((error) => {
|
|
25
|
+
catalogAcknowledgmentError = error;
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
return { tools: current.tools.map(toMCPTool) };
|
|
29
|
+
});
|
|
30
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
31
|
+
const current = catalog;
|
|
32
|
+
const tool = current.byName.get(request.params.name);
|
|
33
|
+
if (tool == null) throw new McpError(ErrorCode.InvalidParams, `Unknown host tool: ${request.params.name}`);
|
|
34
|
+
const input = request.params.arguments ?? {};
|
|
35
|
+
return toCallToolResult({ result: await invoke({
|
|
36
|
+
toolName: tool.name,
|
|
37
|
+
input,
|
|
38
|
+
catalogRevision: current.revision
|
|
39
|
+
}) });
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
server,
|
|
43
|
+
updateCatalog: async ({ revision: nextRevision, tools: nextTools }) => {
|
|
44
|
+
catalog = createCatalog({
|
|
45
|
+
revision: nextRevision,
|
|
46
|
+
tools: nextTools
|
|
47
|
+
});
|
|
48
|
+
await server.sendToolListChanged();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createCatalog({ revision, tools }) {
|
|
53
|
+
return {
|
|
54
|
+
revision,
|
|
55
|
+
tools: [...tools],
|
|
56
|
+
byName: new Map(tools.map((tool) => [tool.name, tool]))
|
|
57
|
+
};
|
|
82
58
|
}
|
|
83
59
|
function toMCPTool(tool) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
function requireObjectSchema({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}) {
|
|
107
|
-
return {
|
|
108
|
-
content: [
|
|
109
|
-
{
|
|
110
|
-
type: "text",
|
|
111
|
-
text: stringifyOutput({ output: result.output })
|
|
112
|
-
}
|
|
113
|
-
],
|
|
114
|
-
...result.isError ? { isError: true } : {},
|
|
115
|
-
_meta: {
|
|
116
|
-
"ai-sdk-harness-acp-correlation": result.correlationToken
|
|
117
|
-
}
|
|
118
|
-
};
|
|
60
|
+
return {
|
|
61
|
+
name: tool.name,
|
|
62
|
+
...tool.description == null ? {} : { description: tool.description },
|
|
63
|
+
inputSchema: requireObjectSchema({
|
|
64
|
+
toolName: tool.name,
|
|
65
|
+
schema: tool.inputSchema ?? { type: "object" }
|
|
66
|
+
})
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function requireObjectSchema({ toolName, schema }) {
|
|
70
|
+
if (schema == null || typeof schema !== "object" || Array.isArray(schema) || Reflect.get(schema, "type") !== void 0 && Reflect.get(schema, "type") !== "object") throw new Error(`Host tool ${toolName} must use an object JSON Schema for MCP.`);
|
|
71
|
+
return schema;
|
|
72
|
+
}
|
|
73
|
+
function toCallToolResult({ result }) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: stringifyOutput({ output: result.output })
|
|
78
|
+
}],
|
|
79
|
+
...result.isError ? { isError: true } : {},
|
|
80
|
+
_meta: { "ai-sdk-harness-acp-correlation": result.correlationToken }
|
|
81
|
+
};
|
|
119
82
|
}
|
|
120
83
|
function stringifyOutput({ output }) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
var relayAgent = new Agent({ keepAlive: true, timeout: 0 });
|
|
134
|
-
async function postHostToolRelay({
|
|
135
|
-
relayUrl: relayUrl2,
|
|
136
|
-
relayCredential: relayCredential2,
|
|
137
|
-
path,
|
|
138
|
-
body
|
|
139
|
-
}) {
|
|
140
|
-
const serializedBody = JSON.stringify(body);
|
|
141
|
-
return new Promise((resolve, reject) => {
|
|
142
|
-
const relayRequest = request(
|
|
143
|
-
new URL(path, relayUrl2),
|
|
144
|
-
{
|
|
145
|
-
agent: relayAgent,
|
|
146
|
-
method: "POST",
|
|
147
|
-
headers: {
|
|
148
|
-
authorization: `Bearer ${relayCredential2}`,
|
|
149
|
-
"content-type": "application/json",
|
|
150
|
-
"content-length": Buffer.byteLength(serializedBody)
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
(response) => {
|
|
154
|
-
const chunks = [];
|
|
155
|
-
response.on("data", (chunk) => chunks.push(chunk));
|
|
156
|
-
response.on("error", reject);
|
|
157
|
-
response.on("end", () => {
|
|
158
|
-
void new Response(Buffer.concat(chunks)).json().then((value) => {
|
|
159
|
-
const status = response.statusCode ?? 0;
|
|
160
|
-
resolve({
|
|
161
|
-
status,
|
|
162
|
-
ok: status >= 200 && status < 300,
|
|
163
|
-
value
|
|
164
|
-
});
|
|
165
|
-
}, reject);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
);
|
|
169
|
-
relayRequest.on("error", reject);
|
|
170
|
-
relayRequest.end(serializedBody);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// src/v1/bridge/host-tool-mcp.ts
|
|
175
|
-
var catalogPath = requireEnvironmentVariable({
|
|
176
|
-
name: "AI_SDK_ACP_HOST_TOOLS_FILE"
|
|
177
|
-
});
|
|
178
|
-
var relayUrl = requireEnvironmentVariable({
|
|
179
|
-
name: "AI_SDK_ACP_HOST_TOOL_RELAY_URL"
|
|
84
|
+
if (output === void 0) return "null";
|
|
85
|
+
try {
|
|
86
|
+
return JSON.stringify(output);
|
|
87
|
+
} catch {
|
|
88
|
+
return JSON.stringify({ error: "Host tool output could not be serialized as JSON." });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/v1/bridge/host-tool-relay-client.ts
|
|
93
|
+
const relayAgent = new Agent({
|
|
94
|
+
keepAlive: true,
|
|
95
|
+
timeout: 0
|
|
180
96
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
97
|
+
async function postHostToolRelay({ relayUrl, relayCredential, path, body }) {
|
|
98
|
+
const serializedBody = JSON.stringify(body);
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const relayRequest = request(new URL(path, relayUrl), {
|
|
101
|
+
agent: relayAgent,
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: {
|
|
104
|
+
authorization: `Bearer ${relayCredential}`,
|
|
105
|
+
"content-type": "application/json",
|
|
106
|
+
"content-length": Buffer.byteLength(serializedBody)
|
|
107
|
+
}
|
|
108
|
+
}, (response) => {
|
|
109
|
+
const chunks = [];
|
|
110
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
111
|
+
response.on("error", reject);
|
|
112
|
+
response.on("end", () => {
|
|
113
|
+
new Response(Buffer.concat(chunks)).json().then((value) => {
|
|
114
|
+
const status = response.statusCode ?? 0;
|
|
115
|
+
resolve({
|
|
116
|
+
status,
|
|
117
|
+
ok: status >= 200 && status < 300,
|
|
118
|
+
value
|
|
119
|
+
});
|
|
120
|
+
}, reject);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
relayRequest.on("error", reject);
|
|
124
|
+
relayRequest.end(serializedBody);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/v1/bridge/host-tool-mcp.ts
|
|
129
|
+
const catalogPath = requireEnvironmentVariable({ name: "AI_SDK_ACP_HOST_TOOLS_FILE" });
|
|
130
|
+
const relayUrl = requireEnvironmentVariable({ name: "AI_SDK_ACP_HOST_TOOL_RELAY_URL" });
|
|
131
|
+
const relayCredential = requireEnvironmentVariable({ name: "AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL" });
|
|
132
|
+
const hostToolServer = createHostToolMCPServer({
|
|
133
|
+
tools: await readToolCatalog({ path: catalogPath }),
|
|
134
|
+
invoke: async ({ toolName, input, catalogRevision }) => validateInvocationResult({ value: await postRelay({
|
|
135
|
+
path: "/invoke",
|
|
136
|
+
body: {
|
|
137
|
+
requestId: randomUUID(),
|
|
138
|
+
toolName,
|
|
139
|
+
input,
|
|
140
|
+
catalogRevision
|
|
141
|
+
}
|
|
142
|
+
}) }),
|
|
143
|
+
onListTools: async ({ revision }) => {
|
|
144
|
+
await postRelay({
|
|
145
|
+
path: "/catalog/seen",
|
|
146
|
+
body: { revision }
|
|
147
|
+
});
|
|
148
|
+
}
|
|
204
149
|
});
|
|
205
150
|
await hostToolServer.server.connect(new StdioServerTransport());
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
151
|
+
watchCatalog({
|
|
152
|
+
initialRevision: 1,
|
|
153
|
+
updateCatalog: hostToolServer.updateCatalog
|
|
209
154
|
}).catch((error) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
214
|
-
void hostToolServer.server.close();
|
|
215
|
-
process.exitCode = 1;
|
|
155
|
+
process.stderr.write(`Host tool catalog synchronization failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
156
|
+
hostToolServer.server.close();
|
|
157
|
+
process.exitCode = 1;
|
|
216
158
|
});
|
|
217
|
-
async function watchCatalog({
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}) {
|
|
257
|
-
if (!Array.isArray(value) || !value.every(isTool)) {
|
|
258
|
-
throw new Error("Invalid host tool catalog.");
|
|
259
|
-
}
|
|
260
|
-
return value;
|
|
261
|
-
}
|
|
262
|
-
async function readToolCatalog({
|
|
263
|
-
path
|
|
264
|
-
}) {
|
|
265
|
-
const text = await readFile(path, "utf8");
|
|
266
|
-
const value = await new Response(text, {
|
|
267
|
-
headers: { "content-type": "application/json" }
|
|
268
|
-
}).json();
|
|
269
|
-
return validateToolCatalog({ value });
|
|
159
|
+
async function watchCatalog({ initialRevision, updateCatalog }) {
|
|
160
|
+
let revision = initialRevision;
|
|
161
|
+
for (;;) {
|
|
162
|
+
const value = await postRelay({
|
|
163
|
+
path: "/catalog/next",
|
|
164
|
+
body: { afterRevision: revision }
|
|
165
|
+
});
|
|
166
|
+
if (isRecord(value) && value.closed === true) return;
|
|
167
|
+
if (!isRecord(value) || !Number.isSafeInteger(value.revision) || value.revision < revision) throw new Error("Invalid host tool catalog poll response.");
|
|
168
|
+
const nextRevision = value.revision;
|
|
169
|
+
if (nextRevision === revision) continue;
|
|
170
|
+
await updateCatalog({
|
|
171
|
+
revision: nextRevision,
|
|
172
|
+
tools: validateToolCatalog({ value: value.tools })
|
|
173
|
+
});
|
|
174
|
+
revision = nextRevision;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function postRelay({ path, body }) {
|
|
178
|
+
const response = await postHostToolRelay({
|
|
179
|
+
relayUrl,
|
|
180
|
+
relayCredential,
|
|
181
|
+
path,
|
|
182
|
+
body
|
|
183
|
+
});
|
|
184
|
+
const { value } = response;
|
|
185
|
+
if (!response.ok) throw new Error(readErrorMessage({
|
|
186
|
+
value,
|
|
187
|
+
status: response.status
|
|
188
|
+
}));
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
function validateToolCatalog({ value }) {
|
|
192
|
+
if (!Array.isArray(value) || !value.every(isTool)) throw new Error("Invalid host tool catalog.");
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
async function readToolCatalog({ path }) {
|
|
196
|
+
const text = await readFile(path, "utf8");
|
|
197
|
+
return validateToolCatalog({ value: await new Response(text, { headers: { "content-type": "application/json" } }).json() });
|
|
270
198
|
}
|
|
271
199
|
function isTool(value) {
|
|
272
|
-
|
|
200
|
+
return isRecord(value) && typeof value.name === "string" && (value.description === void 0 || typeof value.description === "string") && (value.inputSchema === void 0 || isRecord(value.inputSchema) && !Array.isArray(value.inputSchema));
|
|
273
201
|
}
|
|
274
|
-
function validateInvocationResult({
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
output: value.output,
|
|
282
|
-
...value.isError ? { isError: true } : {},
|
|
283
|
-
correlationToken: value.correlationToken
|
|
284
|
-
};
|
|
202
|
+
function validateInvocationResult({ value }) {
|
|
203
|
+
if (!isRecord(value) || typeof value.correlationToken !== "string" || value.isError !== void 0 && typeof value.isError !== "boolean") throw new Error("Invalid host tool relay response.");
|
|
204
|
+
return {
|
|
205
|
+
output: value.output,
|
|
206
|
+
...value.isError ? { isError: true } : {},
|
|
207
|
+
correlationToken: value.correlationToken
|
|
208
|
+
};
|
|
285
209
|
}
|
|
286
|
-
function readErrorMessage({
|
|
287
|
-
|
|
288
|
-
status
|
|
289
|
-
}) {
|
|
290
|
-
return isRecord(value) && typeof value.error === "string" ? value.error : `Host tool relay returned HTTP ${status}.`;
|
|
210
|
+
function readErrorMessage({ value, status }) {
|
|
211
|
+
return isRecord(value) && typeof value.error === "string" ? value.error : `Host tool relay returned HTTP ${status}.`;
|
|
291
212
|
}
|
|
292
213
|
function requireEnvironmentVariable({ name }) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
return value;
|
|
214
|
+
const value = env[name];
|
|
215
|
+
if (value == null || value.length === 0) throw new Error(`Missing ${name}.`);
|
|
216
|
+
return value;
|
|
298
217
|
}
|
|
299
218
|
function isRecord(value) {
|
|
300
|
-
|
|
219
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
301
220
|
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export {};
|
|
223
|
+
|
|
302
224
|
//# sourceMappingURL=host-tool-mcp.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/v1/bridge/host-tool-mcp.ts","../../src/v1/bridge/host-tool-mcp-server.ts","../../src/v1/bridge/host-tool-relay-client.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { env } from 'node:process';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { HarnessV1BridgeToolWire } from '@ai-sdk/harness';\nimport {\n createHostToolMCPServer,\n type HostToolMCPInvocationResult,\n} from './host-tool-mcp-server';\nimport { postHostToolRelay } from './host-tool-relay-client';\n\nconst catalogPath = requireEnvironmentVariable({\n name: 'AI_SDK_ACP_HOST_TOOLS_FILE',\n});\nconst relayUrl = requireEnvironmentVariable({\n name: 'AI_SDK_ACP_HOST_TOOL_RELAY_URL',\n});\nconst relayCredential = requireEnvironmentVariable({\n name: 'AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL',\n});\nconst tools = await readToolCatalog({ path: catalogPath });\nconst hostToolServer = createHostToolMCPServer({\n tools,\n invoke: async ({ toolName, input, catalogRevision }) =>\n validateInvocationResult({\n value: await postRelay({\n path: '/invoke',\n body: {\n requestId: randomUUID(),\n toolName,\n input,\n catalogRevision,\n },\n }),\n }),\n onListTools: async ({ revision }) => {\n await postRelay({\n path: '/catalog/seen',\n body: { revision },\n });\n },\n});\n\nawait hostToolServer.server.connect(new StdioServerTransport());\nvoid watchCatalog({\n initialRevision: 1,\n updateCatalog: hostToolServer.updateCatalog,\n}).catch(error => {\n process.stderr.write(\n `Host tool catalog synchronization failed: ${\n error instanceof Error ? error.message : String(error)\n }\\n`,\n );\n void hostToolServer.server.close();\n process.exitCode = 1;\n});\n\nasync function watchCatalog({\n initialRevision,\n updateCatalog,\n}: {\n initialRevision: number;\n updateCatalog: (options: {\n revision: number;\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n }) => Promise<void>;\n}): Promise<void> {\n let revision = initialRevision;\n for (;;) {\n const value = await postRelay({\n path: '/catalog/next',\n body: { afterRevision: revision },\n });\n if (isRecord(value) && value.closed === true) return;\n if (\n !isRecord(value) ||\n !Number.isSafeInteger(value.revision) ||\n (value.revision as number) < revision\n ) {\n throw new Error('Invalid host tool catalog poll response.');\n }\n const nextRevision = value.revision as number;\n if (nextRevision === revision) continue;\n const nextTools = validateToolCatalog({ value: value.tools });\n await updateCatalog({ revision: nextRevision, tools: nextTools });\n revision = nextRevision;\n }\n}\n\nasync function postRelay({\n path,\n body,\n}: {\n path: string;\n body: Readonly<Record<string, unknown>>;\n}): Promise<unknown> {\n const response = await postHostToolRelay({\n relayUrl,\n relayCredential,\n path,\n body,\n });\n const { value } = response;\n if (!response.ok) {\n throw new Error(readErrorMessage({ value, status: response.status }));\n }\n return value;\n}\n\nfunction validateToolCatalog({\n value,\n}: {\n value: unknown;\n}): ReadonlyArray<HarnessV1BridgeToolWire> {\n if (!Array.isArray(value) || !value.every(isTool)) {\n throw new Error('Invalid host tool catalog.');\n }\n return value;\n}\n\nasync function readToolCatalog({\n path,\n}: {\n path: string;\n}): Promise<ReadonlyArray<HarnessV1BridgeToolWire>> {\n const text = await readFile(path, 'utf8');\n const value = await new Response(text, {\n headers: { 'content-type': 'application/json' },\n }).json();\n return validateToolCatalog({ value });\n}\n\nfunction isTool(value: unknown): value is HarnessV1BridgeToolWire {\n return (\n isRecord(value) &&\n typeof value.name === 'string' &&\n (value.description === undefined ||\n typeof value.description === 'string') &&\n (value.inputSchema === undefined ||\n (isRecord(value.inputSchema) && !Array.isArray(value.inputSchema)))\n );\n}\n\nfunction validateInvocationResult({\n value,\n}: {\n value: unknown;\n}): HostToolMCPInvocationResult {\n if (\n !isRecord(value) ||\n typeof value.correlationToken !== 'string' ||\n (value.isError !== undefined && typeof value.isError !== 'boolean')\n ) {\n throw new Error('Invalid host tool relay response.');\n }\n return {\n output: value.output,\n ...(value.isError ? { isError: true } : {}),\n correlationToken: value.correlationToken,\n };\n}\n\nfunction readErrorMessage({\n value,\n status,\n}: {\n value: unknown;\n status: number;\n}): string {\n return isRecord(value) && typeof value.error === 'string'\n ? value.error\n : `Host tool relay returned HTTP ${status}.`;\n}\n\nfunction requireEnvironmentVariable({ name }: { name: string }): string {\n const value = env[name];\n if (value == null || value.length === 0) {\n throw new Error(`Missing ${name}.`);\n }\n return value;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n type CallToolResult,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport type { HarnessV1BridgeToolWire } from '@ai-sdk/harness';\n\ndeclare const __PACKAGE_VERSION__: string | undefined;\n\nconst VERSION: string =\n typeof __PACKAGE_VERSION__ !== 'undefined'\n ? __PACKAGE_VERSION__\n : '0.0.0-test';\n\nexport type HostToolMCPInvocationResult = {\n readonly output: unknown;\n readonly isError?: boolean;\n readonly correlationToken: string;\n};\n\nexport type HostToolMCPServer = {\n readonly server: Server;\n updateCatalog(options: {\n revision: number;\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n }): Promise<void>;\n};\n\nexport function createHostToolMCPServer({\n tools,\n revision = 1,\n invoke,\n onListTools,\n}: {\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n revision?: number;\n invoke: (options: {\n toolName: string;\n input: Readonly<Record<string, unknown>>;\n catalogRevision: number;\n }) => Promise<HostToolMCPInvocationResult>;\n onListTools?: (options: { revision: number }) => Promise<void>;\n}): HostToolMCPServer {\n let catalog = createCatalog({ revision, tools });\n let catalogAcknowledgmentError: unknown;\n const server = new Server(\n {\n name: '@ai-sdk/harness-acp-host-tools',\n version: VERSION,\n },\n { capabilities: { tools: { listChanged: true } } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n if (catalogAcknowledgmentError != null) {\n throw catalogAcknowledgmentError;\n }\n const current = catalog;\n if (onListTools != null) {\n setImmediate(() => {\n void onListTools({ revision: current.revision }).catch(error => {\n catalogAcknowledgmentError = error;\n });\n });\n }\n return { tools: current.tools.map(toMCPTool) };\n });\n server.setRequestHandler(CallToolRequestSchema, async request => {\n const current = catalog;\n const tool = current.byName.get(request.params.name);\n if (tool == null) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown host tool: ${request.params.name}`,\n );\n }\n const input = request.params.arguments ?? {};\n const result = await invoke({\n toolName: tool.name,\n input,\n catalogRevision: current.revision,\n });\n return toCallToolResult({ result });\n });\n\n return {\n server,\n updateCatalog: async ({ revision: nextRevision, tools: nextTools }) => {\n catalog = createCatalog({\n revision: nextRevision,\n tools: nextTools,\n });\n await server.sendToolListChanged();\n },\n };\n}\n\nfunction createCatalog({\n revision,\n tools,\n}: {\n revision: number;\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n}): {\n readonly revision: number;\n readonly tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n readonly byName: ReadonlyMap<string, HarnessV1BridgeToolWire>;\n} {\n return {\n revision,\n tools: [...tools],\n byName: new Map(tools.map(tool => [tool.name, tool])),\n };\n}\n\nfunction toMCPTool(tool: HarnessV1BridgeToolWire): Tool {\n return {\n name: tool.name,\n ...(tool.description == null ? {} : { description: tool.description }),\n inputSchema: requireObjectSchema({\n toolName: tool.name,\n schema: tool.inputSchema ?? { type: 'object' },\n }),\n };\n}\n\nfunction requireObjectSchema({\n toolName,\n schema,\n}: {\n toolName: string;\n schema: unknown;\n}): Tool['inputSchema'] {\n if (\n schema == null ||\n typeof schema !== 'object' ||\n Array.isArray(schema) ||\n (Reflect.get(schema, 'type') !== undefined &&\n Reflect.get(schema, 'type') !== 'object')\n ) {\n throw new Error(\n `Host tool ${toolName} must use an object JSON Schema for MCP.`,\n );\n }\n return schema as Tool['inputSchema'];\n}\n\nfunction toCallToolResult({\n result,\n}: {\n result: HostToolMCPInvocationResult;\n}): CallToolResult {\n return {\n content: [\n {\n type: 'text',\n text: stringifyOutput({ output: result.output }),\n },\n ],\n ...(result.isError ? { isError: true } : {}),\n _meta: {\n 'ai-sdk-harness-acp-correlation': result.correlationToken,\n },\n };\n}\n\nfunction stringifyOutput({ output }: { output: unknown }): string {\n if (output === undefined) return 'null';\n try {\n return JSON.stringify(output);\n } catch {\n return JSON.stringify({\n error: 'Host tool output could not be serialized as JSON.',\n });\n }\n}\n","import { Agent, request } from 'node:http';\n\n/*\n * A host-tool call can remain pending while a person reviews an approval.\n * The loopback connection therefore must not impose an inactivity deadline.\n */\nconst relayAgent = new Agent({ keepAlive: true, timeout: 0 });\n\nexport async function postHostToolRelay({\n relayUrl,\n relayCredential,\n path,\n body,\n}: {\n relayUrl: string;\n relayCredential: string;\n path: string;\n body: Readonly<Record<string, unknown>>;\n}): Promise<{ status: number; ok: boolean; value: unknown }> {\n const serializedBody = JSON.stringify(body);\n\n return new Promise((resolve, reject) => {\n const relayRequest = request(\n new URL(path, relayUrl),\n {\n agent: relayAgent,\n method: 'POST',\n headers: {\n authorization: `Bearer ${relayCredential}`,\n 'content-type': 'application/json',\n 'content-length': Buffer.byteLength(serializedBody),\n },\n },\n response => {\n const chunks: Buffer[] = [];\n response.on('data', (chunk: Buffer) => chunks.push(chunk));\n response.on('error', reject);\n response.on('end', () => {\n void new Response(Buffer.concat(chunks)).json().then(value => {\n const status = response.statusCode ?? 0;\n resolve({\n status,\n ok: status >= 200 && status < 300,\n value,\n });\n }, reject);\n });\n },\n );\n relayRequest.on('error', reject);\n relayRequest.end(serializedBody);\n });\n}\n"],"mappings":"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","names":["tools","request","relayUrl","relayCredential"]}
|
|
1
|
+
{"version":3,"file":"host-tool-mcp.mjs","names":[],"sources":["../../src/v1/bridge/host-tool-mcp-server.ts","../../src/v1/bridge/host-tool-relay-client.ts","../../src/v1/bridge/host-tool-mcp.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n type CallToolResult,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport type { HarnessV1BridgeToolWire } from '@ai-sdk/harness';\n\ndeclare const __PACKAGE_VERSION__: string | undefined;\n\nconst VERSION: string =\n typeof __PACKAGE_VERSION__ !== 'undefined'\n ? __PACKAGE_VERSION__\n : '0.0.0-test';\n\nexport type HostToolMCPInvocationResult = {\n readonly output: unknown;\n readonly isError?: boolean;\n readonly correlationToken: string;\n};\n\nexport type HostToolMCPServer = {\n readonly server: Server;\n updateCatalog(options: {\n revision: number;\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n }): Promise<void>;\n};\n\nexport function createHostToolMCPServer({\n tools,\n revision = 1,\n invoke,\n onListTools,\n}: {\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n revision?: number;\n invoke: (options: {\n toolName: string;\n input: Readonly<Record<string, unknown>>;\n catalogRevision: number;\n }) => Promise<HostToolMCPInvocationResult>;\n onListTools?: (options: { revision: number }) => Promise<void>;\n}): HostToolMCPServer {\n let catalog = createCatalog({ revision, tools });\n let catalogAcknowledgmentError: unknown;\n const server = new Server(\n {\n name: '@ai-sdk/harness-acp-host-tools',\n version: VERSION,\n },\n { capabilities: { tools: { listChanged: true } } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n if (catalogAcknowledgmentError != null) {\n throw catalogAcknowledgmentError;\n }\n const current = catalog;\n if (onListTools != null) {\n setImmediate(() => {\n void onListTools({ revision: current.revision }).catch(error => {\n catalogAcknowledgmentError = error;\n });\n });\n }\n return { tools: current.tools.map(toMCPTool) };\n });\n server.setRequestHandler(CallToolRequestSchema, async request => {\n const current = catalog;\n const tool = current.byName.get(request.params.name);\n if (tool == null) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown host tool: ${request.params.name}`,\n );\n }\n const input = request.params.arguments ?? {};\n const result = await invoke({\n toolName: tool.name,\n input,\n catalogRevision: current.revision,\n });\n return toCallToolResult({ result });\n });\n\n return {\n server,\n updateCatalog: async ({ revision: nextRevision, tools: nextTools }) => {\n catalog = createCatalog({\n revision: nextRevision,\n tools: nextTools,\n });\n await server.sendToolListChanged();\n },\n };\n}\n\nfunction createCatalog({\n revision,\n tools,\n}: {\n revision: number;\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n}): {\n readonly revision: number;\n readonly tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n readonly byName: ReadonlyMap<string, HarnessV1BridgeToolWire>;\n} {\n return {\n revision,\n tools: [...tools],\n byName: new Map(tools.map(tool => [tool.name, tool])),\n };\n}\n\nfunction toMCPTool(tool: HarnessV1BridgeToolWire): Tool {\n return {\n name: tool.name,\n ...(tool.description == null ? {} : { description: tool.description }),\n inputSchema: requireObjectSchema({\n toolName: tool.name,\n schema: tool.inputSchema ?? { type: 'object' },\n }),\n };\n}\n\nfunction requireObjectSchema({\n toolName,\n schema,\n}: {\n toolName: string;\n schema: unknown;\n}): Tool['inputSchema'] {\n if (\n schema == null ||\n typeof schema !== 'object' ||\n Array.isArray(schema) ||\n (Reflect.get(schema, 'type') !== undefined &&\n Reflect.get(schema, 'type') !== 'object')\n ) {\n throw new Error(\n `Host tool ${toolName} must use an object JSON Schema for MCP.`,\n );\n }\n return schema as Tool['inputSchema'];\n}\n\nfunction toCallToolResult({\n result,\n}: {\n result: HostToolMCPInvocationResult;\n}): CallToolResult {\n return {\n content: [\n {\n type: 'text',\n text: stringifyOutput({ output: result.output }),\n },\n ],\n ...(result.isError ? { isError: true } : {}),\n _meta: {\n 'ai-sdk-harness-acp-correlation': result.correlationToken,\n },\n };\n}\n\nfunction stringifyOutput({ output }: { output: unknown }): string {\n if (output === undefined) return 'null';\n try {\n return JSON.stringify(output);\n } catch {\n return JSON.stringify({\n error: 'Host tool output could not be serialized as JSON.',\n });\n }\n}\n","import { Agent, request } from 'node:http';\n\n/*\n * A host-tool call can remain pending while a person reviews an approval.\n * The loopback connection therefore must not impose an inactivity deadline.\n */\nconst relayAgent = new Agent({ keepAlive: true, timeout: 0 });\n\nexport async function postHostToolRelay({\n relayUrl,\n relayCredential,\n path,\n body,\n}: {\n relayUrl: string;\n relayCredential: string;\n path: string;\n body: Readonly<Record<string, unknown>>;\n}): Promise<{ status: number; ok: boolean; value: unknown }> {\n const serializedBody = JSON.stringify(body);\n\n return new Promise((resolve, reject) => {\n const relayRequest = request(\n new URL(path, relayUrl),\n {\n agent: relayAgent,\n method: 'POST',\n headers: {\n authorization: `Bearer ${relayCredential}`,\n 'content-type': 'application/json',\n 'content-length': Buffer.byteLength(serializedBody),\n },\n },\n response => {\n const chunks: Buffer[] = [];\n response.on('data', (chunk: Buffer) => chunks.push(chunk));\n response.on('error', reject);\n response.on('end', () => {\n void new Response(Buffer.concat(chunks)).json().then(value => {\n const status = response.statusCode ?? 0;\n resolve({\n status,\n ok: status >= 200 && status < 300,\n value,\n });\n }, reject);\n });\n },\n );\n relayRequest.on('error', reject);\n relayRequest.end(serializedBody);\n });\n}\n","import { randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { env } from 'node:process';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { HarnessV1BridgeToolWire } from '@ai-sdk/harness';\nimport {\n createHostToolMCPServer,\n type HostToolMCPInvocationResult,\n} from './host-tool-mcp-server';\nimport { postHostToolRelay } from './host-tool-relay-client';\n\nconst catalogPath = requireEnvironmentVariable({\n name: 'AI_SDK_ACP_HOST_TOOLS_FILE',\n});\nconst relayUrl = requireEnvironmentVariable({\n name: 'AI_SDK_ACP_HOST_TOOL_RELAY_URL',\n});\nconst relayCredential = requireEnvironmentVariable({\n name: 'AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL',\n});\nconst tools = await readToolCatalog({ path: catalogPath });\nconst hostToolServer = createHostToolMCPServer({\n tools,\n invoke: async ({ toolName, input, catalogRevision }) =>\n validateInvocationResult({\n value: await postRelay({\n path: '/invoke',\n body: {\n requestId: randomUUID(),\n toolName,\n input,\n catalogRevision,\n },\n }),\n }),\n onListTools: async ({ revision }) => {\n await postRelay({\n path: '/catalog/seen',\n body: { revision },\n });\n },\n});\n\nawait hostToolServer.server.connect(new StdioServerTransport());\nvoid watchCatalog({\n initialRevision: 1,\n updateCatalog: hostToolServer.updateCatalog,\n}).catch(error => {\n process.stderr.write(\n `Host tool catalog synchronization failed: ${\n error instanceof Error ? error.message : String(error)\n }\\n`,\n );\n void hostToolServer.server.close();\n process.exitCode = 1;\n});\n\nasync function watchCatalog({\n initialRevision,\n updateCatalog,\n}: {\n initialRevision: number;\n updateCatalog: (options: {\n revision: number;\n tools: ReadonlyArray<HarnessV1BridgeToolWire>;\n }) => Promise<void>;\n}): Promise<void> {\n let revision = initialRevision;\n for (;;) {\n const value = await postRelay({\n path: '/catalog/next',\n body: { afterRevision: revision },\n });\n if (isRecord(value) && value.closed === true) return;\n if (\n !isRecord(value) ||\n !Number.isSafeInteger(value.revision) ||\n (value.revision as number) < revision\n ) {\n throw new Error('Invalid host tool catalog poll response.');\n }\n const nextRevision = value.revision as number;\n if (nextRevision === revision) continue;\n const nextTools = validateToolCatalog({ value: value.tools });\n await updateCatalog({ revision: nextRevision, tools: nextTools });\n revision = nextRevision;\n }\n}\n\nasync function postRelay({\n path,\n body,\n}: {\n path: string;\n body: Readonly<Record<string, unknown>>;\n}): Promise<unknown> {\n const response = await postHostToolRelay({\n relayUrl,\n relayCredential,\n path,\n body,\n });\n const { value } = response;\n if (!response.ok) {\n throw new Error(readErrorMessage({ value, status: response.status }));\n }\n return value;\n}\n\nfunction validateToolCatalog({\n value,\n}: {\n value: unknown;\n}): ReadonlyArray<HarnessV1BridgeToolWire> {\n if (!Array.isArray(value) || !value.every(isTool)) {\n throw new Error('Invalid host tool catalog.');\n }\n return value;\n}\n\nasync function readToolCatalog({\n path,\n}: {\n path: string;\n}): Promise<ReadonlyArray<HarnessV1BridgeToolWire>> {\n const text = await readFile(path, 'utf8');\n const value = await new Response(text, {\n headers: { 'content-type': 'application/json' },\n }).json();\n return validateToolCatalog({ value });\n}\n\nfunction isTool(value: unknown): value is HarnessV1BridgeToolWire {\n return (\n isRecord(value) &&\n typeof value.name === 'string' &&\n (value.description === undefined ||\n typeof value.description === 'string') &&\n (value.inputSchema === undefined ||\n (isRecord(value.inputSchema) && !Array.isArray(value.inputSchema)))\n );\n}\n\nfunction validateInvocationResult({\n value,\n}: {\n value: unknown;\n}): HostToolMCPInvocationResult {\n if (\n !isRecord(value) ||\n typeof value.correlationToken !== 'string' ||\n (value.isError !== undefined && typeof value.isError !== 'boolean')\n ) {\n throw new Error('Invalid host tool relay response.');\n }\n return {\n output: value.output,\n ...(value.isError ? { isError: true } : {}),\n correlationToken: value.correlationToken,\n };\n}\n\nfunction readErrorMessage({\n value,\n status,\n}: {\n value: unknown;\n status: number;\n}): string {\n return isRecord(value) && typeof value.error === 'string'\n ? value.error\n : `Host tool relay returned HTTP ${status}.`;\n}\n\nfunction requireEnvironmentVariable({ name }: { name: string }): string {\n const value = env[name];\n if (value == null || value.length === 0) {\n throw new Error(`Missing ${name}.`);\n }\n return value;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return value != null && typeof value === 'object' && !Array.isArray(value);\n}\n"],"mappings":"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"}
|