bopodev-db 0.1.31 → 0.1.32
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/.turbo/turbo-build.log +1 -1
- package/dist/repositories/company-assistant-chat.d.ts +56 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/legacy.d.ts +17 -0
- package/dist/schema.d.ts +504 -0
- package/package.json +1 -1
- package/src/migrations/0006_company_assistant_chat.sql +20 -0
- package/src/migrations/0007_cost_ledger_company_assistant.sql +7 -0
- package/src/migrations/meta/_journal.json +14 -0
- package/src/repositories/company-assistant-chat.ts +126 -0
- package/src/repositories/index.ts +1 -0
- package/src/repositories/legacy.ts +85 -1
- package/src/schema.ts +30 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { BopoDb } from "../client";
|
|
2
|
+
export type AssistantMessageRole = "user" | "assistant" | "system";
|
|
3
|
+
export declare function getOrCreateAssistantThread(db: BopoDb, companyId: string): Promise<{
|
|
4
|
+
id: string;
|
|
5
|
+
companyId: string;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
updatedAt: Date;
|
|
8
|
+
}>;
|
|
9
|
+
/** New empty thread; previous threads and messages remain in the database. */
|
|
10
|
+
export declare function createAssistantThread(db: BopoDb, companyId: string): Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
companyId: string;
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
updatedAt: Date;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function getAssistantThreadById(db: BopoDb, companyId: string, threadId: string): Promise<{
|
|
17
|
+
id: string;
|
|
18
|
+
companyId: string;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
updatedAt: Date;
|
|
21
|
+
} | null>;
|
|
22
|
+
export declare function touchAssistantThread(db: BopoDb, threadId: string): Promise<void>;
|
|
23
|
+
export declare function insertAssistantMessage(db: BopoDb, input: {
|
|
24
|
+
threadId: string;
|
|
25
|
+
companyId: string;
|
|
26
|
+
role: AssistantMessageRole;
|
|
27
|
+
body: string;
|
|
28
|
+
metadataJson?: string | null;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
id: string;
|
|
31
|
+
threadId: string;
|
|
32
|
+
companyId: string;
|
|
33
|
+
role: string;
|
|
34
|
+
body: string;
|
|
35
|
+
metadataJson: string | null;
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function listAssistantMessages(db: BopoDb, threadId: string, limit?: number): Promise<{
|
|
39
|
+
id: string;
|
|
40
|
+
threadId: string;
|
|
41
|
+
companyId: string;
|
|
42
|
+
role: string;
|
|
43
|
+
body: string;
|
|
44
|
+
metadataJson: string | null;
|
|
45
|
+
createdAt: Date;
|
|
46
|
+
}[]>;
|
|
47
|
+
/** Threads with at least one message in `[startInclusive, endExclusive)` on `created_at`. */
|
|
48
|
+
export declare function listAssistantChatThreadStatsInCreatedAtRange(db: BopoDb, companyId: string, startInclusive: Date, endExclusive: Date): Promise<Array<{
|
|
49
|
+
threadId: string;
|
|
50
|
+
messageCount: number;
|
|
51
|
+
}>>;
|
|
52
|
+
/** Threads with at least one message in the UTC calendar month (for callers without local bounds). */
|
|
53
|
+
export declare function listAssistantChatThreadStatsInUtcMonth(db: BopoDb, companyId: string, year: number, month1Based: number): Promise<Array<{
|
|
54
|
+
threadId: string;
|
|
55
|
+
messageCount: number;
|
|
56
|
+
}>>;
|
|
@@ -880,6 +880,10 @@ export declare function appendCost(db: BopoDb, input: {
|
|
|
880
880
|
projectId?: string | null;
|
|
881
881
|
issueId?: string | null;
|
|
882
882
|
agentId?: string | null;
|
|
883
|
+
/** Discriminator for reporting (e.g. `company_assistant`); null for heartbeat / legacy */
|
|
884
|
+
costCategory?: string | null;
|
|
885
|
+
assistantThreadId?: string | null;
|
|
886
|
+
assistantMessageId?: string | null;
|
|
883
887
|
}): Promise<string>;
|
|
884
888
|
export declare function listCostEntries(db: BopoDb, companyId: string, limit?: number): Promise<{
|
|
885
889
|
id: string;
|
|
@@ -888,6 +892,9 @@ export declare function listCostEntries(db: BopoDb, companyId: string, limit?: n
|
|
|
888
892
|
projectId: string | null;
|
|
889
893
|
issueId: string | null;
|
|
890
894
|
agentId: string | null;
|
|
895
|
+
costCategory: string | null;
|
|
896
|
+
assistantThreadId: string | null;
|
|
897
|
+
assistantMessageId: string | null;
|
|
891
898
|
providerType: string;
|
|
892
899
|
runtimeModelId: string | null;
|
|
893
900
|
pricingProviderType: string | null;
|
|
@@ -899,6 +906,16 @@ export declare function listCostEntries(db: BopoDb, companyId: string, limit?: n
|
|
|
899
906
|
usdCostStatus: string | null;
|
|
900
907
|
createdAt: Date;
|
|
901
908
|
}[]>;
|
|
909
|
+
export type CostLedgerAggregate = {
|
|
910
|
+
rowCount: number;
|
|
911
|
+
tokenInput: number;
|
|
912
|
+
tokenOutput: number;
|
|
913
|
+
/** Sum of `usd_cost` as a decimal string (full precision from DB). */
|
|
914
|
+
usdTotal: string;
|
|
915
|
+
};
|
|
916
|
+
/** Sum every ledger row for the company in `[startInclusive, endExclusive)` (typically UTC month). */
|
|
917
|
+
export declare function aggregateCompanyCostLedgerInRange(db: BopoDb, companyId: string, startInclusive: Date, endExclusive: Date): Promise<CostLedgerAggregate>;
|
|
918
|
+
export declare function aggregateCompanyCostLedgerAllTime(db: BopoDb, companyId: string): Promise<CostLedgerAggregate>;
|
|
902
919
|
export declare function listHeartbeatRuns(db: BopoDb, companyId: string, limit?: number): Promise<{
|
|
903
920
|
id: string;
|
|
904
921
|
companyId: string;
|
package/dist/schema.d.ts
CHANGED
|
@@ -3854,6 +3854,207 @@ export declare const attentionInboxStates: import("drizzle-orm/pg-core").PgTable
|
|
|
3854
3854
|
};
|
|
3855
3855
|
dialect: "pg";
|
|
3856
3856
|
}>;
|
|
3857
|
+
export declare const companyAssistantThreads: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3858
|
+
name: "company_assistant_threads";
|
|
3859
|
+
schema: undefined;
|
|
3860
|
+
columns: {
|
|
3861
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
3862
|
+
name: "id";
|
|
3863
|
+
tableName: "company_assistant_threads";
|
|
3864
|
+
dataType: "string";
|
|
3865
|
+
columnType: "PgText";
|
|
3866
|
+
data: string;
|
|
3867
|
+
driverParam: string;
|
|
3868
|
+
notNull: true;
|
|
3869
|
+
hasDefault: false;
|
|
3870
|
+
isPrimaryKey: true;
|
|
3871
|
+
isAutoincrement: false;
|
|
3872
|
+
hasRuntimeDefault: false;
|
|
3873
|
+
enumValues: [string, ...string[]];
|
|
3874
|
+
baseColumn: never;
|
|
3875
|
+
identity: undefined;
|
|
3876
|
+
generated: undefined;
|
|
3877
|
+
}, {}, {}>;
|
|
3878
|
+
companyId: import("drizzle-orm/pg-core").PgColumn<{
|
|
3879
|
+
name: "company_id";
|
|
3880
|
+
tableName: "company_assistant_threads";
|
|
3881
|
+
dataType: "string";
|
|
3882
|
+
columnType: "PgText";
|
|
3883
|
+
data: string;
|
|
3884
|
+
driverParam: string;
|
|
3885
|
+
notNull: true;
|
|
3886
|
+
hasDefault: false;
|
|
3887
|
+
isPrimaryKey: false;
|
|
3888
|
+
isAutoincrement: false;
|
|
3889
|
+
hasRuntimeDefault: false;
|
|
3890
|
+
enumValues: [string, ...string[]];
|
|
3891
|
+
baseColumn: never;
|
|
3892
|
+
identity: undefined;
|
|
3893
|
+
generated: undefined;
|
|
3894
|
+
}, {}, {}>;
|
|
3895
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3896
|
+
name: "created_at";
|
|
3897
|
+
tableName: "company_assistant_threads";
|
|
3898
|
+
dataType: "date";
|
|
3899
|
+
columnType: "PgTimestamp";
|
|
3900
|
+
data: Date;
|
|
3901
|
+
driverParam: string;
|
|
3902
|
+
notNull: true;
|
|
3903
|
+
hasDefault: true;
|
|
3904
|
+
isPrimaryKey: false;
|
|
3905
|
+
isAutoincrement: false;
|
|
3906
|
+
hasRuntimeDefault: false;
|
|
3907
|
+
enumValues: undefined;
|
|
3908
|
+
baseColumn: never;
|
|
3909
|
+
identity: undefined;
|
|
3910
|
+
generated: undefined;
|
|
3911
|
+
}, {}, {}>;
|
|
3912
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3913
|
+
name: "updated_at";
|
|
3914
|
+
tableName: "company_assistant_threads";
|
|
3915
|
+
dataType: "date";
|
|
3916
|
+
columnType: "PgTimestamp";
|
|
3917
|
+
data: Date;
|
|
3918
|
+
driverParam: string;
|
|
3919
|
+
notNull: true;
|
|
3920
|
+
hasDefault: true;
|
|
3921
|
+
isPrimaryKey: false;
|
|
3922
|
+
isAutoincrement: false;
|
|
3923
|
+
hasRuntimeDefault: false;
|
|
3924
|
+
enumValues: undefined;
|
|
3925
|
+
baseColumn: never;
|
|
3926
|
+
identity: undefined;
|
|
3927
|
+
generated: undefined;
|
|
3928
|
+
}, {}, {}>;
|
|
3929
|
+
};
|
|
3930
|
+
dialect: "pg";
|
|
3931
|
+
}>;
|
|
3932
|
+
export declare const companyAssistantMessages: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3933
|
+
name: "company_assistant_messages";
|
|
3934
|
+
schema: undefined;
|
|
3935
|
+
columns: {
|
|
3936
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
3937
|
+
name: "id";
|
|
3938
|
+
tableName: "company_assistant_messages";
|
|
3939
|
+
dataType: "string";
|
|
3940
|
+
columnType: "PgText";
|
|
3941
|
+
data: string;
|
|
3942
|
+
driverParam: string;
|
|
3943
|
+
notNull: true;
|
|
3944
|
+
hasDefault: false;
|
|
3945
|
+
isPrimaryKey: true;
|
|
3946
|
+
isAutoincrement: false;
|
|
3947
|
+
hasRuntimeDefault: false;
|
|
3948
|
+
enumValues: [string, ...string[]];
|
|
3949
|
+
baseColumn: never;
|
|
3950
|
+
identity: undefined;
|
|
3951
|
+
generated: undefined;
|
|
3952
|
+
}, {}, {}>;
|
|
3953
|
+
threadId: import("drizzle-orm/pg-core").PgColumn<{
|
|
3954
|
+
name: "thread_id";
|
|
3955
|
+
tableName: "company_assistant_messages";
|
|
3956
|
+
dataType: "string";
|
|
3957
|
+
columnType: "PgText";
|
|
3958
|
+
data: string;
|
|
3959
|
+
driverParam: string;
|
|
3960
|
+
notNull: true;
|
|
3961
|
+
hasDefault: false;
|
|
3962
|
+
isPrimaryKey: false;
|
|
3963
|
+
isAutoincrement: false;
|
|
3964
|
+
hasRuntimeDefault: false;
|
|
3965
|
+
enumValues: [string, ...string[]];
|
|
3966
|
+
baseColumn: never;
|
|
3967
|
+
identity: undefined;
|
|
3968
|
+
generated: undefined;
|
|
3969
|
+
}, {}, {}>;
|
|
3970
|
+
companyId: import("drizzle-orm/pg-core").PgColumn<{
|
|
3971
|
+
name: "company_id";
|
|
3972
|
+
tableName: "company_assistant_messages";
|
|
3973
|
+
dataType: "string";
|
|
3974
|
+
columnType: "PgText";
|
|
3975
|
+
data: string;
|
|
3976
|
+
driverParam: string;
|
|
3977
|
+
notNull: true;
|
|
3978
|
+
hasDefault: false;
|
|
3979
|
+
isPrimaryKey: false;
|
|
3980
|
+
isAutoincrement: false;
|
|
3981
|
+
hasRuntimeDefault: false;
|
|
3982
|
+
enumValues: [string, ...string[]];
|
|
3983
|
+
baseColumn: never;
|
|
3984
|
+
identity: undefined;
|
|
3985
|
+
generated: undefined;
|
|
3986
|
+
}, {}, {}>;
|
|
3987
|
+
role: import("drizzle-orm/pg-core").PgColumn<{
|
|
3988
|
+
name: "role";
|
|
3989
|
+
tableName: "company_assistant_messages";
|
|
3990
|
+
dataType: "string";
|
|
3991
|
+
columnType: "PgText";
|
|
3992
|
+
data: string;
|
|
3993
|
+
driverParam: string;
|
|
3994
|
+
notNull: true;
|
|
3995
|
+
hasDefault: false;
|
|
3996
|
+
isPrimaryKey: false;
|
|
3997
|
+
isAutoincrement: false;
|
|
3998
|
+
hasRuntimeDefault: false;
|
|
3999
|
+
enumValues: [string, ...string[]];
|
|
4000
|
+
baseColumn: never;
|
|
4001
|
+
identity: undefined;
|
|
4002
|
+
generated: undefined;
|
|
4003
|
+
}, {}, {}>;
|
|
4004
|
+
body: import("drizzle-orm/pg-core").PgColumn<{
|
|
4005
|
+
name: "body";
|
|
4006
|
+
tableName: "company_assistant_messages";
|
|
4007
|
+
dataType: "string";
|
|
4008
|
+
columnType: "PgText";
|
|
4009
|
+
data: string;
|
|
4010
|
+
driverParam: string;
|
|
4011
|
+
notNull: true;
|
|
4012
|
+
hasDefault: false;
|
|
4013
|
+
isPrimaryKey: false;
|
|
4014
|
+
isAutoincrement: false;
|
|
4015
|
+
hasRuntimeDefault: false;
|
|
4016
|
+
enumValues: [string, ...string[]];
|
|
4017
|
+
baseColumn: never;
|
|
4018
|
+
identity: undefined;
|
|
4019
|
+
generated: undefined;
|
|
4020
|
+
}, {}, {}>;
|
|
4021
|
+
metadataJson: import("drizzle-orm/pg-core").PgColumn<{
|
|
4022
|
+
name: "metadata_json";
|
|
4023
|
+
tableName: "company_assistant_messages";
|
|
4024
|
+
dataType: "string";
|
|
4025
|
+
columnType: "PgText";
|
|
4026
|
+
data: string;
|
|
4027
|
+
driverParam: string;
|
|
4028
|
+
notNull: false;
|
|
4029
|
+
hasDefault: false;
|
|
4030
|
+
isPrimaryKey: false;
|
|
4031
|
+
isAutoincrement: false;
|
|
4032
|
+
hasRuntimeDefault: false;
|
|
4033
|
+
enumValues: [string, ...string[]];
|
|
4034
|
+
baseColumn: never;
|
|
4035
|
+
identity: undefined;
|
|
4036
|
+
generated: undefined;
|
|
4037
|
+
}, {}, {}>;
|
|
4038
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
4039
|
+
name: "created_at";
|
|
4040
|
+
tableName: "company_assistant_messages";
|
|
4041
|
+
dataType: "date";
|
|
4042
|
+
columnType: "PgTimestamp";
|
|
4043
|
+
data: Date;
|
|
4044
|
+
driverParam: string;
|
|
4045
|
+
notNull: true;
|
|
4046
|
+
hasDefault: true;
|
|
4047
|
+
isPrimaryKey: false;
|
|
4048
|
+
isAutoincrement: false;
|
|
4049
|
+
hasRuntimeDefault: false;
|
|
4050
|
+
enumValues: undefined;
|
|
4051
|
+
baseColumn: never;
|
|
4052
|
+
identity: undefined;
|
|
4053
|
+
generated: undefined;
|
|
4054
|
+
}, {}, {}>;
|
|
4055
|
+
};
|
|
4056
|
+
dialect: "pg";
|
|
4057
|
+
}>;
|
|
3857
4058
|
export declare const costLedger: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3858
4059
|
name: "cost_ledger";
|
|
3859
4060
|
schema: undefined;
|
|
@@ -3960,6 +4161,57 @@ export declare const costLedger: import("drizzle-orm/pg-core").PgTableWithColumn
|
|
|
3960
4161
|
identity: undefined;
|
|
3961
4162
|
generated: undefined;
|
|
3962
4163
|
}, {}, {}>;
|
|
4164
|
+
costCategory: import("drizzle-orm/pg-core").PgColumn<{
|
|
4165
|
+
name: "cost_category";
|
|
4166
|
+
tableName: "cost_ledger";
|
|
4167
|
+
dataType: "string";
|
|
4168
|
+
columnType: "PgText";
|
|
4169
|
+
data: string;
|
|
4170
|
+
driverParam: string;
|
|
4171
|
+
notNull: false;
|
|
4172
|
+
hasDefault: false;
|
|
4173
|
+
isPrimaryKey: false;
|
|
4174
|
+
isAutoincrement: false;
|
|
4175
|
+
hasRuntimeDefault: false;
|
|
4176
|
+
enumValues: [string, ...string[]];
|
|
4177
|
+
baseColumn: never;
|
|
4178
|
+
identity: undefined;
|
|
4179
|
+
generated: undefined;
|
|
4180
|
+
}, {}, {}>;
|
|
4181
|
+
assistantThreadId: import("drizzle-orm/pg-core").PgColumn<{
|
|
4182
|
+
name: "assistant_thread_id";
|
|
4183
|
+
tableName: "cost_ledger";
|
|
4184
|
+
dataType: "string";
|
|
4185
|
+
columnType: "PgText";
|
|
4186
|
+
data: string;
|
|
4187
|
+
driverParam: string;
|
|
4188
|
+
notNull: false;
|
|
4189
|
+
hasDefault: false;
|
|
4190
|
+
isPrimaryKey: false;
|
|
4191
|
+
isAutoincrement: false;
|
|
4192
|
+
hasRuntimeDefault: false;
|
|
4193
|
+
enumValues: [string, ...string[]];
|
|
4194
|
+
baseColumn: never;
|
|
4195
|
+
identity: undefined;
|
|
4196
|
+
generated: undefined;
|
|
4197
|
+
}, {}, {}>;
|
|
4198
|
+
assistantMessageId: import("drizzle-orm/pg-core").PgColumn<{
|
|
4199
|
+
name: "assistant_message_id";
|
|
4200
|
+
tableName: "cost_ledger";
|
|
4201
|
+
dataType: "string";
|
|
4202
|
+
columnType: "PgText";
|
|
4203
|
+
data: string;
|
|
4204
|
+
driverParam: string;
|
|
4205
|
+
notNull: false;
|
|
4206
|
+
hasDefault: false;
|
|
4207
|
+
isPrimaryKey: false;
|
|
4208
|
+
isAutoincrement: false;
|
|
4209
|
+
hasRuntimeDefault: false;
|
|
4210
|
+
enumValues: [string, ...string[]];
|
|
4211
|
+
baseColumn: never;
|
|
4212
|
+
identity: undefined;
|
|
4213
|
+
generated: undefined;
|
|
4214
|
+
}, {}, {}>;
|
|
3963
4215
|
providerType: import("drizzle-orm/pg-core").PgColumn<{
|
|
3964
4216
|
name: "provider_type";
|
|
3965
4217
|
tableName: "cost_ledger";
|
|
@@ -9114,6 +9366,57 @@ export declare const schema: {
|
|
|
9114
9366
|
identity: undefined;
|
|
9115
9367
|
generated: undefined;
|
|
9116
9368
|
}, {}, {}>;
|
|
9369
|
+
costCategory: import("drizzle-orm/pg-core").PgColumn<{
|
|
9370
|
+
name: "cost_category";
|
|
9371
|
+
tableName: "cost_ledger";
|
|
9372
|
+
dataType: "string";
|
|
9373
|
+
columnType: "PgText";
|
|
9374
|
+
data: string;
|
|
9375
|
+
driverParam: string;
|
|
9376
|
+
notNull: false;
|
|
9377
|
+
hasDefault: false;
|
|
9378
|
+
isPrimaryKey: false;
|
|
9379
|
+
isAutoincrement: false;
|
|
9380
|
+
hasRuntimeDefault: false;
|
|
9381
|
+
enumValues: [string, ...string[]];
|
|
9382
|
+
baseColumn: never;
|
|
9383
|
+
identity: undefined;
|
|
9384
|
+
generated: undefined;
|
|
9385
|
+
}, {}, {}>;
|
|
9386
|
+
assistantThreadId: import("drizzle-orm/pg-core").PgColumn<{
|
|
9387
|
+
name: "assistant_thread_id";
|
|
9388
|
+
tableName: "cost_ledger";
|
|
9389
|
+
dataType: "string";
|
|
9390
|
+
columnType: "PgText";
|
|
9391
|
+
data: string;
|
|
9392
|
+
driverParam: string;
|
|
9393
|
+
notNull: false;
|
|
9394
|
+
hasDefault: false;
|
|
9395
|
+
isPrimaryKey: false;
|
|
9396
|
+
isAutoincrement: false;
|
|
9397
|
+
hasRuntimeDefault: false;
|
|
9398
|
+
enumValues: [string, ...string[]];
|
|
9399
|
+
baseColumn: never;
|
|
9400
|
+
identity: undefined;
|
|
9401
|
+
generated: undefined;
|
|
9402
|
+
}, {}, {}>;
|
|
9403
|
+
assistantMessageId: import("drizzle-orm/pg-core").PgColumn<{
|
|
9404
|
+
name: "assistant_message_id";
|
|
9405
|
+
tableName: "cost_ledger";
|
|
9406
|
+
dataType: "string";
|
|
9407
|
+
columnType: "PgText";
|
|
9408
|
+
data: string;
|
|
9409
|
+
driverParam: string;
|
|
9410
|
+
notNull: false;
|
|
9411
|
+
hasDefault: false;
|
|
9412
|
+
isPrimaryKey: false;
|
|
9413
|
+
isAutoincrement: false;
|
|
9414
|
+
hasRuntimeDefault: false;
|
|
9415
|
+
enumValues: [string, ...string[]];
|
|
9416
|
+
baseColumn: never;
|
|
9417
|
+
identity: undefined;
|
|
9418
|
+
generated: undefined;
|
|
9419
|
+
}, {}, {}>;
|
|
9117
9420
|
providerType: import("drizzle-orm/pg-core").PgColumn<{
|
|
9118
9421
|
name: "provider_type";
|
|
9119
9422
|
tableName: "cost_ledger";
|
|
@@ -10659,5 +10962,206 @@ export declare const schema: {
|
|
|
10659
10962
|
};
|
|
10660
10963
|
dialect: "pg";
|
|
10661
10964
|
}>;
|
|
10965
|
+
companyAssistantThreads: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
10966
|
+
name: "company_assistant_threads";
|
|
10967
|
+
schema: undefined;
|
|
10968
|
+
columns: {
|
|
10969
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
10970
|
+
name: "id";
|
|
10971
|
+
tableName: "company_assistant_threads";
|
|
10972
|
+
dataType: "string";
|
|
10973
|
+
columnType: "PgText";
|
|
10974
|
+
data: string;
|
|
10975
|
+
driverParam: string;
|
|
10976
|
+
notNull: true;
|
|
10977
|
+
hasDefault: false;
|
|
10978
|
+
isPrimaryKey: true;
|
|
10979
|
+
isAutoincrement: false;
|
|
10980
|
+
hasRuntimeDefault: false;
|
|
10981
|
+
enumValues: [string, ...string[]];
|
|
10982
|
+
baseColumn: never;
|
|
10983
|
+
identity: undefined;
|
|
10984
|
+
generated: undefined;
|
|
10985
|
+
}, {}, {}>;
|
|
10986
|
+
companyId: import("drizzle-orm/pg-core").PgColumn<{
|
|
10987
|
+
name: "company_id";
|
|
10988
|
+
tableName: "company_assistant_threads";
|
|
10989
|
+
dataType: "string";
|
|
10990
|
+
columnType: "PgText";
|
|
10991
|
+
data: string;
|
|
10992
|
+
driverParam: string;
|
|
10993
|
+
notNull: true;
|
|
10994
|
+
hasDefault: false;
|
|
10995
|
+
isPrimaryKey: false;
|
|
10996
|
+
isAutoincrement: false;
|
|
10997
|
+
hasRuntimeDefault: false;
|
|
10998
|
+
enumValues: [string, ...string[]];
|
|
10999
|
+
baseColumn: never;
|
|
11000
|
+
identity: undefined;
|
|
11001
|
+
generated: undefined;
|
|
11002
|
+
}, {}, {}>;
|
|
11003
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
11004
|
+
name: "created_at";
|
|
11005
|
+
tableName: "company_assistant_threads";
|
|
11006
|
+
dataType: "date";
|
|
11007
|
+
columnType: "PgTimestamp";
|
|
11008
|
+
data: Date;
|
|
11009
|
+
driverParam: string;
|
|
11010
|
+
notNull: true;
|
|
11011
|
+
hasDefault: true;
|
|
11012
|
+
isPrimaryKey: false;
|
|
11013
|
+
isAutoincrement: false;
|
|
11014
|
+
hasRuntimeDefault: false;
|
|
11015
|
+
enumValues: undefined;
|
|
11016
|
+
baseColumn: never;
|
|
11017
|
+
identity: undefined;
|
|
11018
|
+
generated: undefined;
|
|
11019
|
+
}, {}, {}>;
|
|
11020
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
11021
|
+
name: "updated_at";
|
|
11022
|
+
tableName: "company_assistant_threads";
|
|
11023
|
+
dataType: "date";
|
|
11024
|
+
columnType: "PgTimestamp";
|
|
11025
|
+
data: Date;
|
|
11026
|
+
driverParam: string;
|
|
11027
|
+
notNull: true;
|
|
11028
|
+
hasDefault: true;
|
|
11029
|
+
isPrimaryKey: false;
|
|
11030
|
+
isAutoincrement: false;
|
|
11031
|
+
hasRuntimeDefault: false;
|
|
11032
|
+
enumValues: undefined;
|
|
11033
|
+
baseColumn: never;
|
|
11034
|
+
identity: undefined;
|
|
11035
|
+
generated: undefined;
|
|
11036
|
+
}, {}, {}>;
|
|
11037
|
+
};
|
|
11038
|
+
dialect: "pg";
|
|
11039
|
+
}>;
|
|
11040
|
+
companyAssistantMessages: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
11041
|
+
name: "company_assistant_messages";
|
|
11042
|
+
schema: undefined;
|
|
11043
|
+
columns: {
|
|
11044
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
11045
|
+
name: "id";
|
|
11046
|
+
tableName: "company_assistant_messages";
|
|
11047
|
+
dataType: "string";
|
|
11048
|
+
columnType: "PgText";
|
|
11049
|
+
data: string;
|
|
11050
|
+
driverParam: string;
|
|
11051
|
+
notNull: true;
|
|
11052
|
+
hasDefault: false;
|
|
11053
|
+
isPrimaryKey: true;
|
|
11054
|
+
isAutoincrement: false;
|
|
11055
|
+
hasRuntimeDefault: false;
|
|
11056
|
+
enumValues: [string, ...string[]];
|
|
11057
|
+
baseColumn: never;
|
|
11058
|
+
identity: undefined;
|
|
11059
|
+
generated: undefined;
|
|
11060
|
+
}, {}, {}>;
|
|
11061
|
+
threadId: import("drizzle-orm/pg-core").PgColumn<{
|
|
11062
|
+
name: "thread_id";
|
|
11063
|
+
tableName: "company_assistant_messages";
|
|
11064
|
+
dataType: "string";
|
|
11065
|
+
columnType: "PgText";
|
|
11066
|
+
data: string;
|
|
11067
|
+
driverParam: string;
|
|
11068
|
+
notNull: true;
|
|
11069
|
+
hasDefault: false;
|
|
11070
|
+
isPrimaryKey: false;
|
|
11071
|
+
isAutoincrement: false;
|
|
11072
|
+
hasRuntimeDefault: false;
|
|
11073
|
+
enumValues: [string, ...string[]];
|
|
11074
|
+
baseColumn: never;
|
|
11075
|
+
identity: undefined;
|
|
11076
|
+
generated: undefined;
|
|
11077
|
+
}, {}, {}>;
|
|
11078
|
+
companyId: import("drizzle-orm/pg-core").PgColumn<{
|
|
11079
|
+
name: "company_id";
|
|
11080
|
+
tableName: "company_assistant_messages";
|
|
11081
|
+
dataType: "string";
|
|
11082
|
+
columnType: "PgText";
|
|
11083
|
+
data: string;
|
|
11084
|
+
driverParam: string;
|
|
11085
|
+
notNull: true;
|
|
11086
|
+
hasDefault: false;
|
|
11087
|
+
isPrimaryKey: false;
|
|
11088
|
+
isAutoincrement: false;
|
|
11089
|
+
hasRuntimeDefault: false;
|
|
11090
|
+
enumValues: [string, ...string[]];
|
|
11091
|
+
baseColumn: never;
|
|
11092
|
+
identity: undefined;
|
|
11093
|
+
generated: undefined;
|
|
11094
|
+
}, {}, {}>;
|
|
11095
|
+
role: import("drizzle-orm/pg-core").PgColumn<{
|
|
11096
|
+
name: "role";
|
|
11097
|
+
tableName: "company_assistant_messages";
|
|
11098
|
+
dataType: "string";
|
|
11099
|
+
columnType: "PgText";
|
|
11100
|
+
data: string;
|
|
11101
|
+
driverParam: string;
|
|
11102
|
+
notNull: true;
|
|
11103
|
+
hasDefault: false;
|
|
11104
|
+
isPrimaryKey: false;
|
|
11105
|
+
isAutoincrement: false;
|
|
11106
|
+
hasRuntimeDefault: false;
|
|
11107
|
+
enumValues: [string, ...string[]];
|
|
11108
|
+
baseColumn: never;
|
|
11109
|
+
identity: undefined;
|
|
11110
|
+
generated: undefined;
|
|
11111
|
+
}, {}, {}>;
|
|
11112
|
+
body: import("drizzle-orm/pg-core").PgColumn<{
|
|
11113
|
+
name: "body";
|
|
11114
|
+
tableName: "company_assistant_messages";
|
|
11115
|
+
dataType: "string";
|
|
11116
|
+
columnType: "PgText";
|
|
11117
|
+
data: string;
|
|
11118
|
+
driverParam: string;
|
|
11119
|
+
notNull: true;
|
|
11120
|
+
hasDefault: false;
|
|
11121
|
+
isPrimaryKey: false;
|
|
11122
|
+
isAutoincrement: false;
|
|
11123
|
+
hasRuntimeDefault: false;
|
|
11124
|
+
enumValues: [string, ...string[]];
|
|
11125
|
+
baseColumn: never;
|
|
11126
|
+
identity: undefined;
|
|
11127
|
+
generated: undefined;
|
|
11128
|
+
}, {}, {}>;
|
|
11129
|
+
metadataJson: import("drizzle-orm/pg-core").PgColumn<{
|
|
11130
|
+
name: "metadata_json";
|
|
11131
|
+
tableName: "company_assistant_messages";
|
|
11132
|
+
dataType: "string";
|
|
11133
|
+
columnType: "PgText";
|
|
11134
|
+
data: string;
|
|
11135
|
+
driverParam: string;
|
|
11136
|
+
notNull: false;
|
|
11137
|
+
hasDefault: false;
|
|
11138
|
+
isPrimaryKey: false;
|
|
11139
|
+
isAutoincrement: false;
|
|
11140
|
+
hasRuntimeDefault: false;
|
|
11141
|
+
enumValues: [string, ...string[]];
|
|
11142
|
+
baseColumn: never;
|
|
11143
|
+
identity: undefined;
|
|
11144
|
+
generated: undefined;
|
|
11145
|
+
}, {}, {}>;
|
|
11146
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
11147
|
+
name: "created_at";
|
|
11148
|
+
tableName: "company_assistant_messages";
|
|
11149
|
+
dataType: "date";
|
|
11150
|
+
columnType: "PgTimestamp";
|
|
11151
|
+
data: Date;
|
|
11152
|
+
driverParam: string;
|
|
11153
|
+
notNull: true;
|
|
11154
|
+
hasDefault: true;
|
|
11155
|
+
isPrimaryKey: false;
|
|
11156
|
+
isAutoincrement: false;
|
|
11157
|
+
hasRuntimeDefault: false;
|
|
11158
|
+
enumValues: undefined;
|
|
11159
|
+
baseColumn: never;
|
|
11160
|
+
identity: undefined;
|
|
11161
|
+
generated: undefined;
|
|
11162
|
+
}, {}, {}>;
|
|
11163
|
+
};
|
|
11164
|
+
dialect: "pg";
|
|
11165
|
+
}>;
|
|
10662
11166
|
};
|
|
10663
11167
|
export declare const touchUpdatedAtSql: import("drizzle-orm").SQL<unknown>;
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
CREATE TABLE "company_assistant_threads" (
|
|
2
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
3
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
4
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
5
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE INDEX "company_assistant_threads_company_idx" ON "company_assistant_threads" ("company_id");
|
|
9
|
+
|
|
10
|
+
CREATE TABLE "company_assistant_messages" (
|
|
11
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
12
|
+
"thread_id" text NOT NULL REFERENCES "company_assistant_threads"("id") ON DELETE CASCADE,
|
|
13
|
+
"company_id" text NOT NULL REFERENCES "companies"("id") ON DELETE CASCADE,
|
|
14
|
+
"role" text NOT NULL,
|
|
15
|
+
"body" text NOT NULL,
|
|
16
|
+
"metadata_json" text,
|
|
17
|
+
"created_at" timestamp DEFAULT now() NOT NULL
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX "company_assistant_messages_thread_created_idx" ON "company_assistant_messages" ("thread_id","created_at");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ALTER TABLE "cost_ledger" ADD COLUMN "cost_category" text;
|
|
2
|
+
--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "cost_ledger" ADD COLUMN "assistant_thread_id" text REFERENCES "company_assistant_threads"("id") ON DELETE SET NULL;
|
|
4
|
+
--> statement-breakpoint
|
|
5
|
+
ALTER TABLE "cost_ledger" ADD COLUMN "assistant_message_id" text REFERENCES "company_assistant_messages"("id") ON DELETE SET NULL;
|
|
6
|
+
--> statement-breakpoint
|
|
7
|
+
CREATE INDEX "idx_cost_ledger_company_category_created" ON "cost_ledger" ("company_id", "cost_category", "created_at");
|
|
@@ -43,6 +43,20 @@
|
|
|
43
43
|
"when": 1743000000000,
|
|
44
44
|
"tag": "0005_work_loops",
|
|
45
45
|
"breakpoints": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"idx": 6,
|
|
49
|
+
"version": "7",
|
|
50
|
+
"when": 1743100000000,
|
|
51
|
+
"tag": "0006_company_assistant_chat",
|
|
52
|
+
"breakpoints": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"idx": 7,
|
|
56
|
+
"version": "7",
|
|
57
|
+
"when": 1743200000000,
|
|
58
|
+
"tag": "0007_cost_ledger_company_assistant",
|
|
59
|
+
"breakpoints": true
|
|
46
60
|
}
|
|
47
61
|
]
|
|
48
62
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { and, asc, count, desc, eq, gte, lt } from "drizzle-orm";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import type { BopoDb } from "../client";
|
|
4
|
+
import { companyAssistantMessages, companyAssistantThreads } from "../schema";
|
|
5
|
+
|
|
6
|
+
export type AssistantMessageRole = "user" | "assistant" | "system";
|
|
7
|
+
|
|
8
|
+
export async function getOrCreateAssistantThread(db: BopoDb, companyId: string) {
|
|
9
|
+
const [existing] = await db
|
|
10
|
+
.select()
|
|
11
|
+
.from(companyAssistantThreads)
|
|
12
|
+
.where(eq(companyAssistantThreads.companyId, companyId))
|
|
13
|
+
.orderBy(desc(companyAssistantThreads.updatedAt))
|
|
14
|
+
.limit(1);
|
|
15
|
+
if (existing) {
|
|
16
|
+
return existing;
|
|
17
|
+
}
|
|
18
|
+
return createAssistantThreadRow(db, companyId);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function createAssistantThreadRow(db: BopoDb, companyId: string) {
|
|
22
|
+
const id = nanoid(16);
|
|
23
|
+
const now = new Date();
|
|
24
|
+
await db.insert(companyAssistantThreads).values({
|
|
25
|
+
id,
|
|
26
|
+
companyId,
|
|
27
|
+
createdAt: now,
|
|
28
|
+
updatedAt: now
|
|
29
|
+
});
|
|
30
|
+
const [row] = await db.select().from(companyAssistantThreads).where(eq(companyAssistantThreads.id, id)).limit(1);
|
|
31
|
+
return row!;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** New empty thread; previous threads and messages remain in the database. */
|
|
35
|
+
export async function createAssistantThread(db: BopoDb, companyId: string) {
|
|
36
|
+
return createAssistantThreadRow(db, companyId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function getAssistantThreadById(db: BopoDb, companyId: string, threadId: string) {
|
|
40
|
+
const [row] = await db
|
|
41
|
+
.select()
|
|
42
|
+
.from(companyAssistantThreads)
|
|
43
|
+
.where(and(eq(companyAssistantThreads.id, threadId), eq(companyAssistantThreads.companyId, companyId)))
|
|
44
|
+
.limit(1);
|
|
45
|
+
return row ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function touchAssistantThread(db: BopoDb, threadId: string) {
|
|
49
|
+
await db
|
|
50
|
+
.update(companyAssistantThreads)
|
|
51
|
+
.set({ updatedAt: new Date() })
|
|
52
|
+
.where(eq(companyAssistantThreads.id, threadId));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function insertAssistantMessage(
|
|
56
|
+
db: BopoDb,
|
|
57
|
+
input: {
|
|
58
|
+
threadId: string;
|
|
59
|
+
companyId: string;
|
|
60
|
+
role: AssistantMessageRole;
|
|
61
|
+
body: string;
|
|
62
|
+
metadataJson?: string | null;
|
|
63
|
+
}
|
|
64
|
+
) {
|
|
65
|
+
const id = nanoid(16);
|
|
66
|
+
await db.insert(companyAssistantMessages).values({
|
|
67
|
+
id,
|
|
68
|
+
threadId: input.threadId,
|
|
69
|
+
companyId: input.companyId,
|
|
70
|
+
role: input.role,
|
|
71
|
+
body: input.body,
|
|
72
|
+
metadataJson: input.metadataJson ?? null
|
|
73
|
+
});
|
|
74
|
+
await touchAssistantThread(db, input.threadId);
|
|
75
|
+
const [row] = await db.select().from(companyAssistantMessages).where(eq(companyAssistantMessages.id, id)).limit(1);
|
|
76
|
+
return row!;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function listAssistantMessages(db: BopoDb, threadId: string, limit = 100) {
|
|
80
|
+
const capped = Math.min(Math.max(1, limit), 200);
|
|
81
|
+
return db
|
|
82
|
+
.select()
|
|
83
|
+
.from(companyAssistantMessages)
|
|
84
|
+
.where(eq(companyAssistantMessages.threadId, threadId))
|
|
85
|
+
.orderBy(asc(companyAssistantMessages.createdAt))
|
|
86
|
+
.limit(capped);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Threads with at least one message in `[startInclusive, endExclusive)` on `created_at`. */
|
|
90
|
+
export async function listAssistantChatThreadStatsInCreatedAtRange(
|
|
91
|
+
db: BopoDb,
|
|
92
|
+
companyId: string,
|
|
93
|
+
startInclusive: Date,
|
|
94
|
+
endExclusive: Date
|
|
95
|
+
): Promise<Array<{ threadId: string; messageCount: number }>> {
|
|
96
|
+
const rows = await db
|
|
97
|
+
.select({
|
|
98
|
+
threadId: companyAssistantMessages.threadId,
|
|
99
|
+
messageCount: count()
|
|
100
|
+
})
|
|
101
|
+
.from(companyAssistantMessages)
|
|
102
|
+
.where(
|
|
103
|
+
and(
|
|
104
|
+
eq(companyAssistantMessages.companyId, companyId),
|
|
105
|
+
gte(companyAssistantMessages.createdAt, startInclusive),
|
|
106
|
+
lt(companyAssistantMessages.createdAt, endExclusive)
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
.groupBy(companyAssistantMessages.threadId);
|
|
110
|
+
return rows.map((r) => ({
|
|
111
|
+
threadId: r.threadId,
|
|
112
|
+
messageCount: Number(r.messageCount) || 0
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Threads with at least one message in the UTC calendar month (for callers without local bounds). */
|
|
117
|
+
export async function listAssistantChatThreadStatsInUtcMonth(
|
|
118
|
+
db: BopoDb,
|
|
119
|
+
companyId: string,
|
|
120
|
+
year: number,
|
|
121
|
+
month1Based: number
|
|
122
|
+
): Promise<Array<{ threadId: string; messageCount: number }>> {
|
|
123
|
+
const startUtc = new Date(Date.UTC(year, month1Based - 1, 1, 0, 0, 0, 0));
|
|
124
|
+
const endExclusiveUtc = new Date(Date.UTC(year, month1Based, 1, 0, 0, 0, 0));
|
|
125
|
+
return listAssistantChatThreadStatsInCreatedAtRange(db, companyId, startUtc, endExclusiveUtc);
|
|
126
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, asc, desc, eq, gt, inArray, notInArray, sql } from "drizzle-orm";
|
|
1
|
+
import { and, asc, desc, eq, gt, gte, inArray, lt, notInArray, sql } from "drizzle-orm";
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
3
|
import type { BopoDb } from "../client";
|
|
4
4
|
import {
|
|
@@ -1518,6 +1518,10 @@ export async function appendCost(
|
|
|
1518
1518
|
projectId?: string | null;
|
|
1519
1519
|
issueId?: string | null;
|
|
1520
1520
|
agentId?: string | null;
|
|
1521
|
+
/** Discriminator for reporting (e.g. `company_assistant`); null for heartbeat / legacy */
|
|
1522
|
+
costCategory?: string | null;
|
|
1523
|
+
assistantThreadId?: string | null;
|
|
1524
|
+
assistantMessageId?: string | null;
|
|
1521
1525
|
}
|
|
1522
1526
|
) {
|
|
1523
1527
|
const id = nanoid(14);
|
|
@@ -1525,6 +1529,9 @@ export async function appendCost(
|
|
|
1525
1529
|
id,
|
|
1526
1530
|
companyId: input.companyId,
|
|
1527
1531
|
runId: input.runId ?? null,
|
|
1532
|
+
costCategory: input.costCategory ?? null,
|
|
1533
|
+
assistantThreadId: input.assistantThreadId ?? null,
|
|
1534
|
+
assistantMessageId: input.assistantMessageId ?? null,
|
|
1528
1535
|
providerType: input.providerType,
|
|
1529
1536
|
runtimeModelId: input.runtimeModelId ?? null,
|
|
1530
1537
|
pricingProviderType: input.pricingProviderType ?? null,
|
|
@@ -1550,6 +1557,83 @@ export async function listCostEntries(db: BopoDb, companyId: string, limit = 200
|
|
|
1550
1557
|
.limit(limit);
|
|
1551
1558
|
}
|
|
1552
1559
|
|
|
1560
|
+
export type CostLedgerAggregate = {
|
|
1561
|
+
rowCount: number;
|
|
1562
|
+
tokenInput: number;
|
|
1563
|
+
tokenOutput: number;
|
|
1564
|
+
/** Sum of `usd_cost` as a decimal string (full precision from DB). */
|
|
1565
|
+
usdTotal: string;
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
/** Sum every ledger row for the company in `[startInclusive, endExclusive)` (typically UTC month). */
|
|
1569
|
+
export async function aggregateCompanyCostLedgerInRange(
|
|
1570
|
+
db: BopoDb,
|
|
1571
|
+
companyId: string,
|
|
1572
|
+
startInclusive: Date,
|
|
1573
|
+
endExclusive: Date
|
|
1574
|
+
): Promise<CostLedgerAggregate> {
|
|
1575
|
+
const [row] = await db
|
|
1576
|
+
.select({
|
|
1577
|
+
rowCount: sql<number>`count(*)::int`,
|
|
1578
|
+
tokenInput: sql<string>`coalesce(sum(${costLedger.tokenInput})::text, '0')`,
|
|
1579
|
+
tokenOutput: sql<string>`coalesce(sum(${costLedger.tokenOutput})::text, '0')`,
|
|
1580
|
+
usdTotal: sql<string>`coalesce(sum(${costLedger.usdCost})::text, '0')`
|
|
1581
|
+
})
|
|
1582
|
+
.from(costLedger)
|
|
1583
|
+
.where(
|
|
1584
|
+
and(
|
|
1585
|
+
eq(costLedger.companyId, companyId),
|
|
1586
|
+
gte(costLedger.createdAt, startInclusive),
|
|
1587
|
+
lt(costLedger.createdAt, endExclusive)
|
|
1588
|
+
)
|
|
1589
|
+
);
|
|
1590
|
+
|
|
1591
|
+
const parseBigIntish = (v: string) => {
|
|
1592
|
+
try {
|
|
1593
|
+
return Number(BigInt(v));
|
|
1594
|
+
} catch {
|
|
1595
|
+
return Number.parseInt(v, 10) || 0;
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
return {
|
|
1600
|
+
rowCount: Number(row?.rowCount ?? 0),
|
|
1601
|
+
tokenInput: parseBigIntish(String(row?.tokenInput ?? "0")),
|
|
1602
|
+
tokenOutput: parseBigIntish(String(row?.tokenOutput ?? "0")),
|
|
1603
|
+
usdTotal: String(row?.usdTotal ?? "0").trim() || "0"
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
export async function aggregateCompanyCostLedgerAllTime(
|
|
1608
|
+
db: BopoDb,
|
|
1609
|
+
companyId: string
|
|
1610
|
+
): Promise<CostLedgerAggregate> {
|
|
1611
|
+
const [row] = await db
|
|
1612
|
+
.select({
|
|
1613
|
+
rowCount: sql<number>`count(*)::int`,
|
|
1614
|
+
tokenInput: sql<string>`coalesce(sum(${costLedger.tokenInput})::text, '0')`,
|
|
1615
|
+
tokenOutput: sql<string>`coalesce(sum(${costLedger.tokenOutput})::text, '0')`,
|
|
1616
|
+
usdTotal: sql<string>`coalesce(sum(${costLedger.usdCost})::text, '0')`
|
|
1617
|
+
})
|
|
1618
|
+
.from(costLedger)
|
|
1619
|
+
.where(eq(costLedger.companyId, companyId));
|
|
1620
|
+
|
|
1621
|
+
const parseBigIntish = (v: string) => {
|
|
1622
|
+
try {
|
|
1623
|
+
return Number(BigInt(v));
|
|
1624
|
+
} catch {
|
|
1625
|
+
return Number.parseInt(v, 10) || 0;
|
|
1626
|
+
}
|
|
1627
|
+
};
|
|
1628
|
+
|
|
1629
|
+
return {
|
|
1630
|
+
rowCount: Number(row?.rowCount ?? 0),
|
|
1631
|
+
tokenInput: parseBigIntish(String(row?.tokenInput ?? "0")),
|
|
1632
|
+
tokenOutput: parseBigIntish(String(row?.tokenOutput ?? "0")),
|
|
1633
|
+
usdTotal: String(row?.usdTotal ?? "0").trim() || "0"
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1553
1637
|
export async function listHeartbeatRuns(db: BopoDb, companyId: string, limit = 100) {
|
|
1554
1638
|
return db
|
|
1555
1639
|
.select()
|
package/src/schema.ts
CHANGED
|
@@ -366,6 +366,29 @@ export const attentionInboxStates = pgTable(
|
|
|
366
366
|
(table) => [primaryKey({ columns: [table.companyId, table.actorId, table.itemKey] })]
|
|
367
367
|
);
|
|
368
368
|
|
|
369
|
+
export const companyAssistantThreads = pgTable("company_assistant_threads", {
|
|
370
|
+
id: text("id").primaryKey(),
|
|
371
|
+
companyId: text("company_id")
|
|
372
|
+
.notNull()
|
|
373
|
+
.references(() => companies.id, { onDelete: "cascade" }),
|
|
374
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
375
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull()
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
export const companyAssistantMessages = pgTable("company_assistant_messages", {
|
|
379
|
+
id: text("id").primaryKey(),
|
|
380
|
+
threadId: text("thread_id")
|
|
381
|
+
.notNull()
|
|
382
|
+
.references(() => companyAssistantThreads.id, { onDelete: "cascade" }),
|
|
383
|
+
companyId: text("company_id")
|
|
384
|
+
.notNull()
|
|
385
|
+
.references(() => companies.id, { onDelete: "cascade" }),
|
|
386
|
+
role: text("role").notNull(),
|
|
387
|
+
body: text("body").notNull(),
|
|
388
|
+
metadataJson: text("metadata_json"),
|
|
389
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
390
|
+
});
|
|
391
|
+
|
|
369
392
|
export const costLedger = pgTable("cost_ledger", {
|
|
370
393
|
id: text("id").primaryKey(),
|
|
371
394
|
companyId: text("company_id")
|
|
@@ -375,6 +398,10 @@ export const costLedger = pgTable("cost_ledger", {
|
|
|
375
398
|
projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
|
|
376
399
|
issueId: text("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
377
400
|
agentId: text("agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
401
|
+
/** e.g. `company_assistant` for owner-assistant chat; null for heartbeat and legacy rows */
|
|
402
|
+
costCategory: text("cost_category"),
|
|
403
|
+
assistantThreadId: text("assistant_thread_id").references(() => companyAssistantThreads.id, { onDelete: "set null" }),
|
|
404
|
+
assistantMessageId: text("assistant_message_id").references(() => companyAssistantMessages.id, { onDelete: "set null" }),
|
|
378
405
|
providerType: text("provider_type").notNull(),
|
|
379
406
|
runtimeModelId: text("runtime_model_id"),
|
|
380
407
|
pricingProviderType: text("pricing_provider_type"),
|
|
@@ -536,7 +563,9 @@ export const schema = {
|
|
|
536
563
|
templateVersions,
|
|
537
564
|
templateInstalls,
|
|
538
565
|
agentIssueLabels,
|
|
539
|
-
projectWorkspaces
|
|
566
|
+
projectWorkspaces,
|
|
567
|
+
companyAssistantThreads,
|
|
568
|
+
companyAssistantMessages
|
|
540
569
|
};
|
|
541
570
|
|
|
542
571
|
export const touchUpdatedAtSql = sql`CURRENT_TIMESTAMP`;
|