@prestyj/ai 5.9.0 → 5.10.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/README.md CHANGED
@@ -40,7 +40,7 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
40
40
 
41
41
  | Provider | Models | Notes |
42
42
  |---|---|---|
43
- | `anthropic` | Claude Opus 4.8, Sonnet 5, Haiku 4.5 | Extended thinking, prompt caching, server-side compaction |
43
+ | `anthropic` | Claude Opus 5, Sonnet 5, Haiku 4.5 | Extended thinking, prompt caching, server-side compaction |
44
44
  | `openai` | GPT-4.1, o3, o4-mini | Supports OAuth (codex endpoint) and API keys |
45
45
  | `glm` | GLM-5.1, GLM-4.7 | Z.AI platform, OpenAI-compatible |
46
46
  | `moonshot` | Kimi K3, Kimi K2.7 Code | Moonshot platform, OpenAI-compatible |
package/dist/index.cjs CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  formatErrorForDisplay: () => formatErrorForDisplay,
43
43
  isHardBillingMessage: () => isHardBillingMessage,
44
44
  isUsageLimitError: () => isUsageLimitError,
45
+ localWireModelId: () => localWireModelId,
45
46
  palsuAssistantMessage: () => palsuAssistantMessage,
46
47
  palsuText: () => palsuText,
47
48
  palsuThinking: () => palsuThinking,
@@ -561,6 +562,39 @@ function normalizeRootForAnthropic(schema) {
561
562
  return out;
562
563
  }
563
564
 
565
+ // src/providers/reasoning-field.ts
566
+ var REASONING_FIELD_ALIASES = [
567
+ "reasoning_content",
568
+ "reasoning",
569
+ "reasoning_text"
570
+ ];
571
+ var DEFAULT_REASONING_FIELD = REASONING_FIELD_ALIASES[0];
572
+ function readReasoning(obj) {
573
+ if (!obj) return void 0;
574
+ for (const field of REASONING_FIELD_ALIASES) {
575
+ const value = obj[field];
576
+ if (typeof value === "string" && value) return { field, text: value };
577
+ }
578
+ return void 0;
579
+ }
580
+ function reasoningFieldKey(provider, baseUrl, model) {
581
+ return `${provider}|${baseUrl ?? ""}|${model}`;
582
+ }
583
+ var MAX_REMEMBERED_ENDPOINTS = 64;
584
+ var detectedFields = /* @__PURE__ */ new Map();
585
+ function rememberReasoningField(key, field) {
586
+ if (detectedFields.get(key) === field) return;
587
+ detectedFields.set(key, field);
588
+ while (detectedFields.size > MAX_REMEMBERED_ENDPOINTS) {
589
+ const oldest = detectedFields.keys().next();
590
+ if (oldest.done) break;
591
+ detectedFields.delete(oldest.value);
592
+ }
593
+ }
594
+ function getReasoningField(key) {
595
+ return detectedFields.get(key) ?? DEFAULT_REASONING_FIELD;
596
+ }
597
+
564
598
  // src/providers/transform.ts
