@nemesis-oss/ollama-sdk 1.12.0 → 1.13.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.cjs CHANGED
@@ -1765,9 +1765,10 @@ var ModelAffinityRouter = class {
1765
1765
  this.now = options.now ?? Date.now;
1766
1766
  }
1767
1767
  /**
1768
- * Synchronously reorders `candidates` so endpoints whose fresh `/api/ps`
1768
+ * Synchronously reorders `candidates` so that, within each priority tier
1769
+ * (run of consecutive equal `priority`), endpoints whose fresh `/api/ps`
1769
1770
  * snapshot shows `model` resident come first (relative order preserved within
1770
- * each group). Returns the input unchanged — and kicks a background refresh —
1771
+ * each group). Never moves an endpoint across tiers. Returns the input unchanged — and kicks a background refresh —
1771
1772
  * when any candidate's snapshot is missing or stale: a wrong reorder is worse
1772
1773
  * than no reorder, and the first request after a cache miss simply isn't
1773
1774
  * reordered. No-op without a `model` or with fewer than two candidates.
@@ -1784,13 +1785,28 @@ var ModelAffinityRouter = class {
1784
1785
  }
1785
1786
  }
1786
1787
  if (!allFresh) return candidates;
1787
- const resident = candidates.filter((ep) => {
1788
+ const isResident = (ep) => {
1788
1789
  const entry = this.cache.get(ep.name);
1789
1790
  return entry !== void 0 && isModelResident(entry.models, model);
1790
- });
1791
- if (resident.length === 0 || resident.length === candidates.length) return candidates;
1792
- const residentSet = new Set(resident.map((ep) => ep.name));
1793
- return [...resident, ...candidates.filter((ep) => !residentSet.has(ep.name))];
1791
+ };
1792
+ const reordered = [];
1793
+ let changed = false;
1794
+ for (let start = 0; start < candidates.length; ) {
1795
+ const tierPriority = candidates[start].priority ?? 0;
1796
+ let end = start + 1;
1797
+ while (end < candidates.length && (candidates[end].priority ?? 0) === tierPriority) end++;
1798
+ const tier = candidates.slice(start, end);
1799
+ const resident = tier.filter(isResident);
1800
+ if (resident.length === 0 || resident.length === tier.length) {
1801
+ reordered.push(...tier);
1802
+ } else {
1803
+ const tierOrder = [...resident, ...tier.filter((ep) => !isResident(ep))];
1804
+ changed ||= tierOrder.some((ep, i) => ep !== tier[i]);
1805
+ reordered.push(...tierOrder);
1806
+ }
1807
+ start = end;
1808
+ }
1809
+ return changed ? reordered : candidates;
1794
1810
  }
1795
1811
  /**
1796
1812
  * Forces a refresh of the affinity cache for the given endpoints (deduped with
@@ -2835,7 +2851,11 @@ var ModelsClient = class {
2835
2851
  var OpenAIResponsesStreamError = class extends OllamaClientError {
2836
2852
  responsePayload;
2837
2853
  constructor(message, responsePayload) {
2838
- super(message, { code: "openai_responses_stream_error", retryable: false, response: { body: responsePayload } });
2854
+ super(message, {
2855
+ code: "openai_responses_stream_error",
2856
+ retryable: false,
2857
+ response: { body: responsePayload }
2858
+ });
2839
2859
  this.responsePayload = responsePayload;
2840
2860
  }
2841
2861
  };
@@ -2977,7 +2997,9 @@ var OpenAIChatCompletionStream = class {
2977
2997
  } finally {
2978
2998
  if (!completed) {
2979
2999
  this.source.abort?.();
2980
- this.rejectFinal(new OllamaAbortError("OpenAI Chat Completion stream ended before completion"));
3000
+ this.rejectFinal(
3001
+ new OllamaAbortError("OpenAI Chat Completion stream ended before completion")
3002
+ );
2981
3003
  }
2982
3004
  }
2983
3005
  }
@@ -3106,7 +3128,14 @@ var OpenAIResponsesStream = class {
3106
3128
  existing.kind = kind;
3107
3129
  return existing;
3108
3130
  }
3109
- const state = { itemId, kind, content: /* @__PURE__ */ new Map(), summary: /* @__PURE__ */ new Map(), arguments: "", reasoningText: "" };
3131
+ const state = {
3132
+ itemId,
3133
+ kind,
3134
+ content: /* @__PURE__ */ new Map(),
3135
+ summary: /* @__PURE__ */ new Map(),
3136
+ arguments: "",
3137
+ reasoningText: ""
3138
+ };
3110
3139
  outputs.set(outputIndex, state);
