@sixb/pg 0.1.6 → 0.1.7

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 (47) hide show
  1. package/dist/agents/rows.d.ts +8 -5
  2. package/dist/agents/rows.d.ts.map +1 -1
  3. package/dist/agents/runs.d.ts +3 -2
  4. package/dist/agents/runs.d.ts.map +1 -1
  5. package/dist/agents/threads.d.ts.map +1 -1
  6. package/dist/ai-cost-groups.d.ts +5 -0
  7. package/dist/ai-cost-groups.d.ts.map +1 -0
  8. package/dist/auth-storage/auth-storage.d.ts +4 -1
  9. package/dist/auth-storage/auth-storage.d.ts.map +1 -1
  10. package/dist/auth-storage/device-authorizations.d.ts +29 -0
  11. package/dist/auth-storage/device-authorizations.d.ts.map +1 -0
  12. package/dist/auth-storage/rows.d.ts +19 -1
  13. package/dist/auth-storage/rows.d.ts.map +1 -1
  14. package/dist/auth-storage/shared.d.ts +1 -1
  15. package/dist/auth-storage/shared.d.ts.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1721 -393
  19. package/dist/index.js.map +17 -14
  20. package/dist/migrations.d.ts.map +1 -1
  21. package/dist/pg-ai-cost-storage.d.ts +2 -1
  22. package/dist/pg-ai-cost-storage.d.ts.map +1 -1
  23. package/dist/pg-ai-limit-storage.d.ts +34 -0
  24. package/dist/pg-ai-limit-storage.d.ts.map +1 -0
  25. package/dist/pg-ai-usage-storage.d.ts +28 -0
  26. package/dist/pg-ai-usage-storage.d.ts.map +1 -1
  27. package/package.json +4 -4
  28. package/src/agents/rows.ts +38 -9
  29. package/src/agents/runs.ts +148 -11
  30. package/src/agents/threads.ts +1 -17
  31. package/src/ai-cost-groups.ts +102 -0
  32. package/src/auth-storage/auth-storage.ts +84 -0
  33. package/src/auth-storage/device-authorizations.ts +131 -0
  34. package/src/auth-storage/rows.ts +41 -0
  35. package/src/auth-storage/shared.ts +1 -0
  36. package/src/index.ts +8 -1
  37. package/src/migrations/029-model-accounting.sql +24 -0
  38. package/src/migrations/030-ai-usage-limits.sql +94 -0
  39. package/src/migrations/031-subagent-runs.sql +97 -0
  40. package/src/migrations/032-conversation-run-spec.sql +23 -0
  41. package/src/migrations/033-retire-agent-definitions.sql +28 -0
  42. package/src/migrations/034-device-authorizations.sql +24 -0
  43. package/src/migrations.ts +18 -0
  44. package/src/pg-ai-cost-storage.ts +71 -24
  45. package/src/pg-ai-limit-storage.ts +901 -0
  46. package/src/pg-ai-usage-storage.ts +34 -0
  47. package/src/pg-workflow-run-storage.ts +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAmE3B,OAAO,KAAK,EAAE,GAAG,EAAa,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAE1F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/D,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,wBAAwB,CAAC,CAEzC;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,GACjB,SAAS,eAAe,EAAE,CAQ5B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,wBAAwB,CAAC,CAAA;CAC5D,GAAG,eAAe,CAqBlB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKrD;AA6KD,eAAO,MAAM,yBAAyB,wCAgCpC,CAAA"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AA+E3B,OAAO,KAAK,EAAE,GAAG,EAAa,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAE1F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/D,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,wBAAwB,CAAC,CAEzC;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,GACjB,SAAS,eAAe,EAAE,CAQ5B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,wBAAwB,CAAC,CAAA;CAC5D,GAAG,eAAe,CAqBlB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKrD;AA6KD,eAAO,MAAM,yBAAyB,wCAsCpC,CAAA"}
@@ -1,9 +1,10 @@
1
- import { type AiAccountingOverview, type AiCostStorage, type AiCostSummary, type AiModelCallCostRecord, type ListAiModelCallAccountingInput, type ListAiModelCallAccountingResult, type QueryAiAccountingOverviewInput, type SummarizeAiCostExecutionsInput } from "@sixb/core/storage";
1
+ import { type AiAccountingOverview, type AiCostStorage, type AiCostSummary, type AiModelCallCostRecord, type ListAiModelCallAccountingInput, type ListAiModelCallAccountingResult, type ListAiModelCallGroupsInput, type ListAiModelCallGroupsResult, type QueryAiAccountingOverviewInput, type SummarizeAiCostExecutionsInput } from "@sixb/core/storage";
2
2
  import type { PgStoreClient } from "./transactions";
3
3
  /** PostgreSQL-backed immutable valuations stored in one append-only table. */
