@witqq/agent-sdk 0.7.0 → 0.8.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.
Files changed (147) hide show
  1. package/README.md +140 -34
  2. package/dist/{types-CqvUAYxt.d.cts → agent-CW9XbmG_.d.ts} +137 -102
  3. package/dist/{types-CqvUAYxt.d.ts → agent-DxY68NZL.d.cts} +137 -102
  4. package/dist/auth/index.cjs +72 -1
  5. package/dist/auth/index.cjs.map +1 -1
  6. package/dist/auth/index.d.cts +21 -154
  7. package/dist/auth/index.d.ts +21 -154
  8. package/dist/auth/index.js +72 -1
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/backends/claude.cjs +480 -261
  11. package/dist/backends/claude.cjs.map +1 -1
  12. package/dist/backends/claude.d.cts +3 -1
  13. package/dist/backends/claude.d.ts +3 -1
  14. package/dist/backends/claude.js +480 -261
  15. package/dist/backends/claude.js.map +1 -1
  16. package/dist/backends/copilot.cjs +329 -97
  17. package/dist/backends/copilot.cjs.map +1 -1
  18. package/dist/backends/copilot.d.cts +12 -4
  19. package/dist/backends/copilot.d.ts +12 -4
  20. package/dist/backends/copilot.js +329 -97
  21. package/dist/backends/copilot.js.map +1 -1
  22. package/dist/backends/vercel-ai.cjs +294 -61
  23. package/dist/backends/vercel-ai.cjs.map +1 -1
  24. package/dist/backends/vercel-ai.d.cts +3 -1
  25. package/dist/backends/vercel-ai.d.ts +3 -1
  26. package/dist/backends/vercel-ai.js +294 -61
  27. package/dist/backends/vercel-ai.js.map +1 -1
  28. package/dist/backends-BSrsBYFn.d.cts +39 -0
  29. package/dist/backends-BSrsBYFn.d.ts +39 -0
  30. package/dist/chat/accumulator.cjs +1 -1
  31. package/dist/chat/accumulator.cjs.map +1 -1
  32. package/dist/chat/accumulator.d.cts +5 -2
  33. package/dist/chat/accumulator.d.ts +5 -2
  34. package/dist/chat/accumulator.js +1 -1
  35. package/dist/chat/accumulator.js.map +1 -1
  36. package/dist/chat/backends.cjs +736 -746
  37. package/dist/chat/backends.cjs.map +1 -1
  38. package/dist/chat/backends.d.cts +10 -6
  39. package/dist/chat/backends.d.ts +10 -6
  40. package/dist/chat/backends.js +736 -725
  41. package/dist/chat/backends.js.map +1 -1
  42. package/dist/chat/context.cjs +50 -0
  43. package/dist/chat/context.cjs.map +1 -1
  44. package/dist/chat/context.d.cts +27 -3
  45. package/dist/chat/context.d.ts +27 -3
  46. package/dist/chat/context.js +50 -0
  47. package/dist/chat/context.js.map +1 -1
  48. package/dist/chat/core.cjs +25 -2
  49. package/dist/chat/core.cjs.map +1 -1
  50. package/dist/chat/core.d.cts +30 -381
  51. package/dist/chat/core.d.ts +30 -381
  52. package/dist/chat/core.js +24 -3
  53. package/dist/chat/core.js.map +1 -1
  54. package/dist/chat/errors.cjs +48 -26
  55. package/dist/chat/errors.cjs.map +1 -1
  56. package/dist/chat/errors.d.cts +6 -31
  57. package/dist/chat/errors.d.ts +6 -31
  58. package/dist/chat/errors.js +48 -25
  59. package/dist/chat/errors.js.map +1 -1
  60. package/dist/chat/events.cjs.map +1 -1
  61. package/dist/chat/events.d.cts +6 -2
  62. package/dist/chat/events.d.ts +6 -2
  63. package/dist/chat/events.js.map +1 -1
  64. package/dist/chat/index.cjs +1199 -1008
  65. package/dist/chat/index.cjs.map +1 -1
  66. package/dist/chat/index.d.cts +35 -10
  67. package/dist/chat/index.d.ts +35 -10
  68. package/dist/chat/index.js +1196 -987
  69. package/dist/chat/index.js.map +1 -1
  70. package/dist/chat/react/theme.css +2517 -0
  71. package/dist/chat/react.cjs +2003 -1153
  72. package/dist/chat/react.cjs.map +1 -1
  73. package/dist/chat/react.d.cts +590 -121
  74. package/dist/chat/react.d.ts +590 -121
  75. package/dist/chat/react.js +1984 -1151
  76. package/dist/chat/react.js.map +1 -1
  77. package/dist/chat/runtime.cjs +401 -186
  78. package/dist/chat/runtime.cjs.map +1 -1
  79. package/dist/chat/runtime.d.cts +92 -28
  80. package/dist/chat/runtime.d.ts +92 -28
  81. package/dist/chat/runtime.js +401 -186
  82. package/dist/chat/runtime.js.map +1 -1
  83. package/dist/chat/server.cjs +2234 -209
  84. package/dist/chat/server.cjs.map +1 -1
  85. package/dist/chat/server.d.cts +451 -90
  86. package/dist/chat/server.d.ts +451 -90
  87. package/dist/chat/server.js +2221 -210
  88. package/dist/chat/server.js.map +1 -1
  89. package/dist/chat/sessions.cjs +25 -43
  90. package/dist/chat/sessions.cjs.map +1 -1
  91. package/dist/chat/sessions.d.cts +37 -118
  92. package/dist/chat/sessions.d.ts +37 -118
  93. package/dist/chat/sessions.js +25 -43
  94. package/dist/chat/sessions.js.map +1 -1
  95. package/dist/chat/sqlite.cjs +441 -0
  96. package/dist/chat/sqlite.cjs.map +1 -0
  97. package/dist/chat/sqlite.d.cts +128 -0
  98. package/dist/chat/sqlite.d.ts +128 -0
  99. package/dist/chat/sqlite.js +435 -0
  100. package/dist/chat/sqlite.js.map +1 -0
  101. package/dist/chat/state.cjs +14 -1
  102. package/dist/chat/state.cjs.map +1 -1
  103. package/dist/chat/state.d.cts +5 -2
  104. package/dist/chat/state.d.ts +5 -2
  105. package/dist/chat/state.js +14 -1
  106. package/dist/chat/state.js.map +1 -1
  107. package/dist/chat/storage.cjs +19 -10
  108. package/dist/chat/storage.cjs.map +1 -1
  109. package/dist/chat/storage.d.cts +11 -5
  110. package/dist/chat/storage.d.ts +11 -5
  111. package/dist/chat/storage.js +19 -10
  112. package/dist/chat/storage.js.map +1 -1
  113. package/dist/errors-C-so0M4t.d.cts +33 -0
  114. package/dist/errors-C-so0M4t.d.ts +33 -0
  115. package/dist/errors-CmVvczxZ.d.cts +28 -0
  116. package/dist/errors-CmVvczxZ.d.ts +28 -0
  117. package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-C1JnJGVR.d.ts} +28 -23
  118. package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-C7DSqPyX.d.cts} +28 -23
  119. package/dist/index.cjs +340 -46
  120. package/dist/index.cjs.map +1 -1
  121. package/dist/index.d.cts +292 -123
  122. package/dist/index.d.ts +292 -123
  123. package/dist/index.js +334 -47
  124. package/dist/index.js.map +1 -1
  125. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  126. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  127. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  128. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  129. package/dist/testing.cjs +383 -0
  130. package/dist/testing.cjs.map +1 -0
  131. package/dist/testing.d.cts +132 -0
  132. package/dist/testing.d.ts +132 -0
  133. package/dist/testing.js +377 -0
  134. package/dist/testing.js.map +1 -0
  135. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  136. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  137. package/dist/{transport-DX1Nhm4N.d.cts → transport-Cdh3M0tS.d.cts} +5 -4
  138. package/dist/{transport-D1OaUgRk.d.ts → transport-Ciap4PWK.d.ts} +5 -4
  139. package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
  140. package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
  141. package/dist/types-DRgd_9R7.d.cts +363 -0
  142. package/dist/types-ajANVzf7.d.ts +363 -0
  143. package/package.json +31 -6
  144. package/dist/errors-BDLbNu9w.d.cts +0 -13
  145. package/dist/errors-BDLbNu9w.d.ts +0 -13
  146. package/dist/types-DLZzlJxt.d.ts +0 -39
  147. package/dist/types-tE0CXwBl.d.cts +0 -39
@@ -108,6 +108,56 @@ var ContextWindowManager = class {
108
108
  });
109
109
  return { ...result, messages: updatedMessages };
110
110
  }
