@nemesis-oss/ollama-sdk 1.10.0 → 1.12.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.
package/dist/index.d.cts CHANGED
@@ -1,12 +1,12 @@
1
- import { F as FetchLike, M as Middleware, L as Logger, R as RequestLifecycleHook, H as HttpClient, c as OllamaStreamEvent, A as AbortableAsyncIterable, d as OllamaStreamEventType, P as ProgressStreamResult, B as BinaryBody, O as OllamaRuntime, C as ChatStreamResult, G as GenerateStreamResult, e as RawUsageSource, T as TokenUsage } from './runtime-BsdBbuOv.cjs';
2
- export { D as DoneEvent, E as ErrorEvent, f as HttpBody, g as HttpClientOptions, h as HttpRequestOptions, i as LifecycleErrorEvent, j as LifecycleRetryEvent, k as LifecycleStartEvent, l as LifecycleSuccessEvent, m as LogFn, n as MessageEvent, o as MiddlewareContext, N as NANOS_PER_MS, p as NANOS_PER_SECOND, q as NOOP_LOGGER, r as NextFunction, s as OllamaAbortError, t as OllamaAgentMaxIterationsError, u as OllamaAgentMaxToolCallsError, v as OllamaAuthError, w as OllamaBadGatewayError, x as OllamaClientError, y as OllamaClientErrorOptions, z as OllamaErrorRequestContext, J as OllamaErrorResponseContext, K as OllamaGenericClientError, Q as OllamaIncompatibleModelError, U as OllamaMcpError, V as OllamaModelRoutingError, W as OllamaNetworkError, X as OllamaNotFoundError, Y as OllamaOverloadedError, Z as OllamaQuotaExceededError, _ as OllamaRateLimitError, $ as OllamaRequestTooLargeError, a0 as OllamaRequestValidationError, a1 as OllamaResponseValidationError, a2 as OllamaServerError, a3 as OllamaServerVersionUnknownError, a4 as OllamaSkillInvalidError, a5 as OllamaSkillNotFoundError, a6 as OllamaStreamError, a7 as OllamaTimeoutError, a8 as OllamaToolTimeoutError, a9 as OllamaToolValidationError, aa as OllamaUnsupportedCapabilityError, ab as RequestContext, ac as RequestLifecycleEvent, ad as ResponseContext, ae as SseEvent, af as ThinkingEvent, ag as TokenEvent, ah as ToolCallEvent, ai as composeMiddleware, aj as createConsoleLogger, ak as extractUsage, al as mapError, am as parseSseStream } from './runtime-BsdBbuOv.cjs';
3
- import { T as ThinkingMetadata, M as ModelResponse, S as ShowRequestOptions, a as ShowResponse, P as PullRequestOptions, b as ProgressResponse, c as PushRequestOptions, C as CreateRequestOptions, D as DeleteRequestOptions, d as StatusResponse, e as CopyRequestOptions, f as PsResponse, V as VersionResponse, R as RequestCancellationOptions, g as ToolParameters, h as ThinkValue, i as ToolCall, j as ChatRequestOptions, k as ModelOptions, l as ToolDefinition, F as FormatOption, m as ChatResponse, n as Message, G as GenerateRequestOptions, o as GenerateResponse, E as EmbedRequestOptions, p as EmbedResponse, q as EmbeddingsRequestOptions, r as EmbeddingsResponse, W as WebSearchRequestOptions, s as WebSearchResponse, t as WebFetchRequestOptions, u as WebFetchResponse, U as UsageRequestOptions, B as BalanceRequestOptions, v as VisionInput } from './types-BtQexFpT.cjs';
4
- export { L as ListResponse, w as Logprob, x as LogprobToken, y as ModelDetails, z as Role, A as ToolCallFunction, H as ToolFunctionDefinition, I as ToolProperty, J as UsageRange, K as UsageScope, N as WebSearchResult, O as imageStringNeedsResolution, Q as resolveImageInput } from './types-BtQexFpT.cjs';
1
+ import { T as ThinkingMetadata, M as ModelResponse, R as RequestCancellationOptions, S as ShowRequestOptions, a as ShowResponse, P as PullRequestOptions, b as ProgressResponse, c as PushRequestOptions, C as CreateRequestOptions, D as DeleteRequestOptions, d as StatusResponse, e as CopyRequestOptions, f as PsResponse, V as VersionResponse, g as ToolParameters, h as ThinkValue, i as ToolCall, j as ChatRequestOptions, k as ModelOptions, l as ToolDefinition, F as FormatOption, m as ChatResponse, n as Message, G as GenerateRequestOptions, o as GenerateResponse, E as EmbedRequestOptions, p as EmbedResponse, q as EmbeddingsRequestOptions, r as EmbeddingsResponse, W as WebSearchRequestOptions, s as WebSearchResponse, t as WebFetchRequestOptions, u as WebFetchResponse, U as UsageRequestOptions, B as BalanceRequestOptions, v as PingResult, w as VisionInput } from './types-B57ZZfp3.cjs';
2
+ export { L as ListResponse, x as Logprob, y as LogprobToken, z as ModelDetails, A as Role, H as ToolCallFunction, I as ToolFunctionDefinition, J as ToolProperty, K as UsageRange, N as UsageScope, O as WebSearchResult, Q as imageStringNeedsResolution, X as resolveImageInput } from './types-B57ZZfp3.cjs';
3
+ import { H as HttpClient, F as FetchLike, M as Middleware, L as Logger, R as RequestLifecycleHook, c as OllamaStreamEvent, A as AbortableAsyncIterable, d as OllamaStreamEventType, P as ProgressStreamResult, B as BinaryBody, O as OllamaRuntime, C as ChatStreamResult, G as GenerateStreamResult, e as RawUsageSource, T as TokenUsage } from './runtime-DmI1cOEq.cjs';
4
+ export { D as DoneEvent, E as ErrorEvent, f as HttpBody, g as HttpClientOptions, h as HttpRequestOptions, i as LifecycleErrorEvent, j as LifecycleRetryEvent, k as LifecycleStartEvent, l as LifecycleSuccessEvent, m as LogFn, n as MessageEvent, o as MiddlewareContext, N as NANOS_PER_MS, p as NANOS_PER_SECOND, q as NOOP_LOGGER, r as NextFunction, s as OllamaAbortError, t as OllamaAgentMaxIterationsError, u as OllamaAgentMaxToolCallsError, v as OllamaAgentToolLoopError, w as OllamaAuthError, x as OllamaBadGatewayError, y as OllamaClientError, z as OllamaClientErrorOptions, J as OllamaErrorRequestContext, K as OllamaErrorResponseContext, Q as OllamaGenericClientError, U as OllamaIncompatibleModelError, V as OllamaMcpError, W as OllamaModelRoutingError, X as OllamaNetworkError, Y as OllamaNotFoundError, Z as OllamaOverloadedError, _ as OllamaQuotaExceededError, $ as OllamaRateLimitError, a0 as OllamaRequestTooLargeError, a1 as OllamaRequestValidationError, a2 as OllamaResponseValidationError, a3 as OllamaServerError, a4 as OllamaServerVersionUnknownError, a5 as OllamaSkillInvalidError, a6 as OllamaSkillNotFoundError, a7 as OllamaStreamError, a8 as OllamaTimeoutError, a9 as OllamaToolTimeoutError, aa as OllamaToolValidationError, ab as OllamaUnsupportedCapabilityError, ac as RequestContext, ad as RequestLifecycleEvent, ae as ResponseContext, af as SseEvent, ag as ThinkingEvent, ah as TokenEvent, ai as ToolCallEvent, aj as composeMiddleware, ak as createConsoleLogger, al as extractUsage, am as mapError, an as parseSseStream } from './runtime-DmI1cOEq.cjs';
5
5
  import { z } from 'zod';
