@sixb/core 0.1.2 → 0.1.4
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/actions/index.js +19 -17
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/worker.js +4 -4
- package/dist/actions/worker.js.map +1 -1
- package/dist/agents/adapters.d.ts +11 -1
- package/dist/agents/adapters.d.ts.map +1 -1
- package/dist/agents/context-compaction.d.ts +40 -0
- package/dist/agents/context-compaction.d.ts.map +1 -0
- package/dist/agents/context.js +1 -1
- package/dist/agents/errors.d.ts +1 -1
- package/dist/agents/errors.d.ts.map +1 -1
- package/dist/agents/execution.d.ts +3 -0
- package/dist/agents/execution.d.ts.map +1 -1
- package/dist/agents/ids.d.ts +2 -0
- package/dist/agents/ids.d.ts.map +1 -1
- package/dist/agents/index.d.ts +10 -6
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +51 -35
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/request.d.ts.map +1 -1
- package/dist/agents/streams.d.ts +17 -1
- package/dist/agents/streams.d.ts.map +1 -1
- package/dist/agents/streams.js +5 -3
- package/dist/agents/streams.js.map +1 -1
- package/dist/agents/thread-context-projection.d.ts +19 -0
- package/dist/agents/thread-context-projection.d.ts.map +1 -0
- package/dist/agents/tool-definition.d.ts.map +1 -1
- package/dist/agents/tool-result.d.ts +6 -0
- package/dist/agents/tool-result.d.ts.map +1 -0
- package/dist/agents/types.d.ts +46 -1
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/validation.d.ts +2 -2
- package/dist/agents/validation.d.ts.map +1 -1
- package/dist/auth/index.js +11 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/authorization/index.js +6 -2
- package/dist/authorization/index.js.map +1 -1
- package/dist/bootstrap/index.js +33 -31
- package/dist/bootstrap/index.js.map +1 -1
- package/dist/{chunk-ys1nse2v.js → chunk-1wk20wn3.js} +438 -246
- package/dist/{chunk-ys1nse2v.js.map → chunk-1wk20wn3.js.map} +11 -11
- package/dist/{chunk-sa4340y1.js → chunk-1yz3x65y.js} +7 -7
- package/dist/{chunk-gqdd7508.js → chunk-4rh2m6zg.js} +1 -1
- package/dist/{chunk-fmx1sz2h.js → chunk-72q2y8hn.js} +3 -3
- package/dist/{chunk-fmx1sz2h.js.map → chunk-72q2y8hn.js.map} +4 -4
- package/dist/{chunk-nnyp53zt.js → chunk-7gw7mepb.js} +259 -9
- package/dist/chunk-7gw7mepb.js.map +14 -0
- package/dist/{chunk-nten49ta.js → chunk-819wnjz8.js} +11 -5
- package/dist/chunk-819wnjz8.js.map +12 -0
- package/dist/chunk-96swp2wd.js +47 -0
- package/dist/chunk-96swp2wd.js.map +10 -0
- package/dist/{chunk-4x43bkbp.js → chunk-a09jx26t.js} +1 -1
- package/dist/{chunk-st4xgxey.js → chunk-afjpa2hs.js} +205 -48
- package/dist/chunk-afjpa2hs.js.map +45 -0
- package/dist/{chunk-0mbx247x.js → chunk-c43qr6s0.js} +427 -99
- package/dist/chunk-c43qr6s0.js.map +46 -0
- package/dist/{chunk-zg9yryhz.js → chunk-cbkm2vam.js} +3 -3
- package/dist/{chunk-57zpb99z.js → chunk-dm72y8wt.js} +26 -3
- package/dist/chunk-dm72y8wt.js.map +10 -0
- package/dist/chunk-drjqm1aa.js +913 -0
- package/dist/chunk-drjqm1aa.js.map +15 -0
- package/dist/chunk-dysw7nsm.js.map +1 -1
- package/dist/{chunk-8ryen3p3.js → chunk-f6erf2vv.js} +1 -1
- package/dist/{chunk-34bqh420.js → chunk-f83ka50g.js} +188 -44
- package/dist/chunk-f83ka50g.js.map +23 -0
- package/dist/{chunk-bct7hmfc.js → chunk-g13869yf.js} +3 -3
- package/dist/{chunk-0k4avqr2.js → chunk-hbeh8j9w.js} +2 -2
- package/dist/{chunk-qvybnnpe.js → chunk-nzw7sm3b.js} +2 -2
- package/dist/{chunk-k9pts48t.js → chunk-pn8n8sba.js} +16 -15
- package/dist/{chunk-k9pts48t.js.map → chunk-pn8n8sba.js.map} +3 -3
- package/dist/{chunk-63te770v.js → chunk-qewtks41.js} +8 -8
- package/dist/{chunk-63te770v.js.map → chunk-qewtks41.js.map} +2 -2
- package/dist/{chunk-85j5ybfj.js → chunk-rdeagz6b.js} +2 -2
- package/dist/{chunk-85j5ybfj.js.map → chunk-rdeagz6b.js.map} +3 -3
- package/dist/chunk-rhrbz7d4.js +36 -0
- package/dist/chunk-rhrbz7d4.js.map +10 -0
- package/dist/{chunk-bh6k3nc6.js → chunk-rs1wbvy6.js} +1 -1
- package/dist/{chunk-88ppkr54.js → chunk-rte5sm3z.js} +26 -2
- package/dist/chunk-rte5sm3z.js.map +12 -0
- package/dist/{chunk-n8herkry.js → chunk-s31d0qmq.js} +57 -20
- package/dist/{chunk-n8herkry.js.map → chunk-s31d0qmq.js.map} +8 -7
- package/dist/{chunk-7pc8b818.js → chunk-sxwk41qa.js} +1 -1
- package/dist/{chunk-663csy96.js → chunk-t0jjkep3.js} +2 -1
- package/dist/chunk-t0jjkep3.js.map +10 -0
- package/dist/{chunk-c6qd89g5.js → chunk-tek1qwgm.js} +6 -6
- package/dist/{chunk-c6qd89g5.js.map → chunk-tek1qwgm.js.map} +1 -1
- package/dist/{chunk-jaj13078.js → chunk-tkdscva6.js} +3 -33
- package/dist/{chunk-jaj13078.js.map → chunk-tkdscva6.js.map} +3 -4
- package/dist/{chunk-5egfka8j.js → chunk-tycbzedm.js} +2 -2
- package/dist/{chunk-g9t3kdtx.js → chunk-x2yw73hy.js} +1 -1
- package/dist/{chunk-57r1cpxx.js → chunk-xc7hq65b.js} +3 -2
- package/dist/{chunk-57r1cpxx.js.map → chunk-xc7hq65b.js.map} +3 -3
- package/dist/chunk-xdf9yswn.js +133 -0
- package/dist/chunk-xdf9yswn.js.map +11 -0
- package/dist/execution/agent.js +29 -27
- package/dist/execution/agent.js.map +1 -1
- package/dist/execution/primitive.js +29 -27
- package/dist/execution/primitive.js.map +2 -2
- package/dist/execution/request.js +30 -28
- package/dist/execution/request.js.map +2 -2
- package/dist/http/index.js +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -58
- package/dist/index.js.map +1 -1
- package/dist/lake-storage/types.d.ts +2 -1
- package/dist/lake-storage/types.d.ts.map +1 -1
- package/dist/materialization/model.d.ts +4 -0
- package/dist/materialization/model.d.ts.map +1 -1
- package/dist/materializer/edits/apply.d.ts +6 -1
- package/dist/materializer/edits/apply.d.ts.map +1 -1
- package/dist/materializer/edits/working-state.d.ts +2 -0
- package/dist/materializer/edits/working-state.d.ts.map +1 -1
- package/dist/materializer/effective/resolve.d.ts +3 -1
- package/dist/materializer/effective/resolve.d.ts.map +1 -1
- package/dist/materializer/execution/work-records.d.ts.map +1 -1
- package/dist/materializer/index.js +19 -17
- package/dist/materializer/index.js.map +1 -1
- package/dist/materializer/projections/replacement-plan.d.ts.map +1 -1
- package/dist/materializer/shared/normalize.d.ts.map +1 -1
- package/dist/models/index.d.ts +40 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/objects/execution.d.ts +6 -1
- package/dist/objects/execution.d.ts.map +1 -1
- package/dist/objects/index.js +21 -19
- package/dist/objects/index.js.map +1 -1
- package/dist/objects/link/endpoints.d.ts.map +1 -1
- package/dist/objects/query/executor.d.ts +1 -0
- package/dist/objects/query/executor.d.ts.map +1 -1
- package/dist/objects/query/explain.d.ts +1 -0
- package/dist/objects/query/explain.d.ts.map +1 -1
- package/dist/objects/query/index.d.ts +3 -1
- package/dist/objects/query/index.d.ts.map +1 -1
- package/dist/objects/query/index.js +30 -5
- package/dist/objects/query/index.js.map +1 -1
- package/dist/objects/query/ir.d.ts +14 -1
- package/dist/objects/query/ir.d.ts.map +1 -1
- package/dist/objects/query/links.d.ts +25 -0
- package/dist/objects/query/links.d.ts.map +1 -0
- package/dist/objects/query/normalize.d.ts.map +1 -1
- package/dist/objects/query/planner.d.ts.map +1 -1
- package/dist/objects/query/validate.d.ts +1 -0
- package/dist/objects/query/validate.d.ts.map +1 -1
- package/dist/pipelines/index.js +16 -15
- package/dist/pipelines/index.js.map +1 -1
- package/dist/projections/builders.d.ts +52 -10
- package/dist/projections/builders.d.ts.map +1 -1
- package/dist/projections/index.d.ts +2 -1
- package/dist/projections/index.d.ts.map +1 -1
- package/dist/projections/internal.d.ts +4 -1
- package/dist/projections/internal.d.ts.map +1 -1
- package/dist/projections/internal.js +22 -17
- package/dist/projections/internal.js.map +1 -1
- package/dist/projections/run-dispatch.d.ts +1 -0
- package/dist/projections/run-dispatch.d.ts.map +1 -1
- package/dist/projections/timestamp.d.ts +3 -0
- package/dist/projections/timestamp.d.ts.map +1 -0
- package/dist/projections/types.d.ts +21 -9
- package/dist/projections/types.d.ts.map +1 -1
- package/dist/projections/validation.d.ts +6 -5
- package/dist/projections/validation.d.ts.map +1 -1
- package/dist/query.d.ts +2 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +2 -2
- package/dist/queues/index.d.ts +1 -1
- package/dist/queues/index.d.ts.map +1 -1
- package/dist/queues/types.d.ts +7 -0
- package/dist/queues/types.d.ts.map +1 -1
- package/dist/runtime/ai-cost.d.ts +8 -0
- package/dist/runtime/ai-cost.d.ts.map +1 -0
- package/dist/runtime/create.d.ts +3 -0
- package/dist/runtime/create.d.ts.map +1 -1
- package/dist/runtime/definitions.d.ts +3 -0
- package/dist/runtime/definitions.d.ts.map +1 -1
- package/dist/runtime/host.d.ts +2 -0
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/resolve-definitions.d.ts +2 -0
- package/dist/runtime/resolve-definitions.d.ts.map +1 -1
- package/dist/storage/agents/in-memory.d.ts +22 -4
- package/dist/storage/agents/in-memory.d.ts.map +1 -1
- package/dist/storage/agents/index.d.ts +1 -1
- package/dist/storage/agents/index.d.ts.map +1 -1
- package/dist/storage/agents/provider.d.ts +31 -0
- package/dist/storage/agents/provider.d.ts.map +1 -1
- package/dist/storage/agents/provider.js +22 -7
- package/dist/storage/agents/provider.js.map +1 -1
- package/dist/storage/agents/types.d.ts +75 -4
- package/dist/storage/agents/types.d.ts.map +1 -1
- package/dist/storage/ai-cost/analytics.d.ts +61 -0
- package/dist/storage/ai-cost/analytics.d.ts.map +1 -0
- package/dist/storage/ai-cost/codec.d.ts +14 -0
- package/dist/storage/ai-cost/codec.d.ts.map +1 -0
- package/dist/storage/ai-cost/errors.d.ts +7 -0
- package/dist/storage/ai-cost/errors.d.ts.map +1 -0
- package/dist/storage/ai-cost/in-memory.d.ts +32 -0
- package/dist/storage/ai-cost/in-memory.d.ts.map +1 -0
- package/dist/storage/ai-cost/index.d.ts +6 -0
- package/dist/storage/ai-cost/index.d.ts.map +1 -0
- package/dist/storage/ai-cost/provider.d.ts +7 -0
- package/dist/storage/ai-cost/provider.d.ts.map +1 -0
- package/dist/storage/ai-cost/provider.js +132 -0
- package/dist/storage/ai-cost/provider.js.map +10 -0
- package/dist/storage/ai-cost/types.d.ts +154 -0
- package/dist/storage/ai-cost/types.d.ts.map +1 -0
- package/dist/storage/ai-cost/validation.d.ts +7 -0
- package/dist/storage/ai-cost/validation.d.ts.map +1 -0
- package/dist/storage/ai-usage/in-memory.d.ts +2 -1
- package/dist/storage/ai-usage/in-memory.d.ts.map +1 -1
- package/dist/storage/ai-usage/index.d.ts +1 -1
- package/dist/storage/ai-usage/index.d.ts.map +1 -1
- package/dist/storage/ai-usage/types.d.ts +6 -0
- package/dist/storage/ai-usage/types.d.ts.map +1 -1
- package/dist/storage/in-memory/index.d.ts +5 -0
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +6 -3
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +33 -21
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/objects/in-memory.d.ts +6 -3
- package/dist/storage/objects/in-memory.d.ts.map +1 -1
- package/dist/storage/objects/index.d.ts +3 -1
- package/dist/storage/objects/index.d.ts.map +1 -1
- package/dist/storage/objects/keys.d.ts +16 -0
- package/dist/storage/objects/keys.d.ts.map +1 -0
- package/dist/storage/objects/types.d.ts +33 -6
- package/dist/storage/objects/types.d.ts.map +1 -1
- package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
- package/dist/storage/ontology/materializations.d.ts +3 -0
- package/dist/storage/ontology/materializations.d.ts.map +1 -1
- package/dist/storage/ontology/provider.js +1 -1
- package/dist/storage/operation-scope.d.ts.map +1 -1
- package/dist/storage/operation-scope.js +1 -1
- package/dist/storage/types.d.ts +2 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/workflow-runs/provider.js +10 -5
- package/dist/storage/workflow-runs/provider.js.map +1 -1
- package/dist/syncs/index.js +16 -15
- package/dist/syncs/index.js.map +1 -1
- package/dist/testing/agent-storage-contract.d.ts +3 -2
- package/dist/testing/agent-storage-contract.d.ts.map +1 -1
- package/dist/testing/ai-cost-storage-contract.d.ts +12 -0
- package/dist/testing/ai-cost-storage-contract.d.ts.map +1 -0
- package/dist/testing/ai-usage-storage-contract.d.ts.map +1 -1
- package/dist/testing/effective-storage-contract.d.ts +1 -1
- package/dist/testing/effective-storage-contract.d.ts.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2525 -1480
- package/dist/testing/index.js.map +10 -9
- package/dist/testing/lake-storage-contract.d.ts.map +1 -1
- package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
- package/dist/testing/object-query-contract.d.ts +1 -1
- package/dist/testing/object-query-contract.d.ts.map +1 -1
- package/dist/webhooks/internal.js +14 -13
- package/dist/webhooks/internal.js.map +2 -2
- package/dist/workers/index.js +12 -2
- package/dist/workers/index.js.map +3 -3
- package/dist/workers/queue-worker.d.ts +2 -0
- package/dist/workers/queue-worker.d.ts.map +1 -1
- package/dist/workflows/execution.d.ts +3 -0
- package/dist/workflows/execution.d.ts.map +1 -1
- package/dist/workflows/index.js +30 -28
- package/dist/workflows/index.js.map +1 -1
- package/package.json +7 -1
- package/src/agents/adapters.ts +64 -23
- package/src/agents/builders.ts +3 -3
- package/src/agents/context-compaction.ts +261 -0
- package/src/agents/errors.ts +2 -2
- package/src/agents/execution.ts +14 -2
- package/src/agents/ids.ts +5 -0
- package/src/agents/index.ts +32 -6
- package/src/agents/request.ts +5 -0
- package/src/agents/streams.ts +56 -0
- package/src/agents/thread-context-projection.ts +93 -0
- package/src/agents/tool-definition.ts +17 -2
- package/src/agents/tool-result.ts +61 -0
- package/src/agents/types.ts +57 -1
- package/src/agents/validation.ts +36 -6
- package/src/http/api-routes.ts +7 -7
- package/src/index.ts +27 -0
- package/src/lake-storage/types.ts +2 -1
- package/src/materialization/model.ts +4 -0
- package/src/materializer/edits/apply.ts +55 -2
- package/src/materializer/edits/operations.ts +8 -1
- package/src/materializer/edits/working-state.ts +7 -0
- package/src/materializer/effective/resolve.ts +78 -9
- package/src/materializer/execution/work-records.ts +3 -1
- package/src/materializer/projections/entry-validator.ts +23 -1
- package/src/materializer/projections/replacement-plan.ts +2 -0
- package/src/materializer/shared/normalize.ts +16 -0
- package/src/materializer/telemetry/append.ts +33 -16
- package/src/models/index.ts +71 -0
- package/src/objects/execution.ts +20 -0
- package/src/objects/link/endpoints.ts +2 -1
- package/src/objects/query/executor.ts +42 -11
- package/src/objects/query/explain.ts +11 -0
- package/src/objects/query/index.ts +6 -0
- package/src/objects/query/ir.ts +16 -0
- package/src/objects/query/links.ts +308 -0
- package/src/objects/query/normalize.ts +40 -1
- package/src/objects/query/planner.ts +5 -0
- package/src/objects/query/validate.ts +58 -2
- package/src/projections/builders.ts +320 -50
- package/src/projections/index.ts +7 -0
- package/src/projections/internal.ts +4 -1
- package/src/projections/ownership.ts +5 -5
- package/src/projections/registry.ts +16 -3
- package/src/projections/revision.ts +35 -5
- package/src/projections/run-dispatch.ts +10 -1
- package/src/projections/timestamp.ts +66 -0
- package/src/projections/types.ts +20 -9
- package/src/projections/validation.ts +85 -19
- package/src/query.ts +2 -0
- package/src/queues/index.ts +1 -0
- package/src/queues/types.ts +8 -0
- package/src/runtime/ai-cost.ts +15 -0
- package/src/runtime/create.ts +4 -0
- package/src/runtime/definitions.ts +3 -0
- package/src/runtime/host.ts +2 -0
- package/src/runtime/resolve-definitions.ts +28 -0
- package/src/storage/agents/in-memory.ts +186 -4
- package/src/storage/agents/index.ts +5 -0
- package/src/storage/agents/provider.ts +225 -0
- package/src/storage/agents/types.ts +82 -3
- package/src/storage/ai-cost/analytics.ts +602 -0
- package/src/storage/ai-cost/codec.ts +136 -0
- package/src/storage/ai-cost/errors.ts +12 -0
- package/src/storage/ai-cost/in-memory.ts +164 -0
- package/src/storage/ai-cost/index.ts +35 -0
- package/src/storage/ai-cost/provider.ts +20 -0
- package/src/storage/ai-cost/types.ts +193 -0
- package/src/storage/ai-cost/validation.ts +337 -0
- package/src/storage/ai-usage/in-memory.ts +23 -0
- package/src/storage/ai-usage/index.ts +1 -0
- package/src/storage/ai-usage/types.ts +10 -0
- package/src/storage/in-memory/index.ts +50 -0
- package/src/storage/index.ts +48 -0
- package/src/storage/objects/in-memory.ts +103 -16
- package/src/storage/objects/index.ts +9 -0
- package/src/storage/objects/keys.ts +36 -0
- package/src/storage/objects/types.ts +34 -6
- package/src/storage/ontology/in-memory/materializations.ts +1 -0
- package/src/storage/ontology/materializations.ts +3 -0
- package/src/storage/ontology/provider-header-validation.ts +2 -2
- package/src/storage/operation-scope.ts +1 -0
- package/src/storage/types.ts +2 -0
- package/src/testing/agent-storage-contract.ts +353 -2
- package/src/testing/ai-cost-storage-contract.ts +320 -0
- package/src/testing/ai-usage-storage-contract.ts +35 -0
- package/src/testing/effective-storage-contract.ts +194 -4
- package/src/testing/index.ts +5 -0
- package/src/testing/lake-storage-contract.ts +56 -0
- package/src/testing/materializer-storage-contract.ts +120 -2
- package/src/testing/object-query-contract.ts +208 -1
- package/src/workers/queue-worker.ts +14 -1
- package/src/workflows/execution.ts +31 -10
- package/dist/agents/prompt.d.ts +0 -15
- package/dist/agents/prompt.d.ts.map +0 -1
- package/dist/chunk-0mbx247x.js.map +0 -43
- package/dist/chunk-34bqh420.js.map +0 -23
- package/dist/chunk-57zpb99z.js.map +0 -10
- package/dist/chunk-663csy96.js.map +0 -10
- package/dist/chunk-6wetht33.js +0 -89
- package/dist/chunk-6wetht33.js.map +0 -10
- package/dist/chunk-88ppkr54.js.map +0 -12
- package/dist/chunk-nnyp53zt.js.map +0 -13
- package/dist/chunk-nten49ta.js.map +0 -12
- package/dist/chunk-st4xgxey.js.map +0 -45
- package/dist/chunk-vp1vc6ee.js +0 -35
- package/dist/chunk-vp1vc6ee.js.map +0 -11
- package/src/agents/prompt.ts +0 -57
- /package/dist/{chunk-sa4340y1.js.map → chunk-1yz3x65y.js.map} +0 -0
- /package/dist/{chunk-gqdd7508.js.map → chunk-4rh2m6zg.js.map} +0 -0
- /package/dist/{chunk-4x43bkbp.js.map → chunk-a09jx26t.js.map} +0 -0
- /package/dist/{chunk-zg9yryhz.js.map → chunk-cbkm2vam.js.map} +0 -0
- /package/dist/{chunk-8ryen3p3.js.map → chunk-f6erf2vv.js.map} +0 -0
- /package/dist/{chunk-bct7hmfc.js.map → chunk-g13869yf.js.map} +0 -0
- /package/dist/{chunk-0k4avqr2.js.map → chunk-hbeh8j9w.js.map} +0 -0
- /package/dist/{chunk-qvybnnpe.js.map → chunk-nzw7sm3b.js.map} +0 -0
- /package/dist/{chunk-bh6k3nc6.js.map → chunk-rs1wbvy6.js.map} +0 -0
- /package/dist/{chunk-7pc8b818.js.map → chunk-sxwk41qa.js.map} +0 -0
- /package/dist/{chunk-5egfka8j.js.map → chunk-tycbzedm.js.map} +0 -0
- /package/dist/{chunk-g9t3kdtx.js.map → chunk-x2yw73hy.js.map} +0 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AiModelCallUsage,
|
|
3
|
+
type AiModelCallUsageRecord,
|
|
4
|
+
aggregateAiModelCallUsage,
|
|
5
|
+
normalizeAiModelCallUsage,
|
|
6
|
+
} from "../ai-usage"
|
|
7
|
+
import type {
|
|
8
|
+
AiAccountingAgentBreakdown,
|
|
9
|
+
AiAccountingAggregate,
|
|
10
|
+
AiAccountingAttribution,
|
|
11
|
+
AiAccountingBucket,
|
|
12
|
+
AiAccountingModelBreakdown,
|
|
13
|
+
AiAccountingOverview,
|
|
14
|
+
AiAccountingTimeBucket,
|
|
15
|
+
AiAccountingWorkflowBreakdown,
|
|
16
|
+
AiCostSummary,
|
|
17
|
+
AiModelCallAccountingItem,
|
|
18
|
+
AiModelCallCostRecord,
|
|
19
|
+
AiMoney,
|
|
20
|
+
ListAiModelCallAccountingInput,
|
|
21
|
+
ListAiModelCallAccountingResult,
|
|
22
|
+
QueryAiAccountingOverviewInput,
|
|
23
|
+
} from "./types"
|
|
24
|
+
|
|
25
|
+
const DEFAULT_MODEL_CALL_PAGE_SIZE = 50
|
|
26
|
+
const MAX_MODEL_CALL_PAGE_SIZE = 200
|
|
27
|
+
const MAX_CHART_BUCKETS = 10_000
|
|
28
|
+
|
|
29
|
+
/** One joined immutable model-call row used by in-memory analytics and bounded SQL pages. */
|
|
30
|
+
export interface AiAccountingRecordSetItem {
|
|
31
|
+
readonly usage: AiModelCallUsageRecord
|
|
32
|
+
readonly attribution?: AiAccountingAttribution
|
|
33
|
+
readonly cost?: AiModelCallCostRecord
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NormalizedAiAccountingQuery {
|
|
37
|
+
readonly projectId: string
|
|
38
|
+
readonly from: Date
|
|
39
|
+
readonly to: Date
|
|
40
|
+
readonly providerId?: string
|
|
41
|
+
readonly modelId?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface NormalizedAiModelCallAccountingQuery extends NormalizedAiAccountingQuery {
|
|
45
|
+
readonly executionId?: string
|
|
46
|
+
readonly valuationStatus?: ListAiModelCallAccountingInput["valuationStatus"]
|
|
47
|
+
readonly limit: number
|
|
48
|
+
readonly offset: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface AiAccountingUsageMeterFragment {
|
|
52
|
+
readonly presentCallCount: string
|
|
53
|
+
readonly total: string | null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One SQL-aggregated currency fragment; providers merge fragments without loading model calls. */
|
|
57
|
+
export interface AiAccountingAggregateFragment {
|
|
58
|
+
readonly dimension: "totals" | "series" | "model" | "agent" | "workflow"
|
|
59
|
+
readonly start?: Date
|
|
60
|
+
readonly providerId?: string
|
|
61
|
+
readonly modelId?: string
|
|
62
|
+
readonly agentId?: string
|
|
63
|
+
readonly workflowId?: string
|
|
64
|
+
readonly modelCallCount: string
|
|
65
|
+
readonly usage: {
|
|
66
|
+
readonly reportedCallCount: string
|
|
67
|
+
readonly inputTokens: AiAccountingUsageMeterFragment
|
|
68
|
+
readonly outputTokens: AiAccountingUsageMeterFragment
|
|
69
|
+
readonly uncachedInputTokens: AiAccountingUsageMeterFragment
|
|
70
|
+
readonly cacheReadInputTokens: AiAccountingUsageMeterFragment
|
|
71
|
+
readonly cacheWriteInputTokens: AiAccountingUsageMeterFragment
|
|
72
|
+
readonly textOutputTokens: AiAccountingUsageMeterFragment
|
|
73
|
+
readonly reasoningOutputTokens: AiAccountingUsageMeterFragment
|
|
74
|
+
}
|
|
75
|
+
readonly costs: {
|
|
76
|
+
readonly amount?: AiMoney
|
|
77
|
+
readonly ratedCallCount: string
|
|
78
|
+
readonly unpriceableCallCount: string
|
|
79
|
+
readonly unvaluedCallCount: string
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function normalizeAiAccountingQuery(
|
|
84
|
+
input: Omit<QueryAiAccountingOverviewInput, "bucket"> | ListAiModelCallAccountingInput
|
|
85
|
+
): NormalizedAiAccountingQuery {
|
|
86
|
+
assertNonBlank(input.projectId, "accounting projectId")
|
|
87
|
+
assertDate(input.from, "accounting from")
|
|
88
|
+
assertDate(input.to, "accounting to")
|
|
89
|
+
if (input.from.getTime() >= input.to.getTime()) {
|
|
90
|
+
throw new TypeError("[Sixb] AI accounting from must be before to.")
|
|
91
|
+
}
|
|
92
|
+
if (input.providerId !== undefined) assertNonBlank(input.providerId, "accounting providerId")
|
|
93
|
+
if (input.modelId !== undefined) assertNonBlank(input.modelId, "accounting modelId")
|
|
94
|
+
return {
|
|
95
|
+
projectId: input.projectId,
|
|
96
|
+
from: new Date(input.from),
|
|
97
|
+
to: new Date(input.to),
|
|
98
|
+
...(input.providerId === undefined ? {} : { providerId: input.providerId }),
|
|
99
|
+
...(input.modelId === undefined ? {} : { modelId: input.modelId }),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function normalizeAiModelCallAccountingQuery(
|
|
104
|
+
input: ListAiModelCallAccountingInput
|
|
105
|
+
): NormalizedAiModelCallAccountingQuery {
|
|
106
|
+
const query = normalizeAiAccountingQuery(input)
|
|
107
|
+
if (input.executionId !== undefined) {
|
|
108
|
+
assertNonBlank(input.executionId, "accounting executionId")
|
|
109
|
+
}
|
|
110
|
+
const { limit, offset } = normalizePage(input.limit, input.offset)
|
|
111
|
+
return {
|
|
112
|
+
...query,
|
|
113
|
+
...(input.executionId === undefined ? {} : { executionId: input.executionId }),
|
|
114
|
+
...(input.valuationStatus === undefined ? {} : { valuationStatus: input.valuationStatus }),
|
|
115
|
+
limit,
|
|
116
|
+
offset,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function aiAccountingItemMatchesQuery(
|
|
121
|
+
item: AiAccountingRecordSetItem,
|
|
122
|
+
query: NormalizedAiAccountingQuery
|
|
123
|
+
): boolean {
|
|
124
|
+
const occurredAt = item.usage.occurredAt.getTime()
|
|
125
|
+
return (
|
|
126
|
+
item.usage.projectId === query.projectId &&
|
|
127
|
+
occurredAt >= query.from.getTime() &&
|
|
128
|
+
occurredAt < query.to.getTime() &&
|
|
129
|
+
(query.providerId === undefined || item.usage.providerId === query.providerId) &&
|
|
130
|
+
(query.modelId === undefined || item.usage.requestedModelId === query.modelId)
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function buildAiAccountingOverview(
|
|
135
|
+
input: QueryAiAccountingOverviewInput,
|
|
136
|
+
source: Iterable<AiAccountingRecordSetItem>
|
|
137
|
+
): AiAccountingOverview {
|
|
138
|
+
const query = normalizeAiAccountingQuery(input)
|
|
139
|
+
assertAiAccountingBucket(input.bucket)
|
|
140
|
+
const items = Array.from(source).filter((item) => aiAccountingItemMatchesQuery(item, query))
|
|
141
|
+
const periods = accountingPeriods(query.from, query.to, input.bucket)
|
|
142
|
+
const periodItems = new Map<number, AiAccountingRecordSetItem[]>(
|
|
143
|
+
periods.map((period) => [period.start.getTime(), []])
|
|
144
|
+
)
|
|
145
|
+
const modelItems = new Map<string, AiAccountingRecordSetItem[]>()
|
|
146
|
+
const agentItems = new Map<string, AiAccountingRecordSetItem[]>()
|
|
147
|
+
const workflowItems = new Map<string, AiAccountingRecordSetItem[]>()
|
|
148
|
+
|
|
149
|
+
for (const item of items) {
|
|
150
|
+
const periodStart = aiAccountingBucketStart(item.usage.occurredAt, input.bucket).getTime()
|
|
151
|
+
periodItems.get(periodStart)?.push(item)
|
|
152
|
+
appendGrouped(
|
|
153
|
+
modelItems,
|
|
154
|
+
JSON.stringify([item.usage.providerId, item.usage.requestedModelId]),
|
|
155
|
+
item
|
|
156
|
+
)
|
|
157
|
+
if (item.attribution) appendGrouped(agentItems, item.attribution.agentId, item)
|
|
158
|
+
if (item.attribution?.kind === "workflowAgent") {
|
|
159
|
+
appendGrouped(workflowItems, item.attribution.workflowId, item)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const series: AiAccountingTimeBucket[] = periods.map((period) => ({
|
|
164
|
+
...period,
|
|
165
|
+
...aggregateAccountingItems(periodItems.get(period.start.getTime()) ?? []),
|
|
166
|
+
}))
|
|
167
|
+
const models: AiAccountingModelBreakdown[] = [...modelItems]
|
|
168
|
+
.map(([key, grouped]) => {
|
|
169
|
+
const [providerId, modelId] = JSON.parse(key) as [string, string]
|
|
170
|
+
return { providerId, modelId, ...aggregateAccountingItems(grouped) }
|
|
171
|
+
})
|
|
172
|
+
.sort(compareModelBreakdowns)
|
|
173
|
+
const agents: AiAccountingAgentBreakdown[] = [...agentItems]
|
|
174
|
+
.map(([agentId, grouped]) => ({ agentId, ...aggregateAccountingItems(grouped) }))
|
|
175
|
+
.sort(compareAgentBreakdowns)
|
|
176
|
+
const workflows: AiAccountingWorkflowBreakdown[] = [...workflowItems]
|
|
177
|
+
.map(([workflowId, grouped]) => ({ workflowId, ...aggregateAccountingItems(grouped) }))
|
|
178
|
+
.sort(compareWorkflowBreakdowns)
|
|
179
|
+
|
|
180
|
+
return structuredClone({
|
|
181
|
+
range: { from: query.from, to: query.to },
|
|
182
|
+
bucket: input.bucket,
|
|
183
|
+
totals: aggregateAccountingItems(items),
|
|
184
|
+
series,
|
|
185
|
+
models,
|
|
186
|
+
agents,
|
|
187
|
+
workflows,
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function buildAiAccountingOverviewFromFragments(
|
|
192
|
+
input: QueryAiAccountingOverviewInput,
|
|
193
|
+
fragments: Iterable<AiAccountingAggregateFragment>
|
|
194
|
+
): AiAccountingOverview {
|
|
195
|
+
const query = normalizeAiAccountingQuery(input)
|
|
196
|
+
const periods = accountingPeriods(query.from, query.to, input.bucket)
|
|
197
|
+
const totals = aggregateFragmentAccumulator()
|
|
198
|
+
const series = new Map<number, AggregateFragmentAccumulator>(
|
|
199
|
+
periods.map((period) => [period.start.getTime(), aggregateFragmentAccumulator()])
|
|
200
|
+
)
|
|
201
|
+
const models = new Map<string, AggregateFragmentAccumulator>()
|
|
202
|
+
const agents = new Map<string, AggregateFragmentAccumulator>()
|
|
203
|
+
const workflows = new Map<string, AggregateFragmentAccumulator>()
|
|
204
|
+
|
|
205
|
+
for (const fragment of fragments) {
|
|
206
|
+
let target: AggregateFragmentAccumulator
|
|
207
|
+
switch (fragment.dimension) {
|
|
208
|
+
case "totals":
|
|
209
|
+
target = totals
|
|
210
|
+
break
|
|
211
|
+
case "series": {
|
|
212
|
+
const start = requiredFragmentDate(fragment.start, "series start")
|
|
213
|
+
const existing = series.get(start.getTime())
|
|
214
|
+
if (!existing) {
|
|
215
|
+
throw new TypeError("[Sixb] AI accounting SQL series returned an unexpected bucket.")
|
|
216
|
+
}
|
|
217
|
+
target = existing
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
case "model":
|
|
221
|
+
target = getFragmentAccumulator(
|
|
222
|
+
models,
|
|
223
|
+
JSON.stringify([
|
|
224
|
+
requiredFragmentString(fragment.providerId, "providerId"),
|
|
225
|
+
requiredFragmentString(fragment.modelId, "modelId"),
|
|
226
|
+
])
|
|
227
|
+
)
|
|
228
|
+
break
|
|
229
|
+
case "agent":
|
|
230
|
+
target = getFragmentAccumulator(agents, requiredFragmentString(fragment.agentId, "agentId"))
|
|
231
|
+
break
|
|
232
|
+
case "workflow":
|
|
233
|
+
target = getFragmentAccumulator(
|
|
234
|
+
workflows,
|
|
235
|
+
requiredFragmentString(fragment.workflowId, "workflowId")
|
|
236
|
+
)
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
appendAggregateFragment(target, fragment)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return structuredClone({
|
|
243
|
+
range: { from: query.from, to: query.to },
|
|
244
|
+
bucket: input.bucket,
|
|
245
|
+
totals: finishAggregateFragment(totals),
|
|
246
|
+
series: periods.map((period) => ({
|
|
247
|
+
...period,
|
|
248
|
+
...finishAggregateFragment(series.get(period.start.getTime())!),
|
|
249
|
+
})),
|
|
250
|
+
models: [...models]
|
|
251
|
+
.map(([key, value]) => {
|
|
252
|
+
const [providerId, modelId] = JSON.parse(key) as [string, string]
|
|
253
|
+
return { providerId, modelId, ...finishAggregateFragment(value) }
|
|
254
|
+
})
|
|
255
|
+
.sort(compareModelBreakdowns),
|
|
256
|
+
agents: [...agents]
|
|
257
|
+
.map(([agentId, value]) => ({ agentId, ...finishAggregateFragment(value) }))
|
|
258
|
+
.sort(compareAgentBreakdowns),
|
|
259
|
+
workflows: [...workflows]
|
|
260
|
+
.map(([workflowId, value]) => ({ workflowId, ...finishAggregateFragment(value) }))
|
|
261
|
+
.sort(compareWorkflowBreakdowns),
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function buildAiModelCallAccountingList(
|
|
266
|
+
input: ListAiModelCallAccountingInput,
|
|
267
|
+
source: Iterable<AiAccountingRecordSetItem>
|
|
268
|
+
): ListAiModelCallAccountingResult {
|
|
269
|
+
const query = normalizeAiModelCallAccountingQuery(input)
|
|
270
|
+
const { limit, offset } = query
|
|
271
|
+
const items = Array.from(source)
|
|
272
|
+
.filter((item) => aiAccountingItemMatchesQuery(item, query))
|
|
273
|
+
.map(toAccountingItem)
|
|
274
|
+
.filter(
|
|
275
|
+
(item) =>
|
|
276
|
+
(input.executionId === undefined || item.usage.executionId === input.executionId) &&
|
|
277
|
+
(input.valuationStatus === undefined || item.valuationStatus === input.valuationStatus)
|
|
278
|
+
)
|
|
279
|
+
.sort(compareAccountingItems)
|
|
280
|
+
const page = items.slice(offset, offset + limit)
|
|
281
|
+
return structuredClone({
|
|
282
|
+
items: page,
|
|
283
|
+
hasMore: offset + page.length < items.length,
|
|
284
|
+
total: items.length,
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function toAccountingItem(item: AiAccountingRecordSetItem): AiModelCallAccountingItem {
|
|
289
|
+
return {
|
|
290
|
+
usage: item.usage,
|
|
291
|
+
...(item.attribution === undefined ? {} : { attribution: item.attribution }),
|
|
292
|
+
...(item.cost === undefined ? {} : { cost: item.cost }),
|
|
293
|
+
valuationStatus: item.cost?.status ?? "unvalued",
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function aiAccountingBucketStart(at: Date, bucket: AiAccountingBucket): Date {
|
|
298
|
+
assertDate(at, "accounting bucket timestamp")
|
|
299
|
+
assertAiAccountingBucket(bucket)
|
|
300
|
+
const start = new Date(at)
|
|
301
|
+
start.setUTCMinutes(0, 0, 0)
|
|
302
|
+
if (bucket === "hour") return start
|
|
303
|
+
start.setUTCHours(0)
|
|
304
|
+
if (bucket === "day") return start
|
|
305
|
+
const mondayOffset = (start.getUTCDay() + 6) % 7
|
|
306
|
+
start.setUTCDate(start.getUTCDate() - mondayOffset)
|
|
307
|
+
return start
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function aggregateAccountingItems(
|
|
311
|
+
items: readonly AiAccountingRecordSetItem[]
|
|
312
|
+
): AiAccountingAggregate {
|
|
313
|
+
return {
|
|
314
|
+
modelCallCount: items.length,
|
|
315
|
+
usage: aggregateAiModelCallUsage(items.map((item) => item.usage.usage)),
|
|
316
|
+
costs: aggregateCosts(items),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function aggregateCosts(items: readonly AiAccountingRecordSetItem[]): AiCostSummary {
|
|
321
|
+
const amounts = new Map<string, bigint>()
|
|
322
|
+
let ratedCallCount = 0
|
|
323
|
+
let unpriceableCallCount = 0
|
|
324
|
+
let unvaluedCallCount = 0
|
|
325
|
+
for (const item of items) {
|
|
326
|
+
if (item.cost?.status === "rated") {
|
|
327
|
+
ratedCallCount += 1
|
|
328
|
+
addAmount(amounts, item.cost.money.currency, item.cost.money.amountNanos)
|
|
329
|
+
} else if (item.cost?.status === "unpriceable") {
|
|
330
|
+
unpriceableCallCount += 1
|
|
331
|
+
} else {
|
|
332
|
+
unvaluedCallCount += 1
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
amounts: [...amounts]
|
|
337
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
338
|
+
.map(([currency, amountNanos]) => ({ currency, amountNanos: amountNanos.toString() })),
|
|
339
|
+
ratedCallCount,
|
|
340
|
+
unpriceableCallCount,
|
|
341
|
+
unvaluedCallCount,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const AGGREGATE_USAGE_FIELDS = [
|
|
346
|
+
"inputTokens",
|
|
347
|
+
"outputTokens",
|
|
348
|
+
"uncachedInputTokens",
|
|
349
|
+
"cacheReadInputTokens",
|
|
350
|
+
"cacheWriteInputTokens",
|
|
351
|
+
"textOutputTokens",
|
|
352
|
+
"reasoningOutputTokens",
|
|
353
|
+
] as const
|
|
354
|
+
|
|
355
|
+
type AggregateUsageField = (typeof AGGREGATE_USAGE_FIELDS)[number]
|
|
356
|
+
|
|
357
|
+
interface AggregateFragmentAccumulator {
|
|
358
|
+
modelCallCount: bigint
|
|
359
|
+
reportedUsageCallCount: bigint
|
|
360
|
+
usage: Record<AggregateUsageField, { presentCallCount: bigint; total: bigint }>
|
|
361
|
+
amounts: Map<string, bigint>
|
|
362
|
+
ratedCallCount: bigint
|
|
363
|
+
unpriceableCallCount: bigint
|
|
364
|
+
unvaluedCallCount: bigint
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function aggregateFragmentAccumulator(): AggregateFragmentAccumulator {
|
|
368
|
+
return {
|
|
369
|
+
modelCallCount: 0n,
|
|
370
|
+
reportedUsageCallCount: 0n,
|
|
371
|
+
usage: Object.fromEntries(
|
|
372
|
+
AGGREGATE_USAGE_FIELDS.map((field) => [field, { presentCallCount: 0n, total: 0n }])
|
|
373
|
+
) as AggregateFragmentAccumulator["usage"],
|
|
374
|
+
amounts: new Map(),
|
|
375
|
+
ratedCallCount: 0n,
|
|
376
|
+
unpriceableCallCount: 0n,
|
|
377
|
+
unvaluedCallCount: 0n,
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function getFragmentAccumulator(
|
|
382
|
+
values: Map<string, AggregateFragmentAccumulator>,
|
|
383
|
+
key: string
|
|
384
|
+
): AggregateFragmentAccumulator {
|
|
385
|
+
const existing = values.get(key)
|
|
386
|
+
if (existing) return existing
|
|
387
|
+
const created = aggregateFragmentAccumulator()
|
|
388
|
+
values.set(key, created)
|
|
389
|
+
return created
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function appendAggregateFragment(
|
|
393
|
+
target: AggregateFragmentAccumulator,
|
|
394
|
+
fragment: AiAccountingAggregateFragment
|
|
395
|
+
): void {
|
|
396
|
+
target.modelCallCount += naturalBigInt(fragment.modelCallCount, "modelCallCount")
|
|
397
|
+
target.reportedUsageCallCount += naturalBigInt(
|
|
398
|
+
fragment.usage.reportedCallCount,
|
|
399
|
+
"reported usage call count"
|
|
400
|
+
)
|
|
401
|
+
for (const field of AGGREGATE_USAGE_FIELDS) {
|
|
402
|
+
const source = fragment.usage[field]
|
|
403
|
+
const presentCallCount = naturalBigInt(source.presentCallCount, `${field} call count`)
|
|
404
|
+
target.usage[field].presentCallCount += presentCallCount
|
|
405
|
+
if (source.total !== null) {
|
|
406
|
+
target.usage[field].total += naturalBigInt(source.total, `${field} total`)
|
|
407
|
+
} else if (presentCallCount !== 0n) {
|
|
408
|
+
throw new TypeError(`[Sixb] AI accounting SQL ${field} total is missing.`)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
target.ratedCallCount += naturalBigInt(fragment.costs.ratedCallCount, "rated call count")
|
|
412
|
+
target.unpriceableCallCount += naturalBigInt(
|
|
413
|
+
fragment.costs.unpriceableCallCount,
|
|
414
|
+
"unpriceable call count"
|
|
415
|
+
)
|
|
416
|
+
target.unvaluedCallCount += naturalBigInt(fragment.costs.unvaluedCallCount, "unvalued call count")
|
|
417
|
+
if (fragment.costs.amount) {
|
|
418
|
+
addAmount(target.amounts, fragment.costs.amount.currency, fragment.costs.amount.amountNanos)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function finishAggregateFragment(value: AggregateFragmentAccumulator): AiAccountingAggregate {
|
|
423
|
+
const classifiedCallCount =
|
|
424
|
+
value.ratedCallCount + value.unpriceableCallCount + value.unvaluedCallCount
|
|
425
|
+
if (
|
|
426
|
+
classifiedCallCount !== value.modelCallCount ||
|
|
427
|
+
value.reportedUsageCallCount > value.modelCallCount ||
|
|
428
|
+
AGGREGATE_USAGE_FIELDS.some(
|
|
429
|
+
(field) => value.usage[field].presentCallCount > value.modelCallCount
|
|
430
|
+
)
|
|
431
|
+
) {
|
|
432
|
+
throw new TypeError("[Sixb] AI accounting SQL aggregate counts are inconsistent.")
|
|
433
|
+
}
|
|
434
|
+
const modelCallCount = safeAggregateNumber(value.modelCallCount, "model-call count")
|
|
435
|
+
const usageInput: Partial<Record<AggregateUsageField, number>> = {}
|
|
436
|
+
if (value.modelCallCount > 0n) {
|
|
437
|
+
for (const field of AGGREGATE_USAGE_FIELDS) {
|
|
438
|
+
const aggregate = value.usage[field]
|
|
439
|
+
if (aggregate.presentCallCount === value.modelCallCount) {
|
|
440
|
+
usageInput[field] = safeAggregateNumber(aggregate.total, field)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
let usage: AiModelCallUsage = normalizeAiModelCallUsage(usageInput)
|
|
445
|
+
if (usage.reportingStatus === "unavailable" && value.reportedUsageCallCount > 0n) {
|
|
446
|
+
usage = { ...usage, reportingStatus: "partial" }
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
modelCallCount,
|
|
450
|
+
usage,
|
|
451
|
+
costs: {
|
|
452
|
+
amounts: [...value.amounts]
|
|
453
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
454
|
+
.map(([currency, amountNanos]) => ({ currency, amountNanos: amountNanos.toString() })),
|
|
455
|
+
ratedCallCount: safeAggregateNumber(value.ratedCallCount, "rated call count"),
|
|
456
|
+
unpriceableCallCount: safeAggregateNumber(
|
|
457
|
+
value.unpriceableCallCount,
|
|
458
|
+
"unpriceable call count"
|
|
459
|
+
),
|
|
460
|
+
unvaluedCallCount: safeAggregateNumber(value.unvaluedCallCount, "unvalued call count"),
|
|
461
|
+
},
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function naturalBigInt(value: string, field: string): bigint {
|
|
466
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) {
|
|
467
|
+
throw new TypeError(`[Sixb] AI accounting SQL ${field} must be a natural integer.`)
|
|
468
|
+
}
|
|
469
|
+
return BigInt(value)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function safeAggregateNumber(value: bigint, field: string): number {
|
|
473
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
474
|
+
throw new TypeError(`[Sixb] AI accounting ${field} exceeds the safe integer range.`)
|
|
475
|
+
}
|
|
476
|
+
return Number(value)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function requiredFragmentString(value: string | undefined, field: string): string {
|
|
480
|
+
if (value === undefined || value.length === 0) {
|
|
481
|
+
throw new TypeError(`[Sixb] AI accounting SQL ${field} is missing.`)
|
|
482
|
+
}
|
|
483
|
+
return value
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function requiredFragmentDate(value: Date | undefined, field: string): Date {
|
|
487
|
+
if (value === undefined) throw new TypeError(`[Sixb] AI accounting SQL ${field} is missing.`)
|
|
488
|
+
assertDate(value, field)
|
|
489
|
+
return value
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function accountingPeriods(
|
|
493
|
+
from: Date,
|
|
494
|
+
to: Date,
|
|
495
|
+
bucket: AiAccountingBucket
|
|
496
|
+
): Array<{ start: Date; end: Date }> {
|
|
497
|
+
const result: Array<{ start: Date; end: Date }> = []
|
|
498
|
+
let cursor = aiAccountingBucketStart(from, bucket)
|
|
499
|
+
while (cursor.getTime() < to.getTime()) {
|
|
500
|
+
if (result.length >= MAX_CHART_BUCKETS) {
|
|
501
|
+
throw new RangeError(`[Sixb] AI accounting query exceeds ${MAX_CHART_BUCKETS} chart buckets.`)
|
|
502
|
+
}
|
|
503
|
+
const next = nextBucket(cursor, bucket)
|
|
504
|
+
result.push({ start: new Date(cursor), end: next })
|
|
505
|
+
cursor = next
|
|
506
|
+
}
|
|
507
|
+
return result
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function nextBucket(start: Date, bucket: AiAccountingBucket): Date {
|
|
511
|
+
const next = new Date(start)
|
|
512
|
+
if (bucket === "hour") next.setUTCHours(next.getUTCHours() + 1)
|
|
513
|
+
else if (bucket === "day") next.setUTCDate(next.getUTCDate() + 1)
|
|
514
|
+
else next.setUTCDate(next.getUTCDate() + 7)
|
|
515
|
+
return next
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function appendGrouped(
|
|
519
|
+
groups: Map<string, AiAccountingRecordSetItem[]>,
|
|
520
|
+
key: string,
|
|
521
|
+
item: AiAccountingRecordSetItem
|
|
522
|
+
): void {
|
|
523
|
+
const existing = groups.get(key)
|
|
524
|
+
if (existing) existing.push(item)
|
|
525
|
+
else groups.set(key, [item])
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function addAmount(amounts: Map<string, bigint>, currency: string, amountNanos: string): void {
|
|
529
|
+
amounts.set(currency, (amounts.get(currency) ?? 0n) + BigInt(amountNanos))
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function normalizePage(
|
|
533
|
+
requestedLimit: number | undefined,
|
|
534
|
+
requestedOffset: number | undefined
|
|
535
|
+
): { limit: number; offset: number } {
|
|
536
|
+
const limit = requestedLimit ?? DEFAULT_MODEL_CALL_PAGE_SIZE
|
|
537
|
+
const offset = requestedOffset ?? 0
|
|
538
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_MODEL_CALL_PAGE_SIZE) {
|
|
539
|
+
throw new TypeError(
|
|
540
|
+
`[Sixb] AI accounting list limit must be an integer between 1 and ${MAX_MODEL_CALL_PAGE_SIZE}.`
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
544
|
+
throw new TypeError("[Sixb] AI accounting list offset must be a non-negative integer.")
|
|
545
|
+
}
|
|
546
|
+
return { limit, offset }
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function compareAccountingItems(
|
|
550
|
+
left: AiModelCallAccountingItem,
|
|
551
|
+
right: AiModelCallAccountingItem
|
|
552
|
+
): number {
|
|
553
|
+
return (
|
|
554
|
+
right.usage.occurredAt.getTime() - left.usage.occurredAt.getTime() ||
|
|
555
|
+
left.usage.id.localeCompare(right.usage.id)
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function compareAgentBreakdowns(
|
|
560
|
+
left: AiAccountingAgentBreakdown,
|
|
561
|
+
right: AiAccountingAgentBreakdown
|
|
562
|
+
): number {
|
|
563
|
+
return right.modelCallCount - left.modelCallCount || left.agentId.localeCompare(right.agentId)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function compareWorkflowBreakdowns(
|
|
567
|
+
left: AiAccountingWorkflowBreakdown,
|
|
568
|
+
right: AiAccountingWorkflowBreakdown
|
|
569
|
+
): number {
|
|
570
|
+
return (
|
|
571
|
+
right.modelCallCount - left.modelCallCount || left.workflowId.localeCompare(right.workflowId)
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function compareModelBreakdowns(
|
|
576
|
+
left: AiAccountingModelBreakdown,
|
|
577
|
+
right: AiAccountingModelBreakdown
|
|
578
|
+
): number {
|
|
579
|
+
return (
|
|
580
|
+
right.modelCallCount - left.modelCallCount ||
|
|
581
|
+
left.providerId.localeCompare(right.providerId) ||
|
|
582
|
+
left.modelId.localeCompare(right.modelId)
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function assertAiAccountingBucket(bucket: AiAccountingBucket): void {
|
|
587
|
+
if (bucket !== "hour" && bucket !== "day" && bucket !== "week") {
|
|
588
|
+
throw new TypeError("[Sixb] AI accounting bucket must be hour, day, or week.")
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function assertDate(value: Date, label: string): void {
|
|
593
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
|
|
594
|
+
throw new TypeError(`[Sixb] AI ${label} must be a valid Date.`)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function assertNonBlank(value: string, label: string): void {
|
|
599
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
600
|
+
throw new TypeError(`[Sixb] AI ${label} must be nonblank.`)
|
|
601
|
+
}
|
|
602
|
+
}
|