@untangle-code/embeddings-ollama 0.0.0-stage → 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +187 -2
  4. package/THIRD_PARTY_LICENSES.md +24 -0
  5. package/dist/backoff.d.ts +43 -0
  6. package/dist/backoff.d.ts.map +1 -0
  7. package/dist/backoff.js +38 -0
  8. package/dist/backoff.js.map +1 -0
  9. package/dist/cache.d.ts +17 -0
  10. package/dist/cache.d.ts.map +1 -0
  11. package/dist/cache.js +17 -0
  12. package/dist/cache.js.map +1 -0
  13. package/dist/concurrency.d.ts +11 -0
  14. package/dist/concurrency.d.ts.map +1 -0
  15. package/dist/concurrency.js +31 -0
  16. package/dist/concurrency.js.map +1 -0
  17. package/dist/endpoint.d.ts +16 -0
  18. package/dist/endpoint.d.ts.map +1 -0
  19. package/dist/endpoint.js +32 -0
  20. package/dist/endpoint.js.map +1 -0
  21. package/dist/http.d.ts +54 -0
  22. package/dist/http.d.ts.map +1 -0
  23. package/dist/http.js +136 -0
  24. package/dist/http.js.map +1 -0
  25. package/dist/index.d.ts +13 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +18 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/inflight.d.ts +49 -0
  30. package/dist/inflight.d.ts.map +1 -0
  31. package/dist/inflight.js +87 -0
  32. package/dist/inflight.js.map +1 -0
  33. package/dist/ollama-api.d.ts +45 -0
  34. package/dist/ollama-api.d.ts.map +1 -0
  35. package/dist/ollama-api.js +40 -0
  36. package/dist/ollama-api.js.map +1 -0
  37. package/dist/provider.d.ts +111 -0
  38. package/dist/provider.d.ts.map +1 -0
  39. package/dist/provider.js +374 -0
  40. package/dist/provider.js.map +1 -0
  41. package/dist/readiness.d.ts +31 -0
  42. package/dist/readiness.d.ts.map +1 -0
  43. package/dist/readiness.js +105 -0
  44. package/dist/readiness.js.map +1 -0
  45. package/dist/sleep.d.ts +8 -0
  46. package/dist/sleep.d.ts.map +1 -0
  47. package/dist/sleep.js +20 -0
  48. package/dist/sleep.js.map +1 -0
  49. package/dist/truncation.d.ts +30 -0
  50. package/dist/truncation.d.ts.map +1 -0
  51. package/dist/truncation.js +39 -0
  52. package/dist/truncation.js.map +1 -0
  53. package/dist/types.d.ts +108 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +7 -0
  56. package/dist/types.js.map +1 -0
  57. package/dist/validation.d.ts +2 -0
  58. package/dist/validation.d.ts.map +1 -0
  59. package/dist/validation.js +16 -0
  60. package/dist/validation.js.map +1 -0
  61. package/package.json +62 -3
  62. package/src/backoff.ts +73 -0
  63. package/src/cache.ts +30 -0
  64. package/src/concurrency.ts +37 -0
  65. package/src/endpoint.ts +31 -0
  66. package/src/http.ts +168 -0
  67. package/src/index.ts +21 -0
  68. package/src/inflight.ts +102 -0
  69. package/src/ollama-api.ts +93 -0
  70. package/src/provider.ts +477 -0
  71. package/src/readiness.ts +203 -0
  72. package/src/sleep.ts +27 -0
  73. package/src/truncation.ts +41 -0
  74. package/src/types.ts +132 -0
  75. package/src/validation.ts +23 -0
