@nemesis-oss/ollama-sdk 1.19.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +8 -8
  3. package/dist/{anthropic-CAvwqIjx.d.ts → anthropic-BFOC6eI7.d.ts} +7 -1
  4. package/dist/{anthropic-DIPnBwlf.d.cts → anthropic-C2-qaxjI.d.cts} +7 -1
  5. package/dist/{compose-C4QxEZOS.d.cts → compose-BXXgHmrS.d.cts} +1 -1
  6. package/dist/{compose-75UFDzOO.d.ts → compose-DaG83yId.d.ts} +1 -1
  7. package/dist/generated-api.cjs +1 -1
  8. package/dist/generated-api.cjs.map +1 -1
  9. package/dist/generated-api.d.cts +7 -7
  10. package/dist/generated-api.d.ts +7 -7
  11. package/dist/generated-api.js +1 -1
  12. package/dist/generated-api.js.map +1 -1
  13. package/dist/generated-models-schemas.cjs +25 -2
  14. package/dist/generated-models-schemas.cjs.map +1 -1
  15. package/dist/generated-models-schemas.d.cts +97 -5
  16. package/dist/generated-models-schemas.d.ts +97 -5
  17. package/dist/generated-models-schemas.js +24 -3
  18. package/dist/generated-models-schemas.js.map +1 -1
  19. package/dist/generated-runtime.cjs +23 -2
  20. package/dist/generated-runtime.cjs.map +1 -1
  21. package/dist/generated-runtime.d.cts +2 -2
  22. package/dist/generated-runtime.d.ts +2 -2
  23. package/dist/generated-runtime.js +23 -2
  24. package/dist/generated-runtime.js.map +1 -1
  25. package/dist/index.cjs +227 -28
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +169 -46
  28. package/dist/index.d.ts +169 -46
  29. package/dist/index.js +222 -29
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp/tools.json +1 -1
  32. package/dist/mcp-generated.cjs +1 -1
  33. package/dist/mcp-generated.cjs.map +1 -1
  34. package/dist/mcp-generated.d.cts +2 -2
  35. package/dist/mcp-generated.d.ts +2 -2
  36. package/dist/mcp-generated.js +1 -1
  37. package/dist/mcp-generated.js.map +1 -1
  38. package/dist/{runtime-C28YEAHC.d.cts → runtime-CpO3H6yy.d.cts} +55 -2
  39. package/dist/{runtime-DYCxYGG-.d.ts → runtime-DPSrPwAa.d.ts} +55 -2
  40. package/dist/skills.cjs.map +1 -1
  41. package/dist/skills.d.cts +3 -3
  42. package/dist/skills.d.ts +3 -3
  43. package/dist/skills.js.map +1 -1
  44. package/dist/{types-Y3u0FtdN.d.cts → types-CnsZ24tm.d.cts} +17 -1
  45. package/dist/{types-Y3u0FtdN.d.ts → types-CnsZ24tm.d.ts} +17 -1
  46. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -303,6 +303,17 @@ var OllamaToolTimeoutError = class extends OllamaClientError {
303
303
  this.timeoutMs = options.timeoutMs;
304
304
  }
305
305
  };
