@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,29 @@
1
+ /**
2
+ * Memory tools for AI agents — Anthropic memory and custom operation-backed tools.
3
+ *
4
+ * @see https://ai-sdk.dev/docs/agents/memory
5
+ * @see https://console.anthropic.com/docs/en/agents-and-tools/tool-use/memory-tool
6
+ */
7
+ import type { Tool } from 'ai';
8
+ import type { AgentMemoryStore } from './agent-memory-store';
9
+ /** Action shape from Anthropic memory_20250818 tool. */
10
+ export interface AnthropicMemoryAction {
11
+ command: 'view' | 'create' | 'str_replace' | 'insert' | 'delete' | 'rename';
12
+ path?: string;
13
+ view_range?: [number, number];
14
+ file_text?: string;
15
+ old_str?: string;
16
+ new_str?: string;
17
+ insert_line?: number;
18
+ insert_text?: string;
19
+ old_path?: string;
20
+ new_path?: string;
21
+ }
22
+ /**
23
+ * Creates the Anthropic memory tool backed by an AgentMemoryStore.
24
+ * Use when provider is Anthropic and agent needs persistent memory.
25
+ *
26
+ * @param store - Storage backend (e.g. InMemoryAgentMemoryStore for dev)
27
+ * @returns AI SDK tool for use with ToolLoopAgent
28
+ */
29
+ export declare function createAnthropicMemoryTool(store: AgentMemoryStore): Tool;
@@ -0,0 +1,45 @@
1
+ // @bun
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 = import.meta.require;
14
+
15
+ // src/tools/memory-tools.ts
16
+ import { anthropic } from "@ai-sdk/anthropic";
17
+ function createAnthropicMemoryTool(store) {
18
+ const memory = anthropic.tools.memory_20250818({
19
+ execute: async (action) => {
20
+ switch (action.command) {
21
+ case "view":
22
+ return store.view(action.path ?? "/memories", action.view_range);
23
+ case "create":
24
+ return store.create(action.path ?? "/memories/untitled", action.file_text ?? "");
25
+ case "str_replace":
26
+ return store.strReplace(action.path ?? "/memories", action.old_str ?? "", action.new_str ?? "");
27
+ case "insert":
28
+ return store.insert(action.path ?? "/memories", action.insert_line ?? 0, action.insert_text ?? "");
29
+ case "delete":
30
+ return store.delete(action.path ?? "/memories");
31
+ case "rename":
32
+ return store.rename(action.old_path ?? "/memories", action.new_path ?? "/memories");
33
+ default:
34
+ return `Unknown command: ${action.command}`;
35
+ }
36
+ }
37
+ });
38
+ return memory;
39
+ }
40
+ var init_memory_tools = () => {};
41
+ init_memory_tools();
42
+
43
+ export {
44
+ createAnthropicMemoryTool
45
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Operation-as-tool: creates a ToolHandler that executes a ContractSpec operation.
3
+ *
4
+ * One contract → REST, GraphQL, MCP, agent tool. When an agent tool has
5
+ * operationRef, this module provides the handler that delegates to
6
+ * OperationSpecRegistry.execute.
7
+ */
8
+ import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
9
+ import type { OperationRef } from '../spec/spec';
10
+ import type { ToolHandler } from '../types';
11
+ /**
12
+ * Create a ToolHandler that executes a ContractSpec operation.
13
+ *
14
+ * @param registry - OperationSpecRegistry with the operation registered and handler bound
15
+ * @param operationRef - Reference to the operation (key, optional version)
16
+ * @returns ToolHandler that delegates to registry.execute
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const handler = createOperationToolHandler(opsRegistry, { key: 'knowledge.search', version: '1.0.0' });
21
+ * toolHandlers.set('search_knowledge', handler);
22
+ * ```
23
+ */
24
+ export declare function createOperationToolHandler(registry: OperationSpecRegistry, operationRef: OperationRef): ToolHandler;
@@ -0,0 +1,35 @@
1
+ // @bun
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 = import.meta.require;
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,66 @@
1
+ /**
2
+ * Subagent tool factory for AI SDK parent-child agent delegation.
3
+ *
4
+ * Creates a tool whose execute calls a subagent, optionally streaming
5
+ * preliminary results and controlling what the model sees via toModelOutput.
6
+ *
7
+ * @see https://ai-sdk.dev/docs/agents/subagents
8
+ */
9
+ import { type Tool } from 'ai';
10
+ /** Subagent interface compatible with ToolLoopAgent stream. */
11
+ export interface SubagentLike {
12
+ stream(params: {
13
+ prompt: string;
14
+ abortSignal?: AbortSignal;
15
+ }): Promise<{
16
+ toUIMessageStream(): AsyncIterable<unknown> | ReadableStream<unknown>;
17
+ }>;
18
+ /** Optional: for passConversationHistory; when present, used instead of stream when messages are passed */
19
+ generate?(params: {
20
+ messages: {
21
+ role: string;
22
+ content: string | unknown[];
23
+ }[];
24
+ abortSignal?: AbortSignal;
25
+ }): Promise<{
26
+ text: string;
27
+ }>;
28
+ }
29
+ export interface CreateSubagentToolOptions {
30
+ /** Subagent to delegate to (ToolLoopAgent or compatible) */
31
+ subagent: SubagentLike;
32
+ /** Tool name (unique within the parent agent) */
33
+ name?: string;
34
+ /** Human-readable description for the LLM */
35
+ description?: string;
36
+ /** Input parameter name for the task (default: 'task') */
37
+ taskParam?: string;
38
+ /** Whether to extract summary for toModelOutput (default: true) */
39
+ toModelSummary?: boolean;
40
+ /** Pass full conversation history to subagent (opt-in; defeats context isolation; disables streaming) */
41
+ passConversationHistory?: boolean;
42
+ }
43
+ /**
44
+ * Create an AI SDK tool that delegates to a subagent.
45
+ *
46
+ * Supports streaming preliminary results via async generator execute.
47
+ * When toModelSummary is true, toModelOutput extracts the last text part
48
+ * so the parent model sees only a summary.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const researchSubagent = new ToolLoopAgent({ model, instructions, tools });
53
+ * const researchTool = createSubagentTool({
54
+ * subagent: researchSubagent,
55
+ * name: 'research',
56
+ * description: 'Research a topic in depth.',
57
+ * });
58
+ * const mainAgent = new ToolLoopAgent({
59
+ * model,
60
+ * tools: { research: researchTool },
61
+ * });
62
+ * ```
63
+ */
64
+ export declare function createSubagentTool(options: CreateSubagentToolOptions): Tool<{
65
+ task: string;
66
+ }, unknown>;
@@ -0,0 +1,95 @@
1
+ // @bun
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 = import.meta.require;
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
+ };
@@ -1,24 +1,40 @@
1
1
  import { type Tool } from 'ai';
