@intelligo-dev/executions 1.0.0-beta.1

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.
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Model registry and execution cost accounting.
3
+ *
4
+ * This lives in `executions` rather than beside the provider clients
5
+ * because it is not AI code: it is what an execution costs. The
6
+ * boundary records actor, workspace, capability, usage and cost
7
+ * (ADR-0003), and the price of a token is the last of those.
8
+ *
9
+ * Deliberately a leaf module — no database, no provider SDK, no
10
+ * imports at all — and reachable as `@intelligo-dev/executions/pricing`
11
+ * so a client bundle can read a display name or a price without
12
+ * pulling in Drizzle. `@intelligo-dev/ai` re-exports it, so the ids that
13
+ * pick a provider and the ids that carry a price stay one list; two
14
+ * lists is how a model runs on Gemini and bills at Claude rates.
15
+ */
16
+ /**
17
+ * Model configuration with display information and cost tracking
18
+ *
19
+ * 6 models, 3 providers: Google (2), OpenAI (3), Anthropic (1)
20
+ */
21
+ export const MODEL_CONFIGS = {
22
+ // Google Gemini
23
+ "google/gemini-2.5-flash": {
24
+ provider: "google",
25
+ model: "gemini-2.5-flash",
26
+ displayName: "Gemini 2.5 Flash",
27
+ costPerMInputTokens: 0.3,
28
+ costPerMOutputTokens: 2.5,
29
+ capabilities: {
30
+ thinking: true,
31
+ toolCall: true,
32
+ vision: true,
33
+ webSearch: true,
34
+ codeExec: true,
35
+ },
36
+ },
37
+ "google/gemini-2.5-pro": {
38
+ provider: "google",
39
+ model: "gemini-2.5-pro",
40
+ displayName: "Gemini 2.5 Pro",
41
+ costPerMInputTokens: 1.25,
42
+ costPerMOutputTokens: 10,
43
+ capabilities: {
44
+ thinking: true,
45
+ toolCall: true,
46
+ vision: true,
47
+ webSearch: true,
48
+ codeExec: true,
49
+ },
50
+ },
51
+ // OpenAI
52
+ "openai/gpt-5-mini": {
53
+ provider: "openai",
54
+ model: "gpt-5-mini",
55
+ displayName: "GPT-5 Mini",
56
+ costPerMInputTokens: 0.25,
57
+ costPerMOutputTokens: 2.0,
58
+ capabilities: {
59
+ thinking: true,
60
+ toolCall: true,
61
+ vision: true,
62
+ webSearch: false,
63
+ codeExec: false,
64
+ },
65
+ },
66
+ "openai/gpt-5.4-mini": {
67
+ provider: "openai",
68
+ model: "gpt-5.4-mini",
69
+ displayName: "GPT-5.4 Mini",
70
+ costPerMInputTokens: 0.75,
71
+ costPerMOutputTokens: 4.5,
72
+ capabilities: {
73
+ thinking: true,
74
+ toolCall: true,
75
+ vision: true,
76
+ webSearch: true,
77
+ codeExec: true,
78
+ },
79
+ },
80
+ "openai/o4-mini": {
81
+ provider: "openai",
82
+ model: "o4-mini",
83
+ displayName: "o4-mini",
84
+ costPerMInputTokens: 1.1,
85
+ costPerMOutputTokens: 4.4,
86
+ capabilities: {
87
+ thinking: true,
88
+ toolCall: true,
89
+ vision: true,
90
+ webSearch: true,
91
+ codeExec: true,
92
+ },
93
+ },
94
+ // Anthropic
95
+ "anthropic/claude-sonnet-4-6": {
96
+ provider: "anthropic",
97
+ model: "claude-sonnet-4-6-20260214",
98
+ displayName: "Claude Sonnet 4.6",
99
+ costPerMInputTokens: 3.0,
100
+ costPerMOutputTokens: 15.0,
101
+ capabilities: {
102
+ thinking: true,
103
+ toolCall: true,
104
+ vision: true,
105
+ webSearch: true,
106
+ codeExec: true,
107
+ },
108
+ },
109
+ };
110
+ /**
111
+ * Calculate the dollar cost for a given model and token counts.
112
+ */
113
+ export function calculateCost(modelId, inputTokens, outputTokens) {
114
+ const config = MODEL_CONFIGS[modelId];
115
+ if (!config) {
116
+ // Loud fallback: an unknown model ID means somewhere upstream is
117
+ // shipping a stale string that doesn't match MODEL_CONFIGS. We
118
+ // price it at the highest known rate so we don't accidentally give
119
+ // away expensive completions for free, but the warning here is the
120
+ // contract — silent fallback is how we ended up over-billing every
121
+ // free Gemini turn at Claude prices.
122
+ console.warn(`[calculateCost] Unknown model "${modelId}" — pricing at worst-case Claude rate ($3/$15 per M). Add it to MODEL_CONFIGS.`);
123
+ return (inputTokens / 1000000) * 3.0 + (outputTokens / 1000000) * 15.0;
124
+ }
125
+ return ((inputTokens / 1000000) * config.costPerMInputTokens +
126
+ (outputTokens / 1000000) * config.costPerMOutputTokens);
127
+ }
128
+ /**
129
+ * Default billing margin multiplier applied on top of raw model cost.
130
+ * Covers infra, tool overhead, FX volatility, and profit. Tunable per
131
+ * deploy via billing_settings.margin_multiplier; this constant is the
132
+ * fallback when the DB value is absent.
133
+ *
134
+ * INVARIANT — 65% gross margin floor (founder contract):
135
+ * gross_margin = 1 − 1/multiplier
136
+ * We must keep gross_margin ≥ 0.65 across every model in MODEL_CONFIGS,
137
+ * which means multiplier ≥ 1/0.35 ≈ 2.857. The current value of 4
138
+ * yields 75%, leaving comfortable buffer above the floor for FX moves
139
+ * and unexpected provider price hikes. The Vitest assertion in
140
+ * models.test.ts pins this — drop below 2.857 at your own risk.
141
+ */
142
+ export const DEFAULT_BILLING_MARGIN = 4;
143
+ /**
144
+ * Default USD→MNT exchange rate fallback. Real value lives in
145
+ * billing_settings.usd_to_mnt_rate and is read per request.
146
+ */
147
+ export const DEFAULT_USD_TO_MNT_RATE = 3450;
148
+ /**
149
+ * Per-model maximum output budget used for worst-case pre-request cost
150
+ * estimation in checkQuota. Numbers are conservative — cap on streamed
151
+ * output tokens we'd actually let a single chat turn produce.
152
+ */
153
+ export const MODEL_OUTPUT_BUDGET = {
154
+ "google/gemini-2.5-flash": 8000,
155
+ "google/gemini-2.5-pro": 8000,
156
+ "openai/gpt-5-mini": 8000,
157
+ "openai/gpt-5.4-mini": 8000,
158
+ "openai/o4-mini": 8000,
159
+ "anthropic/claude-sonnet-4-6": 8000,
160
+ };
161
+ /**
162
+ * Convert raw model cost to MNT user-facing charged amount.
163
+ *
164
+ * chargedMnt = ceil( rawCostUsd × margin × fxRate )
165
+ *
166
+ * The rounding is upward so micro-fractions never let a free request
167
+ * slip through; over-billing per request is at most 1 MNT.
168
+ */
169
+ export function calculateChargedMnt(modelId, inputTokens, outputTokens, fxRate = DEFAULT_USD_TO_MNT_RATE, margin = DEFAULT_BILLING_MARGIN) {
170
+ const rawCostUsd = calculateCost(modelId, inputTokens, outputTokens);
171
+ const chargedMnt = Math.ceil(rawCostUsd * margin * fxRate);
172
+ return { rawCostUsd, chargedMnt };
173
+ }
174
+ /**
175
+ * Worst-case MNT cost estimate for a single chat turn against a given
176
+ * model. Used by checkQuota to refuse requests whose ceiling cost would
177
+ * exceed remaining balance, before we burn provider tokens. Uses the
178
+ * full MODEL_OUTPUT_BUDGET as the output side and a generous 16K input
179
+ * budget to cover system prompt + conversation history.
180
+ */
181
+ export function estimateWorstCaseChargedMnt(modelId, fxRate = DEFAULT_USD_TO_MNT_RATE, margin = DEFAULT_BILLING_MARGIN) {
182
+ const outputBudget = MODEL_OUTPUT_BUDGET[modelId] ?? 8000;
183
+ const inputBudget = 16000;
184
+ return calculateChargedMnt(modelId, inputBudget, outputBudget, fxRate, margin)
185
+ .chargedMnt;
186
+ }
187
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gBAAgB;IAChB,yBAAyB,EAAE;QACzB,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,kBAAkB;QAC/B,mBAAmB,EAAE,GAAG;QACxB,oBAAoB,EAAE,GAAG;QACzB,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,IAAI;SACf;KACF;IACD,uBAAuB,EAAE;QACvB,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,gBAAgB;QAC7B,mBAAmB,EAAE,IAAI;QACzB,oBAAoB,EAAE,EAAE;QACxB,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,IAAI;SACf;KACF;IACD,SAAS;IACT,mBAAmB,EAAE;QACnB,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,YAAY;QACzB,mBAAmB,EAAE,IAAI;QACzB,oBAAoB,EAAE,GAAG;QACzB,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,KAAK;SAChB;KACF;IACD,qBAAqB,EAAE;QACrB,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,cAAc;QAC3B,mBAAmB,EAAE,IAAI;QACzB,oBAAoB,EAAE,GAAG;QACzB,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,IAAI;SACf;KACF;IACD,gBAAgB,EAAE;QAChB,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,SAAS;QACtB,mBAAmB,EAAE,GAAG;QACxB,oBAAoB,EAAE,GAAG;QACzB,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,IAAI;SACf;KACF;IACD,YAAY;IACZ,6BAA6B,EAAE;QAC7B,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,mBAAmB;QAChC,mBAAmB,EAAE,GAAG;QACxB,oBAAoB,EAAE,IAAI;QAC1B,YAAY,EAAE;YACZ,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,IAAI;SACf;KACF;CACO,CAAC;AAIX;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,WAAmB,EACnB,YAAoB;IAEpB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAkB,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,iEAAiE;QACjE,+DAA+D;QAC/D,mEAAmE;QACnE,mEAAmE;QACnE,mEAAmE;QACnE,qCAAqC;QACrC,OAAO,CAAC,IAAI,CACV,kCAAkC,OAAO,gFAAgF,CAC1H,CAAC;QACF,OAAO,CAAC,WAAW,GAAG,OAAS,CAAC,GAAG,GAAG,GAAG,CAAC,YAAY,GAAG,OAAS,CAAC,GAAG,IAAI,CAAC;IAC7E,CAAC;IACD,OAAO,CACL,CAAC,WAAW,GAAG,OAAS,CAAC,GAAG,MAAM,CAAC,mBAAmB;QACtD,CAAC,YAAY,GAAG,OAAS,CAAC,GAAG,MAAM,CAAC,oBAAoB,CACzD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAA4B;IAC1D,yBAAyB,EAAE,IAAK;IAChC,uBAAuB,EAAE,IAAK;IAC9B,mBAAmB,EAAE,IAAK;IAC1B,qBAAqB,EAAE,IAAK;IAC5B,gBAAgB,EAAE,IAAK;IACvB,6BAA6B,EAAE,IAAK;CACrC,CAAC;AAOF;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,WAAmB,EACnB,YAAoB,EACpB,SAAiB,uBAAuB,EACxC,SAAiB,sBAAsB;IAEvC,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;IAC3D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAAe,EACf,SAAiB,uBAAuB,EACxC,SAAiB,sBAAsB;IAEvC,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAkB,CAAC,IAAI,IAAK,CAAC;IACtE,MAAM,WAAW,GAAG,KAAM,CAAC;IAC3B,OAAO,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC;SAC3E,UAAU,CAAC;AAChB,CAAC"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Read models over the executions table — for the product's usage UI
3
+ * and, in Phase 5, the Intelligo admin console.
4
+ */
5
+ export type ListExecutionsOptions = {
6
+ workspaceId?: string;
7
+ userId?: string;
8
+ capability?: string;
9
+ status?: "running" | "settling" | "succeeded" | "failed" | "refused";
10
+ /** Keyset pagination: rows started strictly before this. */
11
+ before?: Date;
12
+ limit?: number;
13
+ };
14
+ export declare function listExecutions(options?: ListExecutionsOptions): Promise<{
15
+ id: string;
16
+ workspaceId: string;
17
+ userId: string | null;
18
+ capability: string;
19
+ requestId: string;
20
+ status: string;
21
+ model: string | null;
22
+ inputTokens: number | null;
23
+ outputTokens: number | null;
24
+ totalTokens: number | null;
25
+ chargedMnt: number | null;
26
+ reservedMnt: number | null;
27
+ refusalReason: string | null;
28
+ errorMessage: string | null;
29
+ startedAt: Date;
30
+ finishedAt: Date | null;
31
+ durationMs: number | null;
32
+ metadata: Record<string, unknown> | null;
33
+ }[]>;
34
+ export declare function getExecutionByRequestId(requestId: string): Promise<{
35
+ id: string;
36
+ workspaceId: string;
37
+ userId: string | null;
38
+ capability: string;
39
+ requestId: string;
40
+ status: string;
41
+ model: string | null;
42
+ inputTokens: number | null;
43
+ outputTokens: number | null;
44
+ totalTokens: number | null;
45
+ chargedMnt: number | null;
46
+ reservedMnt: number | null;
47
+ refusalReason: string | null;
48
+ errorMessage: string | null;
49
+ startedAt: Date;
50
+ finishedAt: Date | null;
51
+ durationMs: number | null;
52
+ metadata: Record<string, unknown> | null;
53
+ } | null>;
54
+ /**
55
+ * Aggregate counts, tokens, and charge for a workspace over a window.
56
+ * Refused executions are counted separately — they consumed no tokens
57
+ * but they are the signal that a plan's limits are biting.
58
+ */
59
+ export declare function summarizeExecutions(workspaceId: string, window: {
60
+ from: Date;
61
+ to: Date;
62
+ }): Promise<{
63
+ byStatus: {
64
+ [k: string]: {
65
+ count: number;
66
+ totalTokens: number;
67
+ chargedMnt: number;
68
+ };
69
+ };
70
+ totals: {
71
+ count: number;
72
+ totalTokens: number;
73
+ chargedMnt: number;
74
+ };
75
+ }>;
76
+ /**
77
+ * Per-day totals for a workspace over a window — the read model behind
78
+ * a usage chart.
79
+ *
80
+ * Aggregated in SQL rather than by bucketing rows in the application:
81
+ * a month of a busy workspace is thousands of rows to ship over the
82
+ * wire to produce thirty numbers, and any consumer that had to do that
83
+ * bucketing itself would be writing the query this package should own.
84
+ *
85
+ * Days with no activity are absent rather than zero — the caller knows
86
+ * 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. `date` is
88
+ * a `YYYY-MM-DD` string in UTC, so the series is stable regardless of
89
+ * where the reader is.
90
+ */
91
+ export declare function summarizeExecutionsByDay(workspaceId: string, window: {
92
+ from: Date;
93
+ to: Date;
94
+ }): Promise<Array<{
95
+ date: string;
96
+ count: number;
97
+ totalTokens: number;
98
+ chargedMnt: number;
99
+ }>>;
100
+ /**
101
+ * Executions stuck in a non-terminal state past a cutoff — a stream
102
+ * that died without reaching complete()/fail() (`running`), or a
103
+ * settlement that threw partway (`settling`). The cleanup cron reports
104
+ * these; they are the operational signal that usage went unrecorded.
105
+ */
106
+ export declare function findStaleExecutions(olderThan: Date, limit?: number): Promise<{
107
+ id: string;
108
+ workspaceId: string;
109
+ userId: string | null;
110
+ capability: string;
111
+ requestId: string;
112
+ status: string;
113
+ model: string | null;
114
+ inputTokens: number | null;
115
+ outputTokens: number | null;
116
+ totalTokens: number | null;
117
+ chargedMnt: number | null;
118
+ reservedMnt: number | null;
119
+ refusalReason: string | null;
120
+ errorMessage: string | null;
121
+ startedAt: Date;
122
+ finishedAt: Date | null;
123
+ durationMs: number | null;
124
+ metadata: Record<string, unknown> | null;
125
+ }[]>;
126
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,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,GAAE,qBAA0B;;;;;;;;;;;;;;;;;;;KAmBvE;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;;;;;;;;;;;;;GAyCjC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GAC/B,OAAO,CACR,KAAK,CAAC;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CACH,CA2BA;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,SAAM;;;;;;;;;;;;;;;;;;;KAYrE"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Read models over the executions table — for the product's usage UI
3
+ * and, in Phase 5, the Intelligo admin console.
4
+ */
5
+ import { db } from "@intelligo-dev/core/db";
6
+ import { and, desc, eq, gte, inArray, lt, lte, sql } from "drizzle-orm";
7
+ import { executions } from "./db/schema";
8
+ export async function listExecutions(options = {}) {
9
+ const filters = [
10
+ options.workspaceId
11
+ ? eq(executions.workspaceId, options.workspaceId)
12
+ : undefined,
13
+ options.userId ? eq(executions.userId, options.userId) : undefined,
14
+ options.capability
15
+ ? eq(executions.capability, options.capability)
16
+ : undefined,
17
+ options.status ? eq(executions.status, options.status) : undefined,
18
+ options.before ? lt(executions.startedAt, options.before) : undefined,
19
+ ].filter(Boolean);
20
+ return db
21
+ .select()
22
+ .from(executions)
23
+ .where(filters.length > 0 ? and(...filters) : undefined)
24
+ .orderBy(desc(executions.startedAt))
25
+ .limit(Math.min(options.limit ?? 50, 500));
26
+ }
27
+ export async function getExecutionByRequestId(requestId) {
28
+ const [row] = await db
29
+ .select()
30
+ .from(executions)
31
+ .where(eq(executions.requestId, requestId))
32
+ .limit(1);
33
+ return row ?? null;
34
+ }
35
+ /**
36
+ * Aggregate counts, tokens, and charge for a workspace over a window.
37
+ * Refused executions are counted separately — they consumed no tokens
38
+ * but they are the signal that a plan's limits are biting.
39
+ */
40
+ export async function summarizeExecutions(workspaceId, window) {
41
+ const rows = await db
42
+ .select({
43
+ status: executions.status,
44
+ count: sql `count(*)`,
45
+ totalTokens: sql `coalesce(sum(${executions.totalTokens}), 0)`,
46
+ chargedMnt: sql `coalesce(sum(${executions.chargedMnt}), 0)`,
47
+ })
48
+ .from(executions)
49
+ .where(and(eq(executions.workspaceId, workspaceId), gte(executions.startedAt, window.from), lte(executions.startedAt, window.to)))
50
+ .groupBy(executions.status);
51
+ const byStatus = Object.fromEntries(rows.map((r) => [
52
+ r.status,
53
+ {
54
+ count: Number(r.count),
55
+ totalTokens: Number(r.totalTokens),
56
+ chargedMnt: Number(r.chargedMnt),
57
+ },
58
+ ]));
59
+ return {
60
+ byStatus,
61
+ totals: rows.reduce((acc, r) => ({
62
+ count: acc.count + Number(r.count),
63
+ totalTokens: acc.totalTokens + Number(r.totalTokens),
64
+ chargedMnt: acc.chargedMnt + Number(r.chargedMnt),
65
+ }), { count: 0, totalTokens: 0, chargedMnt: 0 }),
66
+ };
67
+ }
68
+ /**
69
+ * Per-day totals for a workspace over a window — the read model behind
70
+ * a usage chart.
71
+ *
72
+ * Aggregated in SQL rather than by bucketing rows in the application:
73
+ * a month of a busy workspace is thousands of rows to ship over the
74
+ * wire to produce thirty numbers, and any consumer that had to do that
75
+ * bucketing itself would be writing the query this package should own.
76
+ *
77
+ * Days with no activity are absent rather than zero — the caller knows
78
+ * 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. `date` is
80
+ * a `YYYY-MM-DD` string in UTC, so the series is stable regardless of
81
+ * where the reader is.
82
+ */
83
+ export async function summarizeExecutionsByDay(workspaceId, window) {
84
+ const day = sql `to_char(${executions.startedAt} AT TIME ZONE 'UTC', 'YYYY-MM-DD')`;
85
+ const rows = await db
86
+ .select({
87
+ date: day,
88
+ count: sql `count(*)`,
89
+ totalTokens: sql `coalesce(sum(${executions.totalTokens}), 0)`,
90
+ chargedMnt: sql `coalesce(sum(${executions.chargedMnt}), 0)`,
91
+ })
92
+ .from(executions)
93
+ .where(and(eq(executions.workspaceId, workspaceId), gte(executions.startedAt, window.from), lte(executions.startedAt, window.to)))
94
+ .groupBy(day)
95
+ .orderBy(day);
96
+ return rows.map((row) => ({
97
+ date: row.date,
98
+ count: Number(row.count),
99
+ totalTokens: Number(row.totalTokens),
100
+ chargedMnt: Number(row.chargedMnt),
101
+ }));
102
+ }
103
+ /**
104
+ * Executions stuck in a non-terminal state past a cutoff — a stream
105
+ * that died without reaching complete()/fail() (`running`), or a
106
+ * settlement that threw partway (`settling`). The cleanup cron reports
107
+ * these; they are the operational signal that usage went unrecorded.
108
+ */
109
+ export async function findStaleExecutions(olderThan, limit = 100) {
110
+ return db
111
+ .select()
112
+ .from(executions)
113
+ .where(and(inArray(executions.status, ["running", "settling"]), lt(executions.startedAt, olderThan)))
114
+ .orderBy(desc(executions.startedAt))
115
+ .limit(limit);
116
+ }
117
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@intelligo-dev/executions",
3
+ "version": "1.0.0-beta.1",
4
+ "license": "Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/intelligo-mn/framework.git",
8
+ "directory": "packages/executions"
9
+ },
10
+ "homepage": "https://github.com/intelligo-mn/framework#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/intelligo-mn/framework/issues"
13
+ },
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "./pricing": {
21
+ "types": "./dist/pricing.d.ts",
22
+ "default": "./dist/pricing.js"
23
+ },
24
+ "./db": {
25
+ "types": "./dist/db/schema.d.ts",
26
+ "default": "./dist/db/schema.js"
27
+ }
28
+ },
29
+ "dependencies": {
30
+ "drizzle-orm": "^0.45.1",
31
+ "@intelligo-dev/audit": "1.0.0-beta.1",
32
+ "@intelligo-dev/core": "1.0.0-beta.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.0.0",
36
+ "@types/pg": "^8.16.0",
37
+ "pg": "^8.18.0",
38
+ "typescript": "^5.7.2"
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "type-check": "tsc --noEmit",
48
+ "lint": "eslint .",
49
+ "build": "tsc -p tsconfig.build.json"
50
+ }
51
+ }