@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.
@@ -3363,6 +3363,53 @@ function defineTool(definition) {
3363
3363
  // src/runtime/agent-runtime.ts
3364
3364
  init_env();
3365
3365
 
3366
+ // src/providers/pricing.ts
3367
+ init_catalog();
3368
+ var MODEL_PRICING = getCatalogModelPricingMap();
3369
+ var DEFAULT_PRICING = {
3370
+ anthropic: { inputPerMillion: 3, outputPerMillion: 15, contextWindow: 2e5 },
3371
+ openai: { inputPerMillion: 2.5, outputPerMillion: 10, contextWindow: 128e3 },
3372
+ codex: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 128e3 },
3373
+ // ChatGPT Plus/Pro subscription
3374
+ gemini: { inputPerMillion: 0.1, outputPerMillion: 0.4, contextWindow: 1e6 },
3375
+ vertex: { inputPerMillion: 0.1, outputPerMillion: 0.4, contextWindow: 1048576 },
3376
+ kimi: { inputPerMillion: 1.2, outputPerMillion: 1.2, contextWindow: 8192 },
3377
+ "kimi-code": { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 131072 },
3378
+ // Included in subscription
3379
+ copilot: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 2e5 },
3380
+ // Included in subscription
3381
+ lmstudio: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 32768 },
3382
+ // Free - local models
3383
+ ollama: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 128e3 },
3384
+ // Free - local models
3385
+ groq: { inputPerMillion: 0.05, outputPerMillion: 0.08, contextWindow: 128e3 },
3386
+ // Free tier available
3387
+ openrouter: { inputPerMillion: 2, outputPerMillion: 8, contextWindow: 2e5 },
3388
+ // Varies by model
3389
+ mistral: { inputPerMillion: 0.25, outputPerMillion: 0.75, contextWindow: 32768 },
3390
+ deepseek: { inputPerMillion: 0.14, outputPerMillion: 0.28, contextWindow: 128e3 },
3391
+ // Very cheap
3392
+ together: { inputPerMillion: 0.2, outputPerMillion: 0.2, contextWindow: 32768 },
3393
+ huggingface: { inputPerMillion: 0, outputPerMillion: 0, contextWindow: 32768 },
3394
+ // Free tier
3395
+ qwen: { inputPerMillion: 0.3, outputPerMillion: 1.2, contextWindow: 131072 }
3396
+ // qwen-coder-plus pricing
3397
+ };
3398
+ function estimateCost(model2, inputTokens, outputTokens, provider) {
3399
+ const pricing = MODEL_PRICING[model2] ?? (provider ? DEFAULT_PRICING[provider] : DEFAULT_PRICING.anthropic);
3400
+ const inputCost = inputTokens / 1e6 * pricing.inputPerMillion;
3401
+ const outputCost = outputTokens / 1e6 * pricing.outputPerMillion;
3402
+ return {
3403
+ inputCost,
3404
+ outputCost,
3405
+ totalCost: inputCost + outputCost,
3406
+ inputTokens,
3407
+ outputTokens,
3408
+ model: model2,
3409
+ currency: "USD"
3410
+ };
3411
+ }
3412
+
3366
3413
  // src/runtime/agent-modes.ts
3367
3414
  var AGENT_MODES = {
3368
3415
  ask: {
@@ -3422,6 +3469,22 @@ function listAgentModes() {
3422
3469
  }
3423
3470
 
3424
3471
  // src/runtime/context.ts
3472
+ var RuntimePolicyViolation = class extends Error {
3473
+ code;
3474
+ subject;
3475
+ tenantId;
3476
+ policyPath;
3477
+ severity;
3478
+ constructor(input) {
3479
+ super(input.message);
3480
+ this.name = "RuntimePolicyViolation";
3481
+ this.code = input.code;
3482
+ this.subject = input.subject;
3483
+ this.tenantId = input.tenantId;
3484
+ this.policyPath = input.policyPath;
3485
+ this.severity = input.severity ?? "blocked";
3486
+ }
3487
+ };
3425
3488
  function createRuntimeRequestContext(input = {}) {
3426
3489
  return {
3427
3490
  surface: input.surface ?? "api",
@@ -3464,6 +3527,27 @@ function runtimeContextToMetadata(context) {
3464
3527
  dataClassification: context.policy?.dataBoundary?.classification
3465
3528
  };
3466
3529
  }
3530
+ function createRuntimeTenantBoundary(context, hostMode = "local") {
3531
+ return {
3532
+ hostMode,
3533
+ surface: context?.surface ?? "api",
3534
+ tenantId: context?.tenant?.id,
3535
+ required: hostMode === "hosted" && context?.surface !== "cli"
3536
+ };
3537
+ }
3538
+ function assertRuntimeTenantBoundary(context, hostMode = "local", subject = "runtime operation") {
3539
+ const boundary = createRuntimeTenantBoundary(context, hostMode);
3540
+ if (boundary.required && !boundary.tenantId) {
3541
+ throw new RuntimePolicyViolation({
3542
+ code: "tenant_required",
3543
+ subject,
3544
+ tenantId: boundary.tenantId,
3545
+ policyPath: "runtimeContext.tenant.id",
3546
+ message: `Runtime tenant is required for hosted ${boundary.surface} operations.`
3547
+ });
3548
+ }
3549
+ return boundary;
3550
+ }
3467
3551
  function evaluateRuntimeToolPolicy(policy, input) {
3468
3552
  if (policy?.allowedTools && !policy.allowedTools.includes(input.toolName)) {
3469
3553
  return {
@@ -3507,23 +3591,65 @@ function evaluateRuntimeRiskPolicy(policy, input) {
3507
3591
  }
3508
3592
  return { allowed: true, risk: input.risk };
3509
3593
  }
3594
+ function assertRuntimeTurnWithinPolicy(policy, input) {
3595
+ const maxTurns = policy?.costBudget?.maxTurns;
3596
+ if (maxTurns !== void 0 && input.currentTurns >= maxTurns) {
3597
+ throw new RuntimePolicyViolation({
3598
+ code: "max_turns_exceeded",
3599
+ subject: input.subject,
3600
+ tenantId: input.tenantId,
3601
+ policyPath: "runtimePolicy.costBudget.maxTurns",
3602
+ message: `Runtime policy turn budget exceeded: ${input.currentTurns}/${maxTurns}`
3603
+ });
3604
+ }
3605
+ }
3510
3606
  function assertRuntimeUsageWithinPolicy(policy, usage) {
3511
3607
  const budget = policy?.costBudget;
3608
+ const subject = usage.subject ?? "runtime usage";
3512
3609
  if (!budget) return;
3513
3610
  if (budget.maxInputTokens !== void 0 && (usage.inputTokens ?? 0) > budget.maxInputTokens) {
3514
- throw new Error(
3515
- `Runtime policy input token budget exceeded: ${usage.inputTokens ?? 0}/${budget.maxInputTokens}`
3516
- );
3611
+ throw new RuntimePolicyViolation({
3612
+ code: "input_tokens_exceeded",
3613
+ subject,
3614
+ tenantId: usage.tenantId,
3615
+ policyPath: "runtimePolicy.costBudget.maxInputTokens",
3616
+ message: `Runtime policy input token budget exceeded: ${usage.inputTokens ?? 0}/${budget.maxInputTokens}`
3617
+ });
3517
3618
  }
3518
3619
  if (budget.maxOutputTokens !== void 0 && (usage.outputTokens ?? 0) > budget.maxOutputTokens) {
3519
- throw new Error(
3520
- `Runtime policy output token budget exceeded: ${usage.outputTokens ?? 0}/${budget.maxOutputTokens}`
3521
- );
3620
+ throw new RuntimePolicyViolation({
3621
+ code: "output_tokens_exceeded",
3622
+ subject,
3623
+ tenantId: usage.tenantId,
3624
+ policyPath: "runtimePolicy.costBudget.maxOutputTokens",
3625
+ message: `Runtime policy output token budget exceeded: ${usage.outputTokens ?? 0}/${budget.maxOutputTokens}`
3626
+ });
3627
+ }
3628
+ if (budget.maxEstimatedCostUsd !== void 0 && (usage.estimatedCostUsd ?? 0) > budget.maxEstimatedCostUsd) {
3629
+ throw new RuntimePolicyViolation({
3630
+ code: "estimated_cost_exceeded",
3631
+ subject,
3632
+ tenantId: usage.tenantId,
3633
+ policyPath: "runtimePolicy.costBudget.maxEstimatedCostUsd",
3634
+ message: `Runtime policy estimated cost budget exceeded: ${usage.estimatedCostUsd ?? 0}/${budget.maxEstimatedCostUsd}`
3635
+ });
3522
3636
  }
3523
3637
  }
3638
+ function createRetentionCutoffs(policy, now = /* @__PURE__ */ new Date()) {
3639
+ const retention = policy?.retention;
3640
+ return {
3641
+ conversationBefore: cutoffIso(now, retention?.conversationDays),
3642
+ eventBefore: cutoffIso(now, retention?.eventDays),
3643
+ artifactBefore: cutoffIso(now, retention?.artifactDays)
3644
+ };
3645
+ }
3524
3646
  function cloneRuntimePolicy(policy) {
3525
3647
  return mergeRuntimePolicy(void 0, policy) ?? {};
3526
3648
  }
3649
+ function cutoffIso(now, days) {
3650
+ if (days === void 0) return void 0;
3651
+ return new Date(now.getTime() - days * 24 * 60 * 60 * 1e3).toISOString();
3652
+ }
3527
3653
  function riskRank(risk) {
3528
3654
  switch (risk) {
3529
3655
  case "read-only":
@@ -7855,10 +7981,6 @@ var VertexProvider = class {
7855
7981
  }
7856
7982
  };
7857
7983
 
7858
- // src/providers/pricing.ts
7859
- init_catalog();
7860
- getCatalogModelPricingMap();
7861
-
7862
7984
  // src/providers/circuit-breaker.ts
7863
7985
  init_errors();
7864
7986
  var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
@@ -8431,6 +8553,7 @@ var LEGACY_ROLE_MAPPINGS = [
8431
8553
  { legacy: "coder", role: "coder", reason: "legacy executor role" },
8432
8554
  { legacy: "test", role: "tester", reason: "test authoring/execution" },
8433
8555
  { legacy: "tester", role: "tester", reason: "legacy executor role" },
8556
+ { legacy: "verifier", role: "tester", reason: "verification maps to tester capability" },
8434
8557
  { legacy: "tdd", role: "tester", reason: "test-first implementation" },
8435
8558
  { legacy: "e2e", role: "tester", reason: "end-to-end testing" },
8436
8559
  { legacy: "review", role: "reviewer", reason: "code review" },
@@ -8444,7 +8567,10 @@ var LEGACY_ROLE_MAPPINGS = [
8444
8567
  { legacy: "docs", role: "docs", reason: "documentation" },
8445
8568
  { legacy: "database", role: "database", reason: "database work" }
8446
8569
  ];
8447
- new Map(LEGACY_ROLE_MAPPINGS.map((mapping) => [mapping.legacy, mapping]));
8570
+ var LEGACY_ROLE_MAP = new Map(LEGACY_ROLE_MAPPINGS.map((mapping) => [mapping.legacy, mapping]));
8571
+ function mapLegacyAgentRole(legacyRole, fallback = "coder") {
8572
+ return LEGACY_ROLE_MAP.get(legacyRole)?.role ?? fallback;
8573
+ }
8448
8574
  function assertProvenance(provenance) {
8449
8575
  if (!provenance.workflowRunId) {
8450
8576
  throw new Error("Shared workspace writes require workflowRunId provenance.");
@@ -8524,6 +8650,39 @@ var InMemorySharedWorkspaceStore = class {
8524
8650
  this.records = [];
8525
8651
  }
8526
8652
  };
8653
+ function evaluateAgentToolPolicy(input) {
8654
+ const manifestEntry = input.manifest?.[input.toolName];
8655
+ const risk = manifestEntry?.risk ?? input.capability.risk;
8656
+ if (!input.capability.allowedTools.includes(input.toolName)) {
8657
+ return {
8658
+ allowed: false,
8659
+ risk,
8660
+ reason: `Tool '${input.toolName}' is not allowed for agent role '${input.capability.role}'.`
8661
+ };
8662
+ }
8663
+ if (manifestEntry?.requiredCapability) {
8664
+ const allowedRoles = Array.isArray(manifestEntry.requiredCapability) ? manifestEntry.requiredCapability : [manifestEntry.requiredCapability];
8665
+ if (!allowedRoles.includes(input.capability.role)) {
8666
+ return {
8667
+ allowed: false,
8668
+ risk,
8669
+ reason: `Tool '${input.toolName}' requires role ${allowedRoles.join(", ")}.`
8670
+ };
8671
+ }
8672
+ }
8673
+ if (riskRank2(risk) > riskRank2(input.capability.risk)) {
8674
+ return {
8675
+ allowed: false,
8676
+ risk,
8677
+ reason: `Tool '${input.toolName}' risk '${risk}' exceeds agent capability risk '${input.capability.risk}'.`
8678
+ };
8679
+ }
8680
+ return {
8681
+ allowed: true,
8682
+ risk,
8683
+ requiresConsent: manifestEntry?.requiresConsent ?? (risk === "destructive" || risk === "secrets-sensitive")
8684
+ };
8685
+ }
8527
8686
  function createAgentTraceContext(input = {}) {
8528
8687
  return {
8529
8688
  traceId: input.traceId ?? `trace-${randomUUID()}`,
@@ -8964,7 +9123,7 @@ var NULL_EVENT_LOG = {
8964
9123
  function graphNodeToTask(node, workflowInput) {
8965
9124
  return {
8966
9125
  id: node.id,
8967
- role: node.agentRole ?? "coder",
9126
+ role: node.agentRole ?? mapLegacyAgentRole(node.id, "coder"),
8968
9127
  objective: node.description,
8969
9128
  context: {
8970
9129
  workflowInput,
@@ -9111,6 +9270,20 @@ function readPath(input, path38) {
9111
9270
  return void 0;
9112
9271
  }, input);
9113
9272
  }
9273
+ function riskRank2(risk) {
9274
+ switch (risk) {
9275
+ case "read-only":
9276
+ return 0;
9277
+ case "network":
9278
+ return 1;
9279
+ case "write":
9280
+ return 2;
9281
+ case "destructive":
9282
+ return 3;
9283
+ case "secrets-sensitive":
9284
+ return 4;
9285
+ }
9286
+ }
9114
9287
  function cloneUnknown(value) {
9115
9288
  if (value === void 0 || value === null) return value;
9116
9289
  try {
@@ -9136,6 +9309,206 @@ function cloneArtifact(artifact) {
9136
9309
  };
9137
9310
  }
9138
9311
 
9312
+ // src/runtime/agent-runner.ts
9313
+ var AgentRunner = class {
9314
+ constructor(options = {}) {
9315
+ this.options = options;
9316
+ }
9317
+ options;
9318
+ async run(input) {
9319
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
9320
+ const trace = input.trace ?? createAgentTraceContext({ taskId: input.task.id });
9321
+ this.options.eventLog?.record("agent.started", {
9322
+ taskId: input.task.id,
9323
+ role: input.task.role,
9324
+ trace
9325
+ });
9326
+ try {
9327
+ const raw = await (this.options.executor ?? defaultExecutor)({
9328
+ task: input.task,
9329
+ capability: input.capability,
9330
+ trace,
9331
+ assertToolAllowed: (toolName) => {
9332
+ const decision = evaluateAgentToolPolicy({
9333
+ capability: input.capability,
9334
+ toolName,
9335
+ manifest: input.toolRiskManifest
9336
+ });
9337
+ this.options.eventLog?.record("agent.tool.called", {
9338
+ taskId: input.task.id,
9339
+ role: input.task.role,
9340
+ toolName,
9341
+ decision,
9342
+ trace
9343
+ });
9344
+ if (!decision.allowed) {
9345
+ throw new Error(decision.reason ?? `Tool '${toolName}' is not allowed.`);
9346
+ }
9347
+ }
9348
+ });
9349
+ const result = normalizeAgentRunResult({
9350
+ id: `${input.task.id}-run-${Date.now().toString(36)}`,
9351
+ taskId: input.task.id,
9352
+ role: input.task.role,
9353
+ success: raw.success ?? true,
9354
+ output: raw.output,
9355
+ turns: raw.turns,
9356
+ toolsUsed: raw.toolsUsed,
9357
+ usage: {
9358
+ inputTokens: raw.inputTokens ?? 0,
9359
+ outputTokens: raw.outputTokens ?? 0,
9360
+ estimated: raw.inputTokens === void 0 || raw.outputTokens === void 0
9361
+ },
9362
+ startedAt,
9363
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
9364
+ durationMs: Date.now() - Date.parse(startedAt),
9365
+ error: raw.error,
9366
+ metadata: { ...raw.metadata, trace }
9367
+ });
9368
+ this.options.eventLog?.record(result.success ? "agent.completed" : "agent.failed", {
9369
+ taskId: input.task.id,
9370
+ role: input.task.role,
9371
+ agentRunId: result.id,
9372
+ trace,
9373
+ error: result.error
9374
+ });
9375
+ return result;
9376
+ } catch (error) {
9377
+ const message = error instanceof Error ? error.message : String(error);
9378
+ const result = normalizeAgentRunResult({
9379
+ id: `${input.task.id}-run-${Date.now().toString(36)}`,
9380
+ taskId: input.task.id,
9381
+ role: input.task.role,
9382
+ success: false,
9383
+ output: message,
9384
+ startedAt,
9385
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
9386
+ durationMs: Date.now() - Date.parse(startedAt),
9387
+ error: message,
9388
+ metadata: { trace }
9389
+ });
9390
+ this.options.eventLog?.record("agent.failed", {
9391
+ taskId: input.task.id,
9392
+ role: input.task.role,
9393
+ agentRunId: result.id,
9394
+ trace,
9395
+ error: message
9396
+ });
9397
+ return result;
9398
+ }
9399
+ }
9400
+ };
9401
+ async function defaultExecutor(context) {
9402
+ return {
9403
+ output: `Agent ${context.capability.role} accepted task '${context.task.objective}'.`
9404
+ };
9405
+ }
9406
+
9407
+ // src/runtime/runtime-agent-node-executor.ts
9408
+ var RuntimeAgentNodeExecutor = class {
9409
+ constructor(options) {
9410
+ this.options = options;
9411
+ this.runner = options.runner ?? new AgentRunner(options.runnerOptions);
9412
+ }
9413
+ options;
9414
+ runner;
9415
+ execute = async (execution) => {
9416
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
9417
+ const definition = this.options.registry.getByRole(execution.task.role);
9418
+ if (!definition) {
9419
+ return normalizeAgentRunResult({
9420
+ id: `${execution.workflowRunId}-${execution.node.id}-missing-definition`,
9421
+ taskId: execution.task.id,
9422
+ role: execution.task.role,
9423
+ success: false,
9424
+ output: "",
9425
+ startedAt,
9426
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
9427
+ error: `No agent definition registered for role '${execution.task.role}'.`,
9428
+ metadata: {
9429
+ workflowRunId: execution.workflowRunId,
9430
+ nodeId: execution.node.id,
9431
+ trace: execution.trace
9432
+ }
9433
+ });
9434
+ }
9435
+ const blockedTool = this.findBlockedTool(definition, execution);
9436
+ if (blockedTool) {
9437
+ return normalizeAgentRunResult({
9438
+ id: `${execution.workflowRunId}-${execution.node.id}-policy-blocked`,
9439
+ taskId: execution.task.id,
9440
+ role: execution.task.role,
9441
+ success: false,
9442
+ output: "",
9443
+ startedAt,
9444
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
9445
+ error: blockedTool,
9446
+ metadata: {
9447
+ workflowRunId: execution.workflowRunId,
9448
+ nodeId: execution.node.id,
9449
+ agentDefinitionId: definition.id,
9450
+ trace: execution.trace
9451
+ }
9452
+ });
9453
+ }
9454
+ const input = {
9455
+ task: {
9456
+ ...execution.task,
9457
+ context: {
9458
+ ...execution.task.context,
9459
+ instructions: definition.instructions,
9460
+ sharedState: execution.sharedState.readForRole(definition.role)
9461
+ }
9462
+ },
9463
+ capability: definition.capability,
9464
+ trace: execution.trace,
9465
+ toolRiskManifest: this.options.toolRiskManifest
9466
+ };
9467
+ const result = await this.runner.run(input);
9468
+ return normalizeAgentRunResult({
9469
+ ...result,
9470
+ metadata: {
9471
+ ...result.metadata,
9472
+ workflowRunId: execution.workflowRunId,
9473
+ nodeId: execution.node.id,
9474
+ agentDefinitionId: definition.id
9475
+ }
9476
+ });
9477
+ };
9478
+ findBlockedTool(definition, execution) {
9479
+ for (const toolName of execution.node.requiredTools ?? []) {
9480
+ const agentDecision = evaluateAgentToolPolicy({
9481
+ capability: definition.capability,
9482
+ toolName,
9483
+ manifest: this.options.toolRiskManifest
9484
+ });
9485
+ execution.eventLog.record("agent.tool.called", {
9486
+ workflowRunId: execution.workflowRunId,
9487
+ nodeId: execution.node.id,
9488
+ taskId: execution.task.id,
9489
+ role: execution.task.role,
9490
+ toolName,
9491
+ decision: agentDecision,
9492
+ trace: execution.trace
9493
+ });
9494
+ if (!agentDecision.allowed) {
9495
+ return agentDecision.reason ?? `Tool '${toolName}' is not allowed for agent.`;
9496
+ }
9497
+ const runtimeDecision = evaluateRuntimeToolPolicy(this.options.runtimePolicy, {
9498
+ toolName,
9499
+ risk: agentDecision.risk
9500
+ });
9501
+ if (!runtimeDecision.allowed) {
9502
+ return runtimeDecision.reason ?? `Tool '${toolName}' is blocked by runtime policy.`;
9503
+ }
9504
+ }
9505
+ return void 0;
9506
+ }
9507
+ };
9508
+ function createRuntimeAgentNodeExecutor(options) {
9509
+ return new RuntimeAgentNodeExecutor(options).execute;
9510
+ }
9511
+
9139
9512
  // src/runtime/workflow-registry.ts
9140
9513
  function cloneWorkflow(workflow) {
9141
9514
  return {
@@ -9465,14 +9838,22 @@ var WorkflowEngine = class {
9465
9838
  this.catalog = catalog;
9466
9839
  this.eventLog = eventLog;
9467
9840
  this.sharedState = options.sharedState ?? new InMemorySharedWorkspaceStore();
9468
- this.nodeExecutor = options.nodeExecutor;
9469
9841
  this.runtimePolicy = options.runtimePolicy;
9842
+ this.runtimeContext = options.runtimeContext;
9843
+ this.runtimeHostMode = options.runtimeHostMode ?? "local";
9844
+ this.nodeExecutor = options.nodeExecutor ?? (options.agentDefinitionRegistry ? createRuntimeAgentNodeExecutor({
9845
+ ...options.agentNodeExecutorOptions,
9846
+ registry: options.agentDefinitionRegistry,
9847
+ runtimePolicy: options.runtimePolicy
9848
+ }) : void 0);
9470
9849
  }
9471
9850
  catalog;
9472
9851
  eventLog;
9473
9852
  handlers = /* @__PURE__ */ new Map();
9474
9853
  sharedState;
9475
9854
  runtimePolicy;
9855
+ runtimeContext;
9856
+ runtimeHostMode;
9476
9857
  nodeExecutor;
9477
9858
  registerHandler(workflowId, handler) {
9478
9859
  if (!this.catalog.get(workflowId)) {
@@ -9487,6 +9868,7 @@ var WorkflowEngine = class {
9487
9868
  return this.catalog.createPlan(workflowId, input, this.eventLog);
9488
9869
  }
9489
9870
  async run(request) {
9871
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "workflow.run");
9490
9872
  const workflow = this.catalog.get(request.workflowId);
9491
9873
  if (!workflow) {
9492
9874
  throw new Error(`Unknown workflow: ${request.workflowId}`);
@@ -9606,7 +9988,14 @@ var AgentRuntime = class {
9606
9988
  this.model = options.model ?? options.providerConfig?.model ?? getDefaultModel(options.providerType);
9607
9989
  this.runtimeContext = options.runtimeContext ? createRuntimeRequestContext(options.runtimeContext) : void 0;
9608
9990
  this.runtimePolicy = mergeRuntimePolicy(this.runtimeContext?.policy, options.runtimePolicy);
9609
- this.workflowEngine = options.workflowEngine ?? createWorkflowEngine(void 0, this.eventLog, { runtimePolicy: this.runtimePolicy });
9991
+ this.runtimeHostMode = options.runtimeHostMode ?? "local";
9992
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "runtime.initialize");
9993
+ this.workflowEngine = options.workflowEngine ?? createWorkflowEngine(void 0, this.eventLog, {
9994
+ runtimePolicy: this.runtimePolicy,
9995
+ runtimeContext: this.runtimeContext,
9996
+ runtimeHostMode: this.runtimeHostMode,
9997
+ agentDefinitionRegistry: options.agentDefinitionRegistry
9998
+ });
9610
9999
  }
9611
10000
  options;
9612
10001
  providerRegistry;
@@ -9622,6 +10011,9 @@ var AgentRuntime = class {
9622
10011
  provider;
9623
10012
  runtimeContext;
9624
10013
  runtimePolicy;
10014
+ runtimeHostMode;
10015
+ requestTimestampsBySubject = /* @__PURE__ */ new Map();
10016
+ activeRuns = 0;
9625
10017
  async initialize() {
9626
10018
  const providerInjected = Boolean(this.options.provider);
9627
10019
  const provider = this.options.provider ?? await this.providerRegistry.createProvider(this.providerType, {
@@ -9670,10 +10062,12 @@ var AgentRuntime = class {
9670
10062
  },
9671
10063
  modes: listAgentModes(),
9672
10064
  context: this.runtimeContext,
9673
- policy: this.runtimePolicy
10065
+ policy: this.runtimePolicy,
10066
+ hostMode: this.runtimeHostMode
9674
10067
  };
9675
10068
  }
9676
10069
  createSession(options = {}) {
10070
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "session.create");
9677
10071
  const session = this.runtimeSessionStore.create({
9678
10072
  ...options,
9679
10073
  metadata: {
@@ -9697,6 +10091,27 @@ var AgentRuntime = class {
9697
10091
  listSessions() {
9698
10092
  return this.runtimeSessionStore.list();
9699
10093
  }
10094
+ cleanupRetention(options = {}) {
10095
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "retention.cleanup");
10096
+ const dryRun = options.dryRun ?? true;
10097
+ const cutoffs = createRetentionCutoffs(this.runtimePolicy, options.now);
10098
+ const expiredSessionIds = cutoffs.conversationBefore ? this.runtimeSessionStore.list().filter((session) => session.updatedAt < cutoffs.conversationBefore).map((session) => session.id) : [];
10099
+ const deletedSessionIds = dryRun ? [] : expiredSessionIds.filter((id) => this.runtimeSessionStore.delete(id));
10100
+ this.eventLog.record("retention.cleanup", {
10101
+ dryRun,
10102
+ cutoffs,
10103
+ expiredSessionIds,
10104
+ deletedSessionIds,
10105
+ tenantId: this.runtimeContext?.tenant?.id,
10106
+ runtimeApi: true
10107
+ });
10108
+ return {
10109
+ dryRun,
10110
+ cutoffs,
10111
+ expiredSessionIds,
10112
+ deletedSessionIds
10113
+ };
10114
+ }
9700
10115
  async runTurn(input) {
9701
10116
  const provider = this.provider;
9702
10117
  if (!provider) {
@@ -9707,6 +10122,12 @@ var AgentRuntime = class {
9707
10122
  throw new Error(`Runtime session not found: ${input.sessionId}`);
9708
10123
  }
9709
10124
  const effectiveSession = input.mode && input.mode !== session.mode ? { ...session, mode: input.mode } : session;
10125
+ assertRuntimeTurnWithinPolicy(this.runtimePolicy, {
10126
+ subject: "turn.run",
10127
+ currentTurns: countUserTurns(effectiveSession),
10128
+ tenantId: this.runtimeContext?.tenant?.id
10129
+ });
10130
+ const releaseRuntimeRequest = this.beginRuntimeRequest("turn.run");
9710
10131
  this.eventLog.record("turn.started", {
9711
10132
  sessionId: effectiveSession.id,
9712
10133
  provider: this.providerType,
@@ -9723,7 +10144,13 @@ var AgentRuntime = class {
9723
10144
  permissionPolicy: this.permissionPolicy,
9724
10145
  eventLog: this.eventLog
9725
10146
  });
9726
- assertRuntimeUsageWithinPolicy(this.runtimePolicy, result.usage);
10147
+ const estimatedCostUsd = this.estimateTurnCost(result);
10148
+ assertRuntimeUsageWithinPolicy(this.runtimePolicy, {
10149
+ ...result.usage,
10150
+ estimatedCostUsd,
10151
+ tenantId: this.runtimeContext?.tenant?.id,
10152
+ subject: "turn.run"
10153
+ });
9727
10154
  const updatedSession = this.runtimeSessionStore.update({
9728
10155
  ...effectiveSession,
9729
10156
  messages: [
@@ -9740,6 +10167,7 @@ var AgentRuntime = class {
9740
10167
  sessionId: updatedSession.id,
9741
10168
  inputTokens: result.usage.inputTokens,
9742
10169
  outputTokens: result.usage.outputTokens,
10170
+ estimatedCostUsd,
9743
10171
  model: result.model,
9744
10172
  runtimeApi: true
9745
10173
  });
@@ -9751,6 +10179,8 @@ var AgentRuntime = class {
9751
10179
  runtimeApi: true
9752
10180
  });
9753
10181
  throw error;
10182
+ } finally {
10183
+ releaseRuntimeRequest();
9754
10184
  }
9755
10185
  }
9756
10186
  async *streamTurn(input) {
@@ -9763,6 +10193,12 @@ var AgentRuntime = class {
9763
10193
  throw new Error(`Runtime session not found: ${input.sessionId}`);
9764
10194
  }
9765
10195
  const effectiveSession = input.mode && input.mode !== session.mode ? { ...session, mode: input.mode } : session;
10196
+ assertRuntimeTurnWithinPolicy(this.runtimePolicy, {
10197
+ subject: "turn.stream",
10198
+ currentTurns: countUserTurns(effectiveSession),
10199
+ tenantId: this.runtimeContext?.tenant?.id
10200
+ });
10201
+ const releaseRuntimeRequest = this.beginRuntimeRequest("turn.stream");
9766
10202
  const messages = [
9767
10203
  ...effectiveSession.messages,
9768
10204
  {
@@ -9812,7 +10248,13 @@ var AgentRuntime = class {
9812
10248
  model: input.options?.model ?? this.getModel(),
9813
10249
  mode: effectiveSession.mode
9814
10250
  };
9815
- assertRuntimeUsageWithinPolicy(this.runtimePolicy, result.usage);
10251
+ const estimatedCostUsd = this.estimateTurnCost(result);
10252
+ assertRuntimeUsageWithinPolicy(this.runtimePolicy, {
10253
+ ...result.usage,
10254
+ estimatedCostUsd,
10255
+ tenantId: this.runtimeContext?.tenant?.id,
10256
+ subject: "turn.stream"
10257
+ });
9816
10258
  const updatedSession = this.runtimeSessionStore.update({
9817
10259
  ...effectiveSession,
9818
10260
  messages: [
@@ -9831,6 +10273,7 @@ var AgentRuntime = class {
9831
10273
  sessionId: updatedSession.id,
9832
10274
  inputTokens: result.usage.inputTokens,
9833
10275
  outputTokens: result.usage.outputTokens,
10276
+ estimatedCostUsd,
9834
10277
  model: result.model,
9835
10278
  streaming: true,
9836
10279
  runtimeApi: true
@@ -9860,9 +10303,11 @@ var AgentRuntime = class {
9860
10303
  runtimeApi: true
9861
10304
  });
9862
10305
  }
10306
+ releaseRuntimeRequest();
9863
10307
  }
9864
10308
  }
9865
10309
  async executeTool(input) {
10310
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "tool.execute");
9866
10311
  const startedAt = performance.now();
9867
10312
  const session = input.sessionId ? this.getSession(input.sessionId) : void 0;
9868
10313
  if (input.sessionId && !session) {
@@ -9973,6 +10418,7 @@ var AgentRuntime = class {
9973
10418
  };
9974
10419
  }
9975
10420
  assertToolAllowed(mode, toolName, input) {
10421
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "tool.assertAllowed");
9976
10422
  const tool = this.toolRegistry.get(toolName);
9977
10423
  if (!tool) {
9978
10424
  this.eventLog.record("tool.blocked", {
@@ -10002,7 +10448,65 @@ var AgentRuntime = class {
10002
10448
  });
10003
10449
  return allowed;
10004
10450
  }
10451
+ beginRuntimeRequest(subject) {
10452
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, subject);
10453
+ this.assertWithinRateLimit(subject);
10454
+ this.assertWithinConcurrencyLimit(subject);
10455
+ this.activeRuns += 1;
10456
+ let released = false;
10457
+ return () => {
10458
+ if (released) return;
10459
+ released = true;
10460
+ this.activeRuns = Math.max(0, this.activeRuns - 1);
10461
+ };
10462
+ }
10463
+ assertWithinRateLimit(subject) {
10464
+ const maxRequestsPerMinute = this.runtimePolicy?.rateLimit?.maxRequestsPerMinute;
10465
+ if (maxRequestsPerMinute === void 0) return;
10466
+ const now = Date.now();
10467
+ const windowStart = now - 6e4;
10468
+ const key = `${this.runtimeContext?.tenant?.id ?? "global"}:${subject}`;
10469
+ const recent = (this.requestTimestampsBySubject.get(key) ?? []).filter(
10470
+ (timestamp) => timestamp > windowStart
10471
+ );
10472
+ if (recent.length >= maxRequestsPerMinute) {
10473
+ this.requestTimestampsBySubject.set(key, recent);
10474
+ throw new RuntimePolicyViolation({
10475
+ code: "rate_limit_exceeded",
10476
+ subject,
10477
+ tenantId: this.runtimeContext?.tenant?.id,
10478
+ policyPath: "runtimePolicy.rateLimit.maxRequestsPerMinute",
10479
+ message: `Runtime policy rate limit exceeded: ${recent.length}/${maxRequestsPerMinute} requests per minute.`
10480
+ });
10481
+ }
10482
+ recent.push(now);
10483
+ this.requestTimestampsBySubject.set(key, recent);
10484
+ }
10485
+ assertWithinConcurrencyLimit(subject) {
10486
+ const maxConcurrentRuns = this.runtimePolicy?.rateLimit?.maxConcurrentRuns;
10487
+ if (maxConcurrentRuns === void 0) return;
10488
+ if (this.activeRuns >= maxConcurrentRuns) {
10489
+ throw new RuntimePolicyViolation({
10490
+ code: "concurrency_limit_exceeded",
10491
+ subject,
10492
+ tenantId: this.runtimeContext?.tenant?.id,
10493
+ policyPath: "runtimePolicy.rateLimit.maxConcurrentRuns",
10494
+ message: `Runtime policy concurrency limit exceeded: ${this.activeRuns}/${maxConcurrentRuns} active runs.`
10495
+ });
10496
+ }
10497
+ }
10498
+ estimateTurnCost(result) {
10499
+ return estimateCost(
10500
+ result.model,
10501
+ result.usage.inputTokens,
10502
+ result.usage.outputTokens,
10503
+ this.providerType
10504
+ ).totalCost;
10505
+ }
10005
10506
  };
10507
+ function countUserTurns(session) {
10508
+ return session.messages.filter((message) => message.role === "user").length;
10509
+ }
10006
10510
  async function createAgentRuntime(options) {
10007
10511
  const runtime = new AgentRuntime(options);
10008
10512
  await runtime.initialize();