@ai-sdk/harness-acp 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +13 -0
  3. package/README.md +70 -3
  4. package/dist/bridge/host-tool-mcp.mjs +302 -0
  5. package/dist/bridge/host-tool-mcp.mjs.map +1 -0
  6. package/dist/bridge/index.mjs +3278 -0
  7. package/dist/bridge/index.mjs.map +1 -0
  8. package/dist/bridge/package.json +12 -0
  9. package/dist/bridge/pnpm-lock.yaml +816 -0
  10. package/dist/index.d.ts +98 -0
  11. package/dist/index.js +2094 -0
  12. package/dist/index.js.map +1 -0
  13. package/package.json +64 -10
  14. package/src/acp-auth.ts +254 -0
  15. package/src/acp-bridge-protocol.ts +13 -0
  16. package/src/acp-harness.ts +160 -0
  17. package/src/index.ts +19 -0
  18. package/src/v1/acp-v1-bridge-protocol.ts +151 -0
  19. package/src/v1/acp-v1-harness.ts +1447 -0
  20. package/src/v1/acp-v1-lifecycle.ts +115 -0
  21. package/src/v1/acp-v1-prompt.ts +122 -0
  22. package/src/v1/acp-v1-settings.ts +98 -0
  23. package/src/v1/acp-v1-skills.ts +228 -0
  24. package/src/v1/acp-v1-turn-start-config.ts +93 -0
  25. package/src/v1/bridge/acp-diagnostics.ts +57 -0
  26. package/src/v1/bridge/acp-stream-capture.ts +152 -0
  27. package/src/v1/bridge/acp-v1-bridge-environment.ts +95 -0
  28. package/src/v1/bridge/create-emit-stream-event.ts +97 -0
  29. package/src/v1/bridge/host-tool-correlation.ts +640 -0
  30. package/src/v1/bridge/host-tool-mcp-server.ts +180 -0
  31. package/src/v1/bridge/host-tool-mcp.ts +185 -0
  32. package/src/v1/bridge/host-tool-relay-client.ts +53 -0
  33. package/src/v1/bridge/host-tool-relay.ts +510 -0
  34. package/src/v1/bridge/index.ts +729 -0
  35. package/src/v1/bridge/package.json +12 -0
  36. package/src/v1/bridge/permission-controller.ts +152 -0
  37. package/src/v1/bridge/permission-mode.ts +163 -0
  38. package/src/v1/bridge/pnpm-lock.yaml +816 -0
  39. package/src/v1/bridge/profile-values.ts +97 -0
  40. package/src/v1/bridge/protocol-configuration.ts +146 -0
  41. package/src/v1/bridge/recovered-session.ts +145 -0
  42. package/src/v1/bridge/refresh-host-tool-catalog.ts +33 -0
  43. package/src/v1/bridge/session-lifecycle.ts +83 -0
  44. package/src/v1/bridge/stream-translator.ts +901 -0
  45. package/src/v1/implementation.ts +282 -0
  46. package/src/v1/index.ts +16 -0
  47. package/src/version.ts +6 -0
  48. package/index.js +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @ai-sdk/harness-acp
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - ff0f708: feat(harness-acp): introduce ACP harness adapter as a meta adapter to connect to any ACP compatible harness
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [1937bef]
12
+ - @ai-sdk/provider-utils@5.0.24
13
+ - @ai-sdk/harness@1.0.63
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Vercel, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md CHANGED
@@ -1,5 +1,72 @@
1
- # @ai-sdk/harness-acp
1
+ # AI SDK - ACP Harness
2
2
 
