@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.
Files changed (67) hide show
  1. package/README.md +41 -0
  2. package/dist/agent/agent-factory.d.ts +13 -0
  3. package/dist/agent/agent-factory.js +290 -63
  4. package/dist/agent/contract-spec-agent.d.ts +9 -0
  5. package/dist/agent/contract-spec-agent.js +287 -63
  6. package/dist/agent/index.js +353 -129
  7. package/dist/agent/json-runner.js +290 -66
  8. package/dist/agent/unified-agent.js +350 -126
  9. package/dist/exporters/claude-agent-exporter.js +12 -1
  10. package/dist/exporters/index.js +12 -1
  11. package/dist/exporters/opencode-exporter.js +11 -0
  12. package/dist/index.js +11 -0
  13. package/dist/interop/index.js +24 -2
  14. package/dist/interop/spec-consumer.js +11 -0
  15. package/dist/interop/tool-consumer.js +13 -2
  16. package/dist/node/agent/agent-factory.js +290 -63
  17. package/dist/node/agent/contract-spec-agent.js +287 -63
  18. package/dist/node/agent/index.js +353 -129
  19. package/dist/node/agent/json-runner.js +290 -66
  20. package/dist/node/agent/unified-agent.js +350 -126
  21. package/dist/node/exporters/claude-agent-exporter.js +12 -1
  22. package/dist/node/exporters/index.js +12 -1
  23. package/dist/node/exporters/opencode-exporter.js +11 -0
  24. package/dist/node/index.js +11 -0
  25. package/dist/node/interop/index.js +24 -2
  26. package/dist/node/interop/spec-consumer.js +11 -0
  27. package/dist/node/interop/tool-consumer.js +13 -2
  28. package/dist/node/providers/claude-agent-sdk/adapter.js +11 -0
  29. package/dist/node/providers/claude-agent-sdk/index.js +11 -0
  30. package/dist/node/providers/index.js +11 -0
  31. package/dist/node/providers/opencode-sdk/adapter.js +11 -0
  32. package/dist/node/providers/opencode-sdk/index.js +11 -0
  33. package/dist/node/spec/index.js +11 -0
  34. package/dist/node/spec/spec.js +11 -0
  35. package/dist/node/tools/agent-memory-store.js +24 -0
  36. package/dist/node/tools/in-memory-agent-memory-store.js +236 -0
  37. package/dist/node/tools/index.js +463 -42
  38. package/dist/node/tools/memory-tools.js +45 -0
  39. package/dist/node/tools/operation-tool-handler.js +35 -0
  40. package/dist/node/tools/subagent-tool.js +95 -0
  41. package/dist/node/tools/tool-adapter.js +192 -25
  42. package/dist/providers/claude-agent-sdk/adapter.js +11 -0
  43. package/dist/providers/claude-agent-sdk/index.js +11 -0
  44. package/dist/providers/index.js +11 -0
  45. package/dist/providers/opencode-sdk/adapter.js +11 -0
  46. package/dist/providers/opencode-sdk/index.js +11 -0
  47. package/dist/spec/index.js +11 -0
  48. package/dist/spec/spec.d.ts +69 -1
  49. package/dist/spec/spec.js +11 -0
  50. package/dist/tools/agent-memory-store.d.ts +26 -0
  51. package/dist/tools/agent-memory-store.js +24 -0
  52. package/dist/tools/agent-memory-store.test.d.ts +1 -0
  53. package/dist/tools/in-memory-agent-memory-store.d.ts +18 -0
  54. package/dist/tools/in-memory-agent-memory-store.js +236 -0
  55. package/dist/tools/index.d.ts +5 -0
  56. package/dist/tools/index.js +463 -42
  57. package/dist/tools/mcp-client.browser.d.ts +6 -6
  58. package/dist/tools/memory-tools.d.ts +29 -0
  59. package/dist/tools/memory-tools.js +45 -0
  60. package/dist/tools/operation-tool-handler.d.ts +24 -0
  61. package/dist/tools/operation-tool-handler.js +35 -0
  62. package/dist/tools/subagent-tool.d.ts +66 -0
  63. package/dist/tools/subagent-tool.js +95 -0
  64. package/dist/tools/tool-adapter.d.ts +26 -10
  65. package/dist/tools/tool-adapter.js +192 -25
  66. package/dist/types.d.ts +9 -3
  67. package/package.json +67 -7
package/README.md CHANGED
@@ -31,6 +31,47 @@ Stateful AI agent orchestration with type-safe specs, tool execution, knowledge
31
31
 
32
32
  These ports are optional by design and keep external runtime dependencies decoupled from core agent contracts.
33
33
 
