@witqq/agent-sdk 0.6.1 → 0.7.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 (122) hide show
  1. package/README.md +433 -6
  2. package/dist/auth/index.cjs +188 -1
  3. package/dist/auth/index.cjs.map +1 -1
  4. package/dist/auth/index.d.cts +154 -138
  5. package/dist/auth/index.d.ts +154 -138
  6. package/dist/auth/index.js +188 -2
  7. package/dist/auth/index.js.map +1 -1
  8. package/dist/backends/claude.cjs +315 -21
  9. package/dist/backends/claude.cjs.map +1 -1
  10. package/dist/backends/claude.d.cts +2 -1
  11. package/dist/backends/claude.d.ts +2 -1
  12. package/dist/backends/claude.js +315 -21
  13. package/dist/backends/claude.js.map +1 -1
  14. package/dist/backends/copilot.cjs +132 -24
  15. package/dist/backends/copilot.cjs.map +1 -1
  16. package/dist/backends/copilot.d.cts +2 -1
  17. package/dist/backends/copilot.d.ts +2 -1
  18. package/dist/backends/copilot.js +132 -24
  19. package/dist/backends/copilot.js.map +1 -1
  20. package/dist/backends/vercel-ai.cjs +56 -17
  21. package/dist/backends/vercel-ai.cjs.map +1 -1
  22. package/dist/backends/vercel-ai.d.cts +1 -1
  23. package/dist/backends/vercel-ai.d.ts +1 -1
  24. package/dist/backends/vercel-ai.js +56 -17
  25. package/dist/backends/vercel-ai.js.map +1 -1
  26. package/dist/chat/accumulator.cjs +147 -0
  27. package/dist/chat/accumulator.cjs.map +1 -0
  28. package/dist/chat/accumulator.d.cts +61 -0
  29. package/dist/chat/accumulator.d.ts +61 -0
  30. package/dist/chat/accumulator.js +145 -0
  31. package/dist/chat/accumulator.js.map +1 -0
  32. package/dist/chat/backends.cjs +3534 -0
  33. package/dist/chat/backends.cjs.map +1 -0
  34. package/dist/chat/backends.d.cts +62 -0
  35. package/dist/chat/backends.d.ts +62 -0
  36. package/dist/chat/backends.js +3501 -0
  37. package/dist/chat/backends.js.map +1 -0
  38. package/dist/chat/context.cjs +230 -0
  39. package/dist/chat/context.cjs.map +1 -0
  40. package/dist/chat/context.d.cts +167 -0
  41. package/dist/chat/context.d.ts +167 -0
  42. package/dist/chat/context.js +227 -0
  43. package/dist/chat/context.js.map +1 -0
  44. package/dist/chat/core.cjs +282 -0
  45. package/dist/chat/core.cjs.map +1 -0
  46. package/dist/chat/core.d.cts +435 -0
  47. package/dist/chat/core.d.ts +435 -0
  48. package/dist/chat/core.js +261 -0
  49. package/dist/chat/core.js.map +1 -0
  50. package/dist/chat/errors.cjs +251 -0
  51. package/dist/chat/errors.cjs.map +1 -0
  52. package/dist/chat/errors.d.cts +122 -0
  53. package/dist/chat/errors.d.ts +122 -0
  54. package/dist/chat/errors.js +243 -0
  55. package/dist/chat/errors.js.map +1 -0
  56. package/dist/chat/events.cjs +203 -0
  57. package/dist/chat/events.cjs.map +1 -0
  58. package/dist/chat/events.d.cts +241 -0
  59. package/dist/chat/events.d.ts +241 -0
  60. package/dist/chat/events.js +196 -0
  61. package/dist/chat/events.js.map +1 -0
  62. package/dist/chat/index.cjs +5359 -0
  63. package/dist/chat/index.cjs.map +1 -0
  64. package/dist/chat/index.d.cts +52 -0
  65. package/dist/chat/index.d.ts +52 -0
  66. package/dist/chat/index.js +5296 -0
  67. package/dist/chat/index.js.map +1 -0
  68. package/dist/chat/react.cjs +2739 -0
  69. package/dist/chat/react.cjs.map +1 -0
  70. package/dist/chat/react.d.cts +619 -0
  71. package/dist/chat/react.d.ts +619 -0
  72. package/dist/chat/react.js +2714 -0
  73. package/dist/chat/react.js.map +1 -0
  74. package/dist/chat/runtime.cjs +1030 -0
  75. package/dist/chat/runtime.cjs.map +1 -0
  76. package/dist/chat/runtime.d.cts +118 -0
  77. package/dist/chat/runtime.d.ts +118 -0
  78. package/dist/chat/runtime.js +1028 -0
  79. package/dist/chat/runtime.js.map +1 -0
  80. package/dist/chat/server.cjs +643 -0
  81. package/dist/chat/server.cjs.map +1 -0
  82. package/dist/chat/server.d.cts +287 -0
  83. package/dist/chat/server.d.ts +287 -0
  84. package/dist/chat/server.js +617 -0
  85. package/dist/chat/server.js.map +1 -0
  86. package/dist/chat/sessions.cjs +398 -0
  87. package/dist/chat/sessions.cjs.map +1 -0
  88. package/dist/chat/sessions.d.cts +239 -0
  89. package/dist/chat/sessions.d.ts +239 -0
  90. package/dist/chat/sessions.js +394 -0
  91. package/dist/chat/sessions.js.map +1 -0
  92. package/dist/chat/state.cjs +177 -0
  93. package/dist/chat/state.cjs.map +1 -0
  94. package/dist/chat/state.d.cts +92 -0
  95. package/dist/chat/state.d.ts +92 -0
  96. package/dist/chat/state.js +167 -0
  97. package/dist/chat/state.js.map +1 -0
  98. package/dist/chat/storage.cjs +240 -0
  99. package/dist/chat/storage.cjs.map +1 -0
  100. package/dist/chat/storage.d.cts +191 -0
  101. package/dist/chat/storage.d.ts +191 -0
  102. package/dist/chat/storage.js +236 -0
  103. package/dist/chat/storage.js.map +1 -0
  104. package/dist/errors-BDLbNu9w.d.cts +13 -0
  105. package/dist/errors-BDLbNu9w.d.ts +13 -0
  106. package/dist/in-process-transport-C2oPTYs6.d.ts +223 -0
  107. package/dist/in-process-transport-DG-w5G6k.d.cts +223 -0
  108. package/dist/index.cjs +25 -13
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +32 -4
  111. package/dist/index.d.ts +32 -4
  112. package/dist/index.js +25 -13
  113. package/dist/index.js.map +1 -1
  114. package/dist/transport-D1OaUgRk.d.ts +67 -0
  115. package/dist/transport-DX1Nhm4N.d.cts +67 -0
  116. package/dist/types-Bh5AhqD-.d.ts +141 -0
  117. package/dist/types-CGF7AEX1.d.cts +141 -0
  118. package/dist/{types-BvwNzZCj.d.cts → types-CqvUAYxt.d.cts} +21 -3
  119. package/dist/{types-BvwNzZCj.d.ts → types-CqvUAYxt.d.ts} +21 -3
  120. package/dist/types-DLZzlJxt.d.ts +39 -0
  121. package/dist/types-tE0CXwBl.d.cts +39 -0
  122. package/package.json +149 -2