3
- AI SDK harness adapter for Agent Client Protocol.
3
+ `HarnessV1` adapter backed by an NPM-installed
4
+ [Agent Client Protocol](https://agentclientprotocol.com/) version 1 implementation.
5
+ The adapter ships a bridge process that runs inside a sandbox and talks to the
6
+ host over a WebSocket on a sandbox-proxied loopback port. The configured ACP
7
+ implementation runs alongside the bridge inside the sandbox.
4
8
 
5
- The implementation is maintained in the `vercel/ai` repository.
9
+ ## Setup
10
+
11
+ ```bash
12
+ npm i @ai-sdk/harness-acp @ai-sdk/harness @ai-sdk/sandbox-vercel
13
+ ```
14
+
15
+ The bridge installs the configured ACP implementation inside the sandbox the
16
+ first time the session starts.
17
+
18
+ ## Usage
19
+
20
+ This example connects `HarnessAgent` to Codex ACP using direct OpenAI
21
+ authentication:
22
+
23
+ ```ts
24
+ import { HarnessAgent } from '@ai-sdk/harness/agent';
25
+ import { createACP } from '@ai-sdk/harness-acp';
26
+ import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';
27
+
28
+ const codexACP = createACP({
29
+ harnessId: 'acp-codex',
30
+ implementation: {
31
+ type: 'npm',
32
+ mode: 'simple',
33
+ packageName: '@agentclientprotocol/codex-acp',
34
+ version: '1.1.4',
35
+ executable: 'codex-acp',
36
+ forwardEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
37
+ },
38
+ permissionModeMapping: {
39
+ 'allow-reads': null,
40
+ 'allow-edits': null,
41
+ 'allow-all': { type: 'session-mode', modeId: 'agent-full-access' },
42
+ },
43
+ authentication: {
44
+ methodId: 'api-key',
45
+ },
46
+ });
47
+
48
+ const agent = new HarnessAgent({
49
+ harness: codexACP,
50
+ sandbox: createVercelSandbox({
51
+ runtime: 'node24',
52
+ ports: [4000],
53
+ }),
54
+ });
55
+
56
+ const session = await agent.createSession();
57
+ try {
58
+ const result = await agent.generate({
59
+ session,
60
+ prompt: 'Inspect this project and summarize its purpose.',
61
+ });
62
+ console.log(result.text);
63
+ } finally {
64
+ await session.destroy();
65
+ }
66
+ ```
67
+
68
+ Set `CODEX_API_KEY` or `OPENAI_API_KEY` in the host environment. The adapter
69
+ resolves the value when the ACP process starts and does not store it in the
70
+ profile. Codex ACP supports only `permissionMode: 'allow-all'` because its
71
+ restrictive modes enable Codex's internal sandbox. A bridge-backed ACP harness
72
+ requires a sandbox with at least one exposed port.
@@ -0,0 +1,302 @@
1
+ // src/v1/bridge/host-tool-mcp.ts
2
+ import { randomUUID } from "crypto";
3
+ import { readFile } from "fs/promises";
4
+ import { env } from "process";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/v1/bridge/host-tool-mcp-server.ts
8
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
9
+ import {
10
+ CallToolRequestSchema,
11
+ ErrorCode,
12
+ ListToolsRequestSchema,
13
+ McpError
14
+ } from "@modelcontextprotocol/sdk/types.js";
15
+ var VERSION = true ? "1.0.0" : "0.0.0-test";
16
+ function createHostToolMCPServer({
17
+ tools: tools2,
18
+ revision = 1,
19
+ invoke,
20
+ onListTools
21
+ }) {
22
+ let catalog = createCatalog({ revision, tools: tools2 });
23
+ let catalogAcknowledgmentError;
24
+ const server = new Server(
25
+ {
26
+ name: "@ai-sdk/harness-acp-host-tools",
27
+ version: VERSION
28
+ },
29
+ { capabilities: { tools: { listChanged: true } } }
30
+ );
31
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
32
+ if (catalogAcknowledgmentError != null) {
33
+ throw catalogAcknowledgmentError;
34
+ }
35
+ const current = catalog;
36
+ if (onListTools != null) {
37
+ setImmediate(() => {
38
+ void onListTools({ revision: current.revision }).catch((error) => {
39
+ catalogAcknowledgmentError = error;
40
+ });
41
+ });
42
+ }
43
+ return { tools: current.tools.map(toMCPTool) };
44
+ });
45
+ server.setRequestHandler(CallToolRequestSchema, async (request2) => {
46
+ const current = catalog;
47
+ const tool = current.byName.get(request2.params.name);
48
+ if (tool == null) {
49
+ throw new McpError(
50
+ ErrorCode.InvalidParams,
51
+ `Unknown host tool: ${request2.params.name}`
52
+ );
53
+ }
54
+ const input = request2.params.arguments ?? {};
55
+ const result = await invoke({
56
+ toolName: tool.name,
57
+ input,
58
+ catalogRevision: current.revision
59
+ });
60
+ return toCallToolResult({ result });
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
+ };
82
+ }
83
+ function toMCPTool(tool) {
84
+ return {
85
+ name: tool.name,
86
+ ...tool.description == null ? {} : { description: tool.description },
87
+ inputSchema: requireObjectSchema({
88
+ toolName: tool.name,
89
+ schema: tool.inputSchema ?? { type: "object" }
90
+ })
91
+ };
92
+ }
93
+ function requireObjectSchema({
94
+ toolName,
95
+ schema
96
+ }) {
97
+ if (schema == null || typeof schema !== "object" || Array.isArray(schema) || Reflect.get(schema, "type") !== void 0 && Reflect.get(schema, "type") !== "object") {
98
+ throw new Error(
99
+ `Host tool ${toolName} must use an object JSON Schema for MCP.`
100
+ );
101
+ }
102
+ return schema;
103
+ }
104
+ function toCallToolResult({
105
+ result
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
+ };
119
+ }
120
+ function stringifyOutput({ output }) {
121
+ if (output === void 0) return "null";
122
+ try {
123
+ return JSON.stringify(output);
124
+ } catch {
125
+ return JSON.stringify({
126
+ error: "Host tool output could not be serialized as JSON."
127
+ });
128
+ }
129
+ }
130
+
131
+ // src/v1/bridge/host-tool-relay-client.ts
132
+ import { Agent, request } from "http";
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"
180
+ });
181
+ var relayCredential = requireEnvironmentVariable({
182
+ name: "AI_SDK_ACP_HOST_TOOL_RELAY_CREDENTIAL"
183
+ });
184
+ var tools = await readToolCatalog({ path: catalogPath });
185
+ var hostToolServer = createHostToolMCPServer({
186
+ tools,
187
+ invoke: async ({ toolName, input, catalogRevision }) => validateInvocationResult({
188
+ value: await postRelay({
189
+ path: "/invoke",
190
+ body: {
191
+ requestId: randomUUID(),
192
+ toolName,
193
+ input,
194
+ catalogRevision
195
+ }
196
+ })
197
+ }),
198
+ onListTools: async ({ revision }) => {
199
+ await postRelay({
200
+ path: "/catalog/seen",
201
+ body: { revision }
202
+ });
203
+ }
204
+ });
205
+ await hostToolServer.server.connect(new StdioServerTransport());
206
+ void watchCatalog({
207
+ initialRevision: 1,
208
+ updateCatalog: hostToolServer.updateCatalog
209
+ }).catch((error) => {
210
+ process.stderr.write(
211
+ `Host tool catalog synchronization failed: ${error instanceof Error ? error.message : String(error)}
212
+ `
213
+ );
214
+ void hostToolServer.server.close();
215
+ process.exitCode = 1;
216
+ });
217
+ async function watchCatalog({
218
+ initialRevision,
219
+ updateCatalog
220
+ }) {
221
+ let revision = initialRevision;
222
+ for (; ; ) {
223
+ const value = await postRelay({
224
+ path: "/catalog/next",
225
+ body: { afterRevision: revision }
226
+ });
227
+ if (isRecord(value) && value.closed === true) return;
228
+ if (!isRecord(value) || !Number.isSafeInteger(value.revision) || value.revision < revision) {
229
+ throw new Error("Invalid host tool catalog poll response.");
230
+ }
231
+ const nextRevision = value.revision;
232
+ if (nextRevision === revision) continue;
233
+ const nextTools = validateToolCatalog({ value: value.tools });
234
+ await updateCatalog({ revision: nextRevision, tools: nextTools });
235
+ revision = nextRevision;
236
+ }
237
+ }
238
+ async function postRelay({
239
+ path,
240
+ body
241
+ }) {
242
+ const response = await postHostToolRelay({
243
+ relayUrl,
244
+ relayCredential,
245
+ path,
246
+ body
247
+ });
248
+ const { value } = response;
249
+ if (!response.ok) {
250
+ throw new Error(readErrorMessage({ value, status: response.status }));
251
+ }
252
+ return value;
253
+ }
254
+ function validateToolCatalog({
255
+ value
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 });
270
+ }
271
+ function isTool(value) {
272
+ 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
+ }
274
+ function validateInvocationResult({
275
+ value
276
+ }) {
277
+ if (!isRecord(value) || typeof value.correlationToken !== "string" || value.isError !== void 0 && typeof value.isError !== "boolean") {
278
+ throw new Error("Invalid host tool relay response.");
279
+ }
280
+ return {
281
+ output: value.output,
282
+ ...value.isError ? { isError: true } : {},
283
+ correlationToken: value.correlationToken
284
+ };
285
+ }
286
+ function readErrorMessage({
287
+ value,
288
+ status
289
+ }) {
290
+ return isRecord(value) && typeof value.error === "string" ? value.error : `Host tool relay returned HTTP ${status}.`;
291
+ }
292
+ function requireEnvironmentVariable({ name }) {
293
+ const value = env[name];
294
+ if (value == null || value.length === 0) {
295
+ throw new Error(`Missing ${name}.`);
296
+ }
297
+ return value;
298
+ }
299
+ function isRecord(value) {
300
+ return value != null && typeof value === "object" && !Array.isArray(value);
301
+ }
302
+ //# sourceMappingURL=host-tool-mcp.mjs.map
@@ -0,0 +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"]}