export type Message = import("../types").Message; export type ToolDef = import("../types").ToolDef; export type GenerateResult = import("../types").GenerateResult; export type OllamaOptions = { model?: string | undefined; url?: string | undefined; /** * - Normalized thinking switch: `'on'` or `'off'`, and bare-agent sends the right `think` (`/api/chat`; NOT live-verified against a real Ollama thinking model — reply signal `message.thinking`) request field for the provider/model, tries the next known shape when a shape request fails with a 400/422 (not the temperature path), and the call ALWAYS RUNS: if every shape fails that way, ONE plain request without a thinking field decides — plain OK ⇒ `thinkingStatus:'unsupported'`, plain fails ⇒ that plain request's error is thrown as-is; other failures propagate after one request; a model measured as not switchable is sent plain directly. Only the winning shape is cached per instance+model ('unsupported' is never cached); warns once per status. Every reply is checked: `GenerateResult.thinkingStatus` is `'applied'`/`'unsupported'`/`'ignored'`/`'unknown'` (`'ignored'` = 'off' was accepted but the reply shows thinking anyway — warns once, result kept) and the legacy `thinkingApplied` is derived (`true`/`false`/`null`). Unset ⇒ request body byte-identical to before. Overridable per call via `generate(..., { thinkingMode })`; `null` suppresses an instance default. Any value but 'on'/'off' ⇒ `ValidationError`. A NEW field: it does not change the raw `thinking` option; setting both (after per-call override) ⇒ `ValidationError`. See `src/thinking-mode.js` / `thinkingSupport()`. * Per-call `thinkingMode` reaches EVERY provider in a `FallbackProvider` chain — correct for a normalized field (each provider maps it to its own shape; one that cannot — CLIPipe, a non-switchable model — just runs without it and reports `thinkingStatus:'unsupported'`, so the first member answers and the chain does not fall through). */ thinkingMode?: "on" | "off" | null | undefined; exposeErrorBody?: boolean | undefined; /** * - BA-18: request/idle timeout in ms. Bounds a silent or never-answering socket on inactivity so `generate()` rejects with a retryable `TimeoutError` (`code: 'ETIMEDOUT'`, `context.bound: 'idle'`) instead of hanging until the OS TCP timeout. `0`/`Infinity` disables it. Overridable per call via `generate(..., { timeoutMs })`. (A local Ollama that is loading a large model cold can be slow to first byte — raise this or disable it for very large local models.) */ timeoutMs?: number | undefined; /** * - BA-19: TOTAL call-duration deadline in ms, beside `timeoutMs`. The idle bound resets on any socket activity, so a response that trickles a byte forever never trips it and hangs for hours. This is an absolute, non-resetting ceiling; on trip, `generate()` rejects with a TERMINAL `TimeoutError` (`code: 'EDEADLINE'`, `context.bound: 'deadline'`, `retryable: false`). DISABLED by default; `0`/`Infinity` disable. Overridable per call via `generate(..., { deadlineMs })`. (A cold large-model load is a legitimate long single call — leave this disabled or set it generously for such models.) */ deadlineMs?: number | undefined; /** * - BA-27 follow-up: when a tool call's string-shaped * `function.arguments` JSON fails to parse, also attach the FULL raw string (verbatim, uncapped) * to `malformedToolCall.rawArguments` (model-generated; may be large or sensitive). Off by default, * independent of `exposeErrorBody`. See `src/provider-toolcalls.js`. */ exposeMalformedArgs?: boolean | undefined; }; /** @typedef {import('../types').Message} Message */ /** @typedef {import('../types').ToolDef} ToolDef */ /** @typedef {import('../types').GenerateResult} GenerateResult */ /** * @typedef {object} OllamaOptions * @property {string} [model='llama3.2'] * @property {string} [url='http://localhost:11434'] * @property {'on'|'off'|null} [thinkingMode] - Normalized thinking switch: `'on'` or `'off'`, and bare-agent sends the right `think` (`/api/chat`; NOT live-verified against a real Ollama thinking model — reply signal `message.thinking`) request field for the provider/model, tries the next known shape when a shape request fails with a 400/422 (not the temperature path), and the call ALWAYS RUNS: if every shape fails that way, ONE plain request without a thinking field decides — plain OK ⇒ `thinkingStatus:'unsupported'`, plain fails ⇒ that plain request's error is thrown as-is; other failures propagate after one request; a model measured as not switchable is sent plain directly. Only the winning shape is cached per instance+model ('unsupported' is never cached); warns once per status. Every reply is checked: `GenerateResult.thinkingStatus` is `'applied'`/`'unsupported'`/`'ignored'`/`'unknown'` (`'ignored'` = 'off' was accepted but the reply shows thinking anyway — warns once, result kept) and the legacy `thinkingApplied` is derived (`true`/`false`/`null`). Unset ⇒ request body byte-identical to before. Overridable per call via `generate(..., { thinkingMode })`; `null` suppresses an instance default. Any value but 'on'/'off' ⇒ `ValidationError`. A NEW field: it does not change the raw `thinking` option; setting both (after per-call override) ⇒ `ValidationError`. See `src/thinking-mode.js` / `thinkingSupport()`. * Per-call `thinkingMode` reaches EVERY provider in a `FallbackProvider` chain — correct for a normalized field (each provider maps it to its own shape; one that cannot — CLIPipe, a non-switchable model — just runs without it and reports `thinkingStatus:'unsupported'`, so the first member answers and the chain does not fall through). * @property {boolean} [exposeErrorBody=false] * @property {number} [timeoutMs=600000] - BA-18: request/idle timeout in ms. Bounds a silent or never-answering socket on inactivity so `generate()` rejects with a retryable `TimeoutError` (`code: 'ETIMEDOUT'`, `context.bound: 'idle'`) instead of hanging until the OS TCP timeout. `0`/`Infinity` disables it. Overridable per call via `generate(..., { timeoutMs })`. (A local Ollama that is loading a large model cold can be slow to first byte — raise this or disable it for very large local models.) * @property {number} [deadlineMs=0] - BA-19: TOTAL call-duration deadline in ms, beside `timeoutMs`. The idle bound resets on any socket activity, so a response that trickles a byte forever never trips it and hangs for hours. This is an absolute, non-resetting ceiling; on trip, `generate()` rejects with a TERMINAL `TimeoutError` (`code: 'EDEADLINE'`, `context.bound: 'deadline'`, `retryable: false`). DISABLED by default; `0`/`Infinity` disable. Overridable per call via `generate(..., { deadlineMs })`. (A cold large-model load is a legitimate long single call — leave this disabled or set it generously for such models.) * @property {boolean} [exposeMalformedArgs=false] - BA-27 follow-up: when a tool call's string-shaped * `function.arguments` JSON fails to parse, also attach the FULL raw string (verbatim, uncapped) * to `malformedToolCall.rawArguments` (model-generated; may be large or sensitive). Off by default, * independent of `exposeErrorBody`. See `src/provider-toolcalls.js`. */ export class OllamaProvider { /** * @param {OllamaOptions} [options] * @when you want local models via Ollama as the Loop's provider — no API key, self-hosted * @fails normalizes stopReason (promoting a complete tool call) and usage; a malformed tool-call JSON returns no usable calls with usage metered; a socket idle/deadline cut rejects with a retryable error. * @example * const provider = new OllamaProvider({ model: 'llama3' }); */ constructor(options?: OllamaOptions); model: string; url: string; exposeErrorBody: boolean; thinkingMode: "on" | "off" | null; /** @type {Map} shape that worked per mode+model (no re-probing) */ _thinkingShapes: Map; timeoutMs: number | undefined; deadlineMs: number | undefined; exposeMalformedArgs: boolean; /** * Generate a response from a local Ollama instance. * @param {Message[]} messages - Conversation messages. * @param {ToolDef[]} [tools=[]] - Tool definitions. * @param {Record} [options={}] - Options (`thinkingMode` — per-call override of the constructor's, `null` suppresses; `temperature`, `maxTokens`, `timeoutMs` — a per-call override of the constructor's `timeoutMs`, see BA-18; `deadlineMs` — a per-call override of the constructor's `deadlineMs`, see BA-19). * @returns {Promise} * @throws {Error} `[OllamaProvider] ...` — on HTTP errors or invalid JSON response. */ generate(messages: Message[], tools?: ToolDef[], options?: Record): Promise; /** One-time warning (per reason) that this model rejected `temperature` and the request was retried without it (BA-10). */ _warnTemperatureDropped(reason?: string): void; /** * @param {string} path * @param {Record} body * @param {number} [timeoutMs=0] - Idle-socket timeout (ms); 0 disables. See BA-18 / provider-http. * @param {number} [deadlineMs=0] - Total call-duration deadline (ms); 0 disables. See BA-19 / provider-http. * @returns {Promise} */ _request(path: string, body: Record, timeoutMs?: number, deadlineMs?: number): Promise; }