@nemesis-oss/ollama-sdk 1.9.0 → 1.11.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.ts 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-C3rfpkZw.js';
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-C3rfpkZw.js';
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-DVcX_zMp.js';
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-DVcX_zMp.js';
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 VisionInput } from './types-BtQexFpT.js';
2
+ 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.js';
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-CCm4CtU_.js';
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-CCm4CtU_.js';
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-5oYIRS6D.js';
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-5oYIRS6D.js';
8
- import { T as ToolHandler, a as Tool, A as AnyTool, b as ToolRegistry, c as ToolExecutionResult } from './compose-DCVYMvOL.js';
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-DCVYMvOL.js';
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-CfricAhK.js';
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-CfricAhK.js';
8
+ import { T as ToolHandler, a as Tool, A as AnyTool, b as ToolRegistry, c as ToolExecutionResult } from './compose-BQYXdpar.js';
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-BQYXdpar.js';
10
10
  import { M as McpClientLike, a as McpToolDescriptor, b as McpTask, c as McpCallToolResult } from './types-CI-gM2u5.js';
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.js';
12
12
  import * as Otel from '@opentelemetry/api';
@@ -122,6 +122,211 @@ 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
+ * Finds the context window Ollama actually allocated for `model`, from a
295
+ * `GET /api/ps` response. Matching mirrors Ollama's own model resolution:
296
+ * exact match on the entry's `name`/`model`, or — when `model` carries no
297
+ * tag — any entry whose name starts with `<model>:`.
298
+ *
299
+ * Returns `undefined` when the model isn't loaded or the running entry
300
+ * doesn't report `context_length` (older server versions).
301
+ */
302
+ declare function findRunningModelContextLength(psModels: readonly ModelResponse[], model: string): number | undefined;
303
+ /** Internal inputs to {@link resolveContextLength}; each signal independently optional. */
304
+ interface ContextLengthSignals {
305
+ readonly running?: number | undefined;
306
+ readonly parameter?: number | undefined;
307
+ readonly native?: number | undefined;
308
+ }
309
+ /**
310
+ * Resolves the effective context window from whatever signals were
311
+ * discovered, by precedence:
312
+ *
313
+ * 1. **`running`** — the allocated window of the loaded instance. When the
314
+ * model is in memory this is the ground truth; it already incorporates
315
+ * any Modelfile `num_ctx` default.
316
+ * 2. **`parameter`** — the Modelfile's `num_ctx`, i.e. the window the
317
+ * runner *will* allocate for this model on the next cold load.
318
+ * 3. **`native`** — the GGUF maximum. Only used when nothing tighter is
319
+ * known: a fresh `num_ctx`-less load may allocate far less than native
320
+ * (Ollama's conservative default), so native is a *ceiling*, not an
321
+ * expectation.
322
+ * 4. **Fallback** — {@link OLLAMA_FALLBACK_CONTEXT_LENGTH} (2048), the
323
+ * conservative server-side default, so the result is always actionable.
324
+ *
325
+ * Pure — used by `ModelsClient.getContextLength()` and exported for callers
326
+ * that already hold `/api/ps` / `/api/show` responses.
327
+ */
328
+ declare function resolveContextLength(signals: ContextLengthSignals): DiscoveredContextLength;
329
+
125
330
  /**
126
331
  * Exponential backoff with full jitter calculation.
127
332
  */
@@ -445,92 +650,6 @@ interface EndpointHealthCheckResult {
445
650
  }
446
651
  declare function checkEndpointHealth(endpoint: OllamaEndpoint, fetchImpl?: FetchLike, timeoutMs?: number, middleware?: readonly Middleware[] | undefined, onLifecycleEvent?: RequestLifecycleHook | undefined): Promise<EndpointHealthCheckResult>;
447
652
 
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
653
  /**
535
654
  * Dual-mode (AsyncIterator and EventEmitter-like) stream wrapper.
536
655
  */
@@ -606,7 +725,39 @@ declare class ModelsClient {
606
725
  create(request: CreateRequestOptions): Promise<ProgressResponse | OllamaStream<ProgressResponse, ProgressStreamResult>>;
607
726
  delete(request: DeleteRequestOptions): Promise<StatusResponse>;
608
727
  copy(request: CopyRequestOptions): Promise<StatusResponse>;
609
- ps(): Promise<PsResponse>;
728
+ ps(request?: RequestCancellationOptions): Promise<PsResponse>;
729
+ /**
730
+ * Discovers the model's real context window from the server — no more
731
+ * 2048/4096 guesswork. Consults, in precedence order:
732
+ *
733
+ * 1. `GET /api/ps` — the window the **running instance actually
734
+ * allocated** (exact, reflects the Modelfile `num_ctx` default and
735
+ * what fit in memory); skipped when the model isn't loaded, when
736
+ * `skipRunningCheck` is set, or harmlessly on servers without the
737
+ * field.
738
+ * 2. `POST /api/show` `parameters` — a Modelfile-authored
739
+ * `num_ctx <n>` default, i.e. the window a cold load will allocate.
740
+ * 3. `POST /api/show` `model_info["<arch>.context_length"]` — the
741
+ * model's **native GGUF maximum** (a ceiling to raise `num_ctx`
742
+ * toward, not the allocated window).
743
+ * 4. Fallback — Ollama's conservative unset-`num_ctx` default (2048).
744
+ *
745
+ * The result carries every signal it found, so `nativeContextLength:
746
+ * 131072` vs `contextLength: 4096` (a 32× gap seen in the official API
747
+ * examples) is visible rather than guessed. Typical wiring:
748
+ *
749
+ * ```ts
750
+ * const { contextLength, source, nativeContextLength } =
751
+ * await client.models.getContextLength({ model: 'gemma4' });
752
+ * // -> { contextLength: 4096, source: 'running', nativeContextLength: 131072 }
753
+ * ```
754
+ *
755
+ * Makes two server calls (`/api/ps` + `/api/show`) unless
756
+ * `skipRunningCheck` is set. A 404 from `/api/ps` (endpoint without the
757
+ * listing) is treated as "not running" and never fails the lookup;
758
+ * other transport errors propagate normally.
759
+ */
760
+ getContextLength(request: ContextDiscoveryRequestOptions): Promise<DiscoveredContextLength>;
610
761
  version(): Promise<VersionResponse>;
611
762
  /**
612
763
  * Immediately unloads `model` from VRAM/RAM by issuing an empty
@@ -904,6 +1055,147 @@ interface SessionTurn {
904
1055
  /** The full underlying `/api/chat` response (usage, durations, thinking, …). */
905
1056
  readonly response: ChatResponse;
906
1057
  }