565
599
  function hasValidThinkingSignature(part) {
566
600
  return typeof part.signature === "string" && part.signature.trim().length > 0;
@@ -945,12 +979,12 @@ function toAnthropicToolChoice(choice) {
945
979
  return { type: "tool", name: choice.name };
946
980
  }
947
981
  function isAdaptiveThinkingModel(model) {
948
- return /opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-5|fable-5|mythos-5/.test(model);
982
+ return /opus-5|opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-5|fable-5|mythos-5/.test(model);
949
983
  }
950
984
  function toAnthropicThinking(level, maxTokens, model) {
951
985
  if (isAdaptiveThinkingModel(model)) {
952
986
  let effort = level;
953
- if (effort === "xhigh" && !/opus-4-8|opus-4-7/.test(model)) {
987
+ if (effort === "xhigh" && !/opus-5|opus-4-8|opus-4-7/.test(model)) {
954
988
  effort = "high";
955
989
  }
956
990
  return {
@@ -981,6 +1015,7 @@ function remapToolCallId(id, idMap) {
981
1015
  return mapped;
982
1016
  }
983
1017
  function toOpenAIMessages(messages, options) {
1018
+ const reasoningField = options?.reasoningField || DEFAULT_REASONING_FIELD;
984
1019
  const out = [];
985
1020
  const idMap = /* @__PURE__ */ new Map();
986
1021
  const mergeToolResultText = options?.provider === "glm";
@@ -1047,9 +1082,9 @@ function toOpenAIMessages(messages, options) {
1047
1082
  ...hasToolCalls ? { tool_calls: toolCalls } : {}
1048
1083
  };
1049
1084
  if (thinkingParts) {
1050
- assistantMsg.reasoning_content = thinkingParts;
1085
+ assistantMsg[reasoningField] = thinkingParts;
1051
1086
  } else if (options?.thinking && hasToolCalls && options.provider !== "glm") {
1052
- assistantMsg.reasoning_content = " ";
1087
+ assistantMsg[reasoningField] = " ";
1053
1088
  }
1054
1089
  out.push(assistantMsg);
1055
1090
  continue;
@@ -1127,6 +1162,10 @@ function toOpenAIToolChoice(choice) {
1127
1162
  if (choice === "required") return "required";
1128
1163
  return { type: "function", function: { name: choice.name } };
1129
1164
  }
1165
+ function toLocalReasoningEffort(level) {
1166
+ if (level === "max" || level === "ultra" || level === "xhigh") return "max";
1167
+ return level;
1168
+ }
1130
1169
  function toOpenAIReasoningEffort(level, model) {
1131
1170
  const effort = level === "max" || level === "ultra" ? "xhigh" : level;
1132
1171
  if (model.startsWith("fugu") && (effort === "low" || effort === "medium")) {
@@ -1923,7 +1962,9 @@ function streamOpenAI(options) {
1923
1962
  async function* runStream2(options) {
1924
1963
  const providerName = options.provider ?? "openai";
1925
1964
  const useStreaming = options.streaming !== false;
1965
+ const endpointKey = reasoningFieldKey(providerName, options.baseUrl, options.model);
1926
1966
  const client = createClient2(options);
1967
+ const isLocal = options.provider === "local";
1927
1968
  const isKimiK3 = options.provider === "moonshot" && options.model === "kimi-k3";
1928
1969
  const isManagedKimiK3 = isKimiK3 && options.baseUrl?.replace(/\/+$/, "").endsWith("/coding/v1") === true;
1929
1970
  const k3Effort = options.thinking ? toKimiK3Effort(options.thinking) : void 0;
@@ -1946,7 +1987,8 @@ async function* runStream2(options) {
1946
1987
  // disabled K3 must NOT carry placeholder reasoning_content (mirrors the
1947
1988
  // official CLI: reasoning is preserved only while thinking is enabled).
1948
1989
  thinking: isKimiK27 || !!options.thinking,
1949
- supportsImages: options.supportsImages
1990
+ supportsImages: options.supportsImages,
1991
+ reasoningField: getReasoningField(endpointKey)
1950
1992
  });
1951
1993
  const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
1952
1994
  const effectiveTemp = options.temperature ?? defaultTemp;
@@ -1958,7 +2000,7 @@ async function* runStream2(options) {
1958
2000
  ...effectiveTemp != null && !options.thinking && !hasFixedKimiSampling ? { temperature: effectiveTemp } : {},
1959
2001
  ...options.topP != null && !hasFixedKimiSampling ? { top_p: options.topP } : {},
1960
2002
  ...options.stop ? { stop: options.stop } : {},
1961
- ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
2003
+ ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 && !isLocal ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
1962
2004
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
1963
2005
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
1964
2006
  ...useStreaming ? { stream_options: { include_usage: true } } : {}
@@ -1972,6 +2014,11 @@ async function* runStream2(options) {
1972
2014
  paramsAny.prompt_cache_retention = "24h";
1973
2015
  }
1974
2016
  }
2017
+ if (isLocal && options.thinking) {
2018
+ params.reasoning_effort = toLocalReasoningEffort(
2019
+ options.thinking
2020
+ );
2021
+ }
1975
2022
  if (options.provider === "openai" && options.serviceTier) {
1976
2023
  params.service_tier = options.serviceTier;
1977
2024
  }
@@ -2008,8 +2055,8 @@ async function* runStream2(options) {
2008
2055
  const completion = await client.chat.completions.create(params, {
2009
2056
  signal: options.signal ?? void 0
2010
2057
  });
2011
- yield* synthesizeEventsFromCompletion(completion, !!options.thinking);
2012
- return completionToResponse(completion);
2058
+ yield* synthesizeEventsFromCompletion(completion, !!options.thinking, endpointKey);
2059
+ return completionToResponse(completion, endpointKey);
2013
2060
  } catch (err) {
2014
2061
  throw toError2(err, providerName);
2015
2062
  }
@@ -2044,11 +2091,12 @@ async function* runStream2(options) {
2044
2091
  finishReason = choice.finish_reason;
2045
2092
  }
2046
2093
  const delta = choice.delta;
2047
- const reasoningContent = delta.reasoning_content;
2048
- if (typeof reasoningContent === "string" && reasoningContent) {
2049
- thinkingAccum += reasoningContent;
2094
+ const reasoning = readReasoning(delta);
2095
+ if (reasoning) {
2096
+ rememberReasoningField(endpointKey, reasoning.field);
2097
+ thinkingAccum += reasoning.text;
2050
2098
  if (options.thinking) {
2051
- yield { type: "thinking_delta", text: reasoningContent };
2099
+ yield { type: "thinking_delta", text: reasoning.text };
2052
2100
  }
2053
2101
  }
2054
2102
  if (delta.content) {
@@ -2133,16 +2181,17 @@ async function* runStream2(options) {
2133
2181
  yield { type: "done", stopReason };
2134
2182
  return response;
2135
2183
  }
2136
- function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
2184
+ function* synthesizeEventsFromCompletion(completion, thinkingEnabled, endpointKey) {
2137
2185
  const choice = completion.choices?.[0];
2138
2186
  if (!choice) {
2139
2187
  yield { type: "done", stopReason: normalizeOpenAIStopReason(null) };
2140
2188
  return;
2141
2189
  }
2142
2190
  const msg = choice.message;
2143
- const reasoning = msg.reasoning_content;
2144
- if (typeof reasoning === "string" && reasoning && thinkingEnabled) {
2145
- yield { type: "thinking_delta", text: reasoning };
2191
+ const reasoning = readReasoning(msg);
2192
+ if (reasoning) {
2193
+ rememberReasoningField(endpointKey, reasoning.field);
2194
+ if (thinkingEnabled) yield { type: "thinking_delta", text: reasoning.text };
2146
2195
  }
2147
2196
  if (typeof msg.content === "string" && msg.content) {
2148
2197
  yield { type: "text_delta", text: msg.content };
@@ -2170,15 +2219,16 @@ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
2170
2219
  }
2171
2220
  yield { type: "done", stopReason: normalizeOpenAIStopReason(choice.finish_reason ?? null) };
2172
2221
  }
2173
- function completionToResponse(completion) {
2222
+ function completionToResponse(completion, endpointKey) {
2174
2223
  const choice = completion.choices?.[0];
2175
2224
  const contentParts = [];
2176
2225
  let textAccum = "";
2177
2226
  if (choice) {
2178
2227
  const msg = choice.message;
2179
- const reasoning = msg.reasoning_content;
2180
- if (typeof reasoning === "string" && reasoning) {
2181
- contentParts.push({ type: "thinking", text: reasoning });
2228
+ const reasoning = readReasoning(msg);
2229
+ if (reasoning) {
2230
+ rememberReasoningField(endpointKey, reasoning.field);
2231
+ contentParts.push({ type: "thinking", text: reasoning.text });
2182
2232
  }
2183
2233
  if (typeof msg.content === "string" && msg.content) {
2184
2234
  textAccum = msg.content;
@@ -3539,6 +3589,28 @@ providerRegistry.register("minimax", {
3539
3589
  serverTools: void 0
3540
3590
  })
3541
3591
  });
3592
+ function localWireModelId(id) {
3593
+ const match = /^local\/[^/]+\/(.+)$/.exec(id);
3594
+ return match?.[1] ?? id;
3595
+ }
3596
+ providerRegistry.register("local", {
3597
+ // Locally hosted OpenAI-compatible servers (Ollama, LM Studio, llama.cpp,
3598
+ // vLLM). There is no default endpoint: the baseUrl comes from the endpoint
3599
+ // credential the discovery layer wrote, so a missing one is a wiring bug, not
3600
+ // something to paper over with a guess at someone else's port.
3601
+ stream: (options) => {
3602
+ if (!options.baseUrl) {
3603
+ throw new EZCoderAIError(
3604
+ "Local provider requires a baseUrl (e.g. http://127.0.0.1:11434/v1). No local endpoint was resolved for this model \u2014 re-scan for local models."
3605
+ );
3606
+ }
3607
+ return streamOpenAI({
3608
+ ...options,
3609
+ model: localWireModelId(options.model),
3610
+ webSearch: false
3611
+ });
3612
+ }
3613
+ });
3542
3614
  function stream(options) {
3543
3615
  const entry = providerRegistry.get(options.provider);
3544
3616
  if (!entry) {
@@ -3943,6 +4015,7 @@ function registerPalsuProvider(config) {
3943
4015
  formatErrorForDisplay,
3944
4016
  isHardBillingMessage,
3945
4017
  isUsageLimitError,
4018
+ localWireModelId,
3946
4019
  palsuAssistantMessage,
3947
4020
  palsuText,
3948
4021
  palsuThinking,