2
+ import * as z from 'zod';
3
+ import type { OperationSpecRegistry } from '@contractspec/lib.contracts-spec/operations/registry';
4
+ import type { AnyOperationSpec } from '@contractspec/lib.contracts-spec/operations/operation';
2
5
  import type { AgentToolConfig } from '../spec/spec';
3
6
  import type { ToolExecutionContext, ToolHandler } from '../types';
7
+ import { type SubagentLike } from './subagent-tool';
8
+ export declare function specToolToAISDKTool(specTool: AgentToolConfig, handler: ToolHandler, context?: Partial<ToolExecutionContext>, effectiveInputSchema?: z.ZodType,
9
+ /** Optional operation spec for output ref fallback when specTool has no output refs */
10
+ operationSpec?: AnyOperationSpec): Tool<any, any>;
4
11
  /**
5
- * Convert ContractSpec AgentToolConfig to AI SDK CoreTool.
6
- *
7
- * @param specTool - The tool configuration from AgentSpec
8
- * @param handler - The handler function for the tool
9
- * @param context - Partial context to inject into handler calls
10
- * @returns AI SDK CoreTool
12
+ * Registry for resolving subagents by agentId.
11
13
  */
12
- export declare function specToolToAISDKTool(specTool: AgentToolConfig, handler: ToolHandler, context?: Partial<ToolExecutionContext>): Tool<any, any>;
14
+ export type SubagentRegistry = Map<string, SubagentLike>;
15
+ /**
16
+ * Options for specToolsToAISDKTools.
17
+ */
18
+ export interface SpecToolsToAISDKToolsOptions {
19
+ /** Optional OperationSpecRegistry for operation-backed tools (operationRef) */
20
+ operationRegistry?: OperationSpecRegistry;
21
+ /** Optional registry for subagent-backed tools (subagentRef) */
22
+ subagentRegistry?: SubagentRegistry;
23
+ }
13
24
  /**
14
25
  * Convert multiple ContractSpec tool configs to AI SDK tools.
15
26
  *
27
+ * When a tool has operationRef and operationRegistry is provided, the handler
28
+ * and input schema are derived from the operation. Otherwise, handlers must
29
+ * be supplied for each tool.
30
+ *
16
31
  * @param specTools - Array of tool configurations
17
- * @param handlers - Map of tool name to handler function
32
+ * @param handlers - Map of tool name to handler function (for inline tools)
18
33
  * @param context - Partial context to inject into handler calls
34
+ * @param options - Optional operationRegistry for operation-backed tools
19
35
  * @returns Record of AI SDK tools keyed by name
20
36
  */
