@sixb/core 0.1.2 → 0.1.3

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 (264) hide show
  1. package/dist/actions/index.js +16 -13
  2. package/dist/actions/index.js.map +1 -1
  3. package/dist/agents/adapters.d.ts +11 -1
  4. package/dist/agents/adapters.d.ts.map +1 -1
  5. package/dist/agents/context.js +1 -1
  6. package/dist/agents/errors.d.ts +1 -1
  7. package/dist/agents/errors.d.ts.map +1 -1
  8. package/dist/agents/execution.d.ts +3 -0
  9. package/dist/agents/execution.d.ts.map +1 -1
  10. package/dist/agents/index.d.ts +6 -3
  11. package/dist/agents/index.d.ts.map +1 -1
  12. package/dist/agents/index.js +33 -22
  13. package/dist/agents/index.js.map +1 -1
  14. package/dist/agents/prompt.d.ts +8 -2
  15. package/dist/agents/prompt.d.ts.map +1 -1
  16. package/dist/agents/request.d.ts.map +1 -1
  17. package/dist/agents/streams.js +2 -2
  18. package/dist/agents/thread-context-projection.d.ts +19 -0
  19. package/dist/agents/thread-context-projection.d.ts.map +1 -0
  20. package/dist/agents/tool-definition.d.ts.map +1 -1
  21. package/dist/agents/tool-result.d.ts +6 -0
  22. package/dist/agents/tool-result.d.ts.map +1 -0
  23. package/dist/agents/types.d.ts +37 -1
  24. package/dist/agents/types.d.ts.map +1 -1
  25. package/dist/agents/validation.d.ts +1 -1
  26. package/dist/agents/validation.d.ts.map +1 -1
  27. package/dist/auth/index.js +7 -5
  28. package/dist/auth/index.js.map +1 -1
  29. package/dist/authorization/index.js +1 -1
  30. package/dist/bootstrap/index.js +27 -24
  31. package/dist/bootstrap/index.js.map +1 -1
  32. package/dist/{chunk-0mbx247x.js → chunk-177ed2mp.js} +241 -45
  33. package/dist/chunk-177ed2mp.js.map +46 -0
  34. package/dist/{chunk-sa4340y1.js → chunk-1ywq4hzk.js} +7 -7
  35. package/dist/{chunk-zg9yryhz.js → chunk-261ag4t4.js} +3 -3
  36. package/dist/{chunk-k9pts48t.js → chunk-3rsf30p4.js} +5 -5
  37. package/dist/{chunk-8ryen3p3.js → chunk-4aw0jwk0.js} +1 -1
  38. package/dist/{chunk-bh6k3nc6.js → chunk-4p9rh7jj.js} +1 -1
  39. package/dist/{chunk-34bqh420.js → chunk-4z3n1dzt.js} +59 -21
  40. package/dist/{chunk-34bqh420.js.map → chunk-4z3n1dzt.js.map} +4 -4
  41. package/dist/chunk-96swp2wd.js +47 -0
  42. package/dist/chunk-96swp2wd.js.map +10 -0
  43. package/dist/{chunk-n8herkry.js → chunk-bv41fmjn.js} +11 -11
  44. package/dist/{chunk-7pc8b818.js → chunk-c6cfqydk.js} +1 -1
  45. package/dist/{chunk-0k4avqr2.js → chunk-c8bw2b6c.js} +2 -2
  46. package/dist/{chunk-jaj13078.js → chunk-d2adj15j.js} +3 -33
  47. package/dist/{chunk-jaj13078.js.map → chunk-d2adj15j.js.map} +3 -4
  48. package/dist/{chunk-c6qd89g5.js → chunk-d6y2wsfg.js} +3 -3
  49. package/dist/chunk-dysw7nsm.js.map +1 -1
  50. package/dist/{chunk-st4xgxey.js → chunk-ettb0cy4.js} +150 -23
  51. package/dist/chunk-ettb0cy4.js.map +45 -0
  52. package/dist/{chunk-nnyp53zt.js → chunk-f1cyvnrk.js} +1 -1
  53. package/dist/chunk-fmx1sz2h.js.map +2 -2
  54. package/dist/{chunk-bct7hmfc.js → chunk-km4rkj4t.js} +3 -3
  55. package/dist/chunk-kqxgf6kv.js +898 -0
  56. package/dist/chunk-kqxgf6kv.js.map +15 -0
  57. package/dist/{chunk-qvybnnpe.js → chunk-n5x5y80a.js} +2 -2
  58. package/dist/{chunk-4x43bkbp.js → chunk-pxtrxm3p.js} +1 -1
  59. package/dist/chunk-qer6am8w.js +133 -0
  60. package/dist/chunk-qer6am8w.js.map +11 -0
  61. package/dist/{chunk-85j5ybfj.js → chunk-rdeagz6b.js} +2 -2
  62. package/dist/{chunk-85j5ybfj.js.map → chunk-rdeagz6b.js.map} +3 -3
  63. package/dist/chunk-rhrbz7d4.js +36 -0
  64. package/dist/chunk-rhrbz7d4.js.map +10 -0
  65. package/dist/{chunk-663csy96.js → chunk-t0jjkep3.js} +2 -1
  66. package/dist/chunk-t0jjkep3.js.map +10 -0
  67. package/dist/{chunk-5egfka8j.js → chunk-ttba8x6z.js} +2 -2
  68. package/dist/{chunk-nten49ta.js → chunk-wb4051cx.js} +3 -3
  69. package/dist/{chunk-nten49ta.js.map → chunk-wb4051cx.js.map} +1 -1
  70. package/dist/{chunk-57r1cpxx.js → chunk-xc7hq65b.js} +3 -2
  71. package/dist/{chunk-57r1cpxx.js.map → chunk-xc7hq65b.js.map} +3 -3
  72. package/dist/{chunk-ys1nse2v.js → chunk-xmggaxg4.js} +256 -231
  73. package/dist/{chunk-ys1nse2v.js.map → chunk-xmggaxg4.js.map} +8 -10
  74. package/dist/{chunk-g9t3kdtx.js → chunk-z8j83gzd.js} +1 -1
  75. package/dist/{chunk-57zpb99z.js → chunk-zpw39qhj.js} +1 -1
  76. package/dist/execution/agent.js +23 -20
  77. package/dist/execution/agent.js.map +1 -1
  78. package/dist/execution/primitive.js +23 -20
  79. package/dist/execution/primitive.js.map +2 -2
  80. package/dist/execution/request.js +24 -21
  81. package/dist/execution/request.js.map +2 -2
  82. package/dist/index.d.ts +3 -3
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +32 -29
  85. package/dist/index.js.map +1 -1
  86. package/dist/materialization/model.d.ts +4 -0
  87. package/dist/materialization/model.d.ts.map +1 -1
  88. package/dist/materializer/edits/apply.d.ts +6 -1
  89. package/dist/materializer/edits/apply.d.ts.map +1 -1
  90. package/dist/materializer/edits/working-state.d.ts +2 -0
  91. package/dist/materializer/edits/working-state.d.ts.map +1 -1
  92. package/dist/materializer/effective/resolve.d.ts +3 -1
  93. package/dist/materializer/effective/resolve.d.ts.map +1 -1
  94. package/dist/materializer/execution/work-records.d.ts.map +1 -1
  95. package/dist/materializer/index.js +14 -11
  96. package/dist/materializer/index.js.map +1 -1
  97. package/dist/materializer/projections/replacement-plan.d.ts.map +1 -1
  98. package/dist/materializer/shared/normalize.d.ts.map +1 -1
  99. package/dist/objects/index.js +17 -14
  100. package/dist/objects/index.js.map +1 -1
  101. package/dist/objects/query/index.js +2 -2
  102. package/dist/pipelines/index.js +14 -12
  103. package/dist/pipelines/index.js.map +1 -1
  104. package/dist/projections/builders.d.ts +19 -6
  105. package/dist/projections/builders.d.ts.map +1 -1
  106. package/dist/projections/index.d.ts +2 -1
  107. package/dist/projections/index.d.ts.map +1 -1
  108. package/dist/projections/internal.d.ts +4 -1
  109. package/dist/projections/internal.d.ts.map +1 -1
  110. package/dist/projections/internal.js +17 -11
  111. package/dist/projections/internal.js.map +1 -1
  112. package/dist/projections/timestamp.d.ts +3 -0
  113. package/dist/projections/timestamp.d.ts.map +1 -0
  114. package/dist/projections/types.d.ts +9 -0
  115. package/dist/projections/types.d.ts.map +1 -1
  116. package/dist/projections/validation.d.ts.map +1 -1
  117. package/dist/queues/index.d.ts +1 -1
  118. package/dist/queues/index.d.ts.map +1 -1
  119. package/dist/queues/types.d.ts +7 -0
  120. package/dist/queues/types.d.ts.map +1 -1
  121. package/dist/runtime/ai-cost.d.ts +8 -0
  122. package/dist/runtime/ai-cost.d.ts.map +1 -0
  123. package/dist/storage/agents/in-memory.d.ts +18 -4
  124. package/dist/storage/agents/in-memory.d.ts.map +1 -1
  125. package/dist/storage/agents/index.d.ts +1 -1
  126. package/dist/storage/agents/index.d.ts.map +1 -1
  127. package/dist/storage/agents/provider.d.ts +31 -0
  128. package/dist/storage/agents/provider.d.ts.map +1 -1
  129. package/dist/storage/agents/provider.js +18 -7
  130. package/dist/storage/agents/provider.js.map +1 -1
  131. package/dist/storage/agents/types.d.ts +70 -4
  132. package/dist/storage/agents/types.d.ts.map +1 -1
  133. package/dist/storage/ai-cost/analytics.d.ts +61 -0
  134. package/dist/storage/ai-cost/analytics.d.ts.map +1 -0
  135. package/dist/storage/ai-cost/codec.d.ts +14 -0
  136. package/dist/storage/ai-cost/codec.d.ts.map +1 -0
  137. package/dist/storage/ai-cost/errors.d.ts +7 -0
  138. package/dist/storage/ai-cost/errors.d.ts.map +1 -0
  139. package/dist/storage/ai-cost/in-memory.d.ts +32 -0
  140. package/dist/storage/ai-cost/in-memory.d.ts.map +1 -0
  141. package/dist/storage/ai-cost/index.d.ts +6 -0
  142. package/dist/storage/ai-cost/index.d.ts.map +1 -0
  143. package/dist/storage/ai-cost/provider.d.ts +7 -0
  144. package/dist/storage/ai-cost/provider.d.ts.map +1 -0
  145. package/dist/storage/ai-cost/provider.js +132 -0
  146. package/dist/storage/ai-cost/provider.js.map +10 -0
  147. package/dist/storage/ai-cost/types.d.ts +154 -0
  148. package/dist/storage/ai-cost/types.d.ts.map +1 -0
  149. package/dist/storage/ai-cost/validation.d.ts +7 -0
  150. package/dist/storage/ai-cost/validation.d.ts.map +1 -0
  151. package/dist/storage/in-memory/index.d.ts +5 -0
  152. package/dist/storage/in-memory/index.d.ts.map +1 -1
  153. package/dist/storage/index.d.ts +3 -1
  154. package/dist/storage/index.d.ts.map +1 -1
  155. package/dist/storage/index.js +22 -15
  156. package/dist/storage/index.js.map +1 -1
  157. package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
  158. package/dist/storage/ontology/materializations.d.ts +3 -0
  159. package/dist/storage/ontology/materializations.d.ts.map +1 -1
  160. package/dist/storage/operation-scope.d.ts.map +1 -1
  161. package/dist/storage/operation-scope.js +1 -1
  162. package/dist/storage/types.d.ts +2 -0
  163. package/dist/storage/types.d.ts.map +1 -1
  164. package/dist/storage/workflow-runs/provider.js +6 -5
  165. package/dist/storage/workflow-runs/provider.js.map +1 -1
  166. package/dist/syncs/index.js +14 -12
  167. package/dist/syncs/index.js.map +1 -1
  168. package/dist/testing/agent-storage-contract.d.ts +3 -2
  169. package/dist/testing/agent-storage-contract.d.ts.map +1 -1
  170. package/dist/testing/ai-cost-storage-contract.d.ts +12 -0
  171. package/dist/testing/ai-cost-storage-contract.d.ts.map +1 -0
  172. package/dist/testing/index.d.ts +1 -0
  173. package/dist/testing/index.d.ts.map +1 -1
  174. package/dist/testing/index.js +2098 -1468
  175. package/dist/testing/index.js.map +6 -5
  176. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  177. package/dist/webhooks/internal.js +12 -10
  178. package/dist/webhooks/internal.js.map +2 -2
  179. package/dist/workers/index.js +12 -2
  180. package/dist/workers/index.js.map +3 -3
  181. package/dist/workers/queue-worker.d.ts +2 -0
  182. package/dist/workers/queue-worker.d.ts.map +1 -1
  183. package/dist/workflows/execution.d.ts +3 -0
  184. package/dist/workflows/execution.d.ts.map +1 -1
  185. package/dist/workflows/index.js +24 -21
  186. package/dist/workflows/index.js.map +1 -1
  187. package/package.json +7 -1
  188. package/src/agents/adapters.ts +64 -23
  189. package/src/agents/errors.ts +2 -2
  190. package/src/agents/execution.ts +14 -2
  191. package/src/agents/index.ts +18 -1
  192. package/src/agents/prompt.ts +33 -5
  193. package/src/agents/request.ts +5 -0
  194. package/src/agents/thread-context-projection.ts +93 -0
  195. package/src/agents/tool-definition.ts +17 -2
  196. package/src/agents/tool-result.ts +61 -0
  197. package/src/agents/types.ts +47 -1
  198. package/src/agents/validation.ts +1 -1
  199. package/src/index.ts +13 -0
  200. package/src/materialization/model.ts +4 -0
  201. package/src/materializer/edits/apply.ts +55 -2
  202. package/src/materializer/edits/operations.ts +8 -1
  203. package/src/materializer/edits/working-state.ts +7 -0
  204. package/src/materializer/effective/resolve.ts +78 -9
  205. package/src/materializer/execution/work-records.ts +3 -1
  206. package/src/materializer/projections/entry-validator.ts +23 -1
  207. package/src/materializer/projections/replacement-plan.ts +2 -0
  208. package/src/materializer/shared/normalize.ts +16 -0
  209. package/src/projections/builders.ts +100 -35
  210. package/src/projections/index.ts +6 -0
  211. package/src/projections/internal.ts +4 -1
  212. package/src/projections/registry.ts +7 -0
  213. package/src/projections/revision.ts +4 -0
  214. package/src/projections/timestamp.ts +66 -0
  215. package/src/projections/types.ts +7 -0
  216. package/src/projections/validation.ts +24 -0
  217. package/src/queues/index.ts +1 -0
  218. package/src/queues/types.ts +8 -0
  219. package/src/runtime/ai-cost.ts +15 -0
  220. package/src/storage/agents/in-memory.ts +171 -4
  221. package/src/storage/agents/index.ts +5 -0
  222. package/src/storage/agents/provider.ts +225 -0
  223. package/src/storage/agents/types.ts +77 -3
  224. package/src/storage/ai-cost/analytics.ts +602 -0
  225. package/src/storage/ai-cost/codec.ts +136 -0
  226. package/src/storage/ai-cost/errors.ts +12 -0
  227. package/src/storage/ai-cost/in-memory.ts +164 -0
  228. package/src/storage/ai-cost/index.ts +35 -0
  229. package/src/storage/ai-cost/provider.ts +20 -0
  230. package/src/storage/ai-cost/types.ts +193 -0
  231. package/src/storage/ai-cost/validation.ts +337 -0
  232. package/src/storage/in-memory/index.ts +50 -0
  233. package/src/storage/index.ts +37 -0
  234. package/src/storage/ontology/in-memory/materializations.ts +1 -0
  235. package/src/storage/ontology/materializations.ts +3 -0
  236. package/src/storage/operation-scope.ts +1 -0
  237. package/src/storage/types.ts +2 -0
  238. package/src/testing/agent-storage-contract.ts +341 -2
  239. package/src/testing/ai-cost-storage-contract.ts +320 -0
  240. package/src/testing/index.ts +5 -0
  241. package/src/testing/materializer-storage-contract.ts +120 -2
  242. package/src/workers/queue-worker.ts +14 -1
  243. package/src/workflows/execution.ts +31 -10
  244. package/dist/chunk-0mbx247x.js.map +0 -43
  245. package/dist/chunk-663csy96.js.map +0 -10
  246. package/dist/chunk-st4xgxey.js.map +0 -45
  247. package/dist/chunk-vp1vc6ee.js +0 -35
  248. package/dist/chunk-vp1vc6ee.js.map +0 -11
  249. /package/dist/{chunk-sa4340y1.js.map → chunk-1ywq4hzk.js.map} +0 -0
  250. /package/dist/{chunk-zg9yryhz.js.map → chunk-261ag4t4.js.map} +0 -0
  251. /package/dist/{chunk-k9pts48t.js.map → chunk-3rsf30p4.js.map} +0 -0
  252. /package/dist/{chunk-8ryen3p3.js.map → chunk-4aw0jwk0.js.map} +0 -0
  253. /package/dist/{chunk-bh6k3nc6.js.map → chunk-4p9rh7jj.js.map} +0 -0
  254. /package/dist/{chunk-n8herkry.js.map → chunk-bv41fmjn.js.map} +0 -0
  255. /package/dist/{chunk-7pc8b818.js.map → chunk-c6cfqydk.js.map} +0 -0
  256. /package/dist/{chunk-0k4avqr2.js.map → chunk-c8bw2b6c.js.map} +0 -0
  257. /package/dist/{chunk-c6qd89g5.js.map → chunk-d6y2wsfg.js.map} +0 -0
  258. /package/dist/{chunk-nnyp53zt.js.map → chunk-f1cyvnrk.js.map} +0 -0
  259. /package/dist/{chunk-bct7hmfc.js.map → chunk-km4rkj4t.js.map} +0 -0
  260. /package/dist/{chunk-qvybnnpe.js.map → chunk-n5x5y80a.js.map} +0 -0
  261. /package/dist/{chunk-4x43bkbp.js.map → chunk-pxtrxm3p.js.map} +0 -0
  262. /package/dist/{chunk-5egfka8j.js.map → chunk-ttba8x6z.js.map} +0 -0
  263. /package/dist/{chunk-g9t3kdtx.js.map → chunk-z8j83gzd.js.map} +0 -0
  264. /package/dist/{chunk-57zpb99z.js.map → chunk-zpw39qhj.js.map} +0 -0
