@contractspec/lib.ai-agent 7.0.7 → 8.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 (81) hide show
  1. package/README.md +45 -4
  2. package/dist/agent/agent-factory.d.ts +14 -3
  3. package/dist/agent/agent-factory.js +676 -111
  4. package/dist/agent/contract-spec-agent.d.ts +16 -2
  5. package/dist/agent/contract-spec-agent.js +672 -110
  6. package/dist/agent/index.js +685 -118
  7. package/dist/agent/json-runner.d.ts +1 -1
  8. package/dist/agent/json-runner.js +672 -110
  9. package/dist/agent/unified-agent.d.ts +2 -2
  10. package/dist/agent/unified-agent.js +685 -118
  11. package/dist/approval/index.js +6 -1
  12. package/dist/approval/workflow.js +5 -1
  13. package/dist/exporters/claude-agent-exporter.d.ts +1 -1
  14. package/dist/exporters/claude-agent-exporter.js +3 -51
  15. package/dist/exporters/index.js +8 -54
  16. package/dist/exporters/opencode-exporter.d.ts +1 -1
  17. package/dist/exporters/opencode-exporter.js +3 -51
  18. package/dist/exporters/types.d.ts +1 -1
  19. package/dist/index.d.ts +2 -1
  20. package/dist/index.js +3805 -71
  21. package/dist/interop/index.js +3 -51
  22. package/dist/interop/spec-consumer.d.ts +1 -1
  23. package/dist/interop/spec-consumer.js +3 -51
  24. package/dist/interop/tool-consumer.d.ts +1 -1
  25. package/dist/interop/types.d.ts +1 -1
  26. package/dist/knowledge/injector.d.ts +1 -1
  27. package/dist/node/agent/agent-factory.js +676 -111
  28. package/dist/node/agent/contract-spec-agent.js +672 -110
  29. package/dist/node/agent/index.js +685 -118
  30. package/dist/node/agent/json-runner.js +672 -110
  31. package/dist/node/agent/unified-agent.js +685 -118
  32. package/dist/node/approval/index.js +6 -1
  33. package/dist/node/approval/workflow.js +5 -1
  34. package/dist/node/exporters/claude-agent-exporter.js +3 -51
  35. package/dist/node/exporters/index.js +8 -54
  36. package/dist/node/exporters/opencode-exporter.js +3 -51
  37. package/dist/node/index.js +3805 -71
  38. package/dist/node/interop/index.js +3 -51
  39. package/dist/node/interop/spec-consumer.js +3 -51
  40. package/dist/node/providers/claude-agent-sdk/adapter.js +3 -51
  41. package/dist/node/providers/claude-agent-sdk/index.js +3 -51
  42. package/dist/node/providers/index.js +8 -53
  43. package/dist/node/providers/opencode-sdk/adapter.js +4 -51
  44. package/dist/node/providers/opencode-sdk/index.js +4 -51
  45. package/dist/node/session/index.js +26 -1
  46. package/dist/node/session/store.js +26 -1
  47. package/dist/node/telemetry/adapter.js +2 -0
  48. package/dist/node/telemetry/index.js +2 -0
  49. package/dist/providers/claude-agent-sdk/adapter.d.ts +1 -1
  50. package/dist/providers/claude-agent-sdk/adapter.js +3 -51
  51. package/dist/providers/claude-agent-sdk/index.js +3 -51
  52. package/dist/providers/claude-agent-sdk/tool-bridge.d.ts +1 -8
  53. package/dist/providers/index.js +8 -53
  54. package/dist/providers/opencode-sdk/adapter.d.ts +1 -13
  55. package/dist/providers/opencode-sdk/adapter.js +4 -51
  56. package/dist/providers/opencode-sdk/agent-bridge.d.ts +1 -10
  57. package/dist/providers/opencode-sdk/index.js +4 -51
  58. package/dist/providers/opencode-sdk/tool-bridge.d.ts +1 -4
  59. package/dist/providers/types.d.ts +1 -8
  60. package/dist/session/index.js +26 -1
  61. package/dist/session/store.d.ts +2 -2
  62. package/dist/session/store.js +26 -1
  63. package/dist/telemetry/adapter.d.ts +1 -0
  64. package/dist/telemetry/adapter.js +2 -0
  65. package/dist/telemetry/index.js +2 -0
  66. package/dist/tools/knowledge-tool.d.ts +1 -1
  67. package/dist/tools/mcp-server.d.ts +1 -1
  68. package/dist/tools/operation-tool-handler.d.ts +1 -1
  69. package/dist/tools/tool-adapter.d.ts +1 -1
  70. package/dist/types.d.ts +13 -0
  71. package/package.json +8 -44
  72. package/dist/node/spec/index.js +0 -2233
  73. package/dist/node/spec/registry.js +0 -2178
  74. package/dist/node/spec/spec.js +0 -2188
  75. package/dist/spec/index.d.ts +0 -2
  76. package/dist/spec/index.js +0 -2233
  77. package/dist/spec/registry.d.ts +0 -41
  78. package/dist/spec/registry.js +0 -2178
  79. package/dist/spec/spec.d.ts +0 -218
  80. package/dist/spec/spec.js +0 -2188
  81. /package/dist/{spec/spec.test.d.ts → session/store.test.d.ts} +0 -0
@@ -2131,6 +2131,148 @@ var init_i18n = __esm(() => {
2131
2131
  init_messages();
2132
2132
  });
2133
2133
 
