@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
@@ -0,0 +1,35 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
13
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
14
+
15
+ // src/tools/operation-tool-handler.ts
16
+ function toolCtxToHandlerCtx(ctx) {
17
+ return {
18
+ traceId: ctx.metadata?.traceId,
19
+ organizationId: ctx.tenantId ?? null,
20
+ userId: ctx.actorId ?? null,
21
+ actor: ctx.actorId ? "user" : "anonymous",
22
+ channel: "agent",
23
+ roles: []
24
+ };
25
+ }
26
+ function createOperationToolHandler(registry, operationRef) {
27
+ return async (input, context) => {
28
+ const handlerCtx = toolCtxToHandlerCtx(context);
29
+ const result = await registry.execute(operationRef.key, operationRef.version, input ?? {}, handlerCtx);
30
+ return result;
31
+ };
32
+ }
33
+ export {
34
+ createOperationToolHandler
35
+ };
@@ -0,0 +1,95 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
13
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
14
+
15
+ // src/tools/subagent-tool.ts
16
+ import { readUIMessageStream, tool } from "ai";
17
+ import { z } from "zod";
18
+ function toReadableStream(iterable) {
19
+ if (iterable instanceof ReadableStream) {
20
+ return iterable;
21
+ }
22
+ return new ReadableStream({
23
+ async start(controller) {
24
+ try {
25
+ for await (const chunk of iterable) {
26
+ controller.enqueue(chunk);
27
+ }
28
+ } finally {
29
+ controller.close();
30
+ }
31
+ }
32
+ });
33
+ }
34
+ function createSubagentTool(options) {
35
+ const {
36
+ subagent,
37
+ description = "Research a topic or question in depth.",
38
+ taskParam = "task",
39
+ toModelSummary = true,
40
+ passConversationHistory = false
41
+ } = options;
42
+ const inputSchema = z.object({
43
+ [taskParam]: z.string().describe("The research task to complete")
44
+ });
45
+ const execute = async function* (input, options2) {
46
+ const task = String(input[taskParam] ?? input.task ?? "");
47
+ const { abortSignal, messages } = options2 ?? {};
48
+ if (passConversationHistory && messages && messages.length > 0 && typeof subagent.generate === "function") {
49
+ const result2 = await subagent.generate({
50
+ messages: [...messages, { role: "user", content: task }],
51
+ abortSignal
52
+ });
53
+ yield { parts: [{ type: "text", text: result2.text }] };
54
+ return;
55
+ }
56
+ const result = await subagent.stream({
57
+ prompt: task,
58
+ abortSignal
59
+ });
60
+ const uiStream = result.toUIMessageStream();
61
+ const stream = toReadableStream(uiStream);
62
+ for await (const message of readUIMessageStream({
63
+ stream
64
+ })) {
65
+ yield message;
66
+ }
67
+ };
68
+ const toolOptions = {
69
+ description,
70
+ inputSchema,
71
+ execute,
72
+ ...toModelSummary && {
73
+ toModelOutput: ({
74
+ output
75
+ }) => {
76
+ const parts = output?.parts;
77
+ if (!Array.isArray(parts)) {
78
+ return { type: "text", value: "Task completed." };
79
+ }
80
+ const lastTextPart = [...parts].reverse().find((p) => p?.type === "text");
81
+ return {
82
+ type: "text",
83
+ value: lastTextPart?.text ?? "Task completed."
84
+ };
85
+ }
86
+ }
87
+ };
88
+ return tool(toolOptions);
89
+ }
90
+ var init_subagent_tool = () => {};
91
+ init_subagent_tool();
92
+
93
+ export {
94
+ createSubagentTool
95
+ };
@@ -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 specToolToAISDKTool(specTool, handler, context = {}) {
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
- return tool({
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: jsonSchemaToZodSafe(specTool.schema),
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 { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
2391
+ const signal = options?.abortSignal ?? context.signal;
2392
+ const { signal: timeoutSignal, dispose } = createTimeoutSignal(signal, timeoutMs);
2283
2393
  try {
2284
- const execution = handler(input, {
2285
- agentId: context.agentId ?? "unknown",
2286
- sessionId: context.sessionId ?? "unknown",
2287
- tenantId: context.tenantId,
2288
- actorId: context.actorId,
2289
- locale: context.locale,
2290
- metadata: context.metadata,
2291
- signal
2292
- });
2293
- const result = timeoutMs ? await withTimeout(execution, timeoutMs, specTool.name) : await execution;
2294
- return typeof result === "string" ? result : JSON.stringify(result);
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
- const handler = handlers.get(specTool.name);
2306
- if (!handler) {
2307
- throw new Error(createAgentI18n(context.locale).t("error.missingToolHandler", {
2308
- name: specTool.name
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
- tools[specTool.name] = specToolToAISDKTool(specTool, handler, context);
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 async (input, context) => {
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,6 +2547,7 @@ 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
  init_tool_adapter();
2386
2553
 
@@ -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
  }
@@ -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
  }
@@ -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
  }
@@ -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
  }
@@ -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
  }
@@ -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
  }
@@ -1,4 +1,5 @@
1
1
  import type { OwnerShipMeta } from '@contractspec/lib.contracts-spec/ownership';
2
+ import type { DataViewRef, FormRef, PresentationRef } from '@contractspec/lib.contracts-spec/features';
2
3
  import type { KnowledgeCategory } from '@contractspec/lib.contracts-spec/knowledge/spec';
3
4
  import type { PolicyRef } from '@contractspec/lib.contracts-spec/policy/spec';
4
5
  /**
@@ -30,15 +31,51 @@ export interface AgentRuntimeConfig {
30
31
  capabilities?: AgentRuntimeCapabilities;
31
32
  ports?: AgentRuntimePorts;
32
33
  }
34
+ /**
35
+ * Reference to a ContractSpec operation that backs an agent tool.
36
+ * When set, the tool is a projection of the operation (one contract → REST, GraphQL, MCP, agent).
37
+ */
38
+ export interface OperationRef {
39
+ /** Operation key (e.g., "knowledge.search") */
40
+ key: string;
41
+ /** Optional specific version; defaults to latest when omitted */
42
+ version?: string;
43
+ }
44
+ /**
45
+ * Reference to a subagent that backs an agent tool.
46
+ * When set, the tool delegates to the subagent; mutually exclusive with operationRef and manual handler.
47
+ */
48
+ export interface SubagentRef {
49
+ /** Subagent identifier (resolved from subagent registry) */
50
+ agentId: string;
51
+ /** Whether to extract summary for toModelOutput (default: true) */
52
+ toModelSummary?: boolean;
53
+ /**
54
+ * Pass full conversation history to subagent (opt-in).
55
+ * Defeats context isolation; use sparingly. Requires subagent to support generate({ messages }).
56
+ * Streaming is disabled when history is passed.
57
+ */
58
+ passConversationHistory?: boolean;
59
+ }
33
60
  /**
34
61
  * Configuration for a tool that an agent can use.
35
62
  */
36
63
  export interface AgentToolConfig {
37
64
  /** Tool name (unique within the agent) */
38
65
  name: string;
66
+ /**
67
+ * Reference to a ContractSpec operation. When set, the tool is backed by the operation:
68
+ * schema and handler are derived from the operation; no manual handler needed.
69
+ */
70
+ operationRef?: OperationRef;
71
+ /**
72
+ * Reference to a subagent. When set, the tool delegates to the subagent.
73
+ * Mutually exclusive with operationRef; requires subagentRegistry in agent config.
74
+ */
75
+ subagentRef?: SubagentRef;
39
76
  /** Human-readable description for the LLM */
40
77
  description?: string;
41
- /** JSON Schema fragment for tool parameters */
78
+ /** JSON Schema fragment for tool parameters (fallback when no operationRef) */
42
79
  schema?: Record<string, unknown>;
43
80
  /** Optional cooldown in milliseconds between invocations */
44
81
  cooldownMs?: number;
@@ -50,6 +87,21 @@ export interface AgentToolConfig {
50
87
  requiresApproval?: boolean;
51
88
  /** Optional policy guard that must evaluate to allow the tool call */
52
89
  policy?: PolicyRef;
90
+ /**
91
+ * When set, wrap raw output as { presentationKey, data } for ToolResultRenderer.
92
+ * At most one of outputPresentation, outputForm, outputDataView per tool.
93
+ */
94
+ outputPresentation?: PresentationRef;
95
+ /**
96
+ * When set, wrap raw output as { formKey, defaultValues } for ToolResultRenderer.
97
+ * At most one of outputPresentation, outputForm, outputDataView per tool.
98
+ */
99
+ outputForm?: FormRef;
100
+ /**
101
+ * When set, wrap raw output as { dataViewKey, items } for ToolResultRenderer.
102
+ * At most one of outputPresentation, outputForm, outputDataView per tool.
103
+ */
104
+ outputDataView?: DataViewRef;
53
105
  }
54
106
  /**
55
107
  * Reference to a knowledge space that the agent can access.
@@ -79,6 +131,20 @@ export interface AgentMemoryConfig {
79
131
  /** Whether to persist to long-term storage */
80
132
  persistLongTerm?: boolean;
81
133
  }
134
+ /**
135
+ * Memory tools config — model-accessible CRUD (Anthropic memory, custom).
136
+ * Distinct from AgentMemoryConfig (session summarization).
137
+ *
138
+ * @see https://ai-sdk.dev/docs/agents/memory
139
+ */
140
+ export interface AgentMemoryToolsConfig {
141
+ /** Provider: anthropic uses memory_20250818; custom uses operationRef. */
142
+ provider: 'anthropic' | 'custom';
143
+ /** Storage adapter key when using knowledge-backed storage (e.g. ephemeral space) */
144
+ storageAdapter?: string;
145
+ /** Ephemeral KnowledgeSpaceSpec key when using knowledge-backed storage */
146
+ spaceKey?: string;
147
+ }
82
148
  /**
83
149
  * Confidence policy for agent responses.
84
150
  */
@@ -127,6 +193,8 @@ export interface AgentSpec {
127
193
  tools: AgentToolConfig[];
128
194
  /** Memory/session configuration */
129
195
  memory?: AgentMemoryConfig;
196
+ /** Memory tools (model-accessible CRUD) — Anthropic or custom operation-backed */
197
+ memoryTools?: AgentMemoryToolsConfig;
130
198
  /** Knowledge spaces the agent can access */
131
199
  knowledge?: AgentKnowledgeRef[];
132
200
  /** Policy configuration */
package/dist/spec/spec.js CHANGED
@@ -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
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Storage interface for agent memory tools (model-accessible CRUD).
3
+ *
4
+ * Used by Anthropic memory tool and custom memory tools. Distinct from
5
+ * AgentMemoryManager (session summarization). Storage backend can be
6
+ * in-memory, filesystem, or ephemeral knowledge space.
7
+ *
8
+ * @see https://ai-sdk.dev/docs/agents/memory
9
+ * @see https://console.anthropic.com/docs/en/agents-and-tools/tool-use/memory-tool
10
+ */
11
+ export interface AgentMemoryStore {
12
+ /** View directory contents or file contents. Path must be under /memories. */
13
+ view(path: string, viewRange?: [number, number]): Promise<string>;
14
+ /** Create a new file. Path must be under /memories. */
15
+ create(path: string, fileText: string): Promise<string>;
16
+ /** Replace old_str with new_str in file. */
17
+ strReplace(path: string, oldStr: string, newStr: string): Promise<string>;
18
+ /** Insert text at line. insertLine is 0-indexed (0 = before first line). */
19
+ insert(path: string, insertLine: number, insertText: string): Promise<string>;
20
+ /** Delete file or directory recursively. */
21
+ delete(path: string): Promise<string>;
22
+ /** Rename/move file or directory. */
23
+ rename(oldPath: string, newPath: string): Promise<string>;
24
+ }
25
+ /** Validates path is under /memories and prevents traversal. */
26
+ export declare function validateMemoryPath(path: string): void;