@@ -0,0 +1,337 @@
1
+ import type { AiModelCallUsage, AiModelCallUsageRecord } from "../ai-usage"
2
+ import { normalizeAiModelCallUsage } from "../ai-usage"
3
+ import type {
4
+ AiBillableMeter,
5
+ AiBillingIdentity,
6
+ AiCostComponent,
7
+ AiModelCallCostRecord,
8
+ AiPriceSource,
9
+ AiPricingContext,
10
+ AiRatedModelCallCostRecord,
11
+ AiUnpriceableModelCallCostRecord,
12
+ AiUnpriceableReason,
13
+ } from "./types"
14
+
15
+ const SIGNED_INT64_MAX = 9_223_372_036_854_775_807n
16
+ const TOKENS_PER_MILLION = 1_000_000n
17
+ const HALF_MILLION = TOKENS_PER_MILLION / 2n
18
+ const MAX_NUMERIC_TEXT_LENGTH = 128
19
+ const METER_ORDER: Readonly<Record<AiBillableMeter, number>> = {
20
+ "tokens.input.total": 0,
21
+ "tokens.input.uncached": 1,
22
+ "tokens.input.cacheRead": 2,
23
+ "tokens.input.cacheWrite": 3,
24
+ "tokens.output.total": 4,
25
+ "tokens.output.text": 5,
26
+ "tokens.output.reasoning": 6,
27
+ }
28
+ const METERS = new Set(Object.keys(METER_ORDER))
29
+ const UNPRICEABLE_REASONS = new Set<AiUnpriceableReason>([
30
+ "missingBillingIdentity",
31
+ "missingCatalogEntry",
32
+ "missingUsageMeter",
33
+ "unsupportedPricingDimension",
34
+ "invalidUsageForFormula",
35
+ ])
36
+ const CONTEXT_STRING_FIELDS = [
37
+ "serviceTier",
38
+ "region",
39
+ "inferenceGeo",
40
+ "routedProviderId",
41
+ "deploymentId",
42
+ "inferenceProfileId",
43
+ "mode",
44
+ ] as const
45
+
46
+ export function normalizeAiPricingContext(input: AiPricingContext): AiPricingContext {
47
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
48
+ throw new TypeError("[Sixb] AI pricing context must be an object.")
49
+ }
50
+ const result: Record<string, string | boolean | number> = {}
51
+ for (const field of CONTEXT_STRING_FIELDS) {
52
+ const value = input[field]
53
+ if (value !== undefined) {
54
+ assertNonBlank(value, `pricingContext.${field}`)
55
+ result[field] = value
56
+ }
57
+ }
58
+ if (input.batch !== undefined) {
59
+ if (typeof input.batch !== "boolean") {
60
+ throw new TypeError("[Sixb] AI pricingContext.batch must be boolean.")
61
+ }
62
+ result.batch = input.batch
63
+ }
64
+ if (input.cacheWriteTtlSeconds !== undefined) {
65
+ if (!Number.isSafeInteger(input.cacheWriteTtlSeconds) || input.cacheWriteTtlSeconds <= 0) {
66
+ throw new TypeError(
67
+ "[Sixb] AI pricingContext.cacheWriteTtlSeconds must be a positive safe integer."
68
+ )
69
+ }
70
+ result.cacheWriteTtlSeconds = input.cacheWriteTtlSeconds
71
+ }
72
+ return result
73
+ }
74
+
75
+ export function normalizeAiModelCallCostRecord(
76
+ input: AiModelCallCostRecord
77
+ ): AiModelCallCostRecord {
78
+ assertNonBlank(input.projectId, "cost record projectId")
79
+ assertNonBlank(input.usageRecordId, "cost record usageRecordId")
80
+ const base = {
81
+ projectId: input.projectId,
82
+ usageRecordId: input.usageRecordId,
83
+ pricingContext: normalizeAiPricingContext(input.pricingContext),
84
+ priceSource: normalizePriceSource(input.priceSource),
85
+ ratedAt: cloneDate(input.ratedAt, "cost record ratedAt"),
86
+ }
87
+
88
+ if (input.status === "rated") return normalizeRatedCost(input, base)
89
+ if (input.status === "unpriceable") return normalizeUnpriceableCost(input, base)
90
+ throw new TypeError("[Sixb] AI cost status is invalid.")
91
+ }
92
+
93
+ /** Verify that a normalized valuation's billed quantities describe its usage record. */
94
+ export function aiModelCallCostMatchesUsage(
95
+ record: AiModelCallCostRecord,
96
+ usageRecord: AiModelCallUsageRecord
97
+ ): boolean {
98
+ if (record.projectId !== usageRecord.projectId || record.usageRecordId !== usageRecord.id) {
99
+ return false
100
+ }
101
+ if (record.status === "unpriceable") return true
102
+
103
+ const usage = normalizeAiModelCallUsage(usageRecord.usage)
104
+ const components = new Map(record.components.map((component) => [component.meter, component]))
105
+ return (
106
+ inputComponentsMatchUsage(components, usage) && outputComponentsMatchUsage(components, usage)
107
+ )
108
+ }
109
+
110
+ function inputComponentsMatchUsage(
111
+ components: ReadonlyMap<AiBillableMeter, AiCostComponent>,
112
+ usage: AiModelCallUsage
113
+ ): boolean {
114
+ if (usage.inputTokens === undefined) return false
115
+ const total = components.get("tokens.input.total")
116
+ const partitions = [
117
+ ["tokens.input.uncached", usage.uncachedInputTokens],
118
+ ["tokens.input.cacheRead", usage.cacheReadInputTokens],
119
+ ["tokens.input.cacheWrite", usage.cacheWriteInputTokens],
120
+ ] as const
121
+ const presentPartitions = partitions.filter(([meter]) => components.has(meter))
122
+
123
+ if (total) {
124
+ return presentPartitions.length === 0 && BigInt(total.quantity) === BigInt(usage.inputTokens)
125
+ }
126
+ if (presentPartitions.length === 0) return false
127
+
128
+ let quantity = 0n
129
+ for (const [meter, expected] of presentPartitions) {
130
+ if (expected === undefined) return false
131
+ const component = components.get(meter)!
132
+ if (BigInt(component.quantity) !== BigInt(expected)) return false
133
+ quantity += BigInt(component.quantity)
134
+ }
135
+ return quantity === BigInt(usage.inputTokens)
136
+ }
137
+
138
+ function outputComponentsMatchUsage(
139
+ components: ReadonlyMap<AiBillableMeter, AiCostComponent>,
140
+ usage: AiModelCallUsage
141
+ ): boolean {
142
+ if (usage.outputTokens === undefined) return false
143
+ const total = components.get("tokens.output.total")
144
+ const partitions = [
145
+ ["tokens.output.text", usage.textOutputTokens],
146
+ ["tokens.output.reasoning", usage.reasoningOutputTokens],
147
+ ] as const
148
+ const presentPartitions = partitions.filter(([meter]) => components.has(meter))
149
+
150
+ if (total) {
151
+ return presentPartitions.length === 0 && BigInt(total.quantity) === BigInt(usage.outputTokens)
152
+ }
153
+ if (presentPartitions.length === 0) return false
154
+
155
+ let quantity = 0n
156
+ for (const [meter, expected] of presentPartitions) {
157
+ if (expected === undefined) return false
158
+ const component = components.get(meter)!
159
+ if (BigInt(component.quantity) !== BigInt(expected)) return false
160
+ quantity += BigInt(component.quantity)
161
+ }
162
+ return quantity === BigInt(usage.outputTokens)
163
+ }
164
+
165
+ function normalizeRatedCost(
166
+ input: AiRatedModelCallCostRecord,
167
+ base: Pick<
168
+ AiRatedModelCallCostRecord,
169
+ "projectId" | "usageRecordId" | "pricingContext" | "priceSource" | "ratedAt"
170
+ >
171
+ ): AiRatedModelCallCostRecord {
172
+ const billingIdentity = normalizeBillingIdentity(input.billingIdentity)
173
+ const components = input.components.map(normalizeCostComponent).sort(compareComponents)
174
+ if (components.length === 0) throw new TypeError("[Sixb] AI rated cost requires components.")
175
+ if (new Set(components.map((component) => component.meter)).size !== components.length) {
176
+ throw new TypeError("[Sixb] AI rated cost cannot contain duplicate meters.")
177
+ }
178
+ const amountNanos = parseNonnegativeInt64(input.money.amountNanos, "money amountNanos").toString()
179
+ if (amountNanos !== sumComponents(components)) {
180
+ throw new TypeError("[Sixb] AI cost total does not equal its component charges.")
181
+ }
182
+ return {
183
+ ...base,
184
+ status: "rated",
185
+ billingIdentity,
186
+ money: { currency: normalizeCurrency(input.money.currency), amountNanos },
187
+ components,
188
+ }
189
+ }
190
+
191
+ function normalizeUnpriceableCost(
192
+ input: AiUnpriceableModelCallCostRecord,
193
+ base: Pick<
194
+ AiUnpriceableModelCallCostRecord,
195
+ "projectId" | "usageRecordId" | "pricingContext" | "priceSource" | "ratedAt"
196
+ >
197
+ ): AiUnpriceableModelCallCostRecord {
198
+ if (!UNPRICEABLE_REASONS.has(input.reason)) {
199
+ throw new TypeError(`[Sixb] AI unpriceable reason '${String(input.reason)}' is invalid.`)
200
+ }
201
+ const billingIdentity = input.billingIdentity
202
+ ? normalizeBillingIdentity(input.billingIdentity)
203
+ : undefined
204
+ if (input.reason === "missingBillingIdentity" && billingIdentity !== undefined) {
205
+ throw new TypeError("[Sixb] AI missingBillingIdentity cannot contain a billing identity.")
206
+ }
207
+ if (input.reason !== "missingBillingIdentity" && billingIdentity === undefined) {
208
+ throw new TypeError(`[Sixb] AI ${input.reason} requires a billing identity.`)
209
+ }
210
+ const missingMeters = normalizeMissingMeters(input.missingMeters)
211
+ if (input.reason === "missingUsageMeter" && !missingMeters) {
212
+ throw new TypeError("[Sixb] AI missingUsageMeter requires missingMeters.")
213
+ }
214
+ return {
215
+ ...base,
216
+ status: "unpriceable",
217
+ ...(billingIdentity ? { billingIdentity } : {}),
218
+ reason: input.reason,
219
+ ...(missingMeters ? { missingMeters } : {}),
220
+ }
221
+ }
222
+
223
+ function normalizeCostComponent(input: AiCostComponent): AiCostComponent {
224
+ if (!METERS.has(input.meter)) {
225
+ throw new TypeError(`[Sixb] AI cost meter '${String(input.meter)}' is invalid.`)
226
+ }
227
+ const expected = calculateComponent(input.meter, input.quantity, input.rateAmountNanosPerMillion)
228
+ if (expected.chargeAmountNanos !== input.chargeAmountNanos) {
229
+ throw new TypeError(`[Sixb] AI cost component '${input.meter}' charge is invalid.`)
230
+ }
231
+ return expected
232
+ }
233
+
234
+ function calculateComponent(
235
+ meter: AiBillableMeter,
236
+ quantity: number | string,
237
+ rateAmountNanosPerMillion: string
238
+ ): AiCostComponent {
239
+ const normalizedQuantity = parseQuantity(quantity, `${meter} quantity`)
240
+ const rate = parseNonnegativeInt64(rateAmountNanosPerMillion, `${meter} rate`)
241
+ const charge = (normalizedQuantity * rate + HALF_MILLION) / TOKENS_PER_MILLION
242
+ assertSignedInt64(charge, `${meter} charge`)
243
+ return {
244
+ meter,
245
+ quantity: normalizedQuantity.toString(),
246
+ rateAmountNanosPerMillion: rate.toString(),
247
+ chargeAmountNanos: charge.toString(),
248
+ }
249
+ }
250
+
251
+ function normalizePriceSource(input: AiPriceSource): AiPriceSource {
252
+ assertNonBlank(input.sourceId, "price sourceId")
253
+ assertNonBlank(input.sourceEntryId, "price sourceEntryId")
254
+ assertNonBlank(input.sourceVersion, "price sourceVersion")
255
+ assertNonBlank(input.sourceUrl, "price sourceUrl")
256
+ return { ...input, observedAt: cloneDate(input.observedAt, "price source observedAt") }
257
+ }
258
+
259
+ function normalizeBillingIdentity(input: AiBillingIdentity): AiBillingIdentity {
260
+ assertNonBlank(input.providerId, "billingIdentity.providerId")
261
+ assertNonBlank(input.modelId, "billingIdentity.modelId")
262
+ return { providerId: input.providerId, modelId: input.modelId }
263
+ }
264
+
265
+ function normalizeMissingMeters(
266
+ input: readonly AiBillableMeter[] | undefined
267
+ ): readonly AiBillableMeter[] | undefined {
268
+ if (!input?.length) return undefined
269
+ const result = [...new Set(input)]
270
+ for (const meter of result) {
271
+ if (!METERS.has(meter)) throw new TypeError(`[Sixb] AI missing meter '${meter}' is invalid.`)
272
+ }
273
+ return result.sort((left, right) => METER_ORDER[left] - METER_ORDER[right])
274
+ }
275
+
276
+ function sumComponents(components: readonly AiCostComponent[]): string {
277
+ let total = 0n
278
+ for (const component of components) {
279
+ total += parseNonnegativeInt64(component.chargeAmountNanos, "component charge")
280
+ assertSignedInt64(total, "cost total")
281
+ }
282
+ return total.toString()
283
+ }
284
+
285
+ function parseQuantity(value: number | string, field: string): bigint {
286
+ if (typeof value === "number") {
287
+ if (!Number.isSafeInteger(value) || value < 0) {
288
+ throw new TypeError(`[Sixb] AI ${field} must be a non-negative safe integer.`)
289
+ }
290
+ return BigInt(value)
291
+ }
292
+ return parseNonnegativeInt64(value, field)
293
+ }
294
+
295
+ function parseNonnegativeInt64(value: string, field: string): bigint {
296
+ if (
297
+ typeof value !== "string" ||
298
+ value.length === 0 ||
299
+ value.length > MAX_NUMERIC_TEXT_LENGTH ||
300
+ !/^\d+$/.test(value)
301
+ ) {
302
+ throw new TypeError(`[Sixb] AI ${field} must be a non-negative integer string.`)
303
+ }
304
+ const parsed = BigInt(value)
305
+ assertSignedInt64(parsed, field)
306
+ return parsed
307
+ }
308
+
309
+ function compareComponents(left: AiCostComponent, right: AiCostComponent): number {
310
+ return METER_ORDER[left.meter] - METER_ORDER[right.meter]
311
+ }
312
+
313
+ function normalizeCurrency(value: string): string {
314
+ if (!/^[A-Z]{3}$/.test(value)) {
315
+ throw new TypeError("[Sixb] AI currency must be a three-letter uppercase code.")
316
+ }
317
+ return value
318
+ }
319
+
320
+ function assertSignedInt64(value: bigint, field: string): void {
321
+ if (value < 0n || value > SIGNED_INT64_MAX) {
322
+ throw new TypeError(`[Sixb] AI ${field} exceeds the signed 64-bit range.`)
323
+ }
324
+ }
325
+
326
+ function assertNonBlank(value: string, field: string): void {
327
+ if (typeof value !== "string" || value.trim().length === 0) {
328
+ throw new TypeError(`[Sixb] AI ${field} must be nonblank.`)
329
+ }
330
+ }
331
+
332
+ function cloneDate(value: Date, field: string): Date {
333
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
334
+ throw new TypeError(`[Sixb] AI ${field} must be a valid Date.`)
335
+ }
336
+ return new Date(value)
337
+ }
@@ -1,6 +1,7 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks"
2
2
  import { InMemoryActionRunStorage } from "../action-runs"
