@opengeni/worker-bundle 1.0.0-canary.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/activities/agent-run-admission.d.ts +3 -1
  2. package/dist/activities/agent-turn/admission.d.ts +24 -3
  3. package/dist/activities/agent-turn/agent-build.d.ts +4 -0
  4. package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
  5. package/dist/activities/agent-turn/code-search.d.ts +60 -0
  6. package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
  7. package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
  8. package/dist/activities/agent-turn/codex.d.ts +3 -2
  9. package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
  10. package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
  11. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  12. package/dist/activities/agent-turn/errors.d.ts +82 -11
  13. package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
  14. package/dist/activities/agent-turn/final-reply.d.ts +18 -0
  15. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  16. package/dist/activities/agent-turn/finalization.d.ts +4 -2
  17. package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
  18. package/dist/activities/agent-turn/governance-model.d.ts +9 -2
  19. package/dist/activities/agent-turn/history-sink.d.ts +2 -0
  20. package/dist/activities/agent-turn/history.d.ts +2 -0
  21. package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
  22. package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
  23. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  24. package/dist/activities/agent-turn/model-usage.d.ts +6 -0
  25. package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
  26. package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
  27. package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
  28. package/dist/activities/agent-turn/quiescence.d.ts +7 -7
  29. package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
  30. package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
  31. package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
  32. package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
  33. package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
  34. package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
  35. package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
  36. package/dist/activities/agent-turn/session-title.d.ts +34 -2
  37. package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
  38. package/dist/activities/agent-turn/skill-read.d.ts +70 -2
  39. package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
  40. package/dist/activities/agent-turn/skill-search.d.ts +3 -1
  41. package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
  42. package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
  43. package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
  44. package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
  45. package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
  46. package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
  47. package/dist/activities/agent-turn/turn-context.d.ts +29 -0
  48. package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
  49. package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
  50. package/dist/activities/automations.d.ts +3 -1
  51. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  52. package/dist/activities/codemode-dispatcher.d.ts +9 -2
  53. package/dist/activities/codex-capacity.d.ts +18 -2
  54. package/dist/activities/codex-rotation.d.ts +7 -1
  55. package/dist/activities/context-compaction.d.ts +16 -2
  56. package/dist/activities/gmail-files.d.ts +32 -0
  57. package/dist/activities/goals.d.ts +8 -0
  58. package/dist/activities/knowledge-indexing.d.ts +19 -0
  59. package/dist/activities/mcp-account-routes.d.ts +3 -0
  60. package/dist/activities/mcp-credentials.d.ts +12 -13
  61. package/dist/activities/mcp-operation-authority.d.ts +0 -5
  62. package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
  63. package/dist/activities/open-suffix-resume.d.ts +13 -0
  64. package/dist/activities/parent-wake.d.ts +6 -12
  65. package/dist/activities/retained-screenshots.d.ts +7 -2
  66. package/dist/activities/rig-verification.d.ts +2 -3
  67. package/dist/activities/run-credential-renewal.d.ts +1 -0
  68. package/dist/activities/run-credentials.d.ts +15 -3
  69. package/dist/activities/sandbox-images.d.ts +1 -1
  70. package/dist/activities/sandbox-lease.d.ts +47 -9
  71. package/dist/activities/scheduled-tasks.d.ts +4 -35
  72. package/dist/activities/session-state.d.ts +70 -3
  73. package/dist/activities/types.d.ts +55 -6
  74. package/dist/activities/workspace-capture.d.ts +2 -0
  75. package/dist/activities/workspace-credential-provider.d.ts +46 -0
  76. package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
  77. package/dist/activities-control-BNPTRTOE.js.map +1 -0
  78. package/dist/activities-control.d.ts +182 -4
  79. package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
  80. package/dist/activities-turn-5LGZOSVU.js.map +1 -0
  81. package/dist/activities.d.ts +182 -4
  82. package/dist/artifact-materializer-entry.js +5 -2
  83. package/dist/artifact-materializer-entry.js.map +1 -1
  84. package/dist/artifact-outbox-entry.js +5 -2
  85. package/dist/artifact-outbox-entry.js.map +1 -1
  86. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  87. package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
  88. package/dist/chunk-3ZTRWDMN.js.map +1 -0
  89. package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
  90. package/dist/chunk-4ER3YXOT.js.map +1 -0
  91. package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
  92. package/dist/chunk-LJX7Z2KP.js.map +1 -0
  93. package/dist/editable-artifact-materializer-service.d.ts +2 -2
  94. package/dist/editable-artifact-outbox-service.d.ts +2 -2
  95. package/dist/host-export-pump.d.ts +3 -1
  96. package/dist/host-export.d.ts +1 -1
  97. package/dist/host-export.js +1 -1
  98. package/dist/http.d.ts +1 -1
  99. package/dist/index.d.ts +95 -3
  100. package/dist/index.js +137 -43
  101. package/dist/index.js.map +1 -1
  102. package/dist/observability-metrics.d.ts +118 -8
  103. package/dist/retained-process-diagnostics.d.ts +4 -0
  104. package/dist/retained-process-retry.d.ts +7 -0
  105. package/dist/sandbox-resume.d.ts +55 -13
  106. package/dist/sandbox-routing.d.ts +5 -3
  107. package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
  108. package/dist/session-event-fanout.d.ts +6 -0
  109. package/dist/warm-capture-holder.d.ts +3 -0
  110. package/dist/worker-service-lifecycle.d.ts +12 -0
  111. package/dist/workflow-bundle.js +273 -41
  112. package/dist/workflows/activities.d.ts +1 -1
  113. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  114. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  115. package/dist/workflows/session.d.ts +36 -0
  116. package/dist/workflows.d.ts +1 -0
  117. package/package.json +21 -19
  118. package/src/activities/agent-run-admission.ts +17 -1
  119. package/src/activities/agent-turn/admission.ts +66 -3
  120. package/src/activities/agent-turn/agent-build.ts +84 -10
  121. package/src/activities/agent-turn/claim.ts +186 -43
  122. package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
  123. package/src/activities/agent-turn/code-search.ts +275 -0
  124. package/src/activities/agent-turn/codex-capacity.ts +172 -8
  125. package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
  126. package/src/activities/agent-turn/codex.ts +13 -5
  127. package/src/activities/agent-turn/compaction-prep.ts +170 -102
  128. package/src/activities/agent-turn/credential-leases.ts +122 -216
  129. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  130. package/src/activities/agent-turn/errors.ts +647 -48
  131. package/src/activities/agent-turn/failure-settlement.ts +682 -130
  132. package/src/activities/agent-turn/final-reply.ts +46 -0
  133. package/src/activities/agent-turn/finalization-monitor.ts +120 -0
  134. package/src/activities/agent-turn/finalization.ts +146 -43
  135. package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
  136. package/src/activities/agent-turn/governance-model.ts +69 -28
  137. package/src/activities/agent-turn/history-sink.ts +16 -2
  138. package/src/activities/agent-turn/history.ts +38 -11
  139. package/src/activities/agent-turn/input-wait-reply.ts +103 -0
  140. package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
  141. package/src/activities/agent-turn/media-artifacts.ts +2 -1
  142. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  143. package/src/activities/agent-turn/model-usage.ts +64 -3
  144. package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
  145. package/src/activities/agent-turn/optional-repositories.ts +224 -0
  146. package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
  147. package/src/activities/agent-turn/quiescence.ts +10 -7
  148. package/src/activities/agent-turn/recovery-warning.ts +16 -0
  149. package/src/activities/agent-turn/refresh-credentials.ts +44 -0
  150. package/src/activities/agent-turn/run-credentials.ts +116 -55
  151. package/src/activities/agent-turn/run.ts +320 -93
  152. package/src/activities/agent-turn/sandbox-establish.ts +84 -8
  153. package/src/activities/agent-turn/sandbox-provision.ts +3 -1
  154. package/src/activities/agent-turn/sandbox-route.ts +4 -21
  155. package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
  156. package/src/activities/agent-turn/session-title.ts +81 -5
  157. package/src/activities/agent-turn/skill-checkout.ts +111 -24
  158. package/src/activities/agent-turn/skill-install.ts +1 -1
  159. package/src/activities/agent-turn/skill-read.ts +374 -28
  160. package/src/activities/agent-turn/skill-remove.ts +46 -0
  161. package/src/activities/agent-turn/skill-save.ts +1 -1
  162. package/src/activities/agent-turn/skill-search.ts +10 -1
  163. package/src/activities/agent-turn/skill-selection.ts +4 -0
  164. package/src/activities/agent-turn/skill-tools.ts +112 -18
  165. package/src/activities/agent-turn/skill-transfer.ts +68 -23
  166. package/src/activities/agent-turn/stream-attempt.ts +313 -77
  167. package/src/activities/agent-turn/subscription-lease.ts +115 -0
  168. package/src/activities/agent-turn/tool-environment.ts +316 -167
  169. package/src/activities/agent-turn/tool-policy.ts +80 -1
  170. package/src/activities/agent-turn/turn-context.ts +35 -0
  171. package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
  172. package/src/activities/agent-turn/xai-capacity.ts +94 -51
  173. package/src/activities/automations.ts +76 -4
  174. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  175. package/src/activities/capabilities.ts +108 -11
  176. package/src/activities/codemode-dispatcher.ts +36 -8
  177. package/src/activities/codex-capacity.ts +60 -25
  178. package/src/activities/codex-rotation.ts +39 -17
  179. package/src/activities/context-compaction.ts +75 -18
  180. package/src/activities/gmail-files.ts +88 -0
  181. package/src/activities/goals.ts +116 -121
  182. package/src/activities/google-drive-publication.ts +1 -3
  183. package/src/activities/image-generation-operation.ts +1 -1
  184. package/src/activities/knowledge-indexing.ts +269 -15
  185. package/src/activities/knowledge-source-sync.ts +10 -0
  186. package/src/activities/mcp-account-routes.ts +17 -0
  187. package/src/activities/mcp-credentials.ts +83 -194
  188. package/src/activities/mcp-operation-authority.ts +16 -41
  189. package/src/activities/model-call-fact-reconciler.ts +112 -0
  190. package/src/activities/open-suffix-resume.ts +15 -2
  191. package/src/activities/parent-wake.ts +46 -21
  192. package/src/activities/retained-screenshots.ts +82 -11
  193. package/src/activities/rig-verification.ts +38 -37
  194. package/src/activities/run-credential-renewal.ts +13 -1
  195. package/src/activities/run-credentials.ts +59 -17
  196. package/src/activities/run-input.ts +25 -16
  197. package/src/activities/sandbox-images.ts +0 -1
  198. package/src/activities/sandbox-lease.ts +798 -82
  199. package/src/activities/scheduled-tasks.ts +550 -178
  200. package/src/activities/session-state.ts +245 -6
  201. package/src/activities/site-auth-maintenance.ts +3 -0
  202. package/src/activities/types.ts +72 -5
  203. package/src/activities/workflow-wake.ts +2 -17
  204. package/src/activities/workspace-capture.ts +13 -2
  205. package/src/activities/workspace-credential-provider.ts +283 -0
  206. package/src/activities-control.ts +4 -0
  207. package/src/activities-turn.ts +5 -0
  208. package/src/activities.ts +13 -0
  209. package/src/activity-services.ts +16 -4
  210. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  211. package/src/editable-artifact-hint-broker.ts +3 -0
  212. package/src/editable-artifact-materializer-service.ts +7 -3
  213. package/src/editable-artifact-outbox-service.ts +4 -4
  214. package/src/github-rest-mcp.ts +1 -2
  215. package/src/host-export-pump.ts +26 -4
  216. package/src/host-export.ts +3 -0
  217. package/src/http.ts +4 -2
  218. package/src/index.ts +99 -37
  219. package/src/observability-metrics.ts +510 -5
  220. package/src/personal-github-git-credentials.ts +2 -1
  221. package/src/pr-review-credentials.ts +1 -3
  222. package/src/retained-process-diagnostics.ts +50 -0
  223. package/src/retained-process-retry.ts +36 -0
  224. package/src/sandbox-resume.ts +388 -101
  225. package/src/sandbox-routing.ts +150 -25
  226. package/src/sandbox-snapshot-diagnostics.ts +112 -0
  227. package/src/session-event-fanout.ts +23 -0
  228. package/src/warm-capture-holder.ts +15 -0
  229. package/src/worker-service-lifecycle.ts +53 -0
  230. package/src/workflows/activities.ts +16 -1
  231. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  232. package/src/workflows/file-upload-reaper.ts +2 -0
  233. package/src/workflows/scheduled-tasks.ts +26 -13
  234. package/src/workflows/session.ts +242 -22
  235. package/src/workflows.ts +4 -0
  236. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  237. package/dist/activities/packs.d.ts +0 -55
  238. package/dist/activities-control-HMFGSKC5.js.map +0 -1
  239. package/dist/activities-turn-AP67JADS.js.map +0 -1
  240. package/dist/chunk-A4XQWVZK.js.map +0 -1
  241. package/dist/chunk-CYITU5UH.js.map +0 -1
  242. package/dist/chunk-RAMNYSAW.js.map +0 -1
  243. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
  244. package/src/activities/packs.ts +0 -166