6
- import { R as RequestRunner, S as SystemOneChoiceAnswer, o as SystemOneNoulAnswer, p as SystemOneScoreAnswer, q as SystemOneChoiceQuestion, r as SystemOneNoulQuestion, s as SystemOneScoreQuestion, t as SystemOneRequest$1, u as SystemOneResponse$1, v as SystemOneContent, N as NativeApi, w as OpenAICompatClient, x as AnthropicCompatClient } from './anthropic-DuE8M6Tz.cjs';
7
- export { y as AnthropicCacheControl, z as AnthropicContentBlock, B as AnthropicImageContentBlock, C as AnthropicMessage, n as AnthropicMessageStreamEvent, A as AnthropicMessagesRequest, m as AnthropicMessagesResponse, D as AnthropicMessagesStream, E as AnthropicMetadata, F as AnthropicOutputConfig, G as AnthropicRedactedThinkingContentBlock, H as AnthropicSystem, I as AnthropicSystemTextBlock, J as AnthropicTextContentBlock, K as AnthropicThinkingConfig, L as AnthropicThinkingContentBlock, M as AnthropicTool, P as AnthropicToolChoice, Q as AnthropicToolResultContentBlock, T as AnthropicToolUseContentBlock, U as AnthropicUnknownStreamEvent, V as OllamaAnthropicContentBlock, W as OllamaAnthropicMessage, X as OllamaAnthropicMessagesRequest, Y as OllamaAnthropicSystem, Z as OllamaAnthropicSystemTextBlock, _ as OllamaAnthropicTextContentBlock, $ as OllamaAnthropicThinkingConfig, a0 as OllamaAnthropicTool, a1 as OllamaAnthropicToolResultContentBlock, a2 as OllamaOpenAIChatCompletionRequest, a3 as OllamaOpenAICompletionRequest, a4 as OllamaOpenAIEmbeddingRequest, a5 as OllamaOpenAIReasoningEffort, a6 as OllamaOpenAIResponsesRequest, a7 as OpenAIChatCompletionChoice, b as OpenAIChatCompletionChunk, a8 as OpenAIChatCompletionChunkChoice, a9 as OpenAIChatCompletionDelta, O as OpenAIChatCompletionRequest, a as OpenAIChatCompletionResponse, aa as OpenAIChatCompletionStream, ab as OpenAICompletionChoice, e as OpenAICompletionChunk, c as OpenAICompletionRequest, d as OpenAICompletionResponse, ac as OpenAICompletionStream, ad as OpenAIContentPart, ae as OpenAIEmbeddingInput, af as OpenAIEmbeddingItem, f as OpenAIEmbeddingRequest, g as OpenAIEmbeddingResponse, ag as OpenAIFunctionDefinition, ah as OpenAIImageUrlContentPart, h as OpenAIListModelsResponse, ai as OpenAIMessage, i as OpenAIModelItem, aj as OpenAIReasoningEffort, ak as OpenAIResponseFormat, al as OpenAIResponsesCompletedEvent, am as OpenAIResponsesContentPartAddedEvent, an as OpenAIResponsesContentPartDoneEvent, ao as OpenAIResponsesCreatedEvent, ap as OpenAIResponsesError, aq as OpenAIResponsesEventBase, ar as OpenAIResponsesFailedEvent, as as OpenAIResponsesFunctionCallArgumentsDeltaEvent, at as OpenAIResponsesFunctionCallArgumentsDoneEvent, au as OpenAIResponsesInProgressEvent, av as OpenAIResponsesIncompleteDetails, aw as OpenAIResponsesIncompleteEvent, ax as OpenAIResponsesOutputContent, ay as OpenAIResponsesOutputFunctionCall, az as OpenAIResponsesOutputItem, aA as OpenAIResponsesOutputItemAddedEvent, aB as OpenAIResponsesOutputItemDoneEvent, aC as OpenAIResponsesOutputMessage, aD as OpenAIResponsesOutputReasoning, aE as OpenAIResponsesOutputRefusal, aF as OpenAIResponsesOutputTextContent, aG as OpenAIResponsesOutputTextDeltaEvent, aH as OpenAIResponsesOutputTextDoneEvent, aI as OpenAIResponsesQueuedEvent, aJ as OpenAIResponsesReasoningSummaryPartAddedEvent, aK as OpenAIResponsesReasoningSummaryPartDoneEvent, aL as OpenAIResponsesReasoningSummaryTextDeltaEvent, aM as OpenAIResponsesReasoningSummaryTextDoneEvent, aN as OpenAIResponsesReasoningTextDeltaEvent, aO as OpenAIResponsesReasoningTextDoneEvent, aP as OpenAIResponsesRefusalDeltaEvent, aQ as OpenAIResponsesRefusalDoneEvent, j as OpenAIResponsesRequest, k as OpenAIResponsesResponse, aR as OpenAIResponsesStatus, aS as OpenAIResponsesStream, aT as OpenAIResponsesStreamError, l as OpenAIResponsesStreamEvent, aU as OpenAIResponsesUsage, aV as OpenAIStreamOptions, aW as OpenAITextContentPart, aX as OpenAITool, aY as OpenAIToolCall, aZ as OpenAIToolCallDelta, a_ as OpenAIUsage, a$ as SystemOneConfidence, b0 as SystemOneProbabilities } from './anthropic-DuE8M6Tz.cjs';
8
- import { T as ToolHandler, a as Tool, A as AnyTool, b as ToolRegistry, c as ToolExecutionResult } from './compose-lt-Qz3lI.cjs';
9
- export { d as AppliedSkillResult, e as ApplySkillOptions, P as ParsedFrontmatter, S as Skill, f as SkillFrontmatter, g as SkillSummary, h as ToolExecutionContext, i as ToolExecutionFailure, j as ToolExecutionSuccess, k as ToolRegistryOptions, l as applySkill, p as parseFrontmatter } from './compose-lt-Qz3lI.cjs';
6
+ import { R as RequestRunner, S as SystemOneChoiceAnswer, o as SystemOneNoulAnswer, p as SystemOneScoreAnswer, q as SystemOneChoiceQuestion, r as SystemOneNoulQuestion, s as SystemOneScoreQuestion, t as SystemOneRequest$1, u as SystemOneResponse$1, v as SystemOneContent, N as NativeApi, w as OpenAICompatClient, x as AnthropicCompatClient } from './anthropic-Did0lQxP.cjs';
7
+ export { y as AnthropicCacheControl, z as AnthropicContentBlock, B as AnthropicImageContentBlock, C as AnthropicMessage, n as AnthropicMessageStreamEvent, A as AnthropicMessagesRequest, m as AnthropicMessagesResponse, D as AnthropicMessagesStream, E as AnthropicMetadata, F as AnthropicOutputConfig, G as AnthropicRedactedThinkingContentBlock, H as AnthropicSystem, I as AnthropicSystemTextBlock, J as AnthropicTextContentBlock, K as AnthropicThinkingConfig, L as AnthropicThinkingContentBlock, M as AnthropicTool, P as AnthropicToolChoice, Q as AnthropicToolResultContentBlock, T as AnthropicToolUseContentBlock, U as AnthropicUnknownStreamEvent, V as OllamaAnthropicContentBlock, W as OllamaAnthropicMessage, X as OllamaAnthropicMessagesRequest, Y as OllamaAnthropicSystem, Z as OllamaAnthropicSystemTextBlock, _ as OllamaAnthropicTextContentBlock, $ as OllamaAnthropicThinkingConfig, a0 as OllamaAnthropicTool, a1 as OllamaAnthropicToolResultContentBlock, a2 as OllamaOpenAIChatCompletionRequest, a3 as OllamaOpenAICompletionRequest, a4 as OllamaOpenAIEmbeddingRequest, a5 as OllamaOpenAIReasoningEffort, a6 as OllamaOpenAIResponsesRequest, a7 as OpenAIChatCompletionChoice, b as OpenAIChatCompletionChunk, a8 as OpenAIChatCompletionChunkChoice, a9 as OpenAIChatCompletionDelta, O as OpenAIChatCompletionRequest, a as OpenAIChatCompletionResponse, aa as OpenAIChatCompletionStream, ab as OpenAICompletionChoice, e as OpenAICompletionChunk, c as OpenAICompletionRequest, d as OpenAICompletionResponse, ac as OpenAICompletionStream, ad as OpenAIContentPart, ae as OpenAIEmbeddingInput, af as OpenAIEmbeddingItem, f as OpenAIEmbeddingRequest, g as OpenAIEmbeddingResponse, ag as OpenAIFunctionDefinition, ah as OpenAIImageUrlContentPart, h as OpenAIListModelsResponse, ai as OpenAIMessage, i as OpenAIModelItem, aj as OpenAIReasoningEffort, ak as OpenAIResponseFormat, al as OpenAIResponsesCompletedEvent, am as OpenAIResponsesContentPartAddedEvent, an as OpenAIResponsesContentPartDoneEvent, ao as OpenAIResponsesCreatedEvent, ap as OpenAIResponsesError, aq as OpenAIResponsesEventBase, ar as OpenAIResponsesFailedEvent, as as OpenAIResponsesFunctionCallArgumentsDeltaEvent, at as OpenAIResponsesFunctionCallArgumentsDoneEvent, au as OpenAIResponsesInProgressEvent, av as OpenAIResponsesIncompleteDetails, aw as OpenAIResponsesIncompleteEvent, ax as OpenAIResponsesOutputContent, ay as OpenAIResponsesOutputFunctionCall, az as OpenAIResponsesOutputItem, aA as OpenAIResponsesOutputItemAddedEvent, aB as OpenAIResponsesOutputItemDoneEvent, aC as OpenAIResponsesOutputMessage, aD as OpenAIResponsesOutputReasoning, aE as OpenAIResponsesOutputRefusal, aF as OpenAIResponsesOutputTextContent, aG as OpenAIResponsesOutputTextDeltaEvent, aH as OpenAIResponsesOutputTextDoneEvent, aI as OpenAIResponsesQueuedEvent, aJ as OpenAIResponsesReasoningSummaryPartAddedEvent, aK as OpenAIResponsesReasoningSummaryPartDoneEvent, aL as OpenAIResponsesReasoningSummaryTextDeltaEvent, aM as OpenAIResponsesReasoningSummaryTextDoneEvent, aN as OpenAIResponsesReasoningTextDeltaEvent, aO as OpenAIResponsesReasoningTextDoneEvent, aP as OpenAIResponsesRefusalDeltaEvent, aQ as OpenAIResponsesRefusalDoneEvent, j as OpenAIResponsesRequest, k as OpenAIResponsesResponse, aR as OpenAIResponsesStatus, aS as OpenAIResponsesStream, aT as OpenAIResponsesStreamError, l as OpenAIResponsesStreamEvent, aU as OpenAIResponsesUsage, aV as OpenAIStreamOptions, aW as OpenAITextContentPart, aX as OpenAITool, aY as OpenAIToolCall, aZ as OpenAIToolCallDelta, a_ as OpenAIUsage, a$ as SystemOneConfidence, b0 as SystemOneProbabilities } from './anthropic-Did0lQxP.cjs';
8
+ import { T as ToolHandler, a as Tool, A as AnyTool, b as ToolRegistry, c as ToolExecutionResult } from './compose-DhvoUrWa.cjs';
9
+ export { d as AppliedSkillResult, e as ApplySkillOptions, P as ParsedFrontmatter, S as Skill, f as SkillFrontmatter, g as SkillSummary, h as ToolExecutionContext, i as ToolExecutionFailure, j as ToolExecutionSuccess, k as ToolRegistryOptions, l as applySkill, p as parseFrontmatter } from './compose-DhvoUrWa.cjs';
10
10
  import { M as McpClientLike, a as McpToolDescriptor, b as McpTask, c as McpCallToolResult } from './types-CI-gM2u5.cjs';
11
11
  export { d as McpContentBlock, e as McpCreateTaskResult, f as McpElicitationFormRequest, g as McpElicitationHandlers, h as McpElicitationRequest, i as McpElicitationResult, j as McpElicitationUrlRequest, k as McpIcon, l as McpInputRequiredResult, m as McpListToolsParams, n as McpListToolsResult, o as McpRequestOptions, p as McpServerCapabilities, q as McpTaskMethod, r as McpTaskRequest, s as McpTaskStatus, t as McpToolAnnotations, u as McpToolCallResult, v as McpToolExecution } from './types-CI-gM2u5.cjs';
12
12
  import * as Otel from '@opentelemetry/api';
@@ -122,6 +122,220 @@ interface UsageResponse {
122
122
  readonly buckets: readonly UsageBucket[];
123
123
  }
124
124
 
