@voltro/ai 0.63.0 → 0.65.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,6 +1,6 @@
1
- import { a as e, r as t, s as n } from "./provider-BOgeCRkg.js";
1
+ import { c as e, o as t, r as n } from "./provider-RA0getTL.js";
2
2
  import { Output as r, experimental_transcribe as i, generateObject as a, generateText as o, jsonSchema as s, stepCountIs as c, tool as l } from "ai";
3
- import { Context as u, Effect as d, JSONSchema as ee, Metric as f, MetricBoundaries as te, Runtime as ne, Schema as p, Stream as m } from "effect";
3
+ import { Context as u, Effect as d, JSONSchema as ee, Metric as f, MetricBoundaries as te, Runtime as p, Schema as m, Stream as ne } from "effect";
4
4
  import { and as h, eq as g, generateId as _, gte as re, id as v, integer as y, json as b, queryFor as x, registerRetention as S, retentionTtlMsFromEnv as C, table as w, text as T, timestamp as E } from "@voltro/database";
5
5
  import { createHash as ie } from "node:crypto";
6
6
  //#region src/tools.ts
@@ -14,8 +14,8 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
14
14
  for (let r of Object.values(e)) ce(r) && r.execute === void 0 && r.handler === void 0 && (r.handler = t, n += 1);
15
15
  return n;
16
16
  }, ue = (e, t) => {
17
- let n = t ?? ne.defaultRuntime;
18
- return ne.runPromise(n)(e);
17
+ let n = t ?? p.defaultRuntime;
18
+ return p.runPromise(n)(e);
19
19
  }, de = (e, t, n) => n ? e.pipe(d.timeoutFail({
20
20
  duration: `${n} millis`,
21
21
  onTimeout: () => /* @__PURE__ */ Error(`tool '${t}' timed out`)
@@ -40,18 +40,18 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
40
40
  properties: {},
41
41
  additionalProperties: !0
42
42
  };
43
- }, he = (e, t = { store: void 0 }) => {
43
+ }, D = (e, t = { store: void 0 }) => {
44
44
  let n = {};
45
45
  for (let [r, i] of Object.entries(e)) n[i.name || r] = l({
46
46
  description: i.description,
47
47
  inputSchema: s(me(i)),
48
48
  execute: async (e) => {
49
- let n = await d.runPromise(p.decodeUnknown(i.input)(e)), r = await fe(i, n, t);
50
- return i.output ? d.runPromise(p.decodeUnknown(i.output)(r)) : r;
49
+ let n = await d.runPromise(m.decodeUnknown(i.input)(e)), r = await fe(i, n, t);
50
+ return i.output ? d.runPromise(m.decodeUnknown(i.output)(r)) : r;
51
51
  }
52
52
  });
53
53
  return n;
54
- }, D = {
54
+ }, he = {
55
55
  "claude-opus-4-8": {
56
56
  inputPer1M: 15,
57
57
  outputPer1M: 75
@@ -100,7 +100,7 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
100
100
  let t = O;
101
101
  return O = { ...e }, t;
102
102
  }, k = () => ({
103
- ...D,
103
+ ...he,
104
104
  ...O
105
105
  }), A = (e) => {
106
106
  let t = k();
@@ -207,11 +207,11 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
207
207
  ...t.promptDigest === void 0 ? [] : [g("promptDigest", t.promptDigest)]
208
208
  ], r = x(F), i = n.length === 0 ? r : r.where(n.length === 1 ? n[0] : h(...n));
209
209
  return (await e.query(i.descriptor)).reduce((e, t) => e + (Number(t.costMicroUsd) || 0), 0) / 1e6;
210
- }), I = class extends p.TaggedError()("AiBudgetExceeded", {
211
- tenantId: p.NullOr(p.String),
212
- limitUsd: p.Number,
213
- spentUsd: p.Number,
214
- attemptedUsd: p.Number
210
+ }), I = class extends m.TaggedError()("AiBudgetExceeded", {
211
+ tenantId: m.NullOr(m.String),
212
+ limitUsd: m.Number,
213
+ spentUsd: m.Number,
214
+ attemptedUsd: m.Number
215
215
  }) {}, L = "_voltro_ai_budget", R = w(L, {
216
216
  id: v({ prefix: "aib" }),
217
217
  bucket: T().maxLength(191),
@@ -297,41 +297,52 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
297
297
  } }));
298
298
  }), Oe = (e, t) => {
299
299
  let n = Date.now();
300
- return t.pipe(m.tap((t) => t._tag === "done" ? U(f.increment(B), e, "ok").pipe(d.zipRight(U(f.update(V, (Date.now() - n) / 1e3), e, "ok")), d.zipRight(De(e, t.usage ?? {}))) : t._tag === "error" ? U(f.increment(B), e, "error").pipe(d.zipRight(U(f.increment(Ce), e, "error")), d.zipRight(U(f.update(V, (Date.now() - n) / 1e3), e, "error"))) : d.void), m.withSpan("voltro.ai.call", { attributes: {
300
+ return t.pipe(ne.tap((t) => t._tag === "done" ? U(f.increment(B), e, "ok").pipe(d.zipRight(U(f.update(V, (Date.now() - n) / 1e3), e, "ok")), d.zipRight(De(e, t.usage ?? {}))) : t._tag === "error" ? U(f.increment(B), e, "error").pipe(d.zipRight(U(f.increment(Ce), e, "error")), d.zipRight(U(f.update(V, (Date.now() - n) / 1e3), e, "error"))) : d.void), ne.withSpan("voltro.ai.call", { attributes: {
301
301
  "ai.provider": e.provider,
302
302
  "ai.model": e.model,
303
303
  "ai.operation": e.operation
304
304
  } }));
305
- }, G = class extends p.TaggedError()("AiError", {
306
- reason: p.Literal("generation", "decode"),
307
- message: p.String
305
+ }, G = class extends m.TaggedError()("AiError", {
306
+ reason: m.Literal("generation", "decode"),
307
+ message: m.String
308
308
  }) {}, K = (e, t, n) => {
309
309
  let r = [e, ...t ?? []], i = (e) => n(r[e]).pipe(d.catchTag("AiError", (t) => t.reason === "generation" && e + 1 < r.length ? i(e + 1) : d.fail(t)));
310
310
  return i(0);
311
- }, ke = (n) => K(n.provider ?? t(), n.fallbacks, (t) => W(H(t, "generateText"), (e) => e.usage, d.tryPromise({
312
- try: async () => {
313
- let r = e(t), i = await o({
314
- model: r,
315
- prompt: n.prompt,
316
- ...n.system === void 0 ? {} : { system: n.system },
317
- ...n.maxTokens === void 0 ? {} : { maxOutputTokens: n.maxTokens },
318
- ...n.providerOptions === void 0 ? {} : { providerOptions: n.providerOptions }
319
- });
320
- return {
321
- text: i.text,
322
- usage: {
323
- inputTokens: i.usage.inputTokens,
324
- outputTokens: i.usage.outputTokens,
325
- totalTokens: i.usage.totalTokens
326
- },
327
- providerMetadata: i.providerMetadata
328
- };
329
- },
330
- catch: (e) => new G({
331
- reason: "generation",
332
- message: String(e?.message ?? e)
333
- })
334
- }))), q = (e, t) => d.gen(function* () {
311
+ }, ke = (e) => d.gen(function* () {
312
+ let r = e.tools === void 0 ? void 0 : yield* d.runtime(), i = e.tools === void 0 || r === void 0 ? void 0 : D(e.tools, {
313
+ store: void 0,
314
+ ...e.toolContext,
315
+ runtime: r
316
+ });
317
+ return yield* K(e.provider ?? n(), e.fallbacks, (n) => W(H(n, "generateText"), (e) => e.usage, d.tryPromise({
318
+ try: async () => {
319
+ let r = t(n), a = await o({
320
+ model: r,
321
+ prompt: e.prompt,
322
+ ...i === void 0 ? {} : {
323
+ tools: i,
324
+ stopWhen: c(e.maxSteps ?? 8)
325
+ },
326
+ ...e.system === void 0 ? {} : { system: e.system },
327
+ ...e.maxTokens === void 0 ? {} : { maxOutputTokens: e.maxTokens },
328
+ ...e.providerOptions === void 0 ? {} : { providerOptions: e.providerOptions }
329
+ }), s = i !== void 0 && a.totalUsage !== void 0 ? a.totalUsage : a.usage;
330
+ return {
331
+ text: a.text,
332
+ usage: {
333
+ inputTokens: s.inputTokens,
334
+ outputTokens: s.outputTokens,
335
+ totalTokens: s.totalTokens
336
+ },
337
+ providerMetadata: a.providerMetadata
338
+ };
339
+ },
340
+ catch: (e) => new G({
341
+ reason: "generation",
342
+ message: String(e?.message ?? e)
343
+ })
344
+ })));
345
+ }), q = (e, t) => d.gen(function* () {
335
346
  let n = ee.make(e);
336
347
  if (n?.type !== "object") {
337
348
  let e = n?.anyOf === void 0 ? `type:${JSON.stringify(n?.type ?? null)}` : "anyOf (e.g. Schema.Struct({}) or a union)";
@@ -341,15 +352,15 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
341
352
  }));
342
353
  }
343
354
  return n;
344
- }), Ae = (e, t) => q(e, t), je = (n) => K(n.provider ?? t(), n.fallbacks, (t) => W(H(t, "generateObject"), (e) => e.usage, d.gen(function* () {
345
- let r = e(t), i = yield* d.tryPromise({
355
+ }), Ae = (e, t) => q(e, t), je = (e) => K(e.provider ?? n(), e.fallbacks, (n) => W(H(n, "generateObject"), (e) => e.usage, d.gen(function* () {
356
+ let r = t(n), i = yield* d.tryPromise({
346
357
  try: () => a({
347
358
  model: r,
348
- schema: s(n.jsonSchema),
349
- prompt: n.prompt,
350
- ...n.system === void 0 ? {} : { system: n.system },
351
- ...n.maxTokens === void 0 ? {} : { maxOutputTokens: n.maxTokens },
352
- ...n.providerOptions === void 0 ? {} : { providerOptions: n.providerOptions }
359
+ schema: s(e.jsonSchema),
360
+ prompt: e.prompt,
361
+ ...e.system === void 0 ? {} : { system: e.system },
362
+ ...e.maxTokens === void 0 ? {} : { maxOutputTokens: e.maxTokens },
363
+ ...e.providerOptions === void 0 ? {} : { providerOptions: e.providerOptions }
353
364
  }),
354
365
  catch: (e) => new G({
355
366
  reason: "generation",
@@ -374,7 +385,7 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
374
385
  ...e.fallbacks === void 0 ? {} : { fallbacks: e.fallbacks },
375
386
  ...e.maxTokens === void 0 ? {} : { maxTokens: e.maxTokens },
376
387
  ...e.providerOptions === void 0 ? {} : { providerOptions: e.providerOptions }
377
- }), n = yield* p.decodeUnknown(e.schema)(t.object).pipe(d.mapError((e) => new G({
388
+ }), n = yield* m.decodeUnknown(e.schema)(t.object).pipe(d.mapError((e) => new G({
378
389
  reason: "decode",
379
390
  message: `model output failed schema decode: ${String(e)}`
380
391
  })));
@@ -382,23 +393,23 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
382
393
  ...t,
383
394
  object: n
384
395
  };
385
- }), Ne = (n) => d.gen(function* () {
386
- let i = yield* d.runtime(), a = yield* q(n.schema, "generateObjectWithTools"), l = {
396
+ }), Ne = (e) => d.gen(function* () {
397
+ let i = yield* d.runtime(), a = yield* q(e.schema, "generateObjectWithTools"), l = {
387
398
  store: void 0,
388
- ...n.toolContext,
399
+ ...e.toolContext,
389
400
  runtime: i
390
- }, u = he(n.tools, l);
391
- return yield* K(n.provider ?? t(), n.fallbacks, (t) => W(H(t, "generateObjectWithTools"), (e) => e.usage, d.gen(function* () {
392
- let i = e(t), l = yield* d.tryPromise({
401
+ }, u = D(e.tools, l);
402
+ return yield* K(e.provider ?? n(), e.fallbacks, (n) => W(H(n, "generateObjectWithTools"), (e) => e.usage, d.gen(function* () {
403
+ let i = t(n), l = yield* d.tryPromise({
393
404
  try: () => o({
394
405
  model: i,
395
- prompt: n.prompt,
406
+ prompt: e.prompt,
396
407
  tools: u,
397
- stopWhen: c(n.maxSteps ?? 8),
408
+ stopWhen: c(e.maxSteps ?? 8),
398
409
  output: r.object({ schema: s(a) }),
399
- ...n.system === void 0 ? {} : { system: n.system },
400
- ...n.maxTokens === void 0 ? {} : { maxOutputTokens: n.maxTokens },
401
- ...n.providerOptions === void 0 ? {} : { providerOptions: n.providerOptions }
410
+ ...e.system === void 0 ? {} : { system: e.system },
411
+ ...e.maxTokens === void 0 ? {} : { maxOutputTokens: e.maxTokens },
412
+ ...e.providerOptions === void 0 ? {} : { providerOptions: e.providerOptions }
402
413
  }),
403
414
  catch: (e) => new G({
404
415
  reason: "generation",
@@ -406,7 +417,7 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
406
417
  })
407
418
  });
408
419
  return {
409
- object: yield* p.decodeUnknown(n.schema)(l.output).pipe(d.mapError((e) => new G({
420
+ object: yield* m.decodeUnknown(e.schema)(l.output).pipe(d.mapError((e) => new G({
410
421
  reason: "decode",
411
422
  message: `model output failed schema decode: ${String(e)}`
412
423
  }))),
@@ -456,16 +467,16 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
456
467
  });
457
468
  }
458
469
  return t.length > 0 ? t : void 0;
459
- }, Re = (e) => {
460
- let r = e.provider ?? t();
470
+ }, Re = (t) => {
471
+ let r = t.provider ?? n();
461
472
  return W(H(r, "transcribe"), () => ({}), d.tryPromise({
462
473
  try: async () => {
463
- let { audio: t, mediaType: a } = await Ie(e.source, e.resolveRef), o = await i({
464
- model: n(r),
465
- audio: t,
474
+ let { audio: n, mediaType: a } = await Ie(t.source, t.resolveRef), o = await i({
475
+ model: e(r),
476
+ audio: n,
466
477
  ...a === void 0 ? {} : { mediaType: a },
467
- ...e.providerOptions === void 0 ? {} : { providerOptions: e.providerOptions }
468
- }), s = e.timestamps === "none" ? void 0 : Le(o.segments);
478
+ ...t.providerOptions === void 0 ? {} : { providerOptions: t.providerOptions }
479
+ }), s = t.timestamps === "none" ? void 0 : Le(o.segments);
469
480
  return {
470
481
  text: o.text,
471
482
  ...o.language === void 0 ? {} : { language: o.language },
@@ -479,10 +490,10 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
479
490
  message: String(e?.message ?? e)
480
491
  })
481
492
  }));
482
- }, ze = (e) => `sha256:${ie("sha256").update(e).digest("hex").slice(0, 16)}`, Be = class extends p.TaggedError()("PromptRenderError", {
483
- promptId: p.String,
484
- missing: p.Array(p.String),
485
- message: p.String
493
+ }, ze = (e) => `sha256:${ie("sha256").update(e).digest("hex").slice(0, 16)}`, Be = class extends m.TaggedError()("PromptRenderError", {
494
+ promptId: m.String,
495
+ missing: m.Array(m.String),
496
+ message: m.String
486
497
  }) {}, Ve = /\{\{\s*([A-Za-z0-9_.]+)\s*\}\}/g, He = (e) => {
487
498
  let t = /* @__PURE__ */ new Set();
488
499
  for (let n of e.matchAll(Ve)) {
@@ -650,4 +661,4 @@ var ae = (e, t) => e(t), oe = Symbol.for("@voltro/ai/Tool"), se = (e) => ({
650
661
  };
651
662
  }, nt = (e) => `voltro.ai.infer:${e}`, rt = (e, t) => `aiinf_${ie("sha256").update(`${e}\u0000${t}`).digest("hex").slice(0, 40)}`;
652
663
  //#endregion
653
- export { Oe as A, _e as B, Fe as C, ke as D, Ne as E, D as F, ve as G, k as H, R as I, he as J, xe as K, ye as L, L as M, P as N, Ae as O, I as P, F as R, Pe as S, je as T, M as U, j as V, A as W, ce as X, se as Y, le as Z, Xe as _, tt as a, Re as b, et as c, Be as d, We as f, Ue as g, Ye as h, Ze as i, W as j, H as k, Ke as l, ze as m, $e as n, nt as o, Ge as p, ge as q, Qe as r, rt as s, Z as t, J as u, Y as v, Me as w, G as x, Je as y, N as z };
664
+ export { Oe as A, _e as B, Fe as C, ke as D, Ne as E, he as F, ve as G, k as H, R as I, D as J, xe as K, ye as L, L as M, P as N, Ae as O, I as P, F as R, Pe as S, je as T, M as U, j as V, A as W, ce as X, se as Y, le as Z, Xe as _, tt as a, Re as b, et as c, Be as d, We as f, Ue as g, Ye as h, Ze as i, W as j, H as k, Ke as l, ze as m, $e as n, nt as o, Ge as p, ge as q, Qe as r, rt as s, Z as t, J as u, Y as v, Me as w, G as x, Je as y, N as z };
@@ -288,7 +288,14 @@ var u = [], d = (...e) => {
288
288
  }, B = null, V = (e) => {
289
289
  let t = B;
290
290
  return B = e, t;
291
- }, H = () => B, U = (e = process.env) => {
291
+ }, H = () => B, U = (e) => {
292
+ switch (e.toLowerCase()) {
293
+ case "anthropic": return "ANTHROPIC_API_KEY";
294
+ case "openai": return "OPENAI_API_KEY";
295
+ case "gateway": return "AI_GATEWAY_API_KEY";
296
+ default: return;
297
+ }
298
+ }, W = (e = process.env) => {
292
299
  if (B) return B;
293
300
  let t = (e.AI_PROVIDER ?? "mock").toLowerCase(), n = e.AI_MODEL;
294
301
  switch (t) {
@@ -316,4 +323,4 @@ var u = [], d = (...e) => {
316
323
  }
317
324
  };
318
325
  //#endregion
319
- export { c as _, F as a, z as c, p as d, o as f, d as g, h, I as i, V as l, f as m, H as n, L as o, s as p, U as r, R as s, v as t, m as u };
326
+ export { d as _, I as a, R as c, m as d, p as f, h as g, f as h, U as i, z as l, s as m, H as n, F as o, o as p, W as r, L as s, v as t, V as u, c as v };
package/dist/test.js CHANGED
@@ -1,32 +1,32 @@
1
- import { l as e, t } from "./provider-BOgeCRkg.js";
1
+ import { t as e, u as t } from "./provider-RA0getTL.js";
2
2
  //#region src/test/index.ts
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 = (e) => {
4
- let r = new t(n(e));
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
+ let r = new e(n(t));
5
5
  return {
6
6
  config: {
7
7
  name: "mock",
8
- model: e.model ?? "mock",
8
+ model: t.model ?? "mock",
9
9
  script: r,
10
- ...e.generate ? { mockText: e.generate.text } : {}
10
+ ...t.generate ? { mockText: t.generate.text } : {}
11
11
  },
12
12
  script: r
13
13
  };
14
- }, i = (t) => {
15
- let { config: n, script: i } = r(t), a = e(n);
14
+ }, i = (e) => {
15
+ let { config: n, script: i } = r(e), a = t(n);
16
16
  return {
17
17
  calls: i.calls,
18
18
  reset: () => {
19
- e(a);
19
+ t(a);
20
20
  }
21
21
  };
22
- }, a = (t = {}) => {
23
- let { config: n, script: i } = r(t), a = e(n);
22
+ }, a = (e = {}) => {
23
+ let { config: n, script: i } = r(e), a = t(n);
24
24
  return {
25
- ...t.generate ? { generate: () => ({ text: t.generate.text }) } : {},
26
- ...t.generateObject ? { generateObject: () => t.generateObject.object } : {},
25
+ ...e.generate ? { generate: () => ({ text: e.generate.text }) } : {},
26
+ ...e.generateObject ? { generateObject: () => e.generateObject.object } : {},
27
27
  calls: i.calls,
28
28
  reset: () => {
29
- e(a);
29
+ t(a);
30
30
  }
31
31
  };
32
32
  }, o = (e, t, n = (t) => /* @__PURE__ */ Error(`throwNTimes: attempt ${t} of ${e}`)) => {
@@ -1,5 +1,6 @@
1
1
  import { DataStore } from '@voltro/database';
2
2
  import { Effect } from 'effect';
3
+ import { Runtime } from 'effect';
3
4
  import { Schema } from 'effect';
4
5
  import { StepRetryPolicy } from '@voltro/workflow';
5
6
 
@@ -88,6 +89,18 @@ export declare interface AiStepBaseOptions {
88
89
  readonly fallbacks?: ProviderFallbacks;
89
90
  readonly maxTokens?: number;
90
91
  readonly providerOptions?: AiProviderOptions;
92
+ /**
93
+ * Tools the model may call inside this durable step — the whole LLM↔tool
94
+ * loop runs as ONE step: one journal entry, one retry policy, one usage row
95
+ * summing every round-trip. Before this, a durable tool loop had to leave
96
+ * `aiStep` for the inline `generateObjectWithTools`, and lost the journal and
97
+ * the per-step spend recording on the way out.
98
+ */
99
+ readonly tools?: Record<string, AnyTool>;
100
+ /** Max LLM↔tool round-trips before the model must answer. Default 8. */
101
+ readonly maxSteps?: number;
102
+ /** Request context threaded into the tool executors. */
103
+ readonly toolContext?: ToolContext;
91
104
  readonly retry?: StepRetryPolicy;
92
105
  readonly recordPrompt?: PromptRecording;
93
106
  /**
@@ -183,6 +196,24 @@ export declare interface AiStepResult {
183
196
 
184
197
  declare type AnthropicModel = 'claude-opus-4-8' | 'claude-sonnet-4-6' | 'claude-haiku-4-5-20251001' | (string & {});
185
198
 
199
+ /** A heterogeneous tool — the shape `buildSdkTools` / `runAssistant` consume.
200
+ * `defineTool` keeps full input typing on its `execute`/`handler` body, then
201
+ * erases to this shape so a `Record<string, AnyTool>` accepts tools of
202
+ * differing input/output types (Effect's `Schema` encoded type is invariant,
203
+ * so a heterogeneous collection can't preserve per-tool generics). */
204
+ declare interface AnyTool {
205
+ readonly name: string;
206
+ readonly description: string;
207
+ readonly input: Schema.Schema.Any;
208
+ /** See `ToolDef.inputJsonSchema` — a foreign parameter schema shown to the
209
+ * model in place of a rendered `input`. */
210
+ readonly inputJsonSchema?: Record<string, unknown>;
211
+ readonly output?: Schema.Schema.Any;
212
+ readonly execute?: (input: never) => Effect.Effect<unknown, never, unknown>;
213
+ readonly handler?: ToolHandler<never, unknown>;
214
+ readonly timeoutMs?: number;
215
+ }
216
+
186
217
  /** The name a hold is scoped to when the caller does not choose one. Operators
187
218
  * lift by this string, so it has to be stable and sayable. */
188
219
  export declare const DEFAULT_AI_BUDGET_NAME = "ai-usd";
@@ -443,6 +474,31 @@ export declare const resolveStepPrompt: (options: Pick<AiStepBaseOptions, "promp
443
474
  readonly rendered: RenderedPrompt | undefined;
444
475
  };
445
476
 
477
+ /** The slice of the request context a `*.tool.tsx` handler receives. Kept
478
+ * structurally typed here (rather than importing `AppContext` from
479
+ * `@voltro/runtime`) so `@voltro/ai` carries no runtime dependency — the
480
+ * CLI discovery path passes the real `AppContext`, which is a superset.
481
+ *
482
+ * `runtime` is the caller's ambient Effect `Runtime`. When present, tool
483
+ * bodies are run through it (`Runtime.runPromise(runtime)(eff)`) instead of
484
+ * bare `Effect.runPromise`, so a body may `yield*` any service the caller's
485
+ * runtime carries — `JiraService`, the data store layer, `HttpClient`, … —
486
+ * rather than being limited to `R = never`. The agent loop
487
+ * (`streamText`/`runAssistant`) captures `yield* Effect.runtime()` and threads
488
+ * it here; without a runtime, behaviour is unchanged. */
489
+ declare interface ToolContext {
490
+ readonly store: unknown;
491
+ readonly request?: {
492
+ readonly subject?: unknown;
493
+ };
494
+ readonly runtime?: Runtime.Runtime<unknown>;
495
+ }
496
+
497
+ /** A context-receiving tool body — the `*.tool.tsx` default-export form.
498
+ * May return an Effect (with service deps the caller's runtime provides) or
499
+ * a Promise; `buildSdkTools` normalises both. */
500
+ declare type ToolHandler<A, O> = (input: A, ctx: ToolContext) => Effect.Effect<O, never, unknown> | Promise<O>;
501
+
446
502
  /**
447
503
  * Transcription as a durable step — with `offload`, which is the point.
448
504
  *