2134
+ // src/approval/workflow.ts
2135
+ import { randomUUID } from "crypto";
2136
+
2137
+ class InMemoryApprovalStore {
2138
+ items = new Map;
2139
+ maxItems;
2140
+ constructor(options = {}) {
2141
+ this.maxItems = options.maxItems ?? 1000;
2142
+ }
2143
+ async create(request) {
2144
+ this.evictIfNeeded();
2145
+ this.items.set(request.id, request);
2146
+ }
2147
+ async get(id) {
2148
+ return this.items.get(id) ?? null;
2149
+ }
2150
+ async getByToolCallId(toolCallId) {
2151
+ for (const request of this.items.values()) {
2152
+ if (request.toolCallId === toolCallId) {
2153
+ return request;
2154
+ }
2155
+ }
2156
+ return null;
2157
+ }
2158
+ async update(id, updates) {
2159
+ const existing = this.items.get(id);
2160
+ if (existing) {
2161
+ this.items.set(id, { ...existing, ...updates });
2162
+ }
2163
+ }
2164
+ async list(options) {
2165
+ let results = [...this.items.values()];
2166
+ if (options?.status) {
2167
+ results = results.filter((r) => r.status === options.status);
2168
+ }
2169
+ if (options?.agentId) {
2170
+ results = results.filter((r) => r.agentId === options.agentId);
2171
+ }
2172
+ if (options?.tenantId) {
2173
+ results = results.filter((r) => r.tenantId === options.tenantId);
2174
+ }
2175
+ return results.sort((a, b) => b.requestedAt.getTime() - a.requestedAt.getTime());
2176
+ }
2177
+ clear() {
2178
+ this.items.clear();
2179
+ }
2180
+ evictIfNeeded() {
2181
+ if (this.items.size < this.maxItems) {
2182
+ return;
2183
+ }
2184
+ let oldestId = null;
2185
+ let oldestTimestamp = Number.POSITIVE_INFINITY;
2186
+ for (const [id, request] of this.items.entries()) {
2187
+ const ts = request.requestedAt.getTime();
2188
+ if (ts < oldestTimestamp) {
2189
+ oldestTimestamp = ts;
2190
+ oldestId = id;
2191
+ }
2192
+ }
2193
+ if (oldestId) {
2194
+ this.items.delete(oldestId);
2195
+ }
2196
+ }
2197
+ }
2198
+
2199
+ class ApprovalWorkflow {
2200
+ store;
2201
+ constructor(store = new InMemoryApprovalStore) {
2202
+ this.store = store;
2203
+ }
2204
+ async requestApproval(params) {
2205
+ const request = {
2206
+ id: randomUUID(),
2207
+ sessionId: params.sessionId,
2208
+ agentId: params.agentId,
2209
+ tenantId: params.tenantId,
2210
+ toolName: params.toolName,
2211
+ toolCallId: params.toolCallId,
2212
+ toolArgs: params.toolArgs,
2213
+ reason: params.reason,
2214
+ requestedAt: new Date,
2215
+ status: "pending",
2216
+ payload: params.payload
2217
+ };
2218
+ await this.store.create(request);
2219
+ return request;
2220
+ }
2221
+ async requestApprovalFromToolCall(toolCall, context) {
2222
+ return this.requestApproval({
2223
+ sessionId: context.sessionId,
2224
+ agentId: context.agentId,
2225
+ tenantId: context.tenantId,
2226
+ toolName: toolCall.toolName,
2227
+ toolCallId: toolCall.toolCallId,
2228
+ toolArgs: toolCall.args,
2229
+ reason: context.reason ?? createAgentI18n(context.locale).t("approval.toolRequiresApproval", {
2230
+ name: toolCall.toolName
2231
+ })
2232
+ });
2233
+ }
2234
+ async approve(id, reviewer, notes) {
2235
+ await this.store.update(id, {
2236
+ status: "approved",
2237
+ reviewer,
2238
+ resolvedAt: new Date,
2239
+ notes
2240
+ });
2241
+ }
2242
+ async reject(id, reviewer, notes) {
2243
+ await this.store.update(id, {
2244
+ status: "rejected",
2245
+ reviewer,
2246
+ resolvedAt: new Date,
2247
+ notes
2248
+ });
2249
+ }
2250
+ async getStatus(toolCallId) {
2251
+ const request = await this.store.getByToolCallId(toolCallId);
2252
+ return request?.status ?? null;
2253
+ }
2254
+ async isApproved(toolCallId) {
2255
+ const status = await this.getStatus(toolCallId);
2256
+ return status === "approved";
2257
+ }
2258
+ async listPending(options) {
2259
+ return this.store.list({ ...options, status: "pending" });
2260
+ }
2261
+ async get(id) {
2262
+ return this.store.get(id);
2263
+ }
2264
+ }
2265
+ function createApprovalWorkflow(store) {
2266
+ if (store && typeof store === "object" && "create" in store && typeof store.create === "function") {
2267
+ return new ApprovalWorkflow(store);
2268
+ }
2269
+ const options = store;
2270
+ return new ApprovalWorkflow(new InMemoryApprovalStore(options));
2271
+ }
2272
+ var init_workflow = __esm(() => {
2273
+ init_i18n();
2274
+ });
2275
+
2134
2276
  // src/knowledge/injector.ts
