@h-ai/ai 0.1.0-alpha.39 → 0.1.0-alpha.41

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-OZYROB52.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-OZYROB52.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,87 @@ 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
+ var CONVERSATION_SUMMARY_NAME = "hai_internal_conversation_summary_v1";
1558
+ function completeCompression(result, maxTokens) {
1559
+ if (result.compressedTokens > maxTokens) {
1560
+ return err(
1561
+ HaiAIError.CONTEXT_BUDGET_EXCEEDED,
1562
+ aiM("ai_contextBudgetExceeded", {
1563
+ params: {
1564
+ tokens: result.compressedTokens,
1565
+ budget: maxTokens
1566
+ }
1567
+ })
1568
+ );
1569
+ }
1570
+ return ok(result);
1571
+ }
1572
+ function isGeneratedConversationSummary(message) {
1573
+ return message.role === "system" && message.name === CONVERSATION_SUMMARY_NAME;
1574
+ }
1575
+ function partitionSummaryMessages(messages, preserveSystem) {
1576
+ const isPreservedSystem = (message) => {
1577
+ return preserveSystem && message.role === "system" && !isGeneratedConversationSummary(message);
1578
+ };
1579
+ return {
1580
+ systemMessages: messages.filter(isPreservedSystem),
1581
+ conversationMessages: messages.filter((message) => !isPreservedSystem(message))
1582
+ };
1583
+ }
1584
+ function unitizeNonSystemMessages(messages) {
1585
+ const units = [];
1586
+ for (let index = 0; index < messages.length; index += 1) {
1587
+ const message = messages[index];
1588
+ if (message?.role !== "assistant" || !message.tool_calls?.length) {
1589
+ if (message) {
1590
+ units.push([message]);
1591
+ }
1592
+ continue;
1593
+ }
1594
+ const toolCallIds = new Set(message.tool_calls.map((toolCall) => toolCall.id));
1595
+ const unit = [message];
1596
+ let nextIndex = index + 1;
1597
+ while (nextIndex < messages.length) {
1598
+ const nextMessage = messages[nextIndex];
1599
+ if (nextMessage?.role !== "tool" || !toolCallIds.has(nextMessage.tool_call_id)) {
1600
+ break;
1601
+ }
1602
+ unit.push(nextMessage);
1603
+ nextIndex += 1;
1604
+ }
1605
+ units.push(unit);
1606
+ index = nextIndex - 1;
1607
+ }
1608
+ return units;
1609
+ }
1610
+ function flattenUnits(units) {
1611
+ return units.flatMap((unit) => unit);
1612
+ }
1613
+ function splitProtectedSuffix(messages, preserveLastN) {
1614
+ const units = unitizeNonSystemMessages(messages);
1615
+ let protectedStart = units.length;
1616
+ let protectedMessageCount = 0;
1617
+ const requiredMessageCount = Math.max(0, preserveLastN);
1618
+ while (protectedStart > 0 && protectedMessageCount < requiredMessageCount) {
1619
+ protectedStart -= 1;
1620
+ protectedMessageCount += units[protectedStart]?.length ?? 0;
1621
+ }
1622
+ let latestUserUnitIndex = -1;
1623
+ for (let index = units.length - 1; index >= 0; index -= 1) {
1624
+ if (units[index]?.some((message) => message.role === "user")) {
1625
+ latestUserUnitIndex = index;
1626
+ break;
1627
+ }
1628
+ }
1629
+ if (latestUserUnitIndex >= 0) {
1630
+ protectedStart = Math.min(protectedStart, latestUserUnitIndex);
1631
+ }
1632
+ return {
1633
+ removableUnits: units.slice(0, protectedStart),
1634
+ protectedUnits: units.slice(protectedStart)
1635
+ };
1636
+ }
1556
1637
  function createCompressOperations(config, token, summary, modelMaxTokens) {
1557
1638
  function resolveMaxTokens(optionMaxTokens) {
1558
1639
  const fromOption = optionMaxTokens ?? config.defaultMaxTokens;
@@ -1570,29 +1651,38 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
1570
1651
  nonSystemMessages.push(msg);
1571
1652
  }
1572
1653
  }