package/dist/http.js ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Minimal Ollama HTTP client on Node 22's built-in `fetch` (no HTTP client
3
+ * dependency, per the WP-10 task). Every request carries a timeout and
4
+ * respects an external `AbortSignal`; a bounded semaphore caps concurrently
5
+ * in-flight requests (spec.md §9.2 "Verbindungslimit").
6
+ */
7
+ import { joinUrl } from "./endpoint.js";
8
+ /** Thrown by {@link OllamaHttpClient.requestJson} for any failed request. */
9
+ export class OllamaHttpError extends Error {
10
+ status;
11
+ retryable;
12
+ constructor(message, options) {
13
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
14
+ this.name = "OllamaHttpError";
15
+ this.status = options.status;
16
+ this.retryable = options.retryable;
17
+ }
18
+ }
19
+ /** Thrown when a request is aborted by the CALLER's own signal (never retried). */
20
+ export class OllamaRequestCancelledError extends Error {
21
+ constructor(cause) {
22
+ super("Ollama request cancelled", cause === undefined ? undefined : { cause });
23
+ this.name = "OllamaRequestCancelledError";
24
+ }
25
+ }
26
+ /**
27
+ * True for anything that represents the CALLER's own cancellation rather
28
+ * than a transport failure: an {@link OllamaRequestCancelledError}, a raw
29
+ * `AbortError` (e.g. from `backoff.ts`'s own `signal.throwIfAborted()`
30
+ * pre-check, which runs before this client gets a chance to wrap it), or
31
+ * simply `signal.aborted` already being true by the time the error is
32
+ * observed. Used everywhere this adapter must tell "the caller cancelled"
33
+ * apart from "the endpoint is unreachable" (rule 9: cancellation surfaces as
34
+ * `JOB_CANCELLED`, never as a swallowed/misclassified failure).
35
+ */
36
+ export function isRequestCancellation(error, signal) {
37
+ return (error instanceof OllamaRequestCancelledError ||
38
+ (error instanceof DOMException && error.name === "AbortError") ||
39
+ signal?.aborted === true);
40
+ }
41
+ function isRetryableStatus(status) {
42
+ return status === 429 || status >= 500;
43
+ }
44
+ /** Counting semaphore bounding concurrent HTTP requests to one Ollama endpoint. */
45
+ class Semaphore {
46
+ available;
47
+ waiters = [];
48
+ constructor(limit) {
49
+ this.available = Math.max(1, limit);
50
+ }
51
+ async acquire() {
52
+ if (this.available > 0) {
53
+ this.available -= 1;
54
+ return () => this.release();
55
+ }
56
+ await new Promise((resolve) => this.waiters.push(resolve));
57
+ this.available -= 1;
58
+ return () => this.release();
59
+ }
60
+ release() {
61
+ this.available += 1;
62
+ const next = this.waiters.shift();
63
+ if (next) {
64
+ next();
65
+ }
66
+ }
67
+ }
68
+ export class OllamaHttpClient {
69
+ semaphore;
70
+ fetchImpl;
71
+ requestTimeoutMs;
72
+ apiKeyEnv;
73
+ constructor(options) {
74
+ this.semaphore = new Semaphore(options.maxConnections);
75
+ this.fetchImpl = options.fetchImpl ?? fetch;
76
+ this.requestTimeoutMs = options.requestTimeoutMs;
77
+ this.apiKeyEnv = options.apiKeyEnv;
78
+ }
79
+ /**
80
+ * Sends one JSON request and parses a JSON response. Never retries by
81
+ * itself — callers wrap this in `backoff.ts`'s `withRetry`.
82
+ *
83
+ * Throws {@link OllamaRequestCancelledError} when `signal` (the caller's
84
+ * own signal, not the internal timeout) is the reason for the abort, and
85
+ * {@link OllamaHttpError} for every other failure (network error, timeout,
86
+ * non-2xx status), with `retryable` set per spec.md §9.2's transient/
87
+ * permanent split.
88
+ */
89
+ async requestJson(baseUrl, path, init, signal) {
90
+ if (signal?.aborted) {
91
+ throw new OllamaRequestCancelledError(signal.reason);
92
+ }
93
+ const release = await this.semaphore.acquire();
94
+ try {
95
+ const timeoutSignal = AbortSignal.timeout(this.requestTimeoutMs);
96
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
97
+ const headers = { "content-type": "application/json" };
98
+ const token = this.apiKeyEnv ? process.env[this.apiKeyEnv] : undefined;
99
+ if (token) {
100
+ headers["authorization"] = `Bearer ${token}`;
101
+ }
102
+ let response;
103
+ try {
104
+ response = await this.fetchImpl(joinUrl(baseUrl, path), {
105
+ method: init.method,
106
+ headers,
107
+ signal: combinedSignal,
108
+ // Conditionally spread rather than `body: undefined` — RequestInit's
109
+ // `body` has no `| undefined` arm, so exactOptionalPropertyTypes
110
+ // forbids assigning it explicitly (a GET request has no body).
111
+ ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
112
+ });
113
+ }
114
+ catch (error) {
115
+ if (signal?.aborted) {
116
+ throw new OllamaRequestCancelledError(error);
117
+ }
118
+ // Network failure (connection refused, DNS, our own timeout) — transient.
119
+ throw new OllamaHttpError(`Ollama request to ${path} failed: ${String(error)}`, {
120
+ retryable: true,
121
+ cause: error,
122
+ });
123
+ }
124
+ if (!response.ok) {
125
+ const bodyText = await response.text().catch(() => "");
126
+ throw new OllamaHttpError(`Ollama request to ${path} returned HTTP ${response.status}: ${bodyText.slice(0, 500)}`, { status: response.status, retryable: isRetryableStatus(response.status) });
127
+ }
128
+ const text = await response.text();
129
+ return text.length === 0 ? undefined : JSON.parse(text);
130
+ }
131
+ finally {
132
+ release();
133
+ }
134
+ }
135
+ }
136
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @untangle-code/embeddings-ollama — `EmbeddingProvider` adapter for Ollama
3
+ * (spec.md §9, architecture.md §6.8). This module is the only public export
4
+ * of the package (rule 11); everything else (`http.ts`, `ollama-api.ts`,
5
+ * `readiness.ts`, `backoff.ts`, `concurrency.ts`, `truncation.ts`,
6
+ * `endpoint.ts`, `validation.ts`) is an internal implementation detail,
7
+ * imported directly only by this package's own co-located tests.
8
+ */
9
+ export * from "./provider.js";
10
+ export * from "./types.js";
11
+ export * from "./cache.js";
12
+ export * from "./sleep.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,eAAe,CAAC;AAG9B,cAAc,YAAY,CAAC;AAG3B,cAAc,YAAY,CAAC;AAI3B,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @untangle-code/embeddings-ollama — `EmbeddingProvider` adapter for Ollama
3
+ * (spec.md §9, architecture.md §6.8). This module is the only public export
4
+ * of the package (rule 11); everything else (`http.ts`, `ollama-api.ts`,
5
+ * `readiness.ts`, `backoff.ts`, `concurrency.ts`, `truncation.ts`,
6
+ * `endpoint.ts`, `validation.ts`) is an internal implementation detail,
7
+ * imported directly only by this package's own co-located tests.
8
+ */
9
+ // The adapter itself.
10
+ export * from "./provider.js";
11
+ // Public configuration and port types.
12
+ export * from "./types.js";
13
+ // Default in-memory embedding cache (spec.md §9.4) — the port itself lives in types.ts.
14
+ export * from "./cache.js";
15
+ // Real (non-fake) sleep implementation, exported so callers assembling
16
+ // `OllamaEmbeddingProviderDeps` outside tests don't need to reimplement it.
17
+ export * from "./sleep.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,sBAAsB;AACtB,cAAc,eAAe,CAAC;AAE9B,uCAAuC;AACvC,cAAc,YAAY,CAAC;AAE3B,wFAAwF;AACxF,cAAc,YAAY,CAAC;AAE3B,uEAAuE;AACvE,4EAA4E;AAC5E,cAAc,YAAY,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Single-flight / in-flight de-duplication primitives (spec.md §9.4, WP-78).
3
+ *
4
+ * Used to make sure a running async operation for a given key is REUSED by
5
+ * concurrent callers instead of started twice — both for embedding a
6
+ * not-yet-cached text (`provider.ts`'s `embedSlice`) and for the readiness
7
+ * sequence (`ensureReadyOnce`). Deliberately generic and cache-agnostic:
8
+ * these helpers only manage the "who is doing this right now" bookkeeping,
9
+ * never the cache or the network call itself.
10
+ */
11
+ /** An externally-settleable promise: `resolve`/`reject` are exposed to the caller. */
12
+ export interface Deferred<T> {
13
+ promise: Promise<T>;
14
+ resolve: (value: T) => void;
15
+ reject: (reason: unknown) => void;
16
+ }
17
+ /** Creates a {@link Deferred}. The executor runs synchronously, so both fields are always assigned. */
18
+ export declare function createDeferred<T>(): Deferred<T>;
19
+ /**
20
+ * Awaits `shared` — a promise potentially also being awaited by OTHER
21
+ * callers — but lets THIS caller's own `signal` reject early without
22
+ * affecting `shared` itself: the underlying operation keeps running (and
23
+ * other joined callers keep waiting on it) even after one waiter's own
24
+ * signal aborts (WP-78 requirement: one waiter's cancellation must not
25
+ * cancel the operation for everyone else).
26
+ */
27
+ export declare function raceWithSignal<T>(shared: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
28
+ /**
29
+ * Registry of in-flight operations keyed by an arbitrary string. `claim`
30
+ * MUST be called synchronously (no `await`) between checking for an
31
+ * existing entry and registering a new one — see call sites in
32
+ * `provider.ts` — so that two concurrent callers observing the same "no
33
+ * entry yet" state cannot both start the operation (the actual JS-level
34
+ * atomicity comes from there being no `await` in the claiming code, not
35
+ * from this class).
36
+ */
37
+ export declare class InFlightRegistry<T> {
38
+ private readonly entries;
39
+ /** Returns the in-flight promise for `key`, if any is currently registered. */
40
+ get(key: string): Promise<T> | undefined;
41
+ /**
42
+ * Registers `promise` as the in-flight operation for `key`. The entry is
43
+ * removed automatically once `promise` settles — successfully or not — so
44
+ * a failed attempt frees the key immediately for a fresh retry (WP-78:
45
+ * "ein Fehlschlag den Eintrag wieder freigibt").
46
+ */
47
+ register(key: string, promise: Promise<T>): void;
48
+ }
49
+ //# sourceMappingURL=inflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inflight.d.ts","sourceRoot":"","sources":["../src/inflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sFAAsF;AACtF,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED,uGAAuG;AACvG,wBAAgB,cAAc,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAQ/C;AAMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAuBjG;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAgB,CAAC,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IAEzD,+EAA+E;IACxE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS;IAI/C;;;;;OAKG;IACI,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;CAYxD"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Single-flight / in-flight de-duplication primitives (spec.md §9.4, WP-78).
3
+ *
4
+ * Used to make sure a running async operation for a given key is REUSED by
5
+ * concurrent callers instead of started twice — both for embedding a
6
+ * not-yet-cached text (`provider.ts`'s `embedSlice`) and for the readiness
7
+ * sequence (`ensureReadyOnce`). Deliberately generic and cache-agnostic:
8
+ * these helpers only manage the "who is doing this right now" bookkeeping,
9
+ * never the cache or the network call itself.
10
+ */
11
+ /** Creates a {@link Deferred}. The executor runs synchronously, so both fields are always assigned. */
12
+ export function createDeferred() {
13
+ let resolve;
14
+ let reject;
15
+ const promise = new Promise((res, rej) => {
16
+ resolve = res;
17
+ reject = rej;
18
+ });
19
+ return { promise, resolve, reject };
20
+ }
21
+ function toAbortError(signal) {
22
+ return signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
23
+ }
24
+ /**
25
+ * Awaits `shared` — a promise potentially also being awaited by OTHER
26
+ * callers — but lets THIS caller's own `signal` reject early without
27
+ * affecting `shared` itself: the underlying operation keeps running (and
28
+ * other joined callers keep waiting on it) even after one waiter's own
29
+ * signal aborts (WP-78 requirement: one waiter's cancellation must not
30
+ * cancel the operation for everyone else).
31
+ */
32
+ export function raceWithSignal(shared, signal) {
33
+ if (!signal) {
34
+ return shared;
35
+ }
36
+ if (signal.aborted) {
37
+ return Promise.reject(toAbortError(signal));
38
+ }
39
+ return new Promise((resolve, reject) => {
40
+ const onAbort = () => {
41
+ reject(toAbortError(signal));
42
+ };
43
+ signal.addEventListener("abort", onAbort, { once: true });
44
+ shared.then((value) => {
45
+ signal.removeEventListener("abort", onAbort);
46
+ resolve(value);
47
+ }, (error) => {
48
+ signal.removeEventListener("abort", onAbort);
49
+ reject(error);
50
+ });
51
+ });
52
+ }
53
+ /**
54
+ * Registry of in-flight operations keyed by an arbitrary string. `claim`
55
+ * MUST be called synchronously (no `await`) between checking for an
56
+ * existing entry and registering a new one — see call sites in
57
+ * `provider.ts` — so that two concurrent callers observing the same "no
58
+ * entry yet" state cannot both start the operation (the actual JS-level
59
+ * atomicity comes from there being no `await` in the claiming code, not
60
+ * from this class).
61
+ */
62
+ export class InFlightRegistry {
63
+ entries = new Map();
64
+ /** Returns the in-flight promise for `key`, if any is currently registered. */
65
+ get(key) {
66
+ return this.entries.get(key);
67
+ }
68
+ /**
69
+ * Registers `promise` as the in-flight operation for `key`. The entry is
70
+ * removed automatically once `promise` settles — successfully or not — so
71
+ * a failed attempt frees the key immediately for a fresh retry (WP-78:
72
+ * "ein Fehlschlag den Eintrag wieder freigibt").
73
+ */
74
+ register(key, promise) {
75
+ this.entries.set(key, promise);
76
+ // Void: this cleanup must never itself become an unhandled rejection —
77
+ // the caller is responsible for handling/propagating `promise`'s outcome.
78
+ void promise
79
+ .catch(() => undefined)
80
+ .finally(() => {
81
+ if (this.entries.get(key) === promise) {
82
+ this.entries.delete(key);
83
+ }
84
+ });
85
+ }
86
+ }
87
+ //# sourceMappingURL=inflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inflight.js","sourceRoot":"","sources":["../src/inflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,uGAAuG;AACvG,MAAM,UAAU,cAAc;IAC5B,IAAI,OAA4B,CAAC;IACjC,IAAI,MAAkC,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1C,OAAO,GAAG,GAAG,CAAC;QACd,MAAM,GAAG,GAAG,CAAC;IACf,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB;IACvC,OAAO,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;AACpG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAI,MAAkB,EAAE,MAA+B;IACnF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/B,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CACT,CAAC,KAAK,EAAE,EAAE;YACR,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;YACjB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IACV,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEzD,+EAA+E;IACxE,GAAG,CAAC,GAAW;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACI,QAAQ,CAAC,GAAW,EAAE,OAAmB;QAC9C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC/B,uEAAuE;QACvE,0EAA0E;QAC1E,KAAK,OAAO;aACT,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;aACtB,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC;gBACtC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;IACP,CAAC;CACF"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Typed slice of the Ollama HTTP API this adapter uses: `/api/tags` (model
3
+ * listing / reachability probe), `/api/pull` (managed-mode model fetch) and
4
+ * `/api/embed` (batch embedding). Shapes are the subset of each response
5
+ * this adapter reads — Ollama's actual responses carry more fields.
6
+ */
7
+ import type { OllamaHttpClient } from "./http.js";
8
+ export interface OllamaTagV1 {
9
+ name: string;
10
+ model?: string;
11
+ }
12
+ export interface OllamaTagsResponseV1 {
13
+ models: OllamaTagV1[];
14
+ }
15
+ export interface OllamaPullResponseV1 {
16
+ status: string;
17
+ }
18
+ export interface OllamaEmbedResponseV1 {
19
+ model: string;
20
+ embeddings: number[][];
21
+ }
22
+ /** `GET /api/tags` — used both as the reachability probe and the model-availability check. */
23
+ export declare function listTags(client: OllamaHttpClient, baseUrl: string, signal?: AbortSignal): Promise<OllamaTagsResponseV1>;
24
+ /**
25
+ * True when `modelTags` (as returned by {@link listTags}) contains
26
+ * `modelName`. Ollama tag names carry an implicit `:latest` suffix when the
27
+ * configured model name has none, so `"nomic-embed-text"` must match a tag
28
+ * named `"nomic-embed-text:latest"`.
29
+ */
30
+ export declare function tagsContainModel(modelTags: OllamaTagV1[], modelName: string): boolean;
31
+ /**
32
+ * `POST /api/pull` with `stream: false` — Ollama blocks until the pull
33
+ * completes (or fails) and returns a single JSON response, which keeps this
34
+ * call symmetric with every other request this client makes (one request,
35
+ * one JSON response, no NDJSON stream parsing).
36
+ */
37
+ export declare function pullModel(client: OllamaHttpClient, baseUrl: string, modelName: string, signal?: AbortSignal): Promise<OllamaPullResponseV1>;
38
+ /**
39
+ * `POST /api/embed`. `truncate: true` keeps Ollama's own truncation active
40
+ * as the last-resort safety net spec.md §9.3 describes; this adapter's own
41
+ * truncation guard (`truncation.ts`) decides the `embedding_truncated`
42
+ * payload flag independently of whether the server actually had to use it.
43
+ */
44
+ export declare function embed(client: OllamaHttpClient, baseUrl: string, modelName: string, input: string[], signal?: AbortSignal): Promise<OllamaEmbedResponseV1>;
45
+ //# sourceMappingURL=ollama-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama-api.d.ts","sourceRoot":"","sources":["../src/ollama-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;CACxB;AAED,8FAA8F;AAC9F,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAE/B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUrF;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAO/B;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,qBAAqB,CAAC,CAOhC"}
@@ -0,0 +1,40 @@
1
+ /** `GET /api/tags` — used both as the reachability probe and the model-availability check. */
2
+ export async function listTags(client, baseUrl, signal) {
3
+ return client.requestJson(baseUrl, "api/tags", { method: "GET" }, signal);
4
+ }
5
+ /**
6
+ * True when `modelTags` (as returned by {@link listTags}) contains
7
+ * `modelName`. Ollama tag names carry an implicit `:latest` suffix when the
8
+ * configured model name has none, so `"nomic-embed-text"` must match a tag
9
+ * named `"nomic-embed-text:latest"`.
10
+ */
11
+ export function tagsContainModel(modelTags, modelName) {
12
+ const wantsTag = modelName.includes(":");
13
+ return modelTags.some((tag) => {
14
+ const tagName = tag.name;
15
+ if (wantsTag) {
16
+ return tagName === modelName;
17
+ }
18
+ const [tagBase] = tagName.split(":");
19
+ return tagBase === modelName;
20
+ });
21
+ }
22
+ /**
23
+ * `POST /api/pull` with `stream: false` — Ollama blocks until the pull
24
+ * completes (or fails) and returns a single JSON response, which keeps this
25
+ * call symmetric with every other request this client makes (one request,
26
+ * one JSON response, no NDJSON stream parsing).
27
+ */
28
+ export async function pullModel(client, baseUrl, modelName, signal) {
29
+ return client.requestJson(baseUrl, "api/pull", { method: "POST", body: { model: modelName, stream: false } }, signal);
30
+ }
31
+ /**
32
+ * `POST /api/embed`. `truncate: true` keeps Ollama's own truncation active
33
+ * as the last-resort safety net spec.md §9.3 describes; this adapter's own
34
+ * truncation guard (`truncation.ts`) decides the `embedding_truncated`
35
+ * payload flag independently of whether the server actually had to use it.
36
+ */
37
+ export async function embed(client, baseUrl, modelName, input, signal) {
38
+ return client.requestJson(baseUrl, "api/embed", { method: "POST", body: { model: modelName, input, truncate: true } }, signal);
39
+ }
40
+ //# sourceMappingURL=ollama-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama-api.js","sourceRoot":"","sources":["../src/ollama-api.ts"],"names":[],"mappings":"AA0BA,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAwB,EACxB,OAAe,EACf,MAAoB;IAEpB,OAAO,MAAM,CAAC,WAAW,CAAuB,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;AAClG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAwB,EAAE,SAAiB;IAC1E,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QAC5B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC;QACzB,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,OAAO,KAAK,SAAS,CAAC;QAC/B,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,OAAO,KAAK,SAAS,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAwB,EACxB,OAAe,EACf,SAAiB,EACjB,MAAoB;IAEpB,OAAO,MAAM,CAAC,WAAW,CACvB,OAAO,EACP,UAAU,EACV,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAC7D,MAAM,CACP,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,MAAwB,EACxB,OAAe,EACf,SAAiB,EACjB,KAAe,EACf,MAAoB;IAEpB,OAAO,MAAM,CAAC,WAAW,CACvB,OAAO,EACP,WAAW,EACX,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EACrE,MAAM,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * `EmbeddingProvider` port implementation for Ollama (domain `ports.ts`,
3
+ * spec.md §9, architecture.md §6.8).
4
+ */
5
+ import type { Clock, EmbeddingBatchResultV1, EmbeddingProfile, EmbeddingProvider, EmbeddingReadinessV1, PortCallOptions, Random } from "@untangle-code/domain";
6
+ import type { EmbeddingBatchProgressV1, EmbeddingCachePort, OllamaEmbeddingProviderConfig, SleepFn } from "./types.js";
7
+ export interface OllamaEmbeddingProviderDeps {
8
+ clock: Clock;
9
+ random: Random;
10
+ /** Defaults to {@link InMemoryEmbeddingCache} (spec.md §9.4). */
11
+ cache?: EmbeddingCachePort;
12
+ /** Defaults to {@link realSleep}; tests inject a fake that resolves instantly. */
13
+ sleep?: SleepFn;
14
+ /** Defaults to the global `fetch`; tests inject a fake transport. */
15
+ fetchImpl?: typeof fetch;
16
+ }
17
+ export declare class OllamaEmbeddingProvider implements EmbeddingProvider {
18
+ readonly profile: EmbeddingProfile;
19
+ readonly profileId: string;
20
+ private readonly config;
21
+ private readonly clock;
22
+ private readonly random;
23
+ private readonly cache;
24
+ private readonly sleep;
25
+ private readonly httpClient;
26
+ private readonly backoffConfig;
27
+ private cachedReadiness;
28
+ private resolvedEndpointUrl;
29
+ private readyPromise;
30
+ /**
31
+ * One shared fetch-and-cache operation per still-uncached cache key
32
+ * (spec.md §9.4, WP-78): concurrent `embedDocuments`/`embedQuery` calls
33
+ * that race for the same not-yet-cached text join the SAME `/api/embed`
34
+ * request instead of each starting their own.
35
+ */
36
+ private readonly inFlightEmbeds;
37
+ constructor(config: OllamaEmbeddingProviderConfig, deps: OllamaEmbeddingProviderDeps);
38
+ ensureReady(options?: PortCallOptions): Promise<EmbeddingReadinessV1>;
39
+ embedDocuments(preparedTexts: string[], options?: PortCallOptions & {
40
+ onBatchProgress?: (progress: EmbeddingBatchProgressV1) => void;
41
+ }): Promise<EmbeddingBatchResultV1>;
42
+ embedQuery(preparedText: string, options?: PortCallOptions): Promise<number[]>;
43
+ /**
44
+ * Runs the readiness sequence exactly once per instance; concurrent
45
+ * callers (including through model-pull, spec.md §9.1 step 4) await the
46
+ * SAME run rather than each starting their own (WP-78 fehlerklasse: the
47
+ * same in-flight-dedup requirement applies here, not just to `/api/embed`).
48
+ *
49
+ * The shared run itself is started with NO caller's `signal` (it is
50
+ * intentionally not tied to whichever caller happened to trigger it) —
51
+ * `raceWithSignal` below still lets THIS caller's own signal reject its
52
+ * own wait promptly, without cancelling the readiness sequence still
53
+ * running for other, still-waiting callers.
54
+ */
55
+ private ensureReadyOnce;
56
+ private runReadiness;
57
+ private embedTexts;
58
+ private embedSlice;
59
+ /**
60
+ * Fetches every text in `items` in a SINGLE `/api/embed` request and
61
+ * settles each item's `deferred` with its own resulting cache entry, or
62
+ * rejects all of them on failure — which also frees their in-flight slots
63
+ * (`InFlightRegistry.register`'s cleanup), so the next call retries from
64
+ * scratch rather than being stuck on a broken promise (WP-78).
65
+ *
66
+ * Vor dem Absetzen des Requests wird der Cache ein ZWEITES Mal gelesen
67
+ * (WP-117, siehe {@link revalidateBeforeFetch}): die Miss-Entscheidung in
68
+ * `embedSlice` kann veraltet sein, sobald `cache.get()` verzögert auflöst.
69
+ *
70
+ * Intentionally takes no `AbortSignal`: this operation is shared by
71
+ * however many callers joined it, so it is not tied to any single one of
72
+ * their lifecycles (see `embedSlice`/`raceWithSignal`).
73
+ */
74
+ private fetchAndSettle;
75
+ /**
76
+ * Zweite Cache-Prüfung unmittelbar vor dem Request (WP-117).
77
+ *
78
+ * `embedSlice` trifft die Miss-/Join-Entscheidung EINMAL, aus einem
79
+ * einzigen `Promise.all` über `cache.get()`. Ein Cache, dessen `get()` den
80
+ * Zustand zum Aufrufzeitpunkt liest, aber erst später auflöst — also jeder
81
+ * netzgebundene Cache und damit genau der vorgesehene Erweiterungspunkt
82
+ * für einen persistenten Adapter —, öffnet ein Fenster: Aufrufer A kann
83
+ * zwischen der Anfrage und dem Auflösen von B's `get()` vollständig fertig
84
+ * werden, seinen Eintrag schreiben und seinen In-flight-Platz wieder
85
+ * freigeben. B fände dann weder Cache-Treffer noch In-flight-Eintrag und
86
+ * würde denselben Text ein zweites Mal einbetten.
87
+ *
88
+ * Diese Prüfung schließt das Fenster, weil sie STRIKT NACH der Beanspruchung
89
+ * des In-flight-Platzes liegt: der Platz wird erst freigegeben, nachdem
90
+ * `cache.set()` aufgelöst hat (siehe unten), also sieht ein `get()`, das
91
+ * danach abgesetzt wird, den geschriebenen Eintrag.
92
+ *
93
+ * Kein Deadlock und keine Wiederholungsschleife: genau EIN zusätzliches
94
+ * `cache.get()` je beanspruchtem Schlüssel, kein Warten auf fremde
95
+ * Deferreds (die eigenen Plätze hält dieser Aufruf selbst, ein anderer
96
+ * Aufrufer kann sie also nicht beanspruchen), und jedes Deferred wird auf
97
+ * jedem Pfad festgelegt — aus dem Cache, aus der Antwort oder per
98
+ * `reject` im `catch`.
99
+ *
100
+ * Kein `AbortSignal`: dieser Lauf gehört keinem einzelnen Aufrufer
101
+ * (dieselbe Begründung wie bei {@link fetchAndSettle}).
102
+ *
103
+ * Ein Fehler des Caches wird NICHT verschluckt (E4), sondern nach oben
104
+ * gereicht und weist damit die beteiligten Deferreds ab — dasselbe
105
+ * sichtbare Verhalten wie bei einem Fehlschlag der ERSTEN Cache-Lesung in
106
+ * `embedSlice`. Preis dieser Prüfung: eine zusätzliche Cache-Lesung je
107
+ * echtem Miss (dokumentiert in der README).
108
+ */
109
+ private revalidateBeforeFetch;
110
+ }
111
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EACV,KAAK,EACL,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,MAAM,EACP,MAAM,uBAAuB,CAAC;AAe/B,OAAO,KAAK,EACV,wBAAwB,EAGxB,kBAAkB,EAClB,6BAA6B,EAC7B,OAAO,EACR,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,kFAAkF;IAClF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAoED,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,SAAgB,OAAO,EAAE,gBAAgB,CAAC;IAC1C,SAAgB,SAAS,EAAE,MAAM,CAAC;IAElC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAE9C,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,YAAY,CAA4B;IAEhD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA+C;gBAE3D,MAAM,EAAE,6BAA6B,EAAE,IAAI,EAAE,2BAA2B;IAqB9E,WAAW,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAQrE,cAAc,CACzB,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAA;KAAE,GAC7F,OAAO,CAAC,sBAAsB,CAAC;IAUrB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAQ3F;;;;;;;;;;;OAWG;YACW,eAAe;YAcf,YAAY;YAgBZ,UAAU;YAwCV,UAAU;IA6FxB;;;;;;;;;;;;;;OAcG;YACW,cAAc;IA8C5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;YACW,qBAAqB;CAiBpC"}