@@ -0,0 +1,230 @@
1
+ 'use strict';
2
+
3
+ // src/chat/context.ts
4
+ function estimateTokens(message, options) {
5
+ const ratio = options?.charsPerToken ?? 4;
6
+ let charCount = 0;
7
+ charCount += message.role.length + 4;
8
+ for (const part of message.parts) {
9
+ charCount += estimatePartChars(part);
10
+ }
11
+ return Math.ceil(charCount / ratio);
12
+ }
13
+ function estimatePartChars(part) {
14
+ switch (part.type) {
15
+ case "text":
16
+ return part.text.length;
17
+ case "reasoning":
18
+ return part.text.length;
19
+ case "tool_call":
20
+ return JSON.stringify(part.args).length + part.name.length + 20 + (part.result !== void 0 ? JSON.stringify(part.result).length : 0);
21
+ case "source":
22
+ return (part.title?.length ?? 0) + part.url.length + 10;
23
+ case "file":
24
+ return part.name.length + part.data.length + 20;
25
+ }
26
+ }
27
+ var ContextWindowManager = class {
28
+ config;
29
+ constructor(config) {
30
+ this.config = {
31
+ maxTokens: config.maxTokens,
32
+ reservedTokens: config.reservedTokens ?? 0,
33
+ strategy: config.strategy ?? "truncate-oldest",
34
+ estimation: config.estimation,
35
+ summarizer: config.summarizer
36
+ };
37
+ }
38
+ /** Available token budget after reserving tokens */
39
+ get availableBudget() {
40
+ return Math.max(0, this.config.maxTokens - this.config.reservedTokens);
41
+ }
42
+ /**
43
+ * Estimate tokens for a single message.
44
+ * @param message - Message to estimate
45
+ * @returns Estimated token count
46
+ */
47
+ estimateMessageTokens(message) {
48
+ return estimateTokens(message, this.config.estimation);
49
+ }
50
+ /**
51
+ * Fit messages within the token budget using the configured strategy.
52
+ * @param messages - All messages to consider
53
+ * @returns Result with fitted messages and metadata
54
+ */
55
+ fitMessages(messages) {
56
+ if (messages.length === 0) {
57
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
58
+ }
59
+ const budget = this.availableBudget;
60
+ const tokenCounts = messages.map((m) => this.estimateMessageTokens(m));
61
+ const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);
62
+ if (totalTokens <= budget) {
63
+ return {
64
+ messages: [...messages],
65
+ totalTokens,
66
+ removedCount: 0,
67
+ wasTruncated: false
68
+ };
69
+ }
70
+ switch (this.config.strategy) {
71
+ case "truncate-oldest":
72
+ return this.truncateOldest(messages, tokenCounts, budget);
73
+ case "sliding-window":
74
+ return this.slidingWindow(messages, tokenCounts, budget);
75
+ case "summarize-placeholder":
76
+ return this.summarizePlaceholder(messages, tokenCounts, budget);
77
+ }
78
+ }
79
+ /**
80
+ * Async variant of fitMessages that supports async summarization.
81
+ * When strategy is "summarize-placeholder" and a summarizer is configured,
82
+ * calls the summarizer with removed messages and replaces the placeholder text.
83
+ * Falls back to static placeholder if summarizer throws.
84
+ * For other strategies, behaves identically to fitMessages().
85
+ */
86
+ async fitMessagesAsync(messages) {
87
+ const result = this.fitMessages(messages);
88
+ if (this.config.strategy !== "summarize-placeholder" || !result.wasTruncated || !this.config.summarizer) {
89
+ return result;
90
+ }
91
+ const keptIds = new Set(result.messages.map((m) => m.id));
92
+ const removed = messages.filter((m) => !keptIds.has(m.id));
93
+ if (removed.length === 0) return result;
94
+ let summaryText;
95
+ try {
96
+ summaryText = await this.config.summarizer(removed);
97
+ } catch {
98
+ return result;
99
+ }
100
+ const updatedMessages = result.messages.map((m) => {
101
+ if (m.metadata?.isSummary === true) {
102
+ return {
103
+ ...m,
104
+ parts: [{ type: "text", text: summaryText, status: "complete" }]
105
+ };
106
+ }
107
+ return m;
108
+ });
109
+ return { ...result, messages: updatedMessages };
110
+ }
111
+ /**
112
+ * Truncate oldest: keeps system messages, removes oldest non-system messages first.
113
+ * Always keeps the most recent user message.
114
+ */
115
+ truncateOldest(messages, tokenCounts, budget) {
116
+ const systemIndices = [];
117
+ const nonSystemIndices = [];
118
+ for (let i = 0; i < messages.length; i++) {
119
+ if (messages[i].role === "system") {
120
+ systemIndices.push(i);
121
+ } else {
122
+ nonSystemIndices.push(i);
123
+ }
124
+ }
125
+ let usedTokens = systemIndices.reduce(
126
+ (sum, i) => sum + tokenCounts[i],
127
+ 0
128
+ );
129
+ const includedNonSystem = [];
130
+ for (let i = nonSystemIndices.length - 1; i >= 0; i--) {
131
+ const idx = nonSystemIndices[i];
132
+ if (usedTokens + tokenCounts[idx] <= budget) {
133
+ includedNonSystem.unshift(idx);
134
+ usedTokens += tokenCounts[idx];
135
+ }
136
+ }
137
+ const includedSet = /* @__PURE__ */ new Set([...systemIndices, ...includedNonSystem]);
138
+ const result = [];
139
+ let resultTokens = 0;
140
+ for (let i = 0; i < messages.length; i++) {
141
+ if (includedSet.has(i)) {
142
+ result.push(messages[i]);
143
+ resultTokens += tokenCounts[i];
144
+ }
145
+ }
146
+ return {
147
+ messages: result,
148
+ totalTokens: resultTokens,
149
+ removedCount: messages.length - result.length,
150
+ wasTruncated: true
151
+ };
152
+ }
153
+ /**
154
+ * Sliding window: keeps the most recent messages that fit within budget.
155
+ */
156
+ slidingWindow(messages, tokenCounts, budget) {
157
+ const result = [];
158
+ let usedTokens = 0;
159
+ for (let i = messages.length - 1; i >= 0; i--) {
160
+ if (usedTokens + tokenCounts[i] <= budget) {
161
+ result.unshift(messages[i]);
162
+ usedTokens += tokenCounts[i];
163
+ } else {
164
+ break;
165
+ }
166
+ }
167
+ return {
168
+ messages: result,
169
+ totalTokens: usedTokens,
170
+ removedCount: messages.length - result.length,
171
+ wasTruncated: true
172
+ };
173
+ }
174
+ /**
175
+ * Summarize placeholder: replaces truncated messages with a placeholder,
176
+ * preserving system messages and recent context.
177
+ */
178
+ summarizePlaceholder(messages, tokenCounts, budget) {
179
+ const systemMessages = [];
180
+ const nonSystem = [];
181
+ for (let i = 0; i < messages.length; i++) {
182
+ if (messages[i].role === "system") {
183
+ systemMessages.push({ msg: messages[i], tokens: tokenCounts[i] });
184
+ } else {
185
+ nonSystem.push({ msg: messages[i], tokens: tokenCounts[i], idx: i });
186
+ }
187
+ }
188
+ let usedTokens = systemMessages.reduce((s, m) => s + m.tokens, 0);
189
+ const placeholderTokens = 20;
190
+ usedTokens += placeholderTokens;
191
+ const recentKept = [];
192
+ for (let i = nonSystem.length - 1; i >= 0; i--) {
193
+ if (usedTokens + nonSystem[i].tokens <= budget) {
194
+ recentKept.unshift(nonSystem[i]);
195
+ usedTokens += nonSystem[i].tokens;
196
+ } else {
197
+ break;
198
+ }
199
+ }
200
+ const removedCount = messages.length - systemMessages.length - recentKept.length;
201
+ const result = [];
202
+ for (const sm of systemMessages) {
203
+ result.push(sm.msg);
204
+ }
205
+ if (removedCount > 0) {
206
+ result.push({
207
+ id: "context-placeholder",
208
+ role: "system",
209
+ parts: [{ type: "text", text: `[${removedCount} earlier message${removedCount === 1 ? "" : "s"} omitted for context window]`, status: "complete" }],
210
+ metadata: { isSummary: true },
211
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
212
+ status: "complete"
213
+ });
214
+ }
215
+ for (const m of recentKept) {
216
+ result.push(m.msg);
217
+ }
218
+ return {
219
+ messages: result,
220
+ totalTokens: usedTokens,
221
+ removedCount,
222
+ wasTruncated: true
223
+ };
224
+ }
225
+ };
226
+
227
+ exports.ContextWindowManager = ContextWindowManager;
228
+ exports.estimateTokens = estimateTokens;
229
+ //# sourceMappingURL=context.cjs.map
230
+ //# sourceMappingURL=context.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,167 @@
1
+ import { ChatMessage } from './core.cjs';
2
+ import '../types-CqvUAYxt.cjs';
3
+ import 'zod';
4
+
5
+ /**
6
+ * @witqq/agent-sdk/chat/context
7
+ *
8
+ * Context window manager for selecting which messages fit within a token budget.
9
+ * Stateless: takes messages in, returns trimmed messages out.
10
+ * Three overflow strategies: truncate-oldest, sliding-window, summarize-placeholder.
11
+ */
12
+
13
+ /**
14
+ * Options for token estimation.
15
+ */
16
+ interface TokenEstimationOptions {
17
+ /**
18
+ * Characters per token ratio.
19
+ * Lower = more conservative (fewer messages fit).
20
+ * @default 4
21
+ */
22
+ charsPerToken?: number;
23
+ }
24
+ /**
25
+ * Estimate token count for a single chat message.
26
+ * Uses character-based heuristic: `Math.ceil(charCount / charsPerToken)`.
27
+ *
28
+ * Counts:
29
+ * - Text content (string or text parts)
30
+ * - Serialized tool calls and tool results
31
+ * - Thinking blocks
32
+ * - Role overhead (~4 tokens)
33
+ *
34
+ * @param message - Chat message to estimate
35
+ * @param options - Estimation options
36
+ * @returns Estimated token count
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const tokens = estimateTokens(message);
41
+ * const conservative = estimateTokens(message, { charsPerToken: 3 });
42
+ * ```
43
+ */
44
+ declare function estimateTokens(message: ChatMessage, options?: TokenEstimationOptions): number;
45
+ /** Overflow strategy type */
46
+ type OverflowStrategy = "truncate-oldest" | "sliding-window" | "summarize-placeholder";
47
+ /**
48
+ * Async summarizer function for the summarize-placeholder strategy.
49
+ * Receives removed messages and returns a summary string.
50
+ * When configured, replaces the static placeholder text with actual summary.
51
+ */
52
+ type ContextSummarizer = (removedMessages: readonly ChatMessage[]) => Promise<string>;
53
+ /**
54
+ * Configuration for the context window manager.
55
+ */
56
+ interface ContextWindowConfig {
57
+ /** Maximum token budget for the context window */
58
+ maxTokens: number;
59
+ /**
60
+ * Tokens reserved for system prompt and response generation.
61
+ * Subtracted from maxTokens to get available budget.
62
+ * @default 0
63
+ */
64
+ reservedTokens?: number;
65
+ /**
66
+ * Strategy for handling overflow when messages exceed budget.
67
+ * @default "truncate-oldest"
68
+ */
69
+ strategy?: OverflowStrategy;
70
+ /**
71
+ * Token estimation options.
72
+ */
73
+ estimation?: TokenEstimationOptions;
74
+ /**
75
+ * Optional async summarizer for the summarize-placeholder strategy.
76
+ * When provided, replaces the static placeholder with a generated summary.
77
+ * Falls back to static placeholder if summarizer throws.
78
+ */
79
+ summarizer?: ContextSummarizer;
80
+ }
81
+ /**
82
+ * Result of context window trimming.
83
+ */
84
+ interface ContextWindowResult {
85
+ /** Messages that fit within the budget */
86
+ messages: ChatMessage[];
87
+ /** Total estimated tokens for included messages */
88
+ totalTokens: number;
89
+ /** Number of messages removed */
90
+ removedCount: number;
91
+ /** Whether any messages were truncated */
92
+ wasTruncated: boolean;
93
+ }
94
+ /**
95
+ * Context usage statistics for a session.
96
+ * Returned by `IChatRuntime.getContextStats()`.
97
+ */
98
+ interface ContextStats {
99
+ /** Estimated total tokens in the trimmed context */
100
+ totalTokens: number;
101
+ /** Number of messages removed by trimming */
102
+ removedCount: number;
103
+ /** Whether context was truncated */
104
+ wasTruncated: boolean;
105
+ /** Available token budget (maxTokens − reservedTokens) */
106
+ availableBudget: number;
107
+ }
108
+ /**
109
+ * Stateless context window manager.
110
+ * Takes messages and returns the subset that fits within a token budget.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const manager = new ContextWindowManager({
115
+ * maxTokens: 4096,
116
+ * reservedTokens: 500,
117
+ * strategy: "sliding-window",
118
+ * });
119
+ *
120
+ * const result = manager.fitMessages(messages);
121
+ * // result.messages — trimmed to fit budget
122
+ * // result.totalTokens — estimated token usage
123
+ * // result.wasTruncated — whether messages were removed
124
+ * ```
125
+ */
126
+ declare class ContextWindowManager {
127
+ private readonly config;
128
+ constructor(config: ContextWindowConfig);
129
+ /** Available token budget after reserving tokens */
130
+ get availableBudget(): number;
131
+ /**
132
+ * Estimate tokens for a single message.
133
+ * @param message - Message to estimate
134
+ * @returns Estimated token count
135
+ */
136
+ estimateMessageTokens(message: ChatMessage): number;
137
+ /**
138
+ * Fit messages within the token budget using the configured strategy.
139
+ * @param messages - All messages to consider
140
+ * @returns Result with fitted messages and metadata
141
+ */
142
+ fitMessages(messages: readonly ChatMessage[]): ContextWindowResult;
143
+ /**
144
+ * Async variant of fitMessages that supports async summarization.
145
+ * When strategy is "summarize-placeholder" and a summarizer is configured,
146
+ * calls the summarizer with removed messages and replaces the placeholder text.
147
+ * Falls back to static placeholder if summarizer throws.
148
+ * For other strategies, behaves identically to fitMessages().
149
+ */
150
+ fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult>;
151
+ /**
152
+ * Truncate oldest: keeps system messages, removes oldest non-system messages first.
153
+ * Always keeps the most recent user message.
154
+ */
155
+ private truncateOldest;
156
+ /**
157
+ * Sliding window: keeps the most recent messages that fit within budget.
158
+ */
159
+ private slidingWindow;
160
+ /**
161
+ * Summarize placeholder: replaces truncated messages with a placeholder,
162
+ * preserving system messages and recent context.
163
+ */
164
+ private summarizePlaceholder;
165
+ }
166
+
167
+ export { type ContextStats, type ContextSummarizer, type ContextWindowConfig, ContextWindowManager, type ContextWindowResult, type OverflowStrategy, type TokenEstimationOptions, estimateTokens };
@@ -0,0 +1,167 @@
1
+ import { ChatMessage } from './core.js';
2
+ import '../types-CqvUAYxt.js';
3
+ import 'zod';
4
+
5
+ /**
6
+ * @witqq/agent-sdk/chat/context
7
+ *
8
+ * Context window manager for selecting which messages fit within a token budget.
9
+ * Stateless: takes messages in, returns trimmed messages out.
10
+ * Three overflow strategies: truncate-oldest, sliding-window, summarize-placeholder.
11
+ */
12
+
13
+ /**
14
+ * Options for token estimation.
15
+ */
16
+ interface TokenEstimationOptions {
17
+ /**
18
+ * Characters per token ratio.
19
+ * Lower = more conservative (fewer messages fit).
20
+ * @default 4
21
+ */
22
+ charsPerToken?: number;
23
+ }
24
+ /**
25
+ * Estimate token count for a single chat message.
26
+ * Uses character-based heuristic: `Math.ceil(charCount / charsPerToken)`.
27
+ *
28
+ * Counts:
29
+ * - Text content (string or text parts)
30
+ * - Serialized tool calls and tool results
31
+ * - Thinking blocks
32
+ * - Role overhead (~4 tokens)
33
+ *
34
+ * @param message - Chat message to estimate
35
+ * @param options - Estimation options
36
+ * @returns Estimated token count
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const tokens = estimateTokens(message);
41
+ * const conservative = estimateTokens(message, { charsPerToken: 3 });
42
+ * ```
43
+ */
44
+ declare function estimateTokens(message: ChatMessage, options?: TokenEstimationOptions): number;
45
+ /** Overflow strategy type */
46
+ type OverflowStrategy = "truncate-oldest" | "sliding-window" | "summarize-placeholder";
47
+ /**
48
+ * Async summarizer function for the summarize-placeholder strategy.
49
+ * Receives removed messages and returns a summary string.
50
+ * When configured, replaces the static placeholder text with actual summary.
51
+ */
52
+ type ContextSummarizer = (removedMessages: readonly ChatMessage[]) => Promise<string>;
53
+ /**
54
+ * Configuration for the context window manager.
55
+ */
56
+ interface ContextWindowConfig {
57
+ /** Maximum token budget for the context window */
58
+ maxTokens: number;
59
+ /**
60
+ * Tokens reserved for system prompt and response generation.
61
+ * Subtracted from maxTokens to get available budget.
62
+ * @default 0
63
+ */
64
+ reservedTokens?: number;
65
+ /**
66
+ * Strategy for handling overflow when messages exceed budget.
67
+ * @default "truncate-oldest"
68
+ */
69
+ strategy?: OverflowStrategy;
70
+ /**
71
+ * Token estimation options.
72
+ */
73
+ estimation?: TokenEstimationOptions;
74
+ /**
75
+ * Optional async summarizer for the summarize-placeholder strategy.
76
+ * When provided, replaces the static placeholder with a generated summary.
77
+ * Falls back to static placeholder if summarizer throws.
78
+ */
79
+ summarizer?: ContextSummarizer;
80
+ }
81
+ /**
82
+ * Result of context window trimming.
83
+ */
84
+ interface ContextWindowResult {
85
+ /** Messages that fit within the budget */
86
+ messages: ChatMessage[];
87
+ /** Total estimated tokens for included messages */
88
+ totalTokens: number;
89
+ /** Number of messages removed */
90
+ removedCount: number;
91
+ /** Whether any messages were truncated */
92
+ wasTruncated: boolean;
93
+ }
94
+ /**
95
+ * Context usage statistics for a session.
96
+ * Returned by `IChatRuntime.getContextStats()`.
97
+ */
98
+ interface ContextStats {
99
+ /** Estimated total tokens in the trimmed context */
100
+ totalTokens: number;
101
+ /** Number of messages removed by trimming */
102
+ removedCount: number;
103
+ /** Whether context was truncated */
104
+ wasTruncated: boolean;
105
+ /** Available token budget (maxTokens − reservedTokens) */
106
+ availableBudget: number;
107
+ }
108
+ /**
109
+ * Stateless context window manager.
110
+ * Takes messages and returns the subset that fits within a token budget.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const manager = new ContextWindowManager({
115
+ * maxTokens: 4096,
116
+ * reservedTokens: 500,
117
+ * strategy: "sliding-window",
118
+ * });
119
+ *
120
+ * const result = manager.fitMessages(messages);
121
+ * // result.messages — trimmed to fit budget
122
+ * // result.totalTokens — estimated token usage
123
+ * // result.wasTruncated — whether messages were removed
124
+ * ```
125
+ */
126
+ declare class ContextWindowManager {
127
+ private readonly config;
128
+ constructor(config: ContextWindowConfig);
129
+ /** Available token budget after reserving tokens */
130
+ get availableBudget(): number;
131
+ /**
132
+ * Estimate tokens for a single message.
133
+ * @param message - Message to estimate
134
+ * @returns Estimated token count
135
+ */
136
+ estimateMessageTokens(message: ChatMessage): number;
137
+ /**
138
+ * Fit messages within the token budget using the configured strategy.
139
+ * @param messages - All messages to consider
140
+ * @returns Result with fitted messages and metadata
141
+ */
142
+ fitMessages(messages: readonly ChatMessage[]): ContextWindowResult;
143
+ /**
144
+ * Async variant of fitMessages that supports async summarization.
145
+ * When strategy is "summarize-placeholder" and a summarizer is configured,
146
+ * calls the summarizer with removed messages and replaces the placeholder text.
147
+ * Falls back to static placeholder if summarizer throws.
148
+ * For other strategies, behaves identically to fitMessages().
149
+ */
150
+ fitMessagesAsync(messages: readonly ChatMessage[]): Promise<ContextWindowResult>;
151
+ /**
152
+ * Truncate oldest: keeps system messages, removes oldest non-system messages first.
153
+ * Always keeps the most recent user message.
154
+ */
155
+ private truncateOldest;
156
+ /**
157
+ * Sliding window: keeps the most recent messages that fit within budget.
158
+ */
159
+ private slidingWindow;
160
+ /**
161
+ * Summarize placeholder: replaces truncated messages with a placeholder,
162
+ * preserving system messages and recent context.
163
+ */
164
+ private summarizePlaceholder;
165
+ }
166
+
167
+ export { type ContextStats, type ContextSummarizer, type ContextWindowConfig, ContextWindowManager, type ContextWindowResult, type OverflowStrategy, type TokenEstimationOptions, estimateTokens };