4
4
  export declare class PgAiCostStorage implements AiCostStorage {
5
5
  private readonly sql;
6
6
  constructor(sql: PgStoreClient);
7
+ listModelCallGroups(input: ListAiModelCallGroupsInput): Promise<ListAiModelCallGroupsResult>;
7
8
  recordModelCallCost(input: AiModelCallCostRecord): Promise<void>;
8
9
  summarizeExecutions(input: SummarizeAiCostExecutionsInput): Promise<readonly AiCostSummary[]>;
9
10
  queryProjectOverview(input: QueryAiAccountingOverviewInput): Promise<AiAccountingOverview>;
@@ -1 +1 @@
1
- {"version":3,"file":"pg-ai-cost-storage.d.ts","sourceRoot":"","sources":["../src/pg-ai-cost-storage.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAElB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAE1B,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,EACpC,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD,8EAA8E;AAC9E,qBAAa,eAAgB,YAAW,aAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAEzC,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BhE,mBAAmB,CACvB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAsB9B,oBAAoB,CAAC,KAAK,EAAE,8BAA8B,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA6G1F,cAAc,CAClB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,+BAA+B,CAAC;CAsF5C"}
1
+ {"version":3,"file":"pg-ai-cost-storage.d.ts","sourceRoot":"","sources":["../src/pg-ai-cost-storage.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAElB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAE1B,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,EACpC,MAAM,oBAAoB,CAAA;AAG3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD,8EAA8E;AAC9E,qBAAa,eAAgB,YAAW,aAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAEzC,mBAAmB,CACvB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC;IAIjC,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BhE,mBAAmB,CACvB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAsB9B,oBAAoB,CAAC,KAAK,EAAE,8BAA8B,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAoH1F,cAAc,CAClB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,+BAA+B,CAAC;CAwF5C"}
@@ -0,0 +1,34 @@
1
+ import type { AiLimitPolicy, AiLimitPolicyStatus, AiLimitStorage, AiModelCallReservation, CreateAiLimitPolicyInput, DeleteAiLimitPolicyInput, GetAiLimitPolicyInput, ListAiLimitPoliciesInput, ListAiLimitPolicyStatusesInput, MarkAiModelCallReservationUnknownInput, ReconcileAiModelCallInput, RecordAiModelCallLimitActualsInput, ReserveAiModelCallInput, ReserveAiModelCallResult, UpdateAiLimitPolicyInput } from "@sixb/core/storage";
2
+ import { type PgStoreClient } from "./transactions";
3
+ /** PostgreSQL-backed editable AI limits and atomic model-call reservations. */
4
+ export declare class PgAiLimitStorage implements AiLimitStorage {
5
+ private readonly sql;
6
+ private readonly operations;
7
+ constructor(sql: PgStoreClient);
8
+ /** @internal Keep queued work inside the lifetime of the owning SQL transaction. */
9
+ settleOperations(): Promise<void>;
10
+ createPolicy(input: CreateAiLimitPolicyInput): Promise<AiLimitPolicy>;
11
+ updatePolicy(input: UpdateAiLimitPolicyInput): Promise<AiLimitPolicy>;
12
+ deletePolicy(input: DeleteAiLimitPolicyInput): Promise<boolean>;
13
+ getPolicy(input: GetAiLimitPolicyInput): Promise<AiLimitPolicy | null>;
14
+ listPolicies(input: ListAiLimitPoliciesInput): Promise<readonly AiLimitPolicy[]>;
15
+ listPolicyStatuses(input: ListAiLimitPolicyStatusesInput): Promise<readonly AiLimitPolicyStatus[]>;
16
+ reserveModelCall(input: ReserveAiModelCallInput): Promise<ReserveAiModelCallResult>;
17
+ recordModelCallActuals(input: RecordAiModelCallLimitActualsInput): Promise<void>;
18
+ reconcileModelCall(input: ReconcileAiModelCallInput): Promise<AiModelCallReservation>;
19
+ markReservationUnknown(input: MarkAiModelCallReservationUnknownInput): Promise<AiModelCallReservation>;
20
+ private withProjectLock;
21
+ private policyRows;
22
+ private requirePolicy;
23
+ private statusForPolicy;
24
+ private changeCounters;
25
+ private moveEstimateCounters;
26
+ private reconcileEstimateCounters;
27
+ private insertReservation;
28
+ private updateReservation;
29
+ private findReservation;
30
+ private accountingEntries;
31
+ private requireAccountingEntry;
32
+ private assertExecutionExists;
33
+ }
34
+ //# sourceMappingURL=pg-ai-limit-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg-ai-limit-storage.d.ts","sourceRoot":"","sources":["../src/pg-ai-limit-storage.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAEV,aAAa,EACb,mBAAmB,EAGnB,cAAc,EAEd,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,8BAA8B,EAC9B,sCAAsC,EACtC,yBAAyB,EACzB,kCAAkC,EAClC,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAoB,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAA;AAEvF,+EAA+E;AAC/E,qBAAa,gBAAiB,YAAW,cAAc;IAGzC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;gBAE3B,GAAG,EAAE,aAAa;IAE/C,oFAAoF;IACpF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,YAAY,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,aAAa,CAAC;IAsBrE,YAAY,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,aAAa,CAAC;IAiBrE,YAAY,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC;IAY/D,SAAS,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAStE,YAAY,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAOhF,kBAAkB,CACtB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC;IAapC,gBAAgB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAqGnF,sBAAsB,CAAC,KAAK,EAAE,kCAAkC,GAAG,OAAO,CAAC,IAAI,CAAC;IA0ChF,kBAAkB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA+BrF,sBAAsB,CAC1B,KAAK,EAAE,sCAAsC,GAC5C,OAAO,CAAC,sBAAsB,CAAC;YAepB,eAAe;IAe7B,OAAO,CAAC,UAAU;YAYJ,aAAa;YAeb,eAAe;YA2Cf,cAAc;YA8Cd,oBAAoB;YA2BpB,yBAAyB;YAmCzB,iBAAiB;YAkBjB,iBAAiB;YAgBjB,eAAe;YAmBf,iBAAiB;YA8CjB,sBAAsB;YAwCtB,qBAAqB;CAgBpC"}
@@ -1,3 +1,4 @@
1
+ import type { ReadonlyJsonObject } from "@sixb/core/storage";
1
2
  import { type AiModelCallUsageRecord, type AiUsageExecutionSummary, type AiUsageStorage, type GetLatestAiModelCallForExecutionInput, type RecordAiModelCallInput, type RecordAiModelCallResult, type SummarizeAiUsageExecutionInput, type SummarizeAiUsageExecutionsInput } from "@sixb/core/storage";
2
3
  import { type PgStoreClient } from "./transactions";
3
4
  /** PostgreSQL-backed append-only model-call usage ledger. */
@@ -14,4 +15,31 @@ export declare class PgAiUsageStorage implements AiUsageStorage {
14
15
  private executionRows;
15
16
  private assertExecutionExists;
16
17
  }
18
+ interface AiUsageRow {
19
+ readonly project_id: string;
20
+ readonly id: string;
21
+ readonly execution_id: string;
22
+ readonly attempt: number | string;
23
+ readonly call_id: string;
24
+ readonly provider_id: string;
25
+ readonly provider_ids: Record<string, unknown> | string | null;
26
+ readonly requested_model_id: string;
27
+ readonly requested_reasoning: string | Record<string, unknown> | null;
28
+ readonly response_model_id: string | null;
29
+ readonly response_id: string;
30
+ readonly input_tokens: number | string | null;
31
+ readonly output_tokens: number | string | null;
32
+ readonly total_tokens: number | string | null;
33
+ readonly uncached_input_tokens: number | string | null;
34
+ readonly cache_read_input_tokens: number | string | null;
35
+ readonly cache_write_input_tokens: number | string | null;
36
+ readonly text_output_tokens: number | string | null;
37
+ readonly reasoning_output_tokens: number | string | null;
38
+ readonly reporting_status: AiModelCallUsageRecord["usage"]["reportingStatus"];
39
+ readonly raw_usage: ReadonlyJsonObject | string | null;
40
+ readonly occurred_at: Date | string;
41
+ readonly recorded_at: Date | string;
42
+ }
43
+ export declare function requestedReasoningFromRow(value: Exclude<AiUsageRow["requested_reasoning"], null>): AiModelCallUsageRecord["requestedReasoning"];
44
+ export {};
17
45
  //# sourceMappingURL=pg-ai-usage-storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pg-ai-usage-storage.d.ts","sourceRoot":"","sources":["../src/pg-ai-usage-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EAInB,KAAK,qCAAqC,EAE1C,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACrC,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAAE,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAA;AAErE,6DAA6D;AAC7D,qBAAa,gBAAiB,YAAW,cAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAEzC,eAAe,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAgFhF,kBAAkB,CACtB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,uBAAuB,CAAC;IAQ7B,qBAAqB,CACzB,KAAK,EAAE,qCAAqC,GAC3C,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;IAYnC,mBAAmB,CACvB,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,SAAS,uBAAuB,EAAE,CAAC;YAShC,oBAAoB;YAkBpB,WAAW;YAYX,WAAW;IA6BzB,OAAO,CAAC,aAAa;YAiBP,qBAAqB;CAgBpC"}
1
+ {"version":3,"file":"pg-ai-usage-storage.d.ts","sourceRoot":"","sources":["../src/pg-ai-usage-storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sixb/pg",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "PostgreSQL storage provider for Sixb",
5
5
  "keywords": [
6
6
  "sixb",
@@ -33,17 +33,17 @@
33
33
  "scripts": {
34
34
  "build": "bun ../../scripts/build-package.ts",
35
35
  "typecheck": "tsc --noEmit",
36
- "test:e2e": "bun test --preload ./tests/setup.ts ./tests/*.e2e.ts",
36
+ "test:e2e": "bun ../../scripts/ci-guard.ts --stall 90 --max 600 bun test --preload ./tests/setup.ts ./tests/*.e2e.ts",
37
37
  "prepack": "bun run build"
38
38
  },
39
39
  "dependencies": {
40
40
  "postgres": "^3.4.5"
41
41
  },
42
42
  "peerDependencies": {
43
- "@sixb/core": "0.1.6"
43
+ "@sixb/core": "0.1.7"
44
44
  },
45
45
  "devDependencies": {
46
- "@sixb/core": "0.1.6",
46
+ "@sixb/core": "0.1.7",
47
47
  "@types/bun": "latest",
48
48
  "typescript": "^5.7.0"
49
49
  },
@@ -7,7 +7,10 @@ import {
7
7
  type AgentRunDiagnostic,
8
8
  type AgentRunRecord,
9
9
  type AgentThreadRecord,
10
+ type ConversationAgentRunSpec,
10
11
  coerceAgentRunFinishReason,
12
+ type SubagentRunResult,
13
+ type SubagentRunSpec,
11
14
  } from "@sixb/core/storage"
12
15
  import type { SQLClient, SqlParameter } from "../pg-client"
13
16
 
@@ -17,7 +20,6 @@ import type { SQLClient, SqlParameter } from "../pg-client"
17
20
  export interface AgentThreadRow {
18
21
  project_id: string
19
22
  id: string
20
- agent_id: string
21
23
  owner_principal_type: Principal["type"]
22
24
  owner_principal_id: string
23
25
  title: string | null
@@ -33,9 +35,13 @@ export interface AgentRunRow {
33
35
  project_id: string
34
36
  id: string
35
37
  execution_id: string
36
- thread_id: string
37
- agent_id: string
38
- trigger_message_id: string
38
+ kind: AgentRunRecord["kind"]
39
+ thread_id: string | null
40
+ trigger_message_id: string | null
41
+ parent_run_id: string | null
42
+ spawn_key: string | null
43
+ spec: ConversationAgentRunSpec | SubagentRunSpec | string | null
44
+ result: SubagentRunResult | string | null
39
45
  requester_group_ids: string[] | string
40
46
  status: AgentRunRecord["status"]
41
47
  model_id: string | null
@@ -89,7 +95,6 @@ export function rowToThreadRecord(row: AgentThreadRow): AgentThreadRecord {
89
95
  return {
90
96
  id: row.id,
91
97
  projectId: row.project_id,
92
- agentId: row.agent_id,
93
98
  ownerPrincipal: { type: row.owner_principal_type, id: row.owner_principal_id },
94
99
  title: row.title ?? undefined,
95
100
  status: row.status,
@@ -102,13 +107,10 @@ export function rowToThreadRecord(row: AgentThreadRow): AgentThreadRecord {
102
107
  }
103
108
 
104
109
  export function rowToRunRecord(row: AgentRunRow): AgentRunRecord {
105
- return {
110
+ const base = {
106
111
  id: row.id,
107
112
  projectId: row.project_id,
108
113
  executionId: row.execution_id,
109
- threadId: row.thread_id,
110
- agentId: row.agent_id,
111
- triggerMessageId: row.trigger_message_id,
112
114
  requesterGroupIds:
113
115
  typeof row.requester_group_ids === "string"
114
116
  ? (JSON.parse(row.requester_group_ids) as string[])
@@ -130,6 +132,29 @@ export function rowToRunRecord(row: AgentRunRow): AgentRunRecord {
130
132
  startedAt: row.started_at ? new Date(row.started_at) : undefined,
131
133
  completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
132
134
  }
135
+ if (row.kind === "conversation") {
136
+ if (!row.thread_id || !row.trigger_message_id) {
137
+ throw new Error(`[SixbPg] Conversational Agent run '${row.id}' has invalid persisted fields.`)
138
+ }
139
+ return {
140
+ ...base,
141
+ kind: "conversation",
142
+ threadId: row.thread_id,
143
+ triggerMessageId: row.trigger_message_id,
144
+ ...(row.spec === null ? {} : { spec: parseJsonColumn<ConversationAgentRunSpec>(row.spec) }),
145
+ }
146
+ }
147
+ if (!row.parent_run_id || !row.spawn_key || row.spec === null) {
148
+ throw new Error(`[SixbPg] Subagent run '${row.id}' has invalid persisted fields.`)
149
+ }
150
+ return {
151
+ ...base,
152
+ kind: "subagent",
153
+ parentRunId: row.parent_run_id,
154
+ spawnKey: row.spawn_key,
155
+ spec: parseJsonColumn<SubagentRunSpec>(row.spec),
156
+ ...(row.result === null ? {} : { result: parseJsonColumn<SubagentRunResult>(row.result) }),
157
+ }
133
158
  }
134
159
 
135
160
  export function rowToMessageRecord(row: AgentMessageRow): AgentMessageRecord {
@@ -203,6 +228,10 @@ function normalizeDiagnostics(
203
228
  return typeof value === "string" ? (JSON.parse(value) as AgentRunDiagnostic[]) : value
204
229
  }
205
230
 
231
+ function parseJsonColumn<T>(value: unknown): T {
232
+ return typeof value === "string" ? (JSON.parse(value) as T) : (value as T)
233
+ }
234
+
206
235
  // ── offset pagination (threads / messages order by their own column, not started_at) ─────────────
207
236
 
208
237
  export async function queryAgentList<TRow>(input: {
@@ -1,4 +1,10 @@
1
- import { assertAgentRunExecution } from "@sixb/core/internal/agent-run-storage-provider"
1
+ import {
2
+ assertAgentRunExecution,
3
+ assertConversationAgentRunSpec,
4
+ assertCreateSubagentRunInput,
5
+ assertSubagentRunResult,
6
+ subagentRunMatchesCreateInput,
7
+ } from "@sixb/core/internal/agent-run-storage-provider"
2
8
  import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth"
3
9
  import { serializeSixbFailure } from "@sixb/core/internal/errors"
4
10
  import {
@@ -7,7 +13,9 @@ import {
7
13
  type AgentRunStore,
8
14
  AgentStorageError,
9
15
  type ConfirmAgentRunExecutionOwnershipInput,
16
+ type ConversationAgentRunRecord,
10
17
  type CreateAgentRunInput,
18
+ type CreateSubagentRunInput,
11
19
  type ExecutionStorage,
12
20
  type FinishAgentRunInput,
13
21
  type FinishQueuedAgentRunInput,
@@ -15,6 +23,7 @@ import {
15
23
  type ListAgentRunsResult,
16
24
  type ReclaimAgentRunInput,
17
25
  type StartAgentRunInput,
26
+ type SubagentRunRecord,
18
27
  } from "@sixb/core/storage"
19
28
  import type { SQLClient, SqlParameter } from "../pg-client"
20
29
  import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "../run-list-query"
@@ -30,14 +39,14 @@ export class PgAgentRunStore implements AgentRunStore {
30
39
  private readonly executions: ExecutionStorage
31
40
  ) {}
32
41
 
33
- async create(input: CreateAgentRunInput): Promise<AgentRunRecord> {
42
+ async create(input: CreateAgentRunInput): Promise<ConversationAgentRunRecord> {
43
+ assertConversationAgentRunSpec(input.spec, "SixbPg")
34
44
  const createdAt = input.createdAt ?? new Date()
35
45
  await assertAgentRunExecution({
36
46
  executions: this.executions,
37
47
  projectId: input.projectId,
38
48
  executionId: input.executionId,
39
49
  runId: input.id,
40
- agentId: input.agentId,
41
50
  })
42
51
 
43
52
  try {
@@ -54,7 +63,6 @@ export class PgAgentRunStore implements AgentRunStore {
54
63
  `[SixbPg] Agent thread '${input.threadId}' not found for project '${input.projectId}'.`
55
64
  )
56
65
  }
57
-
58
66
  if (thread.active_run_id !== null) {
59
67
  throw new AgentStorageError(
60
68
  "active_run_exists",
@@ -67,9 +75,10 @@ export class PgAgentRunStore implements AgentRunStore {
67
75
  project_id,
68
76
  id,
69
77
  execution_id,
78
+ kind,
70
79
  thread_id,
71
- agent_id,
72
80
  trigger_message_id,
81
+ spec,
73
82
  requester_group_ids,
74
83
  status,
75
84
  attempt,
@@ -78,9 +87,10 @@ export class PgAgentRunStore implements AgentRunStore {
78
87
  ${input.projectId},
79
88
  ${input.id},
80
89
  ${input.executionId},
90
+ ${"conversation"},
81
91
  ${input.threadId},
82
- ${input.agentId},
83
92
  ${input.triggerMessageId},
93
+ ${JSON.stringify(input.spec)}::text::jsonb,
84
94
  ${JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds))}::text::jsonb,
85
95
  ${"queued"},
86
96
  ${0},
@@ -94,7 +104,11 @@ export class PgAgentRunStore implements AgentRunStore {
94
104
  WHERE project_id = ${input.projectId} AND id = ${input.threadId}
95
105
  `
96
106
 
97
- return rowToRunRecord(row)
107
+ const created = rowToRunRecord(row)
108
+ if (created.kind !== "conversation") {
109
+ throw new Error(`[SixbPg] Agent run insert '${input.id}' returned the wrong run kind.`)
110
+ }
111
+ return created
98
112
  })
99
113
  } catch (error) {
100
114
  if (isUniqueViolation(error)) {
@@ -108,6 +122,108 @@ export class PgAgentRunStore implements AgentRunStore {
108
122
  }
109
123
  }
110
124
 
125
+ async createSubagent(input: CreateSubagentRunInput): Promise<SubagentRunRecord> {
126
+ assertCreateSubagentRunInput(input, "SixbPg")
127
+ const execution = await assertAgentRunExecution({
128
+ executions: this.executions,
129
+ projectId: input.projectId,
130
+ executionId: input.executionId,
131
+ runId: input.id,
132
+ })
133
+
134
+ try {
135
+ return await runPgTransaction(this.sql, async (tx) => {
136
+ const [parent] = await tx<AgentRunRow[]>`
137
+ SELECT * FROM agent_runs
138
+ WHERE project_id = ${input.projectId} AND id = ${input.parentRunId}
139
+ FOR UPDATE
140
+ `
141
+ if (!parent || parent.kind !== "conversation") {
142
+ throw new AgentStorageError(
143
+ "run_not_found",
144
+ `[SixbPg] Parent Agent run '${input.parentRunId}' was not found.`
145
+ )
146
+ }
147
+
148
+ const [existingRow] = await tx<AgentRunRow[]>`
149
+ SELECT * FROM agent_runs
150
+ WHERE project_id = ${input.projectId} AND id = ${input.id}
151
+ `
152
+ if (existingRow) {
153
+ const existing = rowToRunRecord(existingRow)
154
+ if (subagentRunMatchesCreateInput(existing, input)) return existing
155
+ throw new AgentStorageError(
156
+ "duplicate_id",
157
+ `[SixbPg] Subagent run '${input.id}' already exists with different immutable inputs.`
158
+ )
159
+ }
160
+ if (parent.status !== "running") {
161
+ throw new AgentStorageError(
162
+ "invalid_state",
163
+ `[SixbPg] Parent Agent run '${parent.id}' is not running (status '${parent.status}').`
164
+ )
165
+ }
166
+ if (
167
+ execution.source.type !== "execution" ||
168
+ execution.source.executionId !== parent.execution_id
169
+ ) {
170
+ throw new AgentStorageError(
171
+ "invalid_input",
172
+ `[SixbPg] Subagent execution '${execution.id}' is not a child of parent execution '${parent.execution_id}'.`
173
+ )
174
+ }
175
+ if (parent.execution_token !== input.parentExecutionToken) {
176
+ throw new AgentStorageError(
177
+ "execution_lost",
178
+ `[SixbPg] Parent Agent run '${parent.id}' is no longer owned by this execution.`
179
+ )
180
+ }
181
+ const parentRecord = rowToRunRecord(parent)
182
+
183
+ const [countRow] = await tx<{ count: string | number }[]>`
184
+ SELECT COUNT(*)::bigint AS count FROM agent_runs
185
+ WHERE project_id = ${input.projectId}
186
+ AND parent_run_id = ${input.parentRunId}
187
+ AND status IN (${"queued"}, ${"running"})
188
+ `
189
+ const activeChildren = Number(countRow?.count ?? 0)
190
+ if (activeChildren >= input.maxActiveChildren) {
191
+ throw new AgentStorageError(
192
+ "active_child_limit",
193
+ `[SixbPg] Agent run '${parent.id}' already has ${activeChildren} active subagents.`
194
+ )
195
+ }
196
+
197
+ const [row] = await tx<AgentRunRow[]>`
198
+ INSERT INTO agent_runs (
199
+ project_id, id, execution_id, kind, parent_run_id, spawn_key, spec,
200
+ requester_group_ids, status, attempt, created_at
201
+ ) VALUES (
202
+ ${input.projectId}, ${input.id}, ${input.executionId}, ${"subagent"},
203
+ ${input.parentRunId}, ${input.spawnKey},
204
+ ${JSON.stringify(input.spec)}::text::jsonb,
205
+ ${JSON.stringify(parentRecord.requesterGroupIds)}::text::jsonb,
206
+ ${"queued"}, ${0}, ${input.createdAt ?? new Date()}
207
+ )
208
+ RETURNING *
209
+ `
210
+ const created = rowToRunRecord(row)
211
+ if (created.kind !== "subagent") {
212
+ throw new Error(`[SixbPg] Subagent insert '${input.id}' returned the wrong run kind.`)
213
+ }
214
+ return created
215
+ })
216
+ } catch (error) {
217
+ if (isUniqueViolation(error)) {
218
+ throw new AgentStorageError(
219
+ "duplicate_id",
220
+ `[SixbPg] Subagent run '${input.id}' conflicts with an existing durable spawn.`
221
+ )
222
+ }
223
+ throw error
224
+ }
225
+ }
226
+
111
227
  async start(input: StartAgentRunInput): Promise<AgentRunRecord> {
112
228
  const startedAt = input.startedAt ?? new Date()
113
229
  return runPgTransaction(this.sql, async (tx) => {
@@ -198,6 +314,20 @@ export class PgAgentRunStore implements AgentRunStore {
198
314
  return runPgTransaction(this.sql, async (tx) => {
199
315
  const run = await this.lockRunning(tx, input.projectId, input.id)
200
316
 
317
+ if (input.status === "succeeded" && run.kind === "subagent") {
318
+ assertSubagentRunResult(input.result, run.id, "SixbPg")
319
+ }
320
+ if (
321
+ input.status === "succeeded" &&
322
+ run.kind === "conversation" &&
323
+ input.result !== undefined
324
+ ) {
325
+ throw new AgentStorageError(
326
+ "invalid_input",
327
+ `[SixbPg] Conversational Agent run '${run.id}' cannot persist a subagent result.`
328
+ )
329
+ }
330
+
201
331
  if (run.execution_token !== input.executionToken) {
202
332
  throw new AgentStorageError(
203
333
  "execution_lost",
@@ -213,6 +343,7 @@ export class PgAgentRunStore implements AgentRunStore {
213
343
  finish_reason = ${input.finishReason ?? null},
214
344
  error = ${errorValue}::text::jsonb,
215
345
  diagnostics = ${input.diagnostics === undefined ? null : JSON.stringify(input.diagnostics)}::text::jsonb,
346
+ result = ${input.status === "succeeded" && input.result !== undefined ? JSON.stringify(input.result) : null}::text::jsonb,
216
347
  execution_token = ${null},
217
348
  execution_queue_lease_expires_at = ${null},
218
349
  completed_at = ${completedAt}
@@ -262,7 +393,7 @@ export class PgAgentRunStore implements AgentRunStore {
262
393
  }
263
394
 
264
395
  async list(input: ListAgentRunsInput): Promise<ListAgentRunsResult> {
265
- if (hasEmptyStatuses(input)) {
396
+ if (hasEmptyStatuses(input) || input.kinds?.length === 0) {
266
397
  return { runs: [], hasMore: false, total: 0 }
267
398
  }
268
399
 
@@ -270,14 +401,19 @@ export class PgAgentRunStore implements AgentRunStore {
270
401
  const params: SqlParameter[] = [input.projectId]
271
402
  let index = 2
272
403
 
404
+ if (input.kinds) {
405
+ whereClauses.push(`kind = ANY($${index++}::text[])`)
406
+ params.push(input.kinds as string[])
407
+ }
408
+
273
409
  if (input.threadId) {
274
410
  whereClauses.push(`thread_id = $${index++}`)
275
411
  params.push(input.threadId)
276
412
  }
277
413
 
278
- if (input.agentId) {
279
- whereClauses.push(`agent_id = $${index++}`)
280
- params.push(input.agentId)
414
+ if (input.parentRunId) {
415
+ whereClauses.push(`parent_run_id = $${index++}`)
416
+ params.push(input.parentRunId)
281
417
  }
282
418
 
283
419
  index = appendRunListFilters(
@@ -336,6 +472,7 @@ export class PgAgentRunStore implements AgentRunStore {
336
472
  }
337
473
 
338
474
  private async releaseThread(sql: SQLClient, run: AgentRunRow, completedAt: Date): Promise<void> {
475
+ if (run.kind !== "conversation") return
339
476
  await sql`
340
477
  UPDATE agent_threads SET active_run_id = ${null}, updated_at = ${completedAt}
341
478
  WHERE project_id = ${run.project_id} AND id = ${run.thread_id} AND active_run_id = ${run.id}
@@ -23,7 +23,6 @@ export class PgAgentThreadStore implements AgentThreadStore {
23
23
  INSERT INTO agent_threads (
24
24
  project_id,
25
25
  id,
26
- agent_id,
27
26
  owner_principal_type,
28
27
  owner_principal_id,
29
28
  title,
@@ -33,7 +32,6 @@ export class PgAgentThreadStore implements AgentThreadStore {
33
32
  ) VALUES (
34
33
  ${input.projectId},
35
34
  ${input.id},
36
- ${input.agentId},
37
35
  ${input.ownerPrincipal.type},
38
36
  ${input.ownerPrincipal.id},
39
37
  ${input.title ?? null},
@@ -66,10 +64,7 @@ export class PgAgentThreadStore implements AgentThreadStore {
66
64
  }
67
65
 
68
66
  async list(input: ListAgentThreadsInput): Promise<ListAgentThreadsResult> {
69
- if (
70
- (input.statuses !== undefined && input.statuses.length === 0) ||
71
- (input.agentIds !== undefined && input.agentIds.length === 0)
72
- ) {
67
+ if (input.statuses !== undefined && input.statuses.length === 0) {
73
68
  return { threads: [], hasMore: false, total: 0 }
74
69
  }
75
70
 
@@ -77,17 +72,6 @@ export class PgAgentThreadStore implements AgentThreadStore {
77
72
  const params: SqlParameter[] = [input.projectId]
78
73
  let index = 2
79
74
 
80
- if (input.agentId) {
81
- whereClauses.push(`agent_id = $${index++}`)
82
- params.push(input.agentId)
83
- }
84
-
85
- if (input.agentIds) {
86
- const placeholders = input.agentIds.map(() => `$${index++}`)
87
- whereClauses.push(`agent_id IN (${placeholders.join(", ")})`)
88
- params.push(...input.agentIds)
89
- }
90
-
91
75
  if (input.statuses) {
92
76
  const placeholders = input.statuses.map(() => `$${index++}`)
93
77
  whereClauses.push(`status IN (${placeholders.join(", ")})`)
@@ -0,0 +1,102 @@
1
+ import {
2
+ type AiModelCallGroupRow,
3
+ aiModelCallGroupFragmentFromRow,
4
+ buildAiModelCallGroupsFromFragments,
5
+ normalizeAiModelCallAccountingQuery,
6
+ } from "@sixb/core/internal/ai-cost-storage-provider"
7
+ import type { ListAiModelCallGroupsInput, ListAiModelCallGroupsResult } from "@sixb/core/storage"
8
+ import type { PgStoreClient } from "./transactions"
9
+
10
+ /** Paginate initiating executions, then aggregate their matching calls without loading usage payloads. */
11
+ export async function listAiModelCallGroups(
12
+ sql: PgStoreClient,
13
+ input: ListAiModelCallGroupsInput
14
+ ): Promise<ListAiModelCallGroupsResult> {
15
+ const query = normalizeAiModelCallAccountingQuery(input)
16
+ const parameters = [
17
+ query.projectId,
18
+ query.from.toISOString(),
19
+ query.to.toISOString(),
20
+ query.providerId ?? null,
21
+ query.modelId ?? null,
22
+ query.valuationStatus ?? null,
23
+ ]
24
+ const [count] = await sql.unsafe<{ total: string }[]>(COUNT_SQL, parameters)
25
+ const rows = await sql.unsafe<AiModelCallGroupRow[]>(PAGE_SQL, [
26
+ ...parameters,
27
+ query.limit,
28
+ query.offset,
29
+ ])
30
+ const total = Number(count?.total)
31
+ if (!Number.isSafeInteger(total) || total < 0)
32
+ throw new RangeError("[Sixb] Invalid model-call group count.")
33
+ const items = buildAiModelCallGroupsFromFragments(rows.map(aiModelCallGroupFragmentFromRow))
34
+ return { items, total, hasMore: query.offset + items.length < total }
35
+ }
36
+
37
+ const FILTERED_SQL = `
38
+ WITH filtered AS (
39
+ SELECT COALESCE(root_agent.execution_id, usage.execution_id) AS root_execution_id,
40
+ usage.execution_id, usage.provider_id, usage.requested_model_id, usage.occurred_at,
41
+ usage.total_tokens,
42
+ COALESCE(cost.status, 'unvalued') AS valuation_status,
43
+ cost.currency AS amount_currency, cost.amount_nanos,
44
+ root_agent.id AS root_agent_run_id,
45
+ root_agent.thread_id AS root_thread_id,
46
+ direct_agent.kind AS direct_kind,
47
+ direct_agent.id AS direct_run_id,
48
+ direct_agent.parent_run_id AS parent_run_id,
49
+ workflow_node.node_id AS workflow_agent_step_id,
50
+ workflow_agent.node_run_id AS workflow_node_run_id,
51
+ workflow_node.workflow_id AS workflow_id,
52
+ workflow_node.workflow_run_id AS workflow_run_id
53
+ FROM ai_model_call_usage AS usage
54
+ LEFT JOIN agent_runs AS direct_agent
55
+ ON direct_agent.project_id = usage.project_id AND direct_agent.execution_id = usage.execution_id
56
+ LEFT JOIN agent_runs AS root_agent
57
+ ON root_agent.project_id = usage.project_id
58
+ AND root_agent.id = COALESCE(direct_agent.parent_run_id, direct_agent.id)
59
+ LEFT JOIN workflow_agent_node_runs AS workflow_agent
60
+ ON workflow_agent.project_id = usage.project_id AND workflow_agent.execution_id = usage.execution_id
61
+ LEFT JOIN workflow_node_runs AS workflow_node
62
+ ON workflow_node.project_id = workflow_agent.project_id AND workflow_node.id = workflow_agent.node_run_id
63
+ LEFT JOIN ai_model_call_valuations AS cost
64
+ ON cost.project_id = usage.project_id AND cost.usage_record_id = usage.id
65
+ WHERE usage.project_id = $1
66
+ AND usage.occurred_at >= $2::timestamptz
67
+ AND usage.occurred_at < $3::timestamptz
68
+ AND ($4::text IS NULL OR usage.provider_id = $4)
69
+ AND ($5::text IS NULL OR usage.requested_model_id = $5)
70
+ AND ($6::text IS NULL OR COALESCE(cost.status, 'unvalued') = $6)
71
+ ), roots AS (
72
+ SELECT root_execution_id, MAX(occurred_at) AS last_call_at
73
+ FROM filtered GROUP BY root_execution_id
74
+ )
75
+ `
76
+ const COUNT_SQL = `${FILTERED_SQL} SELECT CAST(COUNT(*) AS TEXT) AS total FROM roots`
77
+ const PAGE_SQL = `${FILTERED_SQL}, page AS (
78
+ SELECT root_execution_id FROM roots
79
+ ORDER BY last_call_at DESC, root_execution_id COLLATE "C" ASC LIMIT $7 OFFSET $8
80
+ )
81
+ SELECT filtered.root_execution_id, execution_id, provider_id, requested_model_id,
82
+ MIN(occurred_at) AS first_call_at, MAX(occurred_at) AS last_call_at,
83
+ CAST(COUNT(*) AS TEXT) AS call_count,
84
+ CAST(COUNT(total_tokens) AS TEXT) AS token_count,
85
+ CAST(SUM(total_tokens) AS TEXT) AS total_tokens,
86
+ valuation_status, amount_currency,
87
+ CAST(SUM(amount_nanos / 1000000000000) AS TEXT) AS amount_high,
88
+ CAST(SUM((amount_nanos / 1000000) % 1000000) AS TEXT) AS amount_middle,
89
+ CAST(SUM(amount_nanos % 1000000) AS TEXT) AS amount_low,
90
+ MAX(root_agent_run_id) AS root_agent_run_id,
91
+ MAX(root_thread_id) AS root_thread_id,
92
+ MAX(direct_kind) AS direct_kind,
93
+ MAX(direct_run_id) AS direct_run_id,
94
+ MAX(parent_run_id) AS parent_run_id,
95
+ MAX(workflow_agent_step_id) AS workflow_agent_step_id,
96
+ MAX(workflow_node_run_id) AS workflow_node_run_id,
97
+ MAX(workflow_id) AS workflow_id,
98
+ MAX(workflow_run_id) AS workflow_run_id
99
+ FROM filtered JOIN page ON page.root_execution_id = filtered.root_execution_id
100
+ GROUP BY filtered.root_execution_id, execution_id, provider_id, requested_model_id,
101
+ valuation_status, amount_currency
102
+ `