@contractspec/lib.ai-agent 5.0.4 → 6.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.
- package/README.md +41 -0
- package/dist/agent/agent-factory.d.ts +13 -0
- package/dist/agent/agent-factory.js +290 -63
- package/dist/agent/contract-spec-agent.d.ts +9 -0
- package/dist/agent/contract-spec-agent.js +287 -63
- package/dist/agent/index.js +353 -129
- package/dist/agent/json-runner.js +290 -66
- package/dist/agent/unified-agent.js +350 -126
- package/dist/exporters/claude-agent-exporter.js +12 -1
- package/dist/exporters/index.js +12 -1
- package/dist/exporters/opencode-exporter.js +11 -0
- package/dist/index.js +11 -0
- package/dist/interop/index.js +24 -2
- package/dist/interop/spec-consumer.js +11 -0
- package/dist/interop/tool-consumer.js +13 -2
- package/dist/node/agent/agent-factory.js +290 -63
- package/dist/node/agent/contract-spec-agent.js +287 -63
- package/dist/node/agent/index.js +353 -129
- package/dist/node/agent/json-runner.js +290 -66
- package/dist/node/agent/unified-agent.js +350 -126
- package/dist/node/exporters/claude-agent-exporter.js +12 -1
- package/dist/node/exporters/index.js +12 -1
- package/dist/node/exporters/opencode-exporter.js +11 -0
- package/dist/node/index.js +11 -0
- package/dist/node/interop/index.js +24 -2
- package/dist/node/interop/spec-consumer.js +11 -0
- package/dist/node/interop/tool-consumer.js +13 -2
- package/dist/node/providers/claude-agent-sdk/adapter.js +11 -0
- package/dist/node/providers/claude-agent-sdk/index.js +11 -0
- package/dist/node/providers/index.js +11 -0
- package/dist/node/providers/opencode-sdk/adapter.js +11 -0
- package/dist/node/providers/opencode-sdk/index.js +11 -0
- package/dist/node/spec/index.js +11 -0
- package/dist/node/spec/spec.js +11 -0
- package/dist/node/tools/agent-memory-store.js +24 -0
- package/dist/node/tools/in-memory-agent-memory-store.js +236 -0
- package/dist/node/tools/index.js +463 -42
- package/dist/node/tools/memory-tools.js +45 -0
- package/dist/node/tools/operation-tool-handler.js +35 -0
- package/dist/node/tools/subagent-tool.js +95 -0
- package/dist/node/tools/tool-adapter.js +192 -25
- package/dist/providers/claude-agent-sdk/adapter.js +11 -0
- package/dist/providers/claude-agent-sdk/index.js +11 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/opencode-sdk/adapter.js +11 -0
- package/dist/providers/opencode-sdk/index.js +11 -0
- package/dist/spec/index.js +11 -0
- package/dist/spec/spec.d.ts +69 -1
- package/dist/spec/spec.js +11 -0
- package/dist/tools/agent-memory-store.d.ts +26 -0
- package/dist/tools/agent-memory-store.js +24 -0
- package/dist/tools/agent-memory-store.test.d.ts +1 -0
- package/dist/tools/in-memory-agent-memory-store.d.ts +18 -0
- package/dist/tools/in-memory-agent-memory-store.js +236 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +463 -42
- package/dist/tools/mcp-client.browser.d.ts +6 -6
- package/dist/tools/memory-tools.d.ts +29 -0
- package/dist/tools/memory-tools.js +45 -0
- package/dist/tools/operation-tool-handler.d.ts +24 -0
- package/dist/tools/operation-tool-handler.js +35 -0
- package/dist/tools/subagent-tool.d.ts +66 -0
- package/dist/tools/subagent-tool.js +95 -0
- package/dist/tools/tool-adapter.d.ts +26 -10
- package/dist/tools/tool-adapter.js +192 -25
- package/dist/types.d.ts +9 -3
- package/package.json +67 -7
package/dist/tools/index.js
CHANGED
|
@@ -2260,15 +2260,124 @@ function jsonSchemaToZodSafe(schema) {
|
|
|
2260
2260
|
}
|
|
2261
2261
|
var init_json_schema_to_zod = () => {};
|
|
2262
2262
|
|
|
2263
|
+
// src/tools/operation-tool-handler.ts
|
|
2264
|
+
function toolCtxToHandlerCtx(ctx) {
|
|
2265
|
+
return {
|
|
2266
|
+
traceId: ctx.metadata?.traceId,
|
|
2267
|
+
organizationId: ctx.tenantId ?? null,
|
|
2268
|
+
userId: ctx.actorId ?? null,
|
|
2269
|
+
actor: ctx.actorId ? "user" : "anonymous",
|
|
2270
|
+
channel: "agent",
|
|
2271
|
+
roles: []
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
function createOperationToolHandler(registry, operationRef) {
|
|
2275
|
+
return async (input, context) => {
|
|
2276
|
+
const handlerCtx = toolCtxToHandlerCtx(context);
|
|
2277
|
+
const result = await registry.execute(operationRef.key, operationRef.version, input ?? {}, handlerCtx);
|
|
2278
|
+
return result;
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/tools/subagent-tool.ts
|
|
2283
|
+
import { readUIMessageStream, tool } from "ai";
|
|
2284
|
+
import { z as z2 } from "zod";
|
|
2285
|
+
function toReadableStream(iterable) {
|
|
2286
|
+
if (iterable instanceof ReadableStream) {
|
|
2287
|
+
return iterable;
|
|
2288
|
+
}
|
|
2289
|
+
return new ReadableStream({
|
|
2290
|
+
async start(controller) {
|
|
2291
|
+
try {
|
|
2292
|
+
for await (const chunk of iterable) {
|
|
2293
|
+
controller.enqueue(chunk);
|
|
2294
|
+
}
|
|
2295
|
+
} finally {
|
|
2296
|
+
controller.close();
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
function createSubagentTool(options) {
|
|
2302
|
+
const {
|
|
2303
|
+
subagent,
|
|
2304
|
+
description = "Research a topic or question in depth.",
|
|
2305
|
+
taskParam = "task",
|
|
2306
|
+
toModelSummary = true,
|
|
2307
|
+
passConversationHistory = false
|
|
2308
|
+
} = options;
|
|
2309
|
+
const inputSchema = z2.object({
|
|
2310
|
+
[taskParam]: z2.string().describe("The research task to complete")
|
|
2311
|
+
});
|
|
2312
|
+
const execute = async function* (input, options2) {
|
|
2313
|
+
const task = String(input[taskParam] ?? input.task ?? "");
|
|
2314
|
+
const { abortSignal, messages } = options2 ?? {};
|
|
2315
|
+
if (passConversationHistory && messages && messages.length > 0 && typeof subagent.generate === "function") {
|
|
2316
|
+
const result2 = await subagent.generate({
|
|
2317
|
+
messages: [...messages, { role: "user", content: task }],
|
|
2318
|
+
abortSignal
|
|
2319
|
+
});
|
|
2320
|
+
yield { parts: [{ type: "text", text: result2.text }] };
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
const result = await subagent.stream({
|
|
2324
|
+
prompt: task,
|
|
2325
|
+
abortSignal
|
|
2326
|
+
});
|
|
2327
|
+
const uiStream = result.toUIMessageStream();
|
|
2328
|
+
const stream = toReadableStream(uiStream);
|
|
2329
|
+
for await (const message of readUIMessageStream({
|
|
2330
|
+
stream
|
|
2331
|
+
})) {
|
|
2332
|
+
yield message;
|
|
2333
|
+
}
|
|
2334
|
+
};
|
|
2335
|
+
const toolOptions = {
|
|
2336
|
+
description,
|
|
2337
|
+
inputSchema,
|
|
2338
|
+
execute,
|
|
2339
|
+
...toModelSummary && {
|
|
2340
|
+
toModelOutput: ({
|
|
2341
|
+
output
|
|
2342
|
+
}) => {
|
|
2343
|
+
const parts = output?.parts;
|
|
2344
|
+
if (!Array.isArray(parts)) {
|
|
2345
|
+
return { type: "text", value: "Task completed." };
|
|
2346
|
+
}
|
|
2347
|
+
const lastTextPart = [...parts].reverse().find((p) => p?.type === "text");
|
|
2348
|
+
return {
|
|
2349
|
+
type: "text",
|
|
2350
|
+
value: lastTextPart?.text ?? "Task completed."
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2355
|
+
return tool(toolOptions);
|
|
2356
|
+
}
|
|
2357
|
+
var init_subagent_tool = () => {};
|
|
2358
|
+
|
|
2263
2359
|
// src/tools/tool-adapter.ts
|
|
2264
|
-
import { tool } from "ai";
|
|
2265
|
-
function
|
|
2360
|
+
import { tool as tool2 } from "ai";
|
|
2361
|
+
function isAsyncGenerator(value) {
|
|
2362
|
+
return typeof value === "object" && value !== null && typeof value.next === "function" && typeof value[Symbol.asyncIterator] === "function";
|
|
2363
|
+
}
|
|
2364
|
+
function specToolToAISDKTool(specTool, handler, context = {}, effectiveInputSchema, operationSpec) {
|
|
2266
2365
|
let lastInvocationAt;
|
|
2267
|
-
|
|
2366
|
+
const inputSchema = effectiveInputSchema ?? jsonSchemaToZodSafe(specTool.schema);
|
|
2367
|
+
const buildContext = (signal) => ({
|
|
2368
|
+
agentId: context.agentId ?? "unknown",
|
|
2369
|
+
sessionId: context.sessionId ?? "unknown",
|
|
2370
|
+
tenantId: context.tenantId,
|
|
2371
|
+
actorId: context.actorId,
|
|
2372
|
+
locale: context.locale,
|
|
2373
|
+
metadata: context.metadata,
|
|
2374
|
+
signal
|
|
2375
|
+
});
|
|
2376
|
+
return tool2({
|
|
2268
2377
|
description: specTool.description ?? specTool.name,
|
|
2269
|
-
inputSchema
|
|
2378
|
+
inputSchema,
|
|
2270
2379
|
needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
|
|
2271
|
-
execute: async (input)
|
|
2380
|
+
execute: async function* (input, options) {
|
|
2272
2381
|
const now = Date.now();
|
|
2273
2382
|
const cooldownMs = normalizeDuration(specTool.cooldownMs);
|
|
2274
2383
|
if (cooldownMs && lastInvocationAt !== undefined) {
|
|
@@ -2279,19 +2388,20 @@ function specToolToAISDKTool(specTool, handler, context = {}) {
|
|
|
2279
2388
|
}
|
|
2280
2389
|
}
|
|
2281
2390
|
const timeoutMs = normalizeDuration(specTool.timeoutMs);
|
|
2282
|
-
const
|
|
2391
|
+
const signal = options?.abortSignal ?? context.signal;
|
|
2392
|
+
const { signal: timeoutSignal, dispose } = createTimeoutSignal(signal, timeoutMs);
|
|
2283
2393
|
try {
|
|
2284
|
-
const execution = handler(input,
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2394
|
+
const execution = handler(input, buildContext(timeoutSignal));
|
|
2395
|
+
if (isAsyncGenerator(execution)) {
|
|
2396
|
+
for await (const raw of execution) {
|
|
2397
|
+
const wrapped = wrapToolOutputForRendering(specTool, raw, operationSpec);
|
|
2398
|
+
yield typeof wrapped === "string" ? wrapped : wrapped;
|
|
2399
|
+
}
|
|
2400
|
+
} else {
|
|
2401
|
+
const raw = timeoutMs ? await withTimeout(Promise.resolve(execution), timeoutMs, specTool.name) : await Promise.resolve(execution);
|
|
2402
|
+
const wrapped = wrapToolOutputForRendering(specTool, raw, operationSpec);
|
|
2403
|
+
yield typeof wrapped === "string" ? wrapped : wrapped;
|
|
2404
|
+
}
|
|
2295
2405
|
} finally {
|
|
2296
2406
|
dispose();
|
|
2297
2407
|
lastInvocationAt = Date.now();
|
|
@@ -2299,27 +2409,83 @@ function specToolToAISDKTool(specTool, handler, context = {}) {
|
|
|
2299
2409
|
}
|
|
2300
2410
|
});
|
|
2301
2411
|
}
|
|
2302
|
-
function specToolsToAISDKTools(specTools, handlers, context = {}) {
|
|
2412
|
+
function specToolsToAISDKTools(specTools, handlers, context = {}, options) {
|
|
2303
2413
|
const tools = {};
|
|
2304
2414
|
for (const specTool of specTools) {
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
}
|
|
2415
|
+
if (specTool.subagentRef && options?.subagentRegistry) {
|
|
2416
|
+
const subagent = options.subagentRegistry.get(specTool.subagentRef.agentId);
|
|
2417
|
+
if (!subagent) {
|
|
2418
|
+
throw new Error(`Subagent not found: ${specTool.subagentRef.agentId}. Register it in subagentRegistry.`);
|
|
2419
|
+
}
|
|
2420
|
+
if (specTool.requiresApproval === true || specTool.automationSafe === false) {
|
|
2421
|
+
console.warn(`[ContractSpec] Subagent tool "${specTool.name}" cannot use needsApproval. ` + `requiresApproval and automationSafe are ignored for subagent tools (AI SDK limitation). ` + `See https://ai-sdk.dev/docs/agents/subagents#no-tool-approvals-in-subagents`);
|
|
2422
|
+
}
|
|
2423
|
+
tools[specTool.name] = createSubagentTool({
|
|
2424
|
+
subagent,
|
|
2425
|
+
description: specTool.description ?? specTool.name,
|
|
2426
|
+
toModelSummary: specTool.subagentRef.toModelSummary ?? true,
|
|
2427
|
+
passConversationHistory: specTool.subagentRef.passConversationHistory
|
|
2428
|
+
});
|
|
2429
|
+
continue;
|
|
2310
2430
|
}
|
|
2311
|
-
|
|
2431
|
+
let handler;
|
|
2432
|
+
let effectiveInputSchema;
|
|
2433
|
+
let op;
|
|
2434
|
+
if (specTool.operationRef && options?.operationRegistry) {
|
|
2435
|
+
op = options.operationRegistry.get(specTool.operationRef.key, specTool.operationRef.version);
|
|
2436
|
+
if (!op) {
|
|
2437
|
+
throw new Error(`Operation not found: ${specTool.operationRef.key}${specTool.operationRef.version ? `.v${specTool.operationRef.version}` : ""}`);
|
|
2438
|
+
}
|
|
2439
|
+
handler = createOperationToolHandler(options.operationRegistry, specTool.operationRef);
|
|
2440
|
+
effectiveInputSchema = op.io.input?.getZod?.();
|
|
2441
|
+
} else {
|
|
2442
|
+
const manualHandler = handlers.get(specTool.name);
|
|
2443
|
+
if (!manualHandler) {
|
|
2444
|
+
if (specTool.subagentRef) {
|
|
2445
|
+
throw new Error(`Subagent tool "${specTool.name}" requires subagentRegistry. Pass subagentRegistry in ContractSpecAgentConfig.`);
|
|
2446
|
+
}
|
|
2447
|
+
throw new Error(createAgentI18n(context.locale).t("error.missingToolHandler", {
|
|
2448
|
+
name: specTool.name
|
|
2449
|
+
}));
|
|
2450
|
+
}
|
|
2451
|
+
handler = manualHandler;
|
|
2452
|
+
}
|
|
2453
|
+
tools[specTool.name] = specToolToAISDKTool(specTool, handler, context, effectiveInputSchema, op);
|
|
2312
2454
|
}
|
|
2313
2455
|
return tools;
|
|
2314
2456
|
}
|
|
2315
2457
|
function createToolHandler(handler) {
|
|
2316
|
-
return
|
|
2458
|
+
return (input, context) => {
|
|
2317
2459
|
return handler(input, context);
|
|
2318
2460
|
};
|
|
2319
2461
|
}
|
|
2320
2462
|
function buildToolHandlers(handlersObj) {
|
|
2321
2463
|
return new Map(Object.entries(handlersObj));
|
|
2322
2464
|
}
|
|
2465
|
+
function wrapToolOutputForRendering(specTool, result, operationSpec) {
|
|
2466
|
+
const presentation = specTool.outputPresentation ?? operationSpec?.outputPresentation;
|
|
2467
|
+
const form = specTool.outputForm ?? operationSpec?.outputForm;
|
|
2468
|
+
const dataView = specTool.outputDataView ?? operationSpec?.outputDataView;
|
|
2469
|
+
if (presentation) {
|
|
2470
|
+
return {
|
|
2471
|
+
presentationKey: presentation.key,
|
|
2472
|
+
data: result
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
if (form) {
|
|
2476
|
+
return {
|
|
2477
|
+
formKey: form.key,
|
|
2478
|
+
defaultValues: typeof result === "object" && result !== null ? result : {}
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
if (dataView) {
|
|
2482
|
+
return {
|
|
2483
|
+
dataViewKey: dataView.key,
|
|
2484
|
+
items: Array.isArray(result) ? result : result != null ? [result] : []
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
return result;
|
|
2488
|
+
}
|
|
2323
2489
|
function normalizeDuration(value) {
|
|
2324
2490
|
if (value === undefined) {
|
|
2325
2491
|
return;
|
|
@@ -2381,11 +2547,12 @@ function createToolExecutionError(message, code, retryAfterMs) {
|
|
|
2381
2547
|
var init_tool_adapter = __esm(() => {
|
|
2382
2548
|
init_json_schema_to_zod();
|
|
2383
2549
|
init_i18n();
|
|
2550
|
+
init_subagent_tool();
|
|
2384
2551
|
});
|
|
2385
2552
|
|
|
2386
2553
|
// src/tools/knowledge-tool.ts
|
|
2387
|
-
import { tool as
|
|
2388
|
-
import * as
|
|
2554
|
+
import { tool as tool3 } from "ai";
|
|
2555
|
+
import * as z3 from "zod";
|
|
2389
2556
|
function createKnowledgeQueryTool(retriever, knowledgeRefs, locale) {
|
|
2390
2557
|
const i18n = createAgentI18n(locale);
|
|
2391
2558
|
const optionalSpaces = knowledgeRefs.filter((k) => !k.required).map((k) => k.key).filter((key) => retriever.supportsSpace(key));
|
|
@@ -2394,15 +2561,15 @@ function createKnowledgeQueryTool(retriever, knowledgeRefs, locale) {
|
|
|
2394
2561
|
}
|
|
2395
2562
|
const spaceDescriptions = knowledgeRefs.filter((k) => !k.required && retriever.supportsSpace(k.key)).map((k) => `- ${k.key}: ${k.instructions ?? i18n.t("tool.knowledge.spaceDefault")}`).join(`
|
|
2396
2563
|
`);
|
|
2397
|
-
return
|
|
2564
|
+
return tool3({
|
|
2398
2565
|
description: `${i18n.t("tool.knowledge.description")}
|
|
2399
2566
|
|
|
2400
2567
|
${i18n.t("tool.knowledge.availableSpaces")}
|
|
2401
2568
|
${spaceDescriptions}`,
|
|
2402
|
-
inputSchema:
|
|
2403
|
-
query:
|
|
2404
|
-
spaceKey:
|
|
2405
|
-
topK:
|
|
2569
|
+
inputSchema: z3.object({
|
|
2570
|
+
query: z3.string().describe(i18n.t("tool.knowledge.param.query")),
|
|
2571
|
+
spaceKey: z3.enum(optionalSpaces).optional().describe(i18n.t("tool.knowledge.param.spaceKey")),
|
|
2572
|
+
topK: z3.number().optional().default(5).describe(i18n.t("tool.knowledge.param.topK"))
|
|
2406
2573
|
}),
|
|
2407
2574
|
execute: async ({ query, spaceKey, topK }) => {
|
|
2408
2575
|
const spacesToSearch = spaceKey ? [spaceKey] : optionalSpaces;
|
|
@@ -2442,6 +2609,33 @@ var init_knowledge_tool = __esm(() => {
|
|
|
2442
2609
|
init_i18n();
|
|
2443
2610
|
});
|
|
2444
2611
|
|
|
2612
|
+
// src/tools/memory-tools.ts
|
|
2613
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
2614
|
+
function createAnthropicMemoryTool(store) {
|
|
2615
|
+
const memory = anthropic.tools.memory_20250818({
|
|
2616
|
+
execute: async (action) => {
|
|
2617
|
+
switch (action.command) {
|
|
2618
|
+
case "view":
|
|
2619
|
+
return store.view(action.path ?? "/memories", action.view_range);
|
|
2620
|
+
case "create":
|
|
2621
|
+
return store.create(action.path ?? "/memories/untitled", action.file_text ?? "");
|
|
2622
|
+
case "str_replace":
|
|
2623
|
+
return store.strReplace(action.path ?? "/memories", action.old_str ?? "", action.new_str ?? "");
|
|
2624
|
+
case "insert":
|
|
2625
|
+
return store.insert(action.path ?? "/memories", action.insert_line ?? 0, action.insert_text ?? "");
|
|
2626
|
+
case "delete":
|
|
2627
|
+
return store.delete(action.path ?? "/memories");
|
|
2628
|
+
case "rename":
|
|
2629
|
+
return store.rename(action.old_path ?? "/memories", action.new_path ?? "/memories");
|
|
2630
|
+
default:
|
|
2631
|
+
return `Unknown command: ${action.command}`;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
return memory;
|
|
2636
|
+
}
|
|
2637
|
+
var init_memory_tools = () => {};
|
|
2638
|
+
|
|
2445
2639
|
// src/tools/mcp-client-helpers.ts
|
|
2446
2640
|
import {
|
|
2447
2641
|
Experimental_StdioMCPTransport as StdioClientTransport
|
|
@@ -2472,8 +2666,8 @@ function prefixToolNames(config, tools) {
|
|
|
2472
2666
|
return tools;
|
|
2473
2667
|
}
|
|
2474
2668
|
const prefixedTools = {};
|
|
2475
|
-
for (const [toolName,
|
|
2476
|
-
prefixedTools[`${prefix}_${toolName}`] =
|
|
2669
|
+
for (const [toolName, tool4] of Object.entries(tools)) {
|
|
2670
|
+
prefixedTools[`${prefix}_${toolName}`] = tool4;
|
|
2477
2671
|
}
|
|
2478
2672
|
return prefixedTools;
|
|
2479
2673
|
}
|
|
@@ -2579,12 +2773,12 @@ async function createMcpToolsets(configs, options = {}) {
|
|
|
2579
2773
|
for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
|
|
2580
2774
|
serverToolNames[serverName] = toolNames;
|
|
2581
2775
|
}
|
|
2582
|
-
for (const [toolName,
|
|
2776
|
+
for (const [toolName, tool4] of Object.entries(result.tools)) {
|
|
2583
2777
|
const hasCollision = combinedTools[toolName] !== undefined;
|
|
2584
2778
|
if (hasCollision && collisionStrategy === "error") {
|
|
2585
2779
|
throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
|
|
2586
2780
|
}
|
|
2587
|
-
combinedTools[toolName] =
|
|
2781
|
+
combinedTools[toolName] = tool4;
|
|
2588
2782
|
}
|
|
2589
2783
|
}
|
|
2590
2784
|
} catch (error) {
|
|
@@ -2607,11 +2801,231 @@ var init_mcp_client = __esm(() => {
|
|
|
2607
2801
|
init_mcp_client_helpers();
|
|
2608
2802
|
});
|
|
2609
2803
|
|
|
2804
|
+
// src/tools/agent-memory-store.ts
|
|
2805
|
+
function validateMemoryPath(path) {
|
|
2806
|
+
const normalized = path.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2807
|
+
if (!normalized.startsWith("/memories") || normalized.includes("..") || normalized === "/memories/.." || normalized.startsWith("/memories/../")) {
|
|
2808
|
+
throw new Error(`Invalid memory path: ${path}. Path must be under /memories and cannot contain traversal sequences.`);
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
// src/tools/in-memory-agent-memory-store.ts
|
|
2813
|
+
function formatSize(bytes) {
|
|
2814
|
+
if (bytes < 1024)
|
|
2815
|
+
return `${bytes}B`;
|
|
2816
|
+
if (bytes < 1024 * 1024)
|
|
2817
|
+
return `${(bytes / 1024).toFixed(1)}K`;
|
|
2818
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
class InMemoryAgentMemoryStore {
|
|
2822
|
+
store = new Map;
|
|
2823
|
+
async view(path, viewRange) {
|
|
2824
|
+
validateMemoryPath(path);
|
|
2825
|
+
const normalized = path.replace(/\/+/g, "/").replace(/\/$/, "") || "/memories";
|
|
2826
|
+
if (this.isDirectory(normalized)) {
|
|
2827
|
+
return this.listDirectory(normalized);
|
|
2828
|
+
}
|
|
2829
|
+
const content = this.store.get(normalized);
|
|
2830
|
+
if (content === undefined) {
|
|
2831
|
+
return `The path ${path} does not exist. Please provide a valid path.`;
|
|
2832
|
+
}
|
|
2833
|
+
const lines = content.split(`
|
|
2834
|
+
`);
|
|
2835
|
+
if (lines.length > 999999) {
|
|
2836
|
+
return `File ${path} exceeds maximum line limit of 999,999 lines.`;
|
|
2837
|
+
}
|
|
2838
|
+
const [start, end] = viewRange ?? [1, lines.length];
|
|
2839
|
+
const from = Math.max(0, start - 1);
|
|
2840
|
+
const to = Math.min(lines.length, end);
|
|
2841
|
+
const selected = lines.slice(from, to);
|
|
2842
|
+
const numbered = selected.map((line, i) => {
|
|
2843
|
+
const num = from + i + 1;
|
|
2844
|
+
return `${String(num).padStart(6)} ${line}`;
|
|
2845
|
+
}).join(`
|
|
2846
|
+
`);
|
|
2847
|
+
return `Here's the content of ${path} with line numbers:
|
|
2848
|
+
${numbered}`;
|
|
2849
|
+
}
|
|
2850
|
+
async create(path, fileText) {
|
|
2851
|
+
validateMemoryPath(path);
|
|
2852
|
+
const normalized = path.replace(/\/+/g, "/");
|
|
2853
|
+
if (this.store.has(normalized)) {
|
|
2854
|
+
return `Error: File ${path} already exists`;
|
|
2855
|
+
}
|
|
2856
|
+
this.ensureParentDir(normalized);
|
|
2857
|
+
this.store.set(normalized, fileText);
|
|
2858
|
+
return `File created successfully at: ${path}`;
|
|
2859
|
+
}
|
|
2860
|
+
async strReplace(path, oldStr, newStr) {
|
|
2861
|
+
validateMemoryPath(path);
|
|
2862
|
+
const normalized = path.replace(/\/+/g, "/");
|
|
2863
|
+
const content = this.store.get(normalized);
|
|
2864
|
+
if (content === undefined) {
|
|
2865
|
+
return `Error: The path ${path} does not exist. Please provide a valid path.`;
|
|
2866
|
+
}
|
|
2867
|
+
const count = (content.match(new RegExp(escapeRegex(oldStr), "g")) ?? []).length;
|
|
2868
|
+
if (count > 1) {
|
|
2869
|
+
const lines = content.split(`
|
|
2870
|
+
`);
|
|
2871
|
+
const lineNums = [];
|
|
2872
|
+
lines.forEach((line, i) => {
|
|
2873
|
+
if (line.includes(oldStr))
|
|
2874
|
+
lineNums.push(i + 1);
|
|
2875
|
+
});
|
|
2876
|
+
return `No replacement was performed. Multiple occurrences of old_str \`${oldStr}\` in lines: ${lineNums.join(", ")}. Please ensure it is unique`;
|
|
2877
|
+
}
|
|
2878
|
+
if (count === 0) {
|
|
2879
|
+
return `No replacement was performed, old_str \`${oldStr}\` did not appear verbatim in ${path}.`;
|
|
2880
|
+
}
|
|
2881
|
+
const newContent = content.replace(oldStr, newStr);
|
|
2882
|
+
this.store.set(normalized, newContent);
|
|
2883
|
+
const snippet = newContent.split(`
|
|
2884
|
+
`).slice(0, 5);
|
|
2885
|
+
const numbered = snippet.map((line, i) => `${String(i + 1).padStart(6)} ${line}`).join(`
|
|
2886
|
+
`);
|
|
2887
|
+
return `The memory file has been edited.
|
|
2888
|
+
${numbered}`;
|
|
2889
|
+
}
|
|
2890
|
+
async insert(path, insertLine, insertText) {
|
|
2891
|
+
validateMemoryPath(path);
|
|
2892
|
+
const normalized = path.replace(/\/+/g, "/");
|
|
2893
|
+
const content = this.store.get(normalized);
|
|
2894
|
+
if (content === undefined) {
|
|
2895
|
+
return `Error: The path ${path} does not exist`;
|
|
2896
|
+
}
|
|
2897
|
+
const lines = content.split(`
|
|
2898
|
+
`);
|
|
2899
|
+
const n = lines.length;
|
|
2900
|
+
if (insertLine < 0 || insertLine > n) {
|
|
2901
|
+
return `Error: Invalid \`insert_line\` parameter: ${insertLine}. It should be within the range of lines of the file: [0, ${n}]`;
|
|
2902
|
+
}
|
|
2903
|
+
lines.splice(insertLine, 0, insertText.replace(/\n$/, ""));
|
|
2904
|
+
this.store.set(normalized, lines.join(`
|
|
2905
|
+
`));
|
|
2906
|
+
return `The file ${path} has been edited.`;
|
|
2907
|
+
}
|
|
2908
|
+
async delete(path) {
|
|
2909
|
+
validateMemoryPath(path);
|
|
2910
|
+
const normalized = path.replace(/\/+/g, "/");
|
|
2911
|
+
if (this.isDirectory(normalized)) {
|
|
2912
|
+
const prefix = normalized === "/memories" ? "/memories/" : `${normalized}/`;
|
|
2913
|
+
for (const key of this.store.keys()) {
|
|
2914
|
+
if (key.startsWith(prefix) || key === normalized) {
|
|
2915
|
+
this.store.delete(key);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
} else {
|
|
2919
|
+
if (!this.store.has(normalized)) {
|
|
2920
|
+
return `Error: The path ${path} does not exist`;
|
|
2921
|
+
}
|
|
2922
|
+
this.store.delete(normalized);
|
|
2923
|
+
}
|
|
2924
|
+
return `Successfully deleted ${path}`;
|
|
2925
|
+
}
|
|
2926
|
+
async rename(oldPath, newPath) {
|
|
2927
|
+
validateMemoryPath(oldPath);
|
|
2928
|
+
validateMemoryPath(newPath);
|
|
2929
|
+
const oldNorm = oldPath.replace(/\/+/g, "/");
|
|
2930
|
+
const newNorm = newPath.replace(/\/+/g, "/");
|
|
2931
|
+
if (this.store.has(newNorm) || this.hasAnyChild(newNorm)) {
|
|
2932
|
+
return `Error: The destination ${newPath} already exists`;
|
|
2933
|
+
}
|
|
2934
|
+
if (this.isDirectory(oldNorm)) {
|
|
2935
|
+
const oldPrefix = `${oldNorm}/`;
|
|
2936
|
+
const entries = Array.from(this.store.entries()).filter(([k]) => k.startsWith(oldPrefix) || k === oldNorm);
|
|
2937
|
+
const newPrefix = `${newNorm}/`;
|
|
2938
|
+
for (const [k, v] of entries) {
|
|
2939
|
+
this.store.delete(k);
|
|
2940
|
+
const newKey = k === oldNorm ? newNorm : newPrefix + k.slice(oldPrefix.length);
|
|
2941
|
+
this.store.set(newKey, v);
|
|
2942
|
+
}
|
|
2943
|
+
} else {
|
|
2944
|
+
const content = this.store.get(oldNorm);
|
|
2945
|
+
if (content === undefined) {
|
|
2946
|
+
return `Error: The path ${oldPath} does not exist`;
|
|
2947
|
+
}
|
|
2948
|
+
this.store.delete(oldNorm);
|
|
2949
|
+
this.ensureParentDir(newNorm);
|
|
2950
|
+
this.store.set(newNorm, content);
|
|
2951
|
+
}
|
|
2952
|
+
return `Successfully renamed ${oldPath} to ${newPath}`;
|
|
2953
|
+
}
|
|
2954
|
+
isDirectory(path) {
|
|
2955
|
+
if (path === "/memories")
|
|
2956
|
+
return true;
|
|
2957
|
+
for (const key of this.store.keys()) {
|
|
2958
|
+
if (key.startsWith(path + "/"))
|
|
2959
|
+
return true;
|
|
2960
|
+
}
|
|
2961
|
+
return false;
|
|
2962
|
+
}
|
|
2963
|
+
hasAnyChild(path) {
|
|
2964
|
+
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
2965
|
+
for (const key of this.store.keys()) {
|
|
2966
|
+
if (key.startsWith(prefix))
|
|
2967
|
+
return true;
|
|
2968
|
+
}
|
|
2969
|
+
return false;
|
|
2970
|
+
}
|
|
2971
|
+
ensureParentDir(path) {
|
|
2972
|
+
const parts = path.split("/").filter(Boolean);
|
|
2973
|
+
parts.pop();
|
|
2974
|
+
for (let i = 1;i <= parts.length; i++) {
|
|
2975
|
+
const p = "/" + parts.slice(0, i).join("/");
|
|
2976
|
+
if (!this.store.has(p)) {
|
|
2977
|
+
this.store.set(p, "");
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
listDirectory(path) {
|
|
2982
|
+
const prefix = path === "/memories" ? "/memories/" : `${path}/`;
|
|
2983
|
+
const seen = new Set;
|
|
2984
|
+
const entries = [];
|
|
2985
|
+
if (path === "/memories") {
|
|
2986
|
+
entries.push({
|
|
2987
|
+
path: "/memories",
|
|
2988
|
+
size: Array.from(this.store.keys()).filter((k) => k.startsWith("/memories/")).reduce((acc, k) => acc + (this.store.get(k)?.length ?? 0), 0)
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
for (const key of this.store.keys()) {
|
|
2992
|
+
if (!key.startsWith(prefix) && key !== path)
|
|
2993
|
+
continue;
|
|
2994
|
+
const rel = key.slice(prefix.length);
|
|
2995
|
+
const first = rel.split("/")[0];
|
|
2996
|
+
if (!first || first.startsWith(".") || first === "node_modules")
|
|
2997
|
+
continue;
|
|
2998
|
+
const fullPath = path === "/memories" ? `/memories/${first}` : `${path}/${first}`;
|
|
2999
|
+
if (seen.has(fullPath))
|
|
3000
|
+
continue;
|
|
3001
|
+
seen.add(fullPath);
|
|
3002
|
+
let size = 0;
|
|
3003
|
+
if (this.store.has(key)) {
|
|
3004
|
+
size = (this.store.get(key) ?? "").length;
|
|
3005
|
+
} else {
|
|
3006
|
+
for (const k of this.store.keys()) {
|
|
3007
|
+
if (k.startsWith(fullPath + "/") || k === fullPath) {
|
|
3008
|
+
size += (this.store.get(k) ?? "").length;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
entries.push({ path: fullPath, size });
|
|
3013
|
+
}
|
|
3014
|
+
const lines = entries.slice(0, 50).map((e) => `${formatSize(e.size)} ${e.path}`).join(`
|
|
3015
|
+
`);
|
|
3016
|
+
return `Here're the files and directories up to 2 levels deep in ${path}, excluding hidden items and node_modules:
|
|
3017
|
+
${lines}`;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
function escapeRegex(s) {
|
|
3021
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2610
3024
|
// src/tools/mcp-server.ts
|
|
2611
3025
|
init_json_schema_to_zod();
|
|
2612
3026
|
init_i18n();
|
|
2613
3027
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2614
|
-
import * as
|
|
3028
|
+
import * as z4 from "zod";
|
|
2615
3029
|
import { sanitizeMcpName } from "@contractspec/lib.contracts-spec/jsonschema";
|
|
2616
3030
|
function agentToMcpServer(agent, spec) {
|
|
2617
3031
|
const i18n = createAgentI18n(spec.locale);
|
|
@@ -2621,9 +3035,9 @@ function agentToMcpServer(agent, spec) {
|
|
|
2621
3035
|
});
|
|
2622
3036
|
server.registerTool(sanitizeMcpName(spec.meta.key), {
|
|
2623
3037
|
description: spec.description ?? i18n.t("tool.mcp.agentDescription", { key: spec.meta.key }),
|
|
2624
|
-
inputSchema:
|
|
2625
|
-
message:
|
|
2626
|
-
sessionId:
|
|
3038
|
+
inputSchema: z4.object({
|
|
3039
|
+
message: z4.string().describe(i18n.t("tool.mcp.param.message")),
|
|
3040
|
+
sessionId: z4.string().optional().describe(i18n.t("tool.mcp.param.sessionId"))
|
|
2627
3041
|
})
|
|
2628
3042
|
}, async (args) => {
|
|
2629
3043
|
const { message, sessionId } = args;
|
|
@@ -2641,7 +3055,7 @@ function agentToMcpServer(agent, spec) {
|
|
|
2641
3055
|
};
|
|
2642
3056
|
});
|
|
2643
3057
|
for (const toolConfig of spec.tools) {
|
|
2644
|
-
const inputSchema = toolConfig.schema ? jsonSchemaToZodSafe(toolConfig.schema) :
|
|
3058
|
+
const inputSchema = toolConfig.schema ? jsonSchemaToZodSafe(toolConfig.schema) : z4.object({});
|
|
2645
3059
|
server.registerTool(sanitizeMcpName(`${spec.meta.key}.${toolConfig.name}`), {
|
|
2646
3060
|
description: toolConfig.description ?? i18n.t("tool.mcp.toolDescription", { name: toolConfig.name }),
|
|
2647
3061
|
inputSchema
|
|
@@ -2671,15 +3085,22 @@ function createAgentMcpServer(config) {
|
|
|
2671
3085
|
// src/tools/index.ts
|
|
2672
3086
|
init_tool_adapter();
|
|
2673
3087
|
init_knowledge_tool();
|
|
3088
|
+
init_subagent_tool();
|
|
2674
3089
|
init_mcp_client();
|
|
3090
|
+
init_memory_tools();
|
|
2675
3091
|
export {
|
|
3092
|
+
validateMemoryPath,
|
|
2676
3093
|
specToolsToAISDKTools,
|
|
2677
3094
|
specToolToAISDKTool,
|
|
2678
3095
|
mcpServerToTools,
|
|
2679
3096
|
createToolHandler,
|
|
3097
|
+
createSubagentTool,
|
|
3098
|
+
createOperationToolHandler,
|
|
2680
3099
|
createMcpToolsets,
|
|
2681
3100
|
createKnowledgeQueryTool,
|
|
3101
|
+
createAnthropicMemoryTool,
|
|
2682
3102
|
createAgentMcpServer,
|
|
2683
3103
|
buildToolHandlers,
|
|
2684
|
-
agentToMcpServer
|
|
3104
|
+
agentToMcpServer,
|
|
3105
|
+
InMemoryAgentMemoryStore
|
|
2685
3106
|
};
|
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
* Browser-safe stub for mcp-client. MCP stdio/process spawning cannot run in browser.
|
|
3
3
|
* Use this when bundling for client; the real implementation runs only on Node.
|
|
4
4
|
*/
|
|
5
|
-
import type { Tool } from
|
|
6
|
-
export type McpTransportType =
|
|
5
|
+
import type { Tool } from 'ai';
|
|
6
|
+
export type McpTransportType = 'stdio' | 'sse' | 'http';
|
|
7
7
|
interface McpClientBaseConfig {
|
|
8
8
|
name: string;
|
|
9
9
|
toolPrefix?: string;
|
|
10
10
|
clientName?: string;
|
|
11
11
|
clientVersion?: string;
|
|
12
|
-
authMethod?:
|
|
12
|
+
authMethod?: 'api-key' | 'oauth2' | 'bearer';
|
|
13
13
|
authHeaders?: Record<string, string>;
|
|
14
14
|
apiVersion?: string;
|
|
15
15
|
}
|
|
16
16
|
export interface McpStdioClientConfig extends McpClientBaseConfig {
|
|
17
|
-
transport?:
|
|
17
|
+
transport?: 'stdio';
|
|
18
18
|
command: string;
|
|
19
19
|
args?: string[];
|
|
20
20
|
env?: Record<string, string>;
|
|
21
21
|
cwd?: string;
|
|
22
22
|
}
|
|
23
23
|
export interface McpRemoteClientConfig extends McpClientBaseConfig {
|
|
24
|
-
transport:
|
|
24
|
+
transport: 'sse' | 'http';
|
|
25
25
|
url: string;
|
|
26
26
|
headers?: Record<string, string>;
|
|
27
27
|
authProvider?: unknown;
|
|
@@ -35,7 +35,7 @@ export interface McpClientResult {
|
|
|
35
35
|
serverToolNames: Record<string, string[]>;
|
|
36
36
|
}
|
|
37
37
|
export interface CreateMcpToolsetsOptions {
|
|
38
|
-
onNameCollision?:
|
|
38
|
+
onNameCollision?: 'overwrite' | 'error';
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Browser stub: MCP tools require Node (child_process). Returns empty tools.
|