@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/cli/index.js CHANGED
@@ -43237,6 +43237,7 @@ var LEGACY_ROLE_MAPPINGS = [
43237
43237
  { legacy: "coder", role: "coder", reason: "legacy executor role" },
43238
43238
  { legacy: "test", role: "tester", reason: "test authoring/execution" },
43239
43239
  { legacy: "tester", role: "tester", reason: "legacy executor role" },
43240
+ { legacy: "verifier", role: "tester", reason: "verification maps to tester capability" },
43240
43241
  { legacy: "tdd", role: "tester", reason: "test-first implementation" },
43241
43242
  { legacy: "e2e", role: "tester", reason: "end-to-end testing" },
43242
43243
  { legacy: "review", role: "reviewer", reason: "code review" },
@@ -43333,6 +43334,39 @@ var InMemorySharedWorkspaceStore = class {
43333
43334
  this.records = [];
43334
43335
  }
43335
43336
  };
43337
+ function evaluateAgentToolPolicy(input) {
43338
+ const manifestEntry = input.manifest?.[input.toolName];
43339
+ const risk = manifestEntry?.risk ?? input.capability.risk;
43340
+ if (!input.capability.allowedTools.includes(input.toolName)) {
43341
+ return {
43342
+ allowed: false,
43343
+ risk,
43344
+ reason: `Tool '${input.toolName}' is not allowed for agent role '${input.capability.role}'.`
43345
+ };
43346
+ }
43347
+ if (manifestEntry?.requiredCapability) {
43348
+ const allowedRoles = Array.isArray(manifestEntry.requiredCapability) ? manifestEntry.requiredCapability : [manifestEntry.requiredCapability];
43349
+ if (!allowedRoles.includes(input.capability.role)) {
43350
+ return {
43351
+ allowed: false,
43352
+ risk,
43353
+ reason: `Tool '${input.toolName}' requires role ${allowedRoles.join(", ")}.`
43354
+ };
43355
+ }
43356
+ }
43357
+ if (riskRank(risk) > riskRank(input.capability.risk)) {
43358
+ return {
43359
+ allowed: false,
43360
+ risk,
43361
+ reason: `Tool '${input.toolName}' risk '${risk}' exceeds agent capability risk '${input.capability.risk}'.`
43362
+ };
43363
+ }
43364
+ return {
43365
+ allowed: true,
43366
+ risk,
43367
+ requiresConsent: manifestEntry?.requiresConsent ?? (risk === "destructive" || risk === "secrets-sensitive")
43368
+ };
43369
+ }
43336
43370
  function createAgentTraceContext(input = {}) {
43337
43371
  return {
43338
43372
  traceId: input.traceId ?? `trace-${randomUUID()}`,
@@ -43773,7 +43807,7 @@ var NULL_EVENT_LOG = {
43773
43807
  function graphNodeToTask(node, workflowInput) {
43774
43808
  return {
43775
43809
  id: node.id,
43776
- role: node.agentRole ?? "coder",
43810
+ role: node.agentRole ?? mapLegacyAgentRole(node.id, "coder"),
43777
43811
  objective: node.description,
43778
43812
  context: {
43779
43813
  workflowInput,
@@ -43920,6 +43954,20 @@ function readPath(input, path65) {
43920
43954
  return void 0;
43921
43955
  }, input);
43922
43956
  }
43957
+ function riskRank(risk) {
43958
+ switch (risk) {
43959
+ case "read-only":
43960
+ return 0;
43961
+ case "network":
43962
+ return 1;
43963
+ case "write":
43964
+ return 2;
43965
+ case "destructive":
43966
+ return 3;
43967
+ case "secrets-sensitive":
43968
+ return 4;
43969
+ }
43970
+ }
43923
43971
  function cloneUnknown(value) {
43924
43972
  if (value === void 0 || value === null) return value;
43925
43973
  try {
@@ -43946,6 +43994,22 @@ function cloneArtifact(artifact) {
43946
43994
  }
43947
43995
 
43948
43996
  // src/runtime/context.ts
43997
+ var RuntimePolicyViolation = class extends Error {
43998
+ code;
43999
+ subject;
44000
+ tenantId;
44001
+ policyPath;
44002
+ severity;
44003
+ constructor(input) {
44004
+ super(input.message);
44005
+ this.name = "RuntimePolicyViolation";
44006
+ this.code = input.code;
44007
+ this.subject = input.subject;
44008
+ this.tenantId = input.tenantId;
44009
+ this.policyPath = input.policyPath;
44010
+ this.severity = input.severity ?? "blocked";
44011
+ }
44012
+ };
43949
44013
  function createRuntimeRequestContext(input = {}) {
43950
44014
  return {
43951
44015
  surface: input.surface ?? "api",
@@ -43988,6 +44052,27 @@ function runtimeContextToMetadata(context) {
43988
44052
  dataClassification: context.policy?.dataBoundary?.classification
43989
44053
  };
43990
44054
  }
44055
+ function createRuntimeTenantBoundary(context, hostMode = "local") {
44056
+ return {
44057
+ hostMode,
44058
+ surface: context?.surface ?? "api",
44059
+ tenantId: context?.tenant?.id,
44060
+ required: hostMode === "hosted" && context?.surface !== "cli"
44061
+ };
44062
+ }
44063
+ function assertRuntimeTenantBoundary(context, hostMode = "local", subject = "runtime operation") {
44064
+ const boundary = createRuntimeTenantBoundary(context, hostMode);
44065
+ if (boundary.required && !boundary.tenantId) {
44066
+ throw new RuntimePolicyViolation({
44067
+ code: "tenant_required",
44068
+ subject,
44069
+ tenantId: boundary.tenantId,
44070
+ policyPath: "runtimeContext.tenant.id",
44071
+ message: `Runtime tenant is required for hosted ${boundary.surface} operations.`
44072
+ });
44073
+ }
44074
+ return boundary;
44075
+ }
43991
44076
  function evaluateRuntimeToolPolicy(policy, input) {
43992
44077
  if (policy?.allowedTools && !policy.allowedTools.includes(input.toolName)) {
43993
44078
  return {
@@ -43996,7 +44081,7 @@ function evaluateRuntimeToolPolicy(policy, input) {
43996
44081
  risk: input.risk
43997
44082
  };
43998
44083
  }
43999
- if (policy?.maxToolRisk && riskRank(input.risk) > riskRank(policy.maxToolRisk)) {
44084
+ if (policy?.maxToolRisk && riskRank2(input.risk) > riskRank2(policy.maxToolRisk)) {
44000
44085
  return {
44001
44086
  allowed: false,
44002
44087
  reason: `Runtime policy allows tools up to ${policy.maxToolRisk} risk; ${input.toolName} is ${input.risk}.`,
@@ -44014,7 +44099,7 @@ function evaluateRuntimeToolPolicy(policy, input) {
44014
44099
  return { allowed: true, risk: input.risk };
44015
44100
  }
44016
44101
  function evaluateRuntimeRiskPolicy(policy, input) {
44017
- if (policy?.maxToolRisk && riskRank(input.risk) > riskRank(policy.maxToolRisk)) {
44102
+ if (policy?.maxToolRisk && riskRank2(input.risk) > riskRank2(policy.maxToolRisk)) {
44018
44103
  return {
44019
44104
  allowed: false,
44020
44105
  reason: `Runtime policy allows work up to ${policy.maxToolRisk} risk; ${input.subject} is ${input.risk}.`,
@@ -44031,24 +44116,66 @@ function evaluateRuntimeRiskPolicy(policy, input) {
44031
44116
  }
44032
44117
  return { allowed: true, risk: input.risk };
44033
44118
  }
44119
+ function assertRuntimeTurnWithinPolicy(policy, input) {
44120
+ const maxTurns = policy?.costBudget?.maxTurns;
44121
+ if (maxTurns !== void 0 && input.currentTurns >= maxTurns) {
44122
+ throw new RuntimePolicyViolation({
44123
+ code: "max_turns_exceeded",
44124
+ subject: input.subject,
44125
+ tenantId: input.tenantId,
44126
+ policyPath: "runtimePolicy.costBudget.maxTurns",
44127
+ message: `Runtime policy turn budget exceeded: ${input.currentTurns}/${maxTurns}`
44128
+ });
44129
+ }
44130
+ }
44034
44131
  function assertRuntimeUsageWithinPolicy(policy, usage) {
44035
44132
  const budget = policy?.costBudget;
44133
+ const subject = usage.subject ?? "runtime usage";
44036
44134
  if (!budget) return;
44037
44135
  if (budget.maxInputTokens !== void 0 && (usage.inputTokens ?? 0) > budget.maxInputTokens) {
44038
- throw new Error(
44039
- `Runtime policy input token budget exceeded: ${usage.inputTokens ?? 0}/${budget.maxInputTokens}`
44040
- );
44136
+ throw new RuntimePolicyViolation({
44137
+ code: "input_tokens_exceeded",
44138
+ subject,
44139
+ tenantId: usage.tenantId,
44140
+ policyPath: "runtimePolicy.costBudget.maxInputTokens",
44141
+ message: `Runtime policy input token budget exceeded: ${usage.inputTokens ?? 0}/${budget.maxInputTokens}`
44142
+ });
44041
44143
  }
44042
44144
  if (budget.maxOutputTokens !== void 0 && (usage.outputTokens ?? 0) > budget.maxOutputTokens) {
44043
- throw new Error(
44044
- `Runtime policy output token budget exceeded: ${usage.outputTokens ?? 0}/${budget.maxOutputTokens}`
44045
- );
44145
+ throw new RuntimePolicyViolation({
44146
+ code: "output_tokens_exceeded",
44147
+ subject,
44148
+ tenantId: usage.tenantId,
44149
+ policyPath: "runtimePolicy.costBudget.maxOutputTokens",
44150
+ message: `Runtime policy output token budget exceeded: ${usage.outputTokens ?? 0}/${budget.maxOutputTokens}`
44151
+ });
44152
+ }
44153
+ if (budget.maxEstimatedCostUsd !== void 0 && (usage.estimatedCostUsd ?? 0) > budget.maxEstimatedCostUsd) {
44154
+ throw new RuntimePolicyViolation({
44155
+ code: "estimated_cost_exceeded",
44156
+ subject,
44157
+ tenantId: usage.tenantId,
44158
+ policyPath: "runtimePolicy.costBudget.maxEstimatedCostUsd",
44159
+ message: `Runtime policy estimated cost budget exceeded: ${usage.estimatedCostUsd ?? 0}/${budget.maxEstimatedCostUsd}`
44160
+ });
44046
44161
  }
44047
44162
  }
44163
+ function createRetentionCutoffs(policy, now = /* @__PURE__ */ new Date()) {
44164
+ const retention = policy?.retention;
44165
+ return {
44166
+ conversationBefore: cutoffIso(now, retention?.conversationDays),
44167
+ eventBefore: cutoffIso(now, retention?.eventDays),
44168
+ artifactBefore: cutoffIso(now, retention?.artifactDays)
44169
+ };
44170
+ }
44048
44171
  function cloneRuntimePolicy(policy) {
44049
44172
  return mergeRuntimePolicy(void 0, policy) ?? {};
44050
44173
  }
44051
- function riskRank(risk) {
44174
+ function cutoffIso(now, days) {
44175
+ if (days === void 0) return void 0;
44176
+ return new Date(now.getTime() - days * 24 * 60 * 60 * 1e3).toISOString();
44177
+ }
44178
+ function riskRank2(risk) {
44052
44179
  switch (risk) {
44053
44180
  case "read-only":
44054
44181
  return 0;
@@ -58697,6 +58824,7 @@ init_providers();
58697
58824
 
58698
58825
  // src/runtime/agent-runtime.ts
58699
58826
  init_env();
58827
+ init_pricing();
58700
58828
  init_registry4();
58701
58829
 
58702
58830
  // src/runtime/default-turn-runner.ts
@@ -58815,6 +58943,206 @@ function createRuntimeSessionStore() {
58815
58943
  return new InMemoryRuntimeSessionStore();
58816
58944
  }
58817
58945
 
58946
+ // src/runtime/agent-runner.ts
58947
+ var AgentRunner = class {
58948
+ constructor(options = {}) {
58949
+ this.options = options;
58950
+ }
58951
+ options;
58952
+ async run(input) {
58953
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
58954
+ const trace = input.trace ?? createAgentTraceContext({ taskId: input.task.id });
58955
+ this.options.eventLog?.record("agent.started", {
58956
+ taskId: input.task.id,
58957
+ role: input.task.role,
58958
+ trace
58959
+ });
58960
+ try {
58961
+ const raw = await (this.options.executor ?? defaultExecutor)({
58962
+ task: input.task,
58963
+ capability: input.capability,
58964
+ trace,
58965
+ assertToolAllowed: (toolName) => {
58966
+ const decision = evaluateAgentToolPolicy({
58967
+ capability: input.capability,
58968
+ toolName,
58969
+ manifest: input.toolRiskManifest
58970
+ });
58971
+ this.options.eventLog?.record("agent.tool.called", {
58972
+ taskId: input.task.id,
58973
+ role: input.task.role,
58974
+ toolName,
58975
+ decision,
58976
+ trace
58977
+ });
58978
+ if (!decision.allowed) {
58979
+ throw new Error(decision.reason ?? `Tool '${toolName}' is not allowed.`);
58980
+ }
58981
+ }
58982
+ });
58983
+ const result = normalizeAgentRunResult({
58984
+ id: `${input.task.id}-run-${Date.now().toString(36)}`,
58985
+ taskId: input.task.id,
58986
+ role: input.task.role,
58987
+ success: raw.success ?? true,
58988
+ output: raw.output,
58989
+ turns: raw.turns,
58990
+ toolsUsed: raw.toolsUsed,
58991
+ usage: {
58992
+ inputTokens: raw.inputTokens ?? 0,
58993
+ outputTokens: raw.outputTokens ?? 0,
58994
+ estimated: raw.inputTokens === void 0 || raw.outputTokens === void 0
58995
+ },
58996
+ startedAt,
58997
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
58998
+ durationMs: Date.now() - Date.parse(startedAt),
58999
+ error: raw.error,
59000
+ metadata: { ...raw.metadata, trace }
59001
+ });
59002
+ this.options.eventLog?.record(result.success ? "agent.completed" : "agent.failed", {
59003
+ taskId: input.task.id,
59004
+ role: input.task.role,
59005
+ agentRunId: result.id,
59006
+ trace,
59007
+ error: result.error
59008
+ });
59009
+ return result;
59010
+ } catch (error) {
59011
+ const message = error instanceof Error ? error.message : String(error);
59012
+ const result = normalizeAgentRunResult({
59013
+ id: `${input.task.id}-run-${Date.now().toString(36)}`,
59014
+ taskId: input.task.id,
59015
+ role: input.task.role,
59016
+ success: false,
59017
+ output: message,
59018
+ startedAt,
59019
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
59020
+ durationMs: Date.now() - Date.parse(startedAt),
59021
+ error: message,
59022
+ metadata: { trace }
59023
+ });
59024
+ this.options.eventLog?.record("agent.failed", {
59025
+ taskId: input.task.id,
59026
+ role: input.task.role,
59027
+ agentRunId: result.id,
59028
+ trace,
59029
+ error: message
59030
+ });
59031
+ return result;
59032
+ }
59033
+ }
59034
+ };
59035
+ async function defaultExecutor(context) {
59036
+ return {
59037
+ output: `Agent ${context.capability.role} accepted task '${context.task.objective}'.`
59038
+ };
59039
+ }
59040
+
59041
+ // src/runtime/runtime-agent-node-executor.ts
59042
+ var RuntimeAgentNodeExecutor = class {
59043
+ constructor(options) {
59044
+ this.options = options;
59045
+ this.runner = options.runner ?? new AgentRunner(options.runnerOptions);
59046
+ }
59047
+ options;
59048
+ runner;
59049
+ execute = async (execution) => {
59050
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
59051
+ const definition = this.options.registry.getByRole(execution.task.role);
59052
+ if (!definition) {
59053
+ return normalizeAgentRunResult({
59054
+ id: `${execution.workflowRunId}-${execution.node.id}-missing-definition`,
59055
+ taskId: execution.task.id,
59056
+ role: execution.task.role,
59057
+ success: false,
59058
+ output: "",
59059
+ startedAt,
59060
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
59061
+ error: `No agent definition registered for role '${execution.task.role}'.`,
59062
+ metadata: {
59063
+ workflowRunId: execution.workflowRunId,
59064
+ nodeId: execution.node.id,
59065
+ trace: execution.trace
59066
+ }
59067
+ });
59068
+ }
59069
+ const blockedTool = this.findBlockedTool(definition, execution);
59070
+ if (blockedTool) {
59071
+ return normalizeAgentRunResult({
59072
+ id: `${execution.workflowRunId}-${execution.node.id}-policy-blocked`,
59073
+ taskId: execution.task.id,
59074
+ role: execution.task.role,
59075
+ success: false,
59076
+ output: "",
59077
+ startedAt,
59078
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
59079
+ error: blockedTool,
59080
+ metadata: {
59081
+ workflowRunId: execution.workflowRunId,
59082
+ nodeId: execution.node.id,
59083
+ agentDefinitionId: definition.id,
59084
+ trace: execution.trace
59085
+ }
59086
+ });
59087
+ }
59088
+ const input = {
59089
+ task: {
59090
+ ...execution.task,
59091
+ context: {
59092
+ ...execution.task.context,
59093
+ instructions: definition.instructions,
59094
+ sharedState: execution.sharedState.readForRole(definition.role)
59095
+ }
59096
+ },
59097
+ capability: definition.capability,
59098
+ trace: execution.trace,
59099
+ toolRiskManifest: this.options.toolRiskManifest
59100
+ };
59101
+ const result = await this.runner.run(input);
59102
+ return normalizeAgentRunResult({
59103
+ ...result,
59104
+ metadata: {
59105
+ ...result.metadata,
59106
+ workflowRunId: execution.workflowRunId,
59107
+ nodeId: execution.node.id,
59108
+ agentDefinitionId: definition.id
59109
+ }
59110
+ });
59111
+ };
59112
+ findBlockedTool(definition, execution) {
59113
+ for (const toolName of execution.node.requiredTools ?? []) {
59114
+ const agentDecision = evaluateAgentToolPolicy({
59115
+ capability: definition.capability,
59116
+ toolName,
59117
+ manifest: this.options.toolRiskManifest
59118
+ });
59119
+ execution.eventLog.record("agent.tool.called", {
59120
+ workflowRunId: execution.workflowRunId,
59121
+ nodeId: execution.node.id,
59122
+ taskId: execution.task.id,
59123
+ role: execution.task.role,
59124
+ toolName,
59125
+ decision: agentDecision,
59126
+ trace: execution.trace
59127
+ });
59128
+ if (!agentDecision.allowed) {
59129
+ return agentDecision.reason ?? `Tool '${toolName}' is not allowed for agent.`;
59130
+ }
59131
+ const runtimeDecision = evaluateRuntimeToolPolicy(this.options.runtimePolicy, {
59132
+ toolName,
59133
+ risk: agentDecision.risk
59134
+ });
59135
+ if (!runtimeDecision.allowed) {
59136
+ return runtimeDecision.reason ?? `Tool '${toolName}' is blocked by runtime policy.`;
59137
+ }
59138
+ }
59139
+ return void 0;
59140
+ }
59141
+ };
59142
+ function createRuntimeAgentNodeExecutor(options) {
59143
+ return new RuntimeAgentNodeExecutor(options).execute;
59144
+ }
59145
+
58818
59146
  // src/runtime/workflow-registry.ts
58819
59147
  function cloneWorkflow(workflow) {
58820
59148
  return {
@@ -59144,14 +59472,22 @@ var WorkflowEngine = class {
59144
59472
  this.catalog = catalog;
59145
59473
  this.eventLog = eventLog;
59146
59474
  this.sharedState = options.sharedState ?? new InMemorySharedWorkspaceStore();
59147
- this.nodeExecutor = options.nodeExecutor;
59148
59475
  this.runtimePolicy = options.runtimePolicy;
59476
+ this.runtimeContext = options.runtimeContext;
59477
+ this.runtimeHostMode = options.runtimeHostMode ?? "local";
59478
+ this.nodeExecutor = options.nodeExecutor ?? (options.agentDefinitionRegistry ? createRuntimeAgentNodeExecutor({
59479
+ ...options.agentNodeExecutorOptions,
59480
+ registry: options.agentDefinitionRegistry,
59481
+ runtimePolicy: options.runtimePolicy
59482
+ }) : void 0);
59149
59483
  }
59150
59484
  catalog;
59151
59485
  eventLog;
59152
59486
  handlers = /* @__PURE__ */ new Map();
59153
59487
  sharedState;
59154
59488
  runtimePolicy;
59489
+ runtimeContext;
59490
+ runtimeHostMode;
59155
59491
  nodeExecutor;
59156
59492
  registerHandler(workflowId, handler) {
59157
59493
  if (!this.catalog.get(workflowId)) {
@@ -59166,6 +59502,7 @@ var WorkflowEngine = class {
59166
59502
  return this.catalog.createPlan(workflowId, input, this.eventLog);
59167
59503
  }
59168
59504
  async run(request) {
59505
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "workflow.run");
59169
59506
  const workflow = this.catalog.get(request.workflowId);
59170
59507
  if (!workflow) {
59171
59508
  throw new Error(`Unknown workflow: ${request.workflowId}`);
@@ -59285,7 +59622,14 @@ var AgentRuntime = class {
59285
59622
  this.model = options.model ?? options.providerConfig?.model ?? getDefaultModel(options.providerType);
59286
59623
  this.runtimeContext = options.runtimeContext ? createRuntimeRequestContext(options.runtimeContext) : void 0;
59287
59624
  this.runtimePolicy = mergeRuntimePolicy(this.runtimeContext?.policy, options.runtimePolicy);
59288
- this.workflowEngine = options.workflowEngine ?? createWorkflowEngine(void 0, this.eventLog, { runtimePolicy: this.runtimePolicy });
59625
+ this.runtimeHostMode = options.runtimeHostMode ?? "local";
59626
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "runtime.initialize");
59627
+ this.workflowEngine = options.workflowEngine ?? createWorkflowEngine(void 0, this.eventLog, {
59628
+ runtimePolicy: this.runtimePolicy,
59629
+ runtimeContext: this.runtimeContext,
59630
+ runtimeHostMode: this.runtimeHostMode,
59631
+ agentDefinitionRegistry: options.agentDefinitionRegistry
59632
+ });
59289
59633
  }
59290
59634
  options;
59291
59635
  providerRegistry;
@@ -59301,6 +59645,9 @@ var AgentRuntime = class {
59301
59645
  provider;
59302
59646
  runtimeContext;
59303
59647
  runtimePolicy;
59648
+ runtimeHostMode;
59649
+ requestTimestampsBySubject = /* @__PURE__ */ new Map();
59650
+ activeRuns = 0;
59304
59651
  async initialize() {
59305
59652
  const providerInjected = Boolean(this.options.provider);
59306
59653
  const provider = this.options.provider ?? await this.providerRegistry.createProvider(this.providerType, {
@@ -59349,10 +59696,12 @@ var AgentRuntime = class {
59349
59696
  },
59350
59697
  modes: listAgentModes(),
59351
59698
  context: this.runtimeContext,
59352
- policy: this.runtimePolicy
59699
+ policy: this.runtimePolicy,
59700
+ hostMode: this.runtimeHostMode
59353
59701
  };
59354
59702
  }
59355
59703
  createSession(options = {}) {
59704
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "session.create");
59356
59705
  const session = this.runtimeSessionStore.create({
59357
59706
  ...options,
59358
59707
  metadata: {
@@ -59376,6 +59725,27 @@ var AgentRuntime = class {
59376
59725
  listSessions() {
59377
59726
  return this.runtimeSessionStore.list();
59378
59727
  }
59728
+ cleanupRetention(options = {}) {
59729
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "retention.cleanup");
59730
+ const dryRun = options.dryRun ?? true;
59731
+ const cutoffs = createRetentionCutoffs(this.runtimePolicy, options.now);
59732
+ const expiredSessionIds = cutoffs.conversationBefore ? this.runtimeSessionStore.list().filter((session) => session.updatedAt < cutoffs.conversationBefore).map((session) => session.id) : [];
59733
+ const deletedSessionIds = dryRun ? [] : expiredSessionIds.filter((id) => this.runtimeSessionStore.delete(id));
59734
+ this.eventLog.record("retention.cleanup", {
59735
+ dryRun,
59736
+ cutoffs,
59737
+ expiredSessionIds,
59738
+ deletedSessionIds,
59739
+ tenantId: this.runtimeContext?.tenant?.id,
59740
+ runtimeApi: true
59741
+ });
59742
+ return {
59743
+ dryRun,
59744
+ cutoffs,
59745
+ expiredSessionIds,
59746
+ deletedSessionIds
59747
+ };
59748
+ }
59379
59749
  async runTurn(input) {
59380
59750
  const provider = this.provider;
59381
59751
  if (!provider) {
@@ -59386,6 +59756,12 @@ var AgentRuntime = class {
59386
59756
  throw new Error(`Runtime session not found: ${input.sessionId}`);
59387
59757
  }
59388
59758
  const effectiveSession = input.mode && input.mode !== session.mode ? { ...session, mode: input.mode } : session;
59759
+ assertRuntimeTurnWithinPolicy(this.runtimePolicy, {
59760
+ subject: "turn.run",
59761
+ currentTurns: countUserTurns(effectiveSession),
59762
+ tenantId: this.runtimeContext?.tenant?.id
59763
+ });
59764
+ const releaseRuntimeRequest = this.beginRuntimeRequest("turn.run");
59389
59765
  this.eventLog.record("turn.started", {
59390
59766
  sessionId: effectiveSession.id,
59391
59767
  provider: this.providerType,
@@ -59402,7 +59778,13 @@ var AgentRuntime = class {
59402
59778
  permissionPolicy: this.permissionPolicy,
59403
59779
  eventLog: this.eventLog
59404
59780
  });
59405
- assertRuntimeUsageWithinPolicy(this.runtimePolicy, result.usage);
59781
+ const estimatedCostUsd = this.estimateTurnCost(result);
59782
+ assertRuntimeUsageWithinPolicy(this.runtimePolicy, {
59783
+ ...result.usage,
59784
+ estimatedCostUsd,
59785
+ tenantId: this.runtimeContext?.tenant?.id,
59786
+ subject: "turn.run"
59787
+ });
59406
59788
  const updatedSession = this.runtimeSessionStore.update({
59407
59789
  ...effectiveSession,
59408
59790
  messages: [
@@ -59419,6 +59801,7 @@ var AgentRuntime = class {
59419
59801
  sessionId: updatedSession.id,
59420
59802
  inputTokens: result.usage.inputTokens,
59421
59803
  outputTokens: result.usage.outputTokens,
59804
+ estimatedCostUsd,
59422
59805
  model: result.model,
59423
59806
  runtimeApi: true
59424
59807
  });
@@ -59430,6 +59813,8 @@ var AgentRuntime = class {
59430
59813
  runtimeApi: true
59431
59814
  });
59432
59815
  throw error;
59816
+ } finally {
59817
+ releaseRuntimeRequest();
59433
59818
  }
59434
59819
  }
59435
59820
  async *streamTurn(input) {
@@ -59442,6 +59827,12 @@ var AgentRuntime = class {
59442
59827
  throw new Error(`Runtime session not found: ${input.sessionId}`);
59443
59828
  }
59444
59829
  const effectiveSession = input.mode && input.mode !== session.mode ? { ...session, mode: input.mode } : session;
59830
+ assertRuntimeTurnWithinPolicy(this.runtimePolicy, {
59831
+ subject: "turn.stream",
59832
+ currentTurns: countUserTurns(effectiveSession),
59833
+ tenantId: this.runtimeContext?.tenant?.id
59834
+ });
59835
+ const releaseRuntimeRequest = this.beginRuntimeRequest("turn.stream");
59445
59836
  const messages = [
59446
59837
  ...effectiveSession.messages,
59447
59838
  {
@@ -59491,7 +59882,13 @@ var AgentRuntime = class {
59491
59882
  model: input.options?.model ?? this.getModel(),
59492
59883
  mode: effectiveSession.mode
59493
59884
  };
59494
- assertRuntimeUsageWithinPolicy(this.runtimePolicy, result.usage);
59885
+ const estimatedCostUsd = this.estimateTurnCost(result);
59886
+ assertRuntimeUsageWithinPolicy(this.runtimePolicy, {
59887
+ ...result.usage,
59888
+ estimatedCostUsd,
59889
+ tenantId: this.runtimeContext?.tenant?.id,
59890
+ subject: "turn.stream"
59891
+ });
59495
59892
  const updatedSession = this.runtimeSessionStore.update({
59496
59893
  ...effectiveSession,
59497
59894
  messages: [
@@ -59510,6 +59907,7 @@ var AgentRuntime = class {
59510
59907
  sessionId: updatedSession.id,
59511
59908
  inputTokens: result.usage.inputTokens,
59512
59909
  outputTokens: result.usage.outputTokens,
59910
+ estimatedCostUsd,
59513
59911
  model: result.model,
59514
59912
  streaming: true,
59515
59913
  runtimeApi: true
@@ -59539,9 +59937,11 @@ var AgentRuntime = class {
59539
59937
  runtimeApi: true
59540
59938
  });
59541
59939
  }
59940
+ releaseRuntimeRequest();
59542
59941
  }
59543
59942
  }
59544
59943
  async executeTool(input) {
59944
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "tool.execute");
59545
59945
  const startedAt = performance.now();
59546
59946
  const session = input.sessionId ? this.getSession(input.sessionId) : void 0;
59547
59947
  if (input.sessionId && !session) {
@@ -59652,6 +60052,7 @@ var AgentRuntime = class {
59652
60052
  };
59653
60053
  }
59654
60054
  assertToolAllowed(mode, toolName, input) {
60055
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, "tool.assertAllowed");
59655
60056
  const tool = this.toolRegistry.get(toolName);
59656
60057
  if (!tool) {
59657
60058
  this.eventLog.record("tool.blocked", {
@@ -59681,7 +60082,65 @@ var AgentRuntime = class {
59681
60082
  });
59682
60083
  return allowed;
59683
60084
  }
60085
+ beginRuntimeRequest(subject) {
60086
+ assertRuntimeTenantBoundary(this.runtimeContext, this.runtimeHostMode, subject);
60087
+ this.assertWithinRateLimit(subject);
60088
+ this.assertWithinConcurrencyLimit(subject);
60089
+ this.activeRuns += 1;
60090
+ let released = false;
60091
+ return () => {
60092
+ if (released) return;
60093
+ released = true;
60094
+ this.activeRuns = Math.max(0, this.activeRuns - 1);
60095
+ };
60096
+ }
60097
+ assertWithinRateLimit(subject) {
60098
+ const maxRequestsPerMinute = this.runtimePolicy?.rateLimit?.maxRequestsPerMinute;
60099
+ if (maxRequestsPerMinute === void 0) return;
60100
+ const now = Date.now();
60101
+ const windowStart = now - 6e4;
60102
+ const key = `${this.runtimeContext?.tenant?.id ?? "global"}:${subject}`;
60103
+ const recent = (this.requestTimestampsBySubject.get(key) ?? []).filter(
60104
+ (timestamp) => timestamp > windowStart
60105
+ );
60106
+ if (recent.length >= maxRequestsPerMinute) {
60107
+ this.requestTimestampsBySubject.set(key, recent);
60108
+ throw new RuntimePolicyViolation({
60109
+ code: "rate_limit_exceeded",
60110
+ subject,
60111
+ tenantId: this.runtimeContext?.tenant?.id,
60112
+ policyPath: "runtimePolicy.rateLimit.maxRequestsPerMinute",
60113
+ message: `Runtime policy rate limit exceeded: ${recent.length}/${maxRequestsPerMinute} requests per minute.`
60114
+ });
60115
+ }
60116
+ recent.push(now);
60117
+ this.requestTimestampsBySubject.set(key, recent);
60118
+ }
60119
+ assertWithinConcurrencyLimit(subject) {
60120
+ const maxConcurrentRuns = this.runtimePolicy?.rateLimit?.maxConcurrentRuns;
60121
+ if (maxConcurrentRuns === void 0) return;
60122
+ if (this.activeRuns >= maxConcurrentRuns) {
60123
+ throw new RuntimePolicyViolation({
60124
+ code: "concurrency_limit_exceeded",
60125
+ subject,
60126
+ tenantId: this.runtimeContext?.tenant?.id,
60127
+ policyPath: "runtimePolicy.rateLimit.maxConcurrentRuns",
60128
+ message: `Runtime policy concurrency limit exceeded: ${this.activeRuns}/${maxConcurrentRuns} active runs.`
60129
+ });
60130
+ }
60131
+ }
60132
+ estimateTurnCost(result) {
60133
+ return estimateCost(
60134
+ result.model,
60135
+ result.usage.inputTokens,
60136
+ result.usage.outputTokens,
60137
+ this.providerType
60138
+ ).totalCost;
60139
+ }
59684
60140
  };
60141
+ function countUserTurns(session) {
60142
+ return session.messages.filter((message) => message.role === "user").length;
60143
+ }
59685
60144
  async function createAgentRuntime(options) {
59686
60145
  const runtime = new AgentRuntime(options);
59687
60146
  await runtime.initialize();