111
+ /**
112
+ * Trim messages using real token usage data from the previous API call.
113
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
114
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
115
+ *
116
+ * @param messages - All messages in the session
117
+ * @param lastPromptTokens - Real prompt tokens from the last API response
118
+ * @param modelContextWindow - Model's total context window size in tokens
119
+ * @returns Result with fitted messages and metadata
120
+ */
121
+ fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
122
+ if (messages.length === 0) {
123
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
124
+ }
125
+ const budget = modelContextWindow - this.config.reservedTokens;
126
+ if (budget <= 0 || lastPromptTokens <= budget) {
127
+ return {
128
+ messages: [...messages],
129
+ totalTokens: lastPromptTokens,
130
+ removedCount: 0,
131
+ wasTruncated: false
132
+ };
133
+ }
134
+ const avgTokensPerMessage = lastPromptTokens / messages.length;
135
+ const tokensToFree = lastPromptTokens - budget;
136
+ const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
137
+ const nonSystemIndices = [];
138
+ for (let i = 0; i < messages.length; i++) {
139
+ if (messages[i].role === "system") ; else {
140
+ nonSystemIndices.push(i);
141
+ }
142
+ }
143
+ const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
144
+ const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
145
+ const result = [];
146
+ for (let i = 0; i < messages.length; i++) {
147
+ if (!removedIndices.has(i)) {
148
+ result.push(messages[i]);
149
+ }
150
+ }
151
+ const estimatedTokens = Math.round(
152
+ lastPromptTokens * (result.length / messages.length)
153
+ );
154
+ return {
155
+ messages: result,
156
+ totalTokens: estimatedTokens,
157
+ removedCount: removableCount,
158
+ wasTruncated: removableCount > 0
159
+ };
160
+ }
111
161
  /**
112
162
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
113
163
  * Always keeps the most recent user message.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/chat/context.ts"],"names":[],"mappings":"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","file":"context.cjs","sourcesContent":["/**\n * @witqq/agent-sdk/chat/context\n *\n * Context window manager for selecting which messages fit within a token budget.\n * Stateless: takes messages in, returns trimmed messages out.\n * Three overflow strategies: truncate-oldest, sliding-window, summarize-placeholder.\n */\n\nimport type { ChatMessage, MessagePart } from \"./core.js\";\n\n// ─── Token Estimation ──────────────────────────────────────────\n\n/**\n * Options for token estimation.\n */\nexport interface TokenEstimationOptions {\n /**\n * Characters per token ratio.\n * Lower = more conservative (fewer messages fit).\n * @default 4\n */\n charsPerToken?: number;\n}\n\n/**\n * Estimate token count for a single chat message.\n * Uses character-based heuristic: `Math.ceil(charCount / charsPerToken)`.\n *\n * Counts:\n * - Text content (string or text parts)\n * - Serialized tool calls and tool results\n * - Thinking blocks\n * - Role overhead (~4 tokens)\n *\n * @param message - Chat message to estimate\n * @param options - Estimation options\n * @returns Estimated token count\n *\n * @example\n * ```typescript\n * const tokens = estimateTokens(message);\n * const conservative = estimateTokens(message, { charsPerToken: 3 });\n * ```\n */\nexport function estimateTokens(\n message: ChatMessage,\n options?: TokenEstimationOptions,\n): number {\n const ratio = options?.charsPerToken ?? 4;\n let charCount = 0;\n\n // Role overhead\n charCount += message.role.length + 4;\n\n // Parts\n for (const part of message.parts) {\n charCount += estimatePartChars(part);\n }\n\n return Math.ceil(charCount / ratio);\n}\n\nfunction estimatePartChars(part: MessagePart): number {\n switch (part.type) {\n case \"text\":\n return part.text.length;\n case \"reasoning\":\n return part.text.length;\n case \"tool_call\":\n return JSON.stringify(part.args).length + part.name.length + 20 +\n (part.result !== undefined ? JSON.stringify(part.result).length : 0);\n case \"source\":\n return (part.title?.length ?? 0) + part.url.length + 10;\n case \"file\":\n return part.name.length + part.data.length + 20;\n }\n}\n\n// ─── Overflow Strategies ───────────────────────────────────────\n\n/** Overflow strategy type */\nexport type OverflowStrategy =\n | \"truncate-oldest\"\n | \"sliding-window\"\n | \"summarize-placeholder\";\n\n// ─── Context Window Configuration ──────────────────────────────\n\n/**\n * Async summarizer function for the summarize-placeholder strategy.\n * Receives removed messages and returns a summary string.\n * When configured, replaces the static placeholder text with actual summary.\n */\nexport type ContextSummarizer = (removedMessages: readonly ChatMessage[]) => Promise<string>;\n\n/**\n * Configuration for the context window manager.\n */\nexport interface ContextWindowConfig {\n /** Maximum token budget for the context window */\n maxTokens: number;\n\n /**\n * Tokens reserved for system prompt and response generation.\n * Subtracted from maxTokens to get available budget.\n * @default 0\n */\n reservedTokens?: number;\n\n /**\n * Strategy for handling overflow when messages exceed budget.\n * @default \"truncate-oldest\"\n */\n strategy?: OverflowStrategy;\n\n /**\n * Token estimation options.\n */\n estimation?: TokenEstimationOptions;\n\n /**\n * Optional async summarizer for the summarize-placeholder strategy.\n * When provided, replaces the static placeholder with a generated summary.\n * Falls back to static placeholder if summarizer throws.\n */\n summarizer?: ContextSummarizer;\n}\n\n// ─── Context Window Result ─────────────────────────────────────\n\n/**\n * Result of context window trimming.\n */\nexport interface ContextWindowResult {\n /** Messages that fit within the budget */\n messages: ChatMessage[];\n /** Total estimated tokens for included messages */\n totalTokens: number;\n /** Number of messages removed */\n removedCount: number;\n /** Whether any messages were truncated */\n wasTruncated: boolean;\n}\n\n// ─── Context Stats ─────────────────────────────────────────────\n\n/**\n * Context usage statistics for a session.\n * Returned by `IChatRuntime.getContextStats()`.\n */\nexport interface ContextStats {\n /** Estimated total tokens in the trimmed context */\n totalTokens: number;\n /** Number of messages removed by trimming */\n removedCount: number;\n /** Whether context was truncated */\n wasTruncated: boolean;\n /** Available token budget (maxTokens − reservedTokens) */\n availableBudget: number;\n}\n\n// ─── Context Window Manager ────────────────────────────────────\n\n/**\n * Stateless context window manager.\n * Takes messages and returns the subset that fits within a token budget.\n *\n * @example\n * ```typescript\n * const manager = new ContextWindowManager({\n * maxTokens: 4096,\n * reservedTokens: 500,\n * strategy: \"sliding-window\",\n * });\n *\n * const result = manager.fitMessages(messages);\n * // result.messages — trimmed to fit budget\n * // result.totalTokens — estimated token usage\n * // result.wasTruncated — whether messages were removed\n * ```\n */\nexport class ContextWindowManager {\n private readonly config: Required<\n Pick<ContextWindowConfig, \"maxTokens\" | \"reservedTokens\" | \"strategy\">\n > &\n Pick<ContextWindowConfig, \"estimation\" | \"summarizer\">;\n\n constructor(config: ContextWindowConfig) {\n this.config = {\n maxTokens: config.maxTokens,\n reservedTokens: config.reservedTokens ?? 0,\n strategy: config.strategy ?? \"truncate-oldest\",\n estimation: config.estimation,\n summarizer: config.summarizer,\n };\n }\n\n /** Available token budget after reserving tokens */\n get availableBudget(): number {\n return Math.max(0, this.config.maxTokens - this.config.reservedTokens);\n }\n\n /**\n * Estimate tokens for a single message.\n * @param message - Message to estimate\n * @returns Estimated token count\n */\n estimateMessageTokens(message: ChatMessage): number {\n return estimateTokens(message, this.config.estimation);\n }\n\n /**\n * Fit messages within the token budget using the configured strategy.\n * @param messages - All messages to consider\n * @returns Result with fitted messages and metadata\n */\n fitMessages(messages: readonly ChatMessage[]): ContextWindowResult {\n if (messages.length === 0) {\n return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };\n }\n\n const budget = this.availableBudget;\n\n // Calculate tokens for each message\n const tokenCounts = messages.map((m) => this.estimateMessageTokens(m));\n const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);\n\n // All messages fit\n if (totalTokens <= budget) {\n return {\n messages: [...messages],\n totalTokens,\n removedCount: 0,\n wasTruncated: false,\n };\n }\n\n switch (this.config.strategy) {\n case \"truncate-oldest\":\n return this.truncateOldest(messages, tokenCounts, budget);\n case \"sliding-window\":\n return this.slidingWindow(messages, tokenCounts, budget);\n case \"summarize-placeholder\":\n return this.summarizePlaceholder(messages, tokenCounts, budget);\n }\n }\n\n /**\n * Async variant of fitMessages that supports async summarization.\n * When strategy is \"summarize-placeholder\" and a summarizer is configured,\n * calls the summarizer with removed messages and replaces the placeholder text.\n * Falls back to static placeholder if summarizer throws.\n * For other strategies, behaves identically to fitMessages().\n */\n async fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult> {\n const result = this.fitMessages(messages);\n\n // Only enhance if summarize-placeholder strategy, messages were removed, and summarizer is configured\n if (\n this.config.strategy !== \"summarize-placeholder\" ||\n !result.wasTruncated ||\n !this.config.summarizer\n ) {\n return result;\n }\n\n // Find removed messages (those in original but not in result)\n const keptIds = new Set(result.messages.map(m => m.id));\n const removed = messages.filter(m => !keptIds.has(m.id));\n if (removed.length === 0) return result;\n\n // Call async summarizer, fall back to static placeholder on error\n let summaryText: string;\n try {\n summaryText = await this.config.summarizer(removed);\n } catch {\n return result; // Keep static placeholder on summarizer failure\n }\n\n // Replace placeholder text with summarizer output\n const updatedMessages = result.messages.map(m => {\n if ((m.metadata as Record<string, unknown>)?.isSummary === true) {\n return {\n ...m,\n parts: [{ type: \"text\" as const, text: summaryText, status: \"complete\" as const }],\n };\n }\n return m;\n });\n\n return { ...result, messages: updatedMessages };\n }\n\n /**\n * Truncate oldest: keeps system messages, removes oldest non-system messages first.\n * Always keeps the most recent user message.\n */\n private truncateOldest(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // Separate system messages (always kept) and non-system\n const systemIndices: number[] = [];\n const nonSystemIndices: number[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemIndices.push(i);\n } else {\n nonSystemIndices.push(i);\n }\n }\n\n // System messages cost\n let usedTokens = systemIndices.reduce(\n (sum, i) => sum + tokenCounts[i],\n 0,\n );\n\n // If system messages alone exceed budget, still include them\n // (caller should configure reservedTokens properly)\n\n // Try to fit non-system from newest to oldest\n const includedNonSystem: number[] = [];\n for (let i = nonSystemIndices.length - 1; i >= 0; i--) {\n const idx = nonSystemIndices[i];\n if (usedTokens + tokenCounts[idx] <= budget) {\n includedNonSystem.unshift(idx);\n usedTokens += tokenCounts[idx];\n }\n }\n\n // Build result preserving original order\n const includedSet = new Set([...systemIndices, ...includedNonSystem]);\n const result: ChatMessage[] = [];\n let resultTokens = 0;\n for (let i = 0; i < messages.length; i++) {\n if (includedSet.has(i)) {\n result.push(messages[i]);\n resultTokens += tokenCounts[i];\n }\n }\n\n return {\n messages: result,\n totalTokens: resultTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Sliding window: keeps the most recent messages that fit within budget.\n */\n private slidingWindow(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n const result: ChatMessage[] = [];\n let usedTokens = 0;\n\n // Walk from newest to oldest\n for (let i = messages.length - 1; i >= 0; i--) {\n if (usedTokens + tokenCounts[i] <= budget) {\n result.unshift(messages[i]);\n usedTokens += tokenCounts[i];\n } else {\n break;\n }\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Summarize placeholder: replaces truncated messages with a placeholder,\n * preserving system messages and recent context.\n */\n private summarizePlaceholder(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // First, identify system messages and recent messages\n const systemMessages: { msg: ChatMessage; tokens: number }[] = [];\n const nonSystem: { msg: ChatMessage; tokens: number; idx: number }[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemMessages.push({ msg: messages[i], tokens: tokenCounts[i] });\n } else {\n nonSystem.push({ msg: messages[i], tokens: tokenCounts[i], idx: i });\n }\n }\n\n // System message cost\n let usedTokens = systemMessages.reduce((s, m) => s + m.tokens, 0);\n\n // Placeholder costs ~20 tokens\n const placeholderTokens = 20;\n usedTokens += placeholderTokens;\n\n // Fit recent non-system messages from newest\n const recentKept: typeof nonSystem = [];\n for (let i = nonSystem.length - 1; i >= 0; i--) {\n if (usedTokens + nonSystem[i].tokens <= budget) {\n recentKept.unshift(nonSystem[i]);\n usedTokens += nonSystem[i].tokens;\n } else {\n break;\n }\n }\n\n const removedCount =\n messages.length -\n systemMessages.length -\n recentKept.length;\n\n // Build result: system messages, placeholder, recent messages\n const result: ChatMessage[] = [];\n\n // System messages first\n for (const sm of systemMessages) {\n result.push(sm.msg);\n }\n\n // Placeholder if messages were removed\n if (removedCount > 0) {\n result.push({\n id: \"context-placeholder\" as ChatMessage[\"id\"],\n role: \"system\",\n parts: [{ type: \"text\", text: `[${removedCount} earlier message${removedCount === 1 ? \"\" : \"s\"} omitted for context window]`, status: \"complete\" as const }],\n metadata: { isSummary: true },\n createdAt: new Date().toISOString(),\n status: \"complete\",\n });\n }\n\n // Recent messages\n for (const m of recentKept) {\n result.push(m.msg);\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount,\n wasTruncated: true,\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/chat/context.ts"],"names":[],"mappings":";;;AA4CO,SAAS,cAAA,CACd,SACA,OAAA,EACQ;AACR,EAAA,MAAM,KAAA,GAAQ,SAAS,aAAA,IAAiB,CAAA;AACxC,EAAA,IAAI,SAAA,GAAY,CAAA;AAGhB,EAAA,SAAA,IAAa,OAAA,CAAQ,KAAK,MAAA,GAAS,CAAA;AAGnC,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAQ,KAAA,EAAO;AAChC,IAAA,SAAA,IAAa,kBAAkB,IAAI,CAAA;AAAA,EACrC;AAEA,EAAA,OAAO,IAAA,CAAK,IAAA,CAAK,SAAA,GAAY,KAAK,CAAA;AACpC;AAEA,SAAS,kBAAkB,IAAA,EAA2B;AACpD,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,OAAO,KAAK,IAAA,CAAK,MAAA;AAAA,IACnB,KAAK,WAAA;AACH,MAAA,OAAO,KAAK,IAAA,CAAK,MAAA;AAAA,IACnB,KAAK,WAAA;AACH,MAAA,OAAO,KAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,GAAS,KAAK,IAAA,CAAK,MAAA,GAAS,EAAA,IAC1D,IAAA,CAAK,WAAW,MAAA,GAAY,IAAA,CAAK,UAAU,IAAA,CAAK,MAAM,EAAE,MAAA,GAAS,CAAA,CAAA;AAAA,IACtE,KAAK,QAAA;AACH,MAAA,OAAA,CAAQ,KAAK,KAAA,EAAO,MAAA,IAAU,CAAA,IAAK,IAAA,CAAK,IAAI,MAAA,GAAS,EAAA;AAAA,IACvD,KAAK,MAAA;AACH,MAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,KAAK,MAAA,GAAS,EAAA;AAAA;AAEnD;AAmHO,IAAM,uBAAN,MAA2B;AAAA,EACf,MAAA;AAAA,EAKjB,YAAY,MAAA,EAA6B;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,cAAA,EAAgB,OAAO,cAAA,IAAkB,CAAA;AAAA,MACzC,QAAA,EAAU,OAAO,QAAA,IAAY,iBAAA;AAAA,MAC7B,YAAY,MAAA,CAAO,UAAA;AAAA,MACnB,YAAY,MAAA,CAAO;AAAA,KACrB;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,eAAA,GAA0B;AAC5B,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,OAAO,SAAA,GAAY,IAAA,CAAK,OAAO,cAAc,CAAA;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB,OAAA,EAA8B;AAClD,IAAA,OAAO,cAAA,CAAe,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,UAAU,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,QAAA,EAAuD;AACjE,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,MAAA,OAAO,EAAE,UAAU,EAAC,EAAG,aAAa,CAAA,EAAG,YAAA,EAAc,CAAA,EAAG,YAAA,EAAc,KAAA,EAAM;AAAA,IAC9E;AAEA,IAAA,MAAM,SAAS,IAAA,CAAK,eAAA;AAGpB,IAAA,MAAM,WAAA,GAAc,SAAS,GAAA,CAAI,CAAC,MAAM,IAAA,CAAK,qBAAA,CAAsB,CAAC,CAAC,CAAA;AACrE,IAAA,MAAM,WAAA,GAAc,YAAY,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AAGzD,IAAA,IAAI,eAAe,MAAA,EAAQ;AACzB,MAAA,OAAO;AAAA,QACL,QAAA,EAAU,CAAC,GAAG,QAAQ,CAAA;AAAA,QACtB,WAAA;AAAA,QACA,YAAA,EAAc,CAAA;AAAA,QACd,YAAA,EAAc;AAAA,OAChB;AAAA,IACF;AAEA,IAAA,QAAQ,IAAA,CAAK,OAAO,QAAA;AAAU,MAC5B,KAAK,iBAAA;AACH,QAAA,OAAO,IAAA,CAAK,cAAA,CAAe,QAAA,EAAU,WAAA,EAAa,MAAM,CAAA;AAAA,MAC1D,KAAK,gBAAA;AACH,QAAA,OAAO,IAAA,CAAK,aAAA,CAAc,QAAA,EAAU,WAAA,EAAa,MAAM,CAAA;AAAA,MACzD,KAAK,uBAAA;AACH,QAAA,OAAO,IAAA,CAAK,oBAAA,CAAqB,QAAA,EAAU,WAAA,EAAa,MAAM,CAAA;AAAA;AAClE,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iBAAiB,QAAA,EAAgE;AACrF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,WAAA,CAAY,QAAQ,CAAA;AAGxC,IAAA,IACE,IAAA,CAAK,MAAA,CAAO,QAAA,KAAa,uBAAA,IACzB,CAAC,OAAO,YAAA,IACR,CAAC,IAAA,CAAK,MAAA,CAAO,UAAA,EACb;AACA,MAAA,OAAO,MAAA;AAAA,IACT;AAGA,IAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,EAAE,CAAC,CAAA;AACtD,IAAA,MAAM,OAAA,GAAU,SAAS,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AACvD,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AAGjC,IAAA,IAAI,WAAA;AACJ,IAAA,IAAI;AACF,MAAA,WAAA,GAAc,MAAM,IAAA,CAAK,MAAA,CAAO,UAAA,CAAW,OAAO,CAAA;AAAA,IACpD,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AAGA,IAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,KAAK;AAC/C,MAAA,IAAK,CAAA,CAAE,QAAA,EAAsC,SAAA,KAAc,IAAA,EAAM;AAC/D,QAAA,OAAO;AAAA,UACL,GAAG,CAAA;AAAA,UACH,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,QAAiB,IAAA,EAAM,WAAA,EAAa,MAAA,EAAQ,UAAA,EAAqB;AAAA,SACnF;AAAA,MACF;AACA,MAAA,OAAO,CAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,QAAA,EAAU,eAAA,EAAgB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,oBAAA,CACE,QAAA,EACA,gBAAA,EACA,kBAAA,EACqB;AACrB,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,MAAA,OAAO,EAAE,UAAU,EAAC,EAAG,aAAa,CAAA,EAAG,YAAA,EAAc,CAAA,EAAG,YAAA,EAAc,KAAA,EAAM;AAAA,IAC9E;AAEA,IAAA,MAAM,MAAA,GAAS,kBAAA,GAAqB,IAAA,CAAK,MAAA,CAAO,cAAA;AAChD,IAAA,IAAI,MAAA,IAAU,CAAA,IAAK,gBAAA,IAAoB,MAAA,EAAQ;AAC7C,MAAA,OAAO;AAAA,QACL,QAAA,EAAU,CAAC,GAAG,QAAQ,CAAA;AAAA,QACtB,WAAA,EAAa,gBAAA;AAAA,QACb,YAAA,EAAc,CAAA;AAAA,QACd,YAAA,EAAc;AAAA,OAChB;AAAA,IACF;AAGA,IAAA,MAAM,mBAAA,GAAsB,mBAAmB,QAAA,CAAS,MAAA;AAGxD,IAAA,MAAM,eAAe,gBAAA,GAAmB,MAAA;AAExC,IAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,IAAA,CAAK,YAAA,GAAe,mBAAmB,CAAA;AAIrE,IAAA,MAAM,mBAA6B,EAAC;AACpC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA,KAAS,QAAA,EAAU,CAEnC,MAAO;AACL,QAAA,gBAAA,CAAiB,KAAK,CAAC,CAAA;AAAA,MACzB;AAAA,IACF;AAGA,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,GAAA,CAAI,gBAAA,EAAkB,iBAAiB,MAAM,CAAA;AACzE,IAAA,MAAM,iBAAiB,IAAI,GAAA,CAAI,iBAAiB,KAAA,CAAM,CAAA,EAAG,cAAc,CAAC,CAAA;AAExE,IAAA,MAAM,SAAwB,EAAC;AAC/B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,CAAC,cAAA,CAAe,GAAA,CAAI,CAAC,CAAA,EAAG;AAC1B,QAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,MACzB;AAAA,IACF;AAGA,IAAA,MAAM,kBAAkB,IAAA,CAAK,KAAA;AAAA,MAC3B,gBAAA,IAAoB,MAAA,CAAO,MAAA,GAAS,QAAA,CAAS,MAAA;AAAA,KAC/C;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,eAAA;AAAA,MACb,YAAA,EAAc,cAAA;AAAA,MACd,cAAc,cAAA,GAAiB;AAAA,KACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,cAAA,CACN,QAAA,EACA,WAAA,EACA,MAAA,EACqB;AAErB,IAAA,MAAM,gBAA0B,EAAC;AACjC,IAAA,MAAM,mBAA6B,EAAC;AAEpC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA,KAAS,QAAA,EAAU;AACjC,QAAA,aAAA,CAAc,KAAK,CAAC,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,gBAAA,CAAiB,KAAK,CAAC,CAAA;AAAA,MACzB;AAAA,IACF;AAGA,IAAA,IAAI,aAAa,aAAA,CAAc,MAAA;AAAA,MAC7B,CAAC,GAAA,EAAK,CAAA,KAAM,GAAA,GAAM,YAAY,CAAC,CAAA;AAAA,MAC/B;AAAA,KACF;AAMA,IAAA,MAAM,oBAA8B,EAAC;AACrC,IAAA,KAAA,IAAS,IAAI,gBAAA,CAAiB,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AACrD,MAAA,MAAM,GAAA,GAAM,iBAAiB,CAAC,CAAA;AAC9B,MAAA,IAAI,UAAA,GAAa,WAAA,CAAY,GAAG,CAAA,IAAK,MAAA,EAAQ;AAC3C,QAAA,iBAAA,CAAkB,QAAQ,GAAG,CAAA;AAC7B,QAAA,UAAA,IAAc,YAAY,GAAG,CAAA;AAAA,MAC/B;AAAA,IACF;AAGA,IAAA,MAAM,WAAA,uBAAkB,GAAA,CAAI,CAAC,GAAG,aAAA,EAAe,GAAG,iBAAiB,CAAC,CAAA;AACpE,IAAA,MAAM,SAAwB,EAAC;AAC/B,IAAA,IAAI,YAAA,GAAe,CAAA;AACnB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,EAAG;AACtB,QAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,CAAC,CAAC,CAAA;AACvB,QAAA,YAAA,IAAgB,YAAY,CAAC,CAAA;AAAA,MAC/B;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,YAAA;AAAA,MACb,YAAA,EAAc,QAAA,CAAS,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,MACvC,YAAA,EAAc;AAAA,KAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAA,CACN,QAAA,EACA,WAAA,EACA,MAAA,EACqB;AACrB,IAAA,MAAM,SAAwB,EAAC;AAC/B,IAAA,IAAI,UAAA,GAAa,CAAA;AAGjB,IAAA,KAAA,IAAS,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC7C,MAAA,IAAI,UAAA,GAAa,WAAA,CAAY,CAAC,CAAA,IAAK,MAAA,EAAQ;AACzC,QAAA,MAAA,CAAO,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAC,CAAA;AAC1B,QAAA,UAAA,IAAc,YAAY,CAAC,CAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,UAAA;AAAA,MACb,YAAA,EAAc,QAAA,CAAS,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,MACvC,YAAA,EAAc;AAAA,KAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAA,CACN,QAAA,EACA,WAAA,EACA,MAAA,EACqB;AAErB,IAAA,MAAM,iBAAyD,EAAC;AAChE,IAAA,MAAM,YAAiE,EAAC;AAExE,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA,KAAS,QAAA,EAAU;AACjC,QAAA,cAAA,CAAe,IAAA,CAAK,EAAE,GAAA,EAAK,QAAA,CAAS,CAAC,GAAG,MAAA,EAAQ,WAAA,CAAY,CAAC,CAAA,EAAG,CAAA;AAAA,MAClE,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,IAAA,CAAK,EAAE,GAAA,EAAK,QAAA,CAAS,CAAC,CAAA,EAAG,MAAA,EAAQ,WAAA,CAAY,CAAC,CAAA,EAAG,GAAA,EAAK,CAAA,EAAG,CAAA;AAAA,MACrE;AAAA,IACF;AAGA,IAAA,IAAI,UAAA,GAAa,eAAe,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAC,CAAA;AAGhE,IAAA,MAAM,iBAAA,GAAoB,EAAA;AAC1B,IAAA,UAAA,IAAc,iBAAA;AAGd,IAAA,MAAM,aAA+B,EAAC;AACtC,IAAA,KAAA,IAAS,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC9C,MAAA,IAAI,UAAA,GAAa,SAAA,CAAU,CAAC,CAAA,CAAE,UAAU,MAAA,EAAQ;AAC9C,QAAA,UAAA,CAAW,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAC,CAAA;AAC/B,QAAA,UAAA,IAAc,SAAA,CAAU,CAAC,CAAA,CAAE,MAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GACJ,QAAA,CAAS,MAAA,GACT,cAAA,CAAe,SACf,UAAA,CAAW,MAAA;AAGb,IAAA,MAAM,SAAwB,EAAC;AAG/B,IAAA,KAAA,MAAW,MAAM,cAAA,EAAgB;AAC/B,MAAA,MAAA,CAAO,IAAA,CAAK,GAAG,GAAG,CAAA;AAAA,IACpB;AAGA,IAAA,IAAI,eAAe,CAAA,EAAG;AACpB,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,EAAA,EAAI,qBAAA;AAAA,QACJ,IAAA,EAAM,QAAA;AAAA,QACN,OAAO,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,CAAA,CAAA,EAAI,YAAY,CAAA,gBAAA,EAAmB,YAAA,KAAiB,IAAI,EAAA,GAAK,GAAG,CAAA,4BAAA,CAAA,EAAgC,MAAA,EAAQ,YAAqB,CAAA;AAAA,QAC3J,QAAA,EAAU,EAAE,SAAA,EAAW,IAAA,EAAK;AAAA,QAC5B,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,QAClC,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH;AAGA,IAAA,KAAA,MAAW,KAAK,UAAA,EAAY;AAC1B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,GAAG,CAAA;AAAA,IACnB;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,UAAA;AAAA,MACb,YAAA;AAAA,MACA,YAAA,EAAc;AAAA,KAChB;AAAA,EACF;AACF","file":"context.cjs","sourcesContent":["/**\n * @witqq/agent-sdk/chat/context\n *\n * Context window manager for selecting which messages fit within a token budget.\n * Stateless: takes messages in, returns trimmed messages out.\n * Three overflow strategies: truncate-oldest, sliding-window, summarize-placeholder.\n */\n\nimport type { ChatMessage, MessagePart } from \"./core.js\";\n\n// ─── Token Estimation ──────────────────────────────────────────\n\n/**\n * Options for token estimation.\n */\nexport interface TokenEstimationOptions {\n /**\n * Characters per token ratio.\n * Lower = more conservative (fewer messages fit).\n * @default 4\n */\n charsPerToken?: number;\n}\n\n/**\n * Estimate token count for a single chat message.\n * Uses character-based heuristic: `Math.ceil(charCount / charsPerToken)`.\n *\n * Counts:\n * - Text content (string or text parts)\n * - Serialized tool calls and tool results\n * - Thinking blocks\n * - Role overhead (~4 tokens)\n *\n * @param message - Chat message to estimate\n * @param options - Estimation options\n * @returns Estimated token count\n *\n * @example\n * ```typescript\n * const tokens = estimateTokens(message);\n * const conservative = estimateTokens(message, { charsPerToken: 3 });\n * ```\n */\nexport function estimateTokens(\n message: ChatMessage,\n options?: TokenEstimationOptions,\n): number {\n const ratio = options?.charsPerToken ?? 4;\n let charCount = 0;\n\n // Role overhead\n charCount += message.role.length + 4;\n\n // Parts\n for (const part of message.parts) {\n charCount += estimatePartChars(part);\n }\n\n return Math.ceil(charCount / ratio);\n}\n\nfunction estimatePartChars(part: MessagePart): number {\n switch (part.type) {\n case \"text\":\n return part.text.length;\n case \"reasoning\":\n return part.text.length;\n case \"tool_call\":\n return JSON.stringify(part.args).length + part.name.length + 20 +\n (part.result !== undefined ? JSON.stringify(part.result).length : 0);\n case \"source\":\n return (part.title?.length ?? 0) + part.url.length + 10;\n case \"file\":\n return part.name.length + part.data.length + 20;\n }\n}\n\n// ─── Overflow Strategies ───────────────────────────────────────\n\n/** Overflow strategy type */\nexport type OverflowStrategy =\n | \"truncate-oldest\"\n | \"sliding-window\"\n | \"summarize-placeholder\";\n\n// ─── Context Window Configuration ──────────────────────────────\n\n/**\n * Async summarizer function for the summarize-placeholder strategy.\n * Receives removed messages and returns a summary string.\n * When configured, replaces the static placeholder text with actual summary.\n */\nexport type ContextSummarizer = (removedMessages: readonly ChatMessage[]) => Promise<string>;\n\n/**\n * Configuration for the context window manager.\n */\nexport interface ContextWindowConfig {\n /** Maximum token budget for the context window */\n maxTokens: number;\n\n /**\n * Tokens reserved for system prompt and response generation.\n * Subtracted from maxTokens to get available budget.\n * @default 0\n */\n reservedTokens?: number;\n\n /**\n * Strategy for handling overflow when messages exceed budget.\n * @default \"truncate-oldest\"\n */\n strategy?: OverflowStrategy;\n\n /**\n * Token estimation options.\n */\n estimation?: TokenEstimationOptions;\n\n /**\n * Optional async summarizer for the summarize-placeholder strategy.\n * When provided, replaces the static placeholder with a generated summary.\n * Falls back to static placeholder if summarizer throws.\n */\n summarizer?: ContextSummarizer;\n}\n\n// ─── Context Window Result ─────────────────────────────────────\n\n/**\n * Result of context window trimming.\n */\nexport interface ContextWindowResult {\n /** Messages that fit within the budget */\n messages: ChatMessage[];\n /** Total estimated tokens for included messages */\n totalTokens: number;\n /** Number of messages removed */\n removedCount: number;\n /** Whether any messages were truncated */\n wasTruncated: boolean;\n}\n\n// ─── Context Stats ─────────────────────────────────────────────\n\n/**\n * Context usage statistics for a session.\n * Returned by `IChatRuntime.getContextStats()`.\n *\n * When real usage data is available (after the first API response),\n * `realPromptTokens` and `realCompletionTokens` contain actual token counts.\n * `modelContextWindow` is the model's context window from `listModels()`.\n */\nexport interface ContextStats {\n /** Estimated total tokens in the trimmed context (heuristic, kept for backward compat) */\n totalTokens: number;\n /** Number of messages removed by trimming */\n removedCount: number;\n /** Whether context was truncated */\n wasTruncated: boolean;\n /** Available token budget (maxTokens − reservedTokens) */\n availableBudget: number;\n /** Real prompt tokens from the last API response (undefined before first response) */\n realPromptTokens?: number;\n /** Real completion tokens from the last API response (undefined before first response) */\n realCompletionTokens?: number;\n /** Model's context window in tokens from listModels() (undefined if not available) */\n modelContextWindow?: number;\n}\n\n// ─── Context Window Manager ────────────────────────────────────\n\n/**\n * Stateless context window manager.\n * Takes messages and returns the subset that fits within a token budget.\n *\n * @example\n * ```typescript\n * const manager = new ContextWindowManager({\n * maxTokens: 4096,\n * reservedTokens: 500,\n * strategy: \"sliding-window\",\n * });\n *\n * const result = manager.fitMessages(messages);\n * // result.messages — trimmed to fit budget\n * // result.totalTokens — estimated token usage\n * // result.wasTruncated — whether messages were removed\n * ```\n */\nexport class ContextWindowManager {\n private readonly config: Required<\n Pick<ContextWindowConfig, \"maxTokens\" | \"reservedTokens\" | \"strategy\">\n > &\n Pick<ContextWindowConfig, \"estimation\" | \"summarizer\">;\n\n constructor(config: ContextWindowConfig) {\n this.config = {\n maxTokens: config.maxTokens,\n reservedTokens: config.reservedTokens ?? 0,\n strategy: config.strategy ?? \"truncate-oldest\",\n estimation: config.estimation,\n summarizer: config.summarizer,\n };\n }\n\n /** Available token budget after reserving tokens */\n get availableBudget(): number {\n return Math.max(0, this.config.maxTokens - this.config.reservedTokens);\n }\n\n /**\n * Estimate tokens for a single message.\n * @param message - Message to estimate\n * @returns Estimated token count\n */\n estimateMessageTokens(message: ChatMessage): number {\n return estimateTokens(message, this.config.estimation);\n }\n\n /**\n * Fit messages within the token budget using the configured strategy.\n * @param messages - All messages to consider\n * @returns Result with fitted messages and metadata\n */\n fitMessages(messages: readonly ChatMessage[]): ContextWindowResult {\n if (messages.length === 0) {\n return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };\n }\n\n const budget = this.availableBudget;\n\n // Calculate tokens for each message\n const tokenCounts = messages.map((m) => this.estimateMessageTokens(m));\n const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);\n\n // All messages fit\n if (totalTokens <= budget) {\n return {\n messages: [...messages],\n totalTokens,\n removedCount: 0,\n wasTruncated: false,\n };\n }\n\n switch (this.config.strategy) {\n case \"truncate-oldest\":\n return this.truncateOldest(messages, tokenCounts, budget);\n case \"sliding-window\":\n return this.slidingWindow(messages, tokenCounts, budget);\n case \"summarize-placeholder\":\n return this.summarizePlaceholder(messages, tokenCounts, budget);\n }\n }\n\n /**\n * Async variant of fitMessages that supports async summarization.\n * When strategy is \"summarize-placeholder\" and a summarizer is configured,\n * calls the summarizer with removed messages and replaces the placeholder text.\n * Falls back to static placeholder if summarizer throws.\n * For other strategies, behaves identically to fitMessages().\n */\n async fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult> {\n const result = this.fitMessages(messages);\n\n // Only enhance if summarize-placeholder strategy, messages were removed, and summarizer is configured\n if (\n this.config.strategy !== \"summarize-placeholder\" ||\n !result.wasTruncated ||\n !this.config.summarizer\n ) {\n return result;\n }\n\n // Find removed messages (those in original but not in result)\n const keptIds = new Set(result.messages.map(m => m.id));\n const removed = messages.filter(m => !keptIds.has(m.id));\n if (removed.length === 0) return result;\n\n // Call async summarizer, fall back to static placeholder on error\n let summaryText: string;\n try {\n summaryText = await this.config.summarizer(removed);\n } catch {\n return result; // Keep static placeholder on summarizer failure\n }\n\n // Replace placeholder text with summarizer output\n const updatedMessages = result.messages.map(m => {\n if ((m.metadata as Record<string, unknown>)?.isSummary === true) {\n return {\n ...m,\n parts: [{ type: \"text\" as const, text: summaryText, status: \"complete\" as const }],\n };\n }\n return m;\n });\n\n return { ...result, messages: updatedMessages };\n }\n\n /**\n * Trim messages using real token usage data from the previous API call.\n * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.\n * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.\n *\n * @param messages - All messages in the session\n * @param lastPromptTokens - Real prompt tokens from the last API response\n * @param modelContextWindow - Model's total context window size in tokens\n * @returns Result with fitted messages and metadata\n */\n fitMessagesWithUsage(\n messages: readonly ChatMessage[],\n lastPromptTokens: number,\n modelContextWindow: number,\n ): ContextWindowResult {\n if (messages.length === 0) {\n return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };\n }\n\n const budget = modelContextWindow - this.config.reservedTokens;\n if (budget <= 0 || lastPromptTokens <= budget) {\n return {\n messages: [...messages],\n totalTokens: lastPromptTokens,\n removedCount: 0,\n wasTruncated: false,\n };\n }\n\n // Average tokens per message from real data\n const avgTokensPerMessage = lastPromptTokens / messages.length;\n\n // How many tokens we need to free\n const tokensToFree = lastPromptTokens - budget;\n // How many messages to remove (ceil to be safe)\n const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);\n\n // Separate system and non-system messages\n const systemIndices: number[] = [];\n const nonSystemIndices: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemIndices.push(i);\n } else {\n nonSystemIndices.push(i);\n }\n }\n\n // Remove oldest non-system messages (from the beginning of conversation)\n const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);\n const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));\n\n const result: ChatMessage[] = [];\n for (let i = 0; i < messages.length; i++) {\n if (!removedIndices.has(i)) {\n result.push(messages[i]);\n }\n }\n\n // Estimate new total: proportional reduction\n const estimatedTokens = Math.round(\n lastPromptTokens * (result.length / messages.length),\n );\n\n return {\n messages: result,\n totalTokens: estimatedTokens,\n removedCount: removableCount,\n wasTruncated: removableCount > 0,\n };\n }\n\n /**\n * Truncate oldest: keeps system messages, removes oldest non-system messages first.\n * Always keeps the most recent user message.\n */\n private truncateOldest(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // Separate system messages (always kept) and non-system\n const systemIndices: number[] = [];\n const nonSystemIndices: number[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemIndices.push(i);\n } else {\n nonSystemIndices.push(i);\n }\n }\n\n // System messages cost\n let usedTokens = systemIndices.reduce(\n (sum, i) => sum + tokenCounts[i],\n 0,\n );\n\n // If system messages alone exceed budget, still include them\n // (caller should configure reservedTokens properly)\n\n // Try to fit non-system from newest to oldest\n const includedNonSystem: number[] = [];\n for (let i = nonSystemIndices.length - 1; i >= 0; i--) {\n const idx = nonSystemIndices[i];\n if (usedTokens + tokenCounts[idx] <= budget) {\n includedNonSystem.unshift(idx);\n usedTokens += tokenCounts[idx];\n }\n }\n\n // Build result preserving original order\n const includedSet = new Set([...systemIndices, ...includedNonSystem]);\n const result: ChatMessage[] = [];\n let resultTokens = 0;\n for (let i = 0; i < messages.length; i++) {\n if (includedSet.has(i)) {\n result.push(messages[i]);\n resultTokens += tokenCounts[i];\n }\n }\n\n return {\n messages: result,\n totalTokens: resultTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Sliding window: keeps the most recent messages that fit within budget.\n */\n private slidingWindow(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n const result: ChatMessage[] = [];\n let usedTokens = 0;\n\n // Walk from newest to oldest\n for (let i = messages.length - 1; i >= 0; i--) {\n if (usedTokens + tokenCounts[i] <= budget) {\n result.unshift(messages[i]);\n usedTokens += tokenCounts[i];\n } else {\n break;\n }\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Summarize placeholder: replaces truncated messages with a placeholder,\n * preserving system messages and recent context.\n */\n private summarizePlaceholder(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // First, identify system messages and recent messages\n const systemMessages: { msg: ChatMessage; tokens: number }[] = [];\n const nonSystem: { msg: ChatMessage; tokens: number; idx: number }[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemMessages.push({ msg: messages[i], tokens: tokenCounts[i] });\n } else {\n nonSystem.push({ msg: messages[i], tokens: tokenCounts[i], idx: i });\n }\n }\n\n // System message cost\n let usedTokens = systemMessages.reduce((s, m) => s + m.tokens, 0);\n\n // Placeholder costs ~20 tokens\n const placeholderTokens = 20;\n usedTokens += placeholderTokens;\n\n // Fit recent non-system messages from newest\n const recentKept: typeof nonSystem = [];\n for (let i = nonSystem.length - 1; i >= 0; i--) {\n if (usedTokens + nonSystem[i].tokens <= budget) {\n recentKept.unshift(nonSystem[i]);\n usedTokens += nonSystem[i].tokens;\n } else {\n break;\n }\n }\n\n const removedCount =\n messages.length -\n systemMessages.length -\n recentKept.length;\n\n // Build result: system messages, placeholder, recent messages\n const result: ChatMessage[] = [];\n\n // System messages first\n for (const sm of systemMessages) {\n result.push(sm.msg);\n }\n\n // Placeholder if messages were removed\n if (removedCount > 0) {\n result.push({\n id: \"context-placeholder\" as ChatMessage[\"id\"],\n role: \"system\",\n parts: [{ type: \"text\", text: `[${removedCount} earlier message${removedCount === 1 ? \"\" : \"s\"} omitted for context window]`, status: \"complete\" as const }],\n metadata: { isSummary: true },\n createdAt: new Date().toISOString(),\n status: \"complete\",\n });\n }\n\n // Recent messages\n for (const m of recentKept) {\n result.push(m.msg);\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount,\n wasTruncated: true,\n };\n }\n}\n"]}
