@proposit/proposit-core 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/extensions/argument-ingestion/shared/resolve-llm-stage-options.d.ts.map +1 -1
  2. package/dist/extensions/argument-ingestion/shared/resolve-llm-stage-options.js +6 -0
  3. package/dist/extensions/argument-ingestion/shared/resolve-llm-stage-options.js.map +1 -1
  4. package/dist/extensions/argument-ingestion/shared/types.d.ts +18 -7
  5. package/dist/extensions/argument-ingestion/shared/types.d.ts.map +1 -1
  6. package/dist/extensions/argument-ingestion/stages/axiom-indicator-detection.d.ts.map +1 -1
  7. package/dist/extensions/argument-ingestion/stages/axiom-indicator-detection.js +4 -2
  8. package/dist/extensions/argument-ingestion/stages/axiom-indicator-detection.js.map +1 -1
  9. package/dist/extensions/argument-ingestion/stages/citation-source-detection.d.ts.map +1 -1
  10. package/dist/extensions/argument-ingestion/stages/citation-source-detection.js +4 -2
  11. package/dist/extensions/argument-ingestion/stages/citation-source-detection.js.map +1 -1
  12. package/dist/extensions/argument-ingestion/stages/claim-canonicalization.d.ts.map +1 -1
  13. package/dist/extensions/argument-ingestion/stages/claim-canonicalization.js +2 -1
  14. package/dist/extensions/argument-ingestion/stages/claim-canonicalization.js.map +1 -1
  15. package/dist/extensions/argument-ingestion/stages/claim-mention-extraction.d.ts.map +1 -1
  16. package/dist/extensions/argument-ingestion/stages/claim-mention-extraction.js +4 -2
  17. package/dist/extensions/argument-ingestion/stages/claim-mention-extraction.js.map +1 -1
  18. package/dist/extensions/argument-ingestion/stages/claim-type-classification.d.ts.map +1 -1
  19. package/dist/extensions/argument-ingestion/stages/claim-type-classification.js +4 -2
  20. package/dist/extensions/argument-ingestion/stages/claim-type-classification.js.map +1 -1
  21. package/dist/extensions/argument-ingestion/stages/conclusion-selection.d.ts.map +1 -1
  22. package/dist/extensions/argument-ingestion/stages/conclusion-selection.js +2 -1
  23. package/dist/extensions/argument-ingestion/stages/conclusion-selection.js.map +1 -1
  24. package/dist/extensions/argument-ingestion/stages/relation-extraction.d.ts.map +1 -1
  25. package/dist/extensions/argument-ingestion/stages/relation-extraction.js +2 -1
  26. package/dist/extensions/argument-ingestion/stages/relation-extraction.js.map +1 -1
  27. package/dist/extensions/argument-ingestion/stages/segmentation.d.ts.map +1 -1
  28. package/dist/extensions/argument-ingestion/stages/segmentation.js +2 -1
  29. package/dist/extensions/argument-ingestion/stages/segmentation.js.map +1 -1
  30. package/dist/extensions/ollama/errors.d.ts +73 -0
  31. package/dist/extensions/ollama/errors.d.ts.map +1 -0
  32. package/dist/extensions/ollama/errors.js +228 -0
  33. package/dist/extensions/ollama/errors.js.map +1 -0
  34. package/dist/extensions/ollama/index.d.ts +6 -0
  35. package/dist/extensions/ollama/index.d.ts.map +1 -0
  36. package/dist/extensions/ollama/index.js +17 -0
  37. package/dist/extensions/ollama/index.js.map +1 -0
  38. package/dist/extensions/ollama/provider.d.ts +21 -0
  39. package/dist/extensions/ollama/provider.d.ts.map +1 -0
  40. package/dist/extensions/ollama/provider.js +375 -0
  41. package/dist/extensions/ollama/provider.js.map +1 -0
  42. package/dist/extensions/ollama/structured-output.d.ts +18 -0
  43. package/dist/extensions/ollama/structured-output.d.ts.map +1 -0
  44. package/dist/extensions/ollama/structured-output.js +164 -0
  45. package/dist/extensions/ollama/structured-output.js.map +1 -0
  46. package/dist/extensions/ollama/timeout-fetch.d.ts +24 -0
  47. package/dist/extensions/ollama/timeout-fetch.d.ts.map +1 -0
  48. package/dist/extensions/ollama/timeout-fetch.js +76 -0
  49. package/dist/extensions/ollama/timeout-fetch.js.map +1 -0
  50. package/dist/extensions/ollama/types.d.ts +179 -0
  51. package/dist/extensions/ollama/types.d.ts.map +1 -0
  52. package/dist/extensions/ollama/types.js +7 -0
  53. package/dist/extensions/ollama/types.js.map +1 -0
  54. package/dist/extensions/openai/provider.d.ts +26 -0
  55. package/dist/extensions/openai/provider.d.ts.map +1 -1
  56. package/dist/extensions/openai/provider.js +334 -9
  57. package/dist/extensions/openai/provider.js.map +1 -1
  58. package/dist/extensions/openai/types.d.ts +3 -0
  59. package/dist/extensions/openai/types.d.ts.map +1 -1
  60. package/package.json +17 -2
