@contractspec/lib.ai-agent 5.0.4 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/agent/agent-factory.d.ts +13 -0
- package/dist/agent/agent-factory.js +290 -63
- package/dist/agent/contract-spec-agent.d.ts +9 -0
- package/dist/agent/contract-spec-agent.js +287 -63
- package/dist/agent/index.js +353 -129
- package/dist/agent/json-runner.js +290 -66
- package/dist/agent/unified-agent.js +350 -126
- package/dist/exporters/claude-agent-exporter.js +12 -1
- package/dist/exporters/index.js +12 -1
- package/dist/exporters/opencode-exporter.js +11 -0
- package/dist/index.js +11 -0
- package/dist/interop/index.js +24 -2
- package/dist/interop/spec-consumer.js +11 -0
- package/dist/interop/tool-consumer.js +13 -2
- package/dist/node/agent/agent-factory.js +290 -63
- package/dist/node/agent/contract-spec-agent.js +287 -63
- package/dist/node/agent/index.js +353 -129
- package/dist/node/agent/json-runner.js +290 -66
- package/dist/node/agent/unified-agent.js +350 -126
- package/dist/node/exporters/claude-agent-exporter.js +12 -1
- package/dist/node/exporters/index.js +12 -1
- package/dist/node/exporters/opencode-exporter.js +11 -0
- package/dist/node/index.js +11 -0
- package/dist/node/interop/index.js +24 -2
- package/dist/node/interop/spec-consumer.js +11 -0
- package/dist/node/interop/tool-consumer.js +13 -2
- package/dist/node/providers/claude-agent-sdk/adapter.js +11 -0
- package/dist/node/providers/claude-agent-sdk/index.js +11 -0
- package/dist/node/providers/index.js +11 -0
- package/dist/node/providers/opencode-sdk/adapter.js +11 -0
- package/dist/node/providers/opencode-sdk/index.js +11 -0
- package/dist/node/spec/index.js +11 -0
- package/dist/node/spec/spec.js +11 -0
- package/dist/node/tools/agent-memory-store.js +24 -0
- package/dist/node/tools/in-memory-agent-memory-store.js +236 -0
- package/dist/node/tools/index.js +463 -42
- package/dist/node/tools/memory-tools.js +45 -0
- package/dist/node/tools/operation-tool-handler.js +35 -0
- package/dist/node/tools/subagent-tool.js +95 -0
- package/dist/node/tools/tool-adapter.js +192 -25
- package/dist/providers/claude-agent-sdk/adapter.js +11 -0
- package/dist/providers/claude-agent-sdk/index.js +11 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/opencode-sdk/adapter.js +11 -0
- package/dist/providers/opencode-sdk/index.js +11 -0
- package/dist/spec/index.js +11 -0
- package/dist/spec/spec.d.ts +69 -1
- package/dist/spec/spec.js +11 -0
- package/dist/tools/agent-memory-store.d.ts +26 -0
- package/dist/tools/agent-memory-store.js +24 -0
- package/dist/tools/agent-memory-store.test.d.ts +1 -0
- package/dist/tools/in-memory-agent-memory-store.d.ts +18 -0
- package/dist/tools/in-memory-agent-memory-store.js +236 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +463 -42
- package/dist/tools/mcp-client.browser.d.ts +6 -6
- package/dist/tools/memory-tools.d.ts +29 -0
- package/dist/tools/memory-tools.js +45 -0
- package/dist/tools/operation-tool-handler.d.ts +24 -0
- package/dist/tools/operation-tool-handler.js +35 -0
- package/dist/tools/subagent-tool.d.ts +66 -0
- package/dist/tools/subagent-tool.js +95 -0
- package/dist/tools/tool-adapter.d.ts +26 -10
- package/dist/tools/tool-adapter.js +192 -25
- package/dist/types.d.ts +9 -3
- package/package.json +67 -7
package/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
|
|
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
|
-
|
|
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
|
|
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
|
|
2441
|
+
const signal = options?.abortSignal ?? context.signal;
|
|
2442
|
+
const { signal: timeoutSignal, dispose } = createTimeoutSignal(signal, timeoutMs);
|
|
2322
2443
|
try {
|
|
2323
|
-
const execution = handler(input,
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
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
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
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
|
|
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
|
|
2427
|
-
import * as
|
|
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
|
|
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:
|
|
2442
|
-
query:
|
|
2443
|
-
spaceKey:
|
|
2444
|
-
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,
|
|
2515
|
-
prefixedTools[`${prefix}_${toolName}`] =
|
|
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,
|
|
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] =
|
|
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
|
|
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
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
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:
|
|
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 =
|
|
3489
|
-
tenantId:
|
|
3490
|
-
actorId:
|
|
3491
|
-
sessionId:
|
|
3492
|
-
metadata:
|
|
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 */
|