@reactive-agents/llm-provider 0.1.1 → 0.3.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/dist/index.js CHANGED
@@ -1,9 +1,1124 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
1
3
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
4
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
5
  }) : x)(function(x) {
4
6
  if (typeof require !== "undefined") return require.apply(this, arguments);
5
7
  throw Error('Dynamic require of "' + x + '" is not supported');
6
8
  });
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // ../../node_modules/whatwg-fetch/fetch.js
18
+ function isDataView(obj) {
19
+ return obj && DataView.prototype.isPrototypeOf(obj);
20
+ }
21
+ function normalizeName(name) {
22
+ if (typeof name !== "string") {
23
+ name = String(name);
24
+ }
25
+ if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === "") {
26
+ throw new TypeError('Invalid character in header field name: "' + name + '"');
27
+ }
28
+ return name.toLowerCase();
29
+ }
30
+ function normalizeValue(value) {
31
+ if (typeof value !== "string") {
32
+ value = String(value);
33
+ }
34
+ return value;
35
+ }
36
+ function iteratorFor(items) {
37
+ var iterator = {
38
+ next: function() {
39
+ var value = items.shift();
40
+ return { done: value === void 0, value };
41
+ }
42
+ };
43
+ if (support.iterable) {
44
+ iterator[Symbol.iterator] = function() {
45
+ return iterator;
46
+ };
47
+ }
48
+ return iterator;
49
+ }
50
+ function Headers2(headers) {
51
+ this.map = {};
52
+ if (headers instanceof Headers2) {
53
+ headers.forEach(function(value, name) {
54
+ this.append(name, value);
55
+ }, this);
56
+ } else if (Array.isArray(headers)) {
57
+ headers.forEach(function(header) {
58
+ if (header.length != 2) {
59
+ throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
60
+ }
61
+ this.append(header[0], header[1]);
62
+ }, this);
63
+ } else if (headers) {
64
+ Object.getOwnPropertyNames(headers).forEach(function(name) {
65
+ this.append(name, headers[name]);
66
+ }, this);
67
+ }
68
+ }
69
+ function consumed(body) {
70
+ if (body._noBody) return;
71
+ if (body.bodyUsed) {
72
+ return Promise.reject(new TypeError("Already read"));
73
+ }
74
+ body.bodyUsed = true;
75
+ }
76
+ function fileReaderReady(reader) {
77
+ return new Promise(function(resolve2, reject) {
78
+ reader.onload = function() {
79
+ resolve2(reader.result);
80
+ };
81
+ reader.onerror = function() {
82
+ reject(reader.error);
83
+ };
84
+ });
85
+ }
86
+ function readBlobAsArrayBuffer(blob) {
87
+ var reader = new FileReader();
88
+ var promise = fileReaderReady(reader);
89
+ reader.readAsArrayBuffer(blob);
90
+ return promise;
91
+ }
92
+ function readBlobAsText(blob) {
93
+ var reader = new FileReader();
94
+ var promise = fileReaderReady(reader);
95
+ var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
96
+ var encoding = match ? match[1] : "utf-8";
97
+ reader.readAsText(blob, encoding);
98
+ return promise;
99
+ }
100
+ function readArrayBufferAsText(buf) {
101
+ var view = new Uint8Array(buf);
102
+ var chars = new Array(view.length);
103
+ for (var i = 0; i < view.length; i++) {
104
+ chars[i] = String.fromCharCode(view[i]);
105
+ }
106
+ return chars.join("");
107
+ }
108
+ function bufferClone(buf) {
109
+ if (buf.slice) {
110
+ return buf.slice(0);
111
+ } else {
112
+ var view = new Uint8Array(buf.byteLength);
113
+ view.set(new Uint8Array(buf));
114
+ return view.buffer;
115
+ }
116
+ }
117
+ function Body() {
118
+ this.bodyUsed = false;
119
+ this._initBody = function(body) {
120
+ this.bodyUsed = this.bodyUsed;
121
+ this._bodyInit = body;
122
+ if (!body) {
123
+ this._noBody = true;
124
+ this._bodyText = "";
125
+ } else if (typeof body === "string") {
126
+ this._bodyText = body;
127
+ } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
128
+ this._bodyBlob = body;
129
+ } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
130
+ this._bodyFormData = body;
131
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
132
+ this._bodyText = body.toString();
133
+ } else if (support.arrayBuffer && support.blob && isDataView(body)) {
134
+ this._bodyArrayBuffer = bufferClone(body.buffer);
135
+ this._bodyInit = new Blob([this._bodyArrayBuffer]);
136
+ } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
137
+ this._bodyArrayBuffer = bufferClone(body);
138
+ } else {
139
+ this._bodyText = body = Object.prototype.toString.call(body);
140
+ }
141
+ if (!this.headers.get("content-type")) {
142
+ if (typeof body === "string") {
143
+ this.headers.set("content-type", "text/plain;charset=UTF-8");
144
+ } else if (this._bodyBlob && this._bodyBlob.type) {
145
+ this.headers.set("content-type", this._bodyBlob.type);
146
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
147
+ this.headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
148
+ }
149
+ }
150
+ };
151
+ if (support.blob) {
152
+ this.blob = function() {
153
+ var rejected = consumed(this);
154
+ if (rejected) {
155
+ return rejected;
156
+ }
157
+ if (this._bodyBlob) {
158
+ return Promise.resolve(this._bodyBlob);
159
+ } else if (this._bodyArrayBuffer) {
160
+ return Promise.resolve(new Blob([this._bodyArrayBuffer]));
161
+ } else if (this._bodyFormData) {
162
+ throw new Error("could not read FormData body as blob");
163
+ } else {
164
+ return Promise.resolve(new Blob([this._bodyText]));
165
+ }
166
+ };
167
+ }
168
+ this.arrayBuffer = function() {
169
+ if (this._bodyArrayBuffer) {
170
+ var isConsumed = consumed(this);
171
+ if (isConsumed) {
172
+ return isConsumed;
173
+ } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
174
+ return Promise.resolve(
175
+ this._bodyArrayBuffer.buffer.slice(
176
+ this._bodyArrayBuffer.byteOffset,
177
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
178
+ )
179
+ );
180
+ } else {
181
+ return Promise.resolve(this._bodyArrayBuffer);
182
+ }
183
+ } else if (support.blob) {
184
+ return this.blob().then(readBlobAsArrayBuffer);
185
+ } else {
186
+ throw new Error("could not read as ArrayBuffer");
187
+ }
188
+ };
189
+ this.text = function() {
190
+ var rejected = consumed(this);
191
+ if (rejected) {
192
+ return rejected;
193
+ }
194
+ if (this._bodyBlob) {
195
+ return readBlobAsText(this._bodyBlob);
196
+ } else if (this._bodyArrayBuffer) {
197
+ return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
198
+ } else if (this._bodyFormData) {
199
+ throw new Error("could not read FormData body as text");
200
+ } else {
201
+ return Promise.resolve(this._bodyText);
202
+ }
203
+ };
204
+ if (support.formData) {
205
+ this.formData = function() {
206
+ return this.text().then(decode);
207
+ };
208
+ }
209
+ this.json = function() {
210
+ return this.text().then(JSON.parse);
211
+ };
212
+ return this;
213
+ }
214
+ function normalizeMethod(method) {
215
+ var upcased = method.toUpperCase();
216
+ return methods.indexOf(upcased) > -1 ? upcased : method;
217
+ }
218
+ function Request(input, options) {
219
+ if (!(this instanceof Request)) {
220
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
221
+ }
222
+ options = options || {};
223
+ var body = options.body;
224
+ if (input instanceof Request) {
225
+ if (input.bodyUsed) {
226
+ throw new TypeError("Already read");
227
+ }
228
+ this.url = input.url;
229
+ this.credentials = input.credentials;
230
+ if (!options.headers) {
231
+ this.headers = new Headers2(input.headers);
232
+ }
233
+ this.method = input.method;
234
+ this.mode = input.mode;
235
+ this.signal = input.signal;
236
+ if (!body && input._bodyInit != null) {
237
+ body = input._bodyInit;
238
+ input.bodyUsed = true;
239
+ }
240
+ } else {
241
+ this.url = String(input);
242
+ }
243
+ this.credentials = options.credentials || this.credentials || "same-origin";
244
+ if (options.headers || !this.headers) {
245
+ this.headers = new Headers2(options.headers);
246
+ }
247
+ this.method = normalizeMethod(options.method || this.method || "GET");
248
+ this.mode = options.mode || this.mode || null;
249
+ this.signal = options.signal || this.signal || (function() {
250
+ if ("AbortController" in g) {
251
+ var ctrl = new AbortController();
252
+ return ctrl.signal;
253
+ }
254
+ })();
255
+ this.referrer = null;
256
+ if ((this.method === "GET" || this.method === "HEAD") && body) {
257
+ throw new TypeError("Body not allowed for GET or HEAD requests");
258
+ }
259
+ this._initBody(body);
260
+ if (this.method === "GET" || this.method === "HEAD") {
261
+ if (options.cache === "no-store" || options.cache === "no-cache") {
262
+ var reParamSearch = /([?&])_=[^&]*/;
263
+ if (reParamSearch.test(this.url)) {
264
+ this.url = this.url.replace(reParamSearch, "$1_=" + (/* @__PURE__ */ new Date()).getTime());
265
+ } else {
266
+ var reQueryString = /\?/;
267
+ this.url += (reQueryString.test(this.url) ? "&" : "?") + "_=" + (/* @__PURE__ */ new Date()).getTime();
268
+ }
269
+ }
270
+ }
271
+ }
272
+ function decode(body) {
273
+ var form = new FormData();
274
+ body.trim().split("&").forEach(function(bytes) {
275
+ if (bytes) {
276
+ var split = bytes.split("=");
277
+ var name = split.shift().replace(/\+/g, " ");
278
+ var value = split.join("=").replace(/\+/g, " ");
279
+ form.append(decodeURIComponent(name), decodeURIComponent(value));
280
+ }
281
+ });
282
+ return form;
283
+ }
284
+ function parseHeaders(rawHeaders) {
285
+ var headers = new Headers2();
286
+ var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
287
+ preProcessedHeaders.split("\r").map(function(header) {
288
+ return header.indexOf("\n") === 0 ? header.substr(1, header.length) : header;
289
+ }).forEach(function(line) {
290
+ var parts = line.split(":");
291
+ var key = parts.shift().trim();
292
+ if (key) {
293
+ var value = parts.join(":").trim();
294
+ try {
295
+ headers.append(key, value);
296
+ } catch (error) {
297
+ console.warn("Response " + error.message);
298
+ }
299
+ }
300
+ });
301
+ return headers;
302
+ }
303
+ function Response(bodyInit, options) {
304
+ if (!(this instanceof Response)) {
305
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
306
+ }
307
+ if (!options) {
308
+ options = {};
309
+ }
310
+ this.type = "default";
311
+ this.status = options.status === void 0 ? 200 : options.status;
312
+ if (this.status < 200 || this.status > 599) {
313
+ throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
314
+ }
315
+ this.ok = this.status >= 200 && this.status < 300;
316
+ this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
317
+ this.headers = new Headers2(options.headers);
318
+ this.url = options.url || "";
319
+ this._initBody(bodyInit);
320
+ }
321
+ function fetch2(input, init) {
322
+ return new Promise(function(resolve2, reject) {
323
+ var request = new Request(input, init);
324
+ if (request.signal && request.signal.aborted) {
325
+ return reject(new DOMException("Aborted", "AbortError"));
326
+ }
327
+ var xhr = new XMLHttpRequest();
328
+ function abortXhr() {
329
+ xhr.abort();
330
+ }
331
+ xhr.onload = function() {
332
+ var options = {
333
+ statusText: xhr.statusText,
334
+ headers: parseHeaders(xhr.getAllResponseHeaders() || "")
335
+ };
336
+ if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
337
+ options.status = 200;
338
+ } else {
339
+ options.status = xhr.status;
340
+ }
341
+ options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
342
+ var body = "response" in xhr ? xhr.response : xhr.responseText;
343
+ setTimeout(function() {
344
+ resolve2(new Response(body, options));
345
+ }, 0);
346
+ };
347
+ xhr.onerror = function() {
348
+ setTimeout(function() {
349
+ reject(new TypeError("Network request failed"));
350
+ }, 0);
351
+ };
352
+ xhr.ontimeout = function() {
353
+ setTimeout(function() {
354
+ reject(new TypeError("Network request timed out"));
355
+ }, 0);
356
+ };
357
+ xhr.onabort = function() {
358
+ setTimeout(function() {
359
+ reject(new DOMException("Aborted", "AbortError"));
360
+ }, 0);
361
+ };
362
+ function fixUrl(url) {
363
+ try {
364
+ return url === "" && g.location.href ? g.location.href : url;
365
+ } catch (e) {
366
+ return url;
367
+ }
368
+ }
369
+ xhr.open(request.method, fixUrl(request.url), true);
370
+ if (request.credentials === "include") {
371
+ xhr.withCredentials = true;
372
+ } else if (request.credentials === "omit") {
373
+ xhr.withCredentials = false;
374
+ }
375
+ if ("responseType" in xhr) {
376
+ if (support.blob) {
377
+ xhr.responseType = "blob";
378
+ } else if (support.arrayBuffer) {
379
+ xhr.responseType = "arraybuffer";
380
+ }
381
+ }
382
+ if (init && typeof init.headers === "object" && !(init.headers instanceof Headers2 || g.Headers && init.headers instanceof g.Headers)) {
383
+ var names = [];
384
+ Object.getOwnPropertyNames(init.headers).forEach(function(name) {
385
+ names.push(normalizeName(name));
386
+ xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
387
+ });
388
+ request.headers.forEach(function(value, name) {
389
+ if (names.indexOf(name) === -1) {
390
+ xhr.setRequestHeader(name, value);
391
+ }
392
+ });
393
+ } else {
394
+ request.headers.forEach(function(value, name) {
395
+ xhr.setRequestHeader(name, value);
396
+ });
397
+ }
398
+ if (request.signal) {
399
+ request.signal.addEventListener("abort", abortXhr);
400
+ xhr.onreadystatechange = function() {
401
+ if (xhr.readyState === 4) {
402
+ request.signal.removeEventListener("abort", abortXhr);
403
+ }
404
+ };
405
+ }
406
+ xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit);
407
+ });
408
+ }
409
+ var g, support, viewClasses, isArrayBufferView, methods, redirectStatuses, DOMException;
410
+ var init_fetch = __esm({
411
+ "../../node_modules/whatwg-fetch/fetch.js"() {
412
+ "use strict";
413
+ g = typeof globalThis !== "undefined" && globalThis || typeof self !== "undefined" && self || // eslint-disable-next-line no-undef
414
+ typeof global !== "undefined" && global || {};
415
+ support = {
416
+ searchParams: "URLSearchParams" in g,
417
+ iterable: "Symbol" in g && "iterator" in Symbol,
418
+ blob: "FileReader" in g && "Blob" in g && (function() {
419
+ try {
420
+ new Blob();
421
+ return true;
422
+ } catch (e) {
423
+ return false;
424
+ }
425
+ })(),
426
+ formData: "FormData" in g,
427
+ arrayBuffer: "ArrayBuffer" in g
428
+ };
429
+ if (support.arrayBuffer) {
430
+ viewClasses = [
431
+ "[object Int8Array]",
432
+ "[object Uint8Array]",
433
+ "[object Uint8ClampedArray]",
434
+ "[object Int16Array]",
435
+ "[object Uint16Array]",
436
+ "[object Int32Array]",
437
+ "[object Uint32Array]",
438
+ "[object Float32Array]",
439
+ "[object Float64Array]"
440
+ ];
441
+ isArrayBufferView = ArrayBuffer.isView || function(obj) {
442
+ return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
443
+ };
444
+ }
445
+ Headers2.prototype.append = function(name, value) {
446
+ name = normalizeName(name);
447
+ value = normalizeValue(value);
448
+ var oldValue = this.map[name];
449
+ this.map[name] = oldValue ? oldValue + ", " + value : value;
450
+ };
451
+ Headers2.prototype["delete"] = function(name) {
452
+ delete this.map[normalizeName(name)];
453
+ };
454
+ Headers2.prototype.get = function(name) {
455
+ name = normalizeName(name);
456
+ return this.has(name) ? this.map[name] : null;
457
+ };
458
+ Headers2.prototype.has = function(name) {
459
+ return this.map.hasOwnProperty(normalizeName(name));
460
+ };
461
+ Headers2.prototype.set = function(name, value) {
462
+ this.map[normalizeName(name)] = normalizeValue(value);
463
+ };
464
+ Headers2.prototype.forEach = function(callback, thisArg) {
465
+ for (var name in this.map) {
466
+ if (this.map.hasOwnProperty(name)) {
467
+ callback.call(thisArg, this.map[name], name, this);
468
+ }
469
+ }
470
+ };
471
+ Headers2.prototype.keys = function() {
472
+ var items = [];
473
+ this.forEach(function(value, name) {
474
+ items.push(name);
475
+ });
476
+ return iteratorFor(items);
477
+ };
478
+ Headers2.prototype.values = function() {
479
+ var items = [];
480
+ this.forEach(function(value) {
481
+ items.push(value);
482
+ });
483
+ return iteratorFor(items);
484
+ };
485
+ Headers2.prototype.entries = function() {
486
+ var items = [];
487
+ this.forEach(function(value, name) {
488
+ items.push([name, value]);
489
+ });
490
+ return iteratorFor(items);
491
+ };
492
+ if (support.iterable) {
493
+ Headers2.prototype[Symbol.iterator] = Headers2.prototype.entries;
494
+ }
495
+ methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
496
+ Request.prototype.clone = function() {
497
+ return new Request(this, { body: this._bodyInit });
498
+ };
499
+ Body.call(Request.prototype);
500
+ Body.call(Response.prototype);
501
+ Response.prototype.clone = function() {
502
+ return new Response(this._bodyInit, {
503
+ status: this.status,
504
+ statusText: this.statusText,
505
+ headers: new Headers2(this.headers),
506
+ url: this.url
507
+ });
508
+ };
509
+ Response.error = function() {
510
+ var response = new Response(null, { status: 200, statusText: "" });
511
+ response.ok = false;
512
+ response.status = 0;
513
+ response.type = "error";
514
+ return response;
515
+ };
516
+ redirectStatuses = [301, 302, 303, 307, 308];
517
+ Response.redirect = function(url, status) {
518
+ if (redirectStatuses.indexOf(status) === -1) {
519
+ throw new RangeError("Invalid status code");
520
+ }
521
+ return new Response(null, { status, headers: { location: url } });
522
+ };
523
+ DOMException = g.DOMException;
524
+ try {
525
+ new DOMException();
526
+ } catch (err) {
527
+ DOMException = function(message, name) {
528
+ this.message = message;
529
+ this.name = name;
530
+ var error = Error(message);
531
+ this.stack = error.stack;
532
+ };
533
+ DOMException.prototype = Object.create(Error.prototype);
534
+ DOMException.prototype.constructor = DOMException;
535
+ }
536
+ fetch2.polyfill = true;
537
+ if (!g.fetch) {
538
+ g.fetch = fetch2;
539
+ g.Headers = Headers2;
540
+ g.Request = Request;
541
+ g.Response = Response;
542
+ }
543
+ }
544
+ });
545
+
546
+ // ../../node_modules/ollama/dist/browser.mjs
547
+ function getPlatform() {
548
+ if (typeof window !== "undefined" && window.navigator) {
549
+ const nav = navigator;
550
+ if ("userAgentData" in nav && nav.userAgentData?.platform) {
551
+ return `${nav.userAgentData.platform.toLowerCase()} Browser/${navigator.userAgent};`;
552
+ }
553
+ if (navigator.platform) {
554
+ return `${navigator.platform.toLowerCase()} Browser/${navigator.userAgent};`;
555
+ }
556
+ return `unknown Browser/${navigator.userAgent};`;
557
+ } else if (typeof process !== "undefined") {
558
+ return `${process.arch} ${process.platform} Node.js/${process.version}`;
559
+ }
560
+ return "";
561
+ }
562
+ function normalizeHeaders(headers) {
563
+ if (headers instanceof Headers) {
564
+ const obj = {};
565
+ headers.forEach((value, key) => {
566
+ obj[key] = value;
567
+ });
568
+ return obj;
569
+ } else if (Array.isArray(headers)) {
570
+ return Object.fromEntries(headers);
571
+ } else {
572
+ return headers || {};
573
+ }
574
+ }
575
+ var defaultPort, defaultHost, version, __defProp$1, __defNormalProp$1, __publicField$1, ResponseError, AbortableAsyncIterator, checkOk, readEnvVar, fetchWithHeaders, get, post, del, parseJSON, formatHost, __defProp2, __defNormalProp, __publicField, Ollama$1, browser;
576
+ var init_browser = __esm({
577
+ "../../node_modules/ollama/dist/browser.mjs"() {
578
+ "use strict";
579
+ init_fetch();
580
+ defaultPort = "11434";
581
+ defaultHost = `http://127.0.0.1:${defaultPort}`;
582
+ version = "0.6.3";
583
+ __defProp$1 = Object.defineProperty;
584
+ __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
585
+ __publicField$1 = (obj, key, value) => {
586
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
587
+ return value;
588
+ };
589
+ ResponseError = class _ResponseError extends Error {
590
+ constructor(error, status_code) {
591
+ super(error);
592
+ this.error = error;
593
+ this.status_code = status_code;
594
+ this.name = "ResponseError";
595
+ if (Error.captureStackTrace) {
596
+ Error.captureStackTrace(this, _ResponseError);
597
+ }
598
+ }
599
+ };
600
+ AbortableAsyncIterator = class {
601
+ constructor(abortController, itr, doneCallback) {
602
+ __publicField$1(this, "abortController");
603
+ __publicField$1(this, "itr");
604
+ __publicField$1(this, "doneCallback");
605
+ this.abortController = abortController;
606
+ this.itr = itr;
607
+ this.doneCallback = doneCallback;
608
+ }
609
+ abort() {
610
+ this.abortController.abort();
611
+ }
612
+ async *[Symbol.asyncIterator]() {
613
+ for await (const message of this.itr) {
614
+ if ("error" in message) {
615
+ throw new Error(message.error);
616
+ }
617
+ yield message;
618
+ if (message.done || message.status === "success") {
619
+ this.doneCallback();
620
+ return;
621
+ }
622
+ }
623
+ throw new Error("Did not receive done or success response in stream.");
624
+ }
625
+ };
626
+ checkOk = async (response) => {
627
+ if (response.ok) {
628
+ return;
629
+ }
630
+ let message = `Error ${response.status}: ${response.statusText}`;
631
+ let errorData = null;
632
+ if (response.headers.get("content-type")?.includes("application/json")) {
633
+ try {
634
+ errorData = await response.json();
635
+ message = errorData.error || message;
636
+ } catch (error) {
637
+ console.log("Failed to parse error response as JSON");
638
+ }
639
+ } else {
640
+ try {
641
+ console.log("Getting text from response");
642
+ const textResponse = await response.text();
643
+ message = textResponse || message;
644
+ } catch (error) {
645
+ console.log("Failed to get text from error response");
646
+ }
647
+ }
648
+ throw new ResponseError(message, response.status);
649
+ };
650
+ readEnvVar = (obj, key) => {
651
+ return obj[key];
652
+ };
653
+ fetchWithHeaders = async (fetch3, url, options = {}) => {
654
+ const defaultHeaders = {
655
+ "Content-Type": "application/json",
656
+ Accept: "application/json",
657
+ "User-Agent": `ollama-js/${version} (${getPlatform()})`
658
+ };
659
+ options.headers = normalizeHeaders(options.headers);
660
+ try {
661
+ const parsed = new URL(url);
662
+ if (parsed.protocol === "https:" && parsed.hostname === "ollama.com") {
663
+ const apiKey = typeof process === "object" && process !== null && typeof process.env === "object" && process.env !== null ? readEnvVar(process.env, "OLLAMA_API_KEY") : void 0;
664
+ const authorization = options.headers["authorization"] || options.headers["Authorization"];
665
+ if (!authorization && apiKey) {
666
+ options.headers["Authorization"] = `Bearer ${apiKey}`;
667
+ }
668
+ }
669
+ } catch (error) {
670
+ console.error("error parsing url", error);
671
+ }
672
+ const customHeaders = Object.fromEntries(
673
+ Object.entries(options.headers).filter(
674
+ ([key]) => !Object.keys(defaultHeaders).some(
675
+ (defaultKey) => defaultKey.toLowerCase() === key.toLowerCase()
676
+ )
677
+ )
678
+ );
679
+ options.headers = {
680
+ ...defaultHeaders,
681
+ ...customHeaders
682
+ };
683
+ return fetch3(url, options);
684
+ };
685
+ get = async (fetch3, host, options) => {
686
+ const response = await fetchWithHeaders(fetch3, host, {
687
+ headers: options?.headers
688
+ });
689
+ await checkOk(response);
690
+ return response;
691
+ };
692
+ post = async (fetch3, host, data, options) => {
693
+ const isRecord = (input) => {
694
+ return input !== null && typeof input === "object" && !Array.isArray(input);
695
+ };
696
+ const formattedData = isRecord(data) ? JSON.stringify(data) : data;
697
+ const response = await fetchWithHeaders(fetch3, host, {
698
+ method: "POST",
699
+ body: formattedData,
700
+ signal: options?.signal,
701
+ headers: options?.headers
702
+ });
703
+ await checkOk(response);
704
+ return response;
705
+ };
706
+ del = async (fetch3, host, data, options) => {
707
+ const response = await fetchWithHeaders(fetch3, host, {
708
+ method: "DELETE",
709
+ body: JSON.stringify(data),
710
+ headers: options?.headers
711
+ });
712
+ await checkOk(response);
713
+ return response;
714
+ };
715
+ parseJSON = async function* (itr) {
716
+ const decoder = new TextDecoder("utf-8");
717
+ let buffer = "";
718
+ const reader = itr.getReader();
719
+ while (true) {
720
+ const { done, value: chunk } = await reader.read();
721
+ if (done) {
722
+ break;
723
+ }
724
+ buffer += decoder.decode(chunk, { stream: true });
725
+ const parts = buffer.split("\n");
726
+ buffer = parts.pop() ?? "";
727
+ for (const part of parts) {
728
+ try {
729
+ yield JSON.parse(part);
730
+ } catch (error) {
731
+ console.warn("invalid json: ", part);
732
+ }
733
+ }
734
+ }
735
+ buffer += decoder.decode();
736
+ for (const part of buffer.split("\n").filter((p) => p !== "")) {
737
+ try {
738
+ yield JSON.parse(part);
739
+ } catch (error) {
740
+ console.warn("invalid json: ", part);
741
+ }
742
+ }
743
+ };
744
+ formatHost = (host) => {
745
+ if (!host) {
746
+ return defaultHost;
747
+ }
748
+ let isExplicitProtocol = host.includes("://");
749
+ if (host.startsWith(":")) {
750
+ host = `http://127.0.0.1${host}`;
751
+ isExplicitProtocol = true;
752
+ }
753
+ if (!isExplicitProtocol) {
754
+ host = `http://${host}`;
755
+ }
756
+ const url = new URL(host);
757
+ let port = url.port;
758
+ if (!port) {
759
+ if (!isExplicitProtocol) {
760
+ port = defaultPort;
761
+ } else {
762
+ port = url.protocol === "https:" ? "443" : "80";
763
+ }
764
+ }
765
+ let auth = "";
766
+ if (url.username) {
767
+ auth = url.username;
768
+ if (url.password) {
769
+ auth += `:${url.password}`;
770
+ }
771
+ auth += "@";
772
+ }
773
+ let formattedHost = `${url.protocol}//${auth}${url.hostname}:${port}${url.pathname}`;
774
+ if (formattedHost.endsWith("/")) {
775
+ formattedHost = formattedHost.slice(0, -1);
776
+ }
777
+ return formattedHost;
778
+ };
779
+ __defProp2 = Object.defineProperty;
780
+ __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
781
+ __publicField = (obj, key, value) => {
782
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
783
+ return value;
784
+ };
785
+ Ollama$1 = class Ollama {
786
+ constructor(config) {
787
+ __publicField(this, "config");
788
+ __publicField(this, "fetch");
789
+ __publicField(this, "ongoingStreamedRequests", []);
790
+ this.config = {
791
+ host: "",
792
+ headers: config?.headers
793
+ };
794
+ if (!config?.proxy) {
795
+ this.config.host = formatHost(config?.host ?? defaultHost);
796
+ }
797
+ this.fetch = config?.fetch ?? fetch;
798
+ }
799
+ // Abort any ongoing streamed requests to Ollama
800
+ abort() {
801
+ for (const request of this.ongoingStreamedRequests) {
802
+ request.abort();
803
+ }
804
+ this.ongoingStreamedRequests.length = 0;
805
+ }
806
+ /**
807
+ * Processes a request to the Ollama server. If the request is streamable, it will return a
808
+ * AbortableAsyncIterator that yields the response messages. Otherwise, it will return the response
809
+ * object.
810
+ * @param endpoint {string} - The endpoint to send the request to.
811
+ * @param request {object} - The request object to send to the endpoint.
812
+ * @protected {T | AbortableAsyncIterator<T>} - The response object or a AbortableAsyncIterator that yields
813
+ * response messages.
814
+ * @throws {Error} - If the response body is missing or if the response is an error.
815
+ * @returns {Promise<T | AbortableAsyncIterator<T>>} - The response object or a AbortableAsyncIterator that yields the streamed response.
816
+ */
817
+ async processStreamableRequest(endpoint, request) {
818
+ request.stream = request.stream ?? false;
819
+ const host = `${this.config.host}/api/${endpoint}`;
820
+ if (request.stream) {
821
+ const abortController = new AbortController();
822
+ const response2 = await post(this.fetch, host, request, {
823
+ signal: abortController.signal,
824
+ headers: this.config.headers
825
+ });
826
+ if (!response2.body) {
827
+ throw new Error("Missing body");
828
+ }
829
+ const itr = parseJSON(response2.body);
830
+ const abortableAsyncIterator = new AbortableAsyncIterator(
831
+ abortController,
832
+ itr,
833
+ () => {
834
+ const i = this.ongoingStreamedRequests.indexOf(abortableAsyncIterator);
835
+ if (i > -1) {
836
+ this.ongoingStreamedRequests.splice(i, 1);
837
+ }
838
+ }
839
+ );
840
+ this.ongoingStreamedRequests.push(abortableAsyncIterator);
841
+ return abortableAsyncIterator;
842
+ }
843
+ const response = await post(this.fetch, host, request, {
844
+ headers: this.config.headers
845
+ });
846
+ return await response.json();
847
+ }
848
+ /**
849
+ * Encodes an image to base64 if it is a Uint8Array.
850
+ * @param image {Uint8Array | string} - The image to encode.
851
+ * @returns {Promise<string>} - The base64 encoded image.
852
+ */
853
+ async encodeImage(image) {
854
+ if (typeof image !== "string") {
855
+ const uint8Array = new Uint8Array(image);
856
+ let byteString = "";
857
+ const len = uint8Array.byteLength;
858
+ for (let i = 0; i < len; i++) {
859
+ byteString += String.fromCharCode(uint8Array[i]);
860
+ }
861
+ return btoa(byteString);
862
+ }
863
+ return image;
864
+ }
865
+ /**
866
+ * Generates a response from a text prompt.
867
+ * @param request {GenerateRequest} - The request object.
868
+ * @returns {Promise<GenerateResponse | AbortableAsyncIterator<GenerateResponse>>} - The response object or
869
+ * an AbortableAsyncIterator that yields response messages.
870
+ */
871
+ async generate(request) {
872
+ if (request.images) {
873
+ request.images = await Promise.all(request.images.map(this.encodeImage.bind(this)));
874
+ }
875
+ return this.processStreamableRequest("generate", request);
876
+ }
877
+ /**
878
+ * Chats with the model. The request object can contain messages with images that are either
879
+ * Uint8Arrays or base64 encoded strings. The images will be base64 encoded before sending the
880
+ * request.
881
+ * @param request {ChatRequest} - The request object.
882
+ * @returns {Promise<ChatResponse | AbortableAsyncIterator<ChatResponse>>} - The response object or an
883
+ * AbortableAsyncIterator that yields response messages.
884
+ */
885
+ async chat(request) {
886
+ if (request.messages) {
887
+ for (const message of request.messages) {
888
+ if (message.images) {
889
+ message.images = await Promise.all(
890
+ message.images.map(this.encodeImage.bind(this))
891
+ );
892
+ }
893
+ }
894
+ }
895
+ return this.processStreamableRequest("chat", request);
896
+ }
897
+ /**
898
+ * Creates a new model from a stream of data.
899
+ * @param request {CreateRequest} - The request object.
900
+ * @returns {Promise<ProgressResponse | AbortableAsyncIterator<ProgressResponse>>} - The response object or a stream of progress responses.
901
+ */
902
+ async create(request) {
903
+ return this.processStreamableRequest("create", {
904
+ ...request
905
+ });
906
+ }
907
+ /**
908
+ * Pulls a model from the Ollama registry. The request object can contain a stream flag to indicate if the
909
+ * response should be streamed.
910
+ * @param request {PullRequest} - The request object.
911
+ * @returns {Promise<ProgressResponse | AbortableAsyncIterator<ProgressResponse>>} - The response object or
912
+ * an AbortableAsyncIterator that yields response messages.
913
+ */
914
+ async pull(request) {
915
+ return this.processStreamableRequest("pull", {
916
+ name: request.model,
917
+ stream: request.stream,
918
+ insecure: request.insecure
919
+ });
920
+ }
921
+ /**
922
+ * Pushes a model to the Ollama registry. The request object can contain a stream flag to indicate if the
923
+ * response should be streamed.
924
+ * @param request {PushRequest} - The request object.
925
+ * @returns {Promise<ProgressResponse | AbortableAsyncIterator<ProgressResponse>>} - The response object or
926
+ * an AbortableAsyncIterator that yields response messages.
927
+ */
928
+ async push(request) {
929
+ return this.processStreamableRequest("push", {
930
+ name: request.model,
931
+ stream: request.stream,
932
+ insecure: request.insecure
933
+ });
934
+ }
935
+ /**
936
+ * Deletes a model from the server. The request object should contain the name of the model to
937
+ * delete.
938
+ * @param request {DeleteRequest} - The request object.
939
+ * @returns {Promise<StatusResponse>} - The response object.
940
+ */
941
+ async delete(request) {
942
+ await del(
943
+ this.fetch,
944
+ `${this.config.host}/api/delete`,
945
+ { name: request.model },
946
+ { headers: this.config.headers }
947
+ );
948
+ return { status: "success" };
949
+ }
950
+ /**
951
+ * Copies a model from one name to another. The request object should contain the name of the
952
+ * model to copy and the new name.
953
+ * @param request {CopyRequest} - The request object.
954
+ * @returns {Promise<StatusResponse>} - The response object.
955
+ */
956
+ async copy(request) {
957
+ await post(this.fetch, `${this.config.host}/api/copy`, { ...request }, {
958
+ headers: this.config.headers
959
+ });
960
+ return { status: "success" };
961
+ }
962
+ /**
963
+ * Lists the models on the server.
964
+ * @returns {Promise<ListResponse>} - The response object.
965
+ * @throws {Error} - If the response body is missing.
966
+ */
967
+ async list() {
968
+ const response = await get(this.fetch, `${this.config.host}/api/tags`, {
969
+ headers: this.config.headers
970
+ });
971
+ return await response.json();
972
+ }
973
+ /**
974
+ * Shows the metadata of a model. The request object should contain the name of the model.
975
+ * @param request {ShowRequest} - The request object.
976
+ * @returns {Promise<ShowResponse>} - The response object.
977
+ */
978
+ async show(request) {
979
+ const response = await post(this.fetch, `${this.config.host}/api/show`, {
980
+ ...request
981
+ }, {
982
+ headers: this.config.headers
983
+ });
984
+ return await response.json();
985
+ }
986
+ /**
987
+ * Embeds text input into vectors.
988
+ * @param request {EmbedRequest} - The request object.
989
+ * @returns {Promise<EmbedResponse>} - The response object.
990
+ */
991
+ async embed(request) {
992
+ const response = await post(this.fetch, `${this.config.host}/api/embed`, {
993
+ ...request
994
+ }, {
995
+ headers: this.config.headers
996
+ });
997
+ return await response.json();
998
+ }
999
+ /**
1000
+ * Embeds a text prompt into a vector.
1001
+ * @param request {EmbeddingsRequest} - The request object.
1002
+ * @returns {Promise<EmbeddingsResponse>} - The response object.
1003
+ */
1004
+ async embeddings(request) {
1005
+ const response = await post(this.fetch, `${this.config.host}/api/embeddings`, {
1006
+ ...request
1007
+ }, {
1008
+ headers: this.config.headers
1009
+ });
1010
+ return await response.json();
1011
+ }
1012
+ /**
1013
+ * Lists the running models on the server
1014
+ * @returns {Promise<ListResponse>} - The response object.
1015
+ * @throws {Error} - If the response body is missing.
1016
+ */
1017
+ async ps() {
1018
+ const response = await get(this.fetch, `${this.config.host}/api/ps`, {
1019
+ headers: this.config.headers
1020
+ });
1021
+ return await response.json();
1022
+ }
1023
+ /**
1024
+ * Returns the Ollama server version.
1025
+ * @returns {Promise<VersionResponse>} - The server version object.
1026
+ */
1027
+ async version() {
1028
+ const response = await get(this.fetch, `${this.config.host}/api/version`, {
1029
+ headers: this.config.headers
1030
+ });
1031
+ return await response.json();
1032
+ }
1033
+ /**
1034
+ * Performs web search using the Ollama web search API
1035
+ * @param request {WebSearchRequest} - The search request containing query and options
1036
+ * @returns {Promise<WebSearchResponse>} - The search results
1037
+ * @throws {Error} - If the request is invalid or the server returns an error
1038
+ */
1039
+ async webSearch(request) {
1040
+ if (!request.query || request.query.length === 0) {
1041
+ throw new Error("Query is required");
1042
+ }
1043
+ const response = await post(this.fetch, `https://ollama.com/api/web_search`, { ...request }, {
1044
+ headers: this.config.headers
1045
+ });
1046
+ return await response.json();
1047
+ }
1048
+ /**
1049
+ * Fetches a single page using the Ollama web fetch API
1050
+ * @param request {WebFetchRequest} - The fetch request containing a URL
1051
+ * @returns {Promise<WebFetchResponse>} - The fetch result
1052
+ * @throws {Error} - If the request is invalid or the server returns an error
1053
+ */
1054
+ async webFetch(request) {
1055
+ if (!request.url || request.url.length === 0) {
1056
+ throw new Error("URL is required");
1057
+ }
1058
+ const response = await post(this.fetch, `https://ollama.com/api/web_fetch`, { ...request }, { headers: this.config.headers });
1059
+ return await response.json();
1060
+ }
1061
+ };
1062
+ browser = new Ollama$1();
1063
+ }
1064
+ });
1065
+
1066
+ // ../../node_modules/ollama/dist/index.mjs
1067
+ var dist_exports = {};
1068
+ __export(dist_exports, {
1069
+ Ollama: () => Ollama2,
1070
+ default: () => index
1071
+ });
1072
+ import fs, { promises } from "fs";
1073
+ import { resolve } from "path";
1074
+ var Ollama2, index;
1075
+ var init_dist = __esm({
1076
+ "../../node_modules/ollama/dist/index.mjs"() {
1077
+ "use strict";
1078
+ init_browser();
1079
+ init_fetch();
1080
+ Ollama2 = class extends Ollama$1 {
1081
+ async encodeImage(image) {
1082
+ if (typeof image !== "string") {
1083
+ return Buffer.from(image).toString("base64");
1084
+ }
1085
+ try {
1086
+ if (fs.existsSync(image)) {
1087
+ const fileBuffer = await promises.readFile(resolve(image));
1088
+ return Buffer.from(fileBuffer).toString("base64");
1089
+ }
1090
+ } catch {
1091
+ }
1092
+ return image;
1093
+ }
1094
+ /**
1095
+ * checks if a file exists
1096
+ * @param path {string} - The path to the file
1097
+ * @private @internal
1098
+ * @returns {Promise<boolean>} - Whether the file exists or not
1099
+ */
1100
+ async fileExists(path) {
1101
+ try {
1102
+ await promises.access(path);
1103
+ return true;
1104
+ } catch {
1105
+ return false;
1106
+ }
1107
+ }
1108
+ async create(request) {
1109
+ if (request.from && await this.fileExists(resolve(request.from))) {
1110
+ throw Error("Creating with a local path is not currently supported from ollama-js");
1111
+ }
1112
+ if (request.stream) {
1113
+ return super.create(request);
1114
+ } else {
1115
+ return super.create(request);
1116
+ }
1117
+ }
1118
+ };
1119
+ index = new Ollama2();
1120
+ }
1121
+ });
7
1122
 
8
1123
  // src/types.ts
9
1124
  import { Schema } from "effect";
@@ -192,28 +1307,26 @@ var LLMService = class extends Context.Tag("LLMService")() {
192
1307
  import { Context as Context2, Layer } from "effect";
193
1308
  var LLMConfig = class extends Context2.Tag("LLMConfig")() {
194
1309
  };
195
- var LLMConfigFromEnv = Layer.succeed(
196
- LLMConfig,
197
- LLMConfig.of({
198
- defaultProvider: "anthropic",
199
- defaultModel: process.env.LLM_DEFAULT_MODEL ?? "claude-sonnet-4-20250514",
200
- anthropicApiKey: process.env.ANTHROPIC_API_KEY,
201
- openaiApiKey: process.env.OPENAI_API_KEY,
202
- googleApiKey: process.env.GOOGLE_API_KEY,
203
- ollamaEndpoint: process.env.OLLAMA_ENDPOINT ?? "http://localhost:11434",
204
- embeddingConfig: {
205
- model: process.env.EMBEDDING_MODEL ?? "text-embedding-3-small",
206
- dimensions: Number(process.env.EMBEDDING_DIMENSIONS ?? 1536),
207
- provider: process.env.EMBEDDING_PROVIDER ?? "openai",
208
- batchSize: 100
209
- },
210
- supportsPromptCaching: (process.env.LLM_DEFAULT_MODEL ?? "claude-sonnet-4-20250514").startsWith("claude"),
211
- maxRetries: Number(process.env.LLM_MAX_RETRIES ?? 3),
212
- timeoutMs: Number(process.env.LLM_TIMEOUT_MS ?? 3e4),
213
- defaultMaxTokens: 4096,
214
- defaultTemperature: Number(process.env.LLM_DEFAULT_TEMPERATURE ?? 0.7)
215
- })
216
- );
1310
+ var llmConfigFromEnv = LLMConfig.of({
1311
+ defaultProvider: "anthropic",
1312
+ defaultModel: process.env.LLM_DEFAULT_MODEL ?? "claude-sonnet-4-20250514",
1313
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY,
1314
+ openaiApiKey: process.env.OPENAI_API_KEY,
1315
+ googleApiKey: process.env.GOOGLE_API_KEY,
1316
+ ollamaEndpoint: process.env.OLLAMA_ENDPOINT ?? "http://localhost:11434",
1317
+ embeddingConfig: {
1318
+ model: process.env.EMBEDDING_MODEL ?? "text-embedding-3-small",
1319
+ dimensions: Number(process.env.EMBEDDING_DIMENSIONS ?? 1536),
1320
+ provider: process.env.EMBEDDING_PROVIDER ?? "openai",
1321
+ batchSize: 100
1322
+ },
1323
+ supportsPromptCaching: (process.env.LLM_DEFAULT_MODEL ?? "claude-sonnet-4-20250514").startsWith("claude"),
1324
+ maxRetries: Number(process.env.LLM_MAX_RETRIES ?? 3),
1325
+ timeoutMs: Number(process.env.LLM_TIMEOUT_MS ?? 3e4),
1326
+ defaultMaxTokens: 4096,
1327
+ defaultTemperature: Number(process.env.LLM_DEFAULT_TEMPERATURE ?? 0.7)
1328
+ });
1329
+ var LLMConfigFromEnv = Layer.succeed(LLMConfig, llmConfigFromEnv);
217
1330
 
218
1331
  // src/prompt-manager.ts
219
1332
  import { Effect as Effect3, Context as Context3, Layer as Layer2 } from "effect";
@@ -669,6 +1782,14 @@ var toEffectError2 = (error, provider) => {
669
1782
  cause: error
670
1783
  });
671
1784
  };
1785
+ var toOpenAITool = (tool) => ({
1786
+ type: "function",
1787
+ function: {
1788
+ name: tool.name,
1789
+ description: tool.description,
1790
+ parameters: tool.inputSchema
1791
+ }
1792
+ });
672
1793
  var OpenAIProviderLive = Layer4.effect(
673
1794
  LLMService,
674
1795
  Effect5.gen(function* () {
@@ -687,14 +1808,22 @@ var OpenAIProviderLive = Layer4.effect(
687
1808
  complete: (request) => Effect5.gen(function* () {
688
1809
  const client = getClient();
689
1810
  const model = typeof request.model === "string" ? request.model : request.model?.model ?? defaultModel;
1811
+ const messages = toOpenAIMessages(request.messages);
1812
+ if (request.systemPrompt) {
1813
+ messages.unshift({ role: "system", content: request.systemPrompt });
1814
+ }
1815
+ const requestBody = {
1816
+ model,
1817
+ max_tokens: request.maxTokens ?? config.defaultMaxTokens,
1818
+ temperature: request.temperature ?? config.defaultTemperature,
1819
+ messages,
1820
+ stop: request.stopSequences ? [...request.stopSequences] : void 0
1821
+ };
1822
+ if (request.tools && request.tools.length > 0) {
1823
+ requestBody.tools = request.tools.map(toOpenAITool);
1824
+ }
690
1825
  const response = yield* Effect5.tryPromise({
691
- try: () => client.chat.completions.create({
692
- model,
693
- max_tokens: request.maxTokens ?? config.defaultMaxTokens,
694
- temperature: request.temperature ?? config.defaultTemperature,
695
- messages: toOpenAIMessages(request.messages),
696
- stop: request.stopSequences ? [...request.stopSequences] : void 0
697
- }),
1826
+ try: () => client.chat.completions.create(requestBody),
698
1827
  catch: (error) => toEffectError2(error, "openai")
699
1828
  });
700
1829
  return mapOpenAIResponse(response, model);
@@ -722,7 +1851,13 @@ var OpenAIProviderLive = Layer4.effect(
722
1851
  model,
723
1852
  max_tokens: request.maxTokens ?? config.defaultMaxTokens,
724
1853
  temperature: request.temperature ?? config.defaultTemperature,
725
- messages: toOpenAIMessages(request.messages),
1854
+ messages: (() => {
1855
+ const msgs = toOpenAIMessages(request.messages);
1856
+ if (request.systemPrompt) {
1857
+ msgs.unshift({ role: "system", content: request.systemPrompt });
1858
+ }
1859
+ return msgs;
1860
+ })(),
726
1861
  stream: true
727
1862
  });
728
1863
  let fullContent = "";
@@ -873,8 +2008,24 @@ No markdown, no code fences, just raw JSON.`
873
2008
  })
874
2009
  );
875
2010
  var mapOpenAIResponse = (response, model) => {
876
- const content = response.choices[0]?.message?.content ?? "";
877
- const stopReason = response.choices[0]?.finish_reason === "stop" ? "end_turn" : response.choices[0]?.finish_reason === "length" ? "max_tokens" : "end_turn";
2011
+ const message = response.choices[0]?.message;
2012
+ const content = message?.content ?? "";
2013
+ const rawToolCalls = message?.tool_calls;
2014
+ const hasToolCalls = rawToolCalls && rawToolCalls.length > 0;
2015
+ const stopReason = response.choices[0]?.finish_reason === "tool_calls" || hasToolCalls ? "tool_use" : response.choices[0]?.finish_reason === "stop" ? "end_turn" : response.choices[0]?.finish_reason === "length" ? "max_tokens" : "end_turn";
2016
+ const toolCalls = hasToolCalls ? rawToolCalls.map((tc) => {
2017
+ let input;
2018
+ try {
2019
+ input = JSON.parse(tc.function.arguments);
2020
+ } catch {
2021
+ input = { raw: tc.function.arguments };
2022
+ }
2023
+ return {
2024
+ id: tc.id,
2025
+ name: tc.function.name,
2026
+ input
2027
+ };
2028
+ }) : void 0;
878
2029
  return {
879
2030
  content,
880
2031
  stopReason,
@@ -888,7 +2039,8 @@ var mapOpenAIResponse = (response, model) => {
888
2039
  model
889
2040
  )
890
2041
  },
891
- model: response.model ?? model
2042
+ model: response.model ?? model,
2043
+ toolCalls
892
2044
  };
893
2045
  };
894
2046
 
@@ -900,37 +2052,57 @@ var toOllamaMessages = (messages) => messages.map((m) => ({
900
2052
  (b) => b.type === "text"
901
2053
  ).map((b) => b.text).join("")
902
2054
  }));
2055
+ var toOllamaTools = (tools) => {
2056
+ if (!tools || tools.length === 0) return void 0;
2057
+ return tools.map((t) => ({
2058
+ type: "function",
2059
+ function: {
2060
+ name: t.name,
2061
+ description: t.description,
2062
+ parameters: t.inputSchema
2063
+ }
2064
+ }));
2065
+ };
2066
+ var parseToolCalls = (toolCalls) => {
2067
+ if (!toolCalls || toolCalls.length === 0) return void 0;
2068
+ return toolCalls.map((tc, i) => ({
2069
+ id: `ollama-tc-${Date.now()}-${i}`,
2070
+ name: tc.function.name,
2071
+ input: tc.function.arguments
2072
+ }));
2073
+ };
903
2074
  var LocalProviderLive = Layer5.effect(
904
2075
  LLMService,
905
2076
  Effect6.gen(function* () {
906
2077
  const config = yield* LLMConfig;
907
2078
  const endpoint = config.ollamaEndpoint ?? "http://localhost:11434";
908
2079
  const defaultModel = config.defaultModel.startsWith("claude") || config.defaultModel.startsWith("gpt") ? "llama3" : config.defaultModel;
2080
+ const getClient = async () => {
2081
+ const { Ollama: Ollama3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
2082
+ return new Ollama3({ host: endpoint });
2083
+ };
909
2084
  return LLMService.of({
910
2085
  complete: (request) => Effect6.gen(function* () {
911
2086
  const model = typeof request.model === "string" ? request.model : request.model?.model ?? defaultModel;
912
2087
  const response = yield* Effect6.tryPromise({
913
2088
  try: async () => {
914
- const res = await fetch(`${endpoint}/api/chat`, {
915
- method: "POST",
916
- headers: { "Content-Type": "application/json" },
917
- body: JSON.stringify({
918
- model,
919
- messages: toOllamaMessages(request.messages),
920
- stream: false,
921
- options: {
922
- temperature: request.temperature ?? config.defaultTemperature,
923
- num_predict: request.maxTokens ?? config.defaultMaxTokens,
924
- stop: request.stopSequences ? [...request.stopSequences] : void 0
925
- }
926
- })
927
- });
928
- if (!res.ok) {
929
- throw new Error(
930
- `Ollama request failed: ${res.status} ${res.statusText}`
931
- );
2089
+ const client = await getClient();
2090
+ const msgs = toOllamaMessages(request.messages);
2091
+ if (request.systemPrompt) {
2092
+ msgs.unshift({ role: "system", content: request.systemPrompt });
932
2093
  }
933
- return await res.json();
2094
+ return client.chat({
2095
+ model,
2096
+ messages: msgs,
2097
+ tools: toOllamaTools(request.tools),
2098
+ stream: false,
2099
+ keep_alive: "5m",
2100
+ options: {
2101
+ temperature: request.temperature ?? config.defaultTemperature,
2102
+ num_predict: request.maxTokens ?? config.defaultMaxTokens,
2103
+ stop: request.stopSequences ? [...request.stopSequences] : void 0
2104
+ }
2105
+ });
934
2106
  },
935
2107
  catch: (error) => new LLMError({
936
2108
  message: `Ollama request failed: ${error}`,
@@ -941,9 +2113,13 @@ var LocalProviderLive = Layer5.effect(
941
2113
  const content = response.message?.content ?? "";
942
2114
  const inputTokens = response.prompt_eval_count ?? 0;
943
2115
  const outputTokens = response.eval_count ?? 0;
2116
+ const toolCalls = parseToolCalls(
2117
+ response.message?.tool_calls
2118
+ );
2119
+ const hasToolCalls = toolCalls && toolCalls.length > 0;
944
2120
  return {
945
2121
  content,
946
- stopReason: response.done_reason === "stop" ? "end_turn" : response.done_reason === "length" ? "max_tokens" : "end_turn",
2122
+ stopReason: hasToolCalls ? "tool_use" : response.done_reason === "stop" ? "end_turn" : response.done_reason === "length" ? "max_tokens" : "end_turn",
947
2123
  usage: {
948
2124
  inputTokens,
949
2125
  outputTokens,
@@ -951,18 +2127,19 @@ var LocalProviderLive = Layer5.effect(
951
2127
  estimatedCost: 0
952
2128
  // Local models are free
953
2129
  },
954
- model: response.model ?? model
2130
+ model: response.model ?? model,
2131
+ toolCalls
955
2132
  };
956
2133
  }).pipe(
957
2134
  Effect6.retry(retryPolicy),
958
- Effect6.timeout("60 seconds"),
2135
+ Effect6.timeout("120 seconds"),
959
2136
  Effect6.catchTag(
960
2137
  "TimeoutException",
961
2138
  () => Effect6.fail(
962
2139
  new LLMTimeoutError({
963
2140
  message: "Local LLM request timed out",
964
2141
  provider: "ollama",
965
- timeoutMs: 6e4
2142
+ timeoutMs: 12e4
966
2143
  })
967
2144
  )
968
2145
  )
@@ -972,54 +2149,46 @@ var LocalProviderLive = Layer5.effect(
972
2149
  return Stream3.async((emit) => {
973
2150
  const doStream = async () => {
974
2151
  try {
975
- const res = await fetch(`${endpoint}/api/chat`, {
976
- method: "POST",
977
- headers: { "Content-Type": "application/json" },
978
- body: JSON.stringify({
979
- model,
980
- messages: toOllamaMessages(request.messages),
981
- stream: true,
982
- options: {
983
- temperature: request.temperature ?? config.defaultTemperature,
984
- num_predict: request.maxTokens ?? config.defaultMaxTokens
985
- }
986
- })
987
- });
988
- if (!res.ok || !res.body) {
989
- throw new Error(`Ollama stream failed: ${res.status}`);
2152
+ const client = await getClient();
2153
+ const msgs = toOllamaMessages(request.messages);
2154
+ if (request.systemPrompt) {
2155
+ msgs.unshift({ role: "system", content: request.systemPrompt });
990
2156
  }
991
- const reader = res.body.getReader();
992
- const decoder = new TextDecoder();
2157
+ const stream = await client.chat({
2158
+ model,
2159
+ messages: msgs,
2160
+ tools: toOllamaTools(request.tools),
2161
+ stream: true,
2162
+ keep_alive: "5m",
2163
+ options: {
2164
+ temperature: request.temperature ?? config.defaultTemperature,
2165
+ num_predict: request.maxTokens ?? config.defaultMaxTokens
2166
+ }
2167
+ });
993
2168
  let fullContent = "";
994
- while (true) {
995
- const { done, value } = await reader.read();
996
- if (done) break;
997
- const lines = decoder.decode(value, { stream: true }).split("\n").filter(Boolean);
998
- for (const line of lines) {
999
- const parsed = JSON.parse(line);
1000
- if (parsed.message?.content) {
1001
- fullContent += parsed.message.content;
1002
- emit.single({
1003
- type: "text_delta",
1004
- text: parsed.message.content
1005
- });
1006
- }
1007
- if (parsed.done) {
1008
- emit.single({
1009
- type: "content_complete",
1010
- content: fullContent
1011
- });
1012
- emit.single({
1013
- type: "usage",
1014
- usage: {
1015
- inputTokens: parsed.prompt_eval_count ?? 0,
1016
- outputTokens: parsed.eval_count ?? 0,
1017
- totalTokens: (parsed.prompt_eval_count ?? 0) + (parsed.eval_count ?? 0),
1018
- estimatedCost: 0
1019
- }
1020
- });
1021
- emit.end();
1022
- }
2169
+ for await (const chunk of stream) {
2170
+ if (chunk.message?.content) {
2171
+ fullContent += chunk.message.content;
2172
+ emit.single({
2173
+ type: "text_delta",
2174
+ text: chunk.message.content
2175
+ });
2176
+ }
2177
+ if (chunk.done) {
2178
+ emit.single({
2179
+ type: "content_complete",
2180
+ content: fullContent
2181
+ });
2182
+ emit.single({
2183
+ type: "usage",
2184
+ usage: {
2185
+ inputTokens: chunk.prompt_eval_count ?? 0,
2186
+ outputTokens: chunk.eval_count ?? 0,
2187
+ totalTokens: (chunk.prompt_eval_count ?? 0) + (chunk.eval_count ?? 0),
2188
+ estimatedCost: 0
2189
+ }
2190
+ });
2191
+ emit.end();
1023
2192
  }
1024
2193
  }
1025
2194
  } catch (error) {
@@ -1042,79 +2211,68 @@ var LocalProviderLive = Layer5.effect(
1042
2211
  null,
1043
2212
  2
1044
2213
  );
1045
- const messagesWithFormat = [
1046
- ...request.messages,
1047
- {
1048
- role: "user",
1049
- content: `
2214
+ const model = typeof request.model === "string" ? request.model : request.model?.model ?? defaultModel;
2215
+ let lastError = null;
2216
+ const maxRetries = request.maxParseRetries ?? 2;
2217
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2218
+ const msgs = toOllamaMessages(
2219
+ attempt === 0 ? [
2220
+ ...request.messages,
2221
+ {
2222
+ role: "user",
2223
+ content: `
2224
+ Respond with ONLY valid JSON matching this schema:
2225
+ ${schemaStr}
2226
+
2227
+ No markdown, no code fences, just raw JSON.`
2228
+ }
2229
+ ] : [
2230
+ ...request.messages,
2231
+ {
2232
+ role: "user",
2233
+ content: `
1050
2234
  Respond with ONLY valid JSON matching this schema:
1051
2235
  ${schemaStr}
1052
2236
 
1053
2237
  No markdown, no code fences, just raw JSON.`
1054
- }
1055
- ];
1056
- let lastError = null;
1057
- const maxRetries = request.maxParseRetries ?? 2;
1058
- const llm = {
1059
- complete: (req) => Effect6.gen(function* () {
1060
- const model = req.model?.model ?? defaultModel;
1061
- const res = yield* Effect6.tryPromise({
1062
- try: async () => {
1063
- const resp = await fetch(`${endpoint}/api/chat`, {
1064
- method: "POST",
1065
- headers: { "Content-Type": "application/json" },
1066
- body: JSON.stringify({
1067
- model,
1068
- messages: toOllamaMessages(req.messages),
1069
- stream: false,
1070
- options: {
1071
- temperature: req.temperature ?? config.defaultTemperature,
1072
- num_predict: req.maxTokens ?? config.defaultMaxTokens
1073
- }
1074
- })
1075
- });
1076
- return await resp.json();
1077
2238
  },
1078
- catch: (error) => new LLMError({
1079
- message: `Ollama request failed: ${error}`,
1080
- provider: "ollama",
1081
- cause: error
1082
- })
1083
- });
1084
- const content = res.message?.content ?? "";
1085
- const inputTokens = res.prompt_eval_count ?? 0;
1086
- const outputTokens = res.eval_count ?? 0;
1087
- return {
1088
- content,
1089
- stopReason: "end_turn",
1090
- usage: {
1091
- inputTokens,
1092
- outputTokens,
1093
- totalTokens: inputTokens + outputTokens,
1094
- estimatedCost: 0
2239
+ {
2240
+ role: "assistant",
2241
+ content: String(lastError)
1095
2242
  },
1096
- model: res.model ?? model
1097
- };
1098
- })
1099
- };
1100
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1101
- const msgs = attempt === 0 ? messagesWithFormat : [
1102
- ...messagesWithFormat,
1103
- {
1104
- role: "assistant",
1105
- content: String(lastError)
2243
+ {
2244
+ role: "user",
2245
+ content: `That response was not valid JSON. The parse error was: ${String(lastError)}. Please try again with valid JSON only.`
2246
+ }
2247
+ ]
2248
+ );
2249
+ if (request.systemPrompt) {
2250
+ msgs.unshift({ role: "system", content: request.systemPrompt });
2251
+ }
2252
+ const response = yield* Effect6.tryPromise({
2253
+ try: async () => {
2254
+ const client = await getClient();
2255
+ return client.chat({
2256
+ model,
2257
+ messages: msgs,
2258
+ stream: false,
2259
+ format: "json",
2260
+ keep_alive: "5m",
2261
+ options: {
2262
+ temperature: request.temperature ?? config.defaultTemperature,
2263
+ num_predict: request.maxTokens ?? config.defaultMaxTokens
2264
+ }
2265
+ });
1106
2266
  },
1107
- {
1108
- role: "user",
1109
- content: `That response was not valid JSON. The parse error was: ${String(lastError)}. Please try again with valid JSON only.`
1110
- }
1111
- ];
1112
- const response = yield* llm.complete({
1113
- ...request,
1114
- messages: msgs
2267
+ catch: (error) => new LLMError({
2268
+ message: `Ollama request failed: ${error}`,
2269
+ provider: "ollama",
2270
+ cause: error
2271
+ })
1115
2272
  });
2273
+ const content = response.message?.content ?? "";
1116
2274
  try {
1117
- const parsed = JSON.parse(response.content);
2275
+ const parsed = JSON.parse(content);
1118
2276
  const decoded = Schema4.decodeUnknownEither(
1119
2277
  request.outputSchema
1120
2278
  )(parsed);
@@ -1136,21 +2294,13 @@ No markdown, no code fences, just raw JSON.`
1136
2294
  }),
1137
2295
  embed: (texts, model) => Effect6.tryPromise({
1138
2296
  try: async () => {
2297
+ const client = await getClient();
1139
2298
  const embeddingModel = model ?? config.embeddingConfig.model ?? "nomic-embed-text";
1140
- return Promise.all(
1141
- [...texts].map(async (text) => {
1142
- const res = await fetch(`${endpoint}/api/embed`, {
1143
- method: "POST",
1144
- headers: { "Content-Type": "application/json" },
1145
- body: JSON.stringify({
1146
- model: embeddingModel,
1147
- input: text
1148
- })
1149
- });
1150
- const data = await res.json();
1151
- return data.embeddings[0];
1152
- })
1153
- );
2299
+ const response = await client.embed({
2300
+ model: embeddingModel,
2301
+ input: [...texts]
2302
+ });
2303
+ return response.embeddings;
1154
2304
  },
1155
2305
  catch: (error) => new LLMError({
1156
2306
  message: `Embedding failed: ${error}`,
@@ -1622,14 +2772,14 @@ var ComplexityAnalysisSchema = Schema7.Struct({
1622
2772
 
1623
2773
  // src/runtime.ts
1624
2774
  import { Layer as Layer8 } from "effect";
1625
- var createLLMProviderLayer = (provider = "anthropic", testResponses) => {
2775
+ var createLLMProviderLayer = (provider = "anthropic", testResponses, model) => {
1626
2776
  if (provider === "test") {
1627
2777
  return Layer8.mergeAll(
1628
2778
  TestLLMServiceLayer(testResponses ?? {}),
1629
2779
  PromptManagerLive
1630
2780
  );
1631
2781
  }
1632
- const configLayer = LLMConfigFromEnv;
2782
+ const configLayer = model ? Layer8.succeed(LLMConfig, LLMConfig.of({ ...llmConfigFromEnv, defaultModel: model })) : LLMConfigFromEnv;
1633
2783
  const providerLayer = provider === "anthropic" ? AnthropicProviderLive : provider === "openai" ? OpenAIProviderLive : provider === "gemini" ? GeminiProviderLive : LocalProviderLive;
1634
2784
  return Layer8.mergeAll(
1635
2785
  providerLayer.pipe(Layer8.provide(configLayer)),
@@ -1687,6 +2837,7 @@ export {
1687
2837
  createLLMProviderLayer,
1688
2838
  createLLMProviderLayerWithConfig,
1689
2839
  estimateTokenCount,
2840
+ llmConfigFromEnv,
1690
2841
  makeCacheable,
1691
2842
  retryPolicy
1692
2843
  };