34
+ ## Operations as Tools
35
+
36
+ **Preferred pattern:** One contract → multiple surfaces. Reference a ContractSpec operation in your agent; the tool handler and input schema are derived automatically.
37
+
38
+ ```ts
39
+ import { defineAgent } from '@contractspec/lib.ai-agent';
40
+ import { defineQuery } from '@contractspec/lib.contracts-spec';
41
+ import { OperationSpecRegistry } from '@contractspec/lib.contracts-spec';
42
+ import { createAgentFactory } from '@contractspec/lib.ai-agent/agent/agent-factory';
43
+
44
+ // 1. Define operation (existing pattern)
45
+ const SearchKnowledgeQuery = defineQuery({
46
+ meta: { key: 'knowledge.search', version: '1.0.0', owners: ['@platform'], goal: '...', context: '...' },
47
+ io: { input: SearchInputModel, output: SearchOutputModel },
48
+ });
49
+
50
+ // 2. Register and bind handler
51
+ const ops = new OperationSpecRegistry().register(SearchKnowledgeQuery).bind(SearchKnowledgeQuery, handler);
52
+
53
+ // 3. Reference in agent
54
+ const agent = defineAgent({
55
+ meta: { key: 'support.bot', version: '1.0.0', owners: ['@platform'] },
56
+ instructions: 'Resolve support tickets.',
57
+ tools: [
58
+ { name: 'search_knowledge', operationRef: { key: 'knowledge.search', version: '1.0.0' }, automationSafe: true },
59
+ ],
60
+ });
61
+
62
+ // 4. Create factory with operationRegistry
63
+ const factory = createAgentFactory({
64
+ defaultModel,
65
+ registry: agentRegistry,
66
+ toolHandlers: new Map(),
67
+ operationRegistry: ops,
68
+ });
69
+ ```
70
+
71
+ **Output rendering:** When tool output should render via PresentationSpec, FormSpec, or DataViewSpec, add `outputPresentation`, `outputForm`, or `outputDataView` to `AgentToolConfig` (at most one per tool). The tool adapter wraps raw output as `{ presentationKey, data }`, `{ formKey, defaultValues }`, or `{ dataViewKey, items }` for `ToolResultRenderer`. OperationSpec can also declare these refs; when the tool has no output refs, the operation's refs are used as fallback.
72
+
73
+ **Fallback (inline tools):** When the tool is not an operation (LLM subcalls, external APIs), use inline `AgentToolConfig` with `schema` and a manual handler in `toolHandlers`.
74
+
34
75
  ## Bundle spec / surface-runtime integration
35
76
 
36
77
  When building planner agents for `@contractspec/lib.surface-runtime`, planner tools (e.g. `propose-patch`) from `@contractspec/lib.surface-runtime/runtime/planner-tools` map to `AgentToolConfig`. Wire `proposePatchToolConfig` into `AgentSpec.tools`; the handler should validate via `validatePatchProposal` and return `SurfacePatchProposal`. Tools from surface-runtime planner-tools are `AgentToolConfig`-compatible.
@@ -1,10 +1,13 @@
1
1
  import type { LanguageModel, Tool } from 'ai';
2
2
  import type { KnowledgeRetriever } from '@contractspec/lib.knowledge/retriever';
3
3
  import type { ModelSelector } from '@contractspec/lib.ai-providers/selector-types';
4
+ import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
4
5
  import type { AgentSpec } from '../spec/spec';
5
6
  import type { AgentRegistry } from '../spec/registry';
6
7
  import type { ToolHandler } from '../types';
7
8
  import type { McpClientConfig } from '../tools/mcp-client';
9
+ import type { SubagentRegistry } from '../tools/tool-adapter';
10
+ import type { AgentMemoryStore } from '../tools/agent-memory-store';
8
11
  import type { AgentSessionStore } from '../session/store';
9
12
  import type { TelemetryCollector } from '../telemetry/adapter';
10
13
  import type { PostHogLLMConfig, PostHogTracingOptions } from '../telemetry/posthog-types';
