@contractspec/lib.ai-agent 3.0.0 → 3.1.1

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 (56) hide show
  1. package/README.md +14 -2
  2. package/dist/agent/agent-factory.d.ts +3 -0
  3. package/dist/agent/agent-factory.js +209 -27
  4. package/dist/agent/contract-spec-agent.d.ts +3 -0
  5. package/dist/agent/contract-spec-agent.js +207 -26
  6. package/dist/agent/index.js +218 -29
  7. package/dist/agent/json-runner.d.ts +3 -0
  8. package/dist/agent/json-runner.js +214 -28
  9. package/dist/agent/unified-agent.d.ts +3 -0
  10. package/dist/agent/unified-agent.js +211 -27
  11. package/dist/exporters/claude-agent-exporter.js +5 -0
  12. package/dist/exporters/index.js +5 -0
  13. package/dist/exporters/opencode-exporter.js +5 -0
  14. package/dist/index.js +5 -0
  15. package/dist/interop/index.d.ts +1 -0
  16. package/dist/interop/index.js +5 -0
  17. package/dist/interop/runtime-adapters.d.ts +36 -0
  18. package/dist/interop/runtime-adapters.js +1 -0
  19. package/dist/interop/spec-consumer.js +5 -0
  20. package/dist/node/agent/agent-factory.js +209 -27
  21. package/dist/node/agent/contract-spec-agent.js +207 -26
  22. package/dist/node/agent/index.js +218 -29
  23. package/dist/node/agent/json-runner.js +214 -28
  24. package/dist/node/agent/unified-agent.js +211 -27
  25. package/dist/node/exporters/claude-agent-exporter.js +5 -0
  26. package/dist/node/exporters/index.js +5 -0
  27. package/dist/node/exporters/opencode-exporter.js +5 -0
  28. package/dist/node/index.js +5 -0
  29. package/dist/node/interop/index.js +5 -0
  30. package/dist/node/interop/runtime-adapters.js +0 -0
  31. package/dist/node/interop/spec-consumer.js +5 -0
  32. package/dist/node/providers/claude-agent-sdk/adapter.js +5 -0
  33. package/dist/node/providers/claude-agent-sdk/index.js +5 -0
  34. package/dist/node/providers/index.js +5 -0
  35. package/dist/node/providers/opencode-sdk/adapter.js +5 -0
  36. package/dist/node/providers/opencode-sdk/index.js +5 -0
  37. package/dist/node/spec/index.js +5 -0
  38. package/dist/node/spec/spec.js +5 -0
  39. package/dist/node/tools/index.js +86 -10
  40. package/dist/node/tools/tool-adapter.js +86 -10
  41. package/dist/providers/claude-agent-sdk/adapter.js +5 -0
  42. package/dist/providers/claude-agent-sdk/index.js +5 -0
  43. package/dist/providers/index.js +5 -0
  44. package/dist/providers/opencode-sdk/adapter.js +5 -0
  45. package/dist/providers/opencode-sdk/index.js +5 -0
  46. package/dist/spec/index.js +5 -0
  47. package/dist/spec/spec.d.ts +27 -0
  48. package/dist/spec/spec.js +5 -0
  49. package/dist/spec/spec.test.d.ts +1 -0
  50. package/dist/tools/index.js +86 -10
  51. package/dist/tools/mcp-client.d.ts +6 -0
  52. package/dist/tools/mcp-server.d.ts +4 -0
  53. package/dist/tools/tool-adapter.js +86 -10
  54. package/dist/tools/tool-adapter.test.d.ts +1 -0
  55. package/dist/types.d.ts +15 -0
  56. package/package.json +26 -14
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
File without changes
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2263,21 +2263,39 @@ var init_json_schema_to_zod = () => {};
2263
2263
  // src/tools/tool-adapter.ts
2264
2264
  import { tool } from "ai";
2265
2265
  function specToolToAISDKTool(specTool, handler, context = {}) {
2266
+ let lastInvocationAt;
2266
2267
  return tool({
2267
2268
  description: specTool.description ?? specTool.name,
2268
2269
  inputSchema: jsonSchemaToZodSafe(specTool.schema),
2269
2270
  needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
2270
2271
  execute: async (input) => {
2271
- const result = await handler(input, {
2272
- agentId: context.agentId ?? "unknown",
2273
- sessionId: context.sessionId ?? "unknown",
2274
- tenantId: context.tenantId,
2275
- actorId: context.actorId,
2276
- locale: context.locale,
2277
- metadata: context.metadata,
2278
- signal: context.signal
2279
- });
2280
- return typeof result === "string" ? result : JSON.stringify(result);
2272
+ const now = Date.now();
2273
+ const cooldownMs = normalizeDuration(specTool.cooldownMs);
2274
+ if (cooldownMs && lastInvocationAt !== undefined) {
2275
+ const elapsed = now - lastInvocationAt;
2276
+ if (elapsed < cooldownMs) {
2277
+ const retryAfterMs = cooldownMs - elapsed;
2278
+ throw createToolExecutionError(`Tool "${specTool.name}" is cooling down. Retry in ${retryAfterMs}ms.`, "TOOL_COOLDOWN_ACTIVE", retryAfterMs);
2279
+ }
2280
+ }
2281
+ const timeoutMs = normalizeDuration(specTool.timeoutMs);
2282
+ const { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
2283
+ 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);
2295
+ } finally {
2296
+ dispose();
2297
+ lastInvocationAt = Date.now();
2298
+ }
2281
2299
  }
2282
2300
  });
2283
2301
  }
@@ -2302,6 +2320,64 @@ function createToolHandler(handler) {
2302
2320
  function buildToolHandlers(handlersObj) {
2303
2321
  return new Map(Object.entries(handlersObj));
2304
2322
  }
2323
+ function normalizeDuration(value) {
2324
+ if (value === undefined) {
2325
+ return;
2326
+ }
2327
+ if (!Number.isFinite(value)) {
2328
+ return;
2329
+ }
2330
+ if (value <= 0) {
2331
+ return;
2332
+ }
2333
+ return Math.round(value);
2334
+ }
2335
+ function withTimeout(execution, timeoutMs, toolName) {
2336
+ return new Promise((resolve, reject) => {
2337
+ const timeoutHandle = setTimeout(() => {
2338
+ reject(createToolExecutionError(`Tool "${toolName}" timed out after ${timeoutMs}ms.`, "TOOL_EXECUTION_TIMEOUT"));
2339
+ }, timeoutMs);
2340
+ execution.then((result) => {
2341
+ clearTimeout(timeoutHandle);
2342
+ resolve(result);
2343
+ }).catch((error) => {
2344
+ clearTimeout(timeoutHandle);
2345
+ reject(error);
2346
+ });
2347
+ });
2348
+ }
2349
+ function createTimeoutSignal(signal, timeoutMs) {
2350
+ const controller = new AbortController;
2351
+ const abortFromSource = () => controller.abort();
2352
+ if (signal) {
2353
+ if (signal.aborted) {
2354
+ controller.abort();
2355
+ } else {
2356
+ signal.addEventListener("abort", abortFromSource);
2357
+ }
2358
+ }
2359
+ const timeoutHandle = timeoutMs !== undefined ? setTimeout(() => {
2360
+ controller.abort();
2361
+ }, timeoutMs) : undefined;
2362
+ return {
2363
+ signal: controller.signal,
2364
+ dispose: () => {
2365
+ if (timeoutHandle !== undefined) {
2366
+ clearTimeout(timeoutHandle);
2367
+ }
2368
+ if (signal) {
2369
+ signal.removeEventListener("abort", abortFromSource);
2370
+ }
2371
+ }
2372
+ };
2373
+ }
2374
+ function createToolExecutionError(message, code, retryAfterMs) {
2375
+ return Object.assign(new Error(message), {
2376
+ code,
2377
+ kind: "retryable",
2378
+ retryAfterMs
2379
+ });
2380
+ }
2305
2381
  var init_tool_adapter = __esm(() => {
2306
2382
  init_json_schema_to_zod();
2307
2383
  init_i18n();
@@ -2263,21 +2263,39 @@ var init_json_schema_to_zod = () => {};
2263
2263
  // src/tools/tool-adapter.ts
2264
2264
  import { tool } from "ai";
2265
2265
  function specToolToAISDKTool(specTool, handler, context = {}) {
2266
+ let lastInvocationAt;
2266
2267
  return tool({
2267
2268
  description: specTool.description ?? specTool.name,
2268
2269
  inputSchema: jsonSchemaToZodSafe(specTool.schema),
2269
2270
  needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
2270
2271
  execute: async (input) => {
2271
- const result = await handler(input, {
2272
- agentId: context.agentId ?? "unknown",
2273
- sessionId: context.sessionId ?? "unknown",
2274
- tenantId: context.tenantId,
2275
- actorId: context.actorId,
2276
- locale: context.locale,
2277
- metadata: context.metadata,
2278
- signal: context.signal
2279
- });
2280
- return typeof result === "string" ? result : JSON.stringify(result);
2272
+ const now = Date.now();
2273
+ const cooldownMs = normalizeDuration(specTool.cooldownMs);
2274
+ if (cooldownMs && lastInvocationAt !== undefined) {
2275
+ const elapsed = now - lastInvocationAt;
2276
+ if (elapsed < cooldownMs) {
2277
+ const retryAfterMs = cooldownMs - elapsed;
2278
+ throw createToolExecutionError(`Tool "${specTool.name}" is cooling down. Retry in ${retryAfterMs}ms.`, "TOOL_COOLDOWN_ACTIVE", retryAfterMs);
2279
+ }
2280
+ }
2281
+ const timeoutMs = normalizeDuration(specTool.timeoutMs);
2282
+ const { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
2283
+ 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);
2295
+ } finally {
2296
+ dispose();
2297
+ lastInvocationAt = Date.now();
2298
+ }
2281
2299
  }
2282
2300
  });
2283
2301
  }
@@ -2302,6 +2320,64 @@ function createToolHandler(handler) {
2302
2320
  function buildToolHandlers(handlersObj) {
2303
2321
  return new Map(Object.entries(handlersObj));
2304
2322
  }
2323
+ function normalizeDuration(value) {
2324
+ if (value === undefined) {
2325
+ return;
2326
+ }
2327
+ if (!Number.isFinite(value)) {
2328
+ return;
2329
+ }
2330
+ if (value <= 0) {
2331
+ return;
2332
+ }
2333
+ return Math.round(value);
2334
+ }
2335
+ function withTimeout(execution, timeoutMs, toolName) {
2336
+ return new Promise((resolve, reject) => {
2337
+ const timeoutHandle = setTimeout(() => {
2338
+ reject(createToolExecutionError(`Tool "${toolName}" timed out after ${timeoutMs}ms.`, "TOOL_EXECUTION_TIMEOUT"));
2339
+ }, timeoutMs);
2340
+ execution.then((result) => {
2341
+ clearTimeout(timeoutHandle);
2342
+ resolve(result);
2343
+ }).catch((error) => {
2344
+ clearTimeout(timeoutHandle);
2345
+ reject(error);
2346
+ });
2347
+ });
2348
+ }
2349
+ function createTimeoutSignal(signal, timeoutMs) {
2350
+ const controller = new AbortController;
2351
+ const abortFromSource = () => controller.abort();
2352
+ if (signal) {
2353
+ if (signal.aborted) {
2354
+ controller.abort();
2355
+ } else {
2356
+ signal.addEventListener("abort", abortFromSource);
2357
+ }
2358
+ }
2359
+ const timeoutHandle = timeoutMs !== undefined ? setTimeout(() => {
2360
+ controller.abort();
2361
+ }, timeoutMs) : undefined;
2362
+ return {
2363
+ signal: controller.signal,
2364
+ dispose: () => {
2365
+ if (timeoutHandle !== undefined) {
2366
+ clearTimeout(timeoutHandle);
2367
+ }
2368
+ if (signal) {
2369
+ signal.removeEventListener("abort", abortFromSource);
2370
+ }
2371
+ }
2372
+ };
2373
+ }
2374
+ function createToolExecutionError(message, code, retryAfterMs) {
2375
+ return Object.assign(new Error(message), {
2376
+ code,
2377
+ kind: "retryable",
2378
+ retryAfterMs
2379
+ });
2380
+ }
2305
2381
  var init_tool_adapter = __esm(() => {
2306
2382
  init_json_schema_to_zod();
2307
2383
  init_i18n();
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -5,6 +5,31 @@ import type { PolicyRef } from '@contractspec/lib.contracts-spec/policy/spec';
5
5
  * Metadata for an agent specification.
6
6
  */
7
7
  export type AgentMeta = OwnerShipMeta;
8
+ export type AgentRuntimeAdapterKey = 'langgraph' | 'langchain' | 'workflow-devkit';
9
+ export interface AgentRuntimeCapabilities {
10
+ /** Optional external adapter availability map for runtime interoperability. */
11
+ adapters?: Partial<Record<AgentRuntimeAdapterKey, boolean>>;
12
+ /** Whether the agent should persist checkpoints for replay/resume. */
13
+ checkpointing?: boolean;
14
+ /** Whether the agent supports external suspend/resume semantics. */
15
+ suspendResume?: boolean;
16
+ /** Whether the agent can delegate approvals to external gateways. */
17
+ approvalGateway?: boolean;
18
+ }
19
+ export interface AgentRuntimePorts {
20
+ /** Symbolic checkpoint store adapter identifier. */
21
+ checkpointStore?: string;
22
+ /** Symbolic suspend/resume adapter identifier. */
23
+ suspension?: string;
24
+ /** Symbolic retry classifier identifier. */
25
+ retryClassifier?: string;
26
+ /** Symbolic approval gateway identifier. */
27
+ approvalGateway?: string;
28
+ }
29
+ export interface AgentRuntimeConfig {
30
+ capabilities?: AgentRuntimeCapabilities;
31
+ ports?: AgentRuntimePorts;
32
+ }
8
33
  /**
9
34
  * Configuration for a tool that an agent can use.
10
35
  */
@@ -106,6 +131,8 @@ export interface AgentSpec {
106
131
  knowledge?: AgentKnowledgeRef[];
107
132
  /** Policy configuration */
108
133
  policy?: AgentPolicy;
134
+ /** Runtime adapter and portability config. */
135
+ runtime?: AgentRuntimeConfig;
109
136
  /** Maximum steps per generation (defaults to 10) */
110
137
  maxSteps?: number;
111
138
  }
package/dist/spec/spec.js CHANGED
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
2142
2142
  if (!spec.tools?.length) {
2143
2143
  throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2144
2144
  }
2145
+ for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2146
+ if (portRef !== undefined && portRef.trim().length === 0) {
2147
+ throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2148
+ }
2149
+ }
2145
2150
  const toolNames = new Set;
2146
2151
  for (const tool of spec.tools) {
2147
2152
  if (toolNames.has(tool.name)) {
@@ -0,0 +1 @@
1
+ export {};
@@ -2263,21 +2263,39 @@ var init_json_schema_to_zod = () => {};
2263
2263
  // src/tools/tool-adapter.ts
2264
2264
  import { tool } from "ai";
2265
2265
  function specToolToAISDKTool(specTool, handler, context = {}) {
2266
+ let lastInvocationAt;
2266
2267
  return tool({
2267
2268
  description: specTool.description ?? specTool.name,
2268
2269
  inputSchema: jsonSchemaToZodSafe(specTool.schema),
2269
2270
  needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
2270
2271
  execute: async (input) => {
2271
- const result = await handler(input, {
2272
- agentId: context.agentId ?? "unknown",
2273
- sessionId: context.sessionId ?? "unknown",
2274
- tenantId: context.tenantId,
2275
- actorId: context.actorId,
2276
- locale: context.locale,
2277
- metadata: context.metadata,
2278
- signal: context.signal
2279
- });
2280
- return typeof result === "string" ? result : JSON.stringify(result);
2272
+ const now = Date.now();
2273
+ const cooldownMs = normalizeDuration(specTool.cooldownMs);
2274
+ if (cooldownMs && lastInvocationAt !== undefined) {
2275
+ const elapsed = now - lastInvocationAt;
2276
+ if (elapsed < cooldownMs) {
2277
+ const retryAfterMs = cooldownMs - elapsed;
2278
+ throw createToolExecutionError(`Tool "${specTool.name}" is cooling down. Retry in ${retryAfterMs}ms.`, "TOOL_COOLDOWN_ACTIVE", retryAfterMs);
2279
+ }
2280
+ }
2281
+ const timeoutMs = normalizeDuration(specTool.timeoutMs);
2282
+ const { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
2283
+ 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);
2295
+ } finally {
2296
+ dispose();
2297
+ lastInvocationAt = Date.now();
2298
+ }
2281
2299
  }
2282
2300
  });
2283
2301
  }
@@ -2302,6 +2320,64 @@ function createToolHandler(handler) {
2302
2320
  function buildToolHandlers(handlersObj) {
2303
2321
  return new Map(Object.entries(handlersObj));
2304
2322
  }
2323
+ function normalizeDuration(value) {
2324
+ if (value === undefined) {
2325
+ return;
2326
+ }
2327
+ if (!Number.isFinite(value)) {
2328
+ return;
2329
+ }
2330
+ if (value <= 0) {
2331
+ return;
2332
+ }
2333
+ return Math.round(value);
2334
+ }
2335
+ function withTimeout(execution, timeoutMs, toolName) {
2336
+ return new Promise((resolve, reject) => {
2337
+ const timeoutHandle = setTimeout(() => {
2338
+ reject(createToolExecutionError(`Tool "${toolName}" timed out after ${timeoutMs}ms.`, "TOOL_EXECUTION_TIMEOUT"));
2339
+ }, timeoutMs);
2340
+ execution.then((result) => {
2341
+ clearTimeout(timeoutHandle);
2342
+ resolve(result);
2343
+ }).catch((error) => {
2344
+ clearTimeout(timeoutHandle);
2345
+ reject(error);
2346
+ });
2347
+ });
2348
+ }
2349
+ function createTimeoutSignal(signal, timeoutMs) {
2350
+ const controller = new AbortController;
2351
+ const abortFromSource = () => controller.abort();
2352
+ if (signal) {
2353
+ if (signal.aborted) {
2354
+ controller.abort();
2355
+ } else {
2356
+ signal.addEventListener("abort", abortFromSource);
2357
+ }
2358
+ }
2359
+ const timeoutHandle = timeoutMs !== undefined ? setTimeout(() => {
2360
+ controller.abort();
2361
+ }, timeoutMs) : undefined;
2362
+ return {
2363
+ signal: controller.signal,
2364
+ dispose: () => {
2365
+ if (timeoutHandle !== undefined) {
2366
+ clearTimeout(timeoutHandle);
2367
+ }
2368
+ if (signal) {
2369
+ signal.removeEventListener("abort", abortFromSource);
2370
+ }
2371
+ }
2372
+ };
2373
+ }
2374
+ function createToolExecutionError(message, code, retryAfterMs) {
2375
+ return Object.assign(new Error(message), {
2376
+ code,
2377
+ kind: "retryable",
2378
+ retryAfterMs
2379
+ });
2380
+ }
2305
2381
  var init_tool_adapter = __esm(() => {
2306
2382
  init_json_schema_to_zod();
2307
2383
  init_i18n();
@@ -16,6 +16,12 @@ interface McpClientBaseConfig {
16
16
  clientName?: string;
17
17
  /** Optional MCP client version override */
18
18
  clientVersion?: string;
19
+ /** Auth method for the MCP connection. */
20
+ authMethod?: 'api-key' | 'oauth2' | 'bearer';
21
+ /** Custom auth headers. */
22
+ authHeaders?: Record<string, string>;
23
+ /** API version for the MCP server. */
24
+ apiVersion?: string;
19
25
  }
20
26
  /**
21
27
  * Configuration for stdio MCP servers.
@@ -33,6 +33,10 @@ export interface AgentMcpServerConfig {
33
33
  name?: string;
34
34
  /** Optional version override */
35
35
  version?: string;
36
+ /** Auth validation function for incoming MCP requests. */
37
+ validateAuth?: (headers: Record<string, string>) => Promise<boolean> | boolean;
38
+ /** Required auth methods for clients connecting to this MCP server. */
39
+ requiredAuthMethods?: ('api-key' | 'bearer' | 'oauth2')[];
36
40
  }
37
41
  /**
38
42
  * Create an MCP server from configuration.