125
+ /**
126
+ * Model capability detection and runtime environment inference.
127
+ */
128
+
129
+ type RuntimeMode = 'local' | 'cloud' | 'unknown';
130
+ /**
131
+ * Best-effort classification of a model's parallel tool-call emission
132
+ * behavior. Used by agent loops that need to know whether the model
133
+ * is likely to emit multiple `tool_calls` in a single assistant
134
+ * message (parallel) or one-at-a-time (sequential).
135
+ *
136
+ * This is a **heuristic** derived from runtime mode + model family
137
+ * pattern matching against `/api/show`'s reported model_info. The
138
+ * SDK does NOT normalize between cloud and local — it reports the
139
+ * best assessment it can and lets the caller decide how to act.
140
+ *
141
+ * See ADR 0023 for the rationale and the recommended caller pattern.
142
+ */
143
+ type ParallelToolCallBehavior = 'yes' | 'no' | 'unknown';
144
+ interface ModelCapabilities {
145
+ readonly model: string;
146
+ readonly reported: readonly string[];
147
+ readonly supportsTools: boolean;
148
+ readonly supportsVision: boolean;
149
+ readonly supportsEmbedding: boolean;
150
+ readonly supportsCompletion: boolean;
151
+ readonly supportsThinking: boolean;
152
+ /** Model-defined thinking values and default, when /api/show reports them. */
153
+ readonly thinking?: ThinkingMetadata | undefined;
154
+ readonly supportsStreaming: true;
155
+ /** Maximum model context length reported by /api/show model_info, when available. */
156
+ readonly contextLength?: number | undefined;
157
+ /**
158
+ * Best-effort inference, not a guarantee: Ollama's `/api/show` does not report structured
159
+ * output support as a queryable capability, so this is inferred from {@link inferRuntimeMode}
160
+ * — `false` for `cloud`, since Ollama Cloud does not currently support structured outputs;
161
+ * `true` for `local`/`unknown`. A locally-hosted model that genuinely can't follow a JSON
162
+ * schema will still report `true` here; the request itself is the only reliable way to find
163
+ * out. `OllamaClient.chat`/`generate` apply this same inference as a fail-fast pre-flight
164
+ * guard whenever `format` is set, throwing `OllamaUnsupportedCapabilityError` rather than
165
+ * making a network call that Ollama Cloud is known to reject.
166
+ */
167
+ readonly supportsStructuredOutputRequest: boolean;
168
+ /**
169
+ * Best-effort inference of whether the model is likely to emit
170
+ * **multiple `tool_calls` in a single assistant message** (parallel)
171
+ * vs **one tool call per assistant turn** (sequential).
172
+ *
173
+ * The SDK does NOT normalize between cloud and local — it reports
174
+ * the best assessment it can and lets the caller decide. Agent
175
+ * authors who need hard sequential execution should set
176
+ * `ToolRegistry`'s `maxConcurrency: 1` regardless of this field's
177
+ * value, since the model's behavior is not deterministic across
178
+ * prompts.
179
+ *
180
+ * Inference matrix (see ADR 0023):
181
+ *
182
+ * - **`'yes'`** — the model is known to emit parallel tool calls:
183
+ * - Cloud-mode endpoints (the OpenAI/Anthropic compat bridges
184
+ * proxy to proprietary models like GPT-4o, Claude, which
185
+ * freely emit parallel tool calls).
186
+ * - Local models from families that ship with parallel-tool-call
187
+ * training (Qwen 2.5+, Llama 3.1+ tool-use variants, Mistral
188
+ * function-calling fine-tunes, Hermes 2/3, Command R+).
189
+ *
190
+ * - **`'no'`** — the model is known to emit at most one tool call
191
+ * per assistant turn. Currently no model family is classified
192
+ * as `'no'` by default; the field is reserved for future
193
+ * narrowing as more is learned about specific models.
194
+ *
195
+ * - **`'unknown'`** — the model family is not in the known-yes or
196
+ * known-no list, OR `/api/show` didn't return enough
197
+ * information to make an assessment. Callers should treat
198
+ * `'unknown'` as "ask the model and see what it does" — the
199
+ * `ToolRegistry` will execute whatever calls the model emits,
200
+ * in parallel via `Promise.all` by default.
201
+ *
202
+ * See: `src/tools/registry.ts` for `maxConcurrency` enforcement,
203
+ * and ADR 0023 for the full design rationale.
204
+ */
205
+ readonly parallelToolCalls: ParallelToolCallBehavior;
206
+ }
207
+ declare function inferRuntimeMode(baseUrl: string): RuntimeMode;
208
+ /**
209
+ * Scans `/api/show`'s `model_info` for the model's **native** context length
210
+ * (keys are architecture-prefixed: `llama.context_length`,
211
+ * `gemma4.context_length`, …). Also re-exported by `src/context-discovery.ts`
212
+ * as `extractNativeContextLength` for the context-window discovery flow.
213
+ */
214
+ declare function extractContextLength(modelInfo?: Record<string, unknown>): number | undefined;
215
+ declare function detectModelCapabilities(http: HttpClient, model: string, signal?: AbortSignal): Promise<ModelCapabilities>;
216
+ declare function listAvailableModels(http: HttpClient): Promise<ModelResponse[]>;
217
+
218
+ /**
219
+ * Dynamic context-window discovery.
220
+ *
221
+ * Guessing context windows ("it's probably 2048… or 4096?") is how silent
222
+ * truncation happens. Ollama advertises the real numbers in two places, and
223
+ * they answer *different questions*:
224
+ *
225
+ * - **`GET /api/ps`** reports `context_length` for every currently-running
226
+ * model — the window the runner has **actually allocated**. This is the
227
+ * exact, server-enforced number: it already reflects the model's Modelfile
228
+ * `num_ctx` default (if any) and the memory the runner was able to claim.
229
+ * Only meaningful while the model is loaded.
230
+ * - **`POST /api/show`** exposes two deeper signals in its response:
231
+ * - `model_info["<architecture>.context_length"]` — the model's **native**
232
+ * maximum from its GGUF metadata (e.g. `gemma4.context_length: 131072`).
233
+ * This is the ceiling you could raise `num_ctx` to, not the window you
234
+ * have. The official API examples show a model whose native max is
235
+ * 131072 while its running instance allocated 4096 — a 32× gap.
236
+ * - `parameters` — the Modelfile's serialized option list; a
237
+ * `num_ctx <n>` line (when the model's author set one) is the default
238
+ * window the runner will allocate for this model.
239
+ *
240
+ * `OllamaClient.models.getContextLength()` consults them in precedence order
241
+ * (see {@link resolveContextLength}) and returns every signal it found, so
242
+ * callers can both act on the effective window *now* and know how much
243
+ * headroom exists for raising it.
244
+ */
245
+
246
+ /** Request shape for {@link ModelsClient.getContextLength}. */
247
+ interface ContextDiscoveryRequestOptions extends RequestCancellationOptions {
248
+ /** Model to introspect (tag optional — `"llama3.1"` matches `"llama3.1:latest"`). */
249
+ readonly model: string;
250
+ /**
251
+ * Skip the `GET /api/ps` lookup — e.g. on cloud endpoints where the local
252
+ * running-models listing isn't meaningful, or when the extra round-trip
253
+ * isn't wanted. Discovery then falls back to `/api/show` signals only.
254
+ */
255
+ readonly skipRunningCheck?: boolean | undefined;
256
+ }
257
+ /** Where a discovered context window came from, in precedence order. */
258
+ type ContextLengthSource =
259
+ /** `GET /api/ps` — window actually allocated by the running instance (exact). */
260
+ 'running'
261
+ /** `POST /api/show` `parameters` — Modelfile-authored `num_ctx` default. */
262
+ | 'parameters'
263
+ /** `POST /api/show` `model_info["*.context_length"]` — native GGUF maximum. */
264
+ | 'model-info'
265
+ /** Nothing discoverable — Ollama's conservative unset-`num_ctx` default (2048). */
266
+ | 'fallback';
267
+ /** Result of {@link ModelsClient.getContextLength}. */
268
+ interface DiscoveredContextLength {
269
+ /**
270
+ * The effective context window to program against, resolved by precedence:
271
+ * `running` (exact, when loaded) → `parameters` (Modelfile default) →
272
+ * `model-info` (native max) → `2048` fallback.
273
+ */
274
+ readonly contextLength: number;
275
+ /** Which source produced {@link contextLength}. */
276
+ readonly source: ContextLengthSource;
277
+ /** Window allocated by the running instance, when the model is loaded and reports it. */
278
+ readonly runningContextLength?: number | undefined;
279
+ /** Modelfile `num_ctx` default from `/api/show`'s `parameters` string, when set. */
280
+ readonly parameterContextLength?: number | undefined;
281
+ /** Native GGUF maximum from `model_info["*.context_length"]`, when reported. */
282
+ readonly nativeContextLength?: number | undefined;
283
+ }
284
+ /**
285
+ * Parses `num_ctx <n>` out of `/api/show`'s `parameters` string.
286
+ *
287
+ * The string is the Modelfile's serialized option list — one `name value`
288
+ * pair per line, value separated by whitespace padding, e.g.
289
+ * `"num_stop 8192\nnum_ctx 4096"`. Returns `undefined` when no positive
290
+ * integer `num_ctx` line is present.
291
+ */
292
+ declare function extractParameterNumCtx(parameters?: string | undefined): number | undefined;
293
+ /**
294
+ * Whether `model` is currently resident (loaded in VRAM) per a `GET /api/ps`
295
+ * response — exact or tag-less-prefix match, same resolution rules as Ollama
296
+ * itself applies to model names. See {@link matchesModelReference}.
297
+ */
298
+ declare function isModelResident(psModels: readonly {
299
+ readonly name?: string | undefined;
300
+ readonly model?: string | undefined;
301
+ }[], model: string): boolean;
302
+ /**
303
+ * Finds the context window Ollama actually allocated for `model`, from a
304
+ * `GET /api/ps` response. Matching mirrors Ollama's own model resolution:
305
+ * exact match on the entry's `name`/`model`, or — when `model` carries no
306
+ * tag — any entry whose name starts with `<model>:`.
307
+ *
308
+ * Returns `undefined` when the model isn't loaded or the running entry
309
+ * doesn't report `context_length` (older server versions).
310
+ */
311
+ declare function findRunningModelContextLength(psModels: readonly ModelResponse[], model: string): number | undefined;
312
+ /** Internal inputs to {@link resolveContextLength}; each signal independently optional. */
313
+ interface ContextLengthSignals {
314
+ readonly running?: number | undefined;
315
+ readonly parameter?: number | undefined;
316
+ readonly native?: number | undefined;
317
+ }
318
+ /**
319
+ * Resolves the effective context window from whatever signals were
320
+ * discovered, by precedence:
321
+ *
322
+ * 1. **`running`** — the allocated window of the loaded instance. When the
323
+ * model is in memory this is the ground truth; it already incorporates
324
+ * any Modelfile `num_ctx` default.
325
+ * 2. **`parameter`** — the Modelfile's `num_ctx`, i.e. the window the
326
+ * runner *will* allocate for this model on the next cold load.
327
+ * 3. **`native`** — the GGUF maximum. Only used when nothing tighter is
328
+ * known: a fresh `num_ctx`-less load may allocate far less than native
329
+ * (Ollama's conservative default), so native is a *ceiling*, not an
330
+ * expectation.
331
+ * 4. **Fallback** — {@link OLLAMA_FALLBACK_CONTEXT_LENGTH} (2048), the
332
+ * conservative server-side default, so the result is always actionable.
333
+ *
334
+ * Pure — used by `ModelsClient.getContextLength()` and exported for callers
335
+ * that already hold `/api/ps` / `/api/show` responses.
336
+ */
337
+ declare function resolveContextLength(signals: ContextLengthSignals): DiscoveredContextLength;
338
+
125
339
  /**
126
340
  * Exponential backoff with full jitter calculation.
127
341
  */
@@ -152,9 +366,133 @@ declare const DEFAULT_RETRY_CONFIG: RetryConfig;
152
366
  */
153
367
  declare function withRetry<T>(operation: (attempt: number) => Promise<T>, config?: RetryConfig, signal?: AbortSignal): Promise<T>;
154
368
 
