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,168 @@
1
+ import type {
2
+ NormalizedMessage,
3
+ ReasoningPart,
4
+ TextPart,
5
+ ToolResultPart2,
6
+ } from '../core/message';
7
+
8
+ function formatToolResultContent(llmContent: unknown): string {
9
+ if (typeof llmContent === 'string') {
10
+ const truncatedText =
11
+ llmContent.length > 200
12
+ ? `${llmContent.substring(0, 200)}...`
13
+ : llmContent;
14
+ return `: ${truncatedText}`;
15
+ }
16
+ if (Array.isArray(llmContent)) {
17
+ const textParts = llmContent
18
+ .filter((part): part is TextPart => part.type === 'text')
19
+ .map((part) => part.text)
20
+ .join(' ');
21
+ const truncatedText =
22
+ textParts.length > 200 ? `${textParts.substring(0, 200)}...` : textParts;
23
+ if (truncatedText) {
24
+ return `: ${truncatedText}`;
25
+ }
26
+ }
27
+ return '';
28
+ }
29
+
30
+ /**
31
+ * Normalizes messages for compacting by filtering out tool-related content
32
+ * while preserving the conversational flow and essential information.
33
+ *
34
+ * This function transforms tool calls and results into human-readable summaries
35
+ * to make the conversation history suitable for compression without losing
36
+ * important context about what operations were performed.
37
+ *
38
+ * For assistant messages:
39
+ * - Removes tool_use parts
40
+ * - Keeps text and reasoning parts
41
+ * - If no text content exists, converts reasoning to text for readability
42
+ * - If no content remains, uses a default placeholder
43
+ *
44
+ * For tool messages:
45
+ * - Converts to user messages with tool execution summaries
46
+ *
47
+ * @param messages - Array of normalized messages to process
48
+ * @returns Array of normalized messages with tool content converted to summaries
49
+ */
50
+ export function normalizeMessagesForCompact(
51
+ messages: NormalizedMessage[],
52
+ ): NormalizedMessage[] {
53
+ return messages
54
+ .map((message) => {
55
+ if (message.role === 'assistant') {
56
+ if (Array.isArray(message.content)) {
57
+ const filteredContent = message.content.filter(
58
+ (part): part is TextPart | ReasoningPart =>
59
+ part.type === 'text' || part.type === 'reasoning',
60
+ );
61
+
62
+ if (filteredContent.length === 0) {
63
+ return {
64
+ ...message,
65
+ content: [
66
+ {
67
+ type: 'text' as const,
68
+ text: '[Assistant performed tool operations]',
69
+ },
70
+ ],
71
+ };
72
+ }
73
+
74
+ // If there's no text content, convert reasoning to text for readability
75
+ const hasTextPart = filteredContent.some(
76
+ (part) => part.type === 'text',
77
+ );
78
+ if (!hasTextPart) {
79
+ const reasoningTexts = filteredContent
80
+ .filter(
81
+ (part): part is ReasoningPart => part.type === 'reasoning',
82
+ )
83
+ .map((part) => part.text)
84
+ .filter((text) => text.trim().length > 0);
85
+
86
+ if (reasoningTexts.length > 0) {
87
+ return {
88
+ ...message,
89
+ content: [
90
+ {
91
+ type: 'text' as const,
92
+ text: reasoningTexts.join('\n'),
93
+ },
94
+ ],
95
+ };
96
+ }
97
+
98
+ // If even reasoning is empty, use default text
99
+ return {
100
+ ...message,
101
+ content: [
102
+ {
103
+ type: 'text' as const,
104
+ text: '[Assistant performed tool operations]',
105
+ },
106
+ ],
107
+ };
108
+ }
109
+
110
+ return {
111
+ ...message,
112
+ content: filteredContent,
113
+ };
114
+ }
115
+ return message;
116
+ }
117
+
118
+ if (message.role === 'tool') {
119
+ if (Array.isArray(message.content)) {
120
+ const toolSummaries = message.content.map((part: ToolResultPart2) => {
121
+ if (part.type === 'tool-result') {
122
+ const result = part.result;
123
+ let summary = `Tool ${part.toolName} executed`;
124
+
125
+ if (
126
+ result &&
127
+ typeof result === 'object' &&
128
+ 'llmContent' in result
129
+ ) {
130
+ const contentSuffix = formatToolResultContent(
131
+ result.llmContent,
132
+ );
133
+ summary += contentSuffix || ' successfully';
134
+ } else {
135
+ summary += ' successfully';
136
+ }
137
+
138
+ return summary;
139
+ }
140
+ return 'Tool operation completed';
141
+ });
142
+
143
+ return {
144
+ ...message,
145
+ role: 'user' as const,
146
+ content: `[Tool Results Summary: ${toolSummaries.join('; ')}]`,
147
+ };
148
+ }
149
+
150
+ return {
151
+ ...message,
152
+ role: 'user' as const,
153
+ content: '[Tool operations completed]',
154
+ };
155
+ }
156
+
157
+ return message;
158
+ })
159
+ .filter((message) => {
160
+ if (typeof message.content === 'string') {
161
+ return message.content.trim().length > 0;
162
+ }
163
+ if (Array.isArray(message.content)) {
164
+ return message.content.length > 0;
165
+ }
166
+ return true;
167
+ });
168
+ }
@@ -0,0 +1,98 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'pathe';
4
+
5
+ export function relativeToHome(p: string) {
6
+ return p.replace(os.homedir(), '~');
7
+ }
8
+
9
+ export type PathValidationResult =
10
+ | { resultType: 'success'; absolutePath: string }
11
+ | { resultType: 'emptyPath' }
12
+ | { resultType: 'pathNotFound'; directoryPath: string; absolutePath: string }
13
+ | { resultType: 'notADirectory'; directoryPath: string; absolutePath: string }
14
+ | {
15
+ resultType: 'alreadyInWorkingDirectory';
16
+ directoryPath: string;
17
+ workingDir: string;
18
+ };
19
+
20
+ /**
21
+ * Check if childPath is within parentPath
22
+ */
23
+ export function isPathWithin(childPath: string, parentPath: string): boolean {
24
+ const relative = path.relative(parentPath, childPath);
25
+ // If relative path starts with .., childPath is not within parentPath
26
+ return !relative.startsWith('..') && !path.isAbsolute(relative);
27
+ }
28
+
29
+ /**
30
+ * Validate directory path
31
+ * @param inputPath Path input by user
32
+ * @param existingDirectories List of existing working directories (including cwd)
33
+ * @returns Validation result
34
+ */
35
+ export function validateDirectoryPath(
36
+ inputPath: string,
37
+ existingDirectories: string[],
38
+ ): PathValidationResult {
39
+ // Check for empty path
40
+ if (!inputPath || inputPath.trim() === '') {
41
+ return { resultType: 'emptyPath' };
42
+ }
43
+
44
+ // Resolve to absolute path
45
+ const absolutePath = path.resolve(inputPath.trim());
46
+
47
+ // Check if path exists
48
+ if (!fs.existsSync(absolutePath)) {
49
+ return {
50
+ resultType: 'pathNotFound',
51
+ directoryPath: inputPath,
52
+ absolutePath,
53
+ };
54
+ }
55
+
56
+ // Check if it's a directory
57
+ const stats = fs.statSync(absolutePath);
58
+ if (!stats.isDirectory()) {
59
+ return {
60
+ resultType: 'notADirectory',
61
+ directoryPath: inputPath,
62
+ absolutePath,
63
+ };
64
+ }
65
+
66
+ // Check if already in existing working directories
67
+ for (const existingDir of existingDirectories) {
68
+ if (isPathWithin(absolutePath, existingDir)) {
69
+ return {
70
+ resultType: 'alreadyInWorkingDirectory',
71
+ directoryPath: inputPath,
72
+ workingDir: existingDir,
73
+ };
74
+ }
75
+ }
76
+
77
+ return { resultType: 'success', absolutePath };
78
+ }
79
+
80
+ /**
81
+ * Format validation result into user-friendly message
82
+ */
83
+ export function formatValidationMessage(result: PathValidationResult): string {
84
+ switch (result.resultType) {
85
+ case 'emptyPath':
86
+ return 'Please provide a directory path.';
87
+ case 'pathNotFound':
88
+ return `Path ${result.directoryPath} does not exist.`;
89
+ case 'notADirectory': {
90
+ const parentDir = path.dirname(result.absolutePath);
91
+ return `${result.directoryPath} is not a directory. Would you like to add parent directory ${parentDir}?`;
92
+ }
93
+ case 'alreadyInWorkingDirectory':
94
+ return `${result.directoryPath} is already within existing working directory ${result.workingDir}.`;
95
+ case 'success':
96
+ return `Successfully added ${result.absolutePath} as working directory.`;
97
+ }
98
+ }
@@ -0,0 +1,16 @@
1
+ import type { LanguageModelMiddleware } from 'ai';
2
+
3
+ export const prependSystemMessageMiddleware: LanguageModelMiddleware = {
4
+ transformParams: async ({ params }) => {
5
+ return {
6
+ ...params,
7
+ prompt: [
8
+ {
9
+ role: 'system' as const,
10
+ content: "You are Claude Code, Anthropic's official CLI for Claude.",
11
+ },
12
+ ...params.prompt,
13
+ ],
14
+ };
15
+ },
16
+ };
@@ -0,0 +1,32 @@
1
+ import fs from 'fs';
2
+ import { homedir } from 'os';
3
+ import path from 'pathe';
4
+
5
+ const PROJECT_MARKERS = [
6
+ 'package.json',
7
+ 'Cargo.toml',
8
+ 'pyproject.toml',
9
+ 'go.mod',
10
+ 'composer.json',
11
+ 'pom.xml',
12
+ 'build.gradle',
13
+ 'requirements.txt',
14
+ 'Gemfile',
15
+ 'mix.exs',
16
+ 'deno.json',
17
+ 'deno.jsonc',
18
+ ];
19
+
20
+ export function isProjectDirectory(cwd: string): boolean {
21
+ const normalizedCwd = path.resolve(cwd);
22
+ const homeDir = path.resolve(homedir());
23
+
24
+ if (normalizedCwd === homeDir) {
25
+ return false;
26
+ }
27
+
28
+ return PROJECT_MARKERS.some((marker) => {
29
+ const markerPath = path.join(normalizedCwd, marker);
30
+ return fs.existsSync(markerPath);
31
+ });
32
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * HTTP Proxy utilities for AI model providers
3
+ * Handles proxy configuration and custom fetch implementation
4
+ */
5
+
6
+ import type { Dispatcher, RequestInit as UndiciRequestInit } from 'undici';
7
+ import { ProxyAgent } from 'undici';
8
+
9
+ // Module-level cache for ProxyAgent instances (one per unique proxy URL)
10
+ const proxyAgents = new Map<string, ProxyAgent>();
11
+
12
+ // Module-level cache for undici fetch
13
+ let undiciFetch: typeof import('undici').fetch | null = null;
14
+
15
+ /**
16
+ * Validate proxy URL format
17
+ * @param proxyUrl - Proxy URL to validate
18
+ * @returns true if valid, false otherwise
19
+ */
20
+ function isValidProxyUrl(proxyUrl: string): boolean {
21
+ try {
22
+ const url = new URL(proxyUrl);
23
+ // Support http, https, socks5, socks4 protocols
24
+ return ['http:', 'https:', 'socks5:', 'socks4:'].includes(url.protocol);
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Create a custom fetch function that uses the specified proxy
32
+ * This wraps undici's fetch with the ProxyAgent
33
+ *
34
+ * Why needed: Bun's native fetch doesn't support HTTP_PROXY env vars,
35
+ * so we use undici's fetch with ProxyAgent to handle proxy requests
36
+ *
37
+ * @param proxyUrl - Proxy URL (e.g., http://127.0.0.1:7890 or socks5://127.0.0.1:1080)
38
+ * @returns A fetch-compatible function that routes requests through the configured proxy
39
+ * @example
40
+ * const proxyFetch = createProxyFetch('http://127.0.0.1:7890');
41
+ * await proxyFetch('https://api.openai.com/v1/models');
42
+ */
43
+ export function createProxyFetch(proxyUrl: string) {
44
+ // Validate proxy URL format
45
+ if (!isValidProxyUrl(proxyUrl)) {
46
+ console.warn(
47
+ `[Proxy] Invalid proxy URL format: ${proxyUrl}. Expected format: http://host:port, https://host:port, or socks5://host:port`,
48
+ );
49
+ return fetch;
50
+ }
51
+
52
+ // Get or create ProxyAgent for this URL
53
+ let proxyAgent = proxyAgents.get(proxyUrl);
54
+
55
+ if (!proxyAgent) {
56
+ try {
57
+ proxyAgent = new ProxyAgent(proxyUrl);
58
+ proxyAgents.set(proxyUrl, proxyAgent);
59
+ } catch (error) {
60
+ console.error(
61
+ `[Proxy] Failed to create ProxyAgent for ${proxyUrl}:`,
62
+ error,
63
+ );
64
+ // Return native fetch as fallback
65
+ return fetch;
66
+ }
67
+ }
68
+
69
+ // Return a fetch-compatible function
70
+ return async (input: string | URL | Request, init?: RequestInit) => {
71
+ // Lazy load undici fetch (cached at module level)
72
+ if (!undiciFetch) {
73
+ undiciFetch = (await import('undici')).fetch;
74
+ }
75
+
76
+ // Handle different input types properly
77
+ let url: string;
78
+ let requestInit: RequestInit | undefined = init;
79
+
80
+ if (input instanceof Request) {
81
+ // Extract all request properties, not just URL
82
+ url = input.url;
83
+ requestInit = {
84
+ method: input.method,
85
+ headers: input.headers,
86
+ body: input.body,
87
+ ...init, // Allow overrides
88
+ };
89
+ } else if (input instanceof URL) {
90
+ url = input.toString();
91
+ } else {
92
+ url = String(input);
93
+ }
94
+
95
+ // undici fetch with ProxyAgent dispatcher
96
+ // Use undici's RequestInit type to avoid compatibility issues
97
+ return undiciFetch(url, {
98
+ ...requestInit,
99
+ dispatcher: proxyAgent,
100
+ } as UndiciRequestInit);
101
+ };
102
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Generate a random UUID v4 compatible string
3
+ * Compatible with Node.js 18+ without using crypto module
4
+ */
5
+ export function randomUUID(): string {
6
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
7
+ const r = (Math.random() * 16) | 0;
8
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
9
+ return v.toString(16);
10
+ });
11
+ }
@@ -0,0 +1,175 @@
1
+ import type {
2
+ AssistantContent,
3
+ NormalizedMessage,
4
+ TextPart,
5
+ ToolContent,
6
+ ToolResultPart2,
7
+ UserContent,
8
+ } from '../core/message';
9
+
10
+ export function renderSessionMarkdown(opts: {
11
+ sessionId: string;
12
+ title: string;
13
+ projectPath: string;
14
+ model: string | null;
15
+ messages: NormalizedMessage[];
16
+ createdAt: Date;
17
+ updatedAt: Date;
18
+ }): string {
19
+ const lines: string[] = [];
20
+
21
+ const title = normalizeTitle(opts.title) || `Session ${opts.sessionId}`;
22
+
23
+ lines.push(`# ${title}`);
24
+ lines.push('');
25
+ lines.push(`**Session ID:** ${opts.sessionId}`);
26
+ lines.push(`**Project:** ${opts.projectPath}`);
27
+ lines.push(`**Model:** ${opts.model ?? ''}`);
28
+ lines.push(`**Created:** ${formatDate(opts.createdAt)}`);
29
+ lines.push(`**Updated:** ${formatDate(opts.updatedAt)}`);
30
+ lines.push('');
31
+ lines.push('---');
32
+ lines.push('');
33
+
34
+ opts.messages.forEach((m) => {
35
+ if (m.role === 'system') return;
36
+
37
+ const header = (() => {
38
+ if (m.role === 'user') return '## User';
39
+ if (m.role === 'tool') return '## Tool';
40
+ if (m.role === 'assistant') {
41
+ return '## Assistant';
42
+ }
43
+ return '## Message';
44
+ })();
45
+
46
+ if (header) {
47
+ lines.push(header);
48
+ lines.push('');
49
+ }
50
+
51
+ if (m.role === 'tool') {
52
+ const toolContent = m.content as ToolResultPart2[];
53
+ for (const part of toolContent) {
54
+ lines.push(`Tool: ${part.toolName}`);
55
+ const toolModel = getModelFromToolResultPart(part);
56
+ if (toolModel && shouldShowModel(toolModel, opts.model)) {
57
+ lines.push('');
58
+ lines.push(`**Model:** ${toolModel}`);
59
+ }
60
+ lines.push('');
61
+ lines.push('**Input:**');
62
+ lines.push('```json');
63
+ lines.push(JSON.stringify(part.input ?? {}, null, 2));
64
+ lines.push('```');
65
+ lines.push('');
66
+ lines.push('**Output:**');
67
+ lines.push('```');
68
+ lines.push(renderToolResultOutput(part.result.llmContent));
69
+ lines.push('```');
70
+ lines.push('');
71
+ }
72
+ return;
73
+ }
74
+
75
+ const content = renderMessageContent(m.content);
76
+ if (content) lines.push(content);
77
+ lines.push('');
78
+ });
79
+
80
+ return lines.join('\n');
81
+ }
82
+
83
+ function renderMessageContent(
84
+ content: UserContent | AssistantContent | ToolContent | ToolResultPart2[],
85
+ ): string {
86
+ if (typeof content === 'string') return content;
87
+
88
+ return content
89
+ .map((part) => {
90
+ if (!('type' in part)) return String(part);
91
+
92
+ if (part.type === 'text') return (part as TextPart).text;
93
+
94
+ if (part.type === 'reasoning') {
95
+ return `_Thinking:_\n\n${part.text}\n`;
96
+ }
97
+
98
+ if (part.type === 'tool_use') {
99
+ return `Tool: ${part.name}
100
+
101
+ **Input:**
102
+
103
+ \`\`\`json
104
+ ${JSON.stringify(part.input ?? {}, null, 2)}
105
+ \`\`\`
106
+
107
+ **Output:**`;
108
+ }
109
+
110
+ if (part.type === 'tool_result') {
111
+ const result = part.result;
112
+ return `
113
+
114
+ \`\`\`
115
+ ${renderToolResultOutput(result.llmContent)}
116
+ \`\`\`
117
+ `;
118
+ }
119
+
120
+ return `\n\`\`\`\n[${part.type}]\n\`\`\`\n`;
121
+ })
122
+ .join('\n');
123
+ }
124
+
125
+ function renderToolResultOutput(
126
+ output: TextPart['text'] | Array<TextPart | { type: 'image' }>,
127
+ ): string {
128
+ if (typeof output === 'string') return output;
129
+ return output
130
+ .filter((p): p is TextPart => p.type === 'text')
131
+ .map((p) => p.text)
132
+ .join('');
133
+ }
134
+
135
+ function normalizeTitle(text: string): string {
136
+ const trimmed = text.trim();
137
+ if (!trimmed) return '';
138
+ return trimmed
139
+ .split(/\r\n|\r|\n/)[0]
140
+ .slice(0, 80)
141
+ .trim();
142
+ }
143
+
144
+ function formatDate(date: Date): string {
145
+ return date.toLocaleString();
146
+ }
147
+
148
+ function getModelFromToolResultPart(part: ToolResultPart2): string | null {
149
+ if (part.toolName !== 'task') return null;
150
+
151
+ const returnDisplay = part.result.returnDisplay;
152
+ if (!returnDisplay) return null;
153
+ if (typeof returnDisplay !== 'object') return null;
154
+ if (!('type' in returnDisplay)) return null;
155
+ if (returnDisplay.type !== 'agent_result') return null;
156
+ if (!('model' in returnDisplay)) return null;
157
+
158
+ const model = returnDisplay.model;
159
+ return typeof model === 'string' ? model : null;
160
+ }
161
+
162
+ function shouldShowModel(model: string, mainModel: string | null): boolean {
163
+ if (!model) return false;
164
+ if (!mainModel) return true;
165
+
166
+ if (mainModel === model) return false;
167
+
168
+ const parts = mainModel.split('/');
169
+ if (parts.length >= 2) {
170
+ const id = parts.slice(1).join('/');
171
+ if (id === model) return false;
172
+ }
173
+
174
+ return true;
175
+ }