@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.
- package/dist/actions/index.js +16 -13
- package/dist/actions/index.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.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/index.d.ts +6 -3
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +33 -22
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/prompt.d.ts +8 -2
- package/dist/agents/prompt.d.ts.map +1 -1
- package/dist/agents/request.d.ts.map +1 -1
- package/dist/agents/streams.js +2 -2
- 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 +37 -1
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/validation.d.ts +1 -1
- package/dist/agents/validation.d.ts.map +1 -1
- package/dist/auth/index.js +7 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/authorization/index.js +1 -1
- package/dist/bootstrap/index.js +27 -24
- package/dist/bootstrap/index.js.map +1 -1
- package/dist/{chunk-0mbx247x.js → chunk-177ed2mp.js} +241 -45
- package/dist/chunk-177ed2mp.js.map +46 -0
- package/dist/{chunk-sa4340y1.js → chunk-1ywq4hzk.js} +7 -7
- package/dist/{chunk-zg9yryhz.js → chunk-261ag4t4.js} +3 -3
- package/dist/{chunk-k9pts48t.js → chunk-3rsf30p4.js} +5 -5
- package/dist/{chunk-8ryen3p3.js → chunk-4aw0jwk0.js} +1 -1
- package/dist/{chunk-bh6k3nc6.js → chunk-4p9rh7jj.js} +1 -1
- package/dist/{chunk-34bqh420.js → chunk-4z3n1dzt.js} +59 -21
- package/dist/{chunk-34bqh420.js.map → chunk-4z3n1dzt.js.map} +4 -4
- package/dist/chunk-96swp2wd.js +47 -0
- package/dist/chunk-96swp2wd.js.map +10 -0
- package/dist/{chunk-n8herkry.js → chunk-bv41fmjn.js} +11 -11
- package/dist/{chunk-7pc8b818.js → chunk-c6cfqydk.js} +1 -1
- package/dist/{chunk-0k4avqr2.js → chunk-c8bw2b6c.js} +2 -2
- package/dist/{chunk-jaj13078.js → chunk-d2adj15j.js} +3 -33
- package/dist/{chunk-jaj13078.js.map → chunk-d2adj15j.js.map} +3 -4
- package/dist/{chunk-c6qd89g5.js → chunk-d6y2wsfg.js} +3 -3
- package/dist/chunk-dysw7nsm.js.map +1 -1
- package/dist/{chunk-st4xgxey.js → chunk-ettb0cy4.js} +150 -23
- package/dist/chunk-ettb0cy4.js.map +45 -0
- package/dist/{chunk-nnyp53zt.js → chunk-f1cyvnrk.js} +1 -1
- package/dist/chunk-fmx1sz2h.js.map +2 -2
- package/dist/{chunk-bct7hmfc.js → chunk-km4rkj4t.js} +3 -3
- package/dist/chunk-kqxgf6kv.js +898 -0
- package/dist/chunk-kqxgf6kv.js.map +15 -0
- package/dist/{chunk-qvybnnpe.js → chunk-n5x5y80a.js} +2 -2
- package/dist/{chunk-4x43bkbp.js → chunk-pxtrxm3p.js} +1 -1
- package/dist/chunk-qer6am8w.js +133 -0
- package/dist/chunk-qer6am8w.js.map +11 -0
- 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-663csy96.js → chunk-t0jjkep3.js} +2 -1
- package/dist/chunk-t0jjkep3.js.map +10 -0
- package/dist/{chunk-5egfka8j.js → chunk-ttba8x6z.js} +2 -2
- package/dist/{chunk-nten49ta.js → chunk-wb4051cx.js} +3 -3
- package/dist/{chunk-nten49ta.js.map → chunk-wb4051cx.js.map} +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-ys1nse2v.js → chunk-xmggaxg4.js} +256 -231
- package/dist/{chunk-ys1nse2v.js.map → chunk-xmggaxg4.js.map} +8 -10
- package/dist/{chunk-g9t3kdtx.js → chunk-z8j83gzd.js} +1 -1
- package/dist/{chunk-57zpb99z.js → chunk-zpw39qhj.js} +1 -1
- package/dist/execution/agent.js +23 -20
- package/dist/execution/agent.js.map +1 -1
- package/dist/execution/primitive.js +23 -20
- package/dist/execution/primitive.js.map +2 -2
- package/dist/execution/request.js +24 -21
- package/dist/execution/request.js.map +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -29
- package/dist/index.js.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 +14 -11
- 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/objects/index.js +17 -14
- package/dist/objects/index.js.map +1 -1
- package/dist/objects/query/index.js +2 -2
- package/dist/pipelines/index.js +14 -12
- package/dist/pipelines/index.js.map +1 -1
- package/dist/projections/builders.d.ts +19 -6
- 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 +17 -11
- package/dist/projections/internal.js.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 +9 -0
- package/dist/projections/types.d.ts.map +1 -1
- package/dist/projections/validation.d.ts.map +1 -1
- 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/storage/agents/in-memory.d.ts +18 -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 +18 -7
- package/dist/storage/agents/provider.js.map +1 -1
- package/dist/storage/agents/types.d.ts +70 -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/in-memory/index.d.ts +5 -0
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +3 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +22 -15
- package/dist/storage/index.js.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/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 +6 -5
- package/dist/storage/workflow-runs/provider.js.map +1 -1
- package/dist/syncs/index.js +14 -12
- 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/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2098 -1468
- package/dist/testing/index.js.map +6 -5
- package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
- package/dist/webhooks/internal.js +12 -10
- 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 +24 -21
- package/dist/workflows/index.js.map +1 -1
- package/package.json +7 -1
- package/src/agents/adapters.ts +64 -23
- package/src/agents/errors.ts +2 -2
- package/src/agents/execution.ts +14 -2
- package/src/agents/index.ts +18 -1
- package/src/agents/prompt.ts +33 -5
- package/src/agents/request.ts +5 -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 +47 -1
- package/src/agents/validation.ts +1 -1
- package/src/index.ts +13 -0
- 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/projections/builders.ts +100 -35
- package/src/projections/index.ts +6 -0
- package/src/projections/internal.ts +4 -1
- package/src/projections/registry.ts +7 -0
- package/src/projections/revision.ts +4 -0
- package/src/projections/timestamp.ts +66 -0
- package/src/projections/types.ts +7 -0
- package/src/projections/validation.ts +24 -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/storage/agents/in-memory.ts +171 -4
- package/src/storage/agents/index.ts +5 -0
- package/src/storage/agents/provider.ts +225 -0
- package/src/storage/agents/types.ts +77 -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/in-memory/index.ts +50 -0
- package/src/storage/index.ts +37 -0
- package/src/storage/ontology/in-memory/materializations.ts +1 -0
- package/src/storage/ontology/materializations.ts +3 -0
- package/src/storage/operation-scope.ts +1 -0
- package/src/storage/types.ts +2 -0
- package/src/testing/agent-storage-contract.ts +341 -2
- package/src/testing/ai-cost-storage-contract.ts +320 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/materializer-storage-contract.ts +120 -2
- package/src/workers/queue-worker.ts +14 -1
- package/src/workflows/execution.ts +31 -10
- package/dist/chunk-0mbx247x.js.map +0 -43
- package/dist/chunk-663csy96.js.map +0 -10
- package/dist/chunk-st4xgxey.js.map +0 -45
- package/dist/chunk-vp1vc6ee.js +0 -35
- package/dist/chunk-vp1vc6ee.js.map +0 -11
- /package/dist/{chunk-sa4340y1.js.map → chunk-1ywq4hzk.js.map} +0 -0
- /package/dist/{chunk-zg9yryhz.js.map → chunk-261ag4t4.js.map} +0 -0
- /package/dist/{chunk-k9pts48t.js.map → chunk-3rsf30p4.js.map} +0 -0
- /package/dist/{chunk-8ryen3p3.js.map → chunk-4aw0jwk0.js.map} +0 -0
- /package/dist/{chunk-bh6k3nc6.js.map → chunk-4p9rh7jj.js.map} +0 -0
- /package/dist/{chunk-n8herkry.js.map → chunk-bv41fmjn.js.map} +0 -0
- /package/dist/{chunk-7pc8b818.js.map → chunk-c6cfqydk.js.map} +0 -0
- /package/dist/{chunk-0k4avqr2.js.map → chunk-c8bw2b6c.js.map} +0 -0
- /package/dist/{chunk-c6qd89g5.js.map → chunk-d6y2wsfg.js.map} +0 -0
- /package/dist/{chunk-nnyp53zt.js.map → chunk-f1cyvnrk.js.map} +0 -0
- /package/dist/{chunk-bct7hmfc.js.map → chunk-km4rkj4t.js.map} +0 -0
- /package/dist/{chunk-qvybnnpe.js.map → chunk-n5x5y80a.js.map} +0 -0
- /package/dist/{chunk-4x43bkbp.js.map → chunk-pxtrxm3p.js.map} +0 -0
- /package/dist/{chunk-5egfka8j.js.map → chunk-ttba8x6z.js.map} +0 -0
- /package/dist/{chunk-g9t3kdtx.js.map → chunk-z8j83gzd.js.map} +0 -0
- /package/dist/{chunk-57zpb99z.js.map → chunk-zpw39qhj.js.map} +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import type { AiCostStorage, AiModelCallCostRecord } from "../storage/ai-cost"
|
|
3
|
+
import { AiCostStorageError } from "../storage/ai-cost"
|
|
4
|
+
import type { AiUsageStorage } from "../storage/ai-usage"
|
|
5
|
+
import type { ExecutionStorage } from "../storage/executions"
|
|
6
|
+
|
|
7
|
+
export interface AiStorageContractSuiteOptions<TStorage> {
|
|
8
|
+
readonly createStorage: () => TStorage | Promise<TStorage>
|
|
9
|
+
readonly setup?: (storage: TStorage) => void | Promise<void>
|
|
10
|
+
readonly cleanup?: (storage: TStorage) => void | Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const projectId = "cost-contract-project"
|
|
14
|
+
const executionId = "exec_cost_1"
|
|
15
|
+
const otherExecutionId = "exec_cost_2"
|
|
16
|
+
|
|
17
|
+
function priceSource(entry: string) {
|
|
18
|
+
return {
|
|
19
|
+
sourceId: "test-catalog",
|
|
20
|
+
sourceEntryId: entry,
|
|
21
|
+
sourceVersion: "test-catalog-v1",
|
|
22
|
+
sourceUrl: "https://example.test/ai-pricing.json",
|
|
23
|
+
observedAt: new Date("2026-08-01T00:00:00.000Z"),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ratedCost(
|
|
28
|
+
usageRecordId = "usage_1",
|
|
29
|
+
overrides: Partial<Extract<AiModelCallCostRecord, { status: "rated" }>> = {}
|
|
30
|
+
): Extract<AiModelCallCostRecord, { status: "rated" }> {
|
|
31
|
+
return {
|
|
32
|
+
projectId,
|
|
33
|
+
usageRecordId,
|
|
34
|
+
status: "rated",
|
|
35
|
+
billingIdentity: { providerId: "vercel", modelId: "openai/gpt-5" },
|
|
36
|
+
pricingContext: {},
|
|
37
|
+
priceSource: priceSource("vercel/openai/gpt-5"),
|
|
38
|
+
money: { currency: "USD", amountNanos: "95000" },
|
|
39
|
+
components: [
|
|
40
|
+
{
|
|
41
|
+
meter: "tokens.input.uncached",
|
|
42
|
+
quantity: "12",
|
|
43
|
+
rateAmountNanosPerMillion: "1250000000",
|
|
44
|
+
chargeAmountNanos: "15000",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
meter: "tokens.input.cacheRead",
|
|
48
|
+
quantity: "0",
|
|
49
|
+
rateAmountNanosPerMillion: "125000000",
|
|
50
|
+
chargeAmountNanos: "0",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
meter: "tokens.output.total",
|
|
54
|
+
quantity: "8",
|
|
55
|
+
rateAmountNanosPerMillion: "10000000000",
|
|
56
|
+
chargeAmountNanos: "80000",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
ratedAt: new Date("2026-08-01T12:00:00.200Z"),
|
|
60
|
+
...overrides,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unpriceableCost(): Extract<AiModelCallCostRecord, { status: "unpriceable" }> {
|
|
65
|
+
return {
|
|
66
|
+
projectId,
|
|
67
|
+
usageRecordId: "usage_2",
|
|
68
|
+
status: "unpriceable",
|
|
69
|
+
billingIdentity: { providerId: "vercel", modelId: "unpriced/model" },
|
|
70
|
+
pricingContext: {},
|
|
71
|
+
priceSource: priceSource("vercel/unpriced/model"),
|
|
72
|
+
reason: "missingCatalogEntry",
|
|
73
|
+
ratedAt: new Date("2026-08-01T12:00:00.200Z"),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function runAiCostStorageContractSuite<TStorage extends AiCostStorage>(
|
|
78
|
+
name: string,
|
|
79
|
+
options: AiStorageContractSuiteOptions<TStorage>
|
|
80
|
+
): void {
|
|
81
|
+
describe(name, () => {
|
|
82
|
+
async function fixture(): Promise<TStorage> {
|
|
83
|
+
const storage = await options.createStorage()
|
|
84
|
+
await options.setup?.(storage)
|
|
85
|
+
return storage
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
test("records model-call costs idempotently", async () => {
|
|
89
|
+
const storage = await fixture()
|
|
90
|
+
try {
|
|
91
|
+
await expect(storage.recordModelCallCost(ratedCost())).resolves.toBeUndefined()
|
|
92
|
+
await expect(storage.recordModelCallCost(ratedCost())).resolves.toBeUndefined()
|
|
93
|
+
await expect(
|
|
94
|
+
storage.listModelCalls({
|
|
95
|
+
projectId,
|
|
96
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
97
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
98
|
+
valuationStatus: "rated",
|
|
99
|
+
})
|
|
100
|
+
).resolves.toMatchObject({
|
|
101
|
+
total: 1,
|
|
102
|
+
items: [{ usage: { id: "usage_1" }, cost: ratedCost(), valuationStatus: "rated" }],
|
|
103
|
+
})
|
|
104
|
+
} finally {
|
|
105
|
+
await options.cleanup?.(storage)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("stores explicit unpriceable results and pricing context", async () => {
|
|
110
|
+
const storage = await fixture()
|
|
111
|
+
try {
|
|
112
|
+
const deploymentCost: AiModelCallCostRecord = {
|
|
113
|
+
projectId,
|
|
114
|
+
usageRecordId: "usage_deployment",
|
|
115
|
+
status: "unpriceable",
|
|
116
|
+
billingIdentity: { providerId: "openai", modelId: "gpt-5" },
|
|
117
|
+
pricingContext: { deploymentId: "production" },
|
|
118
|
+
priceSource: priceSource("openai/gpt-5"),
|
|
119
|
+
reason: "unsupportedPricingDimension",
|
|
120
|
+
ratedAt: new Date("2026-08-03T12:00:00.200Z"),
|
|
121
|
+
}
|
|
122
|
+
await storage.recordModelCallCost(unpriceableCost())
|
|
123
|
+
await storage.recordModelCallCost(deploymentCost)
|
|
124
|
+
await expect(
|
|
125
|
+
storage.listModelCalls({
|
|
126
|
+
projectId,
|
|
127
|
+
from: new Date("2026-08-03T00:00:00.000Z"),
|
|
128
|
+
to: new Date("2026-08-04T00:00:00.000Z"),
|
|
129
|
+
valuationStatus: "unpriceable",
|
|
130
|
+
})
|
|
131
|
+
).resolves.toMatchObject({
|
|
132
|
+
total: 1,
|
|
133
|
+
items: [{ cost: deploymentCost, valuationStatus: "unpriceable" }],
|
|
134
|
+
})
|
|
135
|
+
} finally {
|
|
136
|
+
await options.cleanup?.(storage)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test("summarizes and queries accounting without inventing missing costs", async () => {
|
|
141
|
+
const storage = await fixture()
|
|
142
|
+
try {
|
|
143
|
+
await storage.recordModelCallCost(ratedCost())
|
|
144
|
+
await storage.recordModelCallCost(unpriceableCost())
|
|
145
|
+
await expect(
|
|
146
|
+
storage.summarizeExecutions({ projectId, executionIds: [executionId] })
|
|
147
|
+
).resolves.toEqual([
|
|
148
|
+
{
|
|
149
|
+
amounts: [{ currency: "USD", amountNanos: "95000" }],
|
|
150
|
+
ratedCallCount: 1,
|
|
151
|
+
unpriceableCallCount: 1,
|
|
152
|
+
unvaluedCallCount: 1,
|
|
153
|
+
},
|
|
154
|
+
])
|
|
155
|
+
|
|
156
|
+
const overview = await storage.queryProjectOverview({
|
|
157
|
+
projectId,
|
|
158
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
159
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
160
|
+
bucket: "day",
|
|
161
|
+
})
|
|
162
|
+
expect(overview.totals).toEqual({
|
|
163
|
+
modelCallCount: 3,
|
|
164
|
+
usage: {
|
|
165
|
+
inputTokens: 15,
|
|
166
|
+
outputTokens: 12,
|
|
167
|
+
totalTokens: 27,
|
|
168
|
+
reportingStatus: "complete",
|
|
169
|
+
},
|
|
170
|
+
costs: {
|
|
171
|
+
amounts: [{ currency: "USD", amountNanos: "95000" }],
|
|
172
|
+
ratedCallCount: 1,
|
|
173
|
+
unpriceableCallCount: 1,
|
|
174
|
+
unvaluedCallCount: 1,
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
expect(overview.series).toMatchObject([{ modelCallCount: 3 }])
|
|
178
|
+
expect(overview.models).toMatchObject([
|
|
179
|
+
{ providerId: "gateway", modelId: "openai/gpt-5", modelCallCount: 2 },
|
|
180
|
+
{ providerId: "gateway", modelId: "unpriced/model", modelCallCount: 1 },
|
|
181
|
+
])
|
|
182
|
+
await expect(
|
|
183
|
+
storage.queryProjectOverview({
|
|
184
|
+
projectId,
|
|
185
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
186
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
187
|
+
bucket: "day",
|
|
188
|
+
providerId: "gateway",
|
|
189
|
+
modelId: "unpriced/model",
|
|
190
|
+
})
|
|
191
|
+
).resolves.toMatchObject({ totals: { modelCallCount: 1 } })
|
|
192
|
+
await expect(
|
|
193
|
+
storage.listModelCalls({
|
|
194
|
+
projectId,
|
|
195
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
196
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
197
|
+
limit: 1,
|
|
198
|
+
offset: 1,
|
|
199
|
+
})
|
|
200
|
+
).resolves.toMatchObject({
|
|
201
|
+
total: 3,
|
|
202
|
+
hasMore: true,
|
|
203
|
+
items: [{ usage: { id: "usage_2" } }],
|
|
204
|
+
})
|
|
205
|
+
await expect(
|
|
206
|
+
storage.listModelCalls({
|
|
207
|
+
projectId,
|
|
208
|
+
from: new Date("2026-08-01T00:00:00.000Z"),
|
|
209
|
+
to: new Date("2026-08-02T00:00:00.000Z"),
|
|
210
|
+
valuationStatus: "unpriceable",
|
|
211
|
+
})
|
|
212
|
+
).resolves.toMatchObject({
|
|
213
|
+
total: 1,
|
|
214
|
+
hasMore: false,
|
|
215
|
+
items: [{ usage: { id: "usage_2" }, valuationStatus: "unpriceable" }],
|
|
216
|
+
})
|
|
217
|
+
} finally {
|
|
218
|
+
await options.cleanup?.(storage)
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test("rejects missing usage and mismatched billed quantities", async () => {
|
|
223
|
+
const storage = await fixture()
|
|
224
|
+
try {
|
|
225
|
+
const missing = ratedCost("missing")
|
|
226
|
+
const missingError = await storage.recordModelCallCost(missing).catch((value) => value)
|
|
227
|
+
expect(missingError).toBeInstanceOf(AiCostStorageError)
|
|
228
|
+
expect((missingError as AiCostStorageError).code).toBe("missing_usage")
|
|
229
|
+
|
|
230
|
+
const base = ratedCost()
|
|
231
|
+
const mismatch = ratedCost("usage_1", {
|
|
232
|
+
money: { currency: "USD", amountNanos: "96250" },
|
|
233
|
+
components: [
|
|
234
|
+
{ ...base.components[0]!, quantity: "13", chargeAmountNanos: "16250" },
|
|
235
|
+
...base.components.slice(1),
|
|
236
|
+
],
|
|
237
|
+
})
|
|
238
|
+
const mismatchError = await storage.recordModelCallCost(mismatch).catch((value) => value)
|
|
239
|
+
expect(mismatchError).toBeInstanceOf(AiCostStorageError)
|
|
240
|
+
expect((mismatchError as AiCostStorageError).code).toBe("cost_mismatch")
|
|
241
|
+
} finally {
|
|
242
|
+
await options.cleanup?.(storage)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Seed immutable usage used by every cost-storage provider contract. */
|
|
249
|
+
export async function seedAiCostStorageContractUsage(
|
|
250
|
+
executions: ExecutionStorage,
|
|
251
|
+
usage: AiUsageStorage
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
for (const fixture of [
|
|
254
|
+
{ projectId, executionId },
|
|
255
|
+
{ projectId, executionId: otherExecutionId },
|
|
256
|
+
]) {
|
|
257
|
+
if (await executions.getById({ projectId: fixture.projectId, id: fixture.executionId }))
|
|
258
|
+
continue
|
|
259
|
+
const primitive = {
|
|
260
|
+
kind: "workflow" as const,
|
|
261
|
+
id: "ai-cost-contract",
|
|
262
|
+
runId: fixture.executionId,
|
|
263
|
+
}
|
|
264
|
+
await executions.create({
|
|
265
|
+
id: fixture.executionId,
|
|
266
|
+
projectId: fixture.projectId,
|
|
267
|
+
executor: { type: "primitive", kind: primitive.kind, runId: primitive.runId },
|
|
268
|
+
source: { type: "event", eventId: `test_event:${fixture.executionId}` },
|
|
269
|
+
correlationId: `test_correlation:${fixture.executionId}`,
|
|
270
|
+
authorizationRef: { type: "trustedPrimitive", primitive },
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const fixture of [
|
|
275
|
+
{ id: "usage_1", model: "openai/gpt-5", input: 12, output: 8 },
|
|
276
|
+
{ id: "usage_2", model: "unpriced/model", input: 1, output: 1 },
|
|
277
|
+
{ id: "usage_3", model: "openai/gpt-5", input: 2, output: 3 },
|
|
278
|
+
]) {
|
|
279
|
+
await usage.recordModelCall({
|
|
280
|
+
id: fixture.id,
|
|
281
|
+
projectId,
|
|
282
|
+
executionId,
|
|
283
|
+
attempt: 1,
|
|
284
|
+
callId: `call_${fixture.id}`,
|
|
285
|
+
requesterGroupIds: [],
|
|
286
|
+
providerId: "gateway",
|
|
287
|
+
requestedModelId: fixture.model,
|
|
288
|
+
responseId: `response_${projectId}_${fixture.id}`,
|
|
289
|
+
usage: {
|
|
290
|
+
inputTokens: fixture.input,
|
|
291
|
+
outputTokens: fixture.output,
|
|
292
|
+
...(fixture.id === "usage_1"
|
|
293
|
+
? { uncachedInputTokens: fixture.input, cacheReadInputTokens: 0 }
|
|
294
|
+
: {}),
|
|
295
|
+
},
|
|
296
|
+
occurredAt: new Date("2026-08-01T12:00:00.000Z"),
|
|
297
|
+
recordedAt: new Date("2026-08-01T12:00:00.100Z"),
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await usage.recordModelCall({
|
|
302
|
+
id: "usage_deployment",
|
|
303
|
+
projectId,
|
|
304
|
+
executionId: otherExecutionId,
|
|
305
|
+
attempt: 1,
|
|
306
|
+
callId: "call_usage_deployment",
|
|
307
|
+
requesterGroupIds: [],
|
|
308
|
+
providerId: "openai.responses",
|
|
309
|
+
requestedModelId: "gpt-5",
|
|
310
|
+
responseId: "response_usage_deployment",
|
|
311
|
+
usage: {
|
|
312
|
+
inputTokens: 2,
|
|
313
|
+
outputTokens: 3,
|
|
314
|
+
uncachedInputTokens: 2,
|
|
315
|
+
cacheReadInputTokens: 0,
|
|
316
|
+
},
|
|
317
|
+
occurredAt: new Date("2026-08-03T12:00:00.000Z"),
|
|
318
|
+
recordedAt: new Date("2026-08-03T12:00:00.100Z"),
|
|
319
|
+
})
|
|
320
|
+
}
|
package/src/testing/index.ts
CHANGED
|
@@ -7,6 +7,11 @@ export {
|
|
|
7
7
|
type AgentStorageContractSuiteOptions,
|
|
8
8
|
runAgentStorageContractSuite,
|
|
9
9
|
} from "./agent-storage-contract"
|
|
10
|
+
export {
|
|
11
|
+
type AiStorageContractSuiteOptions,
|
|
12
|
+
runAiCostStorageContractSuite,
|
|
13
|
+
seedAiCostStorageContractUsage,
|
|
14
|
+
} from "./ai-cost-storage-contract"
|
|
10
15
|
export {
|
|
11
16
|
type AiUsageStorageContractSuiteOptions,
|
|
12
17
|
runAiUsageStorageContractSuite,
|
|
@@ -55,6 +55,23 @@ const devices = defineDataset("storage_contract_devices", {
|
|
|
55
55
|
const readings = defineDataset("storage_contract_readings", {
|
|
56
56
|
schema: [col("device_id", "string"), col("at", "timestamp"), col("value", "float64")],
|
|
57
57
|
})
|
|
58
|
+
const ConflictDevice = defineObjectType({
|
|
59
|
+
id: "StorageContractConflictDevice",
|
|
60
|
+
name: "Conflict device",
|
|
61
|
+
properties: [
|
|
62
|
+
prop("id", "string", { primary: true, required: true }),
|
|
63
|
+
prop("name", "string", { required: true }),
|
|
64
|
+
prop("state", "string", { required: true }),
|
|
65
|
+
],
|
|
66
|
+
})
|
|
67
|
+
const conflictDevices = defineDataset("storage_contract_conflict_devices", {
|
|
68
|
+
schema: [
|
|
69
|
+
col("id", "string"),
|
|
70
|
+
col("name", "string"),
|
|
71
|
+
col("state", "string"),
|
|
72
|
+
col("updated_at", "timestamp"),
|
|
73
|
+
],
|
|
74
|
+
})
|
|
58
75
|
const deviceProjection = defineProjection("storage_contract_devices", Device)
|
|
59
76
|
.fromDataset(devices)
|
|
60
77
|
.properties({ id: "id", name: "name" })
|
|
@@ -71,14 +88,22 @@ const temperatureProjection = defineProjection(
|
|
|
71
88
|
)
|
|
72
89
|
.fromDataset(readings)
|
|
73
90
|
.points({ objectId: "device_id", at: "at", value: "value" })
|
|
91
|
+
const conflictDeviceProjection = defineProjection(
|
|
92
|
+
"storage_contract_conflict_devices",
|
|
93
|
+
ConflictDevice
|
|
94
|
+
)
|
|
95
|
+
.fromDataset(conflictDevices)
|
|
96
|
+
.properties({ id: "id", name: "name", state: "state" })
|
|
97
|
+
.resolveConflicts({ strategy: "mostRecent", sourceTimestamp: "updated_at" })
|
|
74
98
|
|
|
75
|
-
const ontology = new OntologyRegistry({ sources: [Device] })
|
|
99
|
+
const ontology = new OntologyRegistry({ sources: [Device, ConflictDevice] })
|
|
76
100
|
const projections = new ProjectionRegistry({
|
|
77
|
-
projections: [deviceProjection, temperatureProjection],
|
|
101
|
+
projections: [deviceProjection, temperatureProjection, conflictDeviceProjection],
|
|
78
102
|
ontology,
|
|
79
103
|
datasetsById: new Map<string, DatasetDefinition>([
|
|
80
104
|
[devices.id, devices],
|
|
81
105
|
[readings.id, readings],
|
|
106
|
+
[conflictDevices.id, conflictDevices],
|
|
82
107
|
]),
|
|
83
108
|
})
|
|
84
109
|
|
|
@@ -339,6 +364,99 @@ export function runMaterializerStorageContractSuite<TStorage extends Storage>(
|
|
|
339
364
|
}
|
|
340
365
|
})
|
|
341
366
|
|
|
367
|
+
test(`${name} persists per-property Action edit times`, async () => {
|
|
368
|
+
const createdStorage = await provider.createStorage()
|
|
369
|
+
const storage = requireContractStorage(createdStorage)
|
|
370
|
+
let materializationOrdinal = 0
|
|
371
|
+
let now = new Date("2026-02-01T00:00:10.000Z")
|
|
372
|
+
const materializer = createOntologyMaterializer({
|
|
373
|
+
projectId: "materializer-storage-contract",
|
|
374
|
+
ontology,
|
|
375
|
+
projections,
|
|
376
|
+
storage,
|
|
377
|
+
dependencies: {
|
|
378
|
+
batching: { sourceStageRows: 1, statePageRows: 1, planChunkRows: 1 },
|
|
379
|
+
clock: () => now,
|
|
380
|
+
materializationId: () => `conflict-contract-candidate-${++materializationOrdinal}`,
|
|
381
|
+
},
|
|
382
|
+
})
|
|
383
|
+
const runtimeMaterializer = materializer.withScope(runtimeScope())
|
|
384
|
+
const ref = { objectTypeId: ConflictDevice.id, primaryId: "one" }
|
|
385
|
+
const replace = async (
|
|
386
|
+
versionId: string,
|
|
387
|
+
sourceUpdatedAt: string,
|
|
388
|
+
name: string,
|
|
389
|
+
state: string
|
|
390
|
+
) => {
|
|
391
|
+
const datasetVersion = {
|
|
392
|
+
datasetId: conflictDevices.id,
|
|
393
|
+
versionId,
|
|
394
|
+
createdAt: sourceUpdatedAt,
|
|
395
|
+
}
|
|
396
|
+
const execution = await claim(storage, {
|
|
397
|
+
runId: `conflict-${versionId}`,
|
|
398
|
+
projectionId: conflictDeviceProjection.id,
|
|
399
|
+
protocol: "replacement",
|
|
400
|
+
datasetVersion,
|
|
401
|
+
})
|
|
402
|
+
const projectionMaterializerForRun = await projectionMaterializer(
|
|
403
|
+
materializer,
|
|
404
|
+
storage,
|
|
405
|
+
conflictDeviceProjection.id,
|
|
406
|
+
`conflict-${versionId}`
|
|
407
|
+
)
|
|
408
|
+
await projectionMaterializerForRun.projections.replace({
|
|
409
|
+
source: { projectionId: conflictDeviceProjection.id },
|
|
410
|
+
datasetVersion,
|
|
411
|
+
execution,
|
|
412
|
+
entries: entries([
|
|
413
|
+
{
|
|
414
|
+
root: { kind: "object", ref },
|
|
415
|
+
assertions: [{ kind: "object", ref, properties: { name, state }, sourceUpdatedAt }],
|
|
416
|
+
},
|
|
417
|
+
]),
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
const edit = (requestId: string, propertyId: "name" | "state", value: string) =>
|
|
421
|
+
runtimeMaterializer.edits.commit({
|
|
422
|
+
mode: "atomic",
|
|
423
|
+
source: { kind: "runtime", requestId },
|
|
424
|
+
operations: [
|
|
425
|
+
{
|
|
426
|
+
id: requestId,
|
|
427
|
+
kind: "object.patch",
|
|
428
|
+
ref,
|
|
429
|
+
set: { [propertyId]: value },
|
|
430
|
+
unset: [],
|
|
431
|
+
reset: [],
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
expectedObjects: [],
|
|
435
|
+
expectedLinks: [],
|
|
436
|
+
expectedLinkScopes: [],
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
await replace("v1", "2026-02-01T00:00:10.000Z", "Source A", "source-open")
|
|
441
|
+
|
|
442
|
+
now = new Date("2026-02-01T00:00:11.000Z")
|
|
443
|
+
await edit("conflict-name", "name", "Action B")
|
|
444
|
+
now = new Date("2026-02-01T00:00:13.000Z")
|
|
445
|
+
await edit("conflict-state", "state", "action-closed")
|
|
446
|
+
|
|
447
|
+
await replace("v2", "2026-02-01T00:00:12.000Z", "Source C", "source-changed")
|
|
448
|
+
expect(
|
|
449
|
+
await storage.objects.getByPrimaryId({
|
|
450
|
+
projectId: "materializer-storage-contract",
|
|
451
|
+
objectTypeId: ConflictDevice.id,
|
|
452
|
+
primaryId: "one",
|
|
453
|
+
})
|
|
454
|
+
).toMatchObject({ properties: { id: "one", name: "Source C", state: "action-closed" } })
|
|
455
|
+
} finally {
|
|
456
|
+
await provider.cleanup?.(createdStorage)
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
|
|
342
460
|
test(`${name} persists cardinality-one edit authority by scope`, async () => {
|
|
343
461
|
const createdStorage = await provider.createStorage()
|
|
344
462
|
const storage = requireContractStorage(createdStorage)
|
|
@@ -66,11 +66,16 @@ export abstract class QueueWorker<
|
|
|
66
66
|
failureCodes: config.failureCodes,
|
|
67
67
|
workerId: config.workerId,
|
|
68
68
|
leaseMs: config.leaseMs ?? DEFAULT_LEASE_MS,
|
|
69
|
-
claimLimit: config.claimLimit
|
|
69
|
+
claimLimit: normalizeClaimLimit(config.claimLimit),
|
|
70
70
|
idlePollMs: config.idlePollMs ?? DEFAULT_IDLE_POLL_MS,
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/** Maximum jobs this worker process claims and executes at once. */
|
|
75
|
+
get concurrency(): number {
|
|
76
|
+
return this.config.claimLimit
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
protected async run(signal: AbortSignal): Promise<void> {
|
|
75
80
|
const inFlight = new Set<Promise<void>>()
|
|
76
81
|
let runError: unknown = null
|
|
@@ -243,6 +248,14 @@ export abstract class QueueWorker<
|
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
250
|
|
|
251
|
+
function normalizeClaimLimit(value: number | undefined): number {
|
|
252
|
+
const claimLimit = value ?? DEFAULT_CLAIM_LIMIT
|
|
253
|
+
if (!Number.isSafeInteger(claimLimit) || claimLimit < 1) {
|
|
254
|
+
throw new Error("[SixbQueueWorker] Worker concurrency must be a positive safe integer.")
|
|
255
|
+
}
|
|
256
|
+
return claimLimit
|
|
257
|
+
}
|
|
258
|
+
|
|
246
259
|
async function settleOrLog<TJob extends QueueJob, TFailureCode extends SixbErrorCode>(
|
|
247
260
|
delivery: QueueDelivery<TJob, TFailureCode>,
|
|
248
261
|
operation: string,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { canViewWorkflowIntervention, canViewWorkflowRun, isAllowed } from "../authorization"
|
|
2
2
|
import type { AuthorizablePrincipal, ExecutionContext } from "../execution"
|
|
3
|
+
import { resolveExecutionCosts } from "../runtime/ai-cost"
|
|
3
4
|
import { resolveExecutionUsage } from "../runtime/ai-usage"
|
|
4
5
|
import type { SixbRuntimeContext } from "../runtime/types"
|
|
6
|
+
import type { AiCostSummary } from "../storage/ai-cost"
|
|
5
7
|
import type { AiModelCallUsage } from "../storage/ai-usage"
|
|
6
8
|
import type {
|
|
7
9
|
ListWorkflowInterventionsInput,
|
|
@@ -61,6 +63,8 @@ export type ListWorkflowRunNodesInput = Omit<
|
|
|
61
63
|
/** Execution-facing Agent node view derived from its run row and model-call ledger. */
|
|
62
64
|
export interface WorkflowAgentNodeRunView extends Omit<WorkflowAgentNodeRunRecord, "execution"> {
|
|
63
65
|
readonly usage?: AiModelCallUsage
|
|
66
|
+
/** Preferred valuation; omitted when there are no calls or cost storage is unavailable. */
|
|
67
|
+
readonly cost?: AiCostSummary
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/** Execution-facing workflow node view with optional Agent execution details. */
|
|
@@ -226,29 +230,45 @@ async function attachWorkflowNodeViews(
|
|
|
226
230
|
)
|
|
227
231
|
if (agentExecutions.length === 0) return nodes
|
|
228
232
|
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
const executionIds = agentExecutions.map((execution) => execution.executionId)
|
|
234
|
+
const [usages, costs] = await Promise.all([
|
|
235
|
+
resolveExecutionUsage({
|
|
236
|
+
storage: runtime.storage.aiUsage,
|
|
237
|
+
projectId: runtime.projectId,
|
|
238
|
+
executionIds,
|
|
239
|
+
}),
|
|
240
|
+
resolveExecutionCosts({
|
|
241
|
+
storage: runtime.storage.aiCosts,
|
|
242
|
+
projectId: runtime.projectId,
|
|
243
|
+
executionIds,
|
|
244
|
+
}),
|
|
245
|
+
])
|
|
246
|
+
const accountingByExecutionId = new Map(
|
|
247
|
+
agentExecutions.map((execution, index) => [
|
|
248
|
+
execution.executionId,
|
|
249
|
+
{ usage: usages[index], cost: costs[index] },
|
|
250
|
+
])
|
|
236
251
|
)
|
|
237
252
|
|
|
238
253
|
return nodes.map((node, index) => {
|
|
239
254
|
const execution = executions[index]
|
|
240
255
|
if (!execution) return node
|
|
241
|
-
const
|
|
256
|
+
const accounting = accountingByExecutionId.get(execution.executionId)
|
|
242
257
|
return {
|
|
243
258
|
...node,
|
|
244
|
-
agentExecution: toWorkflowAgentNodeRunView(
|
|
259
|
+
agentExecution: toWorkflowAgentNodeRunView(
|
|
260
|
+
execution,
|
|
261
|
+
accounting?.usage,
|
|
262
|
+
accounting?.usage === undefined ? undefined : accounting.cost
|
|
263
|
+
),
|
|
245
264
|
}
|
|
246
265
|
})
|
|
247
266
|
}
|
|
248
267
|
|
|
249
268
|
function toWorkflowAgentNodeRunView(
|
|
250
269
|
execution: WorkflowAgentNodeRunRecord,
|
|
251
|
-
usage: AiModelCallUsage | undefined
|
|
270
|
+
usage: AiModelCallUsage | undefined,
|
|
271
|
+
cost: AiCostSummary | undefined
|
|
252
272
|
): WorkflowAgentNodeRunView {
|
|
253
273
|
return {
|
|
254
274
|
projectId: execution.projectId,
|
|
@@ -267,6 +287,7 @@ function toWorkflowAgentNodeRunView(
|
|
|
267
287
|
startedAt: execution.startedAt,
|
|
268
288
|
completedAt: execution.completedAt,
|
|
269
289
|
...(usage === undefined ? {} : { usage }),
|
|
290
|
+
...(usage === undefined || cost === undefined ? {} : { cost }),
|
|
270
291
|
}
|
|
271
292
|
}
|
|
272
293
|
|