@@ -16,7 +16,9 @@ import {
16
16
  import {
17
17
  calculateGatewayReportedCostBreakdown,
18
18
  calculateGatewayReportedProviderCostMicros,
19
+ calculateModelListUsageCostSnapshot,
19
20
  calculateModelUsageCostBreakdown,
21
+ configuredModelListPricingSchedules,
20
22
  configuredModelPricingSchedules,
21
23
  resolveModelProvider,
22
24
  responseSatisfiesLatencyMode,
@@ -39,6 +41,7 @@ import {
39
41
  type ModelContextContributionSummary,
40
42
  type SessionEvent,
41
43
  } from "@opengeni/contracts";
44
+ import type { InsightsUsageClassMicros } from "@opengeni/contracts/insights-usage";
42
45
  import { safeErrorDiagnostic } from "./errors";
43
46
 
44
47
  export function modelUsageSourceKey(input: {
@@ -293,6 +296,7 @@ export async function processModelResponseTerminalEvent(input: {
293
296
  usage: responseUsage,
294
297
  normalizedUsage,
295
298
  ...(billing ? { billingPath: billing.billingPath } : {}),
299
+ ...(billing ? { billingSnapshot: billing } : {}),
296
300
  ...(billing?.upstreamProvider ? { upstreamProvider: billing.upstreamProvider } : {}),
297
301
  servingAccountHash: accountContext.servingAccountHash,
298
302
  accountChangedFromPrevCall: accountContext.accountChangedFromPrevCall,
@@ -439,6 +443,7 @@ export async function processCompactionModelUsageEvent(input: {
439
443
  usage: input.usage,
440
444
  normalizedUsage,
441
445
  ...(billing ? { billingPath: billing.billingPath } : {}),
446
+ ...(billing ? { billingSnapshot: billing } : {}),
442
447
  ...(billing?.upstreamProvider ? { upstreamProvider: billing.upstreamProvider } : {}),
443
448
  servingAccountHash: accountContext.servingAccountHash,
444
449
  accountChangedFromPrevCall: accountContext.accountChangedFromPrevCall,
@@ -492,6 +497,16 @@ export async function emitModelCallUsage(input: {
492
497
  normalizedUsage?: ModelCallUsageNormalization;
493
498
  /** Accepted billing authority persisted for Insights repair after a soft fact-write failure. */
494
499
  billingPath?: ModelUsageBillingRecord["billingPath"];
500
+ /** Accepted comparisons are frozen with the event, not repriced during fact repair. */
501
+ billingSnapshot?: Pick<
502
+ ModelUsageBillingRecord,
503
+ | "pricedCostMicros"
504
+ | "estimatedProviderCostMicros"
505
+ | "equivalentCreditCostMicros"
506
+ | "pricingSource"
507
+ | "listByClassMicros"
508
+ | "listByClassApprox"
509
+ >;
495
510
  /** Validated Gateway endpoint provider persisted for exact Insights repair. */
496
511
  upstreamProvider?: string;
497
512
  // Prompt-cache research dimensions (log-only; NEVER on a metric label or a
@@ -527,6 +542,16 @@ export async function emitModelCallUsage(input: {
527
542
  sourceKey: input.sourceKey,
528
543
  ...(input.billingPath ? { billingPath: input.billingPath } : {}),
529
544
  ...(input.upstreamProvider ? { upstreamProvider: input.upstreamProvider } : {}),
545
+ ...(input.billingSnapshot
546
+ ? {
547
+ pricedCostMicros: input.billingSnapshot.pricedCostMicros,
548
+ estimatedProviderCostMicros: input.billingSnapshot.estimatedProviderCostMicros,
549
+ equivalentCreditCostMicros: input.billingSnapshot.equivalentCreditCostMicros,
550
+ pricingSource: input.billingSnapshot.pricingSource,
551
+ listByClassMicros: input.billingSnapshot.listByClassMicros ?? null,
552
+ listByClassApprox: input.billingSnapshot.listByClassApprox ?? false,
553
+ }
554
+ : {}),
530
555
  ...telemetry,
531
556
  },
532
557
  },
@@ -605,6 +630,9 @@ export type ModelUsageBillingRecord = {
605
630
  /** Hypothetical OpenGeni credit price at the captured rate; never a debit. */
606
631
  equivalentCreditCostMicros: number | null;
607
632
  pricingSource: "configured_list_price" | "gateway_reported" | null;
633
+ /** Forward-only provider list class snapshot; older facts/events stay unknown. */
634
+ listByClassMicros?: InsightsUsageClassMicros | null;
635
+ listByClassApprox?: boolean;
608
636
  normalizedUsage: ModelCallUsageNormalization;
609
637
  upstreamProvider?: string;
610
638
  };
@@ -665,7 +693,7 @@ export async function recordModelUsageAndDebitCredits(
665
693
  }
666
694
  if (unpinnedWorkspaceGatewayModel && chargesOpenGeniCredits) {
667
695
  throw new Error(
668
- `Workspace Gateway custom model ${input.model} cannot charge OpenGeni credits without pinned pricing`,
696
+ `Workspace Gateway custom model ${input.model} cannot charge Opengeni credits without pinned pricing`,
669
697
  );
670
698
  }
671
699
  }
@@ -697,10 +725,30 @@ export async function recordModelUsageAndDebitCredits(
697
725
  const hasCompleteCoreTokenTelemetry =
698
726
  normalizedUsage.telemetry.inputTokens !== null &&
699
727
  normalizedUsage.telemetry.outputTokens !== null;
728
+ // Comparison rates are deliberately separate from debit authority. The
729
+ // current usage frame does not establish geography/service-tier provenance,
730
+ // so forward class splits stay unknown even when a total estimate is priced.
731
+ const listPricingSchedules = configuredModelListPricingSchedules(settings);
732
+ const configuredListPricingModel = listPricingSchedules[input.model]
733
+ ? input.model
734
+ : input.model.startsWith("codex/") && listPricingSchedules[input.model.slice("codex/".length)]
735
+ ? input.model.slice("codex/".length)
736
+ : null;
737
+ const listSnapshot =
738
+ !gatewayBilling && hasCompleteCoreTokenTelemetry && configuredListPricingModel
739
+ ? calculateModelListUsageCostSnapshot(settings, configuredListPricingModel, sanitizedUsage, {
740
+ latencyMode: input.latencyMode ?? "standard",
741
+ priceContextKnown: false,
742
+ })
743
+ : null;
744
+ const listClasses = {
745
+ listByClassMicros: listSnapshot?.listByClassMicros ?? null,
746
+ listByClassApprox: listSnapshot?.listByClassApprox ?? false,
747
+ };
700
748
  const estimatedProviderCostMicros = gatewayBilling
701
749
  ? (pricingBreakdown?.providerCostMicros ?? null)
702
750
  : hasCompleteCoreTokenTelemetry
703
- ? (pricingBreakdown?.providerCostMicros ?? null)
751
+ ? (listSnapshot?.providerCostMicros ?? pricingBreakdown?.providerCostMicros ?? null)
704
752
  : null;
705
753
  const equivalentCreditCostMicros =
706
754
  pricingBreakdown && !unpinnedWorkspaceGatewayModel
@@ -753,6 +801,7 @@ export async function recordModelUsageAndDebitCredits(
753
801
  estimatedProviderCostMicros,
754
802
  equivalentCreditCostMicros,
755
803
  pricingSource,
804
+ ...listClasses,
756
805
  normalizedUsage,
757
806
  ...(gatewayBilling ? { upstreamProvider: gatewayBilling.finalProvider } : {}),
758
807
  };
@@ -765,6 +814,7 @@ export async function recordModelUsageAndDebitCredits(
765
814
  estimatedProviderCostMicros,
766
815
  equivalentCreditCostMicros,
767
816
  pricingSource,
817
+ ...listClasses,
768
818
  normalizedUsage,
769
819
  ...(gatewayBilling ? { upstreamProvider: gatewayBilling.finalProvider } : {}),
770
820
  };
@@ -819,6 +869,7 @@ export async function recordModelUsageAndDebitCredits(
819
869
  estimatedProviderCostMicros,
820
870
  equivalentCreditCostMicros,
821
871
  pricingSource,
872
+ ...listClasses,
822
873
  normalizedUsage,
823
874
  ...(gatewayBilling ? { upstreamProvider: gatewayBilling.finalProvider } : {}),
824
875
  };
@@ -857,6 +908,8 @@ export async function recordAuthoritativeModelCallFact(input: {
857
908
  estimatedProviderCostMicros: input.billing.estimatedProviderCostMicros,
858
909
  equivalentCreditCostMicros: input.billing.equivalentCreditCostMicros,
859
910
  pricingSource: input.billing.pricingSource,
911
+ listByClassMicros: input.billing.listByClassMicros ?? null,
912
+ listByClassApprox: input.billing.listByClassApprox ?? false,
860
913
  inputTokens: telemetry.inputTokens,
861
914
  outputTokens: telemetry.outputTokens,
862
915
  cachedTokens: telemetry.cachedTokens,
@@ -883,7 +936,9 @@ export function sanitizedModelUsageInput(normalized: ModelCallUsageNormalization
883
936
  ? { outputTokens: normalized.telemetry.outputTokens }
884
937
  : {}),
885
938
  ...(normalized.totalTokens !== null ? { totalTokens: normalized.totalTokens } : {}),
886
- ...(normalized.telemetry.cachedTokens !== null || normalized.telemetry.cacheWriteTokens !== null
939
+ ...(normalized.telemetry.cachedTokens !== null ||
940
+ normalized.telemetry.cacheWriteTokens !== null ||
941
+ normalized.cacheWriteTokensByTtl !== undefined
887
942
  ? {
888
943
  inputTokensDetails: {
889
944
  ...(normalized.telemetry.cachedTokens === null
@@ -892,6 +947,12 @@ export function sanitizedModelUsageInput(normalized: ModelCallUsageNormalization
892
947
  ...(normalized.telemetry.cacheWriteTokens === null
893
948
  ? {}
894
949
  : { cache_write_tokens: normalized.telemetry.cacheWriteTokens }),
950
+ ...(normalized.cacheWriteTokensByTtl?.fiveMinute == null
951
+ ? {}
952
+ : { cache_write_tokens_5m: normalized.cacheWriteTokensByTtl.fiveMinute }),
953
+ ...(normalized.cacheWriteTokensByTtl?.oneHour == null
954
+ ? {}
955
+ : { cache_write_tokens_1h: normalized.cacheWriteTokensByTtl.oneHour }),
895
956
  },
896
957
  }
897
958
  : {}),
@@ -0,0 +1,27 @@
1
+ import type { ModelTransportDispatchClock } from "@opengeni/runtime";
2
+
3
+ type DispatchIdentity = { provider: string; dispatchId: string };
4
+
5
+ /** One immutable physical fetch-entry observation per worker attempt. Generic
6
+ * fallback events already carry that attempt's dispatch/execution identity. */
7
+ export class InitialModelWireDispatchClock {
8
+ private initial: (DispatchIdentity & { initialWireDispatchedAt: string }) | null = null;
9
+
10
+ record(identity: DispatchIdentity, clock: ModelTransportDispatchClock): void {
11
+ if (this.initial !== null) return;
12
+ const timestamp = new Date(clock.dispatchedAtUnixMs);
13
+ if (!Number.isFinite(timestamp.getTime())) return;
14
+ this.initial = { ...identity, initialWireDispatchedAt: timestamp.toISOString() };
15
+ }
16
+
17
+ payload(identity: DispatchIdentity): { initialWireDispatchedAt?: string } {
18
+ if (
19
+ this.initial === null ||
20
+ this.initial.provider !== identity.provider ||
21
+ this.initial.dispatchId !== identity.dispatchId
22
+ ) {
23
+ return {};
24
+ }
25
+ return { initialWireDispatchedAt: this.initial.initialWireDispatchedAt };
26
+ }
27
+ }
@@ -0,0 +1,224 @@
1
+ import type { Settings } from "@opengeni/config";
2
+ import { resourceMountPath, type ResourceRef } from "@opengeni/contracts";
3
+ import { prReviewRegistrationIdFromCredentialBinding } from "@opengeni/core";
4
+ import { areGitHubRepositoriesAllowedForWorkspace, type Database } from "@opengeni/db";
5
+ import {
6
+ findInaccessibleGitHubAppInstallationRepositories,
7
+ githubAppMissingSettings,
8
+ } from "@opengeni/github";
9
+ import { gitHubTokenAuthorizationSelections } from "../environment";
10
+
11
+ /**
12
+ * Turn-time access check for automatically attached (`optional: true`) GitHub
13
+ * App repositories.
14
+ *
15
+ * A Slack-started session attaches the caller's recently used repositories as
16
+ * optional resources. The per-turn workspace-allowlist recheck and the
17
+ * installation-token mint both cover every GitHub repository of the session in
18
+ * one request, so one optional repository that loses access after the session
19
+ * started (an admin removes it from the allowlist, or it is deleted or removed
20
+ * from the App on GitHub) would fail every later turn. Before those checks, this
21
+ * drops each optional repository that is no longer allowlisted or no longer
22
+ * reachable through its installation, for this turn only, and reports it the
23
+ * same way a skipped optional clone is reported. It only ever removes
24
+ * repositories: a remaining repository still passes the unchanged strict
25
+ * allowlist recheck and mint, and an explicitly attached repository is never
26
+ * dropped, so it keeps failing the turn when its access is gone.
27
+ */
28
+
29
+ export const OPTIONAL_REPOSITORY_ACCESS_OPERATION = "optional-repository-access";
30
+
31
+ type OptionalRepositoryCandidate = {
32
+ resource: Extract<ResourceRef, { kind: "repository" }>;
33
+ installationId: number;
34
+ repositoryId: number;
35
+ };
36
+
37
+ export type OptionalRepositoryDropReason = "not_allowlisted" | "inaccessible" | "unverified";
38
+
39
+ export type DropUnavailableOptionalRepositoriesInput = {
40
+ db: Database;
41
+ settings: Settings;
42
+ workspaceId: string;
43
+ activeSandboxBackend: Settings["sandboxBackend"] | undefined;
44
+ /**
45
+ * True when a host `gitCredentials` port mints the Git tokens: the host may
46
+ * use another GitHub App, so this deployment's App is not asked.
47
+ */
48
+ hostMintsGitCredentials: boolean;
49
+ turnResources: readonly ResourceRef[];
50
+ runtimeResources: readonly ResourceRef[];
51
+ /** Publishes one attempt-fenced session event batch. */
52
+ publish: (
53
+ events: Array<{
54
+ type: "sandbox.operation.completed";
55
+ payload: {
56
+ name: typeof OPTIONAL_REPOSITORY_ACCESS_OPERATION;
57
+ repositoryCount: number;
58
+ skippedOptionalRepositories: string[];
59
+ };
60
+ }>,
61
+ ) => Promise<void>;
62
+ /** Operational log line; counts only, never repository names. */
63
+ warn: (message: string, fields: Record<string, string>) => void;
64
+ /** Test seam; defaults to the workspace allowlist under RLS. */
65
+ isAllowlisted?: (installationId: number, repositoryId: number) => Promise<boolean>;
66
+ /** Test seam; defaults to a live GitHub App token-mint probe. */
67
+ findInaccessible?: (installationId: number, repositoryIds: number[]) => Promise<number[]>;
68
+ };
69
+
70
+ function optionalGitHubAppRepositoryCandidate(
71
+ resource: ResourceRef,
72
+ ): OptionalRepositoryCandidate | null {
73
+ if (resource.kind !== "repository" || resource.optional !== true) return null;
74
+ let selections: ReturnType<typeof gitHubTokenAuthorizationSelections>;
75
+ try {
76
+ // The exact selection the allowlist recheck and the mint derive, so the
77
+ // decision here covers precisely what those strict steps would check.
78
+ selections = gitHubTokenAuthorizationSelections([resource]);
79
+ } catch {
80
+ return null;
81
+ }
82
+ const [selection] = selections;
83
+ if (!selection || selections.length !== 1 || selection.repositoryIds.length !== 1) return null;
84
+ // PR-review bindings are authorized by their own registration, not the
85
+ // workspace allowlist; they are never automatically attached.
86
+ if (prReviewRegistrationIdFromCredentialBinding(selection.credentialBindingId)) return null;
87
+ return {
88
+ resource,
89
+ installationId: selection.installationId,
90
+ repositoryId: selection.repositoryIds[0]!,
91
+ };
92
+ }
93
+
94
+ function repositoryIdentity(resource: ResourceRef): string | null {
95
+ return resource.kind === "repository"
96
+ ? `${resource.uri}\u0000${resourceMountPath(resource)}`
97
+ : null;
98
+ }
99
+
100
+ export async function dropUnavailableOptionalRepositories(
101
+ input: DropUnavailableOptionalRepositoriesInput,
102
+ ): Promise<{
103
+ turnResources: ResourceRef[];
104
+ runtimeResources: ResourceRef[];
105
+ /**
106
+ * The same decision for another view of this turn's resources (the GitHub
107
+ * REST tool surface is built from the unbound session resources).
108
+ */
109
+ retainsResource: (resource: ResourceRef) => boolean;
110
+ }> {
111
+ const passthrough = {
112
+ turnResources: [...input.turnResources],
113
+ runtimeResources: [...input.runtimeResources],
114
+ retainsResource: () => true,
115
+ };
116
+ // A Connected Machine receives no platform Git credential and runs neither
117
+ // the allowlist recheck nor the mint, so nothing can fail there.
118
+ if (input.activeSandboxBackend === "selfhosted") return passthrough;
119
+ const candidates = input.turnResources.flatMap(
120
+ (resource) => optionalGitHubAppRepositoryCandidate(resource) ?? [],
121
+ );
122
+ if (candidates.length === 0) return passthrough;
123
+
124
+ const isAllowlisted =
125
+ input.isAllowlisted ??
126
+ ((installationId: number, repositoryId: number) =>
127
+ areGitHubRepositoriesAllowedForWorkspace(input.db, input.workspaceId, installationId, [
128
+ repositoryId,
129
+ ]));
130
+ const dropped = new Map<OptionalRepositoryCandidate, OptionalRepositoryDropReason>();
131
+ const allowlisted: OptionalRepositoryCandidate[] = [];
132
+ const admitted = await Promise.all(
133
+ candidates.map(async (candidate) => {
134
+ try {
135
+ return (await isAllowlisted(candidate.installationId, candidate.repositoryId))
136
+ ? ("allowlisted" as const)
137
+ : ("not_allowlisted" as const);
138
+ } catch {
139
+ // The check itself failed: an optional repository must not fail the
140
+ // turn, so it sits this turn out like an unverifiable GitHub answer.
141
+ return "unverified" as const;
142
+ }
143
+ }),
144
+ );
145
+ candidates.forEach((candidate, index) => {
146
+ const outcome = admitted[index]!;
147
+ if (outcome === "allowlisted") allowlisted.push(candidate);
148
+ else dropped.set(candidate, outcome);
149
+ });
150
+
151
+ const findInaccessible =
152
+ input.findInaccessible ??
153
+ (!input.hostMintsGitCredentials && githubAppMissingSettings(input.settings).length === 0
154
+ ? (installationId: number, repositoryIds: number[]) =>
155
+ findInaccessibleGitHubAppInstallationRepositories(input.settings, {
156
+ installationId,
157
+ repositoryIds,
158
+ })
159
+ : null);
160
+ if (findInaccessible && allowlisted.length > 0) {
161
+ const byInstallation = new Map<number, OptionalRepositoryCandidate[]>();
162
+ for (const candidate of allowlisted) {
163
+ const group = byInstallation.get(candidate.installationId) ?? [];
164
+ group.push(candidate);
165
+ byInstallation.set(candidate.installationId, group);
166
+ }
167
+ await Promise.all(
168
+ [...byInstallation].map(async ([installationId, group]) => {
169
+ try {
170
+ const inaccessible = new Set(
171
+ await findInaccessible(
172
+ installationId,
173
+ group.map((candidate) => candidate.repositoryId),
174
+ ),
175
+ );
176
+ for (const candidate of group) {
177
+ if (inaccessible.has(candidate.repositoryId)) dropped.set(candidate, "inaccessible");
178
+ }
179
+ } catch {
180
+ // GitHub could not answer. An optional repository must never fail
181
+ // the turn, and keeping it would put it into a mint GitHub may then
182
+ // refuse, so it sits this turn out and is checked again next turn.
183
+ for (const candidate of group) dropped.set(candidate, "unverified");
184
+ }
185
+ }),
186
+ );
187
+ }
188
+ if (dropped.size === 0) return passthrough;
189
+
190
+ const droppedIdentities = new Set(
191
+ [...dropped.keys()].flatMap((candidate) => repositoryIdentity(candidate.resource) ?? []),
192
+ );
193
+ const keep = (resource: ResourceRef): boolean => {
194
+ if (resource.kind !== "repository" || resource.optional !== true) return true;
195
+ const identity = repositoryIdentity(resource);
196
+ return identity === null || !droppedIdentities.has(identity);
197
+ };
198
+ const reasons = [...dropped.values()];
199
+ input.warn("optional repository resources were dropped for this turn", {
200
+ droppedCount: String(dropped.size),
201
+ notAllowlistedCount: String(reasons.filter((reason) => reason === "not_allowlisted").length),
202
+ inaccessibleCount: String(reasons.filter((reason) => reason === "inaccessible").length),
203
+ unverifiedCount: String(reasons.filter((reason) => reason === "unverified").length),
204
+ origin: "worker",
205
+ });
206
+ await input.publish([
207
+ {
208
+ type: "sandbox.operation.completed",
209
+ payload: {
210
+ name: OPTIONAL_REPOSITORY_ACCESS_OPERATION,
211
+ repositoryCount: input.turnResources.filter((resource) => resource.kind === "repository")
212
+ .length,
213
+ skippedOptionalRepositories: [
214
+ ...new Set([...dropped.keys()].map((candidate) => resourceMountPath(candidate.resource))),
215
+ ].sort(),
216
+ },
217
+ },
218
+ ]);
219
+ return {
220
+ turnResources: input.turnResources.filter(keep),
221
+ runtimeResources: input.runtimeResources.filter(keep),
222
+ retainsResource: keep,
223
+ };
224
+ }
@@ -1,4 +1,14 @@
1
1
  import type { TurnHistorySink } from "./history-sink";
2
+ import type { Settings } from "@opengeni/config";
3
+
4
+ export class SandboxCapabilitiesChangedError extends Error {
5
+ readonly code = "native_capabilities_changed_this_attempt";
6
+
7
+ constructor() {
8
+ super("The attached sandbox requires rebuilding this attempt's native capabilities.");
9
+ this.name = "SandboxCapabilitiesChangedError";
10
+ }
11
+ }
2
12
 
3
13
  /**
4
14
  * Persist the SDK's complete prior model/tool history before another provider
@@ -8,6 +18,18 @@ import type { TurnHistorySink } from "./history-sink";
8
18
  */
9
19
  export async function checkpointHistoryBeforeProviderDispatch(
10
20
  historySink: Pick<TurnHistorySink, "reconcileConversationTruth">,
21
+ route?: {
22
+ effectiveSandboxBackend: Settings["sandboxBackend"];
23
+ routingEnabled: boolean;
24
+ readActiveSandbox: () => Promise<{ activeSandboxId: string | null } | null>;
25
+ },
11
26
  ): Promise<void> {
12
27
  await historySink.reconcileConversationTruth({ requireDurable: true });
28
+ // A plain Agent cannot acquire SandboxAgent capabilities in place. Preserve
29
+ // its completed tool batch before recovering the same logical turn; the next
30
+ // attempt resolves and authorizes the route normally.
31
+ if (route?.effectiveSandboxBackend === "none" && route.routingEnabled) {
32
+ const active = await route.readActiveSandbox();
33
+ if (active?.activeSandboxId) throw new SandboxCapabilitiesChangedError();
34
+ }
13
35
  }
@@ -91,10 +91,9 @@ export const QUIESCENCE_PROOF_SIGNAL_MAX_RETRY_MS = 5_000;
91
91
  export const TURN_QUIESCENCE_WATCHDOG_MS = 5 * 60_000;
92
92
  export const TURN_FINALIZER_STEP_TIMEOUT_MS = 30_000;
93
93
 
94
- /** A fenced activity must never heartbeat forever while its physical writer
95
- * drain is stuck. The last-resort worker exit stops every in-process writer;
96
- * Kubernetes replaces the pod and the workflow's heartbeat-lease reconciler
97
- * then commits the exact quiescence receipt. */
94
+ /** A fenced activity must never heartbeat forever while physical cleanup is
95
+ * stuck. The callback requests host-owned containment, which drains peer turns
96
+ * before applying its final exit policy. A timeout is never quiescence proof. */
98
97
  export function armTurnQuiescenceWatchdog(input: {
99
98
  enabled: boolean;
100
99
  timeoutMs?: number;
@@ -198,13 +197,16 @@ export async function drainAttemptOwnedSandboxWriters(input: {
198
197
  gitCredentialRenewals: readonly Pick<GitCredentialRenewalController, "stop">[];
199
198
  codemodeTokenRenewal: Pick<CodemodeTokenRenewalController, "stop"> | null;
200
199
  runCredentialRenewal: Pick<RunCredentialRenewalController, "stop"> | null;
200
+ onStage?: (stage: "tool_writers" | "credential_renewals") => void;
201
201
  }): Promise<void> {
202
+ input.onStage?.("tool_writers");
202
203
  if (input.toolCancellationFence) {
203
204
  input.toolCancellationFence.cancel(
204
205
  input.cancellationReason ?? new Error("TURN_ATTEMPT_FENCED"),
205
206
  );
206
207
  await input.toolCancellationFence.waitForQuiescence();
207
208
  }
209
+ input.onStage?.("credential_renewals");
208
210
  await Promise.all(input.gitCredentialRenewals.map(async (renewal) => await renewal.stop()));
209
211
  await input.codemodeTokenRenewal?.stop();
210
212
  await input.runCredentialRenewal?.stop();
@@ -432,13 +434,14 @@ export async function runMandatoryHistoryPersistenceStep<T>(
432
434
  }
433
435
 
434
436
  export type OpStreamFinalizer = {
435
- finalizeOpStreamOps(): Promise<void>;
437
+ finalizeOpStreamOps(toolCallIds?: readonly string[]): Promise<void>;
436
438
  };
437
439
 
438
- /** Release retained Connected Machine output only after the turn is durable. */
440
+ /** Release only durably recorded tool results, or the complete durable turn. */
439
441
  export async function finalizeDurableTurnOpStreams(
440
442
  sessions: readonly unknown[],
441
443
  fallback: OpStreamFinalizer | null,
444
+ toolCallIds?: readonly string[],
442
445
  ): Promise<void> {
443
446
  const candidates = new Set<OpStreamFinalizer>();
444
447
  for (const session of sessions) {
@@ -452,7 +455,7 @@ export async function finalizeDurableTurnOpStreams(
452
455
  }
453
456
  for (const candidate of candidates) {
454
457
  try {
455
- await candidate.finalizeOpStreamOps();
458
+ await candidate.finalizeOpStreamOps(toolCallIds);
456
459
  } catch {
457
460
  // The runner's retention TTL owns the fallback.
458
461
  }
@@ -0,0 +1,16 @@
1
+ import { getSandboxRecoveryDiscontinuity, type Database } from "@opengeni/db";
2
+
3
+ /** v2 also recognizes system-selected checkpoint fallback receipts, and v3
4
+ * the empty-workspace continuation receipts. A worker below the version a
5
+ * session's receipts require cannot claim it (migrations 0526 and 0548). */
6
+ export const FILESYSTEM_DISCONTINUITY_PROTOCOL = 3 as const;
7
+
8
+ export async function recoveryAwareSessionInstructions(
9
+ db: Database,
10
+ workspaceId: string,
11
+ session: { id: string; instructions?: string | null },
12
+ readDiscontinuity: typeof getSandboxRecoveryDiscontinuity = getSandboxRecoveryDiscontinuity,
13
+ ): Promise<string> {
14
+ const filesystemDiscontinuity = await readDiscontinuity(db, workspaceId, session.id);
15
+ return [session.instructions, filesystemDiscontinuity].filter(Boolean).join("\n\n");
16
+ }
@@ -0,0 +1,44 @@
1
+ import type { AttemptToolDefinition } from "@opengeni/codemode";
2
+
3
+ export const REFRESH_CREDENTIALS_TOOL_NAME = "refresh_credentials";
4
+
5
+ export const REFRESH_CREDENTIALS_TOOL_DESCRIPTION =
6
+ "Fetch fresh host-managed credentials for this sandbox now. Use it when a command fails because a token or credential file provided by the workspace has expired or been revoked. The environment variables and credential files that the host manages are replaced for the next command; commands that are already running keep what they started with.";
7
+
8
+ export function createRefreshCredentialsAttemptToolDefinition(input: {
9
+ refresh: () => Promise<"completed" | "auth_needed" | "error" | "not_provisioned">;
10
+ }): AttemptToolDefinition {
11
+ return {
12
+ identity: { serverId: "opengeni", toolName: REFRESH_CREDENTIALS_TOOL_NAME },
13
+ modelName: REFRESH_CREDENTIALS_TOOL_NAME,
14
+ codemodePath: ["opengeni", REFRESH_CREDENTIALS_TOOL_NAME],
15
+ title: "Refresh credentials",
16
+ description: REFRESH_CREDENTIALS_TOOL_DESCRIPTION,
17
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
18
+ annotations: {
19
+ title: "Refresh credentials",
20
+ readOnlyHint: false,
21
+ destructiveHint: false,
22
+ idempotentHint: true,
23
+ openWorldHint: true,
24
+ },
25
+ source: "opengeni",
26
+ approval: "none",
27
+ execute: async (args) => {
28
+ if (Object.keys(args).length !== 0) {
29
+ throw new Error(`${REFRESH_CREDENTIALS_TOOL_NAME} accepts no arguments`);
30
+ }
31
+ const outcome = await input.refresh();
32
+ const text = {
33
+ completed: "Credentials refreshed. Retry the command that failed.",
34
+ auth_needed:
35
+ "The host returned credentials that need user attention (reconnect or grant access). Continue with what is available and tell the user which service needs reconnecting.",
36
+ error:
37
+ "The credential provider could not be reached. The previous credentials are still in place and a retry is scheduled.",
38
+ not_provisioned:
39
+ "Credentials are provisioned when the sandbox first starts; run a command and they will be fetched then.",
40
+ }[outcome];
41
+ return { isError: outcome === "error", content: [{ type: "text", text }] };
42
+ },
43
+ };
44
+ }