@h-ai/ai 0.1.0-alpha.38 → 0.1.0-alpha.40

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.js CHANGED
@@ -1,5 +1,5 @@
1
- import { aiM, AIConfigSchema, MemoryConfigSchema, RetrievalConfigSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, AudioConfigSchema, resolveModelEntry, resolveAudioModel, resolveModelApi } from './chunk-5XL4QDIJ.js';
2
- export { A2AConfigSchema, A2ASkillConfigSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelEntrySchema, ModelScenarioSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, resolveAudioModel, resolveModelApi, resolveModelEntry } from './chunk-5XL4QDIJ.js';
1
+ import { aiM, AIConfigSchema, MemoryConfigSchema, RetrievalConfigSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, AudioConfigSchema, resolveModelEntry, resolveAudioModel, resolveModelApi } from './chunk-DRSZFSCT.js';
2
+ export { A2AConfigSchema, A2ASkillConfigSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelEntrySchema, ModelScenarioSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, resolveAudioModel, resolveModelApi, resolveModelEntry } from './chunk-DRSZFSCT.js';
3
3
  import { HaiAIError } from './chunk-P3GND76X.js';
4
4
  export { HaiAIError } from './chunk-P3GND76X.js';
5
5
  import { core, err, ok } from '@h-ai/core';
@@ -1553,6 +1553,86 @@ function createProvider(name) {
1553
1553
  }
1554
1554
  }
1555
1555
  var logger8 = core.logger.child({ module: "ai", scope: "compress" });
1556
+ var CONVERSATION_SUMMARY_PREFIX = "[Conversation Summary]";
1557
+ function completeCompression(result, maxTokens) {
1558
+ if (result.compressedTokens > maxTokens) {
1559
+ return err(
1560
+ HaiAIError.CONTEXT_BUDGET_EXCEEDED,
1561
+ aiM("ai_contextBudgetExceeded", {
1562
+ params: {
1563
+ tokens: result.compressedTokens,
1564
+ budget: maxTokens
1565
+ }
1566
+ })
1567
+ );
1568
+ }
1569
+ return ok(result);
1570
+ }
1571
+ function isGeneratedConversationSummary(message) {
1572
+ return message.role === "system" && typeof message.content === "string" && message.content.startsWith(CONVERSATION_SUMMARY_PREFIX);
1573
+ }
1574
+ function partitionSummaryMessages(messages, preserveSystem) {
1575
+ const isPreservedSystem = (message) => {
1576
+ return preserveSystem && message.role === "system" && !isGeneratedConversationSummary(message);
1577
+ };
1578
+ return {
1579
+ systemMessages: messages.filter(isPreservedSystem),
1580
+ conversationMessages: messages.filter((message) => !isPreservedSystem(message))
1581
+ };
1582
+ }
1583
+ function unitizeNonSystemMessages(messages) {
1584
+ const units = [];
1585
+ for (let index = 0; index < messages.length; index += 1) {
1586
+ const message = messages[index];
1587
+ if (message?.role !== "assistant" || !message.tool_calls?.length) {
1588
+ if (message) {
1589
+ units.push([message]);
1590
+ }
1591
+ continue;
1592
+ }
1593
+ const toolCallIds = new Set(message.tool_calls.map((toolCall) => toolCall.id));
1594
+ const unit = [message];
1595
+ let nextIndex = index + 1;
1596
+ while (nextIndex < messages.length) {
1597
+ const nextMessage = messages[nextIndex];
1598
+ if (nextMessage?.role !== "tool" || !toolCallIds.has(nextMessage.tool_call_id)) {
1599
+ break;
1600
+ }
1601
+ unit.push(nextMessage);
1602
+ nextIndex += 1;
1603
+ }
1604
+ units.push(unit);
1605
+ index = nextIndex - 1;
1606
+ }
1607
+ return units;
1608
+ }
1609
+ function flattenUnits(units) {
1610
+ return units.flatMap((unit) => unit);
1611
+ }
1612
+ function splitProtectedSuffix(messages, preserveLastN) {
1613
+ const units = unitizeNonSystemMessages(messages);
1614
+ let protectedStart = units.length;
1615
+ let protectedMessageCount = 0;
1616
+ const requiredMessageCount = Math.max(0, preserveLastN);
1617
+ while (protectedStart > 0 && protectedMessageCount < requiredMessageCount) {
1618
+ protectedStart -= 1;
1619
+ protectedMessageCount += units[protectedStart]?.length ?? 0;
1620
+ }
1621
+ let latestUserUnitIndex = -1;
1622
+ for (let index = units.length - 1; index >= 0; index -= 1) {
1623
+ if (units[index]?.some((message) => message.role === "user")) {
1624
+ latestUserUnitIndex = index;
1625
+ break;
1626
+ }
1627
+ }
1628
+ if (latestUserUnitIndex >= 0) {
1629
+ protectedStart = Math.min(protectedStart, latestUserUnitIndex);
1630
+ }
1631
+ return {
1632
+ removableUnits: units.slice(0, protectedStart),
1633
+ protectedUnits: units.slice(protectedStart)
1634
+ };
1635
+ }
1556
1636
  function createCompressOperations(config, token, summary, modelMaxTokens) {
1557
1637
  function resolveMaxTokens(optionMaxTokens) {
1558
1638
  const fromOption = optionMaxTokens ?? config.defaultMaxTokens;
@@ -1570,29 +1650,38 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
1570
1650
  nonSystemMessages.push(msg);
1571
1651
  }
1572
1652
  }
1573
- const preserved = nonSystemMessages.slice(-preserveLastN);
1574
- const result = [...systemMessages, ...preserved];
1575
- const currentTokens = token.estimateMessages(result);
1576
- if (currentTokens <= maxTokens) {
1653
+ const { removableUnits, protectedUnits } = splitProtectedSuffix(nonSystemMessages, preserveLastN);
1654
+ const emptyMessageTokens = token.estimateMessages([]);
1655
+ const estimateUnitTokens = (unit) => {
1656
+ return Math.max(0, token.estimateMessages(unit) - emptyMessageTokens);
1657
+ };
1658
+ let retainedTokens = token.estimateMessages(systemMessages) + protectedUnits.reduce((total, unit) => total + estimateUnitTokens(unit), 0);
1659
+ let retainedStart = removableUnits.length;
1660
+ let retainedUnits = [...protectedUnits];
1661
+ let finalMessages = [...systemMessages, ...flattenUnits(retainedUnits)];
1662
+ if (retainedTokens > maxTokens) {
1577
1663
  return {
1578
- messages: result,
1579
- removedCount: nonSystemMessages.length - preserved.length
1664
+ messages: finalMessages,
1665
+ removedCount: flattenUnits(removableUnits).length
1580
1666
  };
1581
1667
  }
1582
- let finalMessages = [...systemMessages];
1583
- let tokens = token.estimateMessages(finalMessages);
1584
- const addable = [];
1585
- for (let i = nonSystemMessages.length - 1; i >= 0; i--) {
1586
- const msgTokens = token.estimateMessages([nonSystemMessages[i]]);
1587
- if (tokens + msgTokens > maxTokens)
1668
+ for (let index = removableUnits.length - 1; index >= 0; index -= 1) {
1669
+ const unit = removableUnits[index];
1670
+ if (!unit) {
1671
+ continue;
1672
+ }
1673
+ const unitTokens = estimateUnitTokens(unit);
1674
+ if (retainedTokens + unitTokens > maxTokens) {
1588
1675
  break;
1589
- addable.unshift(nonSystemMessages[i]);
1590
- tokens += msgTokens;
1676
+ }
1677
+ retainedTokens += unitTokens;
1678
+ retainedStart = index;
1591
1679
  }
1592
- finalMessages = [...systemMessages, ...addable];
1680
+ retainedUnits = [...removableUnits.slice(retainedStart), ...protectedUnits];
1681
+ finalMessages = [...systemMessages, ...flattenUnits(retainedUnits)];
1593
1682
  return {
1594
1683
  messages: finalMessages,
1595
- removedCount: nonSystemMessages.length - addable.length
1684
+ removedCount: nonSystemMessages.length - flattenUnits(retainedUnits).length
1596
1685
  };
1597
1686
  }
1598
1687
  async function tryCompress(messages, options) {
@@ -1620,25 +1709,25 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
1620
1709
  );
1621
1710
  const compressedTokens = token.estimateMessages(compressed);
1622
1711
  logger8.trace("Sliding window compression completed", { originalTokens, compressedTokens, removedCount });
1623
- return ok({
1712
+ return completeCompression({
1624
1713
  messages: compressed,
1625
1714
  originalTokens,
1626
1715
  compressedTokens,
1627
1716
  removedCount
1628
- });
1717
+ }, maxTokens);
1629
1718
  }
1630
1719
  if (strategy === "summary") {
1631
- const systemMessages2 = preserveSystem ? messages.filter((m) => m.role === "system") : [];
1632
- const nonSystem2 = messages.filter((m) => m.role !== "system" || !preserveSystem);
1633
- const preserved = nonSystem2.slice(-preserveLastN);
1634
- const toSummarize2 = nonSystem2.slice(0, nonSystem2.length - preserveLastN);
1720
+ const { systemMessages: systemMessages2, conversationMessages: conversationMessages2 } = partitionSummaryMessages(messages, preserveSystem);
1721
+ const { removableUnits: removableUnits2, protectedUnits: protectedUnits2 } = splitProtectedSuffix(conversationMessages2, preserveLastN);
1722
+ const preserved = flattenUnits(protectedUnits2);
1723
+ const toSummarize2 = flattenUnits(removableUnits2);
1635
1724
  if (toSummarize2.length === 0) {
1636
- return ok({
1725
+ return completeCompression({
1637
1726
  messages: [...messages],
1638
1727
  originalTokens,
1639
1728
  compressedTokens: originalTokens,
1640
1729
  removedCount: 0
1641
- });
1730
+ }, maxTokens);
1642
1731
  }
1643
1732
  const summaryResult = await summary.generate(toSummarize2, { model: options?.summaryModel });
1644
1733
  if (!summaryResult.success)
@@ -1646,19 +1735,19 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
1646
1735
  const summaryText = summaryResult.data;
1647
1736
  const summaryMessage = {
1648
1737
  role: "system",
1649
- content: `[Conversation Summary]
1738
+ content: `${CONVERSATION_SUMMARY_PREFIX}
1650
1739
  ${summaryText}`
1651
1740
  };
1652
1741
  const compressed = [...systemMessages2, summaryMessage, ...preserved];
1653
1742
  const compressedTokens = token.estimateMessages(compressed);
1654
1743
  logger8.trace("Summary compression completed", { originalTokens, compressedTokens, removedCount: toSummarize2.length });
1655
- return ok({
1744
+ return completeCompression({
1656
1745
  messages: compressed,
1657
1746
  originalTokens,
1658
1747
  compressedTokens,
1659
1748
  removedCount: toSummarize2.length,
1660
1749
  summary: summaryText
1661
- });
1750
+ }, maxTokens);
1662
1751
  }
1663
1752
  const { messages: windowResult, removedCount: windowRemoved } = slidingWindow(
1664
1753
  messages,
@@ -1668,17 +1757,17 @@ ${summaryText}`
1668
1757
  );
1669
1758
  const windowTokens = token.estimateMessages(windowResult);
1670
1759
  if (windowTokens <= maxTokens) {
1671
- return ok({
1760
+ return completeCompression({
1672
1761
  messages: windowResult,
1673
1762
  originalTokens,
1674
1763
  compressedTokens: windowTokens,
1675
1764
  removedCount: windowRemoved
1676
- });
1765
+ }, maxTokens);
1677
1766
  }
1678
- const systemMessages = preserveSystem ? messages.filter((m) => m.role === "system") : [];
1679
- const nonSystem = messages.filter((m) => m.role !== "system" || !preserveSystem);
1680
- const preservedMessages = nonSystem.slice(-preserveLastN);
1681
- const toSummarize = nonSystem.slice(0, nonSystem.length - preserveLastN);
1767
+ const { systemMessages, conversationMessages } = partitionSummaryMessages(messages, preserveSystem);
1768
+ const { removableUnits, protectedUnits } = splitProtectedSuffix(conversationMessages, preserveLastN);
1769
+ const preservedMessages = flattenUnits(protectedUnits);
1770
+ const toSummarize = flattenUnits(removableUnits);
1682
1771
  if (toSummarize.length > 0) {
1683
1772
  const summaryResult = await summary.generate(toSummarize, { model: options?.summaryModel });
1684
1773
  if (!summaryResult.success)
@@ -1686,26 +1775,26 @@ ${summaryText}`
1686
1775
  const summaryText = summaryResult.data;
1687
1776
  const summaryMessage = {
1688
1777
  role: "system",
1689
- content: `[Conversation Summary]
1778
+ content: `${CONVERSATION_SUMMARY_PREFIX}
1690
1779
  ${summaryText}`
1691
1780
  };
1692
1781
  const compressed = [...systemMessages, summaryMessage, ...preservedMessages];
1693
1782
  const compressedTokens = token.estimateMessages(compressed);
1694
1783
  logger8.trace("Hybrid compression completed", { originalTokens, compressedTokens, removedCount: toSummarize.length });
1695
- return ok({
1784
+ return completeCompression({
1696
1785
  messages: compressed,
1697
1786
  originalTokens,
1698
1787
  compressedTokens,
1699
1788
  removedCount: toSummarize.length,
1700
1789
  summary: summaryText
1701
- });
1790
+ }, maxTokens);
1702
1791
  }
1703
- return ok({
1792
+ return completeCompression({
1704
1793
  messages: windowResult,
1705
1794
  originalTokens,
1706
1795
  compressedTokens: windowTokens,
1707
1796
  removedCount: windowRemoved
1708
- });
1797
+ }, maxTokens);
1709
1798
  } catch (error) {
1710
1799
  logger8.error("Context compression failed", { error });
1711
1800
  return err(HaiAIError.CONTEXT_COMPRESS_FAILED, aiM("ai_contextCompressFailed", { params: { error: String(error) } }), error);
@@ -1857,7 +1946,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
1857
1946
  });
1858
1947
  if (!compressResult.success) {
1859
1948
  logger9.warn("Auto-compression failed, keeping original messages", { error: compressResult.error });
1860
- return ok(void 0);
1949
+ return compressResult;
1861
1950
  }
1862
1951
  if (compressResult.data.summary) {
1863
1952
  state.summaries.push({
@@ -2142,7 +2231,9 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
2142
2231
  const signal = combineSignal2(chatOpts?.signal, internalSignal);
2143
2232
  let streamTurn = null;
2144
2233
  try {
2145
- await manager.addMessage({ role: "user", content: message });
2234
+ const addResult = await manager.addMessage({ role: "user", content: message });
2235
+ if (!addResult.success)
2236
+ throw addResult.error;
2146
2237
  const messagesResult = manager.getMessages();
2147
2238
  if (!messagesResult.success) {
2148
2239
  throw new Error(`Failed to get messages: ${String(messagesResult.error)}`);
@@ -4862,7 +4953,8 @@ Only extract memories of these types: ${options.types.join(", ")}`;
4862
4953
  { role: "system", content: options?.systemPrompt ?? MEMORY_EXTRACTION_SYSTEM_PROMPT },
4863
4954
  { role: "user", content: userPrompt }
4864
4955
  ],
4865
- temperature: 0.1
4956
+ temperature: 0.1,
4957
+ signal: options?.signal
4866
4958
  });
4867
4959
  if (!chatResult.success) {
4868
4960
  logger15.warn("Memory extraction LLM call failed", { error: chatResult.error });
@@ -5194,7 +5286,8 @@ async function extractMemories2(context, messages, options) {
5194
5286
  minImportance: options?.minImportance,
5195
5287
  systemPrompt: options?.systemPrompt ?? context.systemPrompt,
5196
5288
  objectId,
5197
- tempModel: options?.tempModel
5289
+ tempModel: options?.tempModel,
5290
+ signal: options?.signal
5198
5291
  });
5199
5292
  if (!extracted.success)
5200
5293
  return extracted;
@@ -5560,7 +5653,8 @@ async function extractAndConsolidate(deps, messages, options) {
5560
5653
  minImportance: options?.minImportance,
5561
5654
  objectId: options?.objectId,
5562
5655
  systemPrompt: options?.systemPrompt ?? deps.systemPrompt,
5563
- tempModel: options?.tempModel
5656
+ tempModel: options?.tempModel,
5657
+ signal: options?.signal
5564
5658
  });
5565
5659
  if (!extractResult.success)
5566
5660
  return extractResult;
@@ -5594,7 +5688,8 @@ async function extractAndConsolidate(deps, messages, options) {
5594
5688
  })
5595
5689
  }
5596
5690
  ],
5597
- temperature: 0.1
5691
+ temperature: 0.1,
5692
+ signal: options?.signal
5598
5693
  });
5599
5694
  if (!consolidationResult.success) {
5600
5695
  return err(HaiAIError.MEMORY_EXTRACT_FAILED, aiM("ai_memoryExtractFailed", { params: { error: String(consolidationResult.error.message) } }), consolidationResult.error);
@@ -6911,37 +7006,11 @@ function estimateMessagesTokens(messages, tokenRatio = 0.25) {
6911
7006
  let total = 0;
6912
7007
  for (const msg of messages) {
6913
7008
  total += 4;
6914
- const content = getMessageContent(msg);
6915
- total += estimateTextTokens(content, tokenRatio);
7009
+ total += estimateTextTokens(JSON.stringify(msg), tokenRatio);
6916
7010
  }
6917
7011
  total += 2;
6918
7012
  return total;
6919
7013
  }
6920
- function getMessageContent(msg) {
6921
- if (msg.role === "assistant") {
6922
- const c = msg.content;
6923
- if (typeof c === "string")
6924
- return c;
6925
- if (Array.isArray(c))
6926
- return c.filter((p) => p.type === "text").map((p) => p.text ?? "").join(" ");
6927
- return "";
6928
- }
6929
- if (msg.role === "tool") {
6930
- const c = msg.content;
6931
- if (typeof c === "string")
6932
- return c;
6933
- if (Array.isArray(c))
6934
- return c.filter((p) => p.type === "text").map((p) => p.text ?? "").join(" ");
6935
- return "";
6936
- }
6937
- const content = msg.content;
6938
- if (typeof content === "string")
6939
- return content;
6940
- if (Array.isArray(content)) {
6941
- return content.filter((c) => c.type === "text").map((c) => c.text).join(" ");
6942
- }
6943
- return "";
6944
- }
6945
7014
  function createTokenOperations(config) {
6946
7015
  const { tokenRatio } = config;
6947
7016
  return {