@contractspec/lib.ai-agent 7.0.10 → 8.0.2

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 (77) 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 +653 -113
  4. package/dist/agent/contract-spec-agent.d.ts +16 -2
  5. package/dist/agent/contract-spec-agent.js +649 -112
  6. package/dist/agent/index.js +660 -118
  7. package/dist/agent/json-runner.d.ts +1 -1
  8. package/dist/agent/json-runner.js +649 -112
  9. package/dist/agent/unified-agent.d.ts +2 -2
  10. package/dist/agent/unified-agent.js +660 -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 +653 -113
  28. package/dist/node/agent/contract-spec-agent.js +649 -112
  29. package/dist/node/agent/index.js +660 -118
  30. package/dist/node/agent/json-runner.js +649 -112
  31. package/dist/node/agent/unified-agent.js +660 -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/telemetry/adapter.js +2 -0
  46. package/dist/node/telemetry/index.js +2 -0
  47. package/dist/providers/claude-agent-sdk/adapter.d.ts +1 -1
  48. package/dist/providers/claude-agent-sdk/adapter.js +3 -51
  49. package/dist/providers/claude-agent-sdk/index.js +3 -51
  50. package/dist/providers/claude-agent-sdk/tool-bridge.d.ts +1 -8
  51. package/dist/providers/index.js +8 -53
  52. package/dist/providers/opencode-sdk/adapter.d.ts +1 -13
  53. package/dist/providers/opencode-sdk/adapter.js +4 -51
  54. package/dist/providers/opencode-sdk/agent-bridge.d.ts +1 -10
  55. package/dist/providers/opencode-sdk/index.js +4 -51
  56. package/dist/providers/opencode-sdk/tool-bridge.d.ts +1 -4
  57. package/dist/providers/types.d.ts +1 -8
  58. package/dist/session/store.d.ts +2 -2
  59. package/dist/telemetry/adapter.d.ts +1 -0
  60. package/dist/telemetry/adapter.js +2 -0
  61. package/dist/telemetry/index.js +2 -0
  62. package/dist/tools/knowledge-tool.d.ts +1 -1
  63. package/dist/tools/mcp-server.d.ts +1 -1
  64. package/dist/tools/operation-tool-handler.d.ts +1 -1
  65. package/dist/tools/tool-adapter.d.ts +1 -1
  66. package/dist/types.d.ts +13 -0
  67. package/package.json +7 -43
  68. package/dist/node/spec/index.js +0 -2233
  69. package/dist/node/spec/registry.js +0 -2178
  70. package/dist/node/spec/spec.js +0 -2188
  71. package/dist/spec/index.d.ts +0 -2
  72. package/dist/spec/index.js +0 -2233
  73. package/dist/spec/registry.d.ts +0 -41
  74. package/dist/spec/registry.js +0 -2178
  75. package/dist/spec/spec.d.ts +0 -218
  76. package/dist/spec/spec.js +0 -2188
  77. package/dist/spec/spec.test.d.ts +0 -1
@@ -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 "node: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)
@@ -2317,56 +2459,6 @@ function generateSessionId() {
2317
2459
  return `sess_${createSecureSessionToken()}`;
2318
2460
  }
2319
2461
 
2320
- // src/spec/spec.ts
2321
- function defineAgent(spec) {
2322
- const i18n = createAgentI18n(spec.locale);
2323
- if (!spec.meta?.key) {
2324
- throw new Error(i18n.t("error.agentKeyRequired"));
2325
- }
2326
- if (typeof spec.meta.version !== "string") {
2327
- throw new Error(i18n.t("error.agentMissingVersion", { key: spec.meta.key }));
2328
- }
2329
- if (!spec.instructions?.trim()) {
2330
- throw new Error(i18n.t("error.agentRequiresInstructions", { key: spec.meta.key }));
2331
- }
2332
- if (!spec.tools?.length) {
2333
- throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
2334
- }
2335
- for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
2336
- if (portRef !== undefined && portRef.trim().length === 0) {
2337
- throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
2338
- }
2339
- }
2340
- const toolNames = new Set;
2341
- for (const tool of spec.tools) {
2342
- if (toolNames.has(tool.name)) {
2343
- throw new Error(i18n.t("error.agentDuplicateTool", {
2344
- key: spec.meta.key,
2345
- name: tool.name
2346
- }));
2347
- }
2348
- toolNames.add(tool.name);
2349
- if (tool.subagentRef && tool.operationRef) {
2350
- throw new Error(`Agent ${spec.meta.key} tool "${tool.name}" cannot have both subagentRef and operationRef. Use one.`);
2351
- }
2352
- const outputRefCount = [
2353
- tool.outputPresentation,
2354
- tool.outputForm,
2355
- tool.outputDataView
2356
- ].filter(Boolean).length;
2357
- if (outputRefCount > 1) {
2358
- throw new Error(`Agent ${spec.meta.key} tool "${tool.name}" has multiple output refs (outputPresentation, outputForm, outputDataView). Use at most one.`);
2359
- }
2360
- }
2361
- return Object.freeze(spec);
2362
- }
2363
- function agentKey(meta) {
2364
- return `${meta.key}.v${meta.version}`;
2365
- }
2366
- var init_spec = __esm(() => {
2367
- init_i18n();
2368
- });
2369
-
2370
2462
  // src/telemetry/adapter.ts