1058
+ /** Options for {@link compactConversationHistory} / {@link ConversationSession.compact}. */
1059
+ interface CompactionOptions {
1060
+ /**
1061
+ * Estimated-token budget the compacted history must fit within (including
1062
+ * any retained system message and the 10%-style reply reservation below).
1063
+ * Estimated with the same CJK-aware heuristic as the pre-flight checks
1064
+ * (`estimateMessageTokens` — ±20–30%, message overhead + tool calls +
1065
+ * `IMAGE_TOKEN_ESTIMATE` per image included).
1066
+ */
1067
+ readonly maxEstimatedTokens: number;
1068
+ /**
1069
+ * Tokens subtracted from the budget and reserved for the model's next
1070
+ * reply (default `0` for the pure function; `session.compact()` defaults
1071
+ * to ~10% of the window, mirroring the pre-flight warning threshold's
1072
+ * headroom).
1073
+ */
1074
+ readonly reserveForReply?: number | undefined;
1075
+ /**
1076
+ * Trailing messages always retained, even when they don't fit the budget
1077
+ * (default `2` — the latest user/assistant exchange). Guarantees a
1078
+ * compacted conversation never loses its most recent context; an
1079
+ * oversized tail is retained deliberately rather than truncated, because
1080
+ * silent truncation is the server's failure mode, not one this SDK
1081
+ * reproduces.
1082
+ */
1083
+ readonly minTailMessages?: number | undefined;
1084
+ }
1085
+ /**
1086
+ * Sliding-window history compaction: keep the pinned system message(s) plus
1087
+ * the most recent turns that fit `maxEstimatedTokens`, dropping the oldest
1088
+ * turns in between.
1089
+ *
1090
+ * Deliberately **not** automatic anywhere in the SDK: compaction rewrites the
1091
+ * prompt prefix, so the next request's KV-cache hit rate drops to zero and
1092
+ * the whole retained suffix re-evaluates. That tradeoff (latency + recompute
1093
+ * for a window that now fits) is sometimes exactly right — long-running
1094
+ * agent threads that would otherwise silently lose their oldest turns — and
1095
+ * sometimes catastrophic (a hot cache-friendly session that never actually
1096
+ * overflows). `ConversationSession.cacheStats` makes the "session no longer
1097
+ * fits" case visible; this function is the explicit, caller-initiated fix.
1098
+ *
1099
+ * Semantics:
1100
+ *
1101
+ * - **Leading `system` messages are always retained** and count against
1102
+ * the budget (they consume context too).
1103
+ * - Non-system messages are walked newest → oldest; each is retained while
1104
+ * it fits the remaining budget, or while fewer than `minTailMessages`
1105
+ * have been retained.
1106
+ * - Mid-conversation `system`/`tool` messages are ordinary tailable
1107
+ * history (only the *leading* system block is pinned).
1108
+ * - Returns a new array; the input is never mutated. Message objects are
1109
+ * reused by reference (sessions freeze them; external arrays are copied
1110
+ * defensively by the caller if needed).
1111
+ *
1112
+ * ```ts
1113
+ * const kept = compactConversationHistory(session.getMessages(), {
1114
+ * maxEstimatedTokens: window.contextLength,
1115
+ * reserveForReply: 512,
1116
+ * });
1117
+ * ```
1118
+ */
1119
+ declare function compactConversationHistory(messages: readonly Message[], options: CompactionOptions): readonly Message[];
1120
+ /** Options for {@link sanitizeHistoryForNextTurn}; all fields optional. */
1121
+ interface HistorySanitizeOptions {
1122
+ /**
1123
+ * Messages — counted from the **end** of the array — whose `images` are
1124
+ * preserved. Default `1`: only the final message keeps its image payload,
1125
+ * which is the rolling one-image window long-running vision chats almost
1126
+ * always want (sanitize after appending the newest turn, and every prior
1127
+ * base64 payload is gone by the next request). `0` evicts images
1128
+ * everywhere; negative values clamp to `0`.
1129
+ */
1130
+ readonly keepImagesOnLastMessages?: number | undefined;
1131
+ /**
1132
+ * Optional note appended to the `content` of each evicted message so the
1133
+ * model can still tell an image used to be there — e.g.
1134
+ * `"[image from an earlier turn removed to save context]"`. When omitted
1135
+ * (default) the `images` field is dropped and the text is untouched,
1136
+ * matching the wire behavior of never having sent the image at all.
1137
+ */
1138
+ readonly imagePlaceholder?: string | undefined;
1139
+ }
1140
+ /**
1141
+ * Vision-history hygiene: returns a copy of `messages` with image payloads
1142
+ * evicted from every message outside the trailing keep-window, conversation
1143
+ * text fully preserved.
1144
+ *
1145
+ * Ollama's `/api/chat` is stateless — each request re-sends and re-bills the
1146
+ * whole history, base64 images included (each one costing real context
1147
+ * tokens via `IMAGE_TOKEN_ESTIMATE` in the pre-flight estimators). A
1148
+ * consumer-managed `messages` array therefore re-uploads every image from
1149
+ * every prior turn on every request, long after the model has already
1150
+ * answered the question that needed them. This helper is the explicit fix:
1151
+ *
1152
+ * ```ts
1153
+ * history.push({ role: 'user', content: 'What is in THIS photo?', images: [nextImage] });
1154
+ * await client.chat({ model, messages: sanitizeHistoryForNextTurn(history) });
1155
+ * ```
1156
+ *
1157
+ * Same philosophy as {@link compactConversationHistory}: caller-initiated,
1158
+ * never automatic. Two deliberate tradeoffs, both documented in ADR 0027:
1159
+ *
1160
+ * - **The model loses access to evicted images** — it keeps the
1161
+ * conversation text (and the optional `imagePlaceholder` note) but can
1162
+ * no longer "see" earlier pictures. That is the point: a bounded
1163
+ * payload in exchange for fading visual memory.
1164
+ * - **KV-prefix invalidation** — evicting rewrites the message at that
1165
+ * position, so the next turn's prompt prefix diverges and the cache
1166
+ * re-evaluates from that point. A hot cache-friendly session shouldn't
1167
+ * sanitize; a growing vision session should.
1168
+ *
1169
+ * Pure: the input array is never mutated, and messages that keep their
1170
+ * images are reused by reference (identity-stable for the untouched ones,
1171
+ * fresh copies only where eviction happened).
1172
+ */
1173
+ declare function sanitizeHistoryForNextTurn(messages: readonly Message[], options?: HistorySanitizeOptions): readonly Message[];
1174
+ /** Options for {@link ConversationSession.compact}; all fields optional. */
1175
+ interface SessionCompactionOptions {
1176
+ /**
1177
+ * Token budget for the compacted history. Defaults to the session's own
1178
+ * window: `options.num_ctx` from the session config, or Ollama's
1179
+ * conservative 2048 fallback when unset — pair with
1180
+ * `client.models.getContextLength()` to program against the real window.
1181
+ */
1182
+ readonly maxEstimatedTokens?: number | undefined;
1183
+ /** Reply reservation; defaults to ~10% of the (derived) budget. */
1184
+ readonly reserveForReply?: number | undefined;
1185
+ /** Trailing messages always kept (default `2`). See {@link CompactionOptions}. */
1186
+ readonly minTailMessages?: number | undefined;
1187
+ }
1188
+ /** What {@link ConversationSession.compact} did. */
1189
+ interface SessionCompactionResult {
1190
+ /** Number of messages dropped from the front of the tailable history. */
1191
+ readonly droppedMessages: number;
1192
+ /** Estimated tokens of the whole history before compaction. */
1193
+ readonly estimatedTokensBefore: number;
1194
+ /** Estimated tokens of the retained history after compaction. */
1195
+ readonly estimatedTokensAfter: number;
1196
+ /** The budget that was applied (after the reply reservation). */
1197
+ readonly effectiveBudget: number;
1198
+ }
907
1199
  /** Per-turn overrides — none of these touch the history prefix, so they are KV-safe. */
