@absolutejs/ai 0.2.3 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.2.4 — 2026-09-20
10
+
11
+ ### Added
12
+
13
+ - **Add reviewed per-model reasoning controls, provider translations, and compatible thinking stream replay.** (`MODEL_REASONING`, `REASONING_EFFORTS`, `getModelReasoning`, `isReasoningSelection`, `supportsReasoningSelection`, `reasoningSelectionLabel`, `ModelReasoning`, `ReasoningSelection`, `ReasoningEffort`)
14
+
9
15
  ## 0.2.3 — 2026-09-20
10
16
 
11
17
  ### Internal
package/changelog.json CHANGED
@@ -2,6 +2,27 @@
2
2
  "contract": 1,
3
3
  "name": "@absolutejs/ai",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "kind": "added",
9
+ "summary": "Add reviewed per-model reasoning controls, provider translations, and compatible thinking stream replay.",
10
+ "symbols": [
11
+ "MODEL_REASONING",
12
+ "REASONING_EFFORTS",
13
+ "getModelReasoning",
14
+ "isReasoningSelection",
15
+ "supportsReasoningSelection",
16
+ "reasoningSelectionLabel",
17
+ "ModelReasoning",
18
+ "ReasoningSelection",
19
+ "ReasoningEffort"
20
+ ]
21
+ }
22
+ ],
23
+ "date": "2026-09-20",
24
+ "version": "0.2.4"
25
+ },
5
26
  {
6
27
  "changes": [
7
28
  {
package/dist/ai/index.js CHANGED
@@ -582,17 +582,70 @@ async function* tapStream(source, params, providerName) {
582
582
  }
583
583
  }
584
584
 
585
+ // src/ai/models/reasoning.ts
586
+ var profile = (kind, efforts, description, source) => ({ kind, efforts, description, source });
587
+ var claude = profile("effort", ["low", "medium", "high", "xhigh", "max"], "More thinking for harder problems; higher settings can take longer and use more credits.", "https://platform.claude.com/docs/en/build-with-claude/effort");
588
+ var claude46 = {
589
+ ...claude,
590
+ efforts: ["low", "medium", "high", "max"]
591
+ };
592
+ var haiku = profile("budget", ["none", "low", "medium", "high"], "Thinking-token budget: Low 2,048; Medium 8,192; High 16,384. Actual usage can be lower.", "https://platform.claude.com/docs/en/build-with-claude/extended-thinking");
593
+ var gpt = profile("effort", ["none", "low", "medium", "high", "xhigh", "max"], claude.description, "https://developers.openai.com/api/docs/models/gpt-5.6-sol");
594
+ var gemini = profile("effort", ["low", "medium", "high"], claude.description, "https://ai.google.dev/gemini-api/docs/openai");
595
+ var deepseek = profile("effort", ["none", "low", "high", "max"], claude.description, "https://api-docs.deepseek.com/guides/thinking_mode/");
596
+ var mistral = profile("effort", ["none", "high"], "Minimal thinking or full reasoning. This model does not offer intermediate effort levels.", "https://docs.mistral.ai/studio/conversations/reasoning");
597
+ var qwen = profile("budget", ["none", "low", "medium", "high"], haiku.description, "https://help.aliyun.com/en/model-studio/deep-thinking");
598
+ var MODEL_REASONING = {
599
+ "anthropic:claude-fable-5-1": claude,
600
+ "anthropic:claude-opus-5": claude,
601
+ "anthropic:claude-sonnet-5": claude,
602
+ "anthropic:claude-sonnet-4-6": claude46,
603
+ "anthropic:claude-opus-4-6": claude46,
604
+ "anthropic:claude-haiku-4-5": haiku,
605
+ "openai:gpt-6-astra": {
606
+ ...gpt,
607
+ efforts: ["low", "medium", "high", "xhigh", "max"],
608
+ source: "https://developers.openai.com/api/docs/models/gpt-6-astra"
609
+ },
610
+ "openai:gpt-5.6-sol": gpt,
611
+ "openai:gpt-5.6-terra": gpt,
612
+ "openai:gpt-5.6-luna": gpt,
613
+ "google:gemini-3.8-flash": gemini,
614
+ "google:gemini-3.1-pro-preview": gemini,
615
+ "google:gemini-2.5-flash": profile("budget", ["none", "low", "medium", "high"], "Thinking-token budget: Low 1,024; Medium 8,192; High 24,576.", gemini.source),
616
+ "xai:grok-4.6": profile("effort", ["low", "medium", "high", "xhigh"], "Always reasons. Choose how much thinking it should do.", "https://docs.x.ai/developers/model-capabilities/text/reasoning"),
617
+ "deepseek:deepseek-flash": deepseek,
618
+ "deepseek:deepseek-v4-pro": deepseek,
619
+ "mistral:mistral-medium-3-5": mistral,
620
+ "mistral:mistral-small-latest": mistral,
621
+ "alibaba:qwen3.8-max": qwen,
622
+ "alibaba:qwen3.7-plus": qwen,
623
+ "moonshot:kimi-k3": profile("effort", ["low", "high", "max"], "Always reasons. Choose how much thinking it should do.", "https://platform.kimi.ai/docs/guide/kimi-k3-quickstart"),
624
+ "moonshot:kimi-k2.7-code": profile("fixed", [], "Thinking is always enabled; this model has no documented adjustable effort control.", "https://www.kimi.ai/resources/kimi-k2-7-code")
625
+ };
626
+ var getModelReasoning = (provider, model) => MODEL_REASONING[`${provider}:${model}`];
627
+
585
628
  // src/ai/providers/reasoning.ts
586
629
  var EFFORT_ORDER = [
630
+ "none",
587
631
  "minimal",
588
632
  "low",
589
633
  "medium",
590
634
  "high",
635
+ "xhigh",
591
636
  "max"
592
637
  ];
593
- var ANTHROPIC_EFFORT = [/opus-4-[5-8]/, /sonnet-4-6/, /fable-5/, /mythos-5/];
638
+ var ANTHROPIC_EFFORT = [
639
+ /opus-4-[5-8]/,
640
+ /sonnet-4-6/,
641
+ /fable-5/,
642
+ /mythos-5/,
643
+ /opus-5/,
644
+ /sonnet-5/
645
+ ];
594
646
  var ANTHROPIC_ADAPTIVE_ONLY = [];
595
647
  var ANTHROPIC_LEGACY_THINKING = [
648
+ /haiku-4-5/,
596
649
  /sonnet-4-5/,
597
650
  /sonnet-4-0/,
598
651
  /sonnet-4-2025/,
@@ -601,14 +654,20 @@ var ANTHROPIC_LEGACY_THINKING = [
601
654
  /opus-4-2025/,
602
655
  /3-7-sonnet/
603
656
  ];
604
- var ANTHROPIC_NO_SAMPLING = [/opus-4-[78]/, /fable-5/, /mythos-5/];
657
+ var ANTHROPIC_NO_SAMPLING = [
658
+ /opus-4-[78]/,
659
+ /fable-5/,
660
+ /mythos-5/,
661
+ /opus-5/,
662
+ /sonnet-5/
663
+ ];
605
664
  var ANTHROPIC_MAX_EFFORT = [
606
665
  /opus-4-[678]/,
607
666
  /sonnet-4-6/,
608
667
  /fable-5/,
609
668
  /mythos-5/
610
669
  ];
611
- var OPENAI_REASONING = [/(^|[^a-z])o[1345](-|$)/, /gpt-5/];
670
+ var OPENAI_REASONING = [/(^|[^a-z])o[1345](-|$)/, /gpt-[56]/];
612
671
  var OPENAI_MINIMAL_EFFORT = [/gpt-5/];
613
672
  var matches = (model, patterns) => patterns.some((pattern) => pattern.test(model));
614
673
  var anthropicReasoningMode = (model) => {
@@ -623,6 +682,8 @@ var anthropicReasoningMode = (model) => {
623
682
  var anthropicSupportsSampling = (model) => !matches(model, ANTHROPIC_NO_SAMPLING);
624
683
  var isOpenAIReasoningModel = (model) => matches(model, OPENAI_REASONING);
625
684
  var EFFORT_BUDGET = {
685
+ none: 0,
686
+ xhigh: 24576,
626
687
  high: 16384,
627
688
  low: 2048,
628
689
  max: 32768,
@@ -668,6 +729,9 @@ var anthropicEffortValue = (model, reasoning) => {
668
729
  const effort = resolveEffort(reasoning);
669
730
  if (!effort)
670
731
  return null;
732
+ const known = getModelReasoning("anthropic", model);
733
+ if (known?.kind === "effort")
734
+ return clampEffort(effort, [...known.efforts]);
671
735
  const allowed = matches(model, ANTHROPIC_MAX_EFFORT) ? ["low", "medium", "high", "max"] : ["low", "medium", "high"];
672
736
  return clampEffort(effort, allowed);
673
737
  };
@@ -677,10 +741,50 @@ var openaiEffortValue = (model, reasoning) => {
677
741
  const effort = resolveEffort(reasoning);
678
742
  if (!effort)
679
743
  return null;
744
+ const known = getModelReasoning("openai", model);
745
+ if (known)
746
+ return clampEffort(effort, [...known.efforts]);
680
747
  const allowed = matches(model, OPENAI_MINIMAL_EFFORT) ? ["minimal", "low", "medium", "high"] : ["low", "medium", "high"];
681
748
  const requested = effort === "max" ? "high" : effort;
682
749
  return clampEffort(requested, allowed);
683
750
  };
751
+ var compatibleReasoningBody = (provider, model, reasoning) => {
752
+ if (!reasoning)
753
+ return {};
754
+ const known = getModelReasoning(provider, model);
755
+ const effort = resolveEffort(reasoning);
756
+ if (!known || !effort)
757
+ return {};
758
+ if (!known.efforts.includes(effort))
759
+ throw new Error(`Unsupported reasoning effort ${effort} for ${provider}:${model}`);
760
+ if (provider === "alibaba")
761
+ return effort === "none" ? { enable_thinking: false } : {
762
+ enable_thinking: true,
763
+ thinking_budget: resolveBudgetTokens(reasoning)
764
+ };
765
+ if (provider === "deepseek")
766
+ return effort === "none" ? { thinking: { type: "disabled" } } : { thinking: { type: "enabled" }, reasoning_effort: effort };
767
+ return { reasoning_effort: effort };
768
+ };
769
+ var geminiThinkingConfig = (model, reasoning) => {
770
+ if (!reasoning)
771
+ return;
772
+ const profile2 = getModelReasoning("google", model);
773
+ const effort = resolveEffort(reasoning);
774
+ if (!profile2 || !effort)
775
+ return;
776
+ if (!profile2.efforts.includes(effort))
777
+ throw new Error(`Unsupported reasoning effort ${effort} for ${model}`);
778
+ if (profile2.kind === "effort")
779
+ return { thinkingLevel: effort };
780
+ const budgets = {
781
+ none: 0,
782
+ low: 1024,
783
+ medium: 8192,
784
+ high: 24576
785
+ };
786
+ return { thinkingBudget: reasoning.budgetTokens ?? budgets[effort] };
787
+ };
684
788
 
685
789
  // src/ai/providers/openaiResponses.ts
686
790
  var h2IfHttps = (url) => url.startsWith("https://") ? { protocol: "http2" } : {};
@@ -1314,15 +1418,28 @@ var mapOpenAIContent = (msg) => {
1314
1418
  }
1315
1419
  const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document" || block.type === "audio" || block.type === "video");
1316
1420
  if (!hasMedia) {
1317
- return null;
1421
+ return msg.content.filter((block) => block.type === "text").map((block) => block.content).join("") || null;
1318
1422
  }
1319
1423
  return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
1320
1424
  };
1321
- var buildRequestBody = (params, capabilityModel = params.model) => {
1425
+ var buildRequestBody = (params, capabilityModel = params.model, providerName = "openai") => {
1322
1426
  const messages = params.messages.flatMap((msg) => {
1323
- if (hasArrayContent(msg) && msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result"))
1324
- return buildToolMessages(msg.content);
1325
- return [{ content: mapOpenAIContent(msg), role: msg.role }];
1427
+ const mapped = hasArrayContent(msg) && msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result") ? buildToolMessages(msg.content) : [{ content: mapOpenAIContent(msg), role: msg.role }];
1428
+ if (hasArrayContent(msg) && msg.role === "assistant") {
1429
+ const assistant = mapped.find((message) => message.role === "assistant");
1430
+ if (assistant) {
1431
+ assistant.content = mapOpenAIContent(msg);
1432
+ const thinking = msg.content.filter((block) => block.type === "thinking").map((block) => block.thinking).join("");
1433
+ if (thinking && ["deepseek", "moonshot", "alibaba"].includes(providerName))
1434
+ assistant.reasoning_content = thinking;
1435
+ if (thinking && providerName === "mistral")
1436
+ assistant.content = [
1437
+ { type: "thinking", thinking: [{ type: "text", text: thinking }] },
1438
+ ...typeof assistant.content === "string" && assistant.content ? [{ type: "text", text: assistant.content }] : []
1439
+ ];
1440
+ }
1441
+ }
1442
+ return mapped;
1326
1443
  });
1327
1444
  if (params.systemPrompt) {
1328
1445
  messages.unshift({ content: params.systemPrompt, role: "system" });
@@ -1365,6 +1482,8 @@ var buildRequestBody = (params, capabilityModel = params.model) => {
1365
1482
  if (typeof params.maxTokens === "number")
1366
1483
  body.max_tokens = params.maxTokens;
1367
1484
  }
1485
+ if (providerName !== "openai")
1486
+ Object.assign(body, compatibleReasoningBody(providerName, params.model, params.reasoning));
1368
1487
  if (params.stopSequences && params.stopSequences.length > 0)
1369
1488
  body.stop = params.stopSequences;
1370
1489
  if (typeof params.seed === "number")
@@ -1468,6 +1587,21 @@ var processToolCallDeltas = (toolCalls, pendingToolCalls) => {
1468
1587
  }
1469
1588
  };
1470
1589
  var processDelta = function* (delta, pendingToolCalls) {
1590
+ if (typeof delta.reasoning_content === "string")
1591
+ yield { type: "thinking", content: delta.reasoning_content };
1592
+ if (Array.isArray(delta.content)) {
1593
+ for (const part of delta.content) {
1594
+ if (!isRecord2(part))
1595
+ continue;
1596
+ if (part.type === "text" && typeof part.text === "string")
1597
+ yield { type: "text", content: part.text };
1598
+ if (part.type === "thinking" && Array.isArray(part.thinking)) {
1599
+ for (const thought of part.thinking)
1600
+ if (isRecord2(thought) && typeof thought.text === "string")
1601
+ yield { type: "thinking", content: thought.text };
1602
+ }
1603
+ }
1604
+ }
1471
1605
  if (typeof delta.content === "string") {
1472
1606
  yield { content: delta.content, type: "text" };
1473
1607
  }
@@ -1730,7 +1864,7 @@ var openai = (config2) => {
1730
1864
  body: (params) => buildResponsesRequestBody(params, false, config2.modelForCapabilities?.(params.model) ?? params.model)
1731
1865
  }),
1732
1866
  stream: (params) => {
1733
- const openaiBody = buildRequestBody(params, config2.modelForCapabilities?.(params.model) ?? params.model);
1867
+ const openaiBody = buildRequestBody(params, config2.modelForCapabilities?.(params.model) ?? params.model, providerName);
1734
1868
  const body = config2.transformRequestBody ? config2.transformRequestBody(openaiBody, params) : openaiBody;
1735
1869
  return async function* () {
1736
1870
  const [apiKey, headers] = await Promise.all([
@@ -1748,7 +1882,7 @@ var alibaba = (config2) => openaiCompatible({
1748
1882
  apiKey: config2.apiKey,
1749
1883
  baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode"
1750
1884
  });
1751
- var deepseek = (config2) => openaiCompatible({
1885
+ var deepseek2 = (config2) => openaiCompatible({
1752
1886
  apiKey: config2.apiKey,
1753
1887
  baseUrl: "https://api.deepseek.com"
1754
1888
  });
@@ -1881,6 +2015,9 @@ var buildRequestBody2 = (params, isImageModel) => {
1881
2015
  contents: convertMessages(params.messages)
1882
2016
  };
1883
2017
  const generationConfig = {};
2018
+ const thinkingConfig = geminiThinkingConfig(params.model, params.reasoning);
2019
+ if (thinkingConfig)
2020
+ generationConfig.thinkingConfig = thinkingConfig;
1884
2021
  if (isImageModel) {
1885
2022
  generationConfig.responseModalities = ["TEXT", "IMAGE"];
1886
2023
  }
@@ -2087,7 +2224,7 @@ var resolveImageModels2 = (raw) => {
2087
2224
  }
2088
2225
  return new Set(raw);
2089
2226
  };
2090
- var gemini = (config2) => {
2227
+ var gemini2 = (config2) => {
2091
2228
  const baseUrl = config2.baseUrl ?? DEFAULT_BASE_URL3;
2092
2229
  const imageModels = resolveImageModels2(config2.imageModels);
2093
2230
  const requestCapacity = async (params, suffix, body) => capacityJson(await (config2.fetch ?? fetch)(`${baseUrl}/v1beta/models/${encodeURIComponent(params.model.replace(/^models\//, ""))}${suffix}`, {
@@ -2314,7 +2451,9 @@ var buildRequestBody3 = (params, configuredMax, configCaching) => {
2314
2451
  if (params.stopSequences && params.stopSequences.length > 0) {
2315
2452
  body.stop_sequences = params.stopSequences;
2316
2453
  }
2317
- const mode = params.reasoning ? anthropicReasoningMode(params.model) : "none";
2454
+ const mode = params.reasoning && params.reasoning.effort !== "none" ? anthropicReasoningMode(params.model) : "none";
2455
+ if (params.reasoning?.effort === "none")
2456
+ body.thinking = { type: "disabled" };
2318
2457
  const thinkingActive = mode !== "none";
2319
2458
  if (!thinkingActive && anthropicSupportsSampling(params.model)) {
2320
2459
  if (typeof params.temperature === "number") {
@@ -8686,14 +8825,14 @@ export {
8686
8825
  createSyncConversationStore,
8687
8826
  createUiCards,
8688
8827
  credentialCard,
8689
- deepseek,
8828
+ deepseek2 as deepseek,
8690
8829
  diffCard,
8691
8830
  estimateOpenRouterCost,
8692
8831
  estimateOpenRouterModelCost,
8693
8832
  exchangeOpenRouterAuthCode,
8694
8833
  fetchProviderApiStatus,
8695
8834
  formCard,
8696
- gemini,
8835
+ gemini2 as gemini,
8697
8836
  generateAI,
8698
8837
  generateAIWithTools,
8699
8838
  generateId,
@@ -8749,5 +8888,5 @@ export {
8749
8888
  xai
8750
8889
  };
8751
8890
 
8752
- //# debugId=165C398044F23C2764756E2164756E21
8891
+ //# debugId=C24827A491DCDA4A64756E2164756E21
8753
8892
  //# sourceMappingURL=index.js.map