2135
2277
  async function injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale) {
2136
2278
  if (!retriever)
@@ -2288,59 +2430,34 @@ class InMemorySessionStore {
2288
2430
  function createInMemorySessionStore(options) {
2289
2431
  return new InMemorySessionStore(options);
2290
2432
  }
2291
- function generateSessionId() {
2292
- return `sess_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
2293
- }
2294
-
2295
- // src/spec/spec.ts
2296
- function defineAgent(spec) {
2297
- const i18n = createAgentI18n(spec.locale);
2298
- if (!spec.meta?.key) {
2299
- throw new Error(i18n.t("error.agentKeyRequired"));
2300
- }
2301
- if (typeof spec.meta.version !== "string") {
2302
- throw new Error(i18n.t("error.agentMissingVersion", { key: spec.meta.key }));
2433
+ function createSecureSessionToken() {
2434
+ const cryptoApi = globalThis.crypto;
2435
+ if (typeof cryptoApi?.randomUUID === "function") {
2436
+ return cryptoApi.randomUUID();
2303
2437
  }
2304
- if (!spec.instructions?.trim()) {
2305
- throw new Error(i18n.t("error.agentRequiresInstructions", { key: spec.meta.key }));
2306
- }
2307
- if (!spec.tools?.length) {
2308
- throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2309
- }
2310
- for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2311
- if (portRef !== undefined && portRef.trim().length === 0) {
2312
- throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2438
+ if (typeof cryptoApi?.getRandomValues === "function") {
2439
+ const bytes = cryptoApi.getRandomValues(new Uint8Array(16));
2440
+ const versionByte = bytes.at(6);
2441
+ const variantByte = bytes.at(8);
2442
+ if (versionByte === undefined || variantByte === undefined) {
2443
+ throw new Error("Secure session token generation requires 16 random bytes.");
2313
2444
  }
2445
+ bytes[6] = versionByte & 15 | 64;
2446
+ bytes[8] = variantByte & 63 | 128;
2447
+ const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0"));
2448
+ return [
2449
+ hex.slice(0, 4).join(""),
2450
+ hex.slice(4, 6).join(""),
2451
+ hex.slice(6, 8).join(""),
2452
+ hex.slice(8, 10).join(""),
2453
+ hex.slice(10, 16).join("")
2454
+ ].join("-");
2314
2455
  }
2315
- const toolNames = new Set;
2316
- for (const tool of spec.tools) {
2317
- if (toolNames.has(tool.name)) {
2318
- throw new Error(i18n.t("error.agentDuplicateTool", {
2319
- key: spec.meta.key,
2320
- name: tool.name
2321
- }));
2322
- }
2323
- toolNames.add(tool.name);
2324
- if (tool.subagentRef && tool.operationRef) {
2325
- throw new Error(`Agent ${spec.meta.key} tool "${tool.name}" cannot have both subagentRef and operationRef. Use one.`);
2326
- }
2327
- const outputRefCount = [
2328
- tool.outputPresentation,
2329
- tool.outputForm,
2330
- tool.outputDataView
2331
- ].filter(Boolean).length;
2332
- if (outputRefCount > 1) {
2333
- throw new Error(`Agent ${spec.meta.key} tool "${tool.name}" has multiple output refs (outputPresentation, outputForm, outputDataView). Use at most one.`);
2334
- }
2335
- }
2336
- return Object.freeze(spec);
2456
+ throw new Error("Secure session IDs require Web Crypto support.");
2337
2457
  }
2338
- function agentKey(meta) {
2339
- return `${meta.key}.v${meta.version}`;
2458
+ function generateSessionId() {
2459
+ return `sess_${createSecureSessionToken()}`;
2340
2460
  }
2341
- var init_spec = __esm(() => {
2342
- init_i18n();
2343
- });
2344
2461
 
2345
2462
  // src/telemetry/adapter.ts
2346
2463
  function parseAgentId(agentId) {
@@ -2409,6 +2526,7 @@ async function trackAgentStep(collector, agentId, step, durationMs, context) {
2409
2526
  agentId,
2410
2527
  actorId: context?.actorId,
2411
2528
  tenantId: context?.tenantId,
2529
+ workflowId: context?.workflowId,
2412
2530
  stepIndex: context?.stepIndex,
2413
2531
  toolName: toolCall.toolName,
2414
2532
  toolCallArgs: toolCall.input,
@@ -2434,6 +2552,7 @@ async function trackAgentStep(collector, agentId, step, durationMs, context) {
2434
2552
  agentId,
2435
2553
  actorId: context?.actorId,
2436
2554
  tenantId: context?.tenantId,
2555
+ workflowId: context?.workflowId,
2437
2556
  stepIndex: context?.stepIndex,
2438
2557
  stepStartedAt: context?.stepStartedAt,
2439
2558
  finishReason: step.finishReason,
@@ -3352,7 +3471,10 @@ var exports_contract_spec_agent = {};
3352
3471
  __export(exports_contract_spec_agent, {
3353
3472
  ContractSpecAgent: () => ContractSpecAgent
3354
3473
  });
3355
- import { randomUUID } from "crypto";
3474
+ import { randomUUID as randomUUID2 } from "crypto";
3475
+ import {
3476
+ agentKey
3477
+ } from "@contractspec/lib.contracts-spec/agent";
3356
3478
  import {
3357
3479
  stepCountIs,
3358
3480
  Experimental_Agent as ToolLoopAgent
@@ -3377,7 +3499,10 @@ class ContractSpecAgent {
3377
3499
  this.mcpCleanup = mcpCleanup;
3378
3500
  }
3379
3501
  static async create(config) {
3380
- const effectiveConfig = config;
3502
+ const effectiveConfig = {
3503
+ ...config,
3504
+ approvalWorkflow: config.approvalWorkflow ?? (config.spec.policy?.escalation?.approvalWorkflow ? createApprovalWorkflow() : undefined)
3505
+ };
3381
3506
  let mcpToolset = null;
3382
3507
  if ((effectiveConfig.mcpServers?.length ?? 0) > 0) {
3383
3508
  mcpToolset = await createMcpToolsets(effectiveConfig.mcpServers ?? [], {
@@ -3430,34 +3555,27 @@ class ContractSpecAgent {
3430
3555
  }
3431
3556
  async generate(params) {
3432
3557
  const sessionId = params.options?.sessionId ?? generateSessionId();
3433
- const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID();
3558
+ const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID2();
3559
+ const workflowId = params.options?.workflowId ?? params.options?.metadata?.["workflowId"];
3560
+ const threadId = params.options?.threadId ?? params.options?.metadata?.["threadId"];
3561
+ const runtimeAdapter = this.resolveRuntimeAdapter();
3434
3562
  this.activeStepContexts.set(sessionId, {
3435
3563
  traceId,
3436
3564
  tenantId: params.options?.tenantId,
3437
3565
  actorId: params.options?.actorId,
3566
+ workflowId,
3567
+ threadId,
3438
3568
  stepIndex: 0,
3439
3569
  stepStartedAt: new Date
3440
3570
  });
3441
- if (this.config.sessionStore && !params.messages?.length) {
3442
- const existing = await this.config.sessionStore.get(sessionId);
3443
- if (!existing) {
3444
- await this.config.sessionStore.create({
3445
- sessionId,
3446
- agentId: this.id,
3447
- tenantId: params.options?.tenantId,
3448
- actorId: params.options?.actorId,
3449
- status: "running",
3450
- messages: [],
3451
- steps: [],
3452
- metadata: params.options?.metadata
3453
- });
3454
- } else if (existing.status !== "running") {
3455
- await this.config.sessionStore.update(sessionId, { status: "running" });
3456
- }
3457
- await this.config.sessionStore.appendMessage(sessionId, {
3458
- role: "user",
3459
- content: params.prompt ?? ""
3571
+ if (!params.messages?.length) {
3572
+ await this.ensureSession({
3573
+ sessionId,
3574
+ prompt: params.prompt ?? "",
3575
+ options: params.options,
3576
+ traceId
3460
3577
  });
3578
+ await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.beforeModel);
3461
3579
  }
3462
3580
  const model = await this.resolveModelForCall({
3463
3581
  sessionId,
@@ -3472,6 +3590,8 @@ class ContractSpecAgent {
3472
3590
  tenantId: params.options?.tenantId,
3473
3591
  actorId: params.options?.actorId,
3474
3592
  sessionId,
3593
+ workflowId,
3594
+ threadId,
3475
3595
  metadata: params.options?.metadata
3476
3596
  }
3477
3597
  };
@@ -3494,26 +3614,94 @@ ${params.prompt}` : params.prompt ?? "";
3494
3614
  });
3495
3615
  }
