@voltro/ai 0.71.1 → 0.73.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.
@@ -1,4 +1,4 @@
1
- import { c as e, o as t, r as n } from "./provider-C_Sms_DA.js";
1
+ import { c as e, o as t, r as n } from "./provider-Cts1BRhY.js";
2
2
  import { NoObjectGeneratedError as r, Output as i, experimental_transcribe as a, generateObject as o, generateText as s, jsonSchema as c, stepCountIs as l, tool as u } from "ai";
3
3
  import { Context as d, Effect as f, JSONSchema as p, Metric as m, MetricBoundaries as ee, Runtime as te, Schema as h, Stream as ne } from "effect";
4
4
  import { and as g, bigint as re, eq as _, generateId as v, gte as ie, id as y, integer as b, isNull as ae, json as x, queryFor as S, registerRetention as C, retentionTtlMsFromEnv as w, table as T, text as E, timestamp as D } from "@voltro/database";
@@ -189,22 +189,26 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
189
189
  }));
190
190
  }), Oe = (e, t) => f.gen(function* () {
191
191
  return Ce(t.reservationId), yield* f.promise(() => Te(e, async (e) => {
192
- let [n] = await e.query(S(xe).where(g(_("reservationId", t.reservationId), t.tenantId === null ? ae("tenantId") : _("tenantId", t.tenantId))).descriptor);
193
- if (!n) return "not-found";
194
- if (n.releasedAt !== null) return "already-released";
195
- let [r] = await e.query(S(M).where(_("bucket", n.bucket)).descriptor);
196
- if (!r || r.tenantId !== t.tenantId) throw Error("AI budget counter missing or mismatched; refusing unproved credit");
197
- let i = P(r.reservedMicroUsd), a = P(n.amountMicroUsd);
198
- if (a > i) throw Error("AI budget receipt exceeds its counter; refusing unproved credit");
199
- let o = /* @__PURE__ */ new Date();
192
+ let n = S(xe).where(g(_("reservationId", t.reservationId), t.tenantId === null ? ae("tenantId") : _("tenantId", t.tenantId))).descriptor, [r] = await e.query(n);
193
+ if (!r) return "not-found";
194
+ if (r.releasedAt !== null) return "already-released";
195
+ let i = /* @__PURE__ */ new Date();
200
196
  if (await e.updateMany("_voltro_ai_budget_reservations", {
201
- releasedAt: o,
202
- updatedAt: o
203
- }, { where: g(_("id", n.id), ae("releasedAt")) }) !== 1) throw new N("AI budget release raced; retry");
197
+ releasedAt: i,
198
+ updatedAt: i
199
+ }, { where: g(_("id", r.id), ae("releasedAt")) }) !== 1) {
200
+ let [t] = await e.query(n);
201
+ if (t !== void 0 && t.releasedAt !== null) return "already-released";
202
+ throw new N("AI budget release raced; retry");
203
+ }
204
+ let [a] = await e.query(S(M).where(_("bucket", r.bucket)).descriptor);
205
+ if (!a || a.tenantId !== t.tenantId) throw Error("AI budget counter missing or mismatched; refusing unproved credit");
206
+ let o = P(a.reservedMicroUsd), s = P(r.amountMicroUsd);
207
+ if (s > o) throw Error("AI budget receipt exceeds its counter; refusing unproved credit");
204
208
  if (await e.updateMany("_voltro_ai_budget", {
205
- reservedMicroUsd: String(i - a),
206
- updatedAt: o
207
- }, { where: g(_("id", r.id), _("reservedMicroUsd", r.reservedMicroUsd)) }) !== 1) throw new N("AI budget counter changed; discard this release and retry");
209
+ reservedMicroUsd: String(o - s),
210
+ updatedAt: i
211
+ }, { where: g(_("id", a.id), _("reservedMicroUsd", a.reservedMicroUsd)) }) !== 1) throw new N("AI budget counter changed; discard this release and retry");
208
212
  return "released";
209
213
  }));