@@ -1,6 +1,9 @@
1
- import { ChatMessage } from './core.cjs';
2
- import '../types-CqvUAYxt.cjs';
1
+ import { c as ChatMessage } from '../types-DRgd_9R7.cjs';
2
+ import '../agent-DxY68NZL.cjs';
3
3
  import 'zod';
4
+ import '../errors-C-so0M4t.cjs';
5
+ import '../types-4vbcmPTp.cjs';
6
+ import '../errors-CmVvczxZ.cjs';
4
7
 
5
8
  /**
6
9
  * @witqq/agent-sdk/chat/context
@@ -94,9 +97,13 @@ interface ContextWindowResult {
94
97
  /**
95
98
  * Context usage statistics for a session.
96
99
  * Returned by `IChatRuntime.getContextStats()`.
100
+ *
101
+ * When real usage data is available (after the first API response),
102
+ * `realPromptTokens` and `realCompletionTokens` contain actual token counts.
103
+ * `modelContextWindow` is the model's context window from `listModels()`.
97
104
  */
98
105
  interface ContextStats {
99
- /** Estimated total tokens in the trimmed context */
106
+ /** Estimated total tokens in the trimmed context (heuristic, kept for backward compat) */
100
107
  totalTokens: number;
101
108
  /** Number of messages removed by trimming */
102
109
  removedCount: number;
@@ -104,6 +111,12 @@ interface ContextStats {
104
111
  wasTruncated: boolean;
105
112
  /** Available token budget (maxTokens − reservedTokens) */
106
113
  availableBudget: number;
114
+ /** Real prompt tokens from the last API response (undefined before first response) */
115
+ realPromptTokens?: number;
116
+ /** Real completion tokens from the last API response (undefined before first response) */
117
+ realCompletionTokens?: number;
118
+ /** Model's context window in tokens from listModels() (undefined if not available) */
119
+ modelContextWindow?: number;
107
120
  }
108
121
  /**
109
122
  * Stateless context window manager.
@@ -148,6 +161,17 @@ declare class ContextWindowManager {
148
161
  * For other strategies, behaves identically to fitMessages().
149
162
  */
150
163
  fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult>;
164
+ /**
165
+ * Trim messages using real token usage data from the previous API call.
166
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
167
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
168
+ *
169
+ * @param messages - All messages in the session
170
+ * @param lastPromptTokens - Real prompt tokens from the last API response
171
+ * @param modelContextWindow - Model's total context window size in tokens
172
+ * @returns Result with fitted messages and metadata
173
+ */
174
+ fitMessagesWithUsage(messages: readonly ChatMessage[], lastPromptTokens: number, modelContextWindow: number): ContextWindowResult;
151
175
  /**
152
176
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
153
177
  * Always keeps the most recent user message.
@@ -1,6 +1,9 @@
1
- import { ChatMessage } from './core.js';
2
- import '../types-CqvUAYxt.js';
1
+ import { c as ChatMessage } from '../types-ajANVzf7.js';
2
+ import '../agent-CW9XbmG_.js';
3
3
  import 'zod';
4
+ import '../errors-C-so0M4t.js';
5
+ import '../types-BxggH0Yh.js';
6
+ import '../errors-CmVvczxZ.js';
4
7
 
5
8
  /**
6
9
  * @witqq/agent-sdk/chat/context
@@ -94,9 +97,13 @@ interface ContextWindowResult {
94
97
  /**
95
98
  * Context usage statistics for a session.
96
99
  * Returned by `IChatRuntime.getContextStats()`.
100
+ *
101
+ * When real usage data is available (after the first API response),
102
+ * `realPromptTokens` and `realCompletionTokens` contain actual token counts.
103
+ * `modelContextWindow` is the model's context window from `listModels()`.
97
104
  */
98
105
  interface ContextStats {
99
- /** Estimated total tokens in the trimmed context */
106
+ /** Estimated total tokens in the trimmed context (heuristic, kept for backward compat) */
100
107
  totalTokens: number;
101
108
  /** Number of messages removed by trimming */
102
109
  removedCount: number;
@@ -104,6 +111,12 @@ interface ContextStats {
104
111
  wasTruncated: boolean;
105
112
  /** Available token budget (maxTokens − reservedTokens) */
106
113
  availableBudget: number;
114
+ /** Real prompt tokens from the last API response (undefined before first response) */
115
+ realPromptTokens?: number;
116
+ /** Real completion tokens from the last API response (undefined before first response) */
117
+ realCompletionTokens?: number;
118
+ /** Model's context window in tokens from listModels() (undefined if not available) */
119
+ modelContextWindow?: number;
107
120
  }
108
121
  /**
109
122
  * Stateless context window manager.
@@ -148,6 +161,17 @@ declare class ContextWindowManager {
148
161
  * For other strategies, behaves identically to fitMessages().
149
162
  */
150
163
  fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult>;
164
+ /**
165
+ * Trim messages using real token usage data from the previous API call.
166
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
167
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
168
+ *
169
+ * @param messages - All messages in the session
170
+ * @param lastPromptTokens - Real prompt tokens from the last API response
171
+ * @param modelContextWindow - Model's total context window size in tokens
172
+ * @returns Result with fitted messages and metadata
173
+ */
174
+ fitMessagesWithUsage(messages: readonly ChatMessage[], lastPromptTokens: number, modelContextWindow: number): ContextWindowResult;
151
175
  /**
152
176
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
153
177
  * Always keeps the most recent user message.
@@ -106,6 +106,56 @@ var ContextWindowManager = class {
106
106
  });
107
107
  return { ...result, messages: updatedMessages };
108
108
  }
109
+ /**
110
+ * Trim messages using real token usage data from the previous API call.
111
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
112
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
113
+ *
114
+ * @param messages - All messages in the session
115
+ * @param lastPromptTokens - Real prompt tokens from the last API response
116
+ * @param modelContextWindow - Model's total context window size in tokens
117
+ * @returns Result with fitted messages and metadata
118
+ */
119
+ fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
120
+ if (messages.length === 0) {
121
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
122
+ }
123
+ const budget = modelContextWindow - this.config.reservedTokens;
124
+ if (budget <= 0 || lastPromptTokens <= budget) {
125
+ return {
126
+ messages: [...messages],
127
+ totalTokens: lastPromptTokens,
128
+ removedCount: 0,
129
+ wasTruncated: false
130
+ };
131
+ }
132
+ const avgTokensPerMessage = lastPromptTokens / messages.length;
133
+ const tokensToFree = lastPromptTokens - budget;
134
+ const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
135
+ const nonSystemIndices = [];
136
+ for (let i = 0; i < messages.length; i++) {
137
+ if (messages[i].role === "system") ; else {
138
+ nonSystemIndices.push(i);
139
+ }
140
+ }
141
+ const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
142
+ const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
143
+ const result = [];
144
+ for (let i = 0; i < messages.length; i++) {
145
+ if (!removedIndices.has(i)) {
146
+ result.push(messages[i]);
147
+ }
148
+ }
149
+ const estimatedTokens = Math.round(
150
+ lastPromptTokens * (result.length / messages.length)
151
+ );
152
+ return {
153
+ messages: result,
154
+ totalTokens: estimatedTokens,
155
+ removedCount: removableCount,
156
+ wasTruncated: removableCount > 0
157
+ };
158
+ }
109
159
  /**
110
160
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
111
161
  * Always keeps the most recent user message.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/chat/context.ts"],"names":[],"mappings":";AA4CO,SAAS,cAAA,CACd,SACA,OAAA,EACQ;AACR,EAAA,MAAM,KAAA,GAAQ,SAAS,aAAA,IAAiB,CAAA;AACxC,EAAA,IAAI,SAAA,GAAY,CAAA;AAGhB,EAAA,SAAA,IAAa,OAAA,CAAQ,KAAK,MAAA,GAAS,CAAA;AAGnC,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAQ,KAAA,EAAO;AAChC,IAAA,SAAA,IAAa,kBAAkB,IAAI,CAAA;AAAA,EACrC;AAEA,EAAA,OAAO,IAAA,CAAK,IAAA,CAAK,SAAA,GAAY,KAAK,CAAA;AACpC;AAEA,SAAS,kBAAkB,IAAA,EAA2B;AACpD,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,OAAO,KAAK,IAAA,CAAK,MAAA;AAAA,IACnB,KAAK,WAAA;AACH,MAAA,OAAO,KAAK,IAAA,CAAK,MAAA;AAAA,IACnB,KAAK,WAAA;AACH,MAAA,OAAO,KAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,GAAS,KAAK,IAAA,CAAK,MAAA,GAAS,EAAA,IAC1D,IAAA,CAAK,WAAW,MAAA,GAAY,IAAA,CAAK,UAAU,IAAA,CAAK,MAAM,EAAE,MAAA,GAAS,CAAA,CAAA;AAAA,IACtE,KAAK,QAAA;AACH,MAAA,OAAA,CAAQ,KAAK,KAAA,EAAO,MAAA,IAAU,CAAA,IAAK,IAAA,CAAK,IAAI,MAAA,GAAS,EAAA;AAAA,IACvD,KAAK,MAAA;AACH,MAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,KAAK,MAAA,GAAS,EAAA;AAAA;AAEnD;AAyGO,IAAM,uBAAN,MAA2B;AAAA,EACf,MAAA;AAAA,EAKjB,YAAY,MAAA,EAA6B;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,cAAA,EAAgB,OAAO,cAAA,IAAkB,CAAA;AAAA,MACzC,QAAA,EAAU,OAAO,QAAA,IAAY,iBAAA;AAAA,MAC7B,YAAY,MAAA,CAAO,UAAA;AAAA,MACnB,YAAY,MAAA,CAAO;AAAA,KACrB;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,eAAA,GAA0B;AAC5B,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,OAAO,SAAA,GAAY,IAAA,CAAK,OAAO,cAAc,CAAA;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB,OAAA,EAA8B;AAClD,IAAA,OAAO,cAAA,CAAe,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,UAAU,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,QAAA,EAAuD;AACjE,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,MAAA,OAAO,EAAE,UAAU,EAAC,EAAG,aAAa,CAAA,EAAG,YAAA,EAAc,CAAA,EAAG,YAAA,EAAc,KAAA,EAAM;AAAA,IAC9E;AAEA,IAAA,MAAM,SAAS,IAAA,CAAK,eAAA;AAGpB,IAAA,MAAM,WAAA,GAAc,SAAS,GAAA,CAAI,CAAC,MAAM,IAAA,CAAK,qBAAA,CAAsB,CAAC,CAAC,CAAA;AACrE,IAAA,MAAM,WAAA,GAAc,YAAY,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AAGzD,IAAA,IAAI,eAAe,MAAA,EAAQ;AACzB,MAAA,OAAO;AAAA,QACL,QAAA,EAAU,CAAC,GAAG,QAAQ,CAAA;AAAA,QACtB,WAAA;AAAA,QACA,YAAA,EAAc,CAAA;AAAA,QACd,YAAA,EAAc;AAAA,OAChB;AAAA,IACF;AAEA,IAAA,QAAQ,IAAA,CAAK,OAAO,QAAA;AAAU,MAC5B,KAAK,iBAAA;AACH,QAAA,OAAO,IAAA,CAAK,cAAA,CAAe,QAAA,EAAU,WAAA,EAAa,MAAM,CAAA;AAAA,MAC1D,KAAK,gBAAA;AACH,QAAA,OAAO,IAAA,CAAK,aAAA,CAAc,QAAA,EAAU,WAAA,EAAa,MAAM,CAAA;AAAA,MACzD,KAAK,uBAAA;AACH,QAAA,OAAO,IAAA,CAAK,oBAAA,CAAqB,QAAA,EAAU,WAAA,EAAa,MAAM,CAAA;AAAA;AAClE,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iBAAiB,QAAA,EAAgE;AACrF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,WAAA,CAAY,QAAQ,CAAA;AAGxC,IAAA,IACE,IAAA,CAAK,MAAA,CAAO,QAAA,KAAa,uBAAA,IACzB,CAAC,OAAO,YAAA,IACR,CAAC,IAAA,CAAK,MAAA,CAAO,UAAA,EACb;AACA,MAAA,OAAO,MAAA;AAAA,IACT;AAGA,IAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,EAAE,CAAC,CAAA;AACtD,IAAA,MAAM,OAAA,GAAU,SAAS,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AACvD,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,MAAA;AAGjC,IAAA,IAAI,WAAA;AACJ,IAAA,IAAI;AACF,MAAA,WAAA,GAAc,MAAM,IAAA,CAAK,MAAA,CAAO,UAAA,CAAW,OAAO,CAAA;AAAA,IACpD,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AAGA,IAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,KAAK;AAC/C,MAAA,IAAK,CAAA,CAAE,QAAA,EAAsC,SAAA,KAAc,IAAA,EAAM;AAC/D,QAAA,OAAO;AAAA,UACL,GAAG,CAAA;AAAA,UACH,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,QAAiB,IAAA,EAAM,WAAA,EAAa,MAAA,EAAQ,UAAA,EAAqB;AAAA,SACnF;AAAA,MACF;AACA,MAAA,OAAO,CAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,QAAA,EAAU,eAAA,EAAgB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,cAAA,CACN,QAAA,EACA,WAAA,EACA,MAAA,EACqB;AAErB,IAAA,MAAM,gBAA0B,EAAC;AACjC,IAAA,MAAM,mBAA6B,EAAC;AAEpC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA,KAAS,QAAA,EAAU;AACjC,QAAA,aAAA,CAAc,KAAK,CAAC,CAAA;AAAA,MACtB,CAAA,MAAO;AACL,QAAA,gBAAA,CAAiB,KAAK,CAAC,CAAA;AAAA,MACzB;AAAA,IACF;AAGA,IAAA,IAAI,aAAa,aAAA,CAAc,MAAA;AAAA,MAC7B,CAAC,GAAA,EAAK,CAAA,KAAM,GAAA,GAAM,YAAY,CAAC,CAAA;AAAA,MAC/B;AAAA,KACF;AAMA,IAAA,MAAM,oBAA8B,EAAC;AACrC,IAAA,KAAA,IAAS,IAAI,gBAAA,CAAiB,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AACrD,MAAA,MAAM,GAAA,GAAM,iBAAiB,CAAC,CAAA;AAC9B,MAAA,IAAI,UAAA,GAAa,WAAA,CAAY,GAAG,CAAA,IAAK,MAAA,EAAQ;AAC3C,QAAA,iBAAA,CAAkB,QAAQ,GAAG,CAAA;AAC7B,QAAA,UAAA,IAAc,YAAY,GAAG,CAAA;AAAA,MAC/B;AAAA,IACF;AAGA,IAAA,MAAM,WAAA,uBAAkB,GAAA,CAAI,CAAC,GAAG,aAAA,EAAe,GAAG,iBAAiB,CAAC,CAAA;AACpE,IAAA,MAAM,SAAwB,EAAC;AAC/B,IAAA,IAAI,YAAA,GAAe,CAAA;AACnB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,EAAG;AACtB,QAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,CAAC,CAAC,CAAA;AACvB,QAAA,YAAA,IAAgB,YAAY,CAAC,CAAA;AAAA,MAC/B;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,YAAA;AAAA,MACb,YAAA,EAAc,QAAA,CAAS,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,MACvC,YAAA,EAAc;AAAA,KAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAA,CACN,QAAA,EACA,WAAA,EACA,MAAA,EACqB;AACrB,IAAA,MAAM,SAAwB,EAAC;AAC/B,IAAA,IAAI,UAAA,GAAa,CAAA;AAGjB,IAAA,KAAA,IAAS,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC7C,MAAA,IAAI,UAAA,GAAa,WAAA,CAAY,CAAC,CAAA,IAAK,MAAA,EAAQ;AACzC,QAAA,MAAA,CAAO,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAC,CAAA;AAC1B,QAAA,UAAA,IAAc,YAAY,CAAC,CAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,UAAA;AAAA,MACb,YAAA,EAAc,QAAA,CAAS,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,MACvC,YAAA,EAAc;AAAA,KAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAA,CACN,QAAA,EACA,WAAA,EACA,MAAA,EACqB;AAErB,IAAA,MAAM,iBAAyD,EAAC;AAChE,IAAA,MAAM,YAAiE,EAAC;AAExE,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA,KAAS,QAAA,EAAU;AACjC,QAAA,cAAA,CAAe,IAAA,CAAK,EAAE,GAAA,EAAK,QAAA,CAAS,CAAC,GAAG,MAAA,EAAQ,WAAA,CAAY,CAAC,CAAA,EAAG,CAAA;AAAA,MAClE,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,IAAA,CAAK,EAAE,GAAA,EAAK,QAAA,CAAS,CAAC,CAAA,EAAG,MAAA,EAAQ,WAAA,CAAY,CAAC,CAAA,EAAG,GAAA,EAAK,CAAA,EAAG,CAAA;AAAA,MACrE;AAAA,IACF;AAGA,IAAA,IAAI,UAAA,GAAa,eAAe,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAC,CAAA;AAGhE,IAAA,MAAM,iBAAA,GAAoB,EAAA;AAC1B,IAAA,UAAA,IAAc,iBAAA;AAGd,IAAA,MAAM,aAA+B,EAAC;AACtC,IAAA,KAAA,IAAS,IAAI,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC9C,MAAA,IAAI,UAAA,GAAa,SAAA,CAAU,CAAC,CAAA,CAAE,UAAU,MAAA,EAAQ;AAC9C,QAAA,UAAA,CAAW,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAC,CAAA;AAC/B,QAAA,UAAA,IAAc,SAAA,CAAU,CAAC,CAAA,CAAE,MAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GACJ,QAAA,CAAS,MAAA,GACT,cAAA,CAAe,SACf,UAAA,CAAW,MAAA;AAGb,IAAA,MAAM,SAAwB,EAAC;AAG/B,IAAA,KAAA,MAAW,MAAM,cAAA,EAAgB;AAC/B,MAAA,MAAA,CAAO,IAAA,CAAK,GAAG,GAAG,CAAA;AAAA,IACpB;AAGA,IAAA,IAAI,eAAe,CAAA,EAAG;AACpB,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,EAAA,EAAI,qBAAA;AAAA,QACJ,IAAA,EAAM,QAAA;AAAA,QACN,OAAO,CAAC,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,CAAA,CAAA,EAAI,YAAY,CAAA,gBAAA,EAAmB,YAAA,KAAiB,IAAI,EAAA,GAAK,GAAG,CAAA,4BAAA,CAAA,EAAgC,MAAA,EAAQ,YAAqB,CAAA;AAAA,QAC3J,QAAA,EAAU,EAAE,SAAA,EAAW,IAAA,EAAK;AAAA,QAC5B,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,QAClC,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH;AAGA,IAAA,KAAA,MAAW,KAAK,UAAA,EAAY;AAC1B,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,GAAG,CAAA;AAAA,IACnB;AAEA,IAAA,OAAO;AAAA,MACL,QAAA,EAAU,MAAA;AAAA,MACV,WAAA,EAAa,UAAA;AAAA,MACb,YAAA;AAAA,MACA,YAAA,EAAc;AAAA,KAChB;AAAA,EACF;AACF","file":"context.js","sourcesContent":["/**\n * @witqq/agent-sdk/chat/context\n *\n * Context window manager for selecting which messages fit within a token budget.\n * Stateless: takes messages in, returns trimmed messages out.\n * Three overflow strategies: truncate-oldest, sliding-window, summarize-placeholder.\n */\n\nimport type { ChatMessage, MessagePart } from \"./core.js\";\n\n// ─── Token Estimation ──────────────────────────────────────────\n\n/**\n * Options for token estimation.\n */\nexport interface TokenEstimationOptions {\n /**\n * Characters per token ratio.\n * Lower = more conservative (fewer messages fit).\n * @default 4\n */\n charsPerToken?: number;\n}\n\n/**\n * Estimate token count for a single chat message.\n * Uses character-based heuristic: `Math.ceil(charCount / charsPerToken)`.\n *\n * Counts:\n * - Text content (string or text parts)\n * - Serialized tool calls and tool results\n * - Thinking blocks\n * - Role overhead (~4 tokens)\n *\n * @param message - Chat message to estimate\n * @param options - Estimation options\n * @returns Estimated token count\n *\n * @example\n * ```typescript\n * const tokens = estimateTokens(message);\n * const conservative = estimateTokens(message, { charsPerToken: 3 });\n * ```\n */\nexport function estimateTokens(\n message: ChatMessage,\n options?: TokenEstimationOptions,\n): number {\n const ratio = options?.charsPerToken ?? 4;\n let charCount = 0;\n\n // Role overhead\n charCount += message.role.length + 4;\n\n // Parts\n for (const part of message.parts) {\n charCount += estimatePartChars(part);\n }\n\n return Math.ceil(charCount / ratio);\n}\n\nfunction estimatePartChars(part: MessagePart): number {\n switch (part.type) {\n case \"text\":\n return part.text.length;\n case \"reasoning\":\n return part.text.length;\n case \"tool_call\":\n return JSON.stringify(part.args).length + part.name.length + 20 +\n (part.result !== undefined ? JSON.stringify(part.result).length : 0);\n case \"source\":\n return (part.title?.length ?? 0) + part.url.length + 10;\n case \"file\":\n return part.name.length + part.data.length + 20;\n }\n}\n\n// ─── Overflow Strategies ───────────────────────────────────────\n\n/** Overflow strategy type */\nexport type OverflowStrategy =\n | \"truncate-oldest\"\n | \"sliding-window\"\n | \"summarize-placeholder\";\n\n// ─── Context Window Configuration ──────────────────────────────\n\n/**\n * Async summarizer function for the summarize-placeholder strategy.\n * Receives removed messages and returns a summary string.\n * When configured, replaces the static placeholder text with actual summary.\n */\nexport type ContextSummarizer = (removedMessages: readonly ChatMessage[]) => Promise<string>;\n\n/**\n * Configuration for the context window manager.\n */\nexport interface ContextWindowConfig {\n /** Maximum token budget for the context window */\n maxTokens: number;\n\n /**\n * Tokens reserved for system prompt and response generation.\n * Subtracted from maxTokens to get available budget.\n * @default 0\n */\n reservedTokens?: number;\n\n /**\n * Strategy for handling overflow when messages exceed budget.\n * @default \"truncate-oldest\"\n */\n strategy?: OverflowStrategy;\n\n /**\n * Token estimation options.\n */\n estimation?: TokenEstimationOptions;\n\n /**\n * Optional async summarizer for the summarize-placeholder strategy.\n * When provided, replaces the static placeholder with a generated summary.\n * Falls back to static placeholder if summarizer throws.\n */\n summarizer?: ContextSummarizer;\n}\n\n// ─── Context Window Result ─────────────────────────────────────\n\n/**\n * Result of context window trimming.\n */\nexport interface ContextWindowResult {\n /** Messages that fit within the budget */\n messages: ChatMessage[];\n /** Total estimated tokens for included messages */\n totalTokens: number;\n /** Number of messages removed */\n removedCount: number;\n /** Whether any messages were truncated */\n wasTruncated: boolean;\n}\n\n// ─── Context Stats ─────────────────────────────────────────────\n\n/**\n * Context usage statistics for a session.\n * Returned by `IChatRuntime.getContextStats()`.\n */\nexport interface ContextStats {\n /** Estimated total tokens in the trimmed context */\n totalTokens: number;\n /** Number of messages removed by trimming */\n removedCount: number;\n /** Whether context was truncated */\n wasTruncated: boolean;\n /** Available token budget (maxTokens − reservedTokens) */\n availableBudget: number;\n}\n\n// ─── Context Window Manager ────────────────────────────────────\n\n/**\n * Stateless context window manager.\n * Takes messages and returns the subset that fits within a token budget.\n *\n * @example\n * ```typescript\n * const manager = new ContextWindowManager({\n * maxTokens: 4096,\n * reservedTokens: 500,\n * strategy: \"sliding-window\",\n * });\n *\n * const result = manager.fitMessages(messages);\n * // result.messages — trimmed to fit budget\n * // result.totalTokens — estimated token usage\n * // result.wasTruncated — whether messages were removed\n * ```\n */\nexport class ContextWindowManager {\n private readonly config: Required<\n Pick<ContextWindowConfig, \"maxTokens\" | \"reservedTokens\" | \"strategy\">\n > &\n Pick<ContextWindowConfig, \"estimation\" | \"summarizer\">;\n\n constructor(config: ContextWindowConfig) {\n this.config = {\n maxTokens: config.maxTokens,\n reservedTokens: config.reservedTokens ?? 0,\n strategy: config.strategy ?? \"truncate-oldest\",\n estimation: config.estimation,\n summarizer: config.summarizer,\n };\n }\n\n /** Available token budget after reserving tokens */\n get availableBudget(): number {\n return Math.max(0, this.config.maxTokens - this.config.reservedTokens);\n }\n\n /**\n * Estimate tokens for a single message.\n * @param message - Message to estimate\n * @returns Estimated token count\n */\n estimateMessageTokens(message: ChatMessage): number {\n return estimateTokens(message, this.config.estimation);\n }\n\n /**\n * Fit messages within the token budget using the configured strategy.\n * @param messages - All messages to consider\n * @returns Result with fitted messages and metadata\n */\n fitMessages(messages: readonly ChatMessage[]): ContextWindowResult {\n if (messages.length === 0) {\n return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };\n }\n\n const budget = this.availableBudget;\n\n // Calculate tokens for each message\n const tokenCounts = messages.map((m) => this.estimateMessageTokens(m));\n const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);\n\n // All messages fit\n if (totalTokens <= budget) {\n return {\n messages: [...messages],\n totalTokens,\n removedCount: 0,\n wasTruncated: false,\n };\n }\n\n switch (this.config.strategy) {\n case \"truncate-oldest\":\n return this.truncateOldest(messages, tokenCounts, budget);\n case \"sliding-window\":\n return this.slidingWindow(messages, tokenCounts, budget);\n case \"summarize-placeholder\":\n return this.summarizePlaceholder(messages, tokenCounts, budget);\n }\n }\n\n /**\n * Async variant of fitMessages that supports async summarization.\n * When strategy is \"summarize-placeholder\" and a summarizer is configured,\n * calls the summarizer with removed messages and replaces the placeholder text.\n * Falls back to static placeholder if summarizer throws.\n * For other strategies, behaves identically to fitMessages().\n */\n async fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult> {\n const result = this.fitMessages(messages);\n\n // Only enhance if summarize-placeholder strategy, messages were removed, and summarizer is configured\n if (\n this.config.strategy !== \"summarize-placeholder\" ||\n !result.wasTruncated ||\n !this.config.summarizer\n ) {\n return result;\n }\n\n // Find removed messages (those in original but not in result)\n const keptIds = new Set(result.messages.map(m => m.id));\n const removed = messages.filter(m => !keptIds.has(m.id));\n if (removed.length === 0) return result;\n\n // Call async summarizer, fall back to static placeholder on error\n let summaryText: string;\n try {\n summaryText = await this.config.summarizer(removed);\n } catch {\n return result; // Keep static placeholder on summarizer failure\n }\n\n // Replace placeholder text with summarizer output\n const updatedMessages = result.messages.map(m => {\n if ((m.metadata as Record<string, unknown>)?.isSummary === true) {\n return {\n ...m,\n parts: [{ type: \"text\" as const, text: summaryText, status: \"complete\" as const }],\n };\n }\n return m;\n });\n\n return { ...result, messages: updatedMessages };\n }\n\n /**\n * Truncate oldest: keeps system messages, removes oldest non-system messages first.\n * Always keeps the most recent user message.\n */\n private truncateOldest(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // Separate system messages (always kept) and non-system\n const systemIndices: number[] = [];\n const nonSystemIndices: number[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemIndices.push(i);\n } else {\n nonSystemIndices.push(i);\n }\n }\n\n // System messages cost\n let usedTokens = systemIndices.reduce(\n (sum, i) => sum + tokenCounts[i],\n 0,\n );\n\n // If system messages alone exceed budget, still include them\n // (caller should configure reservedTokens properly)\n\n // Try to fit non-system from newest to oldest\n const includedNonSystem: number[] = [];\n for (let i = nonSystemIndices.length - 1; i >= 0; i--) {\n const idx = nonSystemIndices[i];\n if (usedTokens + tokenCounts[idx] <= budget) {\n includedNonSystem.unshift(idx);\n usedTokens += tokenCounts[idx];\n }\n }\n\n // Build result preserving original order\n const includedSet = new Set([...systemIndices, ...includedNonSystem]);\n const result: ChatMessage[] = [];\n let resultTokens = 0;\n for (let i = 0; i < messages.length; i++) {\n if (includedSet.has(i)) {\n result.push(messages[i]);\n resultTokens += tokenCounts[i];\n }\n }\n\n return {\n messages: result,\n totalTokens: resultTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Sliding window: keeps the most recent messages that fit within budget.\n */\n private slidingWindow(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n const result: ChatMessage[] = [];\n let usedTokens = 0;\n\n // Walk from newest to oldest\n for (let i = messages.length - 1; i >= 0; i--) {\n if (usedTokens + tokenCounts[i] <= budget) {\n result.unshift(messages[i]);\n usedTokens += tokenCounts[i];\n } else {\n break;\n }\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Summarize placeholder: replaces truncated messages with a placeholder,\n * preserving system messages and recent context.\n */\n private summarizePlaceholder(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // First, identify system messages and recent messages\n const systemMessages: { msg: ChatMessage; tokens: number }[] = [];\n const nonSystem: { msg: ChatMessage; tokens: number; idx: number }[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemMessages.push({ msg: messages[i], tokens: tokenCounts[i] });\n } else {\n nonSystem.push({ msg: messages[i], tokens: tokenCounts[i], idx: i });\n }\n }\n\n // System message cost\n let usedTokens = systemMessages.reduce((s, m) => s + m.tokens, 0);\n\n // Placeholder costs ~20 tokens\n const placeholderTokens = 20;\n usedTokens += placeholderTokens;\n\n // Fit recent non-system messages from newest\n const recentKept: typeof nonSystem = [];\n for (let i = nonSystem.length - 1; i >= 0; i--) {\n if (usedTokens + nonSystem[i].tokens <= budget) {\n recentKept.unshift(nonSystem[i]);\n usedTokens += nonSystem[i].tokens;\n } else {\n break;\n }\n }\n\n const removedCount =\n messages.length -\n systemMessages.length -\n recentKept.length;\n\n // Build result: system messages, placeholder, recent messages\n const result: ChatMessage[] = [];\n\n // System messages first\n for (const sm of systemMessages) {\n result.push(sm.msg);\n }\n\n // Placeholder if messages were removed\n if (removedCount > 0) {\n result.push({\n id: \"context-placeholder\" as ChatMessage[\"id\"],\n role: \"system\",\n parts: [{ type: \"text\", text: `[${removedCount} earlier message${removedCount === 1 ? \"\" : \"s\"} omitted for context window]`, status: \"complete\" as const }],\n metadata: { isSummary: true },\n createdAt: new Date().toISOString(),\n status: \"complete\",\n });\n }\n\n // Recent messages\n for (const m of recentKept) {\n result.push(m.msg);\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount,\n wasTruncated: true,\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/chat/context.ts"],"names":[],"mappings":"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","file":"context.js","sourcesContent":["/**\n * @witqq/agent-sdk/chat/context\n *\n * Context window manager for selecting which messages fit within a token budget.\n * Stateless: takes messages in, returns trimmed messages out.\n * Three overflow strategies: truncate-oldest, sliding-window, summarize-placeholder.\n */\n\nimport type { ChatMessage, MessagePart } from \"./core.js\";\n\n// ─── Token Estimation ──────────────────────────────────────────\n\n/**\n * Options for token estimation.\n */\nexport interface TokenEstimationOptions {\n /**\n * Characters per token ratio.\n * Lower = more conservative (fewer messages fit).\n * @default 4\n */\n charsPerToken?: number;\n}\n\n/**\n * Estimate token count for a single chat message.\n * Uses character-based heuristic: `Math.ceil(charCount / charsPerToken)`.\n *\n * Counts:\n * - Text content (string or text parts)\n * - Serialized tool calls and tool results\n * - Thinking blocks\n * - Role overhead (~4 tokens)\n *\n * @param message - Chat message to estimate\n * @param options - Estimation options\n * @returns Estimated token count\n *\n * @example\n * ```typescript\n * const tokens = estimateTokens(message);\n * const conservative = estimateTokens(message, { charsPerToken: 3 });\n * ```\n */\nexport function estimateTokens(\n message: ChatMessage,\n options?: TokenEstimationOptions,\n): number {\n const ratio = options?.charsPerToken ?? 4;\n let charCount = 0;\n\n // Role overhead\n charCount += message.role.length + 4;\n\n // Parts\n for (const part of message.parts) {\n charCount += estimatePartChars(part);\n }\n\n return Math.ceil(charCount / ratio);\n}\n\nfunction estimatePartChars(part: MessagePart): number {\n switch (part.type) {\n case \"text\":\n return part.text.length;\n case \"reasoning\":\n return part.text.length;\n case \"tool_call\":\n return JSON.stringify(part.args).length + part.name.length + 20 +\n (part.result !== undefined ? JSON.stringify(part.result).length : 0);\n case \"source\":\n return (part.title?.length ?? 0) + part.url.length + 10;\n case \"file\":\n return part.name.length + part.data.length + 20;\n }\n}\n\n// ─── Overflow Strategies ───────────────────────────────────────\n\n/** Overflow strategy type */\nexport type OverflowStrategy =\n | \"truncate-oldest\"\n | \"sliding-window\"\n | \"summarize-placeholder\";\n\n// ─── Context Window Configuration ──────────────────────────────\n\n/**\n * Async summarizer function for the summarize-placeholder strategy.\n * Receives removed messages and returns a summary string.\n * When configured, replaces the static placeholder text with actual summary.\n */\nexport type ContextSummarizer = (removedMessages: readonly ChatMessage[]) => Promise<string>;\n\n/**\n * Configuration for the context window manager.\n */\nexport interface ContextWindowConfig {\n /** Maximum token budget for the context window */\n maxTokens: number;\n\n /**\n * Tokens reserved for system prompt and response generation.\n * Subtracted from maxTokens to get available budget.\n * @default 0\n */\n reservedTokens?: number;\n\n /**\n * Strategy for handling overflow when messages exceed budget.\n * @default \"truncate-oldest\"\n */\n strategy?: OverflowStrategy;\n\n /**\n * Token estimation options.\n */\n estimation?: TokenEstimationOptions;\n\n /**\n * Optional async summarizer for the summarize-placeholder strategy.\n * When provided, replaces the static placeholder with a generated summary.\n * Falls back to static placeholder if summarizer throws.\n */\n summarizer?: ContextSummarizer;\n}\n\n// ─── Context Window Result ─────────────────────────────────────\n\n/**\n * Result of context window trimming.\n */\nexport interface ContextWindowResult {\n /** Messages that fit within the budget */\n messages: ChatMessage[];\n /** Total estimated tokens for included messages */\n totalTokens: number;\n /** Number of messages removed */\n removedCount: number;\n /** Whether any messages were truncated */\n wasTruncated: boolean;\n}\n\n// ─── Context Stats ─────────────────────────────────────────────\n\n/**\n * Context usage statistics for a session.\n * Returned by `IChatRuntime.getContextStats()`.\n *\n * When real usage data is available (after the first API response),\n * `realPromptTokens` and `realCompletionTokens` contain actual token counts.\n * `modelContextWindow` is the model's context window from `listModels()`.\n */\nexport interface ContextStats {\n /** Estimated total tokens in the trimmed context (heuristic, kept for backward compat) */\n totalTokens: number;\n /** Number of messages removed by trimming */\n removedCount: number;\n /** Whether context was truncated */\n wasTruncated: boolean;\n /** Available token budget (maxTokens − reservedTokens) */\n availableBudget: number;\n /** Real prompt tokens from the last API response (undefined before first response) */\n realPromptTokens?: number;\n /** Real completion tokens from the last API response (undefined before first response) */\n realCompletionTokens?: number;\n /** Model's context window in tokens from listModels() (undefined if not available) */\n modelContextWindow?: number;\n}\n\n// ─── Context Window Manager ────────────────────────────────────\n\n/**\n * Stateless context window manager.\n * Takes messages and returns the subset that fits within a token budget.\n *\n * @example\n * ```typescript\n * const manager = new ContextWindowManager({\n * maxTokens: 4096,\n * reservedTokens: 500,\n * strategy: \"sliding-window\",\n * });\n *\n * const result = manager.fitMessages(messages);\n * // result.messages — trimmed to fit budget\n * // result.totalTokens — estimated token usage\n * // result.wasTruncated — whether messages were removed\n * ```\n */\nexport class ContextWindowManager {\n private readonly config: Required<\n Pick<ContextWindowConfig, \"maxTokens\" | \"reservedTokens\" | \"strategy\">\n > &\n Pick<ContextWindowConfig, \"estimation\" | \"summarizer\">;\n\n constructor(config: ContextWindowConfig) {\n this.config = {\n maxTokens: config.maxTokens,\n reservedTokens: config.reservedTokens ?? 0,\n strategy: config.strategy ?? \"truncate-oldest\",\n estimation: config.estimation,\n summarizer: config.summarizer,\n };\n }\n\n /** Available token budget after reserving tokens */\n get availableBudget(): number {\n return Math.max(0, this.config.maxTokens - this.config.reservedTokens);\n }\n\n /**\n * Estimate tokens for a single message.\n * @param message - Message to estimate\n * @returns Estimated token count\n */\n estimateMessageTokens(message: ChatMessage): number {\n return estimateTokens(message, this.config.estimation);\n }\n\n /**\n * Fit messages within the token budget using the configured strategy.\n * @param messages - All messages to consider\n * @returns Result with fitted messages and metadata\n */\n fitMessages(messages: readonly ChatMessage[]): ContextWindowResult {\n if (messages.length === 0) {\n return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };\n }\n\n const budget = this.availableBudget;\n\n // Calculate tokens for each message\n const tokenCounts = messages.map((m) => this.estimateMessageTokens(m));\n const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);\n\n // All messages fit\n if (totalTokens <= budget) {\n return {\n messages: [...messages],\n totalTokens,\n removedCount: 0,\n wasTruncated: false,\n };\n }\n\n switch (this.config.strategy) {\n case \"truncate-oldest\":\n return this.truncateOldest(messages, tokenCounts, budget);\n case \"sliding-window\":\n return this.slidingWindow(messages, tokenCounts, budget);\n case \"summarize-placeholder\":\n return this.summarizePlaceholder(messages, tokenCounts, budget);\n }\n }\n\n /**\n * Async variant of fitMessages that supports async summarization.\n * When strategy is \"summarize-placeholder\" and a summarizer is configured,\n * calls the summarizer with removed messages and replaces the placeholder text.\n * Falls back to static placeholder if summarizer throws.\n * For other strategies, behaves identically to fitMessages().\n */\n async fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult> {\n const result = this.fitMessages(messages);\n\n // Only enhance if summarize-placeholder strategy, messages were removed, and summarizer is configured\n if (\n this.config.strategy !== \"summarize-placeholder\" ||\n !result.wasTruncated ||\n !this.config.summarizer\n ) {\n return result;\n }\n\n // Find removed messages (those in original but not in result)\n const keptIds = new Set(result.messages.map(m => m.id));\n const removed = messages.filter(m => !keptIds.has(m.id));\n if (removed.length === 0) return result;\n\n // Call async summarizer, fall back to static placeholder on error\n let summaryText: string;\n try {\n summaryText = await this.config.summarizer(removed);\n } catch {\n return result; // Keep static placeholder on summarizer failure\n }\n\n // Replace placeholder text with summarizer output\n const updatedMessages = result.messages.map(m => {\n if ((m.metadata as Record<string, unknown>)?.isSummary === true) {\n return {\n ...m,\n parts: [{ type: \"text\" as const, text: summaryText, status: \"complete\" as const }],\n };\n }\n return m;\n });\n\n return { ...result, messages: updatedMessages };\n }\n\n /**\n * Trim messages using real token usage data from the previous API call.\n * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.\n * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.\n *\n * @param messages - All messages in the session\n * @param lastPromptTokens - Real prompt tokens from the last API response\n * @param modelContextWindow - Model's total context window size in tokens\n * @returns Result with fitted messages and metadata\n */\n fitMessagesWithUsage(\n messages: readonly ChatMessage[],\n lastPromptTokens: number,\n modelContextWindow: number,\n ): ContextWindowResult {\n if (messages.length === 0) {\n return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };\n }\n\n const budget = modelContextWindow - this.config.reservedTokens;\n if (budget <= 0 || lastPromptTokens <= budget) {\n return {\n messages: [...messages],\n totalTokens: lastPromptTokens,\n removedCount: 0,\n wasTruncated: false,\n };\n }\n\n // Average tokens per message from real data\n const avgTokensPerMessage = lastPromptTokens / messages.length;\n\n // How many tokens we need to free\n const tokensToFree = lastPromptTokens - budget;\n // How many messages to remove (ceil to be safe)\n const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);\n\n // Separate system and non-system messages\n const systemIndices: number[] = [];\n const nonSystemIndices: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemIndices.push(i);\n } else {\n nonSystemIndices.push(i);\n }\n }\n\n // Remove oldest non-system messages (from the beginning of conversation)\n const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);\n const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));\n\n const result: ChatMessage[] = [];\n for (let i = 0; i < messages.length; i++) {\n if (!removedIndices.has(i)) {\n result.push(messages[i]);\n }\n }\n\n // Estimate new total: proportional reduction\n const estimatedTokens = Math.round(\n lastPromptTokens * (result.length / messages.length),\n );\n\n return {\n messages: result,\n totalTokens: estimatedTokens,\n removedCount: removableCount,\n wasTruncated: removableCount > 0,\n };\n }\n\n /**\n * Truncate oldest: keeps system messages, removes oldest non-system messages first.\n * Always keeps the most recent user message.\n */\n private truncateOldest(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // Separate system messages (always kept) and non-system\n const systemIndices: number[] = [];\n const nonSystemIndices: number[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemIndices.push(i);\n } else {\n nonSystemIndices.push(i);\n }\n }\n\n // System messages cost\n let usedTokens = systemIndices.reduce(\n (sum, i) => sum + tokenCounts[i],\n 0,\n );\n\n // If system messages alone exceed budget, still include them\n // (caller should configure reservedTokens properly)\n\n // Try to fit non-system from newest to oldest\n const includedNonSystem: number[] = [];\n for (let i = nonSystemIndices.length - 1; i >= 0; i--) {\n const idx = nonSystemIndices[i];\n if (usedTokens + tokenCounts[idx] <= budget) {\n includedNonSystem.unshift(idx);\n usedTokens += tokenCounts[idx];\n }\n }\n\n // Build result preserving original order\n const includedSet = new Set([...systemIndices, ...includedNonSystem]);\n const result: ChatMessage[] = [];\n let resultTokens = 0;\n for (let i = 0; i < messages.length; i++) {\n if (includedSet.has(i)) {\n result.push(messages[i]);\n resultTokens += tokenCounts[i];\n }\n }\n\n return {\n messages: result,\n totalTokens: resultTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Sliding window: keeps the most recent messages that fit within budget.\n */\n private slidingWindow(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n const result: ChatMessage[] = [];\n let usedTokens = 0;\n\n // Walk from newest to oldest\n for (let i = messages.length - 1; i >= 0; i--) {\n if (usedTokens + tokenCounts[i] <= budget) {\n result.unshift(messages[i]);\n usedTokens += tokenCounts[i];\n } else {\n break;\n }\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount: messages.length - result.length,\n wasTruncated: true,\n };\n }\n\n /**\n * Summarize placeholder: replaces truncated messages with a placeholder,\n * preserving system messages and recent context.\n */\n private summarizePlaceholder(\n messages: readonly ChatMessage[],\n tokenCounts: number[],\n budget: number,\n ): ContextWindowResult {\n // First, identify system messages and recent messages\n const systemMessages: { msg: ChatMessage; tokens: number }[] = [];\n const nonSystem: { msg: ChatMessage; tokens: number; idx: number }[] = [];\n\n for (let i = 0; i < messages.length; i++) {\n if (messages[i].role === \"system\") {\n systemMessages.push({ msg: messages[i], tokens: tokenCounts[i] });\n } else {\n nonSystem.push({ msg: messages[i], tokens: tokenCounts[i], idx: i });\n }\n }\n\n // System message cost\n let usedTokens = systemMessages.reduce((s, m) => s + m.tokens, 0);\n\n // Placeholder costs ~20 tokens\n const placeholderTokens = 20;\n usedTokens += placeholderTokens;\n\n // Fit recent non-system messages from newest\n const recentKept: typeof nonSystem = [];\n for (let i = nonSystem.length - 1; i >= 0; i--) {\n if (usedTokens + nonSystem[i].tokens <= budget) {\n recentKept.unshift(nonSystem[i]);\n usedTokens += nonSystem[i].tokens;\n } else {\n break;\n }\n }\n\n const removedCount =\n messages.length -\n systemMessages.length -\n recentKept.length;\n\n // Build result: system messages, placeholder, recent messages\n const result: ChatMessage[] = [];\n\n // System messages first\n for (const sm of systemMessages) {\n result.push(sm.msg);\n }\n\n // Placeholder if messages were removed\n if (removedCount > 0) {\n result.push({\n id: \"context-placeholder\" as ChatMessage[\"id\"],\n role: \"system\",\n parts: [{ type: \"text\", text: `[${removedCount} earlier message${removedCount === 1 ? \"\" : \"s\"} omitted for context window]`, status: \"complete\" as const }],\n metadata: { isSummary: true },\n createdAt: new Date().toISOString(),\n status: \"complete\",\n });\n }\n\n // Recent messages\n for (const m of recentKept) {\n result.push(m.msg);\n }\n\n return {\n messages: result,\n totalTokens: usedTokens,\n removedCount,\n wasTruncated: true,\n };\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // src/chat/core.ts
3
+ // src/chat/types.ts
4
4
  function createChatId() {
5
5
  return crypto.randomUUID();
6
6
  }
@@ -11,6 +11,20 @@ function toChatId(value) {
11
11
  }
12
12
  return value;
13
13
  }
14
+ function createTextMessage(text, role = "user") {
15
+ return {
16
+ id: createChatId(),
17
+ role,
18
+ parts: [{ type: "text", text, status: "complete" }],
19
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
20
+ status: "complete"
21
+ };
22
+ }
23
+ function isObservableSession(session) {
24
+ return "subscribe" in session && typeof session.subscribe === "function" && "getSnapshot" in session && typeof session.getSnapshot === "function";
25
+ }
26
+
27
+ // src/chat/chat-utils.ts
14
28
  function getMessageText(message) {
15
29
  return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
16
30
  }
@@ -20,6 +34,8 @@ function getMessageToolCalls(message) {
20
34
  function getMessageReasoning(message) {
21
35
  return message.parts.filter((p) => p.type === "reasoning").map((p) => p.text).join("");
22
36
  }
37
+
38
+ // src/chat/guards.ts
23
39
  function isChatMessage(value) {
24
40
  if (typeof value !== "object" || value === null) return false;
25
41
  const obj = value;
@@ -85,6 +101,8 @@ function isChatEvent(value) {
85
101
  ];
86
102
  return validTypes.includes(obj.type);
87
103
  }
104
+
105
+ // src/chat/bridge.ts
88
106
  function agentEventToChatEvent(event, messageId) {
89
107
  switch (event.type) {
90
108
  case "text_delta":
@@ -137,6 +155,7 @@ function agentEventToChatEvent(event, messageId) {
137
155
  type: "error",
138
156
  error: event.error,
139
157
  recoverable: event.recoverable,
158
+ code: event.code,
140
159
  messageId
141
160
  };
142
161
  case "heartbeat":
@@ -183,11 +202,13 @@ function chatEventToAgentEvent(event) {
183
202
  result: event.result
184
203
  };
185
204
  case "error":
186
- return { type: "error", error: event.error, recoverable: event.recoverable };
205
+ return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
187
206
  default:
188
207
  return null;
189
208
  }
190
209
  }
210
+
211
+ // src/chat/conversion.ts
191
212
  function toAgentMessage(message) {
192
213
  const textContent = getMessageText(message);
193
214
  const toolCallParts = getMessageToolCalls(message);
@@ -262,6 +283,7 @@ exports.adaptAgentEvents = adaptAgentEvents;
262
283
  exports.agentEventToChatEvent = agentEventToChatEvent;
263
284
  exports.chatEventToAgentEvent = chatEventToAgentEvent;
264
285
  exports.createChatId = createChatId;
286
+ exports.createTextMessage = createTextMessage;
265
287
  exports.extractToolResults = extractToolResults;
266
288
  exports.fromAgentMessage = fromAgentMessage;
267
289
  exports.getMessageReasoning = getMessageReasoning;
@@ -272,6 +294,7 @@ exports.isChatMessage = isChatMessage;
272
294
  exports.isChatSession = isChatSession;
273
295
  exports.isFilePart = isFilePart;
274
296
  exports.isMessagePart = isMessagePart;
297
+ exports.isObservableSession = isObservableSession;
275
298
  exports.isReasoningPart = isReasoningPart;
276
299
  exports.isSourcePart = isSourcePart;
277
300
  exports.isTextPart = isTextPart;