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.
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/agent/agent/agentManager.d.ts +38 -0
- package/dist/agent/agent/builtin/common.d.ts +5 -0
- package/dist/agent/agent/builtin/explore.d.ts +5 -0
- package/dist/agent/agent/builtin/general-purpose.d.ts +5 -0
- package/dist/agent/agent/builtin/index.d.ts +5 -0
- package/dist/agent/agent/executor.d.ts +2 -0
- package/dist/agent/agent/types.d.ts +98 -0
- package/dist/api/engine.d.ts +213 -0
- package/dist/communication/index.d.ts +4 -0
- package/dist/communication/messageBus.d.ts +71 -0
- package/dist/core/at.d.ts +26 -0
- package/dist/core/backgroundTaskManager.d.ts +27 -0
- package/dist/core/compact.d.ts +9 -0
- package/dist/core/config.d.ts +103 -0
- package/dist/core/constants.d.ts +32 -0
- package/dist/core/context.d.ts +57 -0
- package/dist/core/globalData.d.ts +21 -0
- package/dist/core/history.d.ts +24 -0
- package/dist/core/ide.d.ts +103 -0
- package/dist/core/jsonl.d.ts +37 -0
- package/dist/core/llmsContext.d.ts +14 -0
- package/dist/core/loop.d.ts +82 -0
- package/dist/core/message.d.ts +132 -0
- package/dist/core/model.d.ts +79 -0
- package/dist/core/output-style/builtin/default.d.ts +2 -0
- package/dist/core/output-style/builtin/explanatory.d.ts +2 -0
- package/dist/core/output-style/builtin/index.d.ts +6 -0
- package/dist/core/output-style/builtin/miao.d.ts +2 -0
- package/dist/core/output-style/builtin/minimal.d.ts +2 -0
- package/dist/core/output-style/types.d.ts +6 -0
- package/dist/core/outputFormat.d.ts +29 -0
- package/dist/core/outputStyle.d.ts +43 -0
- package/dist/core/paths.d.ts +20 -0
- package/dist/core/planSystemPrompt.d.ts +5 -0
- package/dist/core/plugin.d.ts +138 -0
- package/dist/core/project.d.ts +64 -0
- package/dist/core/promptCache.d.ts +3 -0
- package/dist/core/query.d.ts +14 -0
- package/dist/core/rules.d.ts +8 -0
- package/dist/core/systemPrompt.d.ts +9 -0
- package/dist/core/thinking-config.d.ts +3 -0
- package/dist/core/usage.d.ts +14 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +144432 -0
- package/dist/mcp/mcp.d.ts +49 -0
- package/dist/modes/builtin.d.ts +34 -0
- package/dist/modes/index.d.ts +8 -0
- package/dist/modes/registry.d.ts +18 -0
- package/dist/modes/types.d.ts +51 -0
- package/dist/platform/index.d.ts +5 -0
- package/dist/platform/node.d.ts +28 -0
- package/dist/platform/types.d.ts +41 -0
- package/dist/session/session.d.ts +43 -0
- package/dist/skill/skill.d.ts +79 -0
- package/dist/tools/tool.d.ts +119 -0
- package/dist/tools/tools/askUserQuestion.d.ts +48 -0
- package/dist/tools/tools/bash.d.ts +43 -0
- package/dist/tools/tools/edit.d.ts +9 -0
- package/dist/tools/tools/fetch.d.ts +9 -0
- package/dist/tools/tools/glob.d.ts +7 -0
- package/dist/tools/tools/grep.d.ts +22 -0
- package/dist/tools/tools/ls.d.ts +6 -0
- package/dist/tools/tools/read.d.ts +9 -0
- package/dist/tools/tools/skill.d.ts +7 -0
- package/dist/tools/tools/task.d.ts +14 -0
- package/dist/tools/tools/todo.d.ts +37 -0
- package/dist/tools/tools/write.d.ts +7 -0
- package/dist/utils/apiKeyRotation.d.ts +2 -0
- package/dist/utils/applyEdit.d.ts +17 -0
- package/dist/utils/background-detection.d.ts +2 -0
- package/dist/utils/dotenv.d.ts +9 -0
- package/dist/utils/env.d.ts +6 -0
- package/dist/utils/error.d.ts +11 -0
- package/dist/utils/execFileNoThrow.d.ts +8 -0
- package/dist/utils/files.d.ts +10 -0
- package/dist/utils/git.d.ts +163 -0
- package/dist/utils/ide.d.ts +27 -0
- package/dist/utils/ignore.d.ts +6 -0
- package/dist/utils/isLocal.d.ts +1 -0
- package/dist/utils/language.d.ts +9 -0
- package/dist/utils/list.d.ts +20 -0
- package/dist/utils/mergeSystemMessagesMiddleware.d.ts +2 -0
- package/dist/utils/messageNormalization.d.ts +22 -0
- package/dist/utils/path.d.ts +34 -0
- package/dist/utils/prependSystemMessageMiddleware.d.ts +2 -0
- package/dist/utils/project.d.ts +1 -0
- package/dist/utils/proxy.d.ts +18 -0
- package/dist/utils/randomUUID.d.ts +5 -0
- package/dist/utils/renderSessionMarkdown.d.ts +10 -0
- package/dist/utils/ripgrep.d.ts +16 -0
- package/dist/utils/safeFrontMatter.d.ts +11 -0
- package/dist/utils/safeParseJson.d.ts +1 -0
- package/dist/utils/safeStringify.d.ts +1 -0
- package/dist/utils/sanitizeAIResponse.d.ts +30 -0
- package/dist/utils/setTerminalTitle.d.ts +1 -0
- package/dist/utils/shell-execution.d.ts +44 -0
- package/dist/utils/string.d.ts +8 -0
- package/dist/utils/symbols.d.ts +14 -0
- package/dist/utils/system-encoding.d.ts +40 -0
- package/dist/utils/tokenCounter.d.ts +8 -0
- package/dist/utils/username.d.ts +1 -0
- package/package.json +106 -0
- package/src/agent/agent/agentManager.test.ts +124 -0
- package/src/agent/agent/agentManager.ts +372 -0
- package/src/agent/agent/builtin/common.ts +20 -0
- package/src/agent/agent/builtin/explore.ts +53 -0
- package/src/agent/agent/builtin/general-purpose.ts +38 -0
- package/src/agent/agent/builtin/index.ts +13 -0
- package/src/agent/agent/executor.test.ts +339 -0
- package/src/agent/agent/executor.ts +224 -0
- package/src/agent/agent/types.ts +119 -0
- package/src/api/engine.ts +466 -0
- package/src/communication/index.ts +18 -0
- package/src/communication/messageBus.ts +393 -0
- package/src/core/at.ts +315 -0
- package/src/core/backgroundTaskManager.ts +129 -0
- package/src/core/compact.ts +95 -0
- package/src/core/config.ts +441 -0
- package/src/core/constants.ts +82 -0
- package/src/core/context.ts +214 -0
- package/src/core/globalData.ts +77 -0
- package/src/core/history.ts +323 -0
- package/src/core/ide.ts +325 -0
- package/src/core/jsonl.ts +100 -0
- package/src/core/llmsContext.ts +117 -0
- package/src/core/loop.ts +638 -0
- package/src/core/message.ts +304 -0
- package/src/core/model.ts +2198 -0
- package/src/core/output-style/builtin/default.ts +9 -0
- package/src/core/output-style/builtin/explanatory.ts +22 -0
- package/src/core/output-style/builtin/index.ts +19 -0
- package/src/core/output-style/builtin/miao.ts +22 -0
- package/src/core/output-style/builtin/minimal.ts +8 -0
- package/src/core/output-style/types.ts +6 -0
- package/src/core/outputFormat.ts +93 -0
- package/src/core/outputStyle.ts +255 -0
- package/src/core/paths.ts +161 -0
- package/src/core/planSystemPrompt.ts +46 -0
- package/src/core/plugin.ts +299 -0
- package/src/core/project.ts +492 -0
- package/src/core/promptCache.ts +32 -0
- package/src/core/query.ts +46 -0
- package/src/core/rules.ts +56 -0
- package/src/core/systemPrompt.ts +176 -0
- package/src/core/thinking-config.ts +98 -0
- package/src/core/usage.ts +68 -0
- package/src/index.ts +39 -0
- package/src/mcp/mcp.ts +637 -0
- package/src/modes/builtin.ts +305 -0
- package/src/modes/index.ts +22 -0
- package/src/modes/registry.ts +39 -0
- package/src/modes/types.ts +56 -0
- package/src/platform/index.ts +6 -0
- package/src/platform/node.ts +108 -0
- package/src/platform/types.ts +54 -0
- package/src/plugins/index.ts +15 -0
- package/src/session/session.ts +187 -0
- package/src/skill/skill.ts +702 -0
- package/src/tools/tool.ts +378 -0
- package/src/tools/tools/askUserQuestion.ts +134 -0
- package/src/tools/tools/bash.test.ts +425 -0
- package/src/tools/tools/bash.ts +999 -0
- package/src/tools/tools/edit.ts +86 -0
- package/src/tools/tools/fetch.ts +129 -0
- package/src/tools/tools/glob.ts +69 -0
- package/src/tools/tools/grep.test.ts +194 -0
- package/src/tools/tools/grep.ts +358 -0
- package/src/tools/tools/ls.ts +51 -0
- package/src/tools/tools/read.test.ts +169 -0
- package/src/tools/tools/read.ts +284 -0
- package/src/tools/tools/skill.ts +73 -0
- package/src/tools/tools/task.test.ts +262 -0
- package/src/tools/tools/task.ts +284 -0
- package/src/tools/tools/todo.ts +269 -0
- package/src/tools/tools/write.ts +71 -0
- package/src/types.d.ts +18 -0
- package/src/utils/apiKeyRotation.test.ts +70 -0
- package/src/utils/apiKeyRotation.ts +24 -0
- package/src/utils/applyEdit.test.ts +388 -0
- package/src/utils/applyEdit.ts +547 -0
- package/src/utils/background-detection.test.ts +61 -0
- package/src/utils/background-detection.ts +58 -0
- package/src/utils/dotenv.ts +26 -0
- package/src/utils/env.ts +90 -0
- package/src/utils/error.ts +38 -0
- package/src/utils/execFileNoThrow.ts +49 -0
- package/src/utils/files.ts +93 -0
- package/src/utils/git.ts +1152 -0
- package/src/utils/ide.ts +279 -0
- package/src/utils/ignore.ts +275 -0
- package/src/utils/isLocal.ts +6 -0
- package/src/utils/language.ts +33 -0
- package/src/utils/list.ts +200 -0
- package/src/utils/mergeSystemMessagesMiddleware.ts +32 -0
- package/src/utils/messageNormalization.test.ts +401 -0
- package/src/utils/messageNormalization.ts +168 -0
- package/src/utils/path.ts +98 -0
- package/src/utils/prependSystemMessageMiddleware.ts +16 -0
- package/src/utils/project.ts +32 -0
- package/src/utils/proxy.ts +102 -0
- package/src/utils/randomUUID.ts +11 -0
- package/src/utils/renderSessionMarkdown.ts +175 -0
- package/src/utils/ripgrep.ts +189 -0
- package/src/utils/safeFrontMatter.test.ts +118 -0
- package/src/utils/safeFrontMatter.ts +68 -0
- package/src/utils/safeParseJson.ts +7 -0
- package/src/utils/safeStringify.ts +10 -0
- package/src/utils/sanitizeAIResponse.test.ts +135 -0
- package/src/utils/sanitizeAIResponse.ts +55 -0
- package/src/utils/setTerminalTitle.ts +7 -0
- package/src/utils/shell-execution.test.ts +237 -0
- package/src/utils/shell-execution.ts +279 -0
- package/src/utils/string.ts +13 -0
- package/src/utils/symbols.ts +18 -0
- package/src/utils/system-encoding.test.ts +164 -0
- package/src/utils/system-encoding.ts +296 -0
- package/src/utils/tokenCounter.test.ts +38 -0
- package/src/utils/tokenCounter.ts +19 -0
- 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
|
+
}
|