210
214
  }), ke = {
@@ -326,8 +330,11 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
326
330
  promptId: E().nullable(),
327
331
  promptDigest: E().nullable(),
328
332
  promptRevision: b().nullable(),
333
+ requestId: E().maxLength(191).nullable(),
334
+ attempt: b().nullable(),
335
+ receiptKey: E().maxLength(191).backfill((e) => `aiu-pre-receipt-${String(e.id)}`),
329
336
  calledAt: D().default("now")
330
- }).index(["tenantId"]).index(["promptDigest"]), Ie = (e) => {
337
+ }).index(["tenantId"]).index(["promptDigest"]).unique("byAiUsageReceipt", ["receiptKey"]), Ie = (e) => {
331
338
  let t = e?.gateway?.cost, n = typeof t == "string" ? t.trim() : void 0, r = typeof t == "number" ? t : n !== void 0 && /^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(n) ? Number(n) : NaN;
332
339
  return Number.isFinite(r) && r >= 0 ? r : void 0;
333
340
  }, Le = (e, t) => (C({
@@ -340,8 +347,10 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
340
347
  model: t.model,
341
348
  ...t.price === void 0 ? {} : { price: t.price },
342
349
  ...t.actualCostUsd === void 0 ? {} : { actualCostUsd: t.actualCostUsd }
343
- });
344
- return await e.insert(L, {
350
+ }), r = t.receiptKey ?? (t.requestId === void 0 ? v({
351
+ kind: "typeid",
352
+ prefix: "aiu"
353
+ }, "_voltro_ai_usage") : `${t.requestId}#${t.attempt ?? 0}`), i = await e.insertIgnoreWithOutcome(L, {
345
354
  tenantId: t.tenantId ?? null,
346
355
  provider: t.provider,
347
356
  model: t.model,
@@ -355,8 +364,15 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
355
364
  promptId: t.prompt?.promptId ?? null,
356
365
  promptDigest: t.prompt?.digest ?? null,
357
366
  promptRevision: t.prompt?.revision ?? null,
367
+ requestId: t.requestId ?? null,
368
+ attempt: t.attempt ?? null,
369
+ receiptKey: r,
358
370
  calledAt: t.calledAt ?? /* @__PURE__ */ new Date()
359
- }), n;
371
+ }, { conflictColumns: ["receiptKey"] });
372
+ return {
373
+ ...n,
374
+ receipt: i.outcome === "inserted" ? "recorded" : "replayed"
375
+ };
360
376
  })), Re = (e, t = {}) => f.promise(async () => {
361
377
  let n = [
362
378
  ...t.tenantId === void 0 ? [] : [_("tenantId", t.tenantId)],
@@ -480,6 +496,7 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
480
496
  let i = e.tools === void 0 ? void 0 : yield* f.runtime(), a = e.tools === void 0 || i === void 0 ? void 0 : O(e.tools, {
481
497
  store: void 0,
482
498
  ...e.toolContext,
499
+ ...e.abortSignal === void 0 ? {} : { abortSignal: e.abortSignal },
483
500
  runtime: i
484
501
  });
485
502
  return yield* q(e.provider ?? n(), e.fallbacks, (n) => H(B(n, "generateText"), (e) => e.usage, f.tryPromise({
@@ -487,6 +504,7 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
487
504
  let i = t(n), o = await s({
488
505
  model: i,
489
506
  ...r,
507
+ ...e.abortSignal === void 0 ? {} : { abortSignal: e.abortSignal },
490
508
  ...a === void 0 ? {} : {
491
509
  tools: a,
492
510
  stopWhen: l(e.maxSteps ?? 8)
@@ -582,6 +600,7 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
582
600
  let a = yield* f.runtime(), o = yield* J(e.schema, "generateObjectWithTools"), u = {
583
601
  store: void 0,
584
602
  ...e.toolContext,
603
+ ...e.abortSignal === void 0 ? {} : { abortSignal: e.abortSignal },
585
604
  runtime: a
586
605
  }, d = O(e.tools, u);
587
606
  return yield* q(e.provider ?? n(), e.fallbacks, (n) => H(B(n, "generateObjectWithTools"), (e) => e.usage, f.gen(function* () {
@@ -590,6 +609,7 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
590
609
  model: a,
591
610
  prompt: e.prompt,
592
611
  tools: d,
612
+ ...e.abortSignal === void 0 ? {} : { abortSignal: e.abortSignal },
593
613
  stopWhen: l(e.maxSteps ?? 8),
594
614
  output: i.object({ schema: c(o) }),
595
615
  ...e.system === void 0 ? {} : { system: e.system },
@@ -803,7 +823,8 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
803
823
  kind: E().oneOf([
804
824
  "text",
805
825
  "object",
806
- "transcription"
826
+ "transcription",
827
+ "media"
807
828
  ]).default("text"),
808
829
  provider: E(),
809
830
  model: E(),
@@ -816,9 +837,11 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
816
837
  providerOptions: x().nullable(),
817
838
  jsonSchema: x().nullable(),
818
839
  transcription: x().nullable(),
840
+ media: x().nullable(),
819
841
  result: x().nullable(),
820
842
  errorMessage: E().nullable(),
821
843
  attempts: b().default(0),
844
+ fence: b().default(0),
822
845
  leaseUntil: D().nullable(),
823
846
  claimedBy: E().nullable(),
824
847
  enqueuedAt: D().default("now"),
@@ -836,7 +859,7 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
836
859
  runId: $(e.runId),
837
860
  signalName: String(e.signalName),
838
861
  stepName: String(e.stepName),
839
- kind: e.kind === "object" ? "object" : e.kind === "transcription" ? "transcription" : "text",
862
+ kind: e.kind === "object" ? "object" : e.kind === "transcription" ? "transcription" : e.kind === "media" ? "media" : "text",
840
863
  provider: String(e.provider),
841
864
  model: String(e.model),
842
865
  prompt: String(e.prompt ?? ""),
@@ -848,7 +871,9 @@ var de = (e, t) => e(t), fe = Symbol.for("@voltro/ai/Tool"), pe = (e) => ({
848
871
  providerOptions: e.providerOptions ?? null,
849
872
  jsonSchema: e.jsonSchema ?? null,
850
873
  transcription: e.transcription ?? null,
851
- attempts: Q(e.attempts, 0)
874
+ media: e.media ?? null,
875
+ attempts: Q(e.attempts, 0),
876
+ fence: Q(e.fence, 0)
852
877
  }), xt = (e) => {
853
878
  let t = e.attempts + 1;
854
879
  return t >= e.maxAttempts ? {
@@ -30,40 +30,55 @@ var u = [], d = (...e) => {
30
30
  model: n.modelId
31
31
  }), e())
32
32
  };
33
- }, g = (e, t) => ({
34
- ...e,
35
- fetch: async (n, r) => {
36
- let i = e.fetch ?? globalThis.fetch;
37
- if (t === void 0) return i(n, r);
38
- if (r?.method !== "POST" || typeof r.body != "string") throw Error("Gateway media: expected a JSON POST to preserve the requested seed");
39
- let a;
40
- try {
41
- a = JSON.parse(r.body);
42
- } catch {
43
- throw Error("Gateway media: expected a JSON object to preserve the requested seed");
33
+ }, g = (e, t) => {
34
+ let n = Object.fromEntries(Object.entries(t).filter(([, e]) => e !== void 0));
35
+ return {
36
+ ...e,
37
+ fetch: async (t, r) => {
38
+ let i = e.fetch ?? globalThis.fetch;
39
+ if (Object.keys(n).length === 0) return i(t, r);
40
+ if (r?.method !== "POST" || typeof r.body != "string") throw Error("Gateway media: expected a JSON POST to preserve the requested parameters");
41
+ let a;
42
+ try {
43
+ a = JSON.parse(r.body);
44
+ } catch {
45
+ throw Error("Gateway media: expected a JSON object to preserve the requested parameters");
46
+ }
47
+ if (typeof a != "object" || !a || Array.isArray(a)) throw Error("Gateway media: expected a JSON object to preserve the requested parameters");
48
+ return i(t, {
49
+ ...r,
50
+ body: JSON.stringify({
51
+ ...a,
52
+ ...n
53
+ })
54
+ });
44
55
  }
45
- if (typeof a != "object" || !a || Array.isArray(a)) throw Error("Gateway media: expected a JSON object to preserve the requested seed");
46
- return i(n, {
47
- ...r,
48
- body: JSON.stringify({
49
- ...a,
50
- seed: t
51
- })
52
- });
53
- }
54
- }), _ = (e, t) => {
56
+ };
57
+ }, _ = (e, t) => {
55
58
  let n = Reflect.get(e, t, e);
56
59
  return typeof n == "function" && t !== "constructor" ? n.bind(e) : n;
57
60
  }, v = (e, t) => {
58
- let n = i(e).image(t), r = (n) => i(g(e, n.seed)).image(t).doGenerate(n);
61
+ let n = i(e).image(t), r = (n) => i(g(e, {
62
+ seed: n.seed,
63
+ duration: void 0,
64
+ fps: void 0
65
+ })).image(t).doGenerate(n);
59
66
  return new Proxy(n, { get: (e, t) => t === "doGenerate" ? r : _(e, t) });
60
67
  }, y = (e, t) => {
61
68
  let n = i(e).video(t), r = n.doGenerate === void 0 ? void 0 : (n) => {
62
- let r = i(g(e, n.seed)).video(t);
69
+ let r = i(g(e, {
70
+ seed: n.seed,
71
+ duration: n.duration,
72
+ fps: n.fps
73
+ })).video(t);
63
74
  if (!r.doGenerate) throw Error("Gateway video does not implement doGenerate");
64
75
  return r.doGenerate(n);
65
76
  }, a = n.doStart === void 0 ? void 0 : (n) => {
66
- let r = i(g(e, n.seed)).video(t);
77
+ let r = i(g(e, {
78
+ seed: n.seed,
79
+ duration: n.duration,
80
+ fps: n.fps
81
+ })).video(t);
67
82
  if (!r.doStart) throw Error("Gateway video does not implement doStart");
68
83
  return r.doStart(n);
69
84
  };
package/dist/test.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e, u as t } from "./provider-C_Sms_DA.js";
1
+ import { t as e, u as t } from "./provider-Cts1BRhY.js";
2
2
  //#region src/test/index.ts
3
3
  var n = (e) => e.turns && e.turns.length > 0 ? e.turns : e.stream && e.stream.length > 0 ? [{ text: e.stream.join("") }] : e.generate ? [{ text: e.generate.text }] : [{ text: "" }], r = (t) => {
4
4
  let r = new e(n(t));
@@ -43,7 +43,11 @@ export declare interface AiObjectStepOptions<A, I> extends AiStepBaseOptions {
43
43
 
44
44
  export declare class AiOffloadError extends Error {
45
45
  readonly attempts: number;
46
- constructor(stepName: string, message: string, attempts: number);
46
+ /** `failed` — the call was performed and gave up. `unknown` — the worker
47
+ * that submitted it was lost and the provider cannot be asked: the job may
48
+ * have produced (and charged for) its artifact. A media job only. */
49
+ readonly outcome: 'failed' | 'unknown';
50
+ constructor(stepName: string, message: string, attempts: number, outcome?: 'failed' | 'unknown');
47
51
  }
48
52
 
49
53
  export declare class AiOffloadMisuseError extends Error {
@@ -233,6 +237,44 @@ declare type GatewayCreator = 'openai' | 'anthropic' | 'google' | (string & {});
233
237
  * now a type error, not a boot crash). */
234
238
  declare type GatewayModel = `${GatewayCreator}/${string}`;
235
239
 
240
+ /**
241
+ * A media generation as a durable step — always offloaded, because a media
242
+ * job is an external job: minutes long, paid on submission, and owned by
243
+ * whichever process submitted it.
244
+ *
245
+ * The queue row is the job's identity, allocated before the first paid call.
246
+ * A replay finds the row; a dispatcher that dies mid-job leaves a lease that
247
+ * expires — and the reclaiming dispatcher does NOT submit again: the provider
248
+ * has no lookup, so the outcome is reported to the run as `unknown`
249
+ * (`AiOffloadError.outcome`) rather than paid for twice. Artifacts are
250
+ * persisted through the storage plugin's `mediaPersist` capability under
251
+ * `<row>#<attempt>#<index>`, and the run receives URLs and ref ids.
252
+ */
253
+ export declare const mediaStep: (options: MediaStepOptions) => Effect.Effect<MediaStepResult, unknown>;
254
+
255
+ export declare interface MediaStepOptions extends Omit<AiStepBaseOptions, 'system' | 'promptTemplate' | 'promptVars' | 'offload' | 'tools' | 'maxSteps' | 'toolContext' | 'fallbacks' | 'maxTokens'> {
256
+ readonly modality: 'image' | 'video' | 'speech';
257
+ /** Provider parameters — `n`, `size`, `aspectRatio`, `seed` for an image;
258
+ * `durationSeconds`, `resolution`, `fps` for a video; `voice`, `format` for
259
+ * speech. Passed through to the generator, journaled with the job. */
260
+ readonly params?: Record<string, unknown>;
261
+ /** URL inputs — reference images, a mask, a starting frame. URLs only: the
262
+ * job row travels through the queue and a resume payload, never bytes. */
263
+ readonly inputs?: Record<string, unknown>;
264
+ /** Required: a media job is always an external job with a durable identity. */
265
+ readonly store: DataStore;
266
+ }
267
+
268
+ export declare interface MediaStepResult {
269
+ readonly artifacts: ReadonlyArray<{
270
+ readonly url: string;
271
+ readonly refId: string | null;
272
+ readonly mediaType: string;
273
+ }>;
274
+ readonly usage: unknown;
275
+ readonly providerMetadata: unknown;
276
+ }
277
+
236
278
  /** A scripted inline file the mock model emits on a turn (usually an image).
237
279
  * `data` is base64 (a short ascii string works for assertions). */
238
280
  declare interface MockFile {
package/dist/workflow.js CHANGED
@@ -1,5 +1,5 @@
1
- import { r as e } from "./provider-C_Sms_DA.js";
2
- import { D as t, E as n, O as r, R as i, U as a, Z as o, b as s, m as c, o as l, p as u, s as d, t as f, w as p, y as m } from "./inferenceQueue-TwTEBG3Q.js";
1
+ import { r as e } from "./provider-Cts1BRhY.js";
2
+ import { D as t, E as n, O as r, R as i, U as a, Z as o, b as s, m as c, o as l, p as u, s as d, t as f, w as p, y as m } from "./inferenceQueue-CXEVU6mI.js";
3
3
  import { Effect as h, Schema as g } from "effect";
4
4
  import { eq as _ } from "@voltro/database";
5
5
  import { CurrentWorkflowExecutionId as v, awaitSignalSuspending as y, step as b, suspendForBudget as x } from "@voltro/workflow";
@@ -69,38 +69,39 @@ var S = {
69
69
  totalTokens: g.NullOr(g.Number)
70
70
  }), j = (e) => typeof e == "object" && !!e && e.__voltro_ai_error === !0, M = class extends Error {
71
71
  attempts;
72
- constructor(e, t, n) {
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;
72
+ outcome;
73
+ constructor(e, t, n, r = "failed") {
74
+ 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, this.outcome = r;
74
75
  }
75
76
  }, N = class extends Error {
76
77
  constructor(e) {
77
78
  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";
78
79
  }
79
- }, P = (e, t, n, r, i, a) => h.gen(function* () {
80
- let o = e.store;
81
- if (o === void 0) return yield* h.die(new N(e.name));
82
- let s = yield* v;
83
- if (s === void 0) return yield* h.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: u } = D(t), p = d(s, e.name), m = yield* h.promise(() => o.query({
80
+ }, P = (e, t, n, r, i, a, o) => h.gen(function* () {
81
+ let s = e.store;
82
+ if (s === void 0) return yield* h.die(new N(e.name));
83
+ let c = yield* v;
84
+ if (c === void 0) return yield* h.die(/* @__PURE__ */ Error(`aiStep('${e.name}'): offload: true is only valid inside a workflow body — there is no execution to suspend here.`));
85
+ let { provider: u, model: p } = D(t), m = d(c, e.name), g = yield* h.promise(() => s.query({
85
86
  table: "_voltro_workflow_runs",
86
- predicate: _("executionId", s),
87
+ predicate: _("executionId", c),
87
88
  order: [],
88
89
  take: 1,
89
90
  skip: void 0,
90
91
  projection: void 0
91
- })), g = String(m[0]?.tag ?? ""), y = m[0]?.id;
92
- return yield* h.promise(() => o.insertIgnore(f, {
93
- id: p,
92
+ })), y = String(g[0]?.tag ?? ""), b = g[0]?.id;
93
+ return yield* h.promise(() => s.insertIgnore(f, {
94
+ id: m,
94
95
  tenantId: e.tenantId ?? null,
95
- executionId: s,
96
- workflowName: g,
97
- runId: typeof y == "string" ? y : null,
98
- signalName: l(p),
96
+ executionId: c,
97
+ workflowName: y,
98
+ runId: typeof b == "string" ? b : null,
99
+ signalName: l(m),
99
100
  stepName: e.name,
100
101
  status: "pending",
101
102
  kind: n,
102
- provider: c,
103
- model: u,
103
+ provider: u,
104
+ model: p,
104
105
  prompt: i.text,
105
106
  system: i.system ?? null,
106
107
  promptId: i.rendered?.promptId ?? null,
@@ -110,23 +111,25 @@ var S = {
110
111
  providerOptions: e.providerOptions ?? null,
111
112
  jsonSchema: r,
112
113
  ...a === void 0 ? {} : { transcription: a },
114
+ ...o === void 0 ? {} : { media: o },
113
115
  attempts: 0,
116
+ fence: 0,
114
117
  enqueuedAt: /* @__PURE__ */ new Date()
115
- }, { conflictColumns: ["id"] })), p;
116
- }), F = (e, t, n, r, i, a, o) => h.gen(function* () {
117
- let s = yield* b({
118
+ }, { conflictColumns: ["id"] })), m;
119
+ }), F = (e, t, n, r, i, a, o, s) => h.gen(function* () {
120
+ let c = yield* b({
118
121
  name: `${e.name}:enqueue`,
119
122
  success: g.String,
120
123
  input: i,
121
- execute: P(e, t, n, r, a, o)
124
+ execute: P(e, t, n, r, a, o, s)
122
125
  });
123
126
  yield* k(e, a.rendered);
124
- let c = yield* y({ store: e.store }, {
125
- name: l(s),
127
+ let u = yield* y({ store: e.store }, {
128
+ name: l(c),
126
129
  schema: g.Unknown,
127
130
  timeoutMs: e.offloadTimeoutMs ?? 36e5
128
131
  });
129
- return j(c) ? yield* h.die(new M(e.name, c.message, c.attempts)) : c;
132
+ return j(u) ? yield* h.die(new M(e.name, u.message, u.attempts, u.outcome === "unknown" ? "unknown" : "failed")) : u;
130
133
  }), I = class extends Error {
131
134
  constructor(e) {
132
135
  super(`aiStep('${e}'): budget needs \`store\` — the reservation counter and the hold both live in tables. Pass ctx.store.`), this.name = "AiBudgetMisuseError";
@@ -240,6 +243,25 @@ var S = {
240
243
  })))
241
244
  }));
242
245
  }, H = (t) => {
246
+ let n = t.provider ?? e(), r = E({
247
+ ...t,
248
+ prompt: t.prompt
249
+ }), i = {
250
+ modality: t.modality,
251
+ params: t.params ?? {},
252
+ inputs: t.inputs ?? {}
253
+ }, a = {
254
+ ...D(n),
255
+ operation: `generate${t.modality[0].toUpperCase()}${t.modality.slice(1)}`,
256
+ offloaded: !0,
257
+ modality: t.modality,
258
+ ...T(r.text, void 0, t.recordPrompt ?? "digest", r.rendered)
259
+ };
260
+ return ((e) => t.budget === void 0 ? e : h.zipRight(L(t), e))(F({
261
+ ...t,
262
+ offload: !0
263
+ }, n, "media", null, a, r, void 0, i));
264
+ }, U = (t) => {
243
265
  let i = t.provider ?? e(), a = E(t), o = {
244
266
  ...D(i),
245
267
  operation: "generateObject",
@@ -277,4 +299,4 @@ var S = {
277
299
  }));
278
300
  };
279
301
  //#endregion
280
- export { S as AI_STEP_RETRY, I as AiBudgetMisuseError, M as AiOffloadError, N as AiOffloadMisuseError, w as DEFAULT_AI_BUDGET_NAME, C as DEFAULT_OFFLOAD_TIMEOUT_MS, B as TranscriptionStepResult, H as aiObjectStep, z as aiStep, c as promptDigest, T as promptRecordFor, E as resolveStepPrompt, V as transcribeStep };
302
+ export { S as AI_STEP_RETRY, I as AiBudgetMisuseError, M as AiOffloadError, N as AiOffloadMisuseError, w as DEFAULT_AI_BUDGET_NAME, C as DEFAULT_OFFLOAD_TIMEOUT_MS, B as TranscriptionStepResult, U as aiObjectStep, z as aiStep, H as mediaStep, c as promptDigest, T as promptRecordFor, E as resolveStepPrompt, V as transcribeStep };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/ai",
3
- "version": "0.71.1",
3
+ "version": "0.73.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",
@@ -55,10 +55,10 @@
55
55
  "dependencies": {
56
56
  "@ai-sdk/anthropic": "^4.0.49",
57
57
  "@ai-sdk/openai": "^4.0.58",
58
- "@voltro/cache": "0.71.1",
59
- "@voltro/database": "0.71.1",
60
- "@voltro/protocol": "0.71.1",
61
- "@voltro/workflow": "0.71.1",
58
+ "@voltro/cache": "0.73.0",
59
+ "@voltro/database": "0.73.0",
60
+ "@voltro/protocol": "0.73.0",
61
+ "@voltro/workflow": "0.73.0",
62
62
  "ajv": "^8.20.0",
63
63
  "ajv-formats": "^3.0.1"
64
64
  },