1573
- const preserved = nonSystemMessages.slice(-preserveLastN);
1574
- const result = [...systemMessages, ...preserved];
1575
- const currentTokens = token.estimateMessages(result);
1576
- if (currentTokens <= maxTokens) {
1654
+ const { removableUnits, protectedUnits } = splitProtectedSuffix(nonSystemMessages, preserveLastN);
1655
+ const emptyMessageTokens = token.estimateMessages([]);
1656
+ const estimateUnitTokens = (unit) => {
1657
+ return Math.max(0, token.estimateMessages(unit) - emptyMessageTokens);
1658
+ };
1659
+ let retainedTokens = token.estimateMessages(systemMessages) + protectedUnits.reduce((total, unit) => total + estimateUnitTokens(unit), 0);
1660
+ let retainedStart = removableUnits.length;
1661
+ let retainedUnits = [...protectedUnits];
1662
+ let finalMessages = [...systemMessages, ...flattenUnits(retainedUnits)];
1663
+ if (retainedTokens > maxTokens) {
1577
1664
  return {
1578
- messages: result,
1579
- removedCount: nonSystemMessages.length - preserved.length
1665
+ messages: finalMessages,
1666
+ removedCount: flattenUnits(removableUnits).length
1580
1667
  };
1581
1668
  }
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)
1669
+ for (let index = removableUnits.length - 1; index >= 0; index -= 1) {
1670
+ const unit = removableUnits[index];
1671
+ if (!unit) {
1672
+ continue;
1673
+ }
1674
+ const unitTokens = estimateUnitTokens(unit);
1675
+ if (retainedTokens + unitTokens > maxTokens) {
1588
1676
  break;
1589
- addable.unshift(nonSystemMessages[i]);
1590
- tokens += msgTokens;
1677
+ }
1678
+ retainedTokens += unitTokens;
1679
+ retainedStart = index;
1591
1680
  }
1592
- finalMessages = [...systemMessages, ...addable];
1681
+ retainedUnits = [...removableUnits.slice(retainedStart), ...protectedUnits];
1682
+ finalMessages = [...systemMessages, ...flattenUnits(retainedUnits)];
1593
1683
  return {
1594
1684
  messages: finalMessages,
1595
- removedCount: nonSystemMessages.length - addable.length
1685
+ removedCount: nonSystemMessages.length - flattenUnits(retainedUnits).length
1596
1686
  };
1597
1687
  }
1598
1688
  async function tryCompress(messages, options) {
@@ -1620,25 +1710,25 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
1620
1710
  );
1621
1711
  const compressedTokens = token.estimateMessages(compressed);
1622
1712
  logger8.trace("Sliding window compression completed", { originalTokens, compressedTokens, removedCount });
1623
- return ok({
1713
+ return completeCompression({
1624
1714
  messages: compressed,
1625
1715
  originalTokens,
1626
1716
  compressedTokens,
1627
1717
  removedCount
1628
- });
1718
+ }, maxTokens);
1629
1719
  }
1630
1720
  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);
1721
+ const { systemMessages: systemMessages2, conversationMessages: conversationMessages2 } = partitionSummaryMessages(messages, preserveSystem);
1722
+ const { removableUnits: removableUnits2, protectedUnits: protectedUnits2 } = splitProtectedSuffix(conversationMessages2, preserveLastN);
1723
+ const preserved = flattenUnits(protectedUnits2);
1724
+ const toSummarize2 = flattenUnits(removableUnits2);
1635
1725
  if (toSummarize2.length === 0) {
1636
- return ok({
1726
+ return completeCompression({
1637
1727
  messages: [...messages],
1638
1728
  originalTokens,
1639
1729
  compressedTokens: originalTokens,
1640
1730
  removedCount: 0
1641
- });
1731
+ }, maxTokens);
1642
1732
  }
1643
1733
  const summaryResult = await summary.generate(toSummarize2, { model: options?.summaryModel });
1644
1734
  if (!summaryResult.success)
@@ -1646,19 +1736,20 @@ function createCompressOperations(config, token, summary, modelMaxTokens) {
1646
1736
  const summaryText = summaryResult.data;
1647
1737
  const summaryMessage = {
1648
1738
  role: "system",
1649
- content: `[Conversation Summary]
1739
+ name: CONVERSATION_SUMMARY_NAME,
1740
+ content: `${CONVERSATION_SUMMARY_PREFIX}
1650
1741
  ${summaryText}`
1651
1742
  };
1652
1743
  const compressed = [...systemMessages2, summaryMessage, ...preserved];
1653
1744
  const compressedTokens = token.estimateMessages(compressed);
1654
1745
  logger8.trace("Summary compression completed", { originalTokens, compressedTokens, removedCount: toSummarize2.length });
1655
- return ok({
1746
+ return completeCompression({
1656
1747
  messages: compressed,
1657
1748
  originalTokens,
1658
1749
  compressedTokens,
1659
1750
  removedCount: toSummarize2.length,
1660
1751
  summary: summaryText
1661
- });
1752
+ }, maxTokens);
1662
1753
  }
1663
1754
  const { messages: windowResult, removedCount: windowRemoved } = slidingWindow(
1664
1755
  messages,
@@ -1668,17 +1759,17 @@ ${summaryText}`
1668
1759
  );
1669
1760
  const windowTokens = token.estimateMessages(windowResult);
1670
1761
  if (windowTokens <= maxTokens) {
1671
- return ok({
1762
+ return completeCompression({
1672
1763
  messages: windowResult,
1673
1764
  originalTokens,
1674
1765
  compressedTokens: windowTokens,
1675
1766
  removedCount: windowRemoved
1676
- });
1767
+ }, maxTokens);
1677
1768
  }
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);
1769
+ const { systemMessages, conversationMessages } = partitionSummaryMessages(messages, preserveSystem);
1770
+ const { removableUnits, protectedUnits } = splitProtectedSuffix(conversationMessages, preserveLastN);
1771
+ const preservedMessages = flattenUnits(protectedUnits);
1772
+ const toSummarize = flattenUnits(removableUnits);
1682
1773
  if (toSummarize.length > 0) {
1683
1774
  const summaryResult = await summary.generate(toSummarize, { model: options?.summaryModel });
1684
1775
  if (!summaryResult.success)
@@ -1686,26 +1777,27 @@ ${summaryText}`
1686
1777
  const summaryText = summaryResult.data;
1687
1778
  const summaryMessage = {
1688
1779
  role: "system",
1689
- content: `[Conversation Summary]
1780
+ name: CONVERSATION_SUMMARY_NAME,
1781
+ content: `${CONVERSATION_SUMMARY_PREFIX}
1690
1782
  ${summaryText}`
1691
1783
  };