@@ -0,0 +1,228 @@
1
+ // Ollama-provider error classes + the `classifyOllamaError` mapping.
2
+ //
3
+ // The framework's `llmStage` retry policy classifies provider errors
4
+ // by inspecting a `retryReason` tag on the thrown object (see
5
+ // `src/lib/pipelines/stage-helpers.ts` #classifyError) — NOT by class
6
+ // identity. The failure *codes* live in the SDK-free
7
+ // `src/lib/pipelines/failure-codes.ts`. So these Ollama classes carry
8
+ // the same `retryReason` tags as the OpenAI provider's classes and set
9
+ // their `code` from the lib constants. They deliberately do NOT import
10
+ // from `extensions/openai/` and do NOT touch `src/lib/`.
11
+ //
12
+ // Mapping for the framework's default retry policy
13
+ // (`retryOn: ["schema_validation", "transient"]`):
14
+ //
15
+ // * `TransientLlmError` — `retryReason: "transient"`. Genuinely
16
+ // transient local hiccups: mid-stream `ECONNRESET`/socket drop,
17
+ // undici timeout cause-codes (`UND_ERR_HEADERS_TIMEOUT` /
18
+ // `UND_ERR_BODY_TIMEOUT` / `UND_ERR_CONNECT_TIMEOUT` — a long local
19
+ // thinking-model generation that outran the dispatcher timeout),
20
+ // cold-model-load 5xx (model pulled but still loading into VRAM),
21
+ // generic 5xx. Retried by the default policy.
22
+ // * `RateLimitLlmError` — `retryReason: "rate_limit"`. A local
23
+ // daemon rarely rate-limits, but a remote-Ollama / proxy setup can
24
+ // return 429; mapped for contract parity. Not in the default
25
+ // `retryOn`, so it fails fast (callers can opt in).
26
+ // * `SchemaValidationLlmError` — tagged `transient`; the framework's
27
+ // schema-retry path handles it. Thrown only for genuinely
28
+ // malformed / non-parseable JSON in the model's reply — NOT for a
29
+ // context-overflow (which is deterministic; see below).
30
+ // * `NonRetryableLlmError` — no tag; framework surfaces it
31
+ // immediately as `LLM_NON_RETRYABLE_ERROR`. Used for: daemon
32
+ // unreachable (`ECONNREFUSED`), model-not-pulled (404), and
33
+ // context-length / eval errors (deterministic — a retry re-fails).
34
+ // * `ToolLoopExhaustedError` — surfaces from the function-tool loop
35
+ // when the round cap is hit. Non-retryable (no tag).
36
+ import { LLM_NON_RETRYABLE_ERROR, LLM_RATE_LIMITED, LLM_TRANSIENT_ERROR, } from "../../lib/pipelines/failure-codes.js";
37
+ export class TransientLlmError extends Error {
38
+ retryReason = "transient";
39
+ code = LLM_TRANSIENT_ERROR;
40
+ status;
41
+ constructor(args) {
42
+ super(args.message);
43
+ this.name = "TransientLlmError";
44
+ this.status = args.status;
45
+ }
46
+ }
47
+ export class RateLimitLlmError extends Error {
48
+ retryReason = "rate_limit";
49
+ code = LLM_RATE_LIMITED;
50
+ status;
51
+ constructor(args) {
52
+ super(args.message);
53
+ this.name = "RateLimitLlmError";
54
+ this.status = args.status;
55
+ }
56
+ }
57
+ /**
58
+ * Thrown when the model's structured-output reply is genuinely
59
+ * malformed / non-parseable JSON. Tagged `transient` so the framework's
60
+ * default retry policy retries — a single re-roll often produces
61
+ * conforming output.
62
+ *
63
+ * **Do not** route a context-length overflow here: an overflow is
64
+ * deterministic and re-fails on the retried (still-oversized) prompt.
65
+ * `classifyOllamaError` routes overflow to {@link NonRetryableLlmError}.
66
+ */
67
+ export class SchemaValidationLlmError extends Error {
68
+ retryReason = "transient";
69
+ status;
70
+ constructor(args) {
71
+ super(args.message);
72
+ this.name = "SchemaValidationLlmError";
73
+ this.status = args.status;
74
+ }
75
+ }
76
+ export class NonRetryableLlmError extends Error {
77
+ code = LLM_NON_RETRYABLE_ERROR;
78
+ status;
79
+ constructor(args) {
80
+ super(args.message);
81
+ this.name = "NonRetryableLlmError";
82
+ this.status = args.status;
83
+ }
84
+ }
85
+ export class ToolLoopExhaustedError extends Error {
86
+ rounds;
87
+ constructor(args) {
88
+ super(args.message);
89
+ this.name = "ToolLoopExhaustedError";
90
+ this.rounds = args.rounds;
91
+ }
92
+ }
93
+ // -- error-shape probes ---------------------------------------------------
94
+ //
95
+ // The `ollama` SDK throws plain `Error`s (sometimes a `ResponseError`
96
+ // carrying a `status_code`) and lets low-level `fetch` failures bubble
97
+ // up. undici wraps a connection refusal as `TypeError: fetch failed`
98
+ // with a `.cause` carrying the Node `code`. We probe both the error
99
+ // itself and one level of `.cause` for the Node-style `code` and an
100
+ // HTTP-ish `status` / `status_code`.
101
+ function nodeCodeOf(err) {
102
+ const direct = readCode(err);
103
+ if (direct !== undefined)
104
+ return direct;
105
+ if (typeof err === "object" && err !== null) {
106
+ return readCode(err.cause);
107
+ }
108
+ return undefined;
109
+ }
110
+ function readCode(value) {
111
+ if (typeof value !== "object" || value === null)
112
+ return undefined;
113
+ const code = value.code;
114
+ return typeof code === "string" ? code : undefined;
115
+ }
116
+ function statusOf(err) {
117
+ if (typeof err !== "object" || err === null)
118
+ return undefined;
119
+ // `status_code` is the `ollama` SDK's ResponseError wire field — an
120
+ // external snake_case name, exempt from the camelCase rule.
121
+ /* eslint-disable @typescript-eslint/naming-convention */
122
+ const e = err;
123
+ if (typeof e.status === "number")
124
+ return e.status;
125
+ if (typeof e.status_code === "number")
126
+ return e.status_code;
127
+ /* eslint-enable @typescript-eslint/naming-convention */
128
+ return undefined;
129
+ }
130
+ function messageOf(err) {
131
+ if (err instanceof Error)
132
+ return err.message;
133
+ if (typeof err === "string")
134
+ return err;
135
+ return String(err);
136
+ }
137
+ const CONTEXT_OVERFLOW_PATTERN = /context (?:length|window)|maximum context|exceeds the (?:maximum )?context|too (?:many|long).*token|num_ctx/i;
138
+ const MODEL_NOT_FOUND_PATTERN = /model .* not found|not found, try pulling|no such model|pull(?:ing)? it/i;
139
+ /**
140
+ * Map an error surfaced by the `ollama` SDK (or an underlying `fetch`
141
+ * failure) to one of the framework-recognized provider error classes.
142
+ *
143
+ * The mapping is deliberately exhaustive on the cases the reviewer
144
+ * called out so they can't silently regress:
145
+ *
146
+ * - `ECONNREFUSED` → NonRetryable (daemon down; a retry
147
+ * won't bring it up within backoff).
148
+ * - model-not-pulled (404) → NonRetryable (`ollama pull` hint).
149
+ * - context-overflow/eval err → NonRetryable (deterministic; never
150
+ * SchemaValidationLlmError).
151
+ * - `ECONNRESET` / socket drop→ Transient.
152
+ * - undici timeout cause-codes→ Transient (`UND_ERR_HEADERS_TIMEOUT` /
153
+ * `UND_ERR_BODY_TIMEOUT` / `UND_ERR_CONNECT_TIMEOUT`; a long local
154
+ * thinking-model generation outran the timeout — retryable).
155
+ * - cold-load / generic 5xx → Transient.
156
+ * - 429 → RateLimit (remote/proxy setups).
157
+ * - anything else → NonRetryable (safe fail-fast default).
158
+ */
159
+ export function classifyOllamaError(err) {
160
+ const code = nodeCodeOf(err);
161
+ const status = statusOf(err);
162
+ const message = messageOf(err);
163
+ // Daemon unreachable — a connection refusal is the canonical
164
+ // "`ollama serve` isn't running on :11434" failure. A retry within
165
+ // the framework's short backoff won't bring it back up.
166
+ if (code === "ECONNREFUSED") {
167
+ return new NonRetryableLlmError({
168
+ message: `Cannot reach the Ollama daemon (${code}). Is \`ollama serve\` running on :11434? Original error: ${message}`,
169
+ });
170
+ }
171
+ // Mid-stream socket drop / transient connection loss — retryable.
172
+ // Includes undici's timeout cause-codes: a long local thinking-model
173
+ // generation that outruns the dispatcher's headers/body timeout (or a
174
+ // connect timeout) is transient against a still-working daemon, NOT a
175
+ // deterministic failure. The framework's default `retryOn: ["transient"]`
176
+ // then retries instead of dying `LLM_NON_RETRYABLE_ERROR`. undici wraps
177
+ // these as a `TypeError: fetch failed` whose `.cause.code` is the
178
+ // `UND_ERR_*` value — `nodeCodeOf` already probes one level of `.cause`.
179
+ if (code === "ECONNRESET" ||
180
+ code === "ETIMEDOUT" ||
181
+ code === "EPIPE" ||
182
+ code === "UND_ERR_HEADERS_TIMEOUT" ||
183
+ code === "UND_ERR_BODY_TIMEOUT" ||
184
+ code === "UND_ERR_CONNECT_TIMEOUT") {
185
+ return new TransientLlmError({
186
+ message: `Transient connection error talking to the Ollama daemon (${code}): ${message}`,
187
+ });
188
+ }
189
+ // Model not pulled — Ollama returns a 404 whose body says the model
190
+ // was not found. Deterministic until the user pulls it.
191
+ if (status === 404 || MODEL_NOT_FOUND_PATTERN.test(message)) {
192
+ return new NonRetryableLlmError({
193
+ message: `Ollama model not found. Run \`ollama pull <model>\` to download it first. Original error: ${message}`,
194
+ status,
195
+ });
196
+ }
197
+ // Context-length overflow / model-eval error — DETERMINISTIC. Must
198
+ // be NonRetryable: routing this to SchemaValidationLlmError (tagged
199
+ // `transient`) would burn a guaranteed-failing second attempt on
200
+ // the same oversized prompt.
201
+ if (CONTEXT_OVERFLOW_PATTERN.test(message)) {
202
+ return new NonRetryableLlmError({
203
+ message: `Ollama request exceeded the model's context window (deterministic — retrying the same prompt will re-fail). Shorten the input or raise \`num_ctx\`. Original error: ${message}`,
204
+ status,
205
+ });
206
+ }
207
+ if (status === 429) {
208
+ return new RateLimitLlmError({
209
+ message: `Ollama returned 429 (rate-limited; typical of a remote/proxied daemon): ${message}`,
210
+ status,
211
+ });
212
+ }
213
+ // Cold-model-load (model pulled but still loading into VRAM) and
214
+ // any other 5xx are transient — a retry after backoff can succeed
215
+ // once the model is resident.
216
+ if (status !== undefined && status >= 500) {
217
+ return new TransientLlmError({
218
+ message: `Ollama returned ${status.toString()} (possibly a cold model load into VRAM): ${message}`,
219
+ status,
220
+ });
221
+ }
222
+ // Safe default: fail fast rather than retry an unrecognized error.
223
+ return new NonRetryableLlmError({
224
+ message: `Unclassified Ollama error: ${message}`,
225
+ status,
226
+ });
227
+ }
228
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/extensions/ollama/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { OllamaProvider } from "./provider.js";
2
+ export type { TOllamaProviderConfig, TOllamaClient, TOllamaChatRequest, TOllamaChatResponse, } from "./types.js";
3
+ export { typeboxToJsonSchema } from "./structured-output.js";
4
+ export type { TOllamaJsonSchema } from "./structured-output.js";
5
+ export { NonRetryableLlmError, RateLimitLlmError, SchemaValidationLlmError, ToolLoopExhaustedError, TransientLlmError, classifyOllamaError, } from "./errors.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/ollama/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,YAAY,EACR,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,EACH,oBAAoB,EACpB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,GACtB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,17 @@
1
+ // Barrel for the Ollama provider extension.
2
+ //
3
+ // Public surface consumed via the `@proposit/proposit-core/extensions/ollama`
4
+ // subpath export: the provider constructor + its config type + the
5
+ // standard-JSON-schema converter + the error classes (which callers may
6
+ // `instanceof`-match for finer-grained observability).
7
+ //
8
+ // NOTE: the error class names (`NonRetryableLlmError`, …) intentionally
9
+ // mirror the OpenAI provider's names but are *distinct* classes living
10
+ // in this extension. They are surfaced only from this subpath (NOT the
11
+ // package root) to avoid colliding with the root-exported OpenAI error
12
+ // classes. The framework classifies by the `retryReason` tag, not class
13
+ // identity, so the duplication is intentional and harmless.
14
+ export { OllamaProvider } from "./provider.js";
15
+ export { typeboxToJsonSchema } from "./structured-output.js";
16
+ export { NonRetryableLlmError, RateLimitLlmError, SchemaValidationLlmError, ToolLoopExhaustedError, TransientLlmError, classifyOllamaError, } from "./errors.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/ollama/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,8EAA8E;AAC9E,mEAAmE;AACnE,wEAAwE;AACxE,uDAAuD;AACvD,EAAE;AACF,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,uEAAuE;AACvE,wEAAwE;AACxE,4DAA4D;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAO9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EACH,oBAAoB,EACpB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,GACtB,MAAM,aAAa,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { TLlmProvider, TLlmRequest, TLlmResponse } from "../../lib/llm/types.js";
2
+ import type { TOllamaProviderConfig } from "./types.js";
3
+ export declare class OllamaProvider implements TLlmProvider {
4
+ private readonly config;
5
+ private clientPromise;
6
+ private readonly maxToolRounds;
7
+ private readonly numCtx;
8
+ private readonly requestTimeoutMs;
9
+ private readonly stream;
10
+ constructor(config?: TOllamaProviderConfig);
11
+ respond<T>(req: TLlmRequest<T>): Promise<TLlmResponse<T>>;
12
+ private runChatLoop;
13
+ /**
14
+ * Resolve the SDK client: the injected one, or a freshly imported
15
+ * `Ollama` instance. Memoized so the dynamic import + construction
16
+ * runs at most once.
17
+ */
18
+ private getClient;
19
+ private importAndConstructClient;
20
+ }
21
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/extensions/ollama/provider.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EACR,YAAY,EACZ,WAAW,EACX,YAAY,EAGf,MAAM,wBAAwB,CAAA;AAc/B,OAAO,KAAK,EAOR,qBAAqB,EAExB,MAAM,YAAY,CAAA;AAkBnB,qBAAa,cAAe,YAAW,YAAY;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,CAAC,EAAE,qBAAqB;IAUpC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAmCjD,WAAW;IAiJzB;;;;OAIG;IACH,OAAO,CAAC,SAAS;YAQH,wBAAwB;CAiCzC"}
@@ -0,0 +1,375 @@
1
+ // Concrete `TLlmProvider` backed by a local Ollama daemon via the
2
+ // official `ollama` npm SDK.
3
+ //
4
+ // Dev/test only — production stays on OpenAI. The provider exists so a
5
+ // developer can run the entire LLM-backed stack (notably the v2
6
+ // argument-ingestion pipeline) against a self-hosted model
7
+ // (`qwen3.6:latest`) with zero API cost.
8
+ //
9
+ // Deliberate divergences from the in-repo OpenAI provider:
10
+ // * Uses the official `ollama` SDK (an optional peer) rather than raw
11
+ // `fetch`. A missing package surfaces as an actionable error at
12
+ // construction time (dynamic-import-or-throw).
13
+ // * Structured output goes through the Ollama provider's own
14
+ // standard-JSON-schema converter (`./structured-output.ts`), not
15
+ // the OpenAI strict-mode converter.
16
+ // * `reasoningEffort` is ignored (no Ollama analogue);
17
+ // `maxOutputTokens` maps to `options.num_predict` (positive values
18
+ // only — never 0; -1/-2 are Ollama sentinels we never emit).
19
+ // * Thinking is left ON (the SDK/model default) — a prior finding
20
+ // showed `think: false` degrades structured-output fidelity (the
21
+ // model drops the required object wrapper → bare array, failing
22
+ // `Value.Check`). This trades latency (thinking-on stages can run
23
+ // several minutes) for correctness; the generous `requestTimeoutMs`
24
+ // default (see below) accommodates the latency.
25
+ // * A generous per-request timeout (`requestTimeoutMs`, default 20 min)
26
+ // is applied via a PER-PROVIDER undici `Agent` passed as the SDK
27
+ // client's `fetch` — never `setGlobalDispatcher`; a library must not
28
+ // mutate global state. See `./timeout-fetch.ts`.
29
+ // * Errors are classified by `./errors.ts` #classifyOllamaError, which
30
+ // carries the same `retryReason` tags + lib failure-codes as the
31
+ // OpenAI provider. No `ollama → openai` dependency, no lib change.
32
+ //
33
+ // `AbortSignal` is honored by registering an abort listener that calls
34
+ // the SDK client's `abort()`; the SDK then rejects the in-flight
35
+ // `chat()` with an `AbortError`, which the provider re-throws verbatim
36
+ // so `llmStage`'s mid-flight-abort detector marks the stage `skipped`.
37
+ import { debugLlmFailure, debugLlmRequest, debugLlmResponse, } from "../../lib/pipelines/debug-log.js";
38
+ import { typeboxToJsonSchema } from "./structured-output.js";
39
+ import { buildTimeoutFetch } from "./timeout-fetch.js";
40
+ import { NonRetryableLlmError, SchemaValidationLlmError, ToolLoopExhaustedError, classifyOllamaError, } from "./errors.js";
41
+ const STAGE_ID_MARKER = /<!--\s*stage-id:\s*([^\s>]+)\s*-->/;
42
+ const DEFAULT_BASE_URL = "http://localhost:11434";
43
+ const DEFAULT_MAX_TOOL_ROUNDS = 6;
44
+ // Generous default context window. Ollama silently truncates prompts
45
+ // longer than `num_ctx` (no error — the model emits schema-valid JSON
46
+ // from a truncated prompt), and its per-model default is often ~4096,
47
+ // well under a real multi-KB ingestion prompt. See `TOllamaProviderConfig.numCtx`.
48
+ const DEFAULT_NUM_CTX = 32768;
49
+ // Generous per-request timeout for local thinking models. undici's 300s
50
+ // default aborts long structured-extraction generations with
51
+ // UND_ERR_HEADERS_TIMEOUT; 20 min gives qwen3.6-with-thinking room. The
52
+ // timeout is applied via a PER-PROVIDER undici Agent (never global state)
53
+ // — see ./timeout-fetch.ts and TOllamaProviderConfig.requestTimeoutMs.
54
+ const DEFAULT_REQUEST_TIMEOUT_MS = 1_200_000;
55
+ export class OllamaProvider {
56
+ config;
57
+ clientPromise = null;
58
+ maxToolRounds;
59
+ numCtx;
60
+ requestTimeoutMs;
61
+ stream;
62
+ constructor(config) {
63
+ this.config = config ?? {};
64
+ this.maxToolRounds =
65
+ this.config.maxToolCallRounds ?? DEFAULT_MAX_TOOL_ROUNDS;
66
+ this.numCtx = this.config.numCtx ?? DEFAULT_NUM_CTX;
67
+ this.requestTimeoutMs =
68
+ this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
69
+ this.stream = this.config.stream ?? true;
70
+ }
71
+ async respond(req) {
72
+ // Already-aborted short-circuit — don't even construct the
73
+ // client or call the daemon.
74
+ if (req.signal?.aborted) {
75
+ throw abortError();
76
+ }
77
+ const client = await this.getClient();
78
+ const convertedSchema = typeboxToJsonSchema(req.outputSchema);
79
+ const tools = req.tools ? translateTools(req.tools) : undefined;
80
+ const stageIdMatch = STAGE_ID_MARKER.exec(req.systemPrompt);
81
+ const debugStageId = stageIdMatch ? stageIdMatch[1] : null;
82
+ // Wire the AbortSignal to the SDK client's abort(). The SDK
83
+ // rejects the in-flight chat() with an AbortError when this
84
+ // fires.
85
+ const onAbort = () => {
86
+ client.abort();
87
+ };
88
+ req.signal?.addEventListener("abort", onAbort, { once: true });
89
+ try {
90
+ return await this.runChatLoop({
91
+ client,
92
+ req,
93
+ convertedSchema,
94
+ tools,
95
+ debugStageId,
96
+ });
97
+ }
98
+ finally {
99
+ req.signal?.removeEventListener("abort", onAbort);
100
+ }
101
+ }
102
+ async runChatLoop(args) {
103
+ const { client, req, convertedSchema, tools, debugStageId } = args;
104
+ // Running message array. Tool-call rounds append the model's
105
+ // tool_calls echo + the tool result before re-calling.
106
+ const messages = [
107
+ { role: "system", content: req.systemPrompt },
108
+ { role: "user", content: req.userMessage },
109
+ ];
110
+ let lastUsage = { input: 0, output: 0 };
111
+ for (let round = 0; round < this.maxToolRounds; round += 1) {
112
+ const chatRequest = {
113
+ model: req.model,
114
+ messages,
115
+ // Build `format` from the single converted object so the
116
+ // schema can't drift from any prompt-grounding copy.
117
+ format: convertedSchema,
118
+ stream: this.stream,
119
+ };
120
+ if (tools) {
121
+ chatRequest.tools = tools;
122
+ }
123
+ // `temperature: 0` for deterministic structured output;
124
+ // `num_ctx` set generously so Ollama doesn't silently
125
+ // truncate a real multi-KB ingestion prompt (its per-model
126
+ // default is often ~4096). `maxOutputTokens` → num_predict,
127
+ // positive only: 0 means "generate nothing"; -1/-2 are Ollama
128
+ // sentinels we never emit.
129
+ const options = {
130
+ temperature: 0,
131
+ num_ctx: this.numCtx,
132
+ };
133
+ if (req.maxOutputTokens !== undefined && req.maxOutputTokens > 0) {
134
+ options.num_predict = req.maxOutputTokens;
135
+ }
136
+ chatRequest.options = options;
137
+ debugLlmRequest({
138
+ stageId: debugStageId,
139
+ model: req.model,
140
+ maxOutputTokens: req.maxOutputTokens,
141
+ reasoningEffort: req.reasoningEffort,
142
+ systemPromptLen: req.systemPrompt.length,
143
+ userMessageLen: req.userMessage.length,
144
+ systemPromptHead: req.systemPrompt,
145
+ userMessageHead: req.userMessage,
146
+ });
147
+ let response;
148
+ try {
149
+ const raw = await client.chat(chatRequest);
150
+ response = isAsyncIterable(raw) ? await collectStream(raw) : raw;
151
+ }
152
+ catch (err) {
153
+ // Mid-flight abort: the SDK rejects with an AbortError
154
+ // when our signal listener called client.abort().
155
+ // Re-throw verbatim so llmStage marks the stage skipped.
156
+ if (isAbortError(err) || req.signal?.aborted) {
157
+ throw abortError();
158
+ }
159
+ const classified = classifyOllamaError(err);
160
+ debugLlmFailure({
161
+ stageId: debugStageId,
162
+ model: req.model,
163
+ errorName: classified.name,
164
+ errorMessage: classified.message,
165
+ tokenUsage: lastUsage,
166
+ });
167
+ throw classified;
168
+ }
169
+ lastUsage = mergeUsage(lastUsage, {
170
+ input: response.prompt_eval_count ?? 0,
171
+ output: response.eval_count ?? 0,
172
+ });
173
+ const toolCalls = response.message.tool_calls ?? [];
174
+ if (toolCalls.length > 0) {
175
+ // Echo the assistant tool-call message, then append one
176
+ // tool-result message per call before looping.
177
+ messages.push({
178
+ role: "assistant",
179
+ content: response.message.content,
180
+ tool_calls: toolCalls,
181
+ });
182
+ for (const call of toolCalls) {
183
+ const handler = findFunctionHandler(req.tools, call.function.name);
184
+ if (!handler) {
185
+ throw new NonRetryableLlmError({
186
+ message: `Ollama requested unknown function tool "${call.function.name}".`,
187
+ });
188
+ }
189
+ const handlerResult = await handler.handler(call.function.arguments);
190
+ messages.push({
191
+ role: "tool",
192
+ content: typeof handlerResult === "string"
193
+ ? handlerResult
194
+ : JSON.stringify(handlerResult),
195
+ });
196
+ }
197
+ continue;
198
+ }
199
+ const text = response.message.content;
200
+ if (text === undefined || text === "") {
201
+ throw new SchemaValidationLlmError({
202
+ message: "Ollama chat response carried no assistant text content.",
203
+ });
204
+ }
205
+ const parsed = safeParseJson(text);
206
+ debugLlmResponse({
207
+ stageId: debugStageId,
208
+ outputTextLen: text.length,
209
+ tokenUsage: lastUsage,
210
+ });
211
+ return {
212
+ output: parsed,
213
+ tokenUsage: lastUsage,
214
+ // The Ollama chat response is not request-id-bearing;
215
+ // `rawResponseId` is optional, so leaving it undefined is
216
+ // contract-legal. Do not fabricate one.
217
+ rawResponseId: undefined,
218
+ };
219
+ }
220
+ throw new ToolLoopExhaustedError({
221
+ message: `Function-tool agent loop exceeded ${this.maxToolRounds.toString()} rounds without a final response.`,
222
+ rounds: this.maxToolRounds,
223
+ });
224
+ }
225
+ /**
226
+ * Resolve the SDK client: the injected one, or a freshly imported
227
+ * `Ollama` instance. Memoized so the dynamic import + construction
228
+ * runs at most once.
229
+ */
230
+ getClient() {
231
+ if (this.config.client) {
232
+ return Promise.resolve(this.config.client);
233
+ }
234
+ this.clientPromise ??= this.importAndConstructClient();
235
+ return this.clientPromise;
236
+ }
237
+ async importAndConstructClient() {
238
+ const baseUrl = this.config.baseUrl ?? DEFAULT_BASE_URL;
239
+ const importOllama = this.config.importOllama ??
240
+ (() => import("ollama"));
241
+ let mod;
242
+ try {
243
+ mod = await importOllama();
244
+ }
245
+ catch (err) {
246
+ throw new Error("OllamaProvider: the optional `ollama` package is not installed. " +
247
+ "Run `pnpm add ollama` (it is declared as an optional peerDependency) " +
248
+ "or pass a pre-built `client` via the provider config. " +
249
+ `Original import error: ${err instanceof Error ? err.message : String(err)}`);
250
+ }
251
+ // Per-provider raised-timeout fetch (no global mutation). Falls
252
+ // back to the SDK default fetch when undici is unavailable or the
253
+ // caller set requestTimeoutMs to 0.
254
+ const timeoutFetch = await buildTimeoutFetch(this.requestTimeoutMs, this.config.importUndici);
255
+ const sdkConfig = {
256
+ host: baseUrl,
257
+ };
258
+ if (timeoutFetch) {
259
+ sdkConfig.fetch = timeoutFetch;
260
+ }
261
+ return new mod.Ollama(sdkConfig);
262
+ }
263
+ }
264
+ // -- helpers --------------------------------------------------------------
265
+ function abortError() {
266
+ const e = new Error("The Ollama request was aborted.");
267
+ e.name = "AbortError";
268
+ return e;
269
+ }
270
+ function isAbortError(err) {
271
+ return (typeof err === "object" &&
272
+ err !== null &&
273
+ err.name === "AbortError");
274
+ }
275
+ function safeParseJson(raw) {
276
+ try {
277
+ return JSON.parse(raw);
278
+ }
279
+ catch (err) {
280
+ throw new SchemaValidationLlmError({
281
+ message: `Ollama returned malformed JSON in structured-output content: ${err instanceof Error ? err.message : String(err)}`,
282
+ });
283
+ }
284
+ }
285
+ function isAsyncIterable(value) {
286
+ return (typeof value === "object" &&
287
+ value !== null &&
288
+ Symbol.asyncIterator in value);
289
+ }
290
+ /**
291
+ * Consume a streamed `chat()` generation and synthesize a single
292
+ * `TOllamaChatResponse`: concatenated `message.content`, tool_calls
293
+ * captured from any chunk that carries them, and the eval counts from
294
+ * the final (`done: true`) chunk. The synthesized response feeds the
295
+ * existing one-shot processing path unchanged, so `respond()`'s
296
+ * contract is preserved.
297
+ */
298
+ async function collectStream(iterable) {
299
+ let content = "";
300
+ let role = "assistant";
301
+ let toolCalls;
302
+ let promptEvalCount = 0;
303
+ let evalCount = 0;
304
+ for await (const chunk of iterable) {
305
+ const msg = chunk.message;
306
+ if (msg) {
307
+ content += msg.content ?? "";
308
+ if (msg.role)
309
+ role = msg.role;
310
+ // Ollama emits tool_calls complete within a single chunk
311
+ // (not OpenAI-style per-index deltas), so take the latest
312
+ // chunk that carries them — concatenating would DUPLICATE
313
+ // calls. Ingestion is tool-free; only tool-using callers
314
+ // exercise this path.
315
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
316
+ toolCalls = msg.tool_calls;
317
+ }
318
+ }
319
+ // Last-wins: the synthesized response carries the FINAL chunk's
320
+ // single-round eval counts, NOT a cumulative sum across chunks.
321
+ // The terminal chunk reports this round's complete terminal
322
+ // counts, so taking the last value is the correct per-round
323
+ // figure. `runChatLoop`'s `mergeUsage` then SUMS these per-round
324
+ // terminal counts across tool-call rounds — summing the chunk
325
+ // values here instead would double-count within a round.
326
+ if (chunk.prompt_eval_count !== undefined) {
327
+ promptEvalCount = chunk.prompt_eval_count;
328
+ }
329
+ if (chunk.eval_count !== undefined) {
330
+ evalCount = chunk.eval_count;
331
+ }
332
+ }
333
+ return {
334
+ message: { role, content, tool_calls: toolCalls },
335
+ done: true,
336
+ prompt_eval_count: promptEvalCount,
337
+ eval_count: evalCount,
338
+ };
339
+ }
340
+ function mergeUsage(accumulated, next) {
341
+ return {
342
+ input: accumulated.input + next.input,
343
+ output: accumulated.output + next.output,
344
+ };
345
+ }
346
+ function translateTools(tools) {
347
+ return tools.map((tool) => {
348
+ if (tool.kind === "function") {
349
+ return {
350
+ type: "function",
351
+ function: {
352
+ name: tool.name,
353
+ description: tool.description,
354
+ parameters: typeboxToJsonSchema(tool.parameters),
355
+ },
356
+ };
357
+ }
358
+ // Hosted-tool kinds (web_search / file_search / mcp) have no
359
+ // local Ollama equivalent. Fail fast and legibly.
360
+ throw new NonRetryableLlmError({
361
+ message: `Tool kind "${tool.kind}" is not supported by the Ollama provider. Only kind "function" (local handler) is supported.`,
362
+ });
363
+ });
364
+ }
365
+ function findFunctionHandler(tools, name) {
366
+ if (!tools)
367
+ return undefined;
368
+ for (const tool of tools) {
369
+ if (tool.kind === "function" && tool.name === name) {
370
+ return tool;
371
+ }
372
+ }
373
+ return undefined;
374
+ }
375
+ //# sourceMappingURL=provider.js.map