306
+ var OllamaCloudModelVersionError = class extends OllamaClientError {
307
+ model;
308
+ serverVersion;
309
+ minRequiredVersion;
310
+ constructor(message, options) {
311
+ super(message, { ...options, code: "cloud_model_version", retryable: false });
312
+ this.model = options.model;
313
+ this.serverVersion = options.serverVersion;
314
+ this.minRequiredVersion = options.minRequiredVersion;
315
+ }
316
+ };
306
317
  var OllamaUnsupportedCapabilityError = class extends OllamaClientError {
307
318
  capability;
308
319
  constructor(message, options) {
@@ -428,6 +439,43 @@ function isNetworkLevelError(error) {
428
439
  }
429
440
  return false;
430
441
  }
442
+ function lookupHeader(headers, name) {
443
+ if (headers === void 0) {
444
+ return void 0;
445
+ }
446
+ const needle = name.toLowerCase();
447
+ for (const [key, value] of Object.entries(headers)) {
448
+ if (key.toLowerCase() === needle) {
449
+ return value;
450
+ }
451
+ }
452
+ return void 0;
453
+ }
454
+ var HTTP_DATE_PATTERN = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
455
+ function parseRetryAfterMs(headerValue, now = Date.now()) {
456
+ if (headerValue === void 0) {
457
+ return void 0;
458
+ }
459
+ const trimmed = headerValue.trim();
460
+ if (trimmed === "") {
461
+ return void 0;
462
+ }
463
+ if (/^\d+$/.test(trimmed)) {
464
+ const seconds = Number(trimmed);
465
+ if (!Number.isSafeInteger(seconds)) {
466
+ return void 0;
467
+ }
468
+ return seconds * 1e3;
469
+ }
470
+ if (!HTTP_DATE_PATTERN.test(trimmed)) {
471
+ return void 0;
472
+ }
473
+ const instant = Date.parse(trimmed);
474
+ if (Number.isNaN(instant)) {
475
+ return void 0;
476
+ }
477
+ return Math.max(0, instant - now);
478
+ }
431
479
  function statusToError(status, message, options) {
432
480
  if (status === 401 || status === 403) {
433
481
  return new OllamaAuthError(message, { ...options, status });
@@ -439,7 +487,8 @@ function statusToError(status, message, options) {
439
487
  return new OllamaNotFoundError(message, { ...options, status });
440
488
  }
441
489
  if (status === 429) {
442
- return new OllamaRateLimitError(message, { ...options, status });
490
+ const retryAfterMs = parseRetryAfterMs(lookupHeader(options.response?.headers, "retry-after"));
491
+ return new OllamaRateLimitError(message, { ...options, status, retryAfterMs });
443
492
  }
444
493
  if (status === 502) {
445
494
  return new OllamaBadGatewayError(message, { ...options });
@@ -1142,6 +1191,21 @@ function sameHeaders(actual, expected) {
1142
1191
  const expectedKeys = Object.keys(expectedEntries);
1143
1192
  return actualKeys.length === expectedKeys.length && expectedKeys.every((key) => actualEntries[key] === expectedEntries[key]);
1144
1193
  }
1194
+ function retryAfterHeaderFrom(response) {
1195
+ try {
1196
+ const headers = response?.headers;
1197
+ if (headers === null || headers === void 0 || typeof headers.get !== "function") {
1198
+ return void 0;
1199
+ }
1200
+ const value = headers.get("retry-after");
1201
+ if (value === null || value === "") {
1202
+ return void 0;
1203
+ }
1204
+ return { "retry-after": value };
1205
+ } catch {
1206
+ return void 0;
1207
+ }
1208
+ }
1145
1209
  var HttpClient = class {
1146
1210
  baseUrl;
1147
1211
  apiKey;
@@ -1209,7 +1273,12 @@ var HttpClient = class {
1209
1273
  durationMs: Date.now() - startedAt,
1210
1274
  error: mapError(new Error(`HTTP ${response2.status}`), {
1211
1275
  request: { method: request.method, url: request.url },
1212
- response: { status: response2.status }
1276
+ // Mirrors the middleware path, which reports context.headers:
1277
+ // a 429 lifecycle event carries the server's Retry-After.
1278
+ response: {
1279
+ status: response2.status,
1280
+ headers: retryAfterHeaderFrom(response2)
1281
+ }
1213
1282
  }),
1214
1283
  timestamp: Date.now()
1215
1284
  });
@@ -1315,7 +1384,11 @@ var HttpClient = class {
1315
1384
  const message = typeof errorBody === "object" && errorBody !== null && "error" in errorBody ? String(errorBody.error) : `HTTP ${response.status} ${response.statusText}`;
1316
1385
  throw mapError(new Error(message), {
1317
1386
  request: { method, url },
1318
- response: { status: response.status, body: errorBody }
1387
+ response: {
1388
+ status: response.status,
1389
+ headers: retryAfterHeaderFrom(response),
1390
+ body: errorBody
1391
+ }
1319
1392
  });
1320
1393
  }
1321
1394
  if (options.method === "HEAD" || response.status === 204) {
@@ -1380,7 +1453,11 @@ var HttpClient = class {
1380
1453
  const errorText = await response.text();
1381
1454
  throw mapError(new Error(errorText || `HTTP ${response.status}`), {
1382
1455
  request: { method, url },
1383
- response: { status: response.status, body: errorText }
1456
+ response: {
1457
+ status: response.status,
1458
+ headers: retryAfterHeaderFrom(response),
1459
+ body: errorText
1460
+ }
1384
1461
  });
1385
1462
  }
1386
1463
  if (!response.body) {
@@ -1444,7 +1521,11 @@ var HttpClient = class {
1444
1521
  const errorText = await response.text();
1445
1522
  throw mapError(new Error(errorText || `HTTP ${response.status}`), {
1446
1523
  request: { method, url },
1447
- response: { status: response.status, body: errorText }
1524
+ response: {
1525
+ status: response.status,
1526
+ headers: retryAfterHeaderFrom(response),
1527
+ body: errorText
1528
+ }
1448
1529
  });
1449
1530
  }
1450
1531
  if (!response.body) {
@@ -1750,7 +1831,7 @@ function contextWarningMessage(check2, kind) {
1750
1831
  if (check2.windowSource === "client-default") {
1751
1832
  return `${where}: estimated prompt size ${estimate} is close to or beyond the client's \`defaultContextLength\` (${check2.effectiveContextLength}, injected as \`num_ctx\`). The server will likely silently truncate the top of the conversation \u2014 the model loses earlier turns with no error. Raise \`defaultContextLength\`, set \`options.num_ctx\` per request, or shorten the conversation.${suggestion} Estimates are heuristic (\xB120\u201330%); see \`estimateTokens\`.`;
1752
1833
  }
1753
- return `${where}: estimated prompt size ${estimate} outright exceeds Ollama's conservative server-default context window (${OLLAMA_FALLBACK_CONTEXT_LENGTH}\u20134096 tokens when \`num_ctx\` is unset \u2014 model-dependent). Unless the model's default is larger, the server will silently truncate the top of the conversation \u2014 the model loses earlier turns with no error. Set \`options.num_ctx\` (or the client's \`defaultContextLength\`) to an explicit window to make truncation visible and controllable.${suggestion} Estimates are heuristic (\xB120\u201330%); see \`estimateTokens\`.`;
1834
+ return `${where}: estimated prompt size ${estimate} outright exceeds the worst-case floor for the unset-\`num_ctx\` window (${OLLAMA_FALLBACK_CONTEXT_LENGTH} tokens \u2014 current daemons default higher, tiered by VRAM: 4096 / 32768 / 262144; \`OLLAMA_CONTEXT_LENGTH\` can override either way). If your daemon's effective window is that small, the server will silently truncate the top of the conversation \u2014 the model loses earlier turns with no error. Set \`options.num_ctx\` (or the client's \`defaultContextLength\`) to an explicit window \u2014 or discover the real one via \`client.models.getContextLength()\` \u2014 to make truncation visible and controllable.${suggestion} Estimates are heuristic (\xB120\u201330%); see \`estimateTokens\`.`;
1754
1835
  }
1755
1836
 
1756
1837
  // src/context-discovery.ts
@@ -2478,6 +2559,12 @@ function sleep(ms, signal) {
2478
2559
  signal?.addEventListener("abort", onAbort, { once: true });
2479
2560
  });
2480
2561
  }
2562
+ function retryDelayMs(error, attempt, backoff) {
2563
+ if (error instanceof OllamaRateLimitError && error.retryAfterMs !== void 0) {
2564
+ return error.retryAfterMs;
2565
+ }
2566
+ return calculateBackoff(attempt, backoff);
2567
+ }
2481
2568
  async function withRetry(operation, config = DEFAULT_RETRY_CONFIG, signal) {
2482
2569
  let attempt = 0;
2483
2570
  while (true) {
@@ -2489,7 +2576,7 @@ async function withRetry(operation, config = DEFAULT_RETRY_CONFIG, signal) {
2489
2576
  if (attempt >= config.maxRetries || !shouldRetry(error, attempt)) {
2490
2577
  throw error;
2491
2578
  }
2492
- const delayMs = calculateBackoff(attempt, config.backoff);
2579
+ const delayMs = retryDelayMs(error, attempt, config.backoff);
2493
2580
  config.onRetry?.(error, attempt, delayMs);
2494
2581
  await sleep(delayMs, signal);
2495
2582
  attempt += 1;
@@ -2658,7 +2745,8 @@ var ModelsClient = class {
2658
2745
  }
2659
2746
  /**
2660
2747
  * Discovers the model's real context window from the server — no more
2661
- * 2048/4096 guesswork. Consults, in precedence order:
2748
+ * default-window guesswork (current daemons tier the unset-`num_ctx`
2749
+ * default by VRAM: 4k / 32k / 256k). Consults, in precedence order:
2662
2750
  *
2663
2751
  * 1. `GET /api/ps` — the window the **running instance actually
2664
2752
  * allocated** (exact, reflects the Modelfile `num_ctx` default and
@@ -2670,7 +2758,7 @@ var ModelsClient = class {
2670
2758
  * 3. `POST /api/show` `model_info["<arch>.context_length"]` — the
2671
2759
  * model's **native GGUF maximum** (a ceiling to raise `num_ctx`
2672
2760
  * toward, not the allocated window).
2673
- * 4. Fallback — Ollama's conservative unset-`num_ctx` default (2048).
2761
+ * 4. Fallback — the 2048 worst-case floor for the unset-`num_ctx` window.
2674
2762
  *
2675
2763
  * The result carries every signal it found, so `nativeContextLength:
2676
2764
  * 131072` vs `contextLength: 4096` (a 32× gap seen in the official API
@@ -4156,6 +4244,9 @@ var ShowResponseSchema = zod.z.object({
4156
4244
  modified_at: zod.z.string().optional(),
4157
4245
  details: zod.z.object({}).optional(),
4158
4246
  template: zod.z.string().optional(),
4247
+ renderer: zod.z.string().optional(),
4248
+ parser: zod.z.string().optional(),
4249
+ template_source: zod.z.string().optional(),
4159
4250
  capabilities: zod.z.array(zod.z.string()).optional(),
4160
4251
  model_info: zod.z.object({}).optional()
4161
4252
  });
@@ -4247,9 +4338,26 @@ var UsageMetricsSchema = zod.z.object({
4247
4338
  output_tokens: zod.z.number().optional()
4248
4339
  });
4249
4340
 
4341
+ // src/generated/models/UsageGroup.schema.ts
4342
+ var UsageGroupSchema = zod.z.intersection(
4343
+ UsageMetricsSchema,
4344
+ zod.z.object({
4345
+ model: zod.z.string()
4346
+ })
4347
+ );
4348
+
4349
+ // src/generated/models/UsageSummary.schema.ts
4350
+ var UsageSummarySchema = zod.z.intersection(
4351
+ UsageMetricsSchema,
4352
+ zod.z.object({
4353
+ groups: zod.z.array(UsageGroupSchema).optional(),
4354
+ remainder: UsageMetricsSchema.optional()
4355
+ })
4356
+ );
4357
+
4250
4358
  // src/generated/models/UsageBucket.schema.ts
4251
4359
  var UsageBucketSchema = zod.z.intersection(
4252
- UsageMetricsSchema,
4360
+ UsageSummarySchema,
4253
4361
  zod.z.object({
4254
4362
  from: zod.z.string(),
4255
4363
  until: zod.z.string(),
@@ -4262,9 +4370,10 @@ var UsageResponseSchema = zod.z.object({
4262
4370
  range: zod.z.enum(["24h", "7d", "30d"]),
4263
4371
  scope: zod.z.enum(["self", "team"]),
4264
4372
  granularity: zod.z.enum(["hour", "day"]),
4373
+ group_by: zod.z.enum(["model"]).optional(),
4265
4374
  from: zod.z.string(),
4266
4375
  until: zod.z.string(),
4267
- totals: UsageMetricsSchema,
4376
+ totals: UsageSummarySchema,
4268
4377
  buckets: zod.z.array(UsageBucketSchema)
4269
4378
  });
4270
4379
  var VersionResponseSchema = zod.z.object({
@@ -5707,6 +5816,29 @@ function createDecision(client) {
5707
5816
  };
5708
5817
  }
5709
5818
 
5819
+ // src/system-one.ts
5820
+ var MAX_SYSTEM_ONE_IMAGES_BYTES = 32 * 1024 * 1024;
5821
+ var MAX_SYSTEM_ONE_REQUEST_BYTES = 64 * 1024;
5822
+ var CLOUD_MODEL_SUFFIX = ":cloud";
5823
+ var CLOUD_MODEL_MIN_OLLAMA_VERSION = "0.40.5";
5824
+ function isCloudModel(model) {
5825
+ return model.endsWith(CLOUD_MODEL_SUFFIX);
5826
+ }
5827
+ function compareOllamaVersions(a, b) {
5828
+ const pa = a.split(".").map((s) => Number.parseInt(s, 10) || 0);
5829
+ const pb = b.split(".").map((s) => Number.parseInt(s, 10) || 0);
5830
+ for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
5831
+ const va = pa[i] ?? 0;
5832
+ const vb = pb[i] ?? 0;
5833
+ if (va !== vb) return va - vb;
5834
+ }
5835
+ return 0;
5836
+ }
5837
+ function estimateSystemOneRequestBytes(request, options) {
5838
+ const body = options?.excludeImages && request.images !== void 0 ? { ...request, images: void 0 } : request;
5839
+ return new TextEncoder().encode(JSON.stringify(body)).byteLength;
5840
+ }
5841
+
5710
5842
  // src/client.ts
5711
5843
  var logicalRequestSequence = 0;
5712
5844
  function createLogicalRequestId() {
@@ -6333,9 +6465,13 @@ var OllamaClient = class _OllamaClient {
6333
6465
  * launch): `model` also accepts cloud models such as `clef:cloud` —
6334
6466
  * cloud models require Ollama >= 0.40.5 or the hosted
6335
6467
  * `https://ollama.com` endpoint. Requires Ollama >= 0.35.0 locally.
6336
- * The runtime enforces the version constraint automatically (lazily
6468
+ * Both version constraints are enforced client-side before the request
6469
+ * is sent: the blanket 0.35.0 floor by the generated runtime (lazily
6337
6470
  * fetching `/api/version` when `enforceVersion` is `'auto'` or
6338
- * `'strict'`). Request size is checked client-side as a dual cap:
6471
+ * `'strict'`), and the model-dependent 0.40.5 cloud gate by this
6472
+ * wrapper — a cloud model through a local server older than 0.40.5
6473
+ * throws {@link OllamaCloudModelVersionError} without a round-trip
6474
+ * (ADR 0036). Request size is checked client-side as a dual cap:
6339
6475
  * text and schema (images excluded) within 64 KiB AND the whole
6340
6476
  * request within 32 MiB.
6341
6477
  *
@@ -6360,10 +6496,44 @@ var OllamaClient = class _OllamaClient {
6360
6496
  * `client.native.systemOne(request)` directly.
6361
6497
  */
6362
6498
  async systemOne(request) {
6499
+ await this.assertCloudModelSupported(request.model);
6363
6500
  return this.native.systemOne(
6364
6501
  request
6365
6502
  );
6366
6503
  }
6504
+ /**
6505
+ * Cloud-model version gate for {@link systemOne} — throws
6506
+ * {@link OllamaCloudModelVersionError} when a cloud model is requested
6507
+ * through a local server older than {@link CLOUD_MODEL_MIN_OLLAMA_VERSION}
6508
+ * (0.40.5). No-op for non-cloud models and for cloud-mode clients
6509
+ * (hosted `ollama.com`). The `/api/version` probe goes through the normal
6510
+ * failover machinery and is cached per client instance (reset on failure,
6511
+ * so a later gated call retries), so at most one extra round-trip happens
6512
+ * across all gated calls.
6513
+ */
6514
+ async assertCloudModelSupported(model) {
6515
+ if (!isCloudModel(model)) return;
6516
+ const candidates = this.registry.candidates();
6517
+ const baseUrl = candidates[0]?.baseUrl ?? DEFAULT_BASE_URL;
6518
+ if (inferRuntimeMode(baseUrl) !== "local") return;
6519
+ let version;
6520
+ try {
6521
+ if (this.serverVersionCache === void 0) {
6522
+ this.serverVersionCache = this.native.version().then((v) => v.version);
6523
+ }
6524
+ version = await this.serverVersionCache;
6525
+ } catch {
6526
+ this.serverVersionCache = void 0;
6527
+ return;
6528
+ }
6529
+ if (version && compareOllamaVersions(version, CLOUD_MODEL_MIN_OLLAMA_VERSION) < 0) {
6530
+ throw new OllamaCloudModelVersionError(
6531
+ `System One cloud model "${model}" requires Ollama >= ${CLOUD_MODEL_MIN_OLLAMA_VERSION} when routed through a local server (this server reports ${version}). Upgrade Ollama, or point an endpoint at https://ollama.com.`,
6532
+ { model, serverVersion: version, minRequiredVersion: CLOUD_MODEL_MIN_OLLAMA_VERSION }
6533
+ );
6534
+ }
6535
+ }
6536
+ serverVersionCache;
6367
6537
  // --- Web Endpoints ---
6368
6538
  /**
6369
6539
  * Ollama's hosted web search tool (`POST https://ollama.com/api/web_search`) — a fixed
@@ -6371,7 +6541,9 @@ var OllamaClient = class _OllamaClient {
6371
6541
  * through a local server. Requires an Ollama account API key: pass `apiKey` to the
6372
6542
  * client constructor or set `OLLAMA_API_KEY`. Applies the same default `timeoutMs` and
6373
6543
  * retry policy as inference calls, but never cross-endpoint-fails-over — there is only
6374
- * ever the one cloud endpoint to call.
6544
+ * ever the one cloud endpoint to call. If the service answers 429 with a `Retry-After`
6545
+ * header, the transport parses it into `OllamaRateLimitError.retryAfterMs` and the
6546
+ * retry loop sleeps exactly that long instead of jittered backoff (see ADR 0035).
6375
6547
  */
6376
6548
  async webSearch(req) {
6377
6549
  const { count, max_results, ...rest } = req;
@@ -6402,14 +6574,24 @@ var OllamaClient = class _OllamaClient {
6402
6574
  * web search, and web fetch, bucketed by hour (`range: '24h'`) or day (`'7d'`/`'30d'`).
6403
6575
  *
6404
6576
  * See {@link OllamaClient.webSearch} for the cloud-endpoint/auth/timeout/retry
6405
- * behavior, which applies identically here. Query parameters mirror the official
6406
- * endpoint: `range` (`'24h' | '7d' | '30d'`, server default `'7d'`) and `scope`
6407
- * (`'self' | 'team'`, server default `'self'`; team scope requires a team admin).
6577
+ * behavior, which applies identically here. The pinned contract documents this
6578
+ * endpoint's 429 with a `Retry-After` header ("Seconds to wait before retrying"),
6579
+ * which the SDK parses into `OllamaRateLimitError.retryAfterMs` and honors verbatim
6580
+ * when retrying. Query parameters mirror the official endpoint: `range`
6581
+ * (`'24h' | '7d' | '30d'`, server default `'7d'`), `scope`
6582
+ * (`'self' | 'team'`, server default `'self'`; team scope requires a team
6583
+ * admin), and — since the 2026-10-22 pin (upstream 2476d5e) — `groupBy`
6584
+ * (`group_by=model`), which makes `totals` and every bucket carry up to 10
6585
+ * per-model `UsageGroup` entries plus a `remainder` roll-up, with the
6586
+ * selection echoed back as `group_by` on the response.
6408
6587
  * Omitted options are left unset so the server applies its own defaults.
6409
6588
  *
6410
6589
  * ```ts
6411
- * const usage = await client.usage({ range: '24h' });
6590
+ * const usage = await client.usage({ range: '24h', groupBy: 'model' });
6412
6591
  * console.log(usage.totals.request_count, usage.totals.usage_usd);
6592
+ * for (const group of usage.totals.groups ?? []) {
6593
+ * console.log(group.model, group.usage_usd);
6594
+ * }
6413
6595
  * for (const bucket of usage.buckets) {
6414
6596
  * if (bucket.partial) continue; // current hour, still in progress
6415
6597
  * // ...
@@ -6420,6 +6602,7 @@ var OllamaClient = class _OllamaClient {
6420
6602
  const params = new URLSearchParams();
6421
6603
  if (req.range !== void 0) params.set("range", req.range);
6422
6604
  if (req.scope !== void 0) params.set("scope", req.scope);
6605
+ if (req.groupBy !== void 0) params.set("group_by", req.groupBy);
6423
6606
  const query = params.toString();
6424
6607
  return this.executeCloudRequest(
6425
6608
  (http, signal) => http.request({
@@ -6436,7 +6619,9 @@ var OllamaClient = class _OllamaClient {
6436
6619
  * object (`balance_usd`/`allowance_usd`/`period`) or, on legacy plans, session/weekly
6437
6620
  * percentage limits; the shape is discriminated at runtime by the server response.
6438
6621
  * See {@link OllamaClient.webSearch} for the cloud-endpoint/auth/timeout/retry
6439
- * behavior, which applies identically here.
6622
+ * behavior, which applies identically here. Like `/api/usage`, the pinned contract
6623
+ * documents this endpoint's 429 with a `Retry-After` header, parsed into
6624
+ * `OllamaRateLimitError.retryAfterMs` and honored verbatim when retrying (ADR 0035).
6440
6625
  *
6441
6626
  * ```ts
6442
6627
  * const balance = await client.balance();
@@ -6722,6 +6907,21 @@ function sleep2(ms, signal) {
6722
6907
  signal?.addEventListener("abort", onAbort, { once: true });
6723
6908
  });
6724
6909
  }
6910
+ function retryAfterMsFromResponse(response) {
6911
+ try {
6912
+ const headers = response?.headers;
6913
+ if (headers === null || headers === void 0 || typeof headers.get !== "function") {
6914
+ return void 0;
6915
+ }
6916
+ const value = headers.get("retry-after");
6917
+ if (value === null || value === "") {
6918
+ return void 0;
6919
+ }
6920
+ return parseRetryAfterMs(value);
6921
+ } catch {
6922
+ return void 0;
6923
+ }
6924
+ }
6725
6925
  async function fetchWithBackoff(input, init, config = {}) {
6726
6926
  const fetchImpl = config.fetch ?? globalThis.fetch;
6727
6927
  if (typeof fetchImpl !== "function") {
@@ -6743,7 +6943,8 @@ async function fetchWithBackoff(input, init, config = {}) {
6743
6943
  ...signal !== void 0 ? { signal } : {}
6744
6944
  });
6745
6945
  if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
6746
- const delayMs = calculateBackoff(attempt, backoff);
6946
+ const serverDelayMs = response.status === 429 ? retryAfterMsFromResponse(response) : void 0;
6947
+ const delayMs = serverDelayMs ?? calculateBackoff(attempt, backoff);
6747
6948
  config.onRetry?.(attempt, response.status, delayMs);
6748
6949
  await sleep2(delayMs, signal);
6749
6950
  continue;
@@ -8285,14 +8486,6 @@ function createModelAffinityScheduler(client, options) {
8285
8486
  return new ModelAffinityScheduler(client, options);
8286
8487
  }
8287
8488
 
8288
- // src/system-one.ts
8289
- var MAX_SYSTEM_ONE_IMAGES_BYTES = 32 * 1024 * 1024;
8290
- var MAX_SYSTEM_ONE_REQUEST_BYTES = 64 * 1024;
8291
- function estimateSystemOneRequestBytes(request, options) {
8292
- const body = options?.excludeImages && request.images !== void 0 ? { ...request, images: void 0 } : request;
8293
- return new TextEncoder().encode(JSON.stringify(body)).byteLength;
8294
- }
8295
-
8296
8489
  exports.ATTR_GEN_AI_OPERATION_NAME = ATTR_GEN_AI_OPERATION_NAME;
8297
8490
  exports.ATTR_GEN_AI_REQUEST_MODEL = ATTR_GEN_AI_REQUEST_MODEL;
8298
8491
  exports.ATTR_GEN_AI_RESPONSE_MODEL = ATTR_GEN_AI_RESPONSE_MODEL;
@@ -8313,6 +8506,8 @@ exports.ATTR_URL_FULL = ATTR_URL_FULL;
8313
8506
  exports.Agent = Agent;
8314
8507
  exports.AnthropicCompatClient = AnthropicCompatClient;
8315
8508
  exports.AnthropicMessagesStream = AnthropicMessagesStream;
8509
+ exports.CLOUD_MODEL_MIN_OLLAMA_VERSION = CLOUD_MODEL_MIN_OLLAMA_VERSION;
8510
+ exports.CLOUD_MODEL_SUFFIX = CLOUD_MODEL_SUFFIX;
8316
8511
  exports.ConversationSession = ConversationSession;
8317
8512
  exports.DEFAULT_BACKOFF = DEFAULT_BACKOFF;
8318
8513
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
@@ -8351,6 +8546,7 @@ exports.OllamaAuthError = OllamaAuthError;
8351
8546
  exports.OllamaBadGatewayError = OllamaBadGatewayError;
8352
8547
  exports.OllamaClient = OllamaClient;
8353
8548
  exports.OllamaClientError = OllamaClientError;
8549
+ exports.OllamaCloudModelVersionError = OllamaCloudModelVersionError;
8354
8550
  exports.OllamaGenericClientError = OllamaGenericClientError;
8355
8551
  exports.OllamaIncompatibleModelError = OllamaIncompatibleModelError;
8356
8552
  exports.OllamaMcpError = OllamaMcpError;
@@ -8396,6 +8592,7 @@ exports.checkEndpointHealth = checkEndpointHealth;
8396
8592
  exports.checkGenerateContext = checkGenerateContext;
8397
8593
  exports.compactConversationHistory = compactConversationHistory;
8398
8594
  exports.compareDraftPerformance = compareDraftPerformance;
8595
+ exports.compareOllamaVersions = compareOllamaVersions;
8399
8596
  exports.composeMiddleware = composeMiddleware;
8400
8597
  exports.computeProgressPercent = computeProgressPercent;
8401
8598
  exports.contextWarningMessage = contextWarningMessage;
@@ -8428,6 +8625,7 @@ exports.formatWebFetchResult = formatWebFetchResult;
8428
8625
  exports.formatWebSearchResult = formatWebSearchResult;
8429
8626
  exports.imageStringNeedsResolution = imageStringNeedsResolution;
8430
8627
  exports.inferRuntimeMode = inferRuntimeMode;
8628
+ exports.isCloudModel = isCloudModel;
8431
8629
  exports.isKeepAliveSugar = isKeepAliveSugar;
8432
8630
  exports.isKnownDoneReason = isKnownDoneReason;
8433
8631
  exports.isModelResident = isModelResident;
@@ -8448,6 +8646,7 @@ exports.observeStream = observeStream;
8448
8646
  exports.onProgress = onProgress;
8449
8647
  exports.parseFrontmatter = parseFrontmatter;
8450
8648
  exports.parseNdjsonStream = parseNdjsonStream;
8649
+ exports.parseRetryAfterMs = parseRetryAfterMs;
8451
8650
  exports.parseSseStream = parseSseStream;
8452
8651
  exports.parseStructuredOutput = parseStructuredOutput;
8453
8652
  exports.recommendContextWindow = recommendContextWindow;