21
- export declare function specToolsToAISDKTools(specTools: AgentToolConfig[], handlers: Map<string, ToolHandler>, context?: Partial<ToolExecutionContext>): Record<string, Tool<any, any>>;
37
+ export declare function specToolsToAISDKTools(specTools: AgentToolConfig[], handlers: Map<string, ToolHandler>, context?: Partial<ToolExecutionContext>, options?: SpecToolsToAISDKToolsOptions): Record<string, Tool<any, any>>;
22
38
  /**
23
39
  * Type-safe tool handler builder.
24
40
  *
@@ -29,7 +45,7 @@ export declare function specToolsToAISDKTools(specTools: AgentToolConfig[], hand
29
45
  * });
30
46
  * ```
31
47
  */
32
- export declare function createToolHandler<TInput = unknown, TOutput = string>(handler: (input: TInput, context: ToolExecutionContext) => Promise<TOutput> | TOutput): ToolHandler<TInput, TOutput>;
48
+ export declare function createToolHandler<TInput = unknown, TOutput = string>(handler: (input: TInput, context: ToolExecutionContext) => Promise<TOutput> | TOutput | AsyncGenerator<TOutput>): ToolHandler<TInput, TOutput>;
33
49
  /**
34
50
  * Build a tool handlers map from an object.
35
51
  *
@@ -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
 
package/dist/types.d.ts CHANGED
@@ -90,8 +90,13 @@ export interface AgentSessionState {
90
90
  metadata?: Record<string, string>;
91
91
  }
92
92
  export interface AgentGenerateParams {
93
- /** User prompt or message */
94
- prompt: string;
93
+ /** User prompt (required when messages is not provided) */
94
+ prompt?: string;
95
+ /** Full conversation history (for subagent passConversationHistory; when set, prompt is ignored) */
96
+ messages?: {
97
+ role: string;
98
+ content: string | unknown[];
99
+ }[];
95
100
  /** System prompt override (appended to agent instructions) */
96
101
  systemOverride?: string;
97
102
  /** Runtime context options */
@@ -144,8 +149,9 @@ export interface ToolExecutionContext {
144
149
  }
145
150
  /**
146
151
  * Handler function for a tool.
152
+ * May return a Promise (single result) or AsyncGenerator (streaming preliminary results).
147
153
  */
148
- export type ToolHandler<TInput = unknown, TOutput = string> = (input: TInput, context: ToolExecutionContext) => Promise<TOutput>;
154
+ export type ToolHandler<TInput = unknown, TOutput = string> = (input: TInput, context: ToolExecutionContext) => Promise<TOutput> | TOutput | AsyncGenerator<TOutput>;
149
155
  export interface AgentStepMetrics {
150
156
  agentId: string;
151
157
  stepIndex: number;