oricore 1.0.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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/agent/agent/agentManager.d.ts +38 -0
  4. package/dist/agent/agent/builtin/common.d.ts +5 -0
  5. package/dist/agent/agent/builtin/explore.d.ts +5 -0
  6. package/dist/agent/agent/builtin/general-purpose.d.ts +5 -0
  7. package/dist/agent/agent/builtin/index.d.ts +5 -0
  8. package/dist/agent/agent/executor.d.ts +2 -0
  9. package/dist/agent/agent/types.d.ts +98 -0
  10. package/dist/api/engine.d.ts +213 -0
  11. package/dist/communication/index.d.ts +4 -0
  12. package/dist/communication/messageBus.d.ts +71 -0
  13. package/dist/core/at.d.ts +26 -0
  14. package/dist/core/backgroundTaskManager.d.ts +27 -0
  15. package/dist/core/compact.d.ts +9 -0
  16. package/dist/core/config.d.ts +103 -0
  17. package/dist/core/constants.d.ts +32 -0
  18. package/dist/core/context.d.ts +57 -0
  19. package/dist/core/globalData.d.ts +21 -0
  20. package/dist/core/history.d.ts +24 -0
  21. package/dist/core/ide.d.ts +103 -0
  22. package/dist/core/jsonl.d.ts +37 -0
  23. package/dist/core/llmsContext.d.ts +14 -0
  24. package/dist/core/loop.d.ts +82 -0
  25. package/dist/core/message.d.ts +132 -0
  26. package/dist/core/model.d.ts +79 -0
  27. package/dist/core/output-style/builtin/default.d.ts +2 -0
  28. package/dist/core/output-style/builtin/explanatory.d.ts +2 -0
  29. package/dist/core/output-style/builtin/index.d.ts +6 -0
  30. package/dist/core/output-style/builtin/miao.d.ts +2 -0
  31. package/dist/core/output-style/builtin/minimal.d.ts +2 -0
  32. package/dist/core/output-style/types.d.ts +6 -0
  33. package/dist/core/outputFormat.d.ts +29 -0
  34. package/dist/core/outputStyle.d.ts +43 -0
  35. package/dist/core/paths.d.ts +20 -0
  36. package/dist/core/planSystemPrompt.d.ts +5 -0
  37. package/dist/core/plugin.d.ts +138 -0
  38. package/dist/core/project.d.ts +64 -0
  39. package/dist/core/promptCache.d.ts +3 -0
  40. package/dist/core/query.d.ts +14 -0
  41. package/dist/core/rules.d.ts +8 -0
  42. package/dist/core/systemPrompt.d.ts +9 -0
  43. package/dist/core/thinking-config.d.ts +3 -0
  44. package/dist/core/usage.d.ts +14 -0
  45. package/dist/index.d.ts +16 -0
  46. package/dist/index.js +144432 -0
  47. package/dist/mcp/mcp.d.ts +49 -0
  48. package/dist/modes/builtin.d.ts +34 -0
  49. package/dist/modes/index.d.ts +8 -0
  50. package/dist/modes/registry.d.ts +18 -0
  51. package/dist/modes/types.d.ts +51 -0
  52. package/dist/platform/index.d.ts +5 -0
  53. package/dist/platform/node.d.ts +28 -0
  54. package/dist/platform/types.d.ts +41 -0
  55. package/dist/session/session.d.ts +43 -0
  56. package/dist/skill/skill.d.ts +79 -0
  57. package/dist/tools/tool.d.ts +119 -0
  58. package/dist/tools/tools/askUserQuestion.d.ts +48 -0
  59. package/dist/tools/tools/bash.d.ts +43 -0
  60. package/dist/tools/tools/edit.d.ts +9 -0
  61. package/dist/tools/tools/fetch.d.ts +9 -0
  62. package/dist/tools/tools/glob.d.ts +7 -0
  63. package/dist/tools/tools/grep.d.ts +22 -0
  64. package/dist/tools/tools/ls.d.ts +6 -0
  65. package/dist/tools/tools/read.d.ts +9 -0
  66. package/dist/tools/tools/skill.d.ts +7 -0
  67. package/dist/tools/tools/task.d.ts +14 -0
  68. package/dist/tools/tools/todo.d.ts +37 -0
  69. package/dist/tools/tools/write.d.ts +7 -0
  70. package/dist/utils/apiKeyRotation.d.ts +2 -0
  71. package/dist/utils/applyEdit.d.ts +17 -0
  72. package/dist/utils/background-detection.d.ts +2 -0
  73. package/dist/utils/dotenv.d.ts +9 -0
  74. package/dist/utils/env.d.ts +6 -0
  75. package/dist/utils/error.d.ts +11 -0
  76. package/dist/utils/execFileNoThrow.d.ts +8 -0
  77. package/dist/utils/files.d.ts +10 -0
  78. package/dist/utils/git.d.ts +163 -0
  79. package/dist/utils/ide.d.ts +27 -0
  80. package/dist/utils/ignore.d.ts +6 -0
  81. package/dist/utils/isLocal.d.ts +1 -0
  82. package/dist/utils/language.d.ts +9 -0
  83. package/dist/utils/list.d.ts +20 -0
  84. package/dist/utils/mergeSystemMessagesMiddleware.d.ts +2 -0
  85. package/dist/utils/messageNormalization.d.ts +22 -0
  86. package/dist/utils/path.d.ts +34 -0
  87. package/dist/utils/prependSystemMessageMiddleware.d.ts +2 -0
  88. package/dist/utils/project.d.ts +1 -0
  89. package/dist/utils/proxy.d.ts +18 -0
  90. package/dist/utils/randomUUID.d.ts +5 -0
  91. package/dist/utils/renderSessionMarkdown.d.ts +10 -0
  92. package/dist/utils/ripgrep.d.ts +16 -0
  93. package/dist/utils/safeFrontMatter.d.ts +11 -0
  94. package/dist/utils/safeParseJson.d.ts +1 -0
  95. package/dist/utils/safeStringify.d.ts +1 -0
  96. package/dist/utils/sanitizeAIResponse.d.ts +30 -0
  97. package/dist/utils/setTerminalTitle.d.ts +1 -0
  98. package/dist/utils/shell-execution.d.ts +44 -0
  99. package/dist/utils/string.d.ts +8 -0
  100. package/dist/utils/symbols.d.ts +14 -0
  101. package/dist/utils/system-encoding.d.ts +40 -0
  102. package/dist/utils/tokenCounter.d.ts +8 -0
  103. package/dist/utils/username.d.ts +1 -0
  104. package/package.json +106 -0
  105. package/src/agent/agent/agentManager.test.ts +124 -0
  106. package/src/agent/agent/agentManager.ts +372 -0
  107. package/src/agent/agent/builtin/common.ts +20 -0
  108. package/src/agent/agent/builtin/explore.ts +53 -0
  109. package/src/agent/agent/builtin/general-purpose.ts +38 -0
  110. package/src/agent/agent/builtin/index.ts +13 -0
  111. package/src/agent/agent/executor.test.ts +339 -0
  112. package/src/agent/agent/executor.ts +224 -0
  113. package/src/agent/agent/types.ts +119 -0
  114. package/src/api/engine.ts +466 -0
  115. package/src/communication/index.ts +18 -0
  116. package/src/communication/messageBus.ts +393 -0
  117. package/src/core/at.ts +315 -0
  118. package/src/core/backgroundTaskManager.ts +129 -0
  119. package/src/core/compact.ts +95 -0
  120. package/src/core/config.ts +441 -0
  121. package/src/core/constants.ts +82 -0
  122. package/src/core/context.ts +214 -0
  123. package/src/core/globalData.ts +77 -0
  124. package/src/core/history.ts +323 -0
  125. package/src/core/ide.ts +325 -0
  126. package/src/core/jsonl.ts +100 -0
  127. package/src/core/llmsContext.ts +117 -0
  128. package/src/core/loop.ts +638 -0
  129. package/src/core/message.ts +304 -0
  130. package/src/core/model.ts +2198 -0
  131. package/src/core/output-style/builtin/default.ts +9 -0
  132. package/src/core/output-style/builtin/explanatory.ts +22 -0
  133. package/src/core/output-style/builtin/index.ts +19 -0
  134. package/src/core/output-style/builtin/miao.ts +22 -0
  135. package/src/core/output-style/builtin/minimal.ts +8 -0
  136. package/src/core/output-style/types.ts +6 -0
  137. package/src/core/outputFormat.ts +93 -0
  138. package/src/core/outputStyle.ts +255 -0
  139. package/src/core/paths.ts +161 -0
  140. package/src/core/planSystemPrompt.ts +46 -0
  141. package/src/core/plugin.ts +299 -0
  142. package/src/core/project.ts +492 -0
  143. package/src/core/promptCache.ts +32 -0
  144. package/src/core/query.ts +46 -0
  145. package/src/core/rules.ts +56 -0
  146. package/src/core/systemPrompt.ts +176 -0
  147. package/src/core/thinking-config.ts +98 -0
  148. package/src/core/usage.ts +68 -0
  149. package/src/index.ts +39 -0
  150. package/src/mcp/mcp.ts +637 -0
  151. package/src/modes/builtin.ts +305 -0
  152. package/src/modes/index.ts +22 -0
  153. package/src/modes/registry.ts +39 -0
  154. package/src/modes/types.ts +56 -0
  155. package/src/platform/index.ts +6 -0
  156. package/src/platform/node.ts +108 -0
  157. package/src/platform/types.ts +54 -0
  158. package/src/plugins/index.ts +15 -0
  159. package/src/session/session.ts +187 -0
  160. package/src/skill/skill.ts +702 -0
  161. package/src/tools/tool.ts +378 -0
  162. package/src/tools/tools/askUserQuestion.ts +134 -0
  163. package/src/tools/tools/bash.test.ts +425 -0
  164. package/src/tools/tools/bash.ts +999 -0
  165. package/src/tools/tools/edit.ts +86 -0
  166. package/src/tools/tools/fetch.ts +129 -0
  167. package/src/tools/tools/glob.ts +69 -0
  168. package/src/tools/tools/grep.test.ts +194 -0
  169. package/src/tools/tools/grep.ts +358 -0
  170. package/src/tools/tools/ls.ts +51 -0
  171. package/src/tools/tools/read.test.ts +169 -0
  172. package/src/tools/tools/read.ts +284 -0
  173. package/src/tools/tools/skill.ts +73 -0
  174. package/src/tools/tools/task.test.ts +262 -0
  175. package/src/tools/tools/task.ts +284 -0
  176. package/src/tools/tools/todo.ts +269 -0
  177. package/src/tools/tools/write.ts +71 -0
  178. package/src/types.d.ts +18 -0
  179. package/src/utils/apiKeyRotation.test.ts +70 -0
  180. package/src/utils/apiKeyRotation.ts +24 -0
  181. package/src/utils/applyEdit.test.ts +388 -0
  182. package/src/utils/applyEdit.ts +547 -0
  183. package/src/utils/background-detection.test.ts +61 -0
  184. package/src/utils/background-detection.ts +58 -0
  185. package/src/utils/dotenv.ts +26 -0
  186. package/src/utils/env.ts +90 -0
  187. package/src/utils/error.ts +38 -0
  188. package/src/utils/execFileNoThrow.ts +49 -0
  189. package/src/utils/files.ts +93 -0
  190. package/src/utils/git.ts +1152 -0
  191. package/src/utils/ide.ts +279 -0
  192. package/src/utils/ignore.ts +275 -0
  193. package/src/utils/isLocal.ts +6 -0
  194. package/src/utils/language.ts +33 -0
  195. package/src/utils/list.ts +200 -0
  196. package/src/utils/mergeSystemMessagesMiddleware.ts +32 -0
  197. package/src/utils/messageNormalization.test.ts +401 -0
  198. package/src/utils/messageNormalization.ts +168 -0
  199. package/src/utils/path.ts +98 -0
  200. package/src/utils/prependSystemMessageMiddleware.ts +16 -0
  201. package/src/utils/project.ts +32 -0
  202. package/src/utils/proxy.ts +102 -0
  203. package/src/utils/randomUUID.ts +11 -0
  204. package/src/utils/renderSessionMarkdown.ts +175 -0
  205. package/src/utils/ripgrep.ts +189 -0
  206. package/src/utils/safeFrontMatter.test.ts +118 -0
  207. package/src/utils/safeFrontMatter.ts +68 -0
  208. package/src/utils/safeParseJson.ts +7 -0
  209. package/src/utils/safeStringify.ts +10 -0
  210. package/src/utils/sanitizeAIResponse.test.ts +135 -0
  211. package/src/utils/sanitizeAIResponse.ts +55 -0
  212. package/src/utils/setTerminalTitle.ts +7 -0
  213. package/src/utils/shell-execution.test.ts +237 -0
  214. package/src/utils/shell-execution.ts +279 -0
  215. package/src/utils/string.ts +13 -0
  216. package/src/utils/symbols.ts +18 -0
  217. package/src/utils/system-encoding.test.ts +164 -0
  218. package/src/utils/system-encoding.ts +296 -0
  219. package/src/utils/tokenCounter.test.ts +38 -0
  220. package/src/utils/tokenCounter.ts +19 -0
  221. package/src/utils/username.ts +21 -0
@@ -0,0 +1,200 @@
1
+ import createDebug from 'debug';
2
+ import fs from 'fs';
3
+ import { basename, join, relative, sep } from 'pathe';
4
+ import { isIgnored } from './ignore';
5
+ export const MAX_FILES = 1000;
6
+ export const TRUNCATED_MESSAGE = `There are more than ${MAX_FILES} files in the repository. Use the LS tool (passing a specific path), Bash tool, and other tools to explore nested directories. The first ${MAX_FILES} files and directories are included below:\n\n`;
7
+
8
+ const debug = createDebug('oricore:utils:list');
9
+
10
+ // List of product names to check for ignore files
11
+ const PRODUCT_NAMES = ['oricore', 'takumi', 'kwaipilot'];
12
+
13
+ export function listDirectory(
14
+ initialPath: string,
15
+ cwd: string,
16
+ maxFiles: number = MAX_FILES,
17
+ ) {
18
+ const results: string[] = [];
19
+ const queue = [initialPath];
20
+ while (queue.length > 0) {
21
+ if (results.length > maxFiles) {
22
+ return results;
23
+ }
24
+ const path = queue.shift()!;
25
+ if (skip(path)) {
26
+ continue;
27
+ }
28
+ if (path !== initialPath) {
29
+ results.push(relative(cwd, path) + sep);
30
+ }
31
+ let children: fs.Dirent[];
32
+ try {
33
+ children = fs.readdirSync(path, { withFileTypes: true });
34
+ } catch (e) {
35
+ // eg. EPERM, EACCES, ENOENT, etc.
36
+ // Silently skip directories we don't have permission to read
37
+ debug(`[LsTool] Error listing directory: ${path}`, e);
38
+ continue;
39
+ }
40
+ for (const child of children) {
41
+ if (child.name === 'node_modules') {
42
+ continue;
43
+ }
44
+
45
+ const childPath = join(path, child.name);
46
+
47
+ // Skip if ignored by any of the product-specific ignore files
48
+ if (isIgnored(childPath, cwd, PRODUCT_NAMES)) {
49
+ continue;
50
+ }
51
+
52
+ if (child.isDirectory()) {
53
+ queue.push(childPath + sep);
54
+ } else {
55
+ if (skip(childPath)) {
56
+ continue;
57
+ }
58
+ results.push(relative(cwd, childPath));
59
+ if (results.length > maxFiles) {
60
+ return results;
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return results;
66
+ }
67
+
68
+ const SKIP_DOT_FILES = new Set([
69
+ '.git',
70
+ '.env',
71
+ '.vscode',
72
+ '.idea',
73
+ '.bashrc',
74
+ '.bash_profile',
75
+ '.zshrc',
76
+ '.zprofile',
77
+ '.profile',
78
+ '.DS_Store',
79
+ '.localized',
80
+ 'Thumbs.db',
81
+ 'desktop.ini',
82
+ ]);
83
+
84
+ function skip(path: string) {
85
+ const name = basename(path);
86
+ if (path !== '.' && SKIP_DOT_FILES.has(name)) {
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+
92
+ export function listRootDirectory(rootPath: string): string[] {
93
+ const results: string[] = [];
94
+ try {
95
+ const children = fs.readdirSync(rootPath, { withFileTypes: true });
96
+ for (const child of children) {
97
+ if (child.name === 'node_modules' || child.name.startsWith('.')) {
98
+ continue;
99
+ }
100
+
101
+ const childPath = join(rootPath, child.name);
102
+
103
+ // Skip if ignored by any of the product-specific ignore files
104
+ if (isIgnored(childPath, rootPath, PRODUCT_NAMES)) {
105
+ continue;
106
+ }
107
+
108
+ if (child.isDirectory()) {
109
+ results.push(child.name + sep);
110
+ } else {
111
+ results.push(child.name);
112
+ }
113
+ }
114
+ } catch (e) {
115
+ // Silently skip root directories we don't have permission to read
116
+ }
117
+ return results;
118
+ }
119
+
120
+ type TreeNode = {
121
+ name: string;
122
+ path: string;
123
+ type: 'file' | 'directory';
124
+ children?: TreeNode[];
125
+ };
126
+
127
+ export function createFileTree(sortedPaths: string[]): TreeNode[] {
128
+ const root: TreeNode[] = [];
129
+
130
+ for (const path of sortedPaths) {
131
+ const parts = path.split(sep);
132
+ let currentLevel = root;
133
+ let currentPath = '';
134
+
135
+ for (let i = 0; i < parts.length; i++) {
136
+ const part = parts[i]!;
137
+ if (!part) {
138
+ // directories have trailing slashes
139
+ continue;
140
+ }
141
+ currentPath = currentPath ? `${currentPath}${sep}${part}` : part;
142
+ const isLastPart = i === parts.length - 1;
143
+
144
+ const existingNode = currentLevel.find((node) => node.name === part);
145
+
146
+ if (existingNode) {
147
+ currentLevel = existingNode.children || [];
148
+ } else {
149
+ const newNode: TreeNode = {
150
+ name: part,
151
+ path: currentPath,
152
+ type: isLastPart ? 'file' : 'directory',
153
+ };
154
+
155
+ if (!isLastPart) {
156
+ newNode.children = [];
157
+ }
158
+
159
+ currentLevel.push(newNode);
160
+ currentLevel = newNode.children || [];
161
+ }
162
+ }
163
+ }
164
+
165
+ return root;
166
+ }
167
+
168
+ /**
169
+ * eg.
170
+ * - src/
171
+ * - index.ts
172
+ * - utils/
173
+ * - file.ts
174
+ */
175
+ export function printTree(
176
+ cwd: string,
177
+ tree: TreeNode[],
178
+ level = 0,
179
+ prefix = '',
180
+ ): string {
181
+ let result = '';
182
+
183
+ // Add absolute path at root level
184
+ if (level === 0) {
185
+ result += `- ${cwd}${sep}\n`;
186
+ prefix = ' ';
187
+ }
188
+
189
+ for (const node of tree) {
190
+ // Add the current node to the result
191
+ result += `${prefix}${'-'} ${node.name}${node.type === 'directory' ? sep : ''}\n`;
192
+
193
+ // Recursively print children if they exist
194
+ if (node.children && node.children.length > 0) {
195
+ result += printTree(cwd, node.children, level + 1, `${prefix} `);
196
+ }
197
+ }
198
+
199
+ return result;
200
+ }
@@ -0,0 +1,32 @@
1
+ import type { LanguageModelMiddleware } from 'ai';
2
+
3
+ export const mergeSystemMessagesMiddleware: LanguageModelMiddleware = {
4
+ transformParams: async ({ params }) => {
5
+ const mergedPrompt: typeof params.prompt = [];
6
+ let pendingSystemContent: string[] = [];
7
+
8
+ for (const msg of params.prompt) {
9
+ if (msg.role === 'system') {
10
+ pendingSystemContent.push(msg.content);
11
+ } else {
12
+ if (pendingSystemContent.length > 0) {
13
+ mergedPrompt.push({
14
+ role: 'system' as const,
15
+ content: pendingSystemContent.join('\n\n'),
16
+ });
17
+ pendingSystemContent = [];
18
+ }
19
+ mergedPrompt.push(msg);
20
+ }
21
+ }
22
+
23
+ if (pendingSystemContent.length > 0) {
24
+ mergedPrompt.push({
25
+ role: 'system' as const,
26
+ content: pendingSystemContent.join('\n\n'),
27
+ });
28
+ }
29
+
30
+ return { ...params, prompt: mergedPrompt };
31
+ },
32
+ };
@@ -0,0 +1,401 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import type { NormalizedMessage } from '../core/message';
3
+ import { normalizeMessagesForCompact } from './messageNormalization';
4
+
5
+ describe('normalizeMessagesForCompact', () => {
6
+ test('should keep text and reasoning content in assistant messages', () => {
7
+ const messages: NormalizedMessage[] = [
8
+ {
9
+ role: 'assistant',
10
+ content: [
11
+ { type: 'text', text: 'I will help you' },
12
+ { type: 'reasoning', text: 'Need to check the file first' },
13
+ {
14
+ type: 'tool_use',
15
+ id: '1',
16
+ name: 'read',
17
+ input: { file_path: 'test.ts' },
18
+ },
19
+ ],
20
+ text: 'I will help you',
21
+ model: 'gpt-4',
22
+ usage: {
23
+ input_tokens: 100,
24
+ output_tokens: 50,
25
+ },
26
+ type: 'message',
27
+ timestamp: '2024-01-01',
28
+ uuid: 'uuid-1',
29
+ parentUuid: null,
30
+ },
31
+ ];
32
+
33
+ const result = normalizeMessagesForCompact(messages);
34
+
35
+ expect(result).toHaveLength(1);
36
+ expect(result[0].role).toBe('assistant');
37
+ expect(result[0].content).toEqual([
38
+ { type: 'text', text: 'I will help you' },
39
+ { type: 'reasoning', text: 'Need to check the file first' },
40
+ ]);
41
+ });
42
+
43
+ test('should create placeholder text when assistant has only tool_use', () => {
44
+ const messages: NormalizedMessage[] = [
45
+ {
46
+ role: 'assistant',
47
+ content: [
48
+ {
49
+ type: 'tool_use',
50
+ id: '1',
51
+ name: 'read',
52
+ input: { file_path: 'test.ts' },
53
+ },
54
+ ],
55
+ text: '',
56
+ model: 'gpt-4',
57
+ usage: {
58
+ input_tokens: 100,
59
+ output_tokens: 50,
60
+ },
61
+ type: 'message',
62
+ timestamp: '2024-01-01',
63
+ uuid: 'uuid-1',
64
+ parentUuid: null,
65
+ },
66
+ ];
67
+
68
+ const result = normalizeMessagesForCompact(messages);
69
+
70
+ expect(result).toHaveLength(1);
71
+ expect(result[0].content).toEqual([
72
+ { type: 'text', text: '[Assistant performed tool operations]' },
73
+ ]);
74
+ });
75
+
76
+ test('should convert reasoning-only assistant content to readable text', () => {
77
+ const messages: NormalizedMessage[] = [
78
+ {
79
+ role: 'assistant',
80
+ content: [
81
+ {
82
+ type: 'reasoning',
83
+ text: 'The user asked me to revert recent changes to manifest.json.',
84
+ },
85
+ {
86
+ type: 'tool_use',
87
+ id: 'toolu_vrtx_011pD7AhictqR2PsxdL3vE9L',
88
+ name: 'write',
89
+ input: {
90
+ content: '{ "name": "Node Link Handling Plugin" }',
91
+ file_path: '/tmp/manifest.json',
92
+ },
93
+ description: 'packages/figma-link-plugin/manifest.json',
94
+ },
95
+ ],
96
+ text: '',
97
+ model: 'claude-4.5-sonnet',
98
+ usage: {
99
+ input_tokens: 139630,
100
+ output_tokens: 308,
101
+ },
102
+ type: 'message',
103
+ timestamp: '2025-11-27T09:42:48.764Z',
104
+ uuid: '1f050587-ecb1-41f7-97f3-b2d5acb6cb08',
105
+ parentUuid: '19c7f79b-71fe-4d77-bf3d-fcc6080be42f',
106
+ },
107
+ ];
108
+
109
+ const result = normalizeMessagesForCompact(messages);
110
+
111
+ expect(result).toHaveLength(1);
112
+ const content = result[0].content;
113
+ expect(Array.isArray(content)).toBe(true);
114
+ expect(content).toEqual([
115
+ {
116
+ type: 'text',
117
+ text: 'The user asked me to revert recent changes to manifest.json.',
118
+ },
119
+ ]);
120
+ });
121
+
122
+ test('should handle multiple reasoning parts by joining them', () => {
123
+ const messages: NormalizedMessage[] = [
124
+ {
125
+ role: 'assistant',
126
+ content: [
127
+ {
128
+ type: 'reasoning',
129
+ text: 'First, I need to analyze the request.',
130
+ },
131
+ {
132
+ type: 'reasoning',
133
+ text: 'Then, I will execute the appropriate tool.',
134
+ },
135
+ {
136
+ type: 'tool_use',
137
+ id: 'tool-1',
138
+ name: 'bash',
139
+ input: { command: 'ls' },
140
+ },
141
+ ],
142
+ text: '',
143
+ model: 'test-model',
144
+ usage: { input_tokens: 100, output_tokens: 50 },
145
+ type: 'message',
146
+ timestamp: '2025-11-27T10:00:00.000Z',
147
+ uuid: 'test-uuid',
148
+ parentUuid: null,
149
+ },
150
+ ];
151
+
152
+ const result = normalizeMessagesForCompact(messages);
153
+
154
+ expect(result).toHaveLength(1);
155
+ expect(result[0].content).toEqual([
156
+ {
157
+ type: 'text',
158
+ text: 'First, I need to analyze the request.\nThen, I will execute the appropriate tool.',
159
+ },
160
+ ]);
161
+ });
162
+
163
+ test('should use default text when reasoning contains only whitespace', () => {
164
+ const messages: NormalizedMessage[] = [
165
+ {
166
+ role: 'assistant',
167
+ content: [
168
+ {
169
+ type: 'reasoning',
170
+ text: ' \n \t ',
171
+ },
172
+ {
173
+ type: 'tool_use',
174
+ id: 'tool-1',
175
+ name: 'read',
176
+ input: { file_path: 'test.ts' },
177
+ },
178
+ ],
179
+ text: '',
180
+ model: 'test-model',
181
+ usage: { input_tokens: 100, output_tokens: 50 },
182
+ type: 'message',
183
+ timestamp: '2025-11-27T10:00:00.000Z',
184
+ uuid: 'test-uuid',
185
+ parentUuid: null,
186
+ },
187
+ ];
188
+
189
+ const result = normalizeMessagesForCompact(messages);
190
+
191
+ expect(result).toHaveLength(1);
192
+ expect(result[0].content).toEqual([
193
+ {
194
+ type: 'text',
195
+ text: '[Assistant performed tool operations]',
196
+ },
197
+ ]);
198
+ });
199
+
200
+ test('should preserve text content when both text and reasoning exist', () => {
201
+ const messages: NormalizedMessage[] = [
202
+ {
203
+ role: 'assistant',
204
+ content: [
205
+ {
206
+ type: 'text',
207
+ text: 'Let me help you with that.',
208
+ },
209
+ {
210
+ type: 'reasoning',
211
+ text: 'Internal thought process...',
212
+ },
213
+ {
214
+ type: 'tool_use',
215
+ id: 'tool-1',
216
+ name: 'write',
217
+ input: { file_path: 'test.ts', content: 'code' },
218
+ },
219
+ ],
220
+ text: 'Let me help you with that.',
221
+ model: 'test-model',
222
+ usage: { input_tokens: 100, output_tokens: 50 },
223
+ type: 'message',
224
+ timestamp: '2025-11-27T10:00:00.000Z',
225
+ uuid: 'test-uuid',
226
+ parentUuid: null,
227
+ },
228
+ ];
229
+
230
+ const result = normalizeMessagesForCompact(messages);
231
+
232
+ expect(result).toHaveLength(1);
233
+ expect(result[0].content).toEqual([
234
+ {
235
+ type: 'text',
236
+ text: 'Let me help you with that.',
237
+ },
238
+ {
239
+ type: 'reasoning',
240
+ text: 'Internal thought process...',
241
+ },
242
+ ]);
243
+ });
244
+
245
+ test('should convert tool messages to user messages with summary', () => {
246
+ const messages: NormalizedMessage[] = [
247
+ {
248
+ role: 'tool',
249
+ content: [
250
+ {
251
+ type: 'tool-result',
252
+ toolCallId: '1',
253
+ toolName: 'read',
254
+ input: { file_path: 'test.ts' },
255
+ result: {
256
+ llmContent: 'const x = 1;',
257
+ isError: false,
258
+ },
259
+ },
260
+ ],
261
+ type: 'message',
262
+ timestamp: '2024-01-01',
263
+ uuid: 'uuid-2',
264
+ parentUuid: 'uuid-1',
265
+ },
266
+ ];
267
+
268
+ const result = normalizeMessagesForCompact(messages);
269
+
270
+ expect(result).toHaveLength(1);
271
+ expect(result[0].role).toBe('user');
272
+ expect(result[0].content).toContain('Tool read executed');
273
+ expect(result[0].content).toContain('const x = 1;');
274
+ });
275
+
276
+ test('should keep regular user and system messages unchanged', () => {
277
+ const messages: NormalizedMessage[] = [
278
+ {
279
+ role: 'system',
280
+ content: 'You are a helpful assistant',
281
+ type: 'message',
282
+ timestamp: '2024-01-01',
283
+ uuid: 'uuid-0',
284
+ parentUuid: null,
285
+ },
286
+ {
287
+ role: 'user',
288
+ content: 'Help me with this task',
289
+ type: 'message',
290
+ timestamp: '2024-01-01',
291
+ uuid: 'uuid-1',
292
+ parentUuid: 'uuid-0',
293
+ },
294
+ ];
295
+
296
+ const result = normalizeMessagesForCompact(messages);
297
+
298
+ expect(result).toHaveLength(2);
299
+ expect(result[0]).toEqual(messages[0]);
300
+ expect(result[1]).toEqual(messages[1]);
301
+ });
302
+
303
+ test('should truncate long tool results', () => {
304
+ const longText = 'a'.repeat(300);
305
+ const messages: NormalizedMessage[] = [
306
+ {
307
+ role: 'tool',
308
+ content: [
309
+ {
310
+ type: 'tool-result',
311
+ toolCallId: '1',
312
+ toolName: 'read',
313
+ input: { file_path: 'test.ts' },
314
+ result: {
315
+ llmContent: longText,
316
+ isError: false,
317
+ },
318
+ },
319
+ ],
320
+ type: 'message',
321
+ timestamp: '2024-01-01',
322
+ uuid: 'uuid-2',
323
+ parentUuid: 'uuid-1',
324
+ },
325
+ ];
326
+
327
+ const result = normalizeMessagesForCompact(messages);
328
+
329
+ expect(result).toHaveLength(1);
330
+ const content = result[0].content as string;
331
+ expect(content).toContain('...');
332
+ expect(content.length).toBeLessThan(longText.length + 100);
333
+ });
334
+
335
+ test('should filter out empty messages', () => {
336
+ const messages: NormalizedMessage[] = [
337
+ {
338
+ role: 'user',
339
+ content: 'Valid message',
340
+ type: 'message',
341
+ timestamp: '2024-01-01',
342
+ uuid: 'uuid-1',
343
+ parentUuid: null,
344
+ },
345
+ {
346
+ role: 'user',
347
+ content: '',
348
+ type: 'message',
349
+ timestamp: '2024-01-01',
350
+ uuid: 'uuid-2',
351
+ parentUuid: 'uuid-1',
352
+ },
353
+ {
354
+ role: 'user',
355
+ content: ' ',
356
+ type: 'message',
357
+ timestamp: '2024-01-01',
358
+ uuid: 'uuid-3',
359
+ parentUuid: 'uuid-2',
360
+ },
361
+ ];
362
+
363
+ const result = normalizeMessagesForCompact(messages);
364
+
365
+ expect(result).toHaveLength(1);
366
+ expect(result[0].content).toBe('Valid message');
367
+ });
368
+
369
+ test('should handle tool results with array llmContent', () => {
370
+ const messages: NormalizedMessage[] = [
371
+ {
372
+ role: 'tool',
373
+ content: [
374
+ {
375
+ type: 'tool-result',
376
+ toolCallId: '1',
377
+ toolName: 'read',
378
+ input: { file_path: 'test.ts' },
379
+ result: {
380
+ llmContent: [
381
+ { type: 'text', text: 'First part' },
382
+ { type: 'text', text: 'Second part' },
383
+ ],
384
+ isError: false,
385
+ },
386
+ },
387
+ ],
388
+ type: 'message',
389
+ timestamp: '2024-01-01',
390
+ uuid: 'uuid-2',
391
+ parentUuid: 'uuid-1',
392
+ },
393
+ ];
394
+
395
+ const result = normalizeMessagesForCompact(messages);
396
+
397
+ expect(result).toHaveLength(1);
398
+ const content = result[0].content as string;
399
+ expect(content).toContain('First part Second part');
400
+ });
401
+ });