@corbat-tech/coco 2.40.0 → 2.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import * as path17 from 'path';
5
5
  import path17__default, { dirname, join, basename, resolve } from 'path';
6
6
  import * as fs16 from 'fs/promises';
7
7
  import fs16__default, { access, readFile, readdir, writeFile, mkdir } from 'fs/promises';
8
- import { randomUUID, randomBytes, createHash } from 'crypto';
8
+ import { randomUUID, createHmac, timingSafeEqual, randomBytes, createHash } from 'crypto';
9
9
  import * as http from 'http';
10
10
  import { createServer } from 'http';
11
11
  import { fileURLToPath, URL as URL$1 } from 'url';
@@ -3411,8 +3411,8 @@ function fillPrompt(template, variables) {
3411
3411
  let result = template;
3412
3412
  for (const [key, value] of Object.entries(variables)) {
3413
3413
  const placeholder = `{{${key}}}`;
3414
- const stringValue = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
3415
- result = result.replaceAll(placeholder, stringValue);
3414
+ const stringValue2 = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
3415
+ result = result.replaceAll(placeholder, stringValue2);
3416
3416
  }
3417
3417
  return result;
3418
3418
  }
@@ -5341,8 +5341,8 @@ function fillPrompt2(template, variables) {
5341
5341
  let result = template;
5342
5342
  for (const [key, value] of Object.entries(variables)) {
5343
5343
  const placeholder = `{{${key}}}`;
5344
- const stringValue = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
5345
- result = result.replaceAll(placeholder, stringValue);
5344
+ const stringValue2 = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
5345
+ result = result.replaceAll(placeholder, stringValue2);
5346
5346
  }
5347
5347
  return result;
5348
5348
  }
@@ -10888,8 +10888,8 @@ function fillPrompt3(template, variables) {
10888
10888
  let result = template;
10889
10889
  for (const [key, value] of Object.entries(variables)) {
10890
10890
  const placeholder = `{{${key}}}`;
10891
- const stringValue = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
10892
- result = result.replaceAll(placeholder, stringValue);
10891
+ const stringValue2 = typeof value === "string" ? value : typeof value === "number" ? String(value) : JSON.stringify(value, null, 2);
10892
+ result = result.replaceAll(placeholder, stringValue2);
10893
10893
  }
10894
10894
  return result;
10895
10895
  }
@@ -18456,7 +18456,50 @@ var VertexProvider = class {
18456
18456
 
18457
18457
  // src/providers/pricing.ts
18458
18458
  init_catalog();
18459
- getCatalogModelPricingMap();
18459
+ var MODEL_PRICING = getCatalogModelPricingMap();
18460
+ var DEFAULT_PRICING = {
18461
+ anthropic: { inputPerMillion: 3, outputPerMillion: 15, contextWindow: 2e5 },
18462
+ openai: { inputPerMillion: 2.5, outputPerMillion: 10, contextWindow: 128e3 },
18463
+ codex: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 128e3 },
18464
+ // ChatGPT Plus/Pro subscription
18465
+ gemini: { inputPerMillion: 0.1, outputPerMillion: 0.4, contextWindow: 1e6 },
18466
+ vertex: { inputPerMillion: 0.1, outputPerMillion: 0.4, contextWindow: 1048576 },
18467
+ kimi: { inputPerMillion: 1.2, outputPerMillion: 1.2, contextWindow: 8192 },
18468
+ "kimi-code": { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 131072 },
18469
+ // Included in subscription
18470
+ copilot: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 2e5 },
18471
+ // Included in subscription
18472
+ lmstudio: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 32768 },
18473
+ // Free - local models
18474
+ ollama: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 128e3 },
18475
+ // Free - local models
18476
+ groq: { inputPerMillion: 0.05, outputPerMillion: 0.08, contextWindow: 128e3 },
18477
+ // Free tier available
18478
+ openrouter: { inputPerMillion: 2, outputPerMillion: 8, contextWindow: 2e5 },
18479
+ // Varies by model
18480
+ mistral: { inputPerMillion: 0.25, outputPerMillion: 0.75, contextWindow: 32768 },
18481
+ deepseek: { inputPerMillion: 0.14, outputPerMillion: 0.28, contextWindow: 128e3 },
18482
+ // Very cheap
18483
+ together: { inputPerMillion: 0.2, outputPerMillion: 0.2, contextWindow: 32768 },
18484
+ huggingface: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 32768 },
18485
+ // Free tier
18486
+ qwen: { inputPerMillion: 0.3, outputPerMillion: 1.2, contextWindow: 131072 }
18487
+ // qwen-coder-plus pricing
18488
+ };
18489
+ function estimateCost(model2, inputTokens, outputTokens, provider) {
18490
+ const pricing = MODEL_PRICING[model2] ?? (provider ? DEFAULT_PRICING[provider] : DEFAULT_PRICING.anthropic);
18491
+ const inputCost = inputTokens / 1e6 * pricing.inputPerMillion;
18492
+ const outputCost = outputTokens / 1e6 * pricing.outputPerMillion;
18493
+ return {
18494
+ inputCost,
18495
+ outputCost,
18496
+ totalCost: inputCost + outputCost,
18497
+ inputTokens,
18498
+ outputTokens,
18499
+ model: model2,
18500
+ currency: "USD"
18501
+ };
18502
+ }
18460
18503
 
18461
18504
  // src/providers/circuit-breaker.ts
18462
18505
  init_errors();
@@ -19500,6 +19543,22 @@ function isAgentMode(value) {
19500
19543
  }
19501
19544
 
19502
19545
  // src/runtime/context.ts