1692
1784
  const compressed = [...systemMessages, summaryMessage, ...preservedMessages];
1693
1785
  const compressedTokens = token.estimateMessages(compressed);
1694
1786
  logger8.trace("Hybrid compression completed", { originalTokens, compressedTokens, removedCount: toSummarize.length });
1695
- return ok({
1787
+ return completeCompression({
1696
1788
  messages: compressed,
1697
1789
  originalTokens,
1698
1790
  compressedTokens,
1699
1791
  removedCount: toSummarize.length,
1700
1792
  summary: summaryText
1701
- });
1793
+ }, maxTokens);
1702
1794
  }
1703
- return ok({
1795
+ return completeCompression({
1704
1796
  messages: windowResult,
1705
1797
  originalTokens,
1706
1798
  compressedTokens: windowTokens,
1707
1799
  removedCount: windowRemoved
1708
- });
1800
+ }, maxTokens);
1709
1801
  } catch (error) {
1710
1802
  logger8.error("Context compression failed", { error });
1711
1803
  return err(HaiAIError.CONTEXT_COMPRESS_FAILED, aiM("ai_contextCompressFailed", { params: { error: String(error) } }), error);
@@ -1857,7 +1949,7 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
1857
1949
  });
1858
1950
  if (!compressResult.success) {
1859
1951
  logger9.warn("Auto-compression failed, keeping original messages", { error: compressResult.error });
1860
- return ok(void 0);
1952
+ return compressResult;
1861
1953
  }
1862
1954
  if (compressResult.data.summary) {
1863
1955
  state.summaries.push({
@@ -2142,7 +2234,9 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
2142
2234
  const signal = combineSignal2(chatOpts?.signal, internalSignal);
2143
2235
  let streamTurn = null;
2144
2236
  try {
2145
- await manager.addMessage({ role: "user", content: message });
2237
+ const addResult = await manager.addMessage({ role: "user", content: message });
2238
+ if (!addResult.success)
2239
+ throw addResult.error;
2146
2240
  const messagesResult = manager.getMessages();
2147
2241
  if (!messagesResult.success) {
2148
2242
  throw new Error(`Failed to get messages: ${String(messagesResult.error)}`);
@@ -6915,37 +7009,11 @@ function estimateMessagesTokens(messages, tokenRatio = 0.25) {
6915
7009
  let total = 0;
6916
7010
  for (const msg of messages) {
6917
7011
  total += 4;
6918
- const content = getMessageContent(msg);
6919
- total += estimateTextTokens(content, tokenRatio);
7012
+ total += estimateTextTokens(JSON.stringify(msg), tokenRatio);
6920
7013
  }
6921
7014
  total += 2;
6922
7015
  return total;
6923
7016
  }
6924
- function getMessageContent(msg) {
6925
- if (msg.role === "assistant") {
6926
- const c = msg.content;
6927
- if (typeof c === "string")
6928
- return c;
6929
- if (Array.isArray(c))
6930
- return c.filter((p) => p.type === "text").map((p) => p.text ?? "").join(" ");
6931
- return "";
6932
- }
6933
- if (msg.role === "tool") {
6934
- const c = msg.content;
6935
- if (typeof c === "string")
6936
- return c;
6937
- if (Array.isArray(c))
6938
- return c.filter((p) => p.type === "text").map((p) => p.text ?? "").join(" ");
6939
- return "";
6940
- }
6941
- const content = msg.content;
6942
- if (typeof content === "string")
6943
- return content;
6944
- if (Array.isArray(content)) {
6945
- return content.filter((c) => c.type === "text").map((c) => c.text).join(" ");
6946
- }
6947
- return "";
6948
- }
6949
7017
  function createTokenOperations(config) {
6950
7018
  const { tokenRatio } = config;
6951
7019
  return {