@intelligo-dev/executions 1.0.0-beta.1 → 1.0.0-beta.13
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/NOTICE +6 -0
- package/README.md +121 -0
- package/dist/db/schema.d.ts +32 -21
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +23 -23
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +31 -5
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +179 -18
- package/dist/lifecycle.js.map +1 -1
- package/dist/ports.d.ts +33 -14
- package/dist/ports.d.ts.map +1 -1
- package/dist/ports.js +3 -8
- package/dist/ports.js.map +1 -1
- package/dist/pricing.d.ts +107 -137
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +129 -85
- package/dist/pricing.js.map +1 -1
- package/dist/queries.d.ts +39 -26
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +96 -39
- package/dist/queries.js.map +1 -1
- package/package.json +41 -13
- package/src/db/schema.ts +87 -0
- package/src/index.ts +73 -0
- package/src/lifecycle.ts +525 -0
- package/src/ports.ts +100 -0
- package/src/pricing.ts +322 -0
- package/src/queries.ts +235 -0
package/dist/queries.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read models over the executions table
|
|
3
|
-
* and
|
|
2
|
+
* Read models over the executions table, for the product's usage UI
|
|
3
|
+
* and the admin console.
|
|
4
4
|
*/
|
|
5
|
+
import { type Money } from "@intelligo-dev/core/money";
|
|
5
6
|
export type ListExecutionsOptions = {
|
|
6
|
-
|
|
7
|
+
/** Required: every query is scoped to one tenant. */
|
|
8
|
+
workspaceId: string;
|
|
7
9
|
userId?: string;
|
|
8
10
|
capability?: string;
|
|
9
11
|
status?: "running" | "settling" | "succeeded" | "failed" | "refused";
|
|
@@ -11,7 +13,7 @@ export type ListExecutionsOptions = {
|
|
|
11
13
|
before?: Date;
|
|
12
14
|
limit?: number;
|
|
13
15
|
};
|
|
14
|
-
export declare function listExecutions(options
|
|
16
|
+
export declare function listExecutions(options: ListExecutionsOptions): Promise<{
|
|
15
17
|
id: string;
|
|
16
18
|
workspaceId: string;
|
|
17
19
|
userId: string | null;
|
|
@@ -22,8 +24,9 @@ export declare function listExecutions(options?: ListExecutionsOptions): Promise
|
|
|
22
24
|
inputTokens: number | null;
|
|
23
25
|
outputTokens: number | null;
|
|
24
26
|
totalTokens: number | null;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
chargedMicros: number | null;
|
|
28
|
+
reservedMicros: number | null;
|
|
29
|
+
currency: string | null;
|
|
27
30
|
refusalReason: string | null;
|
|
28
31
|
errorMessage: string | null;
|
|
29
32
|
startedAt: Date;
|
|
@@ -42,8 +45,9 @@ export declare function getExecutionByRequestId(requestId: string): Promise<{
|
|
|
42
45
|
inputTokens: number | null;
|
|
43
46
|
outputTokens: number | null;
|
|
44
47
|
totalTokens: number | null;
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
chargedMicros: number | null;
|
|
49
|
+
reservedMicros: number | null;
|
|
50
|
+
currency: string | null;
|
|
47
51
|
refusalReason: string | null;
|
|
48
52
|
errorMessage: string | null;
|
|
49
53
|
startedAt: Date;
|
|
@@ -60,17 +64,15 @@ export declare function summarizeExecutions(workspaceId: string, window: {
|
|
|
60
64
|
from: Date;
|
|
61
65
|
to: Date;
|
|
62
66
|
}): Promise<{
|
|
63
|
-
byStatus: {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
};
|
|
69
|
-
};
|
|
67
|
+
byStatus: Record<string, {
|
|
68
|
+
count: number;
|
|
69
|
+
totalTokens: number;
|
|
70
|
+
charged: Money[];
|
|
71
|
+
}>;
|
|
70
72
|
totals: {
|
|
73
|
+
charged: Money[];
|
|
71
74
|
count: number;
|
|
72
75
|
totalTokens: number;
|
|
73
|
-
chargedMnt: number;
|
|
74
76
|
};
|
|
75
77
|
}>;
|
|
76
78
|
/**
|
|
@@ -84,24 +86,34 @@ export declare function summarizeExecutions(workspaceId: string, window: {
|
|
|
84
86
|
*
|
|
85
87
|
* Days with no activity are absent rather than zero — the caller knows
|
|
86
88
|
* the window it asked for, and a gap means "nothing ran", which a
|
|
87
|
-
* chart should draw as zero and a table should leave empty.
|
|
88
|
-
*
|
|
89
|
-
*
|
|
89
|
+
* chart should draw as zero and a table should leave empty.
|
|
90
|
+
*
|
|
91
|
+
* `date` is a `YYYY-MM-DD` string in `timeZone`, which defaults to UTC.
|
|
92
|
+
* Pass the reader's own zone: bucketing a +08:00 reader's 00:36 turn in
|
|
93
|
+
* UTC files it on the previous day.
|
|
90
94
|
*/
|
|
91
95
|
export declare function summarizeExecutionsByDay(workspaceId: string, window: {
|
|
92
96
|
from: Date;
|
|
93
97
|
to: Date;
|
|
98
|
+
}, options?: {
|
|
99
|
+
timeZone?: string;
|
|
94
100
|
}): Promise<Array<{
|
|
95
101
|
date: string;
|
|
96
102
|
count: number;
|
|
97
103
|
totalTokens: number;
|
|
98
|
-
|
|
104
|
+
charged: Money[];
|
|
99
105
|
}>>;
|
|
100
106
|
/**
|
|
101
|
-
* Executions stuck in a non-terminal state past a cutoff
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
107
|
+
* Executions stuck in a non-terminal state past a cutoff.
|
|
108
|
+
*
|
|
109
|
+
* `running`: the stream died without reaching complete()/fail() —
|
|
110
|
+
* tokens were consumed and nothing was charged. `settling`: the charge
|
|
111
|
+
* was claimed but never confirmed — EITHER `settleUsage` threw and the
|
|
112
|
+
* workspace was not charged, OR the charge committed and the process
|
|
113
|
+
* died before the final `settling → succeeded` flip, in which case the
|
|
114
|
+
* workspace WAS charged. A `usage_records` row (or a `settled`
|
|
115
|
+
* reservation) for the same `requestId` distinguishes the two. This
|
|
116
|
+
* only reports; `createExecutions().reconcile()` repairs a row.
|
|
105
117
|
*/
|
|
106
118
|
export declare function findStaleExecutions(olderThan: Date, limit?: number): Promise<{
|
|
107
119
|
id: string;
|
|
@@ -114,8 +126,9 @@ export declare function findStaleExecutions(olderThan: Date, limit?: number): Pr
|
|
|
114
126
|
inputTokens: number | null;
|
|
115
127
|
outputTokens: number | null;
|
|
116
128
|
totalTokens: number | null;
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
chargedMicros: number | null;
|
|
130
|
+
reservedMicros: number | null;
|
|
131
|
+
currency: string | null;
|
|
119
132
|
refusalReason: string | null;
|
|
120
133
|
errorMessage: string | null;
|
|
121
134
|
startedAt: Date;
|
package/dist/queries.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAS,KAAK,KAAK,EAAE,MAAM,2BAA2B,CAAC;AA2B9D,MAAM,MAAM,qBAAqB,GAAG;IAClC,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IACrE,4DAA4D;IAC5D,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;KAiBlE;AAED,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;UAO9D;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE;;eAwBrB,MAAM;qBAAe,MAAM;iBAAW,KAAK,EAAE;;;;;;;GA4BzD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,EAChC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CACR,KAAK,CAAC;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,EAAE,CAAC;CAClB,CAAC,CACH,CA+CA;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,SAAM;;;;;;;;;;;;;;;;;;;;KAYrE"}
|
package/dist/queries.js
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read models over the executions table
|
|
3
|
-
* and
|
|
2
|
+
* Read models over the executions table, for the product's usage UI
|
|
3
|
+
* and the admin console.
|
|
4
4
|
*/
|
|
5
5
|
import { db } from "@intelligo-dev/core/db";
|
|
6
|
+
import { money } from "@intelligo-dev/core/money";
|
|
6
7
|
import { and, desc, eq, gte, inArray, lt, lte, sql } from "drizzle-orm";
|
|
7
|
-
import { executions } from "./db/schema";
|
|
8
|
-
|
|
8
|
+
import { executions } from "./db/schema.js";
|
|
9
|
+
/**
|
|
10
|
+
* Charges summed per currency.
|
|
11
|
+
*
|
|
12
|
+
* A workspace bills in one currency, so this is normally one entry —
|
|
13
|
+
* but the rows are what the ledger holds, and adding two currencies
|
|
14
|
+
* into a single number is how a total starts lying. Rows with no
|
|
15
|
+
* currency are executions that were never charged.
|
|
16
|
+
*/
|
|
17
|
+
function chargedByCurrency(rows) {
|
|
18
|
+
const totals = new Map();
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
if (!row.currency)
|
|
21
|
+
continue;
|
|
22
|
+
// `sum()` of a bigint column arrives as a string.
|
|
23
|
+
const amount = Number(row.chargedMicros ?? 0);
|
|
24
|
+
if (amount === 0)
|
|
25
|
+
continue;
|
|
26
|
+
totals.set(row.currency, (totals.get(row.currency) ?? 0) + amount);
|
|
27
|
+
}
|
|
28
|
+
return [...totals].map(([code, amount]) => money(amount, code));
|
|
29
|
+
}
|
|
30
|
+
export async function listExecutions(options) {
|
|
9
31
|
const filters = [
|
|
10
|
-
options.workspaceId
|
|
11
|
-
? eq(executions.workspaceId, options.workspaceId)
|
|
12
|
-
: undefined,
|
|
32
|
+
eq(executions.workspaceId, options.workspaceId),
|
|
13
33
|
options.userId ? eq(executions.userId, options.userId) : undefined,
|
|
14
34
|
options.capability
|
|
15
35
|
? eq(executions.capability, options.capability)
|
|
@@ -41,28 +61,38 @@ export async function summarizeExecutions(workspaceId, window) {
|
|
|
41
61
|
const rows = await db
|
|
42
62
|
.select({
|
|
43
63
|
status: executions.status,
|
|
64
|
+
currency: executions.currency,
|
|
44
65
|
count: sql `count(*)`,
|
|
45
66
|
totalTokens: sql `coalesce(sum(${executions.totalTokens}), 0)`,
|
|
46
|
-
|
|
67
|
+
chargedMicros: sql `coalesce(sum(${executions.chargedMicros}), 0)`,
|
|
47
68
|
})
|
|
48
69
|
.from(executions)
|
|
49
70
|
.where(and(eq(executions.workspaceId, workspaceId), gte(executions.startedAt, window.from), lte(executions.startedAt, window.to)))
|
|
50
|
-
.groupBy(executions.status);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
.groupBy(executions.status, executions.currency);
|
|
72
|
+
// A status can arrive as several rows — one per currency — so the
|
|
73
|
+
// per-status view folds them back together.
|
|
74
|
+
const byStatus = {};
|
|
75
|
+
for (const r of rows) {
|
|
76
|
+
const seen = byStatus[r.status] ?? {
|
|
77
|
+
count: 0,
|
|
78
|
+
totalTokens: 0,
|
|
79
|
+
charged: [],
|
|
80
|
+
};
|
|
81
|
+
byStatus[r.status] = {
|
|
82
|
+
count: seen.count + Number(r.count),
|
|
83
|
+
totalTokens: seen.totalTokens + Number(r.totalTokens),
|
|
84
|
+
charged: chargedByCurrency(rows.filter((row) => row.status === r.status)),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
59
87
|
return {
|
|
60
88
|
byStatus,
|
|
61
|
-
totals:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
totals: {
|
|
90
|
+
...rows.reduce((acc, r) => ({
|
|
91
|
+
count: acc.count + Number(r.count),
|
|
92
|
+
totalTokens: acc.totalTokens + Number(r.totalTokens),
|
|
93
|
+
}), { count: 0, totalTokens: 0 }),
|
|
94
|
+
charged: chargedByCurrency(rows),
|
|
95
|
+
},
|
|
66
96
|
};
|
|
67
97
|
}
|
|
68
98
|
/**
|
|
@@ -76,35 +106,62 @@ export async function summarizeExecutions(workspaceId, window) {
|
|
|
76
106
|
*
|
|
77
107
|
* Days with no activity are absent rather than zero — the caller knows
|
|
78
108
|
* the window it asked for, and a gap means "nothing ran", which a
|
|
79
|
-
* chart should draw as zero and a table should leave empty.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
109
|
+
* chart should draw as zero and a table should leave empty.
|
|
110
|
+
*
|
|
111
|
+
* `date` is a `YYYY-MM-DD` string in `timeZone`, which defaults to UTC.
|
|
112
|
+
* Pass the reader's own zone: bucketing a +08:00 reader's 00:36 turn in
|
|
113
|
+
* UTC files it on the previous day.
|
|
82
114
|
*/
|
|
83
|
-
export async function summarizeExecutionsByDay(workspaceId, window) {
|
|
84
|
-
|
|
115
|
+
export async function summarizeExecutionsByDay(workspaceId, window, options = {}) {
|
|
116
|
+
// Bound, not interpolated: the zone reaches here from a cookie the
|
|
117
|
+
// browser wrote, so it is caller input like any other.
|
|
118
|
+
//
|
|
119
|
+
// Two conversions, both needed. `started_at` is a naive timestamp
|
|
120
|
+
// holding a UTC instant, so `AT TIME ZONE 'UTC'` turns it into an
|
|
121
|
+
// instant, and the second `AT TIME ZONE` reads that instant as wall
|
|
122
|
+
// time where the reader is. One conversion would instead *declare*
|
|
123
|
+
// the stored time to be the reader's, which is a different moment.
|
|
124
|
+
const timeZone = options.timeZone ?? "UTC";
|
|
125
|
+
const day = sql `to_char(${executions.startedAt} AT TIME ZONE 'UTC' AT TIME ZONE ${timeZone}, 'YYYY-MM-DD')`;
|
|
85
126
|
const rows = await db
|
|
86
127
|
.select({
|
|
87
128
|
date: day,
|
|
129
|
+
currency: executions.currency,
|
|
88
130
|
count: sql `count(*)`,
|
|
89
131
|
totalTokens: sql `coalesce(sum(${executions.totalTokens}), 0)`,
|
|
90
|
-
|
|
132
|
+
chargedMicros: sql `coalesce(sum(${executions.chargedMicros}), 0)`,
|
|
91
133
|
})
|
|
92
134
|
.from(executions)
|
|
93
135
|
.where(and(eq(executions.workspaceId, workspaceId), gte(executions.startedAt, window.from), lte(executions.startedAt, window.to)))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
// By ordinal, not by the expression: drizzle inlines the fragment
|
|
137
|
+
// again for each clause, and with the zone bound as a parameter
|
|
138
|
+
// that makes three *different* expressions — Postgres then refuses
|
|
139
|
+
// the grouping. A literal zone matched textually and hid this.
|
|
140
|
+
.groupBy(sql `1`, executions.currency)
|
|
141
|
+
.orderBy(sql `1`);
|
|
142
|
+
// One row per day and currency; the series a chart draws is per day.
|
|
143
|
+
const byDay = new Map();
|
|
144
|
+
for (const row of rows) {
|
|
145
|
+
byDay.set(row.date, [...(byDay.get(row.date) ?? []), row]);
|
|
146
|
+
}
|
|
147
|
+
return [...byDay].map(([date, dayRows]) => ({
|
|
148
|
+
date,
|
|
149
|
+
count: dayRows.reduce((sum, row) => sum + Number(row.count), 0),
|
|
150
|
+
totalTokens: dayRows.reduce((sum, row) => sum + Number(row.totalTokens), 0),
|
|
151
|
+
charged: chargedByCurrency(dayRows),
|
|
101
152
|
}));
|
|
102
153
|
}
|
|
103
154
|
/**
|
|
104
|
-
* Executions stuck in a non-terminal state past a cutoff
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
155
|
+
* Executions stuck in a non-terminal state past a cutoff.
|
|
156
|
+
*
|
|
157
|
+
* `running`: the stream died without reaching complete()/fail() —
|
|
158
|
+
* tokens were consumed and nothing was charged. `settling`: the charge
|
|
159
|
+
* was claimed but never confirmed — EITHER `settleUsage` threw and the
|
|
160
|
+
* workspace was not charged, OR the charge committed and the process
|
|
161
|
+
* died before the final `settling → succeeded` flip, in which case the
|
|
162
|
+
* workspace WAS charged. A `usage_records` row (or a `settled`
|
|
163
|
+
* reservation) for the same `requestId` distinguishes the two. This
|
|
164
|
+
* only reports; `createExecutions().reconcile()` repairs a row.
|
|
108
165
|
*/
|
|
109
166
|
export async function findStaleExecutions(olderThan, limit = 100) {
|
|
110
167
|
return db
|
package/dist/queries.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intelligo-dev/executions",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.13",
|
|
4
|
+
"description": "The execution lifecycle (admission, running, settlement) and the model cost registry.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"intelligo",
|
|
7
|
+
"ai-saas",
|
|
8
|
+
"saas",
|
|
9
|
+
"typescript",
|
|
10
|
+
"llm",
|
|
11
|
+
"usage",
|
|
12
|
+
"cost",
|
|
13
|
+
"metering",
|
|
14
|
+
"execution-boundary"
|
|
15
|
+
],
|
|
4
16
|
"license": "Apache-2.0",
|
|
17
|
+
"author": "Turtuvshin Byambaa <toroo.byamba@gmail.com>",
|
|
5
18
|
"repository": {
|
|
6
19
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/intelligo-
|
|
20
|
+
"url": "git+https://github.com/intelligo-dev/intelligo.git",
|
|
8
21
|
"directory": "packages/executions"
|
|
9
22
|
},
|
|
10
|
-
"homepage": "https://
|
|
23
|
+
"homepage": "https://intelligo.dev/docs/packages/executions",
|
|
11
24
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/intelligo-
|
|
25
|
+
"url": "https://github.com/intelligo-dev/intelligo/issues"
|
|
13
26
|
},
|
|
14
27
|
"type": "module",
|
|
15
28
|
"exports": {
|
|
@@ -27,25 +40,40 @@
|
|
|
27
40
|
}
|
|
28
41
|
},
|
|
29
42
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"@intelligo-dev/
|
|
32
|
-
|
|
43
|
+
"@intelligo-dev/audit": "1.0.0-beta.13",
|
|
44
|
+
"@intelligo-dev/core": "1.0.0-beta.13"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"drizzle-orm": "^0.45.1"
|
|
33
48
|
},
|
|
34
49
|
"devDependencies": {
|
|
35
|
-
"@types/node": "^
|
|
36
|
-
"@types/pg": "^8.
|
|
37
|
-
"
|
|
38
|
-
"
|
|
50
|
+
"@types/node": "^26.6.1",
|
|
51
|
+
"@types/pg": "^8.23.1",
|
|
52
|
+
"drizzle-orm": "^0.45.2",
|
|
53
|
+
"pg": "8.18.0",
|
|
54
|
+
"typescript": "^5.9.3"
|
|
39
55
|
},
|
|
40
56
|
"files": [
|
|
41
|
-
"dist"
|
|
57
|
+
"dist",
|
|
58
|
+
"src",
|
|
59
|
+
"!src/**/*.test.ts",
|
|
60
|
+
"!src/**/*.test.tsx",
|
|
61
|
+
"!src/**/__tests__/**",
|
|
62
|
+
"LICENSE",
|
|
63
|
+
"NOTICE",
|
|
64
|
+
"README.md",
|
|
65
|
+
"!dist/**/*.tsbuildinfo"
|
|
42
66
|
],
|
|
43
67
|
"publishConfig": {
|
|
44
68
|
"access": "public"
|
|
45
69
|
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=22.14"
|
|
72
|
+
},
|
|
73
|
+
"sideEffects": false,
|
|
46
74
|
"scripts": {
|
|
47
75
|
"type-check": "tsc --noEmit",
|
|
48
76
|
"lint": "eslint .",
|
|
49
|
-
"build": "tsc -p tsconfig.build.json"
|
|
77
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/fix-esm-extensions.mjs dist"
|
|
50
78
|
}
|
|
51
79
|
}
|
package/src/db/schema.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One row per AI execution: who ran what capability, whether it was
|
|
3
|
+
* admitted, how it ended, and what it cost. It deliberately records
|
|
4
|
+
* nothing about agents, tools, workflows, or messages, which stay
|
|
5
|
+
* native to the chosen AI framework.
|
|
6
|
+
*
|
|
7
|
+
* Related tables:
|
|
8
|
+
* - `usage_records` is the per-request usage/cost detail, linked by
|
|
9
|
+
* `execution_id`.
|
|
10
|
+
* - `monthly_usage` is the O(1) rollup.
|
|
11
|
+
* - `credit_reservations` holds the admission hold;
|
|
12
|
+
* `executions.request_id` is the correlation key between the two.
|
|
13
|
+
*
|
|
14
|
+
* Scanned by drizzle-kit alongside the core schema directory — see
|
|
15
|
+
* packages/core/drizzle.config.ts.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
pgTable,
|
|
20
|
+
text,
|
|
21
|
+
timestamp,
|
|
22
|
+
integer,
|
|
23
|
+
bigint,
|
|
24
|
+
jsonb,
|
|
25
|
+
index,
|
|
26
|
+
} from "drizzle-orm/pg-core";
|
|
27
|
+
import { organization, users } from "@intelligo-dev/core/db/schema";
|
|
28
|
+
|
|
29
|
+
export const executions = pgTable(
|
|
30
|
+
"executions",
|
|
31
|
+
{
|
|
32
|
+
id: text("id").primaryKey(),
|
|
33
|
+
workspaceId: text("workspace_id")
|
|
34
|
+
.notNull()
|
|
35
|
+
.references(() => organization.id, { onDelete: "cascade" }),
|
|
36
|
+
userId: text("user_id").references(() => users.id, {
|
|
37
|
+
onDelete: "set null",
|
|
38
|
+
}),
|
|
39
|
+
/**
|
|
40
|
+
* What the caller asked for, in the product's own vocabulary:
|
|
41
|
+
* "support.reply", "chat.message". Opaque to the framework — used for
|
|
42
|
+
* entitlement decisions, grouping, and admin filtering.
|
|
43
|
+
*/
|
|
44
|
+
capability: text("capability").notNull(),
|
|
45
|
+
/**
|
|
46
|
+
* Correlation key shared with credit_reservations and
|
|
47
|
+
* usage_records. Unique per execution attempt.
|
|
48
|
+
*/
|
|
49
|
+
requestId: text("request_id").notNull().unique(),
|
|
50
|
+
/**
|
|
51
|
+
* running | settling | succeeded | failed | refused.
|
|
52
|
+
* `settling` is the window between claiming the row and confirming
|
|
53
|
+
* the charge; a row that stays there needs an operator.
|
|
54
|
+
*/
|
|
55
|
+
status: text("status").notNull().default("running"),
|
|
56
|
+
/** Model actually used; null until the run reports one. */
|
|
57
|
+
model: text("model"),
|
|
58
|
+
inputTokens: integer("input_tokens"),
|
|
59
|
+
outputTokens: integer("output_tokens"),
|
|
60
|
+
totalTokens: integer("total_tokens"),
|
|
61
|
+
/** Authoritative charge in micros of `currency`, mirrored from usage_records. */
|
|
62
|
+
chargedMicros: bigint("charged_micros", { mode: "number" }),
|
|
63
|
+
/** Worst-case estimate held at admission, in micros of `currency`. */
|
|
64
|
+
reservedMicros: bigint("reserved_micros", { mode: "number" }),
|
|
65
|
+
/** What this row's amounts are denominated in; null until one is set. */
|
|
66
|
+
currency: text("currency"),
|
|
67
|
+
/** Populated on status=refused: why entitlement said no. */
|
|
68
|
+
refusalReason: text("refusal_reason"),
|
|
69
|
+
/** Populated on status=failed. */
|
|
70
|
+
errorMessage: text("error_message"),
|
|
71
|
+
startedAt: timestamp("started_at").notNull().defaultNow(),
|
|
72
|
+
finishedAt: timestamp("finished_at"),
|
|
73
|
+
durationMs: integer("duration_ms"),
|
|
74
|
+
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
75
|
+
},
|
|
76
|
+
(table) => [
|
|
77
|
+
index("executions_workspace_started_idx").on(
|
|
78
|
+
table.workspaceId,
|
|
79
|
+
table.startedAt
|
|
80
|
+
),
|
|
81
|
+
index("executions_status_idx").on(table.status, table.startedAt),
|
|
82
|
+
index("executions_capability_idx").on(table.capability, table.startedAt),
|
|
83
|
+
]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
export type Execution = typeof executions.$inferSelect;
|
|
87
|
+
export type InsertExecution = typeof executions.$inferInsert;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @intelligo-dev/executions — the SaaS execution boundary.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a native AI run (Mastra, AI SDK, anything) with entitlement,
|
|
5
|
+
* credit hold, usage/cost recording, and audit — and records nothing
|
|
6
|
+
* about agents, tools, or messages.
|
|
7
|
+
*
|
|
8
|
+
* The composition root builds one instance with the ports bound:
|
|
9
|
+
*
|
|
10
|
+
* export const executions = createExecutions({
|
|
11
|
+
* checkEntitlement: billingEntitlementPort,
|
|
12
|
+
* settleUsage: billingSettlementPort,
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export { createExecutions } from "./lifecycle";
|
|
17
|
+
export type {
|
|
18
|
+
Executions,
|
|
19
|
+
ExecutionStatus,
|
|
20
|
+
ExecutionRun,
|
|
21
|
+
ReconcileResult,
|
|
22
|
+
BeginExecutionInput,
|
|
23
|
+
CompleteExecutionInput,
|
|
24
|
+
} from "./lifecycle";
|
|
25
|
+
|
|
26
|
+
export type {
|
|
27
|
+
ExecutionPorts,
|
|
28
|
+
EntitlementDecision,
|
|
29
|
+
EntitlementRequest,
|
|
30
|
+
UsageSettlement,
|
|
31
|
+
SettlementResult,
|
|
32
|
+
SettlementQuery,
|
|
33
|
+
} from "./ports";
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
listExecutions,
|
|
37
|
+
getExecutionByRequestId,
|
|
38
|
+
summarizeExecutions,
|
|
39
|
+
findStaleExecutions,
|
|
40
|
+
summarizeExecutionsByDay,
|
|
41
|
+
} from "./queries";
|
|
42
|
+
export type { ListExecutionsOptions } from "./queries";
|
|
43
|
+
|
|
44
|
+
export { executions } from "./db/schema";
|
|
45
|
+
export type { Execution, InsertExecution } from "./db/schema";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Model registry and cost accounting. Also reachable as
|
|
49
|
+
* `@intelligo-dev/executions/pricing`, which is the import to use from
|
|
50
|
+
* anything that reaches a browser — this barrel pulls in the database.
|
|
51
|
+
*/
|
|
52
|
+
export {
|
|
53
|
+
DEFAULT_MODELS,
|
|
54
|
+
UnknownModelError,
|
|
55
|
+
registerModel,
|
|
56
|
+
registerModels,
|
|
57
|
+
getModelPricing,
|
|
58
|
+
isModelRegistered,
|
|
59
|
+
listModels,
|
|
60
|
+
registeredModelIds,
|
|
61
|
+
clearModels,
|
|
62
|
+
DEFAULT_MARGIN_BP,
|
|
63
|
+
PROVIDER_CURRENCY,
|
|
64
|
+
providerCost,
|
|
65
|
+
chargeFor,
|
|
66
|
+
estimateWorstCaseCharge,
|
|
67
|
+
} from "./pricing";
|
|
68
|
+
export type {
|
|
69
|
+
ModelId,
|
|
70
|
+
ModelPricing,
|
|
71
|
+
ModelCapabilities,
|
|
72
|
+
BillingRate,
|
|
73
|
+
} from "./pricing";
|