2371
2463
  function parseAgentId(agentId) {
2372
2464
  const match = agentId.match(/^(.+)\.v(\d+)$/);
@@ -2434,6 +2526,7 @@ async function trackAgentStep(collector, agentId, step, durationMs, context) {
2434
2526
  agentId,
2435
2527
  actorId: context?.actorId,
2436
2528
  tenantId: context?.tenantId,
2529
+ workflowId: context?.workflowId,
2437
2530
  stepIndex: context?.stepIndex,
2438
2531
  toolName: toolCall.toolName,
2439
2532
  toolCallArgs: toolCall.input,
@@ -2459,6 +2552,7 @@ async function trackAgentStep(collector, agentId, step, durationMs, context) {
2459
2552
  agentId,
2460
2553
  actorId: context?.actorId,
2461
2554
  tenantId: context?.tenantId,
2555
+ workflowId: context?.workflowId,
2462
2556
  stepIndex: context?.stepIndex,
2463
2557
  stepStartedAt: context?.stepStartedAt,
2464
2558
  finishReason: step.finishReason,
@@ -3377,7 +3471,10 @@ var exports_contract_spec_agent = {};
3377
3471
  __export(exports_contract_spec_agent, {
3378
3472
  ContractSpecAgent: () => ContractSpecAgent
3379
3473
  });
3380
- import { randomUUID } from "node:crypto";
3474
+ import { randomUUID as randomUUID2 } from "node:crypto";
3475
+ import {
3476
+ agentKey
3477
+ } from "@contractspec/lib.contracts-spec/agent";
3381
3478
  import {
3382
3479
  stepCountIs,
3383
3480
  Experimental_Agent as ToolLoopAgent
@@ -3402,7 +3499,10 @@ class ContractSpecAgent {
3402
3499
  this.mcpCleanup = mcpCleanup;
3403
3500
  }
3404
3501
  static async create(config) {
3405
- const effectiveConfig = config;
3502
+ const effectiveConfig = {
3503
+ ...config,
3504
+ approvalWorkflow: config.approvalWorkflow ?? (config.spec.policy?.escalation?.approvalWorkflow ? createApprovalWorkflow() : undefined)
3505
+ };
3406
3506
  let mcpToolset = null;
3407
3507
  if ((effectiveConfig.mcpServers?.length ?? 0) > 0) {
3408
3508
  mcpToolset = await createMcpToolsets(effectiveConfig.mcpServers ?? [], {
@@ -3455,34 +3555,27 @@ class ContractSpecAgent {
3455
3555
  }
3456
3556
  async generate(params) {
3457
3557
  const sessionId = params.options?.sessionId ?? generateSessionId();
3458
- 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();
3459
3562
  this.activeStepContexts.set(sessionId, {
3460
3563
  traceId,
3461
3564
  tenantId: params.options?.tenantId,
3462
3565
  actorId: params.options?.actorId,
3566
+ workflowId,
3567
+ threadId,
3463
3568
  stepIndex: 0,
3464
3569
  stepStartedAt: new Date
3465
3570
  });
3466
- if (this.config.sessionStore && !params.messages?.length) {
3467
- const existing = await this.config.sessionStore.get(sessionId);
3468
- if (!existing) {
3469
- await this.config.sessionStore.create({
3470
- sessionId,
3471
- agentId: this.id,
3472
- tenantId: params.options?.tenantId,
3473
- actorId: params.options?.actorId,
3474
- status: "running",
3475
- messages: [],
3476
- steps: [],
3477
- metadata: params.options?.metadata
3478
- });
3479
- } else if (existing.status !== "running") {
3480
- await this.config.sessionStore.update(sessionId, { status: "running" });
3481
- }
3482
- await this.config.sessionStore.appendMessage(sessionId, {
3483
- role: "user",
3484
- content: params.prompt ?? ""
3571
+ if (!params.messages?.length) {
3572
+ await this.ensureSession({
3573
+ sessionId,
3574
+ prompt: params.prompt ?? "",
3575
+ options: params.options,
3576
+ traceId
3485
3577
  });
3578
+ await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.beforeModel);
3486
3579
  }
3487
3580
  const model = await this.resolveModelForCall({
3488
3581
  sessionId,
@@ -3497,6 +3590,8 @@ class ContractSpecAgent {
3497
3590
  tenantId: params.options?.tenantId,
3498
3591
  actorId: params.options?.actorId,
3499
3592
  sessionId,
3593
+ workflowId,
3594
+ threadId,
3500
3595
  metadata: params.options?.metadata
3501
3596
  }
3502
3597
  };
@@ -3519,26 +3614,94 @@ ${params.prompt}` : params.prompt ?? "";
3519
3614
  });
3520
3615
  }
3521
3616
  } catch (error) {
3617
+ const executionError = toAgentExecutionError(error);
3522
3618
  if (this.config.sessionStore) {
3523
3619
  await this.config.sessionStore.update(sessionId, {
3524
- status: "failed"
3620
+ status: "failed",
3621
+ traceId,
3622
+ workflowId,
3623
+ threadId,
3624
+ lastError: executionError
3525
3625
  });
3626
+ await this.syncSessionCheckpoint(sessionId);
3526
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
+ });
3527
3638
  this.activeStepContexts.delete(sessionId);
3528
3639
  throw error;
3529
3640
  }
3530
- this.activeStepContexts.delete(sessionId);
3531
- const escalationError = resolveEscalationError(this.spec, result.finishReason);
3532
- if (this.config.sessionStore) {
3533
- await this.config.sessionStore.appendMessage(sessionId, {
3534
- role: "assistant",
3535
- 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
3536
3667
  });
3668
+ }
3669
+ const finalStatus = pendingApproval ? "escalated" : "completed";
3670
+ if (this.config.sessionStore) {
3537
3671
  await this.config.sessionStore.update(sessionId, {
3538
- 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
+ }
3539
3701
  });
3540
3702
  }
3541
3703
  const session = this.config.sessionStore ? await this.config.sessionStore.get(sessionId) : null;
3704
+ this.activeStepContexts.delete(sessionId);
3542
3705
  return {
3543
3706
  text: result.text,
3544
3707
  steps: result.steps,
@@ -3557,23 +3720,25 @@ ${params.prompt}` : params.prompt ?? "";
3557
3720
  finishReason: result.finishReason,
3558
3721
  usage: result.usage,
3559
3722
  session: session ?? undefined,
3560
- pendingApproval: escalationError ? {
3561
- toolName: this.spec.policy?.escalation?.approvalWorkflow ?? "approval_required",
3562
- toolCallId: `approval_${sessionId}`,
3563
- args: {
3564
- reason: escalationError.message,
3565
- code: escalationError.code
3566
- }
3723
+ pendingApproval: pendingApproval ? {
3724
+ toolName: pendingApproval.toolName,
3725
+ toolCallId: pendingApproval.toolCallId,
3726
+ args: pendingApproval.toolArgs
3567
3727
  } : undefined
3568
3728
  };
3569
3729
  }
3570
3730
  async stream(params) {
3571
3731
  const sessionId = params.options?.sessionId ?? generateSessionId();
3572
- 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();
3573
3736
  this.activeStepContexts.set(sessionId, {
3574
3737
  traceId,
3575
3738
  tenantId: params.options?.tenantId,
3576
3739
  actorId: params.options?.actorId,
3740
+ workflowId,
3741
+ threadId,
3577
3742
  stepIndex: 0,
3578
3743
  stepStartedAt: new Date
3579
3744
  });
@@ -3582,6 +3747,13 @@ ${params.prompt}` : params.prompt ?? "";
3582
3747
  ${params.systemOverride}
3583
3748
 
3584
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);
3585
3757
  const model = await this.resolveModelForCall({
3586
3758
  sessionId,
3587
3759
  traceId,
@@ -3589,26 +3761,6 @@ ${params.prompt}` : params.prompt ?? "";
3589
3761
  });
3590
3762
  const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
3591
3763
  const inner = this.createInnerAgent(model, effectiveMaxSteps);
3592
- if (this.config.sessionStore) {
3593
- const existing = await this.config.sessionStore.get(sessionId);
3594
- if (!existing) {
3595
- await this.config.sessionStore.create({
3596
- sessionId,
3597
- agentId: this.id,
3598
- tenantId: params.options?.tenantId,
3599
- actorId: params.options?.actorId,
3600
- status: "running",
3601
- messages: [],
3602
- steps: [],
3603
- metadata: params.options?.metadata
3604
- });
3605
- }
3606
- await this.config.sessionStore.appendMessage(sessionId, {
3607
- role: "user",
3608
- content: prompt
3609
- });
3610
- await this.config.sessionStore.update(sessionId, { status: "running" });
3611
- }
3612
3764
  return inner.stream({
3613
3765
  prompt,
3614
3766
  abortSignal: params.signal,
@@ -3616,21 +3768,92 @@ ${params.prompt}` : params.prompt ?? "";
3616
3768
  tenantId: params.options?.tenantId,
3617
3769
  actorId: params.options?.actorId,
3618
3770
  sessionId,
3771
+ workflowId,
3772
+ threadId,
3619
3773
  metadata: params.options?.metadata
3620
3774
  }
3621
3775
  });
3622
3776
  }
3623
3777
  async handleStepFinish(step) {
3624
3778
  const sessionId = step.options?.sessionId;
3779
+ const context = sessionId ? this.activeStepContexts.get(sessionId) : undefined;
3625
3780
  if (sessionId && this.config.sessionStore) {
3626
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
+ }
3627
3846
  await this.config.sessionStore.update(sessionId, {
3628
- 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
3629
3852
  });
3853
+ await this.syncSessionCheckpoint(sessionId);
3630
3854
  }
3631
3855
  if (this.config.telemetryCollector) {
3632
3856
  const now = new Date;
3633
- const context = sessionId ? this.activeStepContexts.get(sessionId) : undefined;
3634
3857
  const stepStartedAt = context?.stepStartedAt ?? now;
3635
3858
  const durationMs = Math.max(now.getTime() - stepStartedAt.getTime(), 0);
3636
3859
  if (context) {
@@ -3641,6 +3864,7 @@ ${params.prompt}` : params.prompt ?? "";
3641
3864
  sessionId,
3642
3865
  tenantId: context?.tenantId,
3643
3866
  actorId: context?.actorId,
3867
+ workflowId: context?.workflowId,
3644
3868
  traceId: context?.traceId,
3645
3869
  stepIndex: context?.stepIndex,
3646
3870
  stepStartedAt
@@ -3650,6 +3874,215 @@ ${params.prompt}` : params.prompt ?? "";
3650
3874
  }
3651
3875
  }
3652
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
+ }
3653
4086
  createInnerAgent(model, maxSteps) {
3654
4087
  return new ToolLoopAgent({
3655
4088
  model,
@@ -3679,7 +4112,9 @@ ${params.prompt}` : params.prompt ?? "";
3679
4112
  contractspec_trace_id: params.traceId,
3680
4113
  contractspec_agent_id: this.id,
3681
4114
  contractspec_tenant_id: params.options?.tenantId,
3682
- 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
3683
4118
  };
3684
4119
  const tracingOptions = {
3685
4120
  ...posthogConfig.tracingOptions,
@@ -3691,6 +4126,106 @@ ${params.prompt}` : params.prompt ?? "";
3691
4126
  return createPostHogTracedModel2(this.config.model, posthogConfig, tracingOptions);
3692
4127
  }
3693
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
+ }
3694
4229
  function resolveMaxSteps(overrideMaxSteps, specMaxSteps) {
3695
4230
  const candidate = overrideMaxSteps ?? specMaxSteps ?? 10;
3696
4231
  if (!Number.isFinite(candidate)) {
@@ -3733,8 +4268,8 @@ function resolveEscalationError(spec, finishReason) {
3733
4268
  }
3734
4269
  var ContractSpecCallOptionsSchema;
3735
4270
  var init_contract_spec_agent = __esm(() => {
4271
+ init_workflow();
3736
4272
  init_injector();
3737
- init_spec();
3738
4273
  init_adapter();
3739
4274
  init_knowledge_tool();
3740
4275
  init_mcp_client();
@@ -3744,6 +4279,8 @@ var init_contract_spec_agent = __esm(() => {
3744
4279
  tenantId: z4.string().optional(),
3745
4280
  actorId: z4.string().optional(),
3746
4281
  sessionId: z4.string().optional(),
4282
+ workflowId: z4.string().optional(),
4283
+ threadId: z4.string().optional(),
3747
4284
  metadata: z4.record(z4.string(), z4.unknown()).optional()
3748
4285
  });
3749
4286
  });