octaflow 0.14.0
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/LICENSE +21 -0
- package/README.md +288 -0
- package/dist/ai/index.d.ts +397 -0
- package/dist/ai/index.js +466 -0
- package/dist/core-ERYGlYsS.js +574 -0
- package/dist/dispatcher-BydjyrgU.d.ts +26 -0
- package/dist/dispatcher-pgboss/index.d.ts +170 -0
- package/dist/dispatcher-pgboss/index.js +301 -0
- package/dist/hooks-CDRFQZbT.d.ts +56 -0
- package/dist/index-D_fOYfmf.d.ts +580 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.js +1426 -0
- package/dist/logger-BCus3AOa.d.ts +10 -0
- package/dist/observability-BsrvbhDr.d.ts +249 -0
- package/dist/store-pg/index.d.ts +195 -0
- package/dist/store-pg/index.js +671 -0
- package/package.json +116 -0
package/dist/ai/index.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { S as ok, b as noopLogger, f as defineStep, l as buildWorkflow, x as err } from "../core-ERYGlYsS.js";
|
|
2
|
+
import { wrapEmbeddingModel, wrapLanguageModel } from "ai";
|
|
3
|
+
//#region src/ai/instrumented-model.ts
|
|
4
|
+
function createUsageAccumulator() {
|
|
5
|
+
const total = {
|
|
6
|
+
inputTokens: 0,
|
|
7
|
+
outputTokens: 0,
|
|
8
|
+
cacheReadTokens: 0,
|
|
9
|
+
cacheWriteTokens: 0,
|
|
10
|
+
modelId: ""
|
|
11
|
+
};
|
|
12
|
+
let hasAny = false;
|
|
13
|
+
return {
|
|
14
|
+
record(usage) {
|
|
15
|
+
total.inputTokens += usage.inputTokens;
|
|
16
|
+
total.outputTokens += usage.outputTokens;
|
|
17
|
+
total.cacheReadTokens += usage.cacheReadTokens;
|
|
18
|
+
total.cacheWriteTokens += usage.cacheWriteTokens;
|
|
19
|
+
if (usage.modelId) total.modelId = usage.modelId;
|
|
20
|
+
hasAny = true;
|
|
21
|
+
},
|
|
22
|
+
get() {
|
|
23
|
+
return { ...total };
|
|
24
|
+
},
|
|
25
|
+
hasUsage() {
|
|
26
|
+
return hasAny;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wrap a language model with usage-tracking middleware. The accumulator collects
|
|
32
|
+
* token usage from every doGenerate/doStream call routed through the wrapped model.
|
|
33
|
+
*/
|
|
34
|
+
function createInstrumentedModel(model, accumulator) {
|
|
35
|
+
return wrapLanguageModel({
|
|
36
|
+
model,
|
|
37
|
+
middleware: {
|
|
38
|
+
specificationVersion: "v4",
|
|
39
|
+
wrapGenerate: async ({ doGenerate }) => {
|
|
40
|
+
const result = await doGenerate();
|
|
41
|
+
const usage = result.usage;
|
|
42
|
+
if (usage) accumulator.record({
|
|
43
|
+
inputTokens: usage.inputTokens?.total ?? 0,
|
|
44
|
+
outputTokens: usage.outputTokens?.total ?? 0,
|
|
45
|
+
cacheReadTokens: usage.inputTokens?.cacheRead ?? 0,
|
|
46
|
+
cacheWriteTokens: usage.inputTokens?.cacheWrite ?? 0,
|
|
47
|
+
modelId: model.modelId
|
|
48
|
+
});
|
|
49
|
+
return result;
|
|
50
|
+
},
|
|
51
|
+
wrapStream: async ({ doStream }) => {
|
|
52
|
+
const result = await doStream();
|
|
53
|
+
const wrappedStream = result.stream.pipeThrough(new TransformStream({ transform(chunk, controller) {
|
|
54
|
+
controller.enqueue(chunk);
|
|
55
|
+
if (chunk.type === "finish" && chunk.usage) {
|
|
56
|
+
const u = chunk.usage;
|
|
57
|
+
accumulator.record({
|
|
58
|
+
inputTokens: u.inputTokens?.total ?? 0,
|
|
59
|
+
outputTokens: u.outputTokens?.total ?? 0,
|
|
60
|
+
cacheReadTokens: u.inputTokens?.cacheRead ?? 0,
|
|
61
|
+
cacheWriteTokens: u.inputTokens?.cacheWrite ?? 0,
|
|
62
|
+
modelId: model.modelId
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
} }));
|
|
66
|
+
return {
|
|
67
|
+
...result,
|
|
68
|
+
stream: wrappedStream
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/ai/instrumented-embedding-model.ts
|
|
76
|
+
function createEmbeddingUsageAccumulator() {
|
|
77
|
+
let inputTokens = 0;
|
|
78
|
+
let callCount = 0;
|
|
79
|
+
let modelId = "";
|
|
80
|
+
return {
|
|
81
|
+
record(tokens, recordedModelId) {
|
|
82
|
+
inputTokens += tokens;
|
|
83
|
+
callCount += 1;
|
|
84
|
+
if (recordedModelId) modelId = recordedModelId;
|
|
85
|
+
},
|
|
86
|
+
get() {
|
|
87
|
+
return {
|
|
88
|
+
inputTokens,
|
|
89
|
+
callCount,
|
|
90
|
+
modelId
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
hasUsage() {
|
|
94
|
+
return callCount > 0;
|
|
95
|
+
},
|
|
96
|
+
reset() {
|
|
97
|
+
inputTokens = 0;
|
|
98
|
+
callCount = 0;
|
|
99
|
+
modelId = "";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Wrap an embedding model with usage-tracking middleware. The accumulator collects
|
|
105
|
+
* token usage from every doEmbed call routed through the wrapped model — both
|
|
106
|
+
* `embed()` (single value) and `embedMany()` (batched values) invoke doEmbed one or
|
|
107
|
+
* more times, and each invocation is recorded.
|
|
108
|
+
*/
|
|
109
|
+
function createInstrumentedEmbeddingModel(model, accumulator) {
|
|
110
|
+
return wrapEmbeddingModel({
|
|
111
|
+
model,
|
|
112
|
+
middleware: {
|
|
113
|
+
specificationVersion: "v4",
|
|
114
|
+
wrapEmbed: async ({ doEmbed }) => {
|
|
115
|
+
const result = await doEmbed();
|
|
116
|
+
accumulator.record(result.usage?.tokens ?? 0, model.modelId);
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/ai/cost.ts
|
|
124
|
+
/** Default pricing (USD per million tokens), as of 2026-04. */
|
|
125
|
+
const DEFAULT_MODEL_PRICING = {
|
|
126
|
+
"text-embedding-3-small": {
|
|
127
|
+
inputPerMillion: .02,
|
|
128
|
+
outputPerMillion: 0,
|
|
129
|
+
cacheReadPerMillion: 0,
|
|
130
|
+
cacheWritePerMillion: 0
|
|
131
|
+
},
|
|
132
|
+
"text-embedding-3-large": {
|
|
133
|
+
inputPerMillion: .13,
|
|
134
|
+
outputPerMillion: 0,
|
|
135
|
+
cacheReadPerMillion: 0,
|
|
136
|
+
cacheWritePerMillion: 0
|
|
137
|
+
},
|
|
138
|
+
"claude-opus-4-7": {
|
|
139
|
+
inputPerMillion: 5,
|
|
140
|
+
outputPerMillion: 25,
|
|
141
|
+
cacheReadPerMillion: .5,
|
|
142
|
+
cacheWritePerMillion: 6.25
|
|
143
|
+
},
|
|
144
|
+
"claude-opus-4-6": {
|
|
145
|
+
inputPerMillion: 15,
|
|
146
|
+
outputPerMillion: 75,
|
|
147
|
+
cacheReadPerMillion: 1.5,
|
|
148
|
+
cacheWritePerMillion: 18.75
|
|
149
|
+
},
|
|
150
|
+
"claude-sonnet-4-6": {
|
|
151
|
+
inputPerMillion: 3,
|
|
152
|
+
outputPerMillion: 15,
|
|
153
|
+
cacheReadPerMillion: .3,
|
|
154
|
+
cacheWritePerMillion: 3.75
|
|
155
|
+
},
|
|
156
|
+
"claude-sonnet-4-5-20250929": {
|
|
157
|
+
inputPerMillion: 3,
|
|
158
|
+
outputPerMillion: 15,
|
|
159
|
+
cacheReadPerMillion: .3,
|
|
160
|
+
cacheWritePerMillion: 3.75
|
|
161
|
+
},
|
|
162
|
+
"claude-haiku-4-5-20251001": {
|
|
163
|
+
inputPerMillion: .8,
|
|
164
|
+
outputPerMillion: 4,
|
|
165
|
+
cacheReadPerMillion: .08,
|
|
166
|
+
cacheWritePerMillion: 1
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function priciest(pricing) {
|
|
170
|
+
let best;
|
|
171
|
+
for (const p of Object.values(pricing)) if (!best || p.outputPerMillion > best.outputPerMillion) best = p;
|
|
172
|
+
return best ?? {
|
|
173
|
+
inputPerMillion: 0,
|
|
174
|
+
outputPerMillion: 0,
|
|
175
|
+
cacheReadPerMillion: 0,
|
|
176
|
+
cacheWritePerMillion: 0
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Build a cost estimator returning microdollars. */
|
|
180
|
+
function createCostEstimator(options = {}) {
|
|
181
|
+
const pricing = options.pricing ?? DEFAULT_MODEL_PRICING;
|
|
182
|
+
const fallback = options.fallback ?? priciest(pricing);
|
|
183
|
+
return (usage, modelId) => {
|
|
184
|
+
const p = pricing[modelId] ?? fallback;
|
|
185
|
+
const input = usage.inputTokens / 1e6 * p.inputPerMillion;
|
|
186
|
+
const output = usage.outputTokens / 1e6 * p.outputPerMillion;
|
|
187
|
+
const cacheRead = usage.cacheReadTokens / 1e6 * p.cacheReadPerMillion;
|
|
188
|
+
const cacheWrite = usage.cacheWriteTokens / 1e6 * p.cacheWritePerMillion;
|
|
189
|
+
return Math.round((input + output + cacheRead + cacheWrite) * 1e6);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** Convenience estimator using the default pricing table. */
|
|
193
|
+
const estimateCostMicros = createCostEstimator();
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/ai/define-ai-step.ts
|
|
196
|
+
/**
|
|
197
|
+
* Define a type-safe AI workflow step. Identical to flow-core's `defineStep`
|
|
198
|
+
* except the handler context is fixed to `AiContext<THost>`, so handlers get a
|
|
199
|
+
* ready-to-use `ctx.context.model` (instrumented) and `ctx.context.host`.
|
|
200
|
+
*/
|
|
201
|
+
function defineAiStep(config) {
|
|
202
|
+
return defineStep(config);
|
|
203
|
+
}
|
|
204
|
+
/** Build a typed AI workflow from `defineAiStep` definitions. */
|
|
205
|
+
function buildAiWorkflow(config) {
|
|
206
|
+
return buildWorkflow(config);
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/ai/hooks.ts
|
|
210
|
+
/**
|
|
211
|
+
* Wire the AI behaviors (model instrumentation, token/cost capture, quota,
|
|
212
|
+
* usage rollups) into a set of flow-core lifecycle hooks. Pass the result as the
|
|
213
|
+
* engine's `hooks`. flow-core stays AI-free; everything AI lives here.
|
|
214
|
+
*/
|
|
215
|
+
function createAiWorkflowHooks(deps) {
|
|
216
|
+
const estimate = deps.costEstimator ?? createCostEstimator();
|
|
217
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
218
|
+
return {
|
|
219
|
+
async onBeforeStart() {
|
|
220
|
+
if (deps.quotaPolicy) {
|
|
221
|
+
const q = await deps.quotaPolicy.checkQuota();
|
|
222
|
+
if (!q.ok) return q;
|
|
223
|
+
}
|
|
224
|
+
let metadata;
|
|
225
|
+
if (deps.modelResolver.resolveKeySource) metadata = { keySource: await deps.modelResolver.resolveKeySource() };
|
|
226
|
+
return {
|
|
227
|
+
ok: true,
|
|
228
|
+
value: { metadata }
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
async buildStepContext(args) {
|
|
232
|
+
const base = await deps.modelResolver.resolveModel(args);
|
|
233
|
+
const usage = createUsageAccumulator();
|
|
234
|
+
return {
|
|
235
|
+
model: createInstrumentedModel(base, usage),
|
|
236
|
+
host: deps.modelResolver.resolveHost ? await deps.modelResolver.resolveHost(args) : void 0,
|
|
237
|
+
usage
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
async onAfterStep({ workflowId, stepId, context }) {
|
|
241
|
+
const acc = context.usage;
|
|
242
|
+
if (!acc.hasUsage()) return;
|
|
243
|
+
const usage = acc.get();
|
|
244
|
+
const costMicros = estimate(usage, usage.modelId);
|
|
245
|
+
await deps.usageRecorder.recordStepUsage({
|
|
246
|
+
workflowId,
|
|
247
|
+
stepId,
|
|
248
|
+
usage,
|
|
249
|
+
costMicros
|
|
250
|
+
});
|
|
251
|
+
await deps.usageRecorder.incrementWorkflowUsage({
|
|
252
|
+
workflowId,
|
|
253
|
+
usage,
|
|
254
|
+
costMicros
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
async onWorkflowCompleted({ workflowId, workflow }) {
|
|
258
|
+
if (!deps.usageRecorder.recordWorkflowDaily) return;
|
|
259
|
+
const keySource = workflow.metadata?.keySource ?? "platform";
|
|
260
|
+
const date = now().toISOString().split("T")[0];
|
|
261
|
+
await deps.usageRecorder.recordWorkflowDaily({
|
|
262
|
+
workflowId,
|
|
263
|
+
workflowType: workflow.type,
|
|
264
|
+
keySource,
|
|
265
|
+
date
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/ai/usage-window.ts
|
|
272
|
+
/**
|
|
273
|
+
* Calendar-window helpers shared by the quota and usage-aggregation engines.
|
|
274
|
+
* All windows are computed in **UTC**, matching flow's existing rollup date
|
|
275
|
+
* derivation (`now().toISOString().split('T')[0]`).
|
|
276
|
+
*/
|
|
277
|
+
/** Format a `Date` as a `YYYY-MM-DD` calendar date in UTC. */
|
|
278
|
+
function toIsoDate(date) {
|
|
279
|
+
return date.toISOString().split("T")[0];
|
|
280
|
+
}
|
|
281
|
+
/** First day (`YYYY-MM-01`) of the calendar month containing the given `YYYY-MM-DD` date. */
|
|
282
|
+
function monthStartOf(isoDate) {
|
|
283
|
+
return isoDate.slice(0, 7) + "-01";
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/ai/quota.ts
|
|
287
|
+
/**
|
|
288
|
+
* Convenience defaults a consumer can spread over partial per-scope limits.
|
|
289
|
+
* flow does **not** apply these automatically — resolution policy lives in the
|
|
290
|
+
* injected {@link AiQuotaConfigResolver}.
|
|
291
|
+
*/
|
|
292
|
+
const DEFAULT_AI_QUOTA = {
|
|
293
|
+
maxConcurrentWorkflows: 3,
|
|
294
|
+
maxWorkflowsPerDay: 50,
|
|
295
|
+
maxWorkflowsPerMonth: 500
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Build a store-agnostic AI quota service. Enforcement order is concurrent →
|
|
299
|
+
* day → month; the first tripped window short-circuits with its error.
|
|
300
|
+
*/
|
|
301
|
+
function createAiQuotaService(deps) {
|
|
302
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
303
|
+
async function checkQuota(partitionKey) {
|
|
304
|
+
const config = await deps.getQuota(partitionKey);
|
|
305
|
+
if (!config) return ok(void 0);
|
|
306
|
+
const running = await deps.store.countRunningWorkflows(partitionKey);
|
|
307
|
+
if (config.maxConcurrentWorkflows !== null && running >= config.maxConcurrentWorkflows) return err({
|
|
308
|
+
key: "ai_quota_exceeded",
|
|
309
|
+
reason: "concurrent_limit",
|
|
310
|
+
message: `Maximum concurrent AI workflows reached (${config.maxConcurrentWorkflows}). Please wait for running workflows to complete.`,
|
|
311
|
+
limit: config.maxConcurrentWorkflows,
|
|
312
|
+
current: running
|
|
313
|
+
});
|
|
314
|
+
const today = toIsoDate(now());
|
|
315
|
+
if (config.maxWorkflowsPerDay !== null) {
|
|
316
|
+
const todayCount = await deps.store.sumWorkflowCount({
|
|
317
|
+
partitionKey,
|
|
318
|
+
startDate: today,
|
|
319
|
+
endDate: today
|
|
320
|
+
}) + running;
|
|
321
|
+
if (todayCount >= config.maxWorkflowsPerDay) return err({
|
|
322
|
+
key: "ai_quota_exceeded",
|
|
323
|
+
reason: "daily_limit",
|
|
324
|
+
message: `Daily AI workflow limit reached (${config.maxWorkflowsPerDay}). Try again tomorrow.`,
|
|
325
|
+
limit: config.maxWorkflowsPerDay,
|
|
326
|
+
current: todayCount
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (config.maxWorkflowsPerMonth !== null) {
|
|
330
|
+
const monthStart = monthStartOf(today);
|
|
331
|
+
const monthCount = await deps.store.sumWorkflowCount({
|
|
332
|
+
partitionKey,
|
|
333
|
+
startDate: monthStart,
|
|
334
|
+
endDate: today
|
|
335
|
+
}) + running;
|
|
336
|
+
if (monthCount >= config.maxWorkflowsPerMonth) return err({
|
|
337
|
+
key: "ai_quota_exceeded",
|
|
338
|
+
reason: "monthly_limit",
|
|
339
|
+
message: `Monthly AI workflow limit reached (${config.maxWorkflowsPerMonth}). Contact support to increase your limit.`,
|
|
340
|
+
limit: config.maxWorkflowsPerMonth,
|
|
341
|
+
current: monthCount
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return ok(void 0);
|
|
345
|
+
}
|
|
346
|
+
return { checkQuota };
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/ai/usage-aggregation.ts
|
|
350
|
+
function messageOf(error, fallback) {
|
|
351
|
+
return error instanceof Error ? error.message : fallback;
|
|
352
|
+
}
|
|
353
|
+
/** Build a store-agnostic AI usage-aggregation service. */
|
|
354
|
+
function createAiUsageAggregationService(deps) {
|
|
355
|
+
const { store } = deps;
|
|
356
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
357
|
+
const logger = deps.logger ?? noopLogger;
|
|
358
|
+
async function recordWorkflowCompletion(data) {
|
|
359
|
+
try {
|
|
360
|
+
await store.addDailyUsage({
|
|
361
|
+
partitionKey: data.partitionKey,
|
|
362
|
+
date: data.date,
|
|
363
|
+
workflowType: data.workflowType,
|
|
364
|
+
keySource: data.keySource,
|
|
365
|
+
workflowCount: 1,
|
|
366
|
+
usage: data.usage,
|
|
367
|
+
estimatedCostMicros: data.estimatedCostMicros
|
|
368
|
+
});
|
|
369
|
+
return ok(void 0);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const message = messageOf(error, "Unknown aggregation error");
|
|
372
|
+
logger.error("Failed to record workflow completion", error instanceof Error ? error : new Error(message));
|
|
373
|
+
return err({
|
|
374
|
+
key: "ai_usage_error",
|
|
375
|
+
message
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function recordEmbeddingUsage(data) {
|
|
380
|
+
try {
|
|
381
|
+
await store.addDailyUsage({
|
|
382
|
+
partitionKey: data.partitionKey,
|
|
383
|
+
date: data.date,
|
|
384
|
+
workflowType: data.workflowType,
|
|
385
|
+
keySource: data.keySource,
|
|
386
|
+
workflowCount: 1,
|
|
387
|
+
usage: {
|
|
388
|
+
inputTokens: data.inputTokens,
|
|
389
|
+
outputTokens: 0,
|
|
390
|
+
cacheReadTokens: 0,
|
|
391
|
+
cacheWriteTokens: 0
|
|
392
|
+
},
|
|
393
|
+
estimatedCostMicros: data.estimatedCostMicros
|
|
394
|
+
});
|
|
395
|
+
return ok(void 0);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
const message = messageOf(error, "Unknown aggregation error");
|
|
398
|
+
logger.error("Failed to record embedding usage", error instanceof Error ? error : new Error(message));
|
|
399
|
+
return err({
|
|
400
|
+
key: "ai_usage_error",
|
|
401
|
+
message
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function getUsageSummary(params) {
|
|
406
|
+
try {
|
|
407
|
+
return ok(await store.aggregateByDate(params));
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return err({
|
|
410
|
+
key: "ai_usage_error",
|
|
411
|
+
message: messageOf(error, "Unknown query error")
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async function getUsageByWorkflowType(params) {
|
|
416
|
+
try {
|
|
417
|
+
return ok(await store.aggregateByWorkflowType(params));
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return err({
|
|
420
|
+
key: "ai_usage_error",
|
|
421
|
+
message: messageOf(error, "Unknown query error")
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async function getCurrentQuotaUsage(params) {
|
|
426
|
+
try {
|
|
427
|
+
const today = toIsoDate(now());
|
|
428
|
+
const monthStart = monthStartOf(today);
|
|
429
|
+
const { partitionKey } = params;
|
|
430
|
+
const dayQuery = {
|
|
431
|
+
partitionKey,
|
|
432
|
+
startDate: today,
|
|
433
|
+
endDate: today
|
|
434
|
+
};
|
|
435
|
+
const monthQuery = {
|
|
436
|
+
partitionKey,
|
|
437
|
+
startDate: monthStart,
|
|
438
|
+
endDate: today
|
|
439
|
+
};
|
|
440
|
+
const [todayCount, monthCount, runningCount] = await Promise.all([
|
|
441
|
+
store.sumWorkflowCount(dayQuery),
|
|
442
|
+
store.sumWorkflowCount(monthQuery),
|
|
443
|
+
store.countRunningWorkflows(partitionKey)
|
|
444
|
+
]);
|
|
445
|
+
return ok({
|
|
446
|
+
today: { workflowCount: todayCount },
|
|
447
|
+
thisMonth: { workflowCount: monthCount },
|
|
448
|
+
running: { count: runningCount }
|
|
449
|
+
});
|
|
450
|
+
} catch (error) {
|
|
451
|
+
return err({
|
|
452
|
+
key: "ai_usage_error",
|
|
453
|
+
message: messageOf(error, "Unknown query error")
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
recordWorkflowCompletion,
|
|
459
|
+
recordEmbeddingUsage,
|
|
460
|
+
getUsageSummary,
|
|
461
|
+
getUsageByWorkflowType,
|
|
462
|
+
getCurrentQuotaUsage
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
export { DEFAULT_AI_QUOTA, DEFAULT_MODEL_PRICING, buildAiWorkflow, createAiQuotaService, createAiUsageAggregationService, createAiWorkflowHooks, createCostEstimator, createEmbeddingUsageAccumulator, createInstrumentedEmbeddingModel, createInstrumentedModel, createUsageAccumulator, defineAiStep, estimateCostMicros };
|