@contractspec/lib.ai-agent 2.5.0 → 2.7.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.
- package/README.md +33 -2
- package/dist/agent/agent-factory.d.ts +5 -0
- package/dist/agent/agent-factory.js +221 -11
- package/dist/agent/contract-spec-agent.d.ts +8 -0
- package/dist/agent/contract-spec-agent.js +210 -10
- package/dist/agent/index.js +334 -39
- package/dist/agent/json-runner.js +210 -10
- package/dist/agent/unified-agent.d.ts +4 -0
- package/dist/agent/unified-agent.js +334 -39
- package/dist/exporters/claude-agent-exporter.d.ts +1 -0
- package/dist/exporters/claude-agent-exporter.js +11 -1
- package/dist/exporters/index.js +11 -1
- package/dist/exporters/types.d.ts +3 -10
- package/dist/node/agent/agent-factory.js +221 -11
- package/dist/node/agent/contract-spec-agent.js +210 -10
- package/dist/node/agent/index.js +334 -39
- package/dist/node/agent/json-runner.js +210 -10
- package/dist/node/agent/unified-agent.js +334 -39
- package/dist/node/exporters/claude-agent-exporter.js +11 -1
- package/dist/node/exporters/index.js +11 -1
- package/dist/node/providers/claude-agent-sdk/adapter.js +260 -23
- package/dist/node/providers/claude-agent-sdk/index.js +260 -23
- package/dist/node/providers/index.js +260 -23
- package/dist/node/tools/index.js +154 -18
- package/dist/node/tools/mcp-client-helpers.js +106 -0
- package/dist/node/tools/mcp-client.js +155 -18
- package/dist/providers/claude-agent-sdk/adapter.d.ts +4 -0
- package/dist/providers/claude-agent-sdk/adapter.js +260 -23
- package/dist/providers/claude-agent-sdk/index.d.ts +8 -0
- package/dist/providers/claude-agent-sdk/index.js +260 -23
- package/dist/providers/index.js +260 -23
- package/dist/providers/types.d.ts +1 -1
- package/dist/tools/index.js +154 -18
- package/dist/tools/mcp-client-helpers.d.ts +12 -0
- package/dist/tools/mcp-client-helpers.js +106 -0
- package/dist/tools/mcp-client.d.ts +55 -3
- package/dist/tools/mcp-client.js +155 -18
- package/dist/tools/mcp-client.test.d.ts +1 -0
- package/package.json +24 -12
|
@@ -2161,6 +2161,171 @@ var init_spec = __esm(() => {
|
|
|
2161
2161
|
init_i18n();
|
|
2162
2162
|
});
|
|
2163
2163
|
|
|
2164
|
+
// src/tools/mcp-client-helpers.ts
|
|
2165
|
+
import {
|
|
2166
|
+
Experimental_StdioMCPTransport as StdioClientTransport
|
|
2167
|
+
} from "@ai-sdk/mcp/mcp-stdio";
|
|
2168
|
+
function buildMcpTransport(config) {
|
|
2169
|
+
const transport = resolveTransportType(config);
|
|
2170
|
+
if (transport === "stdio") {
|
|
2171
|
+
const stdioConfig = resolveStdioConfig(config);
|
|
2172
|
+
return new StdioClientTransport(stdioConfig);
|
|
2173
|
+
}
|
|
2174
|
+
const remoteConfig = config;
|
|
2175
|
+
const headers = resolveRemoteHeaders(remoteConfig);
|
|
2176
|
+
const remoteTransport = {
|
|
2177
|
+
type: transport,
|
|
2178
|
+
url: requireNonEmptyString(remoteConfig.url, "url", config.name)
|
|
2179
|
+
};
|
|
2180
|
+
if (headers) {
|
|
2181
|
+
remoteTransport.headers = headers;
|
|
2182
|
+
}
|
|
2183
|
+
if (remoteConfig.authProvider) {
|
|
2184
|
+
remoteTransport.authProvider = remoteConfig.authProvider;
|
|
2185
|
+
}
|
|
2186
|
+
return remoteTransport;
|
|
2187
|
+
}
|
|
2188
|
+
function prefixToolNames(config, tools) {
|
|
2189
|
+
const prefix = config.toolPrefix?.trim();
|
|
2190
|
+
if (!prefix) {
|
|
2191
|
+
return tools;
|
|
2192
|
+
}
|
|
2193
|
+
const prefixedTools = {};
|
|
2194
|
+
for (const [toolName, tool] of Object.entries(tools)) {
|
|
2195
|
+
prefixedTools[`${prefix}_${toolName}`] = tool;
|
|
2196
|
+
}
|
|
2197
|
+
return prefixedTools;
|
|
2198
|
+
}
|
|
2199
|
+
function getErrorMessage(error) {
|
|
2200
|
+
if (error instanceof Error) {
|
|
2201
|
+
return error.message;
|
|
2202
|
+
}
|
|
2203
|
+
return String(error);
|
|
2204
|
+
}
|
|
2205
|
+
function resolveTransportType(config) {
|
|
2206
|
+
return config.transport ?? "stdio";
|
|
2207
|
+
}
|
|
2208
|
+
function resolveStdioConfig(config) {
|
|
2209
|
+
const stdioConfig = config;
|
|
2210
|
+
return {
|
|
2211
|
+
command: requireNonEmptyString(stdioConfig.command, "command", config.name),
|
|
2212
|
+
args: stdioConfig.args,
|
|
2213
|
+
env: stdioConfig.env,
|
|
2214
|
+
cwd: stdioConfig.cwd
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
function resolveRemoteHeaders(config) {
|
|
2218
|
+
const headers = {
|
|
2219
|
+
...config.headers ?? {}
|
|
2220
|
+
};
|
|
2221
|
+
const accessToken = config.accessToken ?? resolveEnvToken(config.accessTokenEnvVar);
|
|
2222
|
+
if (accessToken && headers.Authorization === undefined) {
|
|
2223
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
2224
|
+
}
|
|
2225
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
2226
|
+
}
|
|
2227
|
+
function resolveEnvToken(envVarName) {
|
|
2228
|
+
if (!envVarName) {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const value = process.env[envVarName];
|
|
2232
|
+
if (!value) {
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const trimmed = value.trim();
|
|
2236
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
2237
|
+
}
|
|
2238
|
+
function requireNonEmptyString(value, field, serverName) {
|
|
2239
|
+
if (!value) {
|
|
2240
|
+
throw new Error(`MCP server "${serverName}" is missing required "${field}".`);
|
|
2241
|
+
}
|
|
2242
|
+
const trimmed = value.trim();
|
|
2243
|
+
if (trimmed.length === 0) {
|
|
2244
|
+
throw new Error(`MCP server "${serverName}" has an empty "${field}".`);
|
|
2245
|
+
}
|
|
2246
|
+
return trimmed;
|
|
2247
|
+
}
|
|
2248
|
+
var init_mcp_client_helpers = () => {};
|
|
2249
|
+
|
|
2250
|
+
// src/tools/mcp-client.ts
|
|
2251
|
+
import {
|
|
2252
|
+
experimental_createMCPClient
|
|
2253
|
+
} from "@ai-sdk/mcp";
|
|
2254
|
+
async function mcpServerToTools(config) {
|
|
2255
|
+
let client = null;
|
|
2256
|
+
try {
|
|
2257
|
+
const transport = buildMcpTransport(config);
|
|
2258
|
+
client = await experimental_createMCPClient({
|
|
2259
|
+
transport,
|
|
2260
|
+
name: config.clientName,
|
|
2261
|
+
version: config.clientVersion
|
|
2262
|
+
});
|
|
2263
|
+
const tools = await client.tools();
|
|
2264
|
+
const prefixedTools = prefixToolNames(config, tools);
|
|
2265
|
+
const connectedClient = client;
|
|
2266
|
+
return {
|
|
2267
|
+
tools: prefixedTools,
|
|
2268
|
+
cleanup: () => connectedClient.close(),
|
|
2269
|
+
serverToolNames: {
|
|
2270
|
+
[config.name]: Object.keys(prefixedTools)
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
if (client) {
|
|
2275
|
+
await client.close().catch(() => {
|
|
2276
|
+
return;
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
throw new Error(`[MCP:${config.name}] Failed to connect tools: ${getErrorMessage(error)}`);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
async function createMcpToolsets(configs, options = {}) {
|
|
2283
|
+
const connected = [];
|
|
2284
|
+
try {
|
|
2285
|
+
for (const config of configs) {
|
|
2286
|
+
const result = await mcpServerToTools(config);
|
|
2287
|
+
connected.push(result);
|
|
2288
|
+
}
|
|
2289
|
+
} catch (error) {
|
|
2290
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2291
|
+
throw error;
|
|
2292
|
+
}
|
|
2293
|
+
const combinedTools = {};
|
|
2294
|
+
const serverToolNames = {};
|
|
2295
|
+
const collisionStrategy = options.onNameCollision ?? "overwrite";
|
|
2296
|
+
try {
|
|
2297
|
+
for (const result of connected) {
|
|
2298
|
+
for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
|
|
2299
|
+
serverToolNames[serverName] = toolNames;
|
|
2300
|
+
}
|
|
2301
|
+
for (const [toolName, tool] of Object.entries(result.tools)) {
|
|
2302
|
+
const hasCollision = combinedTools[toolName] !== undefined;
|
|
2303
|
+
if (hasCollision && collisionStrategy === "error") {
|
|
2304
|
+
throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
|
|
2305
|
+
}
|
|
2306
|
+
combinedTools[toolName] = tool;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
} catch (error) {
|
|
2310
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2311
|
+
throw error;
|
|
2312
|
+
}
|
|
2313
|
+
return {
|
|
2314
|
+
tools: combinedTools,
|
|
2315
|
+
serverToolNames,
|
|
2316
|
+
cleanup: async () => {
|
|
2317
|
+
const cleanupResults = await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2318
|
+
const failures = cleanupResults.filter((result) => result.status === "rejected");
|
|
2319
|
+
if (failures.length > 0) {
|
|
2320
|
+
throw new Error(`Failed to cleanup ${failures.length} MCP client connection(s).`);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
var init_mcp_client = __esm(() => {
|
|
2326
|
+
init_mcp_client_helpers();
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2164
2329
|
// src/knowledge/injector.ts
|
|
2165
2330
|
async function injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale) {
|
|
2166
2331
|
if (!retriever)
|
|
@@ -2478,6 +2643,8 @@ function summarizeSession(session) {
|
|
|
2478
2643
|
}
|
|
2479
2644
|
|
|
2480
2645
|
// src/providers/claude-agent-sdk/adapter.ts
|
|
2646
|
+
import { randomUUID } from "node:crypto";
|
|
2647
|
+
|
|
2481
2648
|
class ClaudeAgentSDKProvider {
|
|
2482
2649
|
name = "claude-agent-sdk";
|
|
2483
2650
|
version = "1.0.0";
|
|
@@ -2510,11 +2677,23 @@ class ClaudeAgentSDKProvider {
|
|
|
2510
2677
|
if (!this.isAvailable()) {
|
|
2511
2678
|
throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.sdkNotConfigured"));
|
|
2512
2679
|
}
|
|
2680
|
+
let mcpToolset = null;
|
|
2513
2681
|
try {
|
|
2514
2682
|
const toolSet = {};
|
|
2515
2683
|
for (const tool of spec.tools) {
|
|
2516
2684
|
toolSet[tool.name] = specToolToExternalTool(tool);
|
|
2517
2685
|
}
|
|
2686
|
+
if ((this.config.mcpServers?.length ?? 0) > 0) {
|
|
2687
|
+
mcpToolset = await createMcpToolsets(this.config.mcpServers ?? [], {
|
|
2688
|
+
onNameCollision: "error"
|
|
2689
|
+
});
|
|
2690
|
+
for (const [toolName, mcpTool] of Object.entries(mcpToolset.tools)) {
|
|
2691
|
+
if (toolSet[toolName]) {
|
|
2692
|
+
throw new Error(`MCP tool "${toolName}" collides with a ContractSpec tool. Configure MCP toolPrefix values to avoid collisions.`);
|
|
2693
|
+
}
|
|
2694
|
+
toolSet[toolName] = this.mcpToolToExternalTool(toolName, mcpTool);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2518
2697
|
const instructions = await injectStaticKnowledge(spec.instructions, spec.knowledge ?? [], undefined);
|
|
2519
2698
|
const contextId = `claude-${agentKey(spec.meta)}-${Date.now()}`;
|
|
2520
2699
|
const metadata = {
|
|
@@ -2522,6 +2701,7 @@ class ClaudeAgentSDKProvider {
|
|
|
2522
2701
|
extendedThinkingEnabled: this.config.extendedThinking ?? false,
|
|
2523
2702
|
mcpServerIds: this.config.mcpServers?.map((s) => s.name) ?? []
|
|
2524
2703
|
};
|
|
2704
|
+
const cleanupMcp = mcpToolset?.cleanup;
|
|
2525
2705
|
return {
|
|
2526
2706
|
id: contextId,
|
|
2527
2707
|
spec: {
|
|
@@ -2530,9 +2710,18 @@ class ClaudeAgentSDKProvider {
|
|
|
2530
2710
|
},
|
|
2531
2711
|
tools: toolSet,
|
|
2532
2712
|
metadata,
|
|
2533
|
-
cleanup: async () => {
|
|
2713
|
+
cleanup: async () => {
|
|
2714
|
+
if (cleanupMcp) {
|
|
2715
|
+
await cleanupMcp();
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2534
2718
|
};
|
|
2535
2719
|
} catch (error) {
|
|
2720
|
+
if (mcpToolset) {
|
|
2721
|
+
await mcpToolset.cleanup().catch(() => {
|
|
2722
|
+
return;
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2536
2725
|
throw new ContextCreationError(this.name, createAgentI18n(this.config.locale).t("error.provider.contextCreation", {
|
|
2537
2726
|
error: error instanceof Error ? error.message : String(error)
|
|
2538
2727
|
}), error instanceof Error ? error : undefined);
|
|
@@ -2547,7 +2736,7 @@ ${params.systemOverride}` : context.spec.instructions;
|
|
|
2547
2736
|
const claudeContext = buildClaudeAgentContext(params.options);
|
|
2548
2737
|
let session = createEmptyClaudeSession();
|
|
2549
2738
|
session = appendUserMessage(session, params.prompt);
|
|
2550
|
-
const claudeTools = this.prepareToolsForSDK(context
|
|
2739
|
+
const claudeTools = this.prepareToolsForSDK(context);
|
|
2551
2740
|
const rawResponse = await sdk.execute({
|
|
2552
2741
|
model: this.config.model,
|
|
2553
2742
|
system: systemPrompt,
|
|
@@ -2603,7 +2792,7 @@ ${params.systemOverride}` : context.spec.instructions;
|
|
|
2603
2792
|
|
|
2604
2793
|
${params.systemOverride}` : context.spec.instructions;
|
|
2605
2794
|
const claudeContext = buildClaudeAgentContext(params.options);
|
|
2606
|
-
const claudeTools = this.prepareToolsForSDK(context
|
|
2795
|
+
const claudeTools = this.prepareToolsForSDK(context);
|
|
2607
2796
|
const stream = await sdk.stream({
|
|
2608
2797
|
model: this.config.model,
|
|
2609
2798
|
system: systemPrompt,
|
|
@@ -2686,30 +2875,77 @@ ${params.systemOverride}` : context.spec.instructions;
|
|
|
2686
2875
|
throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.claudeSdkMissing"));
|
|
2687
2876
|
}
|
|
2688
2877
|
}
|
|
2689
|
-
prepareToolsForSDK(context
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
if (externalTool
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2878
|
+
prepareToolsForSDK(context) {
|
|
2879
|
+
const i18n = createAgentI18n(this.config.locale);
|
|
2880
|
+
const toolsForSdk = [];
|
|
2881
|
+
for (const [toolName, externalTool] of Object.entries(context.tools)) {
|
|
2882
|
+
if (!externalTool.execute) {
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
toolsForSdk.push({
|
|
2886
|
+
name: toolName,
|
|
2887
|
+
description: externalTool.description ?? i18n.t("tool.fallbackDescription", { name: toolName }),
|
|
2888
|
+
input_schema: this.normalizeToolSchemaForClaude(externalTool.inputSchema),
|
|
2889
|
+
requires_confirmation: externalTool.requiresApproval,
|
|
2890
|
+
execute: async (input) => {
|
|
2891
|
+
const result = await externalTool.execute?.(input);
|
|
2701
2892
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
}
|
|
2896
|
+
return toolsForSdk;
|
|
2897
|
+
}
|
|
2898
|
+
mcpToolToExternalTool(toolName, tool) {
|
|
2899
|
+
return {
|
|
2900
|
+
name: toolName,
|
|
2901
|
+
description: tool.description ?? createAgentI18n(this.config.locale).t("tool.fallbackDescription", {
|
|
2902
|
+
name: toolName
|
|
2903
|
+
}),
|
|
2904
|
+
inputSchema: this.normalizeExternalInputSchema(tool.inputSchema),
|
|
2905
|
+
execute: async (input) => {
|
|
2906
|
+
if (!tool.execute) {
|
|
2907
|
+
throw new Error(createAgentI18n(this.config.locale).t("error.toolNoExecuteHandler", {
|
|
2908
|
+
name: toolName
|
|
2909
|
+
}));
|
|
2910
|
+
}
|
|
2911
|
+
return tool.execute(input, {
|
|
2912
|
+
toolCallId: `mcp-${randomUUID()}`,
|
|
2913
|
+
messages: []
|
|
2702
2914
|
});
|
|
2703
2915
|
}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
normalizeExternalInputSchema(schema) {
|
|
2919
|
+
if (this.isRecord(schema)) {
|
|
2920
|
+
const type = schema["type"];
|
|
2921
|
+
if (type === "object" || schema["properties"]) {
|
|
2922
|
+
return schema;
|
|
2923
|
+
}
|
|
2704
2924
|
}
|
|
2705
|
-
return
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2925
|
+
return {
|
|
2926
|
+
type: "object",
|
|
2927
|
+
properties: {}
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
normalizeToolSchemaForClaude(schema) {
|
|
2931
|
+
if (schema.type === "object") {
|
|
2932
|
+
return {
|
|
2933
|
+
type: "object",
|
|
2934
|
+
properties: schema.properties,
|
|
2935
|
+
required: schema.required,
|
|
2936
|
+
additionalProperties: schema.additionalProperties
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
return {
|
|
2940
|
+
type: "object",
|
|
2941
|
+
properties: {
|
|
2942
|
+
value: schema
|
|
2943
|
+
},
|
|
2944
|
+
required: ["value"]
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
isRecord(value) {
|
|
2948
|
+
return typeof value === "object" && value !== null;
|
|
2713
2949
|
}
|
|
2714
2950
|
async executeTool(toolCall, context, _params) {
|
|
2715
2951
|
const tool = context.tools[toolCall.toolName];
|
|
@@ -2765,6 +3001,7 @@ var init_adapter = __esm(() => {
|
|
|
2765
3001
|
init_types();
|
|
2766
3002
|
init_tool_bridge();
|
|
2767
3003
|
init_injector();
|
|
3004
|
+
init_mcp_client();
|
|
2768
3005
|
init_i18n();
|
|
2769
3006
|
});
|
|
2770
3007
|
|