369
+ /**
370
+ * Multi-host model-affinity routing — prefer the endpoint already holding the
371
+ * requested model in VRAM.
372
+ *
373
+ * Ollama daemons keep a bounded set of models resident (`OLLAMA_MAX_LOADED_MODELS`).
374
+ * With several interchangeable endpoints serving the same models, sending a request
375
+ * to a host that doesn't currently hold the model forces an unload/cold-load swap
376
+ * on that host — the thrashing `ModelAffinityScheduler` avoids for a *single*
377
+ * client's task queues. This router brings the same affinity to the *multi-host*
378
+ * request path: given the candidate endpoints `executeWithFailover` would try
379
+ * anyway, it reorders them so hosts whose `GET /api/ps` shows the requested model
380
+ * resident come first.
381
+ *
382
+ * Design constraints (see ADR 0028):
383
+ *
384
+ * - **Zero added request latency.** Reordering is synchronous and consults only
385
+ * cached `/api/ps` snapshots. A request never waits for a probe: when a
386
+ * snapshot is missing or stale, the request proceeds with the unchanged
387
+ * candidate order and the refresh happens in the background, warming the cache
388
+ * for subsequent requests.
389
+ * - **Race-free with `least-connections`.** The reorder runs entirely between the
390
+ * registry's synchronous `candidates()`/`filterWithCapacity()` and the
391
+ * endpoint's `acquire()` — no `await` in between — so the strategy's
392
+ * no-two-calls-pick-the-same-idle-endpoint guarantee is untouched. Affinity
393
+ * only reorders *within* the runnable set; priority tiers and per-strategy
394
+ * ordering stay intact inside the resident/non-resident groups.
395
+ * - **Best-effort, never blocking, never failing a request.** A probe failure is
396
+ * recorded with a short backoff (`failureRetryMs`) and never cached as data;
397
+ * the affected endpoint is simply treated as unknown until a probe succeeds.
398
+ * - **Fresh-data-only decisions.** Only snapshots within `ttlMs` reorder
399
+ * requests — `/api/ps` can change at any moment (evictions, other clients),
400
+ * so stale data is treated as no data. Last-known snapshots are still exposed
401
+ * via `status()` for observability.
402
+ */
403
+
404
+ /**
405
+ * One `/api/ps` running-model entry, as the router consumes it — name/model only.
406
+ * Optional to match the generated `Ps` wire shape; hand-written `ModelResponse`
407
+ * values (required fields) are assignable as-is.
408
+ */
409
+ interface AffinityRunningModel {
410
+ readonly name?: string | undefined;
411
+ readonly model?: string | undefined;
412
+ }
413
+ /**
414
+ * Options for dynamic model-affinity routing across endpoints. Configured under
415
+ * `OllamaClientConfig.endpointHealth.modelAffinity`; implemented by
416
+ * `OllamaClient`'s request path (`executeWithFailover`), which has HTTP access —
417
+ * the `EndpointRegistry` itself ignores this key.
418
+ */
419
+ interface ModelAffinityRoutingOptions {
420
+ /**
421
+ * How long a `GET /api/ps` snapshot stays fresh enough to reorder requests
422
+ * (default 30_000ms). Mirrors `ModelAffinityScheduler`'s `psCacheTtlMs`.
423
+ */
424
+ readonly ttlMs?: number | undefined;
425
+ /**
426
+ * How long a failed `/api/ps` probe suppresses retries against that endpoint
427
+ * (default 5_000ms) — a dead host isn't probed on every request while it's
428
+ * down, and comes back automatically once it recovers. Failures are never
429
+ * cached as data.
430
+ */
431
+ readonly failureRetryMs?: number | undefined;
432
+ }
433
+ /** Introspection snapshot for one endpoint's affinity cache. */
434
+ interface ModelAffinitySnapshot {
435
+ readonly endpointName: string;
436
+ readonly baseUrl: string;
437
+ /** Model names reported resident by the last successful probe (may be stale). */
438
+ readonly loadedModels: readonly string[];
439
+ /** When the last successful probe completed — `undefined` if it never succeeded. */
440
+ readonly fetchedAt: number | undefined;
441
+ /** Whether the snapshot is within `ttlMs` and therefore still drives reordering. */
442
+ readonly fresh: boolean;
443
+ }
444
+ /** Test seam: injectable clock — defaults to `Date.now`. */
445
+ type NowFn = () => number;
446
+ /**
447
+ * Cache-and-reorder engine for multi-host model affinity. See the module docs for
448
+ * the design constraints; `OllamaClient` constructs one (only when
449
+ * `endpointHealth.modelAffinity` is set) and calls {@link reorder} synchronously
450
+ * inside its request path.
451
+ */
452
+ declare class ModelAffinityRouter {
453
+ private readonly fetchPs;
454
+ private readonly ttlMs;
455
+ private readonly failureRetryMs;
456
+ private readonly now;
457
+ private readonly cache;
458
+ private readonly failedAt;
459
+ private readonly inflight;
460
+ constructor(fetchPs: (endpoint: OllamaEndpoint) => Promise<readonly AffinityRunningModel[]>, options?: ModelAffinityRoutingOptions & {
461
+ readonly now?: NowFn;
462
+ });
463
+ /**
464
+ * Synchronously reorders `candidates` so endpoints whose fresh `/api/ps`
465
+ * snapshot shows `model` resident come first (relative order preserved within
466
+ * each group). Returns the input unchanged — and kicks a background refresh —
467
+ * when any candidate's snapshot is missing or stale: a wrong reorder is worse
468
+ * than no reorder, and the first request after a cache miss simply isn't
469
+ * reordered. No-op without a `model` or with fewer than two candidates.
470
+ */
471
+ reorder(candidates: readonly OllamaEndpoint[], model: string | undefined): readonly OllamaEndpoint[];
472
+ /**
473
+ * Forces a refresh of the affinity cache for the given endpoints (deduped with
474
+ * any in-flight refresh; bypasses the failure backoff, because this is an
475
+ * explicit caller request — `client.warmModelAffinity()`). Resolves once every
476
+ * probe has settled — successfully or not; individual failures never reject.
477
+ */
478
+ warm(endpoints: readonly OllamaEndpoint[]): Promise<void>;
479
+ /** Introspection snapshots for the given endpoints (defaults to cached ones). */
480
+ status(endpoints?: readonly OllamaEndpoint[]): readonly ModelAffinitySnapshot[];
481
+ /**
482
+ * Starts (or joins) a background refresh for `endpoint`, unless one is already
483
+ * in flight or the failure backoff suppresses it. Fire-and-forget: the promise
484
+ * is stored only for dedupe/warm-joins; callers never await it on the request
485
+ * path.
486
+ */
487
+ private kickRefresh;
488
+ /** Refreshes one endpoint's snapshot; never rejects (failures record a backoff). */
489
+ private refresh;
490
+ }
491
+
155
492
  /**
156
493
  * Multi-endpoint registry with priority routing and circuit breaker failover.
157
494
  */
495
+
158
496
  interface OllamaEndpoint {
159
497
  readonly name: string;
160
498
  readonly baseUrl: string;
@@ -222,6 +560,18 @@ interface EndpointRegistryOptions {
222
560
  * any other in-flight request — it never blocks indefinitely on its own.
223
561
  */
224
562
  readonly maxConcurrentPerEndpoint?: number | undefined;
563
+ /**
564
+ * Dynamic model-affinity routing across endpoints: reorder each request's
565
+ * candidates so endpoints whose `GET /api/ps` shows the requested model already
566
+ * resident in VRAM are tried first — avoiding unload/cold-load swaps on
567
+ * `OLLAMA_MAX_LOADED_MODELS`-bounded daemons. Off unless set.
568
+ *
569
+ * Implemented by `OllamaClient`'s request path (`executeWithFailover` + a
570
+ * TTL-cached `ModelAffinityRouter`), not by this registry — the registry is a
571
+ * pure, HTTP-free data structure and simply ignores this key. See
572
+ * `src/providers/model-affinity-router.ts` and ADR 0028.
573
+ */
574
+ readonly modelAffinity?: ModelAffinityRoutingOptions | undefined;
225
575
  }
226
576
  declare class EndpointRegistry {
227
577
  private readonly endpoints;
@@ -445,92 +795,6 @@ interface EndpointHealthCheckResult {
445
795
  }
446
796
  declare function checkEndpointHealth(endpoint: OllamaEndpoint, fetchImpl?: FetchLike, timeoutMs?: number, middleware?: readonly Middleware[] | undefined, onLifecycleEvent?: RequestLifecycleHook | undefined): Promise<EndpointHealthCheckResult>;
447
797
 
448
- /**
449
- * Model capability detection and runtime environment inference.
450
- */
451
-
452
- type RuntimeMode = 'local' | 'cloud' | 'unknown';
453
- /**
454
- * Best-effort classification of a model's parallel tool-call emission
455
- * behavior. Used by agent loops that need to know whether the model
456
- * is likely to emit multiple `tool_calls` in a single assistant
457
- * message (parallel) or one-at-a-time (sequential).
458
- *
459
- * This is a **heuristic** derived from runtime mode + model family
460
- * pattern matching against `/api/show`'s reported model_info. The
461
- * SDK does NOT normalize between cloud and local — it reports the
462
- * best assessment it can and lets the caller decide how to act.
463
- *
464
- * See ADR 0023 for the rationale and the recommended caller pattern.
465
- */
466
- type ParallelToolCallBehavior = 'yes' | 'no' | 'unknown';
467
- interface ModelCapabilities {
468
- readonly model: string;
469
- readonly reported: readonly string[];
470
- readonly supportsTools: boolean;
471
- readonly supportsVision: boolean;
472
- readonly supportsEmbedding: boolean;
473
- readonly supportsCompletion: boolean;
474
- readonly supportsThinking: boolean;
475
- /** Model-defined thinking values and default, when /api/show reports them. */
476
- readonly thinking?: ThinkingMetadata | undefined;
477
- readonly supportsStreaming: true;
478
- /** Maximum model context length reported by /api/show model_info, when available. */
479
- readonly contextLength?: number | undefined;
480
- /**
481
- * Best-effort inference, not a guarantee: Ollama's `/api/show` does not report structured
482
- * output support as a queryable capability, so this is inferred from {@link inferRuntimeMode}
483
- * — `false` for `cloud`, since Ollama Cloud does not currently support structured outputs;
484
- * `true` for `local`/`unknown`. A locally-hosted model that genuinely can't follow a JSON
485
- * schema will still report `true` here; the request itself is the only reliable way to find
486
- * out. `OllamaClient.chat`/`generate` apply this same inference as a fail-fast pre-flight
487
- * guard whenever `format` is set, throwing `OllamaUnsupportedCapabilityError` rather than
488
- * making a network call that Ollama Cloud is known to reject.
489
- */
490
- readonly supportsStructuredOutputRequest: boolean;
491
- /**
492
- * Best-effort inference of whether the model is likely to emit
493
- * **multiple `tool_calls` in a single assistant message** (parallel)
494
- * vs **one tool call per assistant turn** (sequential).
495
- *
496
- * The SDK does NOT normalize between cloud and local — it reports
497
- * the best assessment it can and lets the caller decide. Agent
498
- * authors who need hard sequential execution should set
499
- * `ToolRegistry`'s `maxConcurrency: 1` regardless of this field's
500
- * value, since the model's behavior is not deterministic across
501
- * prompts.
502
- *
503
- * Inference matrix (see ADR 0023):
504
- *
505
- * - **`'yes'`** — the model is known to emit parallel tool calls:
506
- * - Cloud-mode endpoints (the OpenAI/Anthropic compat bridges
507
- * proxy to proprietary models like GPT-4o, Claude, which
508
- * freely emit parallel tool calls).
509
- * - Local models from families that ship with parallel-tool-call
510
- * training (Qwen 2.5+, Llama 3.1+ tool-use variants, Mistral
511
- * function-calling fine-tunes, Hermes 2/3, Command R+).
512
- *
513
- * - **`'no'`** — the model is known to emit at most one tool call
514
- * per assistant turn. Currently no model family is classified
515
- * as `'no'` by default; the field is reserved for future
516
- * narrowing as more is learned about specific models.
517
- *
518
- * - **`'unknown'`** — the model family is not in the known-yes or
519
- * known-no list, OR `/api/show` didn't return enough
520
- * information to make an assessment. Callers should treat
521
- * `'unknown'` as "ask the model and see what it does" — the
522
- * `ToolRegistry` will execute whatever calls the model emits,
523
- * in parallel via `Promise.all` by default.
524
- *
525
- * See: `src/tools/registry.ts` for `maxConcurrency` enforcement,
526
- * and ADR 0023 for the full design rationale.
527
- */
528
- readonly parallelToolCalls: ParallelToolCallBehavior;
529
- }
530
- declare function inferRuntimeMode(baseUrl: string): RuntimeMode;
531
- declare function detectModelCapabilities(http: HttpClient, model: string, signal?: AbortSignal): Promise<ModelCapabilities>;
532
- declare function listAvailableModels(http: HttpClient): Promise<ModelResponse[]>;
533
-
534
798
  /**
535
799
  * Dual-mode (AsyncIterator and EventEmitter-like) stream wrapper.
536
800
  */
@@ -606,7 +870,39 @@ declare class ModelsClient {
606
870
  create(request: CreateRequestOptions): Promise<ProgressResponse | OllamaStream<ProgressResponse, ProgressStreamResult>>;
607
871
  delete(request: DeleteRequestOptions): Promise<StatusResponse>;
608
872
  copy(request: CopyRequestOptions): Promise<StatusResponse>;
609
- ps(): Promise<PsResponse>;
873
+ ps(request?: RequestCancellationOptions): Promise<PsResponse>;
874
+ /**
875
+ * Discovers the model's real context window from the server — no more
876
+ * 2048/4096 guesswork. Consults, in precedence order:
877
+ *
878
+ * 1. `GET /api/ps` — the window the **running instance actually
879
+ * allocated** (exact, reflects the Modelfile `num_ctx` default and
880
+ * what fit in memory); skipped when the model isn't loaded, when
881
+ * `skipRunningCheck` is set, or harmlessly on servers without the
882
+ * field.
883
+ * 2. `POST /api/show` `parameters` — a Modelfile-authored
884
+ * `num_ctx <n>` default, i.e. the window a cold load will allocate.
885
+ * 3. `POST /api/show` `model_info["<arch>.context_length"]` — the
886
+ * model's **native GGUF maximum** (a ceiling to raise `num_ctx`
887
+ * toward, not the allocated window).
888
+ * 4. Fallback — Ollama's conservative unset-`num_ctx` default (2048).
889
+ *
890
+ * The result carries every signal it found, so `nativeContextLength:
891
+ * 131072` vs `contextLength: 4096` (a 32× gap seen in the official API
892
+ * examples) is visible rather than guessed. Typical wiring:
893
+ *
894
+ * ```ts
895
+ * const { contextLength, source, nativeContextLength } =
896
+ * await client.models.getContextLength({ model: 'gemma4' });
897
+ * // -> { contextLength: 4096, source: 'running', nativeContextLength: 131072 }
898
+ * ```
899
+ *
900
+ * Makes two server calls (`/api/ps` + `/api/show`) unless
901
+ * `skipRunningCheck` is set. A 404 from `/api/ps` (endpoint without the
902
+ * listing) is treated as "not running" and never fails the lookup;
903
+ * other transport errors propagate normally.
904
+ */
905
+ getContextLength(request: ContextDiscoveryRequestOptions): Promise<DiscoveredContextLength>;
610
906
  version(): Promise<VersionResponse>;
611
907
  /**
612
908
  * Immediately unloads `model` from VRAM/RAM by issuing an empty
@@ -904,6 +1200,147 @@ interface SessionTurn {
904
1200
  /** The full underlying `/api/chat` response (usage, durations, thinking, …). */
905
1201
  readonly response: ChatResponse;
906
1202
  }
1203
+ /** Options for {@link compactConversationHistory} / {@link ConversationSession.compact}. */
1204
+ interface CompactionOptions {
1205
+ /**
1206
+ * Estimated-token budget the compacted history must fit within (including
1207
+ * any retained system message and the 10%-style reply reservation below).
1208
+ * Estimated with the same CJK-aware heuristic as the pre-flight checks
1209
+ * (`estimateMessageTokens` — ±20–30%, message overhead + tool calls +
1210
+ * `IMAGE_TOKEN_ESTIMATE` per image included).
1211
+ */
1212
+ readonly maxEstimatedTokens: number;
1213
+ /**
1214
+ * Tokens subtracted from the budget and reserved for the model's next
1215
+ * reply (default `0` for the pure function; `session.compact()` defaults
1216
+ * to ~10% of the window, mirroring the pre-flight warning threshold's
1217
+ * headroom).
1218
+ */
1219
+ readonly reserveForReply?: number | undefined;
1220
+ /**
1221
+ * Trailing messages always retained, even when they don't fit the budget
1222
+ * (default `2` — the latest user/assistant exchange). Guarantees a
1223
+ * compacted conversation never loses its most recent context; an
1224
+ * oversized tail is retained deliberately rather than truncated, because
1225
+ * silent truncation is the server's failure mode, not one this SDK
1226
+ * reproduces.
1227
+ */
1228
+ readonly minTailMessages?: number | undefined;
1229
+ }
1230
+ /**
1231
+ * Sliding-window history compaction: keep the pinned system message(s) plus
1232
+ * the most recent turns that fit `maxEstimatedTokens`, dropping the oldest
1233
+ * turns in between.
1234
+ *
1235
+ * Deliberately **not** automatic anywhere in the SDK: compaction rewrites the
1236
+ * prompt prefix, so the next request's KV-cache hit rate drops to zero and
1237
+ * the whole retained suffix re-evaluates. That tradeoff (latency + recompute
1238
+ * for a window that now fits) is sometimes exactly right — long-running
1239
+ * agent threads that would otherwise silently lose their oldest turns — and
1240
+ * sometimes catastrophic (a hot cache-friendly session that never actually
1241
+ * overflows). `ConversationSession.cacheStats` makes the "session no longer
1242
+ * fits" case visible; this function is the explicit, caller-initiated fix.
1243
+ *
1244
+ * Semantics:
1245
+ *
1246
+ * - **Leading `system` messages are always retained** and count against
1247
+ * the budget (they consume context too).
1248
+ * - Non-system messages are walked newest → oldest; each is retained while
1249
+ * it fits the remaining budget, or while fewer than `minTailMessages`
1250
+ * have been retained.
1251
+ * - Mid-conversation `system`/`tool` messages are ordinary tailable
1252
+ * history (only the *leading* system block is pinned).
1253
+ * - Returns a new array; the input is never mutated. Message objects are
1254
+ * reused by reference (sessions freeze them; external arrays are copied
1255
+ * defensively by the caller if needed).
1256
+ *
1257
+ * ```ts
1258
+ * const kept = compactConversationHistory(session.getMessages(), {
1259
+ * maxEstimatedTokens: window.contextLength,
1260
+ * reserveForReply: 512,
1261
+ * });
1262
+ * ```
1263
+ */
1264
+ declare function compactConversationHistory(messages: readonly Message[], options: CompactionOptions): readonly Message[];
1265
+ /** Options for {@link sanitizeHistoryForNextTurn}; all fields optional. */
1266
+ interface HistorySanitizeOptions {
1267
+ /**
1268
+ * Messages — counted from the **end** of the array — whose `images` are
1269
+ * preserved. Default `1`: only the final message keeps its image payload,
1270
+ * which is the rolling one-image window long-running vision chats almost
1271
+ * always want (sanitize after appending the newest turn, and every prior
1272
+ * base64 payload is gone by the next request). `0` evicts images
1273
+ * everywhere; negative values clamp to `0`.
1274
+ */
1275
+ readonly keepImagesOnLastMessages?: number | undefined;
1276
+ /**
1277
+ * Optional note appended to the `content` of each evicted message so the
1278
+ * model can still tell an image used to be there — e.g.
1279
+ * `"[image from an earlier turn removed to save context]"`. When omitted
1280
+ * (default) the `images` field is dropped and the text is untouched,
1281
+ * matching the wire behavior of never having sent the image at all.
1282
+ */
1283
+ readonly imagePlaceholder?: string | undefined;
1284
+ }
1285
+ /**
1286
+ * Vision-history hygiene: returns a copy of `messages` with image payloads
1287
+ * evicted from every message outside the trailing keep-window, conversation
1288
+ * text fully preserved.
1289
+ *
1290
+ * Ollama's `/api/chat` is stateless — each request re-sends and re-bills the
1291
+ * whole history, base64 images included (each one costing real context
1292
+ * tokens via `IMAGE_TOKEN_ESTIMATE` in the pre-flight estimators). A
1293
+ * consumer-managed `messages` array therefore re-uploads every image from
1294
+ * every prior turn on every request, long after the model has already
1295
+ * answered the question that needed them. This helper is the explicit fix:
1296
+ *
1297
+ * ```ts
1298
+ * history.push({ role: 'user', content: 'What is in THIS photo?', images: [nextImage] });
1299
+ * await client.chat({ model, messages: sanitizeHistoryForNextTurn(history) });
1300
+ * ```
1301
+ *
1302
+ * Same philosophy as {@link compactConversationHistory}: caller-initiated,
1303
+ * never automatic. Two deliberate tradeoffs, both documented in ADR 0027:
1304
+ *
1305
+ * - **The model loses access to evicted images** — it keeps the
1306
+ * conversation text (and the optional `imagePlaceholder` note) but can
1307
+ * no longer "see" earlier pictures. That is the point: a bounded
1308
+ * payload in exchange for fading visual memory.
1309
+ * - **KV-prefix invalidation** — evicting rewrites the message at that
1310
+ * position, so the next turn's prompt prefix diverges and the cache
1311
+ * re-evaluates from that point. A hot cache-friendly session shouldn't
1312
+ * sanitize; a growing vision session should.
1313
+ *
1314
+ * Pure: the input array is never mutated, and messages that keep their
1315
+ * images are reused by reference (identity-stable for the untouched ones,
1316
+ * fresh copies only where eviction happened).
1317
+ */
1318
+ declare function sanitizeHistoryForNextTurn(messages: readonly Message[], options?: HistorySanitizeOptions): readonly Message[];
1319
+ /** Options for {@link ConversationSession.compact}; all fields optional. */
1320
+ interface SessionCompactionOptions {
1321
+ /**
1322
+ * Token budget for the compacted history. Defaults to the session's own
1323
+ * window: `options.num_ctx` from the session config, or Ollama's
1324
+ * conservative 2048 fallback when unset — pair with
1325
+ * `client.models.getContextLength()` to program against the real window.
1326
+ */
1327
+ readonly maxEstimatedTokens?: number | undefined;
1328
+ /** Reply reservation; defaults to ~10% of the (derived) budget. */
1329
+ readonly reserveForReply?: number | undefined;
1330
+ /** Trailing messages always kept (default `2`). See {@link CompactionOptions}. */
1331
+ readonly minTailMessages?: number | undefined;
1332
+ }
1333
+ /** What {@link ConversationSession.compact} did. */
1334
+ interface SessionCompactionResult {
1335
+ /** Number of messages dropped from the front of the tailable history. */
1336
+ readonly droppedMessages: number;
1337
+ /** Estimated tokens of the whole history before compaction. */
1338
+ readonly estimatedTokensBefore: number;
1339
+ /** Estimated tokens of the retained history after compaction. */
1340
+ readonly estimatedTokensAfter: number;
1341
+ /** The budget that was applied (after the reply reservation). */
1342
+ readonly effectiveBudget: number;
1343
+ }
907
1344
  /** Per-turn overrides — none of these touch the history prefix, so they are KV-safe. */
908
1345
  interface ConversationSendOptions extends Pick<ChatRequestOptions, 'signal' | 'timeoutMs'> {
909
1346
  readonly options?: ModelOptions | undefined;
@@ -973,6 +1410,23 @@ declare class ConversationSession {
973
1410
  * its frozen messages has no effect on the session.
974
1411
  */
975
1412
  getMessages(): readonly Message[];
1413
+ /**
1414
+ * Compacts the session's history in place: pinned system prompt plus the
1415
+ * most recent turns that fit the budget, oldest tailable turns dropped.
1416
+ *
1417
+ * This is the explicit escape hatch for sessions that outgrew their
1418
+ * window — `cacheStats.hitRate` collapsing toward zero while history grows
1419
+ * is the usual symptom, and the pre-flight warnings on `chat()` fire once
1420
+ * the prompt estimate crosses the threshold. After compaction the next
1421
+ * turn's prompt prefix differs from everything before it, so the KV cache
1422
+ * starts cold — visible as a one-turn `evaluatedTokens` spike in
1423
+ * `cacheStats`. Cumulative cache tallies are deliberately **not** reset:
1424
+ * they are observations, and the spike is part of the story.
1425
+ *
1426
+ * Returns the before/after estimates and the drop count. No-op (zero
1427
+ * drops, equal before/after estimates) when the history already fits.
1428
+ */
1429
+ compact(options?: SessionCompactionOptions): SessionCompactionResult;
976
1430
  /**
977
1431
  * Clears the conversation back to its initial state (system prompt, zero
978
1432
  * turns, zeroed cache statistics). The next turn starts with a cold cache
@@ -1558,6 +2012,16 @@ declare class OllamaClient {
1558
2012
  * (streams, via `executeWithFailover`'s `holdUntil` release path).
1559
2013
  */
1560
2014
  private readonly activeRequests;
2015
+ /**
2016
+ * Dynamic multi-host model-affinity routing, active only when
2017
+ * `config.endpointHealth.modelAffinity` is set — see
2018
+ * `src/providers/model-affinity-router.ts` and ADR 0028. `undefined` means
2019
+ * the request path skips reordering entirely (zero overhead, and no `/api/ps`
2020
+ * probes are ever issued).
2021
+ */
2022
+ private readonly modelAffinity;
2023
+ /** Default timeout for one-off probe requests (`ping`, affinity `/api/ps` refreshes). */
2024
+ private static readonly PROBE_TIMEOUT_MS;
1561
2025
  constructor(config?: OllamaClientConfig);
1562
2026
  get modelsClient(): ModelsClient;
1563
2027
  /**
@@ -1772,7 +2236,15 @@ declare class OllamaClient {
1772
2236
  readonly createModel: (r: CreateRequestOptions) => Promise<ProgressResponse | OllamaStream<ProgressResponse, ProgressStreamResult>>;
1773
2237
  readonly deleteModel: (req: DeleteRequestOptions) => Promise<StatusResponse>;
1774
2238
  readonly copyModel: (req: CopyRequestOptions) => Promise<StatusResponse>;
1775
- readonly ps: () => Promise<PsResponse>;
2239
+ readonly ps: (request?: RequestCancellationOptions) => Promise<PsResponse>;
2240
+ /**
2241
+ * Discovers the model's real context window — the allocated window of the
2242
+ * running instance (`/api/ps`), the Modelfile `num_ctx` default, and the
2243
+ * native GGUF maximum (`/api/show` `model_info`), resolved by precedence.
2244
+ * Convenience alias for {@link ModelsClient.getContextLength} — see that
2245
+ * method for the full contract and the precedence order.
2246
+ */
2247
+ readonly getContextLength: (request: ContextDiscoveryRequestOptions) => Promise<DiscoveredContextLength>;
1776
2248
  readonly version: () => Promise<VersionResponse>;
1777
2249
  readonly createBlob: (digest: string, data: BinaryBody) => Promise<void>;
1778
2250
  readonly checkBlob: (digest: string) => Promise<boolean>;
@@ -1883,7 +2355,49 @@ declare class OllamaClient {
1883
2355
  capabilities(model: string, signal?: AbortSignal): Promise<ModelCapabilities>;
1884
2356
  runtimeMode(): RuntimeMode;
1885
2357
  healthCheck(): Promise<EndpointHealthCheckResult[]>;
2358
+ /**
2359
+ * Ultra-lightweight liveness probe against the client's current best
2360
+ * endpoint: `HEAD /` — the root ping Ollama's documentation defines for
2361
+ * load-balancer health checks (HTTP 200; the daemon answers `GET /` with the
2362
+ * plain-text body "Ollama is running", which a HEAD request doesn't transfer).
2363
+ *
2364
+ * Deliberately a *single-endpoint, single-shot* probe — no failover to other
2365
+ * candidates, no retry — so the answer reflects one concrete host, not "some
2366
+ * endpoint eventually answered". To audit every configured endpoint, use
2367
+ * {@link healthCheck} (which probes all of them, and also reports the server
2368
+ * version at the cost of the heavier `GET /api/version` round trip).
2369
+ *
2370
+ * Resolves — never throws — with `{ healthy: false, error }` for connection
2371
+ * failures and timeouts; only caller-initiated cancellation (`signal`/
2372
+ * `destroy()`) rejects, mirroring how any other aborted request behaves.
2373
+ */
2374
+ ping(options?: RequestCancellationOptions): Promise<PingResult>;
2375
+ /**
2376
+ * Affinity-cache snapshots for every configured endpoint — which models each
2377
+ * host held in VRAM at the last successful `/api/ps` probe, when that was, and
2378
+ * whether it's still fresh. Empty when `endpointHealth.modelAffinity` isn't
2379
+ * configured (no probes are ever issued, so there is nothing to report).
2380
+ */
2381
+ modelAffinityStatus(): readonly ModelAffinitySnapshot[];
2382
+ /**
2383
+ * Explicitly refreshes the model-affinity cache for every configured endpoint
2384
+ * (deduped with any in-flight background refresh). No-op — resolves
2385
+ * immediately — when `endpointHealth.modelAffinity` isn't configured. Use it
2386
+ * to warm the cache at startup so the very first request already routes by
2387
+ * residency instead of waiting for the self-warming first-round refresh.
2388
+ */
2389
+ warmModelAffinity(): Promise<void>;
1886
2390
  endpointStatus(): EndpointHealth[];
2391
+ /**
2392
+ * Fetches one endpoint's `GET /api/ps` running-model list for the affinity
2393
+ * router. Bypasses `executeWithFailover` deliberately: probes must not
2394
+ * reenter the routing machinery they inform, must not touch the registry's
2395
+ * circuit-breaker/failure bookkeeping (a dead host's probe failing must not
2396
+ * cool it down harder than its real traffic already does), and must not fail
2397
+ * over to a different host (the answer is per-host by definition). Failures
2398
+ * propagate to the router, which records a short backoff and retries later.
2399
+ */
2400
+ private fetchEndpointRunningModels;
1887
2401
  /**
1888
2402
  * Immediately aborts every in-flight request and active stream managed by
1889
2403
  * this client — the clean teardown path for short-lived processes, CLI
@@ -2607,6 +3121,22 @@ interface AgentConfig {
2607
3121
  readonly maxIterations?: number | undefined;
2608
3122
  /** Maximum total tool calls allowed during one agent run. */
2609
3123
  readonly maxToolCalls?: number | undefined;
3124
+ /**
3125
+ * Cycle detection: maximum executions of one *identical* tool call — same
3126
+ * tool name and same arguments — during a single run. When the model would
3127
+ * exceed this budget (the classic stuck-loop signature: re-emitting the
3128
+ * same call without reacting to its results), the run fails fast with
3129
+ * {@link OllamaAgentToolLoopError} instead of burning the remaining
3130
+ * iterations on the same wasted call.
3131
+ *
3132
+ * Off by default (`undefined`), matching `maxToolCalls`: an explicit,
3133
+ * caller-owned guardrail. Counting is per canonical signature, so
3134
+ * argument key order never splits one repeated call into several
3135
+ * "distinct" ones. Every call handed to the registry counts — failed,
3136
+ * unregistered, and timed-out executions included, since re-calling a
3137
+ * failing tool with unchanged arguments is exactly the loop this catches.
3138
+ */
3139
+ readonly maxRepeatedToolCalls?: number | undefined;
2610
3140
  readonly hooks?: AgentHooks | undefined;
2611
3141
  /** Validate tool support from /api/show before starting a tool-enabled run. Default: true. */
2612
3142
  readonly validateToolCapability?: boolean | undefined;
@@ -2642,6 +3172,18 @@ interface AgentChatClient {
2642
3172
  readonly message: Message;
2643
3173
  }>;
2644
3174
  }
3175
+ /**
3176
+ * Canonical signature of a tool call for cycle detection: `name(args)` with
3177
+ * recursively key-sorted arguments, so the same call re-emitted by a model
3178
+ * in a different argument key order collapses to one signature instead of
3179
+ * counting as two "distinct" calls.
3180
+ *
3181
+ * ```ts
3182
+ * canonicalToolCallSignature({ function: { name: 'search', arguments: { q: 'x' } } })
3183
+ * // 'search({"q":"x"})'
3184
+ * ```
3185
+ */
3186
+ declare function canonicalToolCallSignature(toolCall: ToolCall): string;
2645
3187
  /**
2646
3188
  * Runs a multi-turn `chat` + tool-execution loop until the model responds without
2647
3189
  * requesting further tool calls, or `maxIterations` is exceeded. Tool calls within a
@@ -2658,6 +3200,7 @@ declare class Agent {
2658
3200
  private readonly tools?;
2659
3201
  private readonly maxIterations;
2660
3202
  private readonly maxToolCalls?;
3203
+ private readonly maxRepeatedToolCalls?;
2661
3204
  private readonly hooks?;
2662
3205
  private readonly validateToolCapability;
2663
3206
  private readonly toolContextSize;
@@ -2720,6 +3263,93 @@ declare const ATTR_OLLAMA_AGENT_MAX_ITERATIONS = "ollama.agent.max_iterations";
2720
3263
  declare const ATTR_OLLAMA_AGENT_ITERATION = "ollama.agent.iteration";
2721
3264
  declare const GEN_AI_SYSTEM_OLLAMA = "ollama";
2722
3265
 
3266
+ /**
3267
+ * Standardized telemetry normalizer — raw nanosecond counters to derived metrics.
3268
+ *
3269
+ * Ollama reports every duration as an **integer in nanoseconds** on its generation
3270
+ * responses (`total_duration`, `load_duration`, `prompt_eval_duration`,
3271
+ * `eval_duration` — see the official "Usage" documentation), plus token counters
3272
+ * (`prompt_eval_count`, `prompt_eval_cached_count`, `eval_count`). Consumers that
3273
+ * want milliseconds, tokens/second, or a cache-hit ratio otherwise each reimplement
3274
+ * the same division-and-rounding math — this module is that math, once, with the
3275
+ * formulas the official docs spell out:
3276
+ *
3277
+ * - `tokensPerSecond` = `eval_count / (eval_duration / 1e9)` — generation throughput.
3278
+ * - `promptTokensPerSecond` = `prompt_eval_count / (prompt_eval_duration / 1e9)` —
3279
+ * prompt ingestion (pre-fill) throughput.
3280
+ * - `cacheHitRatio` = `prompt_eval_cached_count / (cached + evaluated)` — see the
3281
+ * note on {@link FormattedTelemetry.cacheHitRatio} for why the denominator is the
3282
+ * *sum*, not `prompt_eval_count` alone.
3283
+ *
3284
+ * Pure and dependency-free: {@link formatTelemetry} never fetches, never throws, and
3285
+ * treats every missing counter as `0` — partial responses (stream final events,
3286
+ * older servers without `prompt_eval_cached_count`) format cleanly instead of
3287
+ * producing `NaN`/`Infinity`.
3288
+ *
3289
+ * The input is structural: any response object carrying these optional fields —
3290
+ * `GenerateResponse`, `ChatResponse`, the final `GenerateStreamEvent`,
3291
+ * `EmbedResponse` — can be passed directly, no picking/plucking required.
3292
+ */
3293
+ /**
3294
+ * The raw nanosecond/token counters Ollama attaches to generation responses. Every
3295
+ * field is optional because servers report a subset: streaming final events may omit
3296
+ * load counters, older builds predate `prompt_eval_cached_count`, and embeddings
3297
+ * carry no `eval_count`. Accepts full response objects structurally.
3298
+ */
3299
+ interface RawOllamaMetrics {
3300
+ /** Total end-to-end request latency, nanoseconds. */
3301
+ readonly total_duration?: number | undefined;
3302
+ /** Model cold-load and weight-transfer time into VRAM, nanoseconds. */
3303
+ readonly load_duration?: number | undefined;
3304
+ /** Prompt tokens freshly evaluated this request (not served from the KV cache). */
3305
+ readonly prompt_eval_count?: number | undefined;
3306
+ /** Prompt tokens served from the KV prompt cache (prefix reuse). */
3307
+ readonly prompt_eval_cached_count?: number | undefined;
3308
+ /** Prompt ingestion / pre-fill processing time, nanoseconds. */
3309
+ readonly prompt_eval_duration?: number | undefined;
3310
+ /** Tokens generated (the response proper). */
3311
+ readonly eval_count?: number | undefined;
3312
+ /** Token generation time, nanoseconds. */
3313
+ readonly eval_duration?: number | undefined;
3314
+ }
3315
+ /** The normalized, human-scale view of one response's {@link RawOllamaMetrics}. */
3316
+ interface FormattedTelemetry {
3317
+ /** `total_duration` in milliseconds, 2 decimals. */
3318
+ readonly totalLatencyMs: number;
3319
+ /** `load_duration` in milliseconds, 2 decimals — 0 when the model was already resident. */
3320
+ readonly modelLoadMs: number;
3321
+ /** `prompt_eval_duration` in milliseconds, 2 decimals. */
3322
+ readonly promptEvalMs: number;
3323
+ /** `eval_duration` in milliseconds, 2 decimals. */
3324
+ readonly generationMs: number;
3325
+ /** `eval_count / eval_duration` in tokens/second, 1 decimal — 0 when either is absent. */
3326
+ readonly tokensPerSecond: number;
3327
+ /** `prompt_eval_count / prompt_eval_duration` in tokens/second, 1 decimal. */
3328
+ readonly promptTokensPerSecond: number;
3329
+ /**
3330
+ * Share of prompt tokens served from the KV cache:
3331
+ * `cached / (cached + evaluated)`, 3 decimals.
3332
+ *
3333
+ * The denominator is the **sum**, not `prompt_eval_count` alone, because Ollama
3334
+ * reports `prompt_eval_count: 0` on a *full* cache hit — the same house semantics
3335
+ * as `ConversationSession`'s `cacheStats.hitRate` (see `src/conversation.ts`). A
3336
+ * `cached / prompt_eval_count` formula would report a perfect cache as `0`.
3337
+ */
3338
+ readonly cacheHitRatio: number;
3339
+ }
3340
+ /**
3341
+ * Derives the standardized metrics summary from one response's raw nanosecond
3342
+ * counters. Pure — never throws, never fetches; every missing counter reads as `0`.
3343
+ *
3344
+ * ```ts
3345
+ * const res = await client.chat({ model: 'llama3.2', messages });
3346
+ * const t = formatTelemetry(res);
3347
+ * console.log(t.tokensPerSecond, 'tok/s |', t.totalLatencyMs, 'ms |',
3348
+ * `${Math.round(t.cacheHitRatio * 100)}% prompt cache hit`);
3349
+ * ```
3350
+ */
3351
+ declare function formatTelemetry(raw: RawOllamaMetrics): FormattedTelemetry;
3352
+
2723
3353
  /**
2724
3354
  * Cross-runtime helpers with no Node.js API dependency, so they stay safe to import from
2725
3355
  * the core client bundle (see ADR 0006 — Edge Runtime CI).
@@ -2796,6 +3426,11 @@ declare const IMAGE_TOKEN_ESTIMATE = 300;
2796
3426
  * model-specific tokenizer.
2797
3427
  */
2798
3428
  declare function estimateTokens(text: string): number;
3429
+ /**
3430
+ * Rough per-message framing overhead: role tags, chat-template delimiters.
3431
+ */
3432
+ declare const MESSAGE_OVERHEAD_TOKENS = 4;
3433
+ declare function estimateMessageTokens(message: Message): number;
2799
3434
  /**
2800
3435
  * Estimated prompt tokens for a `/api/chat` request: every message plus
2801
3436
  * tool-schema overhead (each tool definition's JSON schema serializes into
@@ -2851,4 +3486,142 @@ declare const OLLAMA_FALLBACK_CONTEXT_LENGTH = 2048;
2851
3486
  */
2852
3487
  declare function contextWarningMessage(check: ContextCheck, kind: 'chat' | 'generate'): string;
2853
3488
 
2854
- export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, AbortableAsyncIterable, Agent, type AgentChatClient, type AgentConfig, type AgentHooks, type AgentResult, type AgentRunInput, type AgentTurn, type AnswerFor, AnthropicCompatClient, AnyTool, type BackoffOptions, BalanceRequestOptions, type BalanceResponse, type BlobUploadResult, ChatRequestOptions, ChatResponse, ChatStreamResult, type ChoiceDecision, type ContextCheck, type ContextCheckOptions, type ContextWindowSource, type ConversationSendOptions, ConversationSession, type ConversationSessionOptions, CopyRequestOptions, CreateRequestOptions, type CumulativeCacheStats, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, type EmbedBatchOptions, type EmbedBatchResult, EmbedRequestOptions, EmbedResponse, EmbeddingsRequestOptions, EmbeddingsResponse, type EndpointHealth, type EndpointHealthCheckResult, EndpointRegistry, type EndpointRegistryOptions, FetchLike, type FetchWithBackoffConfig, FormatOption, GEN_AI_SYSTEM_OLLAMA, GenerateRequestOptions, GenerateResponse, GenerateStreamResult, HttpClient, IMAGE_TOKEN_ESTIMATE, type IncludedBalance, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, type KeepAlive, type KnownDoneReason, type LegacyBalanceLimit, type LegacyIncludedBalance, type LoadMcpToolsOptions, Logger, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, McpBridge, type McpBridgeOptions, McpCallToolResult, McpClientLike, McpTask, McpToolDescriptor, Message, Middleware, type ModelCapabilities, ModelOptions, ModelResponse, ModelsClient, type NoulDecision, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaClient, type OllamaClientConfig, type OllamaCloudFreeTierQuotaBudgets, type OllamaCredentialConfig, type OllamaEndpoint, OllamaStream, OllamaStreamEvent, OllamaStreamEventType, OpenAICompatClient, type OversizedEmbedInput, type ParallelToolCallBehavior, ProgressResponse, ProgressStreamResult, PsResponse, PullRequestOptions, PushRequestOptions, QuotaManager, type QuotaManagerOptions, type QuotaWindowConfig, type QuotaWindowStatus, RETRYABLE_STATUS_CODES, type RankResult, RawUsageSource, RequestCancellationOptions, RequestLifecycleHook, RequestRunner, type ResponsesCreateRequest, type ResponsesCreateResponse, ResponsesModule, type ResponsesReasoningEffort, type ResponsesStreamEvent, type ResponsesToolDefinition, type ResponsesUsage, type RetryConfig, type RuntimeMode, type ScoreDecision, type SessionTurn, ShowRequestOptions, ShowResponse, type SpanAttributeValue, type SpanAttributes, StatusResponse, type SupportedSchema, type SystemOneAnswer, type SystemOneAnswers, SystemOneChoiceAnswer, SystemOneChoiceQuestion, SystemOneContent, SystemOneNoulAnswer, SystemOneNoulQuestion, type SystemOneQuestion, type SystemOneQuestions, type SystemOneRequest, type SystemOneResponse, SystemOneScoreAnswer, SystemOneScoreQuestion, type SystemOneUsage, ThinkValue, ThinkingMetadata, type TimeoutSignal, TokenUsage, Tool, ToolCall, ToolCallAccumulator, ToolDefinition, ToolExecutionResult, ToolHandler, ToolParameters, ToolRegistry, type TurnCacheStats, type UsageBucket, type UsageMetrics, UsageRequestOptions, type UsageResponse, VersionResponse, VisionInput, WebFetchRequestOptions, WebFetchResponse, WebSearchRequestOptions, WebSearchResponse, addSpanEvent, batchEmbed, calculateBackoff, checkChatContext, checkEndpointHealth, checkGenerateContext, contextWarningMessage, createDecision, createOllamaCloudFreeTierQuota, createTimeoutSignal, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateSystemOneRequestBytes, estimateTokens, fetchWithBackoff, findOversizedEmbedInputs, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveCredentialEndpoints, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
3489
+ /**
3490
+ * Model-affinity scheduling: keep the daemon's working set small.
3491
+ *
3492
+ * Ollama's concurrency model (per the official FAQ):
3493
+ *
3494
+ * - A model's KV cache scales by `OLLAMA_NUM_PARALLEL × context_length` —
3495
+ * parallel requests to the *same* loaded model are cheap (each gets its
3496
+ * own slot of the already-allocated cache).
3497
+ * - Requests for a *different* model queue until the first goes idle or is
3498
+ * evicted (`OLLAMA_MAX_LOADED_MODELS`, default 3× GPU count) — a swap is
3499
+ * an expensive unload/cold-load cycle: seconds of VRAM churn that a
3500
+ * concurrent multi-model workload pays again and again when it
3501
+ * interleaves models arbitrarily.
3502
+ * - Once `OLLAMA_MAX_QUEUE` (default 512) requests are waiting, new ones
3503
+ * are rejected outright with 503s.
3504
+ *
3505
+ * `ModelAffinityScheduler` is the client-side counterpart for workloads that
3506
+ * juggle several models — extraction on a coder model, reasoning on a
3507
+ * thinking model, embeddings for retrieval. It groups tasks into per-model
3508
+ * serial queues and caps how many *distinct* models have work in flight
3509
+ * (`concurrentModels`, default 1), preferring to deepen the currently-active
3510
+ * model over starting the next one — the anti-thrashing order. Candidate
3511
+ * lists let a task run on whichever of several interchangeable models is
3512
+ * already loaded (`GET /api/ps`, cached with a TTL).
3513
+ *
3514
+ * It is a user-space primitive, deliberately not wired into `OllamaClient`'s
3515
+ * request path: affinity decisions are workload policy, and the scheduler is
3516
+ * the foundation the multi-instance routing work builds on. See ADR 0026.
3517
+ */
3518
+
3519
+ /**
3520
+ * The minimal client surface the scheduler needs. `OllamaClient` satisfies it
3521
+ * structurally; tests and custom setups can pass any compatible object.
3522
+ */
3523
+ interface ModelAffinityClient {
3524
+ readonly models: {
3525
+ ps(): Promise<{
3526
+ readonly models: ReadonlyArray<{
3527
+ readonly name: string;
3528
+ }>;
3529
+ }>;
3530
+ };
3531
+ }
3532
+ /** Options for {@link ModelAffinityScheduler}. */
3533
+ interface ModelAffinitySchedulerOptions {
3534
+ /**
3535
+ * Maximum number of *distinct* models with work in flight at once
3536
+ * (default `1`). `1` is the anti-thrashing setting: one model stays hot
3537
+ * until its queue drains, then the next loads. Raise it only when the
3538
+ * host has VRAM for several concurrent models
3539
+ * (`OLLAMA_MAX_LOADED_MODELS`).
3540
+ */
3541
+ readonly concurrentModels?: number | undefined;
3542
+ /**
3543
+ * Parallel tasks allowed per active model (default `1`). Raising this
3544
+ * mirrors the server's own `OLLAMA_NUM_PARALLEL` — same-model parallelism
3545
+ * reuses the loaded weights without swap cost.
3546
+ */
3547
+ readonly perModelConcurrency?: number | undefined;
3548
+ /**
3549
+ * How long a `GET /api/ps` result is reused when choosing among candidate
3550
+ * models (default `30_000` ms). Only multi-candidate `run()` calls and
3551
+ * `loadedModels()` consult the server; single-model tasks never do.
3552
+ */
3553
+ readonly psCacheTtlMs?: number | undefined;
3554
+ }
3555
+ /** Live scheduling state, from {@link ModelAffinityScheduler.stats}. */
3556
+ interface ModelAffinityStats {
3557
+ /** Models with at least one task currently executing. */
3558
+ readonly activeModels: readonly string[];
3559
+ /** Tasks waiting in queues (not yet started). */
3560
+ readonly queuedTasks: number;
3561
+ }
3562
+ /**
3563
+ * Groups async work by model with load-aware dispatch.
3564
+ *
3565
+ * ```ts
3566
+ * const scheduler = new ModelAffinityScheduler(client);
3567
+ *
3568
+ * // Serial queue per model; one model hot at a time (default):
3569
+ * await scheduler.run('deepseek-r1', () => reason(client));
3570
+ * await scheduler.run('qwen2.5:coder', () => extract(client));
3571
+ *
3572
+ * // Either-or: picks whichever candidate is already loaded (/api/ps):
3573
+ * const summary = await scheduler.run(
3574
+ * ['qwen2.5:coder', 'qwen2.5:14b'],
3575
+ * (model) => extractWith(client, model),
3576
+ * );
3577
+ * ```
3578
+ *
3579
+ * Dispatch order is **affinity-sticky, not round-robin**: when a queued
3580
+ * model is already active, its tasks start before a different model's (up to
3581
+ * `perModelConcurrency`), because deepening the hot model avoids a swap.
3582
+ * Tasks run in FIFO order within each model's queue. Task functions are the
3583
+ * caller's own — the scheduler adds no retry, timeout, or failover, and a
3584
+ * rejected task simply rejects its `run()` promise while its siblings
3585
+ * proceed.
3586
+ */
3587
+ declare class ModelAffinityScheduler {
3588
+ private readonly client;
3589
+ private readonly queues;
3590
+ private readonly concurrentModels;
3591
+ private readonly perModelConcurrency;
3592
+ private readonly psCacheTtlMs;
3593
+ private psCache;
3594
+ private readonly drainWaiters;
3595
+ constructor(client: ModelAffinityClient, options?: ModelAffinitySchedulerOptions);
3596
+ /**
3597
+ * Runs `task` on the scheduler, serialized per model. When `model` is a
3598
+ * list of candidates, the already-loaded one is preferred (fresh
3599
+ * `/api/ps` data subject to the TTL cache); with none loaded — or when the
3600
+ * lookup fails — the first candidate runs. The chosen model is handed to
3601
+ * the task.
3602
+ */
3603
+ run<T>(model: string | readonly string[], task: (model: string) => Promise<T>): Promise<T>;
3604
+ /**
3605
+ * Names of the models currently loaded, per `GET /api/ps`, cached for
3606
+ * `psCacheTtlMs`. Degrades to an empty list when the server can't answer
3607
+ * (never throws; candidate selection treats it as "nothing loaded").
3608
+ */
3609
+ loadedModels(): Promise<readonly string[]>;
3610
+ /** Current scheduling state — active models and queued task count. */
3611
+ get stats(): ModelAffinityStats;
3612
+ /**
3613
+ * Resolves once every queue is empty and no task is running. Does not
3614
+ * abort anything — tasks are caller-owned; abort them via their own
3615
+ * signals before disposing if needed.
3616
+ */
3617
+ dispose(): Promise<void>;
3618
+ private chooseCandidate;
3619
+ private refreshPsNames;
3620
+ private pump;
3621
+ private activeModelCount;
3622
+ private execute;
3623
+ }
3624
+ /** Convenience factory — `createModelAffinityScheduler(client, opts)` reads better in setups. */
3625
+ declare function createModelAffinityScheduler(client: ModelAffinityClient | OllamaClient, options?: ModelAffinitySchedulerOptions): ModelAffinityScheduler;
3626
+
3627
+ export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, AbortableAsyncIterable, type AffinityRunningModel, Agent, type AgentChatClient, type AgentConfig, type AgentHooks, type AgentResult, type AgentRunInput, type AgentTurn, type AnswerFor, AnthropicCompatClient, AnyTool, type BackoffOptions, BalanceRequestOptions, type BalanceResponse, type BlobUploadResult, ChatRequestOptions, ChatResponse, ChatStreamResult, type ChoiceDecision, type CompactionOptions, type ContextCheck, type ContextCheckOptions, type ContextDiscoveryRequestOptions, type ContextLengthSignals, type ContextLengthSource, type ContextWindowSource, type ConversationSendOptions, ConversationSession, type ConversationSessionOptions, CopyRequestOptions, CreateRequestOptions, type CumulativeCacheStats, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, type DiscoveredContextLength, type EmbedBatchOptions, type EmbedBatchResult, EmbedRequestOptions, EmbedResponse, EmbeddingsRequestOptions, EmbeddingsResponse, type EndpointHealth, type EndpointHealthCheckResult, EndpointRegistry, type EndpointRegistryOptions, FetchLike, type FetchWithBackoffConfig, FormatOption, type FormattedTelemetry, GEN_AI_SYSTEM_OLLAMA, GenerateRequestOptions, GenerateResponse, GenerateStreamResult, type HistorySanitizeOptions, HttpClient, IMAGE_TOKEN_ESTIMATE, type IncludedBalance, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, type KeepAlive, type KnownDoneReason, type LegacyBalanceLimit, type LegacyIncludedBalance, type LoadMcpToolsOptions, Logger, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, type McpBridgeOptions, McpCallToolResult, McpClientLike, McpTask, McpToolDescriptor, Message, Middleware, type ModelAffinityClient, ModelAffinityRouter, type ModelAffinityRoutingOptions, ModelAffinityScheduler, type ModelAffinitySchedulerOptions, type ModelAffinitySnapshot, type ModelAffinityStats, type ModelCapabilities, ModelOptions, ModelResponse, ModelsClient, type NoulDecision, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaClient, type OllamaClientConfig, type OllamaCloudFreeTierQuotaBudgets, type OllamaCredentialConfig, type OllamaEndpoint, OllamaStream, OllamaStreamEvent, OllamaStreamEventType, OpenAICompatClient, type OversizedEmbedInput, type ParallelToolCallBehavior, PingResult, ProgressResponse, ProgressStreamResult, PsResponse, PullRequestOptions, PushRequestOptions, QuotaManager, type QuotaManagerOptions, type QuotaWindowConfig, type QuotaWindowStatus, RETRYABLE_STATUS_CODES, type RankResult, type RawOllamaMetrics, RawUsageSource, RequestCancellationOptions, RequestLifecycleHook, RequestRunner, type ResponsesCreateRequest, type ResponsesCreateResponse, ResponsesModule, type ResponsesReasoningEffort, type ResponsesStreamEvent, type ResponsesToolDefinition, type ResponsesUsage, type RetryConfig, type RuntimeMode, type ScoreDecision, type SessionCompactionOptions, type SessionCompactionResult, type SessionTurn, ShowRequestOptions, ShowResponse, type SpanAttributeValue, type SpanAttributes, StatusResponse, type SupportedSchema, type SystemOneAnswer, type SystemOneAnswers, SystemOneChoiceAnswer, SystemOneChoiceQuestion, SystemOneContent, SystemOneNoulAnswer, SystemOneNoulQuestion, type SystemOneQuestion, type SystemOneQuestions, type SystemOneRequest, type SystemOneResponse, SystemOneScoreAnswer, SystemOneScoreQuestion, type SystemOneUsage, ThinkValue, ThinkingMetadata, type TimeoutSignal, TokenUsage, Tool, ToolCall, ToolCallAccumulator, ToolDefinition, ToolExecutionResult, ToolHandler, ToolParameters, ToolRegistry, type TurnCacheStats, type UsageBucket, type UsageMetrics, UsageRequestOptions, type UsageResponse, VersionResponse, VisionInput, WebFetchRequestOptions, WebFetchResponse, WebSearchRequestOptions, WebSearchResponse, addSpanEvent, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, contextWarningMessage, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };