@h-ai/ai 0.1.0-alpha.39 → 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-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,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)}`);
@@ -6915,37 +7006,11 @@ function estimateMessagesTokens(messages, tokenRatio = 0.25) {
6915
7006
  let total = 0;
6916
7007
  for (const msg of messages) {
6917
7008
  total += 4;
6918
- const content = getMessageContent(msg);
6919
- total += estimateTextTokens(content, tokenRatio);
7009
+ total += estimateTextTokens(JSON.stringify(msg), tokenRatio);
6920
7010
  }
6921
7011
  total += 2;
6922
7012
  return total;
6923
7013
  }
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
7014
  function createTokenOperations(config) {
6950
7015
  const { tokenRatio } = config;
6951
7016
  return {