octaflow 0.30.0 → 0.31.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/README.md +1 -1
- package/dist/ai/index.d.ts +34 -0
- package/dist/ai/index.js +19 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,7 +215,7 @@ next to what your steps actually do.
|
|
|
215
215
|
| 🚑 | **Crash recovery** — a step whose worker died is re-queued while its attempt budget lasts |
|
|
216
216
|
| 💓 | **Heartbeats** — a long step proves it is alive, so a dead one is caught in seconds, not minutes |
|
|
217
217
|
| 🔭 | **Observability** — lifecycle events (run history), per-step spans, and `createMeterObserver` turning the events into OpenTelemetry-style metrics — all pluggable, all structural |
|
|
218
|
-
| 🤖 | **AI add-on** — instrumented models, token/cost capture, quota, daily rollups, refusal-aware models, lenient JSON from local models (`withJsonRepair`), a vendor's refusal explained by key and hint (`explainProviderError`), a per-run circuit breaker, a connection test (`testProvider`) |
|
|
218
|
+
| 🤖 | **AI add-on** — instrumented models, token/cost capture, quota, daily rollups, a `usage` sink that books each step into `@octabits-io/metering` (the paid APIs around the model in the same table, priced at read time), refusal-aware models, lenient JSON from local models (`withJsonRepair`), a vendor's refusal explained by key and hint (`explainProviderError`), a per-run circuit breaker, a connection test (`testProvider`) |
|
|
219
219
|
| 🧱 | **Pluggable everything** — `WorkflowStore`, `Dispatcher`, `StepGate`, `FlowObserver`, hooks |
|
|
220
220
|
|
|
221
221
|
---
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface AccumulatedUsage {
|
|
|
12
12
|
cacheReadTokens: number;
|
|
13
13
|
cacheWriteTokens: number;
|
|
14
14
|
modelId: string;
|
|
15
|
+
/** The provider the model belongs to (`model.provider`), when the call reported it. */
|
|
16
|
+
provider?: string;
|
|
15
17
|
}
|
|
16
18
|
export interface UsageAccumulator {
|
|
17
19
|
/** Record usage from a single LLM call (additive — supports multiple calls per step). */
|
|
@@ -222,6 +224,32 @@ export interface AiUsageRecorder {
|
|
|
222
224
|
date: string;
|
|
223
225
|
}): Promise<void>;
|
|
224
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* What one step's model usage looks like to a metering sink: the booking
|
|
229
|
+
* `@octabits-io/metering`'s `record` takes, by shape — tokens as the vendor
|
|
230
|
+
* reported them, the provider as the service, and the attribution octaflow
|
|
231
|
+
* knows (the run, the workflow type as the feature, the step). A sink that
|
|
232
|
+
* runs inside `withUsage` adds the paying account from the ambient context.
|
|
233
|
+
*/
|
|
234
|
+
export interface AiModelUsageEvent {
|
|
235
|
+
kind: 'model';
|
|
236
|
+
/** The provider (`model.provider`), or `'model'` when the call did not report one. */
|
|
237
|
+
service: string;
|
|
238
|
+
model: string;
|
|
239
|
+
inputTokens: number;
|
|
240
|
+
outputTokens: number;
|
|
241
|
+
cacheReadTokens: number;
|
|
242
|
+
cacheWriteTokens: number;
|
|
243
|
+
context: {
|
|
244
|
+
runId: string;
|
|
245
|
+
feature: string;
|
|
246
|
+
correlationId?: string;
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/** Where a step's model usage is booked — `@octabits-io/metering`'s `Metering` satisfies it. Optional: without one nothing is booked. */
|
|
250
|
+
export interface AiUsageSink {
|
|
251
|
+
record(event: AiModelUsageEvent): unknown;
|
|
252
|
+
}
|
|
225
253
|
export interface AiQuotaPolicy {
|
|
226
254
|
/** Return an error (e.g. `ai_quota_exceeded`) to reject a workflow start. */
|
|
227
255
|
checkQuota(): Promise<Result<void, FlowErrorShape>>;
|
|
@@ -231,6 +259,12 @@ export interface CreateAiWorkflowHooksDeps<THost = unknown> {
|
|
|
231
259
|
usageRecorder: AiUsageRecorder;
|
|
232
260
|
/** Optional quota guard. BYOK exemption etc. is the policy's concern. */
|
|
233
261
|
quotaPolicy?: AiQuotaPolicy;
|
|
262
|
+
/**
|
|
263
|
+
* Optional metering sink: every step's model usage is booked here as well,
|
|
264
|
+
* unpriced, attributed to the run and the workflow type — so a model call
|
|
265
|
+
* and the paid API calls around it land in one table with one context.
|
|
266
|
+
*/
|
|
267
|
+
usage?: AiUsageSink;
|
|
234
268
|
/** Defaults to `createCostEstimator()` (the built-in pricing table). */
|
|
235
269
|
costEstimator?: CostEstimator;
|
|
236
270
|
/** Clock injection for tests. */
|
package/dist/ai/index.js
CHANGED
|
@@ -21,6 +21,7 @@ function createUsageAccumulator() {
|
|
|
21
21
|
total.cacheReadTokens += usage.cacheReadTokens;
|
|
22
22
|
total.cacheWriteTokens += usage.cacheWriteTokens;
|
|
23
23
|
if (usage.modelId) total.modelId = usage.modelId;
|
|
24
|
+
if (usage.provider) total.provider = usage.provider;
|
|
24
25
|
hasAny = true;
|
|
25
26
|
},
|
|
26
27
|
get() {
|
|
@@ -48,7 +49,8 @@ function createInstrumentedModel(model, accumulator) {
|
|
|
48
49
|
outputTokens: usage.outputTokens?.total ?? 0,
|
|
49
50
|
cacheReadTokens: usage.inputTokens?.cacheRead ?? 0,
|
|
50
51
|
cacheWriteTokens: usage.inputTokens?.cacheWrite ?? 0,
|
|
51
|
-
modelId: model.modelId
|
|
52
|
+
modelId: model.modelId,
|
|
53
|
+
provider: model.provider
|
|
52
54
|
});
|
|
53
55
|
return result;
|
|
54
56
|
},
|
|
@@ -63,7 +65,8 @@ function createInstrumentedModel(model, accumulator) {
|
|
|
63
65
|
outputTokens: u.outputTokens?.total ?? 0,
|
|
64
66
|
cacheReadTokens: u.inputTokens?.cacheRead ?? 0,
|
|
65
67
|
cacheWriteTokens: u.inputTokens?.cacheWrite ?? 0,
|
|
66
|
-
modelId: model.modelId
|
|
68
|
+
modelId: model.modelId,
|
|
69
|
+
provider: model.provider
|
|
67
70
|
});
|
|
68
71
|
}
|
|
69
72
|
} }));
|
|
@@ -440,7 +443,7 @@ function createAiWorkflowHooks(deps) {
|
|
|
440
443
|
usage
|
|
441
444
|
};
|
|
442
445
|
},
|
|
443
|
-
async onAfterStep({ workflowId, stepId, context }) {
|
|
446
|
+
async onAfterStep({ workflowId, stepId, workflow, step, context }) {
|
|
444
447
|
const acc = context.usage;
|
|
445
448
|
if (!acc.hasUsage()) return;
|
|
446
449
|
const usage = acc.get();
|
|
@@ -456,6 +459,19 @@ function createAiWorkflowHooks(deps) {
|
|
|
456
459
|
usage,
|
|
457
460
|
costMicros
|
|
458
461
|
});
|
|
462
|
+
deps.usage?.record({
|
|
463
|
+
kind: "model",
|
|
464
|
+
service: usage.provider ?? "model",
|
|
465
|
+
model: usage.modelId,
|
|
466
|
+
inputTokens: usage.inputTokens,
|
|
467
|
+
outputTokens: usage.outputTokens,
|
|
468
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
469
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
470
|
+
context: {
|
|
471
|
+
runId: String(workflowId),
|
|
472
|
+
feature: workflow?.type ?? step?.key ?? "workflow"
|
|
473
|
+
}
|
|
474
|
+
});
|
|
459
475
|
},
|
|
460
476
|
async onWorkflowCompleted({ workflowId, workflow }) {
|
|
461
477
|
if (!deps.usageRecorder.recordWorkflowDaily) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octaflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss, with an optional AI add-on (token/cost/quota). Layered single package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|