3496
3616
  } catch (error) {
3617
+ const executionError = toAgentExecutionError(error);
3497
3618
  if (this.config.sessionStore) {
3498
3619
  await this.config.sessionStore.update(sessionId, {
3499
- status: "failed"
3620
+ status: "failed",
3621
+ traceId,
3622
+ workflowId,
3623
+ threadId,
3624
+ lastError: executionError
3500
3625
  });
3626
+ await this.syncSessionCheckpoint(sessionId);
3501
3627
  }
3628
+ await this.emitAgentEvent("agent.failed", {
3629
+ sessionId,
3630
+ tenantId: params.options?.tenantId,
3631
+ workflowId,
3632
+ traceId,
3633
+ metadata: {
3634
+ error: executionError.message,
3635
+ code: executionError.code ?? executionError.kind
3636
+ }
3637
+ });
3502
3638
  this.activeStepContexts.delete(sessionId);
3503
3639
  throw error;
3504
3640
  }
3505
- this.activeStepContexts.delete(sessionId);
3506
- const escalationError = resolveEscalationError(this.spec, result.finishReason);
3507
- if (this.config.sessionStore) {
3508
- await this.config.sessionStore.appendMessage(sessionId, {
3509
- role: "assistant",
3510
- content: result.text
3641
+ if (!params.messages?.length) {
3642
+ await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.afterModel);
3643
+ }
3644
+ let pendingApproval = this.activeStepContexts.get(sessionId)?.pendingApproval;
3645
+ const escalationError = pendingApproval ? undefined : resolveEscalationError(this.spec, result.finishReason);
3646
+ if (this.config.sessionStore && result.text.trim().length > 0) {
3647
+ const currentSession = await this.config.sessionStore.get(sessionId);
3648
+ const lastMessage = currentSession?.messages.at(-1);
3649
+ const lastContent = lastMessage && "content" in lastMessage ? lastMessage.content : undefined;
3650
+ if (lastMessage?.role !== "assistant" || lastContent !== result.text) {
3651
+ await this.config.sessionStore.appendMessage(sessionId, {
3652
+ role: "assistant",
3653
+ content: result.text
3654
+ });
3655
+ }
3656
+ }
3657
+ if (!pendingApproval && escalationError) {
3658
+ pendingApproval = await this.requestApproval(sessionId, {
3659
+ toolName: this.spec.policy?.escalation?.approvalWorkflow ?? "approval_required",
3660
+ toolCallId: `approval_${sessionId}`,
3661
+ args: {
3662
+ finishReason: result.finishReason,
3663
+ threshold: this.spec.policy?.escalation?.confidenceThreshold ?? this.spec.policy?.confidence?.min
3664
+ },
3665
+ reason: escalationError.message,
3666
+ error: escalationError
3511
3667
  });
3668
+ }
3669
+ const finalStatus = pendingApproval ? "escalated" : "completed";
3670
+ if (this.config.sessionStore) {
3512
3671
  await this.config.sessionStore.update(sessionId, {
3513
- status: escalationError ? "escalated" : "completed"
3672
+ status: finalStatus,
3673
+ traceId,
3674
+ workflowId,
3675
+ threadId,
3676
+ pendingApprovalRequestId: pendingApproval?.id,
3677
+ lastError: pendingApproval ? escalationError : undefined
3678
+ });
3679
+ await this.syncSessionCheckpoint(sessionId);
3680
+ }
3681
+ if (pendingApproval) {
3682
+ await this.emitAgentEvent("agent.escalated", {
3683
+ sessionId,
3684
+ tenantId: params.options?.tenantId,
3685
+ workflowId,
3686
+ traceId,
3687
+ metadata: {
3688
+ approvalRequestId: pendingApproval.id,
3689
+ reason: pendingApproval.reason
3690
+ }
3691
+ });
3692
+ } else {
3693
+ await this.emitAgentEvent("agent.completed", {
3694
+ sessionId,
3695
+ tenantId: params.options?.tenantId,
3696
+ workflowId,
3697
+ traceId,
3698
+ metadata: {
3699
+ finishReason: result.finishReason
3700
+ }
3514
3701
  });
3515
3702
  }
3516
3703
  const session = this.config.sessionStore ? await this.config.sessionStore.get(sessionId) : null;
3704
+ this.activeStepContexts.delete(sessionId);
3517
3705
  return {
3518
3706
  text: result.text,
3519
3707
  steps: result.steps,
@@ -3532,23 +3720,25 @@ ${params.prompt}` : params.prompt ?? "";
3532
3720
  finishReason: result.finishReason,
3533
3721
  usage: result.usage,
3534
3722
  session: session ?? undefined,
3535
- pendingApproval: escalationError ? {
3536
- toolName: this.spec.policy?.escalation?.approvalWorkflow ?? "approval_required",
3537
- toolCallId: `approval_${sessionId}`,
3538
- args: {
3539
- reason: escalationError.message,
3540
- code: escalationError.code
3541
- }
3723
+ pendingApproval: pendingApproval ? {
3724
+ toolName: pendingApproval.toolName,
3725
+ toolCallId: pendingApproval.toolCallId,
3726
+ args: pendingApproval.toolArgs
3542
3727
  } : undefined
3543
3728
  };
3544
3729
  }
3545
3730
  async stream(params) {
3546
3731
  const sessionId = params.options?.sessionId ?? generateSessionId();
3547
- const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID();
3732
+ const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID2();
3733
+ const workflowId = params.options?.workflowId ?? params.options?.metadata?.["workflowId"];
3734
+ const threadId = params.options?.threadId ?? params.options?.metadata?.["threadId"];
3735
+ const runtimeAdapter = this.resolveRuntimeAdapter();
3548
3736
  this.activeStepContexts.set(sessionId, {
3549
3737
  traceId,
3550
3738
  tenantId: params.options?.tenantId,
3551
3739
  actorId: params.options?.actorId,
3740
+ workflowId,
3741
+ threadId,
3552
3742
  stepIndex: 0,
3553
3743
  stepStartedAt: new Date
3554
3744
  });
@@ -3557,6 +3747,13 @@ ${params.prompt}` : params.prompt ?? "";
3557
3747
  ${params.systemOverride}
3558
3748
 
3559
3749
  ${params.prompt}` : params.prompt ?? "";
3750
+ await this.ensureSession({
3751
+ sessionId,
3752
+ prompt,
3753
+ options: params.options,
3754
+ traceId
3755
+ });
3756
+ await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.beforeModel);
3560
3757
  const model = await this.resolveModelForCall({
3561
3758
  sessionId,
3562
3759
  traceId,
@@ -3564,26 +3761,6 @@ ${params.prompt}` : params.prompt ?? "";
3564
3761
  });
3565
3762
  const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
3566
3763
  const inner = this.createInnerAgent(model, effectiveMaxSteps);
3567
- if (this.config.sessionStore) {
3568
- const existing = await this.config.sessionStore.get(sessionId);
3569
- if (!existing) {
3570
- await this.config.sessionStore.create({
3571
- sessionId,
3572
- agentId: this.id,
3573
- tenantId: params.options?.tenantId,
3574
- actorId: params.options?.actorId,
3575
- status: "running",
3576
- messages: [],
3577
- steps: [],
3578
- metadata: params.options?.metadata
3579
- });
3580
- }
3581
- await this.config.sessionStore.appendMessage(sessionId, {
3582
- role: "user",
3583
- content: prompt
3584
- });
3585
- await this.config.sessionStore.update(sessionId, { status: "running" });
3586
- }
3587
3764
  return inner.stream({
3588
3765
  prompt,
3589
3766
  abortSignal: params.signal,
@@ -3591,21 +3768,92 @@ ${params.prompt}` : params.prompt ?? "";
3591
3768
  tenantId: params.options?.tenantId,
3592
3769
  actorId: params.options?.actorId,
3593
3770
  sessionId,
3771
+ workflowId,
3772
+ threadId,
3594
3773
  metadata: params.options?.metadata
3595
3774
  }
3596
3775
  });
3597
3776
  }
3598
3777
  async handleStepFinish(step) {
3599
3778
  const sessionId = step.options?.sessionId;
3779
+ const context = sessionId ? this.activeStepContexts.get(sessionId) : undefined;
3600
3780
  if (sessionId && this.config.sessionStore) {
3601
3781
  await this.config.sessionStore.appendStep(sessionId, step);
3782
+ if (step.text.trim().length > 0) {
3783
+ await this.config.sessionStore.appendMessage(sessionId, {
3784
+ role: "assistant",
3785
+ content: step.text
3786
+ });
3787
+ }
3788
+ for (const toolCall of step.toolCalls ?? []) {
3789
+ await this.emitAgentEvent("agent.tool.called", {
3790
+ sessionId,
3791
+ tenantId: context?.tenantId,
3792
+ workflowId: context?.workflowId,
3793
+ traceId: context?.traceId,
3794
+ stepIndex: context?.stepIndex,
3795
+ toolName: toolCall.toolName,
3796
+ metadata: {
3797
+ toolCallId: toolCall.toolCallId
3798
+ }
3799
+ });
3800
+ }
3801
+ for (const toolResult of step.toolResults ?? []) {
3802
+ const toolCall = step.toolCalls?.find((candidate) => candidate.toolCallId === toolResult.toolCallId);
3803
+ const toolError = extractToolExecutionError(toolResult.output);
3804
+ await this.config.sessionStore.appendMessage(sessionId, {
3805
+ role: "tool",
3806
+ content: stringifyToolResult(toolResult.toolName, toolResult.output)
3807
+ });
3808
+ if (toolError) {
3809
+ await this.emitAgentEvent("agent.tool.failed", {
3810
+ sessionId,
3811
+ tenantId: context?.tenantId,
3812
+ workflowId: context?.workflowId,
3813
+ traceId: context?.traceId,
3814
+ stepIndex: context?.stepIndex,
3815
+ toolName: toolResult.toolName,
3816
+ metadata: {
3817
+ toolCallId: toolResult.toolCallId,
3818
+ error: toolError.message,
3819
+ code: toolError.code
3820
+ }
3821
+ });
3822
+ const shouldEscalate = toolError.kind === "timeout" && this.spec.policy?.escalation?.onTimeout || toolError.kind !== "timeout" && this.spec.policy?.escalation?.onToolFailure;
3823
+ if (shouldEscalate && toolCall && !context?.pendingApproval) {
3824
+ await this.requestApproval(sessionId, {
3825
+ toolName: toolCall.toolName,
3826
+ toolCallId: toolCall.toolCallId,
3827
+ args: toolCall.input,
3828
+ reason: toolError.message,
3829
+ error: toolError
3830
+ });
3831
+ }
3832
+ } else {
3833
+ await this.emitAgentEvent("agent.tool.completed", {
3834
+ sessionId,
3835
+ tenantId: context?.tenantId,
3836
+ workflowId: context?.workflowId,
3837
+ traceId: context?.traceId,
3838
+ stepIndex: context?.stepIndex,
3839
+ toolName: toolResult.toolName,
3840
+ metadata: {
3841
+ toolCallId: toolResult.toolCallId
3842
+ }
3843
+ });
3844
+ }
3845
+ }
3602
3846
  await this.config.sessionStore.update(sessionId, {
3603
- status: step.finishReason === "tool-calls" ? "waiting" : "running"
3847
+ status: context?.pendingApproval ? "escalated" : step.finishReason === "tool-calls" ? "waiting" : "running",
3848
+ workflowId: context?.workflowId,
3849
+ threadId: context?.threadId,
3850
+ traceId: context?.traceId,
3851
+ pendingApprovalRequestId: context?.pendingApproval?.id
3604
3852
  });
3853
+ await this.syncSessionCheckpoint(sessionId);
3605
3854
  }
3606
3855
  if (this.config.telemetryCollector) {
3607
3856
  const now = new Date;
3608
- const context = sessionId ? this.activeStepContexts.get(sessionId) : undefined;
3609
3857
  const stepStartedAt = context?.stepStartedAt ?? now;
3610
3858
  const durationMs = Math.max(now.getTime() - stepStartedAt.getTime(), 0);
3611
3859
  if (context) {
@@ -3616,6 +3864,7 @@ ${params.prompt}` : params.prompt ?? "";
3616
3864
  sessionId,
3617
3865
  tenantId: context?.tenantId,
3618
3866
  actorId: context?.actorId,
3867
+ workflowId: context?.workflowId,
3619
3868
  traceId: context?.traceId,
3620
3869
  stepIndex: context?.stepIndex,
3621
3870
  stepStartedAt
@@ -3625,6 +3874,215 @@ ${params.prompt}` : params.prompt ?? "";
3625
3874
  }
3626
3875
  }
3627
3876
  }
3877
+ async ensureSession(params) {
3878
+ if (!this.config.sessionStore) {
3879
+ return;
3880
+ }
3881
+ const runtimeAdapter = this.resolveRuntimeAdapter();
3882
+ let session = await this.config.sessionStore.get(params.sessionId);
3883
+ const previousStatus = session?.status;
3884
+ if (!session && runtimeAdapter?.checkpoint) {
3885
+ const checkpoint = await runtimeAdapter.checkpoint.load(params.sessionId);
3886
+ if (checkpoint) {
3887
+ session = await this.config.sessionStore.create({
3888
+ ...omitSessionTimestamps(checkpoint.state),
3889
+ workflowId: params.options?.workflowId ?? checkpoint.state.workflowId,
3890
+ threadId: params.options?.threadId ?? checkpoint.state.threadId,
3891
+ traceId: params.traceId,
3892
+ checkpointId: checkpoint.checkpointId,
3893
+ status: "running",
3894
+ pendingApprovalRequestId: undefined,
3895
+ lastError: undefined,
3896
+ metadata: params.options?.metadata ?? checkpoint.state.metadata
3897
+ });
3898
+ }
3899
+ }
3900
+ if (!session) {
3901
+ session = await this.config.sessionStore.create({
3902
+ sessionId: params.sessionId,
3903
+ agentId: this.id,
3904
+ tenantId: params.options?.tenantId,
3905
+ actorId: params.options?.actorId,
3906
+ workflowId: params.options?.workflowId,
3907
+ threadId: params.options?.threadId,
3908
+ traceId: params.traceId,
3909
+ status: "running",
3910
+ messages: [],
3911
+ steps: [],
3912
+ metadata: params.options?.metadata
3913
+ });
3914
+ await this.emitAgentEvent("agent.session.created", {
3915
+ sessionId: params.sessionId,
3916
+ tenantId: params.options?.tenantId,
3917
+ workflowId: params.options?.workflowId,
3918
+ traceId: params.traceId
3919
+ });
3920
+ } else {
3921
+ await this.config.sessionStore.update(params.sessionId, {
3922
+ status: "running",
3923
+ workflowId: params.options?.workflowId ?? session.workflowId,
3924
+ threadId: params.options?.threadId ?? session.threadId,
3925
+ traceId: params.traceId,
3926
+ metadata: params.options?.metadata ?? session.metadata,
3927
+ pendingApprovalRequestId: undefined,
3928
+ lastError: undefined
3929
+ });
3930
+ await this.emitAgentEvent("agent.session.updated", {
3931
+ sessionId: params.sessionId,
3932
+ tenantId: params.options?.tenantId ?? session.tenantId,
3933
+ workflowId: params.options?.workflowId ?? session.workflowId,
3934
+ traceId: params.traceId,
3935
+ metadata: {
3936
+ previousStatus: previousStatus ?? "idle",
3937
+ nextStatus: "running"
3938
+ }
3939
+ });
3940
+ }
3941
+ await this.config.sessionStore.appendMessage(params.sessionId, {
3942
+ role: "user",
3943
+ content: params.prompt
3944
+ });
3945
+ if (runtimeAdapter?.suspendResume && (previousStatus === "waiting" || previousStatus === "escalated")) {
3946
+ await runtimeAdapter.suspendResume.resume({
3947
+ sessionId: params.sessionId,
3948
+ input: params.prompt,
3949
+ metadata: compactStringRecord({
3950
+ traceId: params.traceId,
3951
+ workflowId: params.options?.workflowId,
3952
+ threadId: params.options?.threadId
3953
+ })
3954
+ });
3955
+ }
3956
+ await this.syncSessionCheckpoint(params.sessionId);
3957
+ }
3958
+ resolveRuntimeAdapter() {
3959
+ return resolveRuntimeAdapterBundle(this.spec, this.config.runtimeAdapters);
3960
+ }
3961
+ async syncSessionCheckpoint(sessionId) {
3962
+ if (!this.config.sessionStore) {
3963
+ return null;
3964
+ }
3965
+ const runtimeAdapter = this.resolveRuntimeAdapter();
3966
+ const session = await this.config.sessionStore.get(sessionId);
3967
+ if (!runtimeAdapter?.checkpoint || !session) {
3968
+ return session;
3969
+ }
3970
+ const checkpointId = `${sessionId}:${Date.now()}`;
3971
+ await runtimeAdapter.checkpoint.save({
3972
+ sessionId,
3973
+ threadId: session.threadId,
3974
+ state: session,
3975
+ checkpointId,
3976
+ createdAt: new Date
3977
+ });
3978
+ await this.config.sessionStore.update(sessionId, {
3979
+ checkpointId
3980
+ });
3981
+ return this.config.sessionStore.get(sessionId);
3982
+ }
3983
+ async runSessionMiddleware(sessionId, hook) {
3984
+ if (!hook || !this.config.sessionStore) {
3985
+ return;
3986
+ }
3987
+ const session = await this.config.sessionStore.get(sessionId);
3988
+ if (!session) {
3989
+ return;
3990
+ }
3991
+ const next = await hook(session);
3992
+ if (!next) {
3993
+ return;
3994
+ }
3995
+ await this.config.sessionStore.update(sessionId, {
3996
+ status: next.status,
3997
+ metadata: next.metadata,
3998
+ workflowId: next.workflowId,
3999
+ threadId: next.threadId,
4000
+ traceId: next.traceId,
4001
+ checkpointId: next.checkpointId,
4002
+ pendingApprovalRequestId: next.pendingApprovalRequestId,
4003
+ lastError: next.lastError
4004
+ });
4005
+ await this.syncSessionCheckpoint(sessionId);
4006
+ }
4007
+ async requestApproval(sessionId, params) {
4008
+ const approvalWorkflow = this.config.approvalWorkflow;
4009
+ const context = this.activeStepContexts.get(sessionId);
4010
+ if (!approvalWorkflow || !context || context.pendingApproval) {
4011
+ return context?.pendingApproval;
4012
+ }
4013
+ const request = await approvalWorkflow.requestApproval({
4014
+ sessionId,
4015
+ agentId: this.id,
4016
+ tenantId: context.tenantId,
4017
+ toolName: params.toolName,
4018
+ toolCallId: params.toolCallId,
4019
+ toolArgs: params.args,
4020
+ reason: params.reason,
4021
+ payload: {
4022
+ traceId: context.traceId,
4023
+ workflowId: context.workflowId,
4024
+ threadId: context.threadId,
4025
+ code: params.error?.code
4026
+ }
4027
+ });
4028
+ context.pendingApproval = request;
4029
+ if (this.config.sessionStore) {
4030
+ await this.config.sessionStore.update(sessionId, {
4031
+ status: "escalated",
4032
+ pendingApprovalRequestId: request.id,
4033
+ lastError: params.error
4034
+ });
4035
+ await this.syncSessionCheckpoint(sessionId);
4036
+ }
4037
+ await this.emitAgentEvent("agent.tool.approval_requested", {
4038
+ sessionId,
4039
+ tenantId: context.tenantId,
4040
+ workflowId: context.workflowId,
4041
+ traceId: context.traceId,
4042
+ stepIndex: context.stepIndex,
4043
+ toolName: params.toolName,
4044
+ metadata: {
4045
+ approvalRequestId: request.id,
4046
+ toolCallId: params.toolCallId,
4047
+ reason: params.reason
4048
+ }
4049
+ });
4050
+ await this.emitAgentEvent("agent.escalated", {
4051
+ sessionId,
4052
+ tenantId: context.tenantId,
4053
+ workflowId: context.workflowId,
4054
+ traceId: context.traceId,
4055
+ stepIndex: context.stepIndex,
4056
+ toolName: params.toolName,
4057
+ metadata: {
4058
+ approvalRequestId: request.id,
4059
+ reason: params.reason
4060
+ }
4061
+ });
4062
+ const runtimeAdapter = this.resolveRuntimeAdapter();
4063
+ if (runtimeAdapter?.suspendResume) {
4064
+ await runtimeAdapter.suspendResume.suspend({
4065
+ sessionId,
4066
+ reason: params.reason,
4067
+ metadata: compactStringRecord({
4068
+ traceId: context.traceId,
4069
+ workflowId: context.workflowId,
4070
+ threadId: context.threadId,
4071
+ approvalRequestId: request.id
4072
+ })
4073
+ });
4074
+ }
4075
+ return request;
4076
+ }
4077
+ async emitAgentEvent(event, payload) {
4078
+ if (!this.config.eventEmitter) {
4079
+ return;
4080
+ }
4081
+ await this.config.eventEmitter(event, {
4082
+ agentId: this.id,
4083
+ ...payload
4084
+ });
4085
+ }
3628
4086
  createInnerAgent(model, maxSteps) {
3629
4087
  return new ToolLoopAgent({
3630
4088
  model,
@@ -3654,7 +4112,9 @@ ${params.prompt}` : params.prompt ?? "";
3654
4112
  contractspec_trace_id: params.traceId,
3655
4113
  contractspec_agent_id: this.id,
3656
4114
  contractspec_tenant_id: params.options?.tenantId,
3657
- contractspec_actor_id: params.options?.actorId
4115
+ contractspec_actor_id: params.options?.actorId,
4116
+ contractspec_workflow_id: params.options?.workflowId,
4117
+ contractspec_thread_id: params.options?.threadId
3658
4118
  };
3659
4119
  const tracingOptions = {
3660
4120
  ...posthogConfig.tracingOptions,
@@ -3666,6 +4126,106 @@ ${params.prompt}` : params.prompt ?? "";
3666
4126
  return createPostHogTracedModel2(this.config.model, posthogConfig, tracingOptions);
3667
4127
  }
3668
4128
  }
4129
+ function omitSessionTimestamps(session) {
4130
+ const { createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = session;
4131
+ return rest;
4132
+ }
4133
+ function resolveRuntimeAdapterBundle(spec, runtimeAdapters) {
4134
+ if (!runtimeAdapters) {
4135
+ return;
4136
+ }
4137
+ const preferredKeys = [
4138
+ "langgraph",
4139
+ "langchain",
4140
+ "workflow-devkit"
4141
+ ];
4142
+ for (const key of preferredKeys) {
4143
+ if (spec.runtime?.capabilities?.adapters?.[key] && runtimeAdapters[key]) {
4144
+ return runtimeAdapters[key];
4145
+ }
4146
+ }
4147
+ for (const key of preferredKeys) {
4148
+ if (runtimeAdapters[key]) {
4149
+ return runtimeAdapters[key];
4150
+ }
4151
+ }
4152
+ return;
4153
+ }
4154
+ function compactStringRecord(input) {
4155
+ return Object.fromEntries(Object.entries(input).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0));
4156
+ }
4157
+ function toAgentExecutionError(error) {
4158
+ if (error && typeof error === "object" && "kind" in error && typeof error.kind === "string" && "message" in error && typeof error.message === "string") {
4159
+ return {
4160
+ kind: error.kind,
4161
+ message: error.message,
4162
+ code: "code" in error && typeof error.code === "string" ? error.code : undefined,
4163
+ retryAfterMs: "retryAfterMs" in error && typeof error.retryAfterMs === "number" ? error.retryAfterMs : undefined
4164
+ };
4165
+ }
4166
+ if (error instanceof Error) {
4167
+ return {
4168
+ kind: error.message.toLowerCase().includes("timeout") || error.code === "TOOL_EXECUTION_TIMEOUT" ? "timeout" : "fatal",
4169
+ message: error.message,
4170
+ code: error.code
4171
+ };
4172
+ }
4173
+ return {
4174
+ kind: "fatal",
4175
+ message: String(error)
4176
+ };
4177
+ }
4178
+ function extractToolExecutionError(output) {
4179
+ if (!output) {
4180
+ return;
4181
+ }
4182
+ if (typeof output === "string") {
4183
+ if (output.toLowerCase().includes("error")) {
4184
+ return {
4185
+ kind: output.toLowerCase().includes("timeout") ? "timeout" : "retryable",
4186
+ message: output
4187
+ };
4188
+ }
4189
+ return;
4190
+ }
4191
+ if (typeof output !== "object") {
4192
+ return;
4193
+ }
4194
+ const record2 = output;
4195
+ const nestedError = record2["error"];
4196
+ if (nestedError instanceof Error) {
4197
+ return toAgentExecutionError(nestedError);
4198
+ }
4199
+ if (nestedError && typeof nestedError === "object") {
4200
+ return toAgentExecutionError(nestedError);
4201
+ }
4202
+ if (typeof record2["message"] === "string" && typeof record2["kind"] === "string") {
4203
+ return {
4204
+ kind: record2["kind"],
4205
+ message: record2["message"],
4206
+ code: typeof record2["code"] === "string" ? record2["code"] : undefined,
4207
+ retryAfterMs: typeof record2["retryAfterMs"] === "number" ? record2["retryAfterMs"] : undefined
4208
+ };
4209
+ }
4210
+ if (typeof record2["errorMessage"] === "string") {
4211
+ return {
4212
+ kind: typeof record2["errorCode"] === "string" && record2["errorCode"].includes("TIMEOUT") ? "timeout" : "retryable",
4213
+ message: record2["errorMessage"],
4214
+ code: typeof record2["errorCode"] === "string" ? record2["errorCode"] : undefined
4215
+ };
4216
+ }
4217
+ return;
4218
+ }
4219
+ function stringifyToolResult(toolName, output) {
4220
+ if (typeof output === "string") {
4221
+ return `[${toolName}] ${output}`;
4222
+ }
4223
+ try {
4224
+ return `[${toolName}] ${JSON.stringify(output)}`;
4225
+ } catch {
4226
+ return `[${toolName}] [unserializable result]`;
4227
+ }
4228
+ }
3669
4229
  function resolveMaxSteps(overrideMaxSteps, specMaxSteps) {
3670
4230
  const candidate = overrideMaxSteps ?? specMaxSteps ?? 10;
3671
4231
  if (!Number.isFinite(candidate)) {
@@ -3708,8 +4268,8 @@ function resolveEscalationError(spec, finishReason) {
3708
4268
  }
3709
4269
  var ContractSpecCallOptionsSchema;
3710
4270
  var init_contract_spec_agent = __esm(() => {
4271
+ init_workflow();
3711
4272
  init_injector();
3712
- init_spec();
3713
4273
  init_adapter();
3714
4274
  init_knowledge_tool();
3715
4275
  init_mcp_client();
@@ -3719,6 +4279,8 @@ var init_contract_spec_agent = __esm(() => {
3719
4279
  tenantId: z4.string().optional(),
3720
4280
  actorId: z4.string().optional(),
3721
4281
  sessionId: z4.string().optional(),
4282
+ workflowId: z4.string().optional(),
4283
+ threadId: z4.string().optional(),
3722
4284
  metadata: z4.record(z4.string(), z4.unknown()).optional()
3723
4285
  });
3724
4286
  });