@@ -19,6 +22,12 @@ export interface AgentFactoryConfig {
19
22
  registry: AgentRegistry;
20
23
  /** Global tool handlers map */
21
24
  toolHandlers: Map<string, ToolHandler>;
25
+ /** Optional OperationSpecRegistry for operation-backed tools (operationRef) */
26
+ operationRegistry?: OperationSpecRegistry;
27
+ /** Optional registry for subagent-backed tools (subagentRef) */
28
+ subagentRegistry?: SubagentRegistry;
29
+ /** Optional storage for memory tools (when spec.memoryTools.provider is anthropic) */
30
+ agentMemoryStore?: AgentMemoryStore;
22
31
  /** Optional knowledge retriever */
23
32
  knowledgeRetriever?: KnowledgeRetriever;
24
33
  /** Optional session store */
@@ -48,6 +57,10 @@ export interface CreateAgentOptions {
48
57
  additionalTools?: Record<string, Tool<unknown, unknown>>;
49
58
  /** MCP servers for this instance */
50
59
  mcpServers?: McpClientConfig[];
60
+ /** Override OperationSpecRegistry for operation-backed tools */
61
+ operationRegistry?: OperationSpecRegistry;
62
+ /** Override SubagentRegistry for subagent-backed tools */
63
+ subagentRegistry?: SubagentRegistry;
51
64
  }
52
65
  /**
53
66
  * Factory for creating ContractSpec agents from specs.
@@ -2156,6 +2156,17 @@ function defineAgent(spec) {
2156
2156
  }));
2157
2157
  }
2158
2158
  toolNames.add(tool.name);
2159
+ if (tool.subagentRef && tool.operationRef) {
2160
+ throw new Error(`Agent ${spec.meta.key} tool "${tool.name}" cannot have both subagentRef and operationRef. Use one.`);
2161
+ }
2162
+ const outputRefCount = [
2163
+ tool.outputPresentation,
2164
+ tool.outputForm,
2165
+ tool.outputDataView
2166
+ ].filter(Boolean).length;
2167
+ if (outputRefCount > 1) {
2168
+ throw new Error(`Agent ${spec.meta.key} tool "${tool.name}" has multiple output refs (outputPresentation, outputForm, outputDataView). Use at most one.`);
2169
+ }
2159
2170
  }
2160
2171
  return Object.freeze(spec);
2161
2172
  }
@@ -2299,15 +2310,124 @@ function jsonSchemaToZodSafe(schema) {
2299
2310
  }
2300
2311
  var init_json_schema_to_zod = () => {};
2301
2312
 
2313
+ // src/tools/operation-tool-handler.ts
2314
+ function toolCtxToHandlerCtx(ctx) {
2315
+ return {
2316
+ traceId: ctx.metadata?.traceId,
2317
+ organizationId: ctx.tenantId ?? null,
2318
+ userId: ctx.actorId ?? null,
2319
+ actor: ctx.actorId ? "user" : "anonymous",
2320
+ channel: "agent",
2321
+ roles: []
2322
+ };
2323
+ }
2324
+ function createOperationToolHandler(registry, operationRef) {
2325
+ return async (input, context) => {
2326
+ const handlerCtx = toolCtxToHandlerCtx(context);
2327
+ const result = await registry.execute(operationRef.key, operationRef.version, input ?? {}, handlerCtx);
2328
+ return result;
2329
+ };
2330
+ }
2331
+
2332
+ // src/tools/subagent-tool.ts
2333
+ import { readUIMessageStream, tool } from "ai";
2334
+ import { z as z2 } from "zod";
2335
+ function toReadableStream(iterable) {
2336
+ if (iterable instanceof ReadableStream) {
2337
+ return iterable;
2338
+ }
2339
+ return new ReadableStream({
2340
+ async start(controller) {
2341
+ try {
2342
+ for await (const chunk of iterable) {
2343
+ controller.enqueue(chunk);
2344
+ }
2345
+ } finally {
2346
+ controller.close();
2347
+ }
2348
+ }
2349
+ });
2350
+ }
2351
+ function createSubagentTool(options) {
2352
+ const {
2353
+ subagent,
2354
+ description = "Research a topic or question in depth.",
2355
+ taskParam = "task",
2356
+ toModelSummary = true,
2357
+ passConversationHistory = false
2358
+ } = options;
2359
+ const inputSchema = z2.object({
2360
+ [taskParam]: z2.string().describe("The research task to complete")
2361
+ });
2362
+ const execute = async function* (input, options2) {
2363
+ const task = String(input[taskParam] ?? input.task ?? "");
2364
+ const { abortSignal, messages } = options2 ?? {};
2365
+ if (passConversationHistory && messages && messages.length > 0 && typeof subagent.generate === "function") {
2366
+ const result2 = await subagent.generate({
2367
+ messages: [...messages, { role: "user", content: task }],
2368
+ abortSignal
2369
+ });
2370
+ yield { parts: [{ type: "text", text: result2.text }] };
2371
+ return;
2372
+ }
2373
+ const result = await subagent.stream({
2374
+ prompt: task,
2375
+ abortSignal
2376
+ });
2377
+ const uiStream = result.toUIMessageStream();
2378
+ const stream = toReadableStream(uiStream);
2379
+ for await (const message of readUIMessageStream({
2380
+ stream
2381
+ })) {
2382
+ yield message;
2383
+ }
2384
+ };
2385
+ const toolOptions = {
2386
+ description,
2387
+ inputSchema,
2388
+ execute,
2389
+ ...toModelSummary && {
2390
+ toModelOutput: ({
2391
+ output
2392
+ }) => {
2393
+ const parts = output?.parts;
2394
+ if (!Array.isArray(parts)) {
2395
+ return { type: "text", value: "Task completed." };
2396
+ }
2397
+ const lastTextPart = [...parts].reverse().find((p) => p?.type === "text");
2398
+ return {
2399
+ type: "text",
2400
+ value: lastTextPart?.text ?? "Task completed."
2401
+ };
2402
+ }
2403
+ }
2404
+ };
2405
+ return tool(toolOptions);
2406
+ }
2407
+ var init_subagent_tool = () => {};
2408
+
2302
2409
  // src/tools/tool-adapter.ts
2303
- import { tool } from "ai";
2304
- function specToolToAISDKTool(specTool, handler, context = {}) {
2410
+ import { tool as tool2 } from "ai";
2411
+ function isAsyncGenerator(value) {
2412
+ return typeof value === "object" && value !== null && typeof value.next === "function" && typeof value[Symbol.asyncIterator] === "function";
2413
+ }
2414
+ function specToolToAISDKTool(specTool, handler, context = {}, effectiveInputSchema, operationSpec) {
2305
2415
  let lastInvocationAt;
2306
- return tool({
2416
+ const inputSchema = effectiveInputSchema ?? jsonSchemaToZodSafe(specTool.schema);
2417
+ const buildContext = (signal) => ({
2418
+ agentId: context.agentId ?? "unknown",
2419
+ sessionId: context.sessionId ?? "unknown",
2420
+ tenantId: context.tenantId,
2421
+ actorId: context.actorId,
2422
+ locale: context.locale,
2423
+ metadata: context.metadata,
2424
+ signal
2425
+ });
2426
+ return tool2({
2307
2427
  description: specTool.description ?? specTool.name,
2308
- inputSchema: jsonSchemaToZodSafe(specTool.schema),
2428
+ inputSchema,
2309
2429
  needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
2310
- execute: async (input) => {
2430
+ execute: async function* (input, options) {
2311
2431
  const now = Date.now();
2312
2432
  const cooldownMs = normalizeDuration(specTool.cooldownMs);
2313
2433
  if (cooldownMs && lastInvocationAt !== undefined) {
@@ -2318,19 +2438,20 @@ function specToolToAISDKTool(specTool, handler, context = {}) {
2318
2438
  }
2319
2439
  }
2320
2440
  const timeoutMs = normalizeDuration(specTool.timeoutMs);
2321
- const { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
2441
+ const signal = options?.abortSignal ?? context.signal;
2442
+ const { signal: timeoutSignal, dispose } = createTimeoutSignal(signal, timeoutMs);
2322
2443
  try {
2323
- const execution = handler(input, {
2324
- agentId: context.agentId ?? "unknown",
2325
- sessionId: context.sessionId ?? "unknown",
2326
- tenantId: context.tenantId,
2327
- actorId: context.actorId,
2328
- locale: context.locale,
2329
- metadata: context.metadata,
2330
- signal
2331
- });
2332
- const result = timeoutMs ? await withTimeout(execution, timeoutMs, specTool.name) : await execution;
2333
- return typeof result === "string" ? result : JSON.stringify(result);
2444
+ const execution = handler(input, buildContext(timeoutSignal));
2445
+ if (isAsyncGenerator(execution)) {
2446
+ for await (const raw of execution) {
2447
+ const wrapped = wrapToolOutputForRendering(specTool, raw, operationSpec);
2448
+ yield typeof wrapped === "string" ? wrapped : wrapped;
2449
+ }
2450
+ } else {
2451
+ const raw = timeoutMs ? await withTimeout(Promise.resolve(execution), timeoutMs, specTool.name) : await Promise.resolve(execution);
2452
+ const wrapped = wrapToolOutputForRendering(specTool, raw, operationSpec);
2453
+ yield typeof wrapped === "string" ? wrapped : wrapped;
2454
+ }
2334
2455
  } finally {
2335
2456
  dispose();
2336
2457
  lastInvocationAt = Date.now();
@@ -2338,27 +2459,83 @@ function specToolToAISDKTool(specTool, handler, context = {}) {
2338
2459
  }
2339
2460
  });
2340
2461
  }
2341
- function specToolsToAISDKTools(specTools, handlers, context = {}) {
2462
+ function specToolsToAISDKTools(specTools, handlers, context = {}, options) {
2342
2463
  const tools = {};
2343
2464
  for (const specTool of specTools) {
2344
- const handler = handlers.get(specTool.name);
2345
- if (!handler) {
2346
- throw new Error(createAgentI18n(context.locale).t("error.missingToolHandler", {
2347
- name: specTool.name
2348
- }));
2465
+ if (specTool.subagentRef && options?.subagentRegistry) {
2466
+ const subagent = options.subagentRegistry.get(specTool.subagentRef.agentId);
2467
+ if (!subagent) {
2468
+ throw new Error(`Subagent not found: ${specTool.subagentRef.agentId}. Register it in subagentRegistry.`);
2469
+ }
2470
+ if (specTool.requiresApproval === true || specTool.automationSafe === false) {
2471
+ 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`);
2472
+ }
2473
+ tools[specTool.name] = createSubagentTool({
2474
+ subagent,
2475
+ description: specTool.description ?? specTool.name,
2476
+ toModelSummary: specTool.subagentRef.toModelSummary ?? true,
2477
+ passConversationHistory: specTool.subagentRef.passConversationHistory
2478
+ });
2479
+ continue;
2480
+ }
2481
+ let handler;
2482
+ let effectiveInputSchema;
2483
+ let op;
2484
+ if (specTool.operationRef && options?.operationRegistry) {
2485
+ op = options.operationRegistry.get(specTool.operationRef.key, specTool.operationRef.version);
2486
+ if (!op) {
2487
+ throw new Error(`Operation not found: ${specTool.operationRef.key}${specTool.operationRef.version ? `.v${specTool.operationRef.version}` : ""}`);
2488
+ }
2489
+ handler = createOperationToolHandler(options.operationRegistry, specTool.operationRef);
2490
+ effectiveInputSchema = op.io.input?.getZod?.();
2491
+ } else {
2492
+ const manualHandler = handlers.get(specTool.name);
2493
+ if (!manualHandler) {
2494
+ if (specTool.subagentRef) {
2495
+ throw new Error(`Subagent tool "${specTool.name}" requires subagentRegistry. Pass subagentRegistry in ContractSpecAgentConfig.`);
2496
+ }
2497
+ throw new Error(createAgentI18n(context.locale).t("error.missingToolHandler", {
2498
+ name: specTool.name
2499
+ }));
2500
+ }
2501
+ handler = manualHandler;
2349
2502
  }
2350
- tools[specTool.name] = specToolToAISDKTool(specTool, handler, context);
2503
+ tools[specTool.name] = specToolToAISDKTool(specTool, handler, context, effectiveInputSchema, op);
2351
2504
  }
2352
2505
  return tools;
2353
2506
  }
2354
2507
  function createToolHandler(handler) {
2355
- return async (input, context) => {
2508
+ return (input, context) => {
2356
2509
  return handler(input, context);
2357
2510
  };
2358
2511
  }
2359
2512
  function buildToolHandlers(handlersObj) {
2360
2513
  return new Map(Object.entries(handlersObj));
2361
2514
  }
2515
+ function wrapToolOutputForRendering(specTool, result, operationSpec) {
2516
+ const presentation = specTool.outputPresentation ?? operationSpec?.outputPresentation;
2517
+ const form = specTool.outputForm ?? operationSpec?.outputForm;
2518
+ const dataView = specTool.outputDataView ?? operationSpec?.outputDataView;
2519
+ if (presentation) {
2520
+ return {
2521
+ presentationKey: presentation.key,
2522
+ data: result
2523
+ };
2524
+ }
2525
+ if (form) {
2526
+ return {
2527
+ formKey: form.key,
2528
+ defaultValues: typeof result === "object" && result !== null ? result : {}
2529
+ };
2530
+ }
2531
+ if (dataView) {
2532
+ return {
2533
+ dataViewKey: dataView.key,
2534
+ items: Array.isArray(result) ? result : result != null ? [result] : []
2535
+ };
2536
+ }
2537
+ return result;
2538
+ }
2362
2539
  function normalizeDuration(value) {
2363
2540
  if (value === undefined) {
2364
2541
  return;
@@ -2420,11 +2597,12 @@ function createToolExecutionError(message, code, retryAfterMs) {
2420
2597
  var init_tool_adapter = __esm(() => {
2421
2598
  init_json_schema_to_zod();
2422
2599
  init_i18n();
2600
+ init_subagent_tool();
2423
2601
  });
2424
2602
 
2425
2603
  // src/tools/knowledge-tool.ts
2426
- import { tool as tool2 } from "ai";
2427
- import * as z2 from "zod";
2604
+ import { tool as tool3 } from "ai";
2605
+ import * as z3 from "zod";
2428
2606
  function createKnowledgeQueryTool(retriever, knowledgeRefs, locale) {
2429
2607
  const i18n = createAgentI18n(locale);
2430
2608
  const optionalSpaces = knowledgeRefs.filter((k) => !k.required).map((k) => k.key).filter((key) => retriever.supportsSpace(key));
@@ -2433,15 +2611,15 @@ function createKnowledgeQueryTool(retriever, knowledgeRefs, locale) {
2433
2611
  }
2434
2612
  const spaceDescriptions = knowledgeRefs.filter((k) => !k.required && retriever.supportsSpace(k.key)).map((k) => `- ${k.key}: ${k.instructions ?? i18n.t("tool.knowledge.spaceDefault")}`).join(`
2435
2613
  `);
2436
- return tool2({
2614
+ return tool3({
2437
2615
  description: `${i18n.t("tool.knowledge.description")}
2438
2616
 
2439
2617
  ${i18n.t("tool.knowledge.availableSpaces")}
2440
2618
  ${spaceDescriptions}`,
2441
- inputSchema: z2.object({
2442
- query: z2.string().describe(i18n.t("tool.knowledge.param.query")),
2443
- spaceKey: z2.enum(optionalSpaces).optional().describe(i18n.t("tool.knowledge.param.spaceKey")),
2444
- topK: z2.number().optional().default(5).describe(i18n.t("tool.knowledge.param.topK"))
2619
+ inputSchema: z3.object({
2620
+ query: z3.string().describe(i18n.t("tool.knowledge.param.query")),
2621
+ spaceKey: z3.enum(optionalSpaces).optional().describe(i18n.t("tool.knowledge.param.spaceKey")),
2622
+ topK: z3.number().optional().default(5).describe(i18n.t("tool.knowledge.param.topK"))
2445
2623
  }),
2446
2624
  execute: async ({ query, spaceKey, topK }) => {
2447
2625
  const spacesToSearch = spaceKey ? [spaceKey] : optionalSpaces;
@@ -2481,6 +2659,33 @@ var init_knowledge_tool = __esm(() => {
2481
2659
  init_i18n();
2482
2660
  });
2483
2661
 
2662
+ // src/tools/memory-tools.ts
2663
+ import { anthropic } from "@ai-sdk/anthropic";
2664
+ function createAnthropicMemoryTool(store) {
2665
+ const memory = anthropic.tools.memory_20250818({
2666
+ execute: async (action) => {
2667
+ switch (action.command) {
2668
+ case "view":
2669
+ return store.view(action.path ?? "/memories", action.view_range);
2670
+ case "create":
2671
+ return store.create(action.path ?? "/memories/untitled", action.file_text ?? "");
2672
+ case "str_replace":
2673
+ return store.strReplace(action.path ?? "/memories", action.old_str ?? "", action.new_str ?? "");
2674
+ case "insert":
2675
+ return store.insert(action.path ?? "/memories", action.insert_line ?? 0, action.insert_text ?? "");
2676
+ case "delete":
2677
+ return store.delete(action.path ?? "/memories");
2678
+ case "rename":
2679
+ return store.rename(action.old_path ?? "/memories", action.new_path ?? "/memories");
2680
+ default:
2681
+ return `Unknown command: ${action.command}`;
2682
+ }
2683
+ }
2684
+ });
2685
+ return memory;
2686
+ }
2687
+ var init_memory_tools = () => {};
2688
+
2484
2689
  // src/tools/mcp-client-helpers.ts
2485
2690
  import {
2486
2691
  Experimental_StdioMCPTransport as StdioClientTransport
@@ -2511,8 +2716,8 @@ function prefixToolNames(config, tools) {
2511
2716
  return tools;
2512
2717
  }
2513
2718
  const prefixedTools = {};
2514
- for (const [toolName, tool3] of Object.entries(tools)) {
2515
- prefixedTools[`${prefix}_${toolName}`] = tool3;
2719
+ for (const [toolName, tool4] of Object.entries(tools)) {
2720
+ prefixedTools[`${prefix}_${toolName}`] = tool4;
2516
2721
  }
2517
2722
  return prefixedTools;
2518
2723
  }
@@ -2618,12 +2823,12 @@ async function createMcpToolsets(configs, options = {}) {
2618
2823
  for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
2619
2824
  serverToolNames[serverName] = toolNames;
2620
2825
  }
2621
- for (const [toolName, tool3] of Object.entries(result.tools)) {
2826
+ for (const [toolName, tool4] of Object.entries(result.tools)) {
2622
2827
  const hasCollision = combinedTools[toolName] !== undefined;
2623
2828
  if (hasCollision && collisionStrategy === "error") {
2624
2829
  throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
2625
2830
  }
2626
- combinedTools[toolName] = tool3;
2831
+ combinedTools[toolName] = tool4;
2627
2832
  }
2628
2833
  }
2629
2834
  } catch (error) {
@@ -3146,7 +3351,7 @@ import {
3146
3351
  stepCountIs
3147
3352
  } from "ai";
3148
3353
  import { randomUUID } from "crypto";
3149
- import * as z3 from "zod";
3354
+ import * as z4 from "zod";
3150
3355
 
3151
3356
  class ContractSpecAgent {
3152
3357
  version = "agent-v1";
@@ -3175,12 +3380,19 @@ class ContractSpecAgent {
3175
3380
  }
3176
3381
  try {
3177
3382
  const instructions = await injectStaticKnowledge(effectiveConfig.spec.instructions, effectiveConfig.spec.knowledge ?? [], effectiveConfig.knowledgeRetriever);
3178
- const specTools = specToolsToAISDKTools(effectiveConfig.spec.tools, effectiveConfig.toolHandlers, { agentId: agentKey(effectiveConfig.spec.meta) });
3383
+ const specTools = specToolsToAISDKTools(effectiveConfig.spec.tools, effectiveConfig.toolHandlers, { agentId: agentKey(effectiveConfig.spec.meta) }, {
3384
+ operationRegistry: effectiveConfig.operationRegistry,
3385
+ subagentRegistry: effectiveConfig.subagentRegistry
3386
+ });
3179
3387
  const knowledgeTool = effectiveConfig.knowledgeRetriever ? createKnowledgeQueryTool(effectiveConfig.knowledgeRetriever, effectiveConfig.spec.knowledge ?? []) : null;
3388
+ const memoryTool = effectiveConfig.spec.memoryTools?.provider === "anthropic" && effectiveConfig.agentMemoryStore ? createAnthropicMemoryTool(effectiveConfig.agentMemoryStore) : null;
3180
3389
  const reservedToolNames = new Set(Object.keys(specTools));
3181
3390
  if (knowledgeTool) {
3182
3391
  reservedToolNames.add("query_knowledge");
3183
3392
  }
3393
+ if (memoryTool) {
3394
+ reservedToolNames.add("memory");
3395
+ }
3184
3396
  const conflictingMcpTools = Object.keys(mcpToolset?.tools ?? {}).filter((toolName) => reservedToolNames.has(toolName));
3185
3397
  if (conflictingMcpTools.length > 0) {
3186
3398
  throw new Error(`MCP tools conflict with agent tools: ${conflictingMcpTools.join(", ")}. Configure MCP toolPrefix values to avoid collisions.`);
@@ -3188,6 +3400,7 @@ class ContractSpecAgent {
3188
3400
  const tools = {
3189
3401
  ...specTools,
3190
3402
  ...knowledgeTool ? { query_knowledge: knowledgeTool } : {},
3403
+ ...memoryTool ? { memory: memoryTool } : {},
3191
3404
  ...mcpToolset?.tools ?? {},
3192
3405
  ...effectiveConfig.additionalTools ?? {}
3193
3406
  };
@@ -3219,7 +3432,7 @@ class ContractSpecAgent {
3219
3432
  stepIndex: 0,
3220
3433
  stepStartedAt: new Date
3221
3434
  });
3222
- if (this.config.sessionStore) {
3435
+ if (this.config.sessionStore && !params.messages?.length) {
3223
3436
  const existing = await this.config.sessionStore.get(sessionId);
3224
3437
  if (!existing) {
3225
3438
  await this.config.sessionStore.create({
@@ -3237,14 +3450,9 @@ class ContractSpecAgent {
3237
3450
  }
3238
3451
  await this.config.sessionStore.appendMessage(sessionId, {
3239
3452
  role: "user",
3240
- content: params.prompt
3453
+ content: params.prompt ?? ""
3241
3454
  });
3242
3455
  }
3243
- const prompt = params.systemOverride ? `${this.instructions}
3244
-
3245
- ${params.systemOverride}
3246
-
3247
- ${params.prompt}` : params.prompt;
3248
3456
  const model = await this.resolveModelForCall({
3249
3457
  sessionId,
3250
3458
  traceId,
@@ -3252,18 +3460,33 @@ ${params.prompt}` : params.prompt;
3252
3460
  });
3253
3461
  const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
3254
3462
  const inner = this.createInnerAgent(model, effectiveMaxSteps);
3463
+ const generateOptions = {
3464
+ abortSignal: params.signal,
3465
+ options: {
3466
+ tenantId: params.options?.tenantId,
3467
+ actorId: params.options?.actorId,
3468
+ sessionId,
3469
+ metadata: params.options?.metadata
3470
+ }
3471
+ };
3255
3472
  let result;
3256
3473
  try {
3257
- result = await inner.generate({
3258
- prompt,
3259
- abortSignal: params.signal,
3260
- options: {
3261
- tenantId: params.options?.tenantId,
3262
- actorId: params.options?.actorId,
3263
- sessionId,
3264
- metadata: params.options?.metadata
3265
- }
3266
- });
3474
+ if (params.messages && params.messages.length > 0) {
3475
+ result = await inner.generate({
3476
+ messages: params.messages,
3477
+ ...generateOptions
3478
+ });
3479
+ } else {
3480
+ const prompt = params.systemOverride && params.prompt ? `${this.instructions}
3481
+
3482
+ ${params.systemOverride}
3483
+
3484
+ ${params.prompt}` : params.prompt ?? "";
3485
+ result = await inner.generate({
3486
+ prompt,
3487
+ ...generateOptions
3488
+ });
3489
+ }
3267
3490
  } catch (error) {
3268
3491
  if (this.config.sessionStore) {
3269
3492
  await this.config.sessionStore.update(sessionId, {
@@ -3323,11 +3546,11 @@ ${params.prompt}` : params.prompt;
3323
3546
  stepIndex: 0,
3324
3547
  stepStartedAt: new Date
3325
3548
  });
3326
- const prompt = params.systemOverride ? `${this.instructions}
3549
+ const prompt = params.systemOverride && params.prompt ? `${this.instructions}
3327
3550
 
3328
3551
  ${params.systemOverride}
3329
3552
 
3330
- ${params.prompt}` : params.prompt;
3553
+ ${params.prompt}` : params.prompt ?? "";
3331
3554
  const model = await this.resolveModelForCall({
3332
3555
  sessionId,
3333
3556
  traceId,
@@ -3351,7 +3574,7 @@ ${params.prompt}` : params.prompt;
3351
3574
  }
3352
3575
  await this.config.sessionStore.appendMessage(sessionId, {
3353
3576
  role: "user",
3354
- content: params.prompt
3577
+ content: prompt
3355
3578
  });
3356
3579
  await this.config.sessionStore.update(sessionId, { status: "running" });
3357
3580
  }
@@ -3482,14 +3705,15 @@ var init_contract_spec_agent = __esm(() => {
3482
3705
  init_spec();
3483
3706
  init_tool_adapter();
3484
3707
  init_knowledge_tool();
3708
+ init_memory_tools();
3485
3709
  init_mcp_client();
3486
3710
  init_injector();
3487
3711
  init_adapter();
3488
- ContractSpecCallOptionsSchema = z3.object({
3489
- tenantId: z3.string().optional(),
3490
- actorId: z3.string().optional(),
3491
- sessionId: z3.string().optional(),
3492
- metadata: z3.record(z3.string(), z3.unknown()).optional()
3712
+ ContractSpecCallOptionsSchema = z4.object({
3713
+ tenantId: z4.string().optional(),
3714
+ actorId: z4.string().optional(),
3715
+ sessionId: z4.string().optional(),
3716
+ metadata: z4.record(z4.string(), z4.unknown()).optional()
3493
3717
  });
3494
3718
  });
3495
3719
 
@@ -3525,6 +3749,9 @@ class AgentFactory {
3525
3749
  spec,
3526
3750
  model: options?.model ?? this.config.defaultModel,
3527
3751
  toolHandlers: mergedHandlers,
3752
+ operationRegistry: options?.operationRegistry ?? this.config.operationRegistry,
3753
+ subagentRegistry: options?.subagentRegistry ?? this.config.subagentRegistry,
3754
+ agentMemoryStore: this.config.agentMemoryStore,
3528
3755
  knowledgeRetriever: this.config.knowledgeRetriever,
3529
3756
  sessionStore: this.config.sessionStore,
3530
3757
  telemetryCollector: this.config.telemetryCollector,
@@ -1,8 +1,11 @@
1
1
  import { type LanguageModel, type Tool, type ToolSet } from 'ai';
2
2
  import type { KnowledgeRetriever } from '@contractspec/lib.knowledge/retriever';
3
+ import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
3
4
  import type { AgentSpec } from '../spec/spec';
4
5
  import type { ModelSelector } from '@contractspec/lib.ai-providers/selector-types';
5
6
  import type { AgentGenerateParams, AgentGenerateResult, AgentStreamParams, ToolHandler } from '../types';
7
+ import { type SubagentRegistry } from '../tools/tool-adapter';
8
+ import type { AgentMemoryStore } from '../tools/agent-memory-store';
6
9
  import { type McpClientConfig } from '../tools/mcp-client';
7
10
  import { type AgentSessionStore } from '../session/store';
8
11
  import { type TelemetryCollector } from '../telemetry/adapter';
@@ -21,6 +24,8 @@ export interface ContractSpecAgentConfig {
21
24
  model: LanguageModel;
22
25
  /** Map of tool name to handler function */
23
26
  toolHandlers: Map<string, ToolHandler>;
27
+ /** Optional OperationSpecRegistry for operation-backed tools (operationRef) */
28
+ operationRegistry?: OperationSpecRegistry;
24
29
  /** Optional knowledge retriever for RAG */
25
30
  knowledgeRetriever?: KnowledgeRetriever;
26
31
  /** Optional session store for persistence */
@@ -34,6 +39,10 @@ export interface ContractSpecAgentConfig {
34
39
  };
35
40
  /** Additional AI SDK tools (e.g., from MCP servers) */
36
41
  additionalTools?: Record<string, ExecutableTool>;
42
+ /** Registry for subagent-backed tools (subagentRef) */
43
+ subagentRegistry?: SubagentRegistry;
44
+ /** Storage for memory tools (when memoryTools.provider is anthropic) */
45
+ agentMemoryStore?: AgentMemoryStore;
37
46
  /** MCP servers to connect and expose as tools */
38
47
  mcpServers?: McpClientConfig[];
39
48
  /** Ranking-driven model selector for dynamic per-call routing */