3111
3140
  return state;
3112
3141
  };
@@ -3137,9 +3166,33 @@ var OpenAIResponsesStream = class {
3137
3166
  const reconstructedOutput = () => [...outputs.entries()].sort(([a], [b]) => a - b).map(([outputIndex, state]) => {
3138
3167
  const canonical = canonicalOutputItems.get(outputIndex);
3139
3168
  if (canonical) return canonical;
3140
- if (state.kind === "message") return { type: "message", ...state.itemId ? { id: state.itemId } : {}, ...state.status ? { status: state.status } : {}, role: state.role ?? "assistant", ...state.phase ? { phase: state.phase } : {}, content: [...state.content.entries()].sort(([a], [b]) => a - b).map(([, p]) => p) };
3141
- if (state.kind === "function_call") return { type: "function_call", ...state.itemId ? { id: state.itemId } : {}, ...state.callId ? { call_id: state.callId } : {}, ...state.status ? { status: state.status } : {}, name: state.name ?? "", arguments: state.arguments };
3142
- return { type: "reasoning", ...state.itemId ? { id: state.itemId } : {}, ...state.status ? { status: state.status } : {}, ...state.summary.size ? { summary: [...state.summary.entries()].sort(([a], [b]) => a - b).map(([, text]) => ({ type: "summary_text", text })) } : {}, ...state.reasoningText ? { text: state.reasoningText } : {} };
3169
+ if (state.kind === "message")
3170
+ return {
3171
+ type: "message",
3172
+ ...state.itemId ? { id: state.itemId } : {},
3173
+ ...state.status ? { status: state.status } : {},
3174
+ role: state.role ?? "assistant",
3175
+ ...state.phase ? { phase: state.phase } : {},
3176
+ content: [...state.content.entries()].sort(([a], [b]) => a - b).map(([, p]) => p)
3177
+ };
3178
+ if (state.kind === "function_call")
3179
+ return {
3180
+ type: "function_call",
3181
+ ...state.itemId ? { id: state.itemId } : {},
3182
+ ...state.callId ? { call_id: state.callId } : {},
3183
+ ...state.status ? { status: state.status } : {},
3184
+ name: state.name ?? "",
3185
+ arguments: state.arguments
3186
+ };
3187
+ return {
3188
+ type: "reasoning",
3189
+ ...state.itemId ? { id: state.itemId } : {},
3190
+ ...state.status ? { status: state.status } : {},
3191
+ ...state.summary.size ? {
3192
+ summary: [...state.summary.entries()].sort(([a], [b]) => a - b).map(([, text]) => ({ type: "summary_text", text }))
3193
+ } : {},
3194
+ ...state.reasoningText ? { text: state.reasoningText } : {}
3195
+ };
3143
3196
  });
3144
3197
  try {
3145
3198
  for await (const event of this.source) {
@@ -3183,7 +3236,12 @@ var OpenAIResponsesStream = class {
3183
3236
  case "response.content_part.done": {
3184
3237
  const e = payload;
3185
3238
  if (e.part.type === "output_text" && "text" in e.part && typeof e.part.text === "string" || e.part.type === "refusal" && "refusal" in e.part && typeof e.part.refusal === "string") {
3186
- applyContentPart(e.output_index, e.item_id, e.content_index, e.part);
3239
+ applyContentPart(
3240
+ e.output_index,
3241
+ e.item_id,
3242
+ e.content_index,
3243
+ e.part
3244
+ );
3187
3245
  }
3188
3246
  break;
3189
3247
  }
@@ -3191,7 +3249,10 @@ var OpenAIResponsesStream = class {
3191
3249
  const e = payload;
3192
3250
  const s = ensureState(e.output_index, e.item_id, "message");
3193
3251
  const p = s.content.get(e.content_index);
3194
- s.content.set(e.content_index, { type: "output_text", text: (p?.type === "output_text" ? p.text : "") + e.delta });
3252
+ s.content.set(e.content_index, {
3253
+ type: "output_text",
3254
+ text: (p?.type === "output_text" ? p.text : "") + e.delta
3255
+ });
3195
3256
  break;
3196
3257
  }
3197
3258
  case "response.output_text.done": {
@@ -3204,7 +3265,10 @@ var OpenAIResponsesStream = class {
3204
3265
  const e = payload;
3205
3266
  const s = ensureState(e.output_index, e.item_id, "message");
3206
3267
  const p = s.content.get(e.content_index);
3207
- s.content.set(e.content_index, { type: "refusal", refusal: (p?.type === "refusal" ? p.refusal : "") + e.delta });
3268
+ s.content.set(e.content_index, {
3269
+ type: "refusal",
3270
+ refusal: (p?.type === "refusal" ? p.refusal : "") + e.delta
3271
+ });
3208
3272
  break;
3209
3273
  }
3210
3274
  case "response.refusal.done": {
@@ -3266,20 +3330,31 @@ var OpenAIResponsesStream = class {
3266
3330
  const response = terminalFailure ?? terminalIncomplete;
3267
3331
  const status = terminalFailure ? "failed" : "incomplete";
3268
3332
  const detail = response.error?.message ?? response.incomplete_details?.reason ?? "unknown error";
3269
- throw new OpenAIResponsesStreamError("OpenAI Responses stream " + status + ": " + detail, response);
3333
+ throw new OpenAIResponsesStreamError(
3334
+ "OpenAI Responses stream " + status + ": " + detail,
3335
+ response
3336
+ );
3270
3337
  }
3271
3338
  if (!finalResponse) {
3272
- if (!responseMeta) throw new Error("OpenAI Responses stream ended without a terminal response payload");
3339
+ if (!responseMeta)
3340
+ throw new Error("OpenAI Responses stream ended without a terminal response payload");
3273
3341
  if (responseMeta.status === "failed" || responseMeta.status === "incomplete") {
3274
3342
  const detail = responseMeta.error?.message ?? responseMeta.incomplete_details?.reason ?? "unknown error";
3275
- throw new OpenAIResponsesStreamError("OpenAI Responses stream " + responseMeta.status + ": " + detail, responseMeta);
3343
+ throw new OpenAIResponsesStreamError(
3344
+ "OpenAI Responses stream " + responseMeta.status + ": " + detail,
3345
+ responseMeta
3346
+ );
3276
3347
  }
3277
3348
  finalResponse = { ...responseMeta, output: reconstructedOutput(), status: "completed" };
3278
3349
  } else if (finalResponse.status === "failed" || finalResponse.status === "incomplete") {
3279
3350
  const detail = finalResponse.error?.message ?? finalResponse.incomplete_details?.reason ?? "unknown error";
3280
- throw new OpenAIResponsesStreamError("OpenAI Responses stream " + finalResponse.status + ": " + detail, finalResponse);
3351
+ throw new OpenAIResponsesStreamError(
3352
+ "OpenAI Responses stream " + finalResponse.status + ": " + detail,
3353
+ finalResponse
3354
+ );
3281
3355
  }
3282
- if (finalResponse.output.length === 0 && outputs.size > 0) finalResponse = { ...finalResponse, output: reconstructedOutput() };
3356
+ if (finalResponse.output.length === 0 && outputs.size > 0)
3357
+ finalResponse = { ...finalResponse, output: reconstructedOutput() };
3283
3358
  completed = true;
3284
3359
  this.removeAbortListener?.();
3285
3360
  this.removeAbortListener = void 0;
@@ -3304,14 +3379,11 @@ var OpenAICompatClient = class {
3304
3379
  }
3305
3380
  request(operation, model, signal, holdUntil) {
3306
3381
  if (this.runner) {
3307
- return this.runner(
3308
- (http, runnerSignal) => operation(http, runnerSignal),
3309
- {
3310
- ...model !== void 0 ? { model } : {},
3311
- ...signal !== void 0 ? { signal } : {},
3312
- ...holdUntil !== void 0 ? { holdUntil } : {}
3313
- }
3314
- );
3382
+ return this.runner((http, runnerSignal) => operation(http, runnerSignal), {
3383
+ ...model !== void 0 ? { model } : {},
3384
+ ...signal !== void 0 ? { signal } : {},
3385
+ ...holdUntil !== void 0 ? { holdUntil } : {}
3386
+ });
3315
3387
  }
3316
3388
  return operation(this.http, signal);
3317
3389
  }
@@ -3940,7 +4012,7 @@ var SystemOneContentSchema = zod.z.union([
3940
4012
  var SystemOneChoiceQuestionSchema = zod.z.object({
3941
4013
  type: zod.z.enum(["choice"]),
3942
4014
  instructions: SystemOneContentSchema,
3943
- criteria: zod.z.object({}).catchall(zod.z.union([zod.z.string(), zod.z.null()])).refine((v) => Object.keys(v).length >= 2, { message: "must have at least 2 properties" }).refine((v) => Object.keys(v).length <= 26, { message: "must have at most 26 properties" }).refine((v) => Object.keys(v).every((k) => new RegExp("\\S").test(k)), {
4015
+ criteria: zod.z.object({}).catchall(zod.z.union([zod.z.string(), zod.z.null()])).refine((v) => Object.keys(v).length >= 2, { message: "must have at least 2 properties" }).refine((v) => Object.keys(v).length <= 255, { message: "must have at most 255 properties" }).refine((v) => Object.keys(v).every((k) => new RegExp("\\S").test(k)), {
3944
4016
  message: 'property names must match pattern "\\S"'
3945
4017
  })
3946
4018
  });
@@ -4079,6 +4151,7 @@ var requestSchemas = {
4079
4151
  var responseSchemas = {
4080
4152
  balance: BalanceResponseSchema,
4081
4153
  chat: ChatResponseSchema,
4154
+ copy: StatusResponseSchema,
4082
4155
  create: StatusResponseSchema,
4083
4156
  embed: EmbedResponseSchema,
4084
4157
  generate: GenerateResponseSchema,
@@ -4985,6 +5058,7 @@ var copyOp = {
4985
5058
  method: "POST",
4986
5059
  path: "/api/copy",
4987
5060
  request: { $ref: "#/schemas/CopyRequest" },
5061
+ response: { $ref: "#/schemas/StatusResponse" },
4988
5062
  environment: { local: true, cloud: false },
4989
5063
  transport: { mode: "json", streaming: false },
4990
5064
  capabilities: {},
@@ -6871,6 +6945,93 @@ var ToolRegistry = class {
6871
6945
  };
6872
6946
  }
6873
6947
  };
6948
+ var WEB_SEARCH_TOOL_NAME = "web_search";
6949
+ var WEB_FETCH_TOOL_NAME = "web_fetch";
6950
+ var DEFAULT_MAX_OUTPUT_CHARS = 2e3 * 4;
6951
+ var DEFAULT_MAX_LINKS = 25;
6952
+ var TRUNCATION_MARKER = "\u2026[truncated]";
6953
+ var webSearchSchema = zod.z.object({
6954
+ query: zod.z.string().min(1).describe("The search query string"),
6955
+ max_results: zod.z.number().int().min(1).max(10).optional().describe("Maximum results to return (default 5, max 10)")
6956
+ });
6957
+ var webFetchSchema = zod.z.object({
6958
+ url: zod.z.string().min(1).describe("The URL of the web page to fetch")
6959
+ });
6960
+ function positiveInt2(value, fallback, name) {
6961
+ if (value === void 0) return fallback;
6962
+ if (!Number.isInteger(value) || value < 1) {
6963
+ throw new RangeError(`${name} must be a positive integer, received ${String(value)}`);
6964
+ }
6965
+ return value;
6966
+ }
6967
+ function truncate(text, maxChars) {
6968
+ if (text.length <= maxChars) return text;
6969
+ if (maxChars <= TRUNCATION_MARKER.length) return text.slice(0, maxChars);
6970
+ return text.slice(0, maxChars - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
6971
+ }
6972
+ function formatWebSearchResult(res, maxChars) {
6973
+ const results = res.results.map(({ title, url, content }) => ({ title, url, content }));
6974
+ const full = JSON.stringify({ results });
6975
+ if (full.length <= maxChars || results.length === 0) return truncate(full, maxChars);
6976
+ const overhead = JSON.stringify({
6977
+ results: results.map((r) => ({ ...r, content: "" }))
6978
+ }).length;
6979
+ const perResult = Math.max(0, Math.floor((maxChars - overhead) / results.length));
6980
+ const trimmed = JSON.stringify({
6981
+ results: results.map((r) => ({ ...r, content: truncate(r.content, perResult) }))
6982
+ });
6983
+ return trimmed.length <= maxChars ? trimmed : truncate(trimmed, maxChars);
6984
+ }
6985
+ function formatWebFetchResult(res, maxChars, maxLinks = DEFAULT_MAX_LINKS) {
6986
+ const page = {
6987
+ title: res.title ?? "",
6988
+ content: res.content,
6989
+ links: (res.links ?? []).slice(0, maxLinks)
6990
+ };
6991
+ const full = JSON.stringify(page);
6992
+ if (full.length <= maxChars) return full;
6993
+ const overhead = JSON.stringify({ ...page, content: "" }).length;
6994
+ const trimmed = JSON.stringify({
6995
+ ...page,
6996
+ content: truncate(page.content, Math.max(0, maxChars - overhead))
6997
+ });
6998
+ return trimmed.length <= maxChars ? trimmed : truncate(trimmed, maxChars);
6999
+ }
7000
+ function createWebSearchTool(client, options = {}) {
7001
+ const maxChars = positiveInt2(options.maxOutputChars, DEFAULT_MAX_OUTPUT_CHARS, "maxOutputChars");
7002
+ const defaultMaxResults = options.defaultMaxResults === void 0 ? void 0 : Math.min(positiveInt2(options.defaultMaxResults, 5, "defaultMaxResults"), 10);
7003
+ return defineTool({
7004
+ name: WEB_SEARCH_TOOL_NAME,
7005
+ description: "Search the web for current information. Returns a list of results, each with a title, url, and relevant content snippet.",
7006
+ schema: webSearchSchema,
7007
+ timeoutMs: options.timeoutMs,
7008
+ execute: async ({ query, max_results }, { signal }) => {
7009
+ const res = await client.webSearch({
7010
+ query,
7011
+ ...(max_results ?? defaultMaxResults) !== void 0 ? { max_results: max_results ?? defaultMaxResults } : {},
7012
+ ...signal !== void 0 ? { signal } : {}
7013
+ });
7014
+ return formatWebSearchResult(res, maxChars);
7015
+ }
7016
+ });
7017
+ }
7018
+ function createWebFetchTool(client, options = {}) {
7019
+ const maxChars = positiveInt2(options.maxOutputChars, DEFAULT_MAX_OUTPUT_CHARS, "maxOutputChars");
7020
+ const maxLinks = positiveInt2(options.maxLinks, DEFAULT_MAX_LINKS, "maxLinks");
7021
+ return defineTool({
7022
+ name: WEB_FETCH_TOOL_NAME,
7023
+ description: "Fetch a single web page by URL. Returns the page title, its main content, and links found on the page.",
7024
+ schema: webFetchSchema,
7025
+ timeoutMs: options.timeoutMs,
7026
+ execute: async ({ url }, { signal }) => {
7027
+ const res = await client.webFetch({ url, ...signal !== void 0 ? { signal } : {} });
7028
+ return formatWebFetchResult(res, maxChars, maxLinks);
7029
+ }
7030
+ });
7031
+ }
7032
+ function createWebTools(client, options = {}) {
7033
+ return [createWebSearchTool(client, options), createWebFetchTool(client, options)];
7034
+ }
6874
7035
 
6875
7036
  // src/mcp/json-schema.ts
6876
7037
  function label(path) {
@@ -7807,6 +7968,8 @@ exports.RETRYABLE_STATUS_CODES = RETRYABLE_STATUS_CODES;
7807
7968
  exports.ResponsesModule = ResponsesModule;
7808
7969
  exports.ToolCallAccumulator = ToolCallAccumulator;
7809
7970
  exports.ToolRegistry = ToolRegistry;
7971
+ exports.WEB_FETCH_TOOL_NAME = WEB_FETCH_TOOL_NAME;
7972
+ exports.WEB_SEARCH_TOOL_NAME = WEB_SEARCH_TOOL_NAME;
7810
7973
  exports.addSpanEvent = addSpanEvent;
7811
7974
  exports.applySkill = applySkill;
7812
7975
  exports.batchEmbed = batchEmbed;
@@ -7823,6 +7986,9 @@ exports.createDecision = createDecision;
7823
7986
  exports.createModelAffinityScheduler = createModelAffinityScheduler;
7824
7987
  exports.createOllamaCloudFreeTierQuota = createOllamaCloudFreeTierQuota;
7825
7988
  exports.createTimeoutSignal = createTimeoutSignal;
7989
+ exports.createWebFetchTool = createWebFetchTool;
7990
+ exports.createWebSearchTool = createWebSearchTool;
7991
+ exports.createWebTools = createWebTools;
7826
7992
  exports.defineTool = defineTool;
7827
7993
  exports.detectModelCapabilities = detectModelCapabilities;
7828
7994
  exports.embedBatchOverflowMessage = embedBatchOverflowMessage;
@@ -7839,6 +8005,8 @@ exports.fetchWithBackoff = fetchWithBackoff;
7839
8005
  exports.findOversizedEmbedInputs = findOversizedEmbedInputs;
7840
8006
  exports.findRunningModelContextLength = findRunningModelContextLength;
7841
8007
  exports.formatTelemetry = formatTelemetry;
8008
+ exports.formatWebFetchResult = formatWebFetchResult;
8009
+ exports.formatWebSearchResult = formatWebSearchResult;
7842
8010
  exports.imageStringNeedsResolution = imageStringNeedsResolution;
7843
8011
  exports.inferRuntimeMode = inferRuntimeMode;
7844
8012
  exports.isKeepAliveSugar = isKeepAliveSugar;