908
1200
  interface ConversationSendOptions extends Pick<ChatRequestOptions, 'signal' | 'timeoutMs'> {
909
1201
  readonly options?: ModelOptions | undefined;
@@ -973,6 +1265,23 @@ declare class ConversationSession {
973
1265
  * its frozen messages has no effect on the session.
974
1266
  */
975
1267
  getMessages(): readonly Message[];
1268
+ /**
1269
+ * Compacts the session's history in place: pinned system prompt plus the
1270
+ * most recent turns that fit the budget, oldest tailable turns dropped.
1271
+ *
1272
+ * This is the explicit escape hatch for sessions that outgrew their
1273
+ * window — `cacheStats.hitRate` collapsing toward zero while history grows
1274
+ * is the usual symptom, and the pre-flight warnings on `chat()` fire once
1275
+ * the prompt estimate crosses the threshold. After compaction the next
1276
+ * turn's prompt prefix differs from everything before it, so the KV cache
1277
+ * starts cold — visible as a one-turn `evaluatedTokens` spike in
1278
+ * `cacheStats`. Cumulative cache tallies are deliberately **not** reset:
1279
+ * they are observations, and the spike is part of the story.
1280
+ *
1281
+ * Returns the before/after estimates and the drop count. No-op (zero
1282
+ * drops, equal before/after estimates) when the history already fits.
1283
+ */
1284
+ compact(options?: SessionCompactionOptions): SessionCompactionResult;
976
1285
  /**
977
1286
  * Clears the conversation back to its initial state (system prompt, zero
978
1287
  * turns, zeroed cache statistics). The next turn starts with a cold cache
@@ -981,6 +1290,131 @@ declare class ConversationSession {
981
1290
  reset(): void;
982
1291
  }
983
1292
 
1293
+ /**
1294
+ * Batch-constrained embedding generation for high-volume ingestion pipelines.
1295
+ *
1296
+ * Ollama's `/api/embed` accepts an array of inputs, but two independent limits
1297
+ * make naive usage fragile at corpus scale:
1298
+ *
1299
+ * 1. **Concurrency.** `Promise.all(inputs.map(...))` fires every request at
1300
+ * once; against a local Ollama daemon this contends sockets, saturates the
1301
+ * request queue (`OLLAMA_MAX_QUEUE`, surfacing as 503 `overloaded`), and —
1302
+ * worst case — exhausts GPU VRAM as the model instance serves N prompts
1303
+ * simultaneously.
1304
+ * 2. **Context window.** Ollama truncates any input string longer than the
1305
+ * model's context window (`num_ctx`) *silently* unless `truncate: false` is
1306
+ * set — degraded vectors with no error, no warning, nothing on the wire.
1307
+ *
1308
+ * {@link batchEmbed} addresses the first with a fixed-size worker pool over
1309
+ * `client.embed()` (so every batch still rides the client's full pipeline:
1310
+ * failover, retry, auth, logging, telemetry). The client-level
1311
+ * `embedBatch()` wrapper addresses the second with a pre-flight per-string
1312
+ * token estimate against the resolved `num_ctx` window (see
1313
+ * {@link findOversizedEmbedInputs}).
1314
+ *
1315
+ * Semantics:
1316
+ * - **Order-preserving** — result index `i` is always the embedding of
1317
+ * `input[i]`, regardless of which batch completed first.
1318
+ * - **Fail-fast** — the first batch error aborts every sibling in-flight
1319
+ * batch (surfaces the *original* error, not the derived aborts) and rejects
1320
+ * the whole operation. Vector indexing that half-succeeds is a corrupted
1321
+ * index; callers re-running an ingestion prefer all-or-nothing.
1322
+ * - **Abortable** — the caller's `signal` cancels queued and in-flight batches
1323
+ * alike and rejects with `code: 'aborted'`.
1324
+ */
1325
+
1326
+ /**
1327
+ * Default number of input strings per `/api/embed` request. 32 keeps a single
1328
+ * request's prompt comfortably inside typical embedding-model context windows
1329
+ * (2048+) while amortizing HTTP overhead across the corpus.
1330
+ */
1331
+ declare const DEFAULT_EMBED_BATCH_SIZE = 32;
1332
+ /**
1333
+ * Default number of batches kept in flight concurrently. 3 is deliberately
1334
+ * conservative: a local Ollama daemon serves one model instance per GPU, so
1335
+ * 3 concurrent embedding prompts bound queue depth and VRAM pressure without
1336
+ * leaving the hardware idle between batches. Raise for multi-endpoint
1337
+ * clients or remote daemons; lower (to 1) for constrained devices.
1338
+ */
1339
+ declare const DEFAULT_EMBED_CONCURRENCY = 3;
1340
+ interface EmbedBatchOptions {
1341
+ /** Model to embed with (e.g. `'nomic-embed-text:latest'`). */
1342
+ readonly model: string;
1343
+ /** Input strings, in corpus order. Result index `i` matches `input[i]`. */
1344
+ readonly input: readonly string[];
1345
+ /**
1346
+ * Inputs per `/api/embed` request. Default {@link DEFAULT_EMBED_BATCH_SIZE}
1347
+ * (32). Must be a positive integer.
1348
+ */
1349
+ readonly batchSize?: number | undefined;
1350
+ /**
1351
+ * Batches kept in flight simultaneously. Default
1352
+ * {@link DEFAULT_EMBED_CONCURRENCY} (3). Must be a positive integer.
1353
+ */
1354
+ readonly concurrency?: number | undefined;
1355
+ /** Passed through to each batch request — `false` makes oversized inputs
1356
+ * a server-side error instead of a silent truncation. */
1357
+ readonly truncate?: boolean | undefined;
1358
+ /** Passed through to each batch request (dimensionality truncation). */
1359
+ readonly dimensions?: number | undefined;
1360
+ /** Passed through to each batch request (model unload policy). */
1361
+ readonly keep_alive?: string | number | undefined;
1362
+ /** Ollama model options (`num_ctx`, …) passed through to each batch request. */
1363
+ readonly options?: ModelOptions | undefined;
1364
+ /** Aborts the whole batch operation — queued and in-flight alike. */
1365
+ readonly signal?: AbortSignal | undefined;
1366
+ /** Per-request timeout applied to each individual batch request. */
1367
+ readonly timeoutMs?: number | undefined;
1368
+ /**
1369
+ * Progress callback invoked after each successfully embedded batch as
1370
+ * `onBatchComplete(completedBatches, totalBatches)`. Never called after the
1371
+ * operation rejects.
1372
+ */
1373
+ readonly onBatchComplete?: ((completedBatches: number, totalBatches: number) => void) | undefined;
1374
+ }
1375
+ interface EmbedBatchResult {
1376
+ readonly model: string;
1377
+ /**
1378
+ * Embeddings in input order — `embeddings[i]` corresponds to
1379
+ * `options.input[i]`, independent of batch completion order.
1380
+ */
1381
+ readonly embeddings: readonly (readonly number[])[];
1382
+ /** Number of `/api/embed` requests the corpus was split into. */
1383
+ readonly batchCount: number;
1384
+ }
1385
+ /** An input string whose estimated token count exceeds the context window. */
1386
+ interface OversizedEmbedInput {
1387
+ /** Index into the input array. */
1388
+ readonly index: number;
1389
+ /** Estimated token count for `input[index]`. */
1390
+ readonly estimate: number;
1391
+ }
1392
+ /**
1393
+ * Finds input strings whose heuristic token estimate exceeds `contextLength`
1394
+ * (typically the resolved `num_ctx`). Unlike chat/generate pre-flight there is
1395
+ * no warning margin and no output-token headroom: an embedding request's
1396
+ * prompt IS the whole input, so the exact window is the trip point, and
1397
+ * crossing it means silent truncation (or, with `truncate: false`, a
1398
+ * server-side rejection).
1399
+ */
1400
+ declare function findOversizedEmbedInputs(input: readonly string[], contextLength: number): OversizedEmbedInput[];
1401
+ /**
1402
+ * Builds the warn/throw message for oversized embed inputs (mirrors
1403
+ * `contextWarningMessage` for chat/generate).
1404
+ */
1405
+ declare function embedBatchOverflowMessage(oversized: readonly OversizedEmbedInput[], totalInputs: number, contextLength: number, truncateFalse: boolean): string;
1406
+ /**
1407
+ * Splits `input` into `batchSize` slices and embeds them through `client.embed()`
1408
+ * with at most `concurrency` batches in flight. Order-preserving and fail-fast —
1409
+ * see the module doc for the full semantics.
1410
+ *
1411
+ * Prefer the {@link OllamaClient.embedBatch} wrapper, which additionally
1412
+ * injects `defaultContextLength` as `num_ctx` and enforces the client's
1413
+ * `onContextOverflow` policy against per-string estimates before any request
1414
+ * is sent.
1415
+ */
1416
+ declare function batchEmbed(client: OllamaClient, options: EmbedBatchOptions): Promise<EmbedBatchResult>;
1417
+
984
1418
  /**
985
1419
  * Ergonomic System One types — key-safe generics on top of the generated
986
1420
  * contract models.
@@ -1425,6 +1859,14 @@ declare class OllamaClient {
1425
1859
  private readonly contextWarningThreshold;
1426
1860
  /** Configured overflow policy — `'warn'` (default) or `'throw'`. */
1427
1861
  private readonly onContextOverflow;
1862
+ /**
1863
+ * Controllers for every in-flight request and active stream managed by this
1864
+ * client — see {@link OllamaClient.destroy} and {@link createRequestScope}.
1865
+ * Entries live for exactly as long as the underlying work does: until the
1866
+ * response settles (requests) or the stream's `finalResult` settles
1867
+ * (streams, via `executeWithFailover`'s `holdUntil` release path).
1868
+ */
1869
+ private readonly activeRequests;
1428
1870
  constructor(config?: OllamaClientConfig);
1429
1871
  get modelsClient(): ModelsClient;
1430
1872
  /**
@@ -1523,6 +1965,17 @@ declare class OllamaClient {
1523
1965
  * reference when there's nothing to inject (the common case).
1524
1966
  */
1525
1967
  private injectDefaultContextLength;
1968
+ /**
1969
+ * Creates the per-request abort scope registered with {@link destroy}:
1970
+ * a controller that fires when the caller's `signal` aborts OR when
1971
+ * `destroy()` runs, whichever comes first. `dispose()` removes the
1972
+ * registration when the work it guards settles — for plain requests when
1973
+ * the response settles, for streams when `finalResult` settles (the same
1974
+ * moment `executeWithFailover`'s `holdUntil` releases the endpoint slot),
1975
+ * so a destroy() can tear down a stream mid-consumption but never outlives
1976
+ * one that already finished.
1977
+ */
1978
+ private createRequestScope;
1526
1979
  /**
1527
1980
  * Fail-fast guard for `format` (structured output) requests: throws before any network
1528
1981
  * call if the candidate endpoint is inferred as Ollama Cloud, which does not currently
@@ -1585,6 +2038,36 @@ declare class OllamaClient {
1585
2038
  generateWithSchema<T>(req: Omit<GenerateRequestOptions, 'stream' | 'format'>, schema: z.ZodType<T>): Promise<T>;
1586
2039
  embed(req: EmbedRequestOptions): Promise<EmbedResponse>;
1587
2040
  embedText(model: string, input: string | readonly string[]): Promise<readonly (readonly number[])[]>;
2041
+ /**
2042
+ * Batch-constrained, order-preserving embedding generation for large
2043
+ * corpora — the safe building block for RAG/vector-index ingestion at
2044
+ * scale. Splits `input` into `batchSize` slices and embeds them through
2045
+ * {@link embed} with at most `concurrency` batches in flight (defaults:
2046
+ * 32 × 3), so `Promise.all`-style floods that OOM local daemons and
2047
+ * saturate `OLLAMA_MAX_QUEUE` can't happen by accident. Fail-fast: the
2048
+ * first batch error aborts every sibling in-flight batch and rejects the
2049
+ * whole operation with the original error.
2050
+ *
2051
+ * Before any request is sent, `defaultContextLength` is injected as
2052
+ * `num_ctx` (same as chat/generate) and each input string's token estimate
2053
+ * is checked against the resolved window: Ollama silently truncates
2054
+ * oversized embedding inputs by default, so the client warns (or throws,
2055
+ * under `onContextOverflow: 'throw'`) with the offending indexes — see
2056
+ * `src/embed-batch.ts` for the exact trip point (no margin: an embedding
2057
+ * prompt IS the whole input).
2058
+ *
2059
+ * ```ts
2060
+ * const { embeddings, batchCount } = await client.embedBatch({
2061
+ * model: 'nomic-embed-text:latest',
2062
+ * input: corpus, // e.g. 50k chunks
2063
+ * batchSize: 32, // inputs per /api/embed request
2064
+ * concurrency: 3, // batches in flight
2065
+ * onBatchComplete: (done, total) => progress.log(`${done}/${total}`),
2066
+ * });
2067
+ * // embeddings[i] always corresponds to corpus[i]
2068
+ * ```
2069
+ */
2070
+ embedBatch(options: EmbedBatchOptions): Promise<EmbedBatchResult>;
1588
2071
  /**
1589
2072
  * @deprecated Ollama's `/api/embeddings` endpoint has been superseded by `/api/embed`
1590
2073
  * (exposed here as {@link OllamaClient.embed}), which additionally supports batch
@@ -1598,7 +2081,15 @@ declare class OllamaClient {
1598
2081
  readonly createModel: (r: CreateRequestOptions) => Promise<ProgressResponse | OllamaStream<ProgressResponse, ProgressStreamResult>>;
1599
2082
  readonly deleteModel: (req: DeleteRequestOptions) => Promise<StatusResponse>;
1600
2083
  readonly copyModel: (req: CopyRequestOptions) => Promise<StatusResponse>;
1601
- readonly ps: () => Promise<PsResponse>;
2084
+ readonly ps: (request?: RequestCancellationOptions) => Promise<PsResponse>;
2085
+ /**
2086
+ * Discovers the model's real context window — the allocated window of the
2087
+ * running instance (`/api/ps`), the Modelfile `num_ctx` default, and the
2088
+ * native GGUF maximum (`/api/show` `model_info`), resolved by precedence.
2089
+ * Convenience alias for {@link ModelsClient.getContextLength} — see that
2090
+ * method for the full contract and the precedence order.
2091
+ */
2092
+ readonly getContextLength: (request: ContextDiscoveryRequestOptions) => Promise<DiscoveredContextLength>;
1602
2093
  readonly version: () => Promise<VersionResponse>;
1603
2094
  readonly createBlob: (digest: string, data: BinaryBody) => Promise<void>;
1604
2095
  readonly checkBlob: (digest: string) => Promise<boolean>;
@@ -1710,6 +2201,26 @@ declare class OllamaClient {
1710
2201
  runtimeMode(): RuntimeMode;
1711
2202
  healthCheck(): Promise<EndpointHealthCheckResult[]>;
1712
2203
  endpointStatus(): EndpointHealth[];
2204
+ /**
2205
+ * Immediately aborts every in-flight request and active stream managed by
2206
+ * this client — the clean teardown path for short-lived processes, CLI
2207
+ * runners, and `node:worker_threads` tasks where dangling fetch bodies,
2208
+ * unconsumed stream readers, or queued capacity waiters would otherwise
2209
+ * keep the event loop (and thus the thread/process) alive.
2210
+ *
2211
+ * Every aborted operation rejects with an {@link OllamaAbortError}
2212
+ * (`code: 'aborted'`) carrying `reason` as its message — including requests
2213
+ * still queued behind `maxConcurrentPerEndpoint`, not just dispatched ones.
2214
+ * Streams reject their `finalResult` and release their endpoint slots.
2215
+ *
2216
+ * Idempotent: calling it again (or on a client with nothing in flight)
2217
+ * is a no-op that returns `0`. The client remains usable afterward —
2218
+ * `destroy()` is a drain, not a permanent disable; callers that want a
2219
+ * permanently-dead client should discard the instance.
2220
+ *
2221
+ * @returns the number of in-flight operations that were aborted.
2222
+ */
2223
+ destroy(reason?: string): number;
1713
2224
  get openai(): OpenAICompatClient;
1714
2225
  get anthropic(): AnthropicCompatClient;
1715
2226
  }
@@ -2413,6 +2924,22 @@ interface AgentConfig {
2413
2924
  readonly maxIterations?: number | undefined;
2414
2925
  /** Maximum total tool calls allowed during one agent run. */
2415
2926
  readonly maxToolCalls?: number | undefined;
2927
+ /**
2928
+ * Cycle detection: maximum executions of one *identical* tool call — same
2929
+ * tool name and same arguments — during a single run. When the model would
2930
+ * exceed this budget (the classic stuck-loop signature: re-emitting the
2931
+ * same call without reacting to its results), the run fails fast with
2932
+ * {@link OllamaAgentToolLoopError} instead of burning the remaining
2933
+ * iterations on the same wasted call.
2934
+ *
2935
+ * Off by default (`undefined`), matching `maxToolCalls`: an explicit,
2936
+ * caller-owned guardrail. Counting is per canonical signature, so
2937
+ * argument key order never splits one repeated call into several
2938
+ * "distinct" ones. Every call handed to the registry counts — failed,
2939
+ * unregistered, and timed-out executions included, since re-calling a
2940
+ * failing tool with unchanged arguments is exactly the loop this catches.
2941
+ */
2942
+ readonly maxRepeatedToolCalls?: number | undefined;
2416
2943
  readonly hooks?: AgentHooks | undefined;
2417
2944
  /** Validate tool support from /api/show before starting a tool-enabled run. Default: true. */
2418
2945
  readonly validateToolCapability?: boolean | undefined;
@@ -2448,6 +2975,18 @@ interface AgentChatClient {
2448
2975
  readonly message: Message;
2449
2976
  }>;
2450
2977
  }
2978
+ /**
2979
+ * Canonical signature of a tool call for cycle detection: `name(args)` with
2980
+ * recursively key-sorted arguments, so the same call re-emitted by a model
2981
+ * in a different argument key order collapses to one signature instead of
2982
+ * counting as two "distinct" calls.
2983
+ *
2984
+ * ```ts
2985
+ * canonicalToolCallSignature({ function: { name: 'search', arguments: { q: 'x' } } })
2986
+ * // 'search({"q":"x"})'
2987
+ * ```
2988
+ */
2989
+ declare function canonicalToolCallSignature(toolCall: ToolCall): string;
2451
2990
  /**
2452
2991
  * Runs a multi-turn `chat` + tool-execution loop until the model responds without
2453
2992
  * requesting further tool calls, or `maxIterations` is exceeded. Tool calls within a
@@ -2464,6 +3003,7 @@ declare class Agent {
2464
3003
  private readonly tools?;
2465
3004
  private readonly maxIterations;
2466
3005
  private readonly maxToolCalls?;
3006
+ private readonly maxRepeatedToolCalls?;
2467
3007
  private readonly hooks?;
2468
3008
  private readonly validateToolCapability;
2469
3009
  private readonly toolContextSize;
@@ -2602,6 +3142,11 @@ declare const IMAGE_TOKEN_ESTIMATE = 300;
2602
3142
  * model-specific tokenizer.
2603
3143
  */
2604
3144
  declare function estimateTokens(text: string): number;
3145
+ /**
3146
+ * Rough per-message framing overhead: role tags, chat-template delimiters.
3147
+ */
3148
+ declare const MESSAGE_OVERHEAD_TOKENS = 4;
3149
+ declare function estimateMessageTokens(message: Message): number;
2605
3150
  /**
2606
3151
  * Estimated prompt tokens for a `/api/chat` request: every message plus
2607
3152
  * tool-schema overhead (each tool definition's JSON schema serializes into
@@ -2657,4 +3202,142 @@ declare const OLLAMA_FALLBACK_CONTEXT_LENGTH = 2048;
2657
3202
  */
2658
3203
  declare function contextWarningMessage(check: ContextCheck, kind: 'chat' | 'generate'): string;
2659
3204
 
2660
- 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_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, 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 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, calculateBackoff, checkChatContext, checkEndpointHealth, checkGenerateContext, contextWarningMessage, createDecision, createOllamaCloudFreeTierQuota, createTimeoutSignal, defineTool, detectModelCapabilities, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateSystemOneRequestBytes, estimateTokens, fetchWithBackoff, 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 };
3205
+ /**
3206
+ * Model-affinity scheduling: keep the daemon's working set small.
3207
+ *
3208
+ * Ollama's concurrency model (per the official FAQ):
3209
+ *
3210
+ * - A model's KV cache scales by `OLLAMA_NUM_PARALLEL × context_length` —
3211
+ * parallel requests to the *same* loaded model are cheap (each gets its
3212
+ * own slot of the already-allocated cache).
3213
+ * - Requests for a *different* model queue until the first goes idle or is
3214
+ * evicted (`OLLAMA_MAX_LOADED_MODELS`, default 3× GPU count) — a swap is
3215
+ * an expensive unload/cold-load cycle: seconds of VRAM churn that a
3216
+ * concurrent multi-model workload pays again and again when it
3217
+ * interleaves models arbitrarily.
3218
+ * - Once `OLLAMA_MAX_QUEUE` (default 512) requests are waiting, new ones
3219
+ * are rejected outright with 503s.
3220
+ *
3221
+ * `ModelAffinityScheduler` is the client-side counterpart for workloads that
3222
+ * juggle several models — extraction on a coder model, reasoning on a
3223
+ * thinking model, embeddings for retrieval. It groups tasks into per-model
3224
+ * serial queues and caps how many *distinct* models have work in flight
3225
+ * (`concurrentModels`, default 1), preferring to deepen the currently-active
3226
+ * model over starting the next one — the anti-thrashing order. Candidate
3227
+ * lists let a task run on whichever of several interchangeable models is
3228
+ * already loaded (`GET /api/ps`, cached with a TTL).
3229
+ *
3230
+ * It is a user-space primitive, deliberately not wired into `OllamaClient`'s
3231
+ * request path: affinity decisions are workload policy, and the scheduler is
3232
+ * the foundation the multi-instance routing work builds on. See ADR 0026.
3233
+ */
3234
+
3235
+ /**
3236
+ * The minimal client surface the scheduler needs. `OllamaClient` satisfies it
3237
+ * structurally; tests and custom setups can pass any compatible object.
3238
+ */
3239
+ interface ModelAffinityClient {
3240
+ readonly models: {
3241
+ ps(): Promise<{
3242
+ readonly models: ReadonlyArray<{
3243
+ readonly name: string;
3244
+ }>;
3245
+ }>;
3246
+ };
3247
+ }
3248
+ /** Options for {@link ModelAffinityScheduler}. */
3249
+ interface ModelAffinitySchedulerOptions {
3250
+ /**
3251
+ * Maximum number of *distinct* models with work in flight at once
3252
+ * (default `1`). `1` is the anti-thrashing setting: one model stays hot
3253
+ * until its queue drains, then the next loads. Raise it only when the
3254
+ * host has VRAM for several concurrent models
3255
+ * (`OLLAMA_MAX_LOADED_MODELS`).
3256
+ */
3257
+ readonly concurrentModels?: number | undefined;
3258
+ /**
3259
+ * Parallel tasks allowed per active model (default `1`). Raising this
3260
+ * mirrors the server's own `OLLAMA_NUM_PARALLEL` — same-model parallelism
3261
+ * reuses the loaded weights without swap cost.
3262
+ */
3263
+ readonly perModelConcurrency?: number | undefined;
3264
+ /**
3265
+ * How long a `GET /api/ps` result is reused when choosing among candidate
3266
+ * models (default `30_000` ms). Only multi-candidate `run()` calls and
3267
+ * `loadedModels()` consult the server; single-model tasks never do.
3268
+ */
3269
+ readonly psCacheTtlMs?: number | undefined;
3270
+ }
3271
+ /** Live scheduling state, from {@link ModelAffinityScheduler.stats}. */
3272
+ interface ModelAffinityStats {
3273
+ /** Models with at least one task currently executing. */
3274
+ readonly activeModels: readonly string[];
3275
+ /** Tasks waiting in queues (not yet started). */
3276
+ readonly queuedTasks: number;
3277
+ }
3278
+ /**
3279
+ * Groups async work by model with load-aware dispatch.
3280
+ *
3281
+ * ```ts
3282
+ * const scheduler = new ModelAffinityScheduler(client);
3283
+ *
3284
+ * // Serial queue per model; one model hot at a time (default):
3285
+ * await scheduler.run('deepseek-r1', () => reason(client));
3286
+ * await scheduler.run('qwen2.5:coder', () => extract(client));
3287
+ *
3288
+ * // Either-or: picks whichever candidate is already loaded (/api/ps):
3289
+ * const summary = await scheduler.run(
3290
+ * ['qwen2.5:coder', 'qwen2.5:14b'],
3291
+ * (model) => extractWith(client, model),
3292
+ * );
3293
+ * ```
3294
+ *
3295
+ * Dispatch order is **affinity-sticky, not round-robin**: when a queued
3296
+ * model is already active, its tasks start before a different model's (up to
3297
+ * `perModelConcurrency`), because deepening the hot model avoids a swap.
3298
+ * Tasks run in FIFO order within each model's queue. Task functions are the
3299
+ * caller's own — the scheduler adds no retry, timeout, or failover, and a
3300
+ * rejected task simply rejects its `run()` promise while its siblings
3301
+ * proceed.
3302
+ */
3303
+ declare class ModelAffinityScheduler {
3304
+ private readonly client;
3305
+ private readonly queues;
3306
+ private readonly concurrentModels;
3307
+ private readonly perModelConcurrency;
3308
+ private readonly psCacheTtlMs;
3309
+ private psCache;
3310
+ private readonly drainWaiters;
3311
+ constructor(client: ModelAffinityClient, options?: ModelAffinitySchedulerOptions);
3312
+ /**
3313
+ * Runs `task` on the scheduler, serialized per model. When `model` is a
3314
+ * list of candidates, the already-loaded one is preferred (fresh
3315
+ * `/api/ps` data subject to the TTL cache); with none loaded — or when the
3316
+ * lookup fails — the first candidate runs. The chosen model is handed to
3317
+ * the task.
3318
+ */
3319
+ run<T>(model: string | readonly string[], task: (model: string) => Promise<T>): Promise<T>;
3320
+ /**
3321
+ * Names of the models currently loaded, per `GET /api/ps`, cached for
3322
+ * `psCacheTtlMs`. Degrades to an empty list when the server can't answer
3323
+ * (never throws; candidate selection treats it as "nothing loaded").
3324
+ */
3325
+ loadedModels(): Promise<readonly string[]>;
3326
+ /** Current scheduling state — active models and queued task count. */
3327
+ get stats(): ModelAffinityStats;
3328
+ /**
3329
+ * Resolves once every queue is empty and no task is running. Does not
3330
+ * abort anything — tasks are caller-owned; abort them via their own
3331
+ * signals before disposing if needed.
3332
+ */
3333
+ dispose(): Promise<void>;
3334
+ private chooseCandidate;
3335
+ private refreshPsNames;
3336
+ private pump;
3337
+ private activeModelCount;
3338
+ private execute;
3339
+ }
3340
+ /** Convenience factory — `createModelAffinityScheduler(client, opts)` reads better in setups. */
3341
+ declare function createModelAffinityScheduler(client: ModelAffinityClient | OllamaClient, options?: ModelAffinitySchedulerOptions): ModelAffinityScheduler;
3342
+
3343
+ 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 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, 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, ModelAffinityScheduler, type ModelAffinitySchedulerOptions, 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, 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 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, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };