@xalia/agent 0.6.8 → 0.6.10

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 (152) hide show
  1. package/.env.development +6 -0
  2. package/.env.test +7 -0
  3. package/README.md +11 -0
  4. package/context_system.md +498 -0
  5. package/dist/agent/src/agent/agent.js +169 -87
  6. package/dist/agent/src/agent/agentUtils.js +24 -18
  7. package/dist/agent/src/agent/compressingContextManager.js +10 -14
  8. package/dist/agent/src/agent/context.js +101 -127
  9. package/dist/agent/src/agent/contextWithWorkspace.js +133 -0
  10. package/dist/agent/src/agent/documentSummarizer.js +126 -0
  11. package/dist/agent/src/agent/dummyLLM.js +25 -22
  12. package/dist/agent/src/agent/imageGenLLM.js +22 -25
  13. package/dist/agent/src/agent/imageGenerator.js +2 -10
  14. package/dist/agent/src/agent/llm.js +1 -1
  15. package/dist/agent/src/agent/openAILLM.js +15 -12
  16. package/dist/agent/src/agent/openAILLMStreaming.js +73 -39
  17. package/dist/agent/src/agent/repeatLLM.js +16 -7
  18. package/dist/agent/src/agent/sudoMcpServerManager.js +21 -9
  19. package/dist/agent/src/agent/tokenCounter.js +390 -0
  20. package/dist/agent/src/agent/tokenCounter.test.js +206 -0
  21. package/dist/agent/src/agent/toolSettings.js +17 -0
  22. package/dist/agent/src/agent/tools/calculatorTool.js +45 -0
  23. package/dist/agent/src/agent/tools/contentExtractors/pdfToText.js +55 -0
  24. package/dist/agent/src/agent/tools/datetimeTool.js +38 -0
  25. package/dist/agent/src/agent/tools/fileManager/fileManagerTool.js +156 -0
  26. package/dist/agent/src/agent/tools/fileManager/index.js +31 -0
  27. package/dist/agent/src/agent/tools/fileManager/memoryFileManager.js +102 -0
  28. package/dist/agent/src/{chat/data → agent/tools/fileManager}/mimeTypes.js +3 -1
  29. package/dist/agent/src/agent/tools/fileManager/prompt.js +33 -0
  30. package/dist/agent/src/{chat/data/dbSessionFileModels.js → agent/tools/fileManager/types.js} +7 -0
  31. package/dist/agent/src/agent/tools/index.js +64 -0
  32. package/dist/agent/src/agent/tools/openUrlTool.js +57 -0
  33. package/dist/agent/src/agent/tools/renderTool.js +89 -0
  34. package/dist/agent/src/agent/tools/utils.js +61 -0
  35. package/dist/agent/src/{chat/utils/search.js → agent/tools/webSearch.js} +1 -2
  36. package/dist/agent/src/agent/tools/webSearchTool.js +40 -0
  37. package/dist/agent/src/chat/client/chatClient.js +63 -2
  38. package/dist/agent/src/chat/client/connection.js +6 -1
  39. package/dist/agent/src/chat/client/index.js +4 -1
  40. package/dist/agent/src/chat/client/sessionClient.js +28 -9
  41. package/dist/agent/src/chat/constants.js +8 -0
  42. package/dist/agent/src/chat/data/dbSessionFiles.js +11 -6
  43. package/dist/agent/src/chat/data/dbSessionMessages.js +11 -0
  44. package/dist/agent/src/chat/protocol/messages.js +9 -0
  45. package/dist/agent/src/chat/server/chatContextManager.js +186 -156
  46. package/dist/agent/src/chat/server/conversation.js +3 -0
  47. package/dist/agent/src/chat/server/imageGeneratorTools.js +39 -16
  48. package/dist/agent/src/chat/server/openAIRouterLLM.js +111 -0
  49. package/dist/agent/src/chat/server/openSession.js +253 -91
  50. package/dist/agent/src/chat/server/promptRefiner.js +86 -0
  51. package/dist/agent/src/chat/server/server.js +10 -2
  52. package/dist/agent/src/chat/server/sessionFileManager.js +22 -221
  53. package/dist/agent/src/chat/server/sessionRegistry.js +152 -6
  54. package/dist/agent/src/chat/server/sessionRegistry.test.js +1 -1
  55. package/dist/agent/src/chat/server/titleGenerator.js +112 -0
  56. package/dist/agent/src/chat/server/titleGenerator.test.js +113 -0
  57. package/dist/agent/src/chat/server/tools.js +64 -253
  58. package/dist/agent/src/chat/utils/approvalManager.js +6 -3
  59. package/dist/agent/src/chat/utils/multiAsyncQueue.js +3 -0
  60. package/dist/agent/src/test/agent.test.js +16 -17
  61. package/dist/agent/src/test/chatContextManager.test.js +44 -30
  62. package/dist/agent/src/test/clientServerConnection.test.js +1 -2
  63. package/dist/agent/src/test/compressingContextManager.test.js +22 -36
  64. package/dist/agent/src/test/context.test.js +55 -17
  65. package/dist/agent/src/test/contextTestTools.js +87 -0
  66. package/dist/agent/src/test/dbMcpServerConfigs.test.js +4 -4
  67. package/dist/agent/src/test/dbSessionFiles.test.js +17 -17
  68. package/dist/agent/src/test/testTools.js +6 -1
  69. package/dist/agent/src/test/tools.test.js +27 -9
  70. package/dist/agent/src/tool/agentChat.js +5 -2
  71. package/dist/agent/src/tool/chatMain.js +56 -15
  72. package/dist/agent/src/tool/commandPrompt.js +2 -2
  73. package/dist/agent/src/tool/files.js +7 -8
  74. package/package.json +4 -1
  75. package/scripts/test_chat +195 -173
  76. package/src/agent/agent.ts +257 -137
  77. package/src/agent/agentUtils.ts +32 -20
  78. package/src/agent/compressingContextManager.ts +13 -44
  79. package/src/agent/context.ts +165 -159
  80. package/src/agent/contextWithWorkspace.ts +162 -0
  81. package/src/agent/documentSummarizer.ts +157 -0
  82. package/src/agent/dummyLLM.ts +27 -23
  83. package/src/agent/imageGenLLM.ts +28 -32
  84. package/src/agent/imageGenerator.ts +3 -18
  85. package/src/agent/llm.ts +2 -2
  86. package/src/agent/openAILLM.ts +17 -13
  87. package/src/agent/openAILLMStreaming.ts +99 -43
  88. package/src/agent/repeatLLM.ts +19 -7
  89. package/src/agent/sudoMcpServerManager.ts +41 -20
  90. package/src/agent/test_data/harrypotter.txt +6065 -0
  91. package/src/agent/tokenCounter.test.ts +243 -0
  92. package/src/agent/tokenCounter.ts +483 -0
  93. package/src/agent/toolSettings.ts +24 -0
  94. package/src/agent/tools/calculatorTool.ts +50 -0
  95. package/src/agent/tools/contentExtractors/pdfToText.ts +60 -0
  96. package/src/agent/tools/datetimeTool.ts +41 -0
  97. package/src/agent/tools/fileManager/fileManagerTool.ts +199 -0
  98. package/src/agent/tools/fileManager/index.ts +50 -0
  99. package/src/agent/tools/fileManager/memoryFileManager.ts +120 -0
  100. package/src/{chat/data → agent/tools/fileManager}/mimeTypes.ts +3 -1
  101. package/src/agent/tools/fileManager/prompt.ts +38 -0
  102. package/src/{chat/data/dbSessionFileModels.ts → agent/tools/fileManager/types.ts} +76 -0
  103. package/src/agent/tools/index.ts +49 -0
  104. package/src/agent/tools/openUrlTool.ts +62 -0
  105. package/src/agent/tools/renderTool.ts +92 -0
  106. package/src/agent/tools/utils.ts +74 -0
  107. package/src/{chat/utils/search.ts → agent/tools/webSearch.ts} +0 -1
  108. package/src/agent/tools/webSearchTool.ts +44 -0
  109. package/src/chat/client/chatClient.ts +92 -3
  110. package/src/chat/client/connection.ts +11 -1
  111. package/src/chat/client/index.ts +3 -0
  112. package/src/chat/client/sessionClient.ts +40 -11
  113. package/src/chat/client/sessionFiles.ts +1 -1
  114. package/src/chat/constants.ts +6 -0
  115. package/src/chat/data/dataModels.ts +12 -0
  116. package/src/chat/data/dbSessionFiles.ts +12 -4
  117. package/src/chat/data/dbSessionMessages.ts +34 -0
  118. package/src/chat/protocol/messages.ts +94 -14
  119. package/src/chat/server/chatContextManager.ts +255 -221
  120. package/src/chat/server/connectionManager.ts +1 -1
  121. package/src/chat/server/conversation.ts +3 -0
  122. package/src/chat/server/imageGeneratorTools.ts +62 -30
  123. package/src/chat/server/openAIRouterLLM.ts +168 -0
  124. package/src/chat/server/openSession.ts +381 -138
  125. package/src/chat/server/promptRefiner.ts +106 -0
  126. package/src/chat/server/server.ts +9 -2
  127. package/src/chat/server/sessionFileManager.ts +35 -306
  128. package/src/chat/server/sessionRegistry.test.ts +0 -1
  129. package/src/chat/server/sessionRegistry.ts +228 -4
  130. package/src/chat/server/titleGenerator.test.ts +103 -0
  131. package/src/chat/server/titleGenerator.ts +143 -0
  132. package/src/chat/server/tools.ts +92 -281
  133. package/src/chat/utils/approvalManager.ts +9 -3
  134. package/src/chat/utils/multiAsyncQueue.ts +4 -0
  135. package/src/test/agent.test.ts +25 -30
  136. package/src/test/chatContextManager.test.ts +68 -38
  137. package/src/test/clientServerConnection.test.ts +0 -2
  138. package/src/test/compressingContextManager.test.ts +29 -34
  139. package/src/test/context.test.ts +59 -15
  140. package/src/test/contextTestTools.ts +95 -0
  141. package/src/test/dbMcpServerConfigs.test.ts +4 -4
  142. package/src/test/dbSessionFiles.test.ts +16 -16
  143. package/src/test/testTools.ts +8 -3
  144. package/src/test/tools.test.ts +30 -5
  145. package/src/tool/agentChat.ts +12 -3
  146. package/src/tool/chatMain.ts +59 -18
  147. package/src/tool/commandPrompt.ts +2 -2
  148. package/src/tool/files.ts +1 -3
  149. package/dist/agent/src/agent/tools.js +0 -44
  150. package/src/agent/tools.ts +0 -57
  151. /package/dist/agent/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.js +0 -0
  152. /package/src/{chat/utils → agent/tools/contentExtractors}/htmlToText.ts +0 -0
@@ -0,0 +1,243 @@
1
+ import { describe, it, expect, afterEach, beforeEach } from "vitest";
2
+ import {
3
+ TokenCounter,
4
+ createTokenCounter,
5
+ countTokensQuick,
6
+ ModelDetector,
7
+ } from "./tokenCounter";
8
+ import type { MessageParam } from "./llm";
9
+ import { readFileSync } from "fs";
10
+ import { join } from "path";
11
+
12
+ describe("ModelDetector", () => {
13
+ describe("detectProvider", () => {
14
+ it.each([
15
+ ["gpt-4o", "openai"],
16
+ ["gpt-4", "openai"],
17
+ ["openai/gpt-4o", "openai"],
18
+ ["o1-preview", "openai"],
19
+ ["claude-3-7-sonnet-20250219", "anthropic"],
20
+ ["anthropic/claude-sonnet-4.5", "anthropic"],
21
+ ["gemini-2.0-flash-exp", "google"],
22
+ ["google/gemini-pro", "google"],
23
+ ["unknown-model", "unknown"],
24
+ ["", "unknown"],
25
+ ])("should detect %s as %s", (model, expected) => {
26
+ expect(ModelDetector.detectProvider(model)).toBe(expected);
27
+ });
28
+ });
29
+
30
+ describe("normalizeModel", () => {
31
+ it.each([
32
+ ["openai/gpt-4o", "openai", "gpt-4o"],
33
+ ["anthropic/claude-3-opus", "anthropic", "claude-3-opus"],
34
+ ["google/gemini-pro", "google", "gemini-pro"],
35
+ ["gpt-4o-mini", "openai", "gpt-4o-mini"], // Preserves variants
36
+ ["o1-preview", "openai", "o1-preview"],
37
+ ] as const)("should normalize %s to %s", (input, provider, expected) => {
38
+ expect(ModelDetector.normalizeModel(input, provider)).toBe(expected);
39
+ });
40
+ });
41
+ });
42
+
43
+ describe("TokenCounter", () => {
44
+ let counter: TokenCounter | undefined;
45
+
46
+ afterEach(() => {
47
+ if (counter) {
48
+ counter.free();
49
+ }
50
+ });
51
+
52
+ describe("Basic counting", () => {
53
+ it("should count tokens for OpenAI models", () => {
54
+ counter = new TokenCounter("gpt-4o");
55
+ expect(counter.getProvider()).toBe("openai");
56
+ expect(counter.countTokens("Hello, world!")).toBeGreaterThan(0);
57
+ expect(counter.countTokens("")).toBe(0);
58
+ });
59
+
60
+ it("should count tokens for Anthropic models with +7 overhead", () => {
61
+ counter = new TokenCounter("anthropic/claude-sonnet-4.5");
62
+ expect(counter.getProvider()).toBe("anthropic");
63
+ // With +7 overhead, "Hello, world!" should be ~11 tokens
64
+ expect(counter.countTokens("Hello, world!")).toBeLessThan(15);
65
+ });
66
+
67
+ it("should handle provider prefixes", () => {
68
+ counter = new TokenCounter("openai/gpt-4o");
69
+ expect(counter.getProvider()).toBe("openai");
70
+ expect(counter.getModel()).toBe("openai/gpt-4o");
71
+ });
72
+
73
+ it("should fall back to approximation for unknown models", () => {
74
+ counter = new TokenCounter("unknown-model");
75
+ expect(counter.getProvider()).toBe("unknown");
76
+ const text = "Hello, world!";
77
+ const expected = Math.ceil(text.length / 3.5);
78
+ expect(counter.countTokens(text)).toBe(expected);
79
+ });
80
+ });
81
+
82
+ describe("Message counting", () => {
83
+ beforeEach(() => {
84
+ counter = new TokenCounter("gpt-4o");
85
+ });
86
+
87
+ it("should count simple message with overhead", () => {
88
+ if (!counter) throw new Error("Counter not initialized");
89
+ const message: MessageParam = {
90
+ role: "user",
91
+ content: "Hello, how are you?",
92
+ };
93
+ expect(counter.countMessageTokens(message)).toBeGreaterThan(4);
94
+ });
95
+
96
+ it("should count message with tool calls", () => {
97
+ if (!counter) throw new Error("Counter not initialized");
98
+ const message: MessageParam = {
99
+ role: "assistant",
100
+ content: "Let me help you.",
101
+ tool_calls: [
102
+ {
103
+ id: "call_123",
104
+ type: "function",
105
+ function: {
106
+ name: "get_file",
107
+ arguments: JSON.stringify({ name: "test.txt" }),
108
+ },
109
+ },
110
+ ],
111
+ };
112
+ expect(counter.countMessageTokens(message)).toBeGreaterThan(10);
113
+ });
114
+
115
+ it("should count multimodal message with images", () => {
116
+ if (!counter) throw new Error("Counter not initialized");
117
+ const message: MessageParam = {
118
+ role: "user",
119
+ content: [
120
+ { type: "text", text: "What's in this image?" },
121
+ {
122
+ type: "image_url",
123
+ image_url: { url: "data:image/png;base64,..." },
124
+ },
125
+ ],
126
+ };
127
+ // OpenAI images: ~170 tokens + text + overhead
128
+ expect(counter.countMessageTokens(message)).toBeGreaterThan(170);
129
+ });
130
+
131
+ it("should count conversation with overhead", () => {
132
+ if (!counter) throw new Error("Counter not initialized");
133
+ const messages: MessageParam[] = [
134
+ { role: "system", content: "You are a helpful assistant." },
135
+ { role: "user", content: "Hello!" },
136
+ { role: "assistant", content: "Hi there! How can I help?" },
137
+ ];
138
+ expect(counter.countMessagesTokens(messages)).toBeGreaterThan(20);
139
+ expect(counter.countMessagesTokens([])).toBe(0);
140
+ });
141
+ });
142
+
143
+ describe("Factory functions", () => {
144
+ it("should create counter with factory", () => {
145
+ const testCounter = createTokenCounter("gpt-4o");
146
+ expect(testCounter.getProvider()).toBe("openai");
147
+ testCounter.free();
148
+ });
149
+
150
+ it("should count tokens quickly", () => {
151
+ const count = countTokensQuick("Hello, world!", "gpt-4o");
152
+ expect(count).toBeGreaterThan(0);
153
+ });
154
+ });
155
+
156
+ describe("Edge cases", () => {
157
+ beforeEach(() => {
158
+ counter = new TokenCounter("gpt-4o");
159
+ });
160
+
161
+ it("should handle long text, special chars, and code", () => {
162
+ if (!counter) throw new Error("Counter not initialized");
163
+ expect(counter.countTokens("Hello ".repeat(1000))).toBeGreaterThan(1000);
164
+ expect(counter.countTokens("Hello! 你好 🌍")).toBeGreaterThan(0);
165
+ expect(
166
+ counter.countTokens('function hello() { return "world"; }')
167
+ ).toBeGreaterThan(5);
168
+ });
169
+ });
170
+ });
171
+
172
+ describe("Dev mode accurate counting", () => {
173
+ it("should throw error without dev mode", async () => {
174
+ const counter = new TokenCounter("anthropic/claude-sonnet-4.5");
175
+ await expect(counter.countTokensAccurate("test")).rejects.toThrow(
176
+ "Accurate token counting requires enableDevMode"
177
+ );
178
+ counter.free();
179
+ });
180
+
181
+ it("should compare p50k_base approximation vs Anthropic API", async () => {
182
+ const counter = new TokenCounter("claude-3-7-sonnet-20250219", {
183
+ enableDevMode: true,
184
+ });
185
+
186
+ // Test cases covering different scales
187
+ const readmePath = join(__dirname, "..", "..", "README.md");
188
+ const harryPotterPath = join(__dirname, "test_data", "harrypotter.txt");
189
+
190
+ const testCases = [
191
+ { text: "Hello, world!", label: "short" },
192
+ { text: "The quick brown fox jumps over the lazy dog.", label: "medium" },
193
+ { text: readFileSync(readmePath, "utf-8"), label: "README.md" },
194
+ {
195
+ text: readFileSync(harryPotterPath, "utf-8"),
196
+ label: "harrypotter.txt",
197
+ },
198
+ ];
199
+
200
+ if (process.env.ANTHROPIC_API_KEY) {
201
+ console.log(
202
+ "\n📊 Token Count Comparison (p50k_base+7 vs Anthropic API)\n"
203
+ );
204
+
205
+ for (const { text, label } of testCases) {
206
+ const accurate = await counter.countTokensAccurate(text);
207
+ const approximate = counter.countTokens(text);
208
+ const diff = accurate - approximate;
209
+ const accuracy = ((approximate / accurate) * 100).toFixed(1);
210
+
211
+ const size =
212
+ text.length > 1000
213
+ ? `${String(text.length)} chars`
214
+ : `${String(text.length)} chars`;
215
+
216
+ console.log(`${label.padEnd(20)} [${size}]`);
217
+ console.log(
218
+ ` Accurate: ${accurate.toLocaleString().padStart(8)} tokens`
219
+ );
220
+ console.log(
221
+ ` Approximate: ${approximate.toLocaleString().padStart(8)} tokens`
222
+ );
223
+ const diffStr = (diff > 0 ? "+" : "") + String(diff);
224
+ console.log(` Difference: ${diffStr.padStart(8)} (${accuracy}%)\n`);
225
+
226
+ expect(accurate).toBeGreaterThan(0);
227
+ expect(approximate).toBeGreaterThan(0);
228
+ }
229
+ } else {
230
+ await expect(counter.countTokensAccurate("test")).rejects.toThrow();
231
+ }
232
+
233
+ counter.free();
234
+ });
235
+
236
+ it("should return regular count for non-Anthropic models", async () => {
237
+ const counter = new TokenCounter("gpt-4o", { enableDevMode: true });
238
+ const regular = counter.countTokens("Hello, world!");
239
+ const accurate = await counter.countTokensAccurate("Hello, world!");
240
+ expect(accurate).toBe(regular);
241
+ counter.free();
242
+ });
243
+ });