@voltro/ai 0.1.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.
@@ -0,0 +1,294 @@
1
+ import { anthropic as e, createAnthropic as t } from "@ai-sdk/anthropic";
2
+ import { createOpenAI as n, openai as r } from "@ai-sdk/openai";
3
+ import { createGateway as i, gateway as a } from "@ai-sdk/gateway";
4
+ import { defaultSettingsMiddleware as o, extractReasoningMiddleware as s, simulateStreamingMiddleware as c, wrapLanguageModel as l } from "ai";
5
+ //#region src/middleware.ts
6
+ var u = [], d = (...e) => {
7
+ let t = u;
8
+ return u = e, t;
9
+ }, f = () => u, p = () => {
10
+ u = [];
11
+ }, m = (e, t) => {
12
+ let n = t === void 0 ? [] : Array.isArray(t) ? t : [t], r = [...u, ...n];
13
+ return r.length === 0 ? e : l({
14
+ model: e,
15
+ middleware: r
16
+ });
17
+ }, h = (e) => {
18
+ let t = e?.log ?? ((e) => console.log(`[ai] ${e.phase} model=${e.model}`, e.usage ?? ""));
19
+ return {
20
+ wrapGenerate: async ({ doGenerate: e, model: n }) => {
21
+ let r = await e();
22
+ return t({
23
+ phase: "generate",
24
+ model: n.modelId,
25
+ usage: r.usage
26
+ }), r;
27
+ },
28
+ wrapStream: async ({ doStream: e, model: n }) => (t({
29
+ phase: "stream",
30
+ model: n.modelId
31
+ }), e())
32
+ };
33
+ }, g = {
34
+ inputTokens: {
35
+ total: 0,
36
+ noCache: 0,
37
+ cacheRead: 0,
38
+ cacheWrite: 0
39
+ },
40
+ outputTokens: {
41
+ total: 0,
42
+ text: 0,
43
+ reasoning: 0
44
+ }
45
+ }, _ = (e) => ({
46
+ unified: e,
47
+ raw: void 0
48
+ }), v = class {
49
+ turns;
50
+ cursor = 0;
51
+ calls = [];
52
+ constructor(e) {
53
+ this.turns = e.length > 0 ? e : [{ text: "" }];
54
+ }
55
+ record(e, t) {
56
+ this.calls.push({
57
+ prompt: e,
58
+ tools: t
59
+ });
60
+ }
61
+ next() {
62
+ let e = this.turns[Math.min(this.cursor, this.turns.length - 1)];
63
+ return this.cursor += 1, e;
64
+ }
65
+ reset() {
66
+ this.cursor = 0, this.calls.length = 0;
67
+ }
68
+ }, y = (e) => (e.toolCalls ?? []).map((e, t) => ({
69
+ type: "tool-call",
70
+ toolCallId: `mock_tool_${t}`,
71
+ toolName: e.name,
72
+ input: JSON.stringify(e.input ?? {})
73
+ })), b = (e) => (e.sources ?? []).map((e, t) => e.sourceType === "document" ? {
74
+ type: "source",
75
+ sourceType: "document",
76
+ id: e.id ?? `mock_src_${t}`,
77
+ mediaType: e.mediaType ?? "application/pdf",
78
+ title: e.title ?? "document",
79
+ ...e.filename === void 0 ? {} : { filename: e.filename }
80
+ } : {
81
+ type: "source",
82
+ sourceType: "url",
83
+ id: e.id ?? `mock_src_${t}`,
84
+ url: e.url ?? "https://example.com",
85
+ ...e.title === void 0 ? {} : { title: e.title }
86
+ }), x = (e) => (e.files ?? []).map((e) => ({
87
+ type: "file",
88
+ mediaType: e.mediaType,
89
+ data: {
90
+ type: "data",
91
+ data: e.data
92
+ }
93
+ })), S = (e) => [
94
+ {
95
+ type: "text-start",
96
+ id: "0"
97
+ },
98
+ ...e.split(/(\s+)/).filter((e) => e.length > 0).map((e) => ({
99
+ type: "text-delta",
100
+ id: "0",
101
+ delta: e
102
+ })),
103
+ {
104
+ type: "text-end",
105
+ id: "0"
106
+ }
107
+ ], C = (e) => e.length === 0 ? [] : [
108
+ {
109
+ type: "reasoning-start",
110
+ id: "r0"
111
+ },
112
+ {
113
+ type: "reasoning-delta",
114
+ id: "r0",
115
+ delta: e
116
+ },
117
+ {
118
+ type: "reasoning-end",
119
+ id: "r0"
120
+ }
121
+ ], w = (e, t, n, r) => ({
122
+ specificationVersion: "v4",
123
+ provider: "mock",
124
+ modelId: e,
125
+ supportedUrls: {},
126
+ doGenerate: async ({ prompt: i, tools: a }) => {
127
+ n?.record(i, (a ?? []).map((e) => e.name ?? ""));
128
+ let o = n?.next();
129
+ if (o?.error) throw Error(o.error.message);
130
+ let s = JSON.stringify(i).slice(0, 80), c = o?.toolCalls ?? [];
131
+ return c.length > 0 ? {
132
+ content: c.map((e, t) => ({
133
+ type: "tool-call",
134
+ toolCallId: `mock_tool_${t}`,
135
+ toolName: e.name,
136
+ input: JSON.stringify(e.input ?? {})
137
+ })),
138
+ finishReason: _("tool-calls"),
139
+ usage: g,
140
+ warnings: []
141
+ } : {
142
+ content: [{
143
+ type: "text",
144
+ text: o?.text ?? t ?? `[mock(${e})] ${s}`
145
+ }],
146
+ finishReason: _("stop"),
147
+ usage: g,
148
+ warnings: [],
149
+ ...r === void 0 ? {} : { providerMetadata: r }
150
+ };
151
+ },
152
+ doStream: async ({ prompt: e, tools: r }) => {
153
+ n?.record(e, (r ?? []).map((e) => e.name ?? ""));
154
+ let i = n?.next(), a = (i?.toolCalls?.length ?? 0) > 0, o = i?.text ?? (n ? "" : t ?? "[mock] streamed response"), s = [{
155
+ type: "stream-start",
156
+ warnings: []
157
+ }];
158
+ return i?.error ? (s.push({
159
+ type: "error",
160
+ error: Error(i.error.message)
161
+ }), s.push({
162
+ type: "finish",
163
+ finishReason: _("error"),
164
+ usage: g
165
+ })) : (s.push(...C(i?.reasoning ?? "")), o.length > 0 && s.push(...S(o)), s.push(...b(i ?? {})), s.push(...x(i ?? {})), s.push(...y(i ?? {})), s.push({
166
+ type: "finish",
167
+ finishReason: _(a ? "tool-calls" : "stop"),
168
+ usage: g
169
+ })), { stream: new ReadableStream({ start(e) {
170
+ for (let t of s) e.enqueue(t);
171
+ e.close();
172
+ } }) };
173
+ }
174
+ }), T = "iVBORw0KGgo=", E = "SUQzBAAAAAAAAA==", D = "video/mp4", O = (e, t) => ({
175
+ specificationVersion: "v4",
176
+ provider: "mock",
177
+ modelId: e,
178
+ maxImagesPerCall: 8,
179
+ doGenerate: async ({ n }) => ({
180
+ images: Array.from({ length: Math.max(1, n) }, () => t),
181
+ warnings: [],
182
+ response: {
183
+ timestamp: /* @__PURE__ */ new Date(),
184
+ modelId: e,
185
+ headers: void 0
186
+ },
187
+ usage: {
188
+ inputTokens: 0,
189
+ outputTokens: 0,
190
+ totalTokens: 0
191
+ }
192
+ })
193
+ }), k = (e, t, n) => ({
194
+ specificationVersion: "v4",
195
+ provider: "mock",
196
+ modelId: e,
197
+ doGenerate: async () => ({
198
+ audio: t,
199
+ warnings: [],
200
+ response: {
201
+ timestamp: /* @__PURE__ */ new Date(),
202
+ modelId: e
203
+ },
204
+ ...n === void 0 ? {} : { providerMetadata: n }
205
+ })
206
+ }), A = (e, t, n) => ({
207
+ specificationVersion: "v4",
208
+ provider: "mock",
209
+ modelId: e,
210
+ maxVideosPerCall: 1,
211
+ doGenerate: async () => ({
212
+ videos: [t],
213
+ warnings: [],
214
+ response: {
215
+ timestamp: /* @__PURE__ */ new Date(),
216
+ modelId: e,
217
+ headers: void 0
218
+ },
219
+ ...n === void 0 ? {} : { providerMetadata: n }
220
+ })
221
+ }), j = (e) => e.apiKey !== void 0 || e.baseURL !== void 0, M = (e) => ({
222
+ ...e.apiKey === void 0 ? {} : { apiKey: e.apiKey },
223
+ ...e.baseURL === void 0 ? {} : { baseURL: e.baseURL }
224
+ }), N = (o, s) => m((() => {
225
+ switch (o.name) {
226
+ case "mock": return w(o.model ?? "mock", o.mockText, o.script, o.providerMetadata);
227
+ case "anthropic": return j(o) ? t(M(o))(o.model) : e(o.model);
228
+ case "openai": return j(o) ? n(M(o))(o.model) : r(o.model);
229
+ case "gateway": return j(o) ? i(M(o))(o.model) : a(o.model);
230
+ }
231
+ })(), s), P = (e) => {
232
+ switch (e.name) {
233
+ case "mock": return O(e.model ?? "mock", e.mockMedia?.imageBase64 ?? T);
234
+ case "openai": return j(e) ? n(M(e)).image(e.model) : r.image(e.model);
235
+ case "gateway": return j(e) ? i(M(e)).image(e.model) : a.image(e.model);
236
+ case "anthropic": throw Error("Provider 'anthropic' does not support image generation. Use 'openai' or 'gateway'.");
237
+ }
238
+ }, F = (e) => {
239
+ switch (e.name) {
240
+ case "mock": return k(e.model ?? "mock", e.mockMedia?.audioBase64 ?? E, e.providerMetadata);
241
+ case "openai": return j(e) ? n(M(e)).speech(e.model) : r.speech(e.model);
242
+ case "gateway": return j(e) ? i(M(e)).speech(e.model) : a.speech(e.model);
243
+ case "anthropic": throw Error("Provider 'anthropic' does not support speech generation. Use 'openai' or 'gateway'.");
244
+ }
245
+ }, I = (e) => {
246
+ switch (e.name) {
247
+ case "mock": {
248
+ let t = e.mockMedia, n = t?.videoBase64 === void 0 ? {
249
+ type: "url",
250
+ url: t?.videoUrl ?? `https://mock.local/${e.model ?? "mock"}.mp4`,
251
+ mediaType: D
252
+ } : {
253
+ type: "base64",
254
+ data: t.videoBase64,
255
+ mediaType: D
256
+ };
257
+ return A(e.model ?? "mock", n, e.providerMetadata);
258
+ }
259
+ case "gateway": return j(e) ? i(M(e)).video(e.model) : a.video(e.model);
260
+ case "openai": throw Error("Provider 'openai' does not support video generation. Use 'gateway'.");
261
+ case "anthropic": throw Error("Provider 'anthropic' does not support video generation. Use 'gateway'.");
262
+ }
263
+ }, L = null, R = (e) => {
264
+ let t = L;
265
+ return L = e, t;
266
+ }, z = () => L, B = (e = process.env) => {
267
+ if (L) return L;
268
+ let t = (e.AI_PROVIDER ?? "mock").toLowerCase(), n = e.AI_MODEL;
269
+ switch (t) {
270
+ case "mock": return {
271
+ name: "mock",
272
+ model: n ?? "mock"
273
+ };
274
+ case "anthropic": return {
275
+ name: "anthropic",
276
+ model: n ?? "claude-opus-4-8"
277
+ };
278
+ case "openai": return {
279
+ name: "openai",
280
+ model: n ?? "gpt-5.5"
281
+ };
282
+ case "gateway": {
283
+ let e = n === void 0 ? -1 : n.indexOf("/");
284
+ if (n === void 0 || e <= 0 || e === n.length - 1) throw Error("AI_PROVIDER=gateway requires AI_MODEL set to a 'creator/model' id, e.g. 'openai/gpt-5.5'");
285
+ return {
286
+ name: "gateway",
287
+ model: n
288
+ };
289
+ }
290
+ default: throw Error(`Unknown AI_PROVIDER '${t}'. Expected one of: mock, anthropic, openai, gateway.`);
291
+ }
292
+ };
293
+ //#endregion
294
+ export { N as a, R as c, o as d, s as f, c as g, d as h, P as i, m as l, h as m, z as n, F as o, f as p, B as r, I as s, v as t, p as u };
package/dist/test.d.ts ADDED
@@ -0,0 +1,125 @@
1
+ /** A `MockAi` — the optional injected interface `makeTestContext` accepts.
2
+ * Structurally compatible with `@voltro/testing`'s `MockAi` (no hard dep
3
+ * in either direction). */
4
+ export declare interface MockAi {
5
+ readonly generate?: (...args: ReadonlyArray<unknown>) => unknown;
6
+ readonly generateObject?: (...args: ReadonlyArray<unknown>) => unknown;
7
+ /** Recorded model calls (prompt + tool names), for assertions. */
8
+ readonly calls: ReadonlyArray<{
9
+ readonly prompt: unknown;
10
+ readonly tools: ReadonlyArray<string>;
11
+ }>;
12
+ /** Restore the previous provider override. */
13
+ reset(): void;
14
+ }
15
+
16
+ /**
17
+ * Build a `MockAi` value AND install the provider override, so
18
+ * `makeTestContext({ ai: mockAi({...}) })` covers both the injected `ctx.ai`
19
+ * path and the free-function (`generateText`/`streamText`) path.
20
+ */
21
+ export declare const mockAi: (fixture?: MockAiFixture) => MockAi;
22
+
23
+ /** The fixture shape `useMockAi` / `mockAi` accept. Every field is optional;
24
+ * supply only what the test under exercise needs. */
25
+ export declare interface MockAiFixture {
26
+ /** Canned `generateText` / `ctx.ai.generate` output. */
27
+ readonly generate?: {
28
+ readonly text: string;
29
+ };
30
+ /** Canned `generateObject` / `ctx.ai.generateObject` output. */
31
+ readonly generateObject?: {
32
+ readonly object: unknown;
33
+ };
34
+ /** `streamText` word/chunk sequence (drives token deltas + the free
35
+ * `generateText` mock text on the first turn). */
36
+ readonly stream?: ReadonlyArray<string>;
37
+ /** Scripted tool-call → text turns for the SDK tool loop. Each turn is one
38
+ * `doStream`/`doGenerate` call. Takes precedence over `stream`. */
39
+ readonly turns?: ReadonlyArray<MockTurn>;
40
+ /** Mock model id reported on the override. Default `'mock'`. */
41
+ readonly model?: string;
42
+ }
43
+
44
+ /** A scripted inline file the mock model emits on a turn (usually an image).
45
+ * `data` is base64 (a short ascii string works for assertions). */
46
+ declare interface MockFile {
47
+ readonly mediaType: string;
48
+ readonly data: string;
49
+ }
50
+
51
+ /** A consumable sequence of model turns, plus a recorder for asserting on
52
+ * what the model was asked. Each `doGenerate`/`doStream` advances the
53
+ * cursor by one turn; running past the end repeats the last turn's text
54
+ * (or empties) so a loop that over-steps doesn't hang. */
55
+ declare class MockScript {
56
+ private readonly turns;
57
+ private cursor;
58
+ readonly calls: Array<{
59
+ readonly prompt: unknown;
60
+ readonly tools: ReadonlyArray<string>;
61
+ }>;
62
+ constructor(turns: ReadonlyArray<MockTurn>);
63
+ record(prompt: unknown, tools: ReadonlyArray<string>): void;
64
+ next(): MockTurn;
65
+ reset(): void;
66
+ }
67
+
68
+ /** A scripted citation the mock model emits on a turn (RAG grounding). */
69
+ declare interface MockSource {
70
+ readonly sourceType?: 'url' | 'document';
71
+ readonly id?: string;
72
+ readonly url?: string;
73
+ readonly title?: string;
74
+ readonly mediaType?: string;
75
+ readonly filename?: string;
76
+ }
77
+
78
+ /** A scripted tool call the mock model emits on a turn. */
79
+ declare interface MockToolCall {
80
+ readonly name: string;
81
+ readonly input: unknown;
82
+ }
83
+
84
+ /** One model turn. A turn can carry text (streamed word-by-word), scripted
85
+ * tool calls (which trigger the SDK's tool loop), cited sources, inline files,
86
+ * or an error. A turn with tool calls + no text drives one LLM↔tool
87
+ * round-trip. */
88
+ declare interface MockTurn {
89
+ readonly text?: string;
90
+ /** Reasoning ("thinking") the mock streams before the answer text. */
91
+ readonly reasoning?: string;
92
+ readonly toolCalls?: ReadonlyArray<MockToolCall>;
93
+ readonly sources?: ReadonlyArray<MockSource>;
94
+ readonly files?: ReadonlyArray<MockFile>;
95
+ readonly error?: {
96
+ readonly message: string;
97
+ };
98
+ }
99
+
100
+ /**
101
+ * A test helper: a function that throws the first `n` times it's called, then
102
+ * returns `value`. For exercising retry paths (`Effect.retry`, the SDK's
103
+ * tool-output retry) deterministically.
104
+ *
105
+ * ```ts
106
+ * const flaky = throwNTimes(2, 'ok')
107
+ * flaky() // throws
108
+ * flaky() // throws
109
+ * flaky() // 'ok'
110
+ * ```
111
+ */
112
+ export declare const throwNTimes: <T>(n: number, value: T, error?: (attempt: number) => unknown) => (() => T);
113
+
114
+ /**
115
+ * Install a mock provider override for the duration of a test. Returns the
116
+ * recorded `calls` array (model prompt + tool names) + a `reset()` that
117
+ * restores the previous override. Use in `beforeEach` + `reset()` in
118
+ * `afterEach`.
119
+ */
120
+ export declare const useMockAi: (fixture: MockAiFixture) => {
121
+ readonly calls: MockScript["calls"];
122
+ reset(): void;
123
+ };
124
+
125
+ export { }
package/dist/test.js ADDED
@@ -0,0 +1,40 @@
1
+ import { c as e, t } from "./provider-BOC5wBxx.js";
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));
5
+ return {
6
+ config: {
7
+ name: "mock",
8
+ model: e.model ?? "mock",
9
+ script: r,
10
+ ...e.generate ? { mockText: e.generate.text } : {}
11
+ },
12
+ script: r
13
+ };
14
+ }, i = (t) => {
15
+ let { config: n, script: i } = r(t), a = e(n);
16
+ return {
17
+ calls: i.calls,
18
+ reset: () => {
19
+ e(a);
20
+ }
21
+ };
22
+ }, a = (t = {}) => {
23
+ let { config: n, script: i } = r(t), a = e(n);
24
+ return {
25
+ ...t.generate ? { generate: () => ({ text: t.generate.text }) } : {},
26
+ ...t.generateObject ? { generateObject: () => t.generateObject.object } : {},
27
+ calls: i.calls,
28
+ reset: () => {
29
+ e(a);
30
+ }
31
+ };
32
+ }, o = (e, t, n = (t) => /* @__PURE__ */ Error(`throwNTimes: attempt ${t} of ${e}`)) => {
33
+ let r = 0;
34
+ return () => {
35
+ if (r += 1, r <= e) throw n(r);
36
+ return t;
37
+ };
38
+ };
39
+ //#endregion
40
+ export { a as mockAi, o as throwNTimes, i as useMockAi };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@voltro/ai",
3
+ "version": "0.1.0",
4
+ "description": "Effect-native AI primitives — generateText / generateObject / streamText, tools, agents, media generation, cost tracking + budgets, and provider fallback.",
5
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./agent": {
27
+ "types": "./dist/agent.d.ts",
28
+ "import": "./dist/agent.js",
29
+ "default": "./dist/agent.js"
30
+ },
31
+ "./events": {
32
+ "types": "./dist/events.d.ts",
33
+ "import": "./dist/events.js",
34
+ "default": "./dist/events.js"
35
+ },
36
+ "./test": {
37
+ "types": "./dist/test.d.ts",
38
+ "import": "./dist/test.js",
39
+ "default": "./dist/test.js"
40
+ }
41
+ },
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "sideEffects": false,
46
+ "engines": {
47
+ "node": ">=24.0.0"
48
+ },
49
+ "dependencies": {
50
+ "@ai-sdk/anthropic": "^4.0.12",
51
+ "@ai-sdk/gateway": "^4.0.16",
52
+ "@ai-sdk/openai": "^4.0.11",
53
+ "@ai-sdk/provider": "^4.0.3",
54
+ "@voltro/database": "0.1.0",
55
+ "@voltro/protocol": "0.1.0",
56
+ "ai": "^7.0.22"
57
+ },
58
+ "peerDependencies": {
59
+ "effect": "^3.21.4"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }