@nemesis-oss/ollama-sdk 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -212
- package/README.md +391 -18
- package/dist/compose-BR70yb4W.d.cts +164 -0
- package/dist/compose-DfAqKAFj.d.ts +164 -0
- package/dist/generated-api.cjs +497 -0
- package/dist/generated-api.cjs.map +1 -0
- package/dist/generated-api.d.cts +1651 -0
- package/dist/generated-api.d.ts +1651 -0
- package/dist/generated-api.js +471 -0
- package/dist/generated-api.js.map +1 -0
- package/dist/generated-models-schemas.cjs +328 -0
- package/dist/generated-models-schemas.cjs.map +1 -0
- package/dist/generated-models-schemas.d.cts +463 -0
- package/dist/generated-models-schemas.d.ts +463 -0
- package/dist/generated-models-schemas.js +291 -0
- package/dist/generated-models-schemas.js.map +1 -0
- package/dist/generated-runtime.cjs +205 -0
- package/dist/generated-runtime.cjs.map +1 -0
- package/dist/generated-runtime.d.cts +2 -0
- package/dist/generated-runtime.d.ts +2 -0
- package/dist/generated-runtime.js +203 -0
- package/dist/generated-runtime.js.map +1 -0
- package/dist/index-2RIErNT5.d.cts +583 -0
- package/dist/index-DIQ8NUbU.d.ts +583 -0
- package/dist/index.cjs +2041 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +817 -453
- package/dist/index.d.ts +817 -453
- package/dist/index.js +2032 -209
- package/dist/index.js.map +1 -1
- package/dist/mcp/tools.json +1007 -0
- package/dist/mcp-generated.cjs +429 -0
- package/dist/mcp-generated.cjs.map +1 -0
- package/dist/mcp-generated.d.cts +35 -0
- package/dist/mcp-generated.d.ts +35 -0
- package/dist/mcp-generated.js +424 -0
- package/dist/mcp-generated.js.map +1 -0
- package/dist/mcp-http.cjs +100 -0
- package/dist/mcp-http.cjs.map +1 -0
- package/dist/mcp-http.d.cts +48 -0
- package/dist/mcp-http.d.ts +48 -0
- package/dist/mcp-http.js +96 -0
- package/dist/mcp-http.js.map +1 -0
- package/dist/mcp-stdio.cjs +49 -0
- package/dist/mcp-stdio.cjs.map +1 -0
- package/dist/mcp-stdio.d.cts +34 -0
- package/dist/mcp-stdio.d.ts +34 -0
- package/dist/mcp-stdio.js +47 -0
- package/dist/mcp-stdio.js.map +1 -0
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +3 -2
- package/dist/skills.d.ts +3 -2
- package/dist/skills.js.map +1 -1
- package/dist/types-B9_axjum.d.cts +66 -0
- package/dist/types-B9_axjum.d.ts +66 -0
- package/dist/{compose-CDWq8oxJ.d.cts → types-BQhgW-du.d.cts} +68 -171
- package/dist/{compose-CDWq8oxJ.d.ts → types-BQhgW-du.d.ts} +68 -171
- package/package.json +96 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
4
|
+
var __typeError = (msg) => {
|
|
5
|
+
throw TypeError(msg);
|
|
6
|
+
};
|
|
7
|
+
var __using = (stack, value, async) => {
|
|
8
|
+
if (value != null) {
|
|
9
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
10
|
+
var dispose, inner;
|
|
11
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
12
|
+
if (dispose === void 0) {
|
|
13
|
+
dispose = value[__knownSymbol("dispose")];
|
|
14
|
+
if (async) inner = dispose;
|
|
15
|
+
}
|
|
16
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
17
|
+
if (inner) dispose = function() {
|
|
18
|
+
try {
|
|
19
|
+
inner.call(this);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return Promise.reject(e);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
stack.push([async, dispose, value]);
|
|
25
|
+
} else if (async) {
|
|
26
|
+
stack.push([async]);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
};
|
|
30
|
+
var __callDispose = (stack, error, hasError) => {
|
|
31
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
32
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
33
|
+
};
|
|
34
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
35
|
+
var next = (it) => {
|
|
36
|
+
while (it = stack.pop()) {
|
|
37
|
+
try {
|
|
38
|
+
var result = it[1] && it[1].call(it[2]);
|
|
39
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (hasError) throw error;
|
|
45
|
+
};
|
|
46
|
+
return next();
|
|
47
|
+
};
|
|
48
|
+
|
|
3
49
|
// src/config.ts
|
|
4
50
|
var DEFAULT_BASE_URL = "http://localhost:11434";
|
|
5
51
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -152,6 +198,26 @@ var OllamaModelRoutingError = class extends OllamaClientError {
|
|
|
152
198
|
this.availableModels = options.availableModels;
|
|
153
199
|
}
|
|
154
200
|
};
|
|
201
|
+
var OllamaIncompatibleModelError = class extends OllamaClientError {
|
|
202
|
+
model;
|
|
203
|
+
capability;
|
|
204
|
+
reportedCapabilities;
|
|
205
|
+
constructor(message, options) {
|
|
206
|
+
super(message, { ...options, code: "incompatible_model", retryable: false });
|
|
207
|
+
this.model = options.model;
|
|
208
|
+
this.capability = options.capability;
|
|
209
|
+
this.reportedCapabilities = options.reportedCapabilities;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var OllamaAgentMaxToolCallsError = class extends OllamaClientError {
|
|
213
|
+
maxToolCalls;
|
|
214
|
+
toolCallsExecuted;
|
|
215
|
+
constructor(message, options) {
|
|
216
|
+
super(message, { ...options, code: "agent_max_tool_calls_exceeded", retryable: false });
|
|
217
|
+
this.maxToolCalls = options.maxToolCalls;
|
|
218
|
+
this.toolCallsExecuted = options.toolCallsExecuted;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
155
221
|
var OllamaAgentMaxIterationsError = class extends OllamaClientError {
|
|
156
222
|
maxIterations;
|
|
157
223
|
constructor(message, options) {
|
|
@@ -450,12 +516,87 @@ var EndpointRegistry = class {
|
|
|
450
516
|
}
|
|
451
517
|
};
|
|
452
518
|
|
|
519
|
+
// src/middleware.ts
|
|
520
|
+
function composeMiddleware(middlewares, finalHandler) {
|
|
521
|
+
return function runPipeline(initialRequest) {
|
|
522
|
+
let index = -1;
|
|
523
|
+
function dispatch2(i, currentRequest) {
|
|
524
|
+
if (i <= index) {
|
|
525
|
+
return Promise.reject(new Error("next() called multiple times in middleware"));
|
|
526
|
+
}
|
|
527
|
+
index = i;
|
|
528
|
+
const fn = middlewares[i];
|
|
529
|
+
if (i === middlewares.length || !fn) {
|
|
530
|
+
return finalHandler(currentRequest);
|
|
531
|
+
}
|
|
532
|
+
return fn({
|
|
533
|
+
request: currentRequest,
|
|
534
|
+
next: () => dispatch2(i + 1, currentRequest)
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return dispatch2(0, initialRequest);
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/utils.ts
|
|
542
|
+
async function encodeImage(image) {
|
|
543
|
+
if (typeof image === "string") return image;
|
|
544
|
+
const bufferCtor = globalThis.Buffer;
|
|
545
|
+
if (bufferCtor !== void 0) return bufferCtor.from(image).toString("base64");
|
|
546
|
+
const CHUNK_SIZE = 32768;
|
|
547
|
+
let binary = "";
|
|
548
|
+
for (let i = 0; i < image.length; i += CHUNK_SIZE) {
|
|
549
|
+
binary += String.fromCharCode(...image.subarray(i, i + CHUNK_SIZE));
|
|
550
|
+
}
|
|
551
|
+
return btoa(binary);
|
|
552
|
+
}
|
|
553
|
+
async function encodeImages(images) {
|
|
554
|
+
if (images === void 0) return void 0;
|
|
555
|
+
return Promise.all(images.map((image) => encodeImage(image)));
|
|
556
|
+
}
|
|
557
|
+
async function withEncodedImages(req) {
|
|
558
|
+
if (!req.images?.some((image) => image instanceof Uint8Array)) return req;
|
|
559
|
+
return { ...req, images: await encodeImages(req.images) };
|
|
560
|
+
}
|
|
561
|
+
async function withEncodedMessageImages(messages) {
|
|
562
|
+
if (!messages.some((message) => message.images?.some((image) => image instanceof Uint8Array)))
|
|
563
|
+
return messages;
|
|
564
|
+
return Promise.all(messages.map((message) => withEncodedImages(message)));
|
|
565
|
+
}
|
|
566
|
+
function disposableTimer(timer) {
|
|
567
|
+
return {
|
|
568
|
+
[Symbol.dispose]() {
|
|
569
|
+
clearTimeout(timer);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function disposableReader(reader) {
|
|
574
|
+
return {
|
|
575
|
+
async [Symbol.asyncDispose]() {
|
|
576
|
+
try {
|
|
577
|
+
await reader.cancel();
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
reader.releaseLock();
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function disposableSpan(span) {
|
|
585
|
+
return {
|
|
586
|
+
[Symbol.dispose]() {
|
|
587
|
+
span.end();
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
453
592
|
// src/streaming/ndjson.ts
|
|
454
593
|
async function* parseNdjsonStream(stream) {
|
|
455
|
-
|
|
456
|
-
const decoder = new TextDecoder();
|
|
457
|
-
let buffer = "";
|
|
594
|
+
var _stack = [];
|
|
458
595
|
try {
|
|
596
|
+
const reader = stream.getReader();
|
|
597
|
+
const decoder = new TextDecoder();
|
|
598
|
+
let buffer = "";
|
|
599
|
+
const _reader = __using(_stack, disposableReader(reader), true);
|
|
459
600
|
while (true) {
|
|
460
601
|
const { done, value } = await reader.read();
|
|
461
602
|
if (done) break;
|
|
@@ -477,8 +618,103 @@ async function* parseNdjsonStream(stream) {
|
|
|
477
618
|
if (buffer.trim()) {
|
|
478
619
|
yield JSON.parse(buffer.trim());
|
|
479
620
|
}
|
|
621
|
+
} catch (_) {
|
|
622
|
+
var _error = _, _hasError = true;
|
|
623
|
+
} finally {
|
|
624
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
625
|
+
_promise && await _promise;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/streaming/sse.ts
|
|
630
|
+
function dispatch(event, data, id, retry) {
|
|
631
|
+
if (data.length === 0) return void 0;
|
|
632
|
+
const joined = data.join("\n");
|
|
633
|
+
return {
|
|
634
|
+
...event !== void 0 ? { event } : {},
|
|
635
|
+
data: joined.endsWith("\n") ? joined.slice(0, -1) : joined,
|
|
636
|
+
...id !== void 0 ? { id } : {},
|
|
637
|
+
...retry !== void 0 ? { retry } : {}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
async function* parseSseStream(stream) {
|
|
641
|
+
var _stack = [];
|
|
642
|
+
try {
|
|
643
|
+
const reader = stream.getReader();
|
|
644
|
+
const decoder = new TextDecoder();
|
|
645
|
+
let buffer = "";
|
|
646
|
+
let eventType;
|
|
647
|
+
let data = [];
|
|
648
|
+
let lastEventId;
|
|
649
|
+
let retry;
|
|
650
|
+
const processLine = (line) => {
|
|
651
|
+
if (line.length === 0) {
|
|
652
|
+
const event = dispatch(eventType, data, lastEventId, retry);
|
|
653
|
+
eventType = void 0;
|
|
654
|
+
data = [];
|
|
655
|
+
retry = void 0;
|
|
656
|
+
return event;
|
|
657
|
+
}
|
|
658
|
+
if (line.startsWith(":")) return void 0;
|
|
659
|
+
const separator = line.indexOf(":");
|
|
660
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
661
|
+
let value = separator === -1 ? "" : line.slice(separator + 1);
|
|
662
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
663
|
+
switch (field) {
|
|
664
|
+
case "event":
|
|
665
|
+
eventType = value;
|
|
666
|
+
break;
|
|
667
|
+
case "data":
|
|
668
|
+
data.push(value);
|
|
669
|
+
break;
|
|
670
|
+
case "id":
|
|
671
|
+
if (!value.includes("\0")) lastEventId = value;
|
|
672
|
+
break;
|
|
673
|
+
case "retry": {
|
|
674
|
+
if (/^\d+$/.test(value)) {
|
|
675
|
+
const parsed = Number(value);
|
|
676
|
+
if (Number.isSafeInteger(parsed)) retry = parsed;
|
|
677
|
+
}
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
default:
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
return void 0;
|
|
684
|
+
};
|
|
685
|
+
const _reader = __using(_stack, disposableReader(reader), true);
|
|
686
|
+
while (true) {
|
|
687
|
+
const { done, value } = await reader.read();
|
|
688
|
+
if (done) break;
|
|
689
|
+
buffer += decoder.decode(value, { stream: true });
|
|
690
|
+
while (true) {
|
|
691
|
+
const lf = buffer.indexOf("\n");
|
|
692
|
+
const cr = buffer.indexOf("\r");
|
|
693
|
+
if (lf === -1 && cr === -1) break;
|
|
694
|
+
if (cr !== -1 && lf === -1 && cr === buffer.length - 1) break;
|
|
695
|
+
const lineEnd = lf === -1 ? cr : cr === -1 ? lf : Math.min(lf, cr);
|
|
696
|
+
let separatorLength = 1;
|
|
697
|
+
if (buffer[lineEnd] === "\r" && buffer[lineEnd + 1] === "\n") {
|
|
698
|
+
separatorLength = 2;
|
|
699
|
+
}
|
|
700
|
+
const line = buffer.slice(0, lineEnd);
|
|
701
|
+
buffer = buffer.slice(lineEnd + separatorLength);
|
|
702
|
+
const event = processLine(line);
|
|
703
|
+
if (event) yield event;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
buffer += decoder.decode();
|
|
707
|
+
if (buffer.length > 0) {
|
|
708
|
+
const event = processLine(buffer);
|
|
709
|
+
if (event) yield event;
|
|
710
|
+
}
|
|
711
|
+
const terminal = dispatch(eventType, data, lastEventId, retry);
|
|
712
|
+
if (terminal) yield terminal;
|
|
713
|
+
} catch (_) {
|
|
714
|
+
var _error = _, _hasError = true;
|
|
480
715
|
} finally {
|
|
481
|
-
|
|
716
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
717
|
+
_promise && await _promise;
|
|
482
718
|
}
|
|
483
719
|
}
|
|
484
720
|
|
|
@@ -506,16 +742,22 @@ async function withSpan(name, attributes, fn) {
|
|
|
506
742
|
}
|
|
507
743
|
const tracer = api.trace.getTracer(INSTRUMENTATION_NAME, INSTRUMENTATION_VERSION);
|
|
508
744
|
return tracer.startActiveSpan(name, async (span) => {
|
|
509
|
-
|
|
745
|
+
var _stack = [];
|
|
510
746
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
747
|
+
span.setAttributes(cleanAttributes(attributes));
|
|
748
|
+
const _span = __using(_stack, disposableSpan(span));
|
|
749
|
+
try {
|
|
750
|
+
return await fn(span);
|
|
751
|
+
} catch (err) {
|
|
752
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
753
|
+
span.recordException(error);
|
|
754
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
|
|
755
|
+
throw error;
|
|
756
|
+
}
|
|
757
|
+
} catch (_) {
|
|
758
|
+
var _error = _, _hasError = true;
|
|
517
759
|
} finally {
|
|
518
|
-
|
|
760
|
+
__callDispose(_stack, _error, _hasError);
|
|
519
761
|
}
|
|
520
762
|
});
|
|
521
763
|
}
|
|
@@ -562,16 +804,36 @@ function httpSpanAttributes(method, url) {
|
|
|
562
804
|
[ATTR_SERVER_PORT]: target.port ? Number(target.port) : void 0
|
|
563
805
|
};
|
|
564
806
|
}
|
|
807
|
+
var requestSequence = 0;
|
|
808
|
+
function createRequestId() {
|
|
809
|
+
requestSequence += 1;
|
|
810
|
+
return `ollama-http-${requestSequence}`;
|
|
811
|
+
}
|
|
812
|
+
function sameHeaders(actual, expected) {
|
|
813
|
+
const actualEntries = Object.fromEntries(
|
|
814
|
+
Object.entries(actual).map(([key, value]) => [key.toLowerCase(), value])
|
|
815
|
+
);
|
|
816
|
+
const expectedEntries = Object.fromEntries(expected.entries());
|
|
817
|
+
const actualKeys = Object.keys(actualEntries);
|
|
818
|
+
const expectedKeys = Object.keys(expectedEntries);
|
|
819
|
+
return actualKeys.length === expectedKeys.length && expectedKeys.every((key) => actualEntries[key] === expectedEntries[key]);
|
|
820
|
+
}
|
|
565
821
|
var HttpClient = class {
|
|
566
822
|
baseUrl;
|
|
567
823
|
apiKey;
|
|
568
824
|
defaultHeaders;
|
|
569
825
|
fetchImpl;
|
|
826
|
+
middleware;
|
|
827
|
+
onLifecycleEvent;
|
|
828
|
+
requestId;
|
|
570
829
|
constructor(options) {
|
|
571
830
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
572
831
|
this.apiKey = options.apiKey;
|
|
573
832
|
this.defaultHeaders = options.headers ?? {};
|
|
574
833
|
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
834
|
+
this.middleware = options.middleware ?? [];
|
|
835
|
+
this.onLifecycleEvent = options.onLifecycleEvent;
|
|
836
|
+
this.requestId = options.requestId;
|
|
575
837
|
}
|
|
576
838
|
buildHeaders(customHeaders) {
|
|
577
839
|
const headers = {
|
|
@@ -584,6 +846,131 @@ var HttpClient = class {
|
|
|
584
846
|
}
|
|
585
847
|
return headers;
|
|
586
848
|
}
|
|
849
|
+
async fetchWithMiddleware(request) {
|
|
850
|
+
const requestId = this.requestId ?? createRequestId();
|
|
851
|
+
const startedAt = Date.now();
|
|
852
|
+
let rawResponse;
|
|
853
|
+
this.onLifecycleEvent?.({
|
|
854
|
+
type: "start",
|
|
855
|
+
requestId,
|
|
856
|
+
method: request.method,
|
|
857
|
+
url: request.url,
|
|
858
|
+
timestamp: startedAt
|
|
859
|
+
});
|
|
860
|
+
const finalHandler = async (req) => {
|
|
861
|
+
const init = {
|
|
862
|
+
method: req.method,
|
|
863
|
+
headers: req.headers,
|
|
864
|
+
...req.body !== void 0 ? { body: req.body } : {},
|
|
865
|
+
...req.signal !== void 0 ? { signal: req.signal } : {}
|
|
866
|
+
};
|
|
867
|
+
const response = await this.fetchImpl(req.url, init);
|
|
868
|
+
rawResponse = response;
|
|
869
|
+
const responseHeaders = response.headers && typeof response.headers.entries === "function" ? Object.fromEntries(response.headers.entries()) : {};
|
|
870
|
+
return {
|
|
871
|
+
status: response.status,
|
|
872
|
+
headers: responseHeaders,
|
|
873
|
+
body: response
|
|
874
|
+
};
|
|
875
|
+
};
|
|
876
|
+
try {
|
|
877
|
+
if (this.middleware.length === 0) {
|
|
878
|
+
const response2 = await this.fetchImpl(request.url, {
|
|
879
|
+
method: request.method,
|
|
880
|
+
headers: request.headers,
|
|
881
|
+
...request.body !== void 0 ? { body: request.body } : {},
|
|
882
|
+
...request.signal !== void 0 ? { signal: request.signal } : {}
|
|
883
|
+
});
|
|
884
|
+
if (response2.status >= 200 && response2.status < 300) {
|
|
885
|
+
this.onLifecycleEvent?.({
|
|
886
|
+
type: "success",
|
|
887
|
+
requestId,
|
|
888
|
+
durationMs: Date.now() - startedAt,
|
|
889
|
+
status: response2.status,
|
|
890
|
+
timestamp: Date.now()
|
|
891
|
+
});
|
|
892
|
+
} else {
|
|
893
|
+
this.onLifecycleEvent?.({
|
|
894
|
+
type: "error",
|
|
895
|
+
requestId,
|
|
896
|
+
durationMs: Date.now() - startedAt,
|
|
897
|
+
error: mapError(new Error(`HTTP ${response2.status}`), {
|
|
898
|
+
request: { method: request.method, url: request.url },
|
|
899
|
+
response: { status: response2.status }
|
|
900
|
+
}),
|
|
901
|
+
timestamp: Date.now()
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
return response2;
|
|
905
|
+
}
|
|
906
|
+
const pipeline = composeMiddleware(this.middleware, finalHandler);
|
|
907
|
+
const context = await pipeline(request);
|
|
908
|
+
let response;
|
|
909
|
+
if (rawResponse !== void 0 && context.body === rawResponse) {
|
|
910
|
+
response = rawResponse;
|
|
911
|
+
if (context.status !== response.status || !sameHeaders(context.headers, response.headers)) {
|
|
912
|
+
const cloned = response.clone();
|
|
913
|
+
response = new Response(cloned.body, {
|
|
914
|
+
status: context.status,
|
|
915
|
+
headers: context.headers
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
} else if (context.body instanceof Response) {
|
|
919
|
+
if (context.status === context.body.status && sameHeaders(context.headers, context.body.headers)) {
|
|
920
|
+
response = context.body;
|
|
921
|
+
} else {
|
|
922
|
+
const cloned = context.body.clone();
|
|
923
|
+
response = new Response(cloned.body, {
|
|
924
|
+
status: context.status,
|
|
925
|
+
headers: context.headers
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
} else if (context.body === void 0 || typeof context.body === "string" || context.body instanceof ArrayBuffer || ArrayBuffer.isView(context.body) || context.body instanceof Blob || context.body instanceof ReadableStream) {
|
|
929
|
+
response = new Response(context.body, {
|
|
930
|
+
status: context.status,
|
|
931
|
+
headers: context.headers
|
|
932
|
+
});
|
|
933
|
+
} else {
|
|
934
|
+
response = new Response(JSON.stringify(context.body), {
|
|
935
|
+
status: context.status,
|
|
936
|
+
headers: context.headers
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
if (context.status >= 200 && context.status < 300) {
|
|
940
|
+
this.onLifecycleEvent?.({
|
|
941
|
+
type: "success",
|
|
942
|
+
requestId,
|
|
943
|
+
durationMs: Date.now() - startedAt,
|
|
944
|
+
status: context.status,
|
|
945
|
+
timestamp: Date.now()
|
|
946
|
+
});
|
|
947
|
+
} else {
|
|
948
|
+
this.onLifecycleEvent?.({
|
|
949
|
+
type: "error",
|
|
950
|
+
requestId,
|
|
951
|
+
durationMs: Date.now() - startedAt,
|
|
952
|
+
error: mapError(new Error(`HTTP ${context.status}`), {
|
|
953
|
+
request: { method: request.method, url: request.url },
|
|
954
|
+
response: { status: context.status, headers: context.headers }
|
|
955
|
+
}),
|
|
956
|
+
timestamp: Date.now()
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return response;
|
|
960
|
+
} catch (error) {
|
|
961
|
+
const mapped = mapError(error, {
|
|
962
|
+
request: { method: request.method, url: request.url }
|
|
963
|
+
});
|
|
964
|
+
this.onLifecycleEvent?.({
|
|
965
|
+
type: "error",
|
|
966
|
+
requestId,
|
|
967
|
+
durationMs: Date.now() - startedAt,
|
|
968
|
+
error: mapped,
|
|
969
|
+
timestamp: Date.now()
|
|
970
|
+
});
|
|
971
|
+
throw mapped;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
587
974
|
async request(options) {
|
|
588
975
|
const url = `${this.baseUrl}${options.path}`;
|
|
589
976
|
const method = options.method ?? (options.body !== void 0 || options.rawBody !== void 0 ? "POST" : "GET");
|
|
@@ -597,13 +984,13 @@ var HttpClient = class {
|
|
|
597
984
|
async (span) => {
|
|
598
985
|
try {
|
|
599
986
|
const bodyInit = options.rawBody !== void 0 ? options.rawBody : options.body !== void 0 ? JSON.stringify(options.body) : void 0;
|
|
600
|
-
const
|
|
987
|
+
const response = await this.fetchWithMiddleware({
|
|
988
|
+
url,
|
|
601
989
|
method,
|
|
602
990
|
headers,
|
|
603
991
|
...bodyInit !== void 0 ? { body: bodyInit } : {},
|
|
604
992
|
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
605
|
-
};
|
|
606
|
-
const response = await this.fetchImpl(url, init);
|
|
993
|
+
});
|
|
607
994
|
span?.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
|
|
608
995
|
if (!response.ok) {
|
|
609
996
|
let errorBody;
|
|
@@ -621,6 +1008,9 @@ var HttpClient = class {
|
|
|
621
1008
|
if (options.method === "HEAD" || response.status === 204) {
|
|
622
1009
|
return void 0;
|
|
623
1010
|
}
|
|
1011
|
+
if (options.raw === true) {
|
|
1012
|
+
return response;
|
|
1013
|
+
}
|
|
624
1014
|
if (typeof response.text === "function") {
|
|
625
1015
|
const text = await response.text();
|
|
626
1016
|
return text ? JSON.parse(text) : void 0;
|
|
@@ -635,6 +1025,79 @@ var HttpClient = class {
|
|
|
635
1025
|
}
|
|
636
1026
|
);
|
|
637
1027
|
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Opens an event-stream response and exposes parsed SSE events.
|
|
1030
|
+
*
|
|
1031
|
+
* This is intentionally schema-agnostic so OpenAI and Anthropic compatibility layers
|
|
1032
|
+
* can decode their provider-specific event payloads without duplicating transport logic.
|
|
1033
|
+
*/
|
|
1034
|
+
async requestSseStream(options) {
|
|
1035
|
+
const url = `${this.baseUrl}${options.path}`;
|
|
1036
|
+
const method = options.method ?? "POST";
|
|
1037
|
+
const headers = {
|
|
1038
|
+
...this.buildHeaders(options.headers),
|
|
1039
|
+
Accept: "text/event-stream"
|
|
1040
|
+
};
|
|
1041
|
+
return withSpan(
|
|
1042
|
+
`${method} ${routeTemplate(options.path)}`,
|
|
1043
|
+
httpSpanAttributes(method, url),
|
|
1044
|
+
async (span) => {
|
|
1045
|
+
try {
|
|
1046
|
+
const controller = new AbortController();
|
|
1047
|
+
const removeAbortListener = options.signal !== void 0 ? (() => {
|
|
1048
|
+
if (options.signal.aborted) {
|
|
1049
|
+
controller.abort(options.signal.reason);
|
|
1050
|
+
} else {
|
|
1051
|
+
const onAbort = () => controller.abort(options.signal?.reason);
|
|
1052
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
1053
|
+
return () => options.signal?.removeEventListener("abort", onAbort);
|
|
1054
|
+
}
|
|
1055
|
+
return void 0;
|
|
1056
|
+
})() : void 0;
|
|
1057
|
+
const response = await this.fetchWithMiddleware({
|
|
1058
|
+
url,
|
|
1059
|
+
method,
|
|
1060
|
+
headers,
|
|
1061
|
+
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
1062
|
+
signal: controller.signal
|
|
1063
|
+
});
|
|
1064
|
+
span?.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
|
|
1065
|
+
if (!response.ok) {
|
|
1066
|
+
removeAbortListener?.();
|
|
1067
|
+
const errorText = await response.text();
|
|
1068
|
+
throw mapError(new Error(errorText || `HTTP ${response.status}`), {
|
|
1069
|
+
request: { method, url },
|
|
1070
|
+
response: { status: response.status, body: errorText }
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
if (!response.body) {
|
|
1074
|
+
removeAbortListener?.();
|
|
1075
|
+
throw mapError(new Error("Response body is null, cannot stream SSE"), {
|
|
1076
|
+
request: { method, url }
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
const stream = (async function* () {
|
|
1080
|
+
try {
|
|
1081
|
+
yield* parseSseStream(response.body);
|
|
1082
|
+
} finally {
|
|
1083
|
+
removeAbortListener?.();
|
|
1084
|
+
}
|
|
1085
|
+
})();
|
|
1086
|
+
return {
|
|
1087
|
+
[Symbol.asyncIterator]() {
|
|
1088
|
+
return stream[Symbol.asyncIterator]();
|
|
1089
|
+
},
|
|
1090
|
+
abort: () => {
|
|
1091
|
+
removeAbortListener?.();
|
|
1092
|
+
controller.abort();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
throw mapError(err, { request: { method, url } });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
638
1101
|
async requestStream(options) {
|
|
639
1102
|
const url = `${this.baseUrl}${options.path}`;
|
|
640
1103
|
const method = options.method ?? "POST";
|
|
@@ -644,15 +1107,27 @@ var HttpClient = class {
|
|
|
644
1107
|
httpSpanAttributes(method, url),
|
|
645
1108
|
async (span) => {
|
|
646
1109
|
try {
|
|
647
|
-
const
|
|
1110
|
+
const controller = new AbortController();
|
|
1111
|
+
const removeAbortListener = options.signal !== void 0 ? (() => {
|
|
1112
|
+
if (options.signal.aborted) {
|
|
1113
|
+
controller.abort(options.signal.reason);
|
|
1114
|
+
} else {
|
|
1115
|
+
const onAbort = () => controller.abort(options.signal?.reason);
|
|
1116
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
1117
|
+
return () => options.signal?.removeEventListener("abort", onAbort);
|
|
1118
|
+
}
|
|
1119
|
+
return void 0;
|
|
1120
|
+
})() : void 0;
|
|
1121
|
+
const response = await this.fetchWithMiddleware({
|
|
1122
|
+
url,
|
|
648
1123
|
method,
|
|
649
1124
|
headers,
|
|
650
1125
|
...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
|
|
651
|
-
|
|
652
|
-
};
|
|
653
|
-
const response = await this.fetchImpl(url, init);
|
|
1126
|
+
signal: controller.signal
|
|
1127
|
+
});
|
|
654
1128
|
span?.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, response.status);
|
|
655
1129
|
if (!response.ok) {
|
|
1130
|
+
removeAbortListener?.();
|
|
656
1131
|
const errorText = await response.text();
|
|
657
1132
|
throw mapError(new Error(errorText || `HTTP ${response.status}`), {
|
|
658
1133
|
request: { method, url },
|
|
@@ -660,6 +1135,7 @@ var HttpClient = class {
|
|
|
660
1135
|
});
|
|
661
1136
|
}
|
|
662
1137
|
if (!response.body) {
|
|
1138
|
+
removeAbortListener?.();
|
|
663
1139
|
throw mapError(new Error("Response body is null, cannot stream"), {
|
|
664
1140
|
request: { method, url }
|
|
665
1141
|
});
|
|
@@ -667,7 +1143,17 @@ var HttpClient = class {
|
|
|
667
1143
|
const stream = parseNdjsonStream(response.body);
|
|
668
1144
|
const abortable = {
|
|
669
1145
|
[Symbol.asyncIterator]() {
|
|
670
|
-
return
|
|
1146
|
+
return (async function* () {
|
|
1147
|
+
try {
|
|
1148
|
+
yield* stream;
|
|
1149
|
+
} finally {
|
|
1150
|
+
removeAbortListener?.();
|
|
1151
|
+
}
|
|
1152
|
+
})()[Symbol.asyncIterator]();
|
|
1153
|
+
},
|
|
1154
|
+
abort: () => {
|
|
1155
|
+
removeAbortListener?.();
|
|
1156
|
+
controller.abort();
|
|
671
1157
|
}
|
|
672
1158
|
};
|
|
673
1159
|
return abortable;
|
|
@@ -680,41 +1166,49 @@ var HttpClient = class {
|
|
|
680
1166
|
};
|
|
681
1167
|
|
|
682
1168
|
// src/providers/health-check.ts
|
|
683
|
-
async function checkEndpointHealth(endpoint, fetchImpl, timeoutMs = 5e3) {
|
|
684
|
-
|
|
685
|
-
baseUrl: endpoint.baseUrl,
|
|
686
|
-
apiKey: endpoint.apiKey,
|
|
687
|
-
headers: endpoint.headers,
|
|
688
|
-
fetch: fetchImpl
|
|
689
|
-
});
|
|
690
|
-
const startTime = Date.now();
|
|
691
|
-
const controller = new AbortController();
|
|
692
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1169
|
+
async function checkEndpointHealth(endpoint, fetchImpl, timeoutMs = 5e3, middleware, onLifecycleEvent) {
|
|
1170
|
+
var _stack = [];
|
|
693
1171
|
try {
|
|
694
|
-
const
|
|
695
|
-
path: "/api/version",
|
|
696
|
-
method: "GET",
|
|
697
|
-
signal: controller.signal
|
|
698
|
-
});
|
|
699
|
-
const latencyMs = Date.now() - startTime;
|
|
700
|
-
return {
|
|
701
|
-
name: endpoint.name,
|
|
1172
|
+
const client = new HttpClient({
|
|
702
1173
|
baseUrl: endpoint.baseUrl,
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1174
|
+
apiKey: endpoint.apiKey,
|
|
1175
|
+
headers: endpoint.headers,
|
|
1176
|
+
fetch: fetchImpl,
|
|
1177
|
+
middleware,
|
|
1178
|
+
onLifecycleEvent
|
|
1179
|
+
});
|
|
1180
|
+
const startTime = Date.now();
|
|
1181
|
+
const controller = new AbortController();
|
|
1182
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1183
|
+
const _timer = __using(_stack, disposableTimer(timer));
|
|
1184
|
+
try {
|
|
1185
|
+
const res = await client.request({
|
|
1186
|
+
path: "/api/version",
|
|
1187
|
+
method: "GET",
|
|
1188
|
+
signal: controller.signal
|
|
1189
|
+
});
|
|
1190
|
+
const latencyMs = Date.now() - startTime;
|
|
1191
|
+
return {
|
|
1192
|
+
name: endpoint.name,
|
|
1193
|
+
baseUrl: endpoint.baseUrl,
|
|
1194
|
+
reachable: true,
|
|
1195
|
+
latencyMs,
|
|
1196
|
+
version: res.version
|
|
1197
|
+
};
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
const latencyMs = Date.now() - startTime;
|
|
1200
|
+
return {
|
|
1201
|
+
name: endpoint.name,
|
|
1202
|
+
baseUrl: endpoint.baseUrl,
|
|
1203
|
+
reachable: false,
|
|
1204
|
+
latencyMs,
|
|
1205
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
} catch (_) {
|
|
1209
|
+
var _error = _, _hasError = true;
|
|
716
1210
|
} finally {
|
|
717
|
-
|
|
1211
|
+
__callDispose(_stack, _error, _hasError);
|
|
718
1212
|
}
|
|
719
1213
|
}
|
|
720
1214
|
|
|
@@ -731,13 +1225,24 @@ function inferRuntimeMode(baseUrl) {
|
|
|
731
1225
|
return "unknown";
|
|
732
1226
|
}
|
|
733
1227
|
}
|
|
734
|
-
|
|
1228
|
+
function extractContextLength(modelInfo) {
|
|
1229
|
+
if (!modelInfo) return void 0;
|
|
1230
|
+
for (const [key, value] of Object.entries(modelInfo)) {
|
|
1231
|
+
if (key.endsWith(".context_length") && typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
1232
|
+
return value;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return void 0;
|
|
1236
|
+
}
|
|
1237
|
+
async function detectModelCapabilities(http, model, signal) {
|
|
735
1238
|
const showRes = await http.request({
|
|
736
1239
|
path: "/api/show",
|
|
737
|
-
body: { model }
|
|
1240
|
+
body: { model },
|
|
1241
|
+
...signal !== void 0 ? { signal } : {}
|
|
738
1242
|
});
|
|
739
1243
|
const reported = showRes.capabilities ?? [];
|
|
740
1244
|
const reportedSet = new Set(reported.map((c) => c.toLowerCase()));
|
|
1245
|
+
const contextLength = extractContextLength(showRes.model_info);
|
|
741
1246
|
return {
|
|
742
1247
|
model,
|
|
743
1248
|
reported,
|
|
@@ -746,6 +1251,8 @@ async function detectModelCapabilities(http, model) {
|
|
|
746
1251
|
supportsEmbedding: reportedSet.has("embedding"),
|
|
747
1252
|
supportsCompletion: reportedSet.has("completion") || !reportedSet.has("embedding"),
|
|
748
1253
|
supportsThinking: reportedSet.has("thinking"),
|
|
1254
|
+
...showRes.thinking !== void 0 ? { thinking: showRes.thinking } : {},
|
|
1255
|
+
...contextLength !== void 0 ? { contextLength } : {},
|
|
749
1256
|
supportsStreaming: true,
|
|
750
1257
|
supportsStructuredOutputRequest: inferRuntimeMode(http.baseUrl) !== "cloud"
|
|
751
1258
|
};
|
|
@@ -870,6 +1377,7 @@ function extractUsage(raw) {
|
|
|
870
1377
|
}
|
|
871
1378
|
return {
|
|
872
1379
|
promptTokens,
|
|
1380
|
+
...raw.prompt_eval_cached_count !== void 0 ? { cachedPromptTokens: raw.prompt_eval_cached_count } : {},
|
|
873
1381
|
completionTokens,
|
|
874
1382
|
totalTokens,
|
|
875
1383
|
...tokensPerSecond !== void 0 ? { tokensPerSecond } : {},
|
|
@@ -893,7 +1401,7 @@ function ensureToolCallIds(toolCalls) {
|
|
|
893
1401
|
|
|
894
1402
|
// src/streaming/stream.ts
|
|
895
1403
|
var OllamaStream = class {
|
|
896
|
-
constructor(source, mapChunk, aggregate, initial) {
|
|
1404
|
+
constructor(source, mapChunk, aggregate, initial, signal) {
|
|
897
1405
|
this.source = source;
|
|
898
1406
|
this.mapChunk = mapChunk;
|
|
899
1407
|
this.aggregate = aggregate;
|
|
@@ -902,6 +1410,15 @@ var OllamaStream = class {
|
|
|
902
1410
|
this.resolveFinal = resolve;
|
|
903
1411
|
this.rejectFinal = reject;
|
|
904
1412
|
});
|
|
1413
|
+
if (signal !== void 0) {
|
|
1414
|
+
const onAbort = () => this.abort();
|
|
1415
|
+
if (signal.aborted) {
|
|
1416
|
+
onAbort();
|
|
1417
|
+
} else {
|
|
1418
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1419
|
+
this.removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
905
1422
|
}
|
|
906
1423
|
source;
|
|
907
1424
|
mapChunk;
|
|
@@ -912,11 +1429,15 @@ var OllamaStream = class {
|
|
|
912
1429
|
finalResultPromise;
|
|
913
1430
|
resolveFinal;
|
|
914
1431
|
rejectFinal;
|
|
1432
|
+
removeAbortListener;
|
|
915
1433
|
get finalResult() {
|
|
916
1434
|
return this.finalResultPromise;
|
|
917
1435
|
}
|
|
918
1436
|
abort() {
|
|
1437
|
+
this.removeAbortListener?.();
|
|
1438
|
+
this.removeAbortListener = void 0;
|
|
919
1439
|
this.source.abort?.();
|
|
1440
|
+
this.rejectFinal(new OllamaAbortError("Ollama stream aborted"));
|
|
920
1441
|
}
|
|
921
1442
|
on(type, listener) {
|
|
922
1443
|
if (this.mode === "iterator") {
|
|
@@ -956,7 +1477,11 @@ var OllamaStream = class {
|
|
|
956
1477
|
}
|
|
957
1478
|
}
|
|
958
1479
|
}
|
|
1480
|
+
this.removeAbortListener?.();
|
|
1481
|
+
this.removeAbortListener = void 0;
|
|
959
1482
|
} catch (error) {
|
|
1483
|
+
this.removeAbortListener?.();
|
|
1484
|
+
this.removeAbortListener = void 0;
|
|
960
1485
|
const mapped = mapError(error);
|
|
961
1486
|
this.emit({ type: "error", data: { error: mapped } });
|
|
962
1487
|
this.rejectFinal(mapped);
|
|
@@ -970,12 +1495,14 @@ var OllamaStream = class {
|
|
|
970
1495
|
}
|
|
971
1496
|
this.mode = "iterator";
|
|
972
1497
|
let accumulated = this.initial;
|
|
1498
|
+
let completed = false;
|
|
973
1499
|
try {
|
|
974
1500
|
for await (const chunk of this.source) {
|
|
975
1501
|
accumulated = this.aggregate(accumulated, chunk);
|
|
976
1502
|
const events = this.mapChunk(chunk, accumulated);
|
|
977
1503
|
for (const event of events) {
|
|
978
1504
|
if (event.type === "done") {
|
|
1505
|
+
completed = true;
|
|
979
1506
|
this.resolveFinal(event.data.result);
|
|
980
1507
|
}
|
|
981
1508
|
yield event;
|
|
@@ -985,6 +1512,13 @@ var OllamaStream = class {
|
|
|
985
1512
|
const mapped = mapError(error);
|
|
986
1513
|
this.rejectFinal(mapped);
|
|
987
1514
|
yield { type: "error", data: { error: mapped } };
|
|
1515
|
+
} finally {
|
|
1516
|
+
this.removeAbortListener?.();
|
|
1517
|
+
this.removeAbortListener = void 0;
|
|
1518
|
+
if (!completed) {
|
|
1519
|
+
this.source.abort?.();
|
|
1520
|
+
this.rejectFinal(new OllamaAbortError("Ollama stream ended before completion"));
|
|
1521
|
+
}
|
|
988
1522
|
}
|
|
989
1523
|
}
|
|
990
1524
|
};
|
|
@@ -1038,13 +1572,13 @@ function withStableToolCallIds(source) {
|
|
|
1038
1572
|
}
|
|
1039
1573
|
return Object.assign(generator(), { abort: source.abort });
|
|
1040
1574
|
}
|
|
1041
|
-
function normalizeChatStream(source) {
|
|
1575
|
+
function normalizeChatStream(source, signal) {
|
|
1042
1576
|
const initial = {
|
|
1043
1577
|
message: { role: "assistant", content: "" },
|
|
1044
1578
|
model: "",
|
|
1045
1579
|
done: false
|
|
1046
1580
|
};
|
|
1047
|
-
return new OllamaStream(withStableToolCallIds(source), mapChatChunk, aggregateChat, initial);
|
|
1581
|
+
return new OllamaStream(withStableToolCallIds(source), mapChatChunk, aggregateChat, initial, signal);
|
|
1048
1582
|
}
|
|
1049
1583
|
function aggregateGenerate(accumulated, chunk) {
|
|
1050
1584
|
return {
|
|
@@ -1054,6 +1588,10 @@ function aggregateGenerate(accumulated, chunk) {
|
|
|
1054
1588
|
doneReason: chunk.done ? chunk.done_reason : accumulated.doneReason,
|
|
1055
1589
|
totalDurationMs: chunk.done && chunk.total_duration !== void 0 ? chunk.total_duration / NANOS_PER_MS : accumulated.totalDurationMs,
|
|
1056
1590
|
usage: chunk.done ? extractUsage(chunk) : accumulated.usage,
|
|
1591
|
+
thinking: chunk.thinking !== void 0 ? (accumulated.thinking ?? "") + chunk.thinking : accumulated.thinking,
|
|
1592
|
+
image: chunk.image ?? accumulated.image,
|
|
1593
|
+
completed: chunk.completed ?? accumulated.completed,
|
|
1594
|
+
total: chunk.total ?? accumulated.total,
|
|
1057
1595
|
raw: chunk
|
|
1058
1596
|
};
|
|
1059
1597
|
}
|
|
@@ -1071,9 +1609,9 @@ function mapGenerateChunk(chunk, aggregated) {
|
|
|
1071
1609
|
}
|
|
1072
1610
|
return events;
|
|
1073
1611
|
}
|
|
1074
|
-
function normalizeGenerateStream(source) {
|
|
1612
|
+
function normalizeGenerateStream(source, signal) {
|
|
1075
1613
|
const initial = { response: "", model: "", done: false };
|
|
1076
|
-
return new OllamaStream(source, mapGenerateChunk, aggregateGenerate, initial);
|
|
1614
|
+
return new OllamaStream(source, mapGenerateChunk, aggregateGenerate, initial, signal);
|
|
1077
1615
|
}
|
|
1078
1616
|
function aggregateProgress(_accumulated, chunk) {
|
|
1079
1617
|
return {
|
|
@@ -1093,9 +1631,9 @@ function mapProgressChunk(chunk, aggregated) {
|
|
|
1093
1631
|
}
|
|
1094
1632
|
return events;
|
|
1095
1633
|
}
|
|
1096
|
-
function normalizeProgressStream(source) {
|
|
1634
|
+
function normalizeProgressStream(source, signal) {
|
|
1097
1635
|
const initial = { status: "", done: false };
|
|
1098
|
-
return new OllamaStream(source, mapProgressChunk, aggregateProgress, initial);
|
|
1636
|
+
return new OllamaStream(source, mapProgressChunk, aggregateProgress, initial, signal);
|
|
1099
1637
|
}
|
|
1100
1638
|
|
|
1101
1639
|
// src/transport/backoff.ts
|
|
@@ -1121,8 +1659,33 @@ function isRetryableDefault(error) {
|
|
|
1121
1659
|
}
|
|
1122
1660
|
return false;
|
|
1123
1661
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1662
|
+
function abortError(signal, fallbackMessage) {
|
|
1663
|
+
const reason = signal?.reason;
|
|
1664
|
+
if (reason instanceof OllamaClientError) return reason;
|
|
1665
|
+
return new OllamaAbortError(
|
|
1666
|
+
reason instanceof Error && reason.message ? reason.message : fallbackMessage,
|
|
1667
|
+
{ cause: reason }
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
function sleep(ms, signal) {
|
|
1671
|
+
return new Promise((resolve, reject) => {
|
|
1672
|
+
if (signal?.aborted) {
|
|
1673
|
+
reject(abortError(signal, "Retry backoff aborted"));
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
const timerId = setTimeout(() => {
|
|
1677
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1678
|
+
resolve();
|
|
1679
|
+
}, ms);
|
|
1680
|
+
const onAbort = () => {
|
|
1681
|
+
if (timerId !== void 0) clearTimeout(timerId);
|
|
1682
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1683
|
+
reject(abortError(signal, "Retry backoff aborted"));
|
|
1684
|
+
};
|
|
1685
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
async function withRetry(operation, config = DEFAULT_RETRY_CONFIG, signal) {
|
|
1126
1689
|
let attempt = 0;
|
|
1127
1690
|
while (true) {
|
|
1128
1691
|
try {
|
|
@@ -1135,7 +1698,7 @@ async function withRetry(operation, config = DEFAULT_RETRY_CONFIG) {
|
|
|
1135
1698
|
}
|
|
1136
1699
|
const delayMs = calculateBackoff(attempt, config.backoff);
|
|
1137
1700
|
config.onRetry?.(error, attempt, delayMs);
|
|
1138
|
-
await sleep(delayMs);
|
|
1701
|
+
await sleep(delayMs, signal);
|
|
1139
1702
|
attempt += 1;
|
|
1140
1703
|
}
|
|
1141
1704
|
}
|
|
@@ -1201,7 +1764,7 @@ var ModelsClient = class {
|
|
|
1201
1764
|
body: { ...request, stream: true },
|
|
1202
1765
|
signal
|
|
1203
1766
|
});
|
|
1204
|
-
return normalizeProgressStream(stream);
|
|
1767
|
+
return normalizeProgressStream(stream, signal);
|
|
1205
1768
|
},
|
|
1206
1769
|
{ ...request, singleEndpoint: true, holdUntil: (stream) => stream.finalResult }
|
|
1207
1770
|
);
|
|
@@ -1224,7 +1787,7 @@ var ModelsClient = class {
|
|
|
1224
1787
|
body: { ...request, stream: true },
|
|
1225
1788
|
signal
|
|
1226
1789
|
});
|
|
1227
|
-
return normalizeProgressStream(stream);
|
|
1790
|
+
return normalizeProgressStream(stream, signal);
|
|
1228
1791
|
},
|
|
1229
1792
|
{ ...request, singleEndpoint: true, holdUntil: (stream) => stream.finalResult }
|
|
1230
1793
|
);
|
|
@@ -1318,34 +1881,607 @@ var ModelsClient = class {
|
|
|
1318
1881
|
};
|
|
1319
1882
|
|
|
1320
1883
|
// src/integrations/openai.ts
|
|
1321
|
-
var
|
|
1322
|
-
|
|
1323
|
-
|
|
1884
|
+
var OpenAIResponsesStreamError = class extends OllamaClientError {
|
|
1885
|
+
responsePayload;
|
|
1886
|
+
constructor(message, responsePayload) {
|
|
1887
|
+
super(message, { code: "openai_responses_stream_error", retryable: false, response: { body: responsePayload } });
|
|
1888
|
+
this.responsePayload = responsePayload;
|
|
1324
1889
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1890
|
+
};
|
|
1891
|
+
var OpenAIChatCompletionStream = class {
|
|
1892
|
+
constructor(source, signal) {
|
|
1893
|
+
this.source = source;
|
|
1894
|
+
this.finalResultPromise = new Promise((resolve, reject) => {
|
|
1895
|
+
this.resolveFinal = resolve;
|
|
1896
|
+
this.rejectFinal = reject;
|
|
1331
1897
|
});
|
|
1898
|
+
if (signal !== void 0) {
|
|
1899
|
+
const onAbort = () => this.abort();
|
|
1900
|
+
if (signal.aborted) onAbort();
|
|
1901
|
+
else {
|
|
1902
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1903
|
+
this.removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1332
1906
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1907
|
+
source;
|
|
1908
|
+
finalResultPromise;
|
|
1909
|
+
resolveFinal;
|
|
1910
|
+
rejectFinal;
|
|
1911
|
+
removeAbortListener;
|
|
1912
|
+
get finalResult() {
|
|
1913
|
+
return this.finalResultPromise;
|
|
1914
|
+
}
|
|
1915
|
+
abort() {
|
|
1916
|
+
this.removeAbortListener?.();
|
|
1917
|
+
this.removeAbortListener = void 0;
|
|
1918
|
+
this.source.abort?.();
|
|
1919
|
+
this.rejectFinal(new OllamaAbortError("OpenAI Chat Completion stream aborted"));
|
|
1920
|
+
}
|
|
1921
|
+
async *[Symbol.asyncIterator]() {
|
|
1922
|
+
const choices = /* @__PURE__ */ new Map();
|
|
1923
|
+
let id = "";
|
|
1924
|
+
let model = "";
|
|
1925
|
+
let created = 0;
|
|
1926
|
+
let systemFingerprint;
|
|
1927
|
+
let usage;
|
|
1928
|
+
let completed = false;
|
|
1929
|
+
try {
|
|
1930
|
+
for await (const event of this.source) {
|
|
1931
|
+
if (event.data === "[DONE]") break;
|
|
1932
|
+
let chunk;
|
|
1933
|
+
try {
|
|
1934
|
+
chunk = JSON.parse(event.data);
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
throw new Error("Failed to parse OpenAI chat completion SSE payload", { cause: error });
|
|
1937
|
+
}
|
|
1938
|
+
id = chunk.id || id;
|
|
1939
|
+
model = chunk.model || model;
|
|
1940
|
+
created = chunk.created || created;
|
|
1941
|
+
if (chunk.system_fingerprint !== void 0) systemFingerprint = chunk.system_fingerprint;
|
|
1942
|
+
if (chunk.usage !== null && chunk.usage !== void 0) usage = chunk.usage;
|
|
1943
|
+
for (const choice of chunk.choices) {
|
|
1944
|
+
const existing = choices.get(choice.index);
|
|
1945
|
+
const delta = choice.delta;
|
|
1946
|
+
if (!existing) {
|
|
1947
|
+
const toolCalls = delta.tool_calls?.map((toolCall) => ({
|
|
1948
|
+
id: toolCall.id ?? "",
|
|
1949
|
+
type: "function",
|
|
1950
|
+
function: {
|
|
1951
|
+
name: toolCall.function?.name ?? "",
|
|
1952
|
+
arguments: toolCall.function?.arguments ?? ""
|
|
1953
|
+
}
|
|
1954
|
+
}));
|
|
1955
|
+
choices.set(choice.index, {
|
|
1956
|
+
message: {
|
|
1957
|
+
role: delta.role ?? "assistant",
|
|
1958
|
+
content: delta.content ?? "",
|
|
1959
|
+
...delta.reasoning !== void 0 && delta.reasoning !== null ? { reasoning: delta.reasoning } : {},
|
|
1960
|
+
...toolCalls?.length ? { tool_calls: toolCalls } : {}
|
|
1961
|
+
},
|
|
1962
|
+
finish_reason: choice.finish_reason ?? "",
|
|
1963
|
+
...choice.logprobs !== void 0 ? { logprobs: choice.logprobs } : {}
|
|
1964
|
+
});
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
const priorToolCalls = [...existing.message.tool_calls ?? []];
|
|
1968
|
+
for (const toolCall of delta.tool_calls ?? []) {
|
|
1969
|
+
const position = toolCall.index;
|
|
1970
|
+
const current = priorToolCalls[position];
|
|
1971
|
+
if (current) {
|
|
1972
|
+
priorToolCalls[position] = {
|
|
1973
|
+
...current,
|
|
1974
|
+
...toolCall.id !== void 0 ? { id: toolCall.id } : {},
|
|
1975
|
+
function: {
|
|
1976
|
+
name: current.function?.name ?? toolCall.function?.name ?? "",
|
|
1977
|
+
arguments: (current.function?.arguments ?? "") + (toolCall.function?.arguments ?? "")
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
} else {
|
|
1981
|
+
priorToolCalls[position] = {
|
|
1982
|
+
id: toolCall.id ?? "",
|
|
1983
|
+
type: "function",
|
|
1984
|
+
function: {
|
|
1985
|
+
name: toolCall.function?.name ?? "",
|
|
1986
|
+
arguments: toolCall.function?.arguments ?? ""
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
choices.set(choice.index, {
|
|
1992
|
+
message: {
|
|
1993
|
+
...existing.message,
|
|
1994
|
+
...delta.content !== void 0 && delta.content !== null ? { content: existing.message.content + delta.content } : {},
|
|
1995
|
+
...delta.reasoning !== void 0 && delta.reasoning !== null ? { reasoning: (existing.message.reasoning ?? "") + delta.reasoning } : {},
|
|
1996
|
+
...priorToolCalls.length ? { tool_calls: priorToolCalls } : {}
|
|
1997
|
+
},
|
|
1998
|
+
finish_reason: choice.finish_reason ?? existing.finish_reason,
|
|
1999
|
+
...choice.logprobs !== void 0 ? { logprobs: choice.logprobs } : {}
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
yield chunk;
|
|
2003
|
+
}
|
|
2004
|
+
const response = {
|
|
2005
|
+
id,
|
|
2006
|
+
object: "chat.completion",
|
|
2007
|
+
created,
|
|
2008
|
+
model,
|
|
2009
|
+
...systemFingerprint !== void 0 ? { system_fingerprint: systemFingerprint } : {},
|
|
2010
|
+
choices: [...choices.entries()].sort(([a], [b]) => a - b).map(([index, choice]) => ({
|
|
2011
|
+
index,
|
|
2012
|
+
message: choice.message,
|
|
2013
|
+
finish_reason: choice.finish_reason,
|
|
2014
|
+
...choice.logprobs !== void 0 ? { logprobs: choice.logprobs } : {}
|
|
2015
|
+
})),
|
|
2016
|
+
...usage !== void 0 ? { usage } : {}
|
|
2017
|
+
};
|
|
2018
|
+
completed = true;
|
|
2019
|
+
this.removeAbortListener?.();
|
|
2020
|
+
this.removeAbortListener = void 0;
|
|
2021
|
+
this.resolveFinal(response);
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
this.removeAbortListener?.();
|
|
2024
|
+
this.removeAbortListener = void 0;
|
|
2025
|
+
this.rejectFinal(error);
|
|
2026
|
+
throw error;
|
|
2027
|
+
} finally {
|
|
2028
|
+
if (!completed) {
|
|
2029
|
+
this.source.abort?.();
|
|
2030
|
+
this.rejectFinal(new OllamaAbortError("OpenAI Chat Completion stream ended before completion"));
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
var OpenAICompletionStream = class {
|
|
2036
|
+
constructor(source, signal) {
|
|
2037
|
+
this.source = source;
|
|
2038
|
+
this.finalResultPromise = new Promise((resolve, reject) => {
|
|
2039
|
+
this.resolveFinal = resolve;
|
|
2040
|
+
this.rejectFinal = reject;
|
|
2041
|
+
});
|
|
2042
|
+
if (signal !== void 0) {
|
|
2043
|
+
const onAbort = () => this.abort();
|
|
2044
|
+
if (signal.aborted) onAbort();
|
|
2045
|
+
else {
|
|
2046
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2047
|
+
this.removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
source;
|
|
2052
|
+
finalResultPromise;
|
|
2053
|
+
resolveFinal;
|
|
2054
|
+
rejectFinal;
|
|
2055
|
+
removeAbortListener;
|
|
2056
|
+
get finalResult() {
|
|
2057
|
+
return this.finalResultPromise;
|
|
2058
|
+
}
|
|
2059
|
+
abort() {
|
|
2060
|
+
this.removeAbortListener?.();
|
|
2061
|
+
this.removeAbortListener = void 0;
|
|
2062
|
+
this.source.abort?.();
|
|
2063
|
+
this.rejectFinal(new OllamaAbortError("OpenAI Completion stream aborted"));
|
|
2064
|
+
}
|
|
2065
|
+
async *[Symbol.asyncIterator]() {
|
|
2066
|
+
const texts = /* @__PURE__ */ new Map();
|
|
2067
|
+
let id = "";
|
|
2068
|
+
let model = "";
|
|
2069
|
+
let created = 0;
|
|
2070
|
+
let usage;
|
|
2071
|
+
let completed = false;
|
|
2072
|
+
try {
|
|
2073
|
+
for await (const event of this.source) {
|
|
2074
|
+
if (event.data === "[DONE]") break;
|
|
2075
|
+
const chunk = JSON.parse(event.data);
|
|
2076
|
+
id = chunk.id || id;
|
|
2077
|
+
model = chunk.model || model;
|
|
2078
|
+
created = chunk.created || created;
|
|
2079
|
+
if (chunk.usage !== null && chunk.usage !== void 0) usage = chunk.usage;
|
|
2080
|
+
for (const choice of chunk.choices) {
|
|
2081
|
+
const existing = texts.get(choice.index);
|
|
2082
|
+
texts.set(choice.index, {
|
|
2083
|
+
text: (existing?.text ?? "") + (choice.text ?? ""),
|
|
2084
|
+
index: choice.index,
|
|
2085
|
+
finish_reason: choice.finish_reason ?? existing?.finish_reason ?? null,
|
|
2086
|
+
...choice.logprobs !== void 0 ? { logprobs: choice.logprobs } : {}
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
yield chunk;
|
|
2090
|
+
}
|
|
2091
|
+
const response = {
|
|
2092
|
+
id,
|
|
2093
|
+
object: "text_completion",
|
|
2094
|
+
created,
|
|
2095
|
+
model,
|
|
2096
|
+
choices: [...texts.values()].sort((a, b) => a.index - b.index),
|
|
2097
|
+
...usage !== void 0 ? { usage } : {}
|
|
2098
|
+
};
|
|
2099
|
+
completed = true;
|
|
2100
|
+
this.removeAbortListener?.();
|
|
2101
|
+
this.removeAbortListener = void 0;
|
|
2102
|
+
this.resolveFinal(response);
|
|
2103
|
+
} catch (error) {
|
|
2104
|
+
this.removeAbortListener?.();
|
|
2105
|
+
this.removeAbortListener = void 0;
|
|
2106
|
+
this.rejectFinal(error);
|
|
2107
|
+
throw error;
|
|
2108
|
+
} finally {
|
|
2109
|
+
if (!completed) {
|
|
2110
|
+
this.source.abort?.();
|
|
2111
|
+
this.rejectFinal(new OllamaAbortError("OpenAI Completion stream ended before completion"));
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
var OpenAIResponsesStream = class {
|
|
2117
|
+
constructor(source, signal) {
|
|
2118
|
+
this.source = source;
|
|
2119
|
+
this.finalResultPromise = new Promise((resolve, reject) => {
|
|
2120
|
+
this.resolveFinal = resolve;
|
|
2121
|
+
this.rejectFinal = reject;
|
|
2122
|
+
});
|
|
2123
|
+
if (signal !== void 0) {
|
|
2124
|
+
const onAbort = () => this.abort();
|
|
2125
|
+
if (signal.aborted) onAbort();
|
|
2126
|
+
else {
|
|
2127
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2128
|
+
this.removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
source;
|
|
2133
|
+
finalResultPromise;
|
|
2134
|
+
resolveFinal;
|
|
2135
|
+
rejectFinal;
|
|
2136
|
+
removeAbortListener;
|
|
2137
|
+
get finalResult() {
|
|
2138
|
+
return this.finalResultPromise;
|
|
2139
|
+
}
|
|
2140
|
+
abort() {
|
|
2141
|
+
this.removeAbortListener?.();
|
|
2142
|
+
this.removeAbortListener = void 0;
|
|
2143
|
+
this.source.abort?.();
|
|
2144
|
+
this.rejectFinal(new OllamaAbortError("OpenAI Responses stream aborted"));
|
|
2145
|
+
}
|
|
2146
|
+
async *[Symbol.asyncIterator]() {
|
|
2147
|
+
let finalResponse;
|
|
2148
|
+
let responseMeta;
|
|
2149
|
+
let terminalFailure;
|
|
2150
|
+
let terminalIncomplete;
|
|
2151
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
2152
|
+
const canonicalOutputItems = /* @__PURE__ */ new Map();
|
|
2153
|
+
let completed = false;
|
|
2154
|
+
const ensureState = (outputIndex, itemId, kind) => {
|
|
2155
|
+
const existing = outputs.get(outputIndex);
|
|
2156
|
+
if (existing) {
|
|
2157
|
+
if (itemId) existing.itemId = itemId;
|
|
2158
|
+
existing.kind = kind;
|
|
2159
|
+
return existing;
|
|
2160
|
+
}
|
|
2161
|
+
const state = { itemId, kind, content: /* @__PURE__ */ new Map(), summary: /* @__PURE__ */ new Map(), arguments: "", reasoningText: "" };
|
|
2162
|
+
outputs.set(outputIndex, state);
|
|
2163
|
+
return state;
|
|
2164
|
+
};
|
|
2165
|
+
const applyOutputItem = (outputIndex, item) => {
|
|
2166
|
+
const kind = item.type === "message" ? "message" : item.type === "function_call" ? "function_call" : "reasoning";
|
|
2167
|
+
const state = ensureState(outputIndex, item.id ?? "", kind);
|
|
2168
|
+
state.itemId = item.id ?? state.itemId;
|
|
2169
|
+
state.status = item.status;
|
|
2170
|
+
if (item.type === "message") {
|
|
2171
|
+
state.role = item.role;
|
|
2172
|
+
state.phase = item.phase;
|
|
2173
|
+
state.content.clear();
|
|
2174
|
+
item.content.forEach((p, i) => state.content.set(i, p));
|
|
2175
|
+
} else if (item.type === "function_call") {
|
|
2176
|
+
state.callId = item.call_id;
|
|
2177
|
+
state.name = item.name;
|
|
2178
|
+
state.arguments = item.arguments;
|
|
2179
|
+
} else {
|
|
2180
|
+
state.summary.clear();
|
|
2181
|
+
item.summary?.forEach((p, i) => state.summary.set(i, p.text));
|
|
2182
|
+
state.reasoningText = item.text ?? "";
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
const applyContentPart = (outputIndex, itemId, contentIndex, part) => {
|
|
2186
|
+
const state = ensureState(outputIndex, itemId, "message");
|
|
2187
|
+
state.content.set(contentIndex, part);
|
|
2188
|
+
};
|
|
2189
|
+
const reconstructedOutput = () => [...outputs.entries()].sort(([a], [b]) => a - b).map(([outputIndex, state]) => {
|
|
2190
|
+
const canonical = canonicalOutputItems.get(outputIndex);
|
|
2191
|
+
if (canonical) return canonical;
|
|
2192
|
+
if (state.kind === "message") return { type: "message", ...state.itemId ? { id: state.itemId } : {}, ...state.status ? { status: state.status } : {}, role: state.role ?? "assistant", ...state.phase ? { phase: state.phase } : {}, content: [...state.content.entries()].sort(([a], [b]) => a - b).map(([, p]) => p) };
|
|
2193
|
+
if (state.kind === "function_call") return { type: "function_call", ...state.itemId ? { id: state.itemId } : {}, ...state.callId ? { call_id: state.callId } : {}, ...state.status ? { status: state.status } : {}, name: state.name ?? "", arguments: state.arguments };
|
|
2194
|
+
return { type: "reasoning", ...state.itemId ? { id: state.itemId } : {}, ...state.status ? { status: state.status } : {}, ...state.summary.size ? { summary: [...state.summary.entries()].sort(([a], [b]) => a - b).map(([, text]) => ({ type: "summary_text", text })) } : {}, ...state.reasoningText ? { text: state.reasoningText } : {} };
|
|
2195
|
+
});
|
|
2196
|
+
try {
|
|
2197
|
+
for await (const event of this.source) {
|
|
2198
|
+
if (event.data === "[DONE]") break;
|
|
2199
|
+
let payload;
|
|
2200
|
+
try {
|
|
2201
|
+
payload = JSON.parse(event.data);
|
|
2202
|
+
} catch (error) {
|
|
2203
|
+
throw new Error("Failed to parse OpenAI Responses SSE payload", { cause: error });
|
|
2204
|
+
}
|
|
2205
|
+
switch (payload.type) {
|
|
2206
|
+
case "response.created":
|
|
2207
|
+
case "response.in_progress":
|
|
2208
|
+
case "response.queued":
|
|
2209
|
+
responseMeta = payload.response;
|
|
2210
|
+
break;
|
|
2211
|
+
case "response.completed":
|
|
2212
|
+
case "response.done":
|
|
2213
|
+
finalResponse = payload.response;
|
|
2214
|
+
break;
|
|
2215
|
+
case "response.failed":
|
|
2216
|
+
terminalFailure = payload.response;
|
|
2217
|
+
finalResponse = terminalFailure;
|
|
2218
|
+
break;
|
|
2219
|
+
case "response.incomplete":
|
|
2220
|
+
terminalIncomplete = payload.response;
|
|
2221
|
+
finalResponse = terminalIncomplete;
|
|
2222
|
+
break;
|
|
2223
|
+
case "response.output_item.added": {
|
|
2224
|
+
const e = payload;
|
|
2225
|
+
applyOutputItem(e.output_index, e.item);
|
|
2226
|
+
break;
|
|
2227
|
+
}
|
|
2228
|
+
case "response.output_item.done": {
|
|
2229
|
+
const e = payload;
|
|
2230
|
+
applyOutputItem(e.output_index, e.item);
|
|
2231
|
+
canonicalOutputItems.set(e.output_index, e.item);
|
|
2232
|
+
break;
|
|
2233
|
+
}
|
|
2234
|
+
case "response.content_part.added":
|
|
2235
|
+
case "response.content_part.done": {
|
|
2236
|
+
const e = payload;
|
|
2237
|
+
if (e.part.type === "output_text" && "text" in e.part && typeof e.part.text === "string" || e.part.type === "refusal" && "refusal" in e.part && typeof e.part.refusal === "string") {
|
|
2238
|
+
applyContentPart(e.output_index, e.item_id, e.content_index, e.part);
|
|
2239
|
+
}
|
|
2240
|
+
break;
|
|
2241
|
+
}
|
|
2242
|
+
case "response.output_text.delta": {
|
|
2243
|
+
const e = payload;
|
|
2244
|
+
const s = ensureState(e.output_index, e.item_id, "message");
|
|
2245
|
+
const p = s.content.get(e.content_index);
|
|
2246
|
+
s.content.set(e.content_index, { type: "output_text", text: (p?.type === "output_text" ? p.text : "") + e.delta });
|
|
2247
|
+
break;
|
|
2248
|
+
}
|
|
2249
|
+
case "response.output_text.done": {
|
|
2250
|
+
const e = payload;
|
|
2251
|
+
const s = ensureState(e.output_index, e.item_id, "message");
|
|
2252
|
+
s.content.set(e.content_index, { type: "output_text", text: e.text });
|
|
2253
|
+
break;
|
|
2254
|
+
}
|
|
2255
|
+
case "response.refusal.delta": {
|
|
2256
|
+
const e = payload;
|
|
2257
|
+
const s = ensureState(e.output_index, e.item_id, "message");
|
|
2258
|
+
const p = s.content.get(e.content_index);
|
|
2259
|
+
s.content.set(e.content_index, { type: "refusal", refusal: (p?.type === "refusal" ? p.refusal : "") + e.delta });
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
2262
|
+
case "response.refusal.done": {
|
|
2263
|
+
const e = payload;
|
|
2264
|
+
const s = ensureState(e.output_index, e.item_id, "message");
|
|
2265
|
+
s.content.set(e.content_index, { type: "refusal", refusal: e.refusal });
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
case "response.function_call_arguments.delta": {
|
|
2269
|
+
const e = payload;
|
|
2270
|
+
const s = ensureState(e.output_index, e.item_id, "function_call");
|
|
2271
|
+
s.arguments += e.delta;
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
case "response.function_call_arguments.done": {
|
|
2275
|
+
const e = payload;
|
|
2276
|
+
const s = ensureState(e.output_index, e.item_id, "function_call");
|
|
2277
|
+
s.arguments = e.arguments;
|
|
2278
|
+
if (e.name !== void 0) s.name = e.name;
|
|
2279
|
+
break;
|
|
2280
|
+
}
|
|
2281
|
+
case "response.reasoning_summary_part.added":
|
|
2282
|
+
case "response.reasoning_summary_part.done": {
|
|
2283
|
+
const e = payload;
|
|
2284
|
+
const s = ensureState(e.output_index, e.item_id, "reasoning");
|
|
2285
|
+
s.summary.set(e.summary_index, e.part.text);
|
|
2286
|
+
break;
|
|
2287
|
+
}
|
|
2288
|
+
case "response.reasoning_summary_text.delta": {
|
|
2289
|
+
const e = payload;
|
|
2290
|
+
const s = ensureState(e.output_index, e.item_id, "reasoning");
|
|
2291
|
+
s.summary.set(e.summary_index, (s.summary.get(e.summary_index) ?? "") + e.delta);
|
|
2292
|
+
break;
|
|
2293
|
+
}
|
|
2294
|
+
case "response.reasoning_summary_text.done": {
|
|
2295
|
+
const e = payload;
|
|
2296
|
+
const s = ensureState(e.output_index, e.item_id, "reasoning");
|
|
2297
|
+
s.summary.set(e.summary_index, e.text);
|
|
2298
|
+
break;
|
|
2299
|
+
}
|
|
2300
|
+
case "response.reasoning_text.delta": {
|
|
2301
|
+
const e = payload;
|
|
2302
|
+
const s = ensureState(e.output_index, e.item_id, "reasoning");
|
|
2303
|
+
s.reasoningText += e.delta;
|
|
2304
|
+
break;
|
|
2305
|
+
}
|
|
2306
|
+
case "response.reasoning_text.done": {
|
|
2307
|
+
const e = payload;
|
|
2308
|
+
const s = ensureState(e.output_index, e.item_id, "reasoning");
|
|
2309
|
+
s.reasoningText = e.text;
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
default:
|
|
2313
|
+
break;
|
|
2314
|
+
}
|
|
2315
|
+
yield payload;
|
|
2316
|
+
}
|
|
2317
|
+
if (terminalFailure || terminalIncomplete) {
|
|
2318
|
+
const response = terminalFailure ?? terminalIncomplete;
|
|
2319
|
+
const status = terminalFailure ? "failed" : "incomplete";
|
|
2320
|
+
const detail = response.error?.message ?? response.incomplete_details?.reason ?? "unknown error";
|
|
2321
|
+
throw new OpenAIResponsesStreamError("OpenAI Responses stream " + status + ": " + detail, response);
|
|
2322
|
+
}
|
|
2323
|
+
if (!finalResponse) {
|
|
2324
|
+
if (!responseMeta) throw new Error("OpenAI Responses stream ended without a terminal response payload");
|
|
2325
|
+
if (responseMeta.status === "failed" || responseMeta.status === "incomplete") {
|
|
2326
|
+
const detail = responseMeta.error?.message ?? responseMeta.incomplete_details?.reason ?? "unknown error";
|
|
2327
|
+
throw new OpenAIResponsesStreamError("OpenAI Responses stream " + responseMeta.status + ": " + detail, responseMeta);
|
|
2328
|
+
}
|
|
2329
|
+
finalResponse = { ...responseMeta, output: reconstructedOutput(), status: "completed" };
|
|
2330
|
+
} else if (finalResponse.status === "failed" || finalResponse.status === "incomplete") {
|
|
2331
|
+
const detail = finalResponse.error?.message ?? finalResponse.incomplete_details?.reason ?? "unknown error";
|
|
2332
|
+
throw new OpenAIResponsesStreamError("OpenAI Responses stream " + finalResponse.status + ": " + detail, finalResponse);
|
|
2333
|
+
}
|
|
2334
|
+
if (finalResponse.output.length === 0 && outputs.size > 0) finalResponse = { ...finalResponse, output: reconstructedOutput() };
|
|
2335
|
+
completed = true;
|
|
2336
|
+
this.removeAbortListener?.();
|
|
2337
|
+
this.removeAbortListener = void 0;
|
|
2338
|
+
this.resolveFinal(finalResponse);
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
this.removeAbortListener?.();
|
|
2341
|
+
this.removeAbortListener = void 0;
|
|
2342
|
+
this.rejectFinal(error);
|
|
2343
|
+
throw error;
|
|
2344
|
+
} finally {
|
|
2345
|
+
if (!completed) {
|
|
2346
|
+
this.source.abort?.();
|
|
2347
|
+
this.rejectFinal(new OllamaAbortError("OpenAI Responses stream ended before completion"));
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
var OpenAICompatClient = class {
|
|
2353
|
+
constructor(http, runner) {
|
|
2354
|
+
this.http = http;
|
|
2355
|
+
this.runner = runner;
|
|
2356
|
+
}
|
|
2357
|
+
http;
|
|
2358
|
+
runner;
|
|
2359
|
+
request(operation, model, signal, holdUntil) {
|
|
2360
|
+
if (this.runner) {
|
|
2361
|
+
return this.runner(
|
|
2362
|
+
(http, runnerSignal) => operation(http, runnerSignal),
|
|
2363
|
+
{
|
|
2364
|
+
...model !== void 0 ? { model } : {},
|
|
2365
|
+
...signal !== void 0 ? { signal } : {},
|
|
2366
|
+
...holdUntil !== void 0 ? { holdUntil } : {}
|
|
2367
|
+
}
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
return operation(this.http, signal);
|
|
2371
|
+
}
|
|
2372
|
+
async createChatCompletion(request, signal) {
|
|
2373
|
+
if (request.stream) {
|
|
2374
|
+
return this.request(
|
|
2375
|
+
(http, requestSignal) => http.requestSseStream({
|
|
2376
|
+
path: "/v1/chat/completions",
|
|
2377
|
+
body: request,
|
|
2378
|
+
signal: requestSignal
|
|
2379
|
+
}).then((source) => new OpenAIChatCompletionStream(source, requestSignal)),
|
|
2380
|
+
request.model,
|
|
2381
|
+
signal,
|
|
2382
|
+
(stream) => stream.finalResult
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
return this.request(
|
|
2386
|
+
(http, requestSignal) => http.request({
|
|
2387
|
+
path: "/v1/chat/completions",
|
|
2388
|
+
body: request,
|
|
2389
|
+
signal: requestSignal
|
|
2390
|
+
}),
|
|
2391
|
+
request.model,
|
|
2392
|
+
signal
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
async chatCompletions(request, signal) {
|
|
2396
|
+
return this.createChatCompletion(request, signal);
|
|
2397
|
+
}
|
|
2398
|
+
async listModels(signal) {
|
|
2399
|
+
return this.request(
|
|
2400
|
+
(http, requestSignal) => http.request({
|
|
2401
|
+
path: "/v1/models",
|
|
2402
|
+
method: "GET",
|
|
2403
|
+
signal: requestSignal
|
|
2404
|
+
}),
|
|
2405
|
+
void 0,
|
|
2406
|
+
signal
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
async retrieveModel(model, signal) {
|
|
2410
|
+
return this.request(
|
|
2411
|
+
(http, requestSignal) => http.request({
|
|
2412
|
+
path: `/v1/models/${encodeURIComponent(model)}`,
|
|
2413
|
+
method: "GET",
|
|
2414
|
+
signal: requestSignal
|
|
2415
|
+
}),
|
|
2416
|
+
model,
|
|
2417
|
+
signal
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
async getModel(model, signal) {
|
|
2421
|
+
return this.retrieveModel(model, signal);
|
|
2422
|
+
}
|
|
2423
|
+
async createCompletion(request, signal) {
|
|
2424
|
+
if (request.stream) {
|
|
2425
|
+
return this.request(
|
|
2426
|
+
(http, requestSignal) => http.requestSseStream({
|
|
2427
|
+
path: "/v1/completions",
|
|
2428
|
+
body: request,
|
|
2429
|
+
signal: requestSignal
|
|
2430
|
+
}).then((source) => new OpenAICompletionStream(source, requestSignal)),
|
|
2431
|
+
request.model,
|
|
2432
|
+
signal,
|
|
2433
|
+
(stream) => stream.finalResult
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
return this.request(
|
|
2437
|
+
(http, requestSignal) => http.request({
|
|
2438
|
+
path: "/v1/completions",
|
|
2439
|
+
body: request,
|
|
2440
|
+
signal: requestSignal
|
|
2441
|
+
}),
|
|
2442
|
+
request.model,
|
|
2443
|
+
signal
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
async completions(request, signal) {
|
|
2447
|
+
return this.createCompletion(request, signal);
|
|
2448
|
+
}
|
|
2449
|
+
async createEmbedding(request, signal) {
|
|
2450
|
+
return this.request(
|
|
2451
|
+
(http, requestSignal) => http.request({
|
|
2452
|
+
path: "/v1/embeddings",
|
|
2453
|
+
body: request,
|
|
2454
|
+
signal: requestSignal
|
|
2455
|
+
}),
|
|
2456
|
+
request.model,
|
|
2457
|
+
signal
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
async embeddings(request, signal) {
|
|
2461
|
+
return this.createEmbedding(request, signal);
|
|
1342
2462
|
}
|
|
1343
2463
|
async createResponses(request, signal) {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
2464
|
+
if (request.stream) {
|
|
2465
|
+
return this.request(
|
|
2466
|
+
(http, requestSignal) => http.requestSseStream({
|
|
2467
|
+
path: "/v1/responses",
|
|
2468
|
+
body: request,
|
|
2469
|
+
signal: requestSignal
|
|
2470
|
+
}).then((source) => new OpenAIResponsesStream(source, requestSignal)),
|
|
2471
|
+
request.model,
|
|
2472
|
+
signal,
|
|
2473
|
+
(stream) => stream.finalResult
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
return this.request(
|
|
2477
|
+
(http, requestSignal) => http.request({
|
|
2478
|
+
path: "/v1/responses",
|
|
2479
|
+
body: request,
|
|
2480
|
+
signal: requestSignal
|
|
2481
|
+
}),
|
|
2482
|
+
request.model,
|
|
1347
2483
|
signal
|
|
1348
|
-
|
|
2484
|
+
);
|
|
1349
2485
|
}
|
|
1350
2486
|
async responses(request, signal) {
|
|
1351
2487
|
return this.createResponses(request, signal);
|
|
@@ -1353,50 +2489,342 @@ var OpenAICompatClient = class {
|
|
|
1353
2489
|
};
|
|
1354
2490
|
|
|
1355
2491
|
// src/integrations/anthropic.ts
|
|
2492
|
+
function isAnthropicKnownStreamEvent(event) {
|
|
2493
|
+
switch (event.type) {
|
|
2494
|
+
case "message_start":
|
|
2495
|
+
case "content_block_start":
|
|
2496
|
+
case "content_block_delta":
|
|
2497
|
+
case "content_block_stop":
|
|
2498
|
+
case "message_delta":
|
|
2499
|
+
case "message_stop":
|
|
2500
|
+
case "ping":
|
|
2501
|
+
case "error":
|
|
2502
|
+
return true;
|
|
2503
|
+
default:
|
|
2504
|
+
return false;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function parseAnthropicEvent(event) {
|
|
2508
|
+
return JSON.parse(event.data);
|
|
2509
|
+
}
|
|
2510
|
+
function mergeContentBlock(current, delta) {
|
|
2511
|
+
if (delta.type === "text_delta" && current.type === "text") {
|
|
2512
|
+
return { ...current, text: current.text + delta.text };
|
|
2513
|
+
}
|
|
2514
|
+
if (delta.type === "input_json_delta" && current.type === "tool_use") {
|
|
2515
|
+
return current;
|
|
2516
|
+
}
|
|
2517
|
+
if (delta.type === "thinking_delta" && current.type === "thinking") {
|
|
2518
|
+
return { ...current, thinking: current.thinking + delta.thinking };
|
|
2519
|
+
}
|
|
2520
|
+
if (delta.type === "signature_delta" && current.type === "thinking") {
|
|
2521
|
+
return { ...current, signature: (current.signature ?? "") + delta.signature };
|
|
2522
|
+
}
|
|
2523
|
+
return current;
|
|
2524
|
+
}
|
|
2525
|
+
function sanitizeAnthropicTextBlock(block) {
|
|
2526
|
+
if (block.cache_control === void 0) return block;
|
|
2527
|
+
const { cache_control: _cacheControl, ...sanitized } = block;
|
|
2528
|
+
return sanitized;
|
|
2529
|
+
}
|
|
2530
|
+
function sanitizeAnthropicContentBlock(block) {
|
|
2531
|
+
if (block.type === "text") {
|
|
2532
|
+
return sanitizeAnthropicTextBlock(block);
|
|
2533
|
+
}
|
|
2534
|
+
if (block.type === "tool_result" && Array.isArray(block.content)) {
|
|
2535
|
+
return {
|
|
2536
|
+
...block,
|
|
2537
|
+
content: block.content.map(sanitizeAnthropicTextBlock)
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
return block;
|
|
2541
|
+
}
|
|
2542
|
+
function sanitizeAnthropicRequest(request) {
|
|
2543
|
+
const messages = request.messages.map((message) => {
|
|
2544
|
+
if (typeof message.content === "string") return message;
|
|
2545
|
+
return {
|
|
2546
|
+
...message,
|
|
2547
|
+
content: message.content.map(sanitizeAnthropicContentBlock)
|
|
2548
|
+
};
|
|
2549
|
+
});
|
|
2550
|
+
const system = Array.isArray(request.system) ? request.system.map(sanitizeAnthropicTextBlock) : request.system;
|
|
2551
|
+
const {
|
|
2552
|
+
tool_choice: _toolChoice,
|
|
2553
|
+
metadata: _metadata,
|
|
2554
|
+
...sanitized
|
|
2555
|
+
} = request;
|
|
2556
|
+
return {
|
|
2557
|
+
...sanitized,
|
|
2558
|
+
messages,
|
|
2559
|
+
...system !== void 0 ? { system } : {}
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
var AnthropicMessagesStream = class {
|
|
2563
|
+
constructor(source, signal) {
|
|
2564
|
+
this.source = source;
|
|
2565
|
+
this.finalResultPromise = new Promise((resolve, reject) => {
|
|
2566
|
+
this.resolveFinal = resolve;
|
|
2567
|
+
this.rejectFinal = reject;
|
|
2568
|
+
});
|
|
2569
|
+
if (signal !== void 0) {
|
|
2570
|
+
const onAbort = () => this.abort();
|
|
2571
|
+
if (signal.aborted) onAbort();
|
|
2572
|
+
else {
|
|
2573
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2574
|
+
this.removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
source;
|
|
2579
|
+
finalResultPromise;
|
|
2580
|
+
resolveFinal;
|
|
2581
|
+
rejectFinal;
|
|
2582
|
+
removeAbortListener;
|
|
2583
|
+
get finalResult() {
|
|
2584
|
+
return this.finalResultPromise;
|
|
2585
|
+
}
|
|
2586
|
+
abort() {
|
|
2587
|
+
this.removeAbortListener?.();
|
|
2588
|
+
this.removeAbortListener = void 0;
|
|
2589
|
+
this.source.abort?.();
|
|
2590
|
+
this.rejectFinal(new OllamaAbortError("Anthropic Messages stream aborted"));
|
|
2591
|
+
}
|
|
2592
|
+
async *[Symbol.asyncIterator]() {
|
|
2593
|
+
let message;
|
|
2594
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
2595
|
+
const toolInputJson = /* @__PURE__ */ new Map();
|
|
2596
|
+
let completed = false;
|
|
2597
|
+
try {
|
|
2598
|
+
for await (const rawEvent of this.source) {
|
|
2599
|
+
let event;
|
|
2600
|
+
try {
|
|
2601
|
+
event = parseAnthropicEvent(rawEvent);
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
throw new Error("Failed to parse Anthropic message SSE payload", { cause: error });
|
|
2604
|
+
}
|
|
2605
|
+
if (!isAnthropicKnownStreamEvent(event)) {
|
|
2606
|
+
yield event;
|
|
2607
|
+
continue;
|
|
2608
|
+
}
|
|
2609
|
+
if (event.type === "error") {
|
|
2610
|
+
throw new Error(event.error.message);
|
|
2611
|
+
}
|
|
2612
|
+
if (event.type === "message_start") {
|
|
2613
|
+
message = {
|
|
2614
|
+
...event.message,
|
|
2615
|
+
content: [],
|
|
2616
|
+
...event.message.usage !== void 0 ? { usage: event.message.usage } : {}
|
|
2617
|
+
};
|
|
2618
|
+
} else if (event.type === "content_block_start") {
|
|
2619
|
+
blocks.set(event.index, event.content_block);
|
|
2620
|
+
} else if (event.type === "content_block_delta") {
|
|
2621
|
+
const current = blocks.get(event.index);
|
|
2622
|
+
if (event.delta.type === "input_json_delta") {
|
|
2623
|
+
toolInputJson.set(event.index, (toolInputJson.get(event.index) ?? "") + event.delta.partial_json);
|
|
2624
|
+
}
|
|
2625
|
+
if (current) blocks.set(event.index, mergeContentBlock(current, event.delta));
|
|
2626
|
+
} else if (event.type === "content_block_stop") {
|
|
2627
|
+
const current = blocks.get(event.index);
|
|
2628
|
+
if (current?.type === "tool_use") {
|
|
2629
|
+
const rawJson = toolInputJson.get(event.index);
|
|
2630
|
+
if (rawJson !== void 0 && rawJson !== "") {
|
|
2631
|
+
try {
|
|
2632
|
+
const parsed = JSON.parse(rawJson);
|
|
2633
|
+
blocks.set(event.index, { ...current, input: parsed });
|
|
2634
|
+
} catch {
|
|
2635
|
+
blocks.set(event.index, current);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
} else if (event.type === "message_delta") {
|
|
2640
|
+
if (message) {
|
|
2641
|
+
message = {
|
|
2642
|
+
...message,
|
|
2643
|
+
stop_reason: event.delta.stop_reason !== void 0 && event.delta.stop_reason !== null ? event.delta.stop_reason : message.stop_reason,
|
|
2644
|
+
stop_sequence: event.delta.stop_sequence ?? message.stop_sequence,
|
|
2645
|
+
...event.usage !== void 0 ? {
|
|
2646
|
+
usage: {
|
|
2647
|
+
input_tokens: event.usage.input_tokens ?? message.usage?.input_tokens ?? 0,
|
|
2648
|
+
output_tokens: event.usage.output_tokens,
|
|
2649
|
+
...event.usage.cache_creation_input_tokens !== void 0 ? { cache_creation_input_tokens: event.usage.cache_creation_input_tokens } : message.usage?.cache_creation_input_tokens !== void 0 ? { cache_creation_input_tokens: message.usage.cache_creation_input_tokens } : {},
|
|
2650
|
+
...event.usage.cache_read_input_tokens !== void 0 ? { cache_read_input_tokens: event.usage.cache_read_input_tokens } : message.usage?.cache_read_input_tokens !== void 0 ? { cache_read_input_tokens: message.usage.cache_read_input_tokens } : {},
|
|
2651
|
+
...event.usage.cache_creation !== void 0 ? { cache_creation: event.usage.cache_creation } : message.usage?.cache_creation !== void 0 ? { cache_creation: message.usage.cache_creation } : {}
|
|
2652
|
+
}
|
|
2653
|
+
} : {}
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
} else if (event.type === "message_stop" && message) {
|
|
2657
|
+
message = {
|
|
2658
|
+
...message,
|
|
2659
|
+
content: [...blocks.entries()].sort(([a], [b]) => a - b).map(([, block]) => block)
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
yield event;
|
|
2663
|
+
}
|
|
2664
|
+
if (!message) {
|
|
2665
|
+
throw new Error("Anthropic Messages stream ended without message_start");
|
|
2666
|
+
}
|
|
2667
|
+
const final = {
|
|
2668
|
+
...message,
|
|
2669
|
+
content: [...blocks.entries()].sort(([a], [b]) => a - b).map(([, block]) => block)
|
|
2670
|
+
};
|
|
2671
|
+
completed = true;
|
|
2672
|
+
this.removeAbortListener?.();
|
|
2673
|
+
this.removeAbortListener = void 0;
|
|
2674
|
+
this.resolveFinal(final);
|
|
2675
|
+
} catch (error) {
|
|
2676
|
+
this.removeAbortListener?.();
|
|
2677
|
+
this.removeAbortListener = void 0;
|
|
2678
|
+
this.rejectFinal(error);
|
|
2679
|
+
throw error;
|
|
2680
|
+
} finally {
|
|
2681
|
+
if (!completed) {
|
|
2682
|
+
this.source.abort?.();
|
|
2683
|
+
this.rejectFinal(new OllamaAbortError("Anthropic Messages stream ended before completion"));
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
1356
2688
|
var AnthropicCompatClient = class {
|
|
1357
|
-
constructor(http) {
|
|
2689
|
+
constructor(http, runner) {
|
|
1358
2690
|
this.http = http;
|
|
2691
|
+
this.runner = runner;
|
|
1359
2692
|
}
|
|
1360
2693
|
http;
|
|
2694
|
+
runner;
|
|
2695
|
+
request(operation, model, signal, holdUntil) {
|
|
2696
|
+
if (this.runner) {
|
|
2697
|
+
return this.runner(
|
|
2698
|
+
(http, runnerSignal) => operation(http, runnerSignal),
|
|
2699
|
+
{
|
|
2700
|
+
model,
|
|
2701
|
+
...signal !== void 0 ? { signal } : {},
|
|
2702
|
+
...holdUntil !== void 0 ? { holdUntil } : {}
|
|
2703
|
+
}
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
return operation(this.http, signal);
|
|
2707
|
+
}
|
|
1361
2708
|
async createMessage(request, signal) {
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
2709
|
+
if (request.stream) {
|
|
2710
|
+
return this.request(
|
|
2711
|
+
(http, requestSignal) => http.requestSseStream({
|
|
2712
|
+
path: "/v1/messages",
|
|
2713
|
+
body: sanitizeAnthropicRequest(request),
|
|
2714
|
+
headers: { "anthropic-version": "2023-06-01" },
|
|
2715
|
+
signal: requestSignal
|
|
2716
|
+
}).then((source) => new AnthropicMessagesStream(source, requestSignal)),
|
|
2717
|
+
request.model,
|
|
2718
|
+
signal,
|
|
2719
|
+
(stream) => stream.finalResult
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
return this.request(
|
|
2723
|
+
(http, requestSignal) => http.request({
|
|
2724
|
+
path: "/v1/messages",
|
|
2725
|
+
body: sanitizeAnthropicRequest(request),
|
|
2726
|
+
headers: { "anthropic-version": "2023-06-01" },
|
|
2727
|
+
signal: requestSignal
|
|
2728
|
+
}),
|
|
2729
|
+
request.model,
|
|
1365
2730
|
signal
|
|
1366
|
-
|
|
2731
|
+
);
|
|
1367
2732
|
}
|
|
1368
2733
|
async messages(request, signal) {
|
|
2734
|
+
if (request.stream) {
|
|
2735
|
+
return this.createMessage(request, signal);
|
|
2736
|
+
}
|
|
1369
2737
|
return this.createMessage(request, signal);
|
|
1370
2738
|
}
|
|
1371
2739
|
};
|
|
1372
2740
|
|
|
1373
|
-
// src/
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
return btoa(binary);
|
|
2741
|
+
// src/generated/runtime/runtime.ts
|
|
2742
|
+
function compareVersions(a, b) {
|
|
2743
|
+
const pa = a.split(".").map((s) => Number.parseInt(s, 10) ?? 0);
|
|
2744
|
+
const pb = b.split(".").map((s) => Number.parseInt(s, 10) ?? 0);
|
|
2745
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
2746
|
+
const va = pa[i] ?? 0;
|
|
2747
|
+
const vb = pb[i] ?? 0;
|
|
2748
|
+
if (va !== vb) return va - vb;
|
|
2749
|
+
}
|
|
2750
|
+
return 0;
|
|
1384
2751
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
2752
|
+
function assertOperationAllowed(operation, options) {
|
|
2753
|
+
const localMode = options.localMode ?? true;
|
|
2754
|
+
if (!localMode && !operation.environment.cloud) {
|
|
2755
|
+
throw new OllamaGenericClientError(
|
|
2756
|
+
`Operation ${operation.operationId} (${operation.method} ${operation.path}) is local-only and not supported in cloud mode.`
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
if (operation.constraints?.minOllamaVersion && options.serverVersion && compareVersions(options.serverVersion, operation.constraints.minOllamaVersion) < 0) {
|
|
2760
|
+
throw new OllamaGenericClientError(
|
|
2761
|
+
`Operation ${operation.operationId} (${operation.method} ${operation.path}) requires Ollama >= ${operation.constraints.minOllamaVersion} (server reports ${options.serverVersion}).`
|
|
2762
|
+
);
|
|
2763
|
+
}
|
|
1388
2764
|
}
|
|
1389
|
-
|
|
1390
|
-
if (
|
|
1391
|
-
|
|
2765
|
+
function shouldStream(req) {
|
|
2766
|
+
if (req.body && typeof req.body === "object" && "stream" in req.body) {
|
|
2767
|
+
const explicit = req.body.stream;
|
|
2768
|
+
if (typeof explicit === "boolean") return explicit;
|
|
2769
|
+
}
|
|
2770
|
+
return Boolean(req.streamingDefault ?? req.operation.transport.streamingDefault);
|
|
1392
2771
|
}
|
|
1393
|
-
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1396
|
-
|
|
2772
|
+
function buildBody(req) {
|
|
2773
|
+
if (req.body === void 0) return void 0;
|
|
2774
|
+
if (typeof req.body !== "object" || req.body === null) return req.body;
|
|
2775
|
+
const body = req.body;
|
|
2776
|
+
if (req.operation.transport.streaming && req.operation.transport.streamingDefault && body.stream === void 0) {
|
|
2777
|
+
return { ...body, stream: true };
|
|
2778
|
+
}
|
|
2779
|
+
return body;
|
|
1397
2780
|
}
|
|
2781
|
+
var OllamaRuntime = class {
|
|
2782
|
+
constructor(options) {
|
|
2783
|
+
this.options = options;
|
|
2784
|
+
}
|
|
2785
|
+
options;
|
|
2786
|
+
/**
|
|
2787
|
+
* Invoke an operation. Called by every generated API method.
|
|
2788
|
+
*
|
|
2789
|
+
* Returns a `Promise<T>` for non-streaming operations, or an
|
|
2790
|
+
* `AsyncGenerator<T>` for streaming operations. The generated API
|
|
2791
|
+
* class's overloads pick the right return type at compile time.
|
|
2792
|
+
*/
|
|
2793
|
+
async invoke(req) {
|
|
2794
|
+
assertOperationAllowed(req.operation, this.options);
|
|
2795
|
+
const body = buildBody(req);
|
|
2796
|
+
const streaming = shouldStream(req);
|
|
2797
|
+
const httpReq = {
|
|
2798
|
+
path: req.operation.path,
|
|
2799
|
+
method: req.operation.method,
|
|
2800
|
+
...body !== void 0 ? { body } : {},
|
|
2801
|
+
...req.signal !== void 0 ? { signal: req.signal } : {},
|
|
2802
|
+
// For streaming calls, request the raw Response so we can pipe it
|
|
2803
|
+
// through parseNdjsonStream directly. This inherits middleware,
|
|
2804
|
+
// retry, telemetry, and error-mapping from HttpClient — the
|
|
2805
|
+
// previous Wave 3 implementation bypassed them with a direct
|
|
2806
|
+
// `fetch()` call.
|
|
2807
|
+
...streaming ? { raw: true } : {}
|
|
2808
|
+
};
|
|
2809
|
+
if (!streaming) {
|
|
2810
|
+
return await this.options.http.request(httpReq);
|
|
2811
|
+
}
|
|
2812
|
+
const response = await this.options.http.request(httpReq);
|
|
2813
|
+
if (!response.body) {
|
|
2814
|
+
throw new OllamaGenericClientError(
|
|
2815
|
+
`Operation ${req.operation.operationId}: streaming response had no body.`
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
return parseNdjsonStream(response.body);
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
1398
2821
|
|
|
1399
2822
|
// src/client.ts
|
|
2823
|
+
var logicalRequestSequence = 0;
|
|
2824
|
+
function createLogicalRequestId() {
|
|
2825
|
+
logicalRequestSequence += 1;
|
|
2826
|
+
return `ollama-request-${logicalRequestSequence}`;
|
|
2827
|
+
}
|
|
1400
2828
|
var OllamaClient = class {
|
|
1401
2829
|
registry;
|
|
1402
2830
|
models;
|
|
@@ -1405,6 +2833,8 @@ var OllamaClient = class {
|
|
|
1405
2833
|
failoverCodes;
|
|
1406
2834
|
fetchImpl;
|
|
1407
2835
|
logger;
|
|
2836
|
+
middleware;
|
|
2837
|
+
onLifecycleEvent;
|
|
1408
2838
|
/**
|
|
1409
2839
|
* API key for Ollama's hosted web tools (`webSearch`/`webFetch`), which always target
|
|
1410
2840
|
* `OLLAMA_CLOUD_BASE_URL` regardless of `config.endpoints` — an Ollama account API key
|
|
@@ -1425,17 +2855,66 @@ var OllamaClient = class {
|
|
|
1425
2855
|
...config.headers !== void 0 ? { headers: config.headers } : {}
|
|
1426
2856
|
}
|
|
1427
2857
|
]);
|
|
1428
|
-
this.registry = new EndpointRegistry(
|
|
2858
|
+
this.registry = new EndpointRegistry(
|
|
2859
|
+
[...endpoints, ...credentialEndpoints],
|
|
2860
|
+
config.endpointHealth
|
|
2861
|
+
);
|
|
1429
2862
|
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1430
2863
|
this.failoverCodes = new Set(config.failoverOn ?? DEFAULT_FAILOVER_CODES);
|
|
1431
2864
|
this.fetchImpl = config.fetch ?? globalThis.fetch;
|
|
1432
2865
|
this.logger = config.logger ?? (config.debug ? createConsoleLogger() : NOOP_LOGGER);
|
|
2866
|
+
this.middleware = config.middleware;
|
|
2867
|
+
this.onLifecycleEvent = config.onLifecycleEvent;
|
|
1433
2868
|
this.retryConfig = typeof config.retries === "number" ? { ...DEFAULT_RETRY_CONFIG, maxRetries: config.retries } : { ...DEFAULT_RETRY_CONFIG, ...config.retries };
|
|
1434
2869
|
this.models = new ModelsClient((op, opts) => this.executeWithFailover(op, opts));
|
|
1435
2870
|
}
|
|
1436
2871
|
get modelsClient() {
|
|
1437
2872
|
return this.models;
|
|
1438
2873
|
}
|
|
2874
|
+
/**
|
|
2875
|
+
* Return a generated {@link OllamaRuntime} that shares this client's
|
|
2876
|
+
* transport (HttpClient + middleware + retry + telemetry).
|
|
2877
|
+
*
|
|
2878
|
+
* Wave 8 (ADR 0018) bridge: lets callers mix the existing `OllamaClient`
|
|
2879
|
+
* API with the generated `NativeApi` / `OpenAIApi` / `AnthropicApi`
|
|
2880
|
+
* surface without configuring two separate HttpClient instances.
|
|
2881
|
+
*
|
|
2882
|
+
* The returned runtime is bound to the registry's first candidate
|
|
2883
|
+
* endpoint (or the resolved single-endpoint config if no `endpoints`
|
|
2884
|
+
* array was provided). For multi-endpoint configs that need per-call
|
|
2885
|
+
* routing, prefer constructing `OllamaRuntime` directly with a
|
|
2886
|
+
* specific HttpClient:
|
|
2887
|
+
*
|
|
2888
|
+
* ```ts
|
|
2889
|
+
* const http = new HttpClient({ baseUrl: 'http://host-a:11434' });
|
|
2890
|
+
* const runtime = new OllamaRuntime({ http });
|
|
2891
|
+
* const api = new NativeApi(runtime);
|
|
2892
|
+
* ```
|
|
2893
|
+
*
|
|
2894
|
+
* The runtime is cached on first call — subsequent calls return the
|
|
2895
|
+
* same instance.
|
|
2896
|
+
*/
|
|
2897
|
+
get runtime() {
|
|
2898
|
+
if (this._runtime === void 0) {
|
|
2899
|
+
const candidates = this.registry.candidates();
|
|
2900
|
+
const endpoint = candidates[0];
|
|
2901
|
+
const baseUrl = endpoint?.baseUrl ?? "http://localhost:11434";
|
|
2902
|
+
const http = new HttpClient({
|
|
2903
|
+
baseUrl,
|
|
2904
|
+
...endpoint?.apiKey !== void 0 ? { apiKey: endpoint.apiKey } : {},
|
|
2905
|
+
...endpoint?.headers !== void 0 ? { headers: endpoint.headers } : {},
|
|
2906
|
+
fetch: this.fetchImpl,
|
|
2907
|
+
...this.middleware !== void 0 ? { middleware: this.middleware } : {},
|
|
2908
|
+
...this.onLifecycleEvent !== void 0 ? { onLifecycleEvent: this.onLifecycleEvent } : {}
|
|
2909
|
+
});
|
|
2910
|
+
this._runtime = new OllamaRuntime({
|
|
2911
|
+
http,
|
|
2912
|
+
localMode: inferRuntimeMode(baseUrl) === "local"
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
return this._runtime;
|
|
2916
|
+
}
|
|
2917
|
+
_runtime;
|
|
1439
2918
|
/**
|
|
1440
2919
|
* Fail-fast guard for `format` (structured output) requests: throws before any network
|
|
1441
2920
|
* call if the candidate endpoint is inferred as Ollama Cloud, which does not currently
|
|
@@ -1453,6 +2932,8 @@ var OllamaClient = class {
|
|
|
1453
2932
|
}
|
|
1454
2933
|
async executeWithFailover(operation, options) {
|
|
1455
2934
|
const timeout = createTimeoutSignal(options?.timeoutMs ?? this.timeoutMs, options?.signal);
|
|
2935
|
+
const requestId = createLogicalRequestId();
|
|
2936
|
+
let deferTimeoutCancel = false;
|
|
1456
2937
|
try {
|
|
1457
2938
|
let lastError;
|
|
1458
2939
|
for (; ; ) {
|
|
@@ -1479,7 +2960,10 @@ var OllamaClient = class {
|
|
|
1479
2960
|
baseUrl: endpoint.baseUrl,
|
|
1480
2961
|
...endpoint.apiKey !== void 0 ? { apiKey: endpoint.apiKey } : {},
|
|
1481
2962
|
...endpoint.headers !== void 0 ? { headers: endpoint.headers } : {},
|
|
1482
|
-
fetch: this.fetchImpl
|
|
2963
|
+
fetch: this.fetchImpl,
|
|
2964
|
+
middleware: this.middleware,
|
|
2965
|
+
onLifecycleEvent: this.onLifecycleEvent,
|
|
2966
|
+
requestId
|
|
1483
2967
|
});
|
|
1484
2968
|
this.registry.acquire(endpoint.name);
|
|
1485
2969
|
let holdPromise;
|
|
@@ -1490,10 +2974,30 @@ var OllamaClient = class {
|
|
|
1490
2974
|
[ATTR_OLLAMA_ENDPOINT_NAME]: endpoint.name,
|
|
1491
2975
|
[ATTR_OLLAMA_ENDPOINT_ATTEMPT]: attemptIndex
|
|
1492
2976
|
},
|
|
1493
|
-
() => withRetry(
|
|
2977
|
+
() => withRetry(
|
|
2978
|
+
() => operation(http, timeout.signal),
|
|
2979
|
+
{
|
|
2980
|
+
...this.retryConfig,
|
|
2981
|
+
onRetry: (error, attempt, delayMs) => {
|
|
2982
|
+
this.retryConfig.onRetry?.(error, attempt, delayMs);
|
|
2983
|
+
this.onLifecycleEvent?.({
|
|
2984
|
+
type: "retry",
|
|
2985
|
+
requestId,
|
|
2986
|
+
attempt: attempt + 1,
|
|
2987
|
+
error,
|
|
2988
|
+
delayMs,
|
|
2989
|
+
timestamp: Date.now()
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
},
|
|
2993
|
+
timeout.signal
|
|
2994
|
+
)
|
|
1494
2995
|
);
|
|
1495
2996
|
this.registry.reportSuccess(endpoint.name);
|
|
1496
|
-
if (options?.holdUntil)
|
|
2997
|
+
if (options?.holdUntil) {
|
|
2998
|
+
holdPromise = options.holdUntil(result);
|
|
2999
|
+
deferTimeoutCancel = true;
|
|
3000
|
+
}
|
|
1497
3001
|
return result;
|
|
1498
3002
|
} catch (err) {
|
|
1499
3003
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -1504,7 +3008,10 @@ var OllamaClient = class {
|
|
|
1504
3008
|
throw error;
|
|
1505
3009
|
} finally {
|
|
1506
3010
|
if (holdPromise) {
|
|
1507
|
-
void holdPromise.catch(() => void 0).finally(() =>
|
|
3011
|
+
void holdPromise.catch(() => void 0).finally(() => {
|
|
3012
|
+
timeout.cancel();
|
|
3013
|
+
this.registry.release(endpoint.name);
|
|
3014
|
+
});
|
|
1508
3015
|
} else {
|
|
1509
3016
|
this.registry.release(endpoint.name);
|
|
1510
3017
|
}
|
|
@@ -1513,11 +3020,18 @@ var OllamaClient = class {
|
|
|
1513
3020
|
throw lastError ?? new Error("No healthy Ollama endpoints available");
|
|
1514
3021
|
}
|
|
1515
3022
|
} finally {
|
|
1516
|
-
timeout.cancel();
|
|
3023
|
+
if (!deferTimeoutCancel) timeout.cancel();
|
|
1517
3024
|
}
|
|
1518
3025
|
}
|
|
1519
3026
|
async chat(req) {
|
|
1520
|
-
const
|
|
3027
|
+
const encodedMessages = await withEncodedMessageImages(req.messages);
|
|
3028
|
+
const messages = encodedMessages.map((message) => {
|
|
3029
|
+
if (message.role === "tool" && message.tool_call_id !== void 0) {
|
|
3030
|
+
const { tool_call_id: _toolCallId, ...nativeMessage } = message;
|
|
3031
|
+
return nativeMessage;
|
|
3032
|
+
}
|
|
3033
|
+
return message;
|
|
3034
|
+
});
|
|
1521
3035
|
if (req.stream) {
|
|
1522
3036
|
return this.executeWithFailover(
|
|
1523
3037
|
async (http, signal) => {
|
|
@@ -1528,7 +3042,7 @@ var OllamaClient = class {
|
|
|
1528
3042
|
body: { ...req, messages, stream: true },
|
|
1529
3043
|
signal
|
|
1530
3044
|
});
|
|
1531
|
-
return normalizeChatStream(stream);
|
|
3045
|
+
return normalizeChatStream(stream, signal);
|
|
1532
3046
|
},
|
|
1533
3047
|
{
|
|
1534
3048
|
signal: req.signal,
|
|
@@ -1592,7 +3106,7 @@ var OllamaClient = class {
|
|
|
1592
3106
|
body: { ...encodedReq, stream: true },
|
|
1593
3107
|
signal
|
|
1594
3108
|
});
|
|
1595
|
-
return normalizeGenerateStream(stream);
|
|
3109
|
+
return normalizeGenerateStream(stream, signal);
|
|
1596
3110
|
},
|
|
1597
3111
|
{
|
|
1598
3112
|
signal: encodedReq.signal,
|
|
@@ -1711,31 +3225,68 @@ var OllamaClient = class {
|
|
|
1711
3225
|
* multi-candidate loop, since `webSearch`/`webFetch` only ever have the one endpoint.
|
|
1712
3226
|
*/
|
|
1713
3227
|
async executeCloudRequest(operation, options) {
|
|
1714
|
-
|
|
3228
|
+
var _stack = [];
|
|
1715
3229
|
try {
|
|
3230
|
+
const timeout = createTimeoutSignal(options.timeoutMs ?? this.timeoutMs, options.signal);
|
|
3231
|
+
const _timeout = __using(_stack, {
|
|
3232
|
+
[Symbol.dispose]() {
|
|
3233
|
+
timeout.cancel();
|
|
3234
|
+
}
|
|
3235
|
+
});
|
|
3236
|
+
const requestId = createLogicalRequestId();
|
|
1716
3237
|
const http = new HttpClient({
|
|
1717
3238
|
baseUrl: OLLAMA_CLOUD_BASE_URL,
|
|
1718
3239
|
apiKey: this.cloudApiKey,
|
|
1719
|
-
fetch: this.fetchImpl
|
|
3240
|
+
fetch: this.fetchImpl,
|
|
3241
|
+
middleware: this.middleware,
|
|
3242
|
+
onLifecycleEvent: this.onLifecycleEvent,
|
|
3243
|
+
requestId
|
|
1720
3244
|
});
|
|
1721
|
-
return await withRetry(
|
|
3245
|
+
return await withRetry(
|
|
3246
|
+
() => operation(http, timeout.signal),
|
|
3247
|
+
{
|
|
3248
|
+
...this.retryConfig,
|
|
3249
|
+
onRetry: (error, attempt, delayMs) => {
|
|
3250
|
+
this.retryConfig.onRetry?.(error, attempt, delayMs);
|
|
3251
|
+
this.onLifecycleEvent?.({
|
|
3252
|
+
type: "retry",
|
|
3253
|
+
requestId,
|
|
3254
|
+
attempt: attempt + 1,
|
|
3255
|
+
error,
|
|
3256
|
+
delayMs,
|
|
3257
|
+
timestamp: Date.now()
|
|
3258
|
+
});
|
|
3259
|
+
}
|
|
3260
|
+
},
|
|
3261
|
+
timeout.signal
|
|
3262
|
+
);
|
|
3263
|
+
} catch (_) {
|
|
3264
|
+
var _error = _, _hasError = true;
|
|
1722
3265
|
} finally {
|
|
1723
|
-
|
|
3266
|
+
__callDispose(_stack, _error, _hasError);
|
|
1724
3267
|
}
|
|
1725
3268
|
}
|
|
1726
3269
|
// --- Capabilities & Health ---
|
|
1727
|
-
capabilities(model) {
|
|
1728
|
-
return this.executeWithFailover(
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
3270
|
+
capabilities(model, signal) {
|
|
3271
|
+
return this.executeWithFailover(
|
|
3272
|
+
(http, runnerSignal) => detectModelCapabilities(http, model, runnerSignal),
|
|
3273
|
+
{
|
|
3274
|
+
singleEndpoint: true,
|
|
3275
|
+
model,
|
|
3276
|
+
...signal !== void 0 ? { signal } : {}
|
|
3277
|
+
}
|
|
3278
|
+
);
|
|
1732
3279
|
}
|
|
1733
3280
|
runtimeMode() {
|
|
1734
3281
|
const ep = this.registry.candidates()[0];
|
|
1735
3282
|
return inferRuntimeMode(ep?.baseUrl ?? DEFAULT_BASE_URL);
|
|
1736
3283
|
}
|
|
1737
3284
|
healthCheck() {
|
|
1738
|
-
return Promise.all(
|
|
3285
|
+
return Promise.all(
|
|
3286
|
+
this.registry.list().map(
|
|
3287
|
+
(ep) => checkEndpointHealth(ep, this.fetchImpl, 5e3, this.middleware, this.onLifecycleEvent)
|
|
3288
|
+
)
|
|
3289
|
+
);
|
|
1739
3290
|
}
|
|
1740
3291
|
endpointStatus() {
|
|
1741
3292
|
return this.registry.status();
|
|
@@ -1748,8 +3299,11 @@ var OllamaClient = class {
|
|
|
1748
3299
|
baseUrl: ep?.baseUrl ?? DEFAULT_BASE_URL,
|
|
1749
3300
|
apiKey: ep?.apiKey,
|
|
1750
3301
|
headers: ep?.headers,
|
|
1751
|
-
fetch: this.fetchImpl
|
|
1752
|
-
|
|
3302
|
+
fetch: this.fetchImpl,
|
|
3303
|
+
middleware: this.middleware,
|
|
3304
|
+
onLifecycleEvent: this.onLifecycleEvent
|
|
3305
|
+
}),
|
|
3306
|
+
(op, opts) => this.executeWithFailover(op, opts)
|
|
1753
3307
|
);
|
|
1754
3308
|
}
|
|
1755
3309
|
get anthropic() {
|
|
@@ -1759,34 +3313,15 @@ var OllamaClient = class {
|
|
|
1759
3313
|
baseUrl: ep?.baseUrl ?? DEFAULT_BASE_URL,
|
|
1760
3314
|
apiKey: ep?.apiKey,
|
|
1761
3315
|
headers: ep?.headers,
|
|
1762
|
-
fetch: this.fetchImpl
|
|
1763
|
-
|
|
3316
|
+
fetch: this.fetchImpl,
|
|
3317
|
+
middleware: this.middleware,
|
|
3318
|
+
onLifecycleEvent: this.onLifecycleEvent
|
|
3319
|
+
}),
|
|
3320
|
+
(op, opts) => this.executeWithFailover(op, opts)
|
|
1764
3321
|
);
|
|
1765
3322
|
}
|
|
1766
3323
|
};
|
|
1767
3324
|
|
|
1768
|
-
// src/middleware.ts
|
|
1769
|
-
function composeMiddleware(middlewares, finalHandler) {
|
|
1770
|
-
return function runPipeline(initialRequest) {
|
|
1771
|
-
let index = -1;
|
|
1772
|
-
function dispatch(i, currentRequest) {
|
|
1773
|
-
if (i <= index) {
|
|
1774
|
-
return Promise.reject(new Error("next() called multiple times in middleware"));
|
|
1775
|
-
}
|
|
1776
|
-
index = i;
|
|
1777
|
-
const fn = middlewares[i];
|
|
1778
|
-
if (i === middlewares.length || !fn) {
|
|
1779
|
-
return finalHandler(currentRequest);
|
|
1780
|
-
}
|
|
1781
|
-
return fn({
|
|
1782
|
-
request: currentRequest,
|
|
1783
|
-
next: () => dispatch(i + 1, currentRequest)
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
return dispatch(0, initialRequest);
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
3325
|
// src/streaming/adapters.ts
|
|
1791
3326
|
function toTextStream(stream) {
|
|
1792
3327
|
const iterator = stream[Symbol.asyncIterator]();
|
|
@@ -2089,25 +3624,32 @@ var ToolRegistry = class {
|
|
|
2089
3624
|
return results;
|
|
2090
3625
|
}
|
|
2091
3626
|
async runWithTimeout(tool, params, ctx, toolName) {
|
|
2092
|
-
|
|
2093
|
-
if (!timeoutMs || timeoutMs <= 0) {
|
|
2094
|
-
return tool.execute(params, ctx);
|
|
2095
|
-
}
|
|
2096
|
-
const controller = new AbortController();
|
|
2097
|
-
const onParentAbort = () => controller.abort(ctx.signal?.reason);
|
|
2098
|
-
if (ctx.signal) {
|
|
2099
|
-
if (ctx.signal.aborted) {
|
|
2100
|
-
controller.abort(ctx.signal.reason);
|
|
2101
|
-
} else {
|
|
2102
|
-
ctx.signal.addEventListener("abort", onParentAbort, { once: true });
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
let timedOut = false;
|
|
2106
|
-
const timer = setTimeout(() => {
|
|
2107
|
-
timedOut = true;
|
|
2108
|
-
controller.abort();
|
|
2109
|
-
}, timeoutMs);
|
|
3627
|
+
var _stack = [];
|
|
2110
3628
|
try {
|
|
3629
|
+
const timeoutMs = tool.timeoutMs ?? this.defaultTimeoutMs;
|
|
3630
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
3631
|
+
return tool.execute(params, ctx);
|
|
3632
|
+
}
|
|
3633
|
+
const controller = new AbortController();
|
|
3634
|
+
const onParentAbort = () => controller.abort(ctx.signal?.reason);
|
|
3635
|
+
if (ctx.signal) {
|
|
3636
|
+
if (ctx.signal.aborted) {
|
|
3637
|
+
controller.abort(ctx.signal.reason);
|
|
3638
|
+
} else {
|
|
3639
|
+
ctx.signal.addEventListener("abort", onParentAbort, { once: true });
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
let timedOut = false;
|
|
3643
|
+
const timer = setTimeout(() => {
|
|
3644
|
+
timedOut = true;
|
|
3645
|
+
controller.abort();
|
|
3646
|
+
}, timeoutMs);
|
|
3647
|
+
const _timer = __using(_stack, disposableTimer(timer));
|
|
3648
|
+
const _listener = __using(_stack, {
|
|
3649
|
+
[Symbol.dispose]() {
|
|
3650
|
+
ctx.signal?.removeEventListener("abort", onParentAbort);
|
|
3651
|
+
}
|
|
3652
|
+
});
|
|
2111
3653
|
const executePromise = Promise.resolve(
|
|
2112
3654
|
tool.execute(params, { ...ctx, signal: controller.signal })
|
|
2113
3655
|
);
|
|
@@ -2124,11 +3666,10 @@ var ToolRegistry = class {
|
|
|
2124
3666
|
});
|
|
2125
3667
|
})
|
|
2126
3668
|
]);
|
|
3669
|
+
} catch (_) {
|
|
3670
|
+
var _error = _, _hasError = true;
|
|
2127
3671
|
} finally {
|
|
2128
|
-
|
|
2129
|
-
if (ctx.signal) {
|
|
2130
|
-
ctx.signal.removeEventListener("abort", onParentAbort);
|
|
2131
|
-
}
|
|
3672
|
+
__callDispose(_stack, _error, _hasError);
|
|
2132
3673
|
}
|
|
2133
3674
|
}
|
|
2134
3675
|
truncateOutput(output) {
|
|
@@ -2150,28 +3691,234 @@ var ToolRegistry = class {
|
|
|
2150
3691
|
};
|
|
2151
3692
|
}
|
|
2152
3693
|
};
|
|
2153
|
-
|
|
3694
|
+
|
|
3695
|
+
// src/mcp/json-schema.ts
|
|
3696
|
+
function label(path) {
|
|
3697
|
+
return path.length === 0 ? "value" : "value" + path.map((part) => typeof part === "number" ? "[" + part + "]" : "." + part).join("");
|
|
3698
|
+
}
|
|
3699
|
+
function equalJson(a, b) {
|
|
3700
|
+
if (Object.is(a, b)) return true;
|
|
3701
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
3702
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
3703
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, i) => equalJson(item, b[i]));
|
|
3704
|
+
}
|
|
3705
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
3706
|
+
const ar = a;
|
|
3707
|
+
const br = b;
|
|
3708
|
+
const ak = Object.keys(ar).sort();
|
|
3709
|
+
const bk = Object.keys(br).sort();
|
|
3710
|
+
return ak.length === bk.length && ak.every((key, i) => key === bk[i] && equalJson(ar[key], br[key]));
|
|
3711
|
+
}
|
|
3712
|
+
return false;
|
|
3713
|
+
}
|
|
3714
|
+
function matchesType(value, type) {
|
|
3715
|
+
switch (type) {
|
|
3716
|
+
case "null":
|
|
3717
|
+
return value === null;
|
|
3718
|
+
case "boolean":
|
|
3719
|
+
return typeof value === "boolean";
|
|
3720
|
+
case "object":
|
|
3721
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3722
|
+
case "array":
|
|
3723
|
+
return Array.isArray(value);
|
|
3724
|
+
case "string":
|
|
3725
|
+
return typeof value === "string";
|
|
3726
|
+
case "number":
|
|
3727
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
3728
|
+
case "integer":
|
|
3729
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
3730
|
+
default:
|
|
3731
|
+
return true;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
function validate(value, schema, path) {
|
|
3735
|
+
if (schema === true || schema === void 0) return [];
|
|
3736
|
+
if (schema === false) return [{ path, message: label(path) + " is not allowed by the schema" }];
|
|
3737
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) return [];
|
|
3738
|
+
const s = schema;
|
|
3739
|
+
const issues = [];
|
|
3740
|
+
if (s["nullable"] === true && value === null) return issues;
|
|
3741
|
+
const type = s["type"];
|
|
3742
|
+
if (typeof type === "string" && !matchesType(value, type)) {
|
|
3743
|
+
return [{ path, message: label(path) + " must be " + type }];
|
|
3744
|
+
}
|
|
3745
|
+
if (Array.isArray(type) && !type.some((t) => typeof t === "string" && matchesType(value, t))) {
|
|
3746
|
+
return [{ path, message: label(path) + " must match one of: " + type.join(", ") }];
|
|
3747
|
+
}
|
|
3748
|
+
if ("const" in s && !equalJson(value, s["const"])) issues.push({ path, message: label(path) + " must equal the schema const value" });
|
|
3749
|
+
const enumValues = s["enum"];
|
|
3750
|
+
if (Array.isArray(enumValues) && !enumValues.some((item) => equalJson(value, item))) issues.push({ path, message: label(path) + " must be one of the allowed enum values" });
|
|
3751
|
+
const anyOf = s["anyOf"];
|
|
3752
|
+
if (Array.isArray(anyOf) && !anyOf.some((candidate) => validate(value, candidate, path).length === 0)) issues.push({ path, message: label(path) + " must satisfy at least one anyOf schema" });
|
|
3753
|
+
const oneOf = s["oneOf"];
|
|
3754
|
+
if (Array.isArray(oneOf)) {
|
|
3755
|
+
const matches = oneOf.filter((candidate) => validate(value, candidate, path).length === 0).length;
|
|
3756
|
+
if (matches !== 1) issues.push({ path, message: label(path) + " must satisfy exactly one oneOf schema" });
|
|
3757
|
+
}
|
|
3758
|
+
const allOf = s["allOf"];
|
|
3759
|
+
if (Array.isArray(allOf)) for (const candidate of allOf) issues.push(...validate(value, candidate, path));
|
|
3760
|
+
if ("not" in s && validate(value, s["not"], path).length === 0) issues.push({ path, message: label(path) + " must not satisfy the not schema" });
|
|
3761
|
+
if (typeof value === "string") {
|
|
3762
|
+
const min = s["minLength"];
|
|
3763
|
+
const max = s["maxLength"];
|
|
3764
|
+
if (typeof min === "number" && value.length < min) issues.push({ path, message: label(path) + " must have at least " + min + " characters" });
|
|
3765
|
+
if (typeof max === "number" && value.length > max) issues.push({ path, message: label(path) + " must have at most " + max + " characters" });
|
|
3766
|
+
if (typeof s["pattern"] === "string") {
|
|
3767
|
+
try {
|
|
3768
|
+
if (!new RegExp(s["pattern"]).test(value)) issues.push({ path, message: label(path) + " does not match the required pattern" });
|
|
3769
|
+
} catch {
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3774
|
+
const numericRules = [
|
|
3775
|
+
["minimum", (n, b) => n >= b, "must be greater than or equal to"],
|
|
3776
|
+
["maximum", (n, b) => n <= b, "must be less than or equal to"],
|
|
3777
|
+
["exclusiveMinimum", (n, b) => n > b, "must be greater than"],
|
|
3778
|
+
["exclusiveMaximum", (n, b) => n < b, "must be less than"]
|
|
3779
|
+
];
|
|
3780
|
+
for (const [keyword, check, phrase] of numericRules) {
|
|
3781
|
+
const bound = s[keyword];
|
|
3782
|
+
if (typeof bound === "number" && !check(value, bound)) issues.push({ path, message: label(path) + " " + phrase + " " + bound });
|
|
3783
|
+
}
|
|
3784
|
+
const multipleOf = s["multipleOf"];
|
|
3785
|
+
if (typeof multipleOf === "number" && multipleOf > 0) {
|
|
3786
|
+
const quotient = value / multipleOf;
|
|
3787
|
+
if (Math.abs(quotient - Math.round(quotient)) > 1e-12) issues.push({ path, message: label(path) + " must be a multiple of " + multipleOf });
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
if (Array.isArray(value)) {
|
|
3791
|
+
const min = s["minItems"];
|
|
3792
|
+
const max = s["maxItems"];
|
|
3793
|
+
if (typeof min === "number" && value.length < min) issues.push({ path, message: label(path) + " must contain at least " + min + " items" });
|
|
3794
|
+
if (typeof max === "number" && value.length > max) issues.push({ path, message: label(path) + " must contain at most " + max + " items" });
|
|
3795
|
+
if (s["uniqueItems"] === true) {
|
|
3796
|
+
for (let i = 0; i < value.length; i += 1) if (value.slice(0, i).some((previous) => equalJson(previous, value[i]))) {
|
|
3797
|
+
issues.push({ path: [...path, i], message: label([...path, i]) + " must be unique" });
|
|
3798
|
+
break;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
if ("items" in s) value.forEach((item, i) => issues.push(...validate(item, s["items"], [...path, i])));
|
|
3802
|
+
if (Array.isArray(s["prefixItems"])) s["prefixItems"].forEach((itemSchema, i) => {
|
|
3803
|
+
if (i < value.length) issues.push(...validate(value[i], itemSchema, [...path, i]));
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
3807
|
+
const object = value;
|
|
3808
|
+
const required = s["required"];
|
|
3809
|
+
if (Array.isArray(required)) {
|
|
3810
|
+
for (const key of required) if (typeof key === "string" && !(key in object)) issues.push({ path: [...path, key], message: label([...path, key]) + " is required" });
|
|
3811
|
+
}
|
|
3812
|
+
const properties = s["properties"];
|
|
3813
|
+
if (typeof properties === "object" && properties !== null && !Array.isArray(properties)) {
|
|
3814
|
+
const propertySchemas = properties;
|
|
3815
|
+
for (const [key, propertySchema] of Object.entries(propertySchemas)) if (key in object) issues.push(...validate(object[key], propertySchema, [...path, key]));
|
|
3816
|
+
const additional = s["additionalProperties"];
|
|
3817
|
+
for (const key of Object.keys(object)) {
|
|
3818
|
+
if (key in propertySchemas) continue;
|
|
3819
|
+
if (additional === false) issues.push({ path: [...path, key], message: label([...path, key]) + " is not allowed" });
|
|
3820
|
+
else if (additional !== true && additional !== void 0) issues.push(...validate(object[key], additional, [...path, key]));
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
const min = s["minProperties"];
|
|
3824
|
+
const max = s["maxProperties"];
|
|
3825
|
+
if (typeof min === "number" && Object.keys(object).length < min) issues.push({ path, message: label(path) + " must contain at least " + min + " properties" });
|
|
3826
|
+
if (typeof max === "number" && Object.keys(object).length > max) issues.push({ path, message: label(path) + " must contain at most " + max + " properties" });
|
|
3827
|
+
}
|
|
3828
|
+
return issues;
|
|
3829
|
+
}
|
|
3830
|
+
function validateJsonSchema(value, schema) {
|
|
3831
|
+
return validate(value, schema, []);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
// src/mcp/mcp-tools.ts
|
|
3835
|
+
function resolveMaxPages(options) {
|
|
3836
|
+
const maxPages = options.maxPages ?? 64;
|
|
3837
|
+
if (!Number.isInteger(maxPages) || maxPages <= 0) {
|
|
3838
|
+
throw new RangeError("MCP maxPages must be a positive integer");
|
|
3839
|
+
}
|
|
3840
|
+
return maxPages;
|
|
3841
|
+
}
|
|
3842
|
+
async function listAllMcpTools(mcpClient, options = {}, signal) {
|
|
3843
|
+
const maxPages = resolveMaxPages(options);
|
|
3844
|
+
const tools = [];
|
|
3845
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3846
|
+
let cursor;
|
|
3847
|
+
for (let pageNumber = 1; pageNumber <= maxPages; pageNumber += 1) {
|
|
3848
|
+
const params = cursor !== void 0 ? { cursor } : void 0;
|
|
3849
|
+
const requestOptions = signal !== void 0 ? { signal } : void 0;
|
|
3850
|
+
const page = await mcpClient.listTools(params, requestOptions);
|
|
3851
|
+
tools.push(...page.tools);
|
|
3852
|
+
if (page.nextCursor === void 0) {
|
|
3853
|
+
return tools;
|
|
3854
|
+
}
|
|
3855
|
+
if (seenCursors.has(page.nextCursor)) {
|
|
3856
|
+
throw new Error("MCP tools/list returned a repeated nextCursor");
|
|
3857
|
+
}
|
|
3858
|
+
seenCursors.add(page.nextCursor);
|
|
3859
|
+
cursor = page.nextCursor;
|
|
3860
|
+
}
|
|
3861
|
+
throw new Error("MCP tools/list exceeded maxPages (" + maxPages + ")");
|
|
3862
|
+
}
|
|
3863
|
+
function formatMcpToolResult(result) {
|
|
3864
|
+
const parts = result.content.map((block) => {
|
|
3865
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
3866
|
+
return block.text;
|
|
3867
|
+
}
|
|
3868
|
+
try {
|
|
3869
|
+
return JSON.stringify(block);
|
|
3870
|
+
} catch {
|
|
3871
|
+
return `[${block.type}]`;
|
|
3872
|
+
}
|
|
3873
|
+
});
|
|
3874
|
+
if (result.structuredContent !== void 0) {
|
|
3875
|
+
try {
|
|
3876
|
+
parts.push(JSON.stringify(result.structuredContent));
|
|
3877
|
+
} catch {
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if (result.isError) {
|
|
3881
|
+
parts.unshift("[MCP tool error]");
|
|
3882
|
+
}
|
|
3883
|
+
return parts.join("\n");
|
|
3884
|
+
}
|
|
3885
|
+
function createInputValidator(inputSchema) {
|
|
3886
|
+
return z.custom().superRefine((value, ctx) => {
|
|
3887
|
+
const issues = validateJsonSchema(value, inputSchema);
|
|
3888
|
+
for (const issue of issues) {
|
|
3889
|
+
ctx.addIssue({
|
|
3890
|
+
code: z.ZodIssueCode.custom,
|
|
3891
|
+
path: [...issue.path],
|
|
3892
|
+
message: issue.message
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
function convertMcpDescriptorToTool(descriptor, mcpClient, namePrefix = "", resultMode = "text") {
|
|
2154
3898
|
const toolName = `${namePrefix}${descriptor.name}`;
|
|
2155
|
-
const inputSchema = descriptor.inputSchema ?? { properties: {} };
|
|
3899
|
+
const inputSchema = descriptor.inputSchema ?? { type: "object", properties: {} };
|
|
2156
3900
|
const properties = inputSchema["properties"] ?? {};
|
|
2157
3901
|
const required = inputSchema["required"] ?? [];
|
|
3902
|
+
const parameters = {
|
|
3903
|
+
...inputSchema,
|
|
3904
|
+
type: "object",
|
|
3905
|
+
properties,
|
|
3906
|
+
...required.length > 0 ? { required } : {}
|
|
3907
|
+
};
|
|
2158
3908
|
return {
|
|
2159
3909
|
name: toolName,
|
|
2160
3910
|
description: descriptor.description ?? "",
|
|
2161
|
-
schema:
|
|
2162
|
-
execute: async (args) => {
|
|
3911
|
+
schema: createInputValidator(inputSchema),
|
|
3912
|
+
execute: async (args, context) => {
|
|
2163
3913
|
try {
|
|
2164
|
-
const result = await mcpClient.callTool(
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
});
|
|
2173
|
-
}
|
|
2174
|
-
return result.content.map((b) => b.type === "text" && b.text ? b.text : `[${b.type}]`).join("\n");
|
|
3914
|
+
const result = await mcpClient.callTool(
|
|
3915
|
+
{
|
|
3916
|
+
name: descriptor.name,
|
|
3917
|
+
arguments: args
|
|
3918
|
+
},
|
|
3919
|
+
context.signal !== void 0 ? { signal: context.signal } : void 0
|
|
3920
|
+
);
|
|
3921
|
+
return resultMode === "structured" ? result : formatMcpToolResult(result);
|
|
2175
3922
|
} catch (err) {
|
|
2176
3923
|
if (err instanceof OllamaMcpError) throw err;
|
|
2177
3924
|
throw new OllamaMcpError(`Failed calling MCP tool "${descriptor.name}"`, {
|
|
@@ -2186,19 +3933,15 @@ function convertMcpDescriptorToTool(descriptor, mcpClient, namePrefix = "") {
|
|
|
2186
3933
|
function: {
|
|
2187
3934
|
name: toolName,
|
|
2188
3935
|
description: descriptor.description ?? "",
|
|
2189
|
-
parameters
|
|
2190
|
-
type: "object",
|
|
2191
|
-
properties,
|
|
2192
|
-
...required.length > 0 ? { required } : {}
|
|
2193
|
-
}
|
|
3936
|
+
parameters
|
|
2194
3937
|
}
|
|
2195
3938
|
}
|
|
2196
3939
|
};
|
|
2197
3940
|
}
|
|
2198
|
-
async function loadMcpTools(mcpClient, options = {}) {
|
|
3941
|
+
async function loadMcpTools(mcpClient, options = {}, signal) {
|
|
2199
3942
|
try {
|
|
2200
|
-
const
|
|
2201
|
-
return tools.map((t) => convertMcpDescriptorToTool(t, mcpClient, options.namePrefix));
|
|
3943
|
+
const tools = await listAllMcpTools(mcpClient, options, signal);
|
|
3944
|
+
return tools.map((t) => convertMcpDescriptorToTool(t, mcpClient, options.namePrefix, options.resultMode));
|
|
2202
3945
|
} catch (err) {
|
|
2203
3946
|
throw new OllamaMcpError("Failed listing MCP tools from client", {
|
|
2204
3947
|
mcpMethod: "listTools",
|
|
@@ -2206,11 +3949,48 @@ async function loadMcpTools(mcpClient, options = {}) {
|
|
|
2206
3949
|
});
|
|
2207
3950
|
}
|
|
2208
3951
|
}
|
|
2209
|
-
async function registerMcpTools(registry, mcpClient, options = {}) {
|
|
2210
|
-
const tools = await loadMcpTools(mcpClient, options);
|
|
3952
|
+
async function registerMcpTools(registry, mcpClient, options = {}, signal) {
|
|
3953
|
+
const tools = await loadMcpTools(mcpClient, options, signal);
|
|
2211
3954
|
registry.registerMany(tools);
|
|
2212
3955
|
}
|
|
2213
3956
|
|
|
3957
|
+
// src/mcp/bridge.ts
|
|
3958
|
+
var McpBridge = class _McpBridge {
|
|
3959
|
+
constructor(client, options = {}) {
|
|
3960
|
+
this.client = client;
|
|
3961
|
+
this.options = options;
|
|
3962
|
+
}
|
|
3963
|
+
client;
|
|
3964
|
+
options;
|
|
3965
|
+
static toOllamaTools(tools, options = {}) {
|
|
3966
|
+
const prefix = options.namePrefix ?? "";
|
|
3967
|
+
return tools.map((tool) => ({
|
|
3968
|
+
type: "function",
|
|
3969
|
+
function: {
|
|
3970
|
+
name: prefix + tool.name,
|
|
3971
|
+
description: tool.description ?? "",
|
|
3972
|
+
parameters: {
|
|
3973
|
+
...tool.inputSchema ?? { type: "object", properties: {} },
|
|
3974
|
+
type: "object",
|
|
3975
|
+
properties: (tool.inputSchema ?? {})["properties"] ?? {}
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}));
|
|
3979
|
+
}
|
|
3980
|
+
async listTools(signal) {
|
|
3981
|
+
return listAllMcpTools(this.client, this.options, signal);
|
|
3982
|
+
}
|
|
3983
|
+
async definitions(signal) {
|
|
3984
|
+
return _McpBridge.toOllamaTools(await this.listTools(signal), this.options);
|
|
3985
|
+
}
|
|
3986
|
+
async loadTools(signal) {
|
|
3987
|
+
return loadMcpTools(this.client, this.options, signal);
|
|
3988
|
+
}
|
|
3989
|
+
async register(registry, signal) {
|
|
3990
|
+
await registry.registerMany(await this.loadTools(signal));
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
|
|
2214
3994
|
// src/skills/frontmatter.ts
|
|
2215
3995
|
function parseYamlValue(raw) {
|
|
2216
3996
|
const trimmed = raw.trim();
|
|
@@ -2312,12 +4092,24 @@ var Agent = class {
|
|
|
2312
4092
|
client;
|
|
2313
4093
|
tools;
|
|
2314
4094
|
maxIterations;
|
|
4095
|
+
maxToolCalls;
|
|
2315
4096
|
hooks;
|
|
4097
|
+
validateToolCapability;
|
|
4098
|
+
toolContextSize;
|
|
2316
4099
|
constructor(client, config = {}) {
|
|
2317
4100
|
this.client = client;
|
|
2318
4101
|
this.tools = config.tools;
|
|
2319
4102
|
this.maxIterations = config.maxIterations ?? 10;
|
|
4103
|
+
this.maxToolCalls = config.maxToolCalls;
|
|
2320
4104
|
this.hooks = config.hooks;
|
|
4105
|
+
if (this.maxToolCalls !== void 0 && (!Number.isInteger(this.maxToolCalls) || this.maxToolCalls < 0)) {
|
|
4106
|
+
throw new RangeError("Agent maxToolCalls must be a non-negative integer");
|
|
4107
|
+
}
|
|
4108
|
+
this.validateToolCapability = config.validateToolCapability ?? true;
|
|
4109
|
+
this.toolContextSize = config.toolContextSize ?? 32768;
|
|
4110
|
+
if (!Number.isInteger(this.toolContextSize) || this.toolContextSize <= 0) {
|
|
4111
|
+
throw new RangeError("Agent toolContextSize must be a positive integer");
|
|
4112
|
+
}
|
|
2321
4113
|
}
|
|
2322
4114
|
run(input) {
|
|
2323
4115
|
return withSpan(
|
|
@@ -2335,6 +4127,30 @@ var Agent = class {
|
|
|
2335
4127
|
const history = [...input.messages];
|
|
2336
4128
|
const turns = [];
|
|
2337
4129
|
const toolDefs = this.tools?.definitions();
|
|
4130
|
+
const hasTools = toolDefs !== void 0 && toolDefs.length > 0;
|
|
4131
|
+
let capabilities;
|
|
4132
|
+
if (hasTools && this.validateToolCapability && this.client.capabilities) {
|
|
4133
|
+
capabilities = await this.client.capabilities(input.model, input.signal);
|
|
4134
|
+
if (!capabilities.supportsTools) {
|
|
4135
|
+
throw new OllamaIncompatibleModelError(
|
|
4136
|
+
`Model "${input.model}" does not advertise the "tools" capability.`,
|
|
4137
|
+
{
|
|
4138
|
+
model: input.model,
|
|
4139
|
+
capability: "tools",
|
|
4140
|
+
reportedCapabilities: capabilities.reported
|
|
4141
|
+
}
|
|
4142
|
+
);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
const shouldAutoSizeContext = hasTools && this.validateToolCapability && capabilities !== void 0 && input.options?.num_ctx === void 0;
|
|
4146
|
+
const effectiveOptions = shouldAutoSizeContext ? {
|
|
4147
|
+
...input.options ?? {},
|
|
4148
|
+
num_ctx: Math.min(
|
|
4149
|
+
this.toolContextSize,
|
|
4150
|
+
capabilities?.contextLength ?? this.toolContextSize
|
|
4151
|
+
)
|
|
4152
|
+
} : input.options;
|
|
4153
|
+
let toolCallsExecuted = 0;
|
|
2338
4154
|
for (let iteration = 1; iteration <= this.maxIterations; iteration++) {
|
|
2339
4155
|
const outcome = await withSpan(
|
|
2340
4156
|
"ollama.agent.turn",
|
|
@@ -2345,7 +4161,7 @@ var Agent = class {
|
|
|
2345
4161
|
model: input.model,
|
|
2346
4162
|
messages: history,
|
|
2347
4163
|
...toolDefs !== void 0 ? { tools: toolDefs } : {},
|
|
2348
|
-
...
|
|
4164
|
+
...effectiveOptions !== void 0 ? { options: effectiveOptions } : {},
|
|
2349
4165
|
...input.think !== void 0 ? { think: input.think } : {},
|
|
2350
4166
|
stream: false,
|
|
2351
4167
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
@@ -2360,17 +4176,24 @@ var Agent = class {
|
|
|
2360
4176
|
this.hooks?.onTurnEnd?.(finalTurn);
|
|
2361
4177
|
return { done: true, finalMessage: assistantMessage };
|
|
2362
4178
|
}
|
|
4179
|
+
if (this.maxToolCalls !== void 0 && toolCallsExecuted + toolCalls.length > this.maxToolCalls) {
|
|
4180
|
+
throw new OllamaAgentMaxToolCallsError(
|
|
4181
|
+
`Agent exceeded max tool calls (${this.maxToolCalls})`,
|
|
4182
|
+
{ maxToolCalls: this.maxToolCalls, toolCallsExecuted }
|
|
4183
|
+
);
|
|
4184
|
+
}
|
|
2363
4185
|
for (const tc of toolCalls) {
|
|
2364
4186
|
this.hooks?.onToolCallStart?.(tc);
|
|
2365
4187
|
}
|
|
2366
4188
|
const toolResults = await this.tools.executeToolCalls(toolCalls, {
|
|
2367
4189
|
signal: input.signal
|
|
2368
4190
|
});
|
|
4191
|
+
toolCallsExecuted += toolCalls.length;
|
|
2369
4192
|
for (const res of toolResults) {
|
|
2370
4193
|
this.hooks?.onToolCallEnd?.(res);
|
|
2371
4194
|
history.push({
|
|
2372
4195
|
role: "tool",
|
|
2373
|
-
|
|
4196
|
+
tool_name: res.toolName,
|
|
2374
4197
|
content: res.outputString
|
|
2375
4198
|
});
|
|
2376
4199
|
}
|
|
@@ -2391,6 +4214,6 @@ var Agent = class {
|
|
|
2391
4214
|
}
|
|
2392
4215
|
};
|
|
2393
4216
|
|
|
2394
|
-
export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, Agent, AnthropicCompatClient, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_FAILOVER_CODES, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, EndpointRegistry, GEN_AI_SYSTEM_OLLAMA, HttpClient, ModelsClient, NANOS_PER_MS, NANOS_PER_SECOND, NOOP_LOGGER, OLLAMA_CLOUD_BASE_URL, OllamaAbortError, OllamaAgentMaxIterationsError, OllamaAuthError, OllamaClient, OllamaClientError, OllamaGenericClientError, OllamaMcpError, OllamaModelRoutingError, OllamaNetworkError, OllamaNotFoundError, OllamaQuotaExceededError, OllamaRateLimitError, OllamaServerError, OllamaSkillInvalidError, OllamaSkillNotFoundError, OllamaStream, OllamaTimeoutError, OllamaToolTimeoutError, OllamaToolValidationError, OllamaUnsupportedCapabilityError, OpenAICompatClient, QuotaManager, ToolRegistry, addSpanEvent, applySkill, calculateBackoff, checkEndpointHealth, composeMiddleware, createConsoleLogger, createOllamaCloudFreeTierQuota, createTimeoutSignal, defineTool, detectModelCapabilities, encodeImage, extractUsage, inferRuntimeMode, listAvailableModels, loadMcpTools, mapError, normalizeChatStream, normalizeGenerateStream, normalizeProgressStream, parseFrontmatter, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveCredentialEndpoints, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
|
|
4217
|
+
export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, Agent, AnthropicCompatClient, AnthropicMessagesStream, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_FAILOVER_CODES, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, EndpointRegistry, GEN_AI_SYSTEM_OLLAMA, HttpClient, McpBridge, ModelsClient, NANOS_PER_MS, NANOS_PER_SECOND, NOOP_LOGGER, OLLAMA_CLOUD_BASE_URL, OllamaAbortError, OllamaAgentMaxIterationsError, OllamaAgentMaxToolCallsError, OllamaAuthError, OllamaClient, OllamaClientError, OllamaGenericClientError, OllamaIncompatibleModelError, OllamaMcpError, OllamaModelRoutingError, OllamaNetworkError, OllamaNotFoundError, OllamaQuotaExceededError, OllamaRateLimitError, OllamaServerError, OllamaSkillInvalidError, OllamaSkillNotFoundError, OllamaStream, OllamaTimeoutError, OllamaToolTimeoutError, OllamaToolValidationError, OllamaUnsupportedCapabilityError, OpenAIChatCompletionStream, OpenAICompatClient, OpenAICompletionStream, OpenAIResponsesStream, OpenAIResponsesStreamError, QuotaManager, ToolRegistry, addSpanEvent, applySkill, calculateBackoff, checkEndpointHealth, composeMiddleware, createConsoleLogger, createOllamaCloudFreeTierQuota, createTimeoutSignal, defineTool, detectModelCapabilities, encodeImage, extractUsage, inferRuntimeMode, listAllMcpTools, listAvailableModels, loadMcpTools, mapError, normalizeChatStream, normalizeGenerateStream, normalizeProgressStream, parseFrontmatter, parseNdjsonStream, parseSseStream, parseStructuredOutput, registerMcpTools, resolveCredentialEndpoints, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
|
|
2395
4218
|
//# sourceMappingURL=index.js.map
|
|
2396
4219
|
//# sourceMappingURL=index.js.map
|