19546
+ var RuntimePolicyViolation = class extends Error {
19547
+ code;
19548
+ subject;
19549
+ tenantId;
19550
+ policyPath;
19551
+ severity;
19552
+ constructor(input) {
19553
+ super(input.message);
19554
+ this.name = "RuntimePolicyViolation";
19555
+ this.code = input.code;
19556
+ this.subject = input.subject;
19557
+ this.tenantId = input.tenantId;
19558
+ this.policyPath = input.policyPath;
19559
+ this.severity = input.severity ?? "blocked";
19560
+ }
19561
+ };
19503
19562
  function createRuntimeRequestContext(input = {}) {
19504
19563
  return {
19505
19564
  surface: input.surface ?? "api",
@@ -19542,6 +19601,27 @@ function runtimeContextToMetadata(context) {
19542
19601
  dataClassification: context.policy?.dataBoundary?.classification
19543
19602
  };
19544
19603
  }
19604
+ function createRuntimeTenantBoundary(context, hostMode = "local") {
19605
+ return {
19606
+ hostMode,
19607
+ surface: context?.surface ?? "api",
19608
+ tenantId: context?.tenant?.id,
19609
+ required: hostMode === "hosted" && context?.surface !== "cli"
19610
+ };
19611
+ }
19612
+ function assertRuntimeTenantBoundary(context, hostMode = "local", subject = "runtime operation") {
19613
+ const boundary = createRuntimeTenantBoundary(context, hostMode);
19614
+ if (boundary.required && !boundary.tenantId) {
19615
+ throw new RuntimePolicyViolation({
19616
+ code: "tenant_required",
19617
+ subject,
19618
+ tenantId: boundary.tenantId,
19619
+ policyPath: "runtimeContext.tenant.id",
19620
+ message: `Runtime tenant is required for hosted ${boundary.surface} operations.`
19621
+ });
19622
+ }
19623
+ return boundary;
19624
+ }
19545
19625
  function evaluateRuntimeToolPolicy(policy, input) {
19546
19626
  if (policy?.allowedTools && !policy.allowedTools.includes(input.toolName)) {
19547
19627
  return {
@@ -19585,23 +19665,65 @@ function evaluateRuntimeRiskPolicy(policy, input) {
19585
19665
  }
19586
19666
  return { allowed: true, risk: input.risk };
19587
19667
  }
19668
+ function assertRuntimeTurnWithinPolicy(policy, input) {
19669
+ const maxTurns = policy?.costBudget?.maxTurns;
19670
+ if (maxTurns !== void 0 && input.currentTurns >= maxTurns) {
19671
+ throw new RuntimePolicyViolation({
19672
+ code: "max_turns_exceeded",
19673
+ subject: input.subject,
19674
+ tenantId: input.tenantId,
19675
+ policyPath: "runtimePolicy.costBudget.maxTurns",
19676
+ message: `Runtime policy turn budget exceeded: ${input.currentTurns}/${maxTurns}`
19677
+ });
19678
+ }
19679
+ }
19588
19680
  function assertRuntimeUsageWithinPolicy(policy, usage) {
19589
19681
  const budget = policy?.costBudget;
19682
+ const subject = usage.subject ?? "runtime usage";
19590
19683
  if (!budget) return;
19591
19684
  if (budget.maxInputTokens !== void 0 && (usage.inputTokens ?? 0) > budget.maxInputTokens) {
19592
- throw new Error(
19593
- `Runtime policy input token budget exceeded: ${usage.inputTokens ?? 0}/${budget.maxInputTokens}`
19594
- );
19685
+ throw new RuntimePolicyViolation({
19686
+ code: "input_tokens_exceeded",
19687
+ subject,
19688
+ tenantId: usage.tenantId,
19689
+ policyPath: "runtimePolicy.costBudget.maxInputTokens",
19690
+ message: `Runtime policy input token budget exceeded: ${usage.inputTokens ?? 0}/${budget.maxInputTokens}`
19691
+ });
19595
19692
  }
19596
19693
  if (budget.maxOutputTokens !== void 0 && (usage.outputTokens ?? 0) > budget.maxOutputTokens) {
19597
- throw new Error(
19598
- `Runtime policy output token budget exceeded: ${usage.outputTokens ?? 0}/${budget.maxOutputTokens}`
19599
- );
19694
+ throw new RuntimePolicyViolation({
19695
+ code: "output_tokens_exceeded",
19696
+ subject,
19697
+ tenantId: usage.tenantId,
19698
+ policyPath: "runtimePolicy.costBudget.maxOutputTokens",
19699
+ message: `Runtime policy output token budget exceeded: ${usage.outputTokens ?? 0}/${budget.maxOutputTokens}`
19700
+ });
19701
+ }
19702
+ if (budget.maxEstimatedCostUsd !== void 0 && (usage.estimatedCostUsd ?? 0) > budget.maxEstimatedCostUsd) {
19703
+ throw new RuntimePolicyViolation({
19704
+ code: "estimated_cost_exceeded",
19705
+ subject,
19706
+ tenantId: usage.tenantId,
19707
+ policyPath: "runtimePolicy.costBudget.maxEstimatedCostUsd",
19708
+ message: `Runtime policy estimated cost budget exceeded: ${usage.estimatedCostUsd ?? 0}/${budget.maxEstimatedCostUsd}`
19709
+ });
19600
19710
  }
19601
19711
  }
19712
+ function createRetentionCutoffs(policy, now = /* @__PURE__ */ new Date()) {
19713
+ const retention = policy?.retention;
19714
+ return {
19715
+ conversationBefore: cutoffIso(now, retention?.conversationDays),
19716
+ eventBefore: cutoffIso(now, retention?.eventDays),
19717
+ artifactBefore: cutoffIso(now, retention?.artifactDays)
19718
+ };
19719
+ }
19602
19720
  function cloneRuntimePolicy(policy) {
19603
19721
  return mergeRuntimePolicy(void 0, policy) ?? {};
19604
19722
  }
19723
+ function cutoffIso(now, days) {
19724
+ if (days === void 0) return void 0;
19725
+ return new Date(now.getTime() - days * 24 * 60 * 60 * 1e3).toISOString();
19726
+ }
19605
19727
  function riskRank(risk) {
19606
19728
  switch (risk) {
19607
19729
  case "read-only":
@@ -20043,6 +20165,7 @@ var LEGACY_ROLE_MAPPINGS = [
20043
20165
  { legacy: "coder", role: "coder", reason: "legacy executor role" },
20044
20166
  { legacy: "test", role: "tester", reason: "test authoring/execution" },
20045
20167
  { legacy: "tester", role: "tester", reason: "legacy executor role" },
20168
+ { legacy: "verifier", role: "tester", reason: "verification maps to tester capability" },
20046
20169
  { legacy: "tdd", role: "tester", reason: "test-first implementation" },
20047
20170
  { legacy: "e2e", role: "tester", reason: "end-to-end testing" },
20048
20171
  { legacy: "review", role: "reviewer", reason: "code review" },
@@ -20778,7 +20901,7 @@ var NULL_EVENT_LOG = {
20778
20901
  function graphNodeToTask(node, workflowInput) {
20779
20902
  return {
20780
20903
  id: node.id,
20781
- role: node.agentRole ?? "coder",
20904
+ role: node.agentRole ?? mapLegacyAgentRole(node.id, "coder"),
20782
20905
  objective: node.description,
20783
20906
  context: {
20784
20907
  workflowInput,
@@ -20964,6 +21087,239 @@ function cloneArtifact(artifact) {
20964
21087
  };
20965
21088
  }
20966
21089
 
21090
+ // src/runtime/agent-runner.ts
21091
+ var AgentRunner = class {
21092
+ constructor(options = {}) {
21093
+ this.options = options;
21094
+ }
21095
+ options;
21096
+ async run(input) {
21097
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
21098
+ const trace = input.trace ?? createAgentTraceContext({ taskId: input.task.id });
21099
+ this.options.eventLog?.record("agent.started", {
21100
+ taskId: input.task.id,
21101
+ role: input.task.role,
21102
+ trace
21103
+ });
21104
+ try {
21105
+ const raw = await (this.options.executor ?? defaultExecutor)({
21106
+ task: input.task,
21107
+ capability: input.capability,
21108
+ trace,
21109
+ assertToolAllowed: (toolName) => {
21110
+ const decision = evaluateAgentToolPolicy({
21111
+ capability: input.capability,
21112
+ toolName,
21113
+ manifest: input.toolRiskManifest
21114
+ });
21115
+ this.options.eventLog?.record("agent.tool.called", {
21116
+ taskId: input.task.id,
21117
+ role: input.task.role,
21118
+ toolName,
21119
+ decision,
21120
+ trace
21121
+ });
21122
+ if (!decision.allowed) {
21123
+ throw new Error(decision.reason ?? `Tool '${toolName}' is not allowed.`);
21124
+ }
21125
+ }
21126
+ });
21127
+ const result = normalizeAgentRunResult({
21128
+ id: `${input.task.id}-run-${Date.now().toString(36)}`,
21129
+ taskId: input.task.id,
21130
+ role: input.task.role,
21131
+ success: raw.success ?? true,
21132
+ output: raw.output,
21133
+ turns: raw.turns,
21134
+ toolsUsed: raw.toolsUsed,
21135
+ usage: {
21136
+ inputTokens: raw.inputTokens ?? 0,
21137
+ outputTokens: raw.outputTokens ?? 0,
21138
+ estimated: raw.inputTokens === void 0 || raw.outputTokens === void 0
21139
+ },
21140
+ startedAt,
21141
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
21142
+ durationMs: Date.now() - Date.parse(startedAt),
21143
+ error: raw.error,
21144
+ metadata: { ...raw.metadata, trace }
21145
+ });
21146
+ this.options.eventLog?.record(result.success ? "agent.completed" : "agent.failed", {
21147
+ taskId: input.task.id,
21148
+ role: input.task.role,
21149
+ agentRunId: result.id,
21150
+ trace,
21151
+ error: result.error
21152
+ });
21153
+ return result;
21154
+ } catch (error) {
21155
+ const message = error instanceof Error ? error.message : String(error);
21156
+ const result = normalizeAgentRunResult({
21157
+ id: `${input.task.id}-run-${Date.now().toString(36)}`,
21158
+ taskId: input.task.id,
21159
+ role: input.task.role,
21160
+ success: false,
21161
+ output: message,
21162
+ startedAt,
21163
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
21164
+ durationMs: Date.now() - Date.parse(startedAt),
21165
+ error: message,
21166
+ metadata: { trace }
21167
+ });
21168
+ this.options.eventLog?.record("agent.failed", {
21169
+ taskId: input.task.id,
21170
+ role: input.task.role,
21171
+ agentRunId: result.id,
21172
+ trace,
21173
+ error: message
21174
+ });
21175
+ return result;
21176
+ }
21177
+ }
21178
+ };
21179
+ function createAgentRunner(options) {
21180
+ return new AgentRunner(options);
21181
+ }
21182
+ async function defaultExecutor(context) {
21183
+ return {
21184
+ output: `Agent ${context.capability.role} accepted task '${context.task.objective}'.`
21185
+ };
21186
+ }
21187
+
21188
+ // src/runtime/runtime-agent-node-executor.ts
21189
+ var AgentDefinitionRegistry = class {
21190
+ definitionsByRole = /* @__PURE__ */ new Map();
21191
+ definitionsById = /* @__PURE__ */ new Map();
21192
+ constructor(definitions = []) {
21193
+ for (const definition of definitions) {
21194
+ this.register(definition);
21195
+ }
21196
+ }
21197
+ register(definition) {
21198
+ this.definitionsById.set(definition.id, cloneDefinition(definition));
21199
+ this.definitionsByRole.set(definition.role, cloneDefinition(definition));
21200
+ }
21201
+ get(id) {
21202
+ const definition = this.definitionsById.get(id);
21203
+ return definition ? cloneDefinition(definition) : void 0;
21204
+ }
21205
+ getByRole(role) {
21206
+ const definition = this.definitionsByRole.get(role);
21207
+ return definition ? cloneDefinition(definition) : void 0;
21208
+ }
21209
+ list() {
21210
+ return [...this.definitionsById.values()].map(cloneDefinition);
21211
+ }
21212
+ };
21213
+ var RuntimeAgentNodeExecutor = class {
21214
+ constructor(options) {
21215
+ this.options = options;
21216
+ this.runner = options.runner ?? new AgentRunner(options.runnerOptions);
21217
+ }
21218
+ options;
21219
+ runner;
21220
+ execute = async (execution) => {
21221
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
21222
+ const definition = this.options.registry.getByRole(execution.task.role);
21223
+ if (!definition) {
21224
+ return normalizeAgentRunResult({
21225
+ id: `${execution.workflowRunId}-${execution.node.id}-missing-definition`,
21226
+ taskId: execution.task.id,
21227
+ role: execution.task.role,
21228
+ success: false,
21229
+ output: "",
21230
+ startedAt,
21231
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
21232
+ error: `No agent definition registered for role '${execution.task.role}'.`,
21233
+ metadata: {
21234
+ workflowRunId: execution.workflowRunId,
21235
+ nodeId: execution.node.id,
21236
+ trace: execution.trace
21237
+ }
21238
+ });
21239
+ }
21240
+ const blockedTool = this.findBlockedTool(definition, execution);
21241
+ if (blockedTool) {
21242
+ return normalizeAgentRunResult({
21243
+ id: `${execution.workflowRunId}-${execution.node.id}-policy-blocked`,
21244
+ taskId: execution.task.id,
21245
+ role: execution.task.role,
21246
+ success: false,
21247
+ output: "",
21248
+ startedAt,
21249
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
21250
+ error: blockedTool,
21251
+ metadata: {
21252
+ workflowRunId: execution.workflowRunId,
21253
+ nodeId: execution.node.id,
21254
+ agentDefinitionId: definition.id,
21255
+ trace: execution.trace
21256
+ }
21257
+ });
21258
+ }
21259
+ const input = {
21260
+ task: {
21261
+ ...execution.task,
21262
+ context: {
21263
+ ...execution.task.context,
21264
+ instructions: definition.instructions,
21265
+ sharedState: execution.sharedState.readForRole(definition.role)
21266
+ }
21267
+ },
21268
+ capability: definition.capability,
21269
+ trace: execution.trace,
21270
+ toolRiskManifest: this.options.toolRiskManifest
21271
+ };
21272
+ const result = await this.runner.run(input);
21273
+ return normalizeAgentRunResult({
21274
+ ...result,
21275
+ metadata: {
21276
+ ...result.metadata,
21277
+ workflowRunId: execution.workflowRunId,
21278
+ nodeId: execution.node.id,
21279
+ agentDefinitionId: definition.id
21280
+ }
21281
+ });
21282
+ };
21283
+ findBlockedTool(definition, execution) {
21284
+ for (const toolName of execution.node.requiredTools ?? []) {
21285
+ const agentDecision = evaluateAgentToolPolicy({
21286
+ capability: definition.capability,
21287
+ toolName,
21288
+ manifest: this.options.toolRiskManifest
21289
+ });
21290
+ execution.eventLog.record("agent.tool.called", {
21291
+ workflowRunId: execution.workflowRunId,
21292
+ nodeId: execution.node.id,
21293
+ taskId: execution.task.id,
21294
+ role: execution.task.role,
21295
+ toolName,
21296
+ decision: agentDecision,
21297
+ trace: execution.trace
21298
+ });
21299
+ if (!agentDecision.allowed) {
21300
+ return agentDecision.reason ?? `Tool '${toolName}' is not allowed for agent.`;
21301
+ }
21302
+ const runtimeDecision = evaluateRuntimeToolPolicy(this.options.runtimePolicy, {
21303
+ toolName,
21304
+ risk: agentDecision.risk
21305
+ });
21306
+ if (!runtimeDecision.allowed) {
21307
+ return runtimeDecision.reason ?? `Tool '${toolName}' is blocked by runtime policy.`;
21308
+ }
21309
+ }
21310
+ return void 0;
21311
+ }
21312
+ };
21313
+ function createAgentDefinitionRegistry(definitions = []) {
21314
+ return new AgentDefinitionRegistry(definitions);
21315
+ }
21316
+ function createRuntimeAgentNodeExecutor(options) {
21317
+ return new RuntimeAgentNodeExecutor(options).execute;
21318
+ }
21319
+ function cloneDefinition(definition) {
21320
+ return structuredClone(definition);
21321
+ }
21322
+
20967
21323
  // src/runtime/workflow-registry.ts
20968
21324
  function cloneWorkflow(workflow) {
20969
21325
  return {
@@ -21297,14 +21653,22 @@ var WorkflowEngine = class {
21297
21653
  this.catalog = catalog;
21298
21654
  this.eventLog = eventLog;
21299
21655
  this.sharedState = options.sharedState ?? new InMemorySharedWorkspaceStore();
21300
- this.nodeExecutor = options.nodeExecutor;
21301
21656
  this.runtimePolicy = options.runtimePolicy;
21657
+ this.runtimeContext = options.runtimeContext;
21658
+ this.runtimeHostMode = options.runtimeHostMode ?? "local";
21659
+ this.nodeExecutor = options.nodeExecutor ?? (options.agentDefinitionRegistry ? createRuntimeAgentNodeExecutor({
21660
+ ...options.agentNodeExecutorOptions,
21661
+ registry: options.agentDefinitionRegistry,
21662
+ runtimePolicy: options.runtimePolicy
21663
+ }) : void 0);
21302
21664
  }
21303
21665
  catalog;
21304
21666
  eventLog;
21305
21667
  handlers = /* @__PURE__ */ new Map();
21306
21668
  sharedState;
21307
21669
  runtimePolicy;
21670
+ runtimeContext;
21671
+ runtimeHostMode;
21308
21672
  nodeExecutor;
21309
21673
  registerHandler(workflowId, handler) {
21310
21674
  if (!this.catalog.get(workflowId)) {
@@ -21319,6 +21683,7 @@ var WorkflowEngine = class {
21319
21683
  return this.catalog.createPlan(workflowId, input, this.eventLog);
21320
21684
  }
21321
21685
  async run(request) {
21686
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "workflow.run");
21322
21687
  const workflow = this.catalog.get(request.workflowId);
21323
21688
  if (!workflow) {
21324
21689
  throw new Error(`Unknown workflow: ${request.workflowId}`);
@@ -21438,7 +21803,14 @@ var AgentRuntime = class {
21438
21803
  this.model = options.model ?? options.providerConfig?.model ?? getDefaultModel(options.providerType);
21439
21804
  this.runtimeContext = options.runtimeContext ? createRuntimeRequestContext(options.runtimeContext) : void 0;
21440
21805
  this.runtimePolicy = mergeRuntimePolicy(this.runtimeContext?.policy, options.runtimePolicy);
21441
- this.workflowEngine = options.workflowEngine ?? createWorkflowEngine(void 0, this.eventLog, { runtimePolicy: this.runtimePolicy });
21806
+ this.runtimeHostMode = options.runtimeHostMode ?? "local";
21807
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "runtime.initialize");
21808
+ this.workflowEngine = options.workflowEngine ?? createWorkflowEngine(void 0, this.eventLog, {
21809
+ runtimePolicy: this.runtimePolicy,
21810
+ runtimeContext: this.runtimeContext,
21811
+ runtimeHostMode: this.runtimeHostMode,
21812
+ agentDefinitionRegistry: options.agentDefinitionRegistry
21813
+ });
21442
21814
  }
21443
21815
  options;
21444
21816
  providerRegistry;
@@ -21454,6 +21826,9 @@ var AgentRuntime = class {
21454
21826
  provider;
21455
21827
  runtimeContext;
21456
21828
  runtimePolicy;
21829
+ runtimeHostMode;
21830
+ requestTimestampsBySubject = /* @__PURE__ */ new Map();
21831
+ activeRuns = 0;
21457
21832
  async initialize() {
21458
21833
  const providerInjected = Boolean(this.options.provider);
21459
21834
  const provider = this.options.provider ?? await this.providerRegistry.createProvider(this.providerType, {
@@ -21502,10 +21877,12 @@ var AgentRuntime = class {
21502
21877
  },
21503
21878
  modes: listAgentModes(),
21504
21879
  context: this.runtimeContext,
21505
- policy: this.runtimePolicy
21880
+ policy: this.runtimePolicy,
21881
+ hostMode: this.runtimeHostMode
21506
21882
  };
21507
21883
  }
21508
21884
  createSession(options = {}) {
21885
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "session.create");
21509
21886
  const session = this.runtimeSessionStore.create({
21510
21887
  ...options,
21511
21888
  metadata: {
@@ -21529,6 +21906,27 @@ var AgentRuntime = class {
21529
21906
  listSessions() {
21530
21907
  return this.runtimeSessionStore.list();
21531
21908
  }
21909
+ cleanupRetention(options = {}) {
21910
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "retention.cleanup");
21911
+ const dryRun = options.dryRun ?? true;
21912
+ const cutoffs = createRetentionCutoffs(this.runtimePolicy, options.now);
21913
+ const expiredSessionIds = cutoffs.conversationBefore ? this.runtimeSessionStore.list().filter((session) => session.updatedAt < cutoffs.conversationBefore).map((session) => session.id) : [];
21914
+ const deletedSessionIds = dryRun ? [] : expiredSessionIds.filter((id) => this.runtimeSessionStore.delete(id));
21915
+ this.eventLog.record("retention.cleanup", {
21916
+ dryRun,
21917
+ cutoffs,
21918
+ expiredSessionIds,
21919
+ deletedSessionIds,
21920
+ tenantId: this.runtimeContext?.tenant?.id,
21921
+ runtimeApi: true
21922
+ });
21923
+ return {
21924
+ dryRun,
21925
+ cutoffs,
21926
+ expiredSessionIds,
21927
+ deletedSessionIds
21928
+ };
21929
+ }
21532
21930
  async runTurn(input) {
21533
21931
  const provider = this.provider;
21534
21932
  if (!provider) {
@@ -21539,6 +21937,12 @@ var AgentRuntime = class {
21539
21937
  throw new Error(`Runtime session not found: ${input.sessionId}`);
21540
21938
  }
21541
21939
  const effectiveSession = input.mode && input.mode !== session.mode ? { ...session, mode: input.mode } : session;
21940
+ assertRuntimeTurnWithinPolicy(this.runtimePolicy, {
21941
+ subject: "turn.run",
21942
+ currentTurns: countUserTurns(effectiveSession),
21943
+ tenantId: this.runtimeContext?.tenant?.id
21944
+ });
21945
+ const releaseRuntimeRequest = this.beginRuntimeRequest("turn.run");
21542
21946
  this.eventLog.record("turn.started", {
21543
21947
  sessionId: effectiveSession.id,
21544
21948
  provider: this.providerType,
@@ -21555,7 +21959,13 @@ var AgentRuntime = class {
21555
21959
  permissionPolicy: this.permissionPolicy,
21556
21960
  eventLog: this.eventLog
21557
21961
  });
21558
- assertRuntimeUsageWithinPolicy(this.runtimePolicy, result.usage);
21962
+ const estimatedCostUsd = this.estimateTurnCost(result);
21963
+ assertRuntimeUsageWithinPolicy(this.runtimePolicy, {
21964
+ ...result.usage,
21965
+ estimatedCostUsd,
21966
+ tenantId: this.runtimeContext?.tenant?.id,
21967
+ subject: "turn.run"
21968
+ });
21559
21969
  const updatedSession = this.runtimeSessionStore.update({
21560
21970
  ...effectiveSession,
21561
21971
  messages: [
@@ -21572,6 +21982,7 @@ var AgentRuntime = class {
21572
21982
  sessionId: updatedSession.id,
21573
21983
  inputTokens: result.usage.inputTokens,
21574
21984
  outputTokens: result.usage.outputTokens,
21985
+ estimatedCostUsd,
21575
21986
  model: result.model,
21576
21987
  runtimeApi: true
21577
21988
  });
@@ -21583,6 +21994,8 @@ var AgentRuntime = class {
21583
21994
  runtimeApi: true
21584
21995
  });
21585
21996
  throw error;
21997
+ } finally {
21998
+ releaseRuntimeRequest();
21586
21999
  }
21587
22000
  }
21588
22001
  async *streamTurn(input) {
@@ -21595,6 +22008,12 @@ var AgentRuntime = class {
21595
22008
  throw new Error(`Runtime session not found: ${input.sessionId}`);
21596
22009
  }
21597
22010
  const effectiveSession = input.mode && input.mode !== session.mode ? { ...session, mode: input.mode } : session;
22011
+ assertRuntimeTurnWithinPolicy(this.runtimePolicy, {
22012
+ subject: "turn.stream",
22013
+ currentTurns: countUserTurns(effectiveSession),
22014
+ tenantId: this.runtimeContext?.tenant?.id
22015
+ });
22016
+ const releaseRuntimeRequest = this.beginRuntimeRequest("turn.stream");
21598
22017
  const messages = [
21599
22018
  ...effectiveSession.messages,
21600
22019
  {
@@ -21644,7 +22063,13 @@ var AgentRuntime = class {
21644
22063
  model: input.options?.model ?? this.getModel(),
21645
22064
  mode: effectiveSession.mode
21646
22065
  };
21647
- assertRuntimeUsageWithinPolicy(this.runtimePolicy, result.usage);
22066
+ const estimatedCostUsd = this.estimateTurnCost(result);
22067
+ assertRuntimeUsageWithinPolicy(this.runtimePolicy, {
22068
+ ...result.usage,
22069
+ estimatedCostUsd,
22070
+ tenantId: this.runtimeContext?.tenant?.id,
22071
+ subject: "turn.stream"
22072
+ });
21648
22073
  const updatedSession = this.runtimeSessionStore.update({
21649
22074
  ...effectiveSession,
21650
22075
  messages: [
@@ -21663,6 +22088,7 @@ var AgentRuntime = class {
21663
22088
  sessionId: updatedSession.id,
21664
22089
  inputTokens: result.usage.inputTokens,
21665
22090
  outputTokens: result.usage.outputTokens,
22091
+ estimatedCostUsd,
21666
22092
  model: result.model,
21667
22093
  streaming: true,
21668
22094
  runtimeApi: true
@@ -21692,9 +22118,11 @@ var AgentRuntime = class {
21692
22118
  runtimeApi: true
21693
22119
  });
21694
22120
  }
22121
+ releaseRuntimeRequest();
21695
22122
  }
21696
22123
  }
21697
22124
  async executeTool(input) {
22125
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "tool.execute");
21698
22126
  const startedAt = performance.now();
21699
22127
  const session = input.sessionId ? this.getSession(input.sessionId) : void 0;
21700
22128
  if (input.sessionId && !session) {
@@ -21805,6 +22233,7 @@ var AgentRuntime = class {
21805
22233
  };
21806
22234
  }
21807
22235
  assertToolAllowed(mode, toolName, input) {
22236
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "tool.assertAllowed");
21808
22237
  const tool = this.toolRegistry.get(toolName);
21809
22238
  if (!tool) {
21810
22239
  this.eventLog.record("tool.blocked", {
@@ -21834,109 +22263,69 @@ var AgentRuntime = class {
21834
22263
  });
21835
22264
  return allowed;
21836
22265
  }
21837
- };
21838
- async function createAgentRuntime(options) {
21839
- const runtime = new AgentRuntime(options);
21840
- await runtime.initialize();
21841
- return runtime;
21842
- }
21843
-
21844
- // src/runtime/agent-runner.ts
21845
- var AgentRunner = class {
21846
- constructor(options = {}) {
21847
- this.options = options;
22266
+ beginRuntimeRequest(subject) {
22267
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, subject);
22268
+ this.assertWithinRateLimit(subject);
22269
+ this.assertWithinConcurrencyLimit(subject);
22270
+ this.activeRuns += 1;
22271
+ let released = false;
22272
+ return () => {
22273
+ if (released) return;
22274
+ released = true;
22275
+ this.activeRuns = Math.max(0, this.activeRuns - 1);
22276
+ };
21848
22277
  }
21849
- options;
21850
- async run(input) {
21851
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
21852
- const trace = input.trace ?? createAgentTraceContext({ taskId: input.task.id });
21853
- this.options.eventLog?.record("agent.started", {
21854
- taskId: input.task.id,
21855
- role: input.task.role,
21856
- trace
21857
- });
21858
- try {
21859
- const raw = await (this.options.executor ?? defaultExecutor)({
21860
- task: input.task,
21861
- capability: input.capability,
21862
- trace,
21863
- assertToolAllowed: (toolName) => {
21864
- const decision = evaluateAgentToolPolicy({
21865
- capability: input.capability,
21866
- toolName,
21867
- manifest: input.toolRiskManifest
21868
- });
21869
- this.options.eventLog?.record("agent.tool.called", {
21870
- taskId: input.task.id,
21871
- role: input.task.role,
21872
- toolName,
21873
- decision,
21874
- trace
21875
- });
21876
- if (!decision.allowed) {
21877
- throw new Error(decision.reason ?? `Tool '${toolName}' is not allowed.`);
21878
- }
21879
- }
21880
- });
21881
- const result = normalizeAgentRunResult({
21882
- id: `${input.task.id}-run-${Date.now().toString(36)}`,
21883
- taskId: input.task.id,
21884
- role: input.task.role,
21885
- success: raw.success ?? true,
21886
- output: raw.output,
21887
- turns: raw.turns,
21888
- toolsUsed: raw.toolsUsed,
21889
- usage: {
21890
- inputTokens: raw.inputTokens ?? 0,
21891
- outputTokens: raw.outputTokens ?? 0,
21892
- estimated: raw.inputTokens === void 0 || raw.outputTokens === void 0
21893
- },
21894
- startedAt,
21895
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
21896
- durationMs: Date.now() - Date.parse(startedAt),
21897
- error: raw.error,
21898
- metadata: { ...raw.metadata, trace }
21899
- });
21900
- this.options.eventLog?.record(result.success ? "agent.completed" : "agent.failed", {
21901
- taskId: input.task.id,
21902
- role: input.task.role,
21903
- agentRunId: result.id,
21904
- trace,
21905
- error: result.error
21906
- });
21907
- return result;
21908
- } catch (error) {
21909
- const message = error instanceof Error ? error.message : String(error);
21910
- const result = normalizeAgentRunResult({
21911
- id: `${input.task.id}-run-${Date.now().toString(36)}`,
21912
- taskId: input.task.id,
21913
- role: input.task.role,
21914
- success: false,
21915
- output: message,
21916
- startedAt,
21917
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
21918
- durationMs: Date.now() - Date.parse(startedAt),
21919
- error: message,
21920
- metadata: { trace }
22278
+ assertWithinRateLimit(subject) {
22279
+ const maxRequestsPerMinute = this.runtimePolicy?.rateLimit?.maxRequestsPerMinute;
22280
+ if (maxRequestsPerMinute === void 0) return;
22281
+ const now = Date.now();
22282
+ const windowStart = now - 6e4;
22283
+ const key = `${this.runtimeContext?.tenant?.id ?? "global"}:${subject}`;
22284
+ const recent = (this.requestTimestampsBySubject.get(key) ?? []).filter(
22285
+ (timestamp) => timestamp > windowStart
22286
+ );
22287
+ if (recent.length >= maxRequestsPerMinute) {
22288
+ this.requestTimestampsBySubject.set(key, recent);
22289
+ throw new RuntimePolicyViolation({
22290
+ code: "rate_limit_exceeded",
22291
+ subject,
22292
+ tenantId: this.runtimeContext?.tenant?.id,
22293
+ policyPath: "runtimePolicy.rateLimit.maxRequestsPerMinute",
22294
+ message: `Runtime policy rate limit exceeded: ${recent.length}/${maxRequestsPerMinute} requests per minute.`
21921
22295
  });
21922
- this.options.eventLog?.record("agent.failed", {
21923
- taskId: input.task.id,
21924
- role: input.task.role,
21925
- agentRunId: result.id,
21926
- trace,
21927
- error: message
22296
+ }
22297
+ recent.push(now);
22298
+ this.requestTimestampsBySubject.set(key, recent);
22299
+ }
22300
+ assertWithinConcurrencyLimit(subject) {
22301
+ const maxConcurrentRuns = this.runtimePolicy?.rateLimit?.maxConcurrentRuns;
22302
+ if (maxConcurrentRuns === void 0) return;
22303
+ if (this.activeRuns >= maxConcurrentRuns) {
22304
+ throw new RuntimePolicyViolation({
22305
+ code: "concurrency_limit_exceeded",
22306
+ subject,
22307
+ tenantId: this.runtimeContext?.tenant?.id,
22308
+ policyPath: "runtimePolicy.rateLimit.maxConcurrentRuns",
22309
+ message: `Runtime policy concurrency limit exceeded: ${this.activeRuns}/${maxConcurrentRuns} active runs.`
21928
22310
  });
21929
- return result;
21930
22311
  }
21931
22312
  }
22313
+ estimateTurnCost(result) {
22314
+ return estimateCost(
22315
+ result.model,
22316
+ result.usage.inputTokens,
22317
+ result.usage.outputTokens,
22318
+ this.providerType
22319
+ ).totalCost;
22320
+ }
21932
22321
  };
21933
- function createAgentRunner(options) {
21934
- return new AgentRunner(options);
22322
+ function countUserTurns(session) {
22323
+ return session.messages.filter((message) => message.role === "user").length;
21935
22324
  }
21936
- async function defaultExecutor(context) {
21937
- return {
21938
- output: `Agent ${context.capability.role} accepted task '${context.task.objective}'.`
21939
- };
22325
+ async function createAgentRuntime(options) {
22326
+ const runtime = new AgentRuntime(options);
22327
+ await runtime.initialize();
22328
+ return runtime;
21940
22329
  }
21941
22330
 
21942
22331
  // src/runtime/tool-calling-turn-runner.ts
@@ -22146,6 +22535,314 @@ function createRuntimeHttpServer(runtime, options = {}) {
22146
22535
  }
22147
22536
  });
22148
22537
  }
22538
+ function createSessionId2() {
22539
+ return `rt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
22540
+ }
22541
+ function parseJson(value, fallback) {
22542
+ if (value === null || value === void 0) return fallback;
22543
+ if (typeof value === "string") return JSON.parse(value);
22544
+ return value;
22545
+ }
22546
+ function dateToIso(value) {
22547
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
22548
+ }
22549
+ function rowToSession(row) {
22550
+ return {
22551
+ id: row.id,
22552
+ createdAt: dateToIso(row.created_at),
22553
+ updatedAt: dateToIso(row.updated_at),
22554
+ mode: row.mode,
22555
+ messages: parseJson(row.messages, []),
22556
+ instructions: row.instructions ?? void 0,
22557
+ metadata: parseJson(row.metadata, {})
22558
+ };
22559
+ }
22560
+ function rowToEvent(row) {
22561
+ return {
22562
+ id: row.id,
22563
+ type: row.type,
22564
+ timestamp: dateToIso(row.timestamp),
22565
+ data: parseJson(row.data, {})
22566
+ };
22567
+ }
22568
+ function requireTenantId(options) {
22569
+ if (!options.tenantId) {
22570
+ throw new Error("Postgres hosted runtime stores require tenantId.");
22571
+ }
22572
+ return options.tenantId;
22573
+ }
22574
+ var AsyncPostgresRuntimeSessionStore = class {
22575
+ constructor(client, options) {
22576
+ this.client = client;
22577
+ this.tenantId = requireTenantId(options);
22578
+ }
22579
+ client;
22580
+ tenantId;
22581
+ async create(options = {}) {
22582
+ const now = (/* @__PURE__ */ new Date()).toISOString();
22583
+ const session = {
22584
+ id: options.id ?? createSessionId2(),
22585
+ createdAt: now,
22586
+ updatedAt: now,
22587
+ mode: options.mode ?? "ask",
22588
+ messages: options.messages ? options.messages.map((message) => ({ ...message })) : [],
22589
+ instructions: options.instructions,
22590
+ metadata: { ...options.metadata, tenantId: this.tenantId }
22591
+ };
22592
+ const result = await this.client.query(
22593
+ `insert into coco_runtime_sessions
22594
+ (id, tenant_id, created_at, updated_at, mode, messages, instructions, metadata)
22595
+ values ($1, $2, $3, $4, $5, $6::jsonb, $7, $8::jsonb)
22596
+ on conflict (id) do update set
22597
+ updated_at = excluded.updated_at,
22598
+ mode = excluded.mode,
22599
+ messages = excluded.messages,
22600
+ instructions = excluded.instructions,
22601
+ metadata = excluded.metadata
22602
+ where coco_runtime_sessions.tenant_id = excluded.tenant_id`,
22603
+ [
22604
+ session.id,
22605
+ this.tenantId,
22606
+ session.createdAt,
22607
+ session.updatedAt,
22608
+ session.mode,
22609
+ JSON.stringify(session.messages),
22610
+ session.instructions ?? null,
22611
+ JSON.stringify(session.metadata)
22612
+ ]
22613
+ );
22614
+ if (result.rowCount === 0) {
22615
+ throw new Error(`Runtime session id is already owned by another tenant: ${session.id}`);
22616
+ }
22617
+ return structuredClone(session);
22618
+ }
22619
+ async get(id) {
22620
+ const result = await this.client.query(
22621
+ `select * from coco_runtime_sessions
22622
+ where id = $1 and tenant_id = $2
22623
+ limit 1`,
22624
+ [id, this.tenantId]
22625
+ );
22626
+ const row = result.rows[0];
22627
+ return row ? rowToSession(row) : void 0;
22628
+ }
22629
+ async update(session) {
22630
+ const updated = {
22631
+ ...session,
22632
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
22633
+ messages: session.messages.map((message) => ({ ...message })),
22634
+ metadata: { ...session.metadata, tenantId: this.tenantId }
22635
+ };
22636
+ const result = await this.client.query(
22637
+ `update coco_runtime_sessions
22638
+ set updated_at = $3, mode = $4, messages = $5::jsonb, instructions = $6, metadata = $7::jsonb
22639
+ where id = $1 and tenant_id = $2`,
22640
+ [
22641
+ updated.id,
22642
+ this.tenantId,
22643
+ updated.updatedAt,
22644
+ updated.mode,
22645
+ JSON.stringify(updated.messages),
22646
+ updated.instructions ?? null,
22647
+ JSON.stringify(updated.metadata)
22648
+ ]
22649
+ );
22650
+ if (result.rowCount === 0) {
22651
+ throw new Error(`Runtime session not found for tenant ${this.tenantId}: ${updated.id}`);
22652
+ }
22653
+ return structuredClone(updated);
22654
+ }
22655
+ async list() {
22656
+ const result = await this.client.query(
22657
+ `select * from coco_runtime_sessions
22658
+ where tenant_id = $1
22659
+ order by updated_at desc`,
22660
+ [this.tenantId]
22661
+ );
22662
+ return result.rows.map(rowToSession);
22663
+ }
22664
+ async delete(id) {
22665
+ const result = await this.client.query(
22666
+ "delete from coco_runtime_sessions where id = $1 and tenant_id = $2",
22667
+ [id, this.tenantId]
22668
+ );
22669
+ return (result.rowCount ?? 0) > 0;
22670
+ }
22671
+ };
22672
+ var AsyncPostgresEventLog = class {
22673
+ constructor(client, options) {
22674
+ this.client = client;
22675
+ this.tenantId = requireTenantId(options);
22676
+ }
22677
+ client;
22678
+ tenantId;
22679
+ async record(type, data = {}) {
22680
+ const event = {
22681
+ id: randomUUID(),
22682
+ type,
22683
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22684
+ data: { ...data, tenantId: this.tenantId }
22685
+ };
22686
+ await this.client.query(
22687
+ `insert into coco_runtime_events (id, tenant_id, type, timestamp, data)
22688
+ values ($1, $2, $3, $4, $5::jsonb)`,
22689
+ [event.id, this.tenantId, event.type, event.timestamp, JSON.stringify(event.data)]
22690
+ );
22691
+ return event;
22692
+ }
22693
+ async list() {
22694
+ return listPostgresRuntimeEvents(this.client, { tenantId: this.tenantId });
22695
+ }
22696
+ async count() {
22697
+ const result = await this.client.query(
22698
+ "select count(*)::int as count from coco_runtime_events where tenant_id = $1",
22699
+ [this.tenantId]
22700
+ );
22701
+ return Number(result.rows[0]?.count ?? 0);
22702
+ }
22703
+ async clear() {
22704
+ await this.client.query("delete from coco_runtime_events where tenant_id = $1", [
22705
+ this.tenantId
22706
+ ]);
22707
+ }
22708
+ };
22709
+ var PostgresRuntimeAuditStore = class {
22710
+ constructor(client, options) {
22711
+ this.client = client;
22712
+ this.tenantId = requireTenantId(options);
22713
+ }
22714
+ client;
22715
+ tenantId;
22716
+ async record(input) {
22717
+ const record = {
22718
+ id: randomUUID(),
22719
+ tenantId: this.tenantId,
22720
+ type: input.type,
22721
+ subject: input.subject,
22722
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22723
+ data: { ...input.data }
22724
+ };
22725
+ await this.client.query(
22726
+ `insert into coco_runtime_audit_events
22727
+ (id, tenant_id, type, subject, timestamp, data)
22728
+ values ($1, $2, $3, $4, $5, $6::jsonb)`,
22729
+ [
22730
+ record.id,
22731
+ record.tenantId,
22732
+ record.type,
22733
+ record.subject ?? null,
22734
+ record.timestamp,
22735
+ JSON.stringify(record.data)
22736
+ ]
22737
+ );
22738
+ return record;
22739
+ }
22740
+ async list() {
22741
+ const result = await this.client.query(
22742
+ `select * from coco_runtime_audit_events
22743
+ where tenant_id = $1
22744
+ order by timestamp asc`,
22745
+ [this.tenantId]
22746
+ );
22747
+ return result.rows.map((row) => ({
22748
+ id: row.id,
22749
+ tenantId: row.tenant_id,
22750
+ type: row.type,
22751
+ subject: row.subject ?? void 0,
22752
+ timestamp: dateToIso(row.timestamp),
22753
+ data: parseJson(row.data, {})
22754
+ }));
22755
+ }
22756
+ };
22757
+ function createAsyncPostgresRuntimeSessionStore(client, options) {
22758
+ return new AsyncPostgresRuntimeSessionStore(client, options);
22759
+ }
22760
+ function createAsyncPostgresEventLog(client, options) {
22761
+ return new AsyncPostgresEventLog(client, options);
22762
+ }
22763
+ function createPostgresRuntimeAuditStore(client, options) {
22764
+ return new PostgresRuntimeAuditStore(client, options);
22765
+ }
22766
+ async function listPostgresRuntimeEvents(client, options = {}) {
22767
+ const result = await client.query(
22768
+ `select * from coco_runtime_events
22769
+ where ($1::text is null or tenant_id = $1)
22770
+ and ($2::text is null or data->>'sessionId' = $2)
22771
+ order by timestamp asc`,
22772
+ [options.tenantId ?? null, options.sessionId ?? null]
22773
+ );
22774
+ return result.rows.map(rowToEvent);
22775
+ }
22776
+
22777
+ // src/runtime/tenant-scope.ts
22778
+ var TenantScopedRuntimeSessionStore = class {
22779
+ constructor(inner, options) {
22780
+ this.inner = inner;
22781
+ this.options = options;
22782
+ }
22783
+ inner;
22784
+ options;
22785
+ create(options = {}) {
22786
+ return this.inner.create({
22787
+ ...options,
22788
+ metadata: this.withTenant(options.metadata)
22789
+ });
22790
+ }
22791
+ get(id) {
22792
+ const session = this.inner.get(id);
22793
+ return session && this.belongsToTenant(session) ? session : void 0;
22794
+ }
22795
+ update(session) {
22796
+ if (!this.belongsToTenant(session)) {
22797
+ throw new Error(
22798
+ `Runtime session ${session.id} does not belong to tenant ${this.options.tenantId}.`
22799
+ );
22800
+ }
22801
+ return this.inner.update({
22802
+ ...session,
22803
+ metadata: this.withTenant(session.metadata)
22804
+ });
22805
+ }
22806
+ list() {
22807
+ return this.inner.list().filter((session) => this.belongsToTenant(session));
22808
+ }
22809
+ delete(id) {
22810
+ if (!this.get(id)) return false;
22811
+ return this.inner.delete(id);
22812
+ }
22813
+ withTenant(metadata) {
22814
+ return { ...metadata, tenantId: this.options.tenantId };
22815
+ }
22816
+ belongsToTenant(session) {
22817
+ return session.metadata["tenantId"] === this.options.tenantId;
22818
+ }
22819
+ };
22820
+ var TenantScopedEventLog = class {
22821
+ constructor(inner, options) {
22822
+ this.inner = inner;
22823
+ this.options = options;
22824
+ }
22825
+ inner;
22826
+ options;
22827
+ record(type, data = {}) {
22828
+ return this.inner.record(type, { ...data, tenantId: this.options.tenantId });
22829
+ }
22830
+ list() {
22831
+ return this.inner.list().filter((event) => event.data["tenantId"] === this.options.tenantId);
22832
+ }
22833
+ count() {
22834
+ return this.list().length;
22835
+ }
22836
+ clear() {
22837
+ throw new Error("Tenant-scoped event logs do not support partial clear.");
22838
+ }
22839
+ };
22840
+ function createTenantScopedRuntimeSessionStore(inner, tenantId) {
22841
+ return new TenantScopedRuntimeSessionStore(inner, { tenantId });
22842
+ }
22843
+ function createTenantScopedEventLog(inner, tenantId) {
22844
+ return new TenantScopedEventLog(inner, { tenantId });
22845
+ }
22149
22846
 
22150
22847
  // src/runtime/extension-manifests.ts
22151
22848
  function createMcpToolPolicy(server, tool, risk, allowedModes = ["ask", "plan", "build", "debug", "review", "architect"]) {
@@ -22239,7 +22936,7 @@ function runGuardrails(stage, content, config = {}) {
22239
22936
  findings.push({
22240
22937
  id,
22241
22938
  stage,
22242
- severity: "warning",
22939
+ severity: actionToSeverity(config.promptInjectionAction ?? "warn"),
22243
22940
  message: `Potential prompt-injection pattern detected: ${id}`
22244
22941
  });
22245
22942
  }
@@ -22262,6 +22959,26 @@ function runGuardrails(stage, content, config = {}) {
22262
22959
  findings
22263
22960
  };
22264
22961
  }
22962
+ async function runGuardrailPipeline(steps, config = {}) {
22963
+ const outputs = [];
22964
+ const findings = [];
22965
+ for (const step of steps) {
22966
+ const effectiveConfig = { ...config, ...step.config };
22967
+ const result = runGuardrails(step.stage, step.content, effectiveConfig);
22968
+ const policyFindings = await effectiveConfig.policyProvider?.evaluate({
22969
+ stage: step.stage,
22970
+ content: result.content,
22971
+ findings: result.findings
22972
+ }) ?? [];
22973
+ outputs.push({ stage: step.stage, content: result.content });
22974
+ findings.push(...result.findings, ...policyFindings);
22975
+ }
22976
+ return {
22977
+ allowed: !findings.some((finding) => finding.severity === "blocked"),
22978
+ outputs,
22979
+ findings
22980
+ };
22981
+ }
22265
22982
  function validateStructuredOutput(output, schema) {
22266
22983
  if (!schema) return [];
22267
22984
  const result = schema.safeParse(output);
@@ -22275,6 +22992,16 @@ function validateStructuredOutput(output, schema) {
22275
22992
  }
22276
22993
  ];
22277
22994
  }
22995
+ function actionToSeverity(action) {
22996
+ switch (action) {
22997
+ case "allow":
22998
+ return "info";
22999
+ case "warn":
23000
+ return "warning";
23001
+ case "block":
23002
+ return "blocked";
23003
+ }
23004
+ }
22278
23005
 
22279
23006
  // src/runtime/blueprints.ts
22280
23007
  function mapActionModeToRuntimeMode(mode) {
@@ -22372,7 +23099,7 @@ var InMemoryKnowledgeRetriever = class {
22372
23099
  const limit = options.limit ?? 5;
22373
23100
  const minScore = options.minScore ?? 0;
22374
23101
  const tenantId = tenantIdFromRetrievalOptions(options);
22375
- return this.documents.filter((document) => sourceMatchesTenant(document.metadata, tenantId, options.dataBoundary)).map((document) => ({
23102
+ return this.documents.filter((document) => sourceAccessible(document, { ...options, tenantId })).map((document) => ({
22376
23103
  ...document,
22377
23104
  score: scoreDocument(document, terms)
22378
23105
  })).filter((source) => source.score >= minScore && source.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
@@ -22397,7 +23124,24 @@ var SimpleTextChunker = class {
22397
23124
  title: document.title,
22398
23125
  content,
22399
23126
  url: document.url,
22400
- metadata: { ...document.metadata, chunkIndex: index }
23127
+ tenantId: document.tenantId,
23128
+ sourceId: document.sourceId,
23129
+ acl: document.acl,
23130
+ classification: document.classification,
23131
+ version: document.version,
23132
+ updatedAt: document.updatedAt,
23133
+ deletedAt: document.deletedAt,
23134
+ metadata: {
23135
+ ...document.metadata,
23136
+ tenantId: document.tenantId ?? document.metadata?.["tenantId"],
23137
+ sourceId: document.sourceId ?? document.metadata?.["sourceId"],
23138
+ acl: document.acl ?? document.metadata?.["acl"],
23139
+ classification: document.classification ?? document.metadata?.["classification"],
23140
+ version: document.version ?? document.metadata?.["version"],
23141
+ updatedAt: document.updatedAt ?? document.metadata?.["updatedAt"],
23142
+ deletedAt: document.deletedAt ?? document.metadata?.["deletedAt"],
23143
+ chunkIndex: index
23144
+ }
22401
23145
  });
22402
23146
  index++;
22403
23147
  offset += maxChars - overlapChars;
@@ -22416,16 +23160,104 @@ var InMemoryVectorStore = class {
22416
23160
  const limit = options.limit ?? 5;
22417
23161
  const minScore = options.minScore ?? 0;
22418
23162
  const tenantId = tenantIdFromRetrievalOptions(options);
22419
- return this.chunks.filter((chunk2) => sourceMatchesTenant(chunk2.metadata, tenantId, options.dataBoundary)).map((chunk2) => ({
23163
+ return this.chunks.filter((chunk2) => sourceAccessible(chunk2, { ...options, tenantId })).map((chunk2) => ({
22420
23164
  id: chunk2.id,
22421
23165
  title: chunk2.title,
22422
23166
  content: chunk2.content,
22423
23167
  url: chunk2.url,
22424
23168
  score: cosineSimilarity(embedding, chunk2.embedding),
22425
- metadata: { ...chunk2.metadata, documentId: chunk2.documentId }
23169
+ metadata: {
23170
+ ...chunk2.metadata,
23171
+ tenantId: chunk2.tenantId ?? chunk2.metadata?.["tenantId"],
23172
+ sourceId: chunk2.sourceId ?? chunk2.metadata?.["sourceId"],
23173
+ acl: chunk2.acl ?? chunk2.metadata?.["acl"],
23174
+ classification: chunk2.classification ?? chunk2.metadata?.["classification"],
23175
+ version: chunk2.version ?? chunk2.metadata?.["version"],
23176
+ updatedAt: chunk2.updatedAt ?? chunk2.metadata?.["updatedAt"],
23177
+ deletedAt: chunk2.deletedAt ?? chunk2.metadata?.["deletedAt"],
23178
+ documentId: chunk2.documentId
23179
+ }
22426
23180
  })).filter((source) => source.score >= minScore).sort((a, b) => b.score - a.score).slice(0, limit);
22427
23181
  }
22428
23182
  };
23183
+ function createDocumentAccessPolicy(input) {
23184
+ return {
23185
+ canAccess({ document, options }) {
23186
+ const tenantId = options.tenantId ?? input.tenantId;
23187
+ const userId = options.userId ?? input.userId;
23188
+ const roles = options.roles ?? input.roles ?? [];
23189
+ const groups = options.groups ?? input.groups ?? [];
23190
+ const metadata = document.metadata ?? {};
23191
+ if (document.deletedAt ?? metadata["deletedAt"]) return false;
23192
+ const documentTenantId = document.tenantId ?? stringMetadata(metadata, "tenantId");
23193
+ const visibility = stringMetadata(metadata, "visibility");
23194
+ if (tenantId && documentTenantId !== tenantId && visibility !== "global") return false;
23195
+ const acl = document.acl ?? metadata["acl"];
23196
+ if (!acl) return input.requireAcl !== true;
23197
+ if (acl.public === true) return true;
23198
+ if (userId && acl.userIds?.includes(userId)) return true;
23199
+ if (acl.roles?.some((role) => roles.includes(role))) return true;
23200
+ if (acl.groups?.some((group) => groups.includes(group))) return true;
23201
+ return false;
23202
+ }
23203
+ };
23204
+ }
23205
+ function createRagIngestionJob(id, pipeline) {
23206
+ const job = {
23207
+ id,
23208
+ status: "pending",
23209
+ async run() {
23210
+ job.status = "running";
23211
+ job.startedAt = (/* @__PURE__ */ new Date()).toISOString();
23212
+ try {
23213
+ job.result = await pipeline.ingest();
23214
+ job.status = "completed";
23215
+ } catch (error) {
23216
+ job.status = "failed";
23217
+ job.error = error instanceof Error ? error.message : String(error);
23218
+ } finally {
23219
+ job.completedAt = (/* @__PURE__ */ new Date()).toISOString();
23220
+ }
23221
+ return { ...job };
23222
+ }
23223
+ };
23224
+ return job;
23225
+ }
23226
+ function verifyCitations(citations, sources) {
23227
+ const byId = new Map(sources.map((source) => [source.id, source]));
23228
+ const unsupportedCitations = [];
23229
+ const missingSourceIds = [];
23230
+ for (const citation of citations) {
23231
+ const source = byId.get(citation.sourceId);
23232
+ if (!source) {
23233
+ missingSourceIds.push(citation.sourceId);
23234
+ unsupportedCitations.push(citation);
23235
+ }
23236
+ }
23237
+ return {
23238
+ valid: unsupportedCitations.length === 0,
23239
+ unsupportedCitations,
23240
+ missingSourceIds
23241
+ };
23242
+ }
23243
+ function evaluateGroundedness(answer, sources) {
23244
+ const answerTerms = new Set(tokenize(answer));
23245
+ const sourceTerms = new Set(sources.flatMap((source) => tokenize(source.content)));
23246
+ const overlap = [...answerTerms].filter((term) => sourceTerms.has(term)).length;
23247
+ const score = answerTerms.size === 0 ? 0 : overlap / answerTerms.size;
23248
+ const injection = /ignore|override|bypass|reveal|exfiltrate/i.test(
23249
+ sources.map((source) => source.content).join("\n")
23250
+ );
23251
+ const reasons = [];
23252
+ if (score < 0.2) reasons.push("Answer has low lexical support in retrieved sources.");
23253
+ if (injection) reasons.push("Retrieved sources contain prompt-injection indicators.");
23254
+ return {
23255
+ grounded: score >= 0.2 && !injection,
23256
+ score,
23257
+ blocked: injection,
23258
+ reasons
23259
+ };
23260
+ }
22429
23261
  function createInMemoryKnowledgeRetriever(documents) {
22430
23262
  return new InMemoryKnowledgeRetriever(documents);
22431
23263
  }
@@ -22492,10 +23324,15 @@ function mergeRetrievalOptionsWithRuntimeContext(options, runtimeContext) {
22492
23324
  function tenantIdFromRetrievalOptions(options) {
22493
23325
  return options.tenantId ?? options.runtimeContext?.tenant?.id;
22494
23326
  }
22495
- function sourceMatchesTenant(metadata, tenantId, dataBoundary) {
22496
- if (!tenantId || dataBoundary?.allowCrossTenantMemory === true) return true;
22497
- const sourceTenantId = metadata?.["tenantId"];
22498
- return sourceTenantId === tenantId || metadata?.["visibility"] === "global";
23327
+ function sourceAccessible(document, options) {
23328
+ if (options.documentAccessPolicy) {
23329
+ return options.documentAccessPolicy.canAccess({ document, options });
23330
+ }
23331
+ const metadata = document.metadata ?? {};
23332
+ if (document.deletedAt ?? metadata["deletedAt"]) return false;
23333
+ if (!options.tenantId || options.dataBoundary?.allowCrossTenantMemory === true) return true;
23334
+ const sourceTenantId = document.tenantId ?? metadata["tenantId"];
23335
+ return sourceTenantId === options.tenantId || metadata["visibility"] === "global";
22499
23336
  }
22500
23337
  async function retrieveFromVectorPipeline(query, options, retrievalOptions) {
22501
23338
  if (!options.embeddingProvider || !options.vectorStore) return [];
@@ -22503,6 +23340,10 @@ async function retrieveFromVectorPipeline(query, options, retrievalOptions) {
22503
23340
  if (!embedding) return [];
22504
23341
  return options.vectorStore.search(embedding, retrievalOptions);
22505
23342
  }
23343
+ function stringMetadata(metadata, key) {
23344
+ const value = metadata[key];
23345
+ return typeof value === "string" ? value : void 0;
23346
+ }
22506
23347
  function cosineSimilarity(a, b) {
22507
23348
  if (a.length === 0 || b.length === 0 || a.length !== b.length) return 0;
22508
23349
  let dot = 0;
@@ -22627,6 +23468,187 @@ var RuntimeToolExecutor = class {
22627
23468
  function createRuntimeToolExecutor(options) {
22628
23469
  return new RuntimeToolExecutor(options);
22629
23470
  }
23471
+ var InMemoryTraceExporter = class {
23472
+ spans = [];
23473
+ async export(span) {
23474
+ this.spans.push(structuredClone(span));
23475
+ }
23476
+ list() {
23477
+ return this.spans.map((span) => structuredClone(span));
23478
+ }
23479
+ clear() {
23480
+ this.spans = [];
23481
+ }
23482
+ };
23483
+ var FileTraceExporter = class {
23484
+ constructor(filePath) {
23485
+ this.filePath = filePath;
23486
+ mkdirSync(dirname(filePath), { recursive: true });
23487
+ }
23488
+ filePath;
23489
+ async export(span) {
23490
+ appendFileSync(this.filePath, JSON.stringify(span) + "\n", "utf-8");
23491
+ }
23492
+ list() {
23493
+ try {
23494
+ return readFileSync(this.filePath, "utf-8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
23495
+ } catch {
23496
+ return [];
23497
+ }
23498
+ }
23499
+ clear() {
23500
+ writeFileSync(this.filePath, "", "utf-8");
23501
+ }
23502
+ };
23503
+ var OpenTelemetryTraceExporter = class {
23504
+ spans = [];
23505
+ async export(span) {
23506
+ this.spans.push(structuredClone(span));
23507
+ }
23508
+ toOtlpJson() {
23509
+ return {
23510
+ resourceSpans: [
23511
+ {
23512
+ scopeSpans: [
23513
+ {
23514
+ spans: this.spans.map((span) => ({
23515
+ traceId: span.traceId,
23516
+ spanId: span.spanId,
23517
+ parentSpanId: span.parentSpanId,
23518
+ name: span.name,
23519
+ kind: span.kind,
23520
+ startTimeUnixNano: Date.parse(span.timestamp) * 1e6,
23521
+ endTimeUnixNano: Date.parse(span.timestamp) * 1e6,
23522
+ attributes: Object.entries(span.attributes).map(([key, value]) => ({
23523
+ key,
23524
+ value: { stringValue: JSON.stringify(value) }
23525
+ }))
23526
+ }))
23527
+ }
23528
+ ]
23529
+ }
23530
+ ]
23531
+ };
23532
+ }
23533
+ };
23534
+ async function exportRuntimeEventsAsSpans(events, exporter) {
23535
+ const spans = events.map(eventToSpan);
23536
+ for (const span of spans) {
23537
+ await exporter.export(span);
23538
+ }
23539
+ await exporter.flush?.();
23540
+ return spans;
23541
+ }
23542
+ function eventToSpan(event) {
23543
+ const trace = isRecord(event.data["trace"]) ? event.data["trace"] : {};
23544
+ const traceId = stringValue(trace["traceId"]) ?? stringValue(event.data["traceId"]) ?? event.id;
23545
+ const spanId = stringValue(trace["spanId"]) ?? stringValue(event.data["spanId"]) ?? event.id;
23546
+ return {
23547
+ id: event.id,
23548
+ traceId,
23549
+ spanId,
23550
+ parentSpanId: stringValue(trace["parentSpanId"]) ?? stringValue(event.data["parentSpanId"]),
23551
+ kind: inferSpanKind(event),
23552
+ name: event.type,
23553
+ timestamp: event.timestamp,
23554
+ attributes: redactTraceAttributes(event.data)
23555
+ };
23556
+ }
23557
+ function collectRuntimeMetrics(events) {
23558
+ const snapshot = {
23559
+ events: events.length,
23560
+ byKind: {
23561
+ workflow: 0,
23562
+ agent: 0,
23563
+ llm: 0,
23564
+ tool: 0,
23565
+ rag: 0,
23566
+ gate: 0,
23567
+ handoff: 0,
23568
+ state: 0,
23569
+ runtime: 0
23570
+ },
23571
+ tokens: { input: 0, output: 0 },
23572
+ estimatedCostUsd: 0,
23573
+ retries: 0,
23574
+ policyBlocks: 0,
23575
+ errorsByClass: {},
23576
+ toolsUsed: {},
23577
+ gatesFailed: 0,
23578
+ tenantUsage: {}
23579
+ };
23580
+ for (const event of events) {
23581
+ const kind = inferSpanKind(event);
23582
+ snapshot.byKind[kind] += 1;
23583
+ const inputTokens = numberValue(event.data["inputTokens"]);
23584
+ const outputTokens = numberValue(event.data["outputTokens"]);
23585
+ const estimatedCostUsd = numberValue(event.data["estimatedCostUsd"]);
23586
+ snapshot.tokens.input += inputTokens;
23587
+ snapshot.tokens.output += outputTokens;
23588
+ snapshot.estimatedCostUsd += estimatedCostUsd;
23589
+ if (event.data["attempt"] && numberValue(event.data["attempt"]) > 1) snapshot.retries += 1;
23590
+ if (event.type === "tool.blocked" || event.data["runtimePolicyBlocked"] === true) {
23591
+ snapshot.policyBlocks += 1;
23592
+ }
23593
+ if (event.type.endsWith(".failed") || event.type === "error") {
23594
+ const key = stringValue(event.data["error"]) ?? event.type;
23595
+ snapshot.errorsByClass[key] = (snapshot.errorsByClass[key] ?? 0) + 1;
23596
+ }
23597
+ const tool = stringValue(event.data["tool"]) ?? stringValue(event.data["toolName"]);
23598
+ if (tool) snapshot.toolsUsed[tool] = (snapshot.toolsUsed[tool] ?? 0) + 1;
23599
+ if (event.type === "workflow.gate.failed") snapshot.gatesFailed += 1;
23600
+ const tenantId = stringValue(event.data["tenantId"]);
23601
+ if (tenantId) {
23602
+ snapshot.tenantUsage[tenantId] ??= {
23603
+ events: 0,
23604
+ inputTokens: 0,
23605
+ outputTokens: 0,
23606
+ estimatedCostUsd: 0
23607
+ };
23608
+ snapshot.tenantUsage[tenantId].events += 1;
23609
+ snapshot.tenantUsage[tenantId].inputTokens += inputTokens;
23610
+ snapshot.tenantUsage[tenantId].outputTokens += outputTokens;
23611
+ snapshot.tenantUsage[tenantId].estimatedCostUsd += estimatedCostUsd;
23612
+ }
23613
+ }
23614
+ return snapshot;
23615
+ }
23616
+ function redactTraceAttributes(input) {
23617
+ return redactUnknown(input);
23618
+ }
23619
+ function inferSpanKind(event) {
23620
+ if (event.type.startsWith("workflow.gate")) return "gate";
23621
+ if (event.type.startsWith("workflow.")) return "workflow";
23622
+ if (event.type.startsWith("agent.handoff")) return "handoff";
23623
+ if (event.type.startsWith("agent.")) return "agent";
23624
+ if (event.type.startsWith("tool.") || event.type === "agent.tool.called") return "tool";
23625
+ if (event.type.startsWith("shared_state.") || event.type.startsWith("checkpoint.")) {
23626
+ return "state";
23627
+ }
23628
+ if (event.type.startsWith("turn.")) return "llm";
23629
+ return "runtime";
23630
+ }
23631
+ function redactUnknown(value) {
23632
+ if (typeof value === "string") {
23633
+ return redactSecrets(value, { enabled: true }).content;
23634
+ }
23635
+ if (Array.isArray(value)) return value.map(redactUnknown);
23636
+ if (isRecord(value)) {
23637
+ return Object.fromEntries(
23638
+ Object.entries(value).map(([key, nested]) => [key, redactUnknown(nested)])
23639
+ );
23640
+ }
23641
+ return value;
23642
+ }
23643
+ function isRecord(value) {
23644
+ return value !== null && typeof value === "object" && !Array.isArray(value);
23645
+ }
23646
+ function stringValue(value) {
23647
+ return typeof value === "string" ? value : void 0;
23648
+ }
23649
+ function numberValue(value) {
23650
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
23651
+ }
22630
23652
  var PUBLIC_WEB_TOOLS = /* @__PURE__ */ new Set(["search_public_docs", "list_public_services"]);
22631
23653
  var CUSTOMER_SUPPORT_TOOLS = /* @__PURE__ */ new Set([
22632
23654
  "search_public_docs",
@@ -34597,8 +35619,6 @@ var AGENT_PRESETS = [
34597
35619
  internalOpsAssistantPreset,
34598
35620
  codingAgentPreset
34599
35621
  ];
34600
-
34601
- // src/adapters/index.ts
34602
35622
  function createHttpAssistantAdapter(runtime) {
34603
35623
  return {
34604
35624
  createSession(metadata = {}) {
@@ -34660,12 +35680,188 @@ function createWhatsAppAssistantAdapter(runtime) {
34660
35680
  }
34661
35681
  };
34662
35682
  }
35683
+ var InMemoryChannelSessionMapper = class {
35684
+ sessions = /* @__PURE__ */ new Map();
35685
+ getSessionId(input) {
35686
+ return this.sessions.get(sessionKey(input));
35687
+ }
35688
+ setSessionId(input) {
35689
+ this.sessions.set(sessionKey(input), input.sessionId);
35690
+ }
35691
+ };
35692
+ var WhatsAppCloudAdapter = class {
35693
+ constructor(options) {
35694
+ this.options = options;
35695
+ this.mapper = options.sessionMapper ?? new InMemoryChannelSessionMapper();
35696
+ }
35697
+ options;
35698
+ mapper;
35699
+ processedMessageIds = /* @__PURE__ */ new Set();
35700
+ optOutSenders = /* @__PURE__ */ new Set();
35701
+ senderTimestamps = /* @__PURE__ */ new Map();
35702
+ validateSignature(rawBody, signature) {
35703
+ if (!signature?.startsWith("sha256=")) return false;
35704
+ const expected = createHmac("sha256", this.options.appSecret).update(rawBody).digest("hex");
35705
+ const actual = signature.slice("sha256=".length);
35706
+ const expectedBuffer = Buffer.from(expected, "hex");
35707
+ const actualBuffer = Buffer.from(actual, "hex");
35708
+ return expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer);
35709
+ }
35710
+ async handleWebhook(input) {
35711
+ if (!this.validateSignature(input.rawBody, input.signature)) {
35712
+ throw new Error("Invalid WhatsApp webhook signature.");
35713
+ }
35714
+ const messages = parseWhatsAppCloudMessages(input.body ?? JSON.parse(input.rawBody));
35715
+ const outputs = [];
35716
+ for (const message of messages) {
35717
+ if (this.processedMessageIds.has(message.messageId)) {
35718
+ return { accepted: true, duplicate: true, outputs };
35719
+ }
35720
+ this.processedMessageIds.add(message.messageId);
35721
+ const senderKey = `${input.tenantId}:${message.from}`;
35722
+ const normalizedText = message.text.trim();
35723
+ if (this.isOptOut(normalizedText)) {
35724
+ this.optOutSenders.add(senderKey);
35725
+ return { accepted: true, optedOut: true, outputs };
35726
+ }
35727
+ if (this.isOptIn(normalizedText)) {
35728
+ this.optOutSenders.delete(senderKey);
35729
+ }
35730
+ if (this.optOutSenders.has(senderKey)) {
35731
+ return { accepted: true, optedOut: true, outputs };
35732
+ }
35733
+ if (!this.consumeRateLimit(senderKey)) {
35734
+ return { accepted: true, rateLimited: true, outputs };
35735
+ }
35736
+ const sessionId = this.getOrCreateSession(input.tenantId, message);
35737
+ const result = await this.options.runtime.runTurn({
35738
+ sessionId,
35739
+ content: normalizedText || mediaFallbackText(message.media),
35740
+ metadata: {
35741
+ surface: "whatsapp",
35742
+ channel: "whatsapp-cloud",
35743
+ tenantId: input.tenantId,
35744
+ phoneNumber: message.from,
35745
+ profileName: message.profileName,
35746
+ messageId: message.messageId,
35747
+ media: message.media
35748
+ }
35749
+ });
35750
+ outputs.push({ sessionId, content: result.content, metadata: { model: result.model } });
35751
+ }
35752
+ return { accepted: true, outputs };
35753
+ }
35754
+ getOrCreateSession(tenantId, message) {
35755
+ const existing = this.mapper.getSessionId({
35756
+ tenantId,
35757
+ channel: "whatsapp-cloud",
35758
+ senderId: message.from
35759
+ });
35760
+ if (existing) return existing;
35761
+ const session = this.options.runtime.createSession({
35762
+ mode: "ask",
35763
+ metadata: {
35764
+ surface: "whatsapp",
35765
+ channel: "whatsapp-cloud",
35766
+ tenantId,
35767
+ phoneNumber: message.from,
35768
+ profileName: message.profileName
35769
+ }
35770
+ });
35771
+ this.mapper.setSessionId({
35772
+ tenantId,
35773
+ channel: "whatsapp-cloud",
35774
+ senderId: message.from,
35775
+ sessionId: session.id
35776
+ });
35777
+ return session.id;
35778
+ }
35779
+ consumeRateLimit(senderKey) {
35780
+ const limit = this.options.maxMessagesPerMinutePerSender;
35781
+ if (!limit) return true;
35782
+ const now = Date.now();
35783
+ const recent = (this.senderTimestamps.get(senderKey) ?? []).filter(
35784
+ (timestamp) => timestamp > now - 6e4
35785
+ );
35786
+ if (recent.length >= limit) {
35787
+ this.senderTimestamps.set(senderKey, recent);
35788
+ return false;
35789
+ }
35790
+ recent.push(now);
35791
+ this.senderTimestamps.set(senderKey, recent);
35792
+ return true;
35793
+ }
35794
+ isOptOut(text2) {
35795
+ const keywords = this.options.optOutKeywords ?? ["stop", "unsubscribe", "baja"];
35796
+ return keywords.includes(text2.toLowerCase());
35797
+ }
35798
+ isOptIn(text2) {
35799
+ const keywords = this.options.optInKeywords ?? ["start", "subscribe", "alta"];
35800
+ return keywords.includes(text2.toLowerCase());
35801
+ }
35802
+ };
35803
+ function createWhatsAppCloudAdapter(options) {
35804
+ return new WhatsAppCloudAdapter(options);
35805
+ }
35806
+ function createInMemoryChannelSessionMapper() {
35807
+ return new InMemoryChannelSessionMapper();
35808
+ }
35809
+ function sessionKey(input) {
35810
+ return `${input.tenantId}:${input.channel}:${input.senderId}`;
35811
+ }
35812
+ function parseWhatsAppCloudMessages(body) {
35813
+ if (!body || typeof body !== "object") return [];
35814
+ const entries = Array.isArray(body.entry) ? body.entry : [];
35815
+ return entries.flatMap((entry) => {
35816
+ const changes = Array.isArray(entry.changes) ? entry.changes : [];
35817
+ return changes.flatMap((change) => {
35818
+ const value = change.value;
35819
+ const contacts = Array.isArray(value?.contacts) ? value.contacts : [];
35820
+ const profileByWaId = new Map(
35821
+ contacts.map((contact) => [
35822
+ contact.wa_id,
35823
+ contact.profile?.name
35824
+ ])
35825
+ );
35826
+ const messages = Array.isArray(value?.messages) ? value.messages : [];
35827
+ return messages.map((message) => normalizeWhatsAppMessage(message, profileByWaId));
35828
+ });
35829
+ });
35830
+ }
35831
+ function normalizeWhatsAppMessage(message, profileByWaId) {
35832
+ const record = message;
35833
+ const from = String(record["from"] ?? "");
35834
+ const type = String(record["type"] ?? "text");
35835
+ const text2 = type === "text" ? String(record["text"]?.body ?? "") : "";
35836
+ const media = normalizeMedia(record, type);
35837
+ return {
35838
+ messageId: String(record["id"] ?? ""),
35839
+ from,
35840
+ text: text2 || media?.caption || "",
35841
+ profileName: profileByWaId.get(from),
35842
+ media
35843
+ };
35844
+ }
35845
+ function normalizeMedia(record, type) {
35846
+ if (!["image", "audio", "video", "document", "sticker"].includes(type)) return void 0;
35847
+ const mediaRecord = record[type];
35848
+ if (!mediaRecord) return void 0;
35849
+ return {
35850
+ id: String(mediaRecord["id"] ?? ""),
35851
+ type,
35852
+ mimeType: typeof mediaRecord["mime_type"] === "string" ? mediaRecord["mime_type"] : void 0,
35853
+ caption: typeof mediaRecord["caption"] === "string" ? mediaRecord["caption"] : void 0
35854
+ };
35855
+ }
35856
+ function mediaFallbackText(media) {
35857
+ return media ? `[${media.type}:${media.id}]` : "";
35858
+ }
34663
35859
 
34664
35860
  // src/index.ts
34665
35861
  init_errors();
34666
35862
  init_logger();
34667
35863
  init_proxy();
34668
35864
 
34669
- export { ADRGenerator, AGENT_MODES, AGENT_PRESETS, AgentGraphEngine, AgentRunner, AgentRuntime, AnthropicProvider, ArchitectureGenerator, BacklogGenerator, CICDGenerator, CocoError, CodeGenerator, CodeReviewer, CompleteExecutor, ConfigError, ConvergeExecutor, DEFAULT_WORKFLOWS, DefaultPermissionPolicy, DefaultRuntimeTurnRunner, DiscoveryEngine, DockerGenerator, DocsGenerator, FileEventLog, FileRuntimeSessionStore, FileSharedWorkspaceStore, InMemoryEventLog, InMemoryKnowledgeRetriever, InMemoryRuntimeSessionStore, InMemorySharedWorkspaceStore, InMemoryVectorStore, OrchestrateExecutor, OutputExecutor, PhaseError, ProviderRegistry, RuntimeToolExecutor, SessionManager, SharedWorkspaceState, SimpleTextChunker, SpecificationGenerator, TaskError, TaskIterator, ToolCallingRuntimeTurnRunner, ToolRegistry, VERSION, WorkflowCatalog, WorkflowEngine, WorkflowRegistry, appointmentBookingAssistantPreset, codingAgentPreset, configExists, createADRGenerator, createAgentArtifact, createAgentFromBlueprint, createAgentGraphEngine, createAgentRunner, createAgentRuntime, createAgentTraceContext, createAnthropicProvider, createArchitectureGenerator, createBacklogGenerator, createBaseBlueprint, createCICDGenerator, createCodeGenerator, createCodeReviewer, createCodingToolRegistry, createCompleteExecutor, createConvergeExecutor, createCustomerSupportToolRegistry, createDefaultConfig, createDefaultRuntimeTurnRunner, createDiscoveryEngine, createDockerGenerator, createDocsGenerator, createEventLog, createFileEventLog, createFileRuntimeSessionStore, createFullToolRegistry, createHttpAssistantAdapter, createInMemoryKnowledgeRetriever, createInMemoryVectorStore, createLogger, createMcpToolPolicy, createNoToolRegistry, createOrchestrateExecutor, createOrchestrator, createOutputExecutor, createPermissionPolicy, createProvider, createProviderRegistry, createPublicWebToolRegistry, createRagPipeline, createRagToolRegistry, createRuntimeHttpServer, createRuntimeRequestContext, createRuntimeSessionStore, createRuntimeToolExecutor, createSafeToolRegistry, createSessionManager, createSimpleTextChunker, createSpecificationGenerator, createStreamingHttpAssistantAdapter, createSummaryArtifact, createSupportRagToolRegistry, createTaskIterator, createToolCallingRuntimeTurnRunner, createToolRegistry, createWebhookAssistantAdapter, createWhatsAppAssistantAdapter, createWorkflowCatalog, createWorkflowEngine, createWorkflowRegistry, customerSupportAssistantPreset, defaultPublicGuardrails, dryRunAgentGraphNodeExecutor, evaluateAgentToolPolicy, formatRetrievedSourcesForPrompt, getAgentMode, installProxyDispatcher, internalOpsAssistantPreset, isAgentMode, listAgentModes, listLegacyAgentRoleMappings, loadConfig, mapActionModeToRuntimeMode, mapLegacyAgentRole, mergeRuntimePolicy, normalizeAgentRunResult, publicWebsiteAssistantPreset, ragKnowledgeAssistantPreset, redactSecrets, registerAllTools, runGuardrails, runtimeContextToMetadata, salesIntakeAssistantPreset, saveConfig, supportRagAssistantPreset, validateAgentCapabilities, validateAgentGraph, validateStructuredOutput, workflowToAgentGraph };
35865
+ export { ADRGenerator, AGENT_MODES, AGENT_PRESETS, AgentDefinitionRegistry, AgentGraphEngine, AgentRunner, AgentRuntime, AnthropicProvider, ArchitectureGenerator, AsyncPostgresEventLog, AsyncPostgresRuntimeSessionStore, BacklogGenerator, CICDGenerator, CocoError, CodeGenerator, CodeReviewer, CompleteExecutor, ConfigError, ConvergeExecutor, DEFAULT_WORKFLOWS, DefaultPermissionPolicy, DefaultRuntimeTurnRunner, DiscoveryEngine, DockerGenerator, DocsGenerator, FileEventLog, FileRuntimeSessionStore, FileSharedWorkspaceStore, FileTraceExporter, InMemoryEventLog, InMemoryKnowledgeRetriever, InMemoryRuntimeSessionStore, InMemorySharedWorkspaceStore, InMemoryTraceExporter, InMemoryVectorStore, OpenTelemetryTraceExporter, OrchestrateExecutor, OutputExecutor, PhaseError, PostgresRuntimeAuditStore, ProviderRegistry, RuntimeAgentNodeExecutor, RuntimePolicyViolation, RuntimeToolExecutor, SessionManager, SharedWorkspaceState, SimpleTextChunker, SpecificationGenerator, TaskError, TaskIterator, TenantScopedEventLog, TenantScopedRuntimeSessionStore, ToolCallingRuntimeTurnRunner, ToolRegistry, VERSION, WorkflowCatalog, WorkflowEngine, WorkflowRegistry, appointmentBookingAssistantPreset, assertRuntimeTenantBoundary, assertRuntimeTurnWithinPolicy, codingAgentPreset, collectRuntimeMetrics, configExists, createADRGenerator, createAgentArtifact, createAgentDefinitionRegistry, createAgentFromBlueprint, createAgentGraphEngine, createAgentRunner, createAgentRuntime, createAgentTraceContext, createAnthropicProvider, createArchitectureGenerator, createAsyncPostgresEventLog, createAsyncPostgresRuntimeSessionStore, createBacklogGenerator, createBaseBlueprint, createCICDGenerator, createCodeGenerator, createCodeReviewer, createCodingToolRegistry, createCompleteExecutor, createConvergeExecutor, createCustomerSupportToolRegistry, createDefaultConfig, createDefaultRuntimeTurnRunner, createDiscoveryEngine, createDockerGenerator, createDocsGenerator, createDocumentAccessPolicy, createEventLog, createFileEventLog, createFileRuntimeSessionStore, createFullToolRegistry, createHttpAssistantAdapter, createInMemoryChannelSessionMapper, createInMemoryKnowledgeRetriever, createInMemoryVectorStore, createLogger, createMcpToolPolicy, createNoToolRegistry, createOrchestrateExecutor, createOrchestrator, createOutputExecutor, createPermissionPolicy, createPostgresRuntimeAuditStore, createProvider, createProviderRegistry, createPublicWebToolRegistry, createRagIngestionJob, createRagPipeline, createRagToolRegistry, createRetentionCutoffs, createRuntimeAgentNodeExecutor, createRuntimeHttpServer, createRuntimeRequestContext, createRuntimeSessionStore, createRuntimeTenantBoundary, createRuntimeToolExecutor, createSafeToolRegistry, createSessionManager, createSimpleTextChunker, createSpecificationGenerator, createStreamingHttpAssistantAdapter, createSummaryArtifact, createSupportRagToolRegistry, createTaskIterator, createTenantScopedEventLog, createTenantScopedRuntimeSessionStore, createToolCallingRuntimeTurnRunner, createToolRegistry, createWebhookAssistantAdapter, createWhatsAppAssistantAdapter, createWhatsAppCloudAdapter, createWorkflowCatalog, createWorkflowEngine, createWorkflowRegistry, customerSupportAssistantPreset, defaultPublicGuardrails, dryRunAgentGraphNodeExecutor, evaluateAgentToolPolicy, evaluateGroundedness, eventToSpan, exportRuntimeEventsAsSpans, formatRetrievedSourcesForPrompt, getAgentMode, installProxyDispatcher, internalOpsAssistantPreset, isAgentMode, listAgentModes, listLegacyAgentRoleMappings, loadConfig, mapActionModeToRuntimeMode, mapLegacyAgentRole, mergeRuntimePolicy, normalizeAgentRunResult, publicWebsiteAssistantPreset, ragKnowledgeAssistantPreset, redactSecrets, redactTraceAttributes, registerAllTools, runGuardrailPipeline, runGuardrails, runtimeContextToMetadata, salesIntakeAssistantPreset, saveConfig, supportRagAssistantPreset, validateAgentCapabilities, validateAgentGraph, validateStructuredOutput, verifyCitations, workflowToAgentGraph };
34670
35866
  //# sourceMappingURL=index.js.map
34671
35867
  //# sourceMappingURL=index.js.map