3
3
  import { type AgentStorage, InMemoryAgentStorage } from "../agents"
4
+ import { type AiAccountingAttribution, type AiCostStorage, InMemoryAiCostStorage } from "../ai-cost"
4
5
  import { type AiUsageStorage, InMemoryAiUsageStorage } from "../ai-usage"
5
6
  import { type AuthStorage, InMemoryAuthStorage } from "../auth"
6
7
  import {
@@ -67,6 +68,9 @@ export class InMemoryStorage implements Storage {
67
68
  private readonly executionStorage = new InMemoryExecutionStorage(this.authStorage)
68
69
  private readonly agentStorage = new InMemoryAgentStorage(this.executionStorage)
69
70
  private readonly aiUsageStorage = new InMemoryAiUsageStorage(this.executionStorage)
71
+ private readonly aiCostStorage = new InMemoryAiCostStorage(this.aiUsageStorage, {
72
+ resolve: (input) => this.resolveAiAccountingAttribution(input),
73
+ })
70
74
  private readonly actionRunStorage = new InMemoryActionRunStorage(this.executionStorage)
71
75
  private readonly syncRunStorage = new InMemorySyncRunStorage(this.executionStorage)
72
76
  private readonly pipelineRunStorage = new InMemoryPipelineRunStorage(this.executionStorage)
@@ -83,6 +87,7 @@ export class InMemoryStorage implements Storage {
83
87
  readonly executions: ExecutionStorage
84
88
  readonly agents: AgentStorage
85
89
  readonly aiUsage: AiUsageStorage
90
+ readonly aiCosts: AiCostStorage
86
91
  readonly actionRuns: InMemoryActionRunStorage
87
92
  readonly syncRuns: SyncRunStorage
88
93
  readonly pipelineRuns: PipelineRunStorage
@@ -108,6 +113,7 @@ export class InMemoryStorage implements Storage {
108
113
  this.executions = createOperationScopedFacade(this.executionStorage, scope)
109
114
  this.agents = createAgentOperationScope(this.agentStorage, scope)
110
115
  this.aiUsage = createOperationScopedFacade(this.aiUsageStorage, scope)
116
+ this.aiCosts = createOperationScopedFacade(this.aiCostStorage, scope)
111
117
  this.actionRuns = createOperationScopedFacade(this.actionRunStorage, scope)
112
118
  this.syncRuns = createOperationScopedFacade(this.syncRunStorage, scope)
113
119
  this.pipelineRuns = createOperationScopedFacade(this.pipelineRunStorage, scope)
@@ -135,6 +141,46 @@ export class InMemoryStorage implements Storage {
135
141
  registerInMemoryStorageTestingAdapter(this, { snapshot: () => this.snapshot() })
136
142
  }
137
143
 
144
+ private async resolveAiAccountingAttribution(input: {
145
+ readonly projectId: string
146
+ readonly executionId: string
147
+ }): Promise<AiAccountingAttribution | undefined> {
148
+ const execution = await this.executionStorage.getById({
149
+ projectId: input.projectId,
150
+ id: input.executionId,
151
+ })
152
+ if (!execution || execution.executor.type !== "agent") return undefined
153
+ const directRun = await this.agentStorage.runs.getById({
154
+ projectId: input.projectId,
155
+ id: execution.executor.runId,
156
+ })
157
+ if (directRun) {
158
+ return {
159
+ kind: "agent",
160
+ agentId: directRun.agentId,
161
+ agentRunId: directRun.id,
162
+ threadId: directRun.threadId,
163
+ }
164
+ }
165
+ const agentRun = await this.workflowRunStorage.agentNodes.getByNodeRunId({
166
+ projectId: input.projectId,
167
+ nodeRunId: execution.executor.runId,
168
+ })
169
+ if (!agentRun) return undefined
170
+ const node = await this.workflowRunStorage.nodes.getById({
171
+ projectId: input.projectId,
172
+ id: agentRun.nodeRunId,
173
+ })
174
+ if (!node) return undefined
175
+ return {
176
+ kind: "workflowAgent",
177
+ agentId: agentRun.agentId,
178
+ nodeRunId: agentRun.nodeRunId,
179
+ workflowId: node.workflowId,
180
+ workflowRunId: node.workflowRunId,
181
+ }
182
+ }
183
+
138
184
  private readonly transactionScope = new AsyncLocalStorage<object>()
139
185
  private readonly activeTransactionTokens = new WeakSet<object>()
140
186
  private readonly materializationLifecycles = new WeakMap<
@@ -250,6 +296,7 @@ export class InMemoryStorage implements Storage {
250
296
  executions: this.executionStorage,
251
297
  agents: this.agentStorage,
252
298
  aiUsage: this.aiUsageStorage,
299
+ aiCosts: this.aiCostStorage,
253
300
  actionRuns: this.actionRunStorage,
254
301
  syncRuns: this.syncRunStorage,
255
302
  pipelineRuns: this.pipelineRunStorage,
@@ -276,6 +323,7 @@ export class InMemoryStorage implements Storage {
276
323
  executions: this.executionStorage.snapshot(),
277
324
  agents: this.agentStorage.snapshot(),
278
325
  aiUsage: this.aiUsageStorage.snapshot(),
326
+ aiCosts: this.aiCostStorage.snapshot(),
279
327
  actionRuns: this.actionRunStorage.snapshot(),
280
328
  syncRuns: this.syncRunStorage.snapshot(),
281
329
  pipelineRuns: this.pipelineRunStorage.snapshot(),
@@ -297,6 +345,7 @@ export class InMemoryStorage implements Storage {
297
345
  this.executionStorage.restore(snapshot.executions)
298
346
  this.agentStorage.restore(snapshot.agents)
299
347
  this.aiUsageStorage.restore(snapshot.aiUsage)
348
+ this.aiCostStorage.restore(snapshot.aiCosts)
300
349
  this.actionRunStorage.restore(snapshot.actionRuns)
301
350
  this.syncRunStorage.restore(snapshot.syncRuns)
302
351
  this.pipelineRunStorage.restore(snapshot.pipelineRuns)
@@ -318,6 +367,7 @@ export interface InMemoryStorageSnapshot {
318
367
  readonly executions: ReturnType<InMemoryExecutionStorage["snapshot"]>
319
368
  readonly agents: ReturnType<InMemoryAgentStorage["snapshot"]>
320
369
  readonly aiUsage: ReturnType<InMemoryAiUsageStorage["snapshot"]>
370
+ readonly aiCosts: ReturnType<InMemoryAiCostStorage["snapshot"]>
321
371
  readonly actionRuns: ReturnType<InMemoryActionRunStorage["snapshot"]>
322
372
  readonly syncRuns: ReturnType<InMemorySyncRunStorage["snapshot"]>
323
373
  readonly pipelineRuns: ReturnType<InMemoryPipelineRunStorage["snapshot"]>
@@ -52,6 +52,9 @@ export {
52
52
  isTerminalActionRun,
53
53
  } from "./action-runs"
54
54
  export type {
55
+ AgentContextCheckpointReason,
56
+ AgentContextCheckpointRecord,
57
+ AgentContextCheckpointStore,
55
58
  AgentExecutionStatus,
56
59
  AgentMessageRecord,
57
60
  AgentMessageRole,
@@ -72,8 +75,10 @@ export type {
72
75
  AgentThreadStore,
73
76
  AppendAgentMessageInput,
74
77
  ConfirmAgentRunExecutionOwnershipInput,
78
+ CreateAgentContextCheckpointInput,
75
79
  CreateAgentRunInput,
76
80
  CreateAgentThreadInput,
81
+ DeleteAgentMessagesByRunInput,
77
82
  FinishAgentRunInput,
78
83
  FinishQueuedAgentRunInput,
79
84
  ListAgentMessagesInput,
@@ -93,6 +98,38 @@ export {
93
98
  coerceAgentRunFinishReason,
94
99
  InMemoryAgentStorage,
95
100
  } from "./agents"
101
+ export type {
102
+ AiAccountingAgentBreakdown,
103
+ AiAccountingAggregate,
104
+ AiAccountingAttribution,
105
+ AiAccountingBucket,
106
+ AiAccountingModelBreakdown,
107
+ AiAccountingOverview,
108
+ AiAccountingRange,
109
+ AiAccountingTimeBucket,
110
+ AiAccountingWorkflowBreakdown,
111
+ AiBillableMeter,
112
+ AiBillingIdentity,
113
+ AiCostComponent,
114
+ AiCostStorage,
115
+ AiCostStorageErrorCode,
116
+ AiCostSummary,
117
+ AiModelCallAccountingItem,
118
+ AiModelCallCostRecord,
119
+ AiMoney,
120
+ AiPriceSource,
121
+ AiPricingContext,
122
+ AiRatedModelCallCostRecord,
123
+ AiUnpriceableModelCallCostRecord,
124
+ AiUnpriceableReason,
125
+ InMemoryAiAccountingAttributionSource,
126
+ InMemoryAiCostStorageSnapshot,
127
+ ListAiModelCallAccountingInput,
128
+ ListAiModelCallAccountingResult,
129
+ QueryAiAccountingOverviewInput,
130
+ SummarizeAiCostExecutionsInput,
131
+ } from "./ai-cost"
132
+ export { AiCostStorageError, InMemoryAiCostStorage } from "./ai-cost"
96
133
  export type {
97
134
  AiModelCallUsage,
98
135
  AiModelCallUsageInput,
@@ -588,6 +588,7 @@ export class InMemoryOntologyMaterializationStorage implements OntologyMateriali
588
588
  projectId,
589
589
  ref: item.ref,
590
590
  value: item.value,
591
+ editedAt: { ...item.editedAt },
591
592
  lastCommitId: item.lastCommitId,
592
593
  updatedAt: item.updatedAt,
593
594
  })
@@ -26,6 +26,8 @@ import type { StoredSourceLinkAssertion, StoredSourceObjectAssertion } from "./s
26
26
  export interface StoredObjectOverride {
27
27
  readonly ref: OntologyObjectRef
28
28
  readonly value: ObjectOverride
29
+ /** Per-property Action/runtime edit time. Legacy rows fall back to `updatedAt`. */
30
+ readonly editedAt?: Readonly<Record<string, string>>
29
31
  readonly lastCommitId: string
30
32
  readonly updatedAt: string
31
33
  }
@@ -160,6 +162,7 @@ export interface MaterializationPlanHeader {
160
162
  export interface ExactObjectOverrideWrite {
161
163
  readonly ref: OntologyObjectRef
162
164
  readonly value: ObjectOverride
165
+ readonly editedAt: Readonly<Record<string, string>>
163
166
  readonly expectedLastCommitId: string | null
164
167
  readonly lastCommitId: string
165
168
  readonly updatedAt: string
@@ -215,6 +215,7 @@ export function createAgentOperationScope<T extends AgentStorage>(
215
215
  threads: createOperationScopedFacade(target.threads, scope),
216
216
  runs: createOperationScopedFacade(target.runs, scope),
217
217
  messages: createOperationScopedFacade(target.messages, scope),
218
+ checkpoints: createOperationScopedFacade(target.checkpoints, scope),
218
219
  }) as T
219
220
  }
220
221
 
@@ -1,5 +1,6 @@
1
1
  import type { ActionRunStorage } from "./action-runs"
2
2
  import type { AgentStorage } from "./agents"
3
+ import type { AiCostStorage } from "./ai-cost"
3
4
  import type { AiUsageStorage } from "./ai-usage"
4
5
  import type { AuthStorage } from "./auth"
5
6
  import type { ConnectorConnectionStorage } from "./connector-connections"
@@ -293,6 +294,7 @@ export interface Storage {
293
294
  auth?: AuthStorage
294
295
  agents?: AgentStorage
295
296
  aiUsage?: AiUsageStorage
297
+ aiCosts?: AiCostStorage
296
298
  actionRuns?: ActionRunStorage
297
299
  syncRuns?: SyncRunStorage
298
300
  pipelineRuns?: PipelineRunStorage