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