@infernetprotocol/engine 0.1.87 → 0.1.88

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infernetprotocol/engine",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "description": "Inference engine abstraction for Infernet — pluggable backends (Mojo+MAX, in-process stub) over a stable async-iterator API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,6 +17,8 @@
17
17
  */
18
18
 
19
19
  import os from "node:os";
20
+ import http from "node:http";
21
+ import https from "node:https";
20
22
  import { randomUUID } from "node:crypto";
21
23
  import { AsyncQueue } from "../async-queue.js";
22
24
  import { MSG, PROTOCOL_VERSION } from "../protocol.js";
@@ -40,6 +42,58 @@ export function defaultNumThread() {
40
42
 
41
43
  export const OLLAMA_DEFAULT_HOST = DEFAULT_HOST;
42
44
 
45
+ /**
46
+ * Minimal fetch over node:http for the /api/chat stream, with no headers or
47
+ * body timeout.
48
+ *
49
+ * Node's global fetch (undici) fails with a bare "fetch failed" when no
50
+ * response headers arrive within 300 s. Ollama sends headers with the first
51
+ * token, and a single-GPU node serves one request at a time: a job queued
52
+ * behind another generation, or a long prompt on a slow card, routinely
53
+ * waits longer than that (cullen3, GTX 745, 2026-11-02: 47 of 49 qwen3:4b
54
+ * jobs failed at ~300 s). The job already has its own expiry and cancel, so
55
+ * the transport must not add a third, shorter deadline.
56
+ *
57
+ * Returns the subset of Response the backend uses: ok, status, text() and
58
+ * an async-iterable body.
59
+ */
60
+ export function ollamaHttpFetch(url, { method = "GET", headers = {}, body, signal } = {}) {
61
+ const u = url instanceof URL ? url : new URL(url);
62
+ const mod = u.protocol === "https:" ? https : http;
63
+ return new Promise((resolve, reject) => {
64
+ if (signal?.aborted) {
65
+ reject(abortError());
66
+ return;
67
+ }
68
+ const req = mod.request(u, { method, headers }, (res) => {
69
+ const status = res.statusCode ?? 0;
70
+ resolve({
71
+ ok: status >= 200 && status < 300,
72
+ status,
73
+ body: res,
74
+ async text() {
75
+ let out = "";
76
+ res.setEncoding("utf8");
77
+ for await (const chunk of res) out += chunk;
78
+ return out;
79
+ }
80
+ });
81
+ });
82
+ const onAbort = () => req.destroy(abortError());
83
+ signal?.addEventListener("abort", onAbort, { once: true });
84
+ req.on("close", () => signal?.removeEventListener("abort", onAbort));
85
+ req.on("error", reject);
86
+ if (body != null) req.write(body);
87
+ req.end();
88
+ });
89
+ }
90
+
91
+ function abortError() {
92
+ const err = new Error("The operation was aborted");
93
+ err.name = "AbortError";
94
+ return err;
95
+ }
96
+
43
97
  export async function isOllamaReachable(host = DEFAULT_HOST, timeoutMs = PROBE_TIMEOUT_MS) {
44
98
  try {
45
99
  const ctrl = new AbortController();
@@ -57,7 +111,7 @@ export async function createOllamaBackend({
57
111
  defaultModel = process.env.INFERNET_ENGINE_MODEL ?? null,
58
112
  numThread = Number.parseInt(process.env.OLLAMA_NUM_THREAD ?? "", 10) || defaultNumThread(),
59
113
  skipProbe = false,
60
- fetchImpl = globalThis.fetch
114
+ fetchImpl = ollamaHttpFetch
61
115
  } = {}) {
62
116
  if (!skipProbe) {
63
117
  const ok = await isOllamaReachable(host);