@voltro/ai 0.33.0 → 0.34.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/CHANGELOG.md +1801 -0
- package/dist/index.d.ts +711 -5
- package/dist/index.js +696 -243
- package/dist/inferenceQueue-uj2kKyqp.js +568 -0
- package/dist/workflow.d.ts +155 -4
- package/dist/workflow.js +171 -96
- package/package.json +5 -5
- package/dist/inferenceQueue-B25RTJZ4.js +0 -447
package/dist/workflow.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ import { StepRetryPolicy } from '@voltro/workflow';
|
|
|
12
12
|
* same rules — see `decideInferenceFailure`. */
|
|
13
13
|
export declare const AI_STEP_RETRY: StepRetryPolicy;
|
|
14
14
|
|
|
15
|
+
export declare class AiBudgetMisuseError extends Error {
|
|
16
|
+
constructor(stepName: string);
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
/** Provider-specific knobs forwarded verbatim to the Vercel AI SDK's
|
|
16
20
|
* `providerOptions` on generateText / streamText / generateObject. The outer
|
|
17
21
|
* key is the provider id, the inner record its options. The headline use is
|
|
@@ -80,7 +84,17 @@ export declare interface AiStepBaseOptions {
|
|
|
80
84
|
* the offloaded call's row, so it has to be stable across a replay — which
|
|
81
85
|
* it is, being a literal in the body. */
|
|
82
86
|
readonly name: string;
|
|
83
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The prompt: either a bare string, or a `definePrompt(...).render(vars)`
|
|
89
|
+
* result.
|
|
90
|
+
*
|
|
91
|
+
* The rendered form is what buys provenance — the step row, the spend ledger
|
|
92
|
+
* row and `_voltro_prompts` all end up carrying the same `promptId` + content
|
|
93
|
+
* digest, so "which prompt version produced this run" is a lookup rather than
|
|
94
|
+
* an archaeology exercise across deploys.
|
|
95
|
+
*/
|
|
96
|
+
readonly prompt: string | RenderedPrompt;
|
|
97
|
+
/** Overrides a rendered prompt's own system message for this call. */
|
|
84
98
|
readonly system?: string;
|
|
85
99
|
readonly provider?: ProviderConfig;
|
|
86
100
|
readonly fallbacks?: ProviderFallbacks;
|
|
@@ -109,6 +123,61 @@ export declare interface AiStepBaseOptions {
|
|
|
109
123
|
readonly offload?: boolean;
|
|
110
124
|
/** End-to-end ceiling on an offloaded call. Default one hour. */
|
|
111
125
|
readonly offloadTimeoutMs?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Hold this call to a per-tenant USD ceiling, checked BEFORE anything is
|
|
128
|
+
* spent. Requires `store` (the counter and the hold both live in tables).
|
|
129
|
+
*
|
|
130
|
+
* See {@link AiStepBudget} — `onExceeded: 'suspend'` is the mode that stops
|
|
131
|
+
* spending without destroying the run.
|
|
132
|
+
*/
|
|
133
|
+
readonly budget?: AiStepBudget;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A per-step USD ceiling, and what happens at it.
|
|
138
|
+
*
|
|
139
|
+
* `onExceeded` is the whole point of this option existing. The framework had
|
|
140
|
+
* two behaviours and neither is a budget CONTROL:
|
|
141
|
+
*
|
|
142
|
+
* - `'fail'` is `requireAiBudget`'s: the call fails with the typed
|
|
143
|
+
* `AiBudgetExceeded` and the whole durable run dies with it, nine steps of
|
|
144
|
+
* work included. Correct for a hard cap you genuinely want enforced by
|
|
145
|
+
* destruction, and the reason budgets get set high.
|
|
146
|
+
* - observation-only (`defineCostBudget`) never stops anything at all.
|
|
147
|
+
*
|
|
148
|
+
* `'suspend'` is the third: the run PARKS on a durable hold, frees its worker,
|
|
149
|
+
* and resumes when the window rolls over or an operator lifts it — then
|
|
150
|
+
* re-checks and continues from where it stopped. Nothing is spent while held,
|
|
151
|
+
* and nothing is lost.
|
|
152
|
+
*/
|
|
153
|
+
export declare interface AiStepBudget {
|
|
154
|
+
/** Stable budget name — what a hold row records and an operator lifts.
|
|
155
|
+
* Default `'ai-usd'`. */
|
|
156
|
+
readonly name?: string;
|
|
157
|
+
/** The per-tenant ceiling in USD. */
|
|
158
|
+
readonly limitUsd: number;
|
|
159
|
+
/** Rolling-window start; folds into the reservation counter's bucket key, so
|
|
160
|
+
* a new window reserves against a fresh counter. */
|
|
161
|
+
readonly since?: Date;
|
|
162
|
+
/**
|
|
163
|
+
* What THIS call is expected to cost, RESERVED atomically before the provider
|
|
164
|
+
* is contacted.
|
|
165
|
+
*
|
|
166
|
+
* Omitting it makes the gate a read: the ceiling still holds against spend
|
|
167
|
+
* that WAS reserved, but N concurrent runs can each observe headroom and then
|
|
168
|
+
* each spend. Pass an estimate for a real cap — that is the difference between
|
|
169
|
+
* a ceiling and a speed bump, and it is the same argument
|
|
170
|
+
* `requireAiBudget`'s CAS-reservation makes against a check-then-act SUM.
|
|
171
|
+
*/
|
|
172
|
+
readonly estimateUsd?: number;
|
|
173
|
+
/** Default `'fail'` — the existing behaviour stays the default, because
|
|
174
|
+
* suspending is a change in what a run DOES and should be asked for. */
|
|
175
|
+
readonly onExceeded?: 'fail' | 'suspend';
|
|
176
|
+
/** Ceiling on how long a hold may park, across every re-park. Default 7 days. */
|
|
177
|
+
readonly holdTimeoutMs?: number;
|
|
178
|
+
/** How often a held run wakes to re-read the counter. Default 15 minutes —
|
|
179
|
+
* a tumbling window rolls over on a clock nothing notifies us about. */
|
|
180
|
+
readonly recheckEveryMs?: number;
|
|
112
181
|
}
|
|
113
182
|
|
|
114
183
|
export declare interface AiStepOptions extends AiStepBaseOptions {
|
|
@@ -126,6 +195,10 @@ export declare interface AiStepResult {
|
|
|
126
195
|
|
|
127
196
|
declare type AnthropicModel = 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001' | (string & {});
|
|
128
197
|
|
|
198
|
+
/** The name a hold is scoped to when the caller does not choose one. Operators
|
|
199
|
+
* lift by this string, so it has to be stable and sayable. */
|
|
200
|
+
export declare const DEFAULT_AI_BUDGET_NAME = "ai-usd";
|
|
201
|
+
|
|
129
202
|
/** Default ceiling on an offloaded call, end to end — one hour. Generous
|
|
130
203
|
* because the wait itself is free (no worker is held), and a run that gives up
|
|
131
204
|
* on a slow provider at ten minutes is a run somebody has to re-drive by hand. */
|
|
@@ -199,8 +272,38 @@ declare interface MockTurn {
|
|
|
199
272
|
|
|
200
273
|
declare type OpenAIModel = 'gpt-5.5' | 'gpt-4o' | 'gpt-4o-mini' | (string & {});
|
|
201
274
|
|
|
202
|
-
|
|
203
|
-
|
|
275
|
+
declare interface PromptDefinition {
|
|
276
|
+
/**
|
|
277
|
+
* Stable identity across revisions. Dotted, like a descriptor tag
|
|
278
|
+
* (`support.triage`) — the same vocabulary the rest of the framework uses for
|
|
279
|
+
* "the name of a thing an app owns".
|
|
280
|
+
*/
|
|
281
|
+
readonly id: string;
|
|
282
|
+
/**
|
|
283
|
+
* The template, with `{{name}}` placeholders. A STRING, not a function, and
|
|
284
|
+
* that is the design: a function has no stable content to hash, so a
|
|
285
|
+
* function-built prompt could be versioned only by hashing its output — which
|
|
286
|
+
* would make every distinct customer message a new "version". Compose in the
|
|
287
|
+
* variables instead.
|
|
288
|
+
*/
|
|
289
|
+
readonly template: string;
|
|
290
|
+
/** Optional system message. Part of the digest — changing it IS a new
|
|
291
|
+
* version, because it changes the model's behaviour as surely as the
|
|
292
|
+
* template does. */
|
|
293
|
+
readonly system?: string;
|
|
294
|
+
/** Optional human label for this revision (`'shorter-system'`). Metadata:
|
|
295
|
+
* deliberately NOT part of the digest, so relabelling does not fork a
|
|
296
|
+
* version. */
|
|
297
|
+
readonly label?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The digest form: a prefix, not the whole hash. This is a correlation handle,
|
|
301
|
+
* and a full sha256 in a dashboard cell is a wall of hex nobody reads.
|
|
302
|
+
*
|
|
303
|
+
* Lives here rather than in `inferStep.ts` (where it started) because it is now
|
|
304
|
+
* the identity of a versioned artefact, and `inferStep` imports
|
|
305
|
+
* `@voltro/workflow` — a prompt registry has no business needing the workflow
|
|
306
|
+
* engine to compute a hash. `inferStep` re-exports it. */
|
|
204
307
|
export declare const promptDigest: (prompt: string) => string;
|
|
205
308
|
|
|
206
309
|
/**
|
|
@@ -210,8 +313,15 @@ export declare const promptDigest: (prompt: string) => string;
|
|
|
210
313
|
* be assertable on its own: "the raw prompt is not in the recorded input" is a
|
|
211
314
|
* property about a string, and testing it through a live model call would test
|
|
212
315
|
* the mock instead.
|
|
316
|
+
*
|
|
317
|
+
* `stamp` is the PROVENANCE half and is recorded in EVERY mode, `'none'`
|
|
318
|
+
* included. That is deliberate and it is the point of the split: the reason to
|
|
319
|
+
* record nothing about a prompt is that its TEXT is sensitive, and a prompt id
|
|
320
|
+
* plus a content digest is neither the text nor derivable from it. Dropping the
|
|
321
|
+
* provenance along with the content would mean the most privacy-conscious
|
|
322
|
+
* setting is also the one where you cannot tell which prompt version ran.
|
|
213
323
|
*/
|
|
214
|
-
export declare const promptRecordFor: (prompt: string, system: string | undefined, mode: PromptRecording) => Record<string, unknown>;
|
|
324
|
+
export declare const promptRecordFor: (prompt: string, system: string | undefined, mode: PromptRecording, stamp?: PromptStamp) => Record<string, unknown>;
|
|
215
325
|
|
|
216
326
|
/**
|
|
217
327
|
* How the prompt is written to the step row the dashboard renders.
|
|
@@ -228,6 +338,21 @@ export declare const promptRecordFor: (prompt: string, system: string | undefine
|
|
|
228
338
|
*/
|
|
229
339
|
export declare type PromptRecording = 'digest' | 'full' | 'none';
|
|
230
340
|
|
|
341
|
+
/** A prompt's identity, as it is stamped onto a step row and a usage row. */
|
|
342
|
+
declare interface PromptStamp {
|
|
343
|
+
/** The author-chosen stable id, e.g. `support.triage`. */
|
|
344
|
+
readonly promptId: string;
|
|
345
|
+
/** Content digest of the template (+ system). CHANGES when the template does
|
|
346
|
+
* — that is the version. */
|
|
347
|
+
readonly digest: string;
|
|
348
|
+
/** The monotonic revision `recordPromptVersion` assigned, once it has been
|
|
349
|
+
* registered. Absent before first registration; the digest is the identity
|
|
350
|
+
* either way. */
|
|
351
|
+
readonly revision?: number;
|
|
352
|
+
/** Optional human label the author gave this revision. */
|
|
353
|
+
readonly label?: string;
|
|
354
|
+
}
|
|
355
|
+
|
|
231
356
|
/**
|
|
232
357
|
* A resolved provider choice — a DISCRIMINATED UNION on `name`. Each provider
|
|
233
358
|
* only carries the fields that apply to it: the mock-only `mockText` / `script`
|
|
@@ -301,4 +426,30 @@ declare type ProviderConfig = {
|
|
|
301
426
|
*/
|
|
302
427
|
declare type ProviderFallbacks = ReadonlyArray<ProviderConfig>;
|
|
303
428
|
|
|
429
|
+
/** What `.render(vars)` produces — a stamp plus the text to send. */
|
|
430
|
+
declare interface RenderedPrompt extends PromptStamp {
|
|
431
|
+
readonly prompt: string;
|
|
432
|
+
readonly system?: string;
|
|
433
|
+
/** The artefact this was rendered from. Carried along so a consumer that
|
|
434
|
+
* receives only the rendered value (`aiStep`) can REGISTER the version
|
|
435
|
+
* without the app having to remember a separate boot-time call. It is the
|
|
436
|
+
* template — code, not data — so carrying it costs nothing a repository
|
|
437
|
+
* checkout does not already cost. */
|
|
438
|
+
readonly definition: PromptDefinition;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Split a step's `prompt` option into the text to send, the system message, and
|
|
443
|
+
* the provenance stamp (if it came from a `definePrompt`).
|
|
444
|
+
*
|
|
445
|
+
* `system` on the step wins over the prompt definition's, so a caller can
|
|
446
|
+
* override for one call without forking a prompt version — the digest still
|
|
447
|
+
* describes the ARTEFACT, and the override is visible in the step row.
|
|
448
|
+
*/
|
|
449
|
+
export declare const resolveStepPrompt: (options: Pick<AiStepBaseOptions, "prompt" | "system">) => {
|
|
450
|
+
readonly text: string;
|
|
451
|
+
readonly system: string | undefined;
|
|
452
|
+
readonly rendered: RenderedPrompt | undefined;
|
|
453
|
+
};
|
|
454
|
+
|
|
304
455
|
export { }
|
package/dist/workflow.js
CHANGED
|
@@ -1,153 +1,228 @@
|
|
|
1
1
|
import { r as e } from "./provider-BOC5wBxx.js";
|
|
2
|
-
import {
|
|
3
|
-
import { Effect as
|
|
4
|
-
import { eq as
|
|
5
|
-
import {
|
|
6
|
-
import { CurrentWorkflowExecutionId as p, awaitSignalSuspending as m, step as h } from "@voltro/workflow";
|
|
2
|
+
import { C as t, D as n, E as r, H as i, U as a, m as o, o as s, p as c, s as l, t as u, y as d, z as f } from "./inferenceQueue-uj2kKyqp.js";
|
|
3
|
+
import { Effect as p, Schema as m } from "effect";
|
|
4
|
+
import { eq as h } from "@voltro/database";
|
|
5
|
+
import { CurrentWorkflowExecutionId as g, awaitSignalSuspending as _, step as v, suspendForBudget as y } from "@voltro/workflow";
|
|
7
6
|
//#region src/inferStep.ts
|
|
8
|
-
var
|
|
7
|
+
var b = {
|
|
9
8
|
maxAttempts: 4,
|
|
10
9
|
strategy: "exponential",
|
|
11
10
|
baseDelay: "1 second",
|
|
12
11
|
maxDelay: "30 seconds",
|
|
13
12
|
jitter: !0,
|
|
14
13
|
respectRetryAfter: !0
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
}, x = 60 * 6e4, S = "ai-usd", C = (e, t, n, r) => {
|
|
15
|
+
let i = r === void 0 ? {} : {
|
|
16
|
+
promptId: r.promptId,
|
|
17
|
+
promptVersion: r.digest,
|
|
18
|
+
...r.revision === void 0 ? {} : { promptRevision: r.revision },
|
|
19
|
+
...r.label === void 0 ? {} : { promptLabel: r.label }
|
|
20
|
+
};
|
|
21
|
+
return n === "none" ? i : n === "full" ? {
|
|
22
|
+
...i,
|
|
23
|
+
prompt: e,
|
|
24
|
+
...t === void 0 ? {} : { system: t }
|
|
25
|
+
} : {
|
|
26
|
+
...i,
|
|
27
|
+
promptDigest: o(e),
|
|
28
|
+
promptChars: e.length,
|
|
29
|
+
...t === void 0 ? {} : {
|
|
30
|
+
systemDigest: o(t),
|
|
31
|
+
systemChars: t.length
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}, w = (e) => c(e.prompt) ? {
|
|
35
|
+
text: e.prompt.prompt,
|
|
36
|
+
system: e.system ?? e.prompt.system,
|
|
37
|
+
rendered: e.prompt
|
|
18
38
|
} : {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
systemChars: t.length
|
|
24
|
-
}
|
|
25
|
-
}, b = (e) => ({
|
|
39
|
+
text: e.prompt,
|
|
40
|
+
system: e.system,
|
|
41
|
+
rendered: void 0
|
|
42
|
+
}, T = (e) => ({
|
|
26
43
|
provider: String(e.provider ?? "unknown"),
|
|
27
44
|
model: String(e.model ?? "unknown")
|
|
28
|
-
}),
|
|
45
|
+
}), E = (e, t, n, r, a) => {
|
|
29
46
|
let o = e.store;
|
|
30
|
-
if (o === void 0) return
|
|
31
|
-
let { provider: s, model: c } =
|
|
32
|
-
return
|
|
47
|
+
if (o === void 0) return p.void;
|
|
48
|
+
let { provider: s, model: c } = T(t), l = f(r.providerMetadata);
|
|
49
|
+
return i(o, {
|
|
33
50
|
provider: s,
|
|
34
51
|
model: c,
|
|
35
|
-
operation:
|
|
52
|
+
operation: n,
|
|
36
53
|
agent: e.name,
|
|
37
|
-
usage:
|
|
54
|
+
usage: r.usage,
|
|
38
55
|
...e.tenantId === void 0 ? {} : { tenantId: e.tenantId },
|
|
39
|
-
...
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
...l === void 0 ? {} : { actualCostUsd: l },
|
|
57
|
+
...a === void 0 ? {} : { prompt: a }
|
|
58
|
+
}).pipe(p.asVoid, p.catchAllCause(() => p.void));
|
|
59
|
+
}, D = (e, t) => {
|
|
60
|
+
let n = e.store;
|
|
61
|
+
return n === void 0 || t === void 0 ? p.void : d(n, {
|
|
62
|
+
...t.definition,
|
|
63
|
+
digest: t.digest
|
|
64
|
+
}).pipe(p.asVoid, p.catchAllCause(() => p.void));
|
|
65
|
+
}, O = m.Struct({
|
|
66
|
+
text: m.String,
|
|
67
|
+
inputTokens: m.NullOr(m.Number),
|
|
68
|
+
outputTokens: m.NullOr(m.Number),
|
|
69
|
+
totalTokens: m.NullOr(m.Number)
|
|
70
|
+
}), k = (e) => typeof e == "object" && !!e && e.__voltro_ai_error === !0, A = class extends Error {
|
|
47
71
|
attempts;
|
|
48
72
|
constructor(e, t, n) {
|
|
49
73
|
super(`aiStep('${e}'): the offloaded model call gave up after ${n} attempt(s): ${t}. The queued call is in _voltro_ai_inferences with status 'failed' — the Flow tab shows it with the provider's own message.`), this.name = "AiOffloadError", this.attempts = n;
|
|
50
74
|
}
|
|
51
|
-
},
|
|
75
|
+
}, j = class extends Error {
|
|
52
76
|
constructor(e) {
|
|
53
77
|
super(`aiStep('${e}'): offload: true needs \`store\` — the queued call lives in a table and the dispatcher reads it from there. Pass ctx.store.`), this.name = "AiOffloadMisuseError";
|
|
54
78
|
}
|
|
55
|
-
},
|
|
56
|
-
let
|
|
57
|
-
if (
|
|
58
|
-
let
|
|
59
|
-
if (
|
|
60
|
-
let { provider:
|
|
79
|
+
}, M = (e, t, n, r, i) => p.gen(function* () {
|
|
80
|
+
let a = e.store;
|
|
81
|
+
if (a === void 0) return yield* p.die(new j(e.name));
|
|
82
|
+
let o = yield* g;
|
|
83
|
+
if (o === void 0) return yield* p.die(/* @__PURE__ */ Error(`aiStep('${e.name}'): offload: true is only valid inside a workflow body — there is no execution to suspend here.`));
|
|
84
|
+
let { provider: c, model: d } = T(t), f = l(o, e.name), m = yield* p.promise(() => a.query({
|
|
61
85
|
table: "_voltro_workflow_runs",
|
|
62
|
-
predicate:
|
|
86
|
+
predicate: h("executionId", o),
|
|
63
87
|
order: [],
|
|
64
88
|
take: 1,
|
|
65
89
|
skip: void 0,
|
|
66
90
|
projection: void 0
|
|
67
|
-
})),
|
|
68
|
-
return yield*
|
|
69
|
-
id:
|
|
91
|
+
})), _ = String(m[0]?.tag ?? ""), v = m[0]?.id;
|
|
92
|
+
return yield* p.promise(() => a.insertIgnore(u, {
|
|
93
|
+
id: f,
|
|
70
94
|
tenantId: e.tenantId ?? null,
|
|
71
|
-
executionId:
|
|
72
|
-
workflowName:
|
|
73
|
-
runId: typeof
|
|
74
|
-
signalName:
|
|
95
|
+
executionId: o,
|
|
96
|
+
workflowName: _,
|
|
97
|
+
runId: typeof v == "string" ? v : null,
|
|
98
|
+
signalName: s(f),
|
|
75
99
|
stepName: e.name,
|
|
76
100
|
status: "pending",
|
|
77
101
|
kind: n,
|
|
78
|
-
provider:
|
|
79
|
-
model:
|
|
80
|
-
prompt:
|
|
81
|
-
system:
|
|
102
|
+
provider: c,
|
|
103
|
+
model: d,
|
|
104
|
+
prompt: i.text,
|
|
105
|
+
system: i.system ?? null,
|
|
106
|
+
promptId: i.rendered?.promptId ?? null,
|
|
107
|
+
promptDigest: i.rendered?.digest ?? null,
|
|
108
|
+
promptRevision: i.rendered?.revision ?? null,
|
|
82
109
|
maxTokens: e.maxTokens ?? null,
|
|
83
110
|
providerOptions: e.providerOptions ?? null,
|
|
84
111
|
jsonSchema: r,
|
|
85
112
|
attempts: 0,
|
|
86
113
|
enqueuedAt: /* @__PURE__ */ new Date()
|
|
87
|
-
}, { conflictColumns: ["id"] })),
|
|
88
|
-
}),
|
|
89
|
-
let
|
|
114
|
+
}, { conflictColumns: ["id"] })), f;
|
|
115
|
+
}), N = (e, t, n, r, i, a) => p.gen(function* () {
|
|
116
|
+
let o = yield* v({
|
|
90
117
|
name: `${e.name}:enqueue`,
|
|
91
|
-
success:
|
|
118
|
+
success: m.String,
|
|
92
119
|
input: i,
|
|
93
|
-
execute:
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
execute: M(e, t, n, r, a)
|
|
121
|
+
});
|
|
122
|
+
yield* D(e, a.rendered);
|
|
123
|
+
let c = yield* _({ store: e.store }, {
|
|
124
|
+
name: s(o),
|
|
125
|
+
schema: m.Unknown,
|
|
97
126
|
timeoutMs: e.offloadTimeoutMs ?? 36e5
|
|
98
127
|
});
|
|
99
|
-
return
|
|
100
|
-
}),
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
return k(c) ? yield* p.die(new A(e.name, c.message, c.attempts)) : c;
|
|
129
|
+
}), P = class extends Error {
|
|
130
|
+
constructor(e) {
|
|
131
|
+
super(`aiStep('${e}'): budget needs \`store\` — the reservation counter and the hold both live in tables. Pass ctx.store.`), this.name = "AiBudgetMisuseError";
|
|
132
|
+
}
|
|
133
|
+
}, F = (e) => p.gen(function* () {
|
|
134
|
+
let t = e.budget;
|
|
135
|
+
if (t === void 0) return;
|
|
136
|
+
let n = e.store;
|
|
137
|
+
if (n === void 0) return yield* p.die(new P(e.name));
|
|
138
|
+
let r = e.tenantId ?? null, i = t.name ?? "ai-usd", o = () => a(n, {
|
|
139
|
+
tenantId: r,
|
|
140
|
+
limitUsd: t.limitUsd,
|
|
141
|
+
addUsd: 0,
|
|
142
|
+
...t.since === void 0 ? {} : { since: t.since }
|
|
143
|
+
}).pipe(p.as(null), p.catchTag("AiBudgetExceeded", (e) => p.succeed(e))), s = () => t.estimateUsd === void 0 ? p.void : a(n, {
|
|
144
|
+
tenantId: r,
|
|
145
|
+
limitUsd: t.limitUsd,
|
|
146
|
+
addUsd: t.estimateUsd,
|
|
147
|
+
...t.since === void 0 ? {} : { since: t.since }
|
|
148
|
+
}), c = yield* o();
|
|
149
|
+
if (c === null) return yield* s();
|
|
150
|
+
if ((t.onExceeded ?? "fail") !== "suspend") return yield* p.fail(c);
|
|
151
|
+
let l = yield* g;
|
|
152
|
+
return l === void 0 ? yield* p.fail(c) : (yield* y({
|
|
153
|
+
store: n,
|
|
154
|
+
budget: i,
|
|
155
|
+
tenantId: r,
|
|
156
|
+
executionId: l,
|
|
157
|
+
workflowName: yield* I(n, l),
|
|
158
|
+
stepName: e.name,
|
|
159
|
+
spentMicroUsd: Math.round(c.spentUsd * 1e6),
|
|
160
|
+
limitMicroUsd: Math.round(t.limitUsd * 1e6),
|
|
161
|
+
hasHeadroom: o().pipe(p.map((e) => e === null)),
|
|
162
|
+
...t.holdTimeoutMs === void 0 ? {} : { timeoutMs: t.holdTimeoutMs },
|
|
163
|
+
...t.recheckEveryMs === void 0 ? {} : { recheckEveryMs: t.recheckEveryMs }
|
|
164
|
+
}).pipe(p.catchAll((e) => p.die(e))), yield* s());
|
|
165
|
+
}), I = (e, t) => p.promise(async () => {
|
|
166
|
+
let n = await e.query({
|
|
167
|
+
table: "_voltro_workflow_runs",
|
|
168
|
+
predicate: h("executionId", t),
|
|
169
|
+
order: [],
|
|
170
|
+
take: 1,
|
|
171
|
+
skip: void 0,
|
|
172
|
+
projection: void 0
|
|
173
|
+
});
|
|
174
|
+
return String(n[0]?.tag ?? "");
|
|
175
|
+
}), L = (t) => {
|
|
176
|
+
let n = t.provider ?? e(), i = w(t), a = {
|
|
177
|
+
...T(n),
|
|
103
178
|
operation: "generateText",
|
|
104
179
|
offloaded: t.offload === !0,
|
|
105
|
-
...
|
|
106
|
-
};
|
|
107
|
-
return t.offload === !0 ?
|
|
180
|
+
...C(i.text, i.system, t.recordPrompt ?? "digest", i.rendered)
|
|
181
|
+
}, o = (e) => t.budget === void 0 ? e : p.zipRight(F(t), e);
|
|
182
|
+
return t.offload === !0 ? o(N(t, n, "text", null, a, i)) : o(v({
|
|
108
183
|
name: t.name,
|
|
109
|
-
success:
|
|
110
|
-
input:
|
|
111
|
-
retry: t.retry ??
|
|
112
|
-
execute:
|
|
113
|
-
prompt:
|
|
184
|
+
success: O,
|
|
185
|
+
input: a,
|
|
186
|
+
retry: t.retry ?? b,
|
|
187
|
+
execute: r({
|
|
188
|
+
prompt: i.text,
|
|
114
189
|
provider: n,
|
|
115
|
-
...
|
|
190
|
+
...i.system === void 0 ? {} : { system: i.system },
|
|
116
191
|
...t.fallbacks === void 0 ? {} : { fallbacks: t.fallbacks },
|
|
117
192
|
...t.maxTokens === void 0 ? {} : { maxTokens: t.maxTokens },
|
|
118
193
|
...t.providerOptions === void 0 ? {} : { providerOptions: t.providerOptions }
|
|
119
|
-
}).pipe(
|
|
194
|
+
}).pipe(p.tap((e) => E(t, n, "generateText", e, i.rendered)), p.tap(() => D(t, i.rendered)), p.map((e) => ({
|
|
120
195
|
text: e.text,
|
|
121
196
|
inputTokens: e.usage.inputTokens ?? null,
|
|
122
197
|
outputTokens: e.usage.outputTokens ?? null,
|
|
123
198
|
totalTokens: e.usage.totalTokens ?? null
|
|
124
199
|
})))
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
let i =
|
|
128
|
-
...
|
|
200
|
+
}));
|
|
201
|
+
}, R = (r) => {
|
|
202
|
+
let i = r.provider ?? e(), a = w(r), o = {
|
|
203
|
+
...T(i),
|
|
129
204
|
operation: "generateObject",
|
|
130
|
-
offloaded:
|
|
131
|
-
...
|
|
132
|
-
};
|
|
133
|
-
return
|
|
134
|
-
let e = yield* r
|
|
135
|
-
return yield*
|
|
136
|
-
}) :
|
|
137
|
-
name:
|
|
138
|
-
success:
|
|
139
|
-
input:
|
|
140
|
-
retry:
|
|
141
|
-
execute:
|
|
142
|
-
prompt:
|
|
143
|
-
schema:
|
|
205
|
+
offloaded: r.offload === !0,
|
|
206
|
+
...C(a.text, a.system, r.recordPrompt ?? "digest", a.rendered)
|
|
207
|
+
}, s = (e) => r.budget === void 0 ? e : p.zipRight(F(r), e);
|
|
208
|
+
return r.offload === !0 ? s(p.gen(function* () {
|
|
209
|
+
let e = yield* n(r.schema, "aiObjectStep"), t = yield* N(r, i, "object", e, o, a);
|
|
210
|
+
return yield* m.decodeUnknown(r.schema)(t);
|
|
211
|
+
})) : s(v({
|
|
212
|
+
name: r.name,
|
|
213
|
+
success: r.schema,
|
|
214
|
+
input: o,
|
|
215
|
+
retry: r.retry ?? b,
|
|
216
|
+
execute: t({
|
|
217
|
+
prompt: a.text,
|
|
218
|
+
schema: r.schema,
|
|
144
219
|
provider: i,
|
|
145
|
-
...
|
|
146
|
-
...
|
|
147
|
-
...
|
|
148
|
-
...
|
|
149
|
-
}).pipe(
|
|
150
|
-
});
|
|
220
|
+
...a.system === void 0 ? {} : { system: a.system },
|
|
221
|
+
...r.fallbacks === void 0 ? {} : { fallbacks: r.fallbacks },
|
|
222
|
+
...r.maxTokens === void 0 ? {} : { maxTokens: r.maxTokens },
|
|
223
|
+
...r.providerOptions === void 0 ? {} : { providerOptions: r.providerOptions }
|
|
224
|
+
}).pipe(p.tap((e) => E(r, i, "generateObject", e, a.rendered)), p.tap(() => D(r, a.rendered)), p.map((e) => e.object))
|
|
225
|
+
}));
|
|
151
226
|
};
|
|
152
227
|
//#endregion
|
|
153
|
-
export {
|
|
228
|
+
export { b as AI_STEP_RETRY, P as AiBudgetMisuseError, A as AiOffloadError, j as AiOffloadMisuseError, S as DEFAULT_AI_BUDGET_NAME, x as DEFAULT_OFFLOAD_TIMEOUT_MS, R as aiObjectStep, L as aiStep, o as promptDigest, C as promptRecordFor, w as resolveStepPrompt };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Effect-native AI primitives — generateText / generateObject / streamText, tools, agents, media generation, cost tracking + budgets, and provider fallback.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"@ai-sdk/gateway": "^4.0.30",
|
|
58
58
|
"@ai-sdk/openai": "^4.0.22",
|
|
59
59
|
"@ai-sdk/provider": "^4.0.4",
|
|
60
|
-
"@voltro/cache": "0.
|
|
61
|
-
"@voltro/database": "0.
|
|
62
|
-
"@voltro/protocol": "0.
|
|
63
|
-
"@voltro/workflow": "0.
|
|
60
|
+
"@voltro/cache": "0.34.0",
|
|
61
|
+
"@voltro/database": "0.34.0",
|
|
62
|
+
"@voltro/protocol": "0.34.0",
|
|
63
|
+
"@voltro/workflow": "0.34.0",
|
